Krita Source Code Documentation
Loading...
Searching...
No Matches
InplaceTransformStrokeStrategy Class Reference

#include <inplace_transform_stroke_strategy.h>

+ Inheritance diagram for InplaceTransformStrokeStrategy:

Classes

struct  BarrierUpdateData
 
class  CalculateConvexHullData
 
struct  Private
 
class  UpdateTransformData
 

Public Types

enum  CommandGroup {
  Clear = 0 , ClearTemporary , Transform , TransformTemporary ,
  TransformLod , TransformLodTemporary
}
 
- Public Types inherited from KisSimpleStrokeStrategy
enum  JobType {
  JOB_INIT = 0 , JOB_CANCEL , JOB_FINISH , JOB_DOSTROKE ,
  JOB_SUSPEND , JOB_RESUME , NJOBS
}
 

Signals

void sigConvexHullCalculated (QPolygon convexHull, void *cookie)
 
void sigTransactionGenerated (TransformTransactionProperties transaction, ToolTransformArgs args, void *cookie)
 

Public Member Functions

void cancelStrokeCallback () override
 
void doStrokeCallback (KisStrokeJobData *data) override
 
void finishStrokeCallback () override
 
void initStrokeCallback () override
 
 InplaceTransformStrokeStrategy (ToolTransformArgs::TransformMode mode, const QString &filterId, bool forceReset, KisNodeList rootNodes, KisSelectionSP selection, KisPaintDeviceSP externalSource, KisStrokeUndoFacade *undoFacade, KisUpdatesFacade *updatesFacade, KisNodeSP imageRoot, bool forceLodMode)
 
 Q_ENUM (CommandGroup)
 
 ~InplaceTransformStrokeStrategy () override
 
- Public Member Functions inherited from KisStrokeStrategyUndoCommandBased
void cancelStrokeCallback () override
 
void doStrokeCallback (KisStrokeJobData *data) override
 
void finishStrokeCallback () override
 
void initStrokeCallback () override
 
 KisStrokeStrategyUndoCommandBased (const KUndo2MagicString &name, bool undo, KisStrokeUndoFacade *undoFacade, KUndo2CommandSP initCommand=KUndo2CommandSP(0), KUndo2CommandSP finishCommand=KUndo2CommandSP(0))
 
void setCommandExtraData (KUndo2CommandExtraData *data)
 
void setMacroId (int value)
 
void setSupportsWrapAroundMode (bool value)
 
void setUsedWhileUndoRedo (bool value)
 
- Public Member Functions inherited from KisRunnableBasedStrokeStrategy
 KisRunnableBasedStrokeStrategy (const KisRunnableBasedStrokeStrategy &rhs)
 
 KisRunnableBasedStrokeStrategy (const QLatin1String &id, const KUndo2MagicString &name=KUndo2MagicString())
 
KisRunnableStrokeJobsInterfacerunnableJobsInterface () const
 
 ~KisRunnableBasedStrokeStrategy ()
 
- Public Member Functions inherited from KisSimpleStrokeStrategy
KisStrokeJobDatacreateCancelData () override
 
KisStrokeJobStrategycreateCancelStrategy () override
 
KisStrokeJobStrategycreateDabStrategy () override
 
KisStrokeJobDatacreateFinishData () override
 
KisStrokeJobStrategycreateFinishStrategy () override
 
KisStrokeJobDatacreateInitData () override
 
KisStrokeJobStrategycreateInitStrategy () override
 
KisStrokeJobDatacreateResumeData () override
 
KisStrokeJobStrategycreateResumeStrategy () override
 
KisStrokeJobDatacreateSuspendData () override
 
KisStrokeJobStrategycreateSuspendStrategy () override
 
 KisSimpleStrokeStrategy (const QLatin1String &id, const KUndo2MagicString &name=KUndo2MagicString())
 
virtual void resumeStrokeCallback ()
 
virtual void suspendStrokeCallback ()
 
- Public Member Functions inherited from KisStrokeStrategy
qreal balancingRatioOverride () const
 
bool canForgetAboutMe () const
 
bool clearsRedoOnStart () const
 
virtual KisStrokeStrategycreateLodClone (int levelOfDetail)
 
KisLodPreferences currentLodPreferences () const
 
bool forceLodModeIfPossible () const
 
QString id () const
 
bool isAsynchronouslyCancellable () const
 
bool isExclusive () const
 
 KisStrokeStrategy (const QLatin1String &id, const KUndo2MagicString &name=KUndo2MagicString())
 
KUndo2MagicString name () const
 
bool needsExplicitCancel () const
 
virtual void notifyUserEndedStroke ()
 
virtual void notifyUserStartedStroke ()
 
bool requestsOtherStrokesToEnd () const
 
void setForceLodModeIfPossible (bool forceLodModeIfPossible)
 
void setMutatedJobsInterface (KisStrokesQueueMutatedJobInterface *mutatedJobsInterface, KisStrokeId strokeId)
 
bool supportsWrapAroundMode () const
 
virtual void tryCancelCurrentStrokeJobAsync ()
 tryCancelCurrentStrokeJobAsync is called by the strokes queue when the stroke is being cancelled. The stroke strategy may or may not handle this request and cancel the currently running long action.
 
virtual ~KisStrokeStrategy ()
 

Protected Member Functions

void postProcessToplevelCommand (KUndo2Command *command) override
 Applies some modifications (e.g. assigning extra data) to the toplevel command.
 
- Protected Member Functions inherited from KisStrokeStrategyUndoCommandBased
void cancelStrokeCallbackImpl (QVector< KisStrokeJobData * > &mutatedJobs)
 
void executeCommand (KUndo2CommandSP command, bool undo)
 
 KisStrokeStrategyUndoCommandBased (const KisStrokeStrategyUndoCommandBased &rhs)
 
void notifyCommandDone (KUndo2CommandSP command, KisStrokeJobData::Sequentiality sequentiality, KisStrokeJobData::Exclusivity exclusivity)
 
void runAndSaveCommand (KUndo2CommandSP command, KisStrokeJobData::Sequentiality sequentiality, KisStrokeJobData::Exclusivity exclusivity)
 
KisStrokeUndoFacadeundoFacade () const
 
- Protected Member Functions inherited from KisSimpleStrokeStrategy
void enableJob (JobType type, bool enable=true, KisStrokeJobData::Sequentiality sequentiality=KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::Exclusivity exclusivity=KisStrokeJobData::NORMAL)
 
 KisSimpleStrokeStrategy (const KisSimpleStrokeStrategy &rhs)
 
- Protected Member Functions inherited from KisStrokeStrategy
void addMutatedJob (KisStrokeJobData *data)
 
void addMutatedJobs (const QVector< KisStrokeJobData * > list)
 
 KisStrokeStrategy (const KisStrokeStrategy &rhs)
 
void setAsynchronouslyCancellable (bool value)
 
void setBalancingRatioOverride (qreal value)
 
void setCanForgetAboutMe (bool value)
 
void setClearsRedoOnStart (bool value)
 
void setExclusive (bool value)
 
void setNeedsExplicitCancel (bool value)
 
void setRequestsOtherStrokesToEnd (bool value)
 
void setSupportsWrapAroundMode (bool value)
 

Private Member Functions

void addDirtyRect (KisNodeSP node, const QRect &rect, int levelOfDetail)
 
QPolygon calculateConvexHull ()
 
int calculatePreferredLevelOfDetail (const QRect &srcRect)
 
void cancelAction (QVector< KisStrokeJobData * > &mutatedJobs)
 
void createCacheAndClearNode (KisNodeSP node)
 
void doCanvasUpdate (bool forceUpdate)
 
void executeAndAddCommand (KUndo2Command *cmd, CommandGroup group, KisStrokeJobData::Sequentiality seq)
 
void fetchAllUpdateRequests (int levelOfDetail, KisBatchNodeUpdateSP updateData)
 
void finalizeStrokeImpl (QVector< KisStrokeJobData * > &mutatedJobs, bool saveCommands)
 
void finishAction (QVector< KisStrokeJobData * > &mutatedJobs)
 
 InplaceTransformStrokeStrategy (const InplaceTransformStrokeStrategy &rhs, int levelOfDetail)
 
void notifyAllCommandsDone ()
 
void reapplyTransform (ToolTransformArgs args, QVector< KisStrokeJobData * > &mutatedJobs, int levelOfDetail, bool useHoldUI)
 
void transformNode (KisNodeSP node, const ToolTransformArgs &config, int levelOfDetail)
 
void tryPostUpdateJob (bool forceUpdate)
 
void undoAllCommands ()
 
void undoTransformCommands (int levelOfDetail)
 

Static Private Member Functions

static void repopulateUI (QVector< KisStrokeJobData * > &mutatedJobs, KisUpdatesFacade *updatesFacade, const QRect &dirtyRect)
 

Private Attributes

const QScopedPointer< Privatem_d
 

