1 /*
2     SPDX-FileCopyrightText: 2014 Alex Merry <alex.merry@kdemail.net>
3 
4     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
5 */
6 
7 #include <stdio.h>
8 
9 #include <QCommandLineParser>
10 #include <QCoreApplication>
11 #include <QDir>
12 #include <QFileInfo>
13 #include <QImage>
14 #include <QImageReader>
15 #include <QTextStream>
16 
17 #include "../tests/format-enum.h"
18 
writeImageData(const char * name,const QString & filename,const QImage & image)19 static void writeImageData(const char *name, const QString &filename, const QImage &image)
20 {
21     QFile file(filename);
22     if (file.open(QIODevice::WriteOnly)) {
23         qint64 written = file.write(reinterpret_cast<const char *>(image.bits()), image.sizeInBytes());
24         if (written == image.sizeInBytes()) {
25             QTextStream(stdout) << "       " << name << " written to " << filename << "\n";
26         } else {
27             QTextStream(stdout) << "       could not write " << name << " to " << filename << ":" << file.errorString() << "\n";
28         }
29     } else {
30         QTextStream(stdout) << "       could not open " << filename << ":" << file.errorString() << "\n";
31     }
32 }
33 
34 template<class Trait>
fuzzyeq(const QImage & im1,const QImage & im2,uchar fuzziness)35 static bool fuzzyeq(const QImage &im1, const QImage &im2, uchar fuzziness)
36 {
37     Q_ASSERT(im1.format() == im2.format());
38     Q_ASSERT(im1.depth() == 24 || im1.depth() == 32 || im1.depth() == 64);
39 
40     const int height = im1.height();
41     const int width = im1.width();
42     for (int i = 0; i < height; ++i) {
43         const Trait *line1 = reinterpret_cast<const Trait *>(im1.scanLine(i));
44         const Trait *line2 = reinterpret_cast<const Trait *>(im2.scanLine(i));
45         for (int j = 0; j < width; ++j) {
46             if (line1[j] > line2[j]) {
47                 if (line1[j] - line2[j] > fuzziness) {
48                     return false;
49                 }
50             } else {
51                 if (line2[j] - line1[j] > fuzziness) {
52                     return false;
53                 }
54             }
55         }
56     }
57     return true;
58 }
59 
60 // allow each byte to be different by up to 1, to allow for rounding errors
fuzzyeq(const QImage & im1,const QImage & im2,uchar fuzziness)61 static bool fuzzyeq(const QImage &im1, const QImage &im2, uchar fuzziness)
62 {
63     return (im1.depth() == 64) ? fuzzyeq<quint16>(im1, im2, fuzziness) : fuzzyeq<quint8>(im1, im2, fuzziness);
64 }
65 
66 // Returns the original format if we support, or returns
67 // format which we preferred to use for `fuzzyeq()`.
68 // We do only support formats with 8-bits/16-bits pre pixel.
69 // If that changed, don't forget to update `fuzzyeq()` too
preferredFormat(QImage::Format fmt)70 static QImage::Format preferredFormat(QImage::Format fmt)
71 {
72     switch (fmt) {
73     case QImage::Format_RGB32:
74     case QImage::Format_ARGB32:
75     case QImage::Format_RGBX64:
76     case QImage::Format_RGBA64:
77         return fmt;
78     default:
79         return QImage::Format_ARGB32;
80     }
81 }
82 
main(int argc,char ** argv)83 int main(int argc, char **argv)
84 {
85     QCoreApplication app(argc, argv);
86     QCoreApplication::removeLibraryPath(QStringLiteral(PLUGIN_DIR));
87     QCoreApplication::addLibraryPath(QStringLiteral(PLUGIN_DIR));
88     QCoreApplication::setApplicationName(QStringLiteral("readtest"));
89     QCoreApplication::setApplicationVersion(QStringLiteral("1.0.0"));
90 
91     QCommandLineParser parser;
92     parser.setApplicationDescription(QStringLiteral("Performs basic image conversion checking."));
93     parser.addHelpOption();
94     parser.addVersionOption();
95     parser.addPositionalArgument(QStringLiteral("format"), QStringLiteral("format to test"));
96     QCommandLineOption fuzz(QStringList() << QStringLiteral("f") << QStringLiteral("fuzz"),
97                             QStringLiteral("Allow for some deviation in ARGB data."),
98                             QStringLiteral("max"));
99     parser.addOption(fuzz);
100 
101     parser.process(app);
102 
103     const QStringList args = parser.positionalArguments();
104     if (args.count() < 1) {
105         QTextStream(stderr) << "Must provide a format\n";
106         parser.showHelp(1);
107     } else if (args.count() > 1) {
108         QTextStream(stderr) << "Too many arguments\n";
109         parser.showHelp(1);
110     }
111 
112     uchar fuzziness = 0;
113     if (parser.isSet(fuzz)) {
114         bool ok;
115         uint fuzzarg = parser.value(fuzz).toUInt(&ok);
116         if (!ok || fuzzarg > 255) {
117             QTextStream(stderr) << "Error: max fuzz argument must be a number between 0 and 255\n";
118             parser.showHelp(1);
119         }
120         fuzziness = uchar(fuzzarg);
121     }
122 
123     QString suffix = args.at(0);
124     QByteArray format = suffix.toLatin1();
125 
126     QDir imgdir(QLatin1String(IMAGEDIR "/") + suffix);
127     imgdir.setNameFilters(QStringList(QLatin1String("*.") + suffix));
128     imgdir.setFilter(QDir::Files);
129 
130     int passed = 0;
131     int failed = 0;
132 
133     QTextStream(stdout) << "********* "
134                         << "Starting basic read tests for " << suffix << " images *********\n";
135 
136     const QList<QByteArray> formats = QImageReader::supportedImageFormats();
137     QStringList formatStrings;
138     formatStrings.reserve(formats.size());
139     std::transform(formats.begin(), formats.end(), std::back_inserter(formatStrings), [](const QByteArray &format) {
140         return QString(format);
141     });
142     QTextStream(stdout) << "QImageReader::supportedImageFormats: " << formatStrings.join(", ") << "\n";
143 
144     const QFileInfoList lstImgDir = imgdir.entryInfoList();
145     for (const QFileInfo &fi : lstImgDir) {
146         int suffixPos = fi.filePath().count() - suffix.count();
147         QString inputfile = fi.filePath();
148         QString expfile = fi.filePath().replace(suffixPos, suffix.count(), QStringLiteral("png"));
149         QString expfilename = QFileInfo(expfile).fileName();
150 
151         QImageReader inputReader(inputfile, format);
152         QImageReader expReader(expfile, "png");
153 
154         QImage inputImage;
155         QImage expImage;
156 
157         if (!expReader.read(&expImage)) {
158             QTextStream(stdout) << "ERROR: " << fi.fileName() << ": could not load " << expfilename << ": " << expReader.errorString() << "\n";
159             ++failed;
160             continue;
161         }
162         if (!inputReader.canRead()) {
163             QTextStream(stdout) << "FAIL : " << fi.fileName() << ": failed can read: " << inputReader.errorString() << "\n";
164             ++failed;
165             continue;
166         }
167         if (!inputReader.read(&inputImage)) {
168             QTextStream(stdout) << "FAIL : " << fi.fileName() << ": failed to load: " << inputReader.errorString() << "\n";
169             ++failed;
170             continue;
171         }
172         if (expImage.width() != inputImage.width()) {
173             QTextStream(stdout) << "FAIL : " << fi.fileName() << ": width was " << inputImage.width() << " but " << expfilename << " width was "
174                                 << expImage.width() << "\n";
175             ++failed;
176         } else if (expImage.height() != inputImage.height()) {
177             QTextStream(stdout) << "FAIL : " << fi.fileName() << ": height was " << inputImage.height() << " but " << expfilename << " height was "
178                                 << expImage.height() << "\n";
179             ++failed;
180         } else {
181             QImage::Format inputFormat = preferredFormat(inputImage.format());
182             QImage::Format expFormat = preferredFormat(expImage.format());
183             QImage::Format cmpFormat = inputFormat == expFormat ? inputFormat : QImage::Format_ARGB32;
184 
185             if (inputImage.format() != cmpFormat) {
186                 QTextStream(stdout) << "INFO : " << fi.fileName() << ": converting " << fi.fileName() << " from " << formatToString(inputImage.format())
187                                     << " to " << formatToString(cmpFormat) << '\n';
188                 inputImage = inputImage.convertToFormat(cmpFormat);
189             }
190             if (expImage.format() != cmpFormat) {
191                 QTextStream(stdout) << "INFO : " << fi.fileName() << ": converting " << expfilename << " from " << formatToString(expImage.format()) << " to "
192                                     << formatToString(cmpFormat) << '\n';
193                 expImage = expImage.convertToFormat(cmpFormat);
194             }
195             if (fuzzyeq(inputImage, expImage, fuzziness)) {
196                 QTextStream(stdout) << "PASS : " << fi.fileName() << "\n";
197                 ++passed;
198             } else {
199                 QTextStream(stdout) << "FAIL : " << fi.fileName() << ": differs from " << expfilename << "\n";
200                 writeImageData("expected data", fi.fileName() + QLatin1String("-expected.data"), expImage);
201                 writeImageData("actual data", fi.fileName() + QLatin1String("-actual.data"), inputImage);
202                 ++failed;
203             }
204         }
205     }
206 
207     QTextStream(stdout) << "Totals: " << passed << " passed, " << failed << " failed\n";
208     QTextStream(stdout) << "********* "
209                         << "Finished basic read tests for " << suffix << " images *********\n";
210 
211     return failed == 0 ? 0 : 1;
212 }
213