File indexing completed on 2024-12-22 04:16:51
0001 /* 0002 * SPDX-FileCopyrightText: 2013,2020 Dmitry Kazakov <dimula73@gmail.com> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "inplace_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 0027 #include "kis_projection_leaf.h" 0028 #include "commands_new/KisSimpleModifyTransformMaskCommand.h" 0029 #include "KisAnimAutoKey.h" 0030 0031 #include "kis_sequential_iterator.h" 0032 #include "kis_selection_mask.h" 0033 #include "kis_image_config.h" 0034 #include "kis_layer_utils.h" 0035 #include <QQueue> 0036 #include <KisDeleteLaterWrapper.h> 0037 #include "transform_transaction_properties.h" 0038 #include "krita_container_utils.h" 0039 #include "commands_new/kis_saved_commands.h" 0040 #include "commands_new/KisLazyCreateTransformMaskKeyframesCommand.h" 0041 #include "kis_command_ids.h" 0042 #include "KisRunnableStrokeJobUtils.h" 0043 #include "commands_new/KisHoldUIUpdatesCommand.h" 0044 #include "KisDecoratedNodeInterface.h" 0045 #include "kis_sync_lod_cache_stroke_strategy.h" 0046 #include "kis_lod_transform.h" 0047 #include <boost/optional.hpp> 0048 #include "kis_selection_mask.h" 0049 #include "kis_undo_stores.h" 0050 #include "kis_transparency_mask.h" 0051 #include "commands_new/KisDisableDirtyRequestsCommand.h" 0052 #include <kis_shape_layer.h> 0053 #include "kis_raster_keyframe_channel.h" 0054 #include "kis_image_animation_interface.h" 0055 #include "KisAnimAutoKey.h" 0056 0057 0058 struct InplaceTransformStrokeStrategy::Private 0059 { 0060 // initial conditions passed from the tool 0061 KisUpdatesFacade *updatesFacade; 0062 KisStrokeUndoFacade *undoFacade; 0063 ToolTransformArgs::TransformMode mode; 0064 QString filterId; 0065 bool forceReset; 0066 KisNodeList rootNodes; 0067 KisSelectionSP selection; 0068 KisPaintDeviceSP externalSource; 0069 KisNodeSP imageRoot; 0070 int currentTime = -1; // NOTE: initialized asynchronously in initStrokeCallback! 0071 int previewLevelOfDetail = -1; 0072 bool forceLodMode = true; 0073 0074 // properties filled by initialization/transformation routines 0075 KisNodeList processedNodes; 0076 ToolTransformArgs initialTransformArgs; 0077 ToolTransformArgs currentTransformArgs; 0078 0079 const KisSavedMacroCommand *overriddenCommand = 0; 0080 0081 QList<KisSelectionSP> deactivatedSelections; 0082 QList<KisSelectionMaskSP> deactivatedOverlaySelectionMasks; 0083 0084 QMutex commandsMutex; 0085 0086 struct SavedCommand { 0087 CommandGroup commandGroup; 0088 KUndo2CommandSP command; 0089 KisStrokeJobData::Sequentiality sequentiality; 0090 }; 0091 0092 QVector<SavedCommand> commands; 0093 0094 QMutex devicesCacheMutex; 0095 QHash<KisPaintDevice*, KisPaintDeviceSP> devicesCacheHash; 0096 QHash<KisTransformMask*, KisPaintDeviceSP> transformMaskCacheHash; 0097 0098 QMutex dirtyRectsMutex; 0099 KisBatchNodeUpdate dirtyRects; 0100 KisBatchNodeUpdate prevDirtyRects; 0101 0102 KisBatchNodeUpdate dirtyPreviewRects; 0103 KisBatchNodeUpdate prevDirtyPreviewRects; 0104 0105 inline KisBatchNodeUpdate& effectiveDirtyRects(int levelOfDetail) { 0106 return levelOfDetail > 0 ? dirtyPreviewRects : dirtyRects; 0107 } 0108 0109 inline KisBatchNodeUpdate& effectivePrevDirtyRects(int levelOfDetail) { 0110 return levelOfDetail > 0 ? prevDirtyPreviewRects : prevDirtyRects; 0111 } 0112 0113 // data for asynchronous updates 0114 boost::optional<ToolTransformArgs> pendingUpdateArgs; 0115 QElapsedTimer updateTimer; 0116 const int updateInterval = 30; 0117 0118 // temporary variable to share data between jobs at the initialization phase 0119 QVector<KisDecoratedNodeInterface*> disabledDecoratedNodes; 0120 0121 /** 0122 * A special cookie-object, which blocks updates in transform mask 0123 * modification commands until the stroke ends. As soon as the stroke 0124 * ends, the object is destroyed and the transform mask modification 0125 * commands start to behave normally. 0126 */ 0127 QSharedPointer<boost::none_t> commandUpdatesBlockerCookie; 0128 0129 bool strokeCompletionHasBeenStarted = false; 0130 0131 KisBatchNodeUpdateSP updateDataForUndo; 0132 KisBatchNodeUpdate initialUpdatesBeforeClear; 0133 }; 0134 0135 0136 InplaceTransformStrokeStrategy::InplaceTransformStrokeStrategy(ToolTransformArgs::TransformMode mode, 0137 const QString &filterId, 0138 bool forceReset, 0139 KisNodeList rootNodes, 0140 KisSelectionSP selection, 0141 KisPaintDeviceSP externalSource, 0142 KisStrokeUndoFacade *undoFacade, 0143 KisUpdatesFacade *updatesFacade, 0144 KisNodeSP imageRoot, 0145 bool forceLodMode) 0146 : KisStrokeStrategyUndoCommandBased(kundo2_i18n("Transform"), false, undoFacade), 0147 m_d(new Private()) 0148 { 0149 0150 m_d->mode = mode; 0151 m_d->filterId = filterId; 0152 m_d->forceReset = forceReset; 0153 m_d->rootNodes = rootNodes; 0154 m_d->selection = selection; 0155 m_d->externalSource = externalSource; 0156 m_d->updatesFacade = updatesFacade; 0157 m_d->undoFacade = undoFacade; 0158 m_d->imageRoot = imageRoot; 0159 m_d->forceLodMode = forceLodMode; 0160 m_d->commandUpdatesBlockerCookie.reset(new boost::none_t(boost::none)); 0161 0162 if (selection) { 0163 Q_FOREACH(KisNodeSP node, rootNodes) { 0164 KIS_SAFE_ASSERT_RECOVER_NOOP(!dynamic_cast<KisTransformMask*>(node.data())); 0165 } 0166 } 0167 setMacroId(KisCommandUtils::TransformToolId); 0168 0169 // TODO: check if can be relaxed 0170 setNeedsExplicitCancel(true); 0171 } 0172 0173 InplaceTransformStrokeStrategy::~InplaceTransformStrokeStrategy() 0174 { 0175 } 0176 0177 void InplaceTransformStrokeStrategy::doStrokeCallback(KisStrokeJobData *data) 0178 { 0179 if (UpdateTransformData *upd = dynamic_cast<UpdateTransformData*>(data)) { 0180 if (upd->destination == UpdateTransformData::PAINT_DEVICE) { 0181 m_d->pendingUpdateArgs = upd->args; 0182 tryPostUpdateJob(false); 0183 } else if (m_d->selection) { 0184 // NOTE: selection is hidden during the transformation, so we 0185 // don't have to do any preview for that. We transform 0186 // that in one go in the end of the stroke. 0187 0188 KisTransaction transaction(m_d->selection->pixelSelection()); 0189 0190 KisProcessingVisitor::ProgressHelper helper(m_d->imageRoot.data()); 0191 KisTransformUtils::transformDevice(upd->args, 0192 m_d->selection->pixelSelection(), &helper); 0193 0194 runAndSaveCommand(KUndo2CommandSP(transaction.endAndTake()), 0195 KisStrokeJobData::CONCURRENT, KisStrokeJobData::NORMAL); 0196 } 0197 0198 } else if (BarrierUpdateData *barrierData = dynamic_cast<BarrierUpdateData *>(data)) { 0199 0200 doCanvasUpdate(barrierData->forceUpdate); 0201 0202 } else if (KisAsynchronousStrokeUpdateHelper::UpdateData *updateData = 0203 dynamic_cast<KisAsynchronousStrokeUpdateHelper::UpdateData*>(data)) { 0204 0205 tryPostUpdateJob(updateData->forceUpdate); 0206 0207 } else { 0208 KisStrokeStrategyUndoCommandBased::doStrokeCallback(data); 0209 } 0210 0211 } 0212 0213 void InplaceTransformStrokeStrategy::tryPostUpdateJob(bool forceUpdate) 0214 { 0215 if (!m_d->pendingUpdateArgs) return; 0216 0217 if (forceUpdate || 0218 (m_d->updateTimer.elapsed() > m_d->updateInterval && 0219 !m_d->updatesFacade->hasUpdatesRunning())) { 0220 0221 addMutatedJob(new BarrierUpdateData(forceUpdate)); 0222 } 0223 } 0224 0225 void InplaceTransformStrokeStrategy::doCanvasUpdate(bool forceUpdate) 0226 { 0227 if (!m_d->pendingUpdateArgs) return; 0228 0229 if (!forceUpdate && 0230 (m_d->updateTimer.elapsed() < m_d->updateInterval || 0231 m_d->updatesFacade->hasUpdatesRunning())) { 0232 0233 return; 0234 } 0235 0236 QVector<KisStrokeJobData *> jobs; 0237 0238 ToolTransformArgs args = *m_d->pendingUpdateArgs; 0239 m_d->pendingUpdateArgs = boost::none; 0240 0241 reapplyTransform(args, jobs, m_d->previewLevelOfDetail, false); 0242 0243 KritaUtils::addJobBarrier(jobs, [this, args]() { 0244 m_d->currentTransformArgs = args; 0245 m_d->updateTimer.restart(); 0246 // sanity check that no job has been squeezed in between 0247 KIS_SAFE_ASSERT_RECOVER_RETURN(!m_d->pendingUpdateArgs); 0248 }); 0249 0250 addMutatedJobs(jobs); 0251 } 0252 0253 int InplaceTransformStrokeStrategy::calculatePreferredLevelOfDetail(const QRect &srcRect) 0254 { 0255 KisLodPreferences lodPreferences = this->currentLodPreferences(); 0256 if (!lodPreferences.lodSupported() || 0257 !(lodPreferences.lodPreferred() || m_d->forceLodMode)) return -1; 0258 0259 const int maxSize = 2000; 0260 const int maxDimension = KisAlgebra2D::maxDimension(srcRect); 0261 0262 const qreal zoom = qMax(1.0, qreal(maxDimension) / maxSize); 0263 0264 const int calculatedLod = qCeil(std::log2(zoom)); 0265 0266 return qMax(calculatedLod, lodPreferences.desiredLevelOfDetail()); 0267 0268 } 0269 0270 0271 void InplaceTransformStrokeStrategy::postProcessToplevelCommand(KUndo2Command *command) 0272 { 0273 KisTransformUtils::postProcessToplevelCommand(command, 0274 m_d->currentTransformArgs, 0275 m_d->rootNodes, 0276 m_d->processedNodes, 0277 m_d->currentTime, 0278 m_d->overriddenCommand); 0279 0280 KisStrokeStrategyUndoCommandBased::postProcessToplevelCommand(command); 0281 } 0282 0283 0284 0285 void InplaceTransformStrokeStrategy::initStrokeCallback() 0286 { 0287 KisStrokeStrategyUndoCommandBased::initStrokeCallback(); 0288 0289 m_d->currentTime = KisTransformUtils::fetchCurrentImageTime(m_d->rootNodes); 0290 0291 QVector<KisStrokeJobData *> extraInitJobs; 0292 0293 if (m_d->selection) { 0294 m_d->selection->setVisible(false); 0295 m_d->deactivatedSelections.append(m_d->selection); 0296 } 0297 0298 Q_FOREACH(KisNodeSP node, m_d->rootNodes) { 0299 KisSelectionMaskSP overlaySelectionMask = 0300 dynamic_cast<KisSelectionMask*>(node->graphListener()->graphOverlayNode()); 0301 if (overlaySelectionMask && node != KisNodeSP(overlaySelectionMask)) { 0302 overlaySelectionMask->setDecorationsVisible(false); 0303 m_d->deactivatedOverlaySelectionMasks.append(overlaySelectionMask); 0304 } 0305 } 0306 0307 if (m_d->rootNodes.size() == 1) { 0308 KisNodeSP rootNode = m_d->rootNodes[0]; 0309 rootNode = KisTransformUtils::tryOverrideRootToTransformMask(rootNode); 0310 0311 if (rootNode->inherits("KisTransformMask") && rootNode->projectionLeaf()->isDroppedNode()) { 0312 rootNode.clear(); 0313 m_d->processedNodes.clear(); 0314 0315 TransformTransactionProperties transaction(QRect(), &m_d->initialTransformArgs, m_d->rootNodes, m_d->processedNodes); 0316 Q_EMIT sigTransactionGenerated(transaction, m_d->initialTransformArgs, this); 0317 return; 0318 } 0319 } 0320 0321 // When placing an external source image, we never work recursively on any layer masks 0322 m_d->processedNodes = KisTransformUtils::fetchNodesList(m_d->mode, m_d->rootNodes, m_d->externalSource, m_d->selection); 0323 0324 bool argsAreInitialized = false; 0325 QVector<KisStrokeJobData *> lastCommandUndoJobs; 0326 0327 // When externalSource is set, it means that we are initializing a new 0328 // stroke following a newActivationWithExternalSource, thus we never try 0329 // to reuse an existing transformation from the undo queue. However, when 0330 // externalSource is not set, tryFetchArgsFromCommandAndUndo may still 0331 // recover a previous stroke that referenced an external source. 0332 if (!m_d->forceReset && !m_d->externalSource) { 0333 if (KisTransformUtils::tryFetchArgsFromCommandAndUndo(&m_d->initialTransformArgs, 0334 m_d->mode, 0335 m_d->rootNodes, 0336 m_d->processedNodes, 0337 m_d->undoFacade, 0338 m_d->currentTime, 0339 &lastCommandUndoJobs, 0340 &m_d->overriddenCommand)) { 0341 argsAreInitialized = true; 0342 } else if (KisTransformUtils::tryInitArgsFromNode(m_d->rootNodes, &m_d->initialTransformArgs)) { 0343 argsAreInitialized = true; 0344 } 0345 } 0346 0347 KritaUtils::addJobBarrier(extraInitJobs, [this]() { 0348 Q_FOREACH (KisNodeSP node, m_d->processedNodes) { 0349 m_d->prevDirtyRects.addUpdate(node, node->projectionPlane()->tightUserVisibleBounds()); 0350 } 0351 0352 m_d->initialUpdatesBeforeClear = m_d->prevDirtyRects.compressed(); 0353 m_d->updateDataForUndo.reset(new KisBatchNodeUpdate(m_d->initialUpdatesBeforeClear)); 0354 0355 executeAndAddCommand(new KisUpdateCommandEx(m_d->updateDataForUndo, m_d->updatesFacade, KisUpdateCommandEx::INITIALIZING, m_d->commandUpdatesBlockerCookie), Clear, KisStrokeJobData::BARRIER); 0356 executeAndAddCommand(new KisDisableDirtyRequestsCommand(m_d->updatesFacade, KisDisableDirtyRequestsCommand::INITIALIZING), Clear, KisStrokeJobData::BARRIER); 0357 }); 0358 0359 extraInitJobs << lastCommandUndoJobs; 0360 0361 if (!lastCommandUndoJobs.isEmpty()) { 0362 KritaUtils::addJobBarrier(extraInitJobs, [this]() { 0363 /** 0364 * In case we are doing a continued action, we need to 0365 * set initial update to the "very initial" state that 0366 * was present before the previous stroke. So here we 0367 * just override the rect calculated before 0368 */ 0369 KisBatchNodeUpdate updates; 0370 0371 Q_FOREACH (KisNodeSP node, m_d->processedNodes) { 0372 updates.addUpdate(node, node->projectionPlane()->tightUserVisibleBounds()); 0373 } 0374 0375 m_d->initialUpdatesBeforeClear = updates.compressed(); 0376 *m_d->updateDataForUndo = m_d->initialUpdatesBeforeClear; 0377 0378 /** 0379 * We need to make sure that the nodes will be successfully be 0380 * transformed back in case the stroke will be finished before 0381 * sigTransactionGenerated() signal is delivered. 0382 */ 0383 m_d->pendingUpdateArgs = m_d->initialTransformArgs; 0384 }); 0385 } 0386 0387 KritaUtils::addJobSequential(extraInitJobs, [this]() { 0388 // When dealing with animated transform mask layers, create keyframe and save the command for undo. 0389 // NOTE: for transform masks we create a keyframe no matter what the user 0390 // settigs are 0391 Q_FOREACH (KisNodeSP node, m_d->processedNodes) { 0392 if (KisTransformMask* transformMask = dynamic_cast<KisTransformMask*>(node.data())) { 0393 if (KisLazyCreateTransformMaskKeyframesCommand::maskHasAnimation(transformMask)) { 0394 runAndSaveCommand(toQShared(new KisLazyCreateTransformMaskKeyframesCommand(transformMask)), KisStrokeJobData::BARRIER, KisStrokeJobData::NORMAL); 0395 } 0396 } else if (KisAutoKey::activeMode() > KisAutoKey::NONE && 0397 node->hasEditablePaintDevice()){ 0398 0399 KUndo2Command *autoKeyframeCommand = 0400 KisAutoKey::tryAutoCreateDuplicatedFrame(node->paintDevice(), 0401 KisAutoKey::SupportsLod); 0402 if (autoKeyframeCommand) { 0403 runAndSaveCommand(toQShared(autoKeyframeCommand), KisStrokeJobData::BARRIER, KisStrokeJobData::NORMAL); 0404 } 0405 } 0406 } 0407 }); 0408 0409 KritaUtils::addJobSequential(extraInitJobs, [this]() { 0410 /** 0411 * We must request shape layers to rerender areas outside image bounds 0412 */ 0413 Q_FOREACH(KisNodeSP node, m_d->rootNodes) { 0414 KisLayerUtils::forceAllHiddenOriginalsUpdate(node); 0415 } 0416 }); 0417 0418 KritaUtils::addJobBarrier(extraInitJobs, [this]() { 0419 /** 0420 * We must ensure that the currently selected subtree 0421 * has finished all its updates. 0422 */ 0423 Q_FOREACH(KisNodeSP node, m_d->rootNodes) { 0424 KisLayerUtils::forceAllDelayedNodesUpdate(node); 0425 } 0426 }); 0427 0428 /// Disable all decorated nodes to generate outline 0429 /// and preview correctly. We will enable them back 0430 /// as soon as preview generation is finished. 0431 KritaUtils::addJobBarrier(extraInitJobs, [this]() { 0432 Q_FOREACH (KisNodeSP node, m_d->processedNodes) { 0433 KisDecoratedNodeInterface *decoratedNode = dynamic_cast<KisDecoratedNodeInterface*>(node.data()); 0434 if (decoratedNode && decoratedNode->decorationsVisible()) { 0435 decoratedNode->setDecorationsVisible(false); 0436 m_d->disabledDecoratedNodes << decoratedNode; 0437 } 0438 } 0439 }); 0440 0441 KritaUtils::addJobBarrier(extraInitJobs, 0442 [this, 0443 argsAreInitialized]() mutable { 0444 QRect srcRect; 0445 0446 KisPaintDeviceSP externalSource = 0447 m_d->externalSource ? m_d->externalSource : 0448 (argsAreInitialized && m_d->initialTransformArgs.externalSource()) ? 0449 m_d->initialTransformArgs.externalSource() : 0; 0450 0451 if (externalSource) { 0452 // Start the transformation around the visible pixels of the external image 0453 srcRect = externalSource->exactBounds(); 0454 } else if (m_d->selection) { 0455 srcRect = m_d->selection->selectedExactRect(); 0456 } else { 0457 srcRect = QRect(); 0458 Q_FOREACH (KisNodeSP node, m_d->processedNodes) { 0459 // group layers may have a projection of layers 0460 // that are locked and will not be transformed 0461 if (node->inherits("KisGroupLayer")) continue; 0462 0463 if (const KisTransformMask *mask = dynamic_cast<const KisTransformMask*>(node.data())) { 0464 srcRect |= mask->sourceDataBounds(); 0465 } else if (const KisSelectionMask *mask = dynamic_cast<const KisSelectionMask*>(node.data())) { 0466 srcRect |= mask->selection()->selectedExactRect(); 0467 } else if (const KisTransparencyMask *mask = dynamic_cast<const KisTransparencyMask*>(node.data())) { 0468 srcRect |= mask->selection()->selectedExactRect(); 0469 } else { 0470 /// We shouldn't include masks or layer styles into the handles rect, 0471 /// in the end, we process the paint device only 0472 srcRect |= node->paintDevice() ? node->paintDevice()->exactBounds() : node->exactBounds(); 0473 } 0474 } 0475 } 0476 0477 TransformTransactionProperties transaction(srcRect, &m_d->initialTransformArgs, m_d->rootNodes, m_d->processedNodes); 0478 if (!argsAreInitialized) { 0479 m_d->initialTransformArgs = KisTransformUtils::resetArgsForMode(m_d->mode, m_d->filterId, transaction, m_d->externalSource); 0480 } 0481 m_d->externalSource.clear(); 0482 0483 const QRect imageBoundsRect = m_d->imageRoot->projection()->defaultBounds()->bounds(); 0484 m_d->previewLevelOfDetail = calculatePreferredLevelOfDetail(srcRect & imageBoundsRect); 0485 0486 if (m_d->previewLevelOfDetail > 0) { 0487 for (auto it = m_d->prevDirtyRects.begin(); it != m_d->prevDirtyRects.end(); ++it) { 0488 KisLodTransform t(m_d->previewLevelOfDetail); 0489 m_d->prevDirtyPreviewRects.addUpdate(it->first, t.map(it->second)); 0490 } 0491 } 0492 0493 Q_EMIT sigTransactionGenerated(transaction, m_d->initialTransformArgs, this); 0494 }); 0495 0496 /// recover back visibility of decorated nodes 0497 KritaUtils::addJobBarrier(extraInitJobs, [this]() { 0498 Q_FOREACH (KisDecoratedNodeInterface *decoratedNode, m_d->disabledDecoratedNodes) { 0499 decoratedNode->setDecorationsVisible(true); 0500 } 0501 m_d->disabledDecoratedNodes.clear(); 0502 }); 0503 0504 Q_FOREACH (KisNodeSP node, m_d->processedNodes) { 0505 KritaUtils::addJobSequential(extraInitJobs, [this, node]() mutable { 0506 createCacheAndClearNode(node); 0507 }); 0508 } 0509 0510 KritaUtils::addJobBarrier(extraInitJobs, [this]() { 0511 QMutexLocker l(&m_d->dirtyRectsMutex); 0512 0513 executeAndAddCommand(new KisDisableDirtyRequestsCommand(m_d->updatesFacade, KisDisableDirtyRequestsCommand::FINALIZING), Clear, KisStrokeJobData::BARRIER); 0514 0515 m_d->updateTimer.start(); 0516 }); 0517 0518 if (!lastCommandUndoJobs.isEmpty()) { 0519 KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->overriddenCommand); 0520 0521 for (auto it = extraInitJobs.begin(); it != extraInitJobs.end(); ++it) { 0522 (*it)->setCancellable(false); 0523 } 0524 } 0525 0526 KritaUtils::addJobBarrier(extraInitJobs, [this]() { 0527 if (m_d->previewLevelOfDetail > 0) { 0528 QVector<KisStrokeJobData*> lodSyncJobs; 0529 0530 KisSyncLodCacheStrokeStrategy::createJobsData(lodSyncJobs, 0531 m_d->imageRoot, 0532 m_d->updatesFacade, 0533 m_d->previewLevelOfDetail, 0534 m_d->devicesCacheHash.values() + 0535 m_d->transformMaskCacheHash.values()); 0536 0537 for (auto it = lodSyncJobs.begin(); it != lodSyncJobs.end(); ++it) { 0538 (*it)->setLevelOfDetailOverride(m_d->previewLevelOfDetail); 0539 } 0540 0541 addMutatedJobs(lodSyncJobs); 0542 } 0543 }); 0544 0545 addMutatedJobs(extraInitJobs); 0546 } 0547 0548 void InplaceTransformStrokeStrategy::finishStrokeCallback() 0549 { 0550 QVector<KisStrokeJobData *> mutatedJobs; 0551 0552 finishAction(mutatedJobs); 0553 0554 if (!mutatedJobs.isEmpty()) { 0555 addMutatedJobs(mutatedJobs); 0556 } 0557 } 0558 0559 void InplaceTransformStrokeStrategy::cancelStrokeCallback() 0560 { 0561 QVector<KisStrokeJobData *> mutatedJobs; 0562 0563 cancelAction(mutatedJobs); 0564 0565 if (!mutatedJobs.isEmpty()) { 0566 addMutatedJobs(mutatedJobs); 0567 } 0568 } 0569 0570 InplaceTransformStrokeStrategy::BarrierUpdateData::BarrierUpdateData(bool _forceUpdate) 0571 : KisAsynchronousStrokeUpdateHelper::UpdateData(_forceUpdate, BARRIER, NORMAL) 0572 { 0573 } 0574 0575 KisStrokeJobData *InplaceTransformStrokeStrategy::BarrierUpdateData::createLodClone(int levelOfDetail) 0576 { 0577 return new BarrierUpdateData(*this, levelOfDetail); 0578 } 0579 0580 InplaceTransformStrokeStrategy::BarrierUpdateData::BarrierUpdateData(const InplaceTransformStrokeStrategy::BarrierUpdateData &rhs, int levelOfDetail) 0581 : KisAsynchronousStrokeUpdateHelper::UpdateData (rhs, levelOfDetail) 0582 { 0583 } 0584 0585 void InplaceTransformStrokeStrategy::executeAndAddCommand(KUndo2Command *cmd, InplaceTransformStrokeStrategy::CommandGroup group, KisStrokeJobData::Sequentiality seq) 0586 { 0587 QMutexLocker l(&m_d->commandsMutex); 0588 KUndo2CommandSP sharedCommand = toQShared(cmd); 0589 executeCommand(sharedCommand, false); 0590 m_d->commands.append({group, sharedCommand, seq}); 0591 } 0592 0593 void InplaceTransformStrokeStrategy::notifyAllCommandsDone() 0594 { 0595 for (auto it = m_d->commands.begin(); it != m_d->commands.end(); ++it) { 0596 if (it->commandGroup == Clear) { 0597 notifyCommandDone(it->command, it->sequentiality, KisStrokeJobData::NORMAL); 0598 } 0599 } 0600 0601 notifyCommandDone(toQShared(new KUndo2Command()), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL); 0602 0603 for (auto it = m_d->commands.begin(); it != m_d->commands.end(); ++it) { 0604 if (it->commandGroup == Transform) { 0605 notifyCommandDone(it->command, it->sequentiality, KisStrokeJobData::NORMAL); 0606 } 0607 } 0608 } 0609 0610 void InplaceTransformStrokeStrategy::undoAllCommands() 0611 { 0612 for (auto it = std::make_reverse_iterator(m_d->commands.end()); 0613 it != std::make_reverse_iterator(m_d->commands.begin()); 0614 ++it) { 0615 0616 executeCommand(it->command, true); 0617 } 0618 0619 m_d->commands.clear(); 0620 } 0621 0622 void InplaceTransformStrokeStrategy::undoTransformCommands(int levelOfDetail) 0623 { 0624 for (auto it = std::make_reverse_iterator(m_d->commands.end()); 0625 it != std::make_reverse_iterator(m_d->commands.begin());) { 0626 0627 if ((levelOfDetail > 0 && 0628 (it->commandGroup == TransformLod || it->commandGroup == TransformLodTemporary)) || 0629 (levelOfDetail <= 0 && 0630 (it->commandGroup == Transform || it->commandGroup == TransformTemporary))) { 0631 0632 executeCommand(it->command, true); 0633 it = std::make_reverse_iterator(m_d->commands.erase(std::next(it).base())); 0634 } else { 0635 ++it; 0636 } 0637 } 0638 } 0639 0640 void InplaceTransformStrokeStrategy::fetchAllUpdateRequests(int levelOfDetail, KisBatchNodeUpdateSP updateData) 0641 { 0642 KisBatchNodeUpdate &dirtyRects = m_d->effectiveDirtyRects(levelOfDetail); 0643 KisBatchNodeUpdate &prevDirtyRects = m_d->effectivePrevDirtyRects(levelOfDetail); 0644 0645 *updateData = (prevDirtyRects | dirtyRects).compressed(); 0646 0647 KisBatchNodeUpdate savedUndoRects = dirtyRects; 0648 0649 if (levelOfDetail > 0) { 0650 0651 for (auto it = savedUndoRects.begin(); it != savedUndoRects.end(); ++it) { 0652 it->second = KisLodTransform::upscaledRect(it->second, levelOfDetail); 0653 } 0654 } 0655 0656 *m_d->updateDataForUndo = (m_d->initialUpdatesBeforeClear | savedUndoRects).compressed(); 0657 prevDirtyRects.clear(); 0658 dirtyRects.swap(prevDirtyRects); 0659 } 0660 0661 void InplaceTransformStrokeStrategy::transformNode(KisNodeSP node, const ToolTransformArgs &config, int levelOfDetail) 0662 { 0663 KisPaintDeviceSP device = node->paintDevice(); 0664 0665 CommandGroup commandGroup = 0666 levelOfDetail > 0 ? TransformLod : Transform; 0667 0668 if (KisExternalLayer *extLayer = 0669 dynamic_cast<KisExternalLayer*>(node.data())) { 0670 0671 if (config.mode() == ToolTransformArgs::FREE_TRANSFORM || 0672 (config.mode() == ToolTransformArgs::PERSPECTIVE_4POINT && 0673 extLayer->supportsPerspectiveTransform())) { 0674 0675 if (levelOfDetail <= 0) { 0676 const QRect oldDirtyRect = extLayer->projectionPlane()->tightUserVisibleBounds() | extLayer->theoreticalBoundingRect(); 0677 0678 QVector3D transformedCenter; 0679 KisTransformWorker w = KisTransformUtils::createTransformWorker(config, 0, 0, &transformedCenter); 0680 QTransform t = w.transform(); 0681 KUndo2Command *cmd = extLayer->transform(t); 0682 0683 executeAndAddCommand(cmd, Transform, KisStrokeJobData::CONCURRENT); 0684 0685 /// we should make sure that the asynchronous shape regeneration 0686 /// has completed before we issue the updates a bit later 0687 if (KisShapeLayer *shapeLayer = dynamic_cast<KisShapeLayer*>(extLayer)) { 0688 shapeLayer->forceUpdateTimedNode(); 0689 } 0690 0691 /** 0692 * Shape layer's projection may not be yet ready right 0693 * after transformation, because it need to do that in 0694 * the GUI thread, so we should approximate that. 0695 */ 0696 const QRect theoreticalNewDirtyRect = 0697 kisGrowRect(t.mapRect(oldDirtyRect), 1); 0698 0699 addDirtyRect(node, 0700 oldDirtyRect | 0701 theoreticalNewDirtyRect | 0702 extLayer->projectionPlane()->tightUserVisibleBounds() | 0703 extLayer->theoreticalBoundingRect(), 0); 0704 return; 0705 } else { 0706 device = node->projection(); 0707 commandGroup = TransformLodTemporary; 0708 } 0709 } 0710 0711 } else { 0712 device = node->paintDevice(); 0713 } 0714 0715 if (device) { 0716 KisPaintDeviceSP cachedPortion; 0717 0718 { 0719 QMutexLocker l(&m_d->devicesCacheMutex); 0720 cachedPortion = m_d->devicesCacheHash[device.data()]; 0721 } 0722 0723 KIS_SAFE_ASSERT_RECOVER_RETURN(cachedPortion); 0724 0725 KisTransaction transaction(device); 0726 0727 KisProcessingVisitor::ProgressHelper helper(node); 0728 KisTransformUtils::transformAndMergeDevice(config, cachedPortion, 0729 device, &helper); 0730 0731 executeAndAddCommand(transaction.endAndTake(), commandGroup, KisStrokeJobData::CONCURRENT); 0732 addDirtyRect(node, cachedPortion->extent() | node->projectionPlane()->tightUserVisibleBounds(), levelOfDetail); 0733 0734 } else if (KisTransformMask *transformMask = 0735 dynamic_cast<KisTransformMask*>(node.data())) { 0736 0737 const QRect oldDirtyRect = transformMask->extent(); 0738 0739 if (levelOfDetail > 0) { 0740 KisPaintDeviceSP cachedPortion; 0741 0742 { 0743 QMutexLocker l(&m_d->devicesCacheMutex); 0744 cachedPortion = m_d->transformMaskCacheHash[transformMask]; 0745 } 0746 0747 KIS_SAFE_ASSERT_RECOVER_RETURN(cachedPortion); 0748 0749 KisPaintDeviceSP dst = new KisPaintDevice(cachedPortion->colorSpace()); 0750 dst->prepareClone(cachedPortion); 0751 0752 KisProcessingVisitor::ProgressHelper helper(node); 0753 KisTransformUtils::transformAndMergeDevice(config, cachedPortion, 0754 dst, &helper); 0755 0756 transformMask->overrideStaticCacheDevice(dst); 0757 } 0758 0759 { 0760 KUndo2Command *cmd = new KisSimpleModifyTransformMaskCommand(transformMask, 0761 KisTransformMaskParamsInterfaceSP( 0762 new KisTransformMaskAdapter(config)), 0763 m_d->commandUpdatesBlockerCookie); 0764 executeAndAddCommand(cmd, commandGroup, KisStrokeJobData::CONCURRENT); 0765 addDirtyRect(node, oldDirtyRect | transformMask->extent(), levelOfDetail); 0766 } 0767 0768 } 0769 } 0770 0771 void InplaceTransformStrokeStrategy::createCacheAndClearNode(KisNodeSP node) 0772 { 0773 KisPaintDeviceSP device; 0774 CommandGroup commandGroup = Clear; 0775 0776 if (KisExternalLayer *extLayer = 0777 dynamic_cast<KisExternalLayer*>(node.data())) { 0778 0779 if (m_d->mode == ToolTransformArgs::FREE_TRANSFORM || 0780 (m_d->mode == ToolTransformArgs::PERSPECTIVE_4POINT && 0781 extLayer->supportsPerspectiveTransform())) { 0782 0783 device = node->projection(); 0784 commandGroup = ClearTemporary; 0785 } 0786 } else if (KisTransformMask *mask = dynamic_cast<KisTransformMask*>(node.data())) { 0787 KIS_SAFE_ASSERT_RECOVER_NOOP(!m_d->selection); 0788 0789 // NOTE: this action should be either sequential or barrier 0790 QMutexLocker l(&m_d->devicesCacheMutex); 0791 if (!m_d->transformMaskCacheHash.contains(mask)) { 0792 0793 KisPaintDeviceSP dev = mask->buildSourcePreviewDevice(); 0794 m_d->transformMaskCacheHash.insert(mask, new KisPaintDevice(*dev)); 0795 0796 return; 0797 } 0798 0799 } else { 0800 device = node->paintDevice(); 0801 } 0802 0803 if (device) { 0804 0805 { 0806 QMutexLocker l(&m_d->devicesCacheMutex); 0807 0808 if (!m_d->devicesCacheHash.contains(device.data())) { 0809 KisPaintDeviceSP cache; 0810 0811 // The image that will be transformed is linked to the original 0812 // layer. We copy existing pixels or use an external source. 0813 if (m_d->initialTransformArgs.externalSource()) { 0814 cache = device->createCompositionSourceDevice(m_d->initialTransformArgs.externalSource()); 0815 } else if (m_d->selection) { 0816 QRect srcRect = m_d->selection->selectedExactRect(); 0817 0818 cache = device->createCompositionSourceDevice(); 0819 KisPainter gc(cache); 0820 gc.setSelection(m_d->selection); 0821 gc.bitBlt(srcRect.topLeft(), device, srcRect); 0822 } else { 0823 cache = device->createCompositionSourceDevice(device); 0824 } 0825 0826 m_d->devicesCacheHash.insert(device.data(), cache); 0827 } 0828 } 0829 0830 // Don't clear the selection or layer when the source is external 0831 if (m_d->initialTransformArgs.externalSource()) return; 0832 0833 KisTransaction transaction(device); 0834 if (m_d->selection) { 0835 device->clearSelection(m_d->selection); 0836 } else { 0837 QRect oldExtent = device->extent(); 0838 device->clear(); 0839 device->setDirty(oldExtent); 0840 } 0841 0842 executeAndAddCommand(transaction.endAndTake(), commandGroup, KisStrokeJobData::CONCURRENT); 0843 } 0844 } 0845 0846 void InplaceTransformStrokeStrategy::reapplyTransform(ToolTransformArgs args, 0847 QVector<KisStrokeJobData *> &mutatedJobs, 0848 int levelOfDetail, 0849 bool useHoldUI) 0850 { 0851 if (levelOfDetail > 0) { 0852 args.scale3dSrcAndDst(KisLodTransform::lodToScale(levelOfDetail)); 0853 } 0854 0855 KisBatchNodeUpdateSP updateData(new KisBatchNodeUpdate()); 0856 0857 CommandGroup commandGroup = 0858 levelOfDetail > 0 ? TransformLod : Transform; 0859 0860 KritaUtils::addJobBarrier(mutatedJobs, levelOfDetail, 0861 [this, args, levelOfDetail, updateData, useHoldUI, commandGroup]() { 0862 0863 // it has its own dirty requests blocking inside 0864 undoTransformCommands(levelOfDetail); 0865 0866 if (useHoldUI) { 0867 executeAndAddCommand(new KisHoldUIUpdatesCommand(m_d->updatesFacade, KisCommandUtils::FlipFlopCommand::INITIALIZING), commandGroup, KisStrokeJobData::BARRIER); 0868 } 0869 0870 executeAndAddCommand(new KisDisableDirtyRequestsCommand(m_d->updatesFacade, KisUpdateCommandEx::INITIALIZING), commandGroup, KisStrokeJobData::BARRIER); 0871 }); 0872 0873 Q_FOREACH (KisNodeSP node, m_d->processedNodes) { 0874 KritaUtils::addJobConcurrent(mutatedJobs, levelOfDetail, 0875 [this, node, args, levelOfDetail]() { 0876 transformNode(node, args, levelOfDetail); 0877 }); 0878 } 0879 0880 KritaUtils::addJobBarrier(mutatedJobs, levelOfDetail, 0881 [this, levelOfDetail, updateData, useHoldUI, commandGroup]() { 0882 0883 fetchAllUpdateRequests(levelOfDetail, updateData); 0884 0885 executeAndAddCommand(new KisDisableDirtyRequestsCommand(m_d->updatesFacade, KisUpdateCommandEx::FINALIZING), commandGroup, KisStrokeJobData::BARRIER); 0886 executeAndAddCommand(new KisUpdateCommandEx(m_d->updateDataForUndo, m_d->updatesFacade, KisUpdateCommandEx::FINALIZING, m_d->commandUpdatesBlockerCookie), commandGroup, KisStrokeJobData::BARRIER); 0887 0888 if (useHoldUI) { 0889 executeAndAddCommand(new KisHoldUIUpdatesCommand(m_d->updatesFacade, KisCommandUtils::FlipFlopCommand::FINALIZING), commandGroup, KisStrokeJobData::BARRIER); 0890 } 0891 0892 /** 0893 * We also emit the updates manually, because KisUpdateCommandEx is 0894 * still blocked by m_d->commandUpdatesBlockerCookie (for easy undo 0895 * purposes) 0896 */ 0897 for (auto it = updateData->begin(); it != updateData->end(); ++it) { 0898 KisTransformMask *transformMask = dynamic_cast<KisTransformMask*>(it->first.data()); 0899 0900 if (transformMask && 0901 ((levelOfDetail <= 0 && !transformMask->transformParams()->isAffine()) || 0902 (levelOfDetail <= 0 && m_d->previewLevelOfDetail > 0))) { 0903 0904 transformMask->threadSafeForceStaticImageUpdate(it->second); 0905 } else { 0906 m_d->updatesFacade->refreshGraphAsync(it->first, it->second); 0907 } 0908 0909 } 0910 }); 0911 } 0912 0913 void InplaceTransformStrokeStrategy::finalizeStrokeImpl(QVector<KisStrokeJobData *> &mutatedJobs, bool saveCommands) 0914 { 0915 KritaUtils::addJobBarrier(mutatedJobs, [this]() { 0916 Q_FOREACH (KisSelectionSP selection, m_d->deactivatedSelections) { 0917 selection->setVisible(true); 0918 } 0919 0920 Q_FOREACH(KisSelectionMaskSP deactivatedOverlaySelectionMask, m_d->deactivatedOverlaySelectionMasks) { 0921 deactivatedOverlaySelectionMask->selection()->setVisible(true); 0922 deactivatedOverlaySelectionMask->setDirty(); 0923 } 0924 0925 m_d->commandUpdatesBlockerCookie.reset(); 0926 }); 0927 0928 0929 if (saveCommands) { 0930 KritaUtils::addJobBarrier(mutatedJobs, [this]() { 0931 notifyAllCommandsDone(); 0932 m_d->commands.clear(); 0933 }); 0934 } 0935 } 0936 0937 void InplaceTransformStrokeStrategy::finishAction(QVector<KisStrokeJobData *> &mutatedJobs) 0938 { 0939 /** 0940 * * Forward to cancelling should happen before the guard for 0941 * finalizingActionsStarted. 0942 * 0943 * * Transform masks may switch mode and become identity, that 0944 * shouldn't be cancelled. 0945 */ 0946 if (m_d->currentTransformArgs.isUnchanging() && 0947 m_d->transformMaskCacheHash.isEmpty() && 0948 !m_d->overriddenCommand) { 0949 0950 cancelAction(mutatedJobs); 0951 return; 0952 } 0953 0954 if (m_d->previewLevelOfDetail > 0) { 0955 /** 0956 * Update jobs from level of detail updates may cause dirtying 0957 * of the transform mask's static cache device. Therefore we must 0958 * ensure that final update of the mask happens strictly after 0959 * them. 0960 */ 0961 KritaUtils::addJobBarrier(mutatedJobs, [this]() { Q_UNUSED(this) }); 0962 0963 if (!m_d->transformMaskCacheHash.isEmpty()) { 0964 KritaUtils::addJobSequential(mutatedJobs, [this]() { 0965 Q_FOREACH (KisTransformMask *mask, m_d->transformMaskCacheHash.keys()) { 0966 mask->overrideStaticCacheDevice(0); 0967 } 0968 0969 /** 0970 * Transform masks don't have internal state switch for LoD mode, 0971 * therefore all the preview transformations must be cancelled 0972 * before applying the final command 0973 */ 0974 undoTransformCommands(m_d->previewLevelOfDetail); 0975 }); 0976 } 0977 0978 reapplyTransform(m_d->currentTransformArgs, mutatedJobs, 0, true); 0979 } else { 0980 if (m_d->pendingUpdateArgs) { 0981 mutatedJobs << new BarrierUpdateData(true); 0982 } 0983 } 0984 0985 mutatedJobs << new UpdateTransformData(m_d->currentTransformArgs, 0986 UpdateTransformData::SELECTION); 0987 0988 // the rest of the transform finishing work cannot be cancelled... 0989 KritaUtils::addJobBarrier(mutatedJobs, [this]() { 0990 m_d->strokeCompletionHasBeenStarted = true; 0991 0992 QVector<KisStrokeJobData *> nonCancellableFinishJobs; 0993 0994 finalizeStrokeImpl(nonCancellableFinishJobs, true); 0995 0996 KritaUtils::addJobBarrier(nonCancellableFinishJobs, [this]() { 0997 KisStrokeStrategyUndoCommandBased::finishStrokeCallback(); 0998 }); 0999 1000 for (auto it = nonCancellableFinishJobs.begin(); it != nonCancellableFinishJobs.end(); ++it) { 1001 (*it)->setCancellable(false); 1002 } 1003 1004 this->addMutatedJobs(nonCancellableFinishJobs); 1005 1006 }); 1007 } 1008 1009 void InplaceTransformStrokeStrategy::cancelAction(QVector<KisStrokeJobData *> &mutatedJobs) 1010 { 1011 /** 1012 * It is too late to cancel anything, the transformation has been completed 1013 * and its commands have been pushed into the undo adapter, so there is no 1014 * way to stop that. 1015 */ 1016 if (m_d->strokeCompletionHasBeenStarted) return; 1017 1018 1019 KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->transformMaskCacheHash.isEmpty() || 1020 (m_d->transformMaskCacheHash.size() == 1 && m_d->processedNodes.size() == 1)); 1021 1022 const bool isChangingTransformMask = !m_d->transformMaskCacheHash.isEmpty(); 1023 1024 if (m_d->initialTransformArgs.isIdentity()) { 1025 KritaUtils::addJobSequential(mutatedJobs, [this]() { 1026 Q_FOREACH (KisTransformMask *mask, m_d->transformMaskCacheHash.keys()) { 1027 mask->overrideStaticCacheDevice(0); 1028 } 1029 }); 1030 1031 1032 KritaUtils::addJobBarrier(mutatedJobs, [this]() { 1033 m_d->commandUpdatesBlockerCookie.reset(); 1034 undoTransformCommands(0); 1035 undoAllCommands(); 1036 }); 1037 finalizeStrokeImpl(mutatedJobs, false); 1038 1039 KritaUtils::addJobSequential(mutatedJobs, [this]() { 1040 Q_FOREACH (KisTransformMask *mask, m_d->transformMaskCacheHash.keys()) { 1041 mask->threadSafeForceStaticImageUpdate(); 1042 } 1043 }); 1044 1045 KritaUtils::addJobBarrier(mutatedJobs, [this]() { 1046 KisStrokeStrategyUndoCommandBased::cancelStrokeCallback(); 1047 }); 1048 } else { 1049 KIS_SAFE_ASSERT_RECOVER_NOOP(isChangingTransformMask || m_d->overriddenCommand); 1050 1051 KritaUtils::addJobSequential(mutatedJobs, [this]() { 1052 Q_FOREACH (KisTransformMask *mask, m_d->transformMaskCacheHash.keys()) { 1053 mask->overrideStaticCacheDevice(0); 1054 } 1055 }); 1056 1057 reapplyTransform(m_d->initialTransformArgs, mutatedJobs, 0, true); 1058 1059 mutatedJobs << new UpdateTransformData(m_d->initialTransformArgs, 1060 UpdateTransformData::SELECTION); 1061 1062 finalizeStrokeImpl(mutatedJobs, bool(m_d->overriddenCommand)); 1063 1064 KritaUtils::addJobSequential(mutatedJobs, [this]() { 1065 Q_FOREACH (KisTransformMask *mask, m_d->transformMaskCacheHash.keys()) { 1066 mask->threadSafeForceStaticImageUpdate(); 1067 } 1068 }); 1069 1070 if (m_d->overriddenCommand) { 1071 KritaUtils::addJobBarrier(mutatedJobs, [this]() { 1072 m_d->currentTransformArgs = m_d->initialTransformArgs; 1073 KisStrokeStrategyUndoCommandBased::finishStrokeCallback(); 1074 }); 1075 } else { 1076 KritaUtils::addJobBarrier(mutatedJobs, [this]() { 1077 KisStrokeStrategyUndoCommandBased::cancelStrokeCallback(); 1078 }); 1079 } 1080 } 1081 } 1082 1083 void InplaceTransformStrokeStrategy::addDirtyRect(KisNodeSP node, const QRect &rect, int levelOfDetail) { 1084 QMutexLocker l(&m_d->dirtyRectsMutex); 1085 m_d->effectiveDirtyRects(levelOfDetail).addUpdate(node, rect); 1086 }