Friends

class InitializeTransformModeStrokeStrategy
 

Additional Inherited Members

- Static Public Member Functions inherited from KisSimpleStrokeStrategy
static QLatin1String jobTypeToString (JobType type)
 

Detailed Description

Definition at line 28 of file inplace_transform_stroke_strategy.h.

Member Enumeration Documentation

◆ CommandGroup

The transformation pipeline usually looks like that:

1) Apply Clear commands for all the layers. Some clear commands might be "temporary", that is, they do not go to the final history, e.g. when clearing a shape layer's projection.

2) Apply TransformLod commands to generate preview of the transformation. Some commands may be declared as "temporary", that is, they do not go to the final history, e.g. for the shape layer, for which we just write to the projection device explicitly.

3) When transformation is changed we undo all TransformLod and TransformTemporary commands to recover the old state. The temporary command recovers the state of shape layers' projection device.

4) Repeat steps 2) and 3) until the user is satisfied.

5) When "Apply" button is pressed, all transform commands are undone like in step 2).

6) All Transform commands are applied at Lod0-level. TransformTemporary is not used atm.

7) All non-temporary commands go to the undo history.

Enumerator
Clear 
ClearTemporary 
Transform 
TransformTemporary 
TransformLod 
TransformLodTemporary 

Definition at line 114 of file inplace_transform_stroke_strategy.h.

Constructor & Destructor Documentation

◆ InplaceTransformStrokeStrategy() [1/2]

InplaceTransformStrokeStrategy::InplaceTransformStrokeStrategy ( ToolTransformArgs::TransformMode mode,
const QString & filterId,
bool forceReset,
KisNodeList rootNodes,
KisSelectionSP selection,
KisPaintDeviceSP externalSource,
KisStrokeUndoFacade * undoFacade,
KisUpdatesFacade * updatesFacade,
KisNodeSP imageRoot,
bool forceLodMode )

Definition at line 143 of file inplace_transform_stroke_strategy.cpp.

154 m_d(new Private())
155{
156
157 m_d->mode = mode;
158 m_d->filterId = filterId;
159 m_d->forceReset = forceReset;
160 m_d->rootNodes = rootNodes;
161 m_d->selection = selection;
162 m_d->externalSource = externalSource;
163 m_d->updatesFacade = updatesFacade;
164 m_d->undoFacade = undoFacade;
165 m_d->imageRoot = imageRoot;
166 m_d->forceLodMode = forceLodMode;
167 m_d->commandUpdatesBlockerCookie.reset(new boost::none_t(boost::none));
168
169 if (selection) {
170 Q_FOREACH(KisNodeSP node, rootNodes) {
171 KIS_SAFE_ASSERT_RECOVER_NOOP(!dynamic_cast<KisTransformMask*>(node.data()));
172 }
173 }
175
176 // TODO: check if can be relaxed
178}
KisStrokeStrategyUndoCommandBased(const KUndo2MagicString &name, bool undo, KisStrokeUndoFacade *undoFacade, KUndo2CommandSP initCommand=KUndo2CommandSP(0), KUndo2CommandSP finishCommand=KUndo2CommandSP(0))
void setNeedsExplicitCancel(bool value)
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
KUndo2MagicString kundo2_i18n(const char *text)

References KisSharedPtr< T >::data(), KIS_SAFE_ASSERT_RECOVER_NOOP, m_d, KisStrokeStrategyUndoCommandBased::setMacroId(), KisStrokeStrategy::setNeedsExplicitCancel(), KisCommandUtils::TransformToolId, and KisStrokeStrategyUndoCommandBased::undoFacade().

◆ ~InplaceTransformStrokeStrategy()

InplaceTransformStrokeStrategy::~InplaceTransformStrokeStrategy ( )
override

Definition at line 180 of file inplace_transform_stroke_strategy.cpp.

181{
182}

◆ InplaceTransformStrokeStrategy() [2/2]

InplaceTransformStrokeStrategy::InplaceTransformStrokeStrategy ( const InplaceTransformStrokeStrategy & rhs,
int levelOfDetail )
private

Member Function Documentation

◆ addDirtyRect()

void InplaceTransformStrokeStrategy::addDirtyRect ( KisNodeSP node,
const QRect & rect,
int levelOfDetail )
private

Definition at line 1228 of file inplace_transform_stroke_strategy.cpp.

1228 {
1229 QMutexLocker l(&m_d->dirtyRectsMutex);
1230 m_d->effectiveDirtyRects(levelOfDetail).addUpdate(node, rect);
1231}

References m_d.

◆ calculateConvexHull()

QPolygon InplaceTransformStrokeStrategy::calculateConvexHull ( )
private

Definition at line 300 of file inplace_transform_stroke_strategy.cpp.

301{
302 // Best effort attempt to calculate the convex hull, mimicking the
303 // approach that computes srcRect in initStrokeCallback below
304 KisPaintDeviceSP externalSource =
305 m_d->externalSource ? m_d->externalSource :
306 m_d->initialTransformArgs.externalSource() ?
307 m_d->initialTransformArgs.externalSource() : 0;
308
309 QVector<QPoint> points;
310 if (externalSource) {
311 points = KisConvexHull::findConvexHull(externalSource);
312 } else if (m_d->selection) {
313 points = KisConvexHull::findConvexHull(m_d->selection->pixelSelection());
314 } else {
315 int numContributions = 0;
316 Q_FOREACH (KisNodeSP node, m_d->processedNodes) {
317 if (node->inherits("KisGroupLayer")) continue;
318
319 if (dynamic_cast<const KisTransformMask*>(node.data())) {
320 return QPolygon(); // Produce no convex hull if a KisTransformMask is present
321 } else {
322 KisPaintDeviceSP device;
323 // Get the original device as per createCacheAndClearNode below
324 if (KisExternalLayer *extLayer = dynamic_cast<KisExternalLayer*>(node.data())) {
325 device = extLayer->projection();
326 } else {
327 device = node->paintDevice();
328 }
329 if (device) {
330 // Use the original device to get the cached device containing the original image data
331 KisPaintDeviceSP toUse;
332 {
333 QMutexLocker l(&m_d->devicesCacheMutex);
334 if (m_d->devicesCacheHash.contains(device.data())) {
335 toUse = m_d->devicesCacheHash[device.data()];
336 } else {
337 toUse = device;
338 }
339 }
340 /* This sometimes does not agree with the original exactBounds
341 because of colorspace changes between the original device
342 and cached. E.g. When the defaultPixel changes as follows it
343 triggers different behavior in calculateExactBounds:
344 KoColor ("ALPHA", "Alpha":0) => KoColor ("GRAYA", "Gray":0, "Alpha":255)
345 */
346 const bool isConvertedSelection =
347 node->paintDevice() &&
349 *toUse->colorSpace() == *node->paintDevice()->compositionSourceColorSpace();
350
351
352 QPolygon polygon = isConvertedSelection ?
355
356 points += polygon;
357
358 numContributions += 1;
359 } else {
360 // When can this happen? Should it continue instead?
361 ENTER_FUNCTION() << "Bailing out, device was null" << ppVar(node);
362 return QPolygon();
363 }
364 }
365 }
366 if (numContributions > 1) {
367 points = KisConvexHull::findConvexHull(points);
368 }
369 }
370 return QPolygon(points);
371}
const KoID AlphaColorModelID("A", ki18n("Alpha mask"))
virtual const KoColorSpace * compositionSourceColorSpace() const
const KoColorSpace * colorSpace() const
virtual KoID colorModelId() const =0
#define ENTER_FUNCTION()
Definition kis_debug.h:178
#define ppVar(var)
Definition kis_debug.h:155
QPolygon findConvexHullSelectionLike(KisPaintDeviceSP device)
QPolygon findConvexHull(const QVector< QPoint > &points)
virtual KisPaintDeviceSP paintDevice() const =0

References AlphaColorModelID, KoColorSpace::colorModelId(), KisPaintDevice::colorSpace(), KisPaintDevice::compositionSourceColorSpace(), KisSharedPtr< T >::data(), ENTER_FUNCTION, KisConvexHull::findConvexHull(), KisConvexHull::findConvexHullSelectionLike(), m_d, KisBaseNode::paintDevice(), and ppVar.

◆ calculatePreferredLevelOfDetail()

int InplaceTransformStrokeStrategy::calculatePreferredLevelOfDetail ( const QRect & srcRect)
private

Definition at line 270 of file inplace_transform_stroke_strategy.cpp.

