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 }