Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_painter_based_stroke_strategy.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2011 Dmitry Kazakov <dimula73@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
8
9#include <KoColorSpace.h>
10#include <KoColor.h>
11#include <KoCompositeOp.h>
12#include "kis_painter.h"
13#include "kis_paint_device.h"
14#include "kis_paint_layer.h"
15#include "kis_transaction.h"
16#include "kis_image.h"
19#include "kis_undo_stores.h"
24#include "KisAnimAutoKey.h"
25
27#include "kis_paintop_preset.h"
29
31#include "kis_command_utils.h"
32
38#include "KisAnimAutoKey.h"
39
40
42 const KUndo2MagicString &name,
43 KisResourcesSnapshotSP resources,
46 m_resources(resources),
47 m_strokeInfos(strokeInfos),
48 m_transaction(0),
49 m_useMergeID(false),
50 m_supportsMaskingBrush(false),
51 m_supportsIndirectPainting(false)
52{
53 init();
54}
55
57 const KUndo2MagicString &name,
58 KisResourcesSnapshotSP resources,
59 KisFreehandStrokeInfo *strokeInfo)
61 m_resources(resources),
62 m_strokeInfos(QVector<KisFreehandStrokeInfo*>() << strokeInfo),
63 m_transaction(0),
64 m_useMergeID(false),
65 m_supportsMaskingBrush(false),
66 m_supportsIndirectPainting(false)
67{
68 init();
69}
70
74
84
87 m_resources(rhs.m_resources),
88 m_useMergeID(rhs.m_useMergeID),
89 m_supportsMaskingBrush(rhs.m_supportsMaskingBrush),
90 m_supportsIndirectPainting(rhs.m_supportsIndirectPainting),
91 m_supportsContinuedInterstrokeData(rhs.m_supportsContinuedInterstrokeData)
92{
93 Q_FOREACH (KisFreehandStrokeInfo *info, rhs.m_strokeInfos) {
94 m_strokeInfos.append(new KisFreehandStrokeInfo(info, levelOfDetail));
95 }
96
98 rhs.m_maskStrokeInfos.isEmpty() &&
99 !rhs.m_transaction &&
100 !rhs.m_targetDevice &&
101 !rhs.m_activeSelection &&
102 "After the stroke has been started, no copying must happen");
103}
104
109
114
119
124
129
131{
134
135 Q_FOREACH (const QRect &rc, rects) {
137 [this, rc] () {
138 this->m_maskingBrushRenderer->updateProjection(rc);
139 }
140 );
141 }
142
143 return jobs;
144}
145
150
155
160
165
170
175
180
185
187 KisPaintDeviceSP maskingDevice,
188 KisSelectionSP selection,
189 bool hasIndirectPainting,
190 const QString &indirectPaintingCompositeOp)
191{
192 Q_FOREACH (KisFreehandStrokeInfo *info, m_strokeInfos) {
193 KisPainter *painter = info->painter;
194
195 painter->begin(targetDevice, !hasIndirectPainting ? selection : nullptr);
197 m_resources->setupPainter(painter);
198
199 if(hasIndirectPainting) {
200 painter->setCompositeOpId(indirectPaintingCompositeOp);
201 painter->setOpacityToUnit();
202 painter->setChannelFlags(QBitArray());
203 }
204 }
205
206 if (maskingDevice) {
207 for (int i = 0; i < m_strokeInfos.size(); i++) {
208 KisFreehandStrokeInfo *maskingInfo =
209 new KisFreehandStrokeInfo(*m_strokeInfos[i]->dragDistance);
210
211 KisPainter *painter = maskingInfo->painter;
212
213 painter->begin(maskingDevice, nullptr);
215
216 KIS_SAFE_ASSERT_RECOVER_NOOP(hasIndirectPainting);
217 m_maskStrokeInfos.append(maskingInfo);
218 }
219 }
220
221 for (int i = 0; i < m_strokeInfos.size(); i++) {
222 m_maskedPainters.append(
224 !m_maskStrokeInfos.isEmpty() ?
225 m_maskStrokeInfos[i] : nullptr));
226 }
227}
228
230{
231 Q_FOREACH (KisFreehandStrokeInfo *info, m_strokeInfos) {
232 delete info;
233 }
234
235 m_strokeInfos.clear();
236
237 Q_FOREACH (KisFreehandStrokeInfo *info, m_maskStrokeInfos) {
238 delete info;
239 }
240
241 m_maskStrokeInfos.clear();
242
244 delete info;
245 }
246
247 m_maskedPainters.clear();
248}
249
251{
253
254 KritaUtils::addJobSequential(jobs, [this] () {
256
257 KUndo2Command *autoKeyframeCommand =
261 if (autoKeyframeCommand) {
262 m_autokeyCommand.reset(autoKeyframeCommand);
263 }
264 });
265
266 KritaUtils::addJobSequential(jobs, [this] () mutable {
268 KisPaintDeviceSP paintDevice = node->paintDevice();
269 KisPaintDeviceSP targetDevice = paintDevice;
271 bool hasIndirectPainting = supportsIndirectPainting() && m_resources->needsIndirectPainting();
272 const QString indirectCompositeOp = m_resources->indirectPaintingCompositeOp();
273
274 if (hasIndirectPainting) {
276 dynamic_cast<KisIndirectPaintingSupport*>(node.data());
277
278 if (indirect) {
283
286 indirect->setTemporarySelection(selection);
287
288 QBitArray channelLockFlags = m_resources->channelLockFlags();
289 indirect->setTemporaryChannelFlags(channelLockFlags);
290 }
291 else {
292 hasIndirectPainting = false;
293 }
294 }
295
296 QScopedPointer<KisInterstrokeDataFactory> interstrokeDataFactory(
297 KisPaintOpRegistry::instance()->createInterstrokeDataFactory(m_resources->currentPaintOpPreset()));
298
299 KIS_SAFE_ASSERT_RECOVER(!interstrokeDataFactory || !hasIndirectPainting) {
300 interstrokeDataFactory.reset();
301 }
302
303 QScopedPointer<KisInterstrokeDataTransactionWrapperFactory> wrapper;
304
305 if (interstrokeDataFactory) {
307 interstrokeDataFactory.take(),
309 }
310
312 -1,
313 wrapper.take()));
314
315 // WARNING: masked brush cannot work without indirect painting mode!
317 m_resources->needsMaskingBrushRendering()) || hasIndirectPainting);
318
319 if (hasIndirectPainting &&
322
323 const QString compositeOpId =
324 m_resources->currentPaintOpPreset()->settings()->maskingBrushCompositeOp();
325
327
328 initPainters(m_maskingBrushRenderer->strokeDevice(),
329 m_maskingBrushRenderer->maskDevice(),
330 selection,
331 hasIndirectPainting,
332 indirectCompositeOp);
333
334 } else {
335 initPainters(targetDevice, nullptr, selection, hasIndirectPainting, indirectCompositeOp);
336 }
337
339 m_activeSelection = selection;
340
341 // sanity check: selection should be applied only once
342 if (selection && !m_strokeInfos.isEmpty()) {
344 dynamic_cast<KisIndirectPaintingSupport*>(node.data());
345 KIS_ASSERT_RECOVER_RETURN(hasIndirectPainting || m_strokeInfos.first()->painter->selection());
346 // when hasIndirectPainting is true, indirect cannot be null
347 KIS_ASSERT_RECOVER_RETURN(!hasIndirectPainting || indirect);
348 KIS_ASSERT_RECOVER_RETURN(!hasIndirectPainting || !indirect->temporarySelection() || !m_strokeInfos.first()->painter->selection());
349 }
350 });
351
353}
354
355namespace {
356
357struct MergeableStrokeUndoCommand : KUndo2Command
358{
359 MergeableStrokeUndoCommand(KisResourcesSnapshotSP resourcesSnapshot)
360 : m_compatibilityInfo(*resourcesSnapshot)
361 {
362 }
363
364 bool timedMergeWith(KUndo2Command *_other) override {
365 if(_other->timedId() == this->timedId() && _other->timedId() != -1 ) {
366 const bool isCompatible =
367 KisSavedCommand::unwrap(_other, [this] (KUndo2Command *cmd) {
368 MergeableStrokeUndoCommand *other =
369 dynamic_cast<MergeableStrokeUndoCommand*>(cmd);
370 return other && m_compatibilityInfo == other->m_compatibilityInfo;
371 });
372
373 if (isCompatible) {
378 return KUndo2Command::timedMergeWith(_other);
379 }
380 }
381 return false;
382 }
383
384 KisStrokeCompatibilityInfo m_compatibilityInfo;
385};
386
387} // namespace
388
390{
393 dynamic_cast<KisIndirectPaintingSupport*>(node.data());
394
395 KisPostExecutionUndoAdapter *undoAdapter =
397
398
399 if (!undoAdapter) {
400 m_fakeUndoData.reset(new FakeUndoData());
401 undoAdapter = m_fakeUndoData->undoAdapter.data();
402 }
403
404 QSharedPointer<KUndo2Command> parentCommand;
405
406 if (!m_useMergeID) {
407 parentCommand.reset(new KUndo2Command());
408 } else {
409 parentCommand.reset(new MergeableStrokeUndoCommand(m_resources));
410 parentCommand->setTimedID(timedID(this->id()));
411 }
412
413 parentCommand->setText(name());
414 parentCommand->setTime(m_transaction->undoCommand()->time());
415 parentCommand->setEndTime(QTime::currentTime());
416
417 if (m_autokeyCommand) {
418 KisCommandUtils::CompositeCommand *wrapper = new KisCommandUtils::CompositeCommand(parentCommand.data());
419 wrapper->addCommand(m_autokeyCommand.take());
420 }
421
422 if (indirect && indirect->hasTemporaryTarget()) {
423 KUndo2MagicString transactionText = m_transaction->text();
424 m_transaction->end();
425 m_transaction.reset();
427
429
430 indirect->mergeToLayerThreaded(node,
431 parentCommand.data(),
433 -1,
434 &jobs);
435
437 [parentCommand, undoAdapter] () {
438 parentCommand->redo();
439
440 if (undoAdapter) {
441 undoAdapter->addCommand(parentCommand);
442 }
443 });
444
448
449 Q_FOREACH (KisRunnableStrokeJobData *job, jobs) {
450 job->setCancellable(false);
451 }
452
454 }
455 else {
456 KisCommandUtils::CompositeCommand *wrapper = new KisCommandUtils::CompositeCommand(parentCommand.data());
457 wrapper->addCommand(m_transaction->endAndTake());
458
459 m_transaction.reset();
461
462 if (undoAdapter) {
463 parentCommand->redo();
464 undoAdapter->addCommand(parentCommand);
465 }
466
467 }
468
469}
470
472{
473 if (!m_transaction) return;
474
475 if (m_autokeyCommand) {
476 m_autokeyCommand->undo();
477 }
478
481 dynamic_cast<KisIndirectPaintingSupport*>(node.data());
482
483 bool revert = true;
484 if (indirect) {
485 KisPaintDeviceSP t = indirect->temporaryTarget();
486 if (t) {
487 m_transaction.reset();
489
490 KisRegion region = t->region();
491 indirect->setTemporaryTarget(nullptr);
492 node->setDirty(region);
493 revert = false;
494 }
495 }
496
497 if (revert) {
498 m_transaction->revert();
499 m_transaction.reset();
501 }
502}
503
505{
508 dynamic_cast<KisIndirectPaintingSupport*>(node.data());
509
510 if(indirect && indirect->hasTemporaryTarget()) {
512 indirect->setTemporaryTarget(nullptr);
513 }
514}
515
517{
520 dynamic_cast<KisIndirectPaintingSupport*>(node.data());
521
522 if(indirect) {
523 // todo: don't ask about paint device
524 // todo:change to an assert
525 if (node->paintDevice() != m_targetDevice) {
530
531 QBitArray channelLockFlags = m_resources->channelLockFlags();
532 indirect->setTemporaryChannelFlags(channelLockFlags);
533 }
534 }
535
536 m_finalMergeSuspender.clear();
537}
538
543
549
float value(const T *src, size_t ch)
virtual bool timedMergeWith(KUndo2Command *other)
virtual int timedId() const
The KisDumbUndoStore class doesn't actually save commands, so you cannot undo or redo!
KisRegion region() const
KisPaintDeviceSP createCompositionSourceDevice() const
void setParentNode(KisNodeWSP parent)
static KisPaintOpRegistry * instance()
void initPainters(KisPaintDeviceSP targetDevice, KisPaintDeviceSP maskingDevice, KisSelectionSP selection, bool hasIndirectPainting, const QString &indirectPaintingCompositeOp)
KisIndirectPaintingSupport::FinalMergeSuspenderSP m_finalMergeSuspender
QVector< KisFreehandStrokeInfo * > m_maskStrokeInfos
QScopedPointer< FakeUndoData > m_fakeUndoData
KisPainterBasedStrokeStrategy(const QLatin1String &id, const KUndo2MagicString &name, KisResourcesSnapshotSP resources, QVector< KisFreehandStrokeInfo * > strokeInfos)
QVector< KisMaskedFreehandStrokePainter * > m_maskedPainters
KisMaskedFreehandStrokePainter * maskedPainter(int strokeInfoId)
QScopedPointer< KisMaskingBrushRenderer > m_maskingBrushRenderer
QVector< KisRunnableStrokeJobData * > doMaskingBrushUpdates(const QVector< QRect > &rects)
QScopedPointer< KUndo2Command > m_autokeyCommand
QVector< KisFreehandStrokeInfo * > m_strokeInfos
QScopedPointer< KisTransaction > m_transaction
void begin(KisPaintDeviceSP device)
void setOpacityToUnit()
void setRunnableStrokeJobsInterface(KisRunnableStrokeJobsInterface *interface)
void setChannelFlags(QBitArray channelFlags)
void setCompositeOpId(const KoCompositeOp *op)
void setupPainter(KisPainter *painter)
KisPaintOpPresetSP currentPaintOpPreset() const
void setupMaskingBrushPainter(KisPainter *painter)
QString indirectPaintingCompositeOp() const
KisPostExecutionUndoAdapter * postExecutionUndoAdapter() const
KisSelectionSP activeSelection() const
KisRunnableStrokeJobsInterface * runnableJobsInterface() const
virtual void addRunnableJobs(const QVector< KisRunnableStrokeJobDataBase * > &list)=0
static auto unwrap(Command *cmd, Func &&func) -> decltype(func(static_cast< Command * >(nullptr)))
void enableJob(JobType type, bool enable=true, KisStrokeJobData::Sequentiality sequentiality=KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::Exclusivity exclusivity=KisStrokeJobData::NORMAL)
void setCancellable(bool value)
KUndo2MagicString name() const
#define KIS_SAFE_ASSERT_RECOVER(cond)
Definition kis_assert.h:126
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:129
#define KIS_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:75
#define KIS_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:97
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
KUndo2Command * tryAutoCreateDuplicatedFrame(KisPaintDeviceSP device, AutoCreateKeyframeFlags flags)
create a new duplicated keyframe if auto-keyframe mode is on
void addJobConcurrent(QVector< Job * > &jobs, Func func)
void addJobSequential(QVector< Job * > &jobs, Func func)
void addJobBarrier(QVector< Job * > &jobs, Func func)
virtual KisPaintDeviceSP paintDevice() const =0
void setTemporaryChannelFlags(const QBitArray &channelFlags)
virtual void mergeToLayerThreaded(KisNodeSP layer, KUndo2Command *parentCommand, const KUndo2MagicString &transactionText, int timedID, QVector< KisRunnableStrokeJobData * > *jobs)
virtual void setCurrentColor(const KoColor &color)
void setTemporarySelection(KisSelectionSP selection)
void setTemporaryCompositeOp(const QString &id)
virtual void setDirty()
Definition kis_node.cpp:577
QScopedPointer< KisPostExecutionUndoAdapter > undoAdapter