[calligra/calligra/2.9] krita: [FEATURE] Multiple layers merge with layer styles on Ctrl+E

Dmitry Kazakov dimula73 at gmail.com
Tue Jul 14 13:08:19 UTC 2015


Git commit 449384f72996385287008b01367c618dbf4395f5 by Dmitry Kazakov.
Committed on 14/07/2015 at 13:08.
Pushed by dkazakov into branch 'calligra/2.9'.

[FEATURE] Multiple layers merge with layer styles on Ctrl+E

This patch implements several features:

1) "Merge selected layers" is now deprecated and you can use usual Ctrl+E
   to merge multiple selection
2) Mass-merging of layers with layer styles works correctly now
3) Merging of clone layers together with their sources will not
   break Krita now.


Fixes T456
CC:kimageshop at kde.org

M  +129  -0    krita/image/kis_image.cc
M  +11   -0    krita/image/kis_image.h
M  +25   -0    krita/image/tests/kis_image_test.cpp
M  +2    -0    krita/image/tests/kis_image_test.h
M  +1    -1    krita/plugins/extensions/dockers/defaultdockers/kis_layer_box.cpp
M  +15   -10   krita/ui/kis_layer_manager.cc
M  +2    -93   krita/ui/kis_node_manager.cpp
M  +1    -1    krita/ui/kis_node_manager.h

http://commits.kde.org/calligra/449384f72996385287008b01367c618dbf4395f5

diff --git a/krita/image/kis_image.cc b/krita/image/kis_image.cc
index c99f2f9..612f926 100644
--- a/krita/image/kis_image.cc
+++ b/krita/image/kis_image.cc
@@ -65,6 +65,8 @@
 #include "kis_update_scheduler.h"
 #include "kis_image_signal_router.h"
 #include "kis_stroke_strategy.h"
+#include "kis_image_barrier_locker.h"
+
 
 #include "kis_undo_stores.h"
 #include "kis_legacy_undo_adapter.h"
@@ -948,6 +950,133 @@ void KisImage::flatten()
     setModified();
 }
 