271{
272 KisLodPreferences lodPreferences = this->currentLodPreferences();
273 if (!lodPreferences.lodSupported() ||
274 !(lodPreferences.lodPreferred() || m_d->forceLodMode)) return -1;
275
276 const int maxSize = 2000;
277 const int maxDimension = KisAlgebra2D::maxDimension(srcRect);
278
279 const qreal zoom = qMax(1.0, qreal(maxDimension) / maxSize);
280
281 const int calculatedLod = qCeil(std::log2(zoom));
282
283 return qMax(calculatedLod, lodPreferences.desiredLevelOfDetail());
284
285}
KisLodPreferences currentLodPreferences() const
QAction * zoom(const QObject *recvr, const char *slot, QObject *parent)
auto maxDimension(Size size) -> decltype(size.width())
int desiredLevelOfDetail() const
bool lodSupported() const
bool lodPreferred() const

References KisStrokeStrategy::currentLodPreferences(), KisLodPreferences::desiredLevelOfDetail(), KisLodPreferences::lodPreferred(), KisLodPreferences::lodSupported(), m_d, and KisAlgebra2D::maxDimension().

◆ cancelAction()

void InplaceTransformStrokeStrategy::cancelAction ( QVector< KisStrokeJobData * > & mutatedJobs)
private

It is too late to cancel anything, the transformation has been completed and its commands have been pushed into the undo adapter, so there is no way to stop that.

We could have issues updates on lodN planes, which did not end up in the final lod0 update, so we should reupload lod0 data to the UI part manually.

We could have issues updates on lodN planes, which did not end up in the final lod0 update, so we should reupload lod0 data to the UI part manually.

Definition at line 1133 of file inplace_transform_stroke_strategy.cpp.

1134{
1140 if (m_d->strokeCompletionHasBeenStarted) return;
1141
1142
1143 KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->transformMaskCacheHash.isEmpty() ||
1144 (m_d->transformMaskCacheHash.size() == 1 && m_d->processedNodes.size() == 1));
1145
1146 const bool isChangingTransformMask = !m_d->transformMaskCacheHash.isEmpty();
1147
1148 if (m_d->initialTransformArgs.isIdentity()) {
1149 KritaUtils::addJobSequential(mutatedJobs, [this]() {
1150 Q_FOREACH (KisTransformMask *mask, m_d->transformMaskCacheHash.keys()) {
1152 }
1153 });
1154
1155
1156 KritaUtils::addJobBarrier(mutatedJobs, [this]() {
1157 m_d->commandUpdatesBlockerCookie.reset();
1160 });
1161
1162 if (m_d->previewLevelOfDetail > 0) {
1168 KritaUtils::addJobBarrier(mutatedJobs, [this]() { Q_UNUSED(this) });
1169 repopulateUI(mutatedJobs, m_d->updatesFacade, m_d->updatesFacade->bounds());
1170 }
1171
1172 finalizeStrokeImpl(mutatedJobs, false);
1173
1174 KritaUtils::addJobSequential(mutatedJobs, [this]() {
1175 Q_FOREACH (KisTransformMask *mask, m_d->transformMaskCacheHash.keys()) {
1177 }
1178 });
1179
1180 KritaUtils::addJobBarrier(mutatedJobs, [this]() {
1182 });
1183 } else {
1184 KIS_SAFE_ASSERT_RECOVER_NOOP(isChangingTransformMask || m_d->overriddenCommand);
1185
1186 KritaUtils::addJobSequential(mutatedJobs, [this]() {
1187 Q_FOREACH (KisTransformMask *mask, m_d->transformMaskCacheHash.keys()) {
1189 }
1190 });
1191
1192 reapplyTransform(m_d->initialTransformArgs, mutatedJobs, 0, true);
1193
1194 if (m_d->previewLevelOfDetail > 0) {
1200 KritaUtils::addJobBarrier(mutatedJobs, [this]() { Q_UNUSED(this) });
1201 repopulateUI(mutatedJobs, m_d->updatesFacade, m_d->updatesFacade->bounds());
1202 }
1203
1204 mutatedJobs << new UpdateTransformData(m_d->initialTransformArgs,
1206
1207 finalizeStrokeImpl(mutatedJobs, bool(m_d->overriddenCommand));
1208
1209 KritaUtils::addJobSequential(mutatedJobs, [this]() {
1210 Q_FOREACH (KisTransformMask *mask, m_d->transformMaskCacheHash.keys()) {
1212 }
1213 });
1214
1215 if (m_d->overriddenCommand) {
1216 KritaUtils::addJobBarrier(mutatedJobs, [this]() {
1217 m_d->currentTransformArgs = m_d->initialTransformArgs;
1219 });
1220 } else {
1221 KritaUtils::addJobBarrier(mutatedJobs, [this]() {
1223 });
1224 }
1225 }
1226}
void finalizeStrokeImpl(QVector< KisStrokeJobData * > &mutatedJobs, bool saveCommands)
static void repopulateUI(QVector< KisStrokeJobData * > &mutatedJobs, KisUpdatesFacade *updatesFacade, const QRect &dirtyRect)
void reapplyTransform(ToolTransformArgs args, QVector< KisStrokeJobData * > &mutatedJobs, int levelOfDetail, bool useHoldUI)
void addJobSequential(QVector< Job * > &jobs, Func func)
void addJobBarrier(QVector< Job * > &jobs, Func func)
void threadSafeForceStaticImageUpdate(const QRect &extraUpdateRect)
void overrideStaticCacheDevice(KisPaintDeviceSP device)

References KritaUtils::addJobBarrier(), KritaUtils::addJobSequential(), KisStrokeStrategyUndoCommandBased::cancelStrokeCallback(), finalizeStrokeImpl(), KisStrokeStrategyUndoCommandBased::finishStrokeCallback(), KIS_SAFE_ASSERT_RECOVER_NOOP, m_d, KisTransformMask::overrideStaticCacheDevice(), reapplyTransform(), repopulateUI(), InplaceTransformStrokeStrategy::UpdateTransformData::SELECTION, KisTransformMask::threadSafeForceStaticImageUpdate(), undoAllCommands(), and undoTransformCommands().

◆ cancelStrokeCallback()

void InplaceTransformStrokeStrategy::cancelStrokeCallback ( )
overridevirtual

Reimplemented from KisSimpleStrokeStrategy.

Definition at line 662 of file inplace_transform_stroke_strategy.cpp.

663{
664 QVector<KisStrokeJobData *> mutatedJobs;
665
666 cancelAction(mutatedJobs);
667
668 if (!mutatedJobs.isEmpty()) {
669 addMutatedJobs(mutatedJobs);
670 }
671}
void cancelAction(QVector< KisStrokeJobData * > &mutatedJobs)
void addMutatedJobs(const QVector< KisStrokeJobData * > list)

References KisStrokeStrategy::addMutatedJobs(), and cancelAction().

◆ createCacheAndClearNode()

void InplaceTransformStrokeStrategy::createCacheAndClearNode ( KisNodeSP node)
private

Definition at line 873 of file inplace_transform_stroke_strategy.cpp.

874{
875 KisPaintDeviceSP device;
876 CommandGroup commandGroup = Clear;
877
878 if (KisExternalLayer *extLayer =
879 dynamic_cast<KisExternalLayer*>(node.data())) {
880
883 extLayer->supportsPerspectiveTransform())) {
884
885 device = node->projection();
886 commandGroup = ClearTemporary;
887 }
888 } else if (KisTransformMask *mask = dynamic_cast<KisTransformMask*>(node.data())) {
890
891 // NOTE: this action should be either sequential or barrier
892 QMutexLocker l(&m_d->devicesCacheMutex);
893 if (!m_d->transformMaskCacheHash.contains(mask)) {
894
895 KisPaintDeviceSP dev = mask->buildSourcePreviewDevice();
896 m_d->transformMaskCacheHash.insert(mask, new KisPaintDevice(*dev));
897
898 return;
899 }
900
901 } else {
902 device = node->paintDevice();
903 }
904
905 if (device) {
906
907 {
908 QMutexLocker l(&m_d->devicesCacheMutex);
909
910 if (!m_d->devicesCacheHash.contains(device.data())) {
911 KisPaintDeviceSP cache;
912
913 // The image that will be transformed is linked to the original
914 // layer. We copy existing pixels or use an external source.
915 if (m_d->initialTransformArgs.externalSource()) {
916 cache = device->createCompositionSourceDevice(m_d->initialTransformArgs.externalSource());
917 } else if (m_d->selection) {
918 QRect srcRect = m_d->selection->selectedExactRect();
919
920 cache = device->createCompositionSourceDevice();
921 KisPainter gc(cache);
922 gc.setSelection(m_d->selection);
923 gc.bitBlt(srcRect.topLeft(), device, srcRect);
924 } else {
925 cache = device->createCompositionSourceDevice(device);
926 }
927
928 m_d->devicesCacheHash.insert(device.data(), cache);
929 }
930 }
931
932 // Don't clear the selection or layer when the source is external
933 if (m_d->initialTransformArgs.externalSource()) return;
934
935 KisTransaction transaction(device);
936 if (m_d->selection) {
937 device->clearSelection(m_d->selection);
938 } else {
939 QRect oldExtent = device->extent();
940 device->clear();
941 device->setDirty(oldExtent);
942 }
943
944 executeAndAddCommand(transaction.endAndTake(), commandGroup, KisStrokeJobData::CONCURRENT);
945 }
946}
void executeAndAddCommand(KUndo2Command *cmd, CommandGroup group, KisStrokeJobData::Sequentiality seq)
void setDirty(const QRect &rc)
virtual void clear()
KisPaintDeviceSP createCompositionSourceDevice() const
QRect extent() const
void clearSelection(KisSelectionSP selection)
virtual KisPaintDeviceSP projection() const =0

