File indexing completed on 2025-03-23 12:43:50
0001 /* 0002 SPDX-FileCopyrightText: 2014 Alex Merry <alex.merry@kdemail.net> 0003 0004 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0005 */ 0006 0007 #include <stdio.h> 0008 0009 #include <QCommandLineParser> 0010 #include <QCoreApplication> 0011 #include <QDir> 0012 #include <QFile> 0013 #include <QFileInfo> 0014 #include <QImage> 0015 #include <QImageReader> 0016 #include <QTextStream> 0017 0018 #include "../tests/format-enum.h" 0019 0020 #include "fuzzyeq.cpp" 0021 0022 /** 0023 * @brief The SequentialFile class 0024 * Class to make a file a sequential access device. This class is used to check if the plugins could works 0025 * on a sequential device such as a socket. 0026 */ 0027 class SequentialFile : public QFile 0028 { 0029 public: 0030 SequentialFile() 0031 : QFile() 0032 { 0033 } 0034 explicit SequentialFile(const QString &name) 0035 : QFile(name) 0036 { 0037 } 0038 #ifndef QT_NO_QOBJECT 0039 explicit SequentialFile(QObject *parent) 0040 : QFile(parent) 0041 { 0042 } 0043 SequentialFile(const QString &name, QObject *parent) 0044 : QFile(name, parent) 0045 { 0046 } 0047 #endif 0048 0049 bool isSequential() const override 0050 { 0051 return true; 0052 } 0053 0054 qint64 size() const override 0055 { 0056 return bytesAvailable(); 0057 } 0058 }; 0059 0060 static void writeImageData(const char *name, const QString &filename, const QImage &image) 0061 { 0062 QFile file(filename); 0063 if (file.open(QIODevice::WriteOnly)) { 0064 qint64 written = file.write(reinterpret_cast<const char *>(image.bits()), image.sizeInBytes()); 0065 if (written == image.sizeInBytes()) { 0066 QTextStream(stdout) << " " << name << " written to " << filename << "\n"; 0067 } else { 0068 QTextStream(stdout) << " could not write " << name << " to " << filename << ":" << file.errorString() << "\n"; 0069 } 0070 } else { 0071 QTextStream(stdout) << " could not open " << filename << ":" << file.errorString() << "\n"; 0072 } 0073 } 0074 0075 // Returns the original format if we support, or returns 0076 // format which we preferred to use for `fuzzyeq()`. 0077 // We do only support formats with 8-bits/16-bits pre pixel. 0078 // If that changed, don't forget to update `fuzzyeq()` too 0079 static QImage::Format preferredFormat(QImage::Format fmt) 0080 { 0081 switch (fmt) { 0082 case QImage::Format_RGB32: 0083 case QImage::Format_ARGB32: 0084 case QImage::Format_RGBX64: 0085 case QImage::Format_RGBA64: 0086 return fmt; 0087 default: 0088 return QImage::Format_ARGB32; 0089 } 0090 } 0091 0092 int main(int argc, char **argv) 0093 { 0094 QCoreApplication app(argc, argv); 0095 QCoreApplication::removeLibraryPath(QStringLiteral(PLUGIN_DIR)); 0096 QCoreApplication::addLibraryPath(QStringLiteral(PLUGIN_DIR)); 0097 QCoreApplication::setApplicationName(QStringLiteral("readtest")); 0098 QCoreApplication::setApplicationVersion(QStringLiteral("1.1.0")); 0099 0100 QCommandLineParser parser; 0101 parser.setApplicationDescription(QStringLiteral("Performs basic image conversion checking.")); 0102 parser.addHelpOption(); 0103 parser.addVersionOption(); 0104 parser.addPositionalArgument(QStringLiteral("format"), QStringLiteral("format to test")); 0105 QCommandLineOption fuzz(QStringList() << QStringLiteral("f") << QStringLiteral("fuzz"), 0106 QStringLiteral("Allow for some deviation in ARGB data."), 0107 QStringLiteral("max")); 0108 parser.addOption(fuzz); 0109 0110 parser.process(app); 0111 0112 const QStringList args = parser.positionalArguments(); 0113 if (args.count() < 1) { 0114 QTextStream(stderr) << "Must provide a format\n"; 0115 parser.showHelp(1); 0116 } else if (args.count() > 1) { 0117 QTextStream(stderr) << "Too many arguments\n"; 0118 parser.showHelp(1); 0119 } 0120 0121 uchar fuzziness = 0; 0122 if (parser.isSet(fuzz)) { 0123 bool ok; 0124 uint fuzzarg = parser.value(fuzz).toUInt(&ok); 0125 if (!ok || fuzzarg > 255) { 0126 QTextStream(stderr) << "Error: max fuzz argument must be a number between 0 and 255\n"; 0127 parser.showHelp(1); 0128 } 0129 fuzziness = uchar(fuzzarg); 0130 } 0131 0132 QString suffix = args.at(0); 0133 QByteArray format = suffix.toLatin1(); 0134 0135 QDir imgdir(QLatin1String(IMAGEDIR "/") + suffix); 0136 imgdir.setFilter(QDir::Files); 0137 0138 int passed = 0; 0139 int failed = 0; 0140 int skipped = 0; 0141 0142 QTextStream(stdout) << "********* " 0143 << "Starting basic read tests for " << suffix << " images *********\n"; 0144 0145 const QList<QByteArray> formats = QImageReader::supportedImageFormats(); 0146 QStringList formatStrings; 0147 formatStrings.reserve(formats.size()); 0148 std::transform(formats.begin(), formats.end(), std::back_inserter(formatStrings), [](const QByteArray &format) { 0149 return QString(format); 0150 }); 0151 QTextStream(stdout) << "QImageReader::supportedImageFormats: " << formatStrings.join(", ") << "\n"; 0152 0153 const QFileInfoList lstImgDir = imgdir.entryInfoList(); 0154 // Launch 2 runs for each test: first run on a random access device, second run on a sequential access device 0155 for (int seq = 0; seq < 2; ++seq) { 0156 if (seq) { 0157 QTextStream(stdout) << "* Run on SEQUENTIAL ACCESS device\n"; 0158 } else { 0159 QTextStream(stdout) << "* Run on RANDOM ACCESS device\n"; 0160 } 0161 for (const QFileInfo &fi : lstImgDir) { 0162 if (!fi.suffix().compare("png", Qt::CaseInsensitive) || !fi.suffix().compare("tif", Qt::CaseInsensitive)) { 0163 continue; 0164 } 0165 int suffixPos = fi.filePath().count() - suffix.count(); 0166 QString inputfile = fi.filePath(); 0167 QString fmt = QStringLiteral("png"); 0168 QString expfile = fi.filePath().replace(suffixPos, suffix.count(), fmt); 0169 if (!QFile::exists(expfile)) { // try with tiff 0170 fmt = QStringLiteral("tif"); 0171 expfile = fi.filePath().replace(suffixPos, suffix.count(), fmt); 0172 } 0173 QString expfilename = QFileInfo(expfile).fileName(); 0174 0175 std::unique_ptr<QIODevice> inputDevice(seq ? new SequentialFile(inputfile) : new QFile(inputfile)); 0176 QImageReader inputReader(inputDevice.get(), format); 0177 QImageReader expReader(expfile, fmt.toLatin1()); 0178 0179 QImage inputImage; 0180 QImage expImage; 0181 0182 // inputImage is auto-rotated to final orientation 0183 inputReader.setAutoTransform(true); 0184 0185 if (!expReader.read(&expImage)) { 0186 QTextStream(stdout) << "ERROR: " << fi.fileName() << ": could not load " << expfilename << ": " << expReader.errorString() << "\n"; 0187 ++failed; 0188 continue; 0189 } 0190 if (!inputReader.canRead()) { 0191 // All plugins must pass the test on a random device. 0192 // canRead() must also return false if the plugin is unable to run on a sequential device. 0193 if (inputDevice->isSequential()) { 0194 QTextStream(stdout) << "SKIP : " << fi.fileName() << ": cannot read on a sequential device (don't worry, it's ok)\n"; 0195 ++skipped; 0196 } else { 0197 QTextStream(stdout) << "FAIL : " << fi.fileName() << ": failed can read: " << inputReader.errorString() << "\n"; 0198 ++failed; 0199 } 0200 continue; 0201 } 0202 if (!inputReader.read(&inputImage)) { 0203 QTextStream(stdout) << "FAIL : " << fi.fileName() << ": failed to load: " << inputReader.errorString() << "\n"; 0204 ++failed; 0205 continue; 0206 } 0207 if (expImage.width() != inputImage.width()) { 0208 QTextStream(stdout) << "FAIL : " << fi.fileName() << ": width was " << inputImage.width() << " but " << expfilename << " width was " 0209 << expImage.width() << "\n"; 0210 ++failed; 0211 } else if (expImage.height() != inputImage.height()) { 0212 QTextStream(stdout) << "FAIL : " << fi.fileName() << ": height was " << inputImage.height() << " but " << expfilename << " height was " 0213 << expImage.height() << "\n"; 0214 ++failed; 0215 } else { 0216 QImage::Format inputFormat = preferredFormat(inputImage.format()); 0217 QImage::Format expFormat = preferredFormat(expImage.format()); 0218 QImage::Format cmpFormat = inputFormat == expFormat ? inputFormat : QImage::Format_ARGB32; 0219 0220 if (inputImage.format() != cmpFormat) { 0221 QTextStream(stdout) << "INFO : " << fi.fileName() << ": converting " << fi.fileName() << " from " << formatToString(inputImage.format()) 0222 << " to " << formatToString(cmpFormat) << '\n'; 0223 inputImage = inputImage.convertToFormat(cmpFormat); 0224 } 0225 if (expImage.format() != cmpFormat) { 0226 QTextStream(stdout) << "INFO : " << fi.fileName() << ": converting " << expfilename << " from " << formatToString(expImage.format()) 0227 << " to " << formatToString(cmpFormat) << '\n'; 0228 expImage = expImage.convertToFormat(cmpFormat); 0229 } 0230 if (fuzzyeq(inputImage, expImage, fuzziness)) { 0231 QTextStream(stdout) << "PASS : " << fi.fileName() << "\n"; 0232 ++passed; 0233 } else { 0234 QTextStream(stdout) << "FAIL : " << fi.fileName() << ": differs from " << expfilename << "\n"; 0235 writeImageData("expected data", fi.fileName() + QLatin1String("-expected.data"), expImage); 0236 writeImageData("actual data", fi.fileName() + QLatin1String("-actual.data"), inputImage); 0237 ++failed; 0238 } 0239 } 0240 } 0241 } 0242 0243 QTextStream(stdout) << "Totals: " << passed << " passed, " << skipped << " skipped, " << failed << " failed\n"; 0244 QTextStream(stdout) << "********* " 0245 << "Finished basic read tests for " << suffix << " images *********\n"; 0246 0247 return failed == 0 ? 0 : 1; 0248 }