File indexing completed on 2024-12-22 04:16:51

0001 /*
0002  *  SPDX-FileCopyrightText: 2013 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "transform_stroke_strategy.h"
0008 
0009 #include <QMutexLocker>
0010 #include "kundo2commandextradata.h"
0011 
0012 #include "kis_node_progress_proxy.h"
0013 
0014 #include <klocalizedstring.h>
0015 #include <kis_node.h>
0016 #include <kis_group_layer.h>
0017 #include <kis_external_layer_iface.h>
0018 #include <kis_transaction.h>
0019 #include <kis_painter.h>
0020 #include <kis_transform_worker.h>
0021 #include <kis_transform_mask.h>
0022 #include "kis_transform_mask_adapter.h"
0023 #include "kis_transform_utils.h"
0024 #include "kis_abstract_projection_plane.h"
0025 #include "kis_recalculate_transform_mask_job.h"
0026 #include "kis_lod_transform.h"
0027 
0028 #include "kis_projection_leaf.h"
0029 #include "commands_new/KisSimpleModifyTransformMaskCommand.h"
0030 
0031 #include "kis_image_animation_interface.h"
0032 #include "KisAnimAutoKey.h"
0033 #include "kis_sequential_iterator.h"
0034 #include "kis_selection_mask.h"
0035 #include "kis_image_config.h"
0036 #include "kis_layer_utils.h"
0037 #include <QQueue>
0038 #include <KisDeleteLaterWrapper.h>
0039 #include "transform_transaction_properties.h"
0040 #include "krita_container_utils.h"
0041 #include "commands_new/kis_saved_commands.h"
0042 #include "commands_new/KisLazyCreateTransformMaskKeyframesCommand.h"
0043 #include "kis_command_ids.h"
0044 #include "KisRunnableStrokeJobUtils.h"
0045 #include "commands_new/KisHoldUIUpdatesCommand.h"
0046 #include "KisDecoratedNodeInterface.h"
0047 #include "kis_paint_device_debug_utils.h"
0048 #include "kis_raster_keyframe_channel.h"
0049 #include "kis_layer_utils.h"
0050 #include "KisAnimAutoKey.h"
0051 
0052 
0053 TransformStrokeStrategy::TransformStrokeStrategy(ToolTransformArgs::TransformMode mode,
0054                                                  const QString &filterId,
0055                                                  bool forceReset,
0056                                                  KisNodeList rootNodes,
0057                                                  KisSelectionSP selection,
0058                                                  KisStrokeUndoFacade *undoFacade,
0059                                                  KisUpdatesFacade *updatesFacade)
0060     : KisStrokeStrategyUndoCommandBased(kundo2_i18n("Transform"), false, undoFacade),
0061       m_updatesFacade(updatesFacade),
0062       m_mode(mode),
0063       m_filterId(filterId),
0064       m_forceReset(forceReset),
0065       m_selection(selection)
0066 {
0067     if (selection) {
0068         Q_FOREACH(KisNodeSP node, rootNodes) {
0069             KIS_SAFE_ASSERT_RECOVER_NOOP(!dynamic_cast<KisTransformMask*>(node.data()));
0070         }
0071     }
0072 
0073     m_rootNodes = rootNodes;
0074     setMacroId(KisCommandUtils::TransformToolId);
0075 }
0076 
0077 TransformStrokeStrategy::~TransformStrokeStrategy()
0078 {
0079 }
0080 
0081 KisPaintDeviceSP TransformStrokeStrategy::createDeviceCache(KisPaintDeviceSP dev)
0082 {
0083     KisPaintDeviceSP cache;
0084 
0085     if (m_selection) {
0086         QRect srcRect = m_selection->selectedExactRect();
0087 
0088         cache = dev->createCompositionSourceDevice();
0089         KisPainter gc(cache);
0090         gc.setSelection(m_selection);
0091         gc.bitBlt(srcRect.topLeft(), dev, srcRect);
0092     } else {
0093         cache = dev->createCompositionSourceDevice(dev);
0094     }
0095 
0096     return cache;
0097 }
0098 
0099 bool TransformStrokeStrategy::haveDeviceInCache(KisPaintDeviceSP src)
0100 {
0101     QMutexLocker l(&m_devicesCacheMutex);
0102     return m_devicesCacheHash.contains(src.data());
0103 }
0104 
0105 void TransformStrokeStrategy::putDeviceCache(KisPaintDeviceSP src, KisPaintDeviceSP cache)
0106 {
0107     QMutexLocker l(&m_devicesCacheMutex);
0108     m_devicesCacheHash.insert(src.data(), cache);
0109 }
0110 
0111 KisPaintDeviceSP TransformStrokeStrategy::getDeviceCache(KisPaintDeviceSP src)
0112 {
0113     QMutexLocker l(&m_devicesCacheMutex);
0114     KisPaintDeviceSP cache = m_devicesCacheHash.value(src.data());
0115     if (!cache) {
0116         warnKrita << "WARNING: Transform Stroke: the device is absent in cache!";
0117     }
0118 
0119     return cache;
0120 }
0121 
0122 bool TransformStrokeStrategy::checkBelongsToSelection(KisPaintDeviceSP device) const
0123 {
0124     return m_selection &&
0125         (device == m_selection->pixelSelection().data() ||
0126          device == m_selection->projection().data());
0127 }
0128 
0129 void TransformStrokeStrategy::doStrokeCallback(KisStrokeJobData *data)
0130 {
0131     TransformData *td = dynamic_cast<TransformData*>(data);
0132     ClearSelectionData *csd = dynamic_cast<ClearSelectionData*>(data);
0133     PreparePreviewData *ppd = dynamic_cast<PreparePreviewData*>(data);
0134     TransformAllData *runAllData = dynamic_cast<TransformAllData*>(data);
0135 
0136 
0137     if (runAllData) {
0138         // here we only save the passed args, actual
0139         // transformation will be performed during
0140         // finish job
0141         m_savedTransformArgs = runAllData->config;
0142     } else if (ppd) {
0143         KisNodeSP rootNode = m_rootNodes[0];
0144         KisNodeList processedNodes = m_processedNodes;
0145         KisPaintDeviceSP previewDevice;
0146 
0147 
0148         if (m_rootNodes.size() > 1) {
0149             const QRect bounds = rootNode->image()->bounds();
0150             const int desiredAnimTime = rootNode->image()->animationInterface()->currentTime();
0151 
0152             KisImageSP clonedImage = new KisImage(0,
0153                                                   bounds.width(),
0154                                                   bounds.height(),
0155                                                   rootNode->image()->colorSpace(),
0156                                                   "transformed_image");
0157 
0158             // BUG: 413968
0159             // Workaround: Group layers wouldn't properly render the right frame
0160             // since `clonedImage` would always have a time value of 0.
0161             clonedImage->animationInterface()->explicitlySetCurrentTime(desiredAnimTime);
0162 
0163             KisNodeSP cloneRoot = clonedImage->rootLayer();
0164 
0165             Q_FOREACH(KisNodeSP node, m_rootNodes) {
0166                 // Masks with unselected parents can't be added.
0167                 if (!node->inherits("KisMask")) {
0168                     clonedImage->addNode(node->clone().data(), cloneRoot);
0169                 }
0170             }
0171 
0172             clonedImage->refreshGraph();
0173             KisLayerUtils::refreshHiddenAreaAsync(clonedImage, cloneRoot, clonedImage->bounds());
0174 
0175             KisLayerUtils::forceAllDelayedNodesUpdate(cloneRoot);
0176             clonedImage->waitForDone();
0177 
0178             previewDevice = createDeviceCache(clonedImage->projection());
0179             previewDevice->setDefaultBounds(cloneRoot->projection()->defaultBounds());
0180 
0181             // we delete the cloned image in GUI thread to ensure
0182             // no signals are still pending
0183             makeKisDeleteLaterWrapper(clonedImage)->deleteLater();
0184         }
0185         else if (rootNode->childCount() || !rootNode->paintDevice()) {
0186             if (KisTransformMask* tmask =
0187                 dynamic_cast<KisTransformMask*>(rootNode.data())) {
0188                 previewDevice = createDeviceCache(tmask->buildPreviewDevice());
0189 
0190                 KIS_SAFE_ASSERT_RECOVER(!m_selection) {
0191                     m_selection = 0;
0192                 }
0193 
0194             } else if (KisGroupLayer *group = dynamic_cast<KisGroupLayer*>(rootNode.data())) {
0195                 const QRect bounds = group->image()->bounds();
0196                 const int desiredAnimTime = group->image()->animationInterface()->currentTime();
0197 
0198                 KisImageSP clonedImage = new KisImage(0,
0199                                                       bounds.width(),
0200                                                       bounds.height(),
0201                                                       group->colorSpace(),
0202                                                       "transformed_image");
0203 
0204                 // BUG: 413968
0205                 // Workaround: Group layers wouldn't properly render the right frame
0206                 // since `clonedImage` would always have a time value of 0.
0207                 clonedImage->animationInterface()->explicitlySetCurrentTime(desiredAnimTime);
0208 
0209                 KisGroupLayerSP clonedGroup = dynamic_cast<KisGroupLayer*>(group->clone().data());
0210 
0211                 // In case the group is pass-through, it needs to be disabled for the preview,
0212                 //   otherwise it will crash (no parent for a preview leaf).
0213                 // Also it needs to be done before setting the root layer for clonedImage.
0214                 // Result: preview for pass-through group is the same as for standard group
0215                 //   (i.e. filter layers in the group won't affect the layer stack for a moment).
0216                 clonedGroup->setPassThroughMode(false);
0217                 clonedImage->setRootLayer(clonedGroup);
0218 
0219                 QQueue<KisNodeSP> linearizedSrcNodes;
0220                 KisLayerUtils::recursiveApplyNodes(rootNode, [&linearizedSrcNodes] (KisNodeSP node) {
0221                     linearizedSrcNodes.enqueue(node);
0222                 });
0223 
0224                 KisLayerUtils::recursiveApplyNodes(KisNodeSP(clonedGroup), [&linearizedSrcNodes, processedNodes] (KisNodeSP node) {
0225                     KisNodeSP srcNode = linearizedSrcNodes.dequeue();
0226 
0227                     if (!processedNodes.contains(srcNode)) {
0228                         node->setVisible(false);
0229                     }
0230                 });
0231 
0232                 clonedImage->refreshGraph();
0233                 KisLayerUtils::refreshHiddenAreaAsync(clonedImage, clonedGroup, clonedImage->bounds());
0234 
0235                 KisLayerUtils::forceAllDelayedNodesUpdate(clonedGroup);
0236                 clonedImage->waitForDone();
0237 
0238                 previewDevice = createDeviceCache(clonedImage->projection());
0239                 previewDevice->setDefaultBounds(group->projection()->defaultBounds());
0240 
0241                 // we delete the cloned image in GUI thread to ensure
0242                 // no signals are still pending
0243                 makeKisDeleteLaterWrapper(clonedImage)->deleteLater();
0244 
0245             } else {
0246                 rootNode->projectionLeaf()->explicitlyRegeneratePassThroughProjection();
0247                 previewDevice = createDeviceCache(rootNode->projection());
0248             }
0249 
0250 
0251 
0252         } else {
0253             KisPaintDeviceSP cacheDevice = createDeviceCache(rootNode->paintDevice());
0254 
0255             if (dynamic_cast<KisSelectionMask*>(rootNode.data())) {
0256                 KIS_SAFE_ASSERT_RECOVER (cacheDevice->colorSpace()->colorModelId() == GrayAColorModelID &&
0257                                          cacheDevice->colorSpace()->colorDepthId() == Integer8BitsColorDepthID) {
0258 
0259                     cacheDevice->convertTo(KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer8BitsColorDepthID.id()));
0260                 }
0261 
0262                 previewDevice = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8());
0263                 const QRect srcRect = cacheDevice->exactBounds();
0264 
0265                 KisSequentialConstIterator srcIt(cacheDevice, srcRect);
0266                 KisSequentialIterator dstIt(previewDevice, srcRect);
0267 
0268                 const int pixelSize = previewDevice->colorSpace()->pixelSize();
0269 
0270 
0271                 KisImageConfig cfg(true);
0272                 KoColor pixel(cfg.selectionOverlayMaskColor(), previewDevice->colorSpace());
0273 
0274                 const qreal coeff = 1.0 / 255.0;
0275                 const qreal baseOpacity = 0.5;
0276 
0277                 while (srcIt.nextPixel() && dstIt.nextPixel()) {
0278                     qreal gray = srcIt.rawDataConst()[0];
0279                     qreal alpha = srcIt.rawDataConst()[1];
0280 
0281                     pixel.setOpacity(quint8(gray * alpha * baseOpacity * coeff));
0282                     memcpy(dstIt.rawData(), pixel.data(), pixelSize);
0283                 }
0284 
0285             } else {
0286                 previewDevice = cacheDevice;
0287             }
0288 
0289             putDeviceCache(rootNode->paintDevice(), cacheDevice);
0290         }
0291 
0292         emit sigPreviewDeviceReady(previewDevice);
0293     }
0294     else if (td) {
0295         if (td->destination == TransformData::PAINT_DEVICE) {
0296             QRect oldExtent = td->node->projectionPlane()->tightUserVisibleBounds();
0297             KisPaintDeviceSP device = td->node->paintDevice();
0298 
0299             if (device && !checkBelongsToSelection(device)) {
0300                 KisPaintDeviceSP cachedPortion = getDeviceCache(device);
0301                 Q_ASSERT(cachedPortion);
0302 
0303                 KisTransaction transaction(device);
0304 
0305                 KisProcessingVisitor::ProgressHelper helper(td->node);
0306                 KisTransformUtils::transformAndMergeDevice(td->config, cachedPortion,
0307                                                            device, &helper);
0308 
0309                 runAndSaveCommand(KUndo2CommandSP(transaction.endAndTake()),
0310                                   KisStrokeJobData::CONCURRENT,
0311                                   KisStrokeJobData::NORMAL);
0312 
0313                 m_updateData->addUpdate(td->node, cachedPortion->extent() | oldExtent | td->node->projectionPlane()->tightUserVisibleBounds());
0314             } else if (KisExternalLayer *extLayer =
0315                   dynamic_cast<KisExternalLayer*>(td->node.data())) {
0316 
0317                 if (td->config.mode() == ToolTransformArgs::FREE_TRANSFORM ||
0318                     (td->config.mode() == ToolTransformArgs::PERSPECTIVE_4POINT &&
0319                      extLayer->supportsPerspectiveTransform())) {
0320 
0321                     QRect oldDirtyRect = oldExtent | extLayer->theoreticalBoundingRect();
0322 
0323                     QVector3D transformedCenter;
0324                     KisTransformWorker w = KisTransformUtils::createTransformWorker(td->config, 0, 0, &transformedCenter);
0325                     QTransform t = w.transform();
0326 
0327                     runAndSaveCommand(KUndo2CommandSP(extLayer->transform(t)),
0328                                       KisStrokeJobData::CONCURRENT,
0329                                       KisStrokeJobData::NORMAL);
0330 
0331                     /**
0332                      * Shape layer's projection may not be yet ready right
0333                      * after transformation, because it need to do that in
0334                      * the GUI thread, so we should approximate that.
0335                      */
0336                     const QRect theoreticalNewDirtyRect =
0337                         kisGrowRect(t.mapRect(oldDirtyRect), 1);
0338 
0339                     m_updateData->addUpdate(td->node, oldDirtyRect | td->node->projectionPlane()->tightUserVisibleBounds() | extLayer->theoreticalBoundingRect() | theoreticalNewDirtyRect);
0340                 }
0341 
0342             } else if (KisTransformMask *transformMask =
0343                        dynamic_cast<KisTransformMask*>(td->node.data())) {
0344 
0345                 runAndSaveCommand(KUndo2CommandSP(
0346                                       new KisSimpleModifyTransformMaskCommand(transformMask,
0347                                                                               KisTransformMaskParamsInterfaceSP(
0348                                                                                   new KisTransformMaskAdapter(td->config)))),
0349                                   KisStrokeJobData::CONCURRENT,
0350                                   KisStrokeJobData::NORMAL);
0351 
0352                 m_updateData->addUpdate(td->node, oldExtent | td->node->extent());
0353             }
0354         } else if (m_selection) {
0355 
0356             /**
0357              * We use usual transaction here, because we cannot calculate
0358              * transformation for perspective and warp workers.
0359              */
0360             KisTransaction transaction(m_selection->pixelSelection());
0361 
0362             KisProcessingVisitor::ProgressHelper helper(td->node);
0363             KisTransformUtils::transformDevice(td->config,
0364                                                m_selection->pixelSelection(),
0365                                                &helper);
0366 
0367             runAndSaveCommand(KUndo2CommandSP(transaction.endAndTake()),
0368                               KisStrokeJobData::CONCURRENT,
0369                               KisStrokeJobData::NORMAL);
0370         }
0371     } else if (csd) {
0372         KisPaintDeviceSP device = csd->node->paintDevice();
0373 
0374         if (device && !checkBelongsToSelection(device)) {
0375             if (!haveDeviceInCache(device)) {
0376                 putDeviceCache(device, createDeviceCache(device));
0377             }
0378             clearSelection(device);
0379 
0380             /**
0381              * Selection masks might have an overlay enabled, we should disable that
0382              */
0383             if (KisSelectionMask *mask = dynamic_cast<KisSelectionMask*>(csd->node.data())) {
0384                 KisSelectionSP selection = mask->selection();
0385                 if (selection) {
0386                     selection->setVisible(false);
0387                     m_deactivatedSelections.append(selection);
0388                 }
0389             }
0390         } else if (KisExternalLayer *externalLayer = dynamic_cast<KisExternalLayer*>(csd->node.data())) {
0391             externalLayer->projectionLeaf()->setTemporaryHiddenFromRendering(true);
0392             m_hiddenProjectionLeaves.append(csd->node);
0393         } else if (KisTransformMask *transformMask =
0394                    dynamic_cast<KisTransformMask*>(csd->node.data())) {
0395 
0396             KisTransformMaskParamsInterfaceSP params = transformMask->transformParams();
0397             params->setHidden(true);
0398 
0399             runAndSaveCommand(KUndo2CommandSP(
0400                                   new KisSimpleModifyTransformMaskCommand(transformMask,
0401                                                                           params)),
0402                                   KisStrokeJobData::SEQUENTIAL,
0403                                   KisStrokeJobData::NORMAL);
0404         }
0405     } else {
0406         KisStrokeStrategyUndoCommandBased::doStrokeCallback(data);
0407     }
0408 }
0409 
0410 void TransformStrokeStrategy::clearSelection(KisPaintDeviceSP device)
0411 {
0412     KisTransaction transaction(device);
0413     if (m_selection) {
0414         device->clearSelection(m_selection);
0415     } else {
0416         device->clear();
0417     }
0418     runAndSaveCommand(KUndo2CommandSP(transaction.endAndTake()),
0419                       KisStrokeJobData::SEQUENTIAL,
0420                       KisStrokeJobData::NORMAL);
0421 }
0422 
0423 void TransformStrokeStrategy::postProcessToplevelCommand(KUndo2Command *command)
0424 {
0425     KIS_SAFE_ASSERT_RECOVER_RETURN(m_savedTransformArgs);
0426 
0427     KisTransformUtils::postProcessToplevelCommand(command,
0428                                                   *m_savedTransformArgs,
0429                                                   m_rootNodes,
0430                                                   m_processedNodes,
0431                                                   m_currentTime,
0432                                                   m_overriddenCommand);
0433 
0434     KisStrokeStrategyUndoCommandBased::postProcessToplevelCommand(command);
0435 }
0436 
0437 void TransformStrokeStrategy::initStrokeCallback()
0438 {
0439     KisStrokeStrategyUndoCommandBased::initStrokeCallback();
0440 
0441     m_currentTime = KisTransformUtils::fetchCurrentImageTime(m_rootNodes);
0442 
0443     if (m_selection) {
0444         m_selection->setVisible(false);
0445         m_deactivatedSelections.append(m_selection);
0446     }
0447 
0448     Q_FOREACH(KisNodeSP node, m_rootNodes) {
0449         KisSelectionMaskSP overlaySelectionMask =
0450                 dynamic_cast<KisSelectionMask*>(node->graphListener()->graphOverlayNode());
0451         if (overlaySelectionMask) {
0452             overlaySelectionMask->setDecorationsVisible(false);
0453             m_deactivatedOverlaySelectionMasks.append(overlaySelectionMask);
0454         }
0455     }
0456 
0457     if (m_rootNodes.size() == 1){
0458         KisNodeSP rootNode = m_rootNodes[0];
0459         rootNode = KisTransformUtils::tryOverrideRootToTransformMask(rootNode);
0460 
0461         if (rootNode->inherits("KisTransformMask") && rootNode->projectionLeaf()->isDroppedNode()) {
0462             rootNode.clear();
0463             m_processedNodes.clear();
0464 
0465             TransformTransactionProperties transaction(QRect(), &m_initialTransformArgs, m_rootNodes, m_processedNodes);
0466             Q_EMIT sigTransactionGenerated(transaction, m_initialTransformArgs, this);
0467             return;
0468         }
0469     }
0470 
0471     ToolTransformArgs initialTransformArgs;
0472     bool isExternalSourcePresent = false;
0473     m_processedNodes = KisTransformUtils::fetchNodesList(m_mode, m_rootNodes, isExternalSourcePresent, m_selection);
0474 
0475     bool argsAreInitialized = false;
0476     QVector<KisStrokeJobData *> lastCommandUndoJobs;
0477 
0478     if (!m_forceReset && KisTransformUtils::tryFetchArgsFromCommandAndUndo(&initialTransformArgs,
0479                                                                            m_mode,
0480                                                                            m_rootNodes,
0481                                                                            m_processedNodes,
0482                                                                            undoFacade(),
0483                                                                            m_currentTime,
0484                                                                            &lastCommandUndoJobs,
0485                                                                            &m_overriddenCommand)) {
0486         argsAreInitialized = true;
0487     } else if (!m_forceReset && KisTransformUtils::tryInitArgsFromNode(m_rootNodes, &initialTransformArgs)) {
0488         argsAreInitialized = true;
0489     }
0490 
0491     QVector<KisStrokeJobData *> extraInitJobs;
0492 
0493     extraInitJobs << new Data(new KisHoldUIUpdatesCommand(m_updatesFacade, KisCommandUtils::FlipFlopCommand::INITIALIZING), false, KisStrokeJobData::BARRIER);
0494 
0495     extraInitJobs << lastCommandUndoJobs;
0496 
0497     KritaUtils::addJobSequential(extraInitJobs, [this]() {
0498         // When dealing with animated transform mask layers, create keyframe and save the command for undo.
0499         // NOTE: for transform masks we create a keyframe no matter what the user
0500         //       settigs are
0501         Q_FOREACH (KisNodeSP node, m_processedNodes) {
0502             if (KisTransformMask* transformMask = dynamic_cast<KisTransformMask*>(node.data())) {
0503                 if (KisLazyCreateTransformMaskKeyframesCommand::maskHasAnimation(transformMask)) {
0504                     runAndSaveCommand(toQShared(new KisLazyCreateTransformMaskKeyframesCommand(transformMask)), KisStrokeJobData::BARRIER, KisStrokeJobData::NORMAL);
0505                 }
0506             } else if (KisAutoKey::activeMode() > KisAutoKey::NONE &&
0507                        node->hasEditablePaintDevice()){
0508 
0509                 KUndo2Command *autoKeyframeCommand =
0510                         KisAutoKey::tryAutoCreateDuplicatedFrame(node->paintDevice(),
0511                                                                  KisAutoKey::SupportsLod);
0512                 if (autoKeyframeCommand) {
0513                     runAndSaveCommand(toQShared(autoKeyframeCommand), KisStrokeJobData::BARRIER, KisStrokeJobData::NORMAL);
0514                 }
0515             }
0516         }
0517     });
0518 
0519     KritaUtils::addJobSequential(extraInitJobs, [this]() {
0520         /**
0521          * We must request shape layers to rerender areas outside image bounds
0522          */
0523         Q_FOREACH(KisNodeSP node, m_rootNodes) {
0524             KisLayerUtils::forceAllHiddenOriginalsUpdate(node);
0525         }
0526     });
0527 
0528     KritaUtils::addJobBarrier(extraInitJobs, [this]() {
0529         /**
0530          * We must ensure that the currently selected subtree
0531          * has finished all its updates.
0532          */
0533         Q_FOREACH(KisNodeSP node, m_rootNodes) {
0534             KisLayerUtils::forceAllDelayedNodesUpdate(node);
0535         }
0536     });
0537 
0538     /// Disable all decorated nodes to generate outline
0539     /// and preview correctly. We will enable them back
0540     /// as soon as preview generation is finished.
0541     KritaUtils::addJobBarrier(extraInitJobs, [this]() {
0542         Q_FOREACH (KisNodeSP node, m_processedNodes) {
0543             KisDecoratedNodeInterface *decoratedNode = dynamic_cast<KisDecoratedNodeInterface*>(node.data());
0544             if (decoratedNode && decoratedNode->decorationsVisible()) {
0545                 decoratedNode->setDecorationsVisible(false);
0546                 m_disabledDecoratedNodes << decoratedNode;
0547             }
0548         }
0549     });
0550 
0551     KritaUtils::addJobBarrier(extraInitJobs, [this, initialTransformArgs, argsAreInitialized]() mutable {
0552         QRect srcRect;
0553 
0554         if (m_selection) {
0555             srcRect = m_selection->selectedExactRect();
0556         } else {
0557             srcRect = QRect();
0558             Q_FOREACH (KisNodeSP node, m_processedNodes) {
0559                 // group layers may have a projection of layers
0560                 // that are locked and will not be transformed
0561                 if (node->inherits("KisGroupLayer")) continue;
0562 
0563                 if (const KisTransformMask *mask = dynamic_cast<const KisTransformMask*>(node.data())) {
0564                     srcRect |= mask->sourceDataBounds();
0565                 } else if (const KisSelectionMask *mask = dynamic_cast<const KisSelectionMask*>(node.data())) {
0566                     srcRect |= mask->selection()->selectedExactRect();
0567                 } else {
0568                     /// We shouldn't include masks or layer styles into the handles rect,
0569                     /// in the end, we process the paint device only
0570                     srcRect |= node->paintDevice() ? node->paintDevice()->exactBounds() : node->exactBounds();
0571                 }
0572             }
0573         }
0574 
0575         TransformTransactionProperties transaction(srcRect, &initialTransformArgs, m_rootNodes, m_processedNodes);
0576         if (!argsAreInitialized) {
0577             initialTransformArgs = KisTransformUtils::resetArgsForMode(m_mode, m_filterId, transaction, 0);
0578         }
0579 
0580         this->m_initialTransformArgs = initialTransformArgs;
0581         emit this->sigTransactionGenerated(transaction, initialTransformArgs, this);
0582     });
0583 
0584     extraInitJobs << new PreparePreviewData();
0585 
0586     KisBatchNodeUpdateSP sharedData(new KisBatchNodeUpdate());
0587 
0588     KritaUtils::addJobBarrier(extraInitJobs, [this, sharedData]() {
0589         KisNodeList filteredRoots = KisLayerUtils::sortAndFilterMergeableInternalNodes(m_processedNodes, true);
0590         Q_FOREACH (KisNodeSP root, filteredRoots) {
0591             sharedData->addUpdate(root, root->projectionPlane()->tightUserVisibleBounds());
0592         }
0593     });
0594 
0595     extraInitJobs << new Data(new KisUpdateCommandEx(sharedData, m_updatesFacade, KisUpdateCommandEx::INITIALIZING), false, Data::BARRIER);
0596 
0597     Q_FOREACH (KisNodeSP node, m_processedNodes) {
0598         extraInitJobs << new ClearSelectionData(node);
0599     }
0600 
0601     extraInitJobs << new Data(new KisUpdateCommandEx(sharedData, m_updatesFacade, KisUpdateCommandEx::FINALIZING), false, Data::BARRIER);
0602 
0603     /// recover back visibility of decorated nodes
0604     KritaUtils::addJobBarrier(extraInitJobs, [this]() {
0605         Q_FOREACH (KisDecoratedNodeInterface *decoratedNode, m_disabledDecoratedNodes) {
0606             decoratedNode->setDecorationsVisible(true);
0607         }
0608         m_disabledDecoratedNodes.clear();
0609     });
0610 
0611     extraInitJobs << new Data(toQShared(new KisHoldUIUpdatesCommand(m_updatesFacade, KisCommandUtils::FlipFlopCommand::FINALIZING)), false, KisStrokeJobData::BARRIER);
0612 
0613     if (!lastCommandUndoJobs.isEmpty()) {
0614         KIS_SAFE_ASSERT_RECOVER_NOOP(m_overriddenCommand);
0615 
0616         for (auto it = extraInitJobs.begin(); it != extraInitJobs.end(); ++it) {
0617             (*it)->setCancellable(false);
0618         }
0619     }
0620 
0621     addMutatedJobs(extraInitJobs);
0622 }
0623 
0624 void TransformStrokeStrategy::finishStrokeImpl(bool applyTransform, const ToolTransformArgs &args)
0625 {
0626     /**
0627      * Since our finishStrokeCallback() initiates new jobs,
0628      * cancellation request may come even after
0629      * finishStrokeCallback() (cancellations may be called
0630      * until there are no jobs left in the stroke's queue).
0631      *
0632      * Therefore we should check for double-entry here and
0633      * make sure the finalizing jobs are no cancellable.
0634      */
0635 
0636     if (m_finalizingActionsStarted) return;
0637     m_finalizingActionsStarted = true;
0638 
0639     QVector<KisStrokeJobData *> mutatedJobs;
0640 
0641     auto restoreTemporaryHiddenNodes = [this] () {
0642         Q_FOREACH (KisNodeSP node, m_hiddenProjectionLeaves) {
0643             node->projectionLeaf()->setTemporaryHiddenFromRendering(false);
0644             if (KisDelayedUpdateNodeInterface *delayedNode = dynamic_cast<KisDelayedUpdateNodeInterface*>(node.data())) {
0645                 delayedNode->forceUpdateTimedNode();
0646             } else {
0647                 node->setDirty();
0648             }
0649         }
0650     };
0651 
0652     if (applyTransform) {
0653         m_savedTransformArgs = args;
0654 
0655         m_updateData.reset(new KisBatchNodeUpdate());
0656 
0657         KritaUtils::addJobBarrier(mutatedJobs, [this] () {
0658             runAndSaveCommand(toQShared(new KisUpdateCommandEx(m_updateData, m_updatesFacade, KisUpdateCommandEx::INITIALIZING)), KisStrokeJobData::BARRIER, KisStrokeJobData::NORMAL);
0659             m_updatesDisabled = true;
0660             m_updatesFacade->disableDirtyRequests();
0661         });
0662 
0663         Q_FOREACH (KisNodeSP node, m_processedNodes) {
0664             mutatedJobs << new TransformData(TransformData::PAINT_DEVICE,
0665                                              args,
0666                                              node);
0667         }
0668         mutatedJobs << new TransformData(TransformData::SELECTION,
0669                                          args,
0670                                          m_rootNodes[0]);
0671 
0672         KritaUtils::addJobBarrier(mutatedJobs, restoreTemporaryHiddenNodes);
0673 
0674         KritaUtils::addJobBarrier(mutatedJobs, [this] () {
0675             m_updatesFacade->enableDirtyRequests();
0676             m_updatesDisabled = false;
0677 
0678             m_updateData->compress();
0679             runAndSaveCommand(toQShared(new KisUpdateCommandEx(m_updateData, m_updatesFacade, KisUpdateCommandEx::FINALIZING)), KisStrokeJobData::BARRIER, KisStrokeJobData::NORMAL);
0680         });
0681     } else {
0682         KritaUtils::addJobBarrier(mutatedJobs, restoreTemporaryHiddenNodes);
0683     }
0684 
0685     KritaUtils::addJobBarrier(mutatedJobs, [this, applyTransform]() {
0686         Q_FOREACH (KisSelectionSP selection, m_deactivatedSelections) {
0687             selection->setVisible(true);
0688         }
0689 
0690         Q_FOREACH(KisSelectionMaskSP deactivatedOverlaySelectionMask, m_deactivatedOverlaySelectionMasks) {
0691             deactivatedOverlaySelectionMask->selection()->setVisible(true);
0692             deactivatedOverlaySelectionMask->setDirty();
0693         }
0694 
0695         if (applyTransform) {
0696             KisStrokeStrategyUndoCommandBased::finishStrokeCallback();
0697         } else {
0698             KisStrokeStrategyUndoCommandBased::cancelStrokeCallback();
0699         }
0700     });
0701 
0702     for (auto it = mutatedJobs.begin(); it != mutatedJobs.end(); ++it) {
0703         (*it)->setCancellable(false);
0704     }
0705 
0706     addMutatedJobs(mutatedJobs);
0707 }
0708 
0709 void TransformStrokeStrategy::finishStrokeCallback()
0710 {
0711     if (!m_savedTransformArgs || m_savedTransformArgs->isUnchanging()) {
0712         cancelStrokeCallback();
0713         return;
0714     }
0715 
0716     finishStrokeImpl(true, *m_savedTransformArgs);
0717 }
0718 
0719 void TransformStrokeStrategy::cancelStrokeCallback()
0720 {
0721     if (m_updatesDisabled) {
0722         m_updatesFacade->enableDirtyRequests();
0723     }
0724 
0725     finishStrokeImpl(!m_initialTransformArgs.isUnchanging(), m_initialTransformArgs);
0726 }