[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