[system/dolphin] src/kitemviews: Port to PreviewJob::generated (QImage) instead of gotPreview (QPixmap)

Méven Car <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit f943419ccdf5f00f8acd4c0cf935a13d12580eee by Méven Car.
Committed on 04/08/2026 at 10:10.
Pushed by meven into branch 'master'.

Port to PreviewJob::generated (QImage) instead of gotPreview (QPixmap)

Both the main preview path and the hover sequence path connected to
PreviewJob::gotPreview and transformed a QPixmap. For every preview KIO
built that QPixmap with QPixmap::fromImage, and Dolphin then round
tripped between pixmap and image space while scaling and framing.

Connect to PreviewJob::generated and do the whole scale and frame
transform in QImage space, converting to a QPixmap once at the end. When
no consumer connects gotPreview, KIO skips the QPixmap::fromImage copy
entirely.

KPixmapModifier gains QImage overloads of scale() and applyFrame(). The
QPixmap applyFrame() overload had no other callers and is removed, while
scale(QPixmap) stays as it is still used elsewhere.

The hover sequence job now sets the device pixel ratio like the main
job. Its frames are generated at full resolution and framed the same way
as the static preview, rather than at 1x and scaled up.

M  +31   -26   src/kitemviews/kfileitemmodelrolesupdater.cpp
M  +9    -7    src/kitemviews/kfileitemmodelrolesupdater.h
M  +18   -5    src/kitemviews/private/kpixmapmodifier.cpp
M  +7    -1    src/kitemviews/private/kpixmapmodifier.h

https://invent.kde.org/system/dolphin/-/commit/f943419ccdf5f00f8acd4c0cf935a13d12580eee

diff --git a/src/kitemviews/kfileitemmodelrolesupdater.cpp b/src/kitemviews/kfileitemmodelrolesupdater.cpp
index af6fc46c44..2f071e0750 100644
--- a/src/kitemviews/kfileitemmodelrolesupdater.cpp
+++ b/src/kitemviews/kfileitemmodelrolesupdater.cpp
@@ -33,6 +33,7 @@
 #include <QApplication>
 #include <QElapsedTimer>
 #include <QFileInfo>
+#include <QImage>
 #include <QPainter>
 #include <QPluginLoader>
 #include <QScopedValueRollback>
@@ -558,7 +559,7 @@ void KFileItemModelRolesUpdater::slotSortRoleChanged(const QByteArray &current,
     }
 }
 
-void KFileItemModelRolesUpdater::slotGotPreview(const KFileItem &item, const QPixmap &pixmap)
+void KFileItemModelRolesUpdater::slotGotPreview(const KFileItem &item, const QImage &image)
 {
     if (m_state != PreviewJobRunning) {
         return;
@@ -572,7 +573,7 @@ void KFileItemModelRolesUpdater::slotGotPreview(const KFileItem &item, const QPi
     }
 
     SmallHash data = rolesData(item, index);
-    data.insert("iconPixmap", transformPreviewPixmap(pixmap));
+    data.insert("iconPixmap", transformPreviewImage(image));
     data.insert("supportsSequencing", m_previewJob->handlesSequences());
 
     setModelData(index, data);
@@ -621,7 +622,7 @@ void KFileItemModelRolesUpdater::slotPreviewJobFinished()
     }
 }
 
