From 4afafee6c17189a75583eddd0102be8628869845 Mon Sep 17 00:00:00 2001 From: Albert Astals Cid Date: Thu, 17 Feb 2022 17:36:24 +0100 Subject: [PATCH] Add write tests for heif/avif/jxl Unfortunately none of them pass since it seems they can't load a png, save it to their format with loseless quality and read it back and get exactly the same contents than the png --- autotests/CMakeLists.txt | 27 ++++++++++- autotests/fuzzyeq.cpp | 37 +++++++++++++++ autotests/readtest.cpp | 34 +------------- autotests/writetest.cpp | 97 +++++++++++++++++++++++++++++----------- 4 files changed, 134 insertions(+), 61 deletions(-) create mode 100644 autotests/fuzzyeq.cpp diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index 65b9c19..f4a4e6c 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -30,6 +30,12 @@ macro(kimageformats_read_tests) endmacro() macro(kimageformats_write_tests) + cmake_parse_arguments(KIF_RT "" "FUZZ" "" ${ARGN}) + set(_fuzzarg) + if (KIF_RT_FUZZ) + set(_fuzzarg -f ${KIF_RT_FUZZ}) + endif() + if (NOT TARGET writetest) add_executable(writetest writetest.cpp) target_link_libraries(writetest Qt${QT_MAJOR_VERSION}::Gui) @@ -37,16 +43,22 @@ macro(kimageformats_write_tests) PRIVATE IMAGEDIR="${CMAKE_CURRENT_SOURCE_DIR}/write") ecm_mark_as_test(writetest) endif() - foreach(_testname ${ARGN}) + foreach(_testname ${KIF_RT_UNPARSED_ARGUMENTS}) string(REGEX MATCH "-lossless$" _is_lossless "${_testname}") + string(REGEX MATCH "-nodatacheck" _is_no_data_check "${_testname}") unset(lossless_arg) + unset(no_data_check_arg) if (_is_lossless) set(lossless_arg "--lossless") string(REGEX REPLACE "-lossless$" "" _testname "${_testname}") endif() + if (_is_no_data_check) + set(no_data_check_arg "--no-data-check") + string(REGEX REPLACE "-nodatacheck$" "" _testname "${_testname}") + endif() add_test( NAME kimageformats-write-${_testname} - COMMAND writetest ${lossless_arg} ${_testname} + COMMAND writetest ${lossless_arg} ${no_data_check_arg} ${_fuzzarg} ${_testname} ) endforeach(_testname) endmacro() @@ -74,18 +86,29 @@ if (TARGET avif) kimageformats_read_tests( avif ) + # because the plug-ins use RGB->YUV conversion which sometimes results in 1 value difference. + kimageformats_write_tests(FUZZ 1 + avif-nodatacheck-lossless + ) endif() if (LibHeif_FOUND) kimageformats_read_tests( heif ) + # because the plug-ins use RGB->YUV conversion which sometimes results in 1 value difference. + kimageformats_write_tests(FUZZ 1 + heif-nodatacheck-lossless + ) endif() if (LibJXL_FOUND AND LibJXLThreads_FOUND) kimageformats_read_tests( jxl ) + kimageformats_write_tests( + jxl-nodatacheck-lossless + ) endif() # Allow some fuzziness when reading this formats, to allow for diff --git a/autotests/fuzzyeq.cpp b/autotests/fuzzyeq.cpp new file mode 100644 index 0000000..c846d64 --- /dev/null +++ b/autotests/fuzzyeq.cpp @@ -0,0 +1,37 @@ +/* + SPDX-FileCopyrightText: 2014 Alex Merry + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +template +static bool fuzzyeq(const QImage &im1, const QImage &im2, uchar fuzziness) +{ + Q_ASSERT(im1.format() == im2.format()); + Q_ASSERT(im1.depth() == 24 || im1.depth() == 32 || im1.depth() == 64); + + 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; + } + } + } + } + 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) +{ + return (im1.depth() == 64) ? fuzzyeq(im1, im2, fuzziness) : fuzzyeq(im1, im2, fuzziness); +} diff --git a/autotests/readtest.cpp b/autotests/readtest.cpp index 64a7dc6..40a6891 100644 --- a/autotests/readtest.cpp +++ b/autotests/readtest.cpp @@ -16,6 +16,8 @@ #include "../tests/format-enum.h" +#include "fuzzyeq.cpp" + static void writeImageData(const char *name, const QString &filename, const QImage &image) { QFile file(filename); @@ -31,38 +33,6 @@ static void writeImageData(const char *name, const QString &filename, const QIma } } -template -static bool fuzzyeq(const QImage &im1, const QImage &im2, uchar fuzziness) -{ - Q_ASSERT(im1.format() == im2.format()); - Q_ASSERT(im1.depth() == 24 || im1.depth() == 32 || im1.depth() == 64); - - 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; - } - } - } - } - 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) -{ - return (im1.depth() == 64) ? fuzzyeq(im1, im2, fuzziness) : fuzzyeq(im1, im2, fuzziness); -} - // Returns the original format if we support, or returns // format which we preferred to use for `fuzzyeq()`. // We do only support formats with 8-bits/16-bits pre pixel. diff --git a/autotests/writetest.cpp b/autotests/writetest.cpp index edd306c..011385f 100644 --- a/autotests/writetest.cpp +++ b/autotests/writetest.cpp @@ -16,6 +16,8 @@ #include #include +#include "fuzzyeq.cpp" + int main(int argc, char **argv) { QCoreApplication app(argc, argv); @@ -31,7 +33,13 @@ int main(int argc, char **argv) parser.addPositionalArgument(QStringLiteral("format"), QStringLiteral("format to test.")); QCommandLineOption lossless(QStringList() << QStringLiteral("l") << QStringLiteral("lossless"), QStringLiteral("Check that reading back the data gives the same image.")); + QCommandLineOption ignoreDataCheck({QStringLiteral("no-data-check")}, QStringLiteral("Don't check that write data is exactly the same.")); + QCommandLineOption fuzz(QStringList() << QStringLiteral("f") << QStringLiteral("fuzz"), + QStringLiteral("Allow for some deviation in ARGB data."), + QStringLiteral("max")); parser.addOption(lossless); + parser.addOption(ignoreDataCheck); + parser.addOption(fuzz); parser.process(app); @@ -44,11 +52,26 @@ int main(int argc, char **argv) parser.showHelp(1); } + uchar fuzziness = 0; + if (parser.isSet(fuzz)) { + bool ok; + uint fuzzarg = parser.value(fuzz).toUInt(&ok); + if (!ok || fuzzarg > 255) { + QTextStream(stderr) << "Error: max fuzz argument must be a number between 0 and 255\n"; + parser.showHelp(1); + } + fuzziness = uchar(fuzzarg); + } + QString suffix = args.at(0); QByteArray format = suffix.toLatin1(); QDir imgdir(QStringLiteral(IMAGEDIR)); - imgdir.setNameFilters(QStringList(QLatin1String("*.") + suffix)); + if (parser.isSet(ignoreDataCheck)) { + imgdir.setNameFilters({QLatin1String("*.png")}); + } else { + imgdir.setNameFilters(QStringList(QLatin1String("*.") + suffix)); + } imgdir.setFilter(QDir::Files); int passed = 0; @@ -58,8 +81,13 @@ int main(int argc, char **argv) << "Starting basic write tests for " << suffix << " images *********\n"; const QFileInfoList lstImgDir = imgdir.entryInfoList(); for (const QFileInfo &fi : lstImgDir) { - int suffixPos = fi.filePath().count() - suffix.count(); - QString pngfile = fi.filePath().replace(suffixPos, suffix.count(), QStringLiteral("png")); + QString pngfile; + if (parser.isSet(ignoreDataCheck)) { + pngfile = fi.filePath(); + } else { + int suffixPos = fi.filePath().count() - suffix.count(); + pngfile = fi.filePath().replace(suffixPos, suffix.count(), QStringLiteral("png")); + } QString pngfilename = QFileInfo(pngfile).fileName(); QImageReader pngReader(pngfile, "png"); @@ -70,29 +98,13 @@ int main(int argc, char **argv) continue; } - QFile expFile(fi.filePath()); - if (!expFile.open(QIODevice::ReadOnly)) { - QTextStream(stdout) << "ERROR: " << fi.fileName() << ": could not open " << fi.fileName() << ": " << expFile.errorString() << "\n"; - ++failed; - continue; - } - QByteArray expData = expFile.readAll(); - if (expData.isEmpty()) { - // check if there was actually anything to read - expFile.reset(); - char buf[1]; - qint64 result = expFile.read(buf, 1); - if (result < 0) { - QTextStream(stdout) << "ERROR: " << fi.fileName() << ": could not load " << fi.fileName() << ": " << expFile.errorString() << "\n"; - ++failed; - continue; - } - } - QByteArray writtenData; { QBuffer buffer(&writtenData); QImageWriter imgWriter(&buffer, format.constData()); + if (parser.isSet(lossless)) { + imgWriter.setQuality(100); + } if (!imgWriter.write(pngImage)) { QTextStream(stdout) << "FAIL : " << fi.fileName() << ": failed to write image data\n"; ++failed; @@ -100,10 +112,31 @@ int main(int argc, char **argv) } } - if (expData != writtenData) { - QTextStream(stdout) << "FAIL : " << fi.fileName() << ": written data differs from " << fi.fileName() << "\n"; - ++failed; - continue; + if (!parser.isSet(ignoreDataCheck)) { + QFile expFile(fi.filePath()); + if (!expFile.open(QIODevice::ReadOnly)) { + QTextStream(stdout) << "ERROR: " << fi.fileName() << ": could not open " << fi.fileName() << ": " << expFile.errorString() << "\n"; + ++failed; + continue; + } + QByteArray expData = expFile.readAll(); + if (expData.isEmpty()) { + // check if there was actually anything to read + expFile.reset(); + char buf[1]; + qint64 result = expFile.read(buf, 1); + if (result < 0) { + QTextStream(stdout) << "ERROR: " << fi.fileName() << ": could not load " << fi.fileName() << ": " << expFile.errorString() << "\n"; + ++failed; + continue; + } + } + + if (expData != writtenData) { + QTextStream(stdout) << "FAIL : " << fi.fileName() << ": written data differs from " << fi.fileName() << "\n"; + ++failed; + continue; + } } QImage reReadImage; @@ -119,8 +152,18 @@ int main(int argc, char **argv) } if (parser.isSet(lossless)) { - if (pngImage != reReadImage) { + if (!fuzzyeq(pngImage, reReadImage, fuzziness)) { QTextStream(stdout) << "FAIL : " << fi.fileName() << ": re-reading the data resulted in a different image\n"; + if (pngImage.size() == reReadImage.size()) { + for (int i = 0; i < pngImage.width(); ++i) { + for (int j = 0; j < pngImage.height(); ++j) { + if (pngImage.pixel(i, j) != reReadImage.pixel(i, j)) { + QTextStream(stdout) << "Pixel is different " << i << ',' << j << ' ' << pngImage.pixel(i, j) << ' ' << reReadImage.pixel(i, j) + << '\n'; + } + } + } + } ++failed; continue; }