diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index 0fe117a..7fd0db4 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -200,7 +200,7 @@ if (OpenEXR_FOUND) ) # Color space conversions from sRGB to linear on saving and # from linear to sRGB on loading result in some rounding errors. - kimageformats_write_tests(FUZZ 5 + kimageformats_write_tests(FUZZ 6 exr-nodatacheck-lossless ) endif() diff --git a/autotests/fuzzyeq.cpp b/autotests/fuzzyeq.cpp index c846d64..1e32ce7 100644 --- a/autotests/fuzzyeq.cpp +++ b/autotests/fuzzyeq.cpp @@ -4,34 +4,66 @@ SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ +#include +#include +#include + +inline int iAbs(const int &v) +{ + return v < 0 ? -v : v; +} + template -static bool fuzzyeq(const QImage &im1, const QImage &im2, uchar fuzziness) +static bool fuzzyeq(const QImage &im1, const QImage &im2, int fuzziness, bool perceptiveFuzzer) { Q_ASSERT(im1.format() == im2.format()); Q_ASSERT(im1.depth() == 24 || im1.depth() == 32 || im1.depth() == 64); + const bool hasAlpha = im1.hasAlphaChannel(); const int height = im1.height(); const int width = im1.width(); for (int i = 0; i < height; ++i) { const Trait *line1 = reinterpret_cast(im1.scanLine(i)); const Trait *line2 = reinterpret_cast(im2.scanLine(i)); for (int j = 0; j < width; ++j) { - if (line1[j] > line2[j]) { - if (line1[j] - line2[j] > fuzziness) { - return false; - } - } else { - if (line2[j] - line1[j] > fuzziness) { - return false; - } + auto &&px1 = line1[j]; + auto &&px2 = line2[j]; + auto fuzz = int(fuzziness); + + // Calculate the deltas + auto dr = iAbs(int(qRed(px2)) - int(qRed(px1))); + auto dg = iAbs(int(qGreen(px2)) - int(qGreen(px1))); + auto db = iAbs(int(qBlue(px2)) - int(qBlue(px1))); + auto da = iAbs(int(qAlpha(px2)) - int(qAlpha(px1))); + + // Always compare alpha even on images without it: some formats (e.g. RGBX64), + // want it set to a certain value (e.g. 65535). + if (da > fuzz) + return false; + + // Calculate the perceptive fuzziness. + if (hasAlpha && perceptiveFuzzer) { + auto alpha = std::max(4, int(qAlpha(px1))); + if (sizeof(Trait) == 4) + fuzz = std::min(fuzz * (255 / alpha), 255); + else + fuzz = std::min(fuzz * (65535 / alpha), 255 * 257); } + + // Compare the deltas of R, G, B components. + if (dr > fuzz) + return false; + if (dg > fuzz) + return false; + if (db > fuzz) + return false; } } return true; } // allow each byte to be different by up to 1, to allow for rounding errors -static bool fuzzyeq(const QImage &im1, const QImage &im2, uchar fuzziness) +static bool fuzzyeq(const QImage &im1, const QImage &im2, uchar fuzziness, bool perceptiveFuzzer = false) { - return (im1.depth() == 64) ? fuzzyeq(im1, im2, fuzziness) : fuzzyeq(im1, im2, fuzziness); + return (im1.depth() == 64) ? fuzzyeq(im1, im2, int(fuzziness) * 257, perceptiveFuzzer) : fuzzyeq(im1, im2, int(fuzziness), perceptiveFuzzer); } diff --git a/autotests/read/xcf/birthday16.xcf.json b/autotests/read/xcf/birthday16.xcf.json index 0f6e5b2..b3727fe 100644 --- a/autotests/read/xcf/birthday16.xcf.json +++ b/autotests/read/xcf/birthday16.xcf.json @@ -3,6 +3,8 @@ "minQtVersion" : "6.7.0", "fileName" : "birthday16.png", "seeAlso" : "https://bugreports.qt.io/browse/QTBUG-120614", + "fuzziness" : 2, + "perceptiveFuzziness" : true, "resolution" : { "dotsPerMeterX" : 4724, "dotsPerMeterY" : 4724 diff --git a/autotests/read/xcf/birthday16fp.xcf.json b/autotests/read/xcf/birthday16fp.xcf.json new file mode 100644 index 0000000..4cc33ad --- /dev/null +++ b/autotests/read/xcf/birthday16fp.xcf.json @@ -0,0 +1,12 @@ +[ + { + "fileName" : "birthday16fp.png", + "seeAlso" : "https://bugreports.qt.io/browse/QTBUG-120614", + "fuzziness" : 2, + "perceptiveFuzziness" : true, + "resolution" : { + "dotsPerMeterX" : 4724, + "dotsPerMeterY" : 4724 + } + } +] diff --git a/autotests/read/xcf/birthday32.xcf.json b/autotests/read/xcf/birthday32.xcf.json index 8cbe05e..6fbd09f 100644 --- a/autotests/read/xcf/birthday32.xcf.json +++ b/autotests/read/xcf/birthday32.xcf.json @@ -3,6 +3,8 @@ "minQtVersion" : "6.7.0", "fileName" : "birthday32.png", "seeAlso" : "https://bugreports.qt.io/browse/QTBUG-120614", + "fuzziness" : 2, + "perceptiveFuzziness" : true, "resolution" : { "dotsPerMeterX" : 4724, "dotsPerMeterY" : 4724 diff --git a/autotests/read/xcf/birthday32fp.xcf.json b/autotests/read/xcf/birthday32fp.xcf.json new file mode 100644 index 0000000..e55cf99 --- /dev/null +++ b/autotests/read/xcf/birthday32fp.xcf.json @@ -0,0 +1,12 @@ +[ + { + "fileName" : "birthday32fp.png", + "seeAlso" : "https://bugreports.qt.io/browse/QTBUG-120614", + "fuzziness" : 2, + "perceptiveFuzziness" : true, + "resolution" : { + "dotsPerMeterX" : 4724, + "dotsPerMeterY" : 4724 + } + } +] diff --git a/autotests/read/xcf/fruktpilot16fplin_icc.xcf.json b/autotests/read/xcf/fruktpilot16fplin_icc.xcf.json new file mode 100644 index 0000000..462f6d5 --- /dev/null +++ b/autotests/read/xcf/fruktpilot16fplin_icc.xcf.json @@ -0,0 +1,11 @@ +[ + { + "fileName" : "fruktpilot16fplin_icc.png", + "seeAlso" : "https://bugreports.qt.io/browse/QTBUG-120614", + "fuzziness" : 1, + "resolution" : { + "dotsPerMeterX" : 2834, + "dotsPerMeterY" : 2834 + } + } +] diff --git a/autotests/readtest.cpp b/autotests/readtest.cpp index 020eb7e..1416ce5 100644 --- a/autotests/readtest.cpp +++ b/autotests/readtest.cpp @@ -210,9 +210,11 @@ int main(int argc, char **argv) QStringLiteral("max")); QCommandLineOption skipOptTest({QStringLiteral("skip-optional-tests")}, QStringLiteral("Skip optional data tests (metadata, resolution, etc.).")); + QCommandLineOption perceptiveFuzz({QStringLiteral("perceptive-fuzz")}, QStringLiteral("The fuzziness value is scaled based on the alpha channel value.")); parser.addOption(fuzz); parser.addOption(skipOptTest); + parser.addOption(perceptiveFuzz); parser.process(app); const QStringList args = parser.positionalArguments(); @@ -378,11 +380,16 @@ int main(int argc, char **argv) expImage = expImage.convertToFormat(cmpFormat); } auto tmpFuzziness = fuzziness; + auto isFuzzPerceptive = parser.isSet(perceptiveFuzz); if (tmpFuzziness == 0) { // If the fuzziness value is not explicitly set I use the one set for the current image. tmpFuzziness = timg.fuzziness(); } - if (fuzzyeq(inputImage, expImage, tmpFuzziness)) { + if (!isFuzzPerceptive) { + // If the perceptiveFuzziness value is not explicitly set I use the one set for the current image. + isFuzzPerceptive = timg.perceptiveFuzziness(); + } + if (fuzzyeq(inputImage, expImage, tmpFuzziness, isFuzzPerceptive)) { QTextStream(stdout) << "PASS : " << fi.fileName() << "\n"; ++passed; } else { diff --git a/autotests/templateimage.cpp b/autotests/templateimage.cpp index 9182f70..65b4fff 100644 --- a/autotests/templateimage.cpp +++ b/autotests/templateimage.cpp @@ -135,6 +135,15 @@ quint8 TemplateImage::fuzziness() const return quint8(obj.value("fuzziness").toInt()); } +bool TemplateImage::perceptiveFuzziness() const +{ + auto obj = searchObject(m_fi); + if (obj.isEmpty()) { + return false; + } + return quint8(obj.value("perceptiveFuzziness").toBool()); +} + QStringList TemplateImage::suffixes() { return QStringList({"png", "tif", "tiff", "json"}); diff --git a/autotests/templateimage.h b/autotests/templateimage.h index 0e515c1..847689e 100644 --- a/autotests/templateimage.h +++ b/autotests/templateimage.h @@ -82,6 +82,16 @@ public: */ quint8 fuzziness() const; + /*! + * \brief perceptiveFuzziness + * The perceptual mode of fuzziness control scales the value according to + * the alpha value: the lower the alpha value (transparent), the more the + * fuzziness value increases according to the following formula: + * - fuzz = fuzz * 255 / alpha + * \return True if the perceptive mode is active, otherwise false. + */ + bool perceptiveFuzziness() const; + /*! * \brief suffixes * \return The list of suffixes considered templates.