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