References KisPainter::bitBlt(), KisPaintDevice::clear(), Clear, KisPaintDevice::clearSelection(), ClearTemporary, KisStrokeJobData::CONCURRENT, KisPaintDevice::createCompositionSourceDevice(), KisSharedPtr< T >::data(), KisTransaction::endAndTake(), executeAndAddCommand(), KisPaintDevice::extent(), ToolTransformArgs::FREE_TRANSFORM, KIS_SAFE_ASSERT_RECOVER_NOOP, m_d, KisBaseNode::paintDevice(), ToolTransformArgs::PERSPECTIVE_4POINT, KisBaseNode::projection(), KisPaintDevice::setDirty(), and KisPainter::setSelection().

◆ doCanvasUpdate()

void InplaceTransformStrokeStrategy::doCanvasUpdate ( bool forceUpdate)
private

Definition at line 242 of file inplace_transform_stroke_strategy.cpp.

243{
244 if (!m_d->pendingUpdateArgs) return;
245
246 if (!forceUpdate &&
247 (m_d->updateTimer.elapsed() < m_d->updateInterval ||
248 m_d->updatesFacade->hasUpdatesRunning())) {
249
250 return;
251 }
252
254
255 ToolTransformArgs args = *m_d->pendingUpdateArgs;
256 m_d->pendingUpdateArgs = boost::none;
257
258 reapplyTransform(args, jobs, m_d->previewLevelOfDetail, false);
259
260 KritaUtils::addJobBarrier(jobs, [this, args]() {
261 m_d->currentTransformArgs = args;
262 m_d->updateTimer.restart();
263 // sanity check that no job has been squeezed in between
264 KIS_SAFE_ASSERT_RECOVER_RETURN(!m_d->pendingUpdateArgs);
265 });
266
267 addMutatedJobs(jobs);
268}
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128

References KritaUtils::addJobBarrier(), KisStrokeStrategy::addMutatedJobs(), KIS_SAFE_ASSERT_RECOVER_RETURN, m_d, and reapplyTransform().

◆ doStrokeCallback()

void InplaceTransformStrokeStrategy::doStrokeCallback ( KisStrokeJobData * data)
overridevirtual

Reimplemented from KisSimpleStrokeStrategy.

Definition at line 184 of file inplace_transform_stroke_strategy.cpp.

185{
186 if (UpdateTransformData *upd = dynamic_cast<UpdateTransformData*>(data)) {
187 if (upd->destination == UpdateTransformData::PAINT_DEVICE) {
188 m_d->pendingUpdateArgs = upd->args;
189 tryPostUpdateJob(false);
190 } else if (m_d->selection) {
191 // NOTE: selection is hidden during the transformation, so we
192 // don't have to do any preview for that. We transform
193 // that in one go in the end of the stroke.
194
195 KisTransaction transaction(m_d->selection->pixelSelection());
196
197 KisProcessingVisitor::ProgressHelper helper(m_d->imageRoot.data());
199 m_d->selection->pixelSelection(), &helper);
200
201 runAndSaveCommand(KUndo2CommandSP(transaction.endAndTake()),
203 }
204
205 } else if (BarrierUpdateData *barrierData = dynamic_cast<BarrierUpdateData *>(data)) {
206
207 doCanvasUpdate(barrierData->forceUpdate);
208
209 } else if (KisAsynchronousStrokeUpdateHelper::UpdateData *updateData =
211
212 tryPostUpdateJob(updateData->forceUpdate);
213
214 } else if (dynamic_cast<CalculateConvexHullData*>(data)) {
215
216 if (!m_d->convexHullHasBeenCalculated) {
217 m_d->convexHullHasBeenCalculated = true;
218 QPolygon hull = calculateConvexHull();
219 if (!hull.isEmpty()) {
220 Q_EMIT sigConvexHullCalculated(hull, this);
221 }
222 }
223
224 } else {
226 }
227
228}
void sigConvexHullCalculated(QPolygon convexHull, void *cookie)
void runAndSaveCommand(KUndo2CommandSP command, KisStrokeJobData::Sequentiality sequentiality, KisStrokeJobData::Exclusivity exclusivity)
void doStrokeCallback(KisStrokeJobData *data) override
static void transformDevice(const ToolTransformArgs &config, KisPaintDeviceSP device, KisProcessingVisitor::ProgressHelper *helper)
QSharedPointer< KUndo2Command > KUndo2CommandSP
Definition kis_types.h:262

References calculateConvexHull(), KisStrokeJobData::CONCURRENT, doCanvasUpdate(), KisStrokeStrategyUndoCommandBased::doStrokeCallback(), KisTransaction::endAndTake(), m_d, KisStrokeJobData::NORMAL, InplaceTransformStrokeStrategy::UpdateTransformData::PAINT_DEVICE, KisStrokeStrategyUndoCommandBased::runAndSaveCommand(), sigConvexHullCalculated(), KisTransformUtils::transformDevice(), and tryPostUpdateJob().

◆ executeAndAddCommand()

void InplaceTransformStrokeStrategy::executeAndAddCommand ( KUndo2Command * cmd,
InplaceTransformStrokeStrategy::CommandGroup group,
KisStrokeJobData::Sequentiality seq )
private

Definition at line 688 of file inplace_transform_stroke_strategy.cpp.

689{
690 QMutexLocker l(&m_d->commandsMutex);
691 KUndo2CommandSP sharedCommand = toQShared(cmd);
692 executeCommand(sharedCommand, false);
693 m_d->commands.append({group, sharedCommand, seq});
694}
void executeCommand(KUndo2CommandSP command, bool undo)
QSharedPointer< T > toQShared(T *ptr)

References KisStrokeStrategyUndoCommandBased::executeCommand(), m_d, and toQShared().

◆ fetchAllUpdateRequests()

void InplaceTransformStrokeStrategy::fetchAllUpdateRequests ( int levelOfDetail,
KisBatchNodeUpdateSP updateData )
private

Definition at line 743 of file inplace_transform_stroke_strategy.cpp.

744{
745 KisBatchNodeUpdate &dirtyRects = m_d->effectiveDirtyRects(levelOfDetail);
746 KisBatchNodeUpdate &prevDirtyRects = m_d->effectivePrevDirtyRects(levelOfDetail);
747
748 *updateData = (prevDirtyRects | dirtyRects).compressed();
749
750 KisBatchNodeUpdate savedUndoRects = dirtyRects;
751
752 if (levelOfDetail > 0) {
753
754 for (auto it = savedUndoRects.begin(); it != savedUndoRects.end(); ++it) {
755 it->second = KisLodTransform::upscaledRect(it->second, levelOfDetail);
756 }
757 }
758
759 *m_d->updateDataForUndo = (m_d->initialUpdatesBeforeClear | savedUndoRects).compressed();
760 prevDirtyRects.clear();
761 dirtyRects.swap(prevDirtyRects);
762}
static QRect upscaledRect(const QRect &srcRect, int lod)

References m_d, and KisLodTransformBase::upscaledRect().

◆ finalizeStrokeImpl()

void InplaceTransformStrokeStrategy::finalizeStrokeImpl ( QVector< KisStrokeJobData * > & mutatedJobs,
bool saveCommands )
private

Definition at line 1015 of file inplace_transform_stroke_strategy.cpp.

1016{
1017 KritaUtils::addJobBarrier(mutatedJobs, [this]() {
1018 Q_FOREACH (KisSelectionSP selection, m_d->deactivatedSelections) {
1019 selection->setVisible(true);
1020 }
1021
1022 Q_FOREACH(KisSelectionMaskSP deactivatedOverlaySelectionMask, m_d->deactivatedOverlaySelectionMasks) {
1023 deactivatedOverlaySelectionMask->selection()->setVisible(true);
1024 deactivatedOverlaySelectionMask->setDirty();
1025 }
1026
1027 m_d->commandUpdatesBlockerCookie.reset();
1028 });
1029
1030
1031 if (saveCommands) {
1032 KritaUtils::addJobBarrier(mutatedJobs, [this]() {
1034 m_d->commands.clear();
1035 });
1036 }
1037}
KisSelectionSP selection
Definition kis_mask.cc:44
void setDirty(const QVector< QRect > &rects) override
void setVisible(bool visible)

