File indexing completed on 2024-05-19 04:32:39
0001 /* 0002 * SPDX-FileCopyrightText: 2016 Dmitry Kazakov <dimula73@gmail.com> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #ifndef QIMAGE_TEST_UTIL_H 0008 #define QIMAGE_TEST_UTIL_H 0009 0010 #ifdef FILES_OUTPUT_DIR 0011 0012 #include <QProcessEnvironment> 0013 #include <QDir> 0014 #include <QApplication> 0015 0016 namespace TestUtil { 0017 0018 inline QString fetchExternalDataFileName(const QString relativeFileName) 0019 { 0020 static QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); 0021 static QString unittestsDataDirPath = "KRITA_UNITTESTS_DATA_DIR"; 0022 0023 QString path; 0024 if (!env.contains(unittestsDataDirPath)) { 0025 warnKrita << "Environment variable" << unittestsDataDirPath << "is not set"; 0026 return QString(); 0027 } else { 0028 path = env.value(unittestsDataDirPath, ""); 0029 } 0030 0031 QString filename = 0032 path + 0033 '/' + 0034 relativeFileName; 0035 0036 return filename; 0037 } 0038 0039 inline QString fetchDataFileLazy(const QString relativeFileName, bool externalTest = false) 0040 { 0041 if (externalTest) { 0042 return fetchExternalDataFileName(relativeFileName); 0043 } else { 0044 QString filename = 0045 QString(FILES_DATA_DIR) + 0046 '/' + 0047 relativeFileName; 0048 0049 if (QFileInfo(filename).exists()) { 0050 return filename; 0051 } 0052 0053 filename = 0054 QString(FILES_DEFAULT_DATA_DIR) + 0055 '/' + 0056 relativeFileName; 0057 0058 if (QFileInfo(filename).exists()) { 0059 return filename; 0060 } 0061 0062 filename = 0063 QFileInfo(qApp->applicationFilePath()).absolutePath() + 0064 "/" + 0065 relativeFileName; 0066 0067 if (QFileInfo(filename).exists()) { 0068 return filename; 0069 } 0070 0071 filename = 0072 QFileInfo(qApp->applicationFilePath()).absolutePath() + 0073 "/data/" + 0074 relativeFileName; 0075 0076 if (QFileInfo(filename).exists()) { 0077 return filename; 0078 } 0079 } 0080 0081 return QString(); 0082 } 0083 0084 // quint8 arguments are automatically converted into int 0085 inline bool compareChannels(int ch1, int ch2, int fuzzy) 0086 { 0087 return qAbs(ch1 - ch2) <= fuzzy; 0088 } 0089 0090 inline bool compareChannelsPremultiplied(int ch1, int alpha1, int ch2, int alpha2, int fuzzy, int fuzzyAlpha) 0091 { 0092 return qAbs(ch1 * alpha1 - ch2 * alpha2) / 255 <= fuzzy * qMax(1, fuzzyAlpha); 0093 } 0094 0095 0096 inline bool compareQImagesImpl(QPoint & pt, const QImage & image1, const QImage & image2, int fuzzy = 0, int fuzzyAlpha = 0, int maxNumFailingPixels = 0, bool showDebug = true, bool premultipliedMode = false) 0097 { 0098 // QTime t; 0099 // t.start(); 0100 0101 const int w1 = image1.width(); 0102 const int h1 = image1.height(); 0103 const int w2 = image2.width(); 0104 const int h2 = image2.height(); 0105 const int bytesPerLine = image1.bytesPerLine(); 0106 0107 if (w1 != w2 || h1 != h2) { 0108 pt.setX(-1); 0109 pt.setY(-1); 0110 qDebug() << "Images have different sizes" << image1.size() << image2.size(); 0111 return false; 0112 } 0113 0114 int numFailingPixels = 0; 0115 0116 for (int y = 0; y < h1; ++y) { 0117 const QRgb * const firstLine = reinterpret_cast<const QRgb *>(image2.scanLine(y)); 0118 const QRgb * const secondLine = reinterpret_cast<const QRgb *>(image1.scanLine(y)); 0119 0120 if (memcmp(firstLine, secondLine, bytesPerLine) != 0) { 0121 for (int x = 0; x < w1; ++x) { 0122 const QRgb a = firstLine[x]; 0123 const QRgb b = secondLine[x]; 0124 0125 bool same = false; 0126 0127 if (!premultipliedMode) { 0128 same = 0129 compareChannels(qRed(a), qRed(b), fuzzy) && 0130 compareChannels(qGreen(a), qGreen(b), fuzzy) && 0131 compareChannels(qBlue(a), qBlue(b), fuzzy); 0132 } else { 0133 same = 0134 compareChannelsPremultiplied(qRed(a), qAlpha(a), qRed(b), qAlpha(b), fuzzy, fuzzyAlpha) && 0135 compareChannelsPremultiplied(qGreen(a), qAlpha(a), qGreen(b), qAlpha(b), fuzzy, fuzzyAlpha) && 0136 compareChannelsPremultiplied(qBlue(a), qAlpha(a), qBlue(b), qAlpha(b), fuzzy, fuzzyAlpha); 0137 } 0138 const bool sameAlpha = compareChannels(qAlpha(a), qAlpha(b), fuzzyAlpha); 0139 const bool bothTransparent = sameAlpha && qAlpha(a)==0; 0140 0141 if (!bothTransparent && (!same || !sameAlpha)) { 0142 pt.setX(x); 0143 pt.setY(y); 0144 numFailingPixels++; 0145 0146 if (showDebug) { 0147 qDebug() << " Different at" << pt 0148 << "source" << qRed(a) << qGreen(a) << qBlue(a) << qAlpha(a) 0149 << "dest" << qRed(b) << qGreen(b) << qBlue(b) << qAlpha(b) 0150 << "fuzzy" << fuzzy 0151 << "fuzzyAlpha" << fuzzyAlpha 0152 << "(" << numFailingPixels << "of" << maxNumFailingPixels << "allowed )"; 0153 } 0154 0155 if (numFailingPixels > maxNumFailingPixels) { 0156 return false; 0157 } 0158 } 0159 } 0160 } 0161 } 0162 // qDebug() << "compareQImages time elapsed:" << t.elapsed(); 0163 // qDebug() << "Images are identical"; 0164 return true; 0165 } 0166 0167 inline bool compareQImages(QPoint & pt, const QImage & image1, const QImage & image2, int fuzzy = 0, int fuzzyAlpha = 0, int maxNumFailingPixels = 0, bool showDebug = true) 0168 { 0169 return compareQImagesImpl(pt, image1, image2, fuzzy, fuzzyAlpha, maxNumFailingPixels, showDebug, false); 0170 } 0171 0172 inline bool compareQImagesPremultiplied(QPoint & pt, const QImage & image1, const QImage & image2, int fuzzy = 0, int fuzzyAlpha = 0, int maxNumFailingPixels = 0, bool showDebug = true) 0173 { 0174 return compareQImagesImpl(pt, image1, image2, fuzzy, fuzzyAlpha, maxNumFailingPixels, showDebug, true); 0175 } 0176 0177 inline bool checkQImageImpl(bool externalTest, 0178 const QImage &srcImage, const QString &testName, 0179 const QString &prefix, const QString &name, 0180 int fuzzy, int fuzzyAlpha, int maxNumFailingPixels, bool premultipliedMode) 0181 { 0182 QImage image = srcImage.convertToFormat(QImage::Format_ARGB32); 0183 0184 if (fuzzyAlpha == -1) { 0185 fuzzyAlpha = fuzzy; 0186 } 0187 0188 QString filename(prefix + "_" + name + ".png"); 0189 QString dumpName(prefix + "_" + name + "_expected.png"); 0190 0191 const QString standardPath = 0192 testName + '/' + 0193 prefix + '/' + filename; 0194 0195 QString fullPath = fetchDataFileLazy(standardPath, externalTest); 0196 0197 if (fullPath.isEmpty() || !QFileInfo(fullPath).exists()) { 0198 // Try without the testname subdirectory 0199 fullPath = fetchDataFileLazy(prefix + '/' + 0200 filename, 0201 externalTest); 0202 } 0203 0204 if (fullPath.isEmpty() || !QFileInfo(fullPath).exists()) { 0205 // Try without the prefix subdirectory 0206 fullPath = fetchDataFileLazy(testName + '/' + 0207 filename, 0208 externalTest); 0209 } 0210 0211 if (!QFileInfo(fullPath).exists()) { 0212 fullPath = ""; 0213 } 0214 0215 bool canSkipExternalTest = fullPath.isEmpty() && externalTest; 0216 QImage ref(fullPath); 0217 0218 bool valid = true; 0219 QPoint t; 0220 if(!compareQImagesImpl(t, image, ref, fuzzy, fuzzyAlpha, maxNumFailingPixels, true, premultipliedMode)) { 0221 bool saveStandardResults = true; 0222 0223 if (canSkipExternalTest) { 0224 static QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); 0225 static QString writeUnittestsVar = "KRITA_WRITE_UNITTESTS"; 0226 0227 int writeUnittests = env.value(writeUnittestsVar, "0").toInt(); 0228 if (writeUnittests) { 0229 QString path = fetchExternalDataFileName(standardPath); 0230 0231 QFileInfo pathInfo(path); 0232 QDir directory; 0233 directory.mkpath(pathInfo.path()); 0234 0235 qDebug() << "--- Saving reference image:" << name << path; 0236 image.save(path); 0237 saveStandardResults = false; 0238 0239 } else { 0240 qDebug() << "--- External image not found. Skipping..." << name; 0241 } 0242 } else { 0243 qDebug() << "--- Wrong image:" << name; 0244 valid = false; 0245 } 0246 0247 if (saveStandardResults) { 0248 image.save(QString(FILES_OUTPUT_DIR) + '/' + filename); 0249 ref.save(QString(FILES_OUTPUT_DIR) + '/' + dumpName); 0250 } 0251 } 0252 0253 return valid; 0254 } 0255 0256 inline bool checkQImage(const QImage &image, const QString &testName, 0257 const QString &prefix, const QString &name, 0258 int fuzzy = 0, int fuzzyAlpha = -1, int maxNumFailingPixels = 0) 0259 { 0260 return checkQImageImpl(false, image, testName, 0261 prefix, name, 0262 fuzzy, fuzzyAlpha, maxNumFailingPixels, false); 0263 } 0264 0265 inline bool checkQImagePremultiplied(const QImage &image, const QString &testName, 0266 const QString &prefix, const QString &name, 0267 int fuzzy = 0, int fuzzyAlpha = -1, int maxNumFailingPixels = 0) 0268 { 0269 return checkQImageImpl(false, image, testName, 0270 prefix, name, 0271 fuzzy, fuzzyAlpha, maxNumFailingPixels, true); 0272 } 0273 0274 0275 inline bool checkQImageExternal(const QImage &image, const QString &testName, 0276 const QString &prefix, const QString &name, 0277 int fuzzy = 0, int fuzzyAlpha = -1, int maxNumFailingPixels = 0) 0278 { 0279 return checkQImageImpl(true, image, testName, 0280 prefix, name, 0281 fuzzy, fuzzyAlpha, maxNumFailingPixels, false); 0282 } 0283 0284 } 0285 0286 #endif // FILES_OUTPUT_DIR 0287 0288 #endif // QIMAGE_TEST_UTIL_H 0289