diff --git a/libs/flake/commands/KoShapeReorderCommand.cpp b/libs/flake/commands/KoShapeReorderCommand.cpp index b0ffdf56e8..e4bda3207d 100644 --- a/libs/flake/commands/KoShapeReorderCommand.cpp +++ b/libs/flake/commands/KoShapeReorderCommand.cpp @@ -1,225 +1,323 @@ /* This file is part of the KDE project * Copyright (C) 2006 Thomas Zander * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoShapeReorderCommand.h" #include "KoShape.h" #include "KoShape_p.h" #include "KoShapeManager.h" #include "KoShapeContainer.h" #include #include #include +KoShapeReorderCommand::IndexedShape::IndexedShape() +{ +} + +KoShapeReorderCommand::IndexedShape::IndexedShape(KoShape *_shape) + : zIndex(_shape->zIndex()), shape(_shape) +{ +} + class KoShapeReorderCommandPrivate { public: + KoShapeReorderCommandPrivate() {} KoShapeReorderCommandPrivate(const QList &s, QList &ni) : shapes(s), newIndexes(ni) { } QList shapes; QList previousIndexes; QList newIndexes; }; KoShapeReorderCommand::KoShapeReorderCommand(const QList &shapes, QList &newIndexes, KUndo2Command *parent) : KUndo2Command(parent), - d(new KoShapeReorderCommandPrivate(shapes, newIndexes)) + d(new KoShapeReorderCommandPrivate(shapes, newIndexes)) { Q_ASSERT(shapes.count() == newIndexes.count()); foreach (KoShape *shape, shapes) d->previousIndexes.append(shape->zIndex()); setText(kundo2_i18n("Reorder shapes")); } +KoShapeReorderCommand::KoShapeReorderCommand(const QList &shapes, KUndo2Command *parent) + : KUndo2Command(parent), + d(new KoShapeReorderCommandPrivate()) +{ + Q_FOREACH (const IndexedShape &index, shapes) { + d->shapes.append(index.shape); + d->newIndexes.append(index.zIndex); + d->previousIndexes.append(index.shape->zIndex()); + } + + setText(kundo2_i18n("Reorder shapes")); +} + KoShapeReorderCommand::~KoShapeReorderCommand() { delete d; } void KoShapeReorderCommand::redo() { KUndo2Command::redo(); for (int i = 0; i < d->shapes.count(); i++) { - d->shapes.at(i)->update(); + // z-index cannot chage the bounding rect of the shape, so + // no united updates needed d->shapes.at(i)->setZIndex(d->newIndexes.at(i)); d->shapes.at(i)->update(); } } void KoShapeReorderCommand::undo() { KUndo2Command::undo(); for (int i = 0; i < d->shapes.count(); i++) { - d->shapes.at(i)->update(); + // z-index cannot chage the bounding rect of the shape, so + // no united updates needed d->shapes.at(i)->setZIndex(d->previousIndexes.at(i)); d->shapes.at(i)->update(); } } static void prepare(KoShape *s, QMap > &newOrder, KoShapeManager *manager, KoShapeReorderCommand::MoveShapeType move) { KoShapeContainer *parent = s->parent(); QMap >::iterator it(newOrder.find(parent)); if (it == newOrder.end()) { QList children; if (parent != 0) { children = parent->shapes(); } else { // get all toplevel shapes children = manager->topLevelShapes(); } std::sort(children.begin(), children.end(), KoShape::compareShapeZIndex); // the append and prepend are needed so that the raise/lower of all shapes works as expected. children.append(0); children.prepend(0); it = newOrder.insert(parent, children); } QList & shapes(newOrder[parent]); int index = shapes.indexOf(s); if (index != -1) { shapes.removeAt(index); switch (move) { case KoShapeReorderCommand::BringToFront: index = shapes.size(); break; case KoShapeReorderCommand::RaiseShape: if (index < shapes.size()) { ++index; } break; case KoShapeReorderCommand::LowerShape: if (index > 0) { --index; } break; case KoShapeReorderCommand::SendToBack: index = 0; break; } shapes.insert(index,s); } } // static KoShapeReorderCommand *KoShapeReorderCommand::createCommand(const QList &shapes, KoShapeManager *manager, MoveShapeType move, KUndo2Command *parent) { /** * TODO: this method doesn't handle the case when one of the shapes * has maximum or minimum zIndex value (which is 16-bit in our case)! */ QList newIndexes; QList changedShapes; QMap > newOrder; QList sortedShapes(shapes); std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); if (move == BringToFront || move == LowerShape) { for (int i = 0; i < sortedShapes.size(); ++i) { prepare(sortedShapes.at(i), newOrder, manager, move); } } else { for (int i = sortedShapes.size() - 1; i >= 0; --i) { prepare(sortedShapes.at(i), newOrder, manager, move); } } QMap >::iterator newIt(newOrder.begin()); for (; newIt!= newOrder.end(); ++newIt) { QList order(newIt.value()); order.removeAll(0); int index = -KoShape::maxZIndex - 1; // set minimum zIndex int pos = 0; for (; pos < order.size(); ++pos) { if (order[pos]->zIndex() > index) { index = order[pos]->zIndex(); } else { break; } } if (pos == order.size()) { //nothing needs to be done continue; } else if (pos <= order.size() / 2) { // new index for the front int startIndex = order[pos]->zIndex() - pos; for (int i = 0; i < pos; ++i) { changedShapes.append(order[i]); newIndexes.append(startIndex++); } } else { //new index for the end for (int i = pos; i < order.size(); ++i) { changedShapes.append(order[i]); newIndexes.append(++index); } } } Q_ASSERT(changedShapes.count() == newIndexes.count()); return changedShapes.isEmpty() ? 0: new KoShapeReorderCommand(changedShapes, newIndexes, parent); } KoShapeReorderCommand *KoShapeReorderCommand::mergeInShape(QList shapes, KoShape *newShape, KUndo2Command *parent) { std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); QList reindexedShapes; QList reindexedIndexes; const int originalShapeZIndex = newShape->zIndex(); int newShapeZIndex = originalShapeZIndex; int lastOccupiedShapeZIndex = originalShapeZIndex + 1; Q_FOREACH (KoShape *shape, shapes) { if (shape == newShape) continue; const int zIndex = shape->zIndex(); if (newShapeZIndex == originalShapeZIndex) { if (zIndex == originalShapeZIndex) { newShapeZIndex = originalShapeZIndex + 1; lastOccupiedShapeZIndex = newShapeZIndex; reindexedShapes << newShape; reindexedIndexes << newShapeZIndex; } } else { if (newShapeZIndex != originalShapeZIndex && zIndex >= newShapeZIndex && zIndex <= lastOccupiedShapeZIndex) { lastOccupiedShapeZIndex = zIndex + 1; reindexedShapes << shape; reindexedIndexes << lastOccupiedShapeZIndex; } } } return !reindexedShapes.isEmpty() ? new KoShapeReorderCommand(reindexedShapes, reindexedIndexes, parent) : 0; } + +namespace { + +QList +homogenizeZIndexes(QList shapes) +{ + if (shapes.isEmpty()) return shapes; + + // the shapes are expected to be sorted, we just need to adjust the indexes + + int lastIndex = shapes.begin()->zIndex; + + auto it = shapes.begin() + 1; + while (it != shapes.end()) { + if (it->zIndex <= lastIndex) { + it->zIndex = lastIndex + 1; + } + lastIndex = it->zIndex; + ++it; + } + + const int overflowSize = shapes.last().zIndex - int(std::numeric_limits::max()); + + if (overflowSize > 0) { + if (shapes.first().zIndex - overflowSize > int(std::numeric_limits::min())) { + for (auto it = shapes.begin(); it != shapes.end(); ++it) { + it->zIndex -= overflowSize; + } + } else { + int index = shapes.size() < int(std::numeric_limits::max()) ? + 0 : + int(std::numeric_limits::max()) - shapes.size(); + + for (auto it = shapes.begin(); it != shapes.end(); ++it) { + it->zIndex = index; + index++; + } + } + } + + return shapes; +} + +} + +QList +KoShapeReorderCommand::mergeDownShapes(QList shapesBelow, QList shapesAbove) +{ + std::sort(shapesBelow.begin(), shapesBelow.end(), KoShape::compareShapeZIndex); + std::sort(shapesAbove.begin(), shapesAbove.end(), KoShape::compareShapeZIndex); + + QList shapes; + Q_FOREACH (KoShape *shape, shapesBelow) { + shapes.append(IndexedShape(shape)); + } + + Q_FOREACH (KoShape *shape, shapesAbove) { + shapes.append(IndexedShape(shape)); + } + + shapes = homogenizeZIndexes(shapes); + + // remove shapes that didn't change + for (auto it = shapes.begin(); it != shapes.end();) { + if (it->zIndex == it->shape->zIndex()) { + it = shapes.erase(it); + } else { + ++it; + } + } + + return shapes; +} diff --git a/libs/flake/commands/KoShapeReorderCommand.h b/libs/flake/commands/KoShapeReorderCommand.h index 1ea442c11d..1a04b59844 100644 --- a/libs/flake/commands/KoShapeReorderCommand.h +++ b/libs/flake/commands/KoShapeReorderCommand.h @@ -1,89 +1,106 @@ /* This file is part of the KDE project * Copyright (C) 2006 Thomas Zander * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOSHAPEREORDERCOMMAND_H #define KOSHAPEREORDERCOMMAND_H #include "kritaflake_export.h" #include #include class KoShape; class KoShapeManager; class KoShapeReorderCommandPrivate; /// This command allows you to change the zIndex of a number of shapes. class KRITAFLAKE_EXPORT KoShapeReorderCommand : public KUndo2Command { +public: + struct IndexedShape { + IndexedShape(); + IndexedShape(KoShape *_shape); + + int zIndex = 0; + KoShape *shape = 0; + }; + + public: /** * Constructor. * @param shapes the set of objects that are moved. * @param newIndexes the new indexes for the shapes. * this list naturally must have the same amount of items as the shapes set. * @param parent the parent command used for macro commands */ KoShapeReorderCommand(const QList &shapes, QList &newIndexes, KUndo2Command *parent = 0); + KoShapeReorderCommand(const QList &shapes, KUndo2Command *parent = 0); ~KoShapeReorderCommand() override; /// An enum for defining what kind of reordering to use. enum MoveShapeType { RaiseShape, ///< raise the selected shape to the level that it is above the shape that is on top of it. LowerShape, ///< Lower the selected shape to the level that it is below the shape that is below it. BringToFront, ///< Raise the selected shape to be on top of all shapes. SendToBack ///< Lower the selected shape to be below all other shapes. }; /** * Create a new KoShapeReorderCommand by calculating the new indexes required to move the shapes * according to the move parameter. * @param shapes all the shapes that should be moved. * @param manager the shapeManager that contains all the shapes that could have their indexes changed. * @param move the moving type. * @param parent the parent command for grouping purposes. * @return command for reording the shapes or 0 if no reordering happened */ static KoShapeReorderCommand *createCommand(const QList &shapes, KoShapeManager *manager, MoveShapeType move, KUndo2Command *parent = 0); /** * @brief mergeInShape adjust zIndex of all the \p shapes and \p newShape to * avoid collisions between \p shapes and \p newShape. * * Note1: \p newShape may or may not be contained in \p shapes, there * is no difference. * Note2: the collisions inside \p shapes are ignored. They are just * adjusted to avoid collisions with \p newShape only * @param parent the parent command for grouping purposes. * @return command for reording the shapes or 0 if no reordering happened */ static KoShapeReorderCommand *mergeInShape(QList shapes, KoShape *newShape, KUndo2Command *parent = 0); + /** + * Put all the shapes in \p shapesAbove above the shapes in \p shapesBelow, adjusting their + * z-index values. + */ + static QList mergeDownShapes(QList shapesBelow, QList shapesAbove); + /// redo the command void redo() override; /// revert the actions done in redo void undo() override; private: KoShapeReorderCommandPrivate * const d; }; #endif diff --git a/libs/flake/commands/KoShapeShadowCommand.cpp b/libs/flake/commands/KoShapeShadowCommand.cpp index 60fe2e8bd6..5f57e9a2fe 100644 --- a/libs/flake/commands/KoShapeShadowCommand.cpp +++ b/libs/flake/commands/KoShapeShadowCommand.cpp @@ -1,125 +1,135 @@ /* This file is part of the KDE project * Copyright (C) 2008 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoShapeShadowCommand.h" #include "KoShape.h" #include "KoShapeShadow.h" #include class Q_DECL_HIDDEN KoShapeShadowCommand::Private { public: Private() {} ~Private() { Q_FOREACH (KoShapeShadow* shadow, oldShadows) { if (shadow && !shadow->deref()) delete shadow; } } void addOldShadow(KoShapeShadow * oldShadow) { if (oldShadow) oldShadow->ref(); oldShadows.append(oldShadow); } void addNewShadow(KoShapeShadow * newShadow) { if (newShadow) newShadow->ref(); newShadows.append(newShadow); } QList shapes; ///< the shapes to set shadow for QList oldShadows; ///< the old shadows, one for each shape QList newShadows; ///< the new shadows to set }; KoShapeShadowCommand::KoShapeShadowCommand(const QList &shapes, KoShapeShadow *shadow, KUndo2Command *parent) : KUndo2Command(parent) , d(new Private()) { d->shapes = shapes; // save old shadows Q_FOREACH (KoShape *shape, d->shapes) { d->addOldShadow(shape->shadow()); d->addNewShadow(shadow); } setText(kundo2_i18n("Set Shadow")); } KoShapeShadowCommand::KoShapeShadowCommand(const QList &shapes, const QList &shadows, KUndo2Command *parent) : KUndo2Command(parent) , d(new Private()) { Q_ASSERT(shapes.count() == shadows.count()); d->shapes = shapes; // save old shadows Q_FOREACH (KoShape *shape, shapes) d->addOldShadow(shape->shadow()); Q_FOREACH (KoShapeShadow * shadow, shadows) d->addNewShadow(shadow); setText(kundo2_i18n("Set Shadow")); } KoShapeShadowCommand::KoShapeShadowCommand(KoShape* shape, KoShapeShadow *shadow, KUndo2Command *parent) : KUndo2Command(parent) , d(new Private()) { d->shapes.append(shape); d->addNewShadow(shadow); d->addOldShadow(shape->shadow()); setText(kundo2_i18n("Set Shadow")); } KoShapeShadowCommand::~KoShapeShadowCommand() { delete d; } void KoShapeShadowCommand::redo() { KUndo2Command::redo(); int shapeCount = d->shapes.count(); for (int i = 0; i < shapeCount; ++i) { KoShape *shape = d->shapes[i]; - shape->update(); - shape->setShadow(d->newShadows[i]); - shape->update(); + + // TODO: implement comparison operator for KoShapeShadow + // (or just deprecate it entirely) + if (shape->shadow() || d->newShadows[i]) { + const QRectF oldBoundingRect = shape->boundingRect(); + shape->setShadow(d->newShadows[i]); + shape->updateAbsolute(oldBoundingRect | shape->boundingRect()); + } } } void KoShapeShadowCommand::undo() { KUndo2Command::undo(); int shapeCount = d->shapes.count(); for (int i = 0; i < shapeCount; ++i) { KoShape *shape = d->shapes[i]; - shape->update(); - shape->setShadow(d->oldShadows[i]); - shape->update(); + + // TODO: implement comparison operator for KoShapeShadow + // (or just deprecate it entirely) + if (shape->shadow() || d->oldShadows[i]) { + const QRectF oldBoundingRect = shape->boundingRect(); + shape->setShadow(d->oldShadows[i]); + shape->updateAbsolute(oldBoundingRect | shape->boundingRect()); + } } } diff --git a/libs/image/tests/kis_image_test.cpp b/libs/image/tests/kis_image_test.cpp index 0580d67aa1..79e6fd2432 100644 --- a/libs/image/tests/kis_image_test.cpp +++ b/libs/image/tests/kis_image_test.cpp @@ -1,1160 +1,1160 @@ /* * Copyright (c) 2005 Adrian Page * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_image_test.h" #include #include #include #include #include "filter/kis_filter.h" #include "filter/kis_filter_configuration.h" #include "filter/kis_filter_registry.h" #include "kis_image.h" #include "kis_paint_layer.h" #include "kis_group_layer.h" #include "kis_adjustment_layer.h" #include "kis_selection.h" #include #include #include "kis_keyframe_channel.h" #include "kis_selection_mask.h" #include "kis_layer_utils.h" #include "kis_annotation.h" #include "KisProofingConfiguration.h" #include "kis_undo_stores.h" #define IMAGE_WIDTH 128 #define IMAGE_HEIGHT 128 void KisImageTest::layerTests() { KisImageSP image = new KisImage(0, IMAGE_WIDTH, IMAGE_WIDTH, 0, "layer tests"); QVERIFY(image->rootLayer() != 0); QVERIFY(image->rootLayer()->firstChild() == 0); KisLayerSP layer = new KisPaintLayer(image, "layer 1", OPACITY_OPAQUE_U8); image->addNode(layer); QVERIFY(image->rootLayer()->firstChild()->objectName() == layer->objectName()); } void KisImageTest::benchmarkCreation() { const QRect imageRect(0,0,3000,2000); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); QList images; QList stores; QBENCHMARK { for (int i = 0; i < 10; i++) { stores << new KisSurrogateUndoStore(); } for (int i = 0; i < 10; i++) { KisImageSP image = new KisImage(stores.takeLast(), imageRect.width(), imageRect.height(), cs, "test image"); images << image; } } } #include "testutil.h" #include "kis_stroke_strategy.h" #include class ForbiddenLodStrokeStrategy : public KisStrokeStrategy { public: ForbiddenLodStrokeStrategy(std::function lodCallback) : m_lodCallback(lodCallback) { } KisStrokeStrategy* createLodClone(int levelOfDetail) override { Q_UNUSED(levelOfDetail); m_lodCallback(); return 0; } private: std::function m_lodCallback; }; void notifyVar(bool *value) { *value = true; } void KisImageTest::testBlockLevelOfDetail() { TestUtil::MaskParent p; QCOMPARE(p.image->currentLevelOfDetail(), 0); p.image->setDesiredLevelOfDetail(1); p.image->waitForDone(); QCOMPARE(p.image->currentLevelOfDetail(), 0); { bool lodCreated = false; KisStrokeId id = p.image->startStroke( new ForbiddenLodStrokeStrategy( std::bind(¬ifyVar, &lodCreated))); p.image->endStroke(id); p.image->waitForDone(); QVERIFY(lodCreated); } p.image->setLevelOfDetailBlocked(true); { bool lodCreated = false; KisStrokeId id = p.image->startStroke( new ForbiddenLodStrokeStrategy( std::bind(¬ifyVar, &lodCreated))); p.image->endStroke(id); p.image->waitForDone(); QVERIFY(!lodCreated); } p.image->setLevelOfDetailBlocked(false); p.image->setDesiredLevelOfDetail(1); { bool lodCreated = false; KisStrokeId id = p.image->startStroke( new ForbiddenLodStrokeStrategy( std::bind(¬ifyVar, &lodCreated))); p.image->endStroke(id); p.image->waitForDone(); QVERIFY(lodCreated); } } void KisImageTest::testConvertImageColorSpace() { const KoColorSpace *cs8 = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 1000, 1000, cs8, "stest"); KisPaintDeviceSP device1 = new KisPaintDevice(cs8); KisLayerSP paint1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8, device1); KisFilterSP filter = KisFilterRegistry::instance()->value("blur"); Q_ASSERT(filter); KisFilterConfigurationSP configuration = filter->defaultConfiguration(); Q_ASSERT(configuration); KisLayerSP blur1 = new KisAdjustmentLayer(image, "blur1", configuration, 0); image->addNode(paint1, image->root()); image->addNode(blur1, image->root()); image->refreshGraph(); const KoColorSpace *cs16 = KoColorSpaceRegistry::instance()->rgb16(); image->lock(); image->convertImageColorSpace(cs16, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); image->unlock(); QVERIFY(*cs16 == *image->colorSpace()); QVERIFY(*cs16 == *image->root()->colorSpace()); QVERIFY(*cs16 == *paint1->colorSpace()); QVERIFY(*cs16 == *blur1->colorSpace()); QVERIFY(!image->root()->compositeOp()); QVERIFY(*cs16 == *paint1->compositeOp()->colorSpace()); QVERIFY(*cs16 == *blur1->compositeOp()->colorSpace()); image->refreshGraph(); } void KisImageTest::testGlobalSelection() { const KoColorSpace *cs8 = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 1000, 1000, cs8, "stest"); QCOMPARE(image->globalSelection(), KisSelectionSP(0)); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 0U); KisSelectionSP selection1 = new KisSelection(new KisDefaultBounds(image)); KisSelectionSP selection2 = new KisSelection(new KisDefaultBounds(image)); image->setGlobalSelection(selection1); QCOMPARE(image->globalSelection(), selection1); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 1U); image->setGlobalSelection(selection2); QCOMPARE(image->globalSelection(), selection2); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 1U); image->deselectGlobalSelection(); QCOMPARE(image->globalSelection(), KisSelectionSP(0)); QCOMPARE(image->canReselectGlobalSelection(), true); QCOMPARE(image->root()->childCount(), 0U); image->reselectGlobalSelection(); QCOMPARE(image->globalSelection(), selection2); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 1U); // mixed deselecting/setting/reselecting image->deselectGlobalSelection(); QCOMPARE(image->globalSelection(), KisSelectionSP(0)); QCOMPARE(image->canReselectGlobalSelection(), true); QCOMPARE(image->root()->childCount(), 0U); image->setGlobalSelection(selection1); QCOMPARE(image->globalSelection(), selection1); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 1U); } void KisImageTest::testCloneImage() { KisImageSP image = new KisImage(0, IMAGE_WIDTH, IMAGE_WIDTH, 0, "layer tests"); QVERIFY(image->rootLayer() != 0); QVERIFY(image->rootLayer()->firstChild() == 0); KisAnnotationSP annotation = new KisAnnotation("mytype", "mydescription", QByteArray()); image->addAnnotation(annotation); QVERIFY(image->annotation("mytype")); KisProofingConfigurationSP proofing = toQShared(new KisProofingConfiguration()); image->setProofingConfiguration(proofing); QVERIFY(image->proofingConfiguration()); const KoColor defaultColor(Qt::green, image->colorSpace()); image->setDefaultProjectionColor(defaultColor); QCOMPARE(image->defaultProjectionColor(), defaultColor); KisLayerSP layer = new KisPaintLayer(image, "layer1", OPACITY_OPAQUE_U8); image->addNode(layer); KisLayerSP layer2 = new KisPaintLayer(image, "layer2", OPACITY_OPAQUE_U8); image->addNode(layer2); QVERIFY(layer->visible()); QVERIFY(layer2->visible()); QVERIFY(TestUtil::findNode(image->root(), "layer1")); QVERIFY(TestUtil::findNode(image->root(), "layer2")); QUuid uuid1 = layer->uuid(); QUuid uuid2 = layer2->uuid(); { KisImageSP newImage = image->clone(); KisNodeSP newLayer1 = TestUtil::findNode(newImage->root(), "layer1"); KisNodeSP newLayer2 = TestUtil::findNode(newImage->root(), "layer2"); QVERIFY(newLayer1); QVERIFY(newLayer2); QVERIFY(newLayer1->uuid() != uuid1); QVERIFY(newLayer2->uuid() != uuid2); KisAnnotationSP newAnnotation = newImage->annotation("mytype"); QVERIFY(newAnnotation); QVERIFY(newAnnotation != annotation); KisProofingConfigurationSP newProofing = newImage->proofingConfiguration(); QVERIFY(newProofing); QVERIFY(newProofing != proofing); QCOMPARE(newImage->defaultProjectionColor(), defaultColor); } { KisImageSP newImage = image->clone(true); KisNodeSP newLayer1 = TestUtil::findNode(newImage->root(), "layer1"); KisNodeSP newLayer2 = TestUtil::findNode(newImage->root(), "layer2"); QVERIFY(newLayer1); QVERIFY(newLayer2); QVERIFY(newLayer1->uuid() == uuid1); QVERIFY(newLayer2->uuid() == uuid2); } } void KisImageTest::testLayerComposition() { KisImageSP image = new KisImage(0, IMAGE_WIDTH, IMAGE_WIDTH, 0, "layer tests"); QVERIFY(image->rootLayer() != 0); QVERIFY(image->rootLayer()->firstChild() == 0); KisLayerSP layer = new KisPaintLayer(image, "layer1", OPACITY_OPAQUE_U8); image->addNode(layer); KisLayerSP layer2 = new KisPaintLayer(image, "layer2", OPACITY_OPAQUE_U8); image->addNode(layer2); QVERIFY(layer->visible()); QVERIFY(layer2->visible()); KisLayerComposition comp(image, "comp 1"); comp.store(); layer2->setVisible(false); QVERIFY(layer->visible()); QVERIFY(!layer2->visible()); KisLayerComposition comp2(image, "comp 2"); comp2.store(); KisLayerCompositionSP comp3 = toQShared(new KisLayerComposition(image, "comp 3")); comp3->store(); image->addComposition(comp3); comp.apply(); QVERIFY(layer->visible()); QVERIFY(layer2->visible()); comp2.apply(); QVERIFY(layer->visible()); QVERIFY(!layer2->visible()); comp.apply(); QVERIFY(layer->visible()); QVERIFY(layer2->visible()); KisImageSP newImage = image->clone(); KisNodeSP newLayer1 = TestUtil::findNode(newImage->root(), "layer1"); KisNodeSP newLayer2 = TestUtil::findNode(newImage->root(), "layer2"); QVERIFY(newLayer1); QVERIFY(newLayer2); QVERIFY(newLayer1->visible()); QVERIFY(newLayer2->visible()); KisLayerComposition newComp1(comp, newImage); newComp1.apply(); QVERIFY(newLayer1->visible()); QVERIFY(newLayer2->visible()); KisLayerComposition newComp2(comp2, newImage); newComp2.apply(); QVERIFY(newLayer1->visible()); QVERIFY(!newLayer2->visible()); newComp1.apply(); QVERIFY(newLayer1->visible()); QVERIFY(newLayer2->visible()); QVERIFY(!newImage->compositions().isEmpty()); KisLayerCompositionSP newComp3 = newImage->compositions().first(); newComp3->apply(); QVERIFY(newLayer1->visible()); QVERIFY(!newLayer2->visible()); } #include "testutil.h" #include "kis_group_layer.h" #include "kis_transparency_mask.h" #include "kis_psd_layer_style.h" struct FlattenTestImage { FlattenTestImage() : refRect(0,0,512,512) , p(refRect) { image = p.image; undoStore = p.undoStore; layer1 = p.layer; layer5 = new KisPaintLayer(p.image, "paint5", 0.4 * OPACITY_OPAQUE_U8); layer5->disableAlphaChannel(true); layer2 = new KisPaintLayer(p.image, "paint2", OPACITY_OPAQUE_U8); tmask = new KisTransparencyMask(); // check channel flags // make addition composite op group1 = new KisGroupLayer(p.image, "group1", OPACITY_OPAQUE_U8); layer3 = new KisPaintLayer(p.image, "paint3", OPACITY_OPAQUE_U8); layer4 = new KisPaintLayer(p.image, "paint4", OPACITY_OPAQUE_U8); layer6 = new KisPaintLayer(p.image, "paint6", OPACITY_OPAQUE_U8); layer7 = new KisPaintLayer(p.image, "paint7", OPACITY_OPAQUE_U8); layer8 = new KisPaintLayer(p.image, "paint8", OPACITY_OPAQUE_U8); layer7->setCompositeOpId(COMPOSITE_ADD); layer8->setCompositeOpId(COMPOSITE_ADD); QRect rect1(100, 100, 100, 100); QRect rect2(150, 150, 150, 150); QRect tmaskRect(200,200,100,100); QRect rect3(400, 100, 100, 100); QRect rect4(500, 100, 100, 100); QRect rect5(50, 50, 100, 100); QRect rect6(50, 250, 100, 100); QRect rect7(50, 350, 50, 50); QRect rect8(50, 400, 50, 50); layer1->paintDevice()->fill(rect1, KoColor(Qt::red, p.image->colorSpace())); layer2->paintDevice()->fill(rect2, KoColor(Qt::green, p.image->colorSpace())); tmask->testingInitSelection(tmaskRect, layer2); layer3->paintDevice()->fill(rect3, KoColor(Qt::blue, p.image->colorSpace())); layer4->paintDevice()->fill(rect4, KoColor(Qt::yellow, p.image->colorSpace())); layer5->paintDevice()->fill(rect5, KoColor(Qt::green, p.image->colorSpace())); layer6->paintDevice()->fill(rect6, KoColor(Qt::cyan, p.image->colorSpace())); layer7->paintDevice()->fill(rect7, KoColor(Qt::red, p.image->colorSpace())); layer8->paintDevice()->fill(rect8, KoColor(Qt::green, p.image->colorSpace())); KisPSDLayerStyleSP style(new KisPSDLayerStyle()); style->dropShadow()->setEffectEnabled(true); style->dropShadow()->setDistance(10.0); style->dropShadow()->setSpread(80.0); style->dropShadow()->setSize(10); style->dropShadow()->setNoise(0); style->dropShadow()->setKnocksOut(false); style->dropShadow()->setOpacity(80.0); layer2->setLayerStyle(style); layer2->setCompositeOpId(COMPOSITE_ADD); group1->setCompositeOpId(COMPOSITE_ADD); p.image->addNode(layer5); p.image->addNode(layer2); p.image->addNode(tmask, layer2); p.image->addNode(group1); p.image->addNode(layer3, group1); p.image->addNode(layer4, group1); p.image->addNode(layer6); p.image->addNode(layer7); p.image->addNode(layer8); p.image->initialRefreshGraph(); // dbgKrita << ppVar(layer1->exactBounds()); // dbgKrita << ppVar(layer5->exactBounds()); // dbgKrita << ppVar(layer2->exactBounds()); // dbgKrita << ppVar(group1->exactBounds()); // dbgKrita << ppVar(layer3->exactBounds()); // dbgKrita << ppVar(layer4->exactBounds()); - TestUtil::ExternalImageChecker chk("flatten", "imagetest"); + TestUtil::ReferenceImageChecker chk("flatten", "imagetest"); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); } QRect refRect; TestUtil::MaskParent p; KisImageSP image; KisSurrogateUndoStore *undoStore; KisPaintLayerSP layer1; KisPaintLayerSP layer2; KisTransparencyMaskSP tmask; KisGroupLayerSP group1; KisPaintLayerSP layer3; KisPaintLayerSP layer4; KisPaintLayerSP layer5; KisPaintLayerSP layer6; KisPaintLayerSP layer7; KisPaintLayerSP layer8; }; template KisLayerSP flattenLayerHelper(ContainerTest &p, KisLayerSP layer, bool nothingHappens = false) { QSignalSpy spy(p.image.data(), SIGNAL(sigNodeAddedAsync(KisNodeSP))); //p.image->flattenLayer(layer); KisLayerUtils::flattenLayer(p.image, layer); p.image->waitForDone(); if (nothingHappens) { Q_ASSERT(!spy.count()); return layer; } Q_ASSERT(spy.count() == 1); QList arguments = spy.takeFirst(); KisNodeSP newNode = arguments.first().value(); KisLayerSP newLayer = qobject_cast(newNode.data()); return newLayer; } void KisImageTest::testFlattenLayer() { FlattenTestImage p; - TestUtil::ExternalImageChecker chk("flatten", "imagetest"); + TestUtil::ReferenceImageChecker chk("flatten", "imagetest"); { QCOMPARE(p.layer2->compositeOpId(), COMPOSITE_ADD); KisLayerSP newLayer = flattenLayerHelper(p, p.layer2); //KisLayerSP newLayer = p.image->flattenLayer(p.layer2); //p.image->waitForDone(); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer2_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); } { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_ADD); KisLayerSP newLayer = flattenLayerHelper(p, p.group1); //KisLayerSP newLayer = p.image->flattenLayer(p.group1); //p.image->waitForDone(); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "02_group1_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_ADD); QCOMPARE(newLayer->exactBounds(), QRect(400, 100, 200, 100)); } { QCOMPARE(p.layer5->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.layer5->alphaChannelDisabled(), true); KisLayerSP newLayer = flattenLayerHelper(p, p.layer5, true); //KisLayerSP newLayer = p.image->flattenLayer(p.layer5); //p.image->waitForDone(); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "03_layer5_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50, 50, 100, 100)); QCOMPARE(newLayer->alphaChannelDisabled(), true); } } #include template KisLayerSP mergeHelper(ContainerTest &p, KisLayerSP layer) { KisNodeSP parent = layer->parent(); const int newIndex = parent->index(layer) - 1; p.image->mergeDown(layer, KisMetaData::MergeStrategyRegistry::instance()->get("Drop")); //KisLayerUtils::mergeDown(p.image, layer, KisMetaData::MergeStrategyRegistry::instance()->get("Drop")); p.image->waitForDone(); KisLayerSP newLayer = qobject_cast(parent->at(newIndex).data()); return newLayer; } void KisImageTest::testMergeDown() { FlattenTestImage p; - TestUtil::ExternalImageChecker img("flatten", "imagetest"); - TestUtil::ExternalImageChecker chk("mergedown_simple", "imagetest"); + TestUtil::ReferenceImageChecker img("flatten", "imagetest"); + TestUtil::ReferenceImageChecker chk("mergedown_simple", "imagetest"); { QCOMPARE(p.layer5->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.layer5->alphaChannelDisabled(), true); KisLayerSP newLayer = mergeHelper(p, p.layer5); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer5_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->alphaChannelDisabled(), false); } { QCOMPARE(p.layer2->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer2->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.layer2); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "02_layer2_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(100, 100, 213, 217)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.group1->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.group1); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "03_group1_mergedown_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(100, 100, 500, 217)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } void KisImageTest::testMergeDownDestinationInheritsAlpha() { FlattenTestImage p; - TestUtil::ExternalImageChecker img("flatten", "imagetest"); - TestUtil::ExternalImageChecker chk("mergedown_dst_inheritsalpha", "imagetest"); + TestUtil::ReferenceImageChecker img("flatten", "imagetest"); + TestUtil::ReferenceImageChecker chk("mergedown_dst_inheritsalpha", "imagetest"); { QCOMPARE(p.layer2->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer2->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.layer2); // WARN: this check is suspicious! QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_proj_merged_layer2_over_layer5_IA")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer2_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50,50, 263, 267)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } void KisImageTest::testMergeDownDestinationCustomCompositeOp() { FlattenTestImage p; - TestUtil::ExternalImageChecker img("flatten", "imagetest"); - TestUtil::ExternalImageChecker chk("mergedown_dst_customop", "imagetest"); + TestUtil::ReferenceImageChecker img("flatten", "imagetest"); + TestUtil::ReferenceImageChecker chk("mergedown_dst_customop", "imagetest"); { QCOMPARE(p.layer6->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.layer6->alphaChannelDisabled(), false); QCOMPARE(p.group1->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.group1->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.layer6); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer6_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50, 100, 550, 250)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } void KisImageTest::testMergeDownDestinationSameCompositeOpLayerStyle() { FlattenTestImage p; - TestUtil::ExternalImageChecker img("flatten", "imagetest"); - TestUtil::ExternalImageChecker chk("mergedown_sameop_ls", "imagetest"); + TestUtil::ReferenceImageChecker img("flatten", "imagetest"); + TestUtil::ReferenceImageChecker chk("mergedown_sameop_ls", "imagetest"); { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.group1->alphaChannelDisabled(), false); QCOMPARE(p.layer2->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer2->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.group1); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_group1_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(197, 100, 403, 217)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } void KisImageTest::testMergeDownDestinationSameCompositeOp() { FlattenTestImage p; - TestUtil::ExternalImageChecker img("flatten", "imagetest"); - TestUtil::ExternalImageChecker chk("mergedown_sameop_fastpath", "imagetest"); + TestUtil::ReferenceImageChecker img("flatten", "imagetest"); + TestUtil::ReferenceImageChecker chk("mergedown_sameop_fastpath", "imagetest"); { QCOMPARE(p.layer8->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer8->alphaChannelDisabled(), false); QCOMPARE(p.layer7->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer7->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.layer8); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer8_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_ADD); QCOMPARE(newLayer->exactBounds(), QRect(50, 350, 50, 100)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } #include "kis_image_animation_interface.h" void KisImageTest::testMergeDownMultipleFrames() { FlattenTestImage p; - TestUtil::ExternalImageChecker img("flatten", "imagetest"); - TestUtil::ExternalImageChecker chk("mergedown_simple", "imagetest"); + TestUtil::ReferenceImageChecker img("flatten", "imagetest"); + TestUtil::ReferenceImageChecker chk("mergedown_simple", "imagetest"); QSet initialFrames; { KisLayerSP l = p.layer5; l->enableAnimation(); KisKeyframeChannel *channel = l->getKeyframeChannel(KisKeyframeChannel::Content.id(), true); channel->addKeyframe(10); channel->addKeyframe(20); channel->addKeyframe(30); QCOMPARE(channel->keyframeCount(), 4); initialFrames = KisLayerUtils::fetchLayerFramesRecursive(l); QCOMPARE(initialFrames.size(), 4); } { QCOMPARE(p.layer5->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.layer5->alphaChannelDisabled(), true); KisLayerSP newLayer = mergeHelper(p, p.layer5); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer5_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->alphaChannelDisabled(), false); QVERIFY(newLayer->isAnimated()); QSet newFrames = KisLayerUtils::fetchLayerFramesRecursive(newLayer); QCOMPARE(newFrames, initialFrames); foreach (int frame, newFrames) { KisImageAnimationInterface *interface = p.image->animationInterface(); int savedSwitchedTime = 0; interface->saveAndResetCurrentTime(frame, &savedSwitchedTime); QCOMPARE(newLayer->exactBounds(), QRect(100,100,100,100)); interface->restoreCurrentTime(&savedSwitchedTime); } p.undoStore->undo(); p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); } } template KisNodeSP mergeMultipleHelper(ContainerTest &p, QList selectedNodes, KisNodeSP putAfter) { QSignalSpy spy(p.image.data(), SIGNAL(sigNodeAddedAsync(KisNodeSP))); p.image->mergeMultipleLayers(selectedNodes, putAfter); //KisLayerUtils::mergeMultipleLayers(p.image, selectedNodes, putAfter); p.image->waitForDone(); Q_ASSERT(spy.count() == 1); QList arguments = spy.takeFirst(); KisNodeSP newNode = arguments.first().value(); return newNode; } void KisImageTest::testMergeMultiple() { FlattenTestImage p; - TestUtil::ExternalImageChecker img("flatten", "imagetest"); - TestUtil::ExternalImageChecker chk("mergemultiple", "imagetest"); + TestUtil::ReferenceImageChecker img("flatten", "imagetest"); + TestUtil::ReferenceImageChecker chk("mergemultiple", "imagetest"); { QList selectedNodes; selectedNodes << p.layer2 << p.group1 << p.layer6; { KisNodeSP newLayer = mergeMultipleHelper(p, selectedNodes, 0); //KisNodeSP newLayer = p.image->mergeMultipleLayers(selectedNodes, 0); //p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer8_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50, 100, 550, 250)); } } p.p.undoStore->undo(); p.image->waitForDone(); // Test reversed order, the result must be the same { QList selectedNodes; selectedNodes << p.layer6 << p.group1 << p.layer2; { KisNodeSP newLayer = mergeMultipleHelper(p, selectedNodes, 0); //KisNodeSP newLayer = p.image->mergeMultipleLayers(selectedNodes, 0); //p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer8_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50, 100, 550, 250)); } } } void testMergeCrossColorSpaceImpl(bool useProjectionColorSpace, bool swapSpaces) { QRect refRect; TestUtil::MaskParent p; KisPaintLayerSP layer1; KisPaintLayerSP layer2; KisPaintLayerSP layer3; const KoColorSpace *cs2 = useProjectionColorSpace ? p.image->colorSpace() : KoColorSpaceRegistry::instance()->lab16(); const KoColorSpace *cs3 = KoColorSpaceRegistry::instance()->rgb16(); if (swapSpaces) { std::swap(cs2, cs3); } dbgKrita << "Testing testMergeCrossColorSpaceImpl:"; dbgKrita << " " << ppVar(cs2); dbgKrita << " " << ppVar(cs3); layer1 = p.layer; layer2 = new KisPaintLayer(p.image, "paint2", OPACITY_OPAQUE_U8, cs2); layer3 = new KisPaintLayer(p.image, "paint3", OPACITY_OPAQUE_U8, cs3); QRect rect1(100, 100, 100, 100); QRect rect2(150, 150, 150, 150); QRect rect3(250, 250, 200, 200); layer1->paintDevice()->fill(rect1, KoColor(Qt::red, layer1->colorSpace())); layer2->paintDevice()->fill(rect2, KoColor(Qt::green, layer2->colorSpace())); layer3->paintDevice()->fill(rect3, KoColor(Qt::blue, layer3->colorSpace())); p.image->addNode(layer2); p.image->addNode(layer3); p.image->initialRefreshGraph(); { KisLayerSP newLayer = mergeHelper(p, layer3); QCOMPARE(newLayer->colorSpace(), p.image->colorSpace()); p.undoStore->undo(); p.image->waitForDone(); } { layer2->disableAlphaChannel(true); KisLayerSP newLayer = mergeHelper(p, layer3); QCOMPARE(newLayer->colorSpace(), p.image->colorSpace()); p.undoStore->undo(); p.image->waitForDone(); } } void KisImageTest::testMergeCrossColorSpace() { testMergeCrossColorSpaceImpl(true, false); testMergeCrossColorSpaceImpl(true, true); testMergeCrossColorSpaceImpl(false, false); testMergeCrossColorSpaceImpl(false, true); } void KisImageTest::testMergeSelectionMasks() { QRect refRect; TestUtil::MaskParent p; QRect rect1(100, 100, 100, 100); QRect rect2(150, 150, 150, 150); QRect rect3(50, 50, 100, 100); KisPaintLayerSP layer1 = p.layer; layer1->paintDevice()->fill(rect1, KoColor(Qt::red, layer1->colorSpace())); p.image->initialRefreshGraph(); KisSelectionSP sel = new KisSelection(layer1->paintDevice()->defaultBounds()); sel->pixelSelection()->select(rect2, MAX_SELECTED); KisSelectionMaskSP mask1 = new KisSelectionMask(p.image); mask1->initSelection(sel, layer1); p.image->addNode(mask1, layer1); QVERIFY(!layer1->selection()); mask1->setActive(true); QCOMPARE(layer1->selection()->selectedExactRect(), QRect(150,150,150,150)); sel->pixelSelection()->select(rect3, MAX_SELECTED); KisSelectionMaskSP mask2 = new KisSelectionMask(p.image); mask2->initSelection(sel, layer1); p.image->addNode(mask2, layer1); QCOMPARE(layer1->selection()->selectedExactRect(), QRect(150,150,150,150)); mask2->setActive(true); QCOMPARE(layer1->selection()->selectedExactRect(), QRect(50,50,250,250)); QList selectedNodes; selectedNodes << mask2 << mask1; { KisNodeSP newLayer = mergeMultipleHelper(p, selectedNodes, 0); QCOMPARE(newLayer->parent(), KisNodeSP(layer1)); QCOMPARE((int)layer1->childCount(), 1); QCOMPARE(layer1->selection()->selectedExactRect(), QRect(50,50,250,250)); } } void KisImageTest::testFlattenImage() { FlattenTestImage p; KisImageSP image = p.image; - TestUtil::ExternalImageChecker img("flatten", "imagetest"); + TestUtil::ReferenceImageChecker img("flatten", "imagetest"); { KisLayerUtils::flattenImage(p.image); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); p.undoStore->undo(); p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); } } struct FlattenPassThroughTestImage { FlattenPassThroughTestImage() : refRect(0,0,512,512) , p(refRect) { image = p.image; undoStore = p.undoStore; group1 = new KisGroupLayer(p.image, "group1", OPACITY_OPAQUE_U8); layer2 = new KisPaintLayer(p.image, "paint2", OPACITY_OPAQUE_U8); layer3 = new KisPaintLayer(p.image, "paint3", OPACITY_OPAQUE_U8); group4 = new KisGroupLayer(p.image, "group4", OPACITY_OPAQUE_U8); layer5 = new KisPaintLayer(p.image, "paint5", OPACITY_OPAQUE_U8); layer6 = new KisPaintLayer(p.image, "paint6", OPACITY_OPAQUE_U8); QRect rect2(100, 100, 100, 100); QRect rect3(150, 150, 100, 100); QRect rect5(200, 200, 100, 100); QRect rect6(250, 250, 100, 100); group1->setPassThroughMode(true); layer2->paintDevice()->fill(rect2, KoColor(Qt::red, p.image->colorSpace())); layer3->paintDevice()->fill(rect3, KoColor(Qt::green, p.image->colorSpace())); group4->setPassThroughMode(true); layer5->paintDevice()->fill(rect5, KoColor(Qt::blue, p.image->colorSpace())); layer6->paintDevice()->fill(rect6, KoColor(Qt::yellow, p.image->colorSpace())); p.image->addNode(group1); p.image->addNode(layer2, group1); p.image->addNode(layer3, group1); p.image->addNode(group4); p.image->addNode(layer5, group4); p.image->addNode(layer6, group4); p.image->initialRefreshGraph(); - TestUtil::ExternalImageChecker chk("passthrough", "imagetest"); + TestUtil::ReferenceImageChecker chk("passthrough", "imagetest"); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); } QRect refRect; TestUtil::MaskParent p; KisImageSP image; KisSurrogateUndoStore *undoStore; KisGroupLayerSP group1; KisPaintLayerSP layer2; KisPaintLayerSP layer3; KisGroupLayerSP group4; KisPaintLayerSP layer5; KisPaintLayerSP layer6; }; void KisImageTest::testFlattenPassThroughLayer() { FlattenPassThroughTestImage p; - TestUtil::ExternalImageChecker chk("passthrough", "imagetest"); + TestUtil::ReferenceImageChecker chk("passthrough", "imagetest"); { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.group1->passThroughMode(), true); KisLayerSP newLayer = flattenLayerHelper(p, p.group1); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_group1_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QVERIFY(newLayer->inherits("KisPaintLayer")); } } void KisImageTest::testMergeTwoPassThroughLayers() { FlattenPassThroughTestImage p; - TestUtil::ExternalImageChecker chk("passthrough", "imagetest"); + TestUtil::ReferenceImageChecker chk("passthrough", "imagetest"); { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.group1->passThroughMode(), true); KisLayerSP newLayer = mergeHelper(p, p.group4); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QVERIFY(newLayer->inherits("KisGroupLayer")); } } void KisImageTest::testMergePaintOverPassThroughLayer() { FlattenPassThroughTestImage p; - TestUtil::ExternalImageChecker chk("passthrough", "imagetest"); + TestUtil::ReferenceImageChecker chk("passthrough", "imagetest"); { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.group1->passThroughMode(), true); KisLayerSP newLayer = flattenLayerHelper(p, p.group4); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(newLayer->inherits("KisPaintLayer")); newLayer = mergeHelper(p, newLayer); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(newLayer->inherits("KisPaintLayer")); } } void KisImageTest::testMergePassThroughOverPaintLayer() { FlattenPassThroughTestImage p; - TestUtil::ExternalImageChecker chk("passthrough", "imagetest"); + TestUtil::ReferenceImageChecker chk("passthrough", "imagetest"); { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.group1->passThroughMode(), true); KisLayerSP newLayer = flattenLayerHelper(p, p.group1); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(newLayer->inherits("KisPaintLayer")); newLayer = mergeHelper(p, p.group4); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(newLayer->inherits("KisPaintLayer")); } } QTEST_MAIN(KisImageTest) diff --git a/libs/image/tests/kis_transform_mask_test.cpp b/libs/image/tests/kis_transform_mask_test.cpp index 53b44be6cd..6ea13dea4f 100644 --- a/libs/image/tests/kis_transform_mask_test.cpp +++ b/libs/image/tests/kis_transform_mask_test.cpp @@ -1,989 +1,989 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_transform_mask_test.h" #include #include #include "kis_transform_mask.h" #include "kis_transform_mask_params_interface.h" #include "testutil.h" #include "kis_algebra_2d.h" #include "kis_safe_transform.h" #include "kis_clone_layer.h" #include "kis_group_layer.h" #include "kis_paint_device_debug_utils.h" inline QString toOctaveFormat(const QTransform &t) { QString s("T = [%1 %2 %3; %4 %5 %6; %7 %8 %9]"); s = s .arg(t.m11()).arg(t.m12()).arg(t.m13()) .arg(t.m21()).arg(t.m22()).arg(t.m23()) .arg(t.m31()).arg(t.m32()).arg(t.m33()); return s; } void KisTransformMaskTest::testSafeTransform() { QTransform transform(-0.177454, -0.805953, -0.00213713, -1.9295, -0.371835, -0.00290463, 3075.05, 2252.32, 7.62371); QRectF testRect(0, 1024, 512, 512); KisSafeTransform t2(transform, QRect(0, 0, 2048, 2048), testRect.toRect()); QPolygonF fwdPoly = t2.mapForward(testRect); QRectF fwdRect = t2.mapRectForward(testRect); QPolygonF bwdPoly = t2.mapBackward(fwdPoly); QRectF bwdRect = t2.mapRectBackward(fwdRect); QPolygon ref; ref.clear(); ref << QPoint(284, 410); ref << QPoint(10, 613); ref << QPoint(35, 532); ref << QPoint(236, 403); ref << QPoint(284, 410); QCOMPARE(fwdPoly.toPolygon(), ref); QCOMPARE(fwdRect.toRect(), QRect(10,403,274,211)); ref.clear(); ref << QPoint(512, 1024); ref << QPoint(512, 1536); ref << QPoint(0, 1536); ref << QPoint(0, 1024); ref << QPoint(512, 1024); QCOMPARE(bwdPoly.toPolygon(), ref); QCOMPARE(bwdRect.toRect(), QRect(0, 994, 1198, 584)); /* QImage image(2500, 2500, QImage::Format_ARGB32); QPainter gc(&image); gc.setPen(Qt::cyan); gc.setOpacity(0.7); gc.setBrush(Qt::red); gc.drawPolygon(t2.srcClipPolygon()); gc.setBrush(Qt::green); gc.drawPolygon(t2.dstClipPolygon()); dbgKrita << ppVar(testRect); dbgKrita << ppVar(fwdPoly); dbgKrita << ppVar(fwdRect); dbgKrita << ppVar(bwdPoly); dbgKrita << ppVar(bwdRect); gc.setBrush(Qt::yellow); gc.drawPolygon(testRect); gc.setBrush(Qt::red); gc.drawPolygon(fwdRect); gc.setBrush(Qt::blue); gc.drawPolygon(fwdPoly); gc.setBrush(Qt::magenta); gc.drawPolygon(bwdRect); gc.setBrush(Qt::cyan); gc.drawPolygon(bwdPoly); gc.end(); image.save("polygons_safety.png"); */ } void KisTransformMaskTest::testSafeTransformUnity() { QTransform transform; QRectF testRect(0, 1024, 512, 512); KisSafeTransform t2(transform, QRect(0, 0, 2048, 2048), testRect.toRect()); QPolygonF fwdPoly = t2.mapForward(testRect); QRectF fwdRect = t2.mapRectForward(testRect); QPolygonF bwdPoly = t2.mapBackward(fwdPoly); QRectF bwdRect = t2.mapRectBackward(fwdRect); QCOMPARE(testRect, fwdRect); QCOMPARE(testRect, bwdRect); QCOMPARE(fwdPoly, QPolygonF(testRect)); QCOMPARE(bwdPoly, QPolygonF(testRect)); } void KisTransformMaskTest::testSafeTransformSingleVanishingPoint() { // rotation around 0X has a single vanishing point for 0Y axis QTransform transform(1, 0, 0, -0.870208, -0.414416, -0.000955222, 132.386, 1082.91, 1.99439); QTransform R; R.rotateRadians(M_PI / 4.0); //transform *= R; QRectF testRect(1536, 1024, 512, 512); KisSafeTransform t2(transform, QRect(0, 0, 2048, 2048), testRect.toRect()); QPolygonF fwdPoly = t2.mapForward(testRect); QRectF fwdRect = t2.mapRectForward(testRect); QPolygonF bwdPoly = t2.mapBackward(fwdPoly); QRectF bwdRect = t2.mapRectBackward(fwdRect); /** * A special weird rect that crosses the vanishing point, * which is (911.001, 433.84) in this case */ QRectF fwdNastyRect(800, 100, 400, 600); //QRectF fwdNastyRect(100, 400, 1000, 800); QRectF bwdNastyRect = t2.mapRectBackward(fwdNastyRect); /* dbgKrita << ppVar(testRect); dbgKrita << ppVar(fwdPoly); dbgKrita << ppVar(fwdRect); dbgKrita << ppVar(bwdPoly); dbgKrita << ppVar(bwdRect); dbgKrita << ppVar(bwdNastyRect); */ QPolygon ref; ref.clear(); ref << QPoint(765,648); ref << QPoint(1269, 648); ref << QPoint(1601, 847); ref << QPoint(629, 847); ref << QPoint(765, 648); QCOMPARE(fwdPoly.toPolygon(), ref); QCOMPARE(fwdRect.toRect(), QRect(629,648,971,199)); ref.clear(); ref << QPoint(1536,1024); ref << QPoint(2048,1024); ref << QPoint(2048,1536); ref << QPoint(1536,1536); ref << QPoint(1536,1024); QCOMPARE(bwdPoly.toPolygon(), ref); QCOMPARE(bwdRect.toRect(), QRect(1398,1024,650,512)); QCOMPARE(bwdNastyRect.toRect(), QRect(1463,0,585,1232)); } bool doPartialTests(const QString &prefix, KisImageSP image, KisLayerSP paintLayer, KisLayerSP visibilityToggleLayer, KisTransformMaskSP mask) { - TestUtil::ExternalImageChecker chk(prefix, "transform_mask_updates"); + TestUtil::ReferenceImageChecker chk(prefix, "transform_mask_updates"); bool result = true; QRect refRect = image->bounds(); int testIndex = 1; QString testName; for (int y = 0; y < refRect.height(); y += 512) { for (int x = 0; x < refRect.width(); x += 512) { QRect rc(x, y, 512, 512); if (rc.right() > refRect.right()) { rc.setRight(refRect.right()); if (rc.isEmpty()) continue; } if (rc.bottom() > refRect.bottom()) { rc.setBottom(refRect.bottom()); if (rc.isEmpty()) continue; } paintLayer->setDirty(rc); image->waitForDone(); testName = QString("tm_%1_partial_%2_%3").arg(testIndex++).arg(x).arg(y); result &= chk.checkImage(image, testName); } } // initial update of the mask to clear the unused portions of the projection // (it updates only when we call set dirty on the mask itself, which happens // in Krita right after the addition of the mask onto a layer) mask->setDirty(); image->waitForDone(); testName = QString("tm_%1_initial_mask_visible_on").arg(testIndex++); result &= chk.checkImage(image, testName); // start layer visibility testing paintLayer->setVisible(false); paintLayer->setDirty(); image->waitForDone(); testName = QString("tm_%1_layer_visible_off").arg(testIndex++); result &= chk.checkImage(image, testName); paintLayer->setVisible(true); paintLayer->setDirty(); image->waitForDone(); testName = QString("tm_%1_layer_visible_on").arg(testIndex++); result &= chk.checkImage(image, testName); if (paintLayer != visibilityToggleLayer) { visibilityToggleLayer->setVisible(false); visibilityToggleLayer->setDirty(); image->waitForDone(); testName = QString("tm_%1_extra_layer_visible_off").arg(testIndex++); result &= chk.checkImage(image, testName); visibilityToggleLayer->setVisible(true); visibilityToggleLayer->setDirty(); image->waitForDone(); testName = QString("tm_%1_extra_layer_visible_on").arg(testIndex++); result &= chk.checkImage(image, testName); } // toggle mask visibility mask->setVisible(false); mask->setDirty(); image->waitForDone(); testName = QString("tm_%1_mask_visible_off").arg(testIndex++); result &= chk.checkImage(image, testName); mask->setVisible(true); mask->setDirty(); image->waitForDone(); testName = QString("tm_%1_mask_visible_on").arg(testIndex++); result &= chk.checkImage(image, testName); // entire bounds update // no clearing, just don't hang up paintLayer->setDirty(refRect); image->waitForDone(); testName = QString("tm_%1_layer_dirty_bounds").arg(testIndex++); result &= chk.checkImage(image, testName); // no clearing, just don't hang up mask->setDirty(refRect); image->waitForDone(); testName = QString("tm_%1_mask_dirty_bounds").arg(testIndex++); result &= chk.checkImage(image, testName); if (paintLayer != visibilityToggleLayer) { // no clearing, just don't hang up visibilityToggleLayer->setDirty(refRect); image->waitForDone(); testName = QString("tm_%1_extra_layer_dirty_bounds").arg(testIndex++); result &= chk.checkImage(image, testName); } QRect fillRect; // partial updates outside fillRect = QRect(-100, 0.5 * refRect.height(), 50, 100); paintLayer->paintDevice()->fill(fillRect, KoColor(Qt::red, image->colorSpace())); paintLayer->setDirty(fillRect); image->waitForDone(); testName = QString("tm_%1_layer_dirty_outside_%2_%3").arg(testIndex++).arg(fillRect.x()).arg(fillRect.y()); result &= chk.checkImage(image, testName); fillRect = QRect(0.5 * refRect.width(), -100, 100, 50); paintLayer->paintDevice()->fill(fillRect, KoColor(Qt::red, image->colorSpace())); paintLayer->setDirty(fillRect); image->waitForDone(); testName = QString("tm_%1_layer_dirty_outside_%2_%3").arg(testIndex++).arg(fillRect.x()).arg(fillRect.y()); result &= chk.checkImage(image, testName); fillRect = QRect(refRect.width() + 50, 0.2 * refRect.height(), 50, 100); paintLayer->paintDevice()->fill(fillRect, KoColor(Qt::red, image->colorSpace())); paintLayer->setDirty(fillRect); image->waitForDone(); testName = QString("tm_%1_layer_dirty_outside_%2_%3").arg(testIndex++).arg(fillRect.x()).arg(fillRect.y()); result &= chk.checkImage(image, testName); // partial update inside fillRect = QRect(0.5 * refRect.width() - 50, 0.5 * refRect.height() - 50, 100, 100); paintLayer->paintDevice()->fill(fillRect, KoColor(Qt::red, image->colorSpace())); paintLayer->setDirty(fillRect); image->waitForDone(); testName = QString("tm_%1_layer_dirty_inside_%2_%3").arg(testIndex++).arg(fillRect.x()).arg(fillRect.y()); result &= chk.checkImage(image, testName); // clear explicitly image->projection()->clear(); mask->setDirty(); image->waitForDone(); testName = QString("tm_%1_mask_dirty_bounds").arg(testIndex++); result &= chk.checkImage(image, testName); KisDumbTransformMaskParams *params = dynamic_cast(mask->transformParams().data()); QTransform t = params->testingGetTransform(); t *= QTransform::fromTranslate(400, 300); params->testingSetTransform(t); mask->setTransformParams(mask->transformParams()); mask->setDirty(); image->waitForDone(); testName = QString("tm_%1_mask_dirty_after_offset").arg(testIndex++); result &= chk.checkImage(image, testName); return result; } void KisTransformMaskTest::testMaskOnPaintLayer() { QImage refImage(TestUtil::fetchDataFileLazy("test_transform_quality.png")); QRect refRect = refImage.rect(); TestUtil::MaskParent p(refRect); p.layer->paintDevice()->convertFromQImage(refImage, 0); KisPaintLayerSP player = new KisPaintLayer(p.image, "bg", OPACITY_OPAQUE_U8, p.image->colorSpace()); p.image->addNode(player, p.image->root(), KisNodeSP()); KisTransformMaskSP mask = new KisTransformMask(); p.image->addNode(mask, p.layer); QTransform transform(-0.177454, -0.805953, -0.00213713, -1.9295, -0.371835, -0.00290463, 3075.05, 2252.32, 7.62371); mask->setTransformParams(KisTransformMaskParamsInterfaceSP( new KisDumbTransformMaskParams(transform))); QVERIFY(doPartialTests("pl", p.image, p.layer, p.layer, mask)); } void KisTransformMaskTest::testMaskOnCloneLayer() { QImage refImage(TestUtil::fetchDataFileLazy("test_transform_quality.png")); QRect refRect = refImage.rect(); TestUtil::MaskParent p(refRect); p.layer->paintDevice()->convertFromQImage(refImage, 0); KisPaintLayerSP player = new KisPaintLayer(p.image, "bg", OPACITY_OPAQUE_U8, p.image->colorSpace()); p.image->addNode(player, p.image->root(), KisNodeSP()); KisCloneLayerSP clone = new KisCloneLayer(p.layer, p.image, "clone", OPACITY_OPAQUE_U8); p.image->addNode(clone, p.image->root()); KisTransformMaskSP mask = new KisTransformMask(); p.image->addNode(mask, clone); QTransform transform(-0.177454, -0.805953, -0.00213713, -1.9295, -0.371835, -0.00290463, 3075.05, 2252.32, 7.62371); mask->setTransformParams(KisTransformMaskParamsInterfaceSP( new KisDumbTransformMaskParams(transform))); QVERIFY(doPartialTests("cl", p.image, p.layer, clone, mask)); } void KisTransformMaskTest::testMaskOnCloneLayerWithOffset() { - TestUtil::ExternalImageChecker chk("clone_offset_simple", "transform_mask_updates"); + TestUtil::ReferenceImageChecker chk("clone_offset_simple", "transform_mask_updates"); QRect refRect(0,0,512,512); QRect fillRect(400,400,100,100); TestUtil::MaskParent p(refRect); p.layer->paintDevice()->fill(fillRect, KoColor(Qt::red, p.layer->colorSpace())); KisPaintLayerSP player = new KisPaintLayer(p.image, "bg", OPACITY_OPAQUE_U8, p.image->colorSpace()); p.image->addNode(player, p.image->root(), KisNodeSP()); KisCloneLayerSP clone = new KisCloneLayer(p.layer, p.image, "clone", OPACITY_OPAQUE_U8); p.image->addNode(clone, p.image->root()); KisTransformMaskSP mask = new KisTransformMask(); p.image->addNode(mask, clone); QTransform transform(1, 0, 0, 0, 1, 0, 0, -150, 1); mask->setTransformParams(KisTransformMaskParamsInterfaceSP( new KisDumbTransformMaskParams(transform))); p.layer->setDirty(refRect); p.image->waitForDone(); chk.checkImage(p.image, "0_initial"); clone->setX(-300); clone->setDirty(); p.image->waitForDone(); chk.checkImage(p.image, "1_after_offset"); mask->setDirty(); p.image->waitForDone(); chk.checkImage(p.image, "2_after_offset_dirty_mask"); QTest::qWait(4000); chk.checkImage(p.image, "3_delayed_regeneration"); KisPaintDeviceSP previewDevice = mask->buildPreviewDevice(); chk.checkDevice(previewDevice, p.image, "4_preview_device"); QVERIFY(chk.testPassed()); QVERIFY(doPartialTests("clone_offset_complex", p.image, p.layer, clone, mask)); } #define CHECK_MASK1_TOGGLE #define CHECK_MASK2_TOGGLE #define CHECK_HIDE_ALL #define CHECK_HIDE_ALL_AFTER_MOVE void KisTransformMaskTest::testMultipleMasks() { - TestUtil::ExternalImageChecker chk("multiple_masks", "transform_mask_updates"); + TestUtil::ReferenceImageChecker chk("multiple_masks", "transform_mask_updates"); QRect refRect(0,0,512,512); QRect fillRect(400,400,100,100); TestUtil::MaskParent p(refRect); p.layer->paintDevice()->fill(fillRect, KoColor(Qt::red, p.layer->colorSpace())); KisPaintLayerSP player = new KisPaintLayer(p.image, "bg", OPACITY_OPAQUE_U8, p.image->colorSpace()); p.image->addNode(player, p.image->root(), KisNodeSP()); //KisCloneLayerSP clone = new KisCloneLayer(p.layer, p.image, "clone", OPACITY_OPAQUE_U8); //p.image->addNode(clone, p.image->root()); KisTransformMaskSP mask1 = new KisTransformMask(); p.image->addNode(mask1, p.layer); KisTransformMaskSP mask2 = new KisTransformMask(); p.image->addNode(mask2, p.layer); mask1->setName("mask1"); mask2->setName("mask2"); p.layer->setDirty(refRect); p.image->waitForDone(); chk.checkImage(p.image, "00_initial_layer_update"); QTransform transform; transform = QTransform::fromTranslate(-150, 0); mask1->setTransformParams(KisTransformMaskParamsInterfaceSP( new KisDumbTransformMaskParams(transform))); p.layer->setDirty(refRect); p.image->waitForDone(); chk.checkImage(p.image, "01_mask1_moved_layer_update"); QTest::qWait(4000); p.image->waitForDone(); chk.checkImage(p.image, "01X_mask1_moved_layer_update"); transform = QTransform::fromTranslate(0, -150); mask2->setTransformParams(KisTransformMaskParamsInterfaceSP( new KisDumbTransformMaskParams(transform))); p.layer->setDirty(refRect); p.image->waitForDone(); chk.checkImage(p.image, "02_mask2_moved_layer_update"); QTest::qWait(4000); p.image->waitForDone(); chk.checkImage(p.image, "02X_mask2_moved_layer_update"); #ifdef CHECK_MASK1_TOGGLE { mask1->setVisible(false); mask1->setDirty(refRect); p.image->waitForDone(); chk.checkImage(p.image, "03_mask1_tg_off_refRect"); QTest::qWait(4000); p.image->waitForDone(); chk.checkImage(p.image, "03X_mask1_tg_off_refRect"); mask1->setVisible(true); mask1->setDirty(refRect); p.image->waitForDone(); chk.checkImage(p.image, "04_mask1_tg_on_refRect"); QTest::qWait(4000); p.image->waitForDone(); chk.checkImage(p.image, "04X_mask1_tg_on_refRect"); mask1->setVisible(false); mask1->setDirty(); p.image->waitForDone(); chk.checkImage(p.image, "05_mask1_tg_off_default_rect"); QTest::qWait(4000); p.image->waitForDone(); chk.checkImage(p.image, "05X_mask1_tg_off_default_rect"); mask1->setVisible(true); mask1->setDirty(); p.image->waitForDone(); chk.checkImage(p.image, "06_mask1_tg_on_default_rect"); QTest::qWait(4000); p.image->waitForDone(); chk.checkImage(p.image, "06X_mask1_tg_on_default_rect"); } #endif /* CHECK_MASK1_TOGGLE */ #ifdef CHECK_MASK2_TOGGLE { mask2->setVisible(false); mask2->setDirty(refRect); p.image->waitForDone(); chk.checkImage(p.image, "07_mask2_tg_off_refRect"); QTest::qWait(4000); p.image->waitForDone(); chk.checkImage(p.image, "07X_mask2_tg_off_refRect"); mask2->setVisible(true); mask2->setDirty(refRect); p.image->waitForDone(); chk.checkImage(p.image, "08_mask2_tg_on_refRect"); QTest::qWait(4000); p.image->waitForDone(); chk.checkImage(p.image, "08X_mask2_tg_on_refRect"); mask2->setVisible(false); mask2->setDirty(refRect); p.image->waitForDone(); chk.checkImage(p.image, "09_mask2_tg_off_default_rect"); QTest::qWait(4000); p.image->waitForDone(); chk.checkImage(p.image, "09X_mask2_tg_off_default_rect"); mask2->setVisible(true); mask2->setDirty(refRect); p.image->waitForDone(); chk.checkImage(p.image, "10_mask2_tg_on_default_rect"); QTest::qWait(4000); p.image->waitForDone(); chk.checkImage(p.image, "10X_mask2_tg_on_default_rect"); } #endif /* CHECK_MASK2_TOGGLE */ #ifdef CHECK_HIDE_ALL { mask1->setVisible(false); mask1->setDirty(); p.image->waitForDone(); chk.checkImage(p.image, "11.1_hide_both_update_default_mask1"); QTest::qWait(4000); p.image->waitForDone(); chk.checkImage(p.image, "11.1X_hide_both_update_default_mask1"); mask2->setVisible(false); mask2->setDirty(); p.image->waitForDone(); chk.checkImage(p.image, "11.2_hide_both_update_default_mask2"); QTest::qWait(4000); p.image->waitForDone(); chk.checkImage(p.image, "11.2X_hide_both_update_default_mask2"); mask1->setVisible(true); mask1->setDirty(); p.image->waitForDone(); chk.checkImage(p.image, "12_sh_mask1_on"); QTest::qWait(4000); p.image->waitForDone(); chk.checkImage(p.image, "12X_sh_mask1_on"); mask1->setVisible(false); mask1->setDirty(); p.image->waitForDone(); chk.checkImage(p.image, "13_sh_mask1_off"); QTest::qWait(4000); p.image->waitForDone(); chk.checkImage(p.image, "13X_sh_mask1_off"); mask2->setVisible(true); mask2->setDirty(); p.image->waitForDone(); chk.checkImage(p.image, "14_sh_mask2_on"); QTest::qWait(4000); p.image->waitForDone(); chk.checkImage(p.image, "14X_sh_mask2_on"); mask2->setVisible(false); mask2->setDirty(); p.image->waitForDone(); chk.checkImage(p.image, "15_sh_mask2_off"); QTest::qWait(4000); p.image->waitForDone(); chk.checkImage(p.image, "15X_sh_mask2_off"); } #endif /* CHECK_HIDE_ALL */ #ifdef CHECK_HIDE_ALL_AFTER_MOVE { transform = QTransform::fromTranslate(50, -150); mask2->setTransformParams(KisTransformMaskParamsInterfaceSP( new KisDumbTransformMaskParams(transform))); mask2->setDirty(); p.image->waitForDone(); chk.checkImage(p.image, "20_moved_mask2"); QTest::qWait(4000); p.image->waitForDone(); chk.checkImage(p.image, "20X_moved_mask2"); } { mask1->setVisible(false); mask1->setDirty(); p.image->waitForDone(); chk.checkImage(p.image, "21.1_hide_both_update_default_mask1"); QTest::qWait(4000); p.image->waitForDone(); chk.checkImage(p.image, "21.1X_hide_both_update_default_mask1"); mask2->setVisible(false); mask2->setDirty(); p.image->waitForDone(); chk.checkImage(p.image, "21.2_hide_both_update_default_mask2"); QTest::qWait(4000); p.image->waitForDone(); chk.checkImage(p.image, "21.2X_hide_both_update_default_mask2"); mask1->setVisible(true); mask1->setDirty(); p.image->waitForDone(); chk.checkImage(p.image, "22_sh_mask1_on"); QTest::qWait(4000); p.image->waitForDone(); chk.checkImage(p.image, "22X_sh_mask1_on"); mask1->setVisible(false); mask1->setDirty(); p.image->waitForDone(); chk.checkImage(p.image, "23_sh_mask1_off"); QTest::qWait(4000); p.image->waitForDone(); chk.checkImage(p.image, "23X_sh_mask1_off"); mask2->setVisible(true); mask2->setDirty(); p.image->waitForDone(); chk.checkImage(p.image, "24_sh_mask2_on"); QTest::qWait(4000); p.image->waitForDone(); chk.checkImage(p.image, "24X_sh_mask2_on"); mask2->setVisible(false); mask2->setDirty(); p.image->waitForDone(); chk.checkImage(p.image, "25_sh_mask2_off"); QTest::qWait(4000); p.image->waitForDone(); chk.checkImage(p.image, "25X_sh_mask2_off"); } #endif /* CHECK_HIDE_ALL_AFTER_MOVE */ QVERIFY(chk.testPassed()); } void KisTransformMaskTest::testMaskWithOffset() { - TestUtil::ExternalImageChecker chk("mask_with_offset", "transform_mask_updates"); + TestUtil::ReferenceImageChecker chk("mask_with_offset", "transform_mask_updates"); QRect refRect(0,0,512,512); QRect fillRect(400,400,100,100); TestUtil::MaskParent p(refRect); p.layer->paintDevice()->fill(fillRect, KoColor(Qt::red, p.layer->colorSpace())); KisPaintLayerSP player = new KisPaintLayer(p.image, "bg", OPACITY_OPAQUE_U8, p.image->colorSpace()); p.image->addNode(player, p.image->root(), KisNodeSP()); KisTransformMaskSP mask1 = new KisTransformMask(); p.image->addNode(mask1, p.layer); mask1->setName("mask1"); p.layer->setDirty(refRect); p.image->waitForDone(); chk.checkImage(p.image, "00_initial_layer_update"); QTest::qWait(4000); p.image->waitForDone(); chk.checkImage(p.image, "00X_initial_layer_update"); QTransform transform; transform = QTransform::fromTranslate(-150, 0); mask1->setTransformParams(KisTransformMaskParamsInterfaceSP( new KisDumbTransformMaskParams(transform))); p.layer->setDirty(refRect); p.image->waitForDone(); chk.checkImage(p.image, "01_mask1_moved_layer_update"); QTest::qWait(4000); p.image->waitForDone(); chk.checkImage(p.image, "01X_mask1_moved_layer_update"); mask1->setY(-150); mask1->setDirty(refRect); p.image->waitForDone(); chk.checkImage(p.image, "02_mask1_y_offset"); QTest::qWait(4000); p.image->waitForDone(); chk.checkImage(p.image, "02X_mask1_y_offset"); QVERIFY(chk.testPassed()); } void KisTransformMaskTest::testWeirdFullUpdates() { //TestUtil::ExternalImageChecker chk("mask_with_offset", "transform_mask_updates"); QRect imageRect(0,0,512,512); QRect fillRect(10, 10, 236, 236); TestUtil::MaskParent p(imageRect); p.layer->paintDevice()->fill(fillRect, KoColor(Qt::red, p.layer->colorSpace())); KisPaintLayerSP player1 = new KisPaintLayer(p.image, "pl1", OPACITY_OPAQUE_U8, p.image->colorSpace()); player1->paintDevice()->fill(fillRect, KoColor(Qt::red, p.layer->colorSpace())); p.image->addNode(player1, p.image->root()); KisTransformMaskSP mask1 = new KisTransformMask(); mask1->setName("mask1"); QTransform transform1 = QTransform::fromTranslate(256, 0); mask1->setTransformParams(KisTransformMaskParamsInterfaceSP( new KisDumbTransformMaskParams(transform1))); p.image->addNode(mask1, player1); KisPaintLayerSP player2 = new KisPaintLayer(p.image, "pl2", OPACITY_OPAQUE_U8, p.image->colorSpace()); player2->paintDevice()->fill(fillRect, KoColor(Qt::red, p.layer->colorSpace())); p.image->addNode(player2, p.image->root()); KisTransformMaskSP mask2 = new KisTransformMask(); mask2->setName("mask2"); QTransform transform2 = QTransform::fromTranslate(0, 256); mask2->setTransformParams(KisTransformMaskParamsInterfaceSP( new KisDumbTransformMaskParams(transform2))); p.image->addNode(mask2, player2); KisPaintLayerSP player3 = new KisPaintLayer(p.image, "pl3", OPACITY_OPAQUE_U8, p.image->colorSpace()); player3->paintDevice()->fill(fillRect, KoColor(Qt::red, p.layer->colorSpace())); p.image->addNode(player3, p.image->root()); KisTransformMaskSP mask3 = new KisTransformMask(); mask3->setName("mask3"); QTransform transform3 = QTransform::fromTranslate(256, 256); mask3->setTransformParams(KisTransformMaskParamsInterfaceSP( new KisDumbTransformMaskParams(transform3))); p.image->addNode(mask3, player3); //p.image->initialRefreshGraph(); p.image->refreshGraphAsync(0, QRect(0,0,256,256), QRect()); p.image->waitForDone(); QVERIFY(player1->projection()->extent().isEmpty()); QVERIFY(player1->projection()->exactBounds().isEmpty()); QVERIFY(player2->projection()->extent().isEmpty()); QVERIFY(player2->projection()->exactBounds().isEmpty()); QVERIFY(player3->projection()->extent().isEmpty()); QVERIFY(player3->projection()->exactBounds().isEmpty()); QCOMPARE(p.image->projection()->exactBounds(), QRect(QRect(10,10,236,236))); p.image->refreshGraphAsync(0, QRect(0,256,256,256), QRect()); p.image->waitForDone(); QVERIFY(player1->projection()->extent().isEmpty()); QVERIFY(player1->projection()->exactBounds().isEmpty()); QVERIFY(!player2->projection()->extent().isEmpty()); QVERIFY(!player2->projection()->exactBounds().isEmpty()); QVERIFY(player3->projection()->extent().isEmpty()); QVERIFY(player3->projection()->exactBounds().isEmpty()); QCOMPARE(p.image->projection()->exactBounds(), QRect(QRect(10,10,236,492))); p.image->refreshGraphAsync(0, QRect(256,0,256,256), QRect()); p.image->waitForDone(); QVERIFY(!player1->projection()->extent().isEmpty()); QVERIFY(!player1->projection()->exactBounds().isEmpty()); QVERIFY(!player2->projection()->extent().isEmpty()); QVERIFY(!player2->projection()->exactBounds().isEmpty()); QVERIFY(player3->projection()->extent().isEmpty()); QVERIFY(player3->projection()->exactBounds().isEmpty()); QCOMPARE(p.image->projection()->exactBounds(), QRect(QRect(10,10,492,492))); QVERIFY((p.image->projection()->region() & QRect(256,256,256,256)).isEmpty()); p.image->refreshGraphAsync(0, QRect(256,256,256,256), QRect()); p.image->waitForDone(); QVERIFY(!player1->projection()->extent().isEmpty()); QVERIFY(!player1->projection()->exactBounds().isEmpty()); QVERIFY(!player2->projection()->extent().isEmpty()); QVERIFY(!player2->projection()->exactBounds().isEmpty()); QVERIFY(!player3->projection()->extent().isEmpty()); QVERIFY(!player3->projection()->exactBounds().isEmpty()); QCOMPARE(p.image->projection()->exactBounds(), QRect(QRect(10,10,492,492))); QVERIFY(!(p.image->projection()->region() & QRect(256,256,256,256)).isEmpty()); p.image->waitForDone(); KIS_DUMP_DEVICE_2(p.image->projection(), imageRect, "image_proj", "dd"); } void KisTransformMaskTest::testTransformHiddenPartsOfTheGroup() { //TestUtil::ExternalImageChecker chk("mask_with_offset", "transform_mask_updates"); QRect imageRect(0,0,512,512); QRect fillRect(10, 10, 236, 236); QRect outsideFillRect = fillRect.translated(0, -1.5 * 256); TestUtil::MaskParent p(imageRect); //p.layer->paintDevice()->fill(fillRect, KoColor(Qt::green, p.layer->colorSpace())); p.image->initialRefreshGraph(); KisGroupLayerSP glayer = new KisGroupLayer(p.image, "gl", OPACITY_OPAQUE_U8); p.image->addNode(glayer, p.image->root()); KisPaintLayerSP player1 = new KisPaintLayer(p.image, "pl1", OPACITY_OPAQUE_U8, p.image->colorSpace()); player1->paintDevice()->fill(fillRect, KoColor(Qt::red, p.layer->colorSpace())); player1->paintDevice()->fill(outsideFillRect, KoColor(Qt::blue, p.layer->colorSpace())); p.image->addNode(player1, glayer); player1->setDirty(); p.image->waitForDone(); QCOMPARE(p.image->projection()->exactBounds(), fillRect); //KIS_DUMP_DEVICE_2(p.image->projection(), imageRect, "image_proj_initial", "dd"); KisTransformMaskSP mask1 = new KisTransformMask(); mask1->setName("mask1"); QTransform transform1 = QTransform::fromTranslate(0, 1.5 * 256); mask1->setTransformParams(KisTransformMaskParamsInterfaceSP( new KisDumbTransformMaskParams(transform1))); p.image->addNode(mask1, player1); mask1->setDirty(); p.image->waitForDone(); /** * Transform mask i sexpected to crop the externals of the layer by 50% * far behind the layer border! Take care! */ QCOMPARE(p.image->projection()->exactBounds(), QRect(10, 128, 236, 384)); //KIS_DUMP_DEVICE_2(p.image->projection(), imageRect, "image_proj_mask", "dd"); } QTEST_MAIN(KisTransformMaskTest) diff --git a/libs/ui/flake/kis_shape_layer.cc b/libs/ui/flake/kis_shape_layer.cc index f6771501c1..2877152be3 100644 --- a/libs/ui/flake/kis_shape_layer.cc +++ b/libs/ui/flake/kis_shape_layer.cc @@ -1,654 +1,676 @@ /* * Copyright (c) 2006-2008 Boudewijn Rempt * Copyright (c) 2007 Thomas Zander * Copyright (c) 2009 Cyrille Berger * Copyright (c) 2011 Jan Hambrecht * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_shape_layer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "SvgWriter.h" #include "SvgParser.h" #include #include #include "kis_default_bounds.h" #include #include "kis_shape_layer_canvas.h" #include "kis_image_view_converter.h" #include #include "kis_node_visitor.h" #include "kis_processing_visitor.h" #include "kis_effect_mask.h" +#include "commands/KoShapeReorderCommand.h" #include class ShapeLayerContainerModel : public SimpleShapeContainerModel { public: ShapeLayerContainerModel(KisShapeLayer *parent) : q(parent) {} void add(KoShape *child) override { SimpleShapeContainerModel::add(child); /** * The shape is always added with the absolute transformation set appropriately. * Here we should just squeeze it into the layer's transformation. */ KIS_SAFE_ASSERT_RECOVER_NOOP(inheritsTransform(child)); if (inheritsTransform(child)) { QTransform parentTransform = q->absoluteTransformation(0); child->applyAbsoluteTransformation(parentTransform.inverted()); } } void remove(KoShape *child) override { KIS_SAFE_ASSERT_RECOVER_NOOP(inheritsTransform(child)); if (inheritsTransform(child)) { QTransform parentTransform = q->absoluteTransformation(0); child->applyAbsoluteTransformation(parentTransform); } SimpleShapeContainerModel::remove(child); } void shapeHasBeenAddedToHierarchy(KoShape *shape, KoShapeContainer *addedToSubtree) override { q->shapeManager()->addShape(shape); SimpleShapeContainerModel::shapeHasBeenAddedToHierarchy(shape, addedToSubtree); } void shapeToBeRemovedFromHierarchy(KoShape *shape, KoShapeContainer *removedFromSubtree) override { q->shapeManager()->remove(shape); SimpleShapeContainerModel::shapeToBeRemovedFromHierarchy(shape, removedFromSubtree); } private: KisShapeLayer *q; }; struct KisShapeLayer::Private { public: Private() : canvas(0) , controller(0) , x(0) , y(0) {} KisPaintDeviceSP paintDevice; KisShapeLayerCanvas * canvas; KoShapeBasedDocumentBase* controller; int x; int y; }; KisShapeLayer::KisShapeLayer(KoShapeBasedDocumentBase* controller, KisImageWSP image, const QString &name, quint8 opacity) : KisExternalLayer(image, name, opacity), KoShapeLayer(new ShapeLayerContainerModel(this)), m_d(new Private()) { initShapeLayer(controller); } KisShapeLayer::KisShapeLayer(const KisShapeLayer& rhs) : KisShapeLayer(rhs, rhs.m_d->controller) { } KisShapeLayer::KisShapeLayer(const KisShapeLayer& _rhs, KoShapeBasedDocumentBase* controller) : KisExternalLayer(_rhs) , KoShapeLayer(new ShapeLayerContainerModel(this)) //no _rhs here otherwise both layer have the same KoShapeContainerModel , m_d(new Private()) { // copy the projection to avoid extra round of updates! initShapeLayer(controller, _rhs.m_d->paintDevice); Q_FOREACH (KoShape *shape, _rhs.shapes()) { KoShape *clonedShape = shape->cloneShape(); KIS_SAFE_ASSERT_RECOVER(clonedShape) { continue; } addShape(clonedShape); } } KisShapeLayer::KisShapeLayer(const KisShapeLayer& _rhs, const KisShapeLayer &_addShapes) : KisExternalLayer(_rhs) , KoShapeLayer(new ShapeLayerContainerModel(this)) //no _merge here otherwise both layer have the same KoShapeContainerModel , m_d(new Private()) { // Make sure our new layer is visible otherwise the shapes cannot be painted. setVisible(true); initShapeLayer(_rhs.m_d->controller); + /** + * With current implementation this matrix will always be an identity, because + * we do not copy the transformation from any of the source layers. But we should + * handle this anyway, to not be caught by this in the future. + */ + const QTransform thisInvertedTransform = this->absoluteTransformation(0).inverted(); + + QList shapesAbove; + QList shapesBelow; + // copy in _rhs's shapes Q_FOREACH (KoShape *shape, _rhs.shapes()) { KoShape *clonedShape = shape->cloneShape(); KIS_SAFE_ASSERT_RECOVER(clonedShape) { continue; } - addShape(clonedShape); + clonedShape->setTransformation(shape->absoluteTransformation(0) * thisInvertedTransform); + shapesBelow.append(clonedShape); } // copy in _addShapes's shapes Q_FOREACH (KoShape *shape, _addShapes.shapes()) { KoShape *clonedShape = shape->cloneShape(); KIS_SAFE_ASSERT_RECOVER(clonedShape) { continue; } - addShape(clonedShape); + clonedShape->setTransformation(shape->absoluteTransformation(0) * thisInvertedTransform); + shapesAbove.append(clonedShape); + } + + QList shapes = + KoShapeReorderCommand::mergeDownShapes(shapesBelow, shapesAbove); + KoShapeReorderCommand cmd(shapes); + cmd.redo(); + + Q_FOREACH (KoShape *shape, shapesBelow + shapesAbove) { + addShape(shape); } } KisShapeLayer::~KisShapeLayer() { /** * Small hack alert: we should avoid updates on shape deletion */ m_d->canvas->prepareForDestroying(); Q_FOREACH (KoShape *shape, shapes()) { shape->setParent(0); delete shape; } delete m_d->canvas; delete m_d; } void KisShapeLayer::initShapeLayer(KoShapeBasedDocumentBase* controller, KisPaintDeviceSP copyFromProjection) { setSupportsLodMoves(false); setShapeId(KIS_SHAPE_LAYER_ID); KIS_ASSERT_RECOVER_NOOP(this->image()); if (!copyFromProjection) { m_d->paintDevice = new KisPaintDevice(image()->colorSpace()); m_d->paintDevice->setDefaultBounds(new KisDefaultBounds(this->image())); m_d->paintDevice->setParentNode(this); } else { m_d->paintDevice = new KisPaintDevice(*copyFromProjection); } m_d->canvas = new KisShapeLayerCanvas(this, image()); m_d->canvas->setProjection(m_d->paintDevice); m_d->canvas->moveToThread(this->thread()); m_d->controller = controller; m_d->canvas->shapeManager()->selection()->disconnect(this); connect(m_d->canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged())); connect(m_d->canvas->selectedShapesProxy(), SIGNAL(currentLayerChanged(const KoShapeLayer*)), this, SIGNAL(currentLayerChanged(const KoShapeLayer*))); connect(this, SIGNAL(sigMoveShapes(const QPointF&)), SLOT(slotMoveShapes(const QPointF&))); } bool KisShapeLayer::allowAsChild(KisNodeSP node) const { return node->inherits("KisMask"); } void KisShapeLayer::setImage(KisImageWSP _image) { KisLayer::setImage(_image); m_d->canvas->setImage(_image); m_d->paintDevice->convertTo(_image->colorSpace()); m_d->paintDevice->setDefaultBounds(new KisDefaultBounds(_image)); } KisLayerSP KisShapeLayer::createMergedLayerTemplate(KisLayerSP prevLayer) { KisShapeLayer *prevShape = dynamic_cast(prevLayer.data()); if (prevShape) return new KisShapeLayer(*prevShape, *this); else return KisExternalLayer::createMergedLayerTemplate(prevLayer); } void KisShapeLayer::fillMergedLayerTemplate(KisLayerSP dstLayer, KisLayerSP prevLayer) { if (!dynamic_cast(dstLayer.data())) { KisLayer::fillMergedLayerTemplate(dstLayer, prevLayer); } } void KisShapeLayer::setParent(KoShapeContainer *parent) { Q_UNUSED(parent) KIS_ASSERT_RECOVER_RETURN(0) } QIcon KisShapeLayer::icon() const { return KisIconUtils::loadIcon("vectorLayer"); } KisPaintDeviceSP KisShapeLayer::original() const { return m_d->paintDevice; } KisPaintDeviceSP KisShapeLayer::paintDevice() const { return 0; } qint32 KisShapeLayer::x() const { return m_d->x; } qint32 KisShapeLayer::y() const { return m_d->y; } void KisShapeLayer::setX(qint32 x) { qint32 delta = x - this->x(); QPointF diff = QPointF(m_d->canvas->viewConverter()->viewToDocumentX(delta), 0); emit sigMoveShapes(diff); // Save new value to satisfy LSP m_d->x = x; } void KisShapeLayer::setY(qint32 y) { qint32 delta = y - this->y(); QPointF diff = QPointF(0, m_d->canvas->viewConverter()->viewToDocumentY(delta)); emit sigMoveShapes(diff); // Save new value to satisfy LSP m_d->y = y; } namespace { void filterTransformableShapes(QList &shapes) { auto it = shapes.begin(); while (it != shapes.end()) { if (shapes.size() == 1) break; if ((*it)->inheritsTransformFromAny(shapes)) { it = shapes.erase(it); } else { ++it; } } } } QList KisShapeLayer::shapesToBeTransformed() { QList shapes = shapeManager()->shapes(); // We expect that **all** the shapes inherit the transform from its parent // SANITY_CHECK: we expect all the shapes inside the // shape layer to inherit transform! Q_FOREACH (KoShape *shape, shapes) { if (shape->parent()) { KIS_SAFE_ASSERT_RECOVER(shape->parent()->inheritsTransform(shape)) { break; } } } shapes << this; filterTransformableShapes(shapes); return shapes; } void KisShapeLayer::slotMoveShapes(const QPointF &diff) { QList shapes = shapesToBeTransformed(); if (shapes.isEmpty()) return; KoShapeMoveCommand cmd(shapes, diff); cmd.redo(); } bool KisShapeLayer::accept(KisNodeVisitor& visitor) { return visitor.visit(this); } void KisShapeLayer::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) { return visitor.visit(this, undoAdapter); } KoShapeManager* KisShapeLayer::shapeManager() const { return m_d->canvas->shapeManager(); } KoViewConverter* KisShapeLayer::converter() const { return m_d->canvas->viewConverter(); } bool KisShapeLayer::visible(bool recursive) const { return KisExternalLayer::visible(recursive); } void KisShapeLayer::setVisible(bool visible, bool isLoading) { KisExternalLayer::setVisible(visible, isLoading); } void KisShapeLayer::forceUpdateTimedNode() { m_d->canvas->forceRepaint(); } #include "SvgWriter.h" #include "SvgParser.h" bool KisShapeLayer::saveShapesToStore(KoStore *store, QList shapes, const QSizeF &sizeInPt) { if (!store->open("content.svg")) { return false; } KoStoreDevice storeDev(store); storeDev.open(QIODevice::WriteOnly); std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); SvgWriter writer(shapes); writer.save(storeDev, sizeInPt); if (!store->close()) { return false; } return true; } QList KisShapeLayer::createShapesFromSvg(QIODevice *device, const QString &baseXmlDir, const QRectF &rectInPixels, qreal resolutionPPI, KoDocumentResourceManager *resourceManager, QSizeF *fragmentSize) { QString errorMsg; int errorLine = 0; int errorColumn; KoXmlDocument doc; bool ok = doc.setContent(device, false, &errorMsg, &errorLine, &errorColumn); if (!ok) { errKrita << "Parsing error in " << "contents.svg" << "! Aborting!" << endl << " In line: " << errorLine << ", column: " << errorColumn << endl << " Error message: " << errorMsg << endl; errKrita << i18n("Parsing error in the main document at line %1, column %2\nError message: %3" , errorLine , errorColumn , errorMsg); } SvgParser parser(resourceManager); parser.setXmlBaseDir(baseXmlDir); parser.setResolution(rectInPixels /* px */, resolutionPPI /* ppi */); return parser.parseSvg(doc.documentElement(), fragmentSize); } bool KisShapeLayer::saveLayer(KoStore * store) const { // FIXME: we handle xRes() only! const QSizeF sizeInPx = image()->bounds().size(); const QSizeF sizeInPt(sizeInPx.width() / image()->xRes(), sizeInPx.height() / image()->yRes()); return saveShapesToStore(store, this->shapes(), sizeInPt); } bool KisShapeLayer::loadSvg(QIODevice *device, const QString &baseXmlDir) { QSizeF fragmentSize; // unused! KisImageSP image = this->image(); // FIXME: we handle xRes() only! KIS_SAFE_ASSERT_RECOVER_NOOP(qFuzzyCompare(image->xRes(), image->yRes())); const qreal resolutionPPI = 72.0 * image->xRes(); QList shapes = createShapesFromSvg(device, baseXmlDir, image->bounds(), resolutionPPI, m_d->controller->resourceManager(), &fragmentSize); Q_FOREACH (KoShape *shape, shapes) { addShape(shape); } return true; } bool KisShapeLayer::loadLayer(KoStore* store) { if (!store) { warnKrita << i18n("No store backend"); return false; } if (store->open("content.svg")) { KoStoreDevice storeDev(store); storeDev.open(QIODevice::ReadOnly); loadSvg(&storeDev, ""); store->close(); return true; } KoOdfReadStore odfStore(store); QString errorMessage; odfStore.loadAndParse(errorMessage); if (!errorMessage.isEmpty()) { warnKrita << errorMessage; return false; } KoXmlElement contents = odfStore.contentDoc().documentElement(); // dbgKrita <<"Start loading OASIS document..." << contents.text(); // dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().localName(); // dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().namespaceURI(); // dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().isElement(); KoXmlElement body(KoXml::namedItemNS(contents, KoXmlNS::office, "body")); if (body.isNull()) { //setErrorMessage( i18n( "Invalid OASIS document. No office:body tag found." ) ); return false; } body = KoXml::namedItemNS(body, KoXmlNS::office, "drawing"); if (body.isNull()) { //setErrorMessage( i18n( "Invalid OASIS document. No office:drawing tag found." ) ); return false; } KoXmlElement page(KoXml::namedItemNS(body, KoXmlNS::draw, "page")); if (page.isNull()) { //setErrorMessage( i18n( "Invalid OASIS document. No draw:page tag found." ) ); return false; } KoXmlElement * master = 0; if (odfStore.styles().masterPages().contains("Standard")) master = odfStore.styles().masterPages().value("Standard"); else if (odfStore.styles().masterPages().contains("Default")) master = odfStore.styles().masterPages().value("Default"); else if (! odfStore.styles().masterPages().empty()) master = odfStore.styles().masterPages().begin().value(); if (master) { const KoXmlElement *style = odfStore.styles().findStyle( master->attributeNS(KoXmlNS::style, "page-layout-name", QString())); KoPageLayout pageLayout; pageLayout.loadOdf(*style); setSize(QSizeF(pageLayout.width, pageLayout.height)); } // We work fine without a master page KoOdfLoadingContext context(odfStore.styles(), odfStore.store()); context.setManifestFile(QString("tar:/") + odfStore.store()->currentPath() + "META-INF/manifest.xml"); KoShapeLoadingContext shapeContext(context, m_d->controller->resourceManager()); KoXmlElement layerElement; forEachElement(layerElement, context.stylesReader().layerSet()) { // FIXME: investigate what is this // KoShapeLayer * l = new KoShapeLayer(); if (!loadOdf(layerElement, shapeContext)) { dbgKrita << "Could not load vector layer!"; return false; } } KoXmlElement child; forEachElement(child, page) { KoShape * shape = KoShapeRegistry::instance()->createShapeFromOdf(child, shapeContext); if (shape) { addShape(shape); } } return true; } void KisShapeLayer::resetCache() { m_d->paintDevice->clear(); QList shapes = m_d->canvas->shapeManager()->shapes(); Q_FOREACH (const KoShape* shape, shapes) { shape->update(); } } KUndo2Command* KisShapeLayer::crop(const QRect & rect) { QPoint oldPos(x(), y()); QPoint newPos = oldPos - rect.topLeft(); return new KisNodeMoveCommand2(this, oldPos, newPos); } KUndo2Command* KisShapeLayer::transform(const QTransform &transform) { QList shapes = shapesToBeTransformed(); if (shapes.isEmpty()) return 0; KisImageViewConverter *converter = dynamic_cast(this->converter()); QTransform realTransform = converter->documentToView() * transform * converter->viewToDocument(); QList oldTransformations; QList newTransformations; QList newShadows; const qreal transformBaseScale = KoUnit::approxTransformScale(transform); Q_FOREACH (const KoShape* shape, shapes) { QTransform oldTransform = shape->transformation(); oldTransformations.append(oldTransform); QTransform globalTransform = shape->absoluteTransformation(0); QTransform localTransform = globalTransform * realTransform * globalTransform.inverted(); newTransformations.append(localTransform * oldTransform); KoShapeShadow *shadow = 0; if (shape->shadow()) { shadow = new KoShapeShadow(*shape->shadow()); shadow->setOffset(transformBaseScale * shadow->offset()); shadow->setBlur(transformBaseScale * shadow->blur()); } newShadows.append(shadow); } KUndo2Command *parentCommand = new KUndo2Command(); new KoShapeTransformCommand(shapes, oldTransformations, newTransformations, parentCommand); new KoShapeShadowCommand(shapes, newShadows, parentCommand); return parentCommand; } diff --git a/libs/ui/flake/kis_shape_layer_canvas.cpp b/libs/ui/flake/kis_shape_layer_canvas.cpp index 3fc56b5c4c..b160c53a1d 100644 --- a/libs/ui/flake/kis_shape_layer_canvas.cpp +++ b/libs/ui/flake/kis_shape_layer_canvas.cpp @@ -1,256 +1,264 @@ /* * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_shape_layer_canvas.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_image_view_converter.h" #include #include #include #include #include "kis_image.h" +#include "kis_global.h" //#define DEBUG_REPAINT KisShapeLayerCanvas::KisShapeLayerCanvas(KisShapeLayer *parent, KisImageWSP image) : KoCanvasBase(0) , m_isDestroying(false) , m_viewConverter(new KisImageViewConverter(image)) , m_shapeManager(new KoShapeManager(this)) , m_selectedShapesProxy(new KoSelectedShapesProxySimple(m_shapeManager.data())) , m_projection(0) , m_parentLayer(parent) , m_image(image) { + /** + * The layour should also add itself to its own shape manager, so that the canvas + * would track its changes/transformations + */ + m_shapeManager->addShape(parent, KoShapeManager::AddWithoutRepaint); m_shapeManager->selection()->setActiveLayer(parent); + connect(this, SIGNAL(forwardRepaint()), SLOT(repaint()), Qt::QueuedConnection); } KisShapeLayerCanvas::~KisShapeLayerCanvas() { + m_shapeManager->remove(m_parentLayer); } void KisShapeLayerCanvas::setImage(KisImageWSP image) { m_viewConverter->setImage(image); } void KisShapeLayerCanvas::prepareForDestroying() { m_isDestroying = true; } void KisShapeLayerCanvas::gridSize(QPointF *offset, QSizeF *spacing) const { Q_ASSERT(false); // This should never be called as this canvas should have no tools. Q_UNUSED(offset); Q_UNUSED(spacing); } bool KisShapeLayerCanvas::snapToGrid() const { Q_ASSERT(false); // This should never be called as this canvas should have no tools. return false; } void KisShapeLayerCanvas::addCommand(KUndo2Command *) { Q_ASSERT(false); // This should never be called as this canvas should have no tools. } KoShapeManager *KisShapeLayerCanvas::shapeManager() const { return m_shapeManager.data(); } KoSelectedShapesProxy *KisShapeLayerCanvas::selectedShapesProxy() const { return m_selectedShapesProxy.data(); } #ifdef DEBUG_REPAINT # include #endif class KisRepaintShapeLayerLayerJob : public KisSpontaneousJob { public: KisRepaintShapeLayerLayerJob(KisShapeLayerSP layer) : m_layer(layer) {} bool overrides(const KisSpontaneousJob *_otherJob) override { const KisRepaintShapeLayerLayerJob *otherJob = dynamic_cast(_otherJob); return otherJob && otherJob->m_layer == m_layer; } void run() override { m_layer->forceUpdateTimedNode(); } int levelOfDetail() const override { return 0; } private: KisShapeLayerSP m_layer; }; void KisShapeLayerCanvas::updateCanvas(const QRectF& rc) { dbgUI << "KisShapeLayerCanvas::updateCanvas()" << rc; //image is 0, if parentLayer is being deleted so don't update if (!m_parentLayer->image() || m_isDestroying) { return; } - QRect r = m_viewConverter->documentToView(rc).toRect(); - r.adjust(-2, -2, 2, 2); // for antialias + // grow for antialiasing + const QRect r = kisGrowRect(m_viewConverter->documentToView(rc).toAlignedRect(), 2); { QMutexLocker locker(&m_dirtyRegionMutex); m_dirtyRegion += r; qreal x, y; m_viewConverter->zoom(&x, &y); } /** * HACK ALERT! * * The shapes may be accessed from both, GUI and worker threads! And we have no real * guard against this until the vector tools will be ported to the strokes framework. * * Here we just avoid the most obvious conflict of threads: * * 1) If the layer if modified by a non-gui (worker) thread, use a spontaneous jobs * to rerender the canvas. The job will be executed (almost) exclusively and it is * the responsibility of the worker thread to add a barrier to wait until this job is * completed, and not try to access the shapes concurrently. * * 2) If the layer is modified by a gui thread, it means that we are being accessed by * a legacy vector tool. It this case just emit a queued signal to make sure the updates * are compressed a little bit (TODO: add a compressor?) */ if (qApp->thread() == QThread::currentThread()) { emit forwardRepaint(); } else { m_image->addSpontaneousJob(new KisRepaintShapeLayerLayerJob(m_parentLayer)); } } void KisShapeLayerCanvas::repaint() { QRect r; { QMutexLocker locker(&m_dirtyRegionMutex); r = m_dirtyRegion.boundingRect(); m_dirtyRegion = QRegion(); } if (r.isEmpty()) return; r = r.intersected(m_parentLayer->image()->bounds()); QImage image(r.width(), r.height(), QImage::Format_ARGB32); image.fill(0); QPainter p(&image); p.setRenderHint(QPainter::Antialiasing); p.setRenderHint(QPainter::TextAntialiasing); p.translate(-r.x(), -r.y()); p.setClipRect(r); #ifdef DEBUG_REPAINT QColor color = QColor(random() % 255, random() % 255, random() % 255); p.fillRect(r, color); #endif m_shapeManager->paint(p, *m_viewConverter, false); p.end(); KisPaintDeviceSP dev = new KisPaintDevice(m_projection->colorSpace()); dev->convertFromQImage(image, 0); KisPainter::copyAreaOptimized(r.topLeft(), dev, m_projection, QRect(QPoint(), r.size())); m_parentLayer->setDirty(r); } KoToolProxy * KisShapeLayerCanvas::toolProxy() const { // Q_ASSERT(false); // This should never be called as this canvas should have no tools. return 0; } KoViewConverter* KisShapeLayerCanvas::viewConverter() const { return m_viewConverter.data(); } QWidget* KisShapeLayerCanvas::canvasWidget() { return 0; } const QWidget* KisShapeLayerCanvas::canvasWidget() const { return 0; } KoUnit KisShapeLayerCanvas::unit() const { Q_ASSERT(false); // This should never be called as this canvas should have no tools. return KoUnit(KoUnit::Point); } void KisShapeLayerCanvas::forceRepaint() { /** * WARNING! Although forceRepaint() may be called from different threads, it is * not entirely safe. If the user plays with shapes at the same time (vector tools are * not ported to strokes yet), the shapes my be accessed from two different places at * the same time, which will cause a crash. * * The only real solution to this is to port vector tools to strokes framework. */ repaint(); } diff --git a/libs/ui/tests/CMakeLists.txt b/libs/ui/tests/CMakeLists.txt index e1a7567439..a9c60cc191 100644 --- a/libs/ui/tests/CMakeLists.txt +++ b/libs/ui/tests/CMakeLists.txt @@ -1,181 +1,182 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) #add_subdirectory(scratchpad) include_directories(${CMAKE_SOURCE_DIR}/libs/image/metadata ${CMAKE_SOURCE_DIR}/sdk/tests ) include(ECMAddTests) macro_add_unittest_definitions() ecm_add_tests( kis_image_view_converter_test.cpp kis_shape_selection_test.cpp kis_recorded_action_editor_test.cpp kis_doc2_test.cpp kis_coordinates_converter_test.cpp kis_grid_config_test.cpp kis_stabilized_events_sampler_test.cpp kis_derived_resources_test.cpp kis_brush_hud_properties_config_test.cpp kis_shape_commands_test.cpp + kis_shape_layer_test.cpp kis_stop_gradient_editor_test.cpp NAME_PREFIX "krita-ui-" LINK_LIBRARIES kritaui Qt5::Test ) ecm_add_tests( kis_file_layer_test.cpp kis_multinode_property_test.cpp NAME_PREFIX "krita-ui-" LINK_LIBRARIES kritaui kritaimage Qt5::Test ) ecm_add_test( kis_selection_decoration_test.cpp ../../../sdk/tests/stroke_testing_utils.cpp TEST_NAME krita-ui-KisSelectionDecorationTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) ecm_add_test( kis_node_dummies_graph_test.cpp ../../../sdk/tests/testutil.cpp TEST_NAME krita-ui-KisNodeDummiesGraphTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) ecm_add_test( kis_node_shapes_graph_test.cpp ../../../sdk/tests/testutil.cpp TEST_NAME krita-ui-KisNodeShapesGraphTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) ecm_add_test( kis_model_index_converter_test.cpp ../../../sdk/tests/testutil.cpp TEST_NAME krita-ui-KisModelIndexConverterTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) ecm_add_test( kis_categorized_list_model_test.cpp modeltest.cpp TEST_NAME krita-ui-KisCategorizedListModelTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) ecm_add_test( kis_resource_server_provider_test.cpp modeltest.cpp TEST_NAME krita-ui-KisResourceServerProviderTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) ecm_add_test( kis_node_juggler_compressed_test.cpp ../../../sdk/tests/testutil.cpp TEST_NAME krita-image-BaseNodeTest LINK_LIBRARIES kritaimage kritaui Qt5::Test) ecm_add_test( kis_animation_exporter_test.cpp TEST_NAME kritaui-animation_exporter_test LINK_LIBRARIES kritaui kritaimage Qt5::Test) set(kis_node_view_test_SRCS kis_node_view_test.cpp ../../../sdk/tests/testutil.cpp) qt5_add_resources(kis_node_view_test_SRCS ${krita_QRCS}) ecm_add_test(${kis_node_view_test_SRCS} TEST_NAME krita-image-kis_node_view_test LINK_LIBRARIES kritaimage kritaui Qt5::Test) ##### Tests that currently fail and should be fixed ##### include(KritaAddBrokenUnitTest) krita_add_broken_unit_test( kis_node_model_test.cpp modeltest.cpp TEST_NAME krita-ui-kis_node_model_test LINK_LIBRARIES kritaui Qt5::Test) krita_add_broken_unit_test( kis_shape_controller_test.cpp kis_dummies_facade_base_test.cpp TEST_NAME krita-ui-kis_shape_controller_test LINK_LIBRARIES kritaimage kritaui Qt5::Test) krita_add_broken_unit_test( kis_prescaled_projection_test.cpp TEST_NAME krita-ui-kis_prescaled_projection_test LINK_LIBRARIES kritaui Qt5::Test) krita_add_broken_unit_test( kis_exiv2_test.cpp TEST_NAME krita-ui-KisExiv2Test LINK_LIBRARIES kritaimage kritaui Qt5::Test) krita_add_broken_unit_test( kis_clipboard_test.cpp TEST_NAME krita-ui-KisClipboardTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) krita_add_broken_unit_test( freehand_stroke_test.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp TEST_NAME krita-ui-FreehandStrokeTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) krita_add_broken_unit_test( FreehandStrokeBenchmark.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp TEST_NAME krita-ui-FreehandStrokeBenchmark LINK_LIBRARIES kritaui kritaimage Qt5::Test) krita_add_broken_unit_test( fill_processing_visitor_test.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp TEST_NAME krita-ui-FillProcessingVisitorTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) krita_add_broken_unit_test( filter_stroke_test.cpp ../../../sdk/tests/stroke_testing_utils.cpp TEST_NAME krita-ui-FilterStrokeTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) krita_add_broken_unit_test( kis_selection_manager_test.cpp TEST_NAME krita-ui-KisSelectionManagerTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) #set_tests_properties(krita-ui-KisSelectionManagerTest PROPERTIES TIMEOUT 300) krita_add_broken_unit_test( kis_node_manager_test.cpp TEST_NAME krita-ui-KisNodeManagerTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) krita_add_broken_unit_test( kis_dummies_facade_test.cpp kis_dummies_facade_base_test.cpp ../../../sdk/tests/testutil.cpp TEST_NAME krita-ui-KisDummiesFacadeTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) krita_add_broken_unit_test( kis_zoom_and_pan_test.cpp ../../../sdk/tests/testutil.cpp TEST_NAME krita-ui-KisZoomAndPanTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) #set_tests_properties(krita-ui-KisZoomAndPanTest PROPERTIES TIMEOUT 300) krita_add_broken_unit_test( kis_action_manager_test.cpp TEST_NAME krita-ui-KisActionManagerTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) krita_add_broken_unit_test( kis_categories_mapper_test.cpp testing_categories_mapper.cpp TEST_NAME krita-ui-KisCategoriesMapperTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) krita_add_broken_unit_test( kis_asl_layer_style_serializer_test.cpp TEST_NAME krita-ui-KisAslLayerStyleSerializerTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) krita_add_broken_unit_test( kis_animation_importer_test.cpp TEST_NAME kritaui-animation_importer_test LINK_LIBRARIES kritaui kritaimage Qt5::Test) krita_add_broken_unit_test( kis_animation_frame_cache_test.cpp TEST_NAME kritaui-animation_frame_cache_test LINK_LIBRARIES kritaui kritaimage Qt5::Test) krita_add_broken_unit_test( ResourceBundleTest.cpp TEST_NAME krita-resourcemanager-ResourceBundleTest LINK_LIBRARIES kritaui kritalibbrush kritalibpaintop Qt5::Test) # FIXME this test doesn't compile #ecm_add_test( # kis_input_manager_test.cpp ../../../sdk/tests/testutil.cpp # TEST_NAME krita-ui-KisInputManagerTest # LINK_LIBRARIES kritaui kritaimage Qt5::Test) diff --git a/libs/ui/tests/data/shape_layer_test/merge_down_00_initial_layer_update.png b/libs/ui/tests/data/shape_layer_test/merge_down_00_initial_layer_update.png new file mode 100644 index 0000000000..b7a268edc2 Binary files /dev/null and b/libs/ui/tests/data/shape_layer_test/merge_down_00_initial_layer_update.png differ diff --git a/libs/ui/tests/data/shape_layer_test/merge_down_02_after_merge_down.png b/libs/ui/tests/data/shape_layer_test/merge_down_02_after_merge_down.png new file mode 100644 index 0000000000..8f96d17e1f Binary files /dev/null and b/libs/ui/tests/data/shape_layer_test/merge_down_02_after_merge_down.png differ diff --git a/libs/ui/tests/data/shape_layer_test/scale_and_merge_down_00_initial_layer_update.png b/libs/ui/tests/data/shape_layer_test/scale_and_merge_down_00_initial_layer_update.png new file mode 100644 index 0000000000..b7a268edc2 Binary files /dev/null and b/libs/ui/tests/data/shape_layer_test/scale_and_merge_down_00_initial_layer_update.png differ diff --git a/libs/ui/tests/data/shape_layer_test/scale_and_merge_down_01_after_scale_down.png b/libs/ui/tests/data/shape_layer_test/scale_and_merge_down_01_after_scale_down.png new file mode 100644 index 0000000000..2a6eae2d52 Binary files /dev/null and b/libs/ui/tests/data/shape_layer_test/scale_and_merge_down_01_after_scale_down.png differ diff --git a/libs/ui/tests/data/shape_layer_test/scale_and_merge_down_02_after_merge_down.png b/libs/ui/tests/data/shape_layer_test/scale_and_merge_down_02_after_merge_down.png new file mode 100644 index 0000000000..833afaea24 Binary files /dev/null and b/libs/ui/tests/data/shape_layer_test/scale_and_merge_down_02_after_merge_down.png differ diff --git a/libs/ui/tests/kis_file_layer_test.cpp b/libs/ui/tests/kis_file_layer_test.cpp index 038e2657d7..0607008b2e 100644 --- a/libs/ui/tests/kis_file_layer_test.cpp +++ b/libs/ui/tests/kis_file_layer_test.cpp @@ -1,146 +1,146 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_file_layer_test.h" #include #include #include #include #include #include void KisFileLayerTest::testFileLayerPlusTransformMaskOffImage() { - TestUtil::ExternalImageChecker chk("flayer_tmask_offimage", "file_layer"); + TestUtil::ReferenceImageChecker chk("flayer_tmask_offimage", "file_layer"); QRect refRect(0,0,640,441); QRect fillRect(400,400,100,100); TestUtil::MaskParent p(refRect); QString refName(TestUtil::fetchDataFileLazy("hakonepa.png")); KisLayerSP flayer = new KisFileLayer(p.image, "", refName, KisFileLayer::None, "flayer", OPACITY_OPAQUE_U8); p.image->addNode(flayer, p.image->root(), KisNodeSP()); QTest::qWait(2000); p.image->waitForDone(); KisTransformMaskSP mask1 = new KisTransformMask(); p.image->addNode(mask1, flayer); mask1->setName("mask1"); flayer->setDirty(refRect); p.image->waitForDone(); chk.checkImage(p.image, "00_initial_layer_update"); QTest::qWait(4000); p.image->waitForDone(); chk.checkImage(p.image, "00X_initial_layer_update"); flayer->setX(580); flayer->setY(400); flayer->setDirty(refRect); p.image->waitForDone(); chk.checkImage(p.image, "01_file_layer_moved"); QTest::qWait(4000); p.image->waitForDone(); chk.checkImage(p.image, "01X_file_layer_moved"); QTransform transform = QTransform::fromTranslate(-580, -400); mask1->setTransformParams(KisTransformMaskParamsInterfaceSP( new KisDumbTransformMaskParams(transform))); /** * NOTE: here we see our image cropped by 1.5 image size rect! * That is expected and controlled by * KisImageConfig::transformMaskOffBoundsReadArea() * parameter */ mask1->setDirty(refRect); p.image->waitForDone(); chk.checkImage(p.image, "02_mask1_moved_mask_update"); QTest::qWait(4000); p.image->waitForDone(); chk.checkImage(p.image, "02X_mask1_moved_mask_update"); QVERIFY(chk.testPassed()); } void KisFileLayerTest::testFileLayerPlusTransformMaskSmallFileBigOffset() { - TestUtil::ExternalImageChecker chk("flayer_tmask_huge_offset", "file_layer"); + TestUtil::ReferenceImageChecker chk("flayer_tmask_huge_offset", "file_layer"); QRect refRect(0,0,2000,1500); QRect fillRect(400,400,100,100); TestUtil::MaskParent p(refRect); QString refName(TestUtil::fetchDataFileLazy("file_layer_source.png")); KisLayerSP flayer = new KisFileLayer(p.image, "", refName, KisFileLayer::None, "flayer", OPACITY_OPAQUE_U8); p.image->addNode(flayer, p.image->root(), KisNodeSP()); QTest::qWait(2000); p.image->waitForDone(); // check whether the default bounds of the file layer are // initialized properly QCOMPARE(flayer->original()->defaultBounds()->bounds(), p.image->bounds()); KisTransformMaskSP mask1 = new KisTransformMask(); p.image->addNode(mask1, flayer); mask1->setName("mask1"); flayer->setDirty(refRect); p.image->waitForDone(); chk.checkImage(p.image, "00_initial_layer_update"); QTest::qWait(4000); p.image->waitForDone(); chk.checkImage(p.image, "00X_initial_layer_update"); QTransform transform; transform = QTransform::fromTranslate(1200, 300); mask1->setTransformParams(KisTransformMaskParamsInterfaceSP( new KisDumbTransformMaskParams(transform))); mask1->setDirty(refRect); p.image->waitForDone(); chk.checkImage(p.image, "01_mask1_moved_mask_update"); QTest::qWait(4000); p.image->waitForDone(); chk.checkImage(p.image, "01X_mask1_moved_mask_update"); QVERIFY(chk.testPassed()); } QTEST_MAIN(KisFileLayerTest) diff --git a/libs/ui/tests/kis_node_juggler_compressed_test.cpp b/libs/ui/tests/kis_node_juggler_compressed_test.cpp index 1c2690eb65..78c3d12bb6 100644 --- a/libs/ui/tests/kis_node_juggler_compressed_test.cpp +++ b/libs/ui/tests/kis_node_juggler_compressed_test.cpp @@ -1,190 +1,190 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_node_juggler_compressed_test.h" #include #include "kis_node_juggler_compressed.h" #include #include void KisNodeJugglerCompressedTest::init() { p.reset(new TestUtil::MaskParent); QRect rect1(100, 100, 100, 100); QRect rect2(150, 150, 150, 150); QRect rect3(50, 50, 100, 100); layer1 = p->layer; layer1->paintDevice()->fill(rect1, KoColor(Qt::red, layer1->colorSpace())); layer2 = new KisPaintLayer(p->image, "paint2", OPACITY_OPAQUE_U8); layer2->paintDevice()->fill(rect2, KoColor(Qt::blue, layer2->colorSpace())); layer3 = new KisPaintLayer(p->image, "paint3", OPACITY_OPAQUE_U8); group4 = new KisGroupLayer(p->image, "group4", OPACITY_OPAQUE_U8); layer5 = new KisPaintLayer(p->image, "paint5", OPACITY_OPAQUE_U8); layer6 = new KisPaintLayer(p->image, "paint6", OPACITY_OPAQUE_U8); p->image->addNode(layer2); p->image->addNode(layer3); p->image->addNode(group4); p->image->addNode(layer5, group4); p->image->addNode(layer6); p->image->initialRefreshGraph(); } void KisNodeJugglerCompressedTest::cleanup() { p.reset(); layer1.clear(); layer2.clear(); } void KisNodeJugglerCompressedTest::testMove(int delayBeforeEnd) { - TestUtil::ExternalImageChecker chk("node_juggler", "move_test"); + TestUtil::ReferenceImageChecker chk("node_juggler", "move_test"); chk.setMaxFailingPixels(0); KisNodeJugglerCompressed juggler(kundo2_i18n("Move Layer"), p->image, 0, 600); QVERIFY(chk.checkImage(p->image, "initial")); juggler.moveNode(layer1, p->image->root(), layer2); QTest::qWait(100); QVERIFY(chk.checkImage(p->image, "initial")); if (delayBeforeEnd) { QTest::qWait(delayBeforeEnd); QVERIFY(chk.checkImage(p->image, "moved")); } juggler.end(); p->image->waitForDone(); QVERIFY(chk.checkImage(p->image, "moved")); p->undoStore->undo(); p->image->waitForDone(); QVERIFY(chk.checkImage(p->image, "initial")); } void KisNodeJugglerCompressedTest::testApplyUndo() { testMove(1000); } void KisNodeJugglerCompressedTest::testEndBeforeUpdate() { testMove(0); } void KisNodeJugglerCompressedTest::testDuplicateImpl(bool externalParent, bool useMove) { - TestUtil::ExternalImageChecker chk("node_juggler", "move_test"); + TestUtil::ReferenceImageChecker chk("node_juggler", "move_test"); chk.setMaxFailingPixels(0); QStringList initialRef; initialRef << "paint1"; initialRef << "paint2"; initialRef << "paint3"; initialRef << "group4"; initialRef << "+paint5"; initialRef << "paint6"; QVERIFY(TestUtil::checkHierarchy(p->image->root(), initialRef)); KisNodeList selectedNodes; selectedNodes << layer2; selectedNodes << layer3; selectedNodes << layer5; KisNodeJugglerCompressed juggler(kundo2_i18n("Duplicate Layers"), p->image, 0, 600); if (!externalParent) { juggler.duplicateNode(selectedNodes); } else { if (useMove) { juggler.moveNode(selectedNodes, p->image->root(), layer6); } else { juggler.copyNode(selectedNodes, p->image->root(), layer6); } } QTest::qWait(1000); juggler.end(); p->image->waitForDone(); QStringList ref; if (!externalParent) { ref << "paint1"; ref << "paint2"; ref << "paint3"; ref << "group4"; ref << "+paint5"; ref << "+Copy of paint2"; ref << "+Copy of paint3"; ref << "+Copy of paint5"; ref << "paint6"; } else if (!useMove) { ref << "paint1"; ref << "paint2"; ref << "paint3"; ref << "group4"; ref << "+paint5"; ref << "paint6"; ref << "Copy of paint2"; ref << "Copy of paint3"; ref << "Copy of paint5"; } else { ref << "paint1"; ref << "group4"; ref << "paint6"; ref << "paint2"; ref << "paint3"; ref << "paint5"; } QVERIFY(TestUtil::checkHierarchy(p->image->root(), ref)); p->undoStore->undo(); p->image->waitForDone(); QVERIFY(TestUtil::checkHierarchy(p->image->root(), initialRef)); } void KisNodeJugglerCompressedTest::testDuplicate() { testDuplicateImpl(false, false); } void KisNodeJugglerCompressedTest::testCopyLayers() { testDuplicateImpl(true, false); } void KisNodeJugglerCompressedTest::testMoveLayers() { testDuplicateImpl(true, true); } QTEST_MAIN(KisNodeJugglerCompressedTest) diff --git a/libs/ui/tests/kis_shape_commands_test.cpp b/libs/ui/tests/kis_shape_commands_test.cpp index 03ca57c682..f93fa5727b 100644 --- a/libs/ui/tests/kis_shape_commands_test.cpp +++ b/libs/ui/tests/kis_shape_commands_test.cpp @@ -1,229 +1,229 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_shape_commands_test.h" #include #include "kis_global.h" #include "kis_shape_layer.h" #include #include #include "testutil.h" #include #include #include #include void KisShapeCommandsTest::testGrouping() { - TestUtil::ExternalImageChecker chk("grouping", "shape_commands_test"); + TestUtil::ReferenceImageChecker chk("grouping", "shape_commands_test"); QRect refRect(0,0,64,64); QScopedPointer doc(KisPart::instance()->createDocument()); TestUtil::MaskParent p(refRect); const qreal resolution = 72.0 / 72.0; p.image->setResolution(resolution, resolution); doc->setCurrentImage(p.image); KisShapeLayerSP shapeLayer = new KisShapeLayer(doc->shapeController(), p.image, "shapeLayer1", 75); { KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); path->moveTo(QPointF(5, 5)); path->lineTo(QPointF(5, 55)); path->lineTo(QPointF(55, 55)); path->lineTo(QPointF(55, 5)); path->close(); path->normalize(); path->setBackground(toQShared(new KoColorBackground(Qt::red))); path->setName("shape1"); path->setZIndex(1); shapeLayer->addShape(path); } { KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); path->moveTo(QPointF(30, 30)); path->lineTo(QPointF(30, 60)); path->lineTo(QPointF(60, 60)); path->lineTo(QPointF(60, 30)); path->close(); path->normalize(); path->setBackground(toQShared(new KoColorBackground(Qt::green))); path->setName("shape2"); path->setZIndex(2); shapeLayer->addShape(path); } p.image->addNode(shapeLayer); shapeLayer->setDirty(); qApp->processEvents(); p.image->waitForDone(); chk.checkImage(p.image, "00_initial_layer_update"); QList shapes = shapeLayer->shapes(); KoShapeGroup *group = new KoShapeGroup(); group->setName("group_shape"); shapeLayer->addShape(group); QScopedPointer cmd( new KoShapeGroupCommand(group, shapes, false, true, true)); cmd->redo(); shapeLayer->setDirty(); qApp->processEvents(); p.image->waitForDone(); chk.checkImage(p.image, "00_initial_layer_update"); cmd->undo(); shapeLayer->setDirty(); qApp->processEvents(); p.image->waitForDone(); chk.checkImage(p.image, "00_initial_layer_update"); QVERIFY(chk.testPassed()); } void KisShapeCommandsTest::testResizeShape(bool normalizeGroup) { - TestUtil::ExternalImageChecker chk("resize_shape", "shape_commands_test"); + TestUtil::ReferenceImageChecker chk("resize_shape", "shape_commands_test"); QRect refRect(0,0,64,64); QScopedPointer doc(KisPart::instance()->createDocument()); TestUtil::MaskParent p(refRect); const qreal resolution = 72.0 / 72.0; p.image->setResolution(resolution, resolution); doc->setCurrentImage(p.image); KisShapeLayerSP shapeLayer = new KisShapeLayer(doc->shapeController(), p.image, "shapeLayer1", 75); { KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); path->moveTo(QPointF(5, 5)); path->lineTo(QPointF(5, 55)); path->lineTo(QPointF(55, 55)); path->lineTo(QPointF(55, 5)); path->close(); path->normalize(); path->setBackground(toQShared(new KoColorBackground(Qt::red))); path->setName("shape1"); path->setZIndex(1); shapeLayer->addShape(path); } { KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); path->moveTo(QPointF(30, 30)); path->lineTo(QPointF(30, 60)); path->lineTo(QPointF(60, 60)); path->lineTo(QPointF(60, 30)); path->close(); path->normalize(); path->setBackground(toQShared(new KoColorBackground(Qt::green))); path->setName("shape2"); path->setZIndex(2); shapeLayer->addShape(path); } p.image->addNode(shapeLayer); shapeLayer->setDirty(); qApp->processEvents(); p.image->waitForDone(); chk.checkImage(p.image, "00_initial_layer_update"); QList shapes = shapeLayer->shapes(); KoShapeGroup *group = new KoShapeGroup(); group->setName("group_shape"); shapeLayer->addShape(group); QScopedPointer cmd( new KoShapeGroupCommand(group, shapes, false, true, normalizeGroup)); cmd->redo(); shapeLayer->setDirty(); qApp->processEvents(); p.image->waitForDone(); chk.checkImage(p.image, "00_initial_layer_update"); qDebug() << "Before:"; qDebug() << ppVar(group->absolutePosition(KoFlake::TopLeft)); qDebug() << ppVar(group->absolutePosition(KoFlake::BottomRight)); qDebug() << ppVar(group->outlineRect()); qDebug() << ppVar(group->transformation()); QCOMPARE(group->absolutePosition(KoFlake::TopLeft), QPointF(5,5)); QCOMPARE(group->absolutePosition(KoFlake::BottomRight), QPointF(60,60)); const QPointF stillPoint = group->absolutePosition(KoFlake::BottomRight); KoFlake::resizeShape(group, 1.2, 1.4, stillPoint, false, true, QTransform()); qDebug() << "After:"; qDebug() << ppVar(group->absolutePosition(KoFlake::TopLeft)); qDebug() << ppVar(group->absolutePosition(KoFlake::BottomRight)); qDebug() << ppVar(group->outlineRect()); qDebug() << ppVar(group->transformation()); QCOMPARE(group->absolutePosition(KoFlake::TopLeft), QPointF(-6,-17)); QCOMPARE(group->absolutePosition(KoFlake::BottomRight), QPointF(60,60)); } void KisShapeCommandsTest::testResizeShape() { testResizeShape(false); } void KisShapeCommandsTest::testResizeShapeNormalized() { testResizeShape(true); } QTEST_MAIN(KisShapeCommandsTest) diff --git a/libs/ui/tests/kis_shape_layer_test.cpp b/libs/ui/tests/kis_shape_layer_test.cpp new file mode 100644 index 0000000000..db934a9e50 --- /dev/null +++ b/libs/ui/tests/kis_shape_layer_test.cpp @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2018 Dmitry Kazakov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "kis_shape_layer_test.h" + +#include + +#include "kis_global.h" + +#include "kis_shape_layer.h" +#include +#include +#include "testutil.h" + +#include +#include + +#include +#include + +#include "kis_filter_strategy.h" + + +void testMergeDownImpl(bool useImageTransformations) +{ + const QString testName = useImageTransformations ? "scale_and_merge_down" : "merge_down"; + + using namespace TestUtil; + + ReferenceImageChecker chk(testName, "shape_layer_test", ReferenceImageChecker::InternalStorage); + chk.setMaxFailingPixels(10); + + QScopedPointer doc(KisPart::instance()->createDocument()); + + const QRect refRect(0,0,64,64); + MaskParent p(refRect); + + const qreal resolution = 72.0 / 72.0; + p.image->setResolution(resolution, resolution); + + doc->setCurrentImage(p.image); + + + KisShapeLayerSP shapeLayer1 = new KisShapeLayer(doc->shapeController(), p.image, "shapeLayer1", 150); + + { + KoPathShape* path = new KoPathShape(); + path->setShapeId(KoPathShapeId); + path->moveTo(QPointF(5, 5)); + path->lineTo(QPointF(5, 55)); + path->lineTo(QPointF(20, 55)); + path->lineTo(QPointF(20, 5)); + path->close(); + path->normalize(); + path->setBackground(toQShared(new KoColorBackground(Qt::red))); + + path->setName("shape1"); + path->setZIndex(1); + shapeLayer1->addShape(path); + } + + KisShapeLayerSP shapeLayer2 = new KisShapeLayer(doc->shapeController(), p.image, "shapeLayer2", 255); + + { + KoPathShape* path = new KoPathShape(); + path->setShapeId(KoPathShapeId); + path->moveTo(QPointF(15, 10)); + path->lineTo(QPointF(15, 55)); + path->lineTo(QPointF(50, 55)); + path->lineTo(QPointF(50, 10)); + path->close(); + path->normalize(); + path->setBackground(toQShared(new KoColorBackground(Qt::green))); + + path->setName("shape2"); + path->setZIndex(1); + shapeLayer2->addShape(path); + } + + p.image->addNode(shapeLayer1); + p.image->addNode(shapeLayer2); + shapeLayer1->setDirty(); + shapeLayer2->setDirty(); + + qApp->processEvents(); + p.image->waitForDone(); + + QCOMPARE(int(p.image->root()->childCount()), 3); + + chk.checkImage(p.image, "00_initial_layer_update"); + + if (useImageTransformations) { + + KisFilterStrategy *strategy = new KisBilinearFilterStrategy(); + p.image->scaleImage(QSize(32, 32), p.image->xRes(), p.image->yRes(), strategy); + + qApp->processEvents(); + p.image->waitForDone(); + qApp->processEvents(); + + chk.checkImage(p.image, "01_after_scale_down"); + } + + p.image->mergeDown(shapeLayer2, KisMetaData::MergeStrategyRegistry::instance()->get("Drop")); + qApp->processEvents(); + p.image->waitForDone(); + qApp->processEvents(); + + QCOMPARE(int(p.image->root()->childCount()), 2); + + KisShapeLayer *newShapeLayer = dynamic_cast(p.image->root()->lastChild().data()); + QVERIFY(newShapeLayer); + + QVERIFY(newShapeLayer != shapeLayer1.data()); + QVERIFY(newShapeLayer != shapeLayer2.data()); + + chk.checkImage(p.image, "02_after_merge_down"); +} + +void KisShapeLayerTest::testMergeDown() +{ + testMergeDownImpl(false); +} + +void KisShapeLayerTest::testScaleAndMergeDown() +{ + testMergeDownImpl(true); +} + +namespace { +KoPathShape* createSimpleShape(int zIndex) +{ + KoPathShape* path = new KoPathShape(); + path->setShapeId(KoPathShapeId); + path->moveTo(QPointF(15, 10)); + path->lineTo(QPointF(15, 55)); + path->lineTo(QPointF(50, 55)); + path->lineTo(QPointF(50, 10)); + path->close(); + path->normalize(); + path->setBackground(toQShared(new KoColorBackground(Qt::green))); + + path->setName(QString("shape_%1").arg(zIndex)); + path->setZIndex(zIndex); + + return path; +} +} + +#include "commands/KoShapeReorderCommand.h" +#include + +void testMergingShapeZIndexesImpl(int firstIndexStart, + int firstIndexStep, + int firstIndexSize, + int secondIndexStart, + int secondIndexStep, + int secondIndexSize) +{ + QList shapesBelow; + QList shapesAbove; + + qDebug() << "Test zIndex merge:"; + qDebug() << " " << ppVar(firstIndexStart) << ppVar(firstIndexStep) << ppVar(firstIndexSize); + qDebug() << " " << ppVar(secondIndexStart) << ppVar(secondIndexStep) << ppVar(secondIndexSize); + + + for (int i = 0; i < firstIndexSize; i++) { + shapesBelow.append(createSimpleShape(firstIndexStart + firstIndexStep * i)); + } + + for (int i = 0; i < secondIndexSize; i++) { + shapesAbove.append(createSimpleShape(secondIndexStart + secondIndexStep * i)); + } + + QList shapes = + KoShapeReorderCommand::mergeDownShapes(shapesBelow, shapesAbove); + + KoShapeReorderCommand cmd(shapes); + cmd.redo(); + + for (int i = 0; i < shapesBelow.size(); i++) { + if (i > 0 && shapesBelow[i - 1]->zIndex() >= shapesBelow[i]->zIndex()) { + qDebug() << ppVar(i); + qDebug() << ppVar(shapesBelow[i - 1]->zIndex()) << ppVar(shapesBelow[i]->zIndex()); + QFAIL("Shapes have wrong ordering after merge!"); + } + } + + if (!shapesBelow.isEmpty() && !shapesAbove.isEmpty()) { + if (shapesBelow.last()->zIndex() >= shapesAbove.first()->zIndex()) { + qDebug() << ppVar(shapesBelow.last()->zIndex()) << ppVar(shapesAbove.first()->zIndex()); + QFAIL("Two shape groups have intersections after merge!"); + } + } + + for (int i = 0; i < shapesAbove.size(); i++) { + if (i > 0 && shapesAbove[i - 1]->zIndex() >= shapesAbove[i]->zIndex()) { + qDebug() << ppVar(i); + qDebug() << ppVar(shapesAbove[i - 1]->zIndex()) << ppVar(shapesAbove[i]->zIndex()); + QFAIL("Shapes have wrong ordering after merge!"); + } + } + +} + +void KisShapeLayerTest::testMergingShapeZIndexes() +{ + testMergingShapeZIndexesImpl(0, 1, 10, + 5, 1, 10); + + testMergingShapeZIndexesImpl(0, 1, 0, + 5, 1, 10); + + testMergingShapeZIndexesImpl(0, 1, 10, + 5, 1, 0); + + testMergingShapeZIndexesImpl(std::numeric_limits::max() - 10, 1, 10, + 5, 1, 10); + + testMergingShapeZIndexesImpl(-32768, 1024, 64, + 0, 1024, 31); + + testMergingShapeZIndexesImpl(-32768+1023, 1024, 64, + 0, 1, 1024); +} + +QTEST_MAIN(KisShapeLayerTest) diff --git a/libs/ui/tests/kis_shape_layer_test.h b/libs/ui/tests/kis_shape_layer_test.h new file mode 100644 index 0000000000..b4c6fd2be0 --- /dev/null +++ b/libs/ui/tests/kis_shape_layer_test.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2018 Dmitry Kazakov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef KISSHAPELAYERTEST_H +#define KISSHAPELAYERTEST_H + +#include + +class KisShapeLayerTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + + void testMergeDown(); + void testScaleAndMergeDown(); + + void testMergingShapeZIndexes(); + +}; + +#endif // KISSHAPELAYERTEST_H diff --git a/plugins/impex/libkra/tests/kis_kra_saver_test.cpp b/plugins/impex/libkra/tests/kis_kra_saver_test.cpp index 3fdc7c998e..0ab3645cab 100644 --- a/plugins/impex/libkra/tests/kis_kra_saver_test.cpp +++ b/plugins/impex/libkra/tests/kis_kra_saver_test.cpp @@ -1,549 +1,549 @@ /* * Copyright (c) 2007 Boudewijn Rempt boud@valdyas.org * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_kra_saver_test.h" #include #include #include #include #include #include #include "filter/kis_filter_registry.h" #include "filter/kis_filter_configuration.h" #include "filter/kis_filter.h" #include "KisDocument.h" #include "kis_image.h" #include "kis_pixel_selection.h" #include "kis_group_layer.h" #include "kis_paint_layer.h" #include "kis_clone_layer.h" #include "kis_adjustment_layer.h" #include "kis_shape_layer.h" #include "kis_filter_mask.h" #include "kis_transparency_mask.h" #include "kis_selection_mask.h" #include "kis_selection.h" #include "kis_fill_painter.h" #include "kis_shape_selection.h" #include "util.h" #include "testutil.h" #include "kis_keyframe_channel.h" #include "kis_image_animation_interface.h" #include "kis_layer_properties_icons.h" #include "kis_transform_mask_params_interface.h" #include #include #include void KisKraSaverTest::initTestCase() { KoResourcePaths::addResourceDir("ko_patterns", QString(SYSTEM_RESOURCES_DATA_DIR) + "/patterns"); KisFilterRegistry::instance(); KisGeneratorRegistry::instance(); } void KisKraSaverTest::testCrashyShapeLayer() { /** * KisShapeLayer used to call setImage from its destructor and * therefore causing an infinite recursion (when at least one transparency * mask was preset. This testcase just checks that. */ //QScopedPointer doc(createCompleteDocument(true)); //Q_UNUSED(doc); } void KisKraSaverTest::testRoundTrip() { KisDocument* doc = createCompleteDocument(); KoColor bgColor(Qt::red, doc->image()->colorSpace()); doc->image()->setDefaultProjectionColor(bgColor); doc->exportDocumentSync(QUrl::fromLocalFile("roundtriptest.kra"), doc->mimeType()); QStringList list; KisCountVisitor cv1(list, KoProperties()); doc->image()->rootLayer()->accept(cv1); KisDocument *doc2 = KisPart::instance()->createDocument(); bool result = doc2->loadNativeFormat("roundtriptest.kra"); QVERIFY(result); KisCountVisitor cv2(list, KoProperties()); doc2->image()->rootLayer()->accept(cv2); QCOMPARE(cv1.count(), cv2.count()); // check whether the BG color is saved correctly QCOMPARE(doc2->image()->defaultProjectionColor(), bgColor); // test round trip of a transform mask KisNode* tnode = TestUtil::findNode(doc2->image()->rootLayer(), "testTransformMask").data(); QVERIFY(tnode); KisTransformMask *tmask = dynamic_cast(tnode); QVERIFY(tmask); KisDumbTransformMaskParams *params = dynamic_cast(tmask->transformParams().data()); QVERIFY(params); QTransform t = params->testingGetTransform(); QCOMPARE(t, createTestingTransform()); delete doc2; delete doc; } void KisKraSaverTest::testSaveEmpty() { KisDocument* doc = createEmptyDocument(); doc->exportDocumentSync(QUrl::fromLocalFile("emptytest.kra"), doc->mimeType()); QStringList list; KisCountVisitor cv1(list, KoProperties()); doc->image()->rootLayer()->accept(cv1); KisDocument *doc2 = KisPart::instance()->createDocument(); doc2->loadNativeFormat("emptytest.kra"); KisCountVisitor cv2(list, KoProperties()); doc2->image()->rootLayer()->accept(cv2); QCOMPARE(cv1.count(), cv2.count()); delete doc2; delete doc; } #include #include "generator/kis_generator_registry.h" #include void testRoundTripFillLayerImpl(const QString &testName, KisFilterConfigurationSP config) { - TestUtil::ExternalImageChecker chk(testName, "fill_layer"); + TestUtil::ReferenceImageChecker chk(testName, "fill_layer"); chk.setFuzzy(2); QScopedPointer doc(KisPart::instance()->createDocument()); // mask parent should be destructed before the document! QRect refRect(0,0,512,512); TestUtil::MaskParent p(refRect); doc->setCurrentImage(p.image); doc->documentInfo()->setAboutInfo("title", p.image->objectName()); KisSelectionSP selection; KisGeneratorLayerSP glayer = new KisGeneratorLayer(p.image, "glayer", config, selection); p.image->addNode(glayer, p.image->root(), KisNodeSP()); glayer->setDirty(); p.image->waitForDone(); chk.checkImage(p.image, "00_initial_layer_update"); doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_fill_layer_test.kra"), doc->mimeType()); QScopedPointer doc2(KisPart::instance()->createDocument()); doc2->loadNativeFormat("roundtrip_fill_layer_test.kra"); doc2->image()->waitForDone(); chk.checkImage(doc2->image(), "01_fill_layer_round_trip"); QVERIFY(chk.testPassed()); } void KisKraSaverTest::testRoundTripFillLayerColor() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisGeneratorSP generator = KisGeneratorRegistry::instance()->get("color"); Q_ASSERT(generator); // warning: we pass null paint device to the default constructed value KisFilterConfigurationSP config = generator->factoryConfiguration(); Q_ASSERT(config); QVariant v; v.setValue(KoColor(Qt::red, cs)); config->setProperty("color", v); testRoundTripFillLayerImpl("fill_layer_color", config); } void KisKraSaverTest::testRoundTripFillLayerPattern() { KisGeneratorSP generator = KisGeneratorRegistry::instance()->get("pattern"); QVERIFY(generator); // warning: we pass null paint device to the default constructed value KisFilterConfigurationSP config = generator->factoryConfiguration(); QVERIFY(config); QVariant v; v.setValue(QString("11_drawed_furry.png")); config->setProperty("pattern", v); testRoundTripFillLayerImpl("fill_layer_pattern", config); } #include "kis_psd_layer_style.h" void KisKraSaverTest::testRoundTripLayerStyles() { - TestUtil::ExternalImageChecker chk("kra_saver_test", "layer_styles"); + TestUtil::ReferenceImageChecker chk("kra_saver_test", "layer_styles"); QRect imageRect(0,0,512,512); // the document should be created before the image! QScopedPointer doc(KisPart::instance()->createDocument()); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(new KisSurrogateUndoStore(), imageRect.width(), imageRect.height(), cs, "test image"); KisPaintLayerSP layer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); KisPaintLayerSP layer2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE_U8); KisPaintLayerSP layer3 = new KisPaintLayer(image, "paint3", OPACITY_OPAQUE_U8); image->addNode(layer1); image->addNode(layer2); image->addNode(layer3); doc->setCurrentImage(image); doc->documentInfo()->setAboutInfo("title", image->objectName()); layer1->paintDevice()->fill(QRect(100, 100, 100, 100), KoColor(Qt::red, cs)); layer2->paintDevice()->fill(QRect(200, 200, 100, 100), KoColor(Qt::green, cs)); layer3->paintDevice()->fill(QRect(300, 300, 100, 100), KoColor(Qt::blue, cs)); KisPSDLayerStyleSP style(new KisPSDLayerStyle()); style->dropShadow()->setEffectEnabled(true); style->dropShadow()->setAngle(-90); style->dropShadow()->setUseGlobalLight(false); layer1->setLayerStyle(style->clone()); style->dropShadow()->setAngle(180); style->dropShadow()->setUseGlobalLight(true); layer2->setLayerStyle(style->clone()); style->dropShadow()->setAngle(90); style->dropShadow()->setUseGlobalLight(false); layer3->setLayerStyle(style->clone()); image->initialRefreshGraph(); chk.checkImage(image, "00_initial_layers"); doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_layer_styles.kra"), doc->mimeType()); QScopedPointer doc2(KisPart::instance()->createDocument()); doc2->loadNativeFormat("roundtrip_layer_styles.kra"); doc2->image()->waitForDone(); chk.checkImage(doc2->image(), "00_initial_layers"); QVERIFY(chk.testPassed()); } void KisKraSaverTest::testRoundTripAnimation() { QScopedPointer doc(KisPart::instance()->createDocument()); QRect imageRect(0,0,512,512); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(new KisSurrogateUndoStore(), imageRect.width(), imageRect.height(), cs, "test image"); KisPaintLayerSP layer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); image->addNode(layer1); layer1->paintDevice()->fill(QRect(100, 100, 50, 50), KoColor(Qt::black, cs)); layer1->paintDevice()->setDefaultPixel(KoColor(Qt::red, cs)); KUndo2Command parentCommand; layer1->enableAnimation(); KisKeyframeChannel *rasterChannel = layer1->getKeyframeChannel(KisKeyframeChannel::Content.id(), true); QVERIFY(rasterChannel); rasterChannel->addKeyframe(10, &parentCommand); image->animationInterface()->switchCurrentTimeAsync(10); image->waitForDone(); layer1->paintDevice()->fill(QRect(200, 50, 10, 10), KoColor(Qt::black, cs)); layer1->paintDevice()->moveTo(25, 15); layer1->paintDevice()->setDefaultPixel(KoColor(Qt::green, cs)); rasterChannel->addKeyframe(20, &parentCommand); image->animationInterface()->switchCurrentTimeAsync(20); image->waitForDone(); layer1->paintDevice()->fill(QRect(150, 200, 30, 30), KoColor(Qt::black, cs)); layer1->paintDevice()->moveTo(100, 50); layer1->paintDevice()->setDefaultPixel(KoColor(Qt::blue, cs)); QVERIFY(!layer1->useInTimeline()); layer1->setUseInTimeline(true); doc->setCurrentImage(image); doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_animation.kra"), doc->mimeType()); QScopedPointer doc2(KisPart::instance()->createDocument()); doc2->loadNativeFormat("roundtrip_animation.kra"); KisImageSP image2 = doc2->image(); KisNodeSP node = image2->root()->firstChild(); QVERIFY(node->inherits("KisPaintLayer")); KisPaintLayerSP layer2 = qobject_cast(node.data()); cs = layer2->paintDevice()->colorSpace(); QCOMPARE(image2->animationInterface()->currentTime(), 20); KisKeyframeChannel *channel = layer2->getKeyframeChannel(KisKeyframeChannel::Content.id()); QVERIFY(channel); QCOMPARE(channel->keyframeCount(), 3); image2->animationInterface()->switchCurrentTimeAsync(0); image2->waitForDone(); QCOMPARE(layer2->paintDevice()->nonDefaultPixelArea(), QRect(64, 64, 128, 128)); QCOMPARE(layer2->paintDevice()->x(), 0); QCOMPARE(layer2->paintDevice()->y(), 0); QCOMPARE(layer2->paintDevice()->defaultPixel(), KoColor(Qt::red, cs)); image2->animationInterface()->switchCurrentTimeAsync(10); image2->waitForDone(); QCOMPARE(layer2->paintDevice()->nonDefaultPixelArea(), QRect(217, 15, 64, 64)); QCOMPARE(layer2->paintDevice()->x(), 25); QCOMPARE(layer2->paintDevice()->y(), 15); QCOMPARE(layer2->paintDevice()->defaultPixel(), KoColor(Qt::green, cs)); image2->animationInterface()->switchCurrentTimeAsync(20); image2->waitForDone(); QCOMPARE(layer2->paintDevice()->nonDefaultPixelArea(), QRect(228, 242, 64, 64)); QCOMPARE(layer2->paintDevice()->x(), 100); QCOMPARE(layer2->paintDevice()->y(), 50); QCOMPARE(layer2->paintDevice()->defaultPixel(), KoColor(Qt::blue, cs)); QVERIFY(layer2->useInTimeline()); } #include "lazybrush/kis_lazy_fill_tools.h" void KisKraSaverTest::testRoundTripColorizeMask() { QRect imageRect(0,0,512,512); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); const KoColorSpace * weirdCS = KoColorSpaceRegistry::instance()->rgb16(); QScopedPointer doc(KisPart::instance()->createDocument()); KisImageSP image = new KisImage(new KisSurrogateUndoStore(), imageRect.width(), imageRect.height(), cs, "test image"); doc->setCurrentImage(image); KisPaintLayerSP layer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8, weirdCS); image->addNode(layer1); KisColorizeMaskSP mask = new KisColorizeMask(); image->addNode(mask, layer1); mask->initializeCompositeOp(); delete mask->setColorSpace(layer1->colorSpace()); { KisPaintDeviceSP key1 = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); key1->fill(QRect(50,50,10,20), KoColor(Qt::black, key1->colorSpace())); mask->testingAddKeyStroke(key1, KoColor(Qt::green, layer1->colorSpace())); // KIS_DUMP_DEVICE_2(key1, refRect, "key1", "dd"); } { KisPaintDeviceSP key2 = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); key2->fill(QRect(150,50,10,20), KoColor(Qt::black, key2->colorSpace())); mask->testingAddKeyStroke(key2, KoColor(Qt::red, layer1->colorSpace())); // KIS_DUMP_DEVICE_2(key2, refRect, "key2", "dd"); } { KisPaintDeviceSP key3 = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); key3->fill(QRect(0,0,10,10), KoColor(Qt::black, key3->colorSpace())); mask->testingAddKeyStroke(key3, KoColor(Qt::blue, layer1->colorSpace()), true); // KIS_DUMP_DEVICE_2(key3, refRect, "key3", "dd"); } KisLayerPropertiesIcons::setNodeProperty(mask, KisLayerPropertiesIcons::colorizeEditKeyStrokes, false, image); KisLayerPropertiesIcons::setNodeProperty(mask, KisLayerPropertiesIcons::colorizeShowColoring, false, image); doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_colorize.kra"), doc->mimeType()); QScopedPointer doc2(KisPart::instance()->createDocument()); doc2->loadNativeFormat("roundtrip_colorize.kra"); KisImageSP image2 = doc2->image(); KisNodeSP node = image2->root()->firstChild()->firstChild(); KisColorizeMaskSP mask2 = dynamic_cast(node.data()); QVERIFY(mask2); QCOMPARE(mask2->compositeOpId(), mask->compositeOpId()); QCOMPARE(mask2->colorSpace(), mask->colorSpace()); QCOMPARE(KisLayerPropertiesIcons::nodeProperty(mask, KisLayerPropertiesIcons::colorizeEditKeyStrokes, true).toBool(), false); QCOMPARE(KisLayerPropertiesIcons::nodeProperty(mask, KisLayerPropertiesIcons::colorizeShowColoring, true).toBool(), false); QList strokes = mask->fetchKeyStrokesDirect(); qDebug() << ppVar(strokes.size()); QCOMPARE(strokes[0].dev->exactBounds(), QRect(50,50,10,20)); QCOMPARE(strokes[0].isTransparent, false); QCOMPARE(strokes[0].color.colorSpace(), weirdCS); QCOMPARE(strokes[1].dev->exactBounds(), QRect(150,50,10,20)); QCOMPARE(strokes[1].isTransparent, false); QCOMPARE(strokes[1].color.colorSpace(), weirdCS); QCOMPARE(strokes[2].dev->exactBounds(), QRect(0,0,10,10)); QCOMPARE(strokes[2].isTransparent, true); QCOMPARE(strokes[2].color.colorSpace(), weirdCS); } #include "kis_shape_layer.h" #include #include void KisKraSaverTest::testRoundTripShapeLayer() { - TestUtil::ExternalImageChecker chk("kra_saver_test", "shape_layer"); + TestUtil::ReferenceImageChecker chk("kra_saver_test", "shape_layer"); QRect refRect(0,0,512,512); QScopedPointer doc(KisPart::instance()->createDocument()); TestUtil::MaskParent p(refRect); const qreal resolution = 144.0 / 72.0; p.image->setResolution(resolution, resolution); doc->setCurrentImage(p.image); doc->documentInfo()->setAboutInfo("title", p.image->objectName()); KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); path->moveTo(QPointF(10, 10)); path->lineTo(QPointF( 10, 110)); path->lineTo(QPointF(110, 110)); path->lineTo(QPointF(110, 10)); path->close(); path->normalize(); path->setBackground(toQShared(new KoColorBackground(Qt::red))); path->setName("my_precious_shape"); KisShapeLayerSP shapeLayer = new KisShapeLayer(doc->shapeController(), p.image, "shapeLayer1", 75); shapeLayer->addShape(path); p.image->addNode(shapeLayer); shapeLayer->setDirty(); qApp->processEvents(); p.image->waitForDone(); chk.checkImage(p.image, "00_initial_layer_update"); doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_shapelayer_test.kra"), doc->mimeType()); QScopedPointer doc2(KisPart::instance()->createDocument()); doc2->loadNativeFormat("roundtrip_shapelayer_test.kra"); qApp->processEvents(); doc2->image()->waitForDone(); QCOMPARE(doc2->image()->xRes(), resolution); QCOMPARE(doc2->image()->yRes(), resolution); chk.checkImage(doc2->image(), "01_shape_layer_round_trip"); QVERIFY(chk.testPassed()); } void KisKraSaverTest::testRoundTripShapeSelection() { - TestUtil::ExternalImageChecker chk("kra_saver_test", "shape_selection"); + TestUtil::ReferenceImageChecker chk("kra_saver_test", "shape_selection"); QRect refRect(0,0,512,512); QScopedPointer doc(KisPart::instance()->createDocument()); TestUtil::MaskParent p(refRect); const qreal resolution = 144.0 / 72.0; p.image->setResolution(resolution, resolution); doc->setCurrentImage(p.image); doc->documentInfo()->setAboutInfo("title", p.image->objectName()); p.layer->paintDevice()->setDefaultPixel(KoColor(Qt::green, p.layer->colorSpace())); KisSelectionSP selection = new KisSelection(p.layer->paintDevice()->defaultBounds()); KisShapeSelection *shapeSelection = new KisShapeSelection(p.image, selection); selection->setShapeSelection(shapeSelection); KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); path->moveTo(QPointF(10, 10)); path->lineTo(QPointF( 10, 110)); path->lineTo(QPointF(110, 110)); path->lineTo(QPointF(110, 10)); path->close(); path->normalize(); path->setBackground(toQShared(new KoColorBackground(Qt::red))); path->setName("my_precious_shape"); shapeSelection->addShape(path); KisTransparencyMaskSP tmask = new KisTransparencyMask(); tmask->setSelection(selection); p.image->addNode(tmask, p.layer); tmask->setDirty(p.image->bounds()); qApp->processEvents(); p.image->waitForDone(); chk.checkImage(p.image, "00_initial_shape_selection"); doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_shapeselection_test.kra"), doc->mimeType()); QScopedPointer doc2(KisPart::instance()->createDocument()); doc2->loadNativeFormat("roundtrip_shapeselection_test.kra"); qApp->processEvents(); doc2->image()->waitForDone(); QCOMPARE(doc2->image()->xRes(), resolution); QCOMPARE(doc2->image()->yRes(), resolution); chk.checkImage(doc2->image(), "00_initial_shape_selection"); KisNodeSP node = doc2->image()->root()->firstChild()->firstChild(); KisTransparencyMask *newMask = dynamic_cast(node.data()); QVERIFY(newMask); QVERIFY(newMask->selection()->hasShapeSelection()); QVERIFY(chk.testPassed()); } QTEST_MAIN(KisKraSaverTest) diff --git a/sdk/tests/testutil.h b/sdk/tests/testutil.h index 3970c73949..42df66135e 100644 --- a/sdk/tests/testutil.h +++ b/sdk/tests/testutil.h @@ -1,492 +1,509 @@ /* * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef TEST_UTIL #define TEST_UTIL #include #include #include #include #include #include #include #include #include #include #include #include "kis_node_graph_listener.h" #include "kis_iterator_ng.h" #include "kis_image.h" #include "testing_nodes.h" #ifndef FILES_DATA_DIR #define FILES_DATA_DIR "." #endif #ifndef FILES_DEFAULT_DATA_DIR #define FILES_DEFAULT_DATA_DIR "." #endif #include "qimage_test_util.h" /** * Routines that are useful for writing efficient tests */ namespace TestUtil { inline KisNodeSP findNode(KisNodeSP root, const QString &name) { if(root->name() == name) return root; KisNodeSP child = root->firstChild(); while (child) { if((root = findNode(child, name))) return root; child = child->nextSibling(); } return KisNodeSP(); } inline void dumpNodeStack(KisNodeSP node, QString prefix = QString("\t")) { qDebug() << node->name(); KisNodeSP child = node->firstChild(); while (child) { if (child->childCount() > 0) { dumpNodeStack(child, prefix + "\t"); } else { qDebug() << prefix << child->name(); } child = child->nextSibling(); } } class TestProgressBar : public KoProgressProxy { public: TestProgressBar() : m_min(0), m_max(0), m_value(0) {} int maximum() const override { return m_max; } void setValue(int value) override { m_value = value; } void setRange(int min, int max) override { m_min = min; m_max = max; } void setFormat(const QString &format) override { m_format = format; } void setAutoNestedName(const QString &name) { m_autoNestedName = name; KoProgressProxy::setAutoNestedName(name); } int min() { return m_min; } int max() { return m_max; } int value() { return m_value; } QString format() { return m_format; } QString autoNestedName() { return m_autoNestedName; } private: int m_min; int m_max; int m_value; QString m_format; QString m_autoNestedName; }; inline bool comparePaintDevices(QPoint & pt, const KisPaintDeviceSP dev1, const KisPaintDeviceSP dev2) { // QTime t; // t.start(); QRect rc1 = dev1->exactBounds(); QRect rc2 = dev2->exactBounds(); if (rc1 != rc2) { pt.setX(-1); pt.setY(-1); } KisHLineConstIteratorSP iter1 = dev1->createHLineConstIteratorNG(0, 0, rc1.width()); KisHLineConstIteratorSP iter2 = dev2->createHLineConstIteratorNG(0, 0, rc1.width()); int pixelSize = dev1->pixelSize(); for (int y = 0; y < rc1.height(); ++y) { do { if (memcmp(iter1->oldRawData(), iter2->oldRawData(), pixelSize) != 0) return false; } while (iter1->nextPixel() && iter2->nextPixel()); iter1->nextRow(); iter2->nextRow(); } // qDebug() << "comparePaintDevices time elapsed:" << t.elapsed(); return true; } template inline bool comparePaintDevicesClever(const KisPaintDeviceSP dev1, const KisPaintDeviceSP dev2, channel_type alphaThreshold = 0) { QRect rc1 = dev1->exactBounds(); QRect rc2 = dev2->exactBounds(); if (rc1 != rc2) { qDebug() << "Devices have different size" << ppVar(rc1) << ppVar(rc2); return false; } KisHLineConstIteratorSP iter1 = dev1->createHLineConstIteratorNG(0, 0, rc1.width()); KisHLineConstIteratorSP iter2 = dev2->createHLineConstIteratorNG(0, 0, rc1.width()); int pixelSize = dev1->pixelSize(); for (int y = 0; y < rc1.height(); ++y) { do { if (memcmp(iter1->oldRawData(), iter2->oldRawData(), pixelSize) != 0) { const channel_type* p1 = reinterpret_cast(iter1->oldRawData()); const channel_type* p2 = reinterpret_cast(iter2->oldRawData()); if (p1[3] < alphaThreshold && p2[3] < alphaThreshold) continue; qDebug() << "Failed compare paint devices:" << iter1->x() << iter1->y(); qDebug() << "src:" << p1[0] << p1[1] << p1[2] << p1[3]; qDebug() << "dst:" << p2[0] << p2[1] << p2[2] << p2[3]; return false; } } while (iter1->nextPixel() && iter2->nextPixel()); iter1->nextRow(); iter2->nextRow(); } return true; } #ifdef FILES_OUTPUT_DIR -struct ExternalImageChecker +struct ReferenceImageChecker { - ExternalImageChecker(const QString &prefix, const QString &testName) - : m_prefix(prefix), + enum StorageType { + InternalStorage = 0, + ExternalStorage + }; + + ReferenceImageChecker(const QString &prefix, const QString &testName, StorageType storageType = ExternalStorage) + : m_storageType(storageType), + m_prefix(prefix), m_testName(testName), m_success(true), m_maxFailingPixels(100), m_fuzzy(1) { } void setMaxFailingPixels(int value) { m_maxFailingPixels = value; } void setFuzzy(int fuzzy){ m_fuzzy = fuzzy; } bool testPassed() const { return m_success; } inline bool checkDevice(KisPaintDeviceSP device, KisImageSP image, const QString &caseName) { - bool result = - checkQImageExternal(device->convertToQImage(0, image->bounds()), - m_testName, - m_prefix, - caseName, m_fuzzy, m_fuzzy, m_maxFailingPixels); + bool result = false; + + + if (m_storageType == ExternalStorage) { + result = checkQImageExternal(device->convertToQImage(0, image->bounds()), + m_testName, + m_prefix, + caseName, m_fuzzy, m_fuzzy, m_maxFailingPixels); + } else { + result = checkQImage(device->convertToQImage(0, image->bounds()), + m_testName, + m_prefix, + caseName, m_fuzzy, m_fuzzy, m_maxFailingPixels); + } m_success &= result; return result; } inline bool checkImage(KisImageSP image, const QString &testName) { bool result = checkDevice(image->projection(), image, testName); m_success &= result; return result; } private: + bool m_storageType; + QString m_prefix; QString m_testName; bool m_success; int m_maxFailingPixels; int m_fuzzy; }; #endif inline quint8 alphaDevicePixel(KisPaintDeviceSP dev, qint32 x, qint32 y) { KisHLineConstIteratorSP iter = dev->createHLineConstIteratorNG(x, y, 1); const quint8 *pix = iter->oldRawData(); return *pix; } inline void alphaDeviceSetPixel(KisPaintDeviceSP dev, qint32 x, qint32 y, quint8 s) { KisHLineIteratorSP iter = dev->createHLineIteratorNG(x, y, 1); quint8 *pix = iter->rawData(); *pix = s; } inline bool checkAlphaDeviceFilledWithPixel(KisPaintDeviceSP dev, const QRect &rc, quint8 expected) { KisHLineIteratorSP it = dev->createHLineIteratorNG(rc.x(), rc.y(), rc.width()); for (int y = rc.y(); y < rc.y() + rc.height(); y++) { for (int x = rc.x(); x < rc.x() + rc.width(); x++) { if(*((quint8*)it->rawData()) != expected) { errKrita << "At point:" << x << y; errKrita << "Expected pixel:" << expected; errKrita << "Actual pixel: " << *((quint8*)it->rawData()); return false; } it->nextPixel(); } it->nextRow(); } return true; } class TestNode : public DefaultNode { Q_OBJECT public: KisNodeSP clone() const override { return KisNodeSP(new TestNode(*this)); } }; class TestGraphListener : public KisNodeGraphListener { public: void aboutToAddANode(KisNode *parent, int index) override { KisNodeGraphListener::aboutToAddANode(parent, index); beforeInsertRow = true; } void nodeHasBeenAdded(KisNode *parent, int index) override { KisNodeGraphListener::nodeHasBeenAdded(parent, index); afterInsertRow = true; } void aboutToRemoveANode(KisNode *parent, int index) override { KisNodeGraphListener::aboutToRemoveANode(parent, index); beforeRemoveRow = true; } void nodeHasBeenRemoved(KisNode *parent, int index) override { KisNodeGraphListener::nodeHasBeenRemoved(parent, index); afterRemoveRow = true; } void aboutToMoveNode(KisNode *parent, int oldIndex, int newIndex) override { KisNodeGraphListener::aboutToMoveNode(parent, oldIndex, newIndex); beforeMove = true; } void nodeHasBeenMoved(KisNode *parent, int oldIndex, int newIndex) override { KisNodeGraphListener::nodeHasBeenMoved(parent, oldIndex, newIndex); afterMove = true; } bool beforeInsertRow; bool afterInsertRow; bool beforeRemoveRow; bool afterRemoveRow; bool beforeMove; bool afterMove; void resetBools() { beforeRemoveRow = false; afterRemoveRow = false; beforeInsertRow = false; afterInsertRow = false; beforeMove = false; afterMove = false; } }; } #include #include #include "kis_undo_stores.h" namespace TestUtil { struct MaskParent { MaskParent(const QRect &_imageRect = QRect(0,0,512,512)) : imageRect(_imageRect) { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); undoStore = new KisSurrogateUndoStore(); image = new KisImage(undoStore, imageRect.width(), imageRect.height(), cs, "test image"); layer = KisPaintLayerSP(new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8)); image->addNode(KisNodeSP(layer.data())); } KisSurrogateUndoStore *undoStore; const QRect imageRect; KisImageSP image; KisPaintLayerSP layer; }; } namespace TestUtil { class MeasureAvgPortion { public: MeasureAvgPortion(int period) : m_period(period), m_val(0), m_total(0), m_cycles(0) { } ~MeasureAvgPortion() { printValues(true); } void addVal(int x) { m_val += x; } void addTotal(int x) { m_total += x; m_cycles++; printValues(); } private: void printValues(bool force = false) { if (m_cycles > m_period || force) { qDebug() << "Val / Total:" << qreal(m_val) / qreal(m_total); qDebug() << "Avg. Val: " << qreal(m_val) / m_cycles; qDebug() << "Avg. Total: " << qreal(m_total) / m_cycles; qDebug() << ppVar(m_val) << ppVar(m_total) << ppVar(m_cycles); m_val = 0; m_total = 0; m_cycles = 0; } } private: int m_period; qint64 m_val; qint64 m_total; qint64 m_cycles; }; struct MeasureDistributionStats { MeasureDistributionStats(int numBins, const QString &name = QString()) : m_numBins(numBins), m_name(name) { reset(); } void reset() { m_values.clear(); m_values.resize(m_numBins); } void addValue(int value) { addValue(value, 1); } void addValue(int value, int increment) { KIS_SAFE_ASSERT_RECOVER_RETURN(value >= 0); if (value >= m_numBins) { m_values[m_numBins - 1] += increment; } else { m_values[value] += increment; } } void print() { qCritical() << "============= Stats =============="; if (!m_name.isEmpty()) { qCritical() << "Name:" << m_name; } int total = 0; for (int i = 0; i < m_numBins; i++) { total += m_values[i]; } for (int i = 0; i < m_numBins; i++) { if (!m_values[i]) continue; const QString lastMarker = i == m_numBins - 1 ? "> " : " "; const QString line = QString(" %1%2: %3 (%4%)") .arg(lastMarker) .arg(i, 3) .arg(m_values[i], 5) .arg(qreal(m_values[i]) / total * 100.0, 7, 'g', 2); qCritical() << qPrintable(line); } qCritical() << "---- ----"; qCritical() << qPrintable(QString("Total: %1").arg(total)); qCritical() << "=================================="; } private: QVector m_values; int m_numBins = 0; QString m_name; }; QStringList getHierarchy(KisNodeSP root, const QString &prefix = ""); bool checkHierarchy(KisNodeSP root, const QStringList &expected); } #endif