File indexing completed on 2025-01-26 04:10:30

0001 /*
0002  * This file is part of Krita
0003  *
0004  * SPDX-FileCopyrightText: 2020 L. E. Segovia <amy@amyspark.me>
0005  *
0006  * SPDX-License-Identifier: GPL-2.0-or-later
0007  */
0008 
0009 #include "kis_qmic_interface.h"
0010 
0011 #include <KisImageSignals.h>
0012 #include <KisViewManager.h>
0013 #include <kis_algebra_2d.h>
0014 #include <kis_debug.h>
0015 #include <kis_image.h>
0016 #include <KisImageBarrierLock.h>
0017 #include <kis_processing_applicator.h>
0018 #include <kis_selection.h>
0019 #include <kundo2magicstring.h>
0020 
0021 #include "gmic.h"
0022 #include "kis_qmic_import_tools.h"
0023 #include "kis_qmic_simple_convertor.h"
0024 #include "kis_qmic_synchronize_layers_command.h"
0025 
0026 struct KisImageInterface::Private {
0027     Private() = default;
0028 
0029     KisViewManager *m_viewManager{nullptr};
0030     InputLayerMode m_inputMode{InputLayerMode::Active};
0031     OutputMode m_outputMode{OutputMode::InPlace};
0032     QVector<KisQMicImageSP> m_sharedMemorySegments{};
0033     KisQmicApplicator *m_gmicApplicator{nullptr};
0034 };
0035 
0036 KisImageInterface::KisImageInterface(KisViewManager *parent)
0037     : p(new Private)
0038 {
0039     p->m_viewManager = parent;
0040     KIS_ASSERT(p->m_viewManager);
0041 }
0042 
0043 KisImageInterface::~KisImageInterface() = default;
0044 
0045 QSize KisImageInterface::gmic_qt_get_image_size(int mode)
0046 {
0047     if (!p->m_viewManager)
0048         return {};
0049 
0050     KisSelectionSP selection = p->m_viewManager->image()->globalSelection();
0051 
0052     if (selection) {
0053         QRect selectionRect = selection->selectedExactRect();
0054         return selectionRect.size();
0055     } else {
0056         p->m_inputMode = static_cast<InputLayerMode>(mode);
0057 
0058         QSize size(0, 0);
0059 
0060         dbgPlugins << "getImageSize()" << mode;
0061 
0062         KisNodeListSP nodes =
0063             KisQmicImportTools::inputNodes(p->m_viewManager->image(),
0064                                            p->m_inputMode,
0065                                            p->m_viewManager->activeNode());
0066         if (nodes->isEmpty()) {
0067             return size;
0068         }
0069 
0070         switch (p->m_inputMode) {
0071         case InputLayerMode::NoInput:
0072         case InputLayerMode::AllInvisible:
0073             break;
0074         case InputLayerMode::Active:
0075         case InputLayerMode::ActiveAndBelow: {
0076             // The last layer in the list is always the layer the user
0077             // has selected in Paint.NET, so it will be treated as the
0078             // active layer. The clipboard layer (if present) will be
0079             // placed above the active layer.
0080             const auto activeLayer = nodes->last();
0081 
0082             // G'MIC takes as the image size the size of the active layer.
0083             // This is a lie since a query to All will imply a query to Active
0084             // through CroppedActiveLayerProxy, and the latter will return a
0085             // bogus size if we reply straight with the paint device's bounds.
0086             if (activeLayer && activeLayer->paintDevice()) {
0087                 const QSize layerSize = activeLayer->exactBounds().size();
0088                 const QSize imageSize = activeLayer->image()->bounds().size();
0089                 size = size.expandedTo(layerSize).expandedTo(imageSize);
0090             }
0091         } break;
0092         case InputLayerMode::All:
0093         case InputLayerMode::ActiveAndAbove:
0094         case InputLayerMode::AllVisible:
0095             for (auto &node : *nodes) {
0096                 if (node && node->paintDevice()) {
0097                     // XXX: when using All, G'MIC will instead do another query
0098                     // through CroppedActiveLayerProxy to determine the image's
0099                     // "size". So we need to be both consistent with the image's
0100                     // bounds, but also extend them in case the layer's
0101                     // partially offscreen.
0102                     const QSize layerSize = node->exactBounds().size();
0103                     const QSize imageSize = node->image()->bounds().size();
0104                     size = size.expandedTo(layerSize).expandedTo(imageSize);
0105                 }
0106             }
0107             break;
0108         case InputLayerMode::AllVisiblesDesc_DEPRECATED:
0109         case InputLayerMode::AllInvisiblesDesc_DEPRECATED:
0110         case InputLayerMode::AllDesc_DEPRECATED: {
0111             warnPlugins << "Inputmode" << static_cast<int>(p->m_inputMode)
0112                         << "is not supported by GMic anymore";
0113             break;
0114         }
0115         default: {
0116             warnPlugins
0117                 << "Inputmode" << static_cast<int>(p->m_inputMode)
0118                 << "must be specified by GMic or is not implemented in Krita";
0119             break;
0120         }
0121         }
0122 
0123         return size;
0124     }
0125 }
0126 
0127 QVector<KisQMicImageSP> KisImageInterface::gmic_qt_get_cropped_images(int inputMode, QRectF &rc)
0128 {
0129     // Create the shared memory segments, and create a new "message" to send back
0130 
0131     QVector<KisQMicImageSP> message;
0132 
0133     if (!p->m_viewManager)
0134         return {};
0135 
0136     if (!p->m_viewManager->image()->tryBarrierLock(true)) return {};
0137 
0138     KisImageBarrierLock lock(p->m_viewManager->image(), std::adopt_lock);
0139 
0140     p->m_inputMode = static_cast<InputLayerMode>(inputMode);
0141 
0142     dbgPlugins << "prepareCroppedImages()" << message << rc << inputMode;
0143 
0144     KisNodeListSP nodes =
0145         KisQmicImportTools::inputNodes(p->m_viewManager->image(),
0146                                        p->m_inputMode,
0147                                        p->m_viewManager->activeNode());
0148     if (nodes->isEmpty()) {
0149         return {};
0150     }
0151 
0152     for (auto &node : *nodes) {
0153          if (node && node->paintDevice()) {
0154             KisSelectionSP selection = p->m_viewManager->image()->globalSelection();
0155 
0156             const QRect cropRect = [&]() {
0157                 if (selection) {
0158                     return selection->selectedExactRect();
0159                 } else {
0160                     // XXX: This is a lie, see gmic_qt_get_image_size as to why
0161                     const QRect nodeBounds = node->exactBounds();
0162                     const QRect imageBounds = node->image()->bounds();
0163 
0164                     if (imageBounds.contains(nodeBounds)) {
0165                         return imageBounds;
0166                     } else {
0167                         return nodeBounds.united(imageBounds);
0168                     }
0169                 }
0170             }();
0171 
0172             dbgPlugins << "Converting node" << node->name() << cropRect;
0173 
0174             const QRectF mappedRect = KisAlgebra2D::mapToRect(cropRect).mapRect(rc);
0175             const QRect resultRect = mappedRect.toAlignedRect();
0176 
0177             QString noParenthesisName(node->name());
0178             noParenthesisName.replace(QChar('('), QChar(21)).replace(QChar(')'), QChar(22));
0179 
0180             const auto translatedMode = KisQmicSimpleConvertor::blendingModeToString(node->compositeOpId());
0181 
0182             const QString name = QString("mode(%1),opacity(%2),pos(%3,%4),name(%5)")
0183                                .arg(translatedMode)
0184                                .arg(node->percentOpacity())
0185                                .arg(cropRect.x())
0186                                .arg(cropRect.y())
0187                                .arg(noParenthesisName);
0188 
0189             auto m = KisQMicImageSP::create(name, resultRect.width(), resultRect.height(), 4);
0190             p->m_sharedMemorySegments << m;
0191 
0192             {
0193                 QMutexLocker lock(&m->m_mutex);
0194 
0195                 KisQmicSimpleConvertor::convertToGmicImageFast(
0196                     node->paintDevice(),
0197                     *m.data(),
0198                     resultRect);
0199             }
0200 
0201             message << m;
0202         }
0203     }
0204 
0205     dbgPlugins << message;
0206 
0207     return message;
0208 }
0209 
0210 void KisImageInterface::gmic_qt_detach()
0211 {
0212     p->m_sharedMemorySegments.clear();
0213 }
0214 
0215 void KisImageInterface::gmic_qt_output_images(int mode, QVector<KisQMicImageSP> layers)
0216 {
0217     // Parse the message. read the shared memory segments, fix up the current image and send an ack
0218     dbgPlugins << "gmic_qt_output_images";
0219     p->m_outputMode = (OutputMode)mode;
0220     if (p->m_outputMode != OutputMode::InPlace) {
0221         errPlugins << "Requested mode" << static_cast<int>(p->m_outputMode) << "which is not implemented yet";
0222         p->m_outputMode = OutputMode::InPlace;
0223     }
0224 
0225     dbgPlugins << "slotStartApplicator();" << layers;
0226     if (!p->m_viewManager)
0227         return;
0228 
0229     dbgPlugins << "Got" << layers.size() << "gmic images";
0230 
0231     {
0232         // Start the applicator
0233         KUndo2MagicString actionName = kundo2_i18n("G'MIC filter");
0234         KisNodeSP rootNode = p->m_viewManager->image()->root();
0235         KisNodeListSP mappedLayers =
0236             KisQmicImportTools::inputNodes(p->m_viewManager->image(),
0237                                            p->m_inputMode,
0238                                            p->m_viewManager->activeNode());
0239         // p->m_gmicApplicator->setProperties(p->m_viewManager->image(), rootNode, images, actionName, layers);
0240         // p->m_gmicApplicator->apply();
0241 
0242         KisImageSignalVector emitSignals;
0243         emitSignals << ComplexSizeChangedSignal();
0244 
0245         KisProcessingApplicator applicator(p->m_viewManager->image(),
0246                                            rootNode,
0247                                            KisProcessingApplicator::RECURSIVE | KisProcessingApplicator::NO_UI_UPDATES,
0248                                            emitSignals,
0249                                            actionName);
0250         dbgPlugins << "Created applicator " << &applicator;
0251 
0252         QRect layerSize;
0253         KisSelectionSP selection = p->m_viewManager->image()->globalSelection();
0254         if (selection) {
0255             layerSize = selection->selectedExactRect();
0256         } else {
0257             layerSize = QRect(0, 0, p->m_viewManager->image()->width(), p->m_viewManager->image()->height());
0258         }
0259 
0260         // This is a three-stage process.
0261 
0262         // 1. Layer sizes must be adjusted individually
0263         // 2. synchronize layer count and convert excess GMic nodes to paint
0264         // layers
0265         // 3. visit the existing nodes and reuse them to apply the remaining
0266         // changes from GMic
0267         applicator.applyCommand(
0268             new KisQmicSynchronizeLayersCommand(mappedLayers,
0269                                                 layers,
0270                                                 p->m_viewManager->image(),
0271                                                 layerSize,
0272                                                 selection),
0273             KisStrokeJobData::SEQUENTIAL,
0274             KisStrokeJobData::EXCLUSIVE);
0275 
0276         applicator.end();
0277     }
0278 }
0279 
0280 QDebug operator<<(QDebug d, const KisQMicImage &model)
0281 {
0282     d << QString("0x%1,%2,%3,%4").arg((quintptr)&model).arg(QString(model.m_layerName.toUtf8().toHex())).arg(model.m_width).arg(model.m_height);
0283     return d;
0284 }