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 }