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)