+bool checkIsSourceForClone(KisNodeSP src, const QList<KisNodeSP> &nodes)
+{
+    foreach (KisNodeSP node, nodes) {
+        if (node == src) continue;
+
+        KisCloneLayer *clone = dynamic_cast<KisCloneLayer*>(node.data());
+
+        if (clone && KisNodeSP(clone->copyFrom()) == src) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+void KisImage::safeRemoveMultipleNodes(QList<KisNodeSP> nodes)
+{
+    while (!nodes.isEmpty()) {
+        QList<KisNodeSP>::iterator it = nodes.begin();
+
+        while (it != nodes.end()) {
+            if (!checkIsSourceForClone(*it, nodes)) {
+                KisNodeSP node = *it;
+                undoAdapter()->addCommand(new KisImageLayerRemoveCommand(this, node));
+                it = nodes.erase(it);
+            } else {
+                ++it;
+            }
+        }
+
+    }
+}
+
+bool checkIsChildOf(KisNodeSP node, const QList<KisNodeSP> &parents)
+{
+    QList<KisNodeSP> nodeParents;
+
+    KisNodeSP parent = node->parent();
+    while (parent) {
+        nodeParents << parent;
+        parent = parent->parent();
+    }
+
+    foreach(KisNodeSP perspectiveParent, parents) {
+        if (nodeParents.contains(perspectiveParent)) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+void filterMergableNodes(QList<KisNodeSP> &nodes)
+{
+    QList<KisNodeSP>::iterator it = nodes.begin();
+
+    while (it != nodes.end()) {
+        if (!dynamic_cast<KisLayer*>(it->data()) ||
+            checkIsChildOf(*it, nodes)) {
+
+            qDebug() << "Skipping node" << ppVar((*it)->name());
+            it = nodes.erase(it);
+        } else {
+            ++it;
+        }
+    }
+}
+
+KisNodeSP KisImage::mergeMultipleLayers(QList<KisNodeSP> mergedNodes, KisNodeSP putAfter)
+{
+    filterMergableNodes(mergedNodes);
+
+    if (mergedNodes.size() <= 1) return KisNodeSP();
+
+    foreach (KisNodeSP layer, mergedNodes) {
+        refreshHiddenArea(layer, bounds());
+    }
+
+    KisPaintDeviceSP mergedDevice = new KisPaintDevice(colorSpace());
+    KisPainter gc(mergedDevice);
+
+    {
+        KisImageBarrierLocker l(this);
+
+        foreach (KisNodeSP layer, mergedNodes) {
+            QRect rc = layer->exactBounds() | bounds();
+            layer->projectionPlane()->apply(&gc, rc);
+        }
+    }
+
+    const QString mergedLayerSuffix = i18n("Merged");
+    QString mergedLayerName = mergedNodes.first()->name();
+
+    if (!mergedLayerName.endsWith(mergedLayerSuffix)) {
+        mergedLayerName = QString("%1 %2")
+            .arg(mergedLayerName).arg(mergedLayerSuffix);
+    }
+
+    KisNodeSP newLayer = new KisPaintLayer(this, mergedLayerName, OPACITY_OPAQUE_U8, mergedDevice);
+
+
+    undoAdapter()->beginMacro(kundo2_i18n("Merge Selected Nodes"));
+
+    if (!putAfter) {
+        putAfter = mergedNodes.last();
+    }
+
+    // Add the new merged node on top of the active node -- checking
+    // whether the parent is going to be deleted
+    KisNodeSP parent = putAfter->parent();
+    while (mergedNodes.contains(parent)) {
+        parent = parent->parent();
+    }
+
+    if (parent == putAfter->parent()) {
+        undoAdapter()->addCommand(new KisImageLayerAddCommand(this, newLayer, parent, putAfter));
+    } else {
+        undoAdapter()->addCommand(new KisImageLayerAddCommand(this, newLayer, parent, parent->lastChild()));
+    }
+
+    safeRemoveMultipleNodes(mergedNodes);
+
+    undoAdapter()->endMacro();
+
+    return newLayer;
+}
+
 KisLayerSP KisImage::mergeDown(KisLayerSP layer, const KisMetaData::MergeStrategy* strategy)
 {
     if (!layer->prevSibling()) return 0;
diff --git a/krita/image/kis_image.h b/krita/image/kis_image.h
index 8dedd03..d35aa7d 100644
--- a/krita/image/kis_image.h
+++ b/krita/image/kis_image.h
@@ -408,6 +408,17 @@ public:
      */
     KisLayerSP flattenLayer(KisLayerSP layer);
 
+    /**
+     * Removes \p nodes in a safe way, that is handling clone layers
+     * reincarnation correctly
+     */
+    void safeRemoveMultipleNodes(QList<KisNodeSP> nodes);
+
+    /**
+     * Merges layers in \p mergedLayers and creates a new layer above
+     * \p putAfter
+     */
+    KisNodeSP mergeMultipleLayers(QList<KisNodeSP> mergedLayers, KisNodeSP putAfter);
 
     /// This overrides interface for KisDefaultBounds
     /// @return the exact bounds of the image in pixel coordinates.
diff --git a/krita/image/tests/kis_image_test.cpp b/krita/image/tests/kis_image_test.cpp
index 7ea3d40..8ed12e9 100644
--- a/krita/image/tests/kis_image_test.cpp
+++ b/krita/image/tests/kis_image_test.cpp
@@ -499,5 +499,30 @@ void KisImageTest::testMergeDownDestinationSameCompositeOp()
     }
 }
 
+void KisImageTest::testMergeMultiple()
+{
+    FlattenTestImage p;
+
+    TestUtil::ExternalImageChecker img("flatten", "imagetest");
+    TestUtil::ExternalImageChecker chk("mergemultiple", "imagetest");
+
+    QList<KisNodeSP> selectedNodes;
+
+    selectedNodes << p.layer2
+                  << p.group1
+                  << p.layer6;
+
+    {
+        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));
+    }
+}
+
 QTEST_KDEMAIN(KisImageTest, NoGUI)
 #include "kis_image_test.moc"
diff --git a/krita/image/tests/kis_image_test.h b/krita/image/tests/kis_image_test.h
index 9238b02..66fcfef 100644
--- a/krita/image/tests/kis_image_test.h
+++ b/krita/image/tests/kis_image_test.h
@@ -39,6 +39,8 @@ private Q_SLOTS:
     void testMergeDownDestinationCustomCompositeOp();
     void testMergeDownDestinationSameCompositeOpLayerStyle();
     void testMergeDownDestinationSameCompositeOp();
+
+    void testMergeMultiple();
 };
 
 #endif
