[graphics/krita] plugins/extensions/qmic: Refactor G'MIC glue code

L. E. Segovia null at kde.org
Tue Apr 26 17:19:10 BST 2022


Git commit 247e82508d750c7499f1e2c941cd177cd2829020 by L. E. Segovia.
Committed on 26/04/2022 at 15:46.
Pushed by lsegovia into branch 'master'.

Refactor G'MIC glue code

This commit refactors the complete G'MIC image import process, making
all steps into Krita commands. This allows undoing and redoing G'MIC
filters, which was previously not possible for two reasons:

- layer properties application was done in-situ (thus the undo system
  was not aware of them, so undo was incomplete for reused layers)
- layer insertion was not only done in-situ, but it also altered the
  internal state of the command (thus making redoing impossible)

This commit performs the following:

- Removal of all unused (pre Krita 5) code.
- Reimplementation of the layer synchronization command into a composite
  command.
- Reimplementation of the layer metadata processing and image composite
  functions from the applicator into separate, fully undoable commands.
- Switch of the processing visitor's layer metadata processing to a
  separate, undoable command (see above).
- Fix leaking the G'MIC image data on KisQMicImage destruction.
- Simplification of the KisImageInterface application step by
  de-signal-slotting it.

BUG: 452155

CCBUG: 452831

CCMAIL: kimageshop at kde.org

M  +1    -4    plugins/extensions/qmic/CMakeLists.txt
M  +11   -13   plugins/extensions/qmic/QMic.cpp
M  +3    -16   plugins/extensions/qmic/QMic.h
M  +19   -11   plugins/extensions/qmic/gmic.h
D  +0    -135  plugins/extensions/qmic/kis_import_qmic_processing_visitor.cpp
D  +0    -56   plugins/extensions/qmic/kis_import_qmic_processing_visitor.h
D  +0    -114  plugins/extensions/qmic/kis_qmic_applicator.cpp
D  +0    -52   plugins/extensions/qmic/kis_qmic_applicator.h
D  +0    -26   plugins/extensions/qmic/kis_qmic_data.cpp
D  +0    -37   plugins/extensions/qmic/kis_qmic_data.h
A  +103  -0    plugins/extensions/qmic/kis_qmic_import_tools.h     [License: GPL(v2.0+)]
M  +86   -62   plugins/extensions/qmic/kis_qmic_interface.cpp
M  +0    -5    plugins/extensions/qmic/kis_qmic_interface.h
A  +87   -0    plugins/extensions/qmic/kis_qmic_processing_visitor.cpp     [License: GPL(v2.0+)]
A  +37   -0    plugins/extensions/qmic/kis_qmic_processing_visitor.h     [License: GPL(v2.0+)]
D  +0    -75   plugins/extensions/qmic/kis_qmic_progress_manager.cpp
D  +0    -42   plugins/extensions/qmic/kis_qmic_progress_manager.h
M  +2    -2    plugins/extensions/qmic/kis_qmic_simple_convertor.cpp
M  +2    -2    plugins/extensions/qmic/kis_qmic_simple_convertor.h
M  +106  -72   plugins/extensions/qmic/kis_qmic_synchronize_layers_command.cpp
M  +12   -19   plugins/extensions/qmic/kis_qmic_synchronize_layers_command.h

https://invent.kde.org/graphics/krita/commit/247e82508d750c7499f1e2c941cd177cd2829020

