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