diff options
16 files changed, 443 insertions, 27 deletions
diff --git a/src/gui/doc/qtgui.qdocconf b/src/gui/doc/qtgui.qdocconf index b07d39fa37..94574a314c 100644 --- a/src/gui/doc/qtgui.qdocconf +++ b/src/gui/doc/qtgui.qdocconf @@ -44,7 +44,8 @@ depends += \ headerdirs += .. sourcedirs += .. \ - ../../../examples/gui/doc/src + ../../../examples/gui/doc/src \ + src/includes exampledirs += ../../../examples/gui \ snippets diff --git a/src/gui/doc/src/external-resources.qdoc b/src/gui/doc/src/external-resources.qdoc index 6a423323ea..480a4057be 100644 --- a/src/gui/doc/src/external-resources.qdoc +++ b/src/gui/doc/src/external-resources.qdoc @@ -49,4 +49,14 @@ /*! \externalpage http://www.opengl.org/wiki/Tessellation_Shader \title OpenGL Tessellation Shaders -*/
\ No newline at end of file +*/ + +/*! + \externalpage https://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html + \title Icon Theme Specification +*/ + +/*! + \externalpage https://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html#directory_layout + \title Icon Theme Specification - Directory Layout +*/ diff --git a/src/gui/doc/src/includes/qiconengine-virtualhookhelper.qdocinc b/src/gui/doc/src/includes/qiconengine-virtualhookhelper.qdocinc new file mode 100644 index 0000000000..b17d2bd66f --- /dev/null +++ b/src/gui/doc/src/includes/qiconengine-virtualhookhelper.qdocinc @@ -0,0 +1,3 @@ +\note This is a helper method and the actual work is done by the +virtual_hook() method, hence this method depends on icon engine support +and may not work with all icon engines. diff --git a/src/gui/image/qicon.cpp b/src/gui/image/qicon.cpp index e8f2c878c8..17734f05f3 100644 --- a/src/gui/image/qicon.cpp +++ b/src/gui/image/qicon.cpp @@ -605,6 +605,51 @@ QFactoryLoader *qt_iconEngineFactoryLoader() \note QIcon needs a QGuiApplication instance before the icon is created. + \section1 High DPI Icons + + There are two ways that QIcon supports \l {High DPI Displays}{high DPI} + icons: via \l addFile() and \l fromTheme(). + + \l addFile() is useful if you have your own custom directory structure and do + not need to use the \l {Icon Theme Specification}{freedesktop.org Icon Theme + Specification}. Icons created via this approach use Qt's \l {High Resolution + Versions of Images}{"@nx" high DPI syntax}. + + Using \l fromTheme() is necessary if you plan on following the Icon Theme + Specification. To make QIcon use the high DPI version of an image, add an + additional entry to the appropriate \c index.theme file: + + \badcode + [Icon Theme] + Name=Test + Comment=Test Theme + + Directories=32x32/actions,32x32@2/actions + + [32x32/actions] + Size=32 + Context=Actions + Type=Fixed + + # High DPI version of the entry above. + [32x32@2/actions] + Size=32 + Scale=2 + Type=Fixed + \endcode + + Your icon theme directory would then look something like this: + + \badcode + ├── 32x32 + │ └── actions + │ └── appointment-new.png + ├── 32x32@2 + │ └── actions + │ └── appointment-new.png + └── index.theme + \endcode + \sa {fowler}{GUI Design Handbook: Iconic Label}, {Icons Example} */ @@ -847,9 +892,10 @@ QPixmap QIcon::pixmap(QWindow *window, const QSize &size, Mode mode, State state } // Try get a pixmap that is big enough to be displayed at device pixel resolution. - QPixmap pixmap = d->engine->pixmap(size * devicePixelRatio, mode, state); - pixmap.setDevicePixelRatio(d->pixmapDevicePixelRatio(devicePixelRatio, size, pixmap.size())); - return pixmap; + QIconEngine::ScaledPixmapArgument scalePixmapArg = { size * devicePixelRatio, mode, state, devicePixelRatio, QPixmap() }; + d->engine->virtual_hook(QIconEngine::ScaledPixmapHook, reinterpret_cast<void*>(&scalePixmapArg)); + scalePixmapArg.pixmap.setDevicePixelRatio(d->pixmapDevicePixelRatio(devicePixelRatio, size, scalePixmapArg.pixmap.size())); + return scalePixmapArg.pixmap; } /*! diff --git a/src/gui/image/qiconengine.cpp b/src/gui/image/qiconengine.cpp index 0ba9844f7a..1f8e5f321a 100644 --- a/src/gui/image/qiconengine.cpp +++ b/src/gui/image/qiconengine.cpp @@ -169,6 +169,12 @@ void QIconEngine::addFile(const QString &/*fileName*/, const QSize &/*size*/, QI bool that can be set to true if the icon is null. This enum value was added in Qt 5.7. + \value ScaledPixmapHook Provides a way to get a pixmap that is scaled + according to the given scale (typically equal to the \l {Glossary Of High + DPI Terms}{device pixel ratio}). The \a data argument of the virtual_hook() + function is a \l ScaledPixmapArgument pointer that contains both the input and + output arguments. This enum value was added in Qt 5.9. + \sa virtual_hook() */ @@ -207,6 +213,60 @@ void QIconEngine::addFile(const QString &/*fileName*/, const QSize &/*size*/, QI vectorial format normally return an empty list. */ +/*! + \class QIconEngine::ScaledPixmapArgument + \since 5.9 + + \inmodule QtGui + + This struct represents arguments to the virtual_hook() function when + the \a id parameter is QIconEngine::ScaledPixmapHook. + + The struct provides a way for icons created via \l QIcon::fromTheme() + to return pixmaps that are designed for the current \l {Glossary Of High + DPI Terms}{device pixel ratio}. The scale for such an icon is specified + using the \l {Icon Theme Specification - Directory Layout}{Scale directory key} + in the appropriate \c index.theme file. + + Icons created via other approaches will return the same result as a call to + \l pixmap() would, and continue to benefit from Qt's \l {High Resolution + Versions of Images}{"@nx" high DPI syntax}. + + \sa virtual_hook(), QIconEngine::IconEngineHook, {High DPI Icons} + */ + +/*! + \variable QIconEngine::ScaledPixmapArgument::size + \brief The requested size of the pixmap. +*/ + +/*! + \variable QIconEngine::ScaledPixmapArgument::mode + \brief The requested mode of the pixmap. + + \sa QIcon::Mode +*/ + +/*! + \variable QIconEngine::ScaledPixmapArgument::state + \brief The requested state of the pixmap. + + \sa QIcon::State +*/ + +/*! + \variable QIconEngine::ScaledPixmapArgument::scale + \brief The requested scale of the pixmap. +*/ + +/*! + \variable QIconEngine::ScaledPixmapArgument::pixmap + + \brief The pixmap that is the best match for the given \l size, \l mode, \l + \state, and \l scale. This is an output parameter that is set after calling + \l virtual_hook(). +*/ + /*! Returns a key that identifies this icon engine. @@ -262,6 +322,13 @@ void QIconEngine::virtual_hook(int id, void *data) arg.sizes.clear(); break; } + case QIconEngine::ScaledPixmapHook: { + // We don't have any notion of scale besides "@nx", so just call pixmap() here. + QIconEngine::ScaledPixmapArgument &arg = + *reinterpret_cast<QIconEngine::ScaledPixmapArgument*>(data); + arg.pixmap = pixmap(arg.size, arg.mode, arg.state); + break; + } default: break; } @@ -273,9 +340,7 @@ void QIconEngine::virtual_hook(int id, void *data) Returns sizes of all images that are contained in the engine for the specific \a mode and \a state. - \note This is a helper method and the actual work is done by - virtual_hook() method, hence this method depends on icon engine support - and may not work with all icon engines. + \include qiconengine-virtualhookhelper.qdocinc */ QList<QSize> QIconEngine::availableSizes(QIcon::Mode mode, QIcon::State state) const { @@ -291,9 +356,7 @@ QList<QSize> QIconEngine::availableSizes(QIcon::Mode mode, QIcon::State state) c Returns the name used to create the engine, if available. - \note This is a helper method and the actual work is done by - virtual_hook() method, hence this method depends on icon engine support - and may not work with all icon engines. + \include qiconengine-virtualhookhelper.qdocinc */ QString QIconEngine::iconName() const { @@ -306,6 +369,8 @@ QString QIconEngine::iconName() const \since 5.7 Returns true if this icon engine represent a null QIcon. + + \include qiconengine-virtualhookhelper.qdocinc */ bool QIconEngine::isNull() const { @@ -314,4 +379,29 @@ bool QIconEngine::isNull() const return isNull; } +/*! + \since 5.9 + + Returns a pixmap for the given \a size, \a mode, \a state and \a scale. + + The \a scale argument is typically equal to the \l {Glossary Of High DPI + Terms}{device pixel ratio} of the display. + + \include qiconengine-virtualhookhelper.qdocinc + + \note Some engines may cast \a scale to an integer. + + \sa ScaledPixmapArgument +*/ +QPixmap QIconEngine::scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale) +{ + ScaledPixmapArgument arg; + arg.size = size; + arg.mode = mode; + arg.state = state; + arg.scale = scale; + const_cast<QIconEngine *>(this)->virtual_hook(QIconEngine::ScaledPixmapHook, reinterpret_cast<void*>(&arg)); + return arg.pixmap; +} + QT_END_NAMESPACE diff --git a/src/gui/image/qiconengine.h b/src/gui/image/qiconengine.h index 783770cd30..0c67ef2686 100644 --- a/src/gui/image/qiconengine.h +++ b/src/gui/image/qiconengine.h @@ -65,7 +65,7 @@ public: virtual bool read(QDataStream &in); virtual bool write(QDataStream &out) const; - enum IconEngineHook { AvailableSizesHook = 1, IconNameHook, IsNullHook }; + enum IconEngineHook { AvailableSizesHook = 1, IconNameHook, IsNullHook, ScaledPixmapHook }; struct AvailableSizesArgument { @@ -79,6 +79,16 @@ public: virtual QString iconName() const; bool isNull() const; // ### Qt6 make virtual + QPixmap scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale); // ### Qt6 make virtual + + struct ScaledPixmapArgument + { + QSize size; + QIcon::Mode mode; + QIcon::State state; + qreal scale; + QPixmap pixmap; + }; virtual void virtual_hook(int id, void *data); diff --git a/src/gui/image/qiconloader.cpp b/src/gui/image/qiconloader.cpp index 324f13a17b..d72c05a3c5 100644 --- a/src/gui/image/qiconloader.cpp +++ b/src/gui/image/qiconloader.cpp @@ -47,6 +47,7 @@ #include <qpa/qplatformtheme.h> #include <QtGui/QIconEngine> #include <QtGui/QPalette> +#include <QtCore/qmath.h> #include <QtCore/QList> #include <QtCore/QDir> #include <QtCore/QSettings> @@ -347,6 +348,10 @@ QIconTheme::QIconTheme(const QString &themeName) dirInfo.maxSize = indexReader.value(directoryKey + QLatin1String("/MaxSize"), size).toInt(); + + dirInfo.scale = indexReader.value(directoryKey + + QLatin1String("/Scale"), + 1).toInt(); m_keyList.append(dirInfo); } } @@ -553,8 +558,11 @@ void QIconLoaderEngine::paint(QPainter *painter, const QRect &rect, * This algorithm is defined by the freedesktop spec: * http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html */ -static bool directoryMatchesSize(const QIconDirInfo &dir, int iconsize) +static bool directoryMatchesSize(const QIconDirInfo &dir, int iconsize, int iconscale) { + if (dir.scale != iconscale) + return false; + if (dir.type == QIconDirInfo::Fixed) { return dir.size == iconsize; @@ -575,24 +583,25 @@ static bool directoryMatchesSize(const QIconDirInfo &dir, int iconsize) * This algorithm is defined by the freedesktop spec: * http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html */ -static int directorySizeDistance(const QIconDirInfo &dir, int iconsize) +static int directorySizeDistance(const QIconDirInfo &dir, int iconsize, int iconscale) { + const int scaledIconSize = iconsize * iconscale; if (dir.type == QIconDirInfo::Fixed) { - return qAbs(dir.size - iconsize); + return qAbs(dir.size * dir.scale - scaledIconSize); } else if (dir.type == QIconDirInfo::Scalable) { - if (iconsize < dir.minSize) - return dir.minSize - iconsize; - else if (iconsize > dir.maxSize) - return iconsize - dir.maxSize; + if (scaledIconSize < dir.minSize * dir.scale) + return dir.minSize * dir.scale - scaledIconSize; + else if (scaledIconSize > dir.maxSize * dir.scale) + return scaledIconSize - dir.maxSize * dir.scale; else return 0; } else if (dir.type == QIconDirInfo::Threshold) { - if (iconsize < dir.size - dir.threshold) - return dir.minSize - iconsize; - else if (iconsize > dir.size + dir.threshold) - return iconsize - dir.maxSize; + if (scaledIconSize < (dir.size - dir.threshold) * dir.scale) + return dir.minSize * dir.scale - scaledIconSize; + else if (scaledIconSize > (dir.size + dir.threshold) * dir.scale) + return scaledIconSize - dir.maxSize * dir.scale; else return 0; } @@ -600,7 +609,7 @@ static int directorySizeDistance(const QIconDirInfo &dir, int iconsize) return INT_MAX; } -QIconLoaderEngineEntry *QIconLoaderEngine::entryForSize(const QSize &size) +QIconLoaderEngineEntry *QIconLoaderEngine::entryForSize(const QSize &size, int scale) { int iconsize = qMin(size.width(), size.height()); @@ -612,7 +621,7 @@ QIconLoaderEngineEntry *QIconLoaderEngine::entryForSize(const QSize &size) // Search for exact matches first for (int i = 0; i < numEntries; ++i) { QIconLoaderEngineEntry *entry = m_info.entries.at(i); - if (directoryMatchesSize(entry->dir, iconsize)) { + if (directoryMatchesSize(entry->dir, iconsize, scale)) { return entry; } } @@ -622,7 +631,7 @@ QIconLoaderEngineEntry *QIconLoaderEngine::entryForSize(const QSize &size) QIconLoaderEngineEntry *closestMatch = 0; for (int i = 0; i < numEntries; ++i) { QIconLoaderEngineEntry *entry = m_info.entries.at(i); - int distance = directorySizeDistance(entry->dir, iconsize); + int distance = directorySizeDistance(entry->dir, iconsize, scale); if (distance < minimalSize) { minimalSize = distance; closestMatch = entry; @@ -665,6 +674,8 @@ QPixmap PixmapEntry::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State st basePixmap.load(filename); QSize actualSize = basePixmap.size(); + // If the size of the best match we have (basePixmap) is larger than the + // requested size, we downscale it to match. if (!actualSize.isNull() && (actualSize.width() > size.width() || actualSize.height() > size.height())) actualSize.scale(size, Qt::KeepAspectRatio); @@ -748,6 +759,15 @@ void QIconLoaderEngine::virtual_hook(int id, void *data) *reinterpret_cast<bool*>(data) = m_info.entries.isEmpty(); } break; + case QIconEngine::ScaledPixmapHook: + { + QIconEngine::ScaledPixmapArgument &arg = *reinterpret_cast<QIconEngine::ScaledPixmapArgument*>(data); + // QIcon::pixmap() multiplies size by the device pixel ratio. + const int integerScale = qCeil(arg.scale); + QIconLoaderEngineEntry *entry = entryForSize(arg.size / integerScale, integerScale); + arg.pixmap = entry ? entry->pixmap(arg.size, arg.mode, arg.state) : QPixmap(); + } + break; default: QIconEngine::virtual_hook(id, data); } diff --git a/src/gui/image/qiconloader_p.h b/src/gui/image/qiconloader_p.h index ed7b7ff7ae..5f3a3ef948 100644 --- a/src/gui/image/qiconloader_p.h +++ b/src/gui/image/qiconloader_p.h @@ -76,12 +76,14 @@ struct QIconDirInfo maxSize(0), minSize(0), threshold(0), + scale(1), type(Threshold) {} QString path; short size; short maxSize; short minSize; short threshold; + short scale; Type type; }; Q_DECLARE_TYPEINFO(QIconDirInfo, Q_MOVABLE_TYPE); @@ -135,7 +137,8 @@ private: bool hasIcon() const; void ensureLoaded(); void virtual_hook(int id, void *data) Q_DECL_OVERRIDE; - QIconLoaderEngineEntry *entryForSize(const QSize &size); + QIconLoaderEngineEntry *entryForSize(const QSize &size, int scale = 1); + QIconLoaderEngine(const QIconLoaderEngine &other); QThemeIconInfo m_info; QString m_iconName; diff --git a/tests/auto/gui/image/image.pro b/tests/auto/gui/image/image.pro index 5a74df4c78..f4e2ab20ba 100644 --- a/tests/auto/gui/image/image.pro +++ b/tests/auto/gui/image/image.pro @@ -10,6 +10,7 @@ SUBDIRS=\ qmovie \ qpicture \ qicon \ + qiconhighdpi !qtHaveModule(network): SUBDIRS -= \ qimagereader diff --git a/tests/auto/gui/image/qiconhighdpi/icons/testtheme/16x16/actions/appointment-new.png b/tests/auto/gui/image/qiconhighdpi/icons/testtheme/16x16/actions/appointment-new.png Binary files differnew file mode 100644 index 0000000000..18b7c6781e --- /dev/null +++ b/tests/auto/gui/image/qiconhighdpi/icons/testtheme/16x16/actions/appointment-new.png diff --git a/tests/auto/gui/image/qiconhighdpi/icons/testtheme/22x22/actions/appointment-new.png b/tests/auto/gui/image/qiconhighdpi/icons/testtheme/22x22/actions/appointment-new.png Binary files differnew file mode 100644 index 0000000000..d676ffd463 --- /dev/null +++ b/tests/auto/gui/image/qiconhighdpi/icons/testtheme/22x22/actions/appointment-new.png diff --git a/tests/auto/gui/image/qiconhighdpi/icons/testtheme/22x22@2/actions/appointment-new.png b/tests/auto/gui/image/qiconhighdpi/icons/testtheme/22x22@2/actions/appointment-new.png Binary files differnew file mode 100644 index 0000000000..6d094d7b54 --- /dev/null +++ b/tests/auto/gui/image/qiconhighdpi/icons/testtheme/22x22@2/actions/appointment-new.png diff --git a/tests/auto/gui/image/qiconhighdpi/icons/testtheme/index.theme b/tests/auto/gui/image/qiconhighdpi/icons/testtheme/index.theme new file mode 100644 index 0000000000..6ab6c15c42 --- /dev/null +++ b/tests/auto/gui/image/qiconhighdpi/icons/testtheme/index.theme @@ -0,0 +1,21 @@ +[Icon Theme] +Name=Test +Comment=Test Theme + +Directories=16x16/actions,22x22/actions,22x22@2/actions + +[16x16/actions] +Size=16 +Context=Actions +Type=Fixed + +[22x22/actions] +Size=22 +Context=Actions +Type=Fixed + +[22x22@2/actions] +Size=22 +Context=Actions +Scale=2 +Type=Fixed diff --git a/tests/auto/gui/image/qiconhighdpi/qiconhighdpi.pro b/tests/auto/gui/image/qiconhighdpi/qiconhighdpi.pro new file mode 100644 index 0000000000..17553158bc --- /dev/null +++ b/tests/auto/gui/image/qiconhighdpi/qiconhighdpi.pro @@ -0,0 +1,8 @@ +CONFIG += testcase +TARGET = tst_qicon + +QT += testlib +SOURCES += tst_qiconhighdpi.cpp +RESOURCES = tst_qiconhighdpi.qrc + +TESTDATA += icons/* diff --git a/tests/auto/gui/image/qiconhighdpi/tst_qiconhighdpi.cpp b/tests/auto/gui/image/qiconhighdpi/tst_qiconhighdpi.cpp new file mode 100644 index 0000000000..ce7f68a0a6 --- /dev/null +++ b/tests/auto/gui/image/qiconhighdpi/tst_qiconhighdpi.cpp @@ -0,0 +1,195 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest/QtTest> +#include <qicon.h> + +class tst_QIconHighDpi : public QObject +{ + Q_OBJECT +public: + tst_QIconHighDpi(); + +private slots: + void initTestCase(); + void fromTheme_data(); + void fromTheme(); +}; + +tst_QIconHighDpi::tst_QIconHighDpi() +{ +} + +void tst_QIconHighDpi::initTestCase() +{ +} + +void tst_QIconHighDpi::fromTheme_data() +{ + QTest::addColumn<int>("requestedSize"); + QTest::addColumn<int>("expectedSize"); + QTest::addColumn<qreal>("expectedDpr"); + + // The pixmaps that we have available can be found in tst_qiconhighdpi.qrc. + // Currently we only have @1 and @2 icons available. + const int dpr = qCeil(qApp->devicePixelRatio()); + + // We have an @2 high DPI version of the 22x22 size of this icon. + switch (dpr) { + case 1: QTest::newRow("22x22,dpr=1") << 22 << 22 << 1.0; break; + case 2: QTest::newRow("22x22,dpr=2") << 22 << 44 << 2.0; break; + case 3: QTest::newRow("22x22,dpr=3") << 22 << 44 << 2.0; break; + } + + // We don't have a high DPI version of the 16x16 size of this icon, + // so directoryMatchesSize() will return false for all directories. + // directorySizeDistance() is then called to find the best match. + // The table below illustrates the results for our available images at various DPRs: + // Available size | Available scale | Requested size | Requested scale | Distance + // 22 * 2 - 16 * 1 = 28 + // 22 * 1 - 16 * 1 = 6 + // 16 * 1 - 16 * 1 = 0 < (16x16) + // Available size | Available scale | Requested size | Requested scale | Distance + // 22 * 2 - 16 * 2 = 12 + // 22 * 1 - 16 * 2 = 10 < (22x22) + // 16 * 1 - 16 * 2 = 16 + // Available size | Available scale | Requested size | Requested scale | Distance + // 22 * 2 - 16 * 3 = 4 < (22x22@2) + // 22 * 1 - 16 * 3 = 26 + // 16 * 1 - 16 * 3 = 32 + // Both of these functions are implementations of the freedesktop icon theme spec, + // which dictates that if there is no matching scale, directorySizeDistance() determines + // the winner, regardless of whether or not the scale is too low for the requested scale. + switch (dpr) { + case 1: QTest::newRow("16x16,dpr=1") << 16 << 16 << 1.0; break; + // PixmapEntry::pixmap() will only downscale the pixmap if actualSize.width() > size.width(). + // In this case, 22 > 32 is false, so a 22x22 pixmap is returned. + case 2: QTest::newRow("16x16,dpr=2") << 16 << 22 << 1.375; break; + case 3: QTest::newRow("16x16,dpr=3") << 16 << 44 << 2.75; break; + } + + // We don't have an 8x8 size of this icon, so: + // Available size | Available scale | Requested size | Requested scale | Distance + // 22 * 2 - 8 * 1 = 36 + // 22 * 1 - 8 * 1 = 14 + // 16 * 1 - 8 * 1 = 8 < (16x16) + // Available size | Available scale | Requested size | Requested scale | Distance + // 22 * 2 - 8 * 2 = 28 + // 22 * 1 - 8 * 2 = 6 + // 16 * 1 - 8 * 2 = 0 < (16x16) + // Available size | Available scale | Requested size | Requested scale | Distance + // 22 * 2 - 8 * 3 = 20 + // 22 * 1 - 8 * 3 = 2 < (22x22) + // 16 * 1 - 8 * 3 = 8 + switch (dpr) { + case 1: QTest::newRow("8x8,dpr=1") << 8 << 8 << 1.0; break; + case 2: QTest::newRow("8x8,dpr=2") << 8 << 16 << 2.0; break; + case 3: QTest::newRow("8x8,dpr=3") << 8 << 22 << 2.75; break; + } + + // We don't have a 44x44 size of this icon, so: + // Available size | Available scale | Requested size | Requested scale | Distance + // 22 * 2 - 44 * 1 = 0 < (22x22@2) + // 22 * 1 - 44 * 1 = 22 + // 16 * 1 - 44 * 1 = 28 + // Available size | Available scale | Requested size | Requested scale | Distance + // 22 * 2 - 44 * 2 = 44 < (22x22@2) + // 22 * 1 - 44 * 2 = 66 + // 16 * 1 - 44 * 2 = 72 + // Available size | Available scale | Requested size | Requested scale | Distance + // 22 * 2 - 44 * 3 = 88 < (22x22@2) + // 22 * 1 - 44 * 3 = 110 + // 16 * 1 - 44 * 3 = 116 + switch (dpr) { + case 1: QTest::newRow("44x44,dpr=1") << 44 << 44 << 1.0; break; + case 2: QTest::newRow("44x44,dpr=2") << 44 << 44 << 1.0; break; + case 3: QTest::newRow("44x44,dpr=3") << 44 << 44 << 1.0; break; + } + + // We don't have a 20x20 size of this icon, so: + // Available size | Available scale | Requested size | Requested scale | Distance + // 22 * 2 - 20 * 1 = 24 + // 22 * 1 - 20 * 1 = 2 < (22x22) + // 16 * 1 - 20 * 1 = 4 + // Available size | Available scale | Requested size | Requested scale | Distance + // 22 * 2 - 20 * 2 = 4 < (22x22@2) + // 22 * 1 - 20 * 2 = 18 + // 16 * 1 - 20 * 2 = 24 + // Available size | Available scale | Requested size | Requested scale | Distance + // 22 * 2 - 20 * 3 = 16 < (22x22@2) + // 22 * 1 - 20 * 3 = 38 + // 16 * 1 - 20 * 3 = 44 + switch (dpr) { + case 1: QTest::newRow("20x20,dpr=1") << 20 << 20 << 1.0; break; + // PixmapEntry::pixmap() will only downscale the pixmap if actualSize.width() > size.width(). + // In this case, 44 > 40 is true, so the 44x44 pixmap is downscaled to 40x40. + case 2: QTest::newRow("20x20,dpr=2") << 20 << 40 << 2.0; break; + case 3: QTest::newRow("20x20,dpr=3") << 20 << 44 << 2.2; break; + } +} + +void tst_QIconHighDpi::fromTheme() +{ + QFETCH(int, requestedSize); + QFETCH(int, expectedSize); + QFETCH(qreal, expectedDpr); + + QString searchPath = QLatin1String(":/icons"); + QIcon::setThemeSearchPaths(QStringList() << searchPath); + QCOMPARE(QIcon::themeSearchPaths().size(), 1); + QCOMPARE(searchPath, QIcon::themeSearchPaths()[0]); + + QString themeName("testtheme"); + QIcon::setThemeName(themeName); + QCOMPARE(QIcon::themeName(), themeName); + + QIcon appointmentIcon = QIcon::fromTheme("appointment-new"); + QVERIFY(!appointmentIcon.isNull()); + QVERIFY(!appointmentIcon.availableSizes(QIcon::Normal, QIcon::Off).isEmpty()); + QVERIFY(appointmentIcon.availableSizes().contains(QSize(16, 16))); + QVERIFY(appointmentIcon.availableSizes().contains(QSize(22, 22))); + + const QPixmap pixmap = appointmentIcon.pixmap(requestedSize); + QCOMPARE(pixmap.size(), QSize(expectedSize, expectedSize)); + // We should get the high DPI version of an image if it exists in the correct directory. + // Note that we didn't pass the QWindow to QIcon::pixmap(), + // because QGuiApplication::devicePixelRatio() will be used if no window was given. + QCOMPARE(pixmap.devicePixelRatio(), expectedDpr); +} + +int main(int argc, char *argv[]) +{ + QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); + QGuiApplication app(argc, argv); + Q_UNUSED(app); + tst_QIconHighDpi test; + QTEST_SET_MAIN_SOURCE_PATH + return QTest::qExec(&test, argc, argv); +} + +#include "tst_qiconhighdpi.moc" diff --git a/tests/auto/gui/image/qiconhighdpi/tst_qiconhighdpi.qrc b/tests/auto/gui/image/qiconhighdpi/tst_qiconhighdpi.qrc new file mode 100644 index 0000000000..80b5e38ee6 --- /dev/null +++ b/tests/auto/gui/image/qiconhighdpi/tst_qiconhighdpi.qrc @@ -0,0 +1,8 @@ +<RCC> + <qresource prefix="/"> + <file>icons/testtheme/16x16/actions/appointment-new.png</file> + <file>icons/testtheme/22x22/actions/appointment-new.png</file> + <file>icons/testtheme/index.theme</file> + <file>icons/testtheme/22x22@2/actions/appointment-new.png</file> + </qresource> +</RCC> |