References KritaUtils::addJobBarrier(), m_d, notifyAllCommandsDone(), KisMask::selection, KisSelectionMask::setDirty(), and KisSelection::setVisible().

◆ finishAction()

void InplaceTransformStrokeStrategy::finishAction ( QVector< KisStrokeJobData * > & mutatedJobs)
private
  • Forward to cancelling should happen before the guard for finalizingActionsStarted.
  • Transform masks may switch mode and become identity, that shouldn't be cancelled.

Update jobs from level of detail updates may cause dirtying of the transform mask's static cache device. Therefore we must ensure that final update of the mask happens strictly after them.

Transform masks don't have internal state switch for LoD mode, therefore all the preview transformations must be cancelled before applying the final command

We could have issues updates on lodN planes, which did not end up in the final lod0 update, so we should reupload lod0 data to the UI part manually.

Definition at line 1052 of file inplace_transform_stroke_strategy.cpp.

1053{
1061 if (m_d->currentTransformArgs.isUnchanging() &&
1062 m_d->transformMaskCacheHash.isEmpty() &&
1063 !m_d->overriddenCommand) {
1064
1065 cancelAction(mutatedJobs);
1066 return;
1067 }
1068
1069 if (m_d->previewLevelOfDetail > 0) {
1076 KritaUtils::addJobBarrier(mutatedJobs, [this]() { Q_UNUSED(this) });
1077
1078 if (!m_d->transformMaskCacheHash.isEmpty()) {
1079 KritaUtils::addJobSequential(mutatedJobs, [this]() {
1080 Q_FOREACH (KisTransformMask *mask, m_d->transformMaskCacheHash.keys()) {
1082 }
1083
1089 undoTransformCommands(m_d->previewLevelOfDetail);
1090 });
1091 }
1092
1093 reapplyTransform(m_d->currentTransformArgs, mutatedJobs, 0, true);
1094
1100 KritaUtils::addJobBarrier(mutatedJobs, [this]() { Q_UNUSED(this) });
1101 repopulateUI(mutatedJobs, m_d->updatesFacade, m_d->updatesFacade->bounds());
1102
1103 } else {
1104 if (m_d->pendingUpdateArgs) {
1105 mutatedJobs << new BarrierUpdateData(true);
1106 }
1107 }
1108
1109 mutatedJobs << new UpdateTransformData(m_d->currentTransformArgs,
1111
1112 // the rest of the transform finishing work cannot be cancelled...
1113 KritaUtils::addJobBarrier(mutatedJobs, [this]() {
1114 m_d->strokeCompletionHasBeenStarted = true;
1115
1116 QVector<KisStrokeJobData *> nonCancellableFinishJobs;
1117
1118 finalizeStrokeImpl(nonCancellableFinishJobs, true);
1119
1120 KritaUtils::addJobBarrier(nonCancellableFinishJobs, [this]() {
1122 });
1123
1124 for (auto it = nonCancellableFinishJobs.begin(); it != nonCancellableFinishJobs.end(); ++it) {
1125 (*it)->setCancellable(false);
1126 }
1127
1128 this->addMutatedJobs(nonCancellableFinishJobs);
1129
1130 });
1131}

References KritaUtils::addJobBarrier(), KritaUtils::addJobSequential(), KisStrokeStrategy::addMutatedJobs(), cancelAction(), finalizeStrokeImpl(), KisStrokeStrategyUndoCommandBased::finishStrokeCallback(), m_d, KisTransformMask::overrideStaticCacheDevice(), reapplyTransform(), repopulateUI(), InplaceTransformStrokeStrategy::UpdateTransformData::SELECTION, and undoTransformCommands().

◆ finishStrokeCallback()

void InplaceTransformStrokeStrategy::finishStrokeCallback ( )
overridevirtual

Reimplemented from KisSimpleStrokeStrategy.

Definition at line 651 of file inplace_transform_stroke_strategy.cpp.

652{
653 QVector<KisStrokeJobData *> mutatedJobs;
654
655 finishAction(mutatedJobs);
656
657 if (!mutatedJobs.isEmpty()) {
658 addMutatedJobs(mutatedJobs);
659 }
660}
void finishAction(QVector< KisStrokeJobData * > &mutatedJobs)

References KisStrokeStrategy::addMutatedJobs(), and finishAction().

◆ initStrokeCallback()

void InplaceTransformStrokeStrategy::initStrokeCallback ( )
overridevirtual

In case we are doing a continued action, we need to set initial update to the "very initial" state that was present before the previous stroke. So here we just override the rect calculated before

We need to make sure that the nodes will be successfully be transformed back in case the stroke will be finished before sigTransactionGenerated() signal is delivered.

We must request shape layers to rerender areas outside image bounds

We must ensure that the currently selected subtree has finished all its updates.

Disable all decorated nodes to generate outline and preview correctly. We will enable them back as soon as preview generation is finished.

Filter masks have special handling of transparency. Their filter may declare if they affect transparent pixels or not. In case of transformations we don't care about that, we should just transform non-default area of the mask.

We shouldn't include masks or layer styles into the handles rect, in the end, we process the paint device only

recover back visibility of decorated nodes

Reimplemented from KisSimpleStrokeStrategy.

Definition at line 374 of file inplace_transform_stroke_strategy.cpp.

375{
377
378 m_d->currentTime = KisTransformUtils::fetchCurrentImageTime(m_d->rootNodes);
379
380 QVector<KisStrokeJobData *> extraInitJobs;
381
382 if (m_d->selection) {
383 m_d->selection->setVisible(false);
384 m_d->deactivatedSelections.append(m_d->selection);
385 }
386
387 Q_FOREACH(KisNodeSP node, m_d->rootNodes) {
388 KisSelectionMaskSP overlaySelectionMask =
389 dynamic_cast<KisSelectionMask*>(node->graphListener()->graphOverlayNode());
390 if (overlaySelectionMask && node != KisNodeSP(overlaySelectionMask)) {
391 overlaySelectionMask->setDecorationsVisible(false);
392 m_d->deactivatedOverlaySelectionMasks.append(overlaySelectionMask);
393 }
394 }
395
396 if (m_d->rootNodes.size() == 1) {
397 KisNodeSP rootNode = m_d->rootNodes[0];
399
400 if (rootNode->inherits("KisTransformMask") && rootNode->projectionLeaf()->isDroppedNode()) {
401 rootNode.clear();
402 m_d->processedNodes.clear();
403
404 TransformTransactionProperties transaction(QRect(), &m_d->initialTransformArgs, m_d->rootNodes, m_d->processedNodes);
405 Q_EMIT sigTransactionGenerated(transaction, m_d->initialTransformArgs, this);
406 return;
407 }
408 }
409
410 // When placing an external source image, we never work recursively on any layer masks
411 m_d->processedNodes = KisTransformUtils::fetchNodesList(m_d->mode, m_d->rootNodes, m_d->externalSource, m_d->selection);
412
413 bool argsAreInitialized = false;
414 QVector<KisStrokeJobData *> lastCommandUndoJobs;
415
416 // When externalSource is set, it means that we are initializing a new
417 // stroke following a newActivationWithExternalSource, thus we never try
418 // to reuse an existing transformation from the undo queue. However, when
419 // externalSource is not set, tryFetchArgsFromCommandAndUndo may still
420 // recover a previous stroke that referenced an external source.
421 if (!m_d->forceReset && !m_d->externalSource) {
423 m_d->mode,
424 m_d->rootNodes,
425 m_d->processedNodes,
426 m_d->undoFacade,
427 m_d->currentTime,
428 &lastCommandUndoJobs,
429 &m_d->overriddenCommand)) {
430 argsAreInitialized = true;
431 } else if (KisTransformUtils::tryInitArgsFromNode(m_d->rootNodes, &m_d->initialTransformArgs)) {
432 argsAreInitialized = true;
433 }
434 }
435
436 KritaUtils::addJobBarrier(extraInitJobs, [this]() {
437 Q_FOREACH (KisNodeSP node, m_d->processedNodes) {
438 m_d->prevDirtyRects.addUpdate(node, node->projectionPlane()->tightUserVisibleBounds());
439 }
440
441 m_d->initialUpdatesBeforeClear = m_d->prevDirtyRects.compressed();
442 m_d->updateDataForUndo.reset(new KisBatchNodeUpdate(m_d->initialUpdatesBeforeClear));
443
444 executeAndAddCommand(new KisUpdateCommandEx(m_d->updateDataForUndo, m_d->updatesFacade, KisUpdateCommandEx::INITIALIZING, m_d->commandUpdatesBlockerCookie), Clear, KisStrokeJobData::BARRIER);
446 });
447
448 extraInitJobs << lastCommandUndoJobs;
449
450 if (!lastCommandUndoJobs.isEmpty()) {
451 KritaUtils::addJobBarrier(extraInitJobs, [this]() {
458 KisBatchNodeUpdate updates;
459
460 Q_FOREACH (KisNodeSP node, m_d->processedNodes) {
461 updates.addUpdate(node, node->projectionPlane()->tightUserVisibleBounds());
462 }
463
464 m_d->initialUpdatesBeforeClear = updates.compressed();
465 *m_d->updateDataForUndo = m_d->initialUpdatesBeforeClear;
466
472 m_d->pendingUpdateArgs = m_d->initialTransformArgs;
473 });
474 }
475
476 KritaUtils::addJobSequential(extraInitJobs, [this]() {
477 // When dealing with animated transform mask layers, create keyframe and save the command for undo.
478 // NOTE: for transform masks we create a keyframe no matter what the user
479 // settings are
480 Q_FOREACH (KisNodeSP node, m_d->processedNodes) {
481 if (KisTransformMask* transformMask = dynamic_cast<KisTransformMask*>(node.data())) {
484 }
486 node->hasEditablePaintDevice()){
487
488 KUndo2Command *autoKeyframeCommand =
491 if (autoKeyframeCommand) {
493 }
494 }
495 }
496 });
497
498 KritaUtils::addJobSequential(extraInitJobs, [this]() {
502 Q_FOREACH(KisNodeSP node, m_d->rootNodes) {
504 }
505 });
506
507 KritaUtils::addJobBarrier(extraInitJobs, [this]() {
512 Q_FOREACH(KisNodeSP node, m_d->rootNodes) {
514 }
515 });
516
520 KritaUtils::addJobBarrier(extraInitJobs, [this]() {
521 Q_FOREACH (KisNodeSP node, m_d->processedNodes) {
522 KisDecoratedNodeInterface *decoratedNode = dynamic_cast<KisDecoratedNodeInterface*>(node.data());
523 if (decoratedNode && decoratedNode->decorationsVisible()) {
524 decoratedNode->setDecorationsVisible(false);
525 m_d->disabledDecoratedNodes << decoratedNode;
526 }
527 }
528 });
529
530 KritaUtils::addJobBarrier(extraInitJobs,
531 [this,
532 argsAreInitialized]() mutable {
533 QRect srcRect;
534
535 KisPaintDeviceSP externalSource =
536 m_d->externalSource ? m_d->externalSource :
537 (argsAreInitialized && m_d->initialTransformArgs.externalSource()) ?
538 m_d->initialTransformArgs.externalSource() : 0;
539
540 if (externalSource) {
541 // Start the transformation around the visible pixels of the external image
542 srcRect = externalSource->exactBounds();
543 } else if (m_d->selection) {
544 srcRect = m_d->selection->selectedExactRect();
545 } else {
546 srcRect = QRect();
547 Q_FOREACH (KisNodeSP node, m_d->processedNodes) {
548 // group layers may have a projection of layers
549 // that are locked and will not be transformed
550 if (node->inherits("KisGroupLayer")) continue;
551
552 if (const KisTransformMask *mask = dynamic_cast<const KisTransformMask*>(node.data())) {
553 srcRect |= mask->sourceDataBounds();
554 } else if (const KisSelectionMask *mask = dynamic_cast<const KisSelectionMask*>(node.data())) {
555 srcRect |= mask->selection()->selectedExactRect();
556 } else if (const KisTransparencyMask *mask = dynamic_cast<const KisTransparencyMask*>(node.data())) {
557 srcRect |= mask->selection()->selectedExactRect();
558 } else if (const KisFilterMask *mask = dynamic_cast<const KisFilterMask*>(node.data())) {
563 if (mask->paintDevice()) {
564 srcRect |= mask->paintDevice()->nonDefaultPixelArea();
565 }
566 } else {
569 srcRect |= node->paintDevice() ? node->paintDevice()->exactBounds() : node->exactBounds();
570 }
571 }
572 }
573
574 TransformTransactionProperties transaction(srcRect, &m_d->initialTransformArgs, m_d->rootNodes, m_d->processedNodes);
575 if (!argsAreInitialized) {
576 m_d->initialTransformArgs = KisTransformUtils::resetArgsForMode(m_d->mode, m_d->filterId, transaction, m_d->externalSource);
577 }
578 m_d->externalSource.clear();
579
580 const QRect imageBoundsRect = m_d->imageRoot->projection()->defaultBounds()->bounds();
581 m_d->previewLevelOfDetail = calculatePreferredLevelOfDetail(srcRect & imageBoundsRect);
582
583 if (m_d->previewLevelOfDetail > 0) {
584 for (auto it = m_d->prevDirtyRects.begin(); it != m_d->prevDirtyRects.end(); ++it) {
585 KisLodTransform t(m_d->previewLevelOfDetail);
586 m_d->prevDirtyPreviewRects.addUpdate(it->first, t.map(it->second));
587 }
588 }
589
590 if (transaction.currentConfig()->boundsRotation() != 0.0) {
591 m_d->convexHullHasBeenCalculated = true;
592 transaction.setConvexHull(calculateConvexHull());
593 transaction.setConvexHullHasBeenRequested(true);
594 }
595
596 Q_EMIT sigTransactionGenerated(transaction, m_d->initialTransformArgs, this);
597 });
598
600 KritaUtils::addJobBarrier(extraInitJobs, [this]() {
601 Q_FOREACH (KisDecoratedNodeInterface *decoratedNode, m_d->disabledDecoratedNodes) {
602 decoratedNode->setDecorationsVisible(true);
603 }
604 m_d->disabledDecoratedNodes.clear();
605 });
606
607 Q_FOREACH (KisNodeSP node, m_d->processedNodes) {
608 KritaUtils::addJobSequential(extraInitJobs, [this, node]() mutable {
610 });
611 }
612
613 KritaUtils::addJobBarrier(extraInitJobs, [this]() {
614 QMutexLocker l(&m_d->dirtyRectsMutex);
615
617
618 m_d->updateTimer.start();
619 });
620
621 if (!lastCommandUndoJobs.isEmpty()) {
622 KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->overriddenCommand);
623
624 for (auto it = extraInitJobs.begin(); it != extraInitJobs.end(); ++it) {
625 (*it)->setCancellable(false);
626 }
627 }
628
629 KritaUtils::addJobBarrier(extraInitJobs, [this]() {
630 if (m_d->previewLevelOfDetail > 0) {
631 QVector<KisStrokeJobData*> lodSyncJobs;
632
633 KisSyncLodCacheStrokeStrategy::createJobsData(lodSyncJobs,
634 m_d->imageRoot,
635 m_d->updatesFacade,
636 m_d->previewLevelOfDetail,
637 m_d->devicesCacheHash.values() +
638 m_d->transformMaskCacheHash.values());
639
640 for (auto it = lodSyncJobs.begin(); it != lodSyncJobs.end(); ++it) {
641 (*it)->setLevelOfDetailOverride(m_d->previewLevelOfDetail);
642 }
643
644 addMutatedJobs(lodSyncJobs);
645 }
646 });
647
648 addMutatedJobs(extraInitJobs);
649}
void sigTransactionGenerated(TransformTransactionProperties transaction, ToolTransformArgs args, void *cookie)
KisBatchNodeUpdate compressed() const
void addUpdate(KisNodeSP node, const QRect &rc)
virtual bool decorationsVisible() const =0
virtual void setDecorationsVisible(bool value, bool update)=0
QRect exactBounds() const
static ToolTransformArgs resetArgsForMode(ToolTransformArgs::TransformMode mode, const QString &filterId, const TransformTransactionProperties &transaction, KisPaintDeviceSP externalSource)
static int fetchCurrentImageTime(KisNodeList rootNodes)
static QList< KisNodeSP > fetchNodesList(ToolTransformArgs::TransformMode mode, KisNodeList rootNodes, bool isExternalSourcePresent, KisSelectionSP selection)
static KisNodeSP tryOverrideRootToTransformMask(KisNodeSP root)
static bool tryInitArgsFromNode(KisNodeList rootNodes, ToolTransformArgs *args)
static bool tryFetchArgsFromCommandAndUndo(ToolTransformArgs *outArgs, ToolTransformArgs::TransformMode mode, KisNodeList currentNodes, KisNodeList selectedNodes, KisStrokeUndoFacade *undoFacade, int currentTime, QVector< KisStrokeJobData * > *undoJobs, const KisSavedMacroCommand **overriddenCommand)
KisSharedPtr< KisNode > KisNodeSP
Definition kis_types.h:86
KUndo2Command * tryAutoCreateDuplicatedFrame(KisPaintDeviceSP device, AutoCreateKeyframeFlags flags)
create a new duplicated keyframe if auto-keyframe mode is on
void forceAllHiddenOriginalsUpdate(KisNodeSP root)
void forceAllDelayedNodesUpdate(KisNodeSP root)
virtual QRect exactBounds() const
bool hasEditablePaintDevice() const
virtual KisNode * graphOverlayNode() const
virtual KisAbstractProjectionPlaneSP projectionPlane() const
Definition kis_node.cpp:240
KisProjectionLeafSP projectionLeaf
Definition kis_node.cpp:93
KisNodeGraphListener * graphListener
Definition kis_node.cpp:87
void setDecorationsVisible(bool value, bool update) override

