File indexing completed on 2024-12-22 04:10:23

0001 /*
0002  *  SPDX-FileCopyrightText: 2007 Boudewijn Rempt <boud@valdyas.org>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "kis_paint_device_test.h"
0008 #include "kis_image_config.h"
0009 #include <simpletest.h>
0010 
0011 #include <QElapsedTimer>
0012 
0013 #include <KoColor.h>
0014 #include <KoColorSpace.h>
0015 #include <KoColorSpaceRegistry.h>
0016 #include <KoStore.h>
0017 
0018 #include "kis_paint_device_writer.h"
0019 #include "kis_painter.h"
0020 #include "kis_types.h"
0021 #include "kis_paint_device.h"
0022 #include "kis_layer.h"
0023 #include "kis_paint_layer.h"
0024 #include "kis_selection.h"
0025 #include "kis_datamanager.h"
0026 #include "kis_global.h"
0027 #include <testutil.h>
0028 #include "kis_transaction.h"
0029 #include "kis_image.h"
0030 #include "config-limit-long-tests.h"
0031 #include "testimage.h"
0032 #include "kis_default_bounds.h"
0033 
0034 
0035 class KisFakePaintDeviceWriter : public KisPaintDeviceWriter {
0036 public:
0037     KisFakePaintDeviceWriter(KoStore *store)
0038         : m_store(store)
0039     {
0040     }
0041 
0042     bool write(const QByteArray &data) override {
0043         return (m_store->write(data) == data.size());
0044     }
0045 
0046     bool write(const char* data, qint64 length) override {
0047         return (m_store->write(data, length) == length);
0048     }
0049 
0050     KoStore *m_store;
0051 };
0052 
0053 void KisPaintDeviceTest::testCreation()
0054 {
0055     const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
0056     KisPaintDeviceSP dev = new KisPaintDevice(cs);
0057     QVERIFY(dev->objectName().isEmpty());
0058 
0059     dev = new KisPaintDevice(cs);
0060     QVERIFY(*dev->colorSpace() == *cs);
0061     QVERIFY(dev->x() == 0);
0062     QVERIFY(dev->y() == 0);
0063     QVERIFY(dev->pixelSize() == cs->pixelSize());
0064     QVERIFY(dev->channelCount() == cs->channelCount());
0065     QVERIFY(dev->dataManager() != 0);
0066 
0067     KisImageSP image = new KisImage(0, 1000, 1000, cs, "merge test");
0068     KisPaintLayerSP layer = new KisPaintLayer(image, "bla", 125);
0069 
0070     dev = new KisPaintDevice(layer.data(), cs);
0071     QVERIFY(*dev->colorSpace() == *cs);
0072     QVERIFY(dev->x() == 0);
0073     QVERIFY(dev->y() == 0);
0074     QVERIFY(dev->pixelSize() == cs->pixelSize());
0075     QVERIFY(dev->channelCount() == cs->channelCount());
0076     QVERIFY(dev->dataManager() != 0);
0077 
0078     // Let the layer go out of scope and see what happens
0079     {
0080         KisPaintLayerSP l2 = new KisPaintLayer(image, "blabla", 250);
0081         dev = new KisPaintDevice(l2.data(), cs);
0082     }
0083 
0084 }
0085 
0086 
0087 void KisPaintDeviceTest::testStore()
0088 {
0089 
0090     const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
0091     KisPaintDeviceSP dev = new KisPaintDevice(cs);
0092 
0093     KoStore * readStore =
0094         KoStore::createStore(QString(FILES_DATA_DIR) + '/' + "store_test.kra", KoStore::Read);
0095     readStore->open("built image/layers/layer0");
0096     QVERIFY(dev->read(readStore->device()));
0097     readStore->close();
0098     delete readStore;
0099 
0100     QVERIFY(dev->exactBounds() == QRect(0, 0, 100, 100));
0101 
0102     KoStore * writeStore =
0103         KoStore::createStore(QString(FILES_OUTPUT_DIR) + '/' + "store_test_out.kra", KoStore::Write);
0104     KisFakePaintDeviceWriter fakeWriter(writeStore);
0105     writeStore->open("built image/layers/layer0");
0106     QVERIFY(dev->write(fakeWriter));
0107     writeStore->close();
0108     delete writeStore;
0109 
0110     KisPaintDeviceSP dev2 = new KisPaintDevice(cs);
0111     readStore =
0112         KoStore::createStore(QString(FILES_OUTPUT_DIR) + '/' + "store_test_out.kra", KoStore::Read);
0113     readStore->open("built image/layers/layer0");
0114     QVERIFY(dev2->read(readStore->device()));
0115     readStore->close();
0116     delete readStore;
0117 
0118     QVERIFY(dev2->exactBounds() == QRect(0, 0, 100, 100));
0119 
0120     QPoint pt;
0121     if (!TestUtil::comparePaintDevices(pt, dev, dev2)) {
0122         QFAIL(QString("Loading a saved image is not pixel perfect, first different pixel: %1,%2 ").arg(pt.x()).arg(pt.y()).toLatin1());
0123     }
0124 
0125 }
0126 
0127 void KisPaintDeviceTest::testGeometry()
0128 {
0129     const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
0130     KisPaintDeviceSP dev = new KisPaintDevice(cs);
0131 
0132     quint8* pixel = new quint8[cs->pixelSize()];
0133     cs->fromQColor(Qt::white, pixel);
0134     dev->fill(0, 0, 512, 512, pixel);
0135 
0136     QCOMPARE(dev->exactBounds(), QRect(0, 0, 512, 512));
0137     QCOMPARE(dev->extent(), QRect(0, 0, 512, 512));
0138 
0139     dev->moveTo(10, 10);
0140 
0141     QCOMPARE(dev->exactBounds(), QRect(10, 10, 512, 512));
0142     QCOMPARE(dev->extent(), QRect(10, 10, 512, 512));
0143 
0144     dev->crop(50, 50, 50, 50);
0145     QCOMPARE(dev->exactBounds(), QRect(50, 50, 50, 50));
0146     QCOMPARE(dev->extent(), QRect(10, 10, 128, 128));
0147 
0148     QColor c;
0149 
0150     dev->clear(QRect(50, 50, 50, 50));
0151     dev->pixel(80, 80, &c);
0152     QVERIFY(c.alpha() == OPACITY_TRANSPARENT_U8);
0153 
0154     dev->fill(0, 0, 512, 512, pixel);
0155     dev->pixel(80, 80, &c);
0156     QVERIFY(c == Qt::white);
0157     QVERIFY(c.alpha() == OPACITY_OPAQUE_U8);
0158 
0159     dev->clear();
0160     dev->pixel(80, 80, &c);
0161     QVERIFY(c.alpha() == OPACITY_TRANSPARENT_U8);
0162 
0163     QVERIFY(dev->extent().isEmpty());
0164     QVERIFY(dev->exactBounds().isEmpty());
0165 
0166 }
0167 
0168 void KisPaintDeviceTest::testClear()
0169 {
0170     const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
0171     KisPaintDeviceSP dev = new KisPaintDevice(cs);
0172 
0173     QVERIFY(!dev->extent().isValid());
0174     QVERIFY(!dev->exactBounds().isValid());
0175 
0176     dev->clear();
0177 
0178     QVERIFY(!dev->extent().isValid());
0179     QVERIFY(!dev->exactBounds().isValid());
0180 
0181     QRect fillRect1(50, 100, 150, 100);
0182     dev->fill(fillRect1, KoColor(Qt::red, cs));
0183 
0184     QCOMPARE(dev->extent(), QRect(0, 64, 256, 192));
0185     QCOMPARE(dev->exactBounds(), fillRect1);
0186 
0187     dev->clear(QRect(100, 100, 100, 100));
0188 
0189     QCOMPARE(dev->extent(), QRect(0, 64, 256, 192));
0190     QCOMPARE(dev->exactBounds(), QRect(50, 100, 50, 100));
0191 
0192     dev->clear();
0193 
0194     QVERIFY(!dev->extent().isValid());
0195     QVERIFY(!dev->exactBounds().isValid());
0196 }
0197 
0198 void KisPaintDeviceTest::testCrop()
0199 {
0200     const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
0201     KisPaintDeviceSP dev = new KisPaintDevice(cs);
0202     quint8* pixel = new quint8[cs->pixelSize()];
0203     cs->fromQColor(Qt::white, pixel);
0204     dev->fill(-14, 8, 433, 512, pixel);
0205 
0206     QVERIFY(dev->exactBounds() == QRect(-14, 8, 433, 512));
0207 
0208     // Crop inside
0209     dev->crop(50, 50, 150, 150);
0210     QVERIFY(dev->exactBounds() == QRect(50, 50, 150, 150));
0211 
0212     // Crop outside, pd should not grow
0213     dev->crop(0, 0, 1000, 1000);
0214     QVERIFY(dev->exactBounds() == QRect(50, 50, 150, 150));
0215 }
0216 
0217 void KisPaintDeviceTest::testRoundtripReadWrite()
0218 {
0219     const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
0220     KisPaintDeviceSP dev = new KisPaintDevice(cs);
0221     QImage image(QString(FILES_DATA_DIR) + '/' + "tile.png");
0222     dev->convertFromQImage(image, 0);
0223 
0224     quint8* bytes = new quint8[cs->pixelSize() * image.width() * image.height()];
0225     memset(bytes, 0, image.width() * image.height() * dev->pixelSize());
0226     dev->readBytes(bytes, image.rect());
0227 
0228     KisPaintDeviceSP dev2 = new KisPaintDevice(cs);
0229     dev2->writeBytes(bytes, image.rect());
0230     QVERIFY(dev2->exactBounds() == image.rect());
0231 
0232     dev2->convertToQImage(0, 0, 0, image.width(), image.height()).save("readwrite.png");
0233 
0234 
0235     QPoint pt;
0236     if (!TestUtil::comparePaintDevices(pt, dev, dev2)) {
0237         QFAIL(QString("Failed round trip using readBytes and writeBytes, first different pixel: %1,%2 ").arg(pt.x()).arg(pt.y()).toLatin1());
0238     }
0239 }
0240 
0241 void logFailure(const QString & reason, const KoColorSpace * srcCs, const KoColorSpace * dstCs)
0242 {
0243     QString profile1("no profile");
0244     QString profile2("no profile");
0245     if (srcCs->profile())
0246         profile1 = srcCs->profile()->name();
0247     if (dstCs->profile())
0248         profile2 = dstCs->profile()->name();
0249 
0250     QWARN(QString("Failed %1 %2 -> %3 %4 %5")
0251           .arg(srcCs->name())
0252           .arg(profile1)
0253           .arg(dstCs->name())
0254           .arg(profile2)
0255           .arg(reason)
0256           .toLatin1());
0257 }
0258 
0259 void KisPaintDeviceTest::testColorSpaceConversion()
0260 {
0261     QImage image(QString(FILES_DATA_DIR) + '/' + "tile.png");
0262     const KoColorSpace* srcCs = KoColorSpaceRegistry::instance()->rgb8();
0263     const KoColorSpace* dstCs = KoColorSpaceRegistry::instance()->lab16();
0264     KisPaintDeviceSP dev = new KisPaintDevice(srcCs);
0265     dev->convertFromQImage(image, 0);
0266     dev->moveTo(10, 10);   // Unalign with tile boundaries
0267     KUndo2Command* cmd = new KUndo2Command();
0268     dev->convertTo(dstCs,
0269                    KoColorConversionTransformation::internalRenderingIntent(),
0270                    KoColorConversionTransformation::internalConversionFlags(),
0271                    cmd);
0272 
0273     QCOMPARE(dev->exactBounds(), QRect(10, 10, image.width(), image.height()));
0274     QCOMPARE(dev->pixelSize(), dstCs->pixelSize());
0275     QVERIFY(*dev->colorSpace() == *dstCs);
0276 
0277     cmd->redo();
0278     cmd->undo();
0279 
0280     QCOMPARE(dev->exactBounds(), QRect(10, 10, image.width(), image.height()));
0281     QCOMPARE(dev->pixelSize(), srcCs->pixelSize());
0282     QVERIFY(*dev->colorSpace() == *srcCs);
0283 
0284     delete cmd;
0285 }
0286 
0287 
0288 void KisPaintDeviceTest::testRoundtripConversion()
0289 {
0290     QImage image(QString(FILES_DATA_DIR) + '/' + "hakonepa.png");
0291     const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
0292     KisPaintDeviceSP dev = new KisPaintDevice(cs);
0293     dev->convertFromQImage(image, 0);
0294     QImage result = dev->convertToQImage(0, 0, 0, 640, 441);
0295 
0296     QPoint errpoint;
0297 
0298     if (!TestUtil::compareQImages(errpoint, image, result)) {
0299         image.save("kis_paint_device_test_test_roundtrip_qimage.png");
0300         result.save("kis_paint_device_test_test_roundtrip_result.png");
0301         QFAIL(QString("Failed to create identical image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1());
0302     }
0303 }
0304 
0305 void KisPaintDeviceTest::testFastBitBlt()
0306 {
0307     QImage image(QString(FILES_DATA_DIR) + '/' + "hakonepa.png");
0308     const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
0309     KisPaintDeviceSP dstDev = new KisPaintDevice(cs);
0310     KisPaintDeviceSP srcDev = new KisPaintDevice(cs);
0311     srcDev->convertFromQImage(image, 0);
0312 
0313     QRect cloneRect(100,100,200,200);
0314     QPoint errpoint;
0315 
0316 
0317     QVERIFY(dstDev->fastBitBltPossible(srcDev));
0318     dstDev->fastBitBlt(srcDev, cloneRect);
0319 
0320     QImage srcImage = srcDev->convertToQImage(0, cloneRect.x(), cloneRect.y(),
0321                                                cloneRect.width(), cloneRect.height());
0322     QImage dstImage = dstDev->convertToQImage(0, cloneRect.x(), cloneRect.y(),
0323                                               cloneRect.width(), cloneRect.height());
0324 
0325     if (!TestUtil::compareQImages(errpoint, srcImage, dstImage)) {
0326         QFAIL(QString("Failed to create identical image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1());
0327     }
0328 
0329     // Test Rough version
0330     dstDev->clear();
0331     dstDev->fastBitBltRough(srcDev, cloneRect);
0332 
0333     srcImage = srcDev->convertToQImage(0, cloneRect.x(), cloneRect.y(),
0334                                        cloneRect.width(), cloneRect.height());
0335     dstImage = dstDev->convertToQImage(0, cloneRect.x(), cloneRect.y(),
0336                                        cloneRect.width(), cloneRect.height());
0337 
0338     if (!TestUtil::compareQImages(errpoint, srcImage, dstImage)) {
0339         QFAIL(QString("Failed to create identical image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1());
0340     }
0341 
0342     srcDev->moveTo(10,10);
0343     QVERIFY(!dstDev->fastBitBltPossible(srcDev));
0344 }
0345 
0346 void KisPaintDeviceTest::testMakeClone()
0347 {
0348     QImage image(QString(FILES_DATA_DIR) + '/' + "hakonepa.png");
0349 
0350     const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
0351     KisPaintDeviceSP srcDev = new KisPaintDevice(cs);
0352     srcDev->convertFromQImage(image, 0);
0353     srcDev->moveTo(10,10);
0354 
0355     const KoColorSpace * weirdCS = KoColorSpaceRegistry::instance()->lab16();
0356     KisPaintDeviceSP dstDev = new KisPaintDevice(weirdCS);
0357     dstDev->moveTo(1000,1000);
0358 
0359     QVERIFY(!dstDev->fastBitBltPossible(srcDev));
0360 
0361     QRect cloneRect(100,100,200,200);
0362     QPoint errpoint;
0363 
0364     dstDev->makeCloneFrom(srcDev, cloneRect);
0365 
0366     QVERIFY(*dstDev->colorSpace() == *srcDev->colorSpace());
0367     QCOMPARE(dstDev->pixelSize(), srcDev->pixelSize());
0368     QCOMPARE(dstDev->x(), srcDev->x());
0369     QCOMPARE(dstDev->y(), srcDev->y());
0370     QCOMPARE(dstDev->exactBounds(), cloneRect);
0371 
0372     QImage srcImage = srcDev->convertToQImage(0, cloneRect.x(), cloneRect.y(),
0373                                               cloneRect.width(), cloneRect.height());
0374     QImage dstImage = dstDev->convertToQImage(0, cloneRect.x(), cloneRect.y(),
0375                                               cloneRect.width(), cloneRect.height());
0376     if (!TestUtil::compareQImages(errpoint, dstImage, srcImage)) {
0377         QFAIL(QString("Failed to create identical image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1());
0378     }
0379 }
0380 
0381 void KisPaintDeviceTest::testThumbnail()
0382 {
0383     QImage image(QString(FILES_DATA_DIR) + '/' + "hakonepa.png");
0384     const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
0385     KisPaintDeviceSP dev = new KisPaintDevice(cs);
0386     dev->convertFromQImage(image, 0);
0387     {
0388         KisPaintDeviceSP thumb = dev->createThumbnailDevice(50, 50);
0389         QRect rc = thumb->exactBounds();
0390         QVERIFY(rc.width() <= 50);
0391         QVERIFY(rc.height() <= 50);
0392     }
0393     {
0394         QImage thumb = dev->createThumbnail(50, 50);
0395         QVERIFY(!thumb.isNull());
0396         QVERIFY(thumb.width() <= 50);
0397         QVERIFY(thumb.height() <= 50);
0398         image.save("kis_paint_device_test_test_thumbnail.png");
0399     }
0400 }
0401 
0402 void KisPaintDeviceTest::testThumbnailDeviceWithOffset()
0403 {
0404     QImage image(QString(FILES_DATA_DIR) + '/' + "hakonepa.png");
0405     const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
0406     KisPaintDeviceSP dev = new KisPaintDevice(cs);
0407     dev->convertFromQImage(image, 0);
0408     dev->setX(10);
0409     dev->setY(10);
0410 
0411     QImage thumb = dev->createThumbnail(640,441,QRect(10,10,640,441));
0412 
0413     image.save("oring.png");
0414     thumb.save("thumb.png");
0415 
0416     QPoint pt;
0417     QVERIFY(TestUtil::compareQImages(pt, thumb, image));
0418 }
0419 
0420 void KisPaintDeviceTest::testCaching()
0421 {
0422     const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
0423     KisPaintDeviceSP dev = new KisPaintDevice(cs);
0424 
0425     quint8* whitePixel = new quint8[cs->pixelSize()];
0426     cs->fromQColor(Qt::white, whitePixel);
0427 
0428     quint8* blackPixel = new quint8[cs->pixelSize()];
0429     cs->fromQColor(Qt::black, blackPixel);
0430 
0431     dev->fill(0, 0, 512, 512, whitePixel);
0432     QImage thumb1 = dev->createThumbnail(50, 50);
0433     QRect exactBounds1 = dev->exactBounds();
0434 
0435     dev->fill(0, 0, 768, 768, blackPixel);
0436     dev->fill(50, 50, 462, 462, whitePixel);
0437     QImage thumb2 = dev->createThumbnail(50, 50);
0438     QRect exactBounds2 = dev->exactBounds();
0439 
0440     dev->moveTo(10, 10);
0441     QImage thumb3 = dev->createThumbnail(50, 50);
0442     QRect exactBounds3 = dev->exactBounds();
0443 
0444     dev->crop(50, 50, 50, 50);
0445     QImage thumb4 = dev->createThumbnail(50, 50);
0446     QRect exactBounds4 = dev->exactBounds();
0447 
0448     QVERIFY(thumb1 != thumb2);
0449     QVERIFY(thumb2 == thumb3); // Cache miss, but image is the same
0450     QVERIFY(thumb3 != thumb4);
0451     QVERIFY(thumb4 != thumb1);
0452 
0453     QCOMPARE(exactBounds1, QRect(0,0,512,512));
0454     QCOMPARE(exactBounds2, QRect(0,0,768,768));
0455     QCOMPARE(exactBounds3, QRect(10,10,768,768));
0456     QCOMPARE(exactBounds4, QRect(50,50,50,50));
0457 }
0458 
0459 void KisPaintDeviceTest::testRegion()
0460 {
0461     const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
0462     KisPaintDeviceSP dev = new KisPaintDevice(cs);
0463 
0464     quint8* whitePixel = new quint8[cs->pixelSize()];
0465     cs->fromQColor(Qt::white, whitePixel);
0466 
0467     dev->fill(0, 0, 10, 10, whitePixel);
0468     dev->fill(70, 70, 10, 10, whitePixel);
0469     dev->fill(129, 0, 10, 10, whitePixel);
0470     dev->fill(0, 1030, 10, 10, whitePixel);
0471 
0472     QVector<QRect> referenceRegion;
0473     referenceRegion += QRect(0,0,64,64);
0474     referenceRegion += QRect(64,64,64,64);
0475     referenceRegion += QRect(128,0,64,64);
0476     referenceRegion += QRect(0,1024,64,64);
0477 
0478     QCOMPARE(dev->exactBounds(), QRect(0,0,139,1040));
0479     QCOMPARE(dev->region(), KisRegion(referenceRegion));
0480 }
0481 
0482 void KisPaintDeviceTest::testPixel()
0483 {
0484     const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
0485     KisPaintDeviceSP dev = new KisPaintDevice(cs);
0486 
0487     QColor c = Qt::red;
0488     quint8 opacity = 125;
0489 
0490     c.setAlpha(opacity);
0491     dev->setPixel(5, 5, c);
0492 
0493     QColor c2;
0494 
0495     dev->pixel(5, 5, &c2);
0496 
0497     QVERIFY(c == c2);
0498     QVERIFY(opacity == c2.alpha());
0499 
0500 }
0501 
0502 void KisPaintDeviceTest::testPlanarReadWrite()
0503 {
0504     const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
0505     KisPaintDeviceSP dev = new KisPaintDevice(cs);
0506 
0507     quint8* pixel = new quint8[cs->pixelSize()];
0508     cs->fromQColor(QColor(255, 200, 155, 100), pixel);
0509     dev->fill(0, 0, 5000, 5000, pixel);
0510     delete[] pixel;
0511 
0512     QColor c1;
0513     dev->pixel(5, 5, &c1);
0514 
0515     QVector<quint8*> planes = dev->readPlanarBytes(500, 500, 100, 100);
0516     QVector<quint8*> swappedPlanes;
0517 
0518     QCOMPARE((int)planes.size(), (int)dev->channelCount());
0519 
0520     for (int i = 0; i < 100*100; i++) {
0521         // BGRA encoded
0522         QVERIFY(planes.at(2)[i] == 255);
0523         QVERIFY(planes.at(1)[i] == 200);
0524         QVERIFY(planes.at(0)[i] == 155);
0525         QVERIFY(planes.at(3)[i] == 100);
0526     }
0527 
0528     for (uint i = 1; i < dev->channelCount() + 1; ++i) {
0529         swappedPlanes.append(planes[dev->channelCount() - i]);
0530     }
0531 
0532     dev->writePlanarBytes(swappedPlanes, 0, 0, 100, 100);
0533 
0534     dev->convertToQImage(0, 0, 0, 1000, 1000).save("planar.png");
0535 
0536     dev->pixel(5, 5, &c1);
0537 
0538     QVERIFY(c1.red() == 200);
0539     QVERIFY(c1.green() == 255);
0540     QVERIFY(c1.blue() == 100);
0541     QVERIFY(c1.alpha() == 155);
0542 
0543     dev->pixel(75, 50, &c1);
0544 
0545     QVERIFY(c1.red() == 200);
0546     QVERIFY(c1.green() == 255);
0547     QVERIFY(c1.blue() == 100);
0548     QVERIFY(c1.alpha() == 155);
0549 
0550     // check if one of the planes is Null.
0551     Q_ASSERT(planes.size() == 4);
0552     delete [] planes[2];
0553     planes[2] = 0;
0554     dev->writePlanarBytes(planes, 0, 0, 100, 100);
0555     dev->convertToQImage(0, 0, 0, 1000, 1000).save("planar_noR.png");
0556 
0557     dev->pixel(75, 50, &c1);
0558 
0559     QCOMPARE(c1.red(), 200);
0560     QCOMPARE(c1.green(), 200);
0561     QCOMPARE(c1.blue(), 155);
0562     QCOMPARE(c1.alpha(), 100);
0563 
0564     QVector<quint8*>::iterator i;
0565     for (i = planes.begin(); i != planes.end(); ++i)
0566     {
0567         delete [] *i;
0568     }
0569     swappedPlanes.clear();
0570 }
0571 
0572 void KisPaintDeviceTest::testBltPerformance()
0573 {
0574     QImage image(QString(FILES_DATA_DIR) + '/' + "hakonepa_transparent.png");
0575     const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
0576     KisPaintDeviceSP fdev = new KisPaintDevice(cs);
0577     fdev->convertFromQImage(image, 0);
0578 
0579     KisPaintDeviceSP dev = new KisPaintDevice(cs);
0580     dev->fill(0, 0, 640, 441, KoColor(Qt::white, cs).data());
0581 
0582     QElapsedTimer t;
0583     t.start();
0584 
0585     int x;
0586 #ifdef LIMIT_LONG_TESTS
0587     int steps = 10;
0588 #else
0589     int steps = 1000;
0590 #endif
0591     for (x = 0; x < steps; ++x) {
0592         KisPainter gc(dev);
0593         gc.bitBlt(QPoint(0, 0), fdev, image.rect());
0594     }
0595 
0596     dbgKrita << x
0597     << "blits"
0598     << " done in "
0599     << t.elapsed()
0600     << "ms";
0601 
0602 
0603 }
0604 
0605 void KisPaintDeviceTest::testDeviceDuplication()
0606 {
0607     QRect fillRect(0,0,64,64);
0608     quint8 fillPixel[4]={255,255,255,255};
0609     QRect clearRect(10,10,20,20);
0610     QImage referenceImage;
0611     QImage resultImage;
0612 
0613     const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
0614     KisPaintDeviceSP device = new KisPaintDevice(cs);
0615 
0616 //    dbgKrita<<"FILLING";
0617     device->fill(fillRect.left(), fillRect.top(),
0618                  fillRect.width(), fillRect.height(),fillPixel);
0619     referenceImage = device->convertToQImage(0);
0620 
0621 
0622     KisTransaction transaction1(device);
0623 //    dbgKrita<<"CLEARING";
0624     device->clear(clearRect);
0625 
0626     transaction1.revert();
0627 
0628     resultImage = device->convertToQImage(0);
0629     QVERIFY(resultImage == referenceImage);
0630 
0631     KisPaintDeviceSP clone =  new KisPaintDevice(*device);
0632 
0633     KisTransaction transaction(clone);
0634 //    dbgKrita<<"CLEARING";
0635     clone->clear(clearRect);
0636 
0637     transaction.revert();
0638 
0639     resultImage = clone->convertToQImage(0);
0640     QVERIFY(resultImage == referenceImage);
0641 
0642 }
0643 
0644 void KisPaintDeviceTest::testTranslate()
0645 {
0646     QRect fillRect(0,0,64,64);
0647     quint8 fillPixel[4]={255,255,255,255};
0648 
0649     const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
0650     KisPaintDeviceSP device = new KisPaintDevice(cs);
0651 
0652     device->fill(fillRect.left(), fillRect.top(),
0653                  fillRect.width(), fillRect.height(),fillPixel);
0654 
0655     device->setX(-10);
0656     device->setY(10);
0657     QCOMPARE(device->exactBounds(), QRect(-10,10,64,64));
0658     QCOMPARE(device->extent(), QRect(-10,10,64,64));
0659 }
0660 
0661 void KisPaintDeviceTest::testOpacity()
0662 {
0663     // blt a semi-transparent image on a white paint device
0664 
0665     QImage image(QString(FILES_DATA_DIR) + '/' + "hakonepa_transparent.png");
0666     const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
0667     KisPaintDeviceSP fdev = new KisPaintDevice(cs);
0668     fdev->convertFromQImage(image, 0);
0669 
0670     KisPaintDeviceSP dev = new KisPaintDevice(cs);
0671     dev->fill(0, 0, 640, 441, KoColor(Qt::white, cs).data());
0672     KisPainter gc(dev);
0673     gc.bitBlt(QPoint(0, 0), fdev, image.rect());
0674 
0675     QImage result = dev->convertToQImage(0, 0, 0, 640, 441);
0676     QImage checkResult(QString(FILES_DATA_DIR) + '/' + "hakonepa_transparent_result.png");
0677     QPoint errpoint;
0678 
0679     if (!TestUtil::compareQImages(errpoint, checkResult, result, 1)) {
0680         checkResult.save("kis_paint_device_test_test_blt_fixed_opacity_expected.png");
0681         result.save("kis_paint_device_test_test_blt_fixed_opacity_result.png");
0682         QFAIL(QString("Failed to create identical image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1());
0683     }
0684 }
0685 
0686 void KisPaintDeviceTest::testExactBoundsWeirdNullAlphaCase()
0687 {
0688     const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
0689     KisPaintDeviceSP dev = new KisPaintDevice(cs);
0690 
0691     QVERIFY(dev->exactBounds().isEmpty());
0692 
0693     dev->fill(QRect(10,10,10,10), KoColor(Qt::white, cs));
0694 
0695     QCOMPARE(dev->exactBounds(), QRect(10,10,10,10));
0696 
0697     const quint8 weirdPixelData[4] = {0,10,0,0};
0698     KoColor weirdColor(weirdPixelData, cs);
0699     dev->setPixel(6,6,weirdColor);
0700 
0701     // such weird pixels should not change our opinion about
0702     // device's size
0703     QCOMPARE(dev->exactBounds(), QRect(10,10,10,10));
0704 }
0705 
0706 void KisPaintDeviceTest::benchmarkExactBoundsNullDefaultPixel()
0707 {
0708     const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
0709     KisPaintDeviceSP dev = new KisPaintDevice(cs);
0710 
0711     QVERIFY(dev->exactBounds().isEmpty());
0712 
0713     QRect fillRect(60,60, 1930, 1930);
0714 
0715     dev->fill(fillRect, KoColor(Qt::white, cs));
0716 
0717     QRect measuredRect;
0718 
0719     QBENCHMARK {
0720         // invalidate the cache
0721         dev->setDirty();
0722         measuredRect = dev->exactBounds();
0723     }
0724 
0725     QCOMPARE(measuredRect, fillRect);
0726 }
0727 
0728 void KisPaintDeviceTest::testAmortizedExactBounds()
0729 {
0730     const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
0731     KisPaintDeviceSP dev = new KisPaintDevice(cs);
0732 
0733     QVERIFY(dev->exactBounds().isEmpty());
0734 
0735     QRect fillRect(60,60, 833, 833);
0736     QRect extent(0,0,896,896);
0737 
0738     dev->fill(fillRect, KoColor(Qt::white, cs));
0739 
0740     QEXPECT_FAIL("", "Expecting the extent, we somehow get the fillrect", Continue);
0741     QCOMPARE(dev->exactBounds(), extent);
0742     QCOMPARE(dev->extent(), extent);
0743 
0744     QCOMPARE(dev->exactBoundsAmortized(), fillRect);
0745 
0746     dev->setDirty();
0747     QEXPECT_FAIL("", "Expecting the fillRect, we somehow get the extent", Continue);
0748     QCOMPARE(dev->exactBoundsAmortized(), fillRect);
0749 
0750     dev->setDirty();
0751     QCOMPARE(dev->exactBoundsAmortized(), extent);
0752 
0753     QTest::qSleep(1100);
0754     QEXPECT_FAIL("", "Expecting the fillRect, we somehow get the extent", Continue);
0755     QCOMPARE(dev->exactBoundsAmortized(), fillRect);
0756 }
0757 
0758 void KisPaintDeviceTest::testNonDefaultPixelArea()
0759 {
0760     const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
0761     KisPaintDeviceSP dev = new KisPaintDevice(cs);
0762 
0763     QVERIFY(dev->exactBounds().isEmpty());
0764     QVERIFY(dev->nonDefaultPixelArea().isEmpty());
0765 
0766     KoColor defPixel(Qt::red, cs);
0767     dev->setDefaultPixel(defPixel);
0768 
0769     QCOMPARE(dev->exactBounds(), KisDefaultBounds::infiniteRect);
0770     QVERIFY(dev->nonDefaultPixelArea().isEmpty());
0771 
0772     QRect fillRect(10,11,18,14);
0773 
0774     dev->fill(fillRect, KoColor(Qt::white, cs));
0775 
0776     QCOMPARE(dev->exactBounds(), KisDefaultBounds::infiniteRect);
0777     QCOMPARE(dev->nonDefaultPixelArea(), fillRect);
0778 
0779 
0780     // non-default pixel variant should also handle weird pixels
0781 
0782     const quint8 weirdPixelData[4] = {0,10,0,0};
0783     KoColor weirdColor(weirdPixelData, cs);
0784     dev->setPixel(100,100,weirdColor);
0785 
0786     // such weird pixels should not change our opinion about
0787     // device's size
0788     QCOMPARE(dev->exactBounds(), KisDefaultBounds::infiniteRect);
0789     QCOMPARE(dev->nonDefaultPixelArea(), fillRect | QRect(100,100,1,1));
0790 }
0791 
0792 void KisPaintDeviceTest::testExactBoundsNonTransparent()
0793 {
0794     const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
0795     KisImageSP image = new KisImage(0, 1000, 1000, cs, "merge test");
0796     KisPaintLayerSP layer = new KisPaintLayer(image, "bla", 125);
0797     KisPaintDeviceSP dev = layer->paintDevice();
0798 
0799     QVERIFY(dev);
0800 
0801     QRect imageRect(0,0,1000,1000);
0802 
0803     KoColor defPixel(Qt::red, cs);
0804     dev->setDefaultPixel(defPixel);
0805 
0806     QCOMPARE(dev->exactBounds(), imageRect);
0807     QVERIFY(dev->nonDefaultPixelArea().isEmpty());
0808 
0809     KoColor fillPixel(Qt::white, cs);
0810 
0811     dev->fill(imageRect, KoColor(Qt::white, cs));
0812     QCOMPARE(dev->exactBounds(), imageRect);
0813     QCOMPARE(dev->nonDefaultPixelArea(), imageRect);
0814 
0815     dev->fill(QRect(1000,0, 1, 1000), KoColor(Qt::white, cs));
0816     QCOMPARE(dev->exactBounds(), QRect(0,0,1001,1000));
0817     QCOMPARE(dev->nonDefaultPixelArea(), QRect(0,0,1001,1000));
0818 
0819     dev->fill(QRect(0,1000, 1000, 1), KoColor(Qt::white, cs));
0820     QCOMPARE(dev->exactBounds(), QRect(0,0,1001,1001));
0821     QCOMPARE(dev->nonDefaultPixelArea(), QRect(0,0,1001,1001));
0822 
0823     dev->fill(QRect(0,-1, 1000, 1), KoColor(Qt::white, cs));
0824     QCOMPARE(dev->exactBounds(), QRect(0,-1,1001,1002));
0825     QCOMPARE(dev->nonDefaultPixelArea(), QRect(0,-1,1001,1002));
0826 
0827     dev->fill(QRect(-1,0, 1, 1000), KoColor(Qt::white, cs));
0828     QCOMPARE(dev->exactBounds(), QRect(-1,-1,1002,1002));
0829     QCOMPARE(dev->nonDefaultPixelArea(), QRect(-1,-1,1002,1002));
0830 }
0831 
0832 KisPaintDeviceSP createWrapAroundPaintDevice(const KoColorSpace *cs)
0833 {
0834     struct TestingDefaultBounds : public KisDefaultBoundsBase {
0835         QRect bounds() const override {
0836             return QRect(0,0,20,20);
0837         }
0838         bool wrapAroundMode() const override {
0839             return true;
0840         }
0841         WrapAroundAxis wrapAroundModeAxis() const override {
0842             return WRAPAROUND_BOTH;
0843         }
0844         int currentLevelOfDetail() const override {
0845             return 0;
0846         }
0847         int currentTime() const override {
0848             return 0;
0849         }
0850         bool externalFrameActive() const override {
0851             return false;
0852         }
0853         void * sourceCookie() const override {
0854             return 0;
0855         }
0856     };
0857 
0858     KisPaintDeviceSP dev = new KisPaintDevice(cs);
0859 
0860     KisDefaultBoundsBaseSP bounds = new TestingDefaultBounds();
0861     dev->setDefaultBounds(bounds);
0862     dev->setSupportsWraparoundMode(true);
0863 
0864     return dev;
0865 }
0866 
0867 void checkReadWriteRoundTrip(KisPaintDeviceSP dev,
0868                              const QRect &rc)
0869 {
0870     KisPaintDeviceSP deviceCopy = new KisPaintDevice(*dev.data());
0871 
0872     int bufSize = rc.width() * rc.height() * dev->pixelSize();
0873 
0874     QScopedArrayPointer<quint8> buf1(new quint8[bufSize]);
0875 
0876     deviceCopy->readBytes(buf1.data(), rc);
0877 
0878     deviceCopy->clear();
0879     QVERIFY(deviceCopy->extent().isEmpty());
0880 
0881 
0882     QScopedArrayPointer<quint8> buf2(new quint8[bufSize]);
0883     deviceCopy->writeBytes(buf1.data(), rc);
0884     deviceCopy->readBytes(buf2.data(), rc);
0885 
0886     QVERIFY(!memcmp(buf1.data(), buf2.data(), bufSize));
0887 }
0888 
0889 
0890 void KisPaintDeviceTest::testReadBytesWrapAround()
0891 {
0892     const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
0893     KisPaintDeviceSP dev = createWrapAroundPaintDevice(cs);
0894 
0895     KoColor c1(Qt::red, cs);
0896     KoColor c2(Qt::green, cs);
0897 
0898     dev->setPixel(3, 3, c1);
0899     dev->setPixel(18, 18, c2);
0900 
0901     const int pixelSize = dev->pixelSize();
0902 
0903     {
0904         QRect readRect(10, 10, 20, 20);
0905         QScopedArrayPointer<quint8> buf(new quint8[readRect.width() *
0906                                               readRect.height() *
0907                                               pixelSize]);
0908         dev->readBytes(buf.data(), readRect);
0909         //dev->convertToQImage(0, readRect.x(), readRect.y(), readRect.width(), readRect.height()).save("final1.png");
0910 
0911         QVERIFY(memcmp(buf.data() + (7 + readRect.width() * 7) * pixelSize, c2.data(), pixelSize));
0912         QVERIFY(!memcmp(buf.data() + (8 + readRect.width() * 8) * pixelSize, c2.data(), pixelSize));
0913 
0914         QVERIFY(memcmp(buf.data() + (12 + readRect.width() * 12) * pixelSize, c1.data(), pixelSize));
0915         QVERIFY(!memcmp(buf.data() + (13 + readRect.width() * 13) * pixelSize, c1.data(), pixelSize));
0916 
0917         checkReadWriteRoundTrip(dev, readRect);
0918     }
0919 
0920     {
0921         // check weird case when the read rect is larger than wrap rect
0922         QRect readRect(10, 10, 30, 30);
0923         QScopedArrayPointer<quint8> buf(new quint8[readRect.width() *
0924                                               readRect.height() *
0925                                               pixelSize]);
0926         dev->readBytes(buf.data(), readRect);
0927         //dev->convertToQImage(0, readRect.x(), readRect.y(), readRect.width(), readRect.height()).save("final2.png");
0928 
0929         QVERIFY(memcmp(buf.data() + (7 + readRect.width() * 7) * pixelSize, c2.data(), pixelSize));
0930         QVERIFY(!memcmp(buf.data() + (8 + readRect.width() * 8) * pixelSize, c2.data(), pixelSize));
0931 
0932         QVERIFY(memcmp(buf.data() + (12 + readRect.width() * 12) * pixelSize, c1.data(), pixelSize));
0933         QVERIFY(!memcmp(buf.data() + (13 + readRect.width() * 13) * pixelSize, c1.data(), pixelSize));
0934 
0935         QVERIFY(memcmp(buf.data() + (27 + readRect.width() * 7) * pixelSize, c2.data(), pixelSize));
0936         QVERIFY(!memcmp(buf.data() + (28 + readRect.width() * 8) * pixelSize, c2.data(), pixelSize));
0937 
0938         QVERIFY(memcmp(buf.data() + (7 + readRect.width() * 27) * pixelSize, c2.data(), pixelSize));
0939         QVERIFY(!memcmp(buf.data() + (8 + readRect.width() * 28) * pixelSize, c2.data(), pixelSize));
0940 
0941         QVERIFY(memcmp(buf.data() + (27 + readRect.width() * 27) * pixelSize, c2.data(), pixelSize));
0942         QVERIFY(!memcmp(buf.data() + (28 + readRect.width() * 28) * pixelSize, c2.data(), pixelSize));
0943 
0944         checkReadWriteRoundTrip(dev, readRect);
0945     }
0946 
0947     {
0948         // even more large
0949         QRect readRect(10, 10, 40, 40);
0950         QScopedArrayPointer<quint8> buf(new quint8[readRect.width() *
0951                                               readRect.height() *
0952                                               pixelSize]);
0953         dev->readBytes(buf.data(), readRect);
0954         //dev->convertToQImage(0, readRect.x(), readRect.y(), readRect.width(), readRect.height()).save("final3.png");
0955 
0956         QVERIFY(memcmp(buf.data() + (7 + readRect.width() * 7) * pixelSize, c2.data(), pixelSize));
0957         QVERIFY(!memcmp(buf.data() + (8 + readRect.width() * 8) * pixelSize, c2.data(), pixelSize));
0958 
0959         QVERIFY(memcmp(buf.data() + (12 + readRect.width() * 12) * pixelSize, c1.data(), pixelSize));
0960         QVERIFY(!memcmp(buf.data() + (13 + readRect.width() * 13) * pixelSize, c1.data(), pixelSize));
0961 
0962         QVERIFY(memcmp(buf.data() + (27 + readRect.width() * 7) * pixelSize, c2.data(), pixelSize));
0963         QVERIFY(!memcmp(buf.data() + (28 + readRect.width() * 8) * pixelSize, c2.data(), pixelSize));
0964 
0965         QVERIFY(memcmp(buf.data() + (7 + readRect.width() * 27) * pixelSize, c2.data(), pixelSize));
0966         QVERIFY(!memcmp(buf.data() + (8 + readRect.width() * 28) * pixelSize, c2.data(), pixelSize));
0967 
0968         QVERIFY(memcmp(buf.data() + (27 + readRect.width() * 27) * pixelSize, c2.data(), pixelSize));
0969         QVERIFY(!memcmp(buf.data() + (28 + readRect.width() * 28) * pixelSize, c2.data(), pixelSize));
0970 
0971         QVERIFY(memcmp(buf.data() + (32 + readRect.width() * 12) * pixelSize, c1.data(), pixelSize));
0972         QVERIFY(!memcmp(buf.data() + (33 + readRect.width() * 13) * pixelSize, c1.data(), pixelSize));
0973 
0974         QVERIFY(memcmp(buf.data() + (12 + readRect.width() * 32) * pixelSize, c1.data(), pixelSize));
0975         QVERIFY(!memcmp(buf.data() + (13 + readRect.width() * 33) * pixelSize, c1.data(), pixelSize));
0976 
0977         QVERIFY(memcmp(buf.data() + (32 + readRect.width() * 32) * pixelSize, c1.data(), pixelSize));
0978         QVERIFY(!memcmp(buf.data() + (33 + readRect.width() * 33) * pixelSize, c1.data(), pixelSize));
0979 
0980         checkReadWriteRoundTrip(dev, readRect);
0981     }
0982 
0983     {
0984         // check if the wrap rect contains the read rect entirely
0985         QRect readRect(1, 1, 10, 10);
0986         QScopedArrayPointer<quint8> buf(new quint8[readRect.width() *
0987                                               readRect.height() *
0988                                               pixelSize]);
0989         dev->readBytes(buf.data(), readRect);
0990         //dev->convertToQImage(0, readRect.x(), readRect.y(), readRect.width(), readRect.height()).save("final4.png");
0991 
0992         QVERIFY(memcmp(buf.data() + (1 + readRect.width() * 1) * pixelSize, c1.data(), pixelSize));
0993         QVERIFY(!memcmp(buf.data() + (2 + readRect.width() * 2) * pixelSize, c1.data(), pixelSize));
0994 
0995         checkReadWriteRoundTrip(dev, readRect);
0996     }
0997 
0998     {
0999         // check if the wrap happens only on vertical side of the rect
1000         QRect readRect(1, 1, 29, 10);
1001         QScopedArrayPointer<quint8> buf(new quint8[readRect.width() *
1002                                               readRect.height() *
1003                                               pixelSize]);
1004         dev->readBytes(buf.data(), readRect);
1005         //dev->convertToQImage(0, readRect.x(), readRect.y(), readRect.width(), readRect.height()).save("final5.png");
1006 
1007         QVERIFY(memcmp(buf.data() + (1 + readRect.width() * 1) * pixelSize, c1.data(), pixelSize));
1008         QVERIFY(!memcmp(buf.data() + (2 + readRect.width() * 2) * pixelSize, c1.data(), pixelSize));
1009 
1010         QVERIFY(memcmp(buf.data() + (21 + readRect.width() * 1) * pixelSize, c1.data(), pixelSize));
1011         QVERIFY(!memcmp(buf.data() + (22 + readRect.width() * 2) * pixelSize, c1.data(), pixelSize));
1012 
1013         checkReadWriteRoundTrip(dev, readRect);
1014     }
1015 
1016     {
1017         // check if the wrap happens only on horizontal side of the rect
1018         QRect readRect(1, 1, 10, 29);
1019         QScopedArrayPointer<quint8> buf(new quint8[readRect.width() *
1020                                               readRect.height() *
1021                                               pixelSize]);
1022         dev->readBytes(buf.data(), readRect);
1023         //dev->convertToQImage(0, readRect.x(), readRect.y(), readRect.width(), readRect.height()).save("final6.png");
1024 
1025         QVERIFY(memcmp(buf.data() + (1 + readRect.width() * 1) * pixelSize, c1.data(), pixelSize));
1026         QVERIFY(!memcmp(buf.data() + (2 + readRect.width() * 2) * pixelSize, c1.data(), pixelSize));
1027 
1028         QVERIFY(memcmp(buf.data() + (1 + readRect.width() * 21) * pixelSize, c1.data(), pixelSize));
1029         QVERIFY(!memcmp(buf.data() + (2 + readRect.width() * 22) * pixelSize, c1.data(), pixelSize));
1030 
1031         checkReadWriteRoundTrip(dev, readRect);
1032     }
1033 }
1034 
1035 #include "kis_random_accessor_ng.h"
1036 
1037 void KisPaintDeviceTest::testWrappedRandomAccessor()
1038 {
1039     const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
1040     KisPaintDeviceSP dev = createWrapAroundPaintDevice(cs);
1041 
1042     KoColor c1(Qt::red, cs);
1043     KoColor c2(Qt::green, cs);
1044 
1045     dev->setPixel(3, 3, c1);
1046     dev->setPixel(18, 18, c2);
1047 
1048     const int pixelSize = dev->pixelSize();
1049 
1050     int x;
1051     int y;
1052 
1053     KisRandomAccessorSP dstIt = dev->createRandomAccessorNG();
1054     x = 3;
1055     y = 3;
1056     dstIt->moveTo(x, y);
1057 
1058     QVERIFY(!memcmp(dstIt->rawData(), c1.data(), pixelSize));
1059     QCOMPARE(dstIt->numContiguousColumns(x), 17);
1060     QCOMPARE(dstIt->numContiguousRows(y), 17);
1061 
1062     x = 23;
1063     y = 23;
1064     dstIt->moveTo(x, y);
1065     QVERIFY(!memcmp(dstIt->rawData(), c1.data(), pixelSize));
1066     QCOMPARE(dstIt->numContiguousColumns(x), 17);
1067     QCOMPARE(dstIt->numContiguousRows(y), 17);
1068 
1069     x = 3;
1070     y = 23;
1071     dstIt->moveTo(x, y);
1072     QVERIFY(!memcmp(dstIt->rawData(), c1.data(), pixelSize));
1073     QCOMPARE(dstIt->numContiguousColumns(x), 17);
1074     QCOMPARE(dstIt->numContiguousRows(y), 17);
1075 
1076     x = 23;
1077     y = 3;
1078     dstIt->moveTo(x, y);
1079     QVERIFY(!memcmp(dstIt->rawData(), c1.data(), pixelSize));
1080     QCOMPARE(dstIt->numContiguousColumns(x), 17);
1081     QCOMPARE(dstIt->numContiguousRows(y), 17);
1082 
1083     x = -17;
1084     y = 3;
1085     dstIt->moveTo(x, y);
1086     QVERIFY(!memcmp(dstIt->rawData(), c1.data(), pixelSize));
1087     QCOMPARE(dstIt->numContiguousColumns(x), 17);
1088     QCOMPARE(dstIt->numContiguousRows(y), 17);
1089 
1090     x = 3;
1091     y = -17;
1092     dstIt->moveTo(x, y);
1093     QVERIFY(!memcmp(dstIt->rawData(), c1.data(), pixelSize));
1094     QCOMPARE(dstIt->numContiguousColumns(x), 17);
1095     QCOMPARE(dstIt->numContiguousRows(y), 17);
1096 
1097     x = -17;
1098     y = -17;
1099     dstIt->moveTo(x, y);
1100     QVERIFY(!memcmp(dstIt->rawData(), c1.data(), pixelSize));
1101     QCOMPARE(dstIt->numContiguousColumns(x), 17);
1102     QCOMPARE(dstIt->numContiguousRows(y), 17);
1103 }
1104 
1105 #include "kis_iterator_ng.h"
1106 
1107 static bool nextRowGeneral(KisHLineIteratorSP it, int y, const QRect &rc) {
1108     it->nextRow();
1109     return y < rc.height();
1110 }
1111 
1112 static bool nextRowGeneral(KisVLineIteratorSP it, int y, const QRect &rc) {
1113     it->nextColumn();
1114     return y < rc.width();
1115 }
1116 
1117 template <class T>
1118 bool checkXY(const QPoint &pt, const QPoint &realPt) {
1119     Q_UNUSED(pt);
1120     Q_UNUSED(realPt);
1121     return false;
1122 }
1123 
1124 template <>
1125 bool checkXY<KisHLineIteratorSP>(const QPoint &pt, const QPoint &realPt) {
1126     return pt == realPt;
1127 }
1128 
1129 template <>
1130 bool checkXY<KisVLineIteratorSP>(const QPoint &pt, const QPoint &realPt) {
1131     return pt.x() == realPt.y() && pt.y() == realPt.x();
1132 }
1133 #include <kis_wrapped_rect.h>
1134 template <class T>
1135 bool checkConseqPixels(int value, const QPoint &pt, const KisWrappedRect &wrappedRect) {
1136     Q_UNUSED(value);
1137     Q_UNUSED(pt);
1138     Q_UNUSED(wrappedRect);
1139     return false;
1140 }
1141 
1142 template <>
1143 bool checkConseqPixels<KisHLineIteratorSP>(int value, const QPoint &pt, const KisWrappedRect &wrappedRect) {
1144     int x = KisWrappedRect::xToWrappedX(pt.x(), wrappedRect.wrapRect(), WRAPAROUND_BOTH);
1145     int borderX = wrappedRect.originalRect().x() + wrappedRect.wrapRect().width();
1146     int conseq = x >= borderX ? wrappedRect.wrapRect().right() - x + 1 : borderX - x;
1147     conseq = qMin(conseq, wrappedRect.originalRect().right() - pt.x() + 1);
1148 
1149     return value == conseq;
1150 }
1151 
1152 template <>
1153 bool checkConseqPixels<KisVLineIteratorSP>(int value, const QPoint &pt, const KisWrappedRect &wrappedRect) {
1154     Q_UNUSED(pt);
1155     Q_UNUSED(wrappedRect);
1156     return value == 1;
1157 }
1158 
1159 template <class IteratorSP>
1160 IteratorSP createIterator(KisPaintDeviceSP dev, const QRect &rc) {
1161     Q_UNUSED(dev);
1162     Q_UNUSED(rc);
1163     return 0;
1164 }
1165 
1166 template <>
1167 KisHLineIteratorSP createIterator(KisPaintDeviceSP dev,
1168                                   const QRect &rc) {
1169     return dev->createHLineIteratorNG(rc.x(), rc.y(), rc.width());
1170 }
1171 
1172 template <>
1173 KisVLineIteratorSP createIterator(KisPaintDeviceSP dev,
1174                                   const QRect &rc) {
1175     return dev->createVLineIteratorNG(rc.x(), rc.y(), rc.height());
1176 }
1177 
1178 template <class IteratorSP>
1179 void testWrappedLineIterator(QString testName, const QRect &rect)
1180 {
1181     testName = QString("%1_%2_%3_%4_%5")
1182         .arg(testName)
1183         .arg(rect.x())
1184         .arg(rect.y())
1185         .arg(rect.width())
1186         .arg(rect.height());
1187 
1188     const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
1189     KisPaintDeviceSP dev = createWrapAroundPaintDevice(cs);
1190 
1191     // test rect fits the wrap rect in both dimensions
1192     IteratorSP it = createIterator<IteratorSP>(dev, rect);
1193 
1194     int y = 0;
1195     do {
1196         int x = 0;
1197         do {
1198             quint8 *data = it->rawData();
1199 
1200             data[0] = 10 * x;
1201             data[1] = 10 * y;
1202             data[2] = 0;
1203             data[3] = 255;
1204 
1205             x++;
1206         } while (it->nextPixel());
1207     } while (nextRowGeneral(it, ++y, rect));
1208 
1209     QRect rc = dev->defaultBounds()->bounds() | dev->exactBounds();
1210     QImage result = dev->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height());
1211 
1212     QVERIFY(TestUtil::checkQImage(result, "paint_device_test", "wrapped_iterators", testName));
1213 }
1214 
1215 template <class IteratorSP>
1216 void testWrappedLineIterator(const QString &testName)
1217 {
1218     testWrappedLineIterator<IteratorSP>(testName, QRect(10,10,20,20));
1219     testWrappedLineIterator<IteratorSP>(testName, QRect(10,10,10,20));
1220     testWrappedLineIterator<IteratorSP>(testName, QRect(10,10,20,10));
1221     testWrappedLineIterator<IteratorSP>(testName, QRect(10,10,10,10));
1222     testWrappedLineIterator<IteratorSP>(testName, QRect(0,0,20,20));
1223 }
1224 
1225 void KisPaintDeviceTest::testWrappedHLineIterator()
1226 {
1227     testWrappedLineIterator<KisHLineIteratorSP>("hline_iterator");
1228 }
1229 
1230 void KisPaintDeviceTest::testWrappedVLineIterator()
1231 {
1232     testWrappedLineIterator<KisVLineIteratorSP>("vline_iterator");
1233 }
1234 
1235 template <class IteratorSP>
1236 void testWrappedLineIteratorReadMoreThanBounds(QString testName)
1237 {
1238     const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
1239     KisPaintDeviceSP dev = createWrapAroundPaintDevice(cs);
1240     KisPaintDeviceSP dst = new KisPaintDevice(cs);
1241 
1242     // fill device with a gradient
1243     QRect bounds = dev->defaultBounds()->bounds();
1244     for (int y = bounds.y(); y < bounds.y() + bounds.height(); y++) {
1245         for (int x = bounds.x(); x < bounds.x() + bounds.width(); x++) {
1246             QColor c((10 * x) % 255, (10 * y) % 255, 0, 255);
1247             dev->setPixel(x, y, c);
1248         }
1249     }
1250 
1251     // test rect doesn't fit the wrap rect in both dimensions
1252     const QRect &rect(bounds.adjusted(-6,-6,8,8));
1253     KisRandomAccessorSP dstIt = dst->createRandomAccessorNG();
1254     IteratorSP it = createIterator<IteratorSP>(dev, rect);
1255 
1256     for (int y = rect.y(); y < rect.y() + rect.height(); y++) {
1257         for (int x = rect.x(); x < rect.x() + rect.width(); x++) {
1258             quint8 *data = it->rawData();
1259 
1260             QVERIFY(checkConseqPixels<IteratorSP>(it->nConseqPixels(), QPoint(x, y), KisWrappedRect(rect, bounds, WRAPAROUND_BOTH)));
1261 
1262             dstIt->moveTo(x, y);
1263             memcpy(dstIt->rawData(), data, cs->pixelSize());
1264 
1265             QVERIFY(checkXY<IteratorSP>(QPoint(it->x(), it->y()), QPoint(x,y)));
1266 
1267             bool stepDone = it->nextPixel();
1268             QCOMPARE(stepDone, x < rect.x() + rect.width() - 1);
1269         }
1270         if (!nextRowGeneral(it, y, rect)) break;
1271     }
1272 
1273     testName = QString("%1_%2_%3_%4_%5")
1274         .arg(testName)
1275         .arg(rect.x())
1276         .arg(rect.y())
1277         .arg(rect.width())
1278         .arg(rect.height());
1279 
1280     QRect rc = rect;
1281     QImage result = dst->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height());
1282     QImage ref = dev->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height());
1283 
1284     QVERIFY(TestUtil::checkQImage(result, "paint_device_test", "wrapped_iterators_huge", testName, 1));
1285 }
1286 
1287 void KisPaintDeviceTest::testWrappedHLineIteratorReadMoreThanBounds()
1288 {
1289     testWrappedLineIteratorReadMoreThanBounds<KisHLineIteratorSP>("hline_iterator");
1290 }
1291 
1292 void KisPaintDeviceTest::testWrappedVLineIteratorReadMoreThanBounds()
1293 {
1294     testWrappedLineIteratorReadMoreThanBounds<KisVLineIteratorSP>("vline_iterator");
1295 }
1296 
1297 void KisPaintDeviceTest::testMoveWrapAround()
1298 {
1299     const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
1300     KisPaintDeviceSP dev = createWrapAroundPaintDevice(cs);
1301 
1302     KoColor c1(Qt::red, cs);
1303     KoColor c2(Qt::green, cs);
1304 
1305     dev->setPixel(3, 3, c1);
1306     dev->setPixel(18, 18, c2);
1307 
1308     // QRect rc = dev->defaultBounds()->bounds();
1309 
1310     //dev->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height()).save("move0.png");
1311     QCOMPARE(dev->exactBounds(), QRect(3,3,16,16));
1312     dev->moveTo(QPoint(10,10));
1313     QCOMPARE(dev->exactBounds(), QRect(8,8,6,6));
1314     //dev->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height()).save("move1.png");
1315 
1316 }
1317 
1318 #include "kis_lock_free_cache.h"
1319 
1320 #define NUM_TYPES 3
1321 
1322 // high-concurrency
1323 #define NUM_CYCLES 500000
1324 #define NUM_THREADS 4
1325 
1326 
1327 struct TestingCache : KisLockFreeCache<int> {
1328     int calculateNewValue() const override {
1329         return m_realValue;
1330     }
1331 
1332     QAtomicInt m_realValue;
1333 };
1334 
1335 class CacheStressJob : public QRunnable
1336 {
1337 public:
1338     CacheStressJob(TestingCache &cache)
1339         : m_cache(cache),
1340           m_oldValue(0)
1341     {
1342     }
1343 
1344     void run() override {
1345         for(qint32 i = 0; i < NUM_CYCLES; i++) {
1346             qint32 type = i % NUM_TYPES;
1347 
1348             switch(type) {
1349             case 0:
1350                 m_cache.m_realValue.ref();
1351                 m_oldValue = m_cache.m_realValue;
1352                 m_cache.invalidate();
1353                 break;
1354             case 1:
1355             {
1356                 int newValue = m_cache.getValue();
1357                 KIS_ASSERT(newValue >= m_oldValue);
1358                 Q_UNUSED(newValue);
1359             }
1360                 break;
1361             case 3:
1362                 QTest::qSleep(3);
1363                 break;
1364             }
1365         }
1366     }
1367 
1368 private:
1369     TestingCache &m_cache;
1370     int m_oldValue;
1371 };
1372 
1373 void KisPaintDeviceTest::testCacheState()
1374 {
1375     TestingCache cache;
1376 
1377     QList<CacheStressJob*> jobsList;
1378     CacheStressJob *job;
1379 
1380     for(qint32 i = 0; i < NUM_THREADS; i++) {
1381         //job = new CacheStressJob(value, cacheValue, cacheState);
1382         job = new CacheStressJob(cache);
1383         job->setAutoDelete(true);
1384         jobsList.append(job);
1385     }
1386 
1387     QThreadPool pool;
1388     pool.setMaxThreadCount(NUM_THREADS);
1389 
1390     Q_FOREACH (job, jobsList) {
1391         pool.start(job);
1392     }
1393 
1394     pool.waitForDone();
1395 }
1396 
1397 struct TestingLodDefaultBounds : public KisDefaultBoundsBase {
1398     TestingLodDefaultBounds(const QRect &bounds = QRect(0,0,100,100))
1399         : m_lod(0), m_bounds(bounds) {}
1400 
1401     QRect bounds() const override {
1402         return m_bounds;
1403     }
1404     bool wrapAroundMode() const override {
1405         return false;
1406     }
1407     WrapAroundAxis wrapAroundModeAxis() const override {
1408         return WRAPAROUND_BOTH;
1409     }
1410 
1411     int currentLevelOfDetail() const override {
1412         return m_lod;
1413     }
1414 
1415     int currentTime() const override {
1416         return 0;
1417     }
1418     bool externalFrameActive() const override {
1419         return false;
1420     }
1421 
1422     void testingSetLevelOfDetail(int lod) {
1423         m_lod = lod;
1424     }
1425     void * sourceCookie() const override {
1426         return 0;
1427     }
1428 
1429 private:
1430     int m_lod;
1431     QRect m_bounds;
1432 };
1433 
1434 void fillGradientDevice(KisPaintDeviceSP dev, const QRect &rect, bool flat = false)
1435 {
1436     if (flat) {
1437         dev->fill(rect, KoColor(Qt::red, dev->colorSpace()));
1438     } else {
1439         // fill device with a gradient
1440         KisSequentialIterator it(dev, rect);
1441         while (it.nextPixel()) {
1442             QColor c((10 * it.x()) & 0xFF, (10 * it.y()) & 0xFF, 0, 255);
1443             KoColor color(c, dev->colorSpace());
1444             memcpy(it.rawData(), color.data(), dev->pixelSize());
1445         }
1446     }
1447 }
1448 #include "kis_lod_transform.h"
1449 void KisPaintDeviceTest::testLodTransform()
1450 {
1451     const int lod = 2; // round to 4
1452     KisLodTransform t(lod);
1453 
1454     QRect rc1(-16, -16, 8, 8);
1455     QRect rc2(-16, -16, 7, 7);
1456     QRect rc3(-15, -15, 7, 7);
1457 
1458     QCOMPARE(t.alignedRect(rc1, lod), rc1);
1459     QCOMPARE(t.alignedRect(rc2, lod), rc1);
1460     QCOMPARE(t.alignedRect(rc3, lod), rc1);
1461 }
1462 
1463 #include "krita_utils.h"
1464 void syncLodCache(KisPaintDeviceSP dev, int levelOfDetail)
1465 {
1466     KisPaintDevice::LodDataStruct* s = dev->createLodDataStruct(levelOfDetail);
1467 
1468     KisRegion region = dev->regionForLodSyncing();
1469     Q_FOREACH(QRect rect2, KritaUtils::splitRegionIntoPatches(region, KritaUtils::optimalPatchSize())) {
1470         dev->updateLodDataStruct(s, rect2);
1471     }
1472 
1473     dev->uploadLodDataStruct(s);
1474 }
1475 
1476 void KisPaintDeviceTest::testLodDevice()
1477 {
1478     const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
1479     KisPaintDeviceSP dev = new KisPaintDevice(cs);
1480 
1481     TestingLodDefaultBounds *bounds = new TestingLodDefaultBounds();
1482     dev->setDefaultBounds(bounds);
1483 
1484     // fill device with a gradient
1485     // QRect rect = dev->defaultBounds()->bounds();
1486     // fillGradientDevice(dev, rect);
1487     fillGradientDevice(dev, QRect(50,50,30,30));
1488 
1489     QCOMPARE(dev->exactBounds(), QRect(50,50,30,30));
1490 
1491     QImage result;
1492 
1493     qDebug() << ppVar(dev->exactBounds());
1494     result = dev->convertToQImage(0,0,0,100,100);
1495     /*QVERIFY*/(TestUtil::checkQImage(result, "paint_device_test",
1496                                   "lod", "initial"));
1497 
1498     bounds->testingSetLevelOfDetail(1);
1499     syncLodCache(dev, 1);
1500     QCOMPARE(dev->exactBounds(), QRect(25,25,15,15));
1501 
1502     qDebug() << ppVar(dev->exactBounds());
1503     result = dev->convertToQImage(0,0,0,100,100);
1504     /*QVERIFY*/(TestUtil::checkQImage(result, "paint_device_test",
1505                                   "lod", "lod1"));
1506 
1507     bounds->testingSetLevelOfDetail(2);
1508     QCOMPARE(dev->exactBounds(), QRect(25,25,15,15));
1509 
1510     qDebug() << ppVar(dev->exactBounds());
1511     result = dev->convertToQImage(0,0,0,100,100);
1512     /*QVERIFY*/(TestUtil::checkQImage(result, "paint_device_test",
1513                                   "lod", "lod1"));
1514 
1515     syncLodCache(dev, 2);
1516     QCOMPARE(dev->exactBounds(), QRect(12,12,8,8));
1517 
1518     qDebug() << ppVar(dev->exactBounds());
1519     result = dev->convertToQImage(0,0,0,100,100);
1520     /*QVERIFY*/(TestUtil::checkQImage(result, "paint_device_test",
1521                                   "lod", "lod2"));
1522 
1523     bounds->testingSetLevelOfDetail(0);
1524 
1525     dev->setX(20);
1526     dev->setY(10);
1527 
1528     bounds->testingSetLevelOfDetail(1);
1529     syncLodCache(dev, 1);
1530 
1531     QCOMPARE(dev->exactBounds(), QRect(35,30,15,15));
1532 
1533 
1534     qDebug() << ppVar(dev->exactBounds()) << ppVar(dev->x()) << ppVar(dev->y());
1535     result = dev->convertToQImage(0,0,0,100,100);
1536     /*QVERIFY*/(TestUtil::checkQImage(result, "paint_device_test",
1537                                   "lod", "lod1-offset-6-14"));
1538 }
1539 
1540 void KisPaintDeviceTest::benchmarkLod1Generation()
1541 {
1542     const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
1543     KisPaintDeviceSP dev = new KisPaintDevice(cs);
1544 
1545     TestingLodDefaultBounds *bounds = new TestingLodDefaultBounds(QRect(0,0,6000,4000));
1546     dev->setDefaultBounds(bounds);
1547 
1548     // fill device with a gradient
1549     QRect rect = dev->defaultBounds()->bounds();
1550     fillGradientDevice(dev, rect, true);
1551 
1552     QBENCHMARK {
1553         bounds->testingSetLevelOfDetail(1);
1554         syncLodCache(dev, 1);
1555     }
1556 }
1557 
1558 void KisPaintDeviceTest::benchmarkLod2Generation()
1559 {
1560     const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
1561     KisPaintDeviceSP dev = new KisPaintDevice(cs);
1562 
1563     TestingLodDefaultBounds *bounds = new TestingLodDefaultBounds(QRect(0,0,6000,4000));
1564     dev->setDefaultBounds(bounds);
1565 
1566     // fill device with a gradient
1567     QRect rect = dev->defaultBounds()->bounds();
1568     fillGradientDevice(dev, rect, true);
1569 
1570     QBENCHMARK {
1571         bounds->testingSetLevelOfDetail(2);
1572         syncLodCache(dev, 2);
1573     }
1574 }
1575 
1576 void KisPaintDeviceTest::benchmarkLod3Generation()
1577 {
1578     const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
1579     KisPaintDeviceSP dev = new KisPaintDevice(cs);
1580 
1581     TestingLodDefaultBounds *bounds = new TestingLodDefaultBounds(QRect(0,0,3000,2000));
1582     dev->setDefaultBounds(bounds);
1583 
1584     // fill device with a gradient
1585     QRect rect = dev->defaultBounds()->bounds();
1586     fillGradientDevice(dev, rect, true);
1587 
1588     QBENCHMARK {
1589         bounds->testingSetLevelOfDetail(3);
1590         syncLodCache(dev, 3);
1591     }
1592 }
1593 
1594 void KisPaintDeviceTest::benchmarkLod4Generation()
1595 {
1596     const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
1597     KisPaintDeviceSP dev = new KisPaintDevice(cs);
1598 
1599     TestingLodDefaultBounds *bounds = new TestingLodDefaultBounds(QRect(0,0,3000,2000));
1600     dev->setDefaultBounds(bounds);
1601 
1602     // fill device with a gradient
1603     QRect rect = dev->defaultBounds()->bounds();
1604     fillGradientDevice(dev, rect, true);
1605 
1606     QBENCHMARK {
1607         bounds->testingSetLevelOfDetail(4);
1608         syncLodCache(dev, 4);
1609     }
1610 }
1611 
1612 #include "kis_keyframe_channel.h"
1613 #include "kis_raster_keyframe_channel.h"
1614 #include "kis_paint_device_frames_interface.h"
1615 #include "testing_timed_default_bounds.h"
1616 
1617 void KisPaintDeviceTest::testFramesSignals_data()
1618 {
1619     QTest::addColumn<bool>("useCommand");
1620 
1621     QTest::newRow("no command") << false;
1622     QTest::newRow("with command") << true;
1623 }
1624 
1625 void KisPaintDeviceTest::testFramesSignals()
1626 {
1627     QFETCH(bool, useCommand);
1628 
1629     const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
1630     KisPaintDeviceSP dev = new KisPaintDevice(cs);
1631 
1632     TestUtil::TestingTimedDefaultBounds *bounds = new TestUtil::TestingTimedDefaultBounds();
1633     dev->setDefaultBounds(bounds);
1634 
1635     KisRasterKeyframeChannel *channel = dev->createKeyframeChannel(KisKeyframeChannel::Raster);
1636     QVERIFY(channel);
1637 
1638     KisPaintDeviceFramesInterface *i = dev->framesInterface();
1639     QVERIFY(i);
1640 
1641     QCOMPARE(i->frames().size(), 1);
1642 
1643     std::optional<int> receivedAddedKey;
1644     std::optional<int> receivedToBeRemovedKey;
1645     std::optional<int> receivedHasBeenRemovedKey;
1646     bool receivedAnyFrameChange = false;
1647 
1648     connect(channel, &KisKeyframeChannel::sigAddedKeyframe, channel,
1649             [&] (const KisKeyframeChannel *ch, int time) {
1650                 QVERIFY(!receivedAddedKey);
1651                 QVERIFY(ch == channel);
1652 
1653                 /**
1654                  * The signal should come **after** the frame has beed added
1655                  */
1656 
1657                 KisKeyframeSP keyframe = ch->keyframeAt(time);
1658                 QVERIFY(keyframe);
1659 
1660                 KisRasterKeyframe *rasterKeyframe =
1661                     dynamic_cast<KisRasterKeyframe*>(keyframe.data());
1662                 QVERIFY(rasterKeyframe);
1663 
1664                 /**
1665                  * The raster keyframe channel should have had time to add the
1666                  * frame to its internal structures.
1667                  */
1668                 QVERIFY(!channel->timesForFrameID(rasterKeyframe->frameID()).isEmpty());
1669 
1670                 receivedAddedKey = time;
1671             });
1672 
1673     connect(channel, &KisKeyframeChannel::sigKeyframeAboutToBeRemoved, channel,
1674             [&] (const KisKeyframeChannel *ch, int time) {
1675                 QVERIFY(!receivedToBeRemovedKey);
1676                 QVERIFY(ch == channel);
1677 
1678                 /**
1679                  * The signal should come **before** the frame has beed removed
1680                  */
1681 
1682                 KisKeyframeSP keyframe = ch->keyframeAt(time);
1683                 QVERIFY(keyframe);
1684 
1685                 KisRasterKeyframe *rasterKeyframe =
1686                     dynamic_cast<KisRasterKeyframe*>(keyframe.data());
1687                 QVERIFY(rasterKeyframe);
1688 
1689                 /**
1690                  * The raster keyframe channel should have info about this frame
1691                  */
1692                 QVERIFY(!channel->timesForFrameID(rasterKeyframe->frameID()).isEmpty());
1693 
1694                 receivedToBeRemovedKey = time;
1695             });
1696 
1697     connect(channel, &KisKeyframeChannel::sigKeyframeHasBeenRemoved, channel,
1698             [&] (const KisKeyframeChannel *ch, int time) {
1699                 QVERIFY(!receivedHasBeenRemovedKey);
1700                 QVERIFY(ch == channel);
1701 
1702                 /**
1703                  * The signal should come **after** the frame has beed removed
1704                  */
1705 
1706                 KisKeyframeSP keyframe = ch->keyframeAt(time);
1707                 QVERIFY(!keyframe);
1708 
1709                 receivedHasBeenRemovedKey = time;
1710             });
1711 
1712     connect(channel, &KisKeyframeChannel::sigAnyKeyframeChange, channel,
1713             [&] () {
1714                 QVERIFY(!receivedAnyFrameChange);
1715                 receivedAnyFrameChange = true;
1716             });
1717 
1718 
1719     QScopedPointer<KUndo2Command> parentCommand;
1720 
1721     if (useCommand) {
1722         parentCommand.reset(new KUndo2Command);
1723     }
1724 
1725     // add keyframe at position 10
1726     channel->addKeyframe(10, parentCommand.data());
1727 
1728     QVERIFY(receivedAddedKey);
1729     QCOMPARE(*receivedAddedKey, 10);
1730     receivedAddedKey = std::nullopt;
1731 
1732     QVERIFY(!receivedToBeRemovedKey);
1733     QVERIFY(!receivedHasBeenRemovedKey);
1734     QVERIFY(receivedAnyFrameChange);
1735     receivedAnyFrameChange = false;
1736 
1737     if (parentCommand) {
1738         // first redo is skipped
1739         parentCommand->redo();
1740         QVERIFY(!receivedAddedKey);
1741         QVERIFY(!receivedToBeRemovedKey);
1742         QVERIFY(!receivedHasBeenRemovedKey);
1743         QVERIFY(!receivedAnyFrameChange);
1744         parentCommand.reset(new KUndo2Command);
1745     }
1746 
1747     // add keyframe at position 10
1748     channel->removeKeyframe(10, parentCommand.data());
1749 
1750     QVERIFY(!receivedAddedKey);
1751 
1752     QVERIFY(receivedToBeRemovedKey);
1753     QCOMPARE(*receivedToBeRemovedKey, 10);
1754     receivedToBeRemovedKey = std::nullopt;
1755 
1756     QVERIFY(receivedHasBeenRemovedKey);
1757     QCOMPARE(*receivedHasBeenRemovedKey, 10);
1758     receivedHasBeenRemovedKey = std::nullopt;
1759 
1760     QVERIFY(receivedAnyFrameChange);
1761     receivedAnyFrameChange = false;
1762 
1763     if (parentCommand) {
1764         // first redo is skipped
1765         parentCommand->redo();
1766         QVERIFY(!receivedAddedKey);
1767         QVERIFY(!receivedToBeRemovedKey);
1768         QVERIFY(!receivedHasBeenRemovedKey);
1769         QVERIFY(!receivedAnyFrameChange);
1770         parentCommand.reset(new KUndo2Command);
1771     }
1772 }
1773 
1774 void KisPaintDeviceTest::testFramesLeaking()
1775 {
1776     const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
1777     KisPaintDeviceSP dev = new KisPaintDevice(cs);
1778 
1779     TestUtil::TestingTimedDefaultBounds *bounds = new TestUtil::TestingTimedDefaultBounds();
1780     dev->setDefaultBounds(bounds);
1781 
1782     KisRasterKeyframeChannel *channel = dev->createKeyframeChannel(KisKeyframeChannel::Raster);
1783     QVERIFY(channel);
1784 
1785     KisPaintDeviceFramesInterface *i = dev->framesInterface();
1786     QVERIFY(i);
1787 
1788     QCOMPARE(i->frames().size(), 1);
1789 
1790     KisPaintDeviceFramesInterface::TestingDataObjects o;
1791 
1792     // Initial state: one frame, m_data is kept separate
1793     o = i->testingGetDataObjects();
1794     QVERIFY(o.m_data);
1795     QVERIFY(!o.m_lodData);
1796     QVERIFY(!o.m_externalFrameData);
1797     QCOMPARE(o.m_frames.size(), 1);
1798     QVERIFY(o.m_currentData == o.m_frames[0]);
1799 
1800     // add keyframe at position 10
1801     channel->addKeyframe(10);
1802 
1803     // two frames, m_data has a default empty value
1804     o = i->testingGetDataObjects();
1805     QVERIFY(o.m_data);
1806     QVERIFY(!o.m_lodData);
1807     QVERIFY(!o.m_externalFrameData);
1808     QCOMPARE(o.m_frames.size(), 2);
1809     QVERIFY(o.m_currentData == o.m_frames[0]);
1810 
1811     // add keyframe at position 20
1812     channel->addKeyframe(20);
1813 
1814     // three frames, m_data is default, current frame is 0
1815     o = i->testingGetDataObjects();
1816     QVERIFY(o.m_data);
1817     QVERIFY(!o.m_lodData);
1818     QVERIFY(!o.m_externalFrameData);
1819     QCOMPARE(o.m_frames.size(), 3);
1820     QVERIFY(o.m_currentData == o.m_frames[0]);
1821 
1822     // switch to frame 10
1823     bounds->testingSetTime(10);
1824 
1825     // three frames, m_data is default, current frame is 10
1826     o = i->testingGetDataObjects();
1827     QVERIFY(o.m_data);
1828     QVERIFY(!o.m_lodData);
1829     QVERIFY(!o.m_externalFrameData);
1830     QVERIFY(o.m_currentData == o.m_frames[1]);
1831     QCOMPARE(o.m_frames.size(), 3);
1832 
1833     // switch to frame 20
1834     bounds->testingSetTime(20);
1835 
1836     // three frames, m_data is default, current frame is 20
1837     o = i->testingGetDataObjects();
1838     QVERIFY(o.m_data);
1839     QVERIFY(!o.m_lodData);
1840     QVERIFY(!o.m_externalFrameData);
1841     QVERIFY(o.m_currentData == o.m_frames[2]);
1842     QCOMPARE(o.m_frames.size(), 3);
1843 
1844     // switch to frame 15
1845     bounds->testingSetTime(15);
1846 
1847     // three frames, m_data is default, current frame is 10
1848     o = i->testingGetDataObjects();
1849     QVERIFY(o.m_data);
1850     QVERIFY(!o.m_lodData);
1851     QVERIFY(!o.m_externalFrameData);
1852     QVERIFY(o.m_currentData == o.m_frames[1]);
1853     QCOMPARE(o.m_frames.size(), 3);
1854 
1855     // deletion of frame 0 is forbidden
1856     QVERIFY(channel->keyframeAt(0));
1857     channel->removeKeyframe(0);
1858 
1859     // delete keyframe at position 11
1860     int keyIndex = channel->activeKeyframeTime(11);
1861     QVERIFY(channel->keyframeAt(keyIndex));
1862     channel->removeKeyframe(keyIndex);
1863 
1864     // two frames, m_data is default, current frame is 0
1865     o = i->testingGetDataObjects();
1866     QVERIFY(o.m_data);
1867     QVERIFY(!o.m_lodData);
1868     QVERIFY(!o.m_externalFrameData);
1869     QCOMPARE(o.m_frames.size(), 2);
1870 
1871     // deletion of frame 0 is forbidden
1872     keyIndex = channel->activeKeyframeTime(11);
1873     QVERIFY(keyIndex != -1);
1874     channel->removeKeyframe(keyIndex);
1875 
1876     // nothing changed
1877     o = i->testingGetDataObjects();
1878     QVERIFY(o.m_data);
1879     QVERIFY(!o.m_lodData);
1880     QVERIFY(!o.m_externalFrameData);
1881     QCOMPARE(o.m_frames.size(), 2);
1882 
1883     // delete keyframe at position 20
1884     keyIndex = channel->activeKeyframeTime(20);
1885     QVERIFY(keyIndex != -1);
1886     channel->removeKeyframe(keyIndex);
1887 
1888     // one keyframe is left at position 0, m_data is default
1889     o = i->testingGetDataObjects();
1890     QVERIFY(o.m_data);
1891     QVERIFY(!o.m_lodData);
1892     QVERIFY(!o.m_externalFrameData);
1893     //QVERIFY(o.m_currentData == o.m_frames[0]);
1894     QCOMPARE(o.m_frames.size(), 1);
1895 
1896     // ensure all the objects in the list of all objects are unique
1897     QList<KisPaintDeviceData*> allObjects = i->testingGetDataObjectsList();
1898     QSet<KisPaintDeviceData*> uniqueObjects;
1899     Q_FOREACH (KisPaintDeviceData *obj, allObjects) {
1900         if (!obj) continue;
1901         QVERIFY(!uniqueObjects.contains(obj));
1902         uniqueObjects.insert(obj);
1903     }
1904 }
1905 
1906 void KisPaintDeviceTest::testFramesUndoRedo()
1907 {
1908     const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
1909     KisPaintDeviceSP dev = new KisPaintDevice(cs);
1910 
1911     TestUtil::TestingTimedDefaultBounds *bounds = new TestUtil::TestingTimedDefaultBounds();
1912     dev->setDefaultBounds(bounds);
1913 
1914     KisRasterKeyframeChannel *channel = dev->createKeyframeChannel(KisKeyframeChannel::Raster);
1915     QVERIFY(channel);
1916 
1917     KisPaintDeviceFramesInterface *i = dev->framesInterface();
1918     QVERIFY(i);
1919 
1920     QCOMPARE(i->frames().size(), 1);
1921 
1922     KisPaintDeviceFramesInterface::TestingDataObjects o;
1923 
1924     // Initial state: one frame, m_data shared
1925     o = i->testingGetDataObjects();
1926     QVERIFY(o.m_data); // default m_data should always be present
1927     QVERIFY(!o.m_lodData);
1928     QVERIFY(!o.m_externalFrameData);
1929     QCOMPARE(o.m_frames.size(), 1);
1930     QVERIFY(o.m_currentData == o.m_frames[0]);
1931 
1932     int frameId = -1;
1933     const int time = 1;
1934 
1935     {   // add a keyframe
1936         KUndo2Command cmdAdd;
1937 
1938         channel->addKeyframe(time, &cmdAdd);
1939         cmdAdd.redo();
1940 
1941         QVERIFY(channel->keyframeAt<KisRasterKeyframe>(time));
1942         frameId = channel->keyframeAt<KisRasterKeyframe>(time)->frameID();
1943         QCOMPARE(frameId, 1);
1944 
1945         o = i->testingGetDataObjects();
1946         QVERIFY(o.m_data); // default m_data should always be present
1947         QVERIFY(!o.m_lodData);
1948         QVERIFY(!o.m_externalFrameData);
1949         QCOMPARE(o.m_frames.size(), 2);
1950         QVERIFY(o.m_currentData == o.m_frames[0]);
1951 
1952         cmdAdd.undo();
1953 
1954         o = i->testingGetDataObjects();
1955         QVERIFY(o.m_data); // default m_data should always be present
1956         QVERIFY(!o.m_lodData);
1957         QVERIFY(!o.m_externalFrameData);
1958         QCOMPARE(o.m_frames.size(), 2);
1959         QVERIFY(o.m_currentData == o.m_frames[0]);
1960 
1961         cmdAdd.redo();
1962 
1963         o = i->testingGetDataObjects();
1964         QVERIFY(o.m_data); // default m_data should always be present
1965         QVERIFY(!o.m_lodData);
1966         QVERIFY(!o.m_externalFrameData);
1967         QCOMPARE(o.m_frames.size(), 2);
1968         QVERIFY(o.m_currentData == o.m_frames[0]);
1969     }
1970 
1971 
1972     {   // Removing a frame should keep the frame in memory until the undo is no longer valid.
1973         KUndo2Command cmdRemove;
1974         KisKeyframeSP keyframe = channel->keyframeAt(time);
1975         QVERIFY(keyframe);
1976 
1977         channel->removeKeyframe(time, &cmdRemove);
1978         cmdRemove.redo();
1979 
1980         o = i->testingGetDataObjects();
1981         QVERIFY(o.m_data); // default m_data should always be present
1982         QVERIFY(!o.m_lodData);
1983         QVERIFY(!o.m_externalFrameData);
1984         QCOMPARE(o.m_frames.size(), 2);
1985         QVERIFY(o.m_currentData == o.m_frames[0]);
1986 
1987         cmdRemove.undo();
1988 
1989         o = i->testingGetDataObjects();
1990         QVERIFY(o.m_data); // default m_data should always be present
1991         QVERIFY(!o.m_lodData);
1992         QVERIFY(!o.m_externalFrameData);
1993         QCOMPARE(o.m_frames.size(), 2);
1994         QVERIFY(o.m_currentData == o.m_frames[0]);
1995 
1996         cmdRemove.redo();
1997 
1998         o = i->testingGetDataObjects();
1999         QVERIFY(o.m_data); // default m_data should always be present
2000         QVERIFY(!o.m_lodData);
2001         QVERIFY(!o.m_externalFrameData);
2002         QCOMPARE(o.m_frames.size(), 2);
2003         QVERIFY(o.m_currentData == o.m_frames[0]);
2004     }
2005 
2006     // Once undo command is destroyed, ensure that memory is freed.
2007     o = i->testingGetDataObjects();
2008     QVERIFY(o.m_data); // default m_data should always be present
2009     QVERIFY(!o.m_lodData);
2010     QVERIFY(!o.m_externalFrameData);
2011     QCOMPARE(o.m_frames.size(), 1);
2012     QVERIFY(o.m_currentData == o.m_frames[0]);
2013 }
2014 
2015 void KisPaintDeviceTest::testFramesUndoRedoWithChannel()
2016 {
2017     const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
2018     KisPaintDeviceSP dev = new KisPaintDevice(cs);
2019 
2020     TestUtil::TestingTimedDefaultBounds *bounds = new TestUtil::TestingTimedDefaultBounds();
2021     dev->setDefaultBounds(bounds);
2022 
2023     KisRasterKeyframeChannel *channel = dev->createKeyframeChannel(KisKeyframeChannel::Raster);
2024     QVERIFY(channel);
2025 
2026     KisPaintDeviceFramesInterface *i = dev->framesInterface();
2027     QVERIFY(i);
2028 
2029     QCOMPARE(i->frames().size(), 1);
2030 
2031     KisPaintDeviceFramesInterface::TestingDataObjects o;
2032 
2033     // Initial state: one frame, m_data shared
2034     o = i->testingGetDataObjects();
2035     QVERIFY(o.m_data); // default m_data should always be present
2036     QVERIFY(!o.m_lodData);
2037     QVERIFY(!o.m_externalFrameData);
2038     QCOMPARE(o.m_frames.size(), 1);
2039     QVERIFY(o.m_currentData == o.m_frames[0]);
2040 
2041 
2042     { // add a keyframe
2043         KUndo2Command cmdAdd;
2044 
2045         channel->addKeyframe(10, &cmdAdd);
2046         cmdAdd.redo();
2047 
2048         QVERIFY(channel->keyframeAt(10));
2049 
2050         o = i->testingGetDataObjects();
2051         QVERIFY(o.m_data); // default m_data should always be present
2052         QVERIFY(!o.m_lodData);
2053         QVERIFY(!o.m_externalFrameData);
2054         QCOMPARE(o.m_frames.size(), 2);
2055         QVERIFY(o.m_currentData == o.m_frames[0]);
2056 
2057         cmdAdd.undo();
2058 
2059         QVERIFY(!channel->keyframeAt(10));
2060 
2061         o = i->testingGetDataObjects();
2062         QVERIFY(o.m_data); // default m_data should always be present
2063         QVERIFY(!o.m_lodData);
2064         QVERIFY(!o.m_externalFrameData);
2065         QCOMPARE(o.m_frames.size(), 2);
2066         QVERIFY(o.m_currentData == o.m_frames[0]);
2067 
2068         cmdAdd.redo();
2069 
2070         QVERIFY(channel->keyframeAt(10));
2071 
2072         o = i->testingGetDataObjects();
2073         QVERIFY(o.m_data); // default m_data should always be present
2074         QVERIFY(!o.m_lodData);
2075         QVERIFY(!o.m_externalFrameData);
2076         QCOMPARE(o.m_frames.size(), 2);
2077         QVERIFY(o.m_currentData == o.m_frames[0]);
2078     }
2079 
2080     {
2081         KUndo2Command cmdRemove;
2082         channel->removeKeyframe(10, &cmdRemove);
2083         cmdRemove.redo();
2084 
2085         QVERIFY(!channel->keyframeAt(10));
2086 
2087         o = i->testingGetDataObjects();
2088         QVERIFY(o.m_data); // default m_data should always be present
2089         QVERIFY(!o.m_lodData);
2090         QVERIFY(!o.m_externalFrameData);
2091         QCOMPARE(o.m_frames.size(), 2);
2092         QVERIFY(o.m_currentData == o.m_frames[0]);
2093 
2094         cmdRemove.undo();
2095 
2096         QVERIFY(channel->keyframeAt(10));
2097 
2098         o = i->testingGetDataObjects();
2099         QVERIFY(o.m_data); // default m_data should always be present
2100         QVERIFY(!o.m_lodData);
2101         QVERIFY(!o.m_externalFrameData);
2102         QCOMPARE(o.m_frames.size(), 2);
2103         QVERIFY(o.m_currentData == o.m_frames[0]);
2104 
2105         cmdRemove.redo();
2106 
2107         QVERIFY(!channel->keyframeAt(10));
2108 
2109         o = i->testingGetDataObjects();
2110         QVERIFY(o.m_data); // default m_data should always be present
2111         QVERIFY(!o.m_lodData);
2112         QVERIFY(!o.m_externalFrameData);
2113         QCOMPARE(o.m_frames.size(), 2);
2114         QVERIFY(o.m_currentData == o.m_frames[0]);
2115     }
2116 
2117     //Like above, ensure that when undo goes out of scope, frame data is also dropped.
2118     o = i->testingGetDataObjects();
2119     QVERIFY(o.m_data); // default m_data should always be present
2120     QVERIFY(!o.m_lodData);
2121     QVERIFY(!o.m_externalFrameData);
2122     QCOMPARE(o.m_frames.size(), 1);
2123     QVERIFY(o.m_currentData == o.m_frames[0]);
2124 
2125     {   //Move Keyframe
2126         KUndo2Command cmdMove;
2127         channel->addKeyframe(10);
2128         channel->moveKeyframe(10, 12, &cmdMove);
2129         cmdMove.redo();
2130 
2131         QVERIFY(!channel->keyframeAt(10));
2132         QVERIFY(channel->keyframeAt(12));
2133 
2134         o = i->testingGetDataObjects();
2135         QVERIFY(o.m_data); // default m_data should always be present
2136         QVERIFY(!o.m_lodData);
2137         QVERIFY(!o.m_externalFrameData);
2138         QCOMPARE(o.m_frames.size(), 2);
2139         QVERIFY(o.m_currentData == o.m_frames[0]);
2140 
2141         cmdMove.undo();
2142 
2143         QVERIFY(channel->keyframeAt(10));
2144         QVERIFY(!channel->keyframeAt(12));
2145 
2146         o = i->testingGetDataObjects();
2147         QVERIFY(o.m_data); // default m_data should always be present
2148         QVERIFY(!o.m_lodData);
2149         QVERIFY(!o.m_externalFrameData);
2150         QCOMPARE(o.m_frames.size(), 2);
2151         QVERIFY(o.m_currentData == o.m_frames[0]);
2152 
2153         cmdMove.redo();
2154 
2155         QVERIFY(!channel->keyframeAt(10));
2156         QVERIFY(channel->keyframeAt(12));
2157 
2158         o = i->testingGetDataObjects();
2159         QVERIFY(o.m_data); // default m_data should always be present
2160         QVERIFY(!o.m_lodData);
2161         QVERIFY(!o.m_externalFrameData);
2162         QCOMPARE(o.m_frames.size(), 2);
2163         QVERIFY(o.m_currentData == o.m_frames[0]);
2164     }
2165 }
2166 
2167 void fillRect(KisPaintDeviceSP dev, int time, const QRect &rc, TestUtil::TestingTimedDefaultBounds *bounds)
2168 {
2169     KUndo2Command parentCommand;
2170     KisRasterKeyframeChannel *channel = dev->keyframeChannel();
2171     channel->addKeyframe(time, &parentCommand);
2172 
2173     const int oldTime = bounds->currentTime();
2174     bounds->testingSetTime(time);
2175 
2176     KoColor color(Qt::red, dev->colorSpace());
2177     dev->fill(rc, color);
2178 
2179     bounds->testingSetTime(oldTime);
2180 }
2181 
2182 bool checkRect(KisPaintDeviceSP dev, int time, const QRect &rc, TestUtil::TestingTimedDefaultBounds *bounds)
2183 {
2184     const int oldTime = bounds->currentTime();
2185     bounds->testingSetTime(time);
2186 
2187     bool result = dev->exactBounds() == rc;
2188 
2189     if (!result) {
2190         qDebug() << "Failed to check frame:" << ppVar(time) << ppVar(rc) << ppVar(dev->exactBounds());
2191     }
2192 
2193     bounds->testingSetTime(oldTime);
2194 
2195     return result;
2196 }
2197 
2198 void testCrossDeviceFrameCopyImpl(bool useChannel)
2199 {
2200     const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
2201     KisPaintDeviceSP dev1 = new KisPaintDevice(cs);
2202     KisPaintDeviceSP dev2 = new KisPaintDevice(cs);
2203 
2204     const KoColorSpace *cs3 = KoColorSpaceRegistry::instance()->rgb16();
2205     KisPaintDeviceSP dev3 = new KisPaintDevice(cs3);
2206 
2207     TestUtil::TestingTimedDefaultBounds *bounds = new TestUtil::TestingTimedDefaultBounds();
2208     dev1->setDefaultBounds(bounds);
2209     dev2->setDefaultBounds(bounds);
2210     dev3->setDefaultBounds(bounds);
2211 
2212     KisRasterKeyframeChannel *channel1 = dev1->createKeyframeChannel(KisKeyframeChannel::Raster);
2213     KisPaintDeviceFramesInterface *i1 = dev1->framesInterface();
2214     QVERIFY(channel1);
2215     QVERIFY(i1);
2216 
2217     KisRasterKeyframeChannel *channel2 = dev2->createKeyframeChannel(KisKeyframeChannel::Raster);
2218     KisPaintDeviceFramesInterface *i2 = dev2->framesInterface();
2219     QVERIFY(channel2);
2220     QVERIFY(i2);
2221 
2222     KisRasterKeyframeChannel *channel3 = dev3->createKeyframeChannel(KisKeyframeChannel::Raster);
2223     KisPaintDeviceFramesInterface *i3 = dev3->framesInterface();
2224     QVERIFY(channel3);
2225     QVERIFY(i3);
2226 
2227     fillRect(dev1, 10, QRect(100,100,100,100), bounds);
2228     fillRect(dev2, 20, QRect(200,200,100,100), bounds);
2229     fillRect(dev3, 30, QRect(300,300,100,100), bounds);
2230 
2231     QCOMPARE(dev1->exactBounds(), QRect());
2232 
2233     const int dst10FrameID = channel1->keyframeAt<KisRasterKeyframe>(10)->frameID();
2234     const int src20FrameID = channel2->keyframeAt<KisRasterKeyframe>(20)->frameID();
2235     const int src30FrameID = channel3->keyframeAt<KisRasterKeyframe>(30)->frameID();
2236 
2237     KUndo2Command cmd1;
2238     if (!useChannel) {
2239         dev1->framesInterface()->uploadFrame(src20FrameID, dst10FrameID, dev2);
2240     } else {
2241         KisKeyframeChannel::copyKeyframe(channel2, 20, channel1, 10, &cmd1 );
2242 
2243     }
2244 
2245     QCOMPARE(dev1->exactBounds(), QRect());
2246     QVERIFY(checkRect(dev1, 10, QRect(200,200,100,100), bounds));
2247 
2248     if (useChannel) {
2249         cmd1.undo();
2250         QVERIFY(checkRect(dev1, 10, QRect(100,100,100,100), bounds));
2251     }
2252 
2253     KUndo2Command cmd2;
2254     if (!useChannel) {
2255         dev1->framesInterface()->uploadFrame(src30FrameID, dst10FrameID, dev3);
2256     } else {
2257         KisKeyframeChannel::copyKeyframe(channel3, 30, channel1, 10, &cmd2);
2258     }
2259 
2260     QCOMPARE(dev1->exactBounds(), QRect());
2261     QVERIFY(checkRect(dev1, 10, QRect(300,300,100,100), bounds));
2262 
2263     if (useChannel) {
2264         cmd2.undo();
2265         QVERIFY(checkRect(dev1, 10, QRect(100,100,100,100), bounds));
2266     }
2267 }
2268 
2269 void KisPaintDeviceTest::testCrossDeviceFrameCopyDirect()
2270 {
2271     testCrossDeviceFrameCopyImpl(false);
2272 }
2273 
2274 void KisPaintDeviceTest::testCrossDeviceFrameCopyChannel()
2275 {
2276     testCrossDeviceFrameCopyImpl(true);
2277 }
2278 
2279 void KisPaintDeviceTest::testCopyPaintDeviceWithFrames()
2280 {
2281     const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
2282     KisPaintDeviceSP dev = new KisPaintDevice(cs);
2283 
2284     TestUtil::TestingTimedDefaultBounds *bounds = new TestUtil::TestingTimedDefaultBounds();
2285     dev->setDefaultBounds(bounds);
2286 
2287     KisRasterKeyframeChannel *channel = dev->createKeyframeChannel(KisKeyframeChannel::Raster);
2288     QVERIFY(channel);
2289 
2290     KisPaintDeviceFramesInterface *i = dev->framesInterface();
2291     QVERIFY(i);
2292 
2293     QCOMPARE(i->frames().size(), 1);
2294 
2295     KisPaintDeviceFramesInterface::TestingDataObjects o;
2296 
2297     // Initial state: one frame, m_data shared
2298     o = i->testingGetDataObjects();
2299     QVERIFY(o.m_data); // m_data should always be present
2300     QVERIFY(!o.m_lodData);
2301     QVERIFY(!o.m_externalFrameData);
2302     QCOMPARE(o.m_frames.size(), 1);
2303     QVERIFY(o.m_currentData == o.m_frames[0]);
2304 
2305 
2306     // add a keyframe
2307 
2308     KUndo2Command cmdAdd;
2309 
2310     channel->addKeyframe(10, &cmdAdd);
2311     cmdAdd.redo();
2312 
2313     QVERIFY(channel->keyframeAt(10));
2314 
2315     o = i->testingGetDataObjects();
2316     QVERIFY(o.m_data); // m_data should always be present
2317     QVERIFY(!o.m_lodData);
2318     QVERIFY(!o.m_externalFrameData);
2319     QCOMPARE(o.m_frames.size(), 2);
2320     //QVERIFY(o.m_currentData == o.m_frames[0]);
2321 
2322     KisPaintDeviceSP newDev = new KisPaintDevice(*dev, KritaUtils::CopyAllFrames);
2323 
2324     QVERIFY(channel->keyframeAt(0));
2325     QVERIFY(channel->keyframeAt(10));
2326 }
2327 
2328 #include <boost/accumulators/accumulators.hpp>
2329 #include <boost/accumulators/statistics/stats.hpp>
2330 #include <boost/accumulators/statistics/variance.hpp>
2331 #include <boost/accumulators/statistics/min.hpp>
2332 #include <boost/accumulators/statistics/max.hpp>
2333 #include <boost/random/mersenne_twister.hpp>
2334 #include <boost/random/uniform_smallint.hpp>
2335 
2336 #include "KoCompositeOpRegistry.h"
2337 
2338 
2339 using namespace boost::accumulators;
2340 
2341 accumulator_set<qreal, stats<tag::variance, tag::max, tag::min> > accum;
2342 
2343 
2344 void KisPaintDeviceTest::testCompositionAssociativity()
2345 {
2346     const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
2347 
2348     qsrand(500);
2349 
2350     boost::mt11213b _rnd0(qrand());
2351     boost::mt11213b _rnd1(qrand());
2352     boost::mt11213b _rnd2(qrand());
2353     boost::mt11213b _rnd3(qrand());
2354 
2355     boost::uniform_smallint<int> rnd0(0, 255);
2356     boost::uniform_smallint<int> rnd1(0, 255);
2357     boost::uniform_smallint<int> rnd2(0, 255);
2358     boost::uniform_smallint<int> rnd3(0, 255);
2359 
2360     QList<KoCompositeOp*> allCompositeOps = cs->compositeOps();
2361 
2362     Q_FOREACH (const KoCompositeOp *op, allCompositeOps) {
2363 
2364         accumulator_set<qreal, stats<tag::variance, tag::max, tag::min> > accum;
2365 
2366         const int numIterations = 10000;
2367 
2368         for (int j = 0; j < numIterations; j++) {
2369             KoColor c1(QColor(rnd0(_rnd0), rnd1(_rnd1), rnd2(_rnd2), rnd3(_rnd3)), cs);
2370             KoColor c2(QColor(rnd0(_rnd0), rnd1(_rnd1), rnd2(_rnd2), rnd3(_rnd3)), cs);
2371             KoColor c3(QColor(rnd0(_rnd0), rnd1(_rnd1), rnd2(_rnd2), rnd3(_rnd3)), cs);
2372             //KoColor c4(QColor(rnd0(_rnd0), rnd1(_rnd1), rnd2(_rnd2), rnd3(_rnd3)), cs);
2373             //KoColor c5(QColor(rnd0(_rnd0), rnd1(_rnd1), rnd2(_rnd2), rnd3(_rnd3)), cs);
2374 
2375             KoColor r1(QColor(Qt::transparent), cs);
2376             KoColor r2(QColor(Qt::transparent), cs);
2377             KoColor r3(QColor(Qt::transparent), cs);
2378 
2379             op->composite(r1.data(), 0, c1.data(), 0, 0,0, 1,1, 255);
2380             op->composite(r1.data(), 0, c2.data(), 0, 0,0, 1,1, 255);
2381             op->composite(r1.data(), 0, c3.data(), 0, 0,0, 1,1, 255);
2382             //op->composite(r1.data(), 0, c4.data(), 0, 0,0, 1,1, 255);
2383             //op->composite(r1.data(), 0, c5.data(), 0, 0,0, 1,1, 255);
2384 
2385             op->composite(r3.data(), 0, c2.data(), 0, 0,0, 1,1, 255);
2386             op->composite(r3.data(), 0, c3.data(), 0, 0,0, 1,1, 255);
2387             //op->composite(r3.data(), 0, c4.data(), 0, 0,0, 1,1, 255);
2388             //op->composite(r3.data(), 0, c5.data(), 0, 0,0, 1,1, 255);
2389 
2390             op->composite(r2.data(), 0, c1.data(), 0, 0,0, 1,1, 255);
2391             op->composite(r2.data(), 0, r3.data(), 0, 0,0, 1,1, 255);
2392 
2393             const quint8 *p1 = r1.data();
2394             const quint8 *p2 = r2.data();
2395 
2396             if (memcmp(p1, p2, 4) != 0) {
2397                 for (int i = 0; i < 4; i++) {
2398                     accum(qAbs(p1[i] - p2[i]));
2399                 }
2400             }
2401 
2402         }
2403 
2404         qDebug("Errors for op %25s err rate %7.2f var %7.2f max %7.2f",
2405                op->id().toLatin1().data(),
2406                (qreal(count(accum)) / (4 * numIterations)),
2407                variance(accum),
2408                count(accum) > 0 ? (max)(accum) : 0);
2409     }
2410 }
2411 
2412 #include <kundo2stack.h>
2413 
2414 struct FillWorker : public QRunnable
2415 {
2416     FillWorker(KisPaintDeviceSP dev, const QRect &fillRect, bool clear)
2417         : m_dev(dev), m_fillRect(fillRect), m_clear(clear)
2418     {
2419         setAutoDelete(true);
2420     }
2421 
2422     void run() {
2423         if (m_clear) {
2424             m_dev->clear(m_fillRect);
2425         } else {
2426             const KoColor fillColor(Qt::red, m_dev->colorSpace());
2427             const int pixelSize = m_dev->colorSpace()->pixelSize();
2428 
2429             KisSequentialIterator it(m_dev, m_fillRect);
2430             while (it.nextPixel()) {
2431                 memcpy(it.rawData(), fillColor.data(), pixelSize);
2432             }
2433         }
2434     }
2435 
2436 private:
2437     KisPaintDeviceSP m_dev;
2438     QRect m_fillRect;
2439     bool m_clear;
2440 };
2441 
2442 #ifdef Q_OS_LINUX
2443 #include <malloc.h>
2444 #endif
2445 
2446 void KisPaintDeviceTest::stressTestMemoryFragmentation()
2447 {
2448     const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
2449     KisPaintDeviceSP dev = new KisPaintDevice(cs);
2450 
2451     KUndo2Stack undoStack;
2452 
2453 #ifdef LIMIT_LONG_TESTS
2454     const int numCycles = 3;
2455     undoStack.setUndoLimit(1);
2456 #else
2457     const int numCycles = 200;
2458     undoStack.setUndoLimit(10);
2459 #endif
2460 
2461     const int numThreads = 16;
2462     const int desiredWidth = 10000;
2463     const int patchSize = 81;
2464     const int numSidePatches = desiredWidth / patchSize;
2465 
2466     QThreadPool pool;
2467     pool.setMaxThreadCount(numThreads);
2468 
2469 
2470     for (int i = 0; i < numCycles; i++) {
2471         qDebug() << "iteration"<< i;
2472         // KisTransaction t(dev);
2473 
2474         for (int y = 0; y < numSidePatches; y++) {
2475             for (int x = 0; x < numSidePatches; x++) {
2476                 const QRect workerRect(x * patchSize, y * patchSize, patchSize, patchSize);
2477                 pool.start(new FillWorker(dev, workerRect, (i + x + y) & 0x1));
2478             }
2479         }
2480 
2481         pool.waitForDone();
2482         // undoStack.push(t.endAndTake());
2483 
2484         qDebug() << "Iteration:" << i;
2485 
2486 #ifdef Q_OS_LINUX
2487         struct mallinfo info = mallinfo();
2488         qDebug() << "Allocated on heap:" << (info.arena >> 20) << "MiB";
2489         qDebug() << "Mmapped regions:" << info.hblks << (info.hblkhd >> 20) << "MiB";
2490         qDebug() << "Free fastbin chunks:" << info.smblks << (info.fsmblks >> 10)  << "KiB";
2491         qDebug() << "Allocated in ordinary blocks" << (info.uordblks >> 20) << "MiB";
2492         qDebug() << "Free in ordinary blocks" << info.ordblks << (info.fordblks >> 20) << "MiB";
2493 #endif
2494         qDebug() << "========================================";
2495     }
2496 
2497     undoStack.clear();
2498 }
2499 
2500 KISTEST_MAIN(KisPaintDeviceTest)