-void KFileItemModelRolesUpdater::slotHoverSequenceGotPreview(const KFileItem &item, const QPixmap &pixmap)
+void KFileItemModelRolesUpdater::slotHoverSequenceGotPreview(const KFileItem &item, const QImage &image)
 {
     const int index = m_model->index(item);
     if (index < 0) {
@@ -648,7 +649,7 @@ void KFileItemModelRolesUpdater::slotHoverSequenceGotPreview(const KFileItem &it
     if (wap < 0.0f || loadedIndex < static_cast<int>(wap)) {
         // Add the preview to the model data
 
-        const QPixmap scaledPixmap = transformPreviewPixmap(pixmap);
+        const QPixmap scaledPixmap = transformPreviewImage(image);
 
         pixmaps.append(scaledPixmap);
         data["hoverSequencePixmaps"] = QVariant::fromValue(pixmaps);
@@ -979,55 +980,58 @@ void KFileItemModelRolesUpdater::startPreviewJob()
         KJobWidgets::setWindow(job, qApp->activeWindow());
     }
 
-    connect(job, &KIO::PreviewJob::gotPreview, this, &KFileItemModelRolesUpdater::slotGotPreview);
+    connect(job, &KIO::PreviewJob::generated, this, &KFileItemModelRolesUpdater::slotGotPreview);
     connect(job, &KIO::PreviewJob::failed, this, &KFileItemModelRolesUpdater::slotPreviewFailed);
     connect(job, &KIO::PreviewJob::finished, this, &KFileItemModelRolesUpdater::slotPreviewJobFinished);
 
     m_previewJob = job;
 }
 
-QPixmap KFileItemModelRolesUpdater::transformPreviewPixmap(const QPixmap &pixmap)
+QPixmap KFileItemModelRolesUpdater::transformPreviewImage(const QImage &image)
 {
-    QPixmap scaledPixmap = pixmap;
-
-    if (pixmap.isNull()) {
-        return scaledPixmap;
+    if (image.isNull()) {
+        return QPixmap();
     }
 
-    if (!pixmap.hasAlpha() && m_iconSize.width() > KIconLoader::SizeSmallMedium && m_iconSize.height() > KIconLoader::SizeSmallMedium) {
+    QImage scaledImage = image;
+
+    if (!image.hasAlphaChannel() && m_iconSize.width() > KIconLoader::SizeSmallMedium && m_iconSize.height() > KIconLoader::SizeSmallMedium) {
         if (m_enlargeSmallPreviews) {
-            KPixmapModifier::applyFrame(scaledPixmap, m_iconSize);
+            KPixmapModifier::applyFrame(scaledImage, m_iconSize);
         } else {
             // Assure that small previews don't get enlarged. Instead they
             // should be shown centered within the frame.
             const QSize contentSize = KPixmapModifier::sizeInsideFrame(m_iconSize);
-            const bool enlargingRequired = scaledPixmap.width() < contentSize.width() && scaledPixmap.height() < contentSize.height();
+            const bool enlargingRequired = scaledImage.width() < contentSize.width() && scaledImage.height() < contentSize.height();
             if (enlargingRequired) {
-                QSize frameSize = scaledPixmap.size() / scaledPixmap.devicePixelRatio();
+                QSize frameSize = scaledImage.size() / scaledImage.devicePixelRatio();
                 frameSize.scale(m_iconSize, Qt::KeepAspectRatio);
 
-                QPixmap largeFrame(frameSize);
+                QImage largeFrame(frameSize, QImage::Format_ARGB32_Premultiplied);
                 largeFrame.fill(Qt::transparent);
 
                 KPixmapModifier::applyFrame(largeFrame, frameSize);
 
                 QPainter painter(&largeFrame);
-                painter.drawPixmap((largeFrame.width() - scaledPixmap.width() / scaledPixmap.devicePixelRatio()) / 2,
-                                   (largeFrame.height() - scaledPixmap.height() / scaledPixmap.devicePixelRatio()) / 2,
-                                   scaledPixmap);
-                scaledPixmap = largeFrame;
+                painter.drawImage((largeFrame.width() - scaledImage.width() / scaledImage.devicePixelRatio()) / 2,
+                                  (largeFrame.height() - scaledImage.height() / scaledImage.devicePixelRatio()) / 2,
+                                  scaledImage);
+                painter.end();
+                scaledImage = largeFrame;
             } else {
                 // The image must be shrunk as it is too large to fit into
                 // the available icon size
-                KPixmapModifier::applyFrame(scaledPixmap, m_iconSize);
+                KPixmapModifier::applyFrame(scaledImage, m_iconSize);
             }
         }
     } else {
-        KPixmapModifier::scale(scaledPixmap, m_iconSize * m_devicePixelRatio);
-        scaledPixmap.setDevicePixelRatio(m_devicePixelRatio);
+        KPixmapModifier::scale(scaledImage, m_iconSize * m_devicePixelRatio);
+        scaledImage.setDevicePixelRatio(m_devicePixelRatio);
     }
 
-    return scaledPixmap;
+    QPixmap result = QPixmap::fromImage(scaledImage);
+    result.setDevicePixelRatio(scaledImage.devicePixelRatio());
+    return result;
 }
 
 QSize KFileItemModelRolesUpdater::cacheSize()
@@ -1081,13 +1085,14 @@ void KFileItemModelRolesUpdater::loadNextHoverSequencePreview()
     }
 
     KIO::PreviewJob *job = new KIO::PreviewJob({m_hoverSequenceItem}, cacheSize(), &m_enabledPlugins);
+    job->setDevicePixelRatio(m_devicePixelRatio);
 
     job->setSequenceIndex(loadSeqIdx);
     if (job->uiDelegate()) {
         KJobWidgets::setWindow(job, qApp->activeWindow());
     }
 
-    connect(job, &KIO::PreviewJob::gotPreview, this, &KFileItemModelRolesUpdater::slotHoverSequenceGotPreview);
+    connect(job, &KIO::PreviewJob::generated, this, &KFileItemModelRolesUpdater::slotHoverSequenceGotPreview);
     connect(job, &KIO::PreviewJob::failed, this, &KFileItemModelRolesUpdater::slotHoverSequencePreviewFailed);
     connect(job, &KIO::PreviewJob::finished, this, &KFileItemModelRolesUpdater::slotHoverSequencePreviewJobFinished);
 
@@ -1097,7 +1102,7 @@ void KFileItemModelRolesUpdater::loadNextHoverSequencePreview()
 void KFileItemModelRolesUpdater::killHoverSequencePreviewJob()
 {
     if (m_hoverSequencePreviewJob) {
-        disconnect(m_hoverSequencePreviewJob, &KIO::PreviewJob::gotPreview, this, &KFileItemModelRolesUpdater::slotHoverSequenceGotPreview);
+        disconnect(m_hoverSequencePreviewJob, &KIO::PreviewJob::generated, this, &KFileItemModelRolesUpdater::slotHoverSequenceGotPreview);
         disconnect(m_hoverSequencePreviewJob, &KIO::PreviewJob::failed, this, &KFileItemModelRolesUpdater::slotHoverSequencePreviewFailed);
         disconnect(m_hoverSequencePreviewJob, &KIO::PreviewJob::finished, this, &KFileItemModelRolesUpdater::slotHoverSequencePreviewJobFinished);
         m_hoverSequencePreviewJob->kill();
@@ -1413,7 +1418,7 @@ void KFileItemModelRolesUpdater::updateAllPreviews()
 void KFileItemModelRolesUpdater::killPreviewJob()
 {
     if (m_previewJob) {
-        disconnect(m_previewJob, &KIO::PreviewJob::gotPreview, this, &KFileItemModelRolesUpdater::slotGotPreview);
+        disconnect(m_previewJob, &KIO::PreviewJob::generated, this, &KFileItemModelRolesUpdater::slotGotPreview);
         disconnect(m_previewJob, &KIO::PreviewJob::failed, this, &KFileItemModelRolesUpdater::slotPreviewFailed);
         disconnect(m_previewJob, &KIO::PreviewJob::finished, this, &KFileItemModelRolesUpdater::slotPreviewJobFinished);
         m_previewJob->kill();
diff --git a/src/kitemviews/kfileitemmodelrolesupdater.h b/src/kitemviews/kfileitemmodelrolesupdater.h
index 6ecc577f3e..9f22261be6 100644
--- a/src/kitemviews/kfileitemmodelrolesupdater.h
+++ b/src/kitemviews/kfileitemmodelrolesupdater.h
@@ -22,6 +22,7 @@
 
 class KDirectoryContentsCounter;
 class KFileItemModel;
+class QImage;
 class QPixmap;
 class QTimer;
 class KOverlayIconPlugin;
@@ -178,13 +179,14 @@ private Q_SLOTS:
     void slotSortRoleChanged(const QByteArray &current, const QByteArray &previous);
 
     /**
-     * Is invoked after a preview has been received successfully.
+     * Is invoked after a preview has been received successfully, connected to
+     * PreviewJob::generated and transformed straight from the QImage.
      *
      * Note that this is not called for hover sequence previews.
      *
      * @see startPreviewJob()
      */
-    void slotGotPreview(const KFileItem &item, const QPixmap &pixmap);
+    void slotGotPreview(const KFileItem &item, const QImage &image);
 
     /**
      * Is invoked after generating a preview has failed.
@@ -209,7 +211,7 @@ private Q_SLOTS:
     /**
      * Is invoked after a hover sequence preview has been received successfully.
      */
-    void slotHoverSequenceGotPreview(const KFileItem &item, const QPixmap &pixmap);
+    void slotHoverSequenceGotPreview(const KFileItem &item, const QImage &image);
 
     /**
      * Is invoked after generating a hover sequence preview has failed.
@@ -282,13 +284,13 @@ private:
     void startPreviewJob();
 
     /**
-     * Transforms a raw preview image, applying scale and frame.
+     * Transforms a raw preview image from a PreviewJob, applying scale and frame.
+     * The transform is done in QImage space and converted to a QPixmap once.
      *
-     * @param pixmap A raw preview image from a PreviewJob.
-     * @param overlays the overlays to add to the pixmap
+     * @param image A raw preview image from a PreviewJob.
      * @return The scaled and decorated preview image.
      */
-    QPixmap transformPreviewPixmap(const QPixmap &pixmap);
+    QPixmap transformPreviewImage(const QImage &image);
 
     /**
      * Starts a PreviewJob for loading the next hover sequence image.
diff --git a/src/kitemviews/private/kpixmapmodifier.cpp b/src/kitemviews/private/kpixmapmodifier.cpp
index bf316b880d..12e0d215e6 100644
--- a/src/kitemviews/private/kpixmapmodifier.cpp
+++ b/src/kitemviews/private/kpixmapmodifier.cpp
@@ -285,10 +285,21 @@ void KPixmapModifier::scale(QPixmap &pixmap, const QSize &scaledSize)
     pixmap.setDevicePixelRatio(dpr);
 }
 
-void KPixmapModifier::applyFrame(QPixmap &icon, const QSize &scaledSize)
+void KPixmapModifier::scale(QImage &image, const QSize &scaledSize)
+{
+    if (scaledSize.isEmpty() || image.isNull()) {
+        image = QImage();
+        return;
+    }
+    const qreal dpr = image.devicePixelRatio();
+    image = image.scaled(scaledSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
+    image.setDevicePixelRatio(dpr);
+}
+
+void KPixmapModifier::applyFrame(QImage &icon, const QSize &scaledSize)
 {
     if (icon.isNull()) {
-        icon = QPixmap(scaledSize);
+        icon = QImage(scaledSize, QImage::Format_ARGB32_Premultiplied);
         icon.fill(Qt::transparent);
         return;
     }
@@ -301,8 +312,9 @@ void KPixmapModifier::applyFrame(QPixmap &icon, const QSize &scaledSize)
     scale(icon, size * dpr);
     icon.setDevicePixelRatio(dpr);
 
-    QPixmap framedIcon(icon.size().width() + (TileSet::LeftMargin + TileSet::RightMargin) * dpr,
-                       icon.size().height() + (TileSet::TopMargin + TileSet::BottomMargin) * dpr);
+    QImage framedIcon(QSize(icon.size().width() + (TileSet::LeftMargin + TileSet::RightMargin) * dpr,
+                            icon.size().height() + (TileSet::TopMargin + TileSet::BottomMargin) * dpr),
+                      QImage::Format_ARGB32_Premultiplied);
     framedIcon.setDevicePixelRatio(dpr);
     framedIcon.fill(Qt::transparent);
 
@@ -311,7 +323,8 @@ void KPixmapModifier::applyFrame(QPixmap &icon, const QSize &scaledSize)
     painter.setCompositionMode(QPainter::CompositionMode_Source);
     tileSet.paint(&painter, QRect(QPoint(0, 0), framedIcon.size() / dpr));
     painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
-    painter.drawPixmap(TileSet::LeftMargin, TileSet::TopMargin, icon);
+    painter.drawImage(TileSet::LeftMargin, TileSet::TopMargin, icon);
+    painter.end();
 
     icon = framedIcon;
 }
diff --git a/src/kitemviews/private/kpixmapmodifier.h b/src/kitemviews/private/kpixmapmodifier.h
index 0d86b01571..e27384cfcb 100644
--- a/src/kitemviews/private/kpixmapmodifier.h
+++ b/src/kitemviews/private/kpixmapmodifier.h
@@ -9,6 +9,7 @@
 
 #include "dolphin_export.h"
 
+class QImage;
 class QPixmap;
 class QSize;
 
@@ -22,12 +23,17 @@ public:
      */
     static void scale(QPixmap &pixmap, const QSize &scaledSize);
 
+    /**
+     * QImage overload of scale().
+     */
+    static void scale(QImage &image, const QSize &scaledSize);
+
     /**
      * Resize and paint a frame round an icon
      * @arg scaledSize is in device-independent pixels
      * The returned image will be scaled by the application devicePixelRatio
      */
-    static void applyFrame(QPixmap &icon, const QSize &scaledSize);
+    static void applyFrame(QImage &icon, const QSize &scaledSize);
 
     /**
      * return and paint a frame round an icon
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.