References KisAutoKey::activeMode(), KritaUtils::addJobBarrier(), KritaUtils::addJobSequential(), KisStrokeStrategy::addMutatedJobs(), KisBatchNodeUpdate::addUpdate(), KisStrokeJobData::BARRIER, ToolTransformArgs::boundsRotation(), calculateConvexHull(), calculatePreferredLevelOfDetail(), KisSharedPtr< T >::clear(), Clear, KisBatchNodeUpdate::compressed(), createCacheAndClearNode(), TransformTransactionProperties::currentConfig(), KisSharedPtr< T >::data(), KisDecoratedNodeInterface::decorationsVisible(), KisBaseNode::exactBounds(), KisPaintDevice::exactBounds(), executeAndAddCommand(), KisTransformUtils::fetchCurrentImageTime(), KisTransformUtils::fetchNodesList(), KisCommandUtils::FlipFlopCommand::FINALIZING, KisLayerUtils::forceAllDelayedNodesUpdate(), KisLayerUtils::forceAllHiddenOriginalsUpdate(), KisNode::graphListener, KisNodeGraphListener::graphOverlayNode(), KisBaseNode::hasEditablePaintDevice(), KisCommandUtils::FlipFlopCommand::INITIALIZING, KisStrokeStrategyUndoCommandBased::initStrokeCallback(), KIS_SAFE_ASSERT_RECOVER_NOOP, m_d, KisLodTransform::map(), KisLazyCreateTransformMaskKeyframesCommand::maskHasAnimation(), KisAutoKey::NONE, KisStrokeJobData::NORMAL, KisBaseNode::paintDevice(), KisNode::projectionLeaf, KisNode::projectionPlane(), KisTransformUtils::resetArgsForMode(), KisStrokeStrategyUndoCommandBased::runAndSaveCommand(), TransformTransactionProperties::setConvexHull(), TransformTransactionProperties::setConvexHullHasBeenRequested(), KisSelectionMask::setDecorationsVisible(), KisDecoratedNodeInterface::setDecorationsVisible(), sigTransactionGenerated(), KisAutoKey::SupportsLod, toQShared(), KisAutoKey::tryAutoCreateDuplicatedFrame(), KisTransformUtils::tryFetchArgsFromCommandAndUndo(), KisTransformUtils::tryInitArgsFromNode(), and KisTransformUtils::tryOverrideRootToTransformMask().

