diff --git a/src/widgets/previewjob.h b/src/widgets/previewjob.h --- a/src/widgets/previewjob.h +++ b/src/widgets/previewjob.h @@ -44,6 +44,8 @@ public: /** * Specifies the type of scaling that is applied to the generated preview. + * For HiDPI, pixel density scaling, @see PreviewJob::setDevicePixelRatio + * * @since 4.7 */ enum ScaleType { @@ -186,6 +188,15 @@ **/ int sequenceIndex() const; + /** + * Request preview to use the device pixel ratio @p dpr. + * The returned thumbnail may not respect the device pixel ratio returned. + * Use QPixmap::devicePixelRatio to check, or paint as neccesary. + * + * @since 5.71 + */ + void setDevicePixelRatio(qreal dpr); + /** * Returns a list of all available preview plugins. The list * contains the basenames of the plugins' .desktop files (no path, @@ -244,6 +255,17 @@ Q_PRIVATE_SLOT(d_func(), void startPreview()) Q_PRIVATE_SLOT(d_func(), void slotThumbData(KIO::Job *, const QByteArray &)) Q_DECLARE_PRIVATE(PreviewJob) + +public: + /** + * Sets a default device Pixel Ratio used for Previews + * @see PreviewJob::setDevicePixelRatio + * + * Defaults to 1 + * + * @since 5.71 + */ + static void setDefaultDevicePixelRatio(qreal devicePixelRatio); }; #if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(4, 7) @@ -288,7 +310,7 @@ * preview plugin that will be used doesn't use icon overlays. * @param iconAlpha transparency to use for the icon overlay * @param scale if the image is to be scaled to the requested size or - * returned in its original size + * returned in its original size @see PreviewJob::setDevicePixelRatio * @param save if the image should be cached for later use * @param enabledPlugins if non-zero, this points to a list containing * the names of the plugins that may be used. diff --git a/src/widgets/previewjob.cpp b/src/widgets/previewjob.cpp --- a/src/widgets/previewjob.cpp +++ b/src/widgets/previewjob.cpp @@ -61,6 +61,10 @@ #include "job_p.h" +namespace { + static qreal s_defaultDevicePixelRatio = 1.0; +} + namespace KIO { struct PreviewItem; @@ -150,6 +154,7 @@ QString thumbRoot; // List of encrypted mount points for checking if we should save thumbnail KMountPoint::List encryptedMountsList; + qreal devicePixelRatio = s_defaultDevicePixelRatio; void getOrCreateThumbnail(); bool statResultThumbnail(); @@ -164,6 +169,12 @@ Q_DECLARE_PUBLIC(PreviewJob) }; + +void PreviewJob::setDefaultDevicePixelRatio(qreal defaultDevicePixelRatio) +{ + s_defaultDevicePixelRatio = defaultDevicePixelRatio; +} + PreviewJob::PreviewJob(const KFileItemList &items, int width, int height, int iconSize, int iconAlpha, bool scale, bool save, const QStringList *enabledPlugins) @@ -385,7 +396,9 @@ } else { cacheWidth = cacheHeight = 256; } - thumbPath = thumbRoot + QLatin1String(cacheWidth == 128 ? "normal/" : "large/"); + thumbPath = thumbRoot + QLatin1String(cacheWidth == 128 ? "normal" : "large"); + thumbPath.append(qFuzzyCompare(devicePixelRatio, 2) ? QStringLiteral("@2x/") : QStringLiteral("/")); + if (!QDir(thumbPath).exists()) { if (QDir().mkpath(thumbPath)) { // Qt5 TODO: mkpath(dirPath, permissions) QFile f(thumbPath); @@ -427,6 +440,11 @@ return d_func()->sequenceIndex; } +void KIO::PreviewJob::setDevicePixelRatio(qreal dpr) +{ + d_func()->devicePixelRatio = dpr; +} + void PreviewJob::setIgnoreMaximumSize(bool ignoreSize) { d_func()->ignoreMaximumSize = ignoreSize; @@ -565,6 +583,13 @@ return false; } + if (thumb.textKeys().contains(QStringLiteral("Thumb::DevicePixelRatio"))) { + const qreal dpr = thumb.text(QStringLiteral("Thumb::DevicePixelRatio")).toDouble(); + thumb.setDevicePixelRatio(dpr); + } else if (thumb.width() == cacheWidth * devicePixelRatio || thumb.width() == cacheHeight * devicePixelRatio) { + thumb.setDevicePixelRatio(devicePixelRatio); + } + QString thumbnailerVersion = currentItem.plugin->property(QStringLiteral("ThumbnailerVersion"), QVariant::String).toString(); if (!thumbnailerVersion.isEmpty() && thumb.text(QStringLiteral("Software")).startsWith(QLatin1String("KDE Thumbnail Generator"))) { @@ -659,6 +684,7 @@ job->addMetaData(QStringLiteral("iconAlpha"), QString().setNum(iconAlpha)); job->addMetaData(QStringLiteral("plugin"), currentItem.plugin->library()); job->addMetaData(QStringLiteral("enabledPlugins"), enabledPlugins.join(QLatin1Char(','))); + job->addMetaData(QStringLiteral("devicePixelRatio"), QString::number(devicePixelRatio)); if (sequenceIndex) { job->addMetaData(QStringLiteral("sequence-index"), QString().setNum(sequenceIndex)); } @@ -669,7 +695,7 @@ shmdt((char *)shmaddr); shmctl(shmid, IPC_RMID, nullptr); } - shmid = shmget(IPC_PRIVATE, cacheWidth * cacheHeight * 4, IPC_CREAT | 0600); + shmid = shmget(IPC_PRIVATE, cacheWidth * cacheHeight * 4 * devicePixelRatio * devicePixelRatio, IPC_CREAT | 0600); if (shmid != -1) { shmaddr = (uchar *)(shmat(shmid, nullptr, SHM_RDONLY)); if (shmaddr == (uchar *) - 1) { @@ -702,9 +728,17 @@ QDataStream str(data); int width, height; quint8 iFormat; + qreal imgDevicePixelRatio = 1; + // TODO KF6: add a version number as first parameter str >> width >> height >> iFormat; + if (iFormat & 0x80) { + // HACK to deduce if imgDevicePixelRatio is present + iFormat &= 0x7f; + str >> imgDevicePixelRatio; + } QImage::Format format = static_cast(iFormat); thumb = QImage(shmaddr, width, height, format).copy(); + thumb.setDevicePixelRatio(imgDevicePixelRatio); } else #endif thumb.loadFromData(data); @@ -719,6 +753,7 @@ thumb.setText(QStringLiteral("Thumb::MTime"), QString::number(tOrig.toSecsSinceEpoch())); thumb.setText(QStringLiteral("Thumb::Size"), number(currentItem.item.size())); thumb.setText(QStringLiteral("Thumb::Mimetype"), currentItem.item.mimetype()); + thumb.setText(QStringLiteral("Thumb::DevicePixelRatio"), QString::number(thumb.devicePixelRatio())); QString thumbnailerVersion = currentItem.plugin->property(QStringLiteral("ThumbnailerVersion"), QVariant::String).toString(); QString signature = QLatin1String("KDE Thumbnail Generator ") + currentItem.plugin->name(); if (!thumbnailerVersion.isEmpty()) { @@ -740,11 +775,12 @@ { Q_Q(PreviewJob); QPixmap pix; - if (thumb.width() > width || thumb.height() > height) { - pix = QPixmap::fromImage(thumb.scaled(QSize(width, height), Qt::KeepAspectRatio, Qt::SmoothTransformation)); + if (thumb.width() > width * thumb.devicePixelRatio() || thumb.height() > height * thumb.devicePixelRatio()) { + pix = QPixmap::fromImage(thumb.scaled(QSize(width * thumb.devicePixelRatio(), height * thumb.devicePixelRatio()), Qt::KeepAspectRatio, Qt::SmoothTransformation)); } else { pix = QPixmap::fromImage(thumb); } + pix.setDevicePixelRatio(thumb.devicePixelRatio()); emit q->gotPreview(currentItem.item, pix); } diff --git a/src/widgets/thumbcreator.h b/src/widgets/thumbcreator.h --- a/src/widgets/thumbcreator.h +++ b/src/widgets/thumbcreator.h @@ -20,6 +20,8 @@ #ifndef _THUMBCREATOR_H_ #define _THUMBCREATOR_H_ +#include + #include "kiowidgets_export.h" class QString; @@ -184,6 +186,46 @@ }; #endif +/** + * @class ThumbCreatorV3 thumbcreator.h + * @since 5.71 + */ +class KIOWIDGETS_EXPORT ThumbCreatorV3 : public ThumbCreator +{ +public: + // TODO KF6 merge ThumbCreatorV3 to ThumbCreator + virtual ~ThumbCreatorV3(); + + bool create(const QString &path, int width, int height, QImage &img) override; + + /** + * Creates a thumbnail. + * + * @p height and @p height are supposed to be device independant, + * the @p devicePixelRatio will be used to scale the resulting image. + * + * The resulting image may not have the requested devicePixelRatio, if, for instance, + * the creator cannot generate a big enough image, use @see QImage::devicePixelRatio to check. + * + * @param path The path of the file to create a preview for. This is + * always a local path. + * @param width The requested preview width + * @param height The requested preview height + * @param img The QImage to store the preview in. + * It may not respect the requested devicePixelRatio. + * @param devicePixelRatio The requested device pixel ratio. + * This is the ratio between device pixels (screen pixel) and + * device independent pixels (virtual screen). + * Defaults to 1 (no ratio applied) + * @see QImage::devicePixelRatio() + * + * @return @c true if a preview was successfully generated and store in @p + * img, @c false otherwise. + */ + virtual bool createV3(const QString &path, int width, int height, QImage &img, qreal devicePixelRatio = 1.0) = 0; +}; + + // KF6 TODO: rename this to something less generic typedef ThumbCreator *(*newCreator)(); diff --git a/src/widgets/thumbcreator.cpp b/src/widgets/thumbcreator.cpp --- a/src/widgets/thumbcreator.cpp +++ b/src/widgets/thumbcreator.cpp @@ -43,3 +43,13 @@ { Q_UNUSED(configurationWidget); } + + +ThumbCreatorV3::~ThumbCreatorV3() +{ +} + +bool ThumbCreatorV3::create(const QString &path, int width, int height, QImage &img) +{ + return createV3(path, width, height, img, 1); +}