Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_filter_stroke_strategy.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2013 Dmitry Kazakov <dimula73@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
8
9#include <filter/kis_filter.h>
11#include <krita_utils.h>
12#include <kis_layer_utils.h>
14#include <kis_transaction.h>
19#include "kis_image_config.h"
21#include "kis_painter.h"
22#include "KisAnimAutoKey.h"
24
25
61
63
81
83
84
85 KisImageSP image() { return m_image; }
86
87 KisNodeSP node() { return m_node; }
88
90
92
94
96
98
99 int frameTime() {
100 return m_frameTime;
101 }
102
104
105 bool shouldRedraw() { return m_shouldRedraw; }
106
108
109public:
114
115private:
127
128};
129
130using namespace KritaUtils;
131
133 : KisFilterStrokeStrategy(filter, filterConfig, resources, toQShared(new ExternalCancelUpdatesStorage()))
134{
135 // by default, cancellation updates are disabled, so we should enable them
136 m_d->cancelledUpdates->shouldIssueCancellationUpdates.ref();
137}
138
140 KisFilterConfigurationSP filterConfig,
141 KisResourcesSnapshotSP resources,
142 ExternalCancelUpdatesStorageSP externalCancelUpdatesStorage)
143 : KisStrokeStrategyUndoCommandBased(kundo2_i18nc("Filter as an effect", "Filter \"%1\"", filter->name()),
144 false,
145 resources->image().data())
146 , m_d(new Private())
147{
148 m_d->filter = filter;
149 m_d->filterConfig = filterConfig;
150 m_d->node = resources->currentNode();
151 m_d->targetDevice = resources->currentNode()->paintDevice();
152 m_d->activeSelection = resources->activeSelection();
153 m_d->image = resources->image();
154 m_d->updatesFacade = resources->image().data();
155 m_d->levelOfDetail = 0;
156 m_d->cancelledUpdates = externalCancelUpdatesStorage;
157
164}
165
168 , m_d(new Private(*rhs.m_d))
169{
170 m_d->levelOfDetail = levelOfDetail;
171}
172
177
193
194
196{
197 FilterJobData *filterFrameData = dynamic_cast<FilterJobData*>(data);
198 KisRunnableStrokeJobData *jobData = dynamic_cast<KisRunnableStrokeJobData*>(data);
199
200 // Populate list of jobs for filter application...
201 if (filterFrameData) {
202
204
206 m_d->activeSelection, m_d->filter, m_d->filterConfig, filterFrameData) );
208 addJobSequential(jobs, [this, shared, progress](){
209 // Switch time if necessary..
210 if (shared->shouldSwitchTime()) {
211 runAndSaveCommand( toQShared( new KisLayerUtils::SwitchFrameCommand(shared->image(), shared->frameTime(), false, shared->storage()) )
212 , KisStrokeJobData::BARRIER, KisStrokeJobData::NORMAL);
213 }
214
215 // Copy snapshot of targetDevice for filter processing..
216 shared->filterDevice = new KisPaintDevice(*shared->targetDevice());
217
218 // Update all necessary rect data based on contents of frame..
219 shared->filterDeviceBounds = shared->filterDevice->exactBounds();
220
221 if (shared->filter()->needsTransparentPixels(shared->filterConfig().data(), shared->targetDevice()->colorSpace())) {
222 shared->filterDeviceBounds |= shared->targetDevice()->defaultBounds()->bounds();
223 }
224
225 shared->processRect = shared->filter()->changedRect(shared->filterDeviceBounds, shared->filterConfig(), shared->levelOfDetail());
226 shared->processRect &= shared->targetDevice()->defaultBounds()->bounds();
227
228 //If we're dealing with some kind of transparency mask, we will create a compositionSourceDevice instead.
229 // Carry over from commit ca810f85 ...
230 if (shared->selection() ||
231 (shared->targetDevice()->colorSpace() != shared->targetDevice()->compositionSourceColorSpace() &&
232 *shared->targetDevice()->colorSpace() != *shared->targetDevice()->compositionSourceColorSpace())) {
233
234 shared->filterDevice = shared->targetDevice()->createCompositionSourceDevice(shared->targetDevice());
235
236 if (shared->selection()) {
237 shared->filterDeviceBounds &= shared->selection()->selectedRect();
238 }
239 }
240
241 // Filter device needs a transaction to prevent grid-patch artifacts from multithreaded read/write.
242 shared->filterDeviceTransaction.reset(new KisTransaction(shared->filterDevice));
243
244
245 // Actually process the device
246
248
249 if (shared->filter()->supportsThreading()) {
250 // Split stroke into patches...
251 QSize size = KritaUtils::optimalPatchSize();
252 QVector<QRect> patches = KritaUtils::splitRectIntoPatches(shared->processRect, size);
253
254 Q_FOREACH (const QRect &patch, patches) {
255 if (!patch.isEmpty()) {
256 addJobConcurrent(processJobs, [patch, shared, progress](){
257 shared->filter()->processImpl(shared->filterDevice, patch,
258 shared->filterConfig().data(),
259 progress->updater());
260 });
261 }
262 }
263 } else {
264 if (!shared->processRect.isEmpty()) {
265 addJobSequential(processJobs, [shared, progress](){
266 shared->filter()->processImpl(shared->filterDevice, shared->processRect,
267 shared->filterConfig().data(),
268 progress->updater());
269 });
270 }
271 }
272
274
275 });
276
277 addJobSequential(jobs, [this, shared](){
278 // We will first apply the transaction to the temporary filterDevice
279 runAndSaveCommand(toQShared(shared->filterDeviceTransaction->endAndTake()), KisStrokeJobData::BARRIER, KisStrokeJobData::NORMAL);
280 shared->filterDeviceTransaction.reset();
281
282 if (!shared->filterDeviceBounds.intersects(
283 shared->filter()->neededRect(shared->processRect, shared->filterConfig().data(), shared->levelOfDetail()))) {
284 return;
285 }
286
287 // Make a transaction, change the target device, and "end" transaction.
288 // Should be useful for undoing later.
289 QScopedPointer<KisTransaction> workingTransaction( new KisTransaction(shared->targetDevice()) );
290 KisPainter::copyAreaOptimized(shared->processRect.topLeft(), shared->filterDevice, shared->targetDevice(), shared->processRect, shared->selection());
292
293 if (shared->shouldRedraw()) {
294 QRect extraUpdateRect;
295 qSwap(extraUpdateRect, m_d->nextExternalUpdateRect);
296
297 shared->node()->setDirty(shared->processRect | extraUpdateRect);
298
304 m_d->nextExternalUpdateRect = shared->processRect;
305 }
306 });
307
308 addJobSequential(jobs, [this, shared](){
309 shared->image()->animationInterface()->invalidateFrame(shared->frameTime(), shared->node());
310 if (shared->shouldSwitchTime()) {
311 runAndSaveCommand( toQShared( new KisLayerUtils::SwitchFrameCommand(shared->image(), shared->frameTime(), true, shared->storage()) )
313 }
314 });
315
317
318 } else if (dynamic_cast<IdleBarrierData*>(data)) {
319 /* noop, just delete that */
320 } else if (jobData) {
321 jobData->run();
322 } else {
324 }
325}
326
328{
329 using namespace KritaUtils;
330
331 const bool shouldIssueCancellationUpdates = m_d->cancelledUpdates->shouldIssueCancellationUpdates;
332
334
338
339 if (shouldIssueCancellationUpdates) {
340 addJobSequential(jobs, [this] () {
341 QRect updateRect =
342 m_d->cancelledUpdates->updateRect |
344
345 if (m_d->levelOfDetail <= 0) {
346 updateRect |= m_d->cancelledUpdates->cancelledLod0UpdateRect;
347 }
348
349 if (!updateRect.isEmpty()) {
350 m_d->node->setDirty(updateRect);
351 }
352 });
353 } else if (!m_d->nextExternalUpdateRect.isEmpty()) {
354
357
358 if (!m_d->hasBeenLodCloned && m_d->levelOfDetail <= 0) {
359 m_d->cancelledUpdates->cancelledLod0UpdateRect = m_d->nextExternalUpdateRect;
360 }
361 }
362
363 addMutatedJobs(jobs);
364}
365
370
372{
373 if (!m_d->filter->supportsLevelOfDetail(m_d->filterConfig.data(), levelOfDetail)) return 0;
374 if (!m_d->node->supportsLodPainting()) return 0;
375
376 KisFilterStrokeStrategy *clone = new KisFilterStrokeStrategy(*this, levelOfDetail);
377 m_d->hasBeenLodCloned = true;
378 return clone;
379}
void doStrokeCallback(KisStrokeJobData *data) override
KisFilterStrokeStrategy(KisFilterSP filter, KisFilterConfigurationSP filterConfig, KisResourcesSnapshotSP resources)
KisStrokeStrategy * createLodClone(int levelOfDetail) override
virtual bool supportsLevelOfDetail(const KisFilterConfigurationSP config, int lod) const
QRectF mapInverted(const QRectF &rc) const
KisPaintInformation map(KisPaintInformation pi) const
static void copyAreaOptimized(const QPoint &dstPt, KisPaintDeviceSP src, KisPaintDeviceSP dst, const QRect &originalSrcRect)
KisSelectionSP activeSelection() const
KisRunnableStrokeJobsInterface * runnableJobsInterface() const
virtual void addRunnableJobs(const QVector< KisRunnableStrokeJobDataBase * > &list)=0
void enableJob(JobType type, bool enable=true, KisStrokeJobData::Sequentiality sequentiality=KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::Exclusivity exclusivity=KisStrokeJobData::NORMAL)
void runAndSaveCommand(KUndo2CommandSP command, KisStrokeJobData::Sequentiality sequentiality, KisStrokeJobData::Exclusivity exclusivity)
void doStrokeCallback(KisStrokeJobData *data) override
void cancelStrokeCallbackImpl(QVector< KisStrokeJobData * > &mutatedJobs)
void addMutatedJobs(const QVector< KisStrokeJobData * > list)
void setNeedsExplicitCancel(bool value)
#define KIS_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:75
QSharedPointer< T > toQShared(T *ptr)
KUndo2MagicString kundo2_i18nc(const char *ctxt, const char *text)
KUndo2Command * tryAutoCreateDuplicatedFrame(KisPaintDeviceSP device, AutoCreateKeyframeFlags flags)
create a new duplicated keyframe if auto-keyframe mode is on
int fetchLayerActiveRasterFrameTime(KisNodeSP node)
QVector< QRect > splitRectIntoPatches(const QRect &rc, const QSize &patchSize)
void addJobConcurrent(QVector< Job * > &jobs, Func func)
void addJobSequential(QVector< Job * > &jobs, Func func)
QSize optimalPatchSize()
virtual bool supportsLodPainting() const
virtual KisPaintDeviceSP paintDevice() const =0
ExternalCancelUpdatesStorageSP cancelledUpdates
The SwitchFrameCommand struct Switches to frame with undo/redo support.
KisLayerUtils::SwitchFrameCommand::SharedStorageSP storage()
KisLayerUtils::SwitchFrameCommand::SharedStorageSP m_storage
SubTaskSharedData(KisImageSP image, KisNodeSP node, int levelOfDetail, KisSelectionSP selection, KisFilterSP filter, KisFilterConfigurationSP config, KisFilterStrokeStrategy::FilterJobData *filterFrameData)
KisFilterConfigurationSP filterConfig()
QSharedPointer< KisTransaction > filterDeviceTransaction
KisFilterConfigurationSP m_filterConfig