◆ notifyAllCommandsDone()

void InplaceTransformStrokeStrategy::notifyAllCommandsDone ( )
private

Definition at line 696 of file inplace_transform_stroke_strategy.cpp.

697{
698 for (auto it = m_d->commands.begin(); it != m_d->commands.end(); ++it) {
699 if (it->commandGroup == Clear) {
700 notifyCommandDone(it->command, it->sequentiality, KisStrokeJobData::NORMAL);
701 }
702 }
703
705
706 for (auto it = m_d->commands.begin(); it != m_d->commands.end(); ++it) {
707 if (it->commandGroup == Transform) {
708 notifyCommandDone(it->command, it->sequentiality, KisStrokeJobData::NORMAL);
709 }
710 }
711}
void notifyCommandDone(KUndo2CommandSP command, KisStrokeJobData::Sequentiality sequentiality, KisStrokeJobData::Exclusivity exclusivity)

References Clear, m_d, KisStrokeJobData::NORMAL, KisStrokeStrategyUndoCommandBased::notifyCommandDone(), KisStrokeJobData::SEQUENTIAL, toQShared(), and Transform.

◆ postProcessToplevelCommand()

void InplaceTransformStrokeStrategy::postProcessToplevelCommand ( KUndo2Command * command)
overrideprotectedvirtual

Applies some modifications (e.g. assigning extra data) to the toplevel command.

Reimplemented from KisStrokeStrategyUndoCommandBased.

Definition at line 288 of file inplace_transform_stroke_strategy.cpp.

289{
291 m_d->currentTransformArgs,
292 m_d->rootNodes,
293 m_d->processedNodes,
294 m_d->currentTime,
295 m_d->overriddenCommand);
296
298}
virtual void postProcessToplevelCommand(KUndo2Command *command)
Applies some modifications (e.g. assigning extra data) to the toplevel command.
static void postProcessToplevelCommand(KUndo2Command *command, const ToolTransformArgs &args, KisNodeList rootNodes, KisNodeList processedNodes, int currentTime, const KisSavedMacroCommand *overriddenCommand)

References m_d, KisStrokeStrategyUndoCommandBased::postProcessToplevelCommand(), and KisTransformUtils::postProcessToplevelCommand().

◆ Q_ENUM()

InplaceTransformStrokeStrategy::Q_ENUM ( CommandGroup )

◆ reapplyTransform()

void InplaceTransformStrokeStrategy::reapplyTransform ( ToolTransformArgs args,
QVector< KisStrokeJobData * > & mutatedJobs,
int levelOfDetail,
bool useHoldUI )
private

We also Q_EMIT the updates manually, because KisUpdateCommandEx is still blocked by m_d->commandUpdatesBlockerCookie (for easy undo purposes)

Definition at line 948 of file inplace_transform_stroke_strategy.cpp.

952{
953 if (levelOfDetail > 0) {
955 }
956
958
959 CommandGroup commandGroup =
960 levelOfDetail > 0 ? TransformLod : Transform;
961
962 KritaUtils::addJobBarrier(mutatedJobs, levelOfDetail,
963 [this, args, levelOfDetail, updateData, useHoldUI, commandGroup]() {
964
965 // it has its own dirty requests blocking inside
966 undoTransformCommands(levelOfDetail);
967
968 if (useHoldUI) {
970 }
971
973 });
974
975 Q_FOREACH (KisNodeSP node, m_d->processedNodes) {
976 KritaUtils::addJobConcurrent(mutatedJobs, levelOfDetail,
977 [this, node, args, levelOfDetail]() {
978 transformNode(node, args, levelOfDetail);
979 });
980 }
981
982 KritaUtils::addJobBarrier(mutatedJobs, levelOfDetail,
983 [this, levelOfDetail, updateData, useHoldUI, commandGroup]() {
984
985 fetchAllUpdateRequests(levelOfDetail, updateData);
986
988 executeAndAddCommand(new KisUpdateCommandEx(m_d->updateDataForUndo, m_d->updatesFacade, KisUpdateCommandEx::FINALIZING, m_d->commandUpdatesBlockerCookie), commandGroup, KisStrokeJobData::BARRIER);
989
990 if (useHoldUI) {
992 }
993
999 for (auto it = updateData->begin(); it != updateData->end(); ++it) {
1000 KisTransformMask *transformMask = dynamic_cast<KisTransformMask*>(it->first.data());
1001
1002 if (transformMask &&
1003 ((levelOfDetail <= 0 && !transformMask->transformParams()->isAffine()) ||
1004 (levelOfDetail <= 0 && m_d->previewLevelOfDetail > 0))) {
1005
1006 transformMask->threadSafeForceStaticImageUpdate(it->second);
1007 } else {
1008 m_d->updatesFacade->refreshGraphAsync(it->first, it->second);
1009 }
1010
1011 }
1012 });
1013}
void transformNode(KisNodeSP node, const ToolTransformArgs &config, int levelOfDetail)
void fetchAllUpdateRequests(int levelOfDetail, KisBatchNodeUpdateSP updateData)
static qreal lodToScale(int levelOfDetail)
void scale3dSrcAndDst(qreal scale)
void addJobConcurrent(QVector< Job * > &jobs, Func func)

References KritaUtils::addJobBarrier(), KritaUtils::addJobConcurrent(), KisStrokeJobData::BARRIER, executeAndAddCommand(), fetchAllUpdateRequests(), KisCommandUtils::FlipFlopCommand::FINALIZING, KisCommandUtils::FlipFlopCommand::INITIALIZING, KisLodTransformBase::lodToScale(), m_d, ToolTransformArgs::scale3dSrcAndDst(), KisTransformMask::threadSafeForceStaticImageUpdate(), Transform, TransformLod, transformNode(), and undoTransformCommands().

◆ repopulateUI()

void InplaceTransformStrokeStrategy::repopulateUI ( QVector< KisStrokeJobData * > & mutatedJobs,
KisUpdatesFacade * updatesFacade,
const QRect & dirtyRect )
staticprivate

Definition at line 1039 of file inplace_transform_stroke_strategy.cpp.

