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