mirror of
https://invent.kde.org/frameworks/kimageformats.git
synced 2025-05-28 00:30:23 -04:00
Read test: added perceptive fuzziness
Added a new parameter to the read tests called `perceptive-fuzz`. The parameter, when active, modifies the fuzziness value based on the alpha value of the pixel. The more transparent the pixel, the more the fuzziness value increases. We have found that some image manipulation functions give different results depending on the architecture (we think it is differences in rounding). These differences can become problematic with small alpha values when there are several image conversions from normal alpha to premultiplied alpha (and vice versa). In particular, the offending plugin is XCF. The parameter should be set if and only if necessary. CMakeList has not been modified to allow it to be enabled on all format images (you can still try it from the command line). To use it, you need to set it in the JSON file of the image that has problems (after careful analysis). More info about the issue on #18 This MR also fixes a bug in `fazzeq()`: it only compared 1/4 of the image. Below is the same XCF image rendered on AMD64 and PowerPC: - AMD64:  - PowerPC:  The image is visually the same because the differences are with very low alpha and therefore are negligible. The patch proposed with this MR is useful in these cases.
This commit is contained in:
parent
424e7a75de
commit
49060026b7
@ -200,7 +200,7 @@ if (OpenEXR_FOUND)
|
|||||||
)
|
)
|
||||||
# Color space conversions from sRGB to linear on saving and
|
# Color space conversions from sRGB to linear on saving and
|
||||||
# from linear to sRGB on loading result in some rounding errors.
|
# from linear to sRGB on loading result in some rounding errors.
|
||||||
kimageformats_write_tests(FUZZ 5
|
kimageformats_write_tests(FUZZ 6
|
||||||
exr-nodatacheck-lossless
|
exr-nodatacheck-lossless
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
@ -4,34 +4,66 @@
|
|||||||
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <QImage>
|
||||||
|
#include <QRgb>
|
||||||
|
#include <QRgba64>
|
||||||
|
|
||||||
|
inline int iAbs(const int &v)
|
||||||
|
{
|
||||||
|
return v < 0 ? -v : v;
|
||||||
|
}
|
||||||
|
|
||||||
template<class Trait>
|
template<class Trait>
|
||||||
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.format() == im2.format());
|
||||||
Q_ASSERT(im1.depth() == 24 || im1.depth() == 32 || im1.depth() == 64);
|
Q_ASSERT(im1.depth() == 24 || im1.depth() == 32 || im1.depth() == 64);
|
||||||
|
|
||||||
|
const bool hasAlpha = im1.hasAlphaChannel();
|
||||||
const int height = im1.height();
|
const int height = im1.height();
|
||||||
const int width = im1.width();
|
const int width = im1.width();
|
||||||
for (int i = 0; i < height; ++i) {
|
for (int i = 0; i < height; ++i) {
|
||||||
const Trait *line1 = reinterpret_cast<const Trait *>(im1.scanLine(i));
|
const Trait *line1 = reinterpret_cast<const Trait *>(im1.scanLine(i));
|
||||||
const Trait *line2 = reinterpret_cast<const Trait *>(im2.scanLine(i));
|
const Trait *line2 = reinterpret_cast<const Trait *>(im2.scanLine(i));
|
||||||
for (int j = 0; j < width; ++j) {
|
for (int j = 0; j < width; ++j) {
|
||||||
if (line1[j] > line2[j]) {
|
auto &&px1 = line1[j];
|
||||||
if (line1[j] - line2[j] > fuzziness) {
|
auto &&px2 = line2[j];
|
||||||
return false;
|
auto fuzz = int(fuzziness);
|
||||||
}
|
|
||||||
} else {
|
// Calculate the deltas
|
||||||
if (line2[j] - line1[j] > fuzziness) {
|
auto dr = iAbs(int(qRed(px2)) - int(qRed(px1)));
|
||||||
return false;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// allow each byte to be different by up to 1, to allow for rounding errors
|
// 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<quint16>(im1, im2, fuzziness) : fuzzyeq<quint8>(im1, im2, fuzziness);
|
return (im1.depth() == 64) ? fuzzyeq<QRgba64>(im1, im2, int(fuzziness) * 257, perceptiveFuzzer) : fuzzyeq<QRgb>(im1, im2, int(fuzziness), perceptiveFuzzer);
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
"minQtVersion" : "6.7.0",
|
"minQtVersion" : "6.7.0",
|
||||||
"fileName" : "birthday16.png",
|
"fileName" : "birthday16.png",
|
||||||
"seeAlso" : "https://bugreports.qt.io/browse/QTBUG-120614",
|
"seeAlso" : "https://bugreports.qt.io/browse/QTBUG-120614",
|
||||||
|
"fuzziness" : 2,
|
||||||
|
"perceptiveFuzziness" : true,
|
||||||
"resolution" : {
|
"resolution" : {
|
||||||
"dotsPerMeterX" : 4724,
|
"dotsPerMeterX" : 4724,
|
||||||
"dotsPerMeterY" : 4724
|
"dotsPerMeterY" : 4724
|
||||||
|
12
autotests/read/xcf/birthday16fp.xcf.json
Normal file
12
autotests/read/xcf/birthday16fp.xcf.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"fileName" : "birthday16fp.png",
|
||||||
|
"seeAlso" : "https://bugreports.qt.io/browse/QTBUG-120614",
|
||||||
|
"fuzziness" : 2,
|
||||||
|
"perceptiveFuzziness" : true,
|
||||||
|
"resolution" : {
|
||||||
|
"dotsPerMeterX" : 4724,
|
||||||
|
"dotsPerMeterY" : 4724
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
@ -3,6 +3,8 @@
|
|||||||
"minQtVersion" : "6.7.0",
|
"minQtVersion" : "6.7.0",
|
||||||
"fileName" : "birthday32.png",
|
"fileName" : "birthday32.png",
|
||||||
"seeAlso" : "https://bugreports.qt.io/browse/QTBUG-120614",
|
"seeAlso" : "https://bugreports.qt.io/browse/QTBUG-120614",
|
||||||
|
"fuzziness" : 2,
|
||||||
|
"perceptiveFuzziness" : true,
|
||||||
"resolution" : {
|
"resolution" : {
|
||||||
"dotsPerMeterX" : 4724,
|
"dotsPerMeterX" : 4724,
|
||||||
"dotsPerMeterY" : 4724
|
"dotsPerMeterY" : 4724
|
||||||
|
12
autotests/read/xcf/birthday32fp.xcf.json
Normal file
12
autotests/read/xcf/birthday32fp.xcf.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"fileName" : "birthday32fp.png",
|
||||||
|
"seeAlso" : "https://bugreports.qt.io/browse/QTBUG-120614",
|
||||||
|
"fuzziness" : 2,
|
||||||
|
"perceptiveFuzziness" : true,
|
||||||
|
"resolution" : {
|
||||||
|
"dotsPerMeterX" : 4724,
|
||||||
|
"dotsPerMeterY" : 4724
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
11
autotests/read/xcf/fruktpilot16fplin_icc.xcf.json
Normal file
11
autotests/read/xcf/fruktpilot16fplin_icc.xcf.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"fileName" : "fruktpilot16fplin_icc.png",
|
||||||
|
"seeAlso" : "https://bugreports.qt.io/browse/QTBUG-120614",
|
||||||
|
"fuzziness" : 1,
|
||||||
|
"resolution" : {
|
||||||
|
"dotsPerMeterX" : 2834,
|
||||||
|
"dotsPerMeterY" : 2834
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
@ -210,9 +210,11 @@ int main(int argc, char **argv)
|
|||||||
QStringLiteral("max"));
|
QStringLiteral("max"));
|
||||||
QCommandLineOption skipOptTest({QStringLiteral("skip-optional-tests")},
|
QCommandLineOption skipOptTest({QStringLiteral("skip-optional-tests")},
|
||||||
QStringLiteral("Skip optional data tests (metadata, resolution, etc.)."));
|
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(fuzz);
|
||||||
parser.addOption(skipOptTest);
|
parser.addOption(skipOptTest);
|
||||||
|
parser.addOption(perceptiveFuzz);
|
||||||
parser.process(app);
|
parser.process(app);
|
||||||
|
|
||||||
const QStringList args = parser.positionalArguments();
|
const QStringList args = parser.positionalArguments();
|
||||||
@ -378,11 +380,16 @@ int main(int argc, char **argv)
|
|||||||
expImage = expImage.convertToFormat(cmpFormat);
|
expImage = expImage.convertToFormat(cmpFormat);
|
||||||
}
|
}
|
||||||
auto tmpFuzziness = fuzziness;
|
auto tmpFuzziness = fuzziness;
|
||||||
|
auto isFuzzPerceptive = parser.isSet(perceptiveFuzz);
|
||||||
if (tmpFuzziness == 0) {
|
if (tmpFuzziness == 0) {
|
||||||
// If the fuzziness value is not explicitly set I use the one set for the current image.
|
// If the fuzziness value is not explicitly set I use the one set for the current image.
|
||||||
tmpFuzziness = timg.fuzziness();
|
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";
|
QTextStream(stdout) << "PASS : " << fi.fileName() << "\n";
|
||||||
++passed;
|
++passed;
|
||||||
} else {
|
} else {
|
||||||
|
@ -135,6 +135,15 @@ quint8 TemplateImage::fuzziness() const
|
|||||||
return quint8(obj.value("fuzziness").toInt());
|
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()
|
QStringList TemplateImage::suffixes()
|
||||||
{
|
{
|
||||||
return QStringList({"png", "tif", "tiff", "json"});
|
return QStringList({"png", "tif", "tiff", "json"});
|
||||||
|
@ -82,6 +82,16 @@ public:
|
|||||||
*/
|
*/
|
||||||
quint8 fuzziness() const;
|
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
|
* \brief suffixes
|
||||||
* \return The list of suffixes considered templates.
|
* \return The list of suffixes considered templates.
|
||||||
|
Loading…
Reference in New Issue
Block a user