diff options
Diffstat (limited to 'tests/auto/gui/image')
41 files changed, 1147 insertions, 139 deletions
diff --git a/tests/auto/gui/image/CMakeLists.txt b/tests/auto/gui/image/CMakeLists.txt index 9cc6d4d2bf..c7fc6f07d3 100644 --- a/tests/auto/gui/image/CMakeLists.txt +++ b/tests/auto/gui/image/CMakeLists.txt @@ -12,14 +12,15 @@ add_subdirectory(qpixmap) add_subdirectory(qimage) add_subdirectory(qimageiohandler) add_subdirectory(qimagewriter) -add_subdirectory(qmovie) -add_subdirectory(qpicture) +if(QT_FEATURE_movie) + add_subdirectory(qmovie) +endif() +if(QT_FEATURE_picture) + add_subdirectory(qpicture) +endif() add_subdirectory(qiconhighdpi) if(QT_FEATURE_private_tests) add_subdirectory(qpixmapcache) endif() -# QTBUG-87669 -if(NOT ANDROID) - add_subdirectory(qicon) -endif() +add_subdirectory(qicon) diff --git a/tests/auto/gui/image/qicoimageformat/CMakeLists.txt b/tests/auto/gui/image/qicoimageformat/CMakeLists.txt index 78f0ef98ea..17ec68df4e 100644 --- a/tests/auto/gui/image/qicoimageformat/CMakeLists.txt +++ b/tests/auto/gui/image/qicoimageformat/CMakeLists.txt @@ -5,6 +5,12 @@ ## tst_qicoimageformat Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qicoimageformat LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + # Collect test data file(GLOB_RECURSE test_data_glob RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} diff --git a/tests/auto/gui/image/qicoimageformat/icons/masked/24bpp.ico b/tests/auto/gui/image/qicoimageformat/icons/masked/24bpp.ico Binary files differnew file mode 100644 index 0000000000..7e9cfa2414 --- /dev/null +++ b/tests/auto/gui/image/qicoimageformat/icons/masked/24bpp.ico diff --git a/tests/auto/gui/image/qicoimageformat/icons/masked/24bpp.png b/tests/auto/gui/image/qicoimageformat/icons/masked/24bpp.png Binary files differnew file mode 100644 index 0000000000..f0a19c05e3 --- /dev/null +++ b/tests/auto/gui/image/qicoimageformat/icons/masked/24bpp.png diff --git a/tests/auto/gui/image/qicoimageformat/icons/masked/32bpp.ico b/tests/auto/gui/image/qicoimageformat/icons/masked/32bpp.ico Binary files differnew file mode 100644 index 0000000000..a22248d76a --- /dev/null +++ b/tests/auto/gui/image/qicoimageformat/icons/masked/32bpp.ico diff --git a/tests/auto/gui/image/qicoimageformat/icons/masked/32bpp.png b/tests/auto/gui/image/qicoimageformat/icons/masked/32bpp.png Binary files differnew file mode 100644 index 0000000000..a6ceac73fa --- /dev/null +++ b/tests/auto/gui/image/qicoimageformat/icons/masked/32bpp.png diff --git a/tests/auto/gui/image/qicoimageformat/tst_qicoimageformat.cpp b/tests/auto/gui/image/qicoimageformat/tst_qicoimageformat.cpp index 47bfb5bca5..136f56facf 100644 --- a/tests/auto/gui/image/qicoimageformat/tst_qicoimageformat.cpp +++ b/tests/auto/gui/image/qicoimageformat/tst_qicoimageformat.cpp @@ -1,5 +1,5 @@ // Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QTest> #include <QtGui> #include <QtCore> @@ -27,6 +27,8 @@ private slots: void pngCompression(); void write_data(); void write(); + void icoMask_data(); + void icoMask(); private: QString m_IconPath; @@ -319,6 +321,33 @@ void tst_QIcoImageFormat::write() } } +void tst_QIcoImageFormat::icoMask_data() +{ + QTest::addColumn<QString>("inFile"); + QTest::addColumn<QString>("outFile"); + + QTest::newRow("24bpp") << "masked/24bpp.ico" << "masked/24bpp.png"; + QTest::newRow("32bpp") << "masked/32bpp.ico" << "masked/32bpp.png"; +} + +void tst_QIcoImageFormat::icoMask() +{ + QFETCH(QString, inFile); + QFETCH(QString, outFile); + + QImage inImage; + QImageReader inReader(m_IconPath + QLatin1Char('/') + inFile); + inReader.read(&inImage); + + QImage outImage; + QImageReader outReader(m_IconPath + QLatin1Char('/') + outFile); + outReader.read(&outImage); + outImage.setColorSpace(inImage.colorSpace()); + outImage = outImage.convertToFormat(inImage.format()); + + QCOMPARE(inImage, outImage); +} + QTEST_MAIN(tst_QIcoImageFormat) #include "tst_qicoimageformat.moc" diff --git a/tests/auto/gui/image/qicon/CMakeLists.txt b/tests/auto/gui/image/qicon/CMakeLists.txt index c900c60d5b..93f75741c0 100644 --- a/tests/auto/gui/image/qicon/CMakeLists.txt +++ b/tests/auto/gui/image/qicon/CMakeLists.txt @@ -5,6 +5,12 @@ ## tst_qicon Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qicon LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + # Collect test data file(GLOB_RECURSE test_data_glob RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} @@ -31,11 +37,14 @@ file(GLOB_RECURSE test_data_glob *.svgz) list(APPEND test_data ${test_data_glob}) +add_subdirectory(plugin) + qt_internal_add_test(tst_qicon SOURCES tst_qicon.cpp LIBRARIES Qt::Gui + TestIconPlugin TESTDATA ${test_data} ) @@ -56,6 +65,10 @@ set(tst_qicon_resource_files "./icons/themeparent/index.theme" "./icons/themeparent/scalable/actions/address-book-new.svg" "./icons/themeparent/scalable/actions/appointment-new.svg" + "./icons/fallbacktheme/index.theme" + "./icons/fallbacktheme/16x16/edit-cut.png" + "./icons/hicolor/index.theme" + "./icons/hicolor/16x16/hicolor-icon.png" "./second_icons/testtheme/32x32/actions/appointment-new.png" "./styles/commonstyle/images/standardbutton-open-128.png" "./styles/commonstyle/images/standardbutton-open-16.png" @@ -95,3 +108,5 @@ qt_internal_extend_target(tst_qicon CONDITION TARGET Qt::Widgets LIBRARIES Qt::Widgets ) + +add_dependencies(tst_qicon TestIconPlugin) diff --git a/tests/auto/gui/image/qicon/icons/fallbacktheme/16x16/edit-cut.png b/tests/auto/gui/image/qicon/icons/fallbacktheme/16x16/edit-cut.png Binary files differnew file mode 100644 index 0000000000..661ef1ad03 --- /dev/null +++ b/tests/auto/gui/image/qicon/icons/fallbacktheme/16x16/edit-cut.png diff --git a/tests/auto/gui/image/qicon/icons/fallbacktheme/index.theme b/tests/auto/gui/image/qicon/icons/fallbacktheme/index.theme new file mode 100644 index 0000000000..809d296669 --- /dev/null +++ b/tests/auto/gui/image/qicon/icons/fallbacktheme/index.theme @@ -0,0 +1,8 @@ +[Icon Theme] +Name=fallbacktheme + +Directories=16x16 + +[16x16] +Size=16 +Type=Fixed diff --git a/tests/auto/gui/image/qicon/icons/hicolor/16x16/hicolor-icon.png b/tests/auto/gui/image/qicon/icons/hicolor/16x16/hicolor-icon.png Binary files differnew file mode 100644 index 0000000000..661ef1ad03 --- /dev/null +++ b/tests/auto/gui/image/qicon/icons/hicolor/16x16/hicolor-icon.png diff --git a/tests/auto/gui/image/qicon/icons/hicolor/index.theme b/tests/auto/gui/image/qicon/icons/hicolor/index.theme new file mode 100644 index 0000000000..e5e5cef9b1 --- /dev/null +++ b/tests/auto/gui/image/qicon/icons/hicolor/index.theme @@ -0,0 +1,11 @@ +[Icon Theme] +Name=hicolor + +# Provide a minimal hicolor theme, so that our hicolor fallback +# lookup during testing will find that theme on all systems. + +Directories=16x16 + +[16x16] +Size=16 +Type=Fixed diff --git a/tests/auto/gui/image/qicon/icons/testtheme/index.theme b/tests/auto/gui/image/qicon/icons/testtheme/index.theme index e18736ab43..53664b14b2 100644 --- a/tests/auto/gui/image/qicon/icons/testtheme/index.theme +++ b/tests/auto/gui/image/qicon/icons/testtheme/index.theme @@ -1,7 +1,7 @@ [Icon Theme] _Name=Test _Comment=Test Theme -Inherits=crystalsvg, themeparent +Inherits=themeparent Example=x-directory-normal # KDE Specific Stuff diff --git a/tests/auto/gui/image/qicon/icons/themeparent/index.theme b/tests/auto/gui/image/qicon/icons/themeparent/index.theme index e536a0bf2f..96267addd6 100644 --- a/tests/auto/gui/image/qicon/icons/themeparent/index.theme +++ b/tests/auto/gui/image/qicon/icons/themeparent/index.theme @@ -1,7 +1,6 @@ [Icon Theme] _Name=Test _Comment=Test Theme -Inherits=gnome,crystalsvg Example=x-directory-normal # KDE Specific Stuff diff --git a/tests/auto/gui/image/qicon/plugin/CMakeLists.txt b/tests/auto/gui/image/qicon/plugin/CMakeLists.txt new file mode 100644 index 0000000000..cae49b2df1 --- /dev/null +++ b/tests/auto/gui/image/qicon/plugin/CMakeLists.txt @@ -0,0 +1,20 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## TestIconEngine Plugin: +##################################################################### + +qt_internal_add_plugin(TestIconPlugin + STATIC + OUTPUT_NAME qtesticonplugin + OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" + SKIP_INSTALL + PLUGIN_TYPE iconengines + DEFAULT_IF TRUE + SOURCES + main.cpp + LIBRARIES + Qt::Core + Qt::Gui +) diff --git a/tests/auto/gui/image/qicon/plugin/main.cpp b/tests/auto/gui/image/qicon/plugin/main.cpp new file mode 100644 index 0000000000..58d9807142 --- /dev/null +++ b/tests/auto/gui/image/qicon/plugin/main.cpp @@ -0,0 +1,73 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <qiconengineplugin.h> +#include <qiconengine.h> + +QT_BEGIN_NAMESPACE + +class TestIconPlugin : public QIconEnginePlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QIconEngineFactoryInterface" FILE "plugin.json") + +public: + QIconEngine *create(const QString &icon) override; +}; + +class TestIconEngine : public QIconEngine +{ +public: + TestIconEngine(const QString &icon) + : m_iconName(QIcon::themeName() + "/" + icon) + { + } + + ~TestIconEngine() + {} + + QIconEngine *clone() const override + { + return new TestIconEngine(m_iconName); + } + + QString key() const override + { + return QStringLiteral("TestIconEngine"); + } + + QString iconName() override + { + return m_iconName; + } + + bool isNull() override + { + return m_iconName.isNull(); + } + + QList<QSize> availableSizes(QIcon::Mode, QIcon::State) override + { + return {{16, 16}, {48, 48}, {64, 64}}; + } + + void paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state) override + { + Q_UNUSED(painter); + Q_UNUSED(rect); + Q_UNUSED(mode); + Q_UNUSED(state); + } + +private: + const QString m_iconName; +}; + +QIconEngine *TestIconPlugin::create(const QString &icon) +{ + return new TestIconEngine(icon); +} + +QT_END_NAMESPACE + +#include "main.moc" diff --git a/tests/auto/gui/image/qicon/plugin/plugin.json b/tests/auto/gui/image/qicon/plugin/plugin.json new file mode 100644 index 0000000000..96b59aa79e --- /dev/null +++ b/tests/auto/gui/image/qicon/plugin/plugin.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "plugintheme", "SpecialTheme" ] +} diff --git a/tests/auto/gui/image/qicon/tst_qicon.cpp b/tests/auto/gui/image/qicon/tst_qicon.cpp index ebb0f1e9d2..d95ee66fb6 100644 --- a/tests/auto/gui/image/qicon/tst_qicon.cpp +++ b/tests/auto/gui/image/qicon/tst_qicon.cpp @@ -1,5 +1,5 @@ // Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QTest> #include <QImageReader> @@ -41,12 +41,16 @@ private slots: void streamAvailableSizes(); void fromTheme(); void fromThemeCache(); + void fromThemeConstant(); #ifndef QT_NO_WIDGETS void task184901_badCache(); #endif void task223279_inconsistentAddFile(); + void themeFromPlugin_data(); + void themeFromPlugin(); + private: bool haveImageFormat(QByteArray const&); @@ -191,21 +195,21 @@ void tst_QIcon::isNull() { // test string constructor with empty string QIcon iconEmptyString = QIcon(QString()); QVERIFY(iconEmptyString.isNull()); - QVERIFY(!iconEmptyString.actualSize(QSize(32, 32)).isValid());; + QVERIFY(!iconEmptyString.actualSize(QSize(32, 32)).isValid()); // test string constructor with non-existing file QIcon iconNoFile = QIcon("imagedoesnotexist"); - QVERIFY(!iconNoFile.isNull()); + QVERIFY(iconNoFile.isNull()); QVERIFY(!iconNoFile.actualSize(QSize(32, 32)).isValid()); // test string constructor with non-existing file with suffix QIcon iconNoFileSuffix = QIcon("imagedoesnotexist.png"); - QVERIFY(!iconNoFileSuffix.isNull()); + QVERIFY(iconNoFileSuffix.isNull()); QVERIFY(!iconNoFileSuffix.actualSize(QSize(32, 32)).isValid()); // test string constructor with existing file but unsupported format QIcon iconUnsupportedFormat = QIcon(m_sourceFileName); - QVERIFY(!iconUnsupportedFormat.isNull()); + QVERIFY(iconUnsupportedFormat.isNull()); QVERIFY(!iconUnsupportedFormat.actualSize(QSize(32, 32)).isValid()); // test string constructor with existing file and supported format @@ -552,6 +556,10 @@ void tst_QIcon::availableSizes() void tst_QIcon::name() { + const auto reset = qScopeGuard([]{ + QIcon::setThemeName({}); + QIcon::setThemeSearchPaths({}); + }); { // No name if icon does not come from a theme QIcon icon(":/image.png"); @@ -629,6 +637,7 @@ void tst_QIcon::task184901_badCache() void tst_QIcon::fromTheme() { + const bool abIconFromPlatform = !QIcon::fromTheme("address-book-new").isNull(); QString firstSearchPath = QLatin1String(":/icons"); QString secondSearchPath = QLatin1String(":/second_icons"); QIcon::setThemeSearchPaths(QStringList() << firstSearchPath << secondSearchPath); @@ -717,14 +726,44 @@ void tst_QIcon::fromTheme() QCOMPARE(i.availableSizes(), abIcon.availableSizes()); } + // Setting or changing the fallback theme should invalidate earlier lookups. + // We can only test this if the system doesn't provide an icon, because once + // we got a valid icon, it will be cached, and even if we proxy to a different + // engine when a fallback theme is set, the cacheKey of the icon will be the + // same. + const QIcon editCut = QIcon::fromTheme("edit-cut"); + if (editCut.isNull()) { + QIcon::setFallbackThemeName("fallbacktheme"); + QVERIFY(!QIcon::fromTheme("edit-cut").isNull()); + } + // Make sure setting the theme name clears the state QIcon::setThemeName(""); abIcon = QIcon::fromTheme("address-book-new"); - QVERIFY(abIcon.isNull()); + QCOMPARE_NE(abIcon.isNull(), abIconFromPlatform); + + // Test fallback icon behavior for empty theme names. + // Can only reliably test this on systems that don't have a + // named system icon theme. + QIcon::setThemeName(""); // Reset user-theme + if (QIcon::themeName().isEmpty()) { + // Test icon from fallback theme even when theme name is empty + QIcon::setFallbackThemeName("fallbacktheme"); + QVERIFY(!QIcon::fromTheme("edit-cut").isNull()); + + // Test icon from fallback path even when theme name is empty + fallbackIcon = QIcon::fromTheme("red"); + QVERIFY(!fallbackIcon.isNull()); + QVERIFY(QIcon::hasThemeIcon("red")); + QCOMPARE(fallbackIcon.availableSizes().size(), 1); + } // Passing a full path to fromTheme is not very useful, but should work anyway QIcon fullPathIcon = QIcon::fromTheme(m_pngImageFileName); QVERIFY(!fullPathIcon.isNull()); + + // Restore to system fallback theme + QIcon::setFallbackThemeName(""); } static inline QString findGtkUpdateIconCache() @@ -813,6 +852,11 @@ void tst_QIcon::fromThemeCache() QVERIFY(QIcon::fromTheme("notexist-fallback").isNull()); } +void tst_QIcon::fromThemeConstant() +{ + const QIcon icon = QIcon::fromTheme(QIcon::ThemeIcon::EditCut); +} + void tst_QIcon::task223279_inconsistentAddFile() { QIcon icon1; @@ -831,6 +875,32 @@ void tst_QIcon::task223279_inconsistentAddFile() QCOMPARE(pm1.size(), pm2.size()); } +Q_IMPORT_PLUGIN(TestIconPlugin) + +void tst_QIcon::themeFromPlugin_data() +{ + QTest::addColumn<QString>("themeName"); + + QTest::addRow("plugintheme") << "plugintheme"; + QTest::addRow("specialtheme") << "specialTheme"; // deliberately not matching case +} + +void tst_QIcon::themeFromPlugin() +{ + QFETCH(const QString, themeName); + auto restoreTheme = qScopeGuard([oldTheme = QIcon::themeName()]{ + QIcon::setThemeName(oldTheme); + }); + + QIcon icon = QIcon::fromTheme("icon1"); + QVERIFY(icon.isNull()); + + QIcon::setThemeName(themeName); + + icon = QIcon::fromTheme("icon1"); + QVERIFY(!icon.isNull()); + QCOMPARE(icon.name(), themeName + "/icon1"); +} QTEST_MAIN(tst_QIcon) #include "tst_qicon.moc" diff --git a/tests/auto/gui/image/qiconhighdpi/CMakeLists.txt b/tests/auto/gui/image/qiconhighdpi/CMakeLists.txt index 6a6717df09..f0ccb97c8a 100644 --- a/tests/auto/gui/image/qiconhighdpi/CMakeLists.txt +++ b/tests/auto/gui/image/qiconhighdpi/CMakeLists.txt @@ -5,6 +5,12 @@ ## tst_qiconhighdpi Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qiconhighdpi LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + # Collect test data file(GLOB_RECURSE test_data_glob RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} diff --git a/tests/auto/gui/image/qiconhighdpi/tst_qiconhighdpi.cpp b/tests/auto/gui/image/qiconhighdpi/tst_qiconhighdpi.cpp index 829c463c6b..34f0132865 100644 --- a/tests/auto/gui/image/qiconhighdpi/tst_qiconhighdpi.cpp +++ b/tests/auto/gui/image/qiconhighdpi/tst_qiconhighdpi.cpp @@ -1,5 +1,5 @@ // Copyright (C) 2017 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QTest> #include <qicon.h> @@ -17,6 +17,7 @@ private slots: void addPixmap_data(); void addPixmap(); void ninePatch(); + void preferUpscale(); }; tst_QIconHighDpi::tst_QIconHighDpi() @@ -211,6 +212,23 @@ void tst_QIconHighDpi::ninePatch() } } +void tst_QIconHighDpi::preferUpscale() +{ + QIcon icon; + + // manual pixmap adder for full control of devicePixelRatio + auto addPixmapWithDpr = [&icon](const QString &path, qreal dpr) { + QImage image(path); + image.setDevicePixelRatio(dpr); + icon.addPixmap(QPixmap::fromImage(image)); + }; + + addPixmapWithDpr(":/icons/testtheme/22x22/actions/appointment-new.png", 1); + addPixmapWithDpr(":/icons/testtheme/22x22@2/actions/appointment-new.png", 2); + + QCOMPARE(icon.pixmap(QSize(22, 22), 1.25f).devicePixelRatio(), 1.25f); +} + int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); diff --git a/tests/auto/gui/image/qimage/CMakeLists.txt b/tests/auto/gui/image/qimage/CMakeLists.txt index ecd68ac82c..8d0842026d 100644 --- a/tests/auto/gui/image/qimage/CMakeLists.txt +++ b/tests/auto/gui/image/qimage/CMakeLists.txt @@ -5,6 +5,12 @@ ## tst_qimage Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qimage LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + # Collect test data file(GLOB_RECURSE test_data_glob RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} diff --git a/tests/auto/gui/image/qimage/images/CGATS001Compat-v2-micro.icc b/tests/auto/gui/image/qimage/images/CGATS001Compat-v2-micro.icc Binary files differnew file mode 100644 index 0000000000..b5a73495bf --- /dev/null +++ b/tests/auto/gui/image/qimage/images/CGATS001Compat-v2-micro.icc diff --git a/tests/auto/gui/image/qimage/images/VideoHD.icc b/tests/auto/gui/image/qimage/images/VideoHD.icc Binary files differnew file mode 100644 index 0000000000..b96eb68136 --- /dev/null +++ b/tests/auto/gui/image/qimage/images/VideoHD.icc diff --git a/tests/auto/gui/image/qimage/tst_qimage.cpp b/tests/auto/gui/image/qimage/tst_qimage.cpp index 415f016519..8086ffcc28 100644 --- a/tests/auto/gui/image/qimage/tst_qimage.cpp +++ b/tests/auto/gui/image/qimage/tst_qimage.cpp @@ -1,5 +1,5 @@ // Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QTest> @@ -15,6 +15,7 @@ #include <stdio.h> #include <qpainter.h> +#include <private/qcmyk_p.h> #include <private/qimage_p.h> #include <private/qdrawhelper_p.h> @@ -108,6 +109,10 @@ private slots: void smoothScaleAlpha(); void smoothScaleFormats_data(); void smoothScaleFormats(); + void smoothScaleNoConversion_data(); + void smoothScaleNoConversion(); + void smoothScale_CMYK_data(); + void smoothScale_CMYK(); void transformed_data(); void transformed(); @@ -167,6 +172,15 @@ private slots: void largeInplaceRgbConversion_data(); void largeInplaceRgbConversion(); + void colorSpaceRgbConversion_data(); + void colorSpaceRgbConversion(); + void colorSpaceCmykConversion_data(); + void colorSpaceCmykConversion(); + void colorSpaceFromGrayConversion_data(); + void colorSpaceFromGrayConversion(); + void colorSpaceToGrayConversion_data(); + void colorSpaceToGrayConversion(); + void deepCopyWhenPaintingActive(); void scaled_QTBUG19157(); @@ -229,6 +243,7 @@ private slots: void largeRasterScale(); void metadataChangeWithReadOnlyPixels(); + void scaleIndexed(); #if defined(Q_OS_WIN) void toWinHBITMAP_data(); @@ -236,6 +251,9 @@ private slots: void fromMonoHBITMAP(); #endif // Q_OS_WIN + void tofromPremultipliedFormat_data(); + void tofromPremultipliedFormat(); + private: const QString m_prefix; }; @@ -315,7 +333,9 @@ static QLatin1String formatToString(QImage::Format format) return QLatin1String("RGBA32FPx4"); case QImage::Format_RGBA32FPx4_Premultiplied: return QLatin1String("RGBA32FPx4pm"); - default: + case QImage::Format_CMYK8888: + return QLatin1String("CMYK8888"); + case QImage::NImageFormats: break; }; Q_UNREACHABLE(); @@ -1133,10 +1153,9 @@ void tst_QImage::rotate_data() QTest::addColumn<QImage::Format>("format"); QTest::addColumn<int>("degrees"); - QList<int> degrees; - degrees << 0 << 90 << 180 << 270; + constexpr int degrees[] = {0, 90, 180, 270}; - foreach (int d, degrees) { + for (int d : degrees) { const QString dB = QString::number(d); for (int i = QImage::Format_Indexed8; i < QImage::NImageFormats; i++) { QImage::Format format = static_cast<QImage::Format>(i); @@ -1502,6 +1521,8 @@ void tst_QImage::setPixelWithAlpha_data() continue; if (c == QImage::Format_Alpha8) continue; + if (c == QImage::Format_CMYK8888) + continue; QTest::newRow(qPrintable(formatToString(QImage::Format(c)))) << QImage::Format(c); } } @@ -2059,6 +2080,94 @@ void tst_QImage::smoothScaleFormats() QVERIFY(rotated.hasAlphaChannel()); } +void tst_QImage::smoothScaleNoConversion_data() +{ + QTest::addColumn<QImage::Format>("format"); + QTest::addRow("Mono") << QImage::Format_Mono; + QTest::addRow("MonoLSB") << QImage::Format_MonoLSB; + QTest::addRow("Indexed8") << QImage::Format_Indexed8; +} + +void tst_QImage::smoothScaleNoConversion() +{ + QFETCH(QImage::Format, format); + QImage img(128, 128, format); + img.fill(1); + img.setColorTable(QList<QRgb>() << qRgba(255,0,0,255) << qRgba(0,0,0,0)); + img = img.scaled(QSize(48, 48), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + QVERIFY(img.hasAlphaChannel()); +} + +void tst_QImage::smoothScale_CMYK_data() +{ + QTest::addColumn<int>("size"); + + const int sizes[] = { 2, 3, 4, 6, 7, 8, 10, 16, 20, 32, 40, 64, 100, 101, 128 }; + for (int size : sizes) + QTest::addRow("%d x %d", size, size) << size; +} + +void tst_QImage::smoothScale_CMYK() +{ + QFETCH(int, size); + QImage img(size, size, QImage::Format_CMYK8888); + QCmyk32 expected(31, 63, 127, 127); + img.fill(expected.toUint()); + + auto getCmykPixel = [](const QImage &image, int x, int y) { + Q_ASSERT(image.format() == QImage::Format_CMYK8888); + const uint *line = reinterpret_cast<const uint *>(image.scanLine(y)); + const uint pixel = line[x]; + return QCmyk32::fromCmyk32(pixel); + }; + + // scale x down, y down + QImage scaled = img.scaled(QSize(1, 1), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + QCmyk32 pixel = getCmykPixel(scaled, 0, 0); + QCOMPARE(pixel, expected); + + // scale x down, y up + scaled = img.scaled(QSize(1, size * 2), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + for (int y = 0; y < scaled.height(); ++y) { + pixel = getCmykPixel(scaled, 0, y); + QCOMPARE(pixel, expected); + } + + // scale x up, y down + scaled = img.scaled(QSize(size * 2, 1), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + for (int x = 0; x < scaled.width(); ++x) { + pixel = getCmykPixel(scaled, x, 0); + QCOMPARE(pixel, expected); + } + + // scale x up + scaled = img.scaled(QSize(size, size * 2), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + for (int y = 0; y < scaled.height(); ++y) { + for (int x = 0; x < scaled.width(); ++x) { + pixel = getCmykPixel(scaled, x, y); + QCOMPARE(pixel, expected); + } + } + + // scale y up + scaled = img.scaled(QSize(size * 2, size), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + for (int y = 0; y < scaled.height(); ++y) { + for (int x = 0; x < scaled.width(); ++x) { + pixel = getCmykPixel(scaled, x, y); + QCOMPARE(pixel, expected); + } + } + + // scale x up, y up + scaled = img.scaled(QSize(size * 2, size * 2), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + for (int y = 0; y < scaled.height(); ++y) { + for (int x = 0; x < scaled.width(); ++x) { + pixel = getCmykPixel(scaled, x, y); + QCOMPARE(pixel, expected); + } + } +} + static int count(const QImage &img, int x, int y, int dx, int dy, QRgb pixel) { int i = 0; @@ -2545,7 +2654,8 @@ void tst_QImage::rgbSwapped_data() for (int i = QImage::Format_Indexed8; i < QImage::NImageFormats; ++i) { if (i == QImage::Format_Alpha8 || i == QImage::Format_Grayscale8 - || i == QImage::Format_Grayscale16) { + || i == QImage::Format_Grayscale16 + || i == QImage::Format_CMYK8888) { continue; } QTest::addRow("%s", formatToString(QImage::Format(i)).data()) << QImage::Format(i); @@ -3027,13 +3137,15 @@ void tst_QImage::inplaceRgbConversion_data() for (int i = QImage::Format_RGB32; i < QImage::NImageFormats; ++i) { if (i == QImage::Format_Alpha8 || i == QImage::Format_Grayscale8 - || i == QImage::Format_Grayscale16) { + || i == QImage::Format_Grayscale16 + || i == QImage::Format_CMYK8888) { continue; } for (int j = QImage::Format_RGB32; j < QImage::NImageFormats; ++j) { if (j == QImage::Format_Alpha8 || j == QImage::Format_Grayscale8 - || j == QImage::Format_Grayscale16) { + || j == QImage::Format_Grayscale16 + || j == QImage::Format_CMYK8888) { continue; } if (i == j) @@ -3214,6 +3326,258 @@ void tst_QImage::largeInplaceRgbConversion() } } +void tst_QImage::colorSpaceRgbConversion_data() +{ + QTest::addColumn<QImage::Format>("fromFormat"); + QTest::addColumn<QImage::Format>("toFormat"); + + // The various possible code paths for color space conversions compatible with RGB color spaces: + QImage::Format formats[] = { + QImage::Format_RGB32, + QImage::Format_ARGB32, + QImage::Format_ARGB32_Premultiplied, + QImage::Format_RGBX64, + QImage::Format_RGBA64, + QImage::Format_RGBA64_Premultiplied, + QImage::Format_RGBX32FPx4, + QImage::Format_RGBA32FPx4, + QImage::Format_RGBA32FPx4_Premultiplied, + QImage::Format_Grayscale8, + QImage::Format_Grayscale16, + }; + + for (auto fromFormat : formats) { + const QLatin1String formatI = formatToString(fromFormat); + for (auto toFormat : formats) { + QTest::addRow("%s -> %s", formatI.data(), formatToString(toFormat).data()) + << fromFormat << toFormat; + } + } +} + +void tst_QImage::colorSpaceRgbConversion() +{ + // Test that all color space conversions work + QFETCH(QImage::Format, fromFormat); + QFETCH(QImage::Format, toFormat); + + bool srcGrayscale = fromFormat == QImage::Format_Grayscale8 || fromFormat == QImage::Format_Grayscale16; + bool dstGrayscale = toFormat == QImage::Format_Grayscale8 || toFormat == QImage::Format_Grayscale16; + + QImage image(16, 16, fromFormat); + image.setColorSpace(QColorSpace::SRgb); + + for (int i = 0; i < image.height(); ++i) { + for (int j = 0; j < image.width(); ++j) { + if (srcGrayscale || dstGrayscale) + image.setPixel(j, i, qRgb((i + j) * 8, (i + j) * 8, (i + j) * 8)); + else + image.setPixel(j, i, qRgb(j * 16, i * 16, (i + j) * 8)); + } + } + + QImage imageConverted = image.convertedToColorSpace(QColorSpace::DisplayP3, toFormat); + QCOMPARE(imageConverted.format(), toFormat); + QCOMPARE(imageConverted.size(), image.size()); + if (dstGrayscale) { + int gray = 0; + for (int x = 0; x < image.width(); ++x) { + int newGray = qGray(imageConverted.pixel(x, 6)); + QCOMPARE_GE(newGray, gray); + gray = newGray; + } + } else { + int red = 0; + int blue = 0; + for (int x = 0; x < image.width(); ++x) { + int newRed = qRed(imageConverted.pixel(x, 5)); + int newBlue = qBlue(imageConverted.pixel(x, 7)); + QCOMPARE_GE(newBlue, blue); + QCOMPARE_GE(newRed, red); + blue = newBlue; + red = newRed; + } + } +} + + +void tst_QImage::colorSpaceCmykConversion_data() +{ + QTest::addColumn<QImage::Format>("toFormat"); + + QImage::Format formats[] = { + QImage::Format_RGB32, + QImage::Format_ARGB32, + QImage::Format_ARGB32_Premultiplied, + QImage::Format_RGBX64, + QImage::Format_RGBA64, + QImage::Format_RGBA64_Premultiplied, + QImage::Format_RGBX32FPx4, + QImage::Format_RGBA32FPx4, + QImage::Format_RGBA32FPx4_Premultiplied, + QImage::Format_Grayscale8, + QImage::Format_Grayscale16, + }; + + for (auto toFormat : formats) + QTest::addRow("CMYK8888 -> %s", formatToString(toFormat).data()) << toFormat; +} + +void tst_QImage::colorSpaceCmykConversion() +{ + QFETCH(QImage::Format, toFormat); + + bool dstGrayscale = toFormat == QImage::Format_Grayscale8 || toFormat == QImage::Format_Grayscale16; + + QImage image(16, 16, QImage::Format_CMYK8888); + QFile iccProfile(m_prefix +"CGATS001Compat-v2-micro.icc"); + iccProfile.open(QIODevice::ReadOnly); + image.setColorSpace(QColorSpace::fromIccProfile(iccProfile.readAll())); + QVERIFY(image.colorSpace().isValid()); + + for (int i = 0; i < image.height(); ++i) { + for (int j = 0; j < image.width(); ++j) { + if (dstGrayscale) + image.setPixel(j, i, qRgb((i + j) * 8, (i + j) * 8, (i + j) * 8)); + else + image.setPixel(j, i, qRgb(j * 16, i * 16, (i + j) * 8)); + } + } + + QImage imageConverted = image.convertedToColorSpace(QColorSpace::SRgb, toFormat); + QCOMPARE(imageConverted.format(), toFormat); + QCOMPARE(imageConverted.size(), image.size()); + if (dstGrayscale) { + int gray = 0; + for (int x = 0; x < image.width(); ++x) { + int newGray = qGray(imageConverted.pixel(x, 6)); + QCOMPARE_GE(newGray, gray); + gray = newGray; + } + } else { + int red = 0; + for (int x = 0; x < image.width(); ++x) { + int newRed = qRed(imageConverted.pixel(x, 5)); + QCOMPARE_GE(newRed, red); + red = newRed; + } + } +} + +void tst_QImage::colorSpaceFromGrayConversion_data() +{ + QTest::addColumn<QImage::Format>("fromFormat"); + QTest::addColumn<QColorSpace>("fromCS"); + QTest::addColumn<QColorSpace>("toCS"); + + QImage::Format formats[] = { + QImage::Format_Grayscale8, + QImage::Format_Grayscale16, + }; + + QList<QColorSpace> colorSpaces = { + QColorSpace::SRgbLinear, + QColorSpace::DisplayP3, + QColorSpace(QPointF(0.31271, 0.32902), QColorSpace::TransferFunction::SRgb), + QColorSpace(QPointF(0.30, 0.33), QColorSpace::TransferFunction::Linear) + }; + std::string names[] = { + "sRgbLinear", + "displayP3", + "graySRgb", + "grayOther", + "videoHD(A2B)" + }; + + QFile iccProfile(m_prefix + "VideoHD.icc"); + iccProfile.open(QIODevice::ReadOnly); + colorSpaces.append(QColorSpace::fromIccProfile(iccProfile.readAll())); + + for (auto fromFormat : formats) { + for (int from = 0; from < 5; ++from) { + for (int to = 0; to < 4; ++to) { + QTest::addRow("%s: %s -> %s", formatToString(fromFormat).data(), names[from].c_str(), names[to].c_str()) + << fromFormat << colorSpaces[from] << colorSpaces[to]; + } + } + } +} + +void tst_QImage::colorSpaceFromGrayConversion() +{ + QFETCH(QImage::Format, fromFormat); + QFETCH(QColorSpace, fromCS); + QFETCH(QColorSpace, toCS); + + QImage image(16, 16, fromFormat); + image.setColorSpace(fromCS); + QVERIFY(image.colorSpace().isValid()); + + for (int i = 0; i < image.height(); ++i) { + for (int j = 0; j < image.width(); ++j) { + image.setPixel(j, i, qRgb((i + j) * 8, (i + j) * 8, (i + j) * 8)); + } + } + QImage imageConverted = image.convertedToColorSpace(toCS); + QCOMPARE(imageConverted.format(), fromFormat); + QCOMPARE(imageConverted.size(), image.size()); + int gray = 0; + for (int x = 0; x < image.width(); ++x) { + int newGray = qGray(imageConverted.pixel(x, 3)); + QCOMPARE_GE(newGray, gray); + gray = newGray; + } +} + +void tst_QImage::colorSpaceToGrayConversion_data() +{ + QTest::addColumn<QImage::Format>("fromFormat"); + + QImage::Format formats[] = { + QImage::Format_RGB32, + QImage::Format_ARGB32, + QImage::Format_ARGB32_Premultiplied, + QImage::Format_RGBX64, + QImage::Format_RGBA64, + QImage::Format_RGBA64_Premultiplied, + QImage::Format_RGBX32FPx4, + QImage::Format_RGBA32FPx4, + QImage::Format_RGBA32FPx4_Premultiplied, + QImage::Format_Grayscale8, + QImage::Format_Grayscale16, + }; + + for (auto fromFormat : formats) + QTest::addRow("%s -> Gray", formatToString(fromFormat).data()) << fromFormat; +} + +void tst_QImage::colorSpaceToGrayConversion() +{ + QFETCH(QImage::Format, fromFormat); + + QImage image(16, 16, fromFormat); + image.setColorSpace(QColorSpace::DisplayP3); + QVERIFY(image.colorSpace().isValid()); + + for (int i = 0; i < image.height(); ++i) { + for (int j = 0; j < image.width(); ++j) { + image.setPixel(j, i, qRgb((i + j) * 8, (i + j) * 8, (i + j) * 8)); + } + } + + QColorSpace grayColorSpace(QPointF(0.31271, 0.32902), QColorSpace::TransferFunction::SRgb); + + QImage imageConverted = image.convertedToColorSpace(grayColorSpace); + QVERIFY(imageConverted.format() == QImage::Format_Grayscale8 || imageConverted.format() == QImage::Format_Grayscale16); + QCOMPARE(imageConverted.size(), image.size()); + int gray = 0; + for (int x = 0; x < image.width(); ++x) { + int newGray = qGray(imageConverted.pixel(x, 11)); + QCOMPARE_GE(newGray, gray); + gray = newGray; + } +} + void tst_QImage::deepCopyWhenPaintingActive() { QImage image(64, 64, QImage::Format_ARGB32_Premultiplied); @@ -3322,7 +3686,8 @@ void tst_QImage::invertPixelsRGB_data() for (int i = QImage::Format_RGB32; i < QImage::NImageFormats; ++i) { if (i == QImage::Format_Alpha8 || i == QImage::Format_Grayscale8 - || i == QImage::Format_Grayscale16) { + || i == QImage::Format_Grayscale16 + || i == QImage::Format_CMYK8888) { continue; } QTest::addRow("%s", formatToString(QImage::Format(i)).data()) << QImage::Format(i); @@ -3689,10 +4054,12 @@ void tst_QImage::metadataPassthrough() QCOMPARE(alphaMask.dotsPerMeterY(), a.dotsPerMeterY()); QCOMPARE(alphaMask.devicePixelRatio(), a.devicePixelRatio()); +#ifndef QT_NO_IMAGE_HEURISTIC_MASK QImage heuristicMask = a.createHeuristicMask(); QCOMPARE(heuristicMask.dotsPerMeterX(), a.dotsPerMeterX()); QCOMPARE(heuristicMask.dotsPerMeterY(), a.dotsPerMeterY()); QCOMPARE(heuristicMask.devicePixelRatio(), a.devicePixelRatio()); +#endif QImage maskFromColor = a.createMaskFromColor(qRgb(0, 0, 0)); QCOMPARE(maskFromColor.dotsPerMeterX(), a.dotsPerMeterX()); @@ -4080,6 +4447,16 @@ void tst_QImage::metadataChangeWithReadOnlyPixels() QCOMPARE(image.constBits(), (const uchar *)data); } +void tst_QImage::scaleIndexed() +{ + QImage image(10, 10, QImage::Format_Indexed8); + image.setColor(0, qRgb(0,0,0)); + image.setColor(1, qRgb(1,1,1)); + image.fill(1); + image.setDevicePixelRatio(2); + QImage image2 = image.scaled(20, 20, Qt::KeepAspectRatio, Qt::SmoothTransformation); // do not crash +} + #if defined(Q_OS_WIN) static inline QColor COLORREFToQColor(COLORREF cr) @@ -4192,5 +4569,27 @@ void tst_QImage::fromMonoHBITMAP() // QTBUG-72343, corruption for mono bitmaps #endif // Q_OS_WIN +void tst_QImage::tofromPremultipliedFormat_data() +{ + QTest::addColumn<QImage::Format>("unpremul"); + QTest::addColumn<QImage::Format>("premul"); + + // Test all available formats with both premultiplied and unpremultiplied versions + QTest::newRow("argb32") << QImage::Format_ARGB32 << QImage::Format_ARGB32_Premultiplied; + QTest::newRow("rgba8888") << QImage::Format_RGBA8888 << QImage::Format_RGBA8888_Premultiplied; + QTest::newRow("rgba64") << QImage::Format_RGBA64 << QImage::Format_RGBA64_Premultiplied; + QTest::newRow("rgba16fpx4") << QImage::Format_RGBA16FPx4 << QImage::Format_RGBA16FPx4_Premultiplied; + QTest::newRow("rgba32fpx4") << QImage::Format_RGBA32FPx4 << QImage::Format_RGBA32FPx4_Premultiplied; +} + +void tst_QImage::tofromPremultipliedFormat() +{ + QFETCH(QImage::Format, unpremul); + QFETCH(QImage::Format, premul); + + QCOMPARE(qt_toPremultipliedFormat(unpremul), premul); + QCOMPARE(qt_toUnpremultipliedFormat(premul), unpremul); +} + QTEST_GUILESS_MAIN(tst_QImage) #include "tst_qimage.moc" diff --git a/tests/auto/gui/image/qimageiohandler/CMakeLists.txt b/tests/auto/gui/image/qimageiohandler/CMakeLists.txt index ccf6c617a9..9fbd9c9b9f 100644 --- a/tests/auto/gui/image/qimageiohandler/CMakeLists.txt +++ b/tests/auto/gui/image/qimageiohandler/CMakeLists.txt @@ -5,6 +5,12 @@ ## tst_qimageiohandler Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qimageiohandler LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qimageiohandler SOURCES tst_qimageiohandler.cpp diff --git a/tests/auto/gui/image/qimageiohandler/tst_qimageiohandler.cpp b/tests/auto/gui/image/qimageiohandler/tst_qimageiohandler.cpp index fd0e61b1ba..bd325e185c 100644 --- a/tests/auto/gui/image/qimageiohandler/tst_qimageiohandler.cpp +++ b/tests/auto/gui/image/qimageiohandler/tst_qimageiohandler.cpp @@ -1,5 +1,5 @@ // Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QTest> diff --git a/tests/auto/gui/image/qimagereader/BLACKLIST b/tests/auto/gui/image/qimagereader/BLACKLIST new file mode 100644 index 0000000000..6422ff1dac --- /dev/null +++ b/tests/auto/gui/image/qimagereader/BLACKLIST @@ -0,0 +1,8 @@ +[setClipRect:SVG: rect] +wayland +[setClipRect:SVGZ: rect] +wayland +[setScaledClipRect:SVG: rect] +wayland +[setScaledClipRect:SVGZ: rect] +wayland diff --git a/tests/auto/gui/image/qimagereader/CMakeLists.txt b/tests/auto/gui/image/qimagereader/CMakeLists.txt index 0c19b33797..2a14ca3c9c 100644 --- a/tests/auto/gui/image/qimagereader/CMakeLists.txt +++ b/tests/auto/gui/image/qimagereader/CMakeLists.txt @@ -5,6 +5,12 @@ ## tst_qimagereader Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qimagereader LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + # Collect test data file(GLOB_RECURSE test_data_glob RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} diff --git a/tests/auto/gui/image/qimagereader/images/image.pbm b/tests/auto/gui/image/qimagereader/images/image.pbm index 67e5efa3e9..e529536ca4 100644 --- a/tests/auto/gui/image/qimagereader/images/image.pbm +++ b/tests/auto/gui/image/qimagereader/images/image.pbm @@ -1,8 +1,8 @@ P1 16 6 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 -1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 -1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 -1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 -1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 -1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 +10 00 00 00 01 00 00 01 +1000 0000 0100 0001 +100000000 1000001 +1000000001000001 +10 000 0000 10000 01 diff --git a/tests/auto/gui/image/qimagereader/tst_qimagereader.cpp b/tests/auto/gui/image/qimagereader/tst_qimagereader.cpp index cfd48ec1e4..96af8b4e9b 100644 --- a/tests/auto/gui/image/qimagereader/tst_qimagereader.cpp +++ b/tests/auto/gui/image/qimagereader/tst_qimagereader.cpp @@ -1,6 +1,5 @@ // Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QTest> @@ -11,7 +10,7 @@ #include <QImageReader> #include <QImageWriter> #include <QPixmap> -#include <QSet> +#include <QScopeGuard> #include <QTcpSocket> #include <QTcpServer> #include <QTimer> @@ -63,6 +62,9 @@ private slots: void setScaledSize_data(); void setScaledSize(); + void setScaledSizeOneDimension_data(); + void setScaledSizeOneDimension(); + void setClipRect_data(); void setClipRect(); @@ -301,25 +303,52 @@ void tst_QImageReader::jpegRgbCmyk() QImage image1(prefix + QLatin1String("YCbCr_cmyk.jpg")); QImage image2(prefix + QLatin1String("YCbCr_cmyk.png")); - if (image1 != image2) { - // first, do some obvious tests - QCOMPARE(image1.height(), image2.height()); - QCOMPARE(image1.width(), image2.width()); - QCOMPARE(image1.format(), image2.format()); - QCOMPARE(image1.format(), QImage::Format_RGB32); - - // compare all the pixels with a slack of 3. This ignores rounding errors - // in libjpeg/libpng, where some versions sacrifice accuracy for speed. - for (int h = 0; h < image1.height(); ++h) { - const uchar *s1 = image1.constScanLine(h); - const uchar *s2 = image2.constScanLine(h); - for (int w = 0; w < image1.width() * 4; ++w) { - if (*s1 != *s2) { - QVERIFY2(qAbs(*s1 - *s2) <= 3, qPrintable(QString("images differ in line %1, col %2 (image1: %3, image2: %4)").arg(h).arg(w).arg(*s1, 0, 16).arg(*s2, 0, 16))); - } - s1++; - s2++; - } + QVERIFY(!image1.isNull()); + QVERIFY(!image2.isNull()); + + QCOMPARE(image1.height(), image2.height()); + QCOMPARE(image1.width(), image2.width()); + + QCOMPARE(image1.format(), QImage::Format_CMYK8888); + QCOMPARE(image2.format(), QImage::Format_RGB32); + + // compare all the pixels with a slack of 3. This ignores rounding errors + // in libjpeg/libpng, where some versions sacrifice accuracy for speed. + const auto fuzzyCompareColors = [](const QColor &c1, const QColor &c2) { + int c1rgba[4]; + int c2rgba[4]; + + c1.getRgb(c1rgba + 0, + c1rgba + 1, + c1rgba + 2, + c1rgba + 3); + + c2.getRgb(c2rgba + 0, + c2rgba + 1, + c2rgba + 2, + c2rgba + 3); + + const auto fuzzyCompare = [](int a, int b) { + return qAbs(a - b) <= 3; + }; + + return fuzzyCompare(c1rgba[0], c2rgba[0]) && + fuzzyCompare(c1rgba[1], c2rgba[1]) && + fuzzyCompare(c1rgba[2], c2rgba[2]) && + fuzzyCompare(c1rgba[3], c2rgba[3]); + }; + + for (int h = 0; h < image1.height(); ++h) { + const uchar *sl1 = image1.constScanLine(h); + const uchar *sl2 = image2.constScanLine(h); + for (int w = 0; w < image1.width(); ++w) { + const uchar *s1 = sl1 + w * 4; + const uchar *s2 = sl2 + w * 4; + + QColor c1 = QColor::fromCmyk(s1[0], s1[1], s1[2], s1[3]); + QColor c2 = QColor::fromRgb(s2[2], s2[1], s2[0]); + QVERIFY2(fuzzyCompareColors(c1, c2), + qPrintable(QString("images differ in line %1, col %2").arg(h).arg(w))); } } } @@ -372,6 +401,60 @@ void tst_QImageReader::setScaledSize() QCOMPARE(image.size(), newSize); } +void tst_QImageReader::setScaledSizeOneDimension_data() +{ + QTest::addColumn<QString>("fileName"); + QTest::addColumn<QByteArray>("format"); + + QTest::newRow("PNG: kollada") << QString("kollada") << QByteArray("png"); + QTest::newRow("JPEG: beavis") << QString("beavis") << QByteArray("jpeg"); + QTest::newRow("GIF: earth") << QString("earth") << QByteArray("gif"); + QTest::newRow("SVG: rect") << QString("rect") << QByteArray("svg"); + QTest::newRow("BMP: colorful") << QString("colorful") << QByteArray("bmp"); + QTest::newRow("XPM: marble") << QString("marble") << QByteArray("xpm"); + QTest::newRow("PPM: teapot") << QString("teapot") << QByteArray("ppm"); + QTest::newRow("XBM: gnus") << QString("gnus") << QByteArray("xbm"); +} + +void tst_QImageReader::setScaledSizeOneDimension() +{ + QFETCH(QString, fileName); + QFETCH(QByteArray, format); + + SKIP_IF_UNSUPPORTED(format); + + const QSize originalSize = QImageReader(prefix + fileName).size(); + QVERIFY(!originalSize.isEmpty()); + + auto testScaledSize = [&] (const QSize &scaledSize) { + QSize expectedSize = scaledSize; + if (scaledSize.width() <= 0) + expectedSize.setWidth(qRound(originalSize.width() * + (qreal(scaledSize.height()) / originalSize.height()))); + else if (scaledSize.height() <= 0) + expectedSize.setHeight(qRound(originalSize.height() * + (qreal(scaledSize.width()) / originalSize.width()))); + + QImageReader reader(prefix + fileName); + reader.setScaledSize(scaledSize); + QImage image = reader.read(); + QVERIFY(!image.isNull()); + QCOMPARE(image.size(), expectedSize); + }; + + // downscale + testScaledSize(QSize(originalSize.width() / 2, 0)); + testScaledSize(QSize(originalSize.width() / 2, -1)); + testScaledSize(QSize(0, originalSize.height() / 2)); + testScaledSize(QSize(-1, originalSize.height() / 2)); + + // upscale + testScaledSize(QSize(originalSize.width() * 2, 0)); + testScaledSize(QSize(originalSize.width() * 2, -1)); + testScaledSize(QSize(0, originalSize.height() * 2)); + testScaledSize(QSize(-1, originalSize.height() * 2)); +} + void tst_QImageReader::task255627_setNullScaledSize_data() { setScaledSize_data(); @@ -533,7 +616,7 @@ void tst_QImageReader::imageFormat_data() QTest::newRow("ppm-4") << QString("test.ppm") << QByteArray("ppm") << QImage::Format_RGB32; QTest::newRow("jpeg-1") << QString("beavis.jpg") << QByteArray("jpeg") << QImage::Format_Grayscale8; - QTest::newRow("jpeg-2") << QString("YCbCr_cmyk.jpg") << QByteArray("jpeg") << QImage::Format_RGB32; + QTest::newRow("jpeg-2") << QString("YCbCr_cmyk.jpg") << QByteArray("jpeg") << QImage::Format_CMYK8888; QTest::newRow("jpeg-3") << QString("YCbCr_rgb.jpg") << QByteArray("jpeg") << QImage::Format_RGB32; QTest::newRow("gif-1") << QString("earth.gif") << QByteArray("gif") << QImage::Format_Invalid; @@ -594,41 +677,31 @@ void tst_QImageReader::multiWordNamedColorXPM() QCOMPARE(image.pixel(0, 2), qRgb(255, 250, 205)); // lemon chiffon } -void tst_QImageReader::supportedFormats() +namespace { +template <typename ForwardIterator> +bool is_sorted_unique(ForwardIterator first, ForwardIterator last) { - QList<QByteArray> formats = QImageReader::supportedImageFormats(); - QList<QByteArray> sortedFormats = formats; - std::sort(sortedFormats.begin(), sortedFormats.end()); - - // check that the list is sorted - QCOMPARE(formats, sortedFormats); - - QSet<QByteArray> formatSet; - foreach (QByteArray format, formats) - formatSet << format; + // a range is sorted with no dups iff each *i < *(i+1), so check that none are >=: + return std::adjacent_find(first, last, std::greater_equal<>{}) == last; +} +} - // check that the list does not contain duplicates - QCOMPARE(formatSet.size(), formats.size()); +void tst_QImageReader::supportedFormats() +{ + const QList<QByteArray> formats = QImageReader::supportedImageFormats(); + auto printOnFailure = qScopeGuard([&] { qDebug() << formats; }); + QVERIFY(is_sorted_unique(formats.begin(), formats.end())); + printOnFailure.dismiss(); } void tst_QImageReader::supportedMimeTypes() { - QList<QByteArray> mimeTypes = QImageReader::supportedMimeTypes(); - QList<QByteArray> sortedMimeTypes = mimeTypes; - std::sort(sortedMimeTypes.begin(), sortedMimeTypes.end()); - - // check that the list is sorted - QCOMPARE(mimeTypes, sortedMimeTypes); - - QSet<QByteArray> mimeTypeSet; - foreach (QByteArray mimeType, mimeTypes) - mimeTypeSet << mimeType; - + const QList<QByteArray> mimeTypes = QImageReader::supportedMimeTypes(); + auto printOnFailure = qScopeGuard([&] { qDebug() << mimeTypes; }); + QVERIFY(is_sorted_unique(mimeTypes.begin(), mimeTypes.end())); // check the list as a minimum contains image/bmp - QVERIFY(mimeTypeSet.contains("image/bmp")); - - // check that the list does not contain duplicates - QCOMPARE(mimeTypeSet.size(), mimeTypes.size()); + QVERIFY(mimeTypes.contains("image/bmp")); + printOnFailure.dismiss(); } void tst_QImageReader::setBackgroundColor_data() @@ -673,7 +746,7 @@ void tst_QImageReader::supportsAnimation_data() QTest::newRow("BMP: colorful") << QString("colorful.bmp") << false; QTest::newRow("BMP: font") << QString("font.bmp") << false; QTest::newRow("BMP: signed char") << QString("crash-signed-char.bmp") << false; - QTest::newRow("BMP: test32bfv4") << QString("test32bfv4.bmp") << false;; + QTest::newRow("BMP: test32bfv4") << QString("test32bfv4.bmp") << false; QTest::newRow("BMP: test32v5") << QString("test32v5.bmp") << false; QTest::newRow("XPM: marble") << QString("marble.xpm") << false; QTest::newRow("PNG: kollada") << QString("kollada.png") << false; @@ -1623,43 +1696,56 @@ void tst_QImageReader::supportsOption_data() QTest::addColumn<QIntList>("options"); QTest::newRow("png") << QString("black.png") - << (QIntList() << QImageIOHandler::Gamma - << QImageIOHandler::Description - << QImageIOHandler::Quality - << QImageIOHandler::CompressionRatio - << QImageIOHandler::Size - << QImageIOHandler::ScaledSize); + << QIntList{ + QImageIOHandler::Gamma, + QImageIOHandler::Description, + QImageIOHandler::Quality, + QImageIOHandler::CompressionRatio, + QImageIOHandler::Size, + QImageIOHandler::ScaledSize, + QImageIOHandler::ImageFormat, + }; } void tst_QImageReader::supportsOption() { QFETCH(QString, fileName); - QFETCH(QIntList, options); - - QSet<QImageIOHandler::ImageOption> allOptions; - allOptions << QImageIOHandler::Size - << QImageIOHandler::ClipRect - << QImageIOHandler::Description - << QImageIOHandler::ScaledClipRect - << QImageIOHandler::ScaledSize - << QImageIOHandler::CompressionRatio - << QImageIOHandler::Gamma - << QImageIOHandler::Quality - << QImageIOHandler::Name - << QImageIOHandler::SubType - << QImageIOHandler::IncrementalReading - << QImageIOHandler::Endianness - << QImageIOHandler::Animation - << QImageIOHandler::BackgroundColor; + QFETCH(const QIntList, options); QImageReader reader(prefix + fileName); - for (int i = 0; i < options.size(); ++i) { - QVERIFY(reader.supportsOption(QImageIOHandler::ImageOption(options.at(i)))); - allOptions.remove(QImageIOHandler::ImageOption(options.at(i))); - } - foreach (QImageIOHandler::ImageOption option, allOptions) - QVERIFY(!reader.supportsOption(option)); + for (int i = 0; ; ++i) { + // this switch ensures the compiler warns when we miss an enumerator [-Wswitch] + // do _not_ add a default case! + switch (const auto o = QImageIOHandler::ImageOption(i)) { + case QImageIOHandler::Size: + case QImageIOHandler::ClipRect: + case QImageIOHandler::Description: + case QImageIOHandler::ScaledClipRect: + case QImageIOHandler::ScaledSize: + case QImageIOHandler::CompressionRatio: + case QImageIOHandler::Gamma: + case QImageIOHandler::Quality: + case QImageIOHandler::Name: + case QImageIOHandler::SubType: + case QImageIOHandler::IncrementalReading: + case QImageIOHandler::Endianness: + case QImageIOHandler::Animation: + case QImageIOHandler::BackgroundColor: + case QImageIOHandler::ImageFormat: + case QImageIOHandler::SupportedSubTypes: + case QImageIOHandler::OptimizedWrite: + case QImageIOHandler::ProgressiveScanWrite: + case QImageIOHandler::ImageTransformation: + { + auto printOnFailure = qScopeGuard([&] { qDebug("failed at %d", i); }); + QCOMPARE(reader.supportsOption(o), options.contains(i)); + printOnFailure.dismiss(); + continue; // ... as long as `i` represents a valid ImageOption value + } + } + break; // ... once `i` no longer represents a valid ImageOption value + } } void tst_QImageReader::autoDetectImageFormat() @@ -1828,13 +1914,13 @@ void tst_QImageReader::testIgnoresFormatAndExtension() SKIP_IF_UNSUPPORTED(expected.toLatin1()); - QList<QByteArray> formats = QImageReader::supportedImageFormats(); + const QList<QByteArray> formats = QImageReader::supportedImageFormats(); QString fileNameBase = prefix + name + QLatin1Char('.'); QString tempPath = m_temporaryDir.path(); if (!tempPath.endsWith(QLatin1Char('/'))) tempPath += QLatin1Char('/'); - foreach (const QByteArray &f, formats) { + for (const QByteArray &f : formats) { if (f == extension.toLocal8Bit()) continue; diff --git a/tests/auto/gui/image/qimagewriter/CMakeLists.txt b/tests/auto/gui/image/qimagewriter/CMakeLists.txt index 31f1a1057a..06273ce7e4 100644 --- a/tests/auto/gui/image/qimagewriter/CMakeLists.txt +++ b/tests/auto/gui/image/qimagewriter/CMakeLists.txt @@ -5,6 +5,12 @@ ## tst_qimagewriter Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qimagewriter LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + # Collect test data file(GLOB_RECURSE test_data_glob RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} diff --git a/tests/auto/gui/image/qimagewriter/tst_qimagewriter.cpp b/tests/auto/gui/image/qimagewriter/tst_qimagewriter.cpp index f64c94bb90..1059cc48ab 100644 --- a/tests/auto/gui/image/qimagewriter/tst_qimagewriter.cpp +++ b/tests/auto/gui/image/qimagewriter/tst_qimagewriter.cpp @@ -1,5 +1,5 @@ // Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QTest> #include <QDebug> diff --git a/tests/auto/gui/image/qmovie/CMakeLists.txt b/tests/auto/gui/image/qmovie/CMakeLists.txt index 00fd2f22c5..52e0a347c4 100644 --- a/tests/auto/gui/image/qmovie/CMakeLists.txt +++ b/tests/auto/gui/image/qmovie/CMakeLists.txt @@ -5,10 +5,16 @@ ## tst_qmovie Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qmovie LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + # Collect test data file(GLOB_RECURSE test_data_glob RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} - animations/*) + animations/* multiframe/*) list(APPEND test_data ${test_data_glob}) qt_internal_add_test(tst_qmovie @@ -16,6 +22,7 @@ qt_internal_add_test(tst_qmovie tst_qmovie.cpp LIBRARIES Qt::Gui + Qt::TestPrivate TESTDATA ${test_data} ) @@ -24,6 +31,7 @@ set(resources_resource_files "animations/comicsecard.gif" "animations/corrupt.gif" "animations/trolltech.gif" + "multiframe/Obj_N2_Internal_Mem.ico" ) qt_internal_add_resource(tst_qmovie "resources" diff --git a/tests/auto/gui/image/qmovie/multiframe/Obj_N2_Internal_Mem.ico b/tests/auto/gui/image/qmovie/multiframe/Obj_N2_Internal_Mem.ico Binary files differnew file mode 100644 index 0000000000..8da119efdd --- /dev/null +++ b/tests/auto/gui/image/qmovie/multiframe/Obj_N2_Internal_Mem.ico diff --git a/tests/auto/gui/image/qmovie/tst_qmovie.cpp b/tests/auto/gui/image/qmovie/tst_qmovie.cpp index 1cf13f6f9c..29c3297043 100644 --- a/tests/auto/gui/image/qmovie/tst_qmovie.cpp +++ b/tests/auto/gui/image/qmovie/tst_qmovie.cpp @@ -1,10 +1,11 @@ // Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QTest> #include <QTestEventLoop> #include <QSignalSpy> +#include <QtTest/private/qpropertytesthelper_p.h> #include <QIODevice> #ifndef QT_NO_WIDGETS @@ -35,12 +36,20 @@ private slots: void playMovie(); void jumpToFrame_data(); void jumpToFrame(); + void frameDelay(); void changeMovieFile(); #ifndef QT_NO_WIDGETS void infiniteLoop(); #endif void emptyMovie(); void bindings(); + void automatedBindings(); +#ifndef QT_NO_ICO + void multiFrameImage(); +#endif + + void setScaledSize_data(); + void setScaledSize(); }; // Testing get/set functions @@ -176,6 +185,17 @@ void tst_QMovie::jumpToFrame() QCOMPARE(movie.currentFrameNumber(), 0); } +void tst_QMovie::frameDelay() +{ + QMovie movie(QFINDTESTDATA("animations/comicsecard.gif")); + QList<int> frameDelays{ 200, 800, 800, 2000, 2600 }; + for (int i = 0; i < movie.frameCount(); i++) { + movie.jumpToFrame(i); + // Processing may have taken a little time, so round to nearest 100ms + QCOMPARE(100 * qRound(movie.nextFrameDelay() / 100.0f), frameDelays[i]); + } +} + void tst_QMovie::changeMovieFile() { QMovie movie(QFINDTESTDATA("animations/comicsecard.gif")); @@ -239,5 +259,78 @@ void tst_QMovie::bindings() QCOMPARE(cacheModeObserver, QMovie::CacheAll); } +void tst_QMovie::automatedBindings() +{ + QMovie movie; + + QTestPrivate::testReadWritePropertyBasics(movie, 50, 100, "speed"); + if (QTest::currentTestFailed()) { + qDebug("Failed property test for QMovie::speed"); + return; + } + + QTestPrivate::testReadWritePropertyBasics(movie, QMovie::CacheAll, QMovie::CacheNone, + "cacheMode"); + if (QTest::currentTestFailed()) { + qDebug("Failed property test for QMovie::cacheMode"); + return; + } +} + +#ifndef QT_NO_ICO +/*! \internal + Test behavior of QMovie with image formats that are multi-frame, + but not normally intended as animation formats (such as tiff and ico). +*/ +void tst_QMovie::multiFrameImage() +{ + QMovie movie(QFINDTESTDATA("multiframe/Obj_N2_Internal_Mem.ico")); + const int expectedFrameCount = 9; + + QCOMPARE(movie.frameCount(), expectedFrameCount); + QVERIFY(movie.isValid()); + movie.setSpeed(1000); // speed up the test: play at 10 FPS (1000% of normal) + QElapsedTimer playTimer; + QSignalSpy frameChangedSpy(&movie, &QMovie::frameChanged); + QSignalSpy errorSpy(&movie, &QMovie::error); + QSignalSpy finishedSpy(&movie, &QMovie::finished); + playTimer.start(); + movie.start(); + QTRY_COMPARE(finishedSpy.size(), 1); + QCOMPARE_GE(playTimer.elapsed(), 100 * expectedFrameCount); + QCOMPARE(movie.nextFrameDelay(), 100); + QCOMPARE(errorSpy.size(), 0); + QCOMPARE(frameChangedSpy.size(), expectedFrameCount); +} +#endif + +void tst_QMovie::setScaledSize_data() +{ + QTest::addColumn<QString>("fileName"); + QTest::addColumn<QSize>("scaledSize"); + QTest::addColumn<QSize>("expectedSize"); + + QTest::newRow("trolltech (50, 50)") << QString("animations/trolltech.gif") << QSize(50, 50) << QSize(50, 50); + QTest::newRow("trolltech (400, 400)") << QString("animations/trolltech.gif") << QSize(400, 400) << QSize(400, 400); + QTest::newRow("trolltech (50, 0)") << QString("animations/trolltech.gif") << QSize(50, 0) << QSize(50, 25); + QTest::newRow("trolltech (50, -1)") << QString("animations/trolltech.gif") << QSize(50, -1) << QSize(50, 25); + QTest::newRow("trolltech (0, 50)") << QString("animations/trolltech.gif") << QSize(0, 50) << QSize(100, 50); + QTest::newRow("trolltech (-1, 50)") << QString("animations/trolltech.gif") << QSize(-1, 50) << QSize(100, 50); +} + +void tst_QMovie::setScaledSize() +{ + QFETCH(QString, fileName); + QFETCH(QSize, scaledSize); + QFETCH(QSize, expectedSize); + + QMovie movie(QFINDTESTDATA(fileName)); + movie.setScaledSize(scaledSize); + + movie.start(); + QCOMPARE(movie.currentFrameNumber(), 0); + QCOMPARE(movie.currentImage().size(), expectedSize); +} + QTEST_MAIN(tst_QMovie) #include "tst_qmovie.moc" diff --git a/tests/auto/gui/image/qpicture/CMakeLists.txt b/tests/auto/gui/image/qpicture/CMakeLists.txt index af77725652..30b0aafd11 100644 --- a/tests/auto/gui/image/qpicture/CMakeLists.txt +++ b/tests/auto/gui/image/qpicture/CMakeLists.txt @@ -5,6 +5,12 @@ ## tst_qpicture Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qpicture LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qpicture SOURCES tst_qpicture.cpp diff --git a/tests/auto/gui/image/qpicture/tst_qpicture.cpp b/tests/auto/gui/image/qpicture/tst_qpicture.cpp index de2b841927..2fa4436154 100644 --- a/tests/auto/gui/image/qpicture/tst_qpicture.cpp +++ b/tests/auto/gui/image/qpicture/tst_qpicture.cpp @@ -1,5 +1,5 @@ // Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QTest> diff --git a/tests/auto/gui/image/qpixmap/CMakeLists.txt b/tests/auto/gui/image/qpixmap/CMakeLists.txt index 61267d396f..8531ef5b7b 100644 --- a/tests/auto/gui/image/qpixmap/CMakeLists.txt +++ b/tests/auto/gui/image/qpixmap/CMakeLists.txt @@ -5,6 +5,12 @@ ## tst_qpixmap Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qpixmap LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + # Collect test data file(GLOB_RECURSE test_data_glob RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} diff --git a/tests/auto/gui/image/qpixmap/tst_qpixmap.cpp b/tests/auto/gui/image/qpixmap/tst_qpixmap.cpp index 4e9be4e033..d8c553b521 100644 --- a/tests/auto/gui/image/qpixmap/tst_qpixmap.cpp +++ b/tests/auto/gui/image/qpixmap/tst_qpixmap.cpp @@ -1,5 +1,5 @@ // Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QSet> #include <QTemporaryFile> @@ -1176,8 +1176,10 @@ void tst_QPixmap::dprPassthrough() pm.convertFromImage(img); QCOMPARE(pm.devicePixelRatio(), dpr); +#ifndef QT_NO_IMAGE_HEURISTIC_MASK QBitmap heuristicMask = src.createHeuristicMask(); QCOMPARE(heuristicMask.devicePixelRatio(), dpr); +#endif QBitmap maskFromColor = src.createMaskFromColor(Qt::white); QCOMPARE(maskFromColor.devicePixelRatio(), dpr); @@ -1423,7 +1425,7 @@ void tst_QPixmap::loadFromDataImage() QPixmap pixmapWithCopy = QPixmap::fromImage(imageRef); QFile file(imagePath); - file.open(QIODevice::ReadOnly); + QVERIFY(file.open(QIODevice::ReadOnly)); QByteArray rawData = file.readAll(); QPixmap directLoadingPixmap; diff --git a/tests/auto/gui/image/qpixmapcache/CMakeLists.txt b/tests/auto/gui/image/qpixmapcache/CMakeLists.txt index 8855d07823..444de9cb3e 100644 --- a/tests/auto/gui/image/qpixmapcache/CMakeLists.txt +++ b/tests/auto/gui/image/qpixmapcache/CMakeLists.txt @@ -5,6 +5,12 @@ ## tst_qpixmapcache Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qpixmapcache LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qpixmapcache SOURCES tst_qpixmapcache.cpp diff --git a/tests/auto/gui/image/qpixmapcache/tst_qpixmapcache.cpp b/tests/auto/gui/image/qpixmapcache/tst_qpixmapcache.cpp index d4c39a2130..8384a46491 100644 --- a/tests/auto/gui/image/qpixmapcache/tst_qpixmapcache.cpp +++ b/tests/auto/gui/image/qpixmapcache/tst_qpixmapcache.cpp @@ -1,5 +1,5 @@ // Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QTest> @@ -7,12 +7,16 @@ #include <qpixmapcache.h> #include "private/qpixmapcache_p.h" +#include <functional> + QT_BEGIN_NAMESPACE // The test requires QT_BUILD_INTERNAL Q_AUTOTEST_EXPORT void qt_qpixmapcache_flush_detached_pixmaps(); Q_AUTOTEST_EXPORT int qt_qpixmapcache_qpixmapcache_total_used(); Q_AUTOTEST_EXPORT int q_QPixmapCache_keyHashSize(); QT_END_NAMESPACE +using namespace Qt::StringLiterals; + class tst_QPixmapCache : public QObject { Q_OBJECT @@ -29,13 +33,22 @@ private slots: void setCacheLimit(); void find(); void insert(); + void failedInsertReturnsInvalidKey(); +#if QT_DEPRECATED_SINCE(6, 6) void replace(); +#endif void remove(); void clear(); void pixmapKey(); void noLeak(); + void clearDoesNotLeakStringKeys(); + void evictionDoesNotLeakStringKeys(); + void reducingCacheLimitDoesNotLeakStringKeys(); void strictCacheLimit(); void noCrashOnLargeInsert(); + +private: + void stringLeak_impl(std::function<void()> whenOp); }; static QPixmapCache::KeyData* getPrivate(QPixmapCache::Key &key) @@ -102,28 +115,32 @@ void tst_QPixmapCache::setCacheLimit() //The int part of the API p1 = new QPixmap(2, 3); QPixmapCache::Key key = QPixmapCache::insert(*p1); - QVERIFY(QPixmapCache::find(key, p1) != 0); + QVERIFY(QPixmapCache::find(key, p1)); delete p1; QPixmapCache::setCacheLimit(0); - QVERIFY(QPixmapCache::find(key, p1) == 0); + QVERIFY(!QPixmapCache::find(key, p1)); - p1 = new QPixmap(2, 3); QPixmapCache::setCacheLimit(1000); - QPixmapCache::replace(key, *p1); - QVERIFY(QPixmapCache::find(key, p1) == 0); +#if QT_DEPRECATED_SINCE(6, 6) + QT_WARNING_PUSH + QT_WARNING_DISABLE_DEPRECATED + p1 = new QPixmap(2, 3); + QVERIFY(!QPixmapCache::replace(key, *p1)); + QVERIFY(!QPixmapCache::find(key, p1)); delete p1; +#endif // QT_DEPRECATED_SINCE(6, 6) //Let check if keys are released when the pixmap cache is //full or has been flushed. QPixmapCache::clear(); p1 = new QPixmap(2, 3); key = QPixmapCache::insert(*p1); - QVERIFY(QPixmapCache::find(key, p1) != 0); + QVERIFY(QPixmapCache::find(key, p1)); p1->detach(); // dectach so that the cache thinks no-one is using it. QPixmapCache::setCacheLimit(0); - QVERIFY(QPixmapCache::find(key, p1) == 0); + QVERIFY(!QPixmapCache::find(key, p1)); QPixmapCache::setCacheLimit(1000); key = QPixmapCache::insert(*p1); QVERIFY(key.isValid()); @@ -137,7 +154,7 @@ void tst_QPixmapCache::setCacheLimit() QPixmap p2; p1 = new QPixmap(2, 3); key = QPixmapCache::insert(*p1); - QVERIFY(QPixmapCache::find(key, &p2) != 0); + QVERIFY(QPixmapCache::find(key, &p2)); //we flush the cache p1->detach(); p2.detach(); @@ -145,8 +162,8 @@ void tst_QPixmapCache::setCacheLimit() QPixmapCache::setCacheLimit(1000); QPixmapCache::Key key2 = QPixmapCache::insert(*p1); QCOMPARE(getPrivate(key2)->key, 1); - QVERIFY(QPixmapCache::find(key, &p2) == 0); - QVERIFY(QPixmapCache::find(key2, &p2) != 0); + QVERIFY(!QPixmapCache::find(key, &p2)); + QVERIFY(QPixmapCache::find(key2, &p2)); QCOMPARE(p2, *p1); delete p1; @@ -169,7 +186,7 @@ void tst_QPixmapCache::setCacheLimit() QCOMPARE(getPrivate(key2)->key, 1); //This old key is not valid anymore after the flush QVERIFY(!key.isValid()); - QVERIFY(QPixmapCache::find(key, &p2) == 0); + QVERIFY(!QPixmapCache::find(key, &p2)); delete p1; } @@ -207,7 +224,7 @@ void tst_QPixmapCache::find() QPixmapCache::insert(p5); //at that time the first key has been erase because no more place in the cache - QVERIFY(QPixmapCache::find(key, &p1) == 0); + QVERIFY(!QPixmapCache::find(key, &p1)); QVERIFY(!key.isValid()); } @@ -266,6 +283,7 @@ void tst_QPixmapCache::insert() for (int i = 0; i < numberOfKeys; ++i) { QPixmap p3(10,10); keys.append(QPixmapCache::insert(p3)); + QVERIFY(keys.back().isValid()); } num = 0; @@ -279,6 +297,35 @@ void tst_QPixmapCache::insert() QVERIFY(num <= estimatedNum); } +void tst_QPixmapCache::failedInsertReturnsInvalidKey() +{ + // + // GIVEN: a pixmap whose memory footprint exceeds the cache's limit: + // + QPixmapCache::setCacheLimit(20); + + QPixmap pm(256, 256); + pm.fill(Qt::transparent); + QCOMPARE_GT(pm.width() * pm.height() * pm.depth() / 8, + QPixmapCache::cacheLimit() * 1024); + + // + // WHEN: trying to add this pixmap to the cache + // + const auto success = QPixmapCache::insert(u"foo"_s, pm); // QString API + { QPixmap r; QVERIFY(!QPixmapCache::find(u"foo"_s, &r)); } + const auto key = QPixmapCache::insert(pm); // "int" API + + // + // THEN: failure is reported to the user + // + QVERIFY(!key.isValid()); // "int" API + QVERIFY(!success); // QString API +} + +#if QT_DEPRECATED_SINCE(6, 6) +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED void tst_QPixmapCache::replace() { //The int part of the API @@ -307,6 +354,8 @@ void tst_QPixmapCache::replace() //Broken keys QCOMPARE(QPixmapCache::replace(QPixmapCache::Key(), p2), false); } +QT_WARNING_POP +#endif // QT_DEPRECATED_SINCE(6, 6) void tst_QPixmapCache::remove() { @@ -340,11 +389,11 @@ void tst_QPixmapCache::remove() QVERIFY(p1.toImage() == p1.toImage()); // sanity check QPixmapCache::remove(key); - QVERIFY(QPixmapCache::find(key, &p1) == 0); + QVERIFY(!QPixmapCache::find(key, &p1)); //Broken key QPixmapCache::remove(QPixmapCache::Key()); - QVERIFY(QPixmapCache::find(QPixmapCache::Key(), &p1) == 0); + QVERIFY(!QPixmapCache::find(QPixmapCache::Key(), &p1)); //Test if keys are release QPixmapCache::clear(); @@ -358,7 +407,7 @@ void tst_QPixmapCache::remove() QPixmapCache::clear(); key = QPixmapCache::insert(p1); QCOMPARE(getPrivate(key)->key, 1); - QVERIFY(QPixmapCache::find(key, &p1) != 0); + QVERIFY(QPixmapCache::find(key, &p1)); QPixmapCache::remove(key); QCOMPARE(p1.isDetached(), true); @@ -368,8 +417,8 @@ void tst_QPixmapCache::remove() QPixmapCache::insert("red", p1); key = QPixmapCache::insert(p1); QPixmapCache::remove(key); - QVERIFY(QPixmapCache::find(key, &p1) == 0); - QVERIFY(QPixmapCache::find("red", &p1) != 0); + QVERIFY(!QPixmapCache::find(key, &p1)); + QVERIFY(QPixmapCache::find("red", &p1)); } void tst_QPixmapCache::clear() @@ -413,7 +462,7 @@ void tst_QPixmapCache::clear() QPixmapCache::clear(); for (int k = 0; k < numberOfKeys; ++k) { - QVERIFY(QPixmapCache::find(keys.at(k), &p1) == 0); + QVERIFY(!QPixmapCache::find(keys.at(k), &p1)); QVERIFY(!keys[k].isValid()); } } @@ -478,6 +527,68 @@ void tst_QPixmapCache::noLeak() QCOMPARE(oldSize, newSize); } +void tst_QPixmapCache::clearDoesNotLeakStringKeys() +{ + stringLeak_impl([] { QPixmapCache::clear(); }); +} + +void tst_QPixmapCache::evictionDoesNotLeakStringKeys() +{ + stringLeak_impl([] { + // fill the cache with other pixmaps to force eviction of "our" pixmap: + constexpr int Iterations = 10; + for (int i = 0; i < Iterations; ++i) { + QPixmap pm(64, 64); + pm.fill(Qt::transparent); + [[maybe_unused]] auto r = QPixmapCache::insert(pm); + } + }); +} + +void tst_QPixmapCache::reducingCacheLimitDoesNotLeakStringKeys() +{ + stringLeak_impl([] { + QPixmapCache::setCacheLimit(0); + }); +} + +void tst_QPixmapCache::stringLeak_impl(std::function<void()> whenOp) +{ + QVERIFY(whenOp); + + QPixmapCache::setCacheLimit(20); // 20KiB + // + // GIVEN: a QPixmap with QString key `key` in QPixmapCache + // + QString key; + { + QPixmap pm(64, 64); + QCOMPARE_LT(pm.width() * pm.height() * std::ceil(pm.depth() / 8.0), + QPixmapCache::cacheLimit() * 1024); + pm.fill(Qt::transparent); + key = u"theKey"_s.repeated(20); // avoid eventual QString SSO + QVERIFY(key.isDetached()); + QPixmapCache::insert(key, pm); + } + QVERIFY(!key.isDetached()); // was saved inside QPixmapCache + + // + // WHEN: performing the given operation + // + whenOp(); + if (QTest::currentTestFailed()) + return; + + // + // THEN: `key` is no longer referenced by QPixmapCache: + // + QVERIFY(key.isDetached()); + // verify that the pixmap is really gone from the cache + // (do it after the key check, because QPixmapCache cleans up `key` on a failed lookup) + QPixmap r; + QVERIFY(!QPixmapCache::find(key, &r)); +} + void tst_QPixmapCache::strictCacheLimit() { |