diff --git a/krita/plugins/extensions/dockers/defaultdockers/kis_layer_box.cpp b/krita/plugins/extensions/dockers/defaultdockers/kis_layer_box.cpp
index f3049f4..e5c430c 100644
--- a/krita/plugins/extensions/dockers/defaultdockers/kis_layer_box.cpp
+++ b/krita/plugins/extensions/dockers/defaultdockers/kis_layer_box.cpp
@@ -560,7 +560,7 @@ void KisLayerBox::slotContextMenuRequested(const QPoint &pos, const QModelIndex
 void KisLayerBox::slotMergeLayer()
 {
     if (!m_canvas) return;
-    m_nodeManager->mergeLayerDown();
+    m_nodeManager->mergeLayer();
 }
 
 void KisLayerBox::slotMinimalView()
diff --git a/krita/ui/kis_layer_manager.cc b/krita/ui/kis_layer_manager.cc
index d55bc20..02626ba 100644
--- a/krita/ui/kis_layer_manager.cc
+++ b/krita/ui/kis_layer_manager.cc
@@ -768,19 +768,24 @@ void KisLayerManager::mergeLayer()
     KisLayerSP layer = activeLayer();
     if (!layer) return;
 
-    if (!layer->prevSibling()) return;
-    KisLayer *prevLayer = dynamic_cast<KisLayer*>(layer->prevSibling().data());
-    if (!prevLayer) return;
+    QList<KisNodeSP> selectedNodes = m_view->nodeManager()->selectedNodes();
+    if (selectedNodes.size() > 1) {
+        image->mergeMultipleLayers(selectedNodes, layer);
+    } else {
+        if (!layer->prevSibling()) return;
+        KisLayer *prevLayer = dynamic_cast<KisLayer*>(layer->prevSibling().data());
+        if (!prevLayer) return;
 
-    if (layer->metaData()->isEmpty() && prevLayer->metaData()->isEmpty()) {
-        image->mergeDown(layer, KisMetaData::MergeStrategyRegistry::instance()->get("Drop"));
+        if (layer->metaData()->isEmpty() && prevLayer->metaData()->isEmpty()) {
+            image->mergeDown(layer, KisMetaData::MergeStrategyRegistry::instance()->get("Drop"));
+        }
+        else {
+            const KisMetaData::MergeStrategy* strategy = KisMetaDataMergeStrategyChooserWidget::showDialog(m_view->mainWindow());
+            if (!strategy) return;
+            image->mergeDown(layer, strategy);
+        }
     }
-    else {
-        const KisMetaData::MergeStrategy* strategy = KisMetaDataMergeStrategyChooserWidget::showDialog(m_view->mainWindow());
-        if (!strategy) return;
-        image->mergeDown(layer, strategy);
 
-    }
     m_view->updateGUI();
 }
 
diff --git a/krita/ui/kis_node_manager.cpp b/krita/ui/kis_node_manager.cpp
index 014ec97..d967c1f 100644
--- a/krita/ui/kis_node_manager.cpp
+++ b/krita/ui/kis_node_manager.cpp
@@ -93,7 +93,6 @@ struct KisNodeManager::Private {
     KisMaskManager * maskManager;
     KisNodeManager* self;
     KisNodeCommandsAdapter* commandsAdapter;
-    KisAction *mergeSelectedLayers;
 
     QList<KisNodeSP> selectedNodes;
 
@@ -171,13 +170,6 @@ KisNodeManager::KisNodeManager(KisViewManager *view)
     m_d->commandsAdapter = new KisNodeCommandsAdapter(view);
 
     connect(m_d->layerManager, SIGNAL(sigLayerActivated(KisLayerSP)), SIGNAL(sigLayerActivated(KisLayerSP)));
-
-    m_d->mergeSelectedLayers = new KisAction(i18n("&Merge Selected Layers"), this);
-    m_d->mergeSelectedLayers->setActivationFlags(KisAction::ACTIVE_LAYER);
-    view->actionManager()->addAction("merge_selected_layers", m_d->mergeSelectedLayers);
-    m_d->mergeSelectedLayers->setShortcut(QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_E));
-    connect(m_d->mergeSelectedLayers, SIGNAL(triggered()), this, SLOT(mergeLayerDown()));
-
 }
 
 KisNodeManager::~KisNodeManager()