diff --git a/plugins/extensions/qmic/CMakeLists.txt b/plugins/extensions/qmic/CMakeLists.txt
index 63f0e6798e..abf9c92e75 100644
--- a/plugins/extensions/qmic/CMakeLists.txt
+++ b/plugins/extensions/qmic/CMakeLists.txt
@@ -1,12 +1,9 @@
 # GMic bindings exposure.
 set(kritaqmicinterface_SOURCES
-    kis_import_qmic_processing_visitor.cpp
     kis_input_output_mapper.cpp
-    kis_qmic_applicator.cpp
-    kis_qmic_data.cpp
     kis_qmic_interface.cpp
+    kis_qmic_processing_visitor.cpp
     kis_qmic_plugin_interface.cpp
-    kis_qmic_progress_manager.cpp
     kis_qmic_simple_convertor.cpp
     kis_qmic_synchronize_image_size_command.cpp
     kis_qmic_synchronize_layers_command.cpp
diff --git a/plugins/extensions/qmic/QMic.cpp b/plugins/extensions/qmic/QMic.cpp
index e205c7788c..c9193eb936 100644
--- a/plugins/extensions/qmic/QMic.cpp
+++ b/plugins/extensions/qmic/QMic.cpp
@@ -1,36 +1,35 @@
 /*
  * SPDX-FileCopyrightText: 2017 Boudewijn Rempt <boud at valdyas.org>
+ * SPDX-FileCopyrightText: 2020 L. E. Segovia <amy at amyspark.me>
  *
  *  SPDX-License-Identifier: GPL-2.0-or-later
  */
 
-#include <KoJsonTrader.h>
+#include "QMic.h"
+
 #include <QApplication>
-#include <QDebug>
-#include <QFileInfo>
-#include <QList>
 #include <QMessageBox>
-#include <QUuid>
+#include <memory>
+
+#include <KisViewManager.h>
+#include <KoJsonTrader.h>
 #include <kis_action.h>
-#include <kis_config.h>
+#include <kis_debug.h>
 #include <klocalizedstring.h>
 #include <kpluginfactory.h>
 
-#include "QMic.h"
-#include "kis_qmic_interface.h"
-#include "KisViewManager.h"
+#include "kis_qmic_plugin_interface.h"
 
 K_PLUGIN_FACTORY_WITH_JSON(QMicFactory, "kritaqmic.json", registerPlugin<QMic>();)
 
 QMic::QMic(QObject *parent, const QVariantList &)
     : KisActionPlugin(parent)
+    , m_qmicAction(createAction("QMic"))
+    , m_againAction(createAction("QMicAgain"))
 {
-    m_qmicAction = createAction("QMic");
     m_qmicAction->setActivationFlags(KisAction::ACTIVE_DEVICE);
-
     connect(m_qmicAction, SIGNAL(triggered()), this, SLOT(slotQMic()));
 
-    m_againAction = createAction("QMicAgain");
     m_againAction->setActivationFlags(KisAction::ACTIVE_DEVICE);
     m_againAction->setEnabled(false);
     connect(m_againAction, SIGNAL(triggered()), this, SLOT(slotQMicAgain()));
@@ -83,7 +82,6 @@ void QMic::slotQMic(bool again)
 
     qDeleteAll(offers);
 
-    m_key = QUuid::createUuid().toString();
     auto image = std::make_shared<KisImageInterface>(this->viewManager().data());
     int status = plugin->launch(image, again);
 
diff --git a/plugins/extensions/qmic/QMic.h b/plugins/extensions/qmic/QMic.h
index 84acf0acc5..7bcfa4243c 100644
--- a/plugins/extensions/qmic/QMic.h
+++ b/plugins/extensions/qmic/QMic.h
@@ -1,5 +1,6 @@
 /*
  * SPDX-FileCopyrightText: 2017 Boudewijn Rempt <boud at valdyas.org>
+ * SPDX-FileCopyrightText: 2022 L. E. Segovia <amy at amyspark.me>
  *
  * SPDX-License-Identifier: GPL-2.0-or-later
  */
@@ -8,20 +9,8 @@
 #define QMIC_H
 
 #include <KisActionPlugin.h>
-#include <QPointer>
-#include <QVariant>
-#include <QVector>
-#include <kis_types.h>
-
-#include "gmic.h"
-#include "kis_qmic_interface.h"
-#include "kis_qmic_plugin_interface.h"
 
 class KisAction;
-class QLocalServer;
-class QSharedMemory;
-
-class KisQmicApplicator;
 
 class QMic : public KisActionPlugin
 {
@@ -31,14 +20,12 @@ public:
     ~QMic() override = default;
 
 private Q_SLOTS:
-
     void slotQMicAgain();
     void slotQMic(bool again = false);
 
 private:
-    QString m_key;
-    KisAction *m_qmicAction {0};
-    KisAction *m_againAction {0};
+    KisAction *m_qmicAction;
+    KisAction *m_againAction;
 };
 
 #endif // QMic_H
diff --git a/plugins/extensions/qmic/gmic.h b/plugins/extensions/qmic/gmic.h
index 795af50112..f8545bf29d 100644
--- a/plugins/extensions/qmic/gmic.h
+++ b/plugins/extensions/qmic/gmic.h
@@ -32,21 +32,29 @@ enum class InputLayerMode {
     Unspecified = 100
 };
 
-template<typename T> struct gmic_image {
-    unsigned int _width;       // Number of image columns (dimension along the X-axis).
-    unsigned int _height;      // Number of image lines (dimension along the Y-axis)
-    unsigned int _depth;       // Number of image slices (dimension along the Z-axis).
-    unsigned int _spectrum;    // Number of image channels (dimension along the C-axis).
-    bool _is_shared;           // Tells if the data buffer is shared by another structure.
-    T *_data;                  // Pointer to the first pixel value.
-    QString name;              // Layer name
-
-    void assign(unsigned int w, unsigned int h, unsigned int d, unsigned int s) {
+template<typename T>
+struct gmic_image {
+    unsigned int _width{0}; // Number of image columns (dimension along the X-axis).
+    unsigned int _height{0}; // Number of image lines (dimension along the Y-axis)
+    unsigned int _depth{1}; // Number of image slices (dimension along the Z-axis).
+    unsigned int _spectrum{3}; // Number of image channels (dimension along the C-axis).
+    bool _is_shared{false}; // Tells if the data buffer is shared by another structure.
+    T *_data{nullptr}; // Pointer to the first pixel value.
+    QString name{}; // Layer name
+
+    // Destructor.
+    ~gmic_image()
+    {
+        if (!_is_shared)
+            delete[] _data;
+    }
+
+    void assign(unsigned int w, unsigned int h, unsigned int d, unsigned int s)
+    {
         _width = w;
         _height = h;
         _depth = d;
         _spectrum = s;
-
     }
 };
 
diff --git a/plugins/extensions/qmic/kis_import_qmic_processing_visitor.cpp b/plugins/extensions/qmic/kis_import_qmic_processing_visitor.cpp
deleted file mode 100644
index b323e787fd..0000000000
--- a/plugins/extensions/qmic/kis_import_qmic_processing_visitor.cpp
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- *  SPDX-FileCopyrightText: 2013 Dmitry Kazakov <dimula73 at gmail.com>
- *  SPDX-FileCopyrightText: 2013 Lukáš Tvrdý <lukast.dev at gmail.com>
- *  SPDX-FileCopyrightText: 2020 L. E. Segovia <amy at amyspark.me>
- *
- *  SPDX-License-Identifier: GPL-2.0-or-later
- */
-
-
-
-#include <kis_transaction.h>
-#include <kis_paint_device.h>
-#include <kis_debug.h>
-
-#include "kis_qmic_simple_convertor.h"
-#include <kis_node.h>
-#include <kis_painter.h>
-#include <commands/kis_image_layer_add_command.h>
-#include <kis_paint_layer.h>
-#include <KoCompositeOpRegistry.h>
-#include <kis_selection.h>
-#include <kis_types.h>
-#include <QtCore/QRegularExpression>
-
-#include "kis_import_qmic_processing_visitor.h"
-#include "gmic.h"
-
-KisImportQmicProcessingVisitor::KisImportQmicProcessingVisitor(const KisNodeListSP nodes, QVector<gmic_image<float> *> images, const QRect &dstRect, KisSelectionSP selection)
-    : m_nodes(nodes),
-      m_images(images),
-      m_dstRect(dstRect),
-      m_selection(selection)
-{
-    dbgPlugins << "KisImportQmicProcessingVisitor";
-}
-
-
-void KisImportQmicProcessingVisitor::gmicImageToPaintDevice(gmic_image<float>& srcGmicImage,
-                                                            KisPaintDeviceSP dst, KisSelectionSP selection, const QRect &dstRect)
-{
-
-    dbgPlugins << "KisImportQmicProcessingVisitor::gmicImageToPaintDevice();";
-    if (selection) {
-        KisPaintDeviceSP src = new KisPaintDevice(dst->colorSpace());
-        KisQmicSimpleConvertor::convertFromGmicFast(srcGmicImage, src, 255.0f);
-
-        KisPainter painter(dst, selection);
-        painter.setCompositeOp(COMPOSITE_COPY);
-        painter.bitBlt(dstRect.topLeft(), src, QRect(QPoint(0,0),dstRect.size()));
-    }
-    else {
-        KisQmicSimpleConvertor::convertFromGmicFast(srcGmicImage, dst, 255.0f);
-    }
-}
-
-void KisImportQmicProcessingVisitor::applyLayerNameChanges(const gmic_image<float> &srcGmicImage, KisNode *node, KisPaintDeviceSP dst)
-{
-    dbgPlugins << "Layer name: " << srcGmicImage.name;
-
-    {
-        const QRegExp modeRe("mode\\(\\s*([^)]*)\\s*\\)");
-        if (modeRe.indexIn(srcGmicImage.name) != -1) {
-            QString modeStr = modeRe.cap(1).trimmed();
-            auto translatedMode = KisQmicSimpleConvertor::stringToBlendingMode(modeStr);
-            dbgPlugins << "Detected mode: " << modeStr << " => " << translatedMode;
-            if (!translatedMode.isNull())
-                node->setCompositeOpId(translatedMode);
-        }
-    }
-
-    {
-        const QRegExp opacityRe("opacity\\(\\s*([^)]*)\\s*\\)");
-
-        if (opacityRe.indexIn(srcGmicImage.name) != -1) {
-            const auto opacity = opacityRe.cap(1).toUInt();
-            dbgPlugins << "Detected opacity: " << opacity;
-            node->setPercentOpacity(opacity);
-        }
-    }
-
-    {
-        const QRegExp nameRe("name\\(\\s*([^)]*)\\s*\\)");
-
-        if (nameRe.indexIn(srcGmicImage.name) != -1) {
-            const auto name = nameRe.cap(1);
-            dbgPlugins << "Detected layer name: " << name;
-            node->setName(name);
-        }
-    }
-
-    // Some GMic filters encode layer position into the layer name.
-    // E.g. from extract foreground: "name([unnamed] [foreground]),pos(55,35)"
-    const QRegularExpression positionPattern(R"(\Wpos\((\d+),(\d+)\))");
-    const QRegularExpressionMatch match = positionPattern.match(srcGmicImage.name);
-    if (match.hasMatch()) {
-        int x = match.captured(1).toInt();
-        int y = match.captured(2).toInt();
-        dst->moveTo(x, y);
-        dbgPlugins << "Detected layer position: " << x << y;
-    }
-}
-
-void KisImportQmicProcessingVisitor::visitNodeWithPaintDevice(KisNode *node, KisUndoAdapter *undoAdapter)
-{
-    int index = m_nodes->indexOf(node);
-    if (index >= 0 && index < m_images.size()) {
-        gmic_image<float> *gimg = m_images[index];
-        dbgPlugins << "Importing layer index" << index << "Size: "<< gimg->_width << "x" << gimg->_height << "colorchannels: " << gimg->_spectrum;
-
-        KisPaintDeviceSP dst = node->paintDevice();
-
-        const KisLayer *layer = dynamic_cast<KisLayer*>(node);
-        const KisSelectionSP selection = layer ? layer->selection() : m_selection;
-
-        KisTransaction transaction(dst);
-        KisImportQmicProcessingVisitor::gmicImageToPaintDevice(*gimg, dst, selection, m_dstRect);
-        KisImportQmicProcessingVisitor::applyLayerNameChanges(*gimg, node, dst);
-        if (undoAdapter) {
-            transaction.commit(undoAdapter);
-            node->setDirty(m_dstRect);
-        }
-    }
-}
-
-void KisImportQmicProcessingVisitor::visitExternalLayer(KisExternalLayer *layer, KisUndoAdapter *undoAdapter)
-{
-    Q_UNUSED(layer);
-    Q_UNUSED(undoAdapter);
-}
-
-void KisImportQmicProcessingVisitor::visitColorizeMask(KisColorizeMask *mask, KisUndoAdapter *undoAdapter)
-{
-    Q_UNUSED(mask);
-    Q_UNUSED(undoAdapter);
-}
diff --git a/plugins/extensions/qmic/kis_import_qmic_processing_visitor.h b/plugins/extensions/qmic/kis_import_qmic_processing_visitor.h
deleted file mode 100644
index aa0eff7e00..0000000000
--- a/plugins/extensions/qmic/kis_import_qmic_processing_visitor.h
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- *  SPDX-FileCopyrightText: 2013 Dmitry Kazakov <dimula73 at gmail.com>
- *  SPDX-FileCopyrightText: 2013 Lukáš Tvrdý <lukast.dev at gmail.com>
- *  SPDX-FileCopyrightText: 2020-2021 L. E. Segovia <amy at amyspark.me>
- *
- *  SPDX-License-Identifier: GPL-2.0-or-later
- */
-
-#ifndef __KIS_IMPORT_GMIC_PROCESSING_VISITOR_H
-#define __KIS_IMPORT_GMIC_PROCESSING_VISITOR_H
-
-#include <processing/kis_simple_processing_visitor.h>
-
-#include <QVector>
-#include <QSharedPointer>
-
-#include <kis_node.h>
-
-#include "gmic.h"
-
-
-class KisUndoAdapter;
-
-class KisImportQmicProcessingVisitor : public KisSimpleProcessingVisitor
-{
-public:
-    KisImportQmicProcessingVisitor(const KisNodeListSP nodes,
-                                   QVector<gmic_image<float> *> images,
-                                   const QRect &dstRect,
-                                   const KisSelectionSP selection
-                                  );
-
-    static void applyLayerNameChanges(const gmic_image<float> &srcGmicImage,
-                                   KisNode *node,
-                                   KisPaintDeviceSP dst
-                                   );
-
-    static void gmicImageToPaintDevice(gmic_image<float>& srcGmicImage,
-                                       KisPaintDeviceSP dstPaintDevice,
-                                       KisSelectionSP selection = 0,
-                                       const QRect &dstRect = QRect());
-
-
-protected:
-    void visitNodeWithPaintDevice(KisNode *node, KisUndoAdapter *undoAdapter) override;
-    void visitExternalLayer(KisExternalLayer *layer, KisUndoAdapter *undoAdapter) override;
-    void visitColorizeMask(KisColorizeMask *mask, KisUndoAdapter *undoAdapter) override;
-
-private:
-    const KisNodeListSP m_nodes;
-    QVector<gmic_image<float> *> m_images;
-    QRect m_dstRect;
-    const KisSelectionSP m_selection;
-};
-
-#endif /* __KIS_IMPORT_GMIC_PROCESSING_VISITOR_H */
diff --git a/plugins/extensions/qmic/kis_qmic_applicator.cpp b/plugins/extensions/qmic/kis_qmic_applicator.cpp
deleted file mode 100644
index b456ea9cd5..0000000000
--- a/plugins/extensions/qmic/kis_qmic_applicator.cpp
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2013 Lukáš Tvrdý <lukast.dev at gmail.com
- *
- *  SPDX-License-Identifier: GPL-2.0-or-later
- */
-#include "kis_qmic_applicator.h"
-
-#include <kis_image_signal_router.h>
-#include <kis_processing_applicator.h>
-
-#include <kis_image.h>
-#include <kis_selection.h>
-
-#include "kis_import_qmic_processing_visitor.h"
-#include "kis_qmic_synchronize_layers_command.h"
-#include "kis_qmic_synchronize_image_size_command.h"
-
-KisQmicApplicator::KisQmicApplicator()
-{
-}
-
-KisQmicApplicator::~KisQmicApplicator()
-{
-}
-
-void KisQmicApplicator::setProperties(KisImageWSP image, KisNodeSP node, QVector<gmic_image<float> *> images, const KUndo2MagicString &actionName, KisNodeListSP kritaNodes)
-{
-    dbgPlugins << "KisQmicApplicator::setProperties();" << ppVar(image) << ppVar(node) << images.size() << actionName << kritaNodes->count();
-
-    m_image = image;
-    m_node = node;
-    m_actionName = actionName;
-    m_kritaNodes = kritaNodes;
-    m_images = images;
-}
-
-
-void KisQmicApplicator::apply()
-{
-    dbgPlugins << "Request for applying the result";
-    cancel();
-
-    KisImageSignalVector emitSignals;
-    emitSignals << ComplexSizeChangedSignal();
-
-    m_applicator.reset(
-        new KisProcessingApplicator(m_image, m_node,
-                                    KisProcessingApplicator::RECURSIVE |
-                                    KisProcessingApplicator::NO_UI_UPDATES,
-                                    emitSignals, m_actionName));
-    dbgPlugins << "Created applicator " << m_applicator;
-
-    m_gmicData = KisQmicDataSP(new KisQmicData());
-
-    QRect layerSize;
-    KisSelectionSP selection = m_image->globalSelection();
-    if (selection) {
-        layerSize = selection->selectedExactRect();
-    }
-    else {
-        layerSize = QRect(0, 0, m_image->width(), m_image->height());
-    }
-
-    // This is a three-stage process.
-
-    if (!selection) {
-        // 1. synchronize Krita image size with biggest gmic layer size
-        m_applicator->applyCommand(new KisQmicSynchronizeImageSizeCommand(m_images, m_image));
-    }
-
-    // 2. synchronize layer count and convert excess GMic nodes to paint layers
-    m_applicator->applyCommand(new KisQmicSynchronizeLayersCommand(m_kritaNodes, m_images, m_image, layerSize, selection), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE);
-
-    // 3. visit the existing nodes and reuse them to apply the remaining changes from GMic
-    KisProcessingVisitorSP  importVisitor = new KisImportQmicProcessingVisitor(m_kritaNodes, m_images, layerSize, selection);
-    m_applicator->applyVisitor(importVisitor, KisStrokeJobData::SEQUENTIAL); // undo information is stored in this visitor
-    m_applicator->explicitlyEmitFinalSignals();
-    emit gmicFinished(true, 0, "done!");
-}
-
-void KisQmicApplicator::cancel()
-{
-    dbgPlugins << "KisQmicApplicator::cancel";
-    if (m_applicator) {
-
-        dbgPlugins << "Cancelling applicator!";
-        m_applicator->cancel();
-
-        dbgPlugins << "deleting applicator: " << m_applicator;
-        m_applicator.reset();
-    }
-    else  {
-        dbgPlugins << "Cancelling applicator: No! Reason: Null applicator!";
-    }
-}
-
-void KisQmicApplicator::finish()
-{
-    dbgPlugins << "Applicator " << m_applicator << " finished";
-    if (m_applicator) {
-        m_applicator->end();
-        m_applicator.reset();
-    }
-}
-
-float KisQmicApplicator::getProgress() const
-{
-    dbgPlugins << "KisQmicApplicator::getProgress";
-
-    if (m_gmicData) {
-        return m_gmicData->progress();
-    }
-    return KisQmicData::INVALID_PROGRESS_VALUE;
-}
diff --git a/plugins/extensions/qmic/kis_qmic_applicator.h b/plugins/extensions/qmic/kis_qmic_applicator.h
deleted file mode 100644
index 37d0cd26f5..0000000000
--- a/plugins/extensions/qmic/kis_qmic_applicator.h
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2013-2014 Lukáš Tvrdý <lukast.dev at gmail.com
- *
- *  SPDX-License-Identifier: GPL-2.0-or-later
- */
-
-#ifndef _KIS_GMIC_APPLICATOR
-#define _KIS_GMIC_APPLICATOR
-
-#include <kundo2magicstring.h>
-
-#include <kis_types.h>
-
-#include <QThread>
-
-#include "gmic.h"
-#include "kis_qmic_data.h"
-#include <QScopedPointer>
-
-class KisProcessingApplicator;
-
-class KisQmicApplicator : public QObject
-{
-    Q_OBJECT
-
-public:
-    KisQmicApplicator();
-    ~KisQmicApplicator();
-    void setProperties(KisImageWSP image, KisNodeSP node, QVector<gmic_image<float> *> images, const KUndo2MagicString &actionName, KisNodeListSP kritaNodes);
-
-    void apply();
-    void cancel();
-    void finish();
-
-    float getProgress() const;
-
-Q_SIGNALS:
-    void gmicFinished(bool successfully, int milliseconds = -1, const QString &msg = QString());
-
-private:
-    QScopedPointer<KisProcessingApplicator> m_applicator;
-    KisImageWSP m_image;
-    KisNodeSP m_node;
-    KUndo2MagicString m_actionName;
-    KisNodeListSP m_kritaNodes;
-    QVector<gmic_image<float> *> m_images;
-    KisQmicDataSP m_gmicData;
-};
-
-#endif
-
-
diff --git a/plugins/extensions/qmic/kis_qmic_data.cpp b/plugins/extensions/qmic/kis_qmic_data.cpp
deleted file mode 100644
index 5b695b23fb..0000000000
--- a/plugins/extensions/qmic/kis_qmic_data.cpp
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- *  SPDX-FileCopyrightText: 2015 Lukáš Tvrdý <lukast.dev at gmail.com>
- *
- *  SPDX-License-Identifier: GPL-2.0-or-later
- */
-
-#include "kis_qmic_data.h"
-#include <kis_debug.h>
-
-const float KisQmicData::INVALID_PROGRESS_VALUE = -2.0f;
-
-KisQmicData::KisQmicData()
-    : m_progress(INVALID_PROGRESS_VALUE)
-    , m_cancel(false)
-{
-}
-
-KisQmicData::~KisQmicData()
-{
-}
-
-void KisQmicData::reset()
-{
-    m_progress = INVALID_PROGRESS_VALUE;
-    m_cancel = false;
-}
diff --git a/plugins/extensions/qmic/kis_qmic_data.h b/plugins/extensions/qmic/kis_qmic_data.h
deleted file mode 100644
index 2e6fce8d6e..0000000000
--- a/plugins/extensions/qmic/kis_qmic_data.h
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- *  SPDX-FileCopyrightText: 2015 Lukáš Tvrdý <lukast.dev at gmail.com>
- *
- *  SPDX-License-Identifier: GPL-2.0-or-later
- */
-
-#ifndef KIS_QMIC_DATA
-#define KIS_QMIC_DATA
-
-#include <QDebug>
-
-class KisQmicData
-{
-public:
-    KisQmicData();
-    ~KisQmicData();
-
-    float progress() const { return m_progress; }
-    bool isCancelled() const { return m_cancel; }
-    void setCancel(bool cancel) { m_cancel = cancel; }
-
-    bool & cancelPtr() { return m_cancel; }
-    float & progressPtr() { return m_progress; }
-
-    void reset();
-
-    static const float INVALID_PROGRESS_VALUE;
-
-private:
-    float m_progress;
-    bool m_cancel;
-};
-
-#include <QSharedPointer>
-typedef QSharedPointer<KisQmicData> KisQmicDataSP;
-
-#endif
diff --git a/plugins/extensions/qmic/kis_qmic_import_tools.h b/plugins/extensions/qmic/kis_qmic_import_tools.h
new file mode 100644
index 0000000000..4d8f0f5cc5
--- /dev/null
+++ b/plugins/extensions/qmic/kis_qmic_import_tools.h
@@ -0,0 +1,103 @@
+/*
+ * SPDX-FileCopyrightText: 2013 Dmitry Kazakov <dimula73 at gmail.com>
+ * SPDX-FileCopyrightText: 2013 Lukáš Tvrdý <lukast.dev at gmail.com>
+ * SPDX-FileCopyrightText: 2022 L. E. Segovia <amy at amyspark.me>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef KIS_QMIC_IMPORT_TOOLS_H
+#define KIS_QMIC_IMPORT_TOOLS_H
+
+#include <QRegularExpression>
+
+#include <KoCompositeOpRegistry.h>
+#include <commands/KisNodeRenameCommand.h>
+#include <commands/kis_node_compositeop_command.h>
+#include <commands/kis_node_opacity_command.h>
+#include <commands_new/kis_node_move_command2.h>
+#include <kis_command_utils.h>
+#include <kis_node.h>
+#include <kis_painter.h>
+#include <kis_types.h>
+
+#include "gmic.h"
+#include "kis_qmic_simple_convertor.h"
+
+namespace KisQmicImportTools
+{
+[[nodiscard]] inline KUndo2Command *applyLayerNameChanges(const gmic_image<float> &srcGmicImage, KisNode *node)
+{
+    dbgPlugins << "KisQmicImportTools::applyLayerNameChanges";
+
+    auto *cmd = new KisCommandUtils::CompositeCommand();
+
+    dbgPlugins << "Layer name: " << srcGmicImage.name;
+
+    {
+        const QRegExp modeRe(R"(mode\(\s*([^)]*)\s*\))");
+        if (modeRe.indexIn(srcGmicImage.name) != -1) {
+            QString modeStr = modeRe.cap(1).trimmed();
+            auto translatedMode = KisQmicSimpleConvertor::stringToBlendingMode(modeStr);
+            dbgPlugins << "Detected mode: " << modeStr << " => " << translatedMode;
+            if (!translatedMode.isNull()) {
+                cmd->addCommand(new KisNodeCompositeOpCommand(node, translatedMode));
+            }
+        }
+    }
+
+    {
+        const QRegExp opacityRe(R"(opacity\(\s*([^)]*)\s*\))");
+
+        if (opacityRe.indexIn(srcGmicImage.name) != -1) {
+            const auto opacity = opacityRe.cap(1).toFloat();
+            dbgPlugins << "Detected opacity: " << opacity << std::lround(opacity / 100.f * 255.f);
+            cmd->addCommand(new KisNodeOpacityCommand(node, static_cast<quint8>(std::lround(float(opacity * 255) / 100.f))));
+        }
+    }
+
+    {
+        const QRegExp nameRe(R"(name\(\s*([^)]*)\s*\))");
+
+        if (nameRe.indexIn(srcGmicImage.name) != -1) {
+            const auto name = nameRe.cap(1);
+            dbgPlugins << "Detected layer name: " << name;
+            cmd->addCommand(new KisNodeRenameCommand(node, node->name(), name));
+            // apply command
+        }
+    }
+
+    {
+        // Some GMic filters encode layer position into the layer name.
+        // E.g. from extract foreground: "name([unnamed] [foreground]),pos(55,35)"
+        const QRegularExpression positionPattern(R"(\Wpos\((\d+),(\d+)\))");
+        const QRegularExpressionMatch match = positionPattern.match(srcGmicImage.name);
+        if (match.hasMatch()) {
+            const auto x = match.captured(1).toInt();
+            const auto y = match.captured(2).toInt();
+            dbgPlugins << "Detected layer position: " << x << y;
+            cmd->addCommand(new KisNodeMoveCommand2(node, QPoint(node->x(), node->y()), QPoint(x, y)));
+        }
+    }
+
+    return cmd;
+}
+
+inline void gmicImageToPaintDevice(const gmic_image<float> &srcGmicImage, KisPaintDeviceSP dst, KisSelectionSP selection = nullptr, const QRect &dstRect = {})
+{
+    dbgPlugins << "KisQmicImportTools::gmicImageToPaintDevice();";
+
+    if (selection) {
+        KisPaintDeviceSP src = new KisPaintDevice(dst->colorSpace());
+        KisQmicSimpleConvertor::convertFromGmicFast(srcGmicImage, src, 255.0f);
+
+        KisPainter painter(dst, selection);
+        painter.setCompositeOp(COMPOSITE_COPY);
+        painter.bitBlt(dstRect.topLeft(), src, QRect(QPoint(0, 0), dstRect.size()));
+    } else {
+        KisQmicSimpleConvertor::convertFromGmicFast(srcGmicImage, dst, 255.0f);
+    }
+}
+} // namespace KisQmicImportTools
+
+#endif // KIS_QMIC_IMPORT_TOOLS_H
diff --git a/plugins/extensions/qmic/kis_qmic_interface.cpp b/plugins/extensions/qmic/kis_qmic_interface.cpp
index 2184e1f463..797c912ad3 100644
--- a/plugins/extensions/qmic/kis_qmic_interface.cpp
+++ b/plugins/extensions/qmic/kis_qmic_interface.cpp
@@ -6,30 +6,33 @@
  * SPDX-License-Identifier: GPL-2.0-or-later
  */
 
-#include <QApplication>
-#include <QMessageBox>
-
-#include "KisViewManager.h"
-#include "kis_algebra_2d.h"
-#include "kis_debug.h"
-#include "kis_image.h"
-#include "kis_image_barrier_locker.h"
 #include "kis_qmic_interface.h"
-#include "kis_input_output_mapper.h"
-#include "kis_qmic_applicator.h"
-#include "kis_qmic_simple_convertor.h"
-#include "kis_selection.h"
+
+#include <KisImageSignals.h>
+#include <KisViewManager.h>
+#include <kis_algebra_2d.h>
+#include <kis_debug.h>
+#include <kis_image.h>
+#include <kis_image_barrier_locker.h>
+#include <kis_input_output_mapper.h>
+#include <kis_processing_applicator.h>
+#include <kis_selection.h>
+#include <kundo2magicstring.h>
 
 #include "gmic.h"
+#include "kis_qmic_processing_visitor.h"
+#include "kis_qmic_simple_convertor.h"
+#include "kis_qmic_synchronize_image_size_command.h"
+#include "kis_qmic_synchronize_layers_command.h"
 
 struct KisImageInterface::Private {
     Private() = default;
 
-    KisViewManager *m_viewManager {nullptr};
+    KisViewManager *m_viewManager{nullptr};
     InputLayerMode m_inputMode{InputLayerMode::Active};
     OutputMode m_outputMode{OutputMode::InPlace};
-    QVector<KisQMicImageSP> m_sharedMemorySegments {};
-    KisQmicApplicator *m_gmicApplicator {nullptr};
+    QVector<KisQMicImageSP> m_sharedMemorySegments{};
+    KisQmicApplicator *m_gmicApplicator{nullptr};
 };
 
 KisImageInterface::KisImageInterface(KisViewManager *parent)
@@ -37,14 +40,9 @@ KisImageInterface::KisImageInterface(KisViewManager *parent)
 {
     p->m_viewManager = parent;
     KIS_ASSERT(p->m_viewManager);
-
-    p->m_gmicApplicator = new KisQmicApplicator();
-    connect(p->m_gmicApplicator, SIGNAL(gmicFinished(bool, int, QString)), this, SLOT(slotGmicFinished(bool, int, QString)));
 }
 
-KisImageInterface::~KisImageInterface()
-{
-}
+KisImageInterface::~KisImageInterface() = default;
 
 QSize KisImageInterface::gmic_qt_get_image_size()
 {
@@ -79,9 +77,8 @@ QVector<KisQMicImageSP> KisImageInterface::gmic_qt_get_cropped_images(int inputM
         return {};
     }
 
-    for (int i = 0; i < nodes->size(); ++i) {
-        KisNodeSP node = nodes->at(i);
-        if (node && node->paintDevice()) {
+    for (auto &node : *nodes) {
+         if (node && node->paintDevice()) {
             QRect cropRect;
 
             KisSelectionSP selection = p->m_viewManager->image()->globalSelection();
@@ -100,9 +97,14 @@ QVector<KisQMicImageSP> KisImageInterface::gmic_qt_get_cropped_images(int inputM
             QString noParenthesisName(node->name());
             noParenthesisName.replace(QChar('('), QChar(21)).replace(QChar(')'), QChar(22));
 
-            auto translatedMode = KisQmicSimpleConvertor::blendingModeToString(node->compositeOpId());
+            const auto translatedMode = KisQmicSimpleConvertor::blendingModeToString(node->compositeOpId());
 
-            QString name = QString("mode(%1),opacity(%2),pos(%3,%4),name(%5)").arg(translatedMode).arg(node->percentOpacity()).arg(cropRect.x()).arg(cropRect.y()).arg(noParenthesisName);
+            const QString name = QString("mode(%1),opacity(%2),pos(%3,%4),name(%5)")
+                               .arg(translatedMode)
+                               .arg(node->percentOpacity())
+                               .arg(cropRect.x())
+                               .arg(cropRect.y())
+                               .arg(noParenthesisName);
 
             auto m = KisQMicImageSP::create(name, resultRect.width(), resultRect.height(), 4);
             p->m_sharedMemorySegments << m;
@@ -113,9 +115,9 @@ QVector<KisQMicImageSP> KisImageInterface::gmic_qt_get_cropped_images(int inputM
                 gmic_image<float> img;
                 img.assign(resultRect.width(), resultRect.height(), 1, 4);
 
-
                 img._data = m->m_data;
-                
+                img._is_shared = true;
+
                 KisQmicSimpleConvertor::convertToGmicImageFast(node->paintDevice(), &img, resultRect);
             }
 
@@ -128,37 +130,33 @@ QVector<KisQMicImageSP> KisImageInterface::gmic_qt_get_cropped_images(int inputM
     return message;
 }
 
-void KisImageInterface::gmic_qt_output_images(int mode, QVector<KisQMicImageSP> layers)
-{
-    // Parse the message. read the shared memory segments, fix up the current image and send an ack
-    dbgPlugins << "gmic_qt_output_images";
-    p->m_outputMode = (OutputMode)mode;
-    if (p->m_outputMode != OutputMode::InPlace) {
-        QMessageBox::warning(qApp->activeWindow(), i18nc("@title:window", "Krita"), i18n("Sorry, this output mode is not implemented yet."));
-        p->m_outputMode = OutputMode::InPlace;
-    }
-    slotStartApplicator(layers);
-}
-
 void KisImageInterface::gmic_qt_detach()
 {
     for (auto memorySegment : p->m_sharedMemorySegments) {
-        dbgPlugins << "detaching" << (quintptr)memorySegment.data();
+        dbgPlugins << "detaching" << memorySegment;
         memorySegment.clear();
     }
     p->m_sharedMemorySegments.clear();
 }
 
-void KisImageInterface::slotStartApplicator(QVector<KisQMicImageSP> gmicImages)
+void KisImageInterface::gmic_qt_output_images(int mode, QVector<KisQMicImageSP> layers)
 {
-    dbgPlugins << "slotStartApplicator();" << gmicImages;
+    // Parse the message. read the shared memory segments, fix up the current image and send an ack
+    dbgPlugins << "gmic_qt_output_images";
+    p->m_outputMode = (OutputMode)mode;
+    if (p->m_outputMode != OutputMode::InPlace) {
+        errPlugins << "Requested mode" << static_cast<int>(p->m_outputMode) << "which is not implemented yet";
+        p->m_outputMode = OutputMode::InPlace;
+    }
+
+    dbgPlugins << "slotStartApplicator();" << layers;
     if (!p->m_viewManager)
         return;
     // Create a vector of gmic images
 
     QVector<gmic_image<float> *> images;
 
-    for (auto &image : gmicImages) {
+    for (const auto &image : layers) {
         QString layerName = image->m_layerName;
         int spectrum = image->m_spectrum;
         int width = image->m_width;
@@ -177,35 +175,61 @@ void KisImageInterface::slotStartApplicator(QVector<KisQMicImageSP> gmicImages)
             gimg->name = layerName;
 
             gimg->_data = new float[width * height * spectrum * sizeof(float)];
-            dbgPlugins << "width" << width << "height" << height << "size" << width * height * spectrum * sizeof(float) << "shared memory size" << image->size();
+            dbgPlugins << "width" << width << "height" << height << "size" << width * height * spectrum * sizeof(float) << "shared memory size"
+                       << image->size();
             memcpy(gimg->_data, image->constData(), width * height * spectrum * sizeof(float));
 
             dbgPlugins << "created gmic image" << gimg->name << gimg->_width << gimg->_height;
-
         }
         images.append(gimg);
     }
 
     dbgPlugins << "Got" << images.size() << "gmic images";
 
-    // Start the applicator
-    KUndo2MagicString actionName = kundo2_i18n("Gmic filter");
-    KisNodeSP rootNode = p->m_viewManager->image()->root();
-    KisInputOutputMapper mapper(p->m_viewManager->image(), p->m_viewManager->activeNode());
-    KisNodeListSP layers = mapper.inputNodes(p->m_inputMode);
+    {
+        // Start the applicator
+        KUndo2MagicString actionName = kundo2_i18n("G'MIC filter");
+        KisNodeSP rootNode = p->m_viewManager->image()->root();
+        KisInputOutputMapper mapper(p->m_viewManager->image(), p->m_viewManager->activeNode());
+        KisNodeListSP mappedLayers = mapper.inputNodes(p->m_inputMode);
+        // p->m_gmicApplicator->setProperties(p->m_viewManager->image(), rootNode, images, actionName, layers);
+        // p->m_gmicApplicator->apply();
+
+        KisImageSignalVector emitSignals;
+        emitSignals << ComplexSizeChangedSignal();
+
+        KisProcessingApplicator applicator(p->m_viewManager->image(),
+                                           rootNode,
+                                           KisProcessingApplicator::RECURSIVE | KisProcessingApplicator::NO_UI_UPDATES,
+                                           emitSignals,
+                                           actionName);
+        dbgPlugins << "Created applicator " << &applicator;
+
+        QRect layerSize;
+        KisSelectionSP selection = p->m_viewManager->image()->globalSelection();
+        if (selection) {
+            layerSize = selection->selectedExactRect();
+        } else {
+            layerSize = QRect(0, 0, p->m_viewManager->image()->width(), p->m_viewManager->image()->height());
+        }
 
-    p->m_gmicApplicator->setProperties(p->m_viewManager->image(), rootNode, images, actionName, layers);
-    p->m_gmicApplicator->apply();
-}
+        // This is a three-stage process.
 
-void KisImageInterface::slotGmicFinished(bool successfully, int milliseconds, const QString &msg)
-{
-    dbgPlugins << "slotGmicFinished();" << successfully << milliseconds << msg;
-    if (successfully) {
-        p->m_gmicApplicator->finish();
-    } else {
-        p->m_gmicApplicator->cancel();
-        QMessageBox::warning(qApp->activeWindow(), i18nc("@title:window", "Krita"), i18n("G'Mic failed, reason:") + msg);
+        if (!selection) {
+            // 1. synchronize Krita image size with biggest gmic layer size
+            applicator.applyCommand(new KisQmicSynchronizeImageSizeCommand(images, p->m_viewManager->image()));
+        }
+
+        // 2. synchronize layer count and convert excess GMic nodes to paint layers
+        applicator.applyCommand(new KisQmicSynchronizeLayersCommand(mappedLayers, images, p->m_viewManager->image(), layerSize, selection),
+                                KisStrokeJobData::SEQUENTIAL,
+                                KisStrokeJobData::EXCLUSIVE);
+
+        // 3. visit the existing nodes and reuse them to apply the remaining changes from GMic
+        applicator.applyVisitor(new KisQmicProcessingVisitor(mappedLayers, images, layerSize, selection),
+                                KisStrokeJobData::SEQUENTIAL); // undo information is stored in this visitor
+
+        applicator.end();
     }
 }
 
diff --git a/plugins/extensions/qmic/kis_qmic_interface.h b/plugins/extensions/qmic/kis_qmic_interface.h
index 85178fcb89..4b2d03ddd6 100644
--- a/plugins/extensions/qmic/kis_qmic_interface.h
+++ b/plugins/extensions/qmic/kis_qmic_interface.h
@@ -16,7 +16,6 @@
 #include <QObject>
 #include <QRect>
 #include <QScopedPointer>
-#include <QSharedPointer>
 #include <QSize>
 #include <QVector>
 
@@ -76,10 +75,6 @@ public:
 private:
     struct Private;
     const QScopedPointer<Private> p;
-
-private Q_SLOTS:
-    void slotStartApplicator(QVector<KisQMicImageSP> gmicImages);
-    void slotGmicFinished(bool successfully, int milliseconds, const QString &msg);
 };
 
 #endif
diff --git a/plugins/extensions/qmic/kis_qmic_processing_visitor.cpp b/plugins/extensions/qmic/kis_qmic_processing_visitor.cpp
new file mode 100644
index 0000000000..11b44245fa
--- /dev/null
+++ b/plugins/extensions/qmic/kis_qmic_processing_visitor.cpp
@@ -0,0 +1,87 @@
+/*
+ *  SPDX-FileCopyrightText: 2013 Dmitry Kazakov <dimula73 at gmail.com>
+ *  SPDX-FileCopyrightText: 2013 Lukáš Tvrdý <lukast.dev at gmail.com>
+ *  SPDX-FileCopyrightText: 2020 L. E. Segovia <amy at amyspark.me>
+ *
+ *  SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <utility>
+
+#include <kis_debug.h>
+#include <kis_node.h>
+#include <kis_paint_device.h>
+#include <kis_paint_layer.h>
+#include <kis_painter.h>
+#include <kis_selection.h>
+#include <kis_transaction.h>
+#include <kis_types.h>
+
+#include "gmic.h"
+#include "kis_qmic_import_tools.h"
+#include "kis_qmic_processing_visitor.h"
+
+struct Q_DECL_HIDDEN KisQmicProcessingVisitor::Private {
+    Private(const KisNodeListSP nodes, QVector<gmic_image<float> *> images, const QRect &dstRect, KisSelectionSP selection)
+        : m_nodes(nodes)
+        , m_images(std::move(images))
+        , m_dstRect(dstRect)
+        , m_selection(selection)
+    {
+    }
+
+    ~Private() = default;
+
+    const KisNodeListSP m_nodes;
+    QVector<gmic_image<float> *> m_images;
+    QRect m_dstRect;
+    const KisSelectionSP m_selection;
+};
+
+KisQmicProcessingVisitor::KisQmicProcessingVisitor(const KisNodeListSP nodes,
+                                                               QVector<gmic_image<float> *> images,
+                                                               const QRect &dstRect,
+                                                               KisSelectionSP selection)
+    : d(new Private{nodes, std::move(images), dstRect, selection})
+{
+    dbgPlugins << "KisImportQmicProcessingVisitor";
+}
+
+KisQmicProcessingVisitor::~KisQmicProcessingVisitor()
+{
+    delete d;
+}
+
+void KisQmicProcessingVisitor::visitNodeWithPaintDevice(KisNode *node, KisUndoAdapter *undoAdapter)
+{
+    int index = d->m_nodes->indexOf(node);
+    if (index >= 0 && index < d->m_images.size()) {
+        const auto *gimg = d->m_images[index];
+        dbgPlugins << "Importing layer index" << index << "Size: " << gimg->_width << "x" << gimg->_height << "colorchannels: " << gimg->_spectrum;
+
+        auto dst = node->paintDevice();
+
+        const auto *layer = dynamic_cast<KisLayer *>(node);
+        const KisSelectionSP selection = layer ? layer->selection() : d->m_selection;
+
+        KisTransaction transaction(dst);
+        KisQmicImportTools::gmicImageToPaintDevice(*gimg, dst, selection, d->m_dstRect);
+        if (undoAdapter) {
+            undoAdapter->addCommand(KisQmicImportTools::applyLayerNameChanges(*gimg, node));
+            transaction.commit(undoAdapter);
+            node->setDirty(d->m_dstRect);
+        }
+    }
+}
+
+void KisQmicProcessingVisitor::visitExternalLayer(KisExternalLayer *layer, KisUndoAdapter *undoAdapter)
+{
+    Q_UNUSED(layer);
+    Q_UNUSED(undoAdapter);
+}
+
+void KisQmicProcessingVisitor::visitColorizeMask(KisColorizeMask *mask, KisUndoAdapter *undoAdapter)
+{
+    Q_UNUSED(mask);
+    Q_UNUSED(undoAdapter);
+}
diff --git a/plugins/extensions/qmic/kis_qmic_processing_visitor.h b/plugins/extensions/qmic/kis_qmic_processing_visitor.h
new file mode 100644
index 0000000000..e5c8a10188
--- /dev/null
+++ b/plugins/extensions/qmic/kis_qmic_processing_visitor.h
@@ -0,0 +1,37 @@
+/*
+ *  SPDX-FileCopyrightText: 2013 Dmitry Kazakov <dimula73 at gmail.com>
+ *  SPDX-FileCopyrightText: 2013 Lukáš Tvrdý <lukast.dev at gmail.com>
+ *  SPDX-FileCopyrightText: 2020-2021 L. E. Segovia <amy at amyspark.me>
+ *
+ *  SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef KIS_QMIC_PROCESSING_VISITOR_H
+#define KIS_QMIC_PROCESSING_VISITOR_H
+
+#include <processing/kis_simple_processing_visitor.h>
+
+#include <QVector>
+
+#include <kis_node.h>
+
+#include "gmic.h"
+
+class KisQmicProcessingVisitor : public KisSimpleProcessingVisitor
+{
+public:
+    KisQmicProcessingVisitor(const KisNodeListSP nodes, QVector<gmic_image<float> *> images, const QRect &dstRect, const KisSelectionSP selection);
+
+    ~KisQmicProcessingVisitor() override;
+
+protected:
+    void visitNodeWithPaintDevice(KisNode *node, KisUndoAdapter *undoAdapter) override;
+    void visitExternalLayer(KisExternalLayer *layer, KisUndoAdapter *undoAdapter) override;
+    void visitColorizeMask(KisColorizeMask *mask, KisUndoAdapter *undoAdapter) override;
+
+private:
+    struct Private;
+    Private const *d;
+};
+
+#endif /* KIS_QMIC_PROCESSING_VISITOR_H */
diff --git a/plugins/extensions/qmic/kis_qmic_progress_manager.cpp b/plugins/extensions/qmic/kis_qmic_progress_manager.cpp
deleted file mode 100644
index 0013a54b56..0000000000
--- a/plugins/extensions/qmic/kis_qmic_progress_manager.cpp
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2014 Lukáš Tvrdý <lukast.dev at gmail.com>
- *
- *  SPDX-License-Identifier: GPL-2.0-or-later
- */
-
-#include "kis_qmic_progress_manager.h"
-#include <QApplication>
-
-#include <KisViewManager.h>
-#include <KoProgressUpdater.h>
-
-static const int UPDATE_PROGRESS_TIMEOUT = 500;
-
-KisQmicProgressManager::KisQmicProgressManager(KisViewManager* viewManager)
-    : m_progressPulseRequest(0)
-{
-        m_progressUpdater = new KoProgressUpdater(viewManager->createUnthreadedUpdater(""));
-        m_progressTimer.setInterval(UPDATE_PROGRESS_TIMEOUT);
-        connect(&m_progressTimer, SIGNAL(timeout()), this, SIGNAL(sigProgress()));
-}
-
-KisQmicProgressManager::~KisQmicProgressManager()
-{
-    QApplication::restoreOverrideCursor();
-    delete m_progressUpdater;
-}
-
-
-void KisQmicProgressManager::initProgress()
-{
-    m_progressTimer.start();
-    QApplication::setOverrideCursor(Qt::WaitCursor);
-    m_updater = m_progressUpdater->startSubtask();
-    m_progressPulseRequest = 0;
-}
-
-void KisQmicProgressManager::updateProgress(float progress)
-{
-    int currentProgress = 0.0;
-    if (progress >= 0.0)  {
-        if (m_progressPulseRequest > 0)
-        {
-            m_progressUpdater->start(100);
-            m_updater = m_progressUpdater->startSubtask();
-            m_progressPulseRequest = 0;
-        }
-        currentProgress = (int)progress;
-    }
-    else  {
-        // pulse
-        m_progressPulseRequest++;
-        if (m_updater->progress() >= 90)
-        {
-            m_progressUpdater->start(100);
-            m_updater = m_progressUpdater->startSubtask();
-        }
-        currentProgress = (m_progressPulseRequest % 10) * 10;
-    }
-
-    dbgPlugins << "Current progress : " << currentProgress << " vs " << progress;
-    m_updater->setProgress(currentProgress);
-}
-
-void KisQmicProgressManager::finishProgress()
-{
-    m_progressTimer.stop();
-    QApplication::restoreOverrideCursor();
-    m_updater->setProgress(100);
-}
-
-bool KisQmicProgressManager::inProgress()
-{
-    return m_progressTimer.isActive();
-}
diff --git a/plugins/extensions/qmic/kis_qmic_progress_manager.h b/plugins/extensions/qmic/kis_qmic_progress_manager.h
deleted file mode 100644
index 80c09ea376..0000000000
--- a/plugins/extensions/qmic/kis_qmic_progress_manager.h
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2014 Lukáš Tvrdý <lukast.dev at gmail.com>
- *
- *  SPDX-License-Identifier: GPL-2.0-or-later
- */
-
-
-#ifndef KIS_GMIC_PROGRESS_MANAGER
-#define KIS_GMIC_PROGRESS_MANAGER
-
-#include <QObject>
-#include <QTimer>
-
-#include <KoUpdater.h>
-#include <kis_types.h>
-
-class KisViewManager;
-class QTimer;
-
-class KisQmicProgressManager : public QObject
-{
-    Q_OBJECT
-public:
-    KisQmicProgressManager(KisViewManager *viewManager);
-    virtual ~KisQmicProgressManager();
-
-    void initProgress();
-    void updateProgress(float progress);
-    void finishProgress();
-    bool inProgress();
-
-Q_SIGNALS:
-    void sigProgress();
-
-private:
-    QTimer m_progressTimer;
-    KoProgressUpdater *m_progressUpdater;
-    KoUpdaterPtr m_updater;
-    quint32 m_progressPulseRequest;
-};
-
-#endif
diff --git a/plugins/extensions/qmic/kis_qmic_simple_convertor.cpp b/plugins/extensions/qmic/kis_qmic_simple_convertor.cpp
index 7706f7473d..8a7ee7acbb 100644
--- a/plugins/extensions/qmic/kis_qmic_simple_convertor.cpp
+++ b/plugins/extensions/qmic/kis_qmic_simple_convertor.cpp
@@ -309,7 +309,7 @@ static KoColorTransformation* createTransformation(const KoColorSpace* colorSpac
 }
 
 
-void KisQmicSimpleConvertor::convertFromGmicFast(gmic_image<float>& gmicImage, KisPaintDeviceSP dst, float gmicUnitValue)
+void KisQmicSimpleConvertor::convertFromGmicFast(const gmic_image<float>& gmicImage, KisPaintDeviceSP dst, float gmicUnitValue)
 {
     dbgPlugins << "convertFromGmicFast";
     const KoColorSpace * dstColorSpace = dst->colorSpace();
@@ -642,7 +642,7 @@ void KisQmicSimpleConvertor::convertToGmicImage(KisPaintDeviceSP dev, gmic_image
     delete pixelToGmicPixelFormat;
 }
 
-void KisQmicSimpleConvertor::convertFromGmicImage(gmic_image<float>& gmicImage, KisPaintDeviceSP dst, float gmicMaxChannelValue)
+void KisQmicSimpleConvertor::convertFromGmicImage(const gmic_image<float>& gmicImage, KisPaintDeviceSP dst, float gmicMaxChannelValue)
 {
     dbgPlugins << "convertFromGmicSlow";
     Q_ASSERT(!dst.isNull());
diff --git a/plugins/extensions/qmic/kis_qmic_simple_convertor.h b/plugins/extensions/qmic/kis_qmic_simple_convertor.h
index 9e63ce7b7c..3c6c3d6359 100644
--- a/plugins/extensions/qmic/kis_qmic_simple_convertor.h
+++ b/plugins/extensions/qmic/kis_qmic_simple_convertor.h
@@ -26,10 +26,10 @@ public:
     // output gmic image will have max channel 255.0
     static void convertToGmicImage(KisPaintDeviceSP dev, gmic_image<float> *gmicImage, QRect rc = QRect());
     // gmicMaxChannelValue indicates if the gmic image pixels rgb has range 0..255 or 0..1.0
-    static void convertFromGmicImage(gmic_image<float>& gmicImage, KisPaintDeviceSP dst, float gmicMaxChannelValue);
+    static void convertFromGmicImage(const gmic_image<float>& gmicImage, KisPaintDeviceSP dst, float gmicMaxChannelValue);
 
     /// Fast versions
-    static void convertFromGmicFast(gmic_image<float>& gmicImage, KisPaintDeviceSP dst, float gmicUnitValue);
+    static void convertFromGmicFast(const gmic_image<float>& gmicImage, KisPaintDeviceSP dst, float gmicUnitValue);
     static void convertToGmicImageFast(KisPaintDeviceSP dev, gmic_image<float> *gmicImage, QRect rc = QRect());
 };
 
diff --git a/plugins/extensions/qmic/kis_qmic_synchronize_layers_command.cpp b/plugins/extensions/qmic/kis_qmic_synchronize_layers_command.cpp
index b694902d44..cd8523c884 100644
--- a/plugins/extensions/qmic/kis_qmic_synchronize_layers_command.cpp
+++ b/plugins/extensions/qmic/kis_qmic_synchronize_layers_command.cpp
@@ -1,107 +1,141 @@
 /*
- * SPDX-FileCopyrightText: 2013 Lukáš Tvrdý <lukast.dev at gmail.com
+ * SPDX-FileCopyrightText: 2013 Lukáš Tvrdý <lukast.dev at gmail.com>
+ * SPDX-FileCopyrightText: 2022 L. E. Segovia <amy at amyspark.me>
  *
  *  SPDX-License-Identifier: GPL-2.0-or-later
  */
 
-#include <kis_qmic_synchronize_layers_command.h>
-#include "kis_import_qmic_processing_visitor.h"
-#include <kis_paint_layer.h>
+#include "kis_qmic_synchronize_layers_command.h"
+
+#include <QList>
+#include <QSharedPointer>
+#include <utility>
+
+#include <KoColorSpaceConstants.h>
+#include <commands/kis_image_layer_add_command.h>
+#include <kis_command_utils.h>
 #include <kis_image.h>
+#include <kis_paint_layer.h>
 #include <kis_selection.h>
 #include <kis_types.h>
-#include <KoColorSpaceConstants.h>
 
-#include <commands/kis_image_layer_add_command.h>
+#include "kis_qmic_import_tools.h"
 
-KisQmicSynchronizeLayersCommand::KisQmicSynchronizeLayersCommand(KisNodeListSP nodes, QVector<gmic_image<float> *> images, KisImageWSP image, const QRect &dstRect, KisSelectionSP selection)
-    :   KUndo2Command(),
-        m_nodes(nodes),
-        m_images(images),
-        m_image(image),
-        m_dstRect(dstRect),
-        m_selection(selection),
-        m_firstRedo(true)
+struct Q_DECL_HIDDEN KisQmicSynchronizeLayersCommand::Private {
+    Private(KisNodeListSP nodes, QVector<gmic_image<float> *> images, KisImageWSP image, const QRect &dstRect, KisSelectionSP selection)
+        : m_nodes(std::move(nodes))
+        , m_newNodes(new KisNodeList())
+        , m_images(std::move(images))
+        , m_image(image)
+        , m_dstRect(dstRect)
+        , m_selection(selection)
+    {
+    }
+
+    ~Private()
+    {
+        qDeleteAll(m_imageCommands);
+        m_imageCommands.clear();
+    }
+
+    KisNodeListSP m_nodes;
+    KisNodeListSP m_newNodes;
+    QVector<gmic_image<float> *> m_images;
+    KisImageWSP m_image;
+    QRect m_dstRect;
+    KisSelectionSP m_selection;
+    QVector<KisImageCommand *> m_imageCommands;
+    bool m_firstRedo;
+};
+
+KisQmicSynchronizeLayersCommand::KisQmicSynchronizeLayersCommand(KisNodeListSP nodes,
+                                                                 QVector<gmic_image<float> *> images,
+                                                                 KisImageWSP image,
+                                                                 const QRect &dstRect,
+                                                                 KisSelectionSP selection)
+    : KisCommandUtils::CompositeCommand()
+    , d(new Private{std::move(nodes), std::move(images), image, dstRect, selection})
 {
     dbgPlugins << "KisQmicSynchronizeLayersCommand";
 }
 
 KisQmicSynchronizeLayersCommand::~KisQmicSynchronizeLayersCommand()
 {
-    qDeleteAll(m_imageCommands);
-    m_imageCommands.clear();
+    delete d;
 }
 
 void KisQmicSynchronizeLayersCommand::redo()
 {
-    dbgPlugins << "KisQmicSynchronizeLayersCommand::Redo" << m_firstRedo;
-
-    if (m_firstRedo) {
-        // if gmic produces more layers
-        if (m_nodes->size() < m_images.size()) {
-
-            if (m_image) {
+    dbgPlugins << "KisQmicSynchronizeLayersCommand::Redo";
 
-                int nodesCount = m_nodes->size();
-                for (int i = nodesCount; i < m_images.size(); i++) {
-
-                    KisPaintDevice * device = new KisPaintDevice(m_image->colorSpace());
-                    KisLayerSP paintLayer = new KisPaintLayer(m_image, QString("New layer %1 from gmic filter").arg(i), OPACITY_OPAQUE_U8, device);
-
-                    KisImportQmicProcessingVisitor::gmicImageToPaintDevice(*m_images[i], device);
-
-                    KisNodeSP aboveThis(nullptr), parent(nullptr);
-
-                    KisImageLayerAddCommand *addLayerCmd(nullptr);
-
-                    if (nodesCount > 0) {
-                        // This node is a copy made by GMic of an existing node;
-                        // give it its name back (the existing node will be reused
-                        // by KisImportQmicProcessingVisitor)
-                        paintLayer->setName(m_nodes->at(i - nodesCount)->name());
-                        aboveThis = m_nodes->last()->prevSibling();
-                        parent = m_nodes->at(0)->parent();
+    const auto pickAboveThis = [&]() {
+        if (!d->m_newNodes->isEmpty()) {
+            return d->m_newNodes->last()->prevSibling();
+        } else {
+            return d->m_nodes->last()->prevSibling();
+        }
+    };
 
-                        dbgPlugins << "Adding paint layer" << (i - nodesCount + 1) << paintLayer << "to parent"
-                                   << parent->name() << "above" << aboveThis;
-                    }
+    const auto getNode = [&](int i) {
+        if (i >= d->m_nodes->size()) {
+            return d->m_newNodes->at(i - d->m_nodes->size());
+        } else {
+            return d->m_nodes->at(i);
+        }
+    };
+
+    // if gmic produces more layers
+    if (d->m_nodes->size() < d->m_images.size()) {
+        if (d->m_image) {
+            const auto nodesCount = d->m_nodes->size();
+            for (int i = nodesCount; i < d->m_images.size(); i++) {
+                KisPaintDeviceSP device = new KisPaintDevice(d->m_image->colorSpace());
+                KisLayerSP paintLayer = new KisPaintLayer(d->m_image, QString("New layer %1 from gmic filter").arg(i), OPACITY_OPAQUE_U8, device);
+
+                KisQmicImportTools::gmicImageToPaintDevice(*d->m_images[i], device, d->m_selection, d->m_dstRect);
+
+                KisNodeSP aboveThis(nullptr);
+                KisNodeSP parent(nullptr);
+
+                if (nodesCount > 0) {
+                    // This node is a copy made by GMic of an existing node;
+                    // give it its name back (the existing node will be reused
+                    // by KisImportQmicProcessingVisitor)
+                    paintLayer->setName(getNode(i - nodesCount)->name());
+                    aboveThis = pickAboveThis();
+                    parent = d->m_nodes->at(0)->parent();
+
+                    dbgPlugins << "Adding paint layer" << (i - nodesCount + 1) << paintLayer << "to parent" << parent->name() << "above" << aboveThis;
+                }
 
-                    KisImportQmicProcessingVisitor::applyLayerNameChanges(*m_images[i], paintLayer.data(), device);
+                auto *layerNameCmd = KisQmicImportTools::applyLayerNameChanges(*d->m_images[i], paintLayer.data());
+                layerNameCmd->redo();
 
-                    addLayerCmd = new KisImageLayerAddCommand(m_image, paintLayer, parent, aboveThis, false, true);
+                addCommand(new KisCommandUtils::SkipFirstRedoWrapper(layerNameCmd));
 
-                    addLayerCmd->redo();
-                    m_imageCommands.append(addLayerCmd);
-                    m_nodes->append(paintLayer);
+                auto *addLayerCmd = new KisImageLayerAddCommand(d->m_image, paintLayer, parent, aboveThis, true, true);
 
-                }
-            }
-            else // small preview
-            {
-                Q_ASSERT(m_nodes->size() > 0);
-                for (int i = m_nodes->size(); i < m_images.size(); i++) {
-                    KisPaintDevice * device = new KisPaintDevice(m_nodes->at(0)->colorSpace());
-                    KisLayerSP paintLayer = new KisPaintLayer(0, "New layer from gmic filter", OPACITY_OPAQUE_U8, device);
-                    m_nodes->append(paintLayer);
-                }
+                addLayerCmd->redo();
+                addCommand(new KisCommandUtils::SkipFirstRedoWrapper(addLayerCmd));
+                d->m_newNodes->append(paintLayer);
             }
-        } // if gmic produces less layers, we are going to drop some
-        else if (m_nodes->size() > int(m_images.size()))
+        } else // small preview
         {
-            dbgPlugins << "no support for removing layers yet!!";
+            Q_ASSERT(!d->m_nodes->empty());
+            for (int i = d->m_nodes->size(); i < d->m_images.size(); i++) {
+                KisPaintDeviceSP device = new KisPaintDevice(d->m_nodes->at(0)->colorSpace());
+                KisLayerSP paintLayer = new KisPaintLayer(nullptr, "New layer from gmic filter", OPACITY_OPAQUE_U8, device);
+                d->m_newNodes->append(paintLayer);
+            }
         }
-    }
-    else
-    {
-        dbgPlugins << "Redo again needed?";
+    } else if (d->m_nodes->size() > int(d->m_images.size())) {
+        // if gmic produces less layers, we are going to drop some
+        errPlugins << "no support for removing layers from G'MIC yet!!";
     }
 }
 
 void KisQmicSynchronizeLayersCommand::undo()
 {
-    KisImageCommand * cmd;
-    Q_FOREACH (cmd, m_imageCommands)
-    {
-        cmd->undo();
-    }
+    KisCommandUtils::CompositeCommand::undo();
+    d->m_newNodes->clear();
 }
diff --git a/plugins/extensions/qmic/kis_qmic_synchronize_layers_command.h b/plugins/extensions/qmic/kis_qmic_synchronize_layers_command.h
index 668b9d84d1..04baa89f0e 100644
--- a/plugins/extensions/qmic/kis_qmic_synchronize_layers_command.h
+++ b/plugins/extensions/qmic/kis_qmic_synchronize_layers_command.h
@@ -1,5 +1,6 @@
 /*
- * SPDX-FileCopyrightText: 2013 Lukáš Tvrdý <lukast.dev at gmail.com
+ * SPDX-FileCopyrightText: 2013 Lukáš Tvrdý <lukast.dev at gmail.com>
+ * SPDX-FileCopyrightText: 2022 L. E. Segovia <amy at amyspark.me>
  *
  *  SPDX-License-Identifier: GPL-2.0-or-later
  */
@@ -9,42 +10,34 @@
 
 #include <kundo2command.h>
 
-#include <QSharedPointer>
-#include <QList>
-
 #include <kis_image.h>
 #include <kis_selection.h>
 #include <kis_node.h>
 #include <kis_types.h>
+#include <kis_command_utils.h>
 
 #include "gmic.h"
 
-class KisImageCommand;
-
-class KisQmicSynchronizeLayersCommand : public KUndo2Command
+class KisQmicSynchronizeLayersCommand : public KisCommandUtils::CompositeCommand
 {
 public:
     KisQmicSynchronizeLayersCommand(KisNodeListSP nodes,
                                     QVector<gmic_image<float> *> images,
                                     KisImageWSP image,
                                     const QRect &dstRect = QRect(),
-                                    const KisSelectionSP selection = 0
+                                    const KisSelectionSP selection = nullptr
     );
 
-    virtual ~KisQmicSynchronizeLayersCommand();
+    ~KisQmicSynchronizeLayersCommand() override;
 
-    virtual void redo();
-    virtual void undo();
+    void redo() override;
+    void undo() override;
 
 private:
-    KisNodeListSP m_nodes;
-    QVector<gmic_image<float> *> m_images;
-    KisImageWSP m_image;
-    QRect m_dstRect;
-    KisSelectionSP m_selection;
-    bool m_firstRedo;
-
-    QVector<KisImageCommand *> m_imageCommands;
+    struct Private;
+    Private* const d;
+
+    Q_DISABLE_COPY(KisQmicSynchronizeLayersCommand);
 };
 
 #endif


More information about the kimageshop mailing list