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