1 /*
2  *  Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  */
18 
19 #ifndef TEST_UTIL
20 #define TEST_UTIL
21 
22 #include <QProcessEnvironment>
23 
24 #include <QList>
25 #include <QTime>
26 #include <QDir>
27 
28 #include <KoConfig.h>
29 #include <KoColorSpace.h>
30 #include <KoColorSpaceRegistry.h>
31 #include <KoColorProfile.h>
32 #include <KoProgressProxy.h>
33 #include <kis_paint_device.h>
34 #include <kis_node.h>
35 #include <kis_undo_adapter.h>
36 #include "kis_node_graph_listener.h"
37 #include "kis_iterator_ng.h"
38 #include "kis_image.h"
39 #include "testing_nodes.h"
40 
41 #include "kistest.h"
42 
43 #ifndef FILES_DATA_DIR
44 #define FILES_DATA_DIR "."
45 #endif
46 
47 #ifndef FILES_DEFAULT_DATA_DIR
48 #define FILES_DEFAULT_DATA_DIR "."
49 #endif
50 
51 #include "qimage_test_util.h"
52 
53 #define KIS_COMPARE_RF(expr, ref) \
54     if ((expr) != (ref)) { \
55         qDebug() << "Compared values are not the same at line" << __LINE__; \
56         qDebug() << "    Actual  : " << #expr << "=" << (expr); \
57         qDebug() << "    Expected: " << #ref << "=" << (ref); \
58         return false; \
59     }
60 
61 /**
62  * Routines that are useful for writing efficient tests
63  */
64 
65 namespace TestUtil
66 {
67 
findNode(KisNodeSP root,const QString & name)68 inline KisNodeSP findNode(KisNodeSP root, const QString &name) {
69     if(root->name() == name) return root;
70 
71     KisNodeSP child = root->firstChild();
72     while (child) {
73         if((root = findNode(child, name))) return root;
74         child = child->nextSibling();
75     }
76 
77     return KisNodeSP();
78 }
79 
80 inline void dumpNodeStack(KisNodeSP node, QString prefix = QString("\t"))
81 {
82     qDebug() << node->name();
83     KisNodeSP child = node->firstChild();
84 
85     while (child) {
86 
87         if (child->childCount() > 0) {
88             dumpNodeStack(child, prefix + "\t");
89         } else {
90             qDebug() << prefix << child->name();
91         }
92         child = child->nextSibling();
93     }
94 }
95 
96 class TestProgressBar : public KoProgressProxy {
97 public:
TestProgressBar()98     TestProgressBar()
99         : m_min(0), m_max(0), m_value(0)
100     {}
101 
maximum()102     int maximum() const override {
103         return m_max;
104     }
setValue(int value)105     void setValue(int value) override {
106         m_value = value;
107     }
setRange(int min,int max)108     void setRange(int min, int max) override {
109         m_min = min;
110         m_max = max;
111     }
setFormat(const QString & format)112     void setFormat(const QString &format) override {
113         m_format = format;
114     }
115 
setAutoNestedName(const QString & name)116     void setAutoNestedName(const QString &name) override {
117         m_autoNestedName = name;
118         KoProgressProxy::setAutoNestedName(name);
119     }
120 
min()121     int min() { return m_min; }
max()122     int max() { return m_max; }
value()123     int value() { return m_value; }
format()124     QString format() { return m_format; }
autoNestedName()125     QString autoNestedName() { return m_autoNestedName; }
126 
127 
128 private:
129     int m_min;
130     int m_max;
131     int m_value;
132     QString m_format;
133     QString m_autoNestedName;
134 };
135 
comparePaintDevices(QPoint & pt,const KisPaintDeviceSP dev1,const KisPaintDeviceSP dev2)136 inline bool comparePaintDevices(QPoint & pt, const KisPaintDeviceSP dev1, const KisPaintDeviceSP dev2)
137 {
138     //     QTime t;
139     //     t.start();
140 
141     QRect rc1 = dev1->exactBounds();
142     QRect rc2 = dev2->exactBounds();
143 
144     if (rc1 != rc2) {
145         pt.setX(-1);
146         pt.setY(-1);
147     }
148 
149     KisHLineConstIteratorSP iter1 = dev1->createHLineConstIteratorNG(0, 0, rc1.width());
150     KisHLineConstIteratorSP iter2 = dev2->createHLineConstIteratorNG(0, 0, rc1.width());
151 
152     int pixelSize = dev1->pixelSize();
153 
154     for (int y = 0; y < rc1.height(); ++y) {
155 
156         do {
157             if (memcmp(iter1->oldRawData(), iter2->oldRawData(), pixelSize) != 0)
158                 return false;
159         } while (iter1->nextPixel() && iter2->nextPixel());
160 
161         iter1->nextRow();
162         iter2->nextRow();
163     }
164     //     qDebug() << "comparePaintDevices time elapsed:" << t.elapsed();
165     return true;
166 }
167 
168 template <typename channel_type>
169 inline bool comparePaintDevicesClever(const KisPaintDeviceSP dev1, const KisPaintDeviceSP dev2, channel_type alphaThreshold = 0)
170 {
171     QRect rc1 = dev1->exactBounds();
172     QRect rc2 = dev2->exactBounds();
173 
174     if (rc1 != rc2) {
175         qDebug() << "Devices have different size" << ppVar(rc1) << ppVar(rc2);
176         return false;
177     }
178 
179     KisHLineConstIteratorSP iter1 = dev1->createHLineConstIteratorNG(0, 0, rc1.width());
180     KisHLineConstIteratorSP iter2 = dev2->createHLineConstIteratorNG(0, 0, rc1.width());
181 
182     int pixelSize = dev1->pixelSize();
183 
184     for (int y = 0; y < rc1.height(); ++y) {
185 
186         do {
187             if (memcmp(iter1->oldRawData(), iter2->oldRawData(), pixelSize) != 0) {
188                 const channel_type* p1 = reinterpret_cast<const channel_type*>(iter1->oldRawData());
189                 const channel_type* p2 = reinterpret_cast<const channel_type*>(iter2->oldRawData());
190 
191                 if (p1[3] < alphaThreshold && p2[3] < alphaThreshold) continue;
192 
193                 qDebug() << "Failed compare paint devices:" << iter1->x() << iter1->y();
194                 qDebug() << "src:" << p1[0] << p1[1] << p1[2] << p1[3];
195                 qDebug() << "dst:" << p2[0] << p2[1] << p2[2] << p2[3];
196                 return false;
197             }
198         } while (iter1->nextPixel() && iter2->nextPixel());
199 
200         iter1->nextRow();
201         iter2->nextRow();
202     }
203 
204     return true;
205 }
206 
207 #ifdef FILES_OUTPUT_DIR
208 
209 struct ReferenceImageChecker
210 {
211     enum StorageType {
212         InternalStorage = 0,
213         ExternalStorage
214     };
215 
216     ReferenceImageChecker(const QString &prefix, const QString &testName, StorageType storageType = ExternalStorage)
m_storageTypeReferenceImageChecker217         : m_storageType(storageType),
218           m_prefix(prefix),
219           m_testName(testName),
220           m_success(true),
221           m_maxFailingPixels(100),
222           m_fuzzy(1)
223         {
224         }
225 
226 
setMaxFailingPixelsReferenceImageChecker227     void setMaxFailingPixels(int value) {
228         m_maxFailingPixels = value;
229     }
230 
setFuzzyReferenceImageChecker231     void setFuzzy(int fuzzy){
232         m_fuzzy = fuzzy;
233     }
234 
testPassedReferenceImageChecker235     bool testPassed() const {
236         return m_success;
237     }
238 
checkDeviceReferenceImageChecker239     inline bool checkDevice(KisPaintDeviceSP device, KisImageSP image, const QString &caseName) {
240         bool result = false;
241 
242 
243         if (m_storageType == ExternalStorage) {
244             result = checkQImageExternal(device->convertToQImage(0, image->bounds()),
245                                          m_testName,
246                                          m_prefix,
247                                          caseName, m_fuzzy, m_fuzzy, m_maxFailingPixels);
248         } else {
249             result = checkQImage(device->convertToQImage(0, image->bounds()),
250                                  m_testName,
251                                  m_prefix,
252                                  caseName, m_fuzzy, m_fuzzy, m_maxFailingPixels);
253         }
254 
255         m_success &= result;
256         return result;
257     }
258 
checkImageReferenceImageChecker259     inline bool checkImage(KisImageSP image, const QString &testName) {
260         bool result = checkDevice(image->projection(), image, testName);
261 
262         m_success &= result;
263         return result;
264     }
265 
266 private:
267     bool m_storageType;
268 
269     QString m_prefix;
270     QString m_testName;
271 
272     bool m_success;
273     int m_maxFailingPixels;
274     int m_fuzzy;
275 };
276 
277 
278 #endif
279 
alphaDevicePixel(KisPaintDeviceSP dev,qint32 x,qint32 y)280 inline quint8 alphaDevicePixel(KisPaintDeviceSP dev, qint32 x, qint32 y)
281 {
282     KisHLineConstIteratorSP iter = dev->createHLineConstIteratorNG(x, y, 1);
283     const quint8 *pix = iter->oldRawData();
284     return *pix;
285 }
286 
alphaDeviceSetPixel(KisPaintDeviceSP dev,qint32 x,qint32 y,quint8 s)287 inline void alphaDeviceSetPixel(KisPaintDeviceSP dev, qint32 x, qint32 y, quint8 s)
288 {
289     KisHLineIteratorSP iter = dev->createHLineIteratorNG(x, y, 1);
290     quint8 *pix = iter->rawData();
291     *pix = s;
292 }
293 
checkAlphaDeviceFilledWithPixel(KisPaintDeviceSP dev,const QRect & rc,quint8 expected)294 inline bool checkAlphaDeviceFilledWithPixel(KisPaintDeviceSP dev, const QRect &rc, quint8 expected)
295 {
296     KisHLineIteratorSP it = dev->createHLineIteratorNG(rc.x(), rc.y(), rc.width());
297 
298     for (int y = rc.y(); y < rc.y() + rc.height(); y++) {
299         for (int x = rc.x(); x < rc.x() + rc.width(); x++) {
300 
301             if(*((quint8*)it->rawData()) != expected) {
302                 errKrita << "At point:" << x << y;
303                 errKrita << "Expected pixel:" << expected;
304                 errKrita << "Actual pixel:  " << *((quint8*)it->rawData());
305                 return false;
306             }
307             it->nextPixel();
308         }
309         it->nextRow();
310     }
311     return true;
312 }
313 
314 class TestNode : public DefaultNode
315 {
316     Q_OBJECT
317 public:
clone()318     KisNodeSP clone() const override {
319         return KisNodeSP(new TestNode(*this));
320     }
321 };
322 
323 class TestGraphListener : public KisNodeGraphListener
324 {
325 public:
326 
aboutToAddANode(KisNode * parent,int index)327     void aboutToAddANode(KisNode *parent, int index) override {
328         KisNodeGraphListener::aboutToAddANode(parent, index);
329         beforeInsertRow = true;
330     }
331 
nodeHasBeenAdded(KisNode * parent,int index)332     void nodeHasBeenAdded(KisNode *parent, int index) override {
333         KisNodeGraphListener::nodeHasBeenAdded(parent, index);
334         afterInsertRow = true;
335     }
336 
aboutToRemoveANode(KisNode * parent,int index)337     void aboutToRemoveANode(KisNode *parent, int index) override {
338         KisNodeGraphListener::aboutToRemoveANode(parent, index);
339         beforeRemoveRow  = true;
340     }
341 
nodeHasBeenRemoved(KisNode * parent,int index)342     void nodeHasBeenRemoved(KisNode *parent, int index) override {
343         KisNodeGraphListener::nodeHasBeenRemoved(parent, index);
344         afterRemoveRow = true;
345     }
346 
aboutToMoveNode(KisNode * parent,int oldIndex,int newIndex)347     void aboutToMoveNode(KisNode *parent, int oldIndex, int newIndex) override {
348         KisNodeGraphListener::aboutToMoveNode(parent, oldIndex, newIndex);
349         beforeMove = true;
350     }
351 
nodeHasBeenMoved(KisNode * parent,int oldIndex,int newIndex)352     void nodeHasBeenMoved(KisNode *parent, int oldIndex, int newIndex) override {
353         KisNodeGraphListener::nodeHasBeenMoved(parent, oldIndex, newIndex);
354         afterMove = true;
355     }
356 
357     bool beforeInsertRow;
358     bool afterInsertRow;
359     bool beforeRemoveRow;
360     bool afterRemoveRow;
361     bool beforeMove;
362     bool afterMove;
363 
resetBools()364     void resetBools() {
365         beforeRemoveRow = false;
366         afterRemoveRow = false;
367         beforeInsertRow = false;
368         afterInsertRow = false;
369         beforeMove = false;
370         afterMove = false;
371     }
372 };
373 
374 }
375 
376 #include <QApplication>
377 #include <kis_paint_layer.h>
378 #include "kis_undo_stores.h"
379 #include "kis_layer_utils.h"
380 
381 namespace TestUtil {
382 
383 struct MaskParent
384 {
385     MaskParent(const QRect &_imageRect = QRect(0,0,512,512))
imageRectMaskParent386         : imageRect(_imageRect) {
387         const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
388         undoStore = new KisSurrogateUndoStore();
389         image = new KisImage(undoStore, imageRect.width(), imageRect.height(), cs, "test image");
390         layer = KisPaintLayerSP(new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8));
391         image->addNode(KisNodeSP(layer.data()));
392     }
393 
waitForImageAndShapeLayersMaskParent394     void waitForImageAndShapeLayers() {
395         qApp->processEvents();
396         image->waitForDone();
397         KisLayerUtils::forceAllDelayedNodesUpdate(image->root());
398         /**
399          * Shape updates have two chanis of compresion, 100ms each.
400          * One in KoShapeManager, the other one in KisShapeLayerCanvas.
401          * Therefore we should wait for a decent amount of time for all
402          * of them to land.
403          */
404         QTest::qWait(500);
405         image->waitForDone();
406     }
407 
408     KisSurrogateUndoStore *undoStore;
409     const QRect imageRect;
410     KisImageSP image;
411     KisPaintLayerSP layer;
412 };
413 
414 }
415 
416 namespace TestUtil {
417 
418 class MeasureAvgPortion
419 {
420 public:
MeasureAvgPortion(int period)421     MeasureAvgPortion(int period)
422         : m_period(period),
423         m_val(0),
424         m_total(0),
425         m_cycles(0)
426     {
427     }
428 
~MeasureAvgPortion()429     ~MeasureAvgPortion() {
430         printValues(true);
431     }
432 
addVal(int x)433     void addVal(int x) {
434         m_val += x;
435     }
436 
addTotal(int x)437     void addTotal(int x) {
438         m_total += x;
439         m_cycles++;
440         printValues();
441     }
442 
443 private:
444     void printValues(bool force = false) {
445         if (m_cycles > m_period || force) {
446             qDebug() << "Val / Total:" << qreal(m_val) / qreal(m_total);
447             qDebug() << "Avg. Val:   " << qreal(m_val) / m_cycles;
448             qDebug() << "Avg. Total: " << qreal(m_total) / m_cycles;
449             qDebug() << ppVar(m_val) << ppVar(m_total) << ppVar(m_cycles);
450 
451             m_val = 0;
452             m_total = 0;
453             m_cycles = 0;
454         }
455     }
456 
457 private:
458     int m_period;
459     qint64 m_val;
460     qint64 m_total;
461     qint64 m_cycles;
462 };
463 
464 struct MeasureDistributionStats {
465     MeasureDistributionStats(int numBins, const QString &name = QString())
m_numBinsMeasureDistributionStats466         : m_numBins(numBins),
467           m_name(name)
468     {
469         reset();
470     }
471 
resetMeasureDistributionStats472     void reset() {
473         m_values.clear();
474         m_values.resize(m_numBins);
475     }
476 
addValueMeasureDistributionStats477     void addValue(int value) {
478         addValue(value, 1);
479     }
480 
addValueMeasureDistributionStats481     void addValue(int value, int increment) {
482         KIS_SAFE_ASSERT_RECOVER_RETURN(value >= 0);
483 
484         if (value >= m_numBins) {
485             m_values[m_numBins - 1] += increment;
486         } else {
487             m_values[value] += increment;
488         }
489     }
490 
printMeasureDistributionStats491     void print() {
492         qCritical() << "============= Stats ==============";
493 
494         if (!m_name.isEmpty()) {
495             qCritical() << "Name:" << m_name;
496         }
497 
498         int total = 0;
499 
500         for (int i = 0; i < m_numBins; i++) {
501             total += m_values[i];
502         }
503 
504         for (int i = 0; i < m_numBins; i++) {
505             if (!m_values[i]) continue;
506 
507             const QString lastMarker = i == m_numBins - 1 ? "> " : "  ";
508 
509             const QString line =
510                 QString("  %1%2: %3 (%4%)")
511                     .arg(lastMarker)
512                     .arg(i, 3)
513                     .arg(m_values[i], 5)
514                     .arg(qreal(m_values[i]) / total * 100.0, 7, 'g', 2);
515 
516             qCritical() << qPrintable(line);
517         }
518         qCritical() << "----                          ----";
519         qCritical() << qPrintable(QString("Total: %1").arg(total));
520         qCritical() << "==================================";
521     }
522 
523 private:
524     QVector<int> m_values;
525     int m_numBins = 0;
526     QString m_name;
527 };
528 
529 QStringList getHierarchy(KisNodeSP root, const QString &prefix = "");
530 bool checkHierarchy(KisNodeSP root, const QStringList &expected);
531 
532 }
533 
534 #endif
535