From 5de95d3aba9819b34e4d480472d72ac950bb8c3d Mon Sep 17 00:00:00 2001 From: Eirik Aavitsland Date: Fri, 4 Oct 2024 12:43:17 +0200 Subject: [PATCH] Add autotest for QImageIO format handler plugins To confirm the functionality of custom imageformats plugins in Qt image reading, both for supporting new formats and for overriding Qt's builtin formats. Change-Id: I6abafdb47301ad1f35b19951e42b1f3b61fe6ec3 Reviewed-by: Allan Sandfeld Jensen (cherry picked from commit 6eefe0ca59dc25f7953b93e8b80c62c78a6ccfde) Reviewed-by: Qt Cherry-pick Bot --- .../gui/image/qimageiohandler/CMakeLists.txt | 13 ++ .../image/qimageiohandler/images/black.bar | Bin 0 -> 3126 bytes .../image/qimageiohandler/images/black.bmp | Bin 0 -> 3126 bytes .../gui/image/qimageiohandler/images/blue | 1 + .../gui/image/qimageiohandler/images/cyan.gif | Bin 0 -> 71 bytes .../image/qimageiohandler/images/green.foo | 1 + .../gui/image/qimageiohandler/images/red.png | 1 + .../image/qimageiohandler/images/white.foo | Bin 0 -> 643 bytes .../image/qimageiohandler/images/yellow.jpg | 1 + .../qimageiohandler/plugin/CMakeLists.txt | 17 ++ .../gui/image/qimageiohandler/plugin/main.cpp | 89 ++++++++ .../image/qimageiohandler/plugin/plugin.json | 4 + .../gui/image/qimageiohandler/pluginlog.h | 22 ++ .../qimageiohandler/tst_qimageiohandler.cpp | 203 ++++++++++++++++++ 14 files changed, 352 insertions(+) create mode 100644 tests/auto/gui/image/qimageiohandler/images/black.bar create mode 100644 tests/auto/gui/image/qimageiohandler/images/black.bmp create mode 100644 tests/auto/gui/image/qimageiohandler/images/blue create mode 100644 tests/auto/gui/image/qimageiohandler/images/cyan.gif create mode 100644 tests/auto/gui/image/qimageiohandler/images/green.foo create mode 100644 tests/auto/gui/image/qimageiohandler/images/red.png create mode 100644 tests/auto/gui/image/qimageiohandler/images/white.foo create mode 100644 tests/auto/gui/image/qimageiohandler/images/yellow.jpg create mode 100644 tests/auto/gui/image/qimageiohandler/plugin/CMakeLists.txt create mode 100644 tests/auto/gui/image/qimageiohandler/plugin/main.cpp create mode 100644 tests/auto/gui/image/qimageiohandler/plugin/plugin.json create mode 100644 tests/auto/gui/image/qimageiohandler/pluginlog.h diff --git a/tests/auto/gui/image/qimageiohandler/CMakeLists.txt b/tests/auto/gui/image/qimageiohandler/CMakeLists.txt index 9fbd9c9b9f4..6f9f1ce7ea6 100644 --- a/tests/auto/gui/image/qimageiohandler/CMakeLists.txt +++ b/tests/auto/gui/image/qimageiohandler/CMakeLists.txt @@ -11,9 +11,22 @@ if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) endif() +file(GLOB_RECURSE test_data_glob + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + images/*) + +list(APPEND test_data ${test_data_glob}) + +add_subdirectory(plugin) + qt_internal_add_test(tst_qimageiohandler SOURCES tst_qimageiohandler.cpp + pluginlog.h LIBRARIES Qt::Gui + TestImagePlugin + TESTDATA ${test_data} ) + +add_dependencies(tst_qimageiohandler TestImagePlugin) diff --git a/tests/auto/gui/image/qimageiohandler/images/black.bar b/tests/auto/gui/image/qimageiohandler/images/black.bar new file mode 100644 index 0000000000000000000000000000000000000000..41e34fd7798412b499bfc0f7eb74eeeab069310e GIT binary patch literal 3126 zcmZ?rHRE9b12Z700mKSW%*Y@CWB~zC{seRM(+_PC3tO3Ue8B72G literal 0 HcmV?d00001 diff --git a/tests/auto/gui/image/qimageiohandler/images/green.foo b/tests/auto/gui/image/qimageiohandler/images/green.foo new file mode 100644 index 00000000000..a5b73ed2e94 --- /dev/null +++ b/tests/auto/gui/image/qimageiohandler/images/green.foo @@ -0,0 +1 @@ +green diff --git a/tests/auto/gui/image/qimageiohandler/images/red.png b/tests/auto/gui/image/qimageiohandler/images/red.png new file mode 100644 index 00000000000..f24fc29aa68 --- /dev/null +++ b/tests/auto/gui/image/qimageiohandler/images/red.png @@ -0,0 +1 @@ +RED diff --git a/tests/auto/gui/image/qimageiohandler/images/white.foo b/tests/auto/gui/image/qimageiohandler/images/white.foo new file mode 100644 index 0000000000000000000000000000000000000000..8085a0d4aa7a80c9bdd6a94a2755db3c7483b355 GIT binary patch literal 643 zcmex=^(PF6}rMnOeST|r4lSw=>~TvNxu(8R<ECr+Na zbot8FYu9hwy!G(W<0ns_J%91?)yGetzkL1n{m0K=Ab&A3Fhjfr_ZgbM1cClyVqsxs zVF&q(k*OSrnFU!`6%E;h90S=C3x$=88aYIqCNA7~kW<+>=!0ld(M2vX6_bamA3L7Bz4oVf+6#0sPd{ga7~l literal 0 HcmV?d00001 diff --git a/tests/auto/gui/image/qimageiohandler/images/yellow.jpg b/tests/auto/gui/image/qimageiohandler/images/yellow.jpg new file mode 100644 index 00000000000..d1ed081dfb9 --- /dev/null +++ b/tests/auto/gui/image/qimageiohandler/images/yellow.jpg @@ -0,0 +1 @@ +yellow diff --git a/tests/auto/gui/image/qimageiohandler/plugin/CMakeLists.txt b/tests/auto/gui/image/qimageiohandler/plugin/CMakeLists.txt new file mode 100644 index 00000000000..e0aaddf2406 --- /dev/null +++ b/tests/auto/gui/image/qimageiohandler/plugin/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## TestImagePlugin: +##################################################################### + +qt_add_plugin(TestImagePlugin + STATIC + OUTPUT_NAME qtestimageplugin + PLUGIN_TYPE imageformats + main.cpp + ../pluginlog.h +) + +qt_autogen_tools_initial_setup(TestImagePlugin) +target_link_libraries(TestImagePlugin PRIVATE Qt::Core Qt::Gui) diff --git a/tests/auto/gui/image/qimageiohandler/plugin/main.cpp b/tests/auto/gui/image/qimageiohandler/plugin/main.cpp new file mode 100644 index 00000000000..5a1289c2abd --- /dev/null +++ b/tests/auto/gui/image/qimageiohandler/plugin/main.cpp @@ -0,0 +1,89 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include +#include +#include "../pluginlog.h" + +class TestImagePlugin : public QImageIOPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QImageIOHandlerFactoryInterface" FILE "plugin.json") + +public: + QImageIOPlugin::Capabilities capabilities(QIODevice *device, const QByteArray &format) const override; + QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const override; +}; + +class MyPngHandler : public QImageIOHandler +{ +public: + bool canRead() const override + { + if (canRead(device())) { + setFormat(device()->peek(1).isUpper() ? "png" : "foo"); + return true; + } + return false; + } + + bool read(QImage *image) override + { + QColor col = QColor::fromString(device()->readAll().simplified()); + if (col.isValid()) { + QImage img(32, 32, QImage::Format_RGB32); + img.fill(col); + *image = img; + return true; + } else { + return false; + } + } + + static bool canRead(QIODevice *device) + { + return device ? QColor::isValidColorName(device->peek(16).simplified()) : false; + } +}; + + +QImageIOPlugin::Capabilities TestImagePlugin::capabilities(QIODevice *device, const QByteArray &format) const +{ + if (format == "png" || format == "foo" || format == "gif") { + PluginLog::append("formatname-matched"); + return (format == "gif") ? CanWrite : CanRead; + } + + Capabilities cap; + if (!format.isEmpty()) { + PluginLog::append("formatname-unmatched"); + return cap; + } + + if (!device->isOpen()) + return cap; + + if (device->isReadable()) { + if (MyPngHandler::canRead(device)) { + PluginLog::append("contents-matched"); + cap |= CanRead; + } else { + PluginLog::append("contents-unmatched"); + } + } + + if (device->isWritable()) + cap |= CanWrite; + + return cap; +} + +QImageIOHandler *TestImagePlugin::create(QIODevice *device, const QByteArray &format) const +{ + QImageIOHandler *handler = new MyPngHandler; + handler->setDevice(device); + handler->setFormat(format); + return handler; +} + +#include "main.moc" diff --git a/tests/auto/gui/image/qimageiohandler/plugin/plugin.json b/tests/auto/gui/image/qimageiohandler/plugin/plugin.json new file mode 100644 index 00000000000..be5282af2fa --- /dev/null +++ b/tests/auto/gui/image/qimageiohandler/plugin/plugin.json @@ -0,0 +1,4 @@ +{ + "Keys": [ "png", "foo", "gif" ], + "MimeTypes": [ "image/png", "image/foo", "image/gif" ] +} diff --git a/tests/auto/gui/image/qimageiohandler/pluginlog.h b/tests/auto/gui/image/qimageiohandler/pluginlog.h new file mode 100644 index 00000000000..140a54c4029 --- /dev/null +++ b/tests/auto/gui/image/qimageiohandler/pluginlog.h @@ -0,0 +1,22 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef PLUGINLOG_H +#define PLUGINLOG_H + +#include +#include + +class PluginLog { +public: + static QStringList get() { return qApp->property(testProp).toStringList(); } + static void set(const QStringList &log) { qApp->setProperty(testProp, log); } + static void clear() { set({}); } + static void append(const QString &msg) { set(get() << msg); } + static int size() { return get().size(); } + static QString item(int index) { return get().value(index); } +private: + static constexpr auto testProp = "_q_testimageplugin"; +}; + +#endif // PLUGINLOG_H diff --git a/tests/auto/gui/image/qimageiohandler/tst_qimageiohandler.cpp b/tests/auto/gui/image/qimageiohandler/tst_qimageiohandler.cpp index bd325e185c6..aefc427cac5 100644 --- a/tests/auto/gui/image/qimageiohandler/tst_qimageiohandler.cpp +++ b/tests/auto/gui/image/qimageiohandler/tst_qimageiohandler.cpp @@ -6,8 +6,11 @@ #include #include +#include #include #include +#include +#include "pluginlog.h" class tst_QImageIOHandler : public QObject { @@ -16,9 +19,27 @@ Q_OBJECT public: tst_QImageIOHandler(); virtual ~tst_QImageIOHandler(); + static void initMain() + { + QHashSeed::setDeterministicGlobalSeed(); + } private slots: void getSetCheck(); + void hasCustomPluginFormat(); + void pluginRead_data(); + void pluginRead(); + void pluginNoAutoDetection_data(); + void pluginNoAutoDetection(); + void pluginDecideFromContent_data(); + void pluginDecideFromContent(); + void pluginDetectedFormat_data(); + void pluginDetectedFormat(); + void pluginWrongFormat_data(); + void pluginWrongFormat(); +private: + QString m_prefix; + QList m_supportedReadFormats; }; class MyImageIOHandler : public QImageIOHandler @@ -31,6 +52,8 @@ public: tst_QImageIOHandler::tst_QImageIOHandler() { + m_prefix = QFINDTESTDATA("images/"); + m_supportedReadFormats = QImageReader::supportedImageFormats(); } tst_QImageIOHandler::~tst_QImageIOHandler() @@ -51,5 +74,185 @@ void tst_QImageIOHandler::getSetCheck() delete var1; } +Q_IMPORT_PLUGIN(TestImagePlugin) + +void tst_QImageIOHandler::hasCustomPluginFormat() +{ + QVERIFY(m_supportedReadFormats.contains("foo")); +} + +void tst_QImageIOHandler::pluginRead_data() +{ + QTest::addColumn("color"); + QTest::addColumn("suffix"); + QTest::addColumn("expectedPluginLog"); + + QTest::newRow("testplugin") << "green" << "foo" << QStringList({ "formatname-matched" }); + QTest::newRow("overridden-builtin") << "red" << "png" << QStringList({ "formatname-matched" }); + QTest::newRow("builtin") << "black" << "bmp" << QStringList({ "formatname-unmatched" }); //### Should be null + QTest::newRow("no-suffix") << "blue" << "" << QStringList({ "contents-matched" }); + QTest::newRow("wrong-suffix") << "yellow" << "jpg" << QStringList({ "contents-matched" }); + QTest::newRow("unknown-suffix") << "black" << "bar" << QStringList({ "formatname-unmatched", "contents-unmatched" }); + if (m_supportedReadFormats.contains("jpeg")) + QTest::newRow("wrong-suffix2") << "white" << "foo" << QStringList({ "formatname-matched" }); + if (m_supportedReadFormats.contains("gif")) + QTest::newRow("plugin-writeonly") << "cyan" << "gif" << QStringList(); +} + +void tst_QImageIOHandler::pluginRead() +{ + QFETCH(QString, color); + QFETCH(QString, suffix); + QFETCH(QStringList, expectedPluginLog); + + PluginLog::clear(); + QString filePath = m_prefix + color + (suffix.isEmpty() ? "" : "." + suffix); + QImage img(filePath); + QCOMPARE(PluginLog::size(), expectedPluginLog.size()); + for (int i = 0; i < PluginLog::size(); i++) + QCOMPARE(PluginLog::item(i), expectedPluginLog.value(i)); + QVERIFY(!img.isNull()); + QCOMPARE(img.pixelColor(0, 0), QColor(color)); +} + +void tst_QImageIOHandler::pluginNoAutoDetection_data() +{ + QTest::addColumn("color"); + QTest::addColumn("suffix"); + QTest::addColumn("expectedPluginLog"); + QTest::addColumn("expectedSuccess"); + + QTest::newRow("testplugin") << "green" << "foo" << QStringList({ "formatname-matched" }) << true; + QTest::newRow("overridden-builtin") << "red" << "png" << QStringList({ "formatname-matched" }) << true; + QTest::newRow("builtin") << "black" << "bmp" << QStringList() << true; + QTest::newRow("no-suffix") << "blue" << "" << QStringList() << false; + QTest::newRow("wrong-suffix") << "yellow" << "jpg" << QStringList() << false; + QTest::newRow("unknown-suffix") << "black" << "bar" << QStringList() << false; + QTest::newRow("wrong-suffix2") << "white" << "foo" << QStringList({ "formatname-matched" }) << false; + QTest::newRow("plugin-writeonly") << "cyan" << "gif" << QStringList() << true; +} + +void tst_QImageIOHandler::pluginNoAutoDetection() +{ + QFETCH(QString, color); + QFETCH(QString, suffix); + QFETCH(QStringList, expectedPluginLog); + QFETCH(bool, expectedSuccess); + + QString filePath = m_prefix + color + (suffix.isEmpty() ? "" : "." + suffix); + { + // Confirm that the file suffix is ignored, i.e. nothing happens if no format is set + PluginLog::clear(); + QImageReader r(filePath); + r.setAutoDetectImageFormat(false); + QImage img = r.read(); + QVERIFY(img.isNull()); + QCOMPARE(PluginLog::size(), 0); + } + + PluginLog::clear(); + QImageReader r(filePath, suffix.toLatin1()); + r.setAutoDetectImageFormat(false); + QImage img = r.read(); + + QCOMPARE(PluginLog::size(), expectedPluginLog.size()); + for (int i = 0; i < PluginLog::size(); i++) + QCOMPARE(PluginLog::item(i), expectedPluginLog.value(i)); + QCOMPARE(!img.isNull(), expectedSuccess); + if (expectedSuccess) + QCOMPARE(img.pixelColor(0, 0), QColor(color)); +} + +void tst_QImageIOHandler::pluginDecideFromContent_data() +{ + QTest::addColumn("color"); + QTest::addColumn("suffix"); + QTest::addColumn("expectedPluginLog"); + + QTest::newRow("testplugin") << "green" << "foo" << QStringList({ "contents-matched" }); + QTest::newRow("overridden-builtin") << "red" << "png" << QStringList({ "contents-matched" }); + QTest::newRow("builtin") << "black" << "bmp" << QStringList({ "contents-unmatched" }); + QTest::newRow("no-suffix") << "blue" << "" << QStringList({ "contents-matched" }); + QTest::newRow("wrong-suffix") << "yellow" << "jpg" << QStringList({ "contents-matched" }); + QTest::newRow("unknown-suffix") << "black" << "bar" << QStringList({ "contents-unmatched" }); + if (m_supportedReadFormats.contains("jpeg")) + QTest::newRow("wrong-suffix2") << "white" << "foo" << QStringList(); +} + +void tst_QImageIOHandler::pluginDecideFromContent() +{ + QFETCH(QString, color); + QFETCH(QString, suffix); + QFETCH(QStringList, expectedPluginLog); + + QString filePath = m_prefix + color + (suffix.isEmpty() ? "" : "." + suffix); + PluginLog::clear(); + QImageReader r(filePath, suffix.toLatin1()); + r.setDecideFormatFromContent(true); + QImage img = r.read(); + + QCOMPARE(PluginLog::size(), expectedPluginLog.size()); + for (int i = 0; i < PluginLog::size(); i++) + QCOMPARE(PluginLog::item(i), expectedPluginLog.value(i)); + QVERIFY(!img.isNull()); + QCOMPARE(img.pixelColor(0, 0), QColor(color)); +} + +void tst_QImageIOHandler::pluginDetectedFormat_data() +{ + QTest::addColumn("color"); + QTest::addColumn("suffix"); + QTest::addColumn("fileFormat"); + + QTest::newRow("testplugin") << "green" << "foo" << "foo"; + QTest::newRow("overridden-builtin") << "red" << "png" << "png"; + QTest::newRow("builtin") << "black" << "bmp" << "bmp"; + QTest::newRow("no-suffix") << "blue" << "" << "png"; + QTest::newRow("wrong-suffix") << "yellow" << "jpg" << "foo"; + QTest::newRow("unknown-suffix") << "black" << "bar" << "bmp"; + if (m_supportedReadFormats.contains("jpeg")) + QTest::newRow("wrong-suffix2") << "white" << "foo" << "jpeg"; + if (m_supportedReadFormats.contains("gif")) + QTest::newRow("plugin-writeonly") << "cyan" << "gif" << "gif"; +} + +void tst_QImageIOHandler::pluginDetectedFormat() +{ + QFETCH(QString, color); + QFETCH(QString, suffix); + QFETCH(QString, fileFormat); + + QString filePath = m_prefix + color + (suffix.isEmpty() ? "" : "." + suffix); + QCOMPARE(QImageReader::imageFormat(filePath), fileFormat.toLatin1()); +} + +void tst_QImageIOHandler::pluginWrongFormat_data() +{ + QTest::addColumn("color"); + QTest::addColumn("suffix"); + QTest::addColumn("forceFormat"); + + QTest::newRow("testplugin") << "green" << "foo" << "bmp"; + QTest::newRow("overridden-builtin") << "red" << "png" << "bmp"; + QTest::newRow("builtin") << "black" << "bmp" << "foo"; + QTest::newRow("no-suffix") << "blue" << "" << "bmp"; + QTest::newRow("wrong-suffix") << "yellow" << "jpg" << "bmp"; + QTest::newRow("unknown-suffix") << "black" << "bar" << "foo"; + QTest::newRow("wrong-suffix2") << "white" << "foo" << "bmp"; +} + +void tst_QImageIOHandler::pluginWrongFormat() +{ + QFETCH(QString, color); + QFETCH(QString, suffix); + QFETCH(QString, forceFormat); + + PluginLog::clear(); + QString filePath = m_prefix + color + (suffix.isEmpty() ? "" : "." + suffix); + QImage img(filePath, forceFormat.toLatin1()); + QVERIFY(img.isNull()); +} + QTEST_MAIN(tst_QImageIOHandler) + #include "tst_qimageiohandler.moc"