Warning, file /graphics/krita/sdk/tests/testutil.h was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002  *  SPDX-FileCopyrightText: 2007 Boudewijn Rempt <boud@valdyas.org>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #ifndef TEST_UTIL
0008 #define TEST_UTIL
0009 
0010 #include <QProcessEnvironment>
0011 
0012 #include <simpletest.h>
0013 #include <QList>
0014 #include <QTime>
0015 #include <QDir>
0016 
0017 #include <KoResource.h>
0018 #include <KoTestConfig.h>
0019 #include <KoColorSpace.h>
0020 #include <KoColorSpaceRegistry.h>
0021 #include <KoColorProfile.h>
0022 #include <KoProgressProxy.h>
0023 #include <kis_paint_device.h>
0024 #include <kis_node.h>
0025 #include <kis_undo_adapter.h>
0026 #include "kis_node_graph_listener.h"
0027 #include "kis_iterator_ng.h"
0028 #include "kis_image.h"
0029 #include "testing_nodes.h"
0030 
0031 #ifndef FILES_DATA_DIR
0032 #define FILES_DATA_DIR "."
0033 #endif
0034 
0035 #ifndef FILES_DEFAULT_DATA_DIR
0036 #define FILES_DEFAULT_DATA_DIR "."
0037 #endif
0038 
0039 #include "qimage_test_util.h"
0040 
0041 #define KIS_COMPARE_RF(expr, ref) \
0042     if ((expr) != (ref)) { \
0043         qDebug() << "Compared values are not the same at line" << __LINE__; \
0044         qDebug() << "    Actual  : " << #expr << "=" << (expr); \
0045         qDebug() << "    Expected: " << #ref << "=" << (ref); \
0046         return false; \
0047     }
0048 
0049 /**
0050  * Routines that are useful for writing efficient tests
0051  */
0052 
0053 namespace TestUtil
0054 {
0055 
0056 inline KisNodeSP findNode(KisNodeSP root, const QString &name) {
0057     if(root->name() == name) return root;
0058 
0059     KisNodeSP child = root->firstChild();
0060     while (child) {
0061         if((root = findNode(child, name))) return root;
0062         child = child->nextSibling();
0063     }
0064 
0065     return KisNodeSP();
0066 }
0067 
0068 inline void dumpNodeStack(KisNodeSP node, QString prefix = QString("\t"))
0069 {
0070     qDebug() << node->name();
0071     KisNodeSP child = node->firstChild();
0072 
0073     while (child) {
0074 
0075         if (child->childCount() > 0) {
0076             dumpNodeStack(child, prefix + "\t");
0077         } else {
0078             qDebug() << prefix << child->name();
0079         }
0080         child = child->nextSibling();
0081     }
0082 }
0083 
0084 class TestProgressBar : public KoProgressProxy {
0085 public:
0086     TestProgressBar()
0087         : m_min(0), m_max(0), m_value(0)
0088     {}
0089 
0090     int maximum() const override {
0091         return m_max;
0092     }
0093     void setValue(int value) override {
0094         m_value = value;
0095     }
0096     void setRange(int min, int max) override {
0097         m_min = min;
0098         m_max = max;
0099     }
0100     void setFormat(const QString &format) override {
0101         m_format = format;
0102     }
0103 
0104     void setAutoNestedName(const QString &name) override {
0105         m_autoNestedName = name;
0106         KoProgressProxy::setAutoNestedName(name);
0107     }
0108 
0109     int min() { return m_min; }
0110     int max() { return m_max; }
0111     int value() { return m_value; }
0112     QString format() { return m_format; }
0113     QString autoNestedName() { return m_autoNestedName; }
0114 
0115 
0116 private:
0117     int m_min;
0118     int m_max;
0119     int m_value;
0120     QString m_format;
0121     QString m_autoNestedName;
0122 };
0123 
0124 inline bool comparePaintDevices(QPoint & pt, const KisPaintDeviceSP dev1, const KisPaintDeviceSP dev2)
0125 {
0126     //     QTime t;
0127     //     t.start();
0128 
0129     QRect rc1 = dev1->exactBounds();
0130     QRect rc2 = dev2->exactBounds();
0131 
0132     if (rc1 != rc2) {
0133         pt.setX(-1);
0134         pt.setY(-1);
0135     }
0136 
0137     KisHLineConstIteratorSP iter1 = dev1->createHLineConstIteratorNG(0, 0, rc1.width());
0138     KisHLineConstIteratorSP iter2 = dev2->createHLineConstIteratorNG(0, 0, rc1.width());
0139 
0140     int pixelSize = dev1->pixelSize();
0141 
0142     for (int y = 0; y < rc1.height(); ++y) {
0143 
0144         do {
0145             if (memcmp(iter1->oldRawData(), iter2->oldRawData(), pixelSize) != 0)
0146                 return false;
0147         } while (iter1->nextPixel() && iter2->nextPixel());
0148 
0149         iter1->nextRow();
0150         iter2->nextRow();
0151     }
0152     //     qDebug() << "comparePaintDevices time elapsed:" << t.elapsed();
0153     return true;
0154 }
0155 
0156 template <typename channel_type>
0157 inline bool comparePaintDevicesClever(const KisPaintDeviceSP dev1, const KisPaintDeviceSP dev2, channel_type alphaThreshold = 0)
0158 {
0159     QRect rc1 = dev1->exactBounds();
0160     QRect rc2 = dev2->exactBounds();
0161 
0162     if (rc1 != rc2) {
0163         qDebug() << "Devices have different size" << ppVar(rc1) << ppVar(rc2);
0164         return false;
0165     }
0166 
0167     KisHLineConstIteratorSP iter1 = dev1->createHLineConstIteratorNG(0, 0, rc1.width());
0168     KisHLineConstIteratorSP iter2 = dev2->createHLineConstIteratorNG(0, 0, rc1.width());
0169 
0170     int pixelSize = dev1->pixelSize();
0171 
0172     for (int y = 0; y < rc1.height(); ++y) {
0173 
0174         do {
0175             if (memcmp(iter1->oldRawData(), iter2->oldRawData(), pixelSize) != 0) {
0176                 const channel_type* p1 = reinterpret_cast<const channel_type*>(iter1->oldRawData());
0177                 const channel_type* p2 = reinterpret_cast<const channel_type*>(iter2->oldRawData());
0178 
0179                 if (p1[3] < alphaThreshold && p2[3] < alphaThreshold) continue;
0180 
0181                 qDebug() << "Failed compare paint devices:" << iter1->x() << iter1->y();
0182                 qDebug() << "src:" << p1[0] << p1[1] << p1[2] << p1[3];
0183                 qDebug() << "dst:" << p2[0] << p2[1] << p2[2] << p2[3];
0184                 return false;
0185             }
0186         } while (iter1->nextPixel() && iter2->nextPixel());
0187 
0188         iter1->nextRow();
0189         iter2->nextRow();
0190     }
0191 
0192     return true;
0193 }
0194 
0195 #ifdef FILES_OUTPUT_DIR
0196 
0197 struct ReferenceImageChecker
0198 {
0199     enum StorageType {
0200         InternalStorage = 0,
0201         ExternalStorage
0202     };
0203 
0204     ReferenceImageChecker(const QString &prefix, const QString &testName, StorageType storageType = ExternalStorage)
0205         : m_storageType(storageType),
0206           m_prefix(prefix),
0207           m_testName(testName),
0208           m_success(true),
0209           m_maxFailingPixels(100),
0210           m_fuzzy(1)
0211         {
0212         }
0213 
0214 
0215     void setMaxFailingPixels(int value) {
0216         m_maxFailingPixels = value;
0217     }
0218 
0219     void setFuzzy(int fuzzy){
0220         m_fuzzy = fuzzy;
0221     }
0222 
0223     bool testPassed() const {
0224         return m_success;
0225     }
0226 
0227     inline bool checkDevice(KisPaintDeviceSP device, KisImageSP image, const QString &caseName) {
0228         bool result = false;
0229 
0230 
0231         if (m_storageType == ExternalStorage) {
0232             result = checkQImageExternal(device->convertToQImage(0, image->bounds()),
0233                                          m_testName,
0234                                          m_prefix,
0235                                          caseName, m_fuzzy, m_fuzzy, m_maxFailingPixels);
0236         } else {
0237             result = checkQImage(device->convertToQImage(0, image->bounds()),
0238                                  m_testName,
0239                                  m_prefix,
0240                                  caseName, m_fuzzy, m_fuzzy, m_maxFailingPixels);
0241         }
0242 
0243         m_success &= result;
0244         return result;
0245     }
0246 
0247     inline bool checkImage(KisImageSP image, const QString &testName) {
0248         bool result = checkDevice(image->projection(), image, testName);
0249 
0250         m_success &= result;
0251         return result;
0252     }
0253 
0254 private:
0255     bool m_storageType;
0256 
0257     QString m_prefix;
0258     QString m_testName;
0259 
0260     bool m_success;
0261     int m_maxFailingPixels;
0262     int m_fuzzy;
0263 };
0264 
0265 
0266 #endif
0267 
0268 inline quint8 alphaDevicePixel(KisPaintDeviceSP dev, qint32 x, qint32 y)
0269 {
0270     KisHLineConstIteratorSP iter = dev->createHLineConstIteratorNG(x, y, 1);
0271     const quint8 *pix = iter->oldRawData();
0272     return *pix;
0273 }
0274 
0275 inline void alphaDeviceSetPixel(KisPaintDeviceSP dev, qint32 x, qint32 y, quint8 s)
0276 {
0277     KisHLineIteratorSP iter = dev->createHLineIteratorNG(x, y, 1);
0278     quint8 *pix = iter->rawData();
0279     *pix = s;
0280 }
0281 
0282 inline bool checkAlphaDeviceFilledWithPixel(KisPaintDeviceSP dev, const QRect &rc, quint8 expected)
0283 {
0284     KisHLineIteratorSP it = dev->createHLineIteratorNG(rc.x(), rc.y(), rc.width());
0285 
0286     for (int y = rc.y(); y < rc.y() + rc.height(); y++) {
0287         for (int x = rc.x(); x < rc.x() + rc.width(); x++) {
0288 
0289             if(*((quint8*)it->rawData()) != expected) {
0290                 errKrita << "At point:" << x << y;
0291                 errKrita << "Expected pixel:" << expected;
0292                 errKrita << "Actual pixel:  " << *((quint8*)it->rawData());
0293                 return false;
0294             }
0295             it->nextPixel();
0296         }
0297         it->nextRow();
0298     }
0299     return true;
0300 }
0301 
0302 class TestNode : public DefaultNode
0303 {
0304     Q_OBJECT
0305 public:
0306     KisNodeSP clone() const override {
0307         return KisNodeSP(new TestNode(*this));
0308     }
0309 };
0310 
0311 class TestGraphListener : public KisNodeGraphListener
0312 {
0313 public:
0314 
0315     void aboutToAddANode(KisNode *parent, int index) override {
0316         KisNodeGraphListener::aboutToAddANode(parent, index);
0317         beforeInsertRow = true;
0318     }
0319 
0320     void nodeHasBeenAdded(KisNode *parent, int index) override {
0321         KisNodeGraphListener::nodeHasBeenAdded(parent, index);
0322         afterInsertRow = true;
0323     }
0324 
0325     void aboutToRemoveANode(KisNode *parent, int index) override {
0326         KisNodeGraphListener::aboutToRemoveANode(parent, index);
0327         beforeRemoveRow  = true;
0328     }
0329 
0330     void nodeHasBeenRemoved(KisNode *parent, int index) override {
0331         KisNodeGraphListener::nodeHasBeenRemoved(parent, index);
0332         afterRemoveRow = true;
0333     }
0334 
0335     void aboutToMoveNode(KisNode *parent, int oldIndex, int newIndex) override {
0336         KisNodeGraphListener::aboutToMoveNode(parent, oldIndex, newIndex);
0337         beforeMove = true;
0338     }
0339 
0340     void nodeHasBeenMoved(KisNode *parent, int oldIndex, int newIndex) override {
0341         KisNodeGraphListener::nodeHasBeenMoved(parent, oldIndex, newIndex);
0342         afterMove = true;
0343     }
0344 
0345     bool beforeInsertRow;
0346     bool afterInsertRow;
0347     bool beforeRemoveRow;
0348     bool afterRemoveRow;
0349     bool beforeMove;
0350     bool afterMove;
0351 
0352     void resetBools() {
0353         beforeRemoveRow = false;
0354         afterRemoveRow = false;
0355         beforeInsertRow = false;
0356         afterInsertRow = false;
0357         beforeMove = false;
0358         afterMove = false;
0359     }
0360 };
0361 
0362 }
0363 
0364 #include <QApplication>
0365 #include <kis_paint_layer.h>
0366 #include "kis_undo_stores.h"
0367 #include "kis_layer_utils.h"
0368 
0369 namespace TestUtil {
0370 
0371 struct MaskParent
0372 {
0373     MaskParent(const QRect &_imageRect = QRect(0,0,512,512))
0374         : imageRect(_imageRect) {
0375         const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
0376         undoStore = new KisSurrogateUndoStore();
0377         image = new KisImage(undoStore, imageRect.width(), imageRect.height(), cs, "test image");
0378         layer = KisPaintLayerSP(new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8));
0379         image->addNode(KisNodeSP(layer.data()));
0380     }
0381 
0382     void waitForImageAndShapeLayers() {
0383         qApp->processEvents();
0384         image->waitForDone();
0385         KisLayerUtils::forceAllDelayedNodesUpdate(image->root());
0386         /**
0387          * Shape updates have two channels of compression, 100ms each.
0388          * One in KoShapeManager, the other one in KisShapeLayerCanvas.
0389          * Therefore we should wait for a decent amount of time for all
0390          * of them to land.
0391          */
0392         QTest::qWait(500);
0393         image->waitForDone();
0394     }
0395 
0396     KisSurrogateUndoStore *undoStore;
0397     const QRect imageRect;
0398     KisImageSP image;
0399     KisPaintLayerSP layer;
0400 };
0401 
0402 }
0403 
0404 namespace TestUtil {
0405 
0406 class MeasureAvgPortion
0407 {
0408 public:
0409     MeasureAvgPortion(int period)
0410         : m_period(period),
0411         m_val(0),
0412         m_total(0),
0413         m_cycles(0)
0414     {
0415     }
0416 
0417     ~MeasureAvgPortion() {
0418         printValues(true);
0419     }
0420 
0421     void addVal(int x) {
0422         m_val += x;
0423     }
0424 
0425     void addTotal(int x) {
0426         m_total += x;
0427         m_cycles++;
0428         printValues();
0429     }
0430 
0431 private:
0432     void printValues(bool force = false) {
0433         if (m_cycles > m_period || force) {
0434             qDebug() << "Val / Total:" << qreal(m_val) / qreal(m_total);
0435             qDebug() << "Avg. Val:   " << qreal(m_val) / m_cycles;
0436             qDebug() << "Avg. Total: " << qreal(m_total) / m_cycles;
0437             qDebug() << ppVar(m_val) << ppVar(m_total) << ppVar(m_cycles);
0438 
0439             m_val = 0;
0440             m_total = 0;
0441             m_cycles = 0;
0442         }
0443     }
0444 
0445 private:
0446     int m_period;
0447     qint64 m_val;
0448     qint64 m_total;
0449     qint64 m_cycles;
0450 };
0451 
0452 struct MeasureDistributionStats {
0453     MeasureDistributionStats(int numBins, const QString &name = QString())
0454         : m_numBins(numBins),
0455           m_name(name)
0456     {
0457         reset();
0458     }
0459 
0460     void reset() {
0461         m_values.clear();
0462         m_values.resize(m_numBins);
0463     }
0464 
0465     void addValue(int value) {
0466         addValue(value, 1);
0467     }
0468 
0469     void addValue(int value, int increment) {
0470         KIS_SAFE_ASSERT_RECOVER_RETURN(value >= 0);
0471 
0472         if (value >= m_numBins) {
0473             m_values[m_numBins - 1] += increment;
0474         } else {
0475             m_values[value] += increment;
0476         }
0477     }
0478 
0479     void print() {
0480         qCritical() << "============= Stats ==============";
0481 
0482         if (!m_name.isEmpty()) {
0483             qCritical() << "Name:" << m_name;
0484         }
0485 
0486         int total = 0;
0487 
0488         for (int i = 0; i < m_numBins; i++) {
0489             total += m_values[i];
0490         }
0491 
0492         for (int i = 0; i < m_numBins; i++) {
0493             if (!m_values[i]) continue;
0494 
0495             const QString lastMarker = i == m_numBins - 1 ? "> " : "  ";
0496 
0497             const QString line =
0498                 QString("  %1%2: %3 (%4%)")
0499                     .arg(lastMarker)
0500                     .arg(i, 3)
0501                     .arg(m_values[i], 5)
0502                     .arg(qreal(m_values[i]) / total * 100.0, 7, 'g', 2);
0503 
0504             qCritical() << qPrintable(line);
0505         }
0506         qCritical() << "----                          ----";
0507         qCritical() << qPrintable(QString("Total: %1").arg(total));
0508         qCritical() << "==================================";
0509     }
0510 
0511 private:
0512     QVector<int> m_values;
0513     int m_numBins = 0;
0514     QString m_name;
0515 };
0516 
0517 QStringList getHierarchy(KisNodeSP root, const QString &prefix = "");
0518 bool checkHierarchy(KisNodeSP root, const QStringList &expected);
0519 
0520 }
0521 
0522 #endif