@@ -907,92 +899,9 @@ void KisNodeManager::activatePreviousNode()
     }
 }
 
-QList<KisNodeSP> hideLayers(KisNodeSP root, QList<KisNodeSP> selectedNodes)
+void KisNodeManager::mergeLayer()
 {
-    QList<KisNodeSP> alreadyHidden;
-
-    foreach(KisNodeSP node, root->childNodes(QStringList(), KoProperties())) {
-        if (!selectedNodes.contains(node)) {
-            if (node->visible()) {
-                node->setVisible(false);
-            }
-            else {
-                alreadyHidden << node;
-            }
-        }
-        if (node->childCount() > 0) {
-            hideLayers(node, selectedNodes);
-        }
-    }
-
-    return alreadyHidden;
-}
-
-void showNodes(KisNodeSP root, QList<KisNodeSP> keepHiddenNodes)
-{
-    foreach(KisNodeSP node, root->childNodes(QStringList(), KoProperties())) {
-        if (!keepHiddenNodes.contains(node)) {
-            node->setVisible(true);
-        }
-        if (node->childCount() > 0) {
-            showNodes(node, keepHiddenNodes);
-        }
-    }
-}
-
-void KisNodeManager::mergeLayerDown()
-{
-    if (m_d->selectedNodes.size() > 1) {
-
-        QList<KisNodeSP> selectedNodes = m_d->selectedNodes;
-        KisLayerSP l = activeLayer();
-
-        // hide every layer that's not in the list of selected nodes
-        QList<KisNodeSP> alreadyHiddenNodes = hideLayers(m_d->imageView->image()->root(), selectedNodes);
-
-        // render and copy the projection
-        m_d->imageView->image()->refreshGraph();
-        m_d->imageView->image()->waitForDone();
-
-        // Copy the projections
-        KisPaintDeviceSP dev = new KisPaintDevice(*m_d->imageView->image()->projection().data());
-        // place the projection in a layer
-        KisPaintLayerSP flattenLayer = new KisPaintLayer(m_d->imageView->image(), i18n("Merged"), OPACITY_OPAQUE_U8, dev);
-
-        // start a big macro
-        m_d->commandsAdapter->beginMacro(kundo2_i18n("Merge Selected Nodes"));
-
-        // Add the new merged node on top of the active node -- checking whether the parent is in the selection
-        KisNodeSP parent = l->parent();
-        while (selectedNodes.contains(parent)) {
-            parent = parent->parent();
-        }
-        if (parent == l->parent()) {
-            m_d->commandsAdapter->addNode(flattenLayer, parent, l);
-        }
-        else {
-            m_d->commandsAdapter->addNode(flattenLayer, parent, parent->lastChild());
-        }
-
-        // remove all nodes in the selection but the active node
-        removeSelectedNodes(selectedNodes);
-
-        m_d->commandsAdapter->endMacro();
-
-        // And unhide
-        showNodes(m_d->imageView->image()->root(), alreadyHiddenNodes);
-
-        m_d->imageView->image()->refreshGraph();
-        m_d->imageView->image()->waitForDone();
-        m_d->imageView->image()->notifyLayersChanged();
-        m_d->imageView->image()->setModified();
-
-        slotNonUiActivatedNode(flattenLayer);
-
-    }
-    else {
-        m_d->layerManager->mergeLayer();
-    }
+    m_d->layerManager->mergeLayer();
 }
 
 void KisNodeManager::rotate(double radians)
diff --git a/krita/ui/kis_node_manager.h b/krita/ui/kis_node_manager.h
index 196223c..cf34566 100644
--- a/krita/ui/kis_node_manager.h
+++ b/krita/ui/kis_node_manager.h
@@ -195,7 +195,7 @@ public Q_SLOTS:
     void saveNodeAsImage();
 
     // merges the active layer with the layer below it.
-    void mergeLayerDown();
+    void mergeLayer();
 
     void slotSplitAlphaIntoMask();
     void slotSplitAlphaWrite();


More information about the kimageshop mailing list