1 /*
2  *  Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
3  *  Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
4  *
5  *  This program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2 of the License, or
8  *  (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program; if not, write to the Free Software
17  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  */
19 
20 #include "kis_image.h"
21 
22 #include <KoConfig.h> // WORDS_BIGENDIAN
23 
24 #include <stdlib.h>
25 #include <math.h>
26 
27 #include <QImage>
28 #include <QPainter>
29 #include <QSize>
30 #include <QDateTime>
31 #include <QRect>
32 #include <QtConcurrent>
33 
34 #include <klocalizedstring.h>
35 
36 #include "KoColorSpaceRegistry.h"
37 #include "KoColor.h"
38 #include "KoColorProfile.h"
39 #include <KoCompositeOpRegistry.h>
40 #include "KisProofingConfiguration.h"
41 
42 #include "kis_adjustment_layer.h"
43 #include "kis_annotation.h"
44 #include "kis_count_visitor.h"
45 #include "kis_filter_strategy.h"
46 #include "kis_group_layer.h"
47 #include "commands/kis_image_commands.h"
48 #include "kis_layer.h"
49 #include "kis_meta_data_merge_strategy_registry.h"
50 #include "kis_paint_layer.h"
51 #include "kis_projection_leaf.h"
52 #include "kis_painter.h"
53 #include "kis_selection.h"
54 #include "kis_transaction.h"
55 #include "kis_meta_data_merge_strategy.h"
56 #include "kis_memory_statistics_server.h"
57 #include "kis_node.h"
58 #include "kis_types.h"
59 
60 #include "kis_image_config.h"
61 #include "kis_update_scheduler.h"
62 #include "kis_image_signal_router.h"
63 #include "kis_image_animation_interface.h"
64 #include "kis_stroke_strategy.h"
65 #include "kis_simple_stroke_strategy.h"
66 #include "kis_image_barrier_locker.h"
67 
68 
69 #include "kis_undo_stores.h"
70 #include "kis_legacy_undo_adapter.h"
71 #include "kis_post_execution_undo_adapter.h"
72 
73 #include "kis_transform_worker.h"
74 #include "kis_processing_applicator.h"
75 #include "processing/kis_crop_processing_visitor.h"
76 #include "processing/kis_crop_selections_processing_visitor.h"
77 #include "processing/kis_transform_processing_visitor.h"
78 #include "processing/kis_convert_color_space_processing_visitor.h"
79 #include "processing/kis_assign_profile_processing_visitor.h"
80 #include "commands_new/kis_image_resize_command.h"
81 #include "commands_new/kis_image_set_resolution_command.h"
82 #include "commands_new/kis_activate_selection_mask_command.h"
83 #include "kis_do_something_command.h"
84 #include "kis_composite_progress_proxy.h"
85 #include "kis_layer_composition.h"
86 #include "kis_wrapped_rect.h"
87 #include "kis_crop_saved_extra_data.h"
88 #include "kis_layer_utils.h"
89 
90 #include "kis_lod_transform.h"
91 
92 #include "kis_suspend_projection_updates_stroke_strategy.h"
93 #include "kis_sync_lod_cache_stroke_strategy.h"
94 
95 #include "kis_projection_updates_filter.h"
96 
97 #include "kis_layer_projection_plane.h"
98 
99 #include "kis_update_time_monitor.h"
100 #include "kis_lockless_stack.h"
101 
102 #include <QtCore>
103 
104 #include <functional>
105 
106 #include "kis_time_range.h"
107 
108 #include "KisRunnableBasedStrokeStrategy.h"
109 #include "KisRunnableStrokeJobData.h"
110 #include "KisRunnableStrokeJobUtils.h"
111 #include "KisRunnableStrokeJobsInterface.h"
112 
113 #include "KisBusyWaitBroker.h"
114 
115 
116 // #define SANITY_CHECKS
117 
118 #ifdef SANITY_CHECKS
119 #define SANITY_CHECK_LOCKED(name)                                       \
120     if (!locked()) warnKrita() << "Locking policy failed:" << name          \
121                                << "has been called without the image"       \
122                                   "being locked";
123 #else
124 #define SANITY_CHECK_LOCKED(name)
125 #endif
126 
127 
128 struct KisImageSPStaticRegistrar {
KisImageSPStaticRegistrarKisImageSPStaticRegistrar129     KisImageSPStaticRegistrar() {
130         qRegisterMetaType<KisImageSP>("KisImageSP");
131     }
132 };
133 static KisImageSPStaticRegistrar __registrar;
134 
135 class KisImage::KisImagePrivate
136 {
137 public:
KisImagePrivate(KisImage * _q,qint32 w,qint32 h,const KoColorSpace * c,KisUndoStore * undo,KisImageAnimationInterface * _animationInterface)138     KisImagePrivate(KisImage *_q, qint32 w, qint32 h,
139                     const KoColorSpace *c,
140                     KisUndoStore *undo,
141                     KisImageAnimationInterface *_animationInterface)
142         : q(_q)
143         , lockedForReadOnly(false)
144         , width(w)
145         , height(h)
146         , colorSpace(c ? c : KoColorSpaceRegistry::instance()->rgb8())
147         , undoStore(undo ? undo : new KisDumbUndoStore())
148         , legacyUndoAdapter(undoStore.data(), _q)
149         , postExecutionUndoAdapter(undoStore.data(), _q)
150         , signalRouter(_q)
151         , animationInterface(_animationInterface)
152         , scheduler(_q, _q)
153         , axesCenter(QPointF(0.5, 0.5))
154     {
155         {
156             KisImageConfig cfg(true);
157             if (cfg.enableProgressReporting()) {
158                 scheduler.setProgressProxy(&compositeProgressProxy);
159             }
160 
161             // Each of these lambdas defines a new factory function.
162             scheduler.setLod0ToNStrokeStrategyFactory(
163                 [=](bool forgettable) {
164                     return KisLodSyncPair(
165                         new KisSyncLodCacheStrokeStrategy(KisImageWSP(q), forgettable),
166                         KisSyncLodCacheStrokeStrategy::createJobsData(KisImageWSP(q)));
167                 });
168 
169             scheduler.setSuspendResumeUpdatesStrokeStrategyFactory(
170                 [=]() {
171                     KisSuspendProjectionUpdatesStrokeStrategy::SharedDataSP data = KisSuspendProjectionUpdatesStrokeStrategy::createSharedData();
172 
173                     KisSuspendResumePair suspend(new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(q), true, data),
174                                                  KisSuspendProjectionUpdatesStrokeStrategy::createSuspendJobsData(KisImageWSP(q)));
175                     KisSuspendResumePair resume(new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(q), false, data),
176                                                 KisSuspendProjectionUpdatesStrokeStrategy::createResumeJobsData(KisImageWSP(q)));
177 
178                     return std::make_pair(suspend, resume);
179                 });
180         }
181 
182         connect(q, SIGNAL(sigImageModified()), KisMemoryStatisticsServer::instance(), SLOT(notifyImageChanged()));
183     }
184 
~KisImagePrivate()185     ~KisImagePrivate() {
186         /**
187          * Stop animation interface. It may use the rootLayer.
188          */
189         delete animationInterface;
190 
191         /**
192          * First delete the nodes, while strokes
193          * and undo are still alive
194          */
195         rootLayer.clear();
196     }
197 
198     KisImage *q;
199 
200     quint32 lockCount = 0;
201     bool lockedForReadOnly;
202 
203     qint32 width;
204     qint32 height;
205 
206     double xres = 1.0;
207     double yres = 1.0;
208 
209     const KoColorSpace * colorSpace;
210     KisProofingConfigurationSP proofingConfig;
211 
212     KisSelectionSP deselectedGlobalSelection;
213     KisGroupLayerSP rootLayer; // The layers are contained in here
214     KisSelectionMaskSP targetOverlaySelectionMask; // the overlay switching stroke will try to switch into this mask
215     KisSelectionMaskSP overlaySelectionMask;
216     QList<KisLayerCompositionSP> compositions;
217     KisNodeSP isolatedRootNode;
218     bool wrapAroundModePermitted = false;
219 
220     QScopedPointer<KisUndoStore> undoStore;
221     KisLegacyUndoAdapter legacyUndoAdapter;
222     KisPostExecutionUndoAdapter postExecutionUndoAdapter;
223 
224     vKisAnnotationSP annotations;
225 
226     QAtomicInt disableUIUpdateSignals;
227     KisLocklessStack<QRect> savedDisabledUIUpdates;
228 
229     // filters are applied in a reversed way, from rbegin() to rend()
230     QVector<KisProjectionUpdatesFilterSP> projectionUpdatesFilters;
231     QStack<KisProjectionUpdatesFilterCookie> disabledUpdatesCookies;
232     KisImageSignalRouter signalRouter;
233     KisImageAnimationInterface *animationInterface;
234     KisUpdateScheduler scheduler;
235     QAtomicInt disableDirtyRequests;
236 
237 
238     KisCompositeProgressProxy compositeProgressProxy;
239 
240     bool blockLevelOfDetail = false;
241 
242     QPointF axesCenter;
243     bool allowMasksOnRootNode = false;
244 
245     bool tryCancelCurrentStrokeAsync();
246 
247     void notifyProjectionUpdatedInPatches(const QRect &rc, QVector<KisRunnableStrokeJobData *> &jobs);
248 
249     void convertImageColorSpaceImpl(const KoColorSpace *dstColorSpace,
250                                     bool convertLayers,
251                                     KoColorConversionTransformation::Intent renderingIntent,
252                                     KoColorConversionTransformation::ConversionFlags conversionFlags);
253 
254     struct SetImageProjectionColorSpace;
255 };
256 
KisImage(KisUndoStore * undoStore,qint32 width,qint32 height,const KoColorSpace * colorSpace,const QString & name)257 KisImage::KisImage(KisUndoStore *undoStore, qint32 width, qint32 height, const KoColorSpace *colorSpace, const QString& name)
258         : QObject(0)
259         , KisShared()
260         , m_d(new KisImagePrivate(this, width, height,
261                                   colorSpace, undoStore,
262                                   new KisImageAnimationInterface(this)))
263 {
264     // make sure KisImage belongs to the GUI thread
265     moveToThread(qApp->thread());
266     connect(this, SIGNAL(sigInternalStopIsolatedModeRequested()), SLOT(stopIsolatedMode()));
267 
268     setObjectName(name);
269     setRootLayer(new KisGroupLayer(this, "root", OPACITY_OPAQUE_U8));
270 }
271 
~KisImage()272 KisImage::~KisImage()
273 {
274     /**
275      * Request the tools to end currently running strokes
276      */
277     waitForDone();
278 
279     delete m_d;
280     disconnect(); // in case Qt gets confused
281 }
282 
fromQImage(const QImage & image,KisUndoStore * undoStore)283 KisImageSP KisImage::fromQImage(const QImage &image, KisUndoStore *undoStore)
284 {
285     const KoColorSpace *colorSpace = 0;
286 
287     switch (image.format()) {
288     case QImage::Format_Invalid:
289     case QImage::Format_Mono:
290     case QImage::Format_MonoLSB:
291         colorSpace = KoColorSpaceRegistry::instance()->graya8();
292         break;
293     case QImage::Format_Indexed8:
294     case QImage::Format_RGB32:
295     case QImage::Format_ARGB32:
296     case QImage::Format_ARGB32_Premultiplied:
297         colorSpace = KoColorSpaceRegistry::instance()->rgb8();
298         break;
299     case QImage::Format_RGB16:
300         colorSpace = KoColorSpaceRegistry::instance()->rgb16();
301         break;
302     case QImage::Format_ARGB8565_Premultiplied:
303     case QImage::Format_RGB666:
304     case QImage::Format_ARGB6666_Premultiplied:
305     case QImage::Format_RGB555:
306     case QImage::Format_ARGB8555_Premultiplied:
307     case QImage::Format_RGB888:
308     case QImage::Format_RGB444:
309     case QImage::Format_ARGB4444_Premultiplied:
310     case QImage::Format_RGBX8888:
311     case QImage::Format_RGBA8888:
312     case QImage::Format_RGBA8888_Premultiplied:
313         colorSpace = KoColorSpaceRegistry::instance()->rgb8();
314         break;
315     case QImage::Format_BGR30:
316     case QImage::Format_A2BGR30_Premultiplied:
317     case QImage::Format_RGB30:
318     case QImage::Format_A2RGB30_Premultiplied:
319         colorSpace = KoColorSpaceRegistry::instance()->rgb8();
320         break;
321     case QImage::Format_Alpha8:
322         colorSpace = KoColorSpaceRegistry::instance()->alpha8();
323         break;
324     case QImage::Format_Grayscale8:
325         colorSpace = KoColorSpaceRegistry::instance()->graya8();
326         break;
327 #if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
328     case QImage::Format_Grayscale16:
329         colorSpace = KoColorSpaceRegistry::instance()->graya16();
330         break;
331 #endif
332 #if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)
333     case QImage::Format_RGBX64:
334     case QImage::Format_RGBA64:
335     case QImage::Format_RGBA64_Premultiplied:
336         colorSpace = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), 0);
337         break;
338 #endif
339     default:
340         colorSpace = 0;
341     }
342 
343     KisImageSP img = new KisImage(undoStore, image.width(), image.height(), colorSpace, i18n("Imported Image"));
344     KisPaintLayerSP layer = new KisPaintLayer(img, img->nextLayerName(), 255);
345     layer->paintDevice()->convertFromQImage(image, 0, 0, 0);
346     img->addNode(layer.data(), img->rootLayer().data());
347 
348     return img;
349 }
350 
clone(bool exactCopy)351 KisImage *KisImage::clone(bool exactCopy)
352 {
353     return new KisImage(*this, 0, exactCopy);
354 }
355 
copyFromImage(const KisImage & rhs)356 void KisImage::copyFromImage(const KisImage &rhs)
357 {
358     copyFromImageImpl(rhs, REPLACE);
359 }
360 
copyFromImageImpl(const KisImage & rhs,int policy)361 void KisImage::copyFromImageImpl(const KisImage &rhs, int policy)
362 {
363     // make sure we choose exactly one from REPLACE and CONSTRUCT
364     KIS_ASSERT_RECOVER_RETURN((policy & REPLACE) != (policy & CONSTRUCT));
365 
366     // only when replacing do we need to emit signals
367 #define EMIT_IF_NEEDED if (!(policy & REPLACE)) {} else emit
368 
369     if (policy & REPLACE) { // if we are constructing the image, these are already set
370         if (m_d->width != rhs.width() || m_d->height != rhs.height()) {
371             m_d->width = rhs.width();
372             m_d->height = rhs.height();
373             emit sigSizeChanged(QPointF(), QPointF());
374         }
375         if (m_d->colorSpace != rhs.colorSpace()) {
376             m_d->colorSpace = rhs.colorSpace();
377             emit sigColorSpaceChanged(m_d->colorSpace);
378         }
379     }
380 
381     // from KisImage::KisImage(const KisImage &, KisUndoStore *, bool)
382     setObjectName(rhs.objectName());
383 
384     if (m_d->xres != rhs.m_d->xres || m_d->yres != rhs.m_d->yres) {
385         m_d->xres = rhs.m_d->xres;
386         m_d->yres = rhs.m_d->yres;
387         EMIT_IF_NEEDED sigResolutionChanged(m_d->xres, m_d->yres);
388     }
389 
390     m_d->allowMasksOnRootNode = rhs.m_d->allowMasksOnRootNode;
391 
392     if (rhs.m_d->proofingConfig) {
393         KisProofingConfigurationSP proofingConfig(new KisProofingConfiguration(*rhs.m_d->proofingConfig));
394         if (policy & REPLACE) {
395             setProofingConfiguration(proofingConfig);
396         } else {
397             m_d->proofingConfig = proofingConfig;
398         }
399     }
400 
401     KisNodeSP newRoot = rhs.root()->clone();
402     newRoot->setGraphListener(this);
403     newRoot->setImage(this);
404     m_d->rootLayer = dynamic_cast<KisGroupLayer*>(newRoot.data());
405     setRoot(newRoot);
406 
407     bool exactCopy = policy & EXACT_COPY;
408 
409     if (exactCopy || rhs.m_d->isolatedRootNode || rhs.m_d->overlaySelectionMask) {
410         QQueue<KisNodeSP> linearizedNodes;
411         KisLayerUtils::recursiveApplyNodes(rhs.root(),
412                                            [&linearizedNodes](KisNodeSP node) {
413                                                linearizedNodes.enqueue(node);
414                                            });
415         KisLayerUtils::recursiveApplyNodes(newRoot,
416                                            [&linearizedNodes, exactCopy, &rhs, this](KisNodeSP node) {
417                                                KisNodeSP refNode = linearizedNodes.dequeue();
418 
419                                                if (exactCopy) {
420                                                    node->setUuid(refNode->uuid());
421                                                }
422 
423                                                if (rhs.m_d->isolatedRootNode &&
424                                                    rhs.m_d->isolatedRootNode == refNode) {
425                                                    m_d->isolatedRootNode = node;
426                                                }
427 
428                                                if (rhs.m_d->overlaySelectionMask &&
429                                                    KisNodeSP(rhs.m_d->overlaySelectionMask) == refNode) {
430                                                    m_d->targetOverlaySelectionMask = dynamic_cast<KisSelectionMask*>(node.data());
431                                                    m_d->overlaySelectionMask = m_d->targetOverlaySelectionMask;
432                                                    m_d->rootLayer->notifyChildMaskChanged();
433                                                }
434                                            });
435     }
436 
437     KisLayerUtils::recursiveApplyNodes(newRoot,
438                                        [](KisNodeSP node) {
439                                            dbgImage << "Node: " << (void *)node.data();
440                                        });
441 
442     m_d->compositions.clear();
443 
444     Q_FOREACH (KisLayerCompositionSP comp, rhs.m_d->compositions) {
445         m_d->compositions << toQShared(new KisLayerComposition(*comp, this));
446     }
447 
448     EMIT_IF_NEEDED sigLayersChangedAsync();
449 
450     vKisAnnotationSP newAnnotations;
451     Q_FOREACH (KisAnnotationSP annotation, rhs.m_d->annotations) {
452         newAnnotations << annotation->clone();
453     }
454     m_d->annotations = newAnnotations;
455 
456     KIS_ASSERT_RECOVER_NOOP(rhs.m_d->projectionUpdatesFilters.isEmpty());
457     KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->disableUIUpdateSignals);
458     KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->disableDirtyRequests);
459 
460     m_d->blockLevelOfDetail = rhs.m_d->blockLevelOfDetail;
461 
462 #undef EMIT_IF_NEEDED
463 }
464 
KisImage(const KisImage & rhs,KisUndoStore * undoStore,bool exactCopy)465 KisImage::KisImage(const KisImage& rhs, KisUndoStore *undoStore, bool exactCopy)
466     : KisNodeFacade(),
467       KisNodeGraphListener(),
468       KisShared(),
469       m_d(new KisImagePrivate(this,
470                               rhs.width(), rhs.height(),
471                               rhs.colorSpace(),
472                               undoStore ? undoStore : new KisDumbUndoStore(),
473                               new KisImageAnimationInterface(*rhs.animationInterface(), this)))
474 {
475     // make sure KisImage belongs to the GUI thread
476     moveToThread(qApp->thread());
477     connect(this, SIGNAL(sigInternalStopIsolatedModeRequested()), SLOT(stopIsolatedMode()));
478 
479     copyFromImageImpl(rhs, CONSTRUCT | (exactCopy ? EXACT_COPY : 0));
480 }
481 
aboutToAddANode(KisNode * parent,int index)482 void KisImage::aboutToAddANode(KisNode *parent, int index)
483 {
484     KisNodeGraphListener::aboutToAddANode(parent, index);
485     SANITY_CHECK_LOCKED("aboutToAddANode");
486 }
487 
nodeHasBeenAdded(KisNode * parent,int index)488 void KisImage::nodeHasBeenAdded(KisNode *parent, int index)
489 {
490     KisNodeGraphListener::nodeHasBeenAdded(parent, index);
491 
492     SANITY_CHECK_LOCKED("nodeHasBeenAdded");
493     m_d->signalRouter.emitNodeHasBeenAdded(parent, index);
494 }
495 
aboutToRemoveANode(KisNode * parent,int index)496 void KisImage::aboutToRemoveANode(KisNode *parent, int index)
497 {
498     KisNodeSP deletedNode = parent->at(index);
499     if (!dynamic_cast<KisSelectionMask*>(deletedNode.data()) &&
500         deletedNode == m_d->isolatedRootNode) {
501 
502         emit sigInternalStopIsolatedModeRequested();
503     }
504 
505     KisNodeGraphListener::aboutToRemoveANode(parent, index);
506 
507     SANITY_CHECK_LOCKED("aboutToRemoveANode");
508     m_d->signalRouter.emitAboutToRemoveANode(parent, index);
509 }
510 
nodeChanged(KisNode * node)511 void KisImage::nodeChanged(KisNode* node)
512 {
513     KisNodeGraphListener::nodeChanged(node);
514     requestStrokeEnd();
515     m_d->signalRouter.emitNodeChanged(node);
516 }
517 
invalidateAllFrames()518 void KisImage::invalidateAllFrames()
519 {
520     invalidateFrames(KisTimeRange::infinite(0), QRect());
521 }
522 
setOverlaySelectionMask(KisSelectionMaskSP mask)523 void KisImage::setOverlaySelectionMask(KisSelectionMaskSP mask)
524 {
525     if (m_d->targetOverlaySelectionMask == mask) return;
526 
527     m_d->targetOverlaySelectionMask = mask;
528 
529     struct UpdateOverlaySelectionStroke : public KisSimpleStrokeStrategy {
530         UpdateOverlaySelectionStroke(KisImageSP image)
531             : KisSimpleStrokeStrategy(QLatin1String("update-overlay-selection-mask"), kundo2_noi18n("update-overlay-selection-mask")),
532               m_image(image)
533         {
534             this->enableJob(JOB_INIT, true, KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE);
535             setClearsRedoOnStart(false);
536         }
537 
538         void initStrokeCallback() {
539             KisSelectionMaskSP oldMask = m_image->m_d->overlaySelectionMask;
540             KisSelectionMaskSP newMask = m_image->m_d->targetOverlaySelectionMask;
541             if (oldMask == newMask) return;
542 
543             KIS_SAFE_ASSERT_RECOVER_RETURN(!newMask || newMask->graphListener() == m_image);
544 
545             m_image->m_d->overlaySelectionMask = newMask;
546 
547             if (oldMask || newMask) {
548                 m_image->m_d->rootLayer->notifyChildMaskChanged();
549             }
550 
551             if (oldMask) {
552                 m_image->m_d->rootLayer->setDirtyDontResetAnimationCache(oldMask->extent());
553             }
554 
555             if (newMask) {
556                 newMask->setDirty();
557             }
558 
559             m_image->undoAdapter()->emitSelectionChanged();
560         }
561 
562     private:
563         KisImageSP m_image;
564     };
565 
566     KisStrokeId id = startStroke(new UpdateOverlaySelectionStroke(this));
567     endStroke(id);
568 }
569 
overlaySelectionMask() const570 KisSelectionMaskSP KisImage::overlaySelectionMask() const
571 {
572     return m_d->overlaySelectionMask;
573 }
574 
hasOverlaySelectionMask() const575 bool KisImage::hasOverlaySelectionMask() const
576 {
577     return m_d->overlaySelectionMask;
578 }
579 
globalSelection() const580 KisSelectionSP KisImage::globalSelection() const
581 {
582     KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask();
583     if (selectionMask) {
584         return selectionMask->selection();
585     } else {
586         return 0;
587     }
588 }
589 
setGlobalSelection(KisSelectionSP globalSelection)590 void KisImage::setGlobalSelection(KisSelectionSP globalSelection)
591 {
592     KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask();
593 
594     if (!globalSelection) {
595         if (selectionMask) {
596             removeNode(selectionMask);
597         }
598     }
599     else {
600         if (!selectionMask) {
601             selectionMask = new KisSelectionMask(this, i18n("Selection Mask"));
602             selectionMask->initSelection(m_d->rootLayer);
603             addNode(selectionMask);
604             // If we do not set the selection now, the setActive call coming next
605             // can be very, very expensive, depending on the size of the image.
606             selectionMask->setSelection(globalSelection);
607             selectionMask->setActive(true);
608         }
609         else {
610             selectionMask->setSelection(globalSelection);
611         }
612 
613         KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->rootLayer->childCount() > 0);
614         KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->rootLayer->selectionMask());
615     }
616 
617     m_d->deselectedGlobalSelection = 0;
618     m_d->legacyUndoAdapter.emitSelectionChanged();
619 }
620 
deselectGlobalSelection()621 void KisImage::deselectGlobalSelection()
622 {
623     KisSelectionSP savedSelection = globalSelection();
624     setGlobalSelection(0);
625     m_d->deselectedGlobalSelection = savedSelection;
626 }
627 
canReselectGlobalSelection()628 bool KisImage::canReselectGlobalSelection()
629 {
630     return m_d->deselectedGlobalSelection;
631 }
632 
reselectGlobalSelection()633 void KisImage::reselectGlobalSelection()
634 {
635     if(m_d->deselectedGlobalSelection) {
636         setGlobalSelection(m_d->deselectedGlobalSelection);
637     }
638 }
639 
nextLayerName(const QString & _baseName) const640 QString KisImage::nextLayerName(const QString &_baseName) const
641 {
642     QString baseName = _baseName;
643 
644     int numLayers = 0;
645     int maxLayerIndex = 0;
646     QRegularExpression numberedLayerRegexp(".* (\\d+)$");
647     KisLayerUtils::recursiveApplyNodes(root(),
648         [&numLayers, &maxLayerIndex, &numberedLayerRegexp] (KisNodeSP node) {
649             if (node->inherits("KisLayer")) {
650                 QRegularExpressionMatch match = numberedLayerRegexp.match(node->name());
651 
652                 if (match.hasMatch()) {
653                     maxLayerIndex = qMax(maxLayerIndex, match.captured(1).toInt());
654                 }
655                 numLayers++;
656             }
657         });
658 
659     // special case if there is only root node
660     if (numLayers == 1) {
661         return i18n("background");
662     }
663 
664     if (baseName.isEmpty()) {
665         baseName = i18n("Paint Layer");
666     }
667 
668     return QString("%1 %2").arg(baseName).arg(maxLayerIndex + 1);
669 }
670 
compositeProgressProxy()671 KisCompositeProgressProxy* KisImage::compositeProgressProxy()
672 {
673     return &m_d->compositeProgressProxy;
674 }
675 
locked() const676 bool KisImage::locked() const
677 {
678     return m_d->lockCount != 0;
679 }
680 
barrierLock(bool readOnly)681 void KisImage::barrierLock(bool readOnly)
682 {
683     if (!locked()) {
684         requestStrokeEnd();
685         KisBusyWaitBroker::instance()->notifyWaitOnImageStarted(this);
686         m_d->scheduler.barrierLock();
687         KisBusyWaitBroker::instance()->notifyWaitOnImageEnded(this);
688         m_d->lockedForReadOnly = readOnly;
689     } else {
690         m_d->lockedForReadOnly &= readOnly;
691     }
692 
693     m_d->lockCount++;
694 }
695 
tryBarrierLock(bool readOnly)696 bool KisImage::tryBarrierLock(bool readOnly)
697 {
698     bool result = true;
699 
700     if (!locked()) {
701         result = m_d->scheduler.tryBarrierLock();
702         m_d->lockedForReadOnly = readOnly;
703     }
704 
705     if (result) {
706         m_d->lockCount++;
707         m_d->lockedForReadOnly &= readOnly;
708     }
709 
710     return result;
711 }
712 
isIdle(bool allowLocked)713 bool KisImage::isIdle(bool allowLocked)
714 {
715     return (allowLocked || !locked()) && m_d->scheduler.isIdle();
716 }
717 
lock()718 void KisImage::lock()
719 {
720     if (!locked()) {
721         requestStrokeEnd();
722         KisBusyWaitBroker::instance()->notifyWaitOnImageStarted(this);
723         m_d->scheduler.lock();
724         KisBusyWaitBroker::instance()->notifyWaitOnImageEnded(this);
725     }
726     m_d->lockCount++;
727     m_d->lockedForReadOnly = false;
728 }
729 
unlock()730 void KisImage::unlock()
731 {
732     Q_ASSERT(locked());
733 
734     if (locked()) {
735         m_d->lockCount--;
736 
737         if (m_d->lockCount == 0) {
738             m_d->scheduler.unlock(!m_d->lockedForReadOnly);
739         }
740     }
741 }
742 
blockUpdates()743 void KisImage::blockUpdates()
744 {
745     m_d->scheduler.blockUpdates();
746 }
747 
unblockUpdates()748 void KisImage::unblockUpdates()
749 {
750     m_d->scheduler.unblockUpdates();
751 }
752 
setSize(const QSize & size)753 void KisImage::setSize(const QSize& size)
754 {
755     m_d->width = size.width();
756     m_d->height = size.height();
757 }
758 
resizeImageImpl(const QRect & newRect,bool cropLayers)759 void KisImage::resizeImageImpl(const QRect& newRect, bool cropLayers)
760 {
761     if (newRect == bounds() && !cropLayers) return;
762 
763     KUndo2MagicString actionName = cropLayers ?
764         kundo2_i18n("Crop Image") :
765         kundo2_i18n("Resize Image");
766 
767     KisImageSignalVector emitSignals;
768     emitSignals << ComplexSizeChangedSignal(newRect, newRect.size());
769     emitSignals << ModifiedSignal;
770 
771     KisCropSavedExtraData *extraData =
772         new KisCropSavedExtraData(cropLayers ?
773                                   KisCropSavedExtraData::CROP_IMAGE :
774                                   KisCropSavedExtraData::RESIZE_IMAGE,
775                                   newRect);
776 
777     KisProcessingApplicator applicator(this, m_d->rootLayer,
778                                        KisProcessingApplicator::RECURSIVE |
779                                        KisProcessingApplicator::NO_UI_UPDATES,
780                                        emitSignals, actionName, extraData);
781 
782     if (cropLayers || !newRect.topLeft().isNull()) {
783         KisProcessingVisitorSP visitor =
784             new KisCropProcessingVisitor(newRect, cropLayers, true);
785         applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT);
786     }
787     applicator.applyCommand(new KisImageResizeCommand(this, newRect.size()));
788     applicator.end();
789 }
790 
resizeImage(const QRect & newRect)791 void KisImage::resizeImage(const QRect& newRect)
792 {
793     resizeImageImpl(newRect, false);
794 }
795 
cropImage(const QRect & newRect)796 void KisImage::cropImage(const QRect& newRect)
797 {
798     resizeImageImpl(newRect, true);
799 }
800 
purgeUnusedData(bool isCancellable)801 void KisImage::purgeUnusedData(bool isCancellable)
802 {
803     /**
804      * WARNING: don't use this function unless you know what you are doing!
805      *
806      * It breaks undo on layers! Therefore, after calling it, KisImage is not
807      * undo-capable anymore!
808      */
809 
810     struct PurgeUnusedDataStroke : public KisRunnableBasedStrokeStrategy {
811         PurgeUnusedDataStroke(KisImageSP image, bool isCancellable)
812             : KisRunnableBasedStrokeStrategy(QLatin1String("purge-unused-data"),
813                                              kundo2_noi18n("purge-unused-data")),
814               m_image(image)
815         {
816             this->enableJob(JOB_INIT, true, KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE);
817             this->enableJob(JOB_DOSTROKE, true);
818             setClearsRedoOnStart(false);
819             setRequestsOtherStrokesToEnd(!isCancellable);
820             setCanForgetAboutMe(isCancellable);
821         }
822 
823         void initStrokeCallback() {
824             KisPaintDeviceList deviceList;
825             QVector<KisStrokeJobData*> jobsData;
826 
827             KisLayerUtils::recursiveApplyNodes(m_image->root(),
828                 [&deviceList](KisNodeSP node) {
829                    deviceList << node->getLodCapableDevices();
830                  });
831 
832             /// make sure we deduplicate the list to avoid
833             /// concurrent write access to the devices
834             KritaUtils::makeContainerUnique(deviceList);
835 
836             Q_FOREACH (KisPaintDeviceSP device, deviceList) {
837                 if (!device) continue;
838 
839                 KritaUtils::addJobConcurrent(jobsData,
840                     [device] () {
841                         const_cast<KisPaintDevice*>(device.data())->purgeDefaultPixels();
842                     });
843             }
844 
845             addMutatedJobs(jobsData);
846         }
847 
848     private:
849         KisImageSP m_image;
850     };
851 
852     KisStrokeId id = startStroke(new PurgeUnusedDataStroke(this, isCancellable));
853     endStroke(id);
854 }
855 
cropNode(KisNodeSP node,const QRect & newRect)856 void KisImage::cropNode(KisNodeSP node, const QRect& newRect)
857 {
858     bool isLayer = qobject_cast<KisLayer*>(node.data());
859     KUndo2MagicString actionName = isLayer ?
860         kundo2_i18n("Crop Layer") :
861         kundo2_i18n("Crop Mask");
862 
863     KisImageSignalVector emitSignals;
864     emitSignals << ModifiedSignal;
865 
866     KisCropSavedExtraData *extraData =
867         new KisCropSavedExtraData(KisCropSavedExtraData::CROP_LAYER,
868                                   newRect, node);
869 
870     KisProcessingApplicator applicator(this, node,
871                                        KisProcessingApplicator::RECURSIVE,
872                                        emitSignals, actionName, extraData);
873 
874     KisProcessingVisitorSP visitor =
875         new KisCropProcessingVisitor(newRect, true, false);
876     applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT);
877     applicator.end();
878 }
879 
scaleImage(const QSize & size,qreal xres,qreal yres,KisFilterStrategy * filterStrategy)880 void KisImage::scaleImage(const QSize &size, qreal xres, qreal yres, KisFilterStrategy *filterStrategy)
881 {
882     bool resolutionChanged = xres != xRes() && yres != yRes();
883     bool sizeChanged = size != this->size();
884 
885     if (!resolutionChanged && !sizeChanged) return;
886 
887     KisImageSignalVector emitSignals;
888     if (resolutionChanged) emitSignals << ResolutionChangedSignal;
889     if (sizeChanged) emitSignals << ComplexSizeChangedSignal(bounds(), size);
890     emitSignals << ModifiedSignal;
891 
892     KUndo2MagicString actionName = sizeChanged ?
893         kundo2_i18n("Scale Image") :
894         kundo2_i18n("Change Image Resolution");
895 
896     KisProcessingApplicator::ProcessingFlags signalFlags =
897         (resolutionChanged || sizeChanged) ?
898                 KisProcessingApplicator::NO_UI_UPDATES :
899                 KisProcessingApplicator::NONE;
900 
901     KisProcessingApplicator applicator(this, m_d->rootLayer,
902                                        KisProcessingApplicator::RECURSIVE | signalFlags,
903                                        emitSignals, actionName);
904 
905     qreal sx = qreal(size.width()) / this->size().width();
906     qreal sy = qreal(size.height()) / this->size().height();
907 
908     QTransform shapesCorrection;
909 
910     if (resolutionChanged) {
911         shapesCorrection = QTransform::fromScale(xRes() / xres, yRes() / yres);
912     }
913 
914     KisProcessingVisitorSP visitor =
915         new KisTransformProcessingVisitor(sx, sy,
916                                           0, 0,
917                                           QPointF(),
918                                           0,
919                                           0, 0,
920                                           filterStrategy,
921                                           shapesCorrection);
922 
923     applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT);
924 
925     if (resolutionChanged) {
926         KUndo2Command *parent =
927             new KisResetShapesCommand(m_d->rootLayer);
928         new KisImageSetResolutionCommand(this, xres, yres, parent);
929         applicator.applyCommand(parent);
930     }
931 
932     if (sizeChanged) {
933         applicator.applyCommand(new KisImageResizeCommand(this, size));
934     }
935 
936     applicator.end();
937 }
938 
scaleNode(KisNodeSP node,const QPointF & center,qreal scaleX,qreal scaleY,KisFilterStrategy * filterStrategy,KisSelectionSP selection)939 void KisImage::scaleNode(KisNodeSP node, const QPointF &center, qreal scaleX, qreal scaleY, KisFilterStrategy *filterStrategy, KisSelectionSP selection)
940 {
941     KUndo2MagicString actionName(kundo2_i18n("Scale Layer"));
942     KisImageSignalVector emitSignals;
943     emitSignals << ModifiedSignal;
944 
945     QPointF offset;
946     {
947         KisTransformWorker worker(0,
948                                   scaleX, scaleY,
949                                   0, 0, 0, 0,
950                                   0.0,
951                                   0, 0, 0, 0);
952         QTransform transform = worker.transform();
953 
954         offset = center - transform.map(center);
955     }
956 
957     KisProcessingApplicator applicator(this, node,
958                                        KisProcessingApplicator::RECURSIVE,
959                                        emitSignals, actionName);
960 
961     KisTransformProcessingVisitor *visitor =
962         new KisTransformProcessingVisitor(scaleX, scaleY,
963                                           0, 0,
964                                           QPointF(),
965                                           0,
966                                           offset.x(), offset.y(),
967                                           filterStrategy);
968 
969     visitor->setSelection(selection);
970 
971     if (selection) {
972         applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT);
973     } else {
974         applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT);
975     }
976 
977     applicator.end();
978 }
979 
rotateImpl(const KUndo2MagicString & actionName,KisNodeSP rootNode,double radians,bool resizeImage,KisSelectionSP selection)980 void KisImage::rotateImpl(const KUndo2MagicString &actionName,
981                           KisNodeSP rootNode,
982                           double radians,
983                           bool resizeImage,
984                           KisSelectionSP selection)
985 {
986     // we can either transform (and resize) the whole image or
987     // transform a selection, we cannot do both at the same time
988     KIS_SAFE_ASSERT_RECOVER(!(bool(selection) && resizeImage)) {
989         selection = 0;
990     }
991 
992     const QRect baseBounds =
993         resizeImage ? bounds() :
994         selection ? selection->selectedExactRect() :
995         rootNode->exactBounds();
996 
997     QPointF offset;
998     QSize newSize;
999 
1000     {
1001         KisTransformWorker worker(0,
1002                                   1.0, 1.0,
1003                                   0, 0, 0, 0,
1004                                   radians,
1005                                   0, 0, 0, 0);
1006         QTransform transform = worker.transform();
1007 
1008         if (resizeImage) {
1009             QRect newRect = transform.mapRect(baseBounds);
1010             newSize = newRect.size();
1011             offset = -newRect.topLeft();
1012         }
1013         else {
1014             QPointF origin = QRectF(baseBounds).center();
1015 
1016             newSize = size();
1017             offset = -(transform.map(origin) - origin);
1018         }
1019     }
1020 
1021     bool sizeChanged = resizeImage &&
1022         (newSize.width() != baseBounds.width() ||
1023          newSize.height() != baseBounds.height());
1024 
1025     // These signals will be emitted after processing is done
1026     KisImageSignalVector emitSignals;
1027     if (sizeChanged) emitSignals << ComplexSizeChangedSignal(baseBounds, newSize);
1028     emitSignals << ModifiedSignal;
1029 
1030     // These flags determine whether updates are transferred to the UI during processing
1031     KisProcessingApplicator::ProcessingFlags signalFlags =
1032         sizeChanged ?
1033         KisProcessingApplicator::NO_UI_UPDATES :
1034         KisProcessingApplicator::NONE;
1035 
1036 
1037     KisProcessingApplicator applicator(this, rootNode,
1038                                        KisProcessingApplicator::RECURSIVE | signalFlags,
1039                                        emitSignals, actionName);
1040 
1041     KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value("Bicubic");
1042 
1043     KisTransformProcessingVisitor *visitor =
1044             new KisTransformProcessingVisitor(1.0, 1.0, 0.0, 0.0,
1045                                               QPointF(),
1046                                               radians,
1047                                               offset.x(), offset.y(),
1048                                               filter);
1049     if (selection) {
1050         visitor->setSelection(selection);
1051     }
1052 
1053     if (selection) {
1054         applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT);
1055     } else {
1056         applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT);
1057     }
1058 
1059     if (sizeChanged) {
1060         applicator.applyCommand(new KisImageResizeCommand(this, newSize));
1061     }
1062     applicator.end();
1063 }
1064 
1065 
rotateImage(double radians)1066 void KisImage::rotateImage(double radians)
1067 {
1068     rotateImpl(kundo2_i18n("Rotate Image"), root(), radians, true, 0);
1069 }
1070 
rotateNode(KisNodeSP node,double radians,KisSelectionSP selection)1071 void KisImage::rotateNode(KisNodeSP node, double radians, KisSelectionSP selection)
1072 {
1073     if (node->inherits("KisMask")) {
1074         rotateImpl(kundo2_i18n("Rotate Mask"), node, radians, false, selection);
1075     } else {
1076         rotateImpl(kundo2_i18n("Rotate Layer"), node, radians, false, selection);
1077     }
1078 }
1079 
shearImpl(const KUndo2MagicString & actionName,KisNodeSP rootNode,bool resizeImage,double angleX,double angleY,KisSelectionSP selection)1080 void KisImage::shearImpl(const KUndo2MagicString &actionName,
1081                          KisNodeSP rootNode,
1082                          bool resizeImage,
1083                          double angleX, double angleY,
1084                          KisSelectionSP selection)
1085 {
1086     const QRect baseBounds =
1087         resizeImage ? bounds() :
1088         selection ? selection->selectedExactRect() :
1089         rootNode->exactBounds();
1090 
1091     const QPointF origin = QRectF(baseBounds).center();
1092 
1093     //angleX, angleY are in degrees
1094     const qreal pi = 3.1415926535897932385;
1095     const qreal deg2rad = pi / 180.0;
1096 
1097     qreal tanX = tan(angleX * deg2rad);
1098     qreal tanY = tan(angleY * deg2rad);
1099 
1100     QPointF offset;
1101     QSize newSize;
1102 
1103     {
1104         KisTransformWorker worker(0,
1105                                   1.0, 1.0,
1106                                   tanX, tanY, origin.x(), origin.y(),
1107                                   0,
1108                                   0, 0, 0, 0);
1109 
1110         QRect newRect = worker.transform().mapRect(baseBounds);
1111         newSize = newRect.size();
1112         if (resizeImage) offset = -newRect.topLeft();
1113     }
1114 
1115     if (newSize == baseBounds.size()) return;
1116 
1117     KisImageSignalVector emitSignals;
1118     if (resizeImage) emitSignals << ComplexSizeChangedSignal(baseBounds, newSize);
1119     emitSignals << ModifiedSignal;
1120 
1121     KisProcessingApplicator::ProcessingFlags signalFlags =
1122         KisProcessingApplicator::RECURSIVE;
1123     if (resizeImage) signalFlags |= KisProcessingApplicator::NO_UI_UPDATES;
1124 
1125     KisProcessingApplicator applicator(this, rootNode,
1126                                        signalFlags,
1127                                        emitSignals, actionName);
1128 
1129     KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value("Bilinear");
1130 
1131     KisTransformProcessingVisitor *visitor =
1132             new KisTransformProcessingVisitor(1.0, 1.0,
1133                                               tanX, tanY, origin,
1134                                               0,
1135                                               offset.x(), offset.y(),
1136                                               filter);
1137 
1138     if (selection) {
1139         visitor->setSelection(selection);
1140     }
1141 
1142     if (selection) {
1143         applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT);
1144     } else {
1145         applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT);
1146     }
1147 
1148     if (resizeImage) {
1149         applicator.applyCommand(new KisImageResizeCommand(this, newSize));
1150     }
1151 
1152     applicator.end();
1153 }
1154 
shearNode(KisNodeSP node,double angleX,double angleY,KisSelectionSP selection)1155 void KisImage::shearNode(KisNodeSP node, double angleX, double angleY, KisSelectionSP selection)
1156 {
1157     if (node->inherits("KisMask")) {
1158         shearImpl(kundo2_i18n("Shear Mask"), node, false,
1159                   angleX, angleY, selection);
1160     } else {
1161         shearImpl(kundo2_i18n("Shear Layer"), node, false,
1162                   angleX, angleY, selection);
1163     }
1164 }
1165 
shear(double angleX,double angleY)1166 void KisImage::shear(double angleX, double angleY)
1167 {
1168     shearImpl(kundo2_i18n("Shear Image"), m_d->rootLayer, true,
1169               angleX, angleY, 0);
1170 }
1171 
convertLayerColorSpace(KisNodeSP node,const KoColorSpace * dstColorSpace,KoColorConversionTransformation::Intent renderingIntent,KoColorConversionTransformation::ConversionFlags conversionFlags)1172 void KisImage::convertLayerColorSpace(KisNodeSP node,
1173                                       const KoColorSpace *dstColorSpace,
1174                                       KoColorConversionTransformation::Intent renderingIntent,
1175                                       KoColorConversionTransformation::ConversionFlags conversionFlags)
1176 {
1177     if (!node->projectionLeaf()->isLayer()) return;
1178 
1179     if (!dstColorSpace) return;
1180 
1181     const bool hasNodesToConvert =
1182         KisLayerUtils::recursiveFindNode(node,
1183             [dstColorSpace](KisNodeSP node) {
1184                 return *node->colorSpace() != *dstColorSpace;
1185             });
1186 
1187     if (!hasNodesToConvert) return;
1188 
1189     KUndo2MagicString actionName =
1190         kundo2_i18n("Convert Layer Color Space");
1191 
1192     KisImageSignalVector emitSignals;
1193     emitSignals << ModifiedSignal;
1194 
1195     KisProcessingApplicator applicator(this, node,
1196                                        KisProcessingApplicator::RECURSIVE,
1197                                        emitSignals, actionName);
1198 
1199     applicator.applyVisitor(
1200         new KisConvertColorSpaceProcessingVisitor(
1201             dstColorSpace,
1202             renderingIntent, conversionFlags),
1203         KisStrokeJobData::CONCURRENT);
1204 
1205     applicator.end();
1206 }
1207 
1208 struct KisImage::KisImagePrivate::SetImageProjectionColorSpace : public KisCommandUtils::FlipFlopCommand
1209 {
SetImageProjectionColorSpaceKisImage::KisImagePrivate::SetImageProjectionColorSpace1210     SetImageProjectionColorSpace(const KoColorSpace *cs, KisImageWSP image,
1211                                  State initialState, KUndo2Command *parent = 0)
1212         : KisCommandUtils::FlipFlopCommand(initialState, parent),
1213           m_cs(cs),
1214           m_image(image)
1215     {
1216     }
1217 
partAKisImage::KisImagePrivate::SetImageProjectionColorSpace1218     void partA() override {
1219         KisImageSP image = m_image;
1220 
1221         if (image) {
1222             image->setProjectionColorSpace(m_cs);
1223         }
1224     }
1225 
1226 private:
1227     const KoColorSpace *m_cs;
1228     KisImageWSP m_image;
1229 };
1230 
convertImageColorSpaceImpl(const KoColorSpace * dstColorSpace,bool convertLayers,KoColorConversionTransformation::Intent renderingIntent,KoColorConversionTransformation::ConversionFlags conversionFlags)1231 void KisImage::KisImagePrivate::convertImageColorSpaceImpl(const KoColorSpace *dstColorSpace,
1232                                                            bool convertLayers,
1233                                                            KoColorConversionTransformation::Intent renderingIntent,
1234                                                            KoColorConversionTransformation::ConversionFlags conversionFlags)
1235 {
1236     const KoColorSpace *srcColorSpace = this->colorSpace;
1237 
1238     if (!dstColorSpace) return;
1239 
1240     if (convertLayers) {
1241         const bool hasNodesToConvert =
1242             KisLayerUtils::recursiveFindNode(this->rootLayer,
1243                 [dstColorSpace](KisNodeSP node) {
1244                     return *node->colorSpace() != *dstColorSpace;
1245                 });
1246 
1247         if (!hasNodesToConvert && *this->colorSpace == *dstColorSpace) return;
1248     } else {
1249         if (*this->colorSpace == *dstColorSpace) return;
1250     }
1251 
1252     const KUndo2MagicString actionName =
1253         convertLayers ?
1254         kundo2_i18n("Convert Image Color Space") :
1255         kundo2_i18n("Convert Projection Color Space");
1256 
1257     KisImageSignalVector emitSignals;
1258     emitSignals << ColorSpaceChangedSignal;
1259     emitSignals << ModifiedSignal;
1260 
1261     KisProcessingApplicator applicator(q, this->rootLayer,
1262                                        KisProcessingApplicator::RECURSIVE |
1263                                        KisProcessingApplicator::NO_UI_UPDATES,
1264                                        emitSignals, actionName);
1265 
1266     applicator.applyCommand(
1267         new KisImagePrivate::SetImageProjectionColorSpace(dstColorSpace,
1268                                                           KisImageWSP(q),
1269                                                           KisCommandUtils::FlipFlopCommand::INITIALIZING),
1270         KisStrokeJobData::BARRIER);
1271 
1272     if (convertLayers) {
1273         applicator.applyVisitor(
1274                     new KisConvertColorSpaceProcessingVisitor(
1275                         dstColorSpace,
1276                         renderingIntent, conversionFlags),
1277                     KisStrokeJobData::CONCURRENT);
1278     } else {
1279         applicator.applyCommand(
1280             new KisDoSomethingCommand<
1281                     KisDoSomethingCommandOps::ResetOp, KisGroupLayerSP>
1282                     (this->rootLayer, false));
1283         applicator.applyCommand(
1284             new KisDoSomethingCommand<
1285                     KisDoSomethingCommandOps::ResetOp, KisGroupLayerSP>
1286                     (this->rootLayer, true));
1287     }
1288 
1289     applicator.applyCommand(
1290         new KisImagePrivate::SetImageProjectionColorSpace(srcColorSpace,
1291                                                           KisImageWSP(q),
1292                                                           KisCommandUtils::FlipFlopCommand::FINALIZING),
1293         KisStrokeJobData::BARRIER);
1294 
1295 
1296     applicator.end();
1297 }
1298 
convertImageColorSpace(const KoColorSpace * dstColorSpace,KoColorConversionTransformation::Intent renderingIntent,KoColorConversionTransformation::ConversionFlags conversionFlags)1299 void KisImage::convertImageColorSpace(const KoColorSpace *dstColorSpace,
1300                                       KoColorConversionTransformation::Intent renderingIntent,
1301                                       KoColorConversionTransformation::ConversionFlags conversionFlags)
1302 {
1303     m_d->convertImageColorSpaceImpl(dstColorSpace, true, renderingIntent, conversionFlags);
1304 }
1305 
convertImageProjectionColorSpace(const KoColorSpace * dstColorSpace)1306 void KisImage::convertImageProjectionColorSpace(const KoColorSpace *dstColorSpace)
1307 {
1308     m_d->convertImageColorSpaceImpl(dstColorSpace, false,
1309                                     KoColorConversionTransformation::internalRenderingIntent(),
1310                                     KoColorConversionTransformation::internalConversionFlags());
1311 }
1312 
1313 
assignLayerProfile(KisNodeSP node,const KoColorProfile * profile)1314 bool KisImage::assignLayerProfile(KisNodeSP node, const KoColorProfile *profile)
1315 {
1316     const KoColorSpace *srcColorSpace = node->colorSpace();
1317 
1318     if (!node->projectionLeaf()->isLayer()) return false;
1319     if (!profile || *srcColorSpace->profile() == *profile) return false;
1320 
1321     KUndo2MagicString actionName = kundo2_i18n("Assign Profile to Layer");
1322 
1323     KisImageSignalVector emitSignals;
1324     emitSignals << ModifiedSignal;
1325 
1326     const KoColorSpace *dstColorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile);
1327     if (!dstColorSpace) return false;
1328 
1329     KisProcessingApplicator applicator(this, node,
1330                                        KisProcessingApplicator::RECURSIVE |
1331                                        KisProcessingApplicator::NO_UI_UPDATES,
1332                                        emitSignals, actionName);
1333 
1334     applicator.applyVisitor(
1335         new KisAssignProfileProcessingVisitor(
1336             srcColorSpace, dstColorSpace),
1337         KisStrokeJobData::CONCURRENT);
1338 
1339     applicator.end();
1340 
1341     return true;
1342 }
1343 
1344 
assignImageProfile(const KoColorProfile * profile,bool blockAllUpdates)1345 bool KisImage::assignImageProfile(const KoColorProfile *profile, bool blockAllUpdates)
1346 {
1347     if (!profile) return false;
1348 
1349     const KoColorSpace *srcColorSpace = m_d->colorSpace;
1350     bool imageProfileIsSame = *srcColorSpace->profile() == *profile;
1351 
1352     imageProfileIsSame &=
1353         !KisLayerUtils::recursiveFindNode(m_d->rootLayer,
1354             [profile] (KisNodeSP node) {
1355                 return *node->colorSpace()->profile() != *profile;
1356             });
1357 
1358     if (imageProfileIsSame) {
1359         dbgImage << "Trying to set the same image profile again" << ppVar(srcColorSpace->profile()->name()) << ppVar(profile->name());
1360         return true;
1361     }
1362 
1363     KUndo2MagicString actionName = kundo2_i18n("Assign Profile");
1364 
1365     KisImageSignalVector emitSignals;
1366     emitSignals << ProfileChangedSignal;
1367     emitSignals << ModifiedSignal;
1368 
1369     const KoColorSpace *dstColorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile);
1370     if (!dstColorSpace) return false;
1371 
1372     KisProcessingApplicator applicator(this, m_d->rootLayer,
1373                                        KisProcessingApplicator::RECURSIVE |
1374                                        (!blockAllUpdates ?
1375                                             KisProcessingApplicator::NO_UI_UPDATES :
1376                                             KisProcessingApplicator::NO_IMAGE_UPDATES),
1377                                        emitSignals, actionName);
1378 
1379     applicator.applyCommand(
1380         new KisImagePrivate::SetImageProjectionColorSpace(dstColorSpace,
1381                                                           KisImageWSP(this),
1382                                                           KisCommandUtils::FlipFlopCommand::INITIALIZING),
1383         KisStrokeJobData::BARRIER);
1384 
1385     applicator.applyVisitor(
1386         new KisAssignProfileProcessingVisitor(
1387             srcColorSpace, dstColorSpace),
1388         KisStrokeJobData::CONCURRENT);
1389 
1390     applicator.applyCommand(
1391         new KisImagePrivate::SetImageProjectionColorSpace(srcColorSpace,
1392                                                           KisImageWSP(this),
1393                                                           KisCommandUtils::FlipFlopCommand::FINALIZING),
1394         KisStrokeJobData::BARRIER);
1395 
1396 
1397     applicator.end();
1398 
1399     return true;
1400 }
1401 
setProjectionColorSpace(const KoColorSpace * colorSpace)1402 void KisImage::setProjectionColorSpace(const KoColorSpace * colorSpace)
1403 {
1404     m_d->colorSpace = colorSpace;
1405 }
1406 
colorSpace() const1407 const KoColorSpace * KisImage::colorSpace() const
1408 {
1409     return m_d->colorSpace;
1410 }
1411 
profile() const1412 const KoColorProfile * KisImage::profile() const
1413 {
1414     return colorSpace()->profile();
1415 }
1416 
xRes() const1417 double KisImage::xRes() const
1418 {
1419     return m_d->xres;
1420 }
1421 
yRes() const1422 double KisImage::yRes() const
1423 {
1424     return m_d->yres;
1425 }
1426 
setResolution(double xres,double yres)1427 void KisImage::setResolution(double xres, double yres)
1428 {
1429     m_d->xres = xres;
1430     m_d->yres = yres;
1431 }
1432 
documentToPixel(const QPointF & documentCoord) const1433 QPointF KisImage::documentToPixel(const QPointF &documentCoord) const
1434 {
1435     return QPointF(documentCoord.x() * xRes(), documentCoord.y() * yRes());
1436 }
1437 
documentToImagePixelFloored(const QPointF & documentCoord) const1438 QPoint KisImage::documentToImagePixelFloored(const QPointF &documentCoord) const
1439 {
1440     QPointF pixelCoord = documentToPixel(documentCoord);
1441     return QPoint(qFloor(pixelCoord.x()), qFloor(pixelCoord.y()));
1442 }
1443 
documentToPixel(const QRectF & documentRect) const1444 QRectF KisImage::documentToPixel(const QRectF &documentRect) const
1445 {
1446     return QRectF(documentToPixel(documentRect.topLeft()), documentToPixel(documentRect.bottomRight()));
1447 }
1448 
pixelToDocument(const QPointF & pixelCoord) const1449 QPointF KisImage::pixelToDocument(const QPointF &pixelCoord) const
1450 {
1451     return QPointF(pixelCoord.x() / xRes(), pixelCoord.y() / yRes());
1452 }
1453 
pixelToDocument(const QPoint & pixelCoord) const1454 QPointF KisImage::pixelToDocument(const QPoint &pixelCoord) const
1455 {
1456     return QPointF((pixelCoord.x() + 0.5) / xRes(), (pixelCoord.y() + 0.5) / yRes());
1457 }
1458 
pixelToDocument(const QRectF & pixelCoord) const1459 QRectF KisImage::pixelToDocument(const QRectF &pixelCoord) const
1460 {
1461     return QRectF(pixelToDocument(pixelCoord.topLeft()), pixelToDocument(pixelCoord.bottomRight()));
1462 }
1463 
width() const1464 qint32 KisImage::width() const
1465 {
1466     return m_d->width;
1467 }
1468 
height() const1469 qint32 KisImage::height() const
1470 {
1471     return m_d->height;
1472 }
1473 
rootLayer() const1474 KisGroupLayerSP KisImage::rootLayer() const
1475 {
1476     Q_ASSERT(m_d->rootLayer);
1477     return m_d->rootLayer;
1478 }
1479 
projection() const1480 KisPaintDeviceSP KisImage::projection() const
1481 {
1482     if (m_d->isolatedRootNode) {
1483         return m_d->isolatedRootNode->projection();
1484     }
1485 
1486 
1487     Q_ASSERT(m_d->rootLayer);
1488     KisPaintDeviceSP projection = m_d->rootLayer->projection();
1489     Q_ASSERT(projection);
1490     return projection;
1491 }
1492 
nlayers() const1493 qint32 KisImage::nlayers() const
1494 {
1495     QStringList list;
1496     list << "KisLayer";
1497 
1498     KisCountVisitor visitor(list, KoProperties());
1499     m_d->rootLayer->accept(visitor);
1500     return visitor.count();
1501 }
1502 
nHiddenLayers() const1503 qint32 KisImage::nHiddenLayers() const
1504 {
1505     QStringList list;
1506     list << "KisLayer";
1507     KoProperties properties;
1508     properties.setProperty("visible", false);
1509     KisCountVisitor visitor(list, properties);
1510     m_d->rootLayer->accept(visitor);
1511 
1512     return visitor.count();
1513 }
1514 
flatten(KisNodeSP activeNode)1515 void KisImage::flatten(KisNodeSP activeNode)
1516 {
1517     KisLayerUtils::flattenImage(this, activeNode);
1518 }
1519 
mergeMultipleLayers(QList<KisNodeSP> mergedNodes,KisNodeSP putAfter)1520 void KisImage::mergeMultipleLayers(QList<KisNodeSP> mergedNodes, KisNodeSP putAfter)
1521 {
1522     if (!KisLayerUtils::tryMergeSelectionMasks(this, mergedNodes, putAfter)) {
1523         KisLayerUtils::mergeMultipleLayers(this, mergedNodes, putAfter);
1524     }
1525 }
1526 
mergeDown(KisLayerSP layer,const KisMetaData::MergeStrategy * strategy)1527 void KisImage::mergeDown(KisLayerSP layer, const KisMetaData::MergeStrategy* strategy)
1528 {
1529     KisLayerUtils::mergeDown(this, layer, strategy);
1530 }
1531 
flattenLayer(KisLayerSP layer)1532 void KisImage::flattenLayer(KisLayerSP layer)
1533 {
1534     KisLayerUtils::flattenLayer(this, layer);
1535 }
1536 
1537 
setModified()1538 void KisImage::setModified()
1539 {
1540     m_d->signalRouter.emitNotification(ModifiedSignal);
1541 }
1542 
setModifiedWithoutUndo()1543 void KisImage::setModifiedWithoutUndo()
1544 {
1545     m_d->signalRouter.emitNotification(ModifiedWithoutUndoSignal);
1546     m_d->signalRouter.emitNotification(ModifiedSignal);
1547 }
1548 
convertToQImage(QRect imageRect,const KoColorProfile * profile)1549 QImage KisImage::convertToQImage(QRect imageRect,
1550                                  const KoColorProfile * profile)
1551 {
1552     qint32 x;
1553     qint32 y;
1554     qint32 w;
1555     qint32 h;
1556     imageRect.getRect(&x, &y, &w, &h);
1557     return convertToQImage(x, y, w, h, profile);
1558 }
1559 
convertToQImage(qint32 x,qint32 y,qint32 w,qint32 h,const KoColorProfile * profile)1560 QImage KisImage::convertToQImage(qint32 x,
1561                                  qint32 y,
1562                                  qint32 w,
1563                                  qint32 h,
1564                                  const KoColorProfile * profile)
1565 {
1566     KisPaintDeviceSP dev = projection();
1567     if (!dev) return QImage();
1568     QImage image = dev->convertToQImage(const_cast<KoColorProfile*>(profile), x, y, w, h,
1569                                         KoColorConversionTransformation::internalRenderingIntent(),
1570                                         KoColorConversionTransformation::internalConversionFlags());
1571 
1572     return image;
1573 }
1574 
1575 
1576 
convertToQImage(const QSize & scaledImageSize,const KoColorProfile * profile)1577 QImage KisImage::convertToQImage(const QSize& scaledImageSize, const KoColorProfile *profile)
1578 {
1579     if (scaledImageSize.isEmpty()) {
1580         return QImage();
1581     }
1582 
1583     KisPaintDeviceSP dev = new KisPaintDevice(colorSpace());
1584     KisPainter gc;
1585     gc.copyAreaOptimized(QPoint(0, 0), projection(), dev, bounds());
1586     gc.end();
1587     double scaleX = qreal(scaledImageSize.width()) / width();
1588     double scaleY = qreal(scaledImageSize.height()) / height();
1589 
1590 
1591     if (scaleX < 1.0/256 || scaleY < 1.0/256) {
1592         // quick checking if we're not trying to scale too much
1593         // convertToQImage uses KisFixedPoint values, which means that the scale cannot be smaller than 1/2^8
1594         // BUG:432182
1595         // FIXME: would be best to extend KisFixedPoint instead
1596         return convertToQImage(size(), profile).scaled(scaledImageSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
1597     }
1598 
1599     QPointer<KoUpdater> updater = new KoDummyUpdater();
1600 
1601     KisTransformWorker worker(dev, scaleX, scaleY, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, updater, KisFilterStrategyRegistry::instance()->value("Bicubic"));
1602     worker.run();
1603 
1604     delete updater;
1605 
1606     return dev->convertToQImage(profile);
1607 }
notifyLayersChanged()1608 void KisImage::notifyLayersChanged()
1609 {
1610     m_d->signalRouter.emitNotification(LayersChangedSignal);
1611 }
1612 
bounds() const1613 QRect KisImage::bounds() const
1614 {
1615     return QRect(0, 0, width(), height());
1616 }
1617 
effectiveLodBounds() const1618 QRect KisImage::effectiveLodBounds() const
1619 {
1620     QRect boundRect = bounds();
1621 
1622     const int lod = currentLevelOfDetail();
1623     if (lod > 0) {
1624         KisLodTransform t(lod);
1625         boundRect = t.map(boundRect);
1626     }
1627 
1628     return boundRect;
1629 }
1630 
postExecutionUndoAdapter() const1631 KisPostExecutionUndoAdapter* KisImage::postExecutionUndoAdapter() const
1632 {
1633     const int lod = currentLevelOfDetail();
1634     return lod > 0 ?
1635         m_d->scheduler.lodNPostExecutionUndoAdapter() :
1636         &m_d->postExecutionUndoAdapter;
1637 }
1638 
lastExecutedCommand() const1639 const KUndo2Command* KisImage::lastExecutedCommand() const
1640 {
1641     return m_d->undoStore->presentCommand();
1642 }
1643 
setUndoStore(KisUndoStore * undoStore)1644 void KisImage::setUndoStore(KisUndoStore *undoStore)
1645 {
1646 
1647     m_d->legacyUndoAdapter.setUndoStore(undoStore);
1648     m_d->postExecutionUndoAdapter.setUndoStore(undoStore);
1649     m_d->undoStore.reset(undoStore);
1650 }
1651 
undoStore()1652 KisUndoStore* KisImage::undoStore()
1653 {
1654     return m_d->undoStore.data();
1655 }
1656 
undoAdapter() const1657 KisUndoAdapter* KisImage::undoAdapter() const
1658 {
1659     return &m_d->legacyUndoAdapter;
1660 }
1661 
setDefaultProjectionColor(const KoColor & color)1662 void KisImage::setDefaultProjectionColor(const KoColor &color)
1663 {
1664     KIS_ASSERT_RECOVER_RETURN(m_d->rootLayer);
1665     m_d->rootLayer->setDefaultProjectionColor(color);
1666 }
1667 
defaultProjectionColor() const1668 KoColor KisImage::defaultProjectionColor() const
1669 {
1670     KIS_ASSERT_RECOVER(m_d->rootLayer) {
1671         return KoColor(Qt::transparent, m_d->colorSpace);
1672     }
1673 
1674     return m_d->rootLayer->defaultProjectionColor();
1675 }
1676 
setRootLayer(KisGroupLayerSP rootLayer)1677 void KisImage::setRootLayer(KisGroupLayerSP rootLayer)
1678 {
1679     emit sigInternalStopIsolatedModeRequested();
1680 
1681     KoColor defaultProjectionColor(Qt::transparent, m_d->colorSpace);
1682 
1683     if (m_d->rootLayer) {
1684         m_d->rootLayer->setGraphListener(0);
1685         m_d->rootLayer->disconnect();
1686 
1687         KisPaintDeviceSP original = m_d->rootLayer->original();
1688         defaultProjectionColor = original->defaultPixel();
1689     }
1690 
1691     m_d->rootLayer = rootLayer;
1692     m_d->rootLayer->disconnect();
1693     m_d->rootLayer->setGraphListener(this);
1694     m_d->rootLayer->setImage(this);
1695 
1696     setRoot(m_d->rootLayer.data());
1697     this->setDefaultProjectionColor(defaultProjectionColor);
1698 }
1699 
addAnnotation(KisAnnotationSP annotation)1700 void KisImage::addAnnotation(KisAnnotationSP annotation)
1701 {
1702     // Find the icc annotation, if there is one
1703     vKisAnnotationSP_it it = m_d->annotations.begin();
1704     while (it != m_d->annotations.end()) {
1705         if ((*it)->type() == annotation->type()) {
1706             *it = annotation;
1707             return;
1708         }
1709         ++it;
1710     }
1711     m_d->annotations.push_back(annotation);
1712 }
1713 
annotation(const QString & type)1714 KisAnnotationSP KisImage::annotation(const QString& type)
1715 {
1716     vKisAnnotationSP_it it = m_d->annotations.begin();
1717     while (it != m_d->annotations.end()) {
1718         if ((*it)->type() == type) {
1719             return *it;
1720         }
1721         ++it;
1722     }
1723     return KisAnnotationSP(0);
1724 }
1725 
removeAnnotation(const QString & type)1726 void KisImage::removeAnnotation(const QString& type)
1727 {
1728     vKisAnnotationSP_it it = m_d->annotations.begin();
1729     while (it != m_d->annotations.end()) {
1730         if ((*it)->type() == type) {
1731             m_d->annotations.erase(it);
1732             return;
1733         }
1734         ++it;
1735     }
1736 }
1737 
beginAnnotations()1738 vKisAnnotationSP_it KisImage::beginAnnotations()
1739 {
1740     return m_d->annotations.begin();
1741 }
1742 
endAnnotations()1743 vKisAnnotationSP_it KisImage::endAnnotations()
1744 {
1745     return m_d->annotations.end();
1746 }
1747 
notifyAboutToBeDeleted()1748 void KisImage::notifyAboutToBeDeleted()
1749 {
1750     emit sigAboutToBeDeleted();
1751 }
1752 
signalRouter()1753 KisImageSignalRouter* KisImage::signalRouter()
1754 {
1755     return &m_d->signalRouter;
1756 }
1757 
waitForDone()1758 void KisImage::waitForDone()
1759 {
1760     requestStrokeEnd();
1761     KisBusyWaitBroker::instance()->notifyWaitOnImageStarted(this);
1762     m_d->scheduler.waitForDone();
1763     KisBusyWaitBroker::instance()->notifyWaitOnImageEnded(this);
1764 }
1765 
startStroke(KisStrokeStrategy * strokeStrategy)1766 KisStrokeId KisImage::startStroke(KisStrokeStrategy *strokeStrategy)
1767 {
1768     /**
1769      * Ask open strokes to end gracefully. All the strokes clients
1770      * (including the one calling this method right now) will get
1771      * a notification that they should probably end their strokes.
1772      * However this is purely their choice whether to end a stroke
1773      * or not.
1774      */
1775     if (strokeStrategy->requestsOtherStrokesToEnd()) {
1776         requestStrokeEnd();
1777     }
1778 
1779     /**
1780      * Some of the strokes can cancel their work with undoing all the
1781      * changes they did to the paint devices. The problem is that undo
1782      * stack will know nothing about it. Therefore, just notify it
1783      * explicitly
1784      */
1785     if (strokeStrategy->clearsRedoOnStart()) {
1786         m_d->undoStore->purgeRedoState();
1787     }
1788 
1789     return m_d->scheduler.startStroke(strokeStrategy);
1790 }
1791 
notifyProjectionUpdatedInPatches(const QRect & rc,QVector<KisRunnableStrokeJobData * > & jobs)1792 void KisImage::KisImagePrivate::notifyProjectionUpdatedInPatches(const QRect &rc, QVector<KisRunnableStrokeJobData*> &jobs)
1793 {
1794     KisImageConfig imageConfig(true);
1795     int patchWidth = imageConfig.updatePatchWidth();
1796     int patchHeight = imageConfig.updatePatchHeight();
1797 
1798     for (int y = 0; y < rc.height(); y += patchHeight) {
1799         for (int x = 0; x < rc.width(); x += patchWidth) {
1800             QRect patchRect(x, y, patchWidth, patchHeight);
1801             patchRect &= rc;
1802 
1803             KritaUtils::addJobConcurrent(jobs, std::bind(&KisImage::notifyProjectionUpdated, q, patchRect));
1804         }
1805     }
1806 }
1807 
startIsolatedMode(KisNodeSP node)1808 bool KisImage::startIsolatedMode(KisNodeSP node)
1809 {
1810     struct StartIsolatedModeStroke : public KisRunnableBasedStrokeStrategy {
1811         StartIsolatedModeStroke(KisNodeSP node, KisImageSP image)
1812             : KisRunnableBasedStrokeStrategy(QLatin1String("start-isolated-mode"),
1813                                              kundo2_noi18n("start-isolated-mode")),
1814               m_newRoot(node),
1815               m_image(image)
1816         {
1817             this->enableJob(JOB_INIT, true, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE);
1818             this->enableJob(JOB_DOSTROKE, true);
1819             this->enableJob(JOB_FINISH, true, KisStrokeJobData::BARRIER);
1820             setClearsRedoOnStart(false);
1821         }
1822 
1823         void initStrokeCallback() override {
1824             // pass-though node don't have any projection prepared, so we should
1825             // explicitly regenerate it before activating isolated mode.
1826             m_newRoot->projectionLeaf()->explicitlyRegeneratePassThroughProjection();
1827 
1828             m_prevRoot = m_image->m_d->isolatedRootNode;
1829 
1830             const bool beforeVisibility = m_newRoot->projectionLeaf()->visible();
1831             const bool prevRootBeforeVisibility = m_prevRoot ? m_prevRoot->projectionLeaf()->visible() : false;
1832 
1833             m_image->m_d->isolatedRootNode = m_newRoot;
1834             emit m_image->sigIsolatedModeChanged();
1835 
1836             const bool afterVisibility = m_newRoot->projectionLeaf()->visible();
1837             const bool prevRootAfterVisibility = m_prevRoot ? m_prevRoot->projectionLeaf()->visible() : false;
1838 
1839             m_newRootNeedsFullRefresh = beforeVisibility != afterVisibility;
1840             m_prevRootNeedsFullRefresh = prevRootBeforeVisibility != prevRootAfterVisibility;
1841         }
1842 
1843         void finishStrokeCallback() override {
1844             // the GUI uses our thread to do the color space conversion so we
1845             // need to emit this signal in multiple threads
1846 
1847             if (m_prevRoot && m_prevRootNeedsFullRefresh) {
1848                 m_image->refreshGraphAsync(m_prevRoot);
1849             }
1850 
1851             if (m_newRootNeedsFullRefresh) {
1852                 m_image->refreshGraphAsync(m_newRoot);
1853             }
1854 
1855             if (!m_prevRootNeedsFullRefresh && !m_newRootNeedsFullRefresh) {
1856                 QVector<KisRunnableStrokeJobData*> jobs;
1857                 m_image->m_d->notifyProjectionUpdatedInPatches(m_image->bounds(), jobs);
1858                 this->runnableJobsInterface()->addRunnableJobs(jobs);
1859             }
1860 
1861             m_image->invalidateAllFrames();
1862         }
1863 
1864     private:
1865         KisNodeSP m_newRoot;
1866         KisNodeSP m_prevRoot;
1867         KisImageSP m_image;
1868         bool m_newRootNeedsFullRefresh = false;
1869         bool m_prevRootNeedsFullRefresh = false;
1870     };
1871 
1872     KisStrokeId id = startStroke(new StartIsolatedModeStroke(node, this));
1873     endStroke(id);
1874 
1875     return true;
1876 }
1877 
stopIsolatedMode()1878 void KisImage::stopIsolatedMode()
1879 {
1880     if (!m_d->isolatedRootNode)  return;
1881 
1882     struct StopIsolatedModeStroke : public KisRunnableBasedStrokeStrategy {
1883         StopIsolatedModeStroke(KisImageSP image)
1884             : KisRunnableBasedStrokeStrategy(QLatin1String("stop-isolated-mode"), kundo2_noi18n("stop-isolated-mode")),
1885               m_image(image),
1886               m_oldRootNode(nullptr),
1887               m_oldNodeNeedsRefresh(false)
1888         {
1889             this->enableJob(JOB_INIT);
1890             this->enableJob(JOB_DOSTROKE, true);
1891             this->enableJob(JOB_FINISH, true, KisStrokeJobData::BARRIER);
1892             setClearsRedoOnStart(false);
1893         }
1894 
1895         void initStrokeCallback() {
1896             if (!m_image->m_d->isolatedRootNode)  return;
1897 
1898             m_oldRootNode = m_image->m_d->isolatedRootNode;
1899             const bool beforeVisibility = m_oldRootNode->projectionLeaf()->visible();
1900             m_image->m_d->isolatedRootNode = 0;
1901             emit m_image->sigIsolatedModeChanged();
1902             const bool afterVisibility = m_oldRootNode->projectionLeaf()->visible();
1903 
1904             m_oldNodeNeedsRefresh = (beforeVisibility != afterVisibility);
1905         }
1906 
1907         void finishStrokeCallback() override {
1908 
1909             m_image->invalidateAllFrames();
1910 
1911             if (m_oldNodeNeedsRefresh){
1912                 m_oldRootNode->setDirty(m_image->bounds());
1913             } else {
1914                 // TODO: Substitute notifyProjectionUpdated() with this code
1915                 // when update optimization is implemented
1916                 //
1917                 // QRect updateRect = bounds() | oldRootNode->extent();
1918                 //oldRootNode->setDirty(updateRect);
1919 
1920                 QVector<KisRunnableStrokeJobData*> jobs;
1921                 m_image->m_d->notifyProjectionUpdatedInPatches(m_image->bounds(), jobs);
1922                 this->runnableJobsInterface()->addRunnableJobs(jobs);
1923             }
1924         }
1925 
1926     private:
1927         KisImageSP m_image;
1928         KisNodeSP m_oldRootNode;
1929         bool m_oldNodeNeedsRefresh;
1930     };
1931 
1932     KisStrokeId id = startStroke(new StopIsolatedModeStroke(this));
1933     endStroke(id);
1934 }
1935 
isolatedModeRoot() const1936 KisNodeSP KisImage::isolatedModeRoot() const
1937 {
1938     return m_d->isolatedRootNode;
1939 }
1940 
addJob(KisStrokeId id,KisStrokeJobData * data)1941 void KisImage::addJob(KisStrokeId id, KisStrokeJobData *data)
1942 {
1943     KisUpdateTimeMonitor::instance()->reportJobStarted(data);
1944     m_d->scheduler.addJob(id, data);
1945 }
1946 
endStroke(KisStrokeId id)1947 void KisImage::endStroke(KisStrokeId id)
1948 {
1949     m_d->scheduler.endStroke(id);
1950 }
1951 
cancelStroke(KisStrokeId id)1952 bool KisImage::cancelStroke(KisStrokeId id)
1953 {
1954     return m_d->scheduler.cancelStroke(id);
1955 }
1956 
tryCancelCurrentStrokeAsync()1957 bool KisImage::KisImagePrivate::tryCancelCurrentStrokeAsync()
1958 {
1959     return scheduler.tryCancelCurrentStrokeAsync();
1960 }
1961 
requestUndoDuringStroke()1962 void KisImage::requestUndoDuringStroke()
1963 {
1964     emit sigUndoDuringStrokeRequested();
1965 }
1966 
requestRedoDuringStroke()1967 void KisImage::requestRedoDuringStroke()
1968 {
1969     emit sigRedoDuringStrokeRequested();
1970 }
1971 
requestStrokeCancellation()1972 void KisImage::requestStrokeCancellation()
1973 {
1974     if (!m_d->tryCancelCurrentStrokeAsync()) {
1975         emit sigStrokeCancellationRequested();
1976     }
1977 }
1978 
tryUndoUnfinishedLod0Stroke()1979 UndoResult KisImage::tryUndoUnfinishedLod0Stroke()
1980 {
1981     return m_d->scheduler.tryUndoLastStrokeAsync();
1982 }
1983 
requestStrokeEnd()1984 void KisImage::requestStrokeEnd()
1985 {
1986     emit sigStrokeEndRequested();
1987     emit sigStrokeEndRequestedActiveNodeFiltered();
1988 }
1989 
requestStrokeEndActiveNode()1990 void KisImage::requestStrokeEndActiveNode()
1991 {
1992     emit sigStrokeEndRequested();
1993 }
1994 
refreshGraph(KisNodeSP root)1995 void KisImage::refreshGraph(KisNodeSP root)
1996 {
1997     refreshGraph(root, bounds(), bounds());
1998 }
1999 
refreshGraph(KisNodeSP root,const QRect & rc,const QRect & cropRect)2000 void KisImage::refreshGraph(KisNodeSP root, const QRect &rc, const QRect &cropRect)
2001 {
2002     if (!root) root = m_d->rootLayer;
2003 
2004     m_d->animationInterface->notifyNodeChanged(root.data(), rc, true);
2005     m_d->scheduler.fullRefresh(root, rc, cropRect);
2006 }
2007 
initialRefreshGraph()2008 void KisImage::initialRefreshGraph()
2009 {
2010     /**
2011      * NOTE: Tricky part. We set crop rect to null, so the clones
2012      * will not rely on precalculated projections of their sources
2013      */
2014 
2015     refreshGraphAsync(0, bounds(), QRect());
2016     waitForDone();
2017 }
2018 
refreshGraphAsync(KisNodeSP root)2019 void KisImage::refreshGraphAsync(KisNodeSP root)
2020 {
2021     refreshGraphAsync(root, bounds(), bounds());
2022 }
2023 
refreshGraphAsync(KisNodeSP root,const QRect & rc)2024 void KisImage::refreshGraphAsync(KisNodeSP root, const QRect &rc)
2025 {
2026     refreshGraphAsync(root, rc, bounds());
2027 }
2028 
refreshGraphAsync(KisNodeSP root,const QRect & rc,const QRect & cropRect)2029 void KisImage::refreshGraphAsync(KisNodeSP root, const QRect &rc, const QRect &cropRect)
2030 {
2031     refreshGraphAsync(root, QVector<QRect>({rc}), cropRect);
2032 }
2033 
refreshGraphAsync(KisNodeSP root,const QVector<QRect> & rects,const QRect & cropRect)2034 void KisImage::refreshGraphAsync(KisNodeSP root, const QVector<QRect> &rects, const QRect &cropRect)
2035 {
2036     if (!root) root = m_d->rootLayer;
2037 
2038     /**
2039      * We iterate through the filters in a reversed way. It makes the most nested filters
2040      * to execute first.
2041      */
2042     for (auto it = m_d->projectionUpdatesFilters.rbegin();
2043          it != m_d->projectionUpdatesFilters.rend();
2044          ++it) {
2045 
2046         KIS_SAFE_ASSERT_RECOVER(*it) { continue; }
2047 
2048         if ((*it)->filterRefreshGraph(this, root.data(), rects, cropRect)) {
2049             return;
2050         }
2051     }
2052 
2053 
2054     m_d->animationInterface->notifyNodeChanged(root.data(), rects, true);
2055     m_d->scheduler.fullRefreshAsync(root, rects, cropRect);
2056 }
2057 
2058 
requestProjectionUpdateNoFilthy(KisNodeSP pseudoFilthy,const QRect & rc,const QRect & cropRect)2059 void KisImage::requestProjectionUpdateNoFilthy(KisNodeSP pseudoFilthy, const QRect &rc, const QRect &cropRect)
2060 {
2061     requestProjectionUpdateNoFilthy(pseudoFilthy, rc, cropRect, true);
2062 }
2063 
requestProjectionUpdateNoFilthy(KisNodeSP pseudoFilthy,const QRect & rc,const QRect & cropRect,const bool resetAnimationCache)2064 void KisImage::requestProjectionUpdateNoFilthy(KisNodeSP pseudoFilthy, const QRect &rc, const QRect &cropRect, const bool resetAnimationCache)
2065 {
2066     KIS_ASSERT_RECOVER_RETURN(pseudoFilthy);
2067 
2068     if (resetAnimationCache) {
2069         m_d->animationInterface->notifyNodeChanged(pseudoFilthy.data(), rc, false);
2070     }
2071     m_d->scheduler.updateProjectionNoFilthy(pseudoFilthy, rc, cropRect);
2072 }
2073 
addSpontaneousJob(KisSpontaneousJob * spontaneousJob)2074 void KisImage::addSpontaneousJob(KisSpontaneousJob *spontaneousJob)
2075 {
2076     m_d->scheduler.addSpontaneousJob(spontaneousJob);
2077 }
2078 
hasUpdatesRunning() const2079 bool KisImage::hasUpdatesRunning() const
2080 {
2081     return m_d->scheduler.hasUpdatesRunning();
2082 }
2083 
addProjectionUpdatesFilter(KisProjectionUpdatesFilterSP filter)2084 KisProjectionUpdatesFilterCookie KisImage::addProjectionUpdatesFilter(KisProjectionUpdatesFilterSP filter)
2085 {
2086     KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(filter, KisProjectionUpdatesFilterCookie());
2087 
2088     m_d->projectionUpdatesFilters.append(filter);
2089 
2090     return KisProjectionUpdatesFilterCookie(filter.data());
2091 }
2092 
removeProjectionUpdatesFilter(KisProjectionUpdatesFilterCookie cookie)2093 KisProjectionUpdatesFilterSP KisImage::removeProjectionUpdatesFilter(KisProjectionUpdatesFilterCookie cookie)
2094 {
2095     KIS_SAFE_ASSERT_RECOVER_NOOP(cookie);
2096     KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->projectionUpdatesFilters.last() == cookie);
2097 
2098     auto it = std::find(m_d->projectionUpdatesFilters.begin(), m_d->projectionUpdatesFilters.end(), cookie);
2099     KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(it != m_d->projectionUpdatesFilters.end(), KisProjectionUpdatesFilterSP());
2100 
2101     KisProjectionUpdatesFilterSP filter = *it;
2102 
2103     m_d->projectionUpdatesFilters.erase(it);
2104 
2105     return filter;
2106 }
2107 
currentProjectionUpdatesFilter() const2108 KisProjectionUpdatesFilterCookie KisImage::currentProjectionUpdatesFilter() const
2109 {
2110     return !m_d->projectionUpdatesFilters.isEmpty() ?
2111                 m_d->projectionUpdatesFilters.last().data() :
2112                 KisProjectionUpdatesFilterCookie();
2113 }
2114 
disableDirtyRequests()2115 void KisImage::disableDirtyRequests()
2116 {
2117     m_d->disabledUpdatesCookies.push(
2118         addProjectionUpdatesFilter(toQShared(new KisDropAllProjectionUpdatesFilter())));
2119 }
2120 
enableDirtyRequests()2121 void KisImage::enableDirtyRequests()
2122 {
2123     KIS_SAFE_ASSERT_RECOVER_RETURN(!m_d->disabledUpdatesCookies.isEmpty());
2124     removeProjectionUpdatesFilter(m_d->disabledUpdatesCookies.pop());
2125 }
2126 
disableUIUpdates()2127 void KisImage::disableUIUpdates()
2128 {
2129     m_d->disableUIUpdateSignals.ref();
2130 }
2131 
notifyBatchUpdateStarted()2132 void KisImage::notifyBatchUpdateStarted()
2133 {
2134     m_d->signalRouter.emitNotifyBatchUpdateStarted();
2135 }
2136 
notifyBatchUpdateEnded()2137 void KisImage::notifyBatchUpdateEnded()
2138 {
2139     m_d->signalRouter.emitNotifyBatchUpdateEnded();
2140 }
2141 
notifyUIUpdateCompleted(const QRect & rc)2142 void KisImage::notifyUIUpdateCompleted(const QRect &rc)
2143 {
2144     notifyProjectionUpdated(rc);
2145 }
2146 
enableUIUpdates()2147 QVector<QRect> KisImage::enableUIUpdates()
2148 {
2149     m_d->disableUIUpdateSignals.deref();
2150 
2151     QRect rect;
2152     QVector<QRect> postponedUpdates;
2153 
2154     while (m_d->savedDisabledUIUpdates.pop(rect)) {
2155         postponedUpdates.append(rect);
2156     }
2157 
2158     return postponedUpdates;
2159 }
2160 
notifyProjectionUpdated(const QRect & rc)2161 void KisImage::notifyProjectionUpdated(const QRect &rc)
2162 {
2163     KisUpdateTimeMonitor::instance()->reportUpdateFinished(rc);
2164 
2165     if (!m_d->disableUIUpdateSignals) {
2166         int lod = currentLevelOfDetail();
2167         QRect dirtyRect = !lod ? rc : KisLodTransform::upscaledRect(rc, lod);
2168 
2169         if (dirtyRect.isEmpty()) return;
2170 
2171         emit sigImageUpdated(dirtyRect);
2172     } else {
2173         m_d->savedDisabledUIUpdates.push(rc);
2174     }
2175 }
2176 
setWorkingThreadsLimit(int value)2177 void KisImage::setWorkingThreadsLimit(int value)
2178 {
2179     m_d->scheduler.setThreadsLimit(value);
2180 }
2181 
workingThreadsLimit() const2182 int KisImage::workingThreadsLimit() const
2183 {
2184     return m_d->scheduler.threadsLimit();
2185 }
2186 
notifySelectionChanged()2187 void KisImage::notifySelectionChanged()
2188 {
2189     /**
2190      * The selection is calculated asynchronously, so it is not
2191      * handled by disableUIUpdates() and other special signals of
2192      * KisImageSignalRouter
2193      */
2194     m_d->legacyUndoAdapter.emitSelectionChanged();
2195 
2196     /**
2197      * Editing of selection masks doesn't necessary produce a
2198      * setDirty() call, so in the end of the stroke we need to request
2199      * direct update of the UI's cache.
2200      */
2201     if (m_d->isolatedRootNode &&
2202         dynamic_cast<KisSelectionMask*>(m_d->isolatedRootNode.data())) {
2203 
2204         notifyProjectionUpdated(bounds());
2205     }
2206 }
2207 
requestProjectionUpdateImpl(KisNode * node,const QVector<QRect> & rects,const QRect & cropRect)2208 void KisImage::requestProjectionUpdateImpl(KisNode *node,
2209                                            const QVector<QRect> &rects,
2210                                            const QRect &cropRect)
2211 {
2212     if (rects.isEmpty()) return;
2213 
2214     m_d->scheduler.updateProjection(node, rects, cropRect);
2215 }
2216 
requestProjectionUpdate(KisNode * node,const QVector<QRect> & rects,bool resetAnimationCache)2217 void KisImage::requestProjectionUpdate(KisNode *node, const QVector<QRect> &rects, bool resetAnimationCache)
2218 {
2219     /**
2220      * We iterate through the filters in a reversed way. It makes the most nested filters
2221      * to execute first.
2222      */
2223     for (auto it = m_d->projectionUpdatesFilters.rbegin();
2224          it != m_d->projectionUpdatesFilters.rend();
2225          ++it) {
2226 
2227         KIS_SAFE_ASSERT_RECOVER(*it) { continue; }
2228 
2229         if ((*it)->filter(this, node, rects, resetAnimationCache)) {
2230             return;
2231         }
2232     }
2233 
2234     if (resetAnimationCache) {
2235         m_d->animationInterface->notifyNodeChanged(node, rects, false);
2236     }
2237 
2238     /**
2239      * Here we use 'permitted' instead of 'active' intentively,
2240      * because the updates may come after the actual stroke has been
2241      * finished. And having some more updates for the stroke not
2242      * supporting the wrap-around mode will not make much harm.
2243      */
2244     if (m_d->wrapAroundModePermitted) {
2245         QVector<QRect> allSplitRects;
2246 
2247         const QRect boundRect = effectiveLodBounds();
2248         Q_FOREACH (const QRect &rc, rects) {
2249             KisWrappedRect splitRect(rc, boundRect);
2250             allSplitRects.append(splitRect);
2251         }
2252 
2253         requestProjectionUpdateImpl(node, allSplitRects, boundRect);
2254 
2255     } else {
2256         requestProjectionUpdateImpl(node, rects, bounds());
2257     }
2258 
2259     KisNodeGraphListener::requestProjectionUpdate(node, rects, resetAnimationCache);
2260 }
2261 
invalidateFrames(const KisTimeRange & range,const QRect & rect)2262 void KisImage::invalidateFrames(const KisTimeRange &range, const QRect &rect)
2263 {
2264     m_d->animationInterface->invalidateFrames(range, rect);
2265 }
2266 
requestTimeSwitch(int time)2267 void KisImage::requestTimeSwitch(int time)
2268 {
2269     m_d->animationInterface->requestTimeSwitchNonGUI(time);
2270 }
2271 
graphOverlayNode() const2272 KisNode *KisImage::graphOverlayNode() const
2273 {
2274     return m_d->overlaySelectionMask.data();
2275 }
2276 
compositions()2277 QList<KisLayerCompositionSP> KisImage::compositions()
2278 {
2279     return m_d->compositions;
2280 }
2281 
addComposition(KisLayerCompositionSP composition)2282 void KisImage::addComposition(KisLayerCompositionSP composition)
2283 {
2284     m_d->compositions.append(composition);
2285 }
2286 
removeComposition(KisLayerCompositionSP composition)2287 void KisImage::removeComposition(KisLayerCompositionSP composition)
2288 {
2289     m_d->compositions.removeAll(composition);
2290 }
2291 
moveCompositionUp(KisLayerCompositionSP composition)2292 void KisImage::moveCompositionUp(KisLayerCompositionSP composition)
2293 {
2294     int index = m_d->compositions.indexOf(composition);
2295     if (index <= 0) {
2296         return;
2297     }
2298     m_d->compositions.move(index, index - 1);
2299 }
2300 
moveCompositionDown(KisLayerCompositionSP composition)2301 void KisImage::moveCompositionDown(KisLayerCompositionSP composition)
2302 {
2303     int index = m_d->compositions.indexOf(composition);
2304     if (index >= m_d->compositions.size() -1) {
2305         return;
2306     }
2307     m_d->compositions.move(index, index + 1);
2308 }
2309 
checkMasksNeedConversion(KisNodeSP root,const QRect & bounds)2310 bool checkMasksNeedConversion(KisNodeSP root, const QRect &bounds)
2311 {
2312     KisSelectionMask *mask = dynamic_cast<KisSelectionMask*>(root.data());
2313     if (mask &&
2314         (!bounds.contains(mask->paintDevice()->exactBounds()) ||
2315          mask->selection()->hasShapeSelection())) {
2316 
2317         return true;
2318     }
2319 
2320     KisNodeSP node = root->firstChild();
2321 
2322     while (node) {
2323         if (checkMasksNeedConversion(node, bounds)) {
2324             return true;
2325         }
2326 
2327         node = node->nextSibling();
2328     }
2329 
2330     return false;
2331 }
2332 
setWrapAroundModePermitted(bool value)2333 void KisImage::setWrapAroundModePermitted(bool value)
2334 {
2335     if (m_d->wrapAroundModePermitted != value) {
2336         requestStrokeEnd();
2337     }
2338 
2339     m_d->wrapAroundModePermitted = value;
2340 
2341     if (m_d->wrapAroundModePermitted &&
2342         checkMasksNeedConversion(root(), bounds())) {
2343 
2344         KisProcessingApplicator applicator(this, root(),
2345                                            KisProcessingApplicator::RECURSIVE,
2346                                            KisImageSignalVector() << ModifiedSignal,
2347                                            kundo2_i18n("Crop Selections"));
2348 
2349         KisProcessingVisitorSP visitor =
2350             new KisCropSelectionsProcessingVisitor(bounds());
2351 
2352         applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT);
2353         applicator.end();
2354     }
2355 }
2356 
wrapAroundModePermitted() const2357 bool KisImage::wrapAroundModePermitted() const
2358 {
2359     return m_d->wrapAroundModePermitted;
2360 }
2361 
wrapAroundModeActive() const2362 bool KisImage::wrapAroundModeActive() const
2363 {
2364     return m_d->wrapAroundModePermitted &&
2365         m_d->scheduler.wrapAroundModeSupported();
2366 }
2367 
setDesiredLevelOfDetail(int lod)2368 void KisImage::setDesiredLevelOfDetail(int lod)
2369 {
2370     if (m_d->blockLevelOfDetail) {
2371         qWarning() << "WARNING: KisImage::setDesiredLevelOfDetail()"
2372                    << "was called while LoD functionality was being blocked!";
2373         return;
2374     }
2375 
2376     m_d->scheduler.setDesiredLevelOfDetail(lod);
2377 }
2378 
currentLevelOfDetail() const2379 int KisImage::currentLevelOfDetail() const
2380 {
2381     if (m_d->blockLevelOfDetail) {
2382         return 0;
2383     }
2384 
2385     return m_d->scheduler.currentLevelOfDetail();
2386 }
2387 
setLevelOfDetailBlocked(bool value)2388 void KisImage::setLevelOfDetailBlocked(bool value)
2389 {
2390     KisImageBarrierLockerRaw l(this);
2391 
2392     if (value && !m_d->blockLevelOfDetail) {
2393         m_d->scheduler.setDesiredLevelOfDetail(0);
2394     }
2395 
2396     m_d->blockLevelOfDetail = value;
2397 }
2398 
explicitRegenerateLevelOfDetail()2399 void KisImage::explicitRegenerateLevelOfDetail()
2400 {
2401     if (!m_d->blockLevelOfDetail) {
2402         m_d->scheduler.explicitRegenerateLevelOfDetail();
2403     }
2404 }
2405 
levelOfDetailBlocked() const2406 bool KisImage::levelOfDetailBlocked() const
2407 {
2408     return m_d->blockLevelOfDetail;
2409 }
2410 
nodeCollapsedChanged(KisNode * node)2411 void KisImage::nodeCollapsedChanged(KisNode * node)
2412 {
2413     Q_UNUSED(node);
2414     emit sigNodeCollapsedChanged();
2415 }
2416 
animationInterface() const2417 KisImageAnimationInterface* KisImage::animationInterface() const
2418 {
2419     return m_d->animationInterface;
2420 }
2421 
setProofingConfiguration(KisProofingConfigurationSP proofingConfig)2422 void KisImage::setProofingConfiguration(KisProofingConfigurationSP proofingConfig)
2423 {
2424     m_d->proofingConfig = proofingConfig;
2425     emit sigProofingConfigChanged();
2426 }
2427 
proofingConfiguration() const2428 KisProofingConfigurationSP KisImage::proofingConfiguration() const
2429 {
2430     if (m_d->proofingConfig) {
2431         return m_d->proofingConfig;
2432     }
2433     return KisProofingConfigurationSP();
2434 }
2435 
mirrorAxesCenter() const2436 QPointF KisImage::mirrorAxesCenter() const
2437 {
2438     return m_d->axesCenter;
2439 }
2440 
setMirrorAxesCenter(const QPointF & value) const2441 void KisImage::setMirrorAxesCenter(const QPointF &value) const
2442 {
2443     m_d->axesCenter = value;
2444 }
2445 
setAllowMasksOnRootNode(bool value)2446 void KisImage::setAllowMasksOnRootNode(bool value)
2447 {
2448     m_d->allowMasksOnRootNode = value;
2449 }
2450 
allowMasksOnRootNode() const2451 bool KisImage::allowMasksOnRootNode() const
2452 {
2453     return m_d->allowMasksOnRootNode;
2454 }
2455