[frameworks/kio] /: StandardThumbnailJob: stamp the device pixel ratio on generated thumbnails
Méven Car <[email protected]>
| Newsgroups | gmane.comp.kde.cvs |
|---|---|
| Message-ID | <[email protected]> |
Git commit 772335086c3373094a0873266b9b4db95049c74a by Méven Car.
Committed on 04/08/2026 at 10:21.
Pushed by meven into branch 'master'.
StandardThumbnailJob: stamp the device pixel ratio on generated thumbnails
The KIO thumbnail worker path records the device pixel ratio on its image, but
the standard thumbnailer left it at 1x, so FilePreviewJob::emitPreview downscaled
those previews back to logical size and they looked soft on HiDPI.
Pass the logical width and device pixel ratio to StandardThumbnailJob instead of
a pre-multiplied width. It still requests the thumbnail at logicalWidth * ratio,
and stamps the ratio its returned resolution supports (floored at 1, capped at the
requested one) on the image while it is still uniquely owned, so consumers receive
it correctly scaled without detaching a shared copy to set it.
Add a test for that computation.
M +25 -0 autotests/filepreviewjobtest.cpp
M +2 -0 autotests/filepreviewjobtest.h
M +2 -4 src/gui/filepreviewjob.cpp
M +15 -7 src/gui/standardthumbnailjob.cpp
M +13 -1 src/gui/standardthumbnailjob_p.h
https://invent.kde.org/frameworks/kio/-/commit/772335086c3373094a0873266b9b4db95049c74a
diff --git a/autotests/filepreviewjobtest.cpp b/autotests/filepreviewjobtest.cpp
index 480fc52f14..3673a8ec7c 100644
--- a/autotests/filepreviewjobtest.cpp
+++ b/autotests/filepreviewjobtest.cpp
@@ -8,12 +8,14 @@
#include "filepreviewjobtest.h"
#include "../src/gui/filepreviewjob.h"
+#include "../src/gui/standardthumbnailjob_p.h"
#include <KFileItem>
#include <QDateTime>
#include <QImage>
#include <QSignalSpy>
+#include <QSize>
#include <QStandardPaths>
#include <QTemporaryDir>
#include <QTemporaryFile>
@@ -208,3 +210,26 @@ void FilePreviewJobTest::testGeneratedImageDevicePixelRatio()
delete job;
}
+
+void FilePreviewJobTest::testSupportedDevicePixelRatio_data()
+{
+ QTest::addColumn<QSize>("imageSize");
+ QTest::addColumn<qreal>("expectedDpr");
+
+ // Shown at logical size 128 with a max ratio of 1.75, so the full-resolution target is
+ // 224 px. The claimed ratio must be what the image resolution supports, floored at 1
+ // and capped at 1.75.
+ QTest::newRow("sufficient") << QSize(224, 224) << qreal(1.75);
+ QTest::newRow("more than max") << QSize(300, 300) << qreal(1.75);
+ QTest::newRow("insufficient") << QSize(150, 150) << (qreal(150) / 128);
+ QTest::newRow("below 1x") << QSize(100, 100) << qreal(1.0);
+ QTest::newRow("non-square uses longer edge") << QSize(224, 90) << qreal(1.75);
+}
+
+void FilePreviewJobTest::testSupportedDevicePixelRatio()
+{
+ QFETCH(QSize, imageSize);
+ QFETCH(qreal, expectedDpr);
+
+ QCOMPARE(KIO::supportedDevicePixelRatio(imageSize, QSize(128, 128), 1.75), expectedDpr);
+}
diff --git a/autotests/filepreviewjobtest.h b/autotests/filepreviewjobtest.h
index 38b55c3ce4..267bac5e59 100644
--- a/autotests/filepreviewjobtest.h
+++ b/autotests/filepreviewjobtest.h
@@ -21,6 +21,8 @@ private Q_SLOTS:
void testCacheSizeValidation();
void testGeneratedImageDevicePixelRatio_data();
void testGeneratedImageDevicePixelRatio();
+ void testSupportedDevicePixelRatio_data();
+ void testSupportedDevicePixelRatio();
};
#endif
diff --git a/src/gui/filepreviewjob.cpp b/src/gui/filepreviewjob.cpp
index 4c3ea3cbb8..bb3ad0c89c 100644
--- a/src/gui/filepreviewjob.cpp
+++ b/src/gui/filepreviewjob.cpp
@@ -587,10 +587,8 @@ void FilePreviewJob::createThumbnail(const QString &pixPath)
}
m_standardThumbnailJob =
- new KIO::StandardThumbnailJob(m_plugin.value(u"Exec"), m_options.size.width() * m_options.devicePixelRatio, pixPath, m_tempDirPath);
- connect(m_standardThumbnailJob, &KIO::StandardThumbnailJob::data, this, [=, this](KIO::Job *job, const QImage &thumb) {
- slotStandardThumbData(job, thumb);
- });
+ new KIO::StandardThumbnailJob(m_plugin.value(u"Exec"), m_options.size.width(), m_options.devicePixelRatio, pixPath, m_tempDirPath);
+ connect(m_standardThumbnailJob, &KIO::StandardThumbnailJob::data, this, &FilePreviewJob::slotStandardThumbData);
connect(m_standardThumbnailJob, &KIO::StandardThumbnailJob::result, this, &FilePreviewJob::emitResult);
m_standardThumbnailJob->start();
return;
diff --git a/src/gui/standardthumbnailjob.cpp b/src/gui/standardthumbnailjob.cpp
index 7f17f89bab..ad82ecf7a4 100644
--- a/src/gui/standardthumbnailjob.cpp
+++ b/src/gui/standardthumbnailjob.cpp
@@ -74,9 +74,10 @@ int ThumbnailerExpander::expandEscapedMacro(const QString &str, int pos, QString
class Q_DECL_HIDDEN KIO::StandardThumbnailJob::Private
{
public:
- explicit Private(const QString &execString, int width, const QString &inputFile, const QString &outputFolder)
+ explicit Private(const QString &execString, int logicalWidth, qreal devicePixelRatio, const QString &inputFile, const QString &outputFolder)
: m_execString(execString)
- , m_width(width)
+ , m_logicalWidth(logicalWidth)
+ , m_devicePixelRatio(devicePixelRatio)
, m_inputFile(inputFile)
, m_outputFolder(outputFolder)
{
@@ -86,15 +87,20 @@ public:
}
QString m_execString;
- int m_width;
+ int m_logicalWidth;
+ qreal m_devicePixelRatio;
QString m_inputFile;
QString m_outputFolder;
QProcess *m_proc;
QTemporaryFile *m_tempFile;
};
-KIO::StandardThumbnailJob::StandardThumbnailJob(const QString &execString, int width, const QString &inputFile, const QString &outputFolder)
- : d(new Private(execString, width, inputFile, outputFolder))
+KIO::StandardThumbnailJob::StandardThumbnailJob(const QString &execString,
+ int logicalWidth,
+ qreal devicePixelRatio,
+ const QString &inputFile,
+ const QString &outputFolder)
+ : d(new Private(execString, logicalWidth, devicePixelRatio, inputFile, outputFolder))
{
setAutoDelete(true);
}
@@ -118,7 +124,7 @@ void KIO::StandardThumbnailJob::StandardThumbnailJob::start()
}
d->m_tempFile->setAutoRemove(false);
- ThumbnailerExpander thumbnailer(d->m_execString, d->m_width, d->m_inputFile, d->m_tempFile->fileName());
+ ThumbnailerExpander thumbnailer(d->m_execString, d->m_logicalWidth * d->m_devicePixelRatio, d->m_inputFile, d->m_tempFile->fileName());
// Emit data on command exit
d->m_proc = new QProcess();
connect(d->m_proc, &QProcess::finished, this, [=, this](const int exitCode, const QProcess::ExitStatus /* exitStatus */) {
@@ -132,7 +138,9 @@ void KIO::StandardThumbnailJob::StandardThumbnailJob::start()
d->m_tempFile->remove();
return;
}
- Q_EMIT data(this, QImage(d->m_tempFile->fileName()));
+ QImage thumb(d->m_tempFile->fileName());
+ thumb.setDevicePixelRatio(supportedDevicePixelRatio(thumb.size(), QSize(d->m_logicalWidth, d->m_logicalWidth), d->m_devicePixelRatio));
+ Q_EMIT data(this, thumb);
emitResult();
// clean temp file
diff --git a/src/gui/standardthumbnailjob_p.h b/src/gui/standardthumbnailjob_p.h
index ba4d4a11fc..cf38cd27aa 100644
--- a/src/gui/standardthumbnailjob_p.h
+++ b/src/gui/standardthumbnailjob_p.h
@@ -6,15 +6,27 @@
#include <kio/job.h>
+#include <QSize>
+
namespace KIO
{
+inline qreal supportedDevicePixelRatio(const QSize &imageSize, const QSize &logicalSize, qreal maxDevicePixelRatio)
+{
+ const int longerLogical = qMax(logicalSize.width(), logicalSize.height());
+ if (longerLogical <= 0) {
+ return maxDevicePixelRatio;
+ }
+ const int longerActual = qMax(imageSize.width(), imageSize.height());
+ return qBound(qreal(1), qreal(longerActual) / longerLogical, maxDevicePixelRatio);
+}
+
class StandardThumbnailJob : public KIO::Job
{
Q_OBJECT
public:
- StandardThumbnailJob(const QString &execString, int width, const QString &inputFile, const QString &outputFile);
+ StandardThumbnailJob(const QString &execString, int logicalWidth, qreal devicePixelRatio, const QString &inputFile, const QString &outputFile);
~StandardThumbnailJob() override;
void start() override;