1040{
1041 const QVector<QRect> finalDirtyRects =
1044
1045 Q_FOREACH (const QRect &rc, finalDirtyRects) {
1046 KritaUtils::addJobConcurrent(mutatedJobs, [rc, updatesFacade] () {
1047 updatesFacade->notifyUIUpdateCompleted(rc);
1048 });
1049 }
1050}
virtual void notifyUIUpdateCompleted(const QRect &rc)=0
QVector< QRect > splitRectIntoPatchesTight(const QRect &rc, const QSize &patchSize)
QSize optimalPatchSize()

References KritaUtils::addJobConcurrent(), KisUpdatesFacade::notifyUIUpdateCompleted(), KritaUtils::optimalPatchSize(), and KritaUtils::splitRectIntoPatchesTight().

◆ sigConvexHullCalculated

void InplaceTransformStrokeStrategy::sigConvexHullCalculated ( QPolygon convexHull,
void * cookie )
signal

◆ sigTransactionGenerated

void InplaceTransformStrokeStrategy::sigTransactionGenerated ( TransformTransactionProperties transaction,
ToolTransformArgs args,
void * cookie )
signal

◆ transformNode()

void InplaceTransformStrokeStrategy::transformNode ( KisNodeSP node,
const ToolTransformArgs & config,
int levelOfDetail )
private

we should make sure that the asynchronous shape regeneration has completed before we issue the updates a bit later

Shape layer's projection may not be yet ready right after transformation, because it need to do that in the GUI thread, so we should approximate that.

Definition at line 764 of file inplace_transform_stroke_strategy.cpp.

765{
766 KisPaintDeviceSP device = node->paintDevice();
767
768 CommandGroup commandGroup =
769 levelOfDetail > 0 ? TransformLod : Transform;
770
771 if (KisExternalLayer *extLayer =
772 dynamic_cast<KisExternalLayer*>(node.data())) {
773
774 if (config.mode() == ToolTransformArgs::FREE_TRANSFORM ||
776 extLayer->supportsPerspectiveTransform())) {
777
778 if (levelOfDetail <= 0) {
779 const QRect oldDirtyRect = extLayer->projectionPlane()->tightUserVisibleBounds() | extLayer->theoreticalBoundingRect();
780
782 QTransform t = w.transform();
783 KUndo2Command *cmd = extLayer->transform(t);
784
786
789 if (KisShapeLayer *shapeLayer = dynamic_cast<KisShapeLayer*>(extLayer)) {
790 shapeLayer->forceUpdateTimedNode();
791 }
792
798 const QRect theoreticalNewDirtyRect =
799 kisGrowRect(t.mapRect(oldDirtyRect), 1);
800
801 addDirtyRect(node,
802 oldDirtyRect |
803 theoreticalNewDirtyRect |
804 extLayer->projectionPlane()->tightUserVisibleBounds() |
805 extLayer->theoreticalBoundingRect(), 0);
806 return;
807 } else {
808 device = node->projection();
809 commandGroup = TransformLodTemporary;
810 }
811 }
812
813 } else {
814 device = node->paintDevice();
815 }
816
817 if (device) {
818 KisPaintDeviceSP cachedPortion;
819
820 {
821 QMutexLocker l(&m_d->devicesCacheMutex);
822 cachedPortion = m_d->devicesCacheHash[device.data()];
823 }
824
825 KIS_SAFE_ASSERT_RECOVER_RETURN(cachedPortion);
826
827 KisTransaction transaction(device);
828
830 KisTransformUtils::transformAndMergeDevice(config, cachedPortion,
831 device, &helper);
832
833 executeAndAddCommand(transaction.endAndTake(), commandGroup, KisStrokeJobData::CONCURRENT);
834 addDirtyRect(node, cachedPortion->extent() | node->projectionPlane()->tightUserVisibleBounds(), levelOfDetail);
835
836 } else if (KisTransformMask *transformMask =
837 dynamic_cast<KisTransformMask*>(node.data())) {
838
839 const QRect oldDirtyRect = transformMask->extent();
840
841 if (levelOfDetail > 0) {
842 KisPaintDeviceSP cachedPortion;
843
844 {
845 QMutexLocker l(&m_d->devicesCacheMutex);
846 cachedPortion = m_d->transformMaskCacheHash[transformMask];
847 }
848
849 KIS_SAFE_ASSERT_RECOVER_RETURN(cachedPortion);
850
851 KisPaintDeviceSP dst = new KisPaintDevice(cachedPortion->colorSpace());
852 dst->prepareClone(cachedPortion);
853
855 KisTransformUtils::transformAndMergeDevice(config, cachedPortion,
856 dst, &helper);
857
858 transformMask->overrideStaticCacheDevice(dst);
859 }
860
861 {
862 KUndo2Command *cmd = new KisSimpleModifyTransformMaskCommand(transformMask,
864 new KisTransformMaskAdapter(config)),
865 m_d->commandUpdatesBlockerCookie);
867 addDirtyRect(node, oldDirtyRect | transformMask->extent(), levelOfDetail);
868 }
869
870 }
871}
void addDirtyRect(KisNodeSP node, const QRect &rect, int levelOfDetail)
void prepareClone(KisPaintDeviceSP src)
static KisTransformWorker createTransformWorker(const ToolTransformArgs &config, KisPaintDeviceSP device, KoUpdaterPtr updater)
static void transformAndMergeDevice(const ToolTransformArgs &config, KisPaintDeviceSP src, KisPaintDeviceSP dst, KisProcessingVisitor::ProgressHelper *helper)
TransformMode mode() const
T kisGrowRect(const T &rect, U offset)
Definition kis_global.h:186
QSharedPointer< KisTransformMaskParamsInterface > KisTransformMaskParamsInterfaceSP

References addDirtyRect(), KisPaintDevice::colorSpace(), KisStrokeJobData::CONCURRENT, KisTransformUtils::createTransformWorker(), KisSharedPtr< T >::data(), KisTransaction::endAndTake(), executeAndAddCommand(), KisPaintDevice::extent(), ToolTransformArgs::FREE_TRANSFORM, KIS_SAFE_ASSERT_RECOVER_RETURN, kisGrowRect(), m_d, ToolTransformArgs::mode(), KisBaseNode::paintDevice(), ToolTransformArgs::PERSPECTIVE_4POINT, KisPaintDevice::prepareClone(), KisBaseNode::projection(), KisNode::projectionPlane(), Transform, KisTransformUtils::transformAndMergeDevice(), TransformLod, and TransformLodTemporary.

◆ tryPostUpdateJob()

void InplaceTransformStrokeStrategy::tryPostUpdateJob ( bool forceUpdate)
private

Definition at line 230 of file inplace_transform_stroke_strategy.cpp.

231{
232 if (!m_d->pendingUpdateArgs) return;
233
234 if (forceUpdate ||
235 (m_d->updateTimer.elapsed() > m_d->updateInterval &&
236 !m_d->updatesFacade->hasUpdatesRunning())) {
237
238 addMutatedJob(new BarrierUpdateData(forceUpdate));
239 }
240}
void addMutatedJob(KisStrokeJobData *data)

References KisStrokeStrategy::addMutatedJob(), and m_d.

◆ undoAllCommands()

void InplaceTransformStrokeStrategy::undoAllCommands ( )
private

Definition at line 713 of file inplace_transform_stroke_strategy.cpp.

714{
715 for (auto it = std::make_reverse_iterator(m_d->commands.end());
716 it != std::make_reverse_iterator(m_d->commands.begin());
717 ++it) {
718
719 executeCommand(it->command, true);
720 }
721
722 m_d->commands.clear();
723}

References KisStrokeStrategyUndoCommandBased::executeCommand(), and m_d.

◆ undoTransformCommands()

void InplaceTransformStrokeStrategy::undoTransformCommands ( int levelOfDetail)
private

Definition at line 725 of file inplace_transform_stroke_strategy.cpp.

726{
727 for (auto it = std::make_reverse_iterator(m_d->commands.end());
728 it != std::make_reverse_iterator(m_d->commands.begin());) {
729
730 if ((levelOfDetail > 0 &&
731 (it->commandGroup == TransformLod || it->commandGroup == TransformLodTemporary)) ||
732 (levelOfDetail <= 0 &&
733 (it->commandGroup == Transform || it->commandGroup == TransformTemporary))) {
734
735 executeCommand(it->command, true);
736 it = std::make_reverse_iterator(m_d->commands.erase(std::next(it).base()));
737 } else {
738 ++it;
739 }
740 }
741}

References KisStrokeStrategyUndoCommandBased::executeCommand(), m_d, Transform, TransformLod, TransformLodTemporary, and TransformTemporary.

Friends And Related Symbol Documentation

◆ InitializeTransformModeStrokeStrategy

friend class InitializeTransformModeStrokeStrategy
friend

Definition at line 149 of file inplace_transform_stroke_strategy.h.

Member Data Documentation

◆ m_d

const QScopedPointer<Private> InplaceTransformStrokeStrategy::m_d
private

Definition at line 180 of file inplace_transform_stroke_strategy.h.


The documentation for this class was generated from the following files: