Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_image.cc
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2002 Patrick Julien <freak@codepimps.org>
3 * SPDX-FileCopyrightText: 2007 Boudewijn Rempt <boud@valdyas.org>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
8#include "kis_image.h"
9
10#include <KoConfig.h> // WORDS_BIGENDIAN
11
12#include <stdlib.h>
13#include <math.h>
14
15#include <QImage>
16#include <QPainter>
17#include <QSize>
18#include <QDateTime>
19#include <QRect>
20
21#include <klocalizedstring.h>
22
24#include "KoColor.h"
25#include "KoColorProfile.h"
28
30#include "kis_annotation.h"
31#include "kis_count_visitor.h"
32#include "kis_filter_strategy.h"
33#include "kis_group_layer.h"
35#include "kis_layer.h"
37#include "kis_paint_layer.h"
38#include "kis_projection_leaf.h"
39#include "kis_painter.h"
40#include "kis_selection.h"
41#include "kis_transaction.h"
44#include "kis_node.h"
45#include "kis_types.h"
46
47#include "kis_image_config.h"
52#include "kis_stroke_strategy.h"
54
55#include "kis_undo_stores.h"
58
71#include "kis_wrapped_rect.h"
73#include "kis_layer_utils.h"
75
76#include "kis_lod_transform.h"
77
80
82
84
86#include "kis_lockless_stack.h"
87
88#include <QtCore>
89
90#include <functional>
91#include <memory>
92
93#include "kis_time_span.h"
94
99
100#include "KisBusyWaitBroker.h"
101#include <KisStaticInitializer.h>
103#include "kis_hdr_metadata.h"
104
105
106// #define SANITY_CHECKS
107
108#ifdef SANITY_CHECKS
109#define SANITY_CHECK_LOCKED(name) \
110 if (!locked()) warnKrita() << "Locking policy failed:" << name \
111 << "has been called without the image" \
112 "being locked";
113#else
114#define SANITY_CHECK_LOCKED(name)
115#endif
116
118 qRegisterMetaType<KisImageSP>("KisImageSP");
119}
120
122{
123public:
124 KisImagePrivate(KisImage *_q, qint32 w, qint32 h,
125 const KoColorSpace *c,
126 KisUndoStore *undo,
127 KisImageAnimationInterface *_animationInterface)
128 : q(_q)
129 , lockedForReadOnly(false)
130 , width(w)
131 , height(h)
132 , colorSpace(c ? c : KoColorSpaceRegistry::instance()->rgb8())
134 , isolateLayer(false)
135 , isolateGroup(false)
136 , undoStore(undo ? undo : new KisDumbUndoStore())
137 , legacyUndoAdapter(undoStore.data(), _q)
139 , signalRouter(_q)
140 , animationInterface(_animationInterface)
141 , scheduler(_q, _q)
142 , axesCenter(QPointF(0.5, 0.5))
143 {
144 {
145 KisImageConfig cfg(true);
146 if (cfg.enableProgressReporting()) {
148 }
149
150 // Each of these lambdas defines a new factory function.
152 [=](bool forgettable) {
153 return KisLodSyncPair(
156 });
157
159 [=]() {
161
166
167 return std::make_pair(suspend, resume);
168 });
169
171 [this] () {
172 undoStore->purgeRedoState();
173 });
174
176 [this] () {
177
179
180 bool addedUIUpdateRequestSuccessfully = false;
181
182 for (auto it = std::make_reverse_iterator(projectionUpdatesFilters.end());
183 it != std::make_reverse_iterator(projectionUpdatesFilters.begin());
184 ++it) {
185
188
189 if (iface) {
191 addedUIUpdateRequestSuccessfully = true;
192 break;
193 }
194 }
195
196 KIS_SAFE_ASSERT_RECOVER_NOOP(addedUIUpdateRequestSuccessfully);
197 });
198 }
199
201
202 connect(q, SIGNAL(sigImageModified()), KisMemoryStatisticsServer::instance(), SLOT(notifyImageChanged()));
203 connect(undoStore.data(), SIGNAL(historyStateChanged()), &signalRouter, SLOT(emitImageModifiedNotification()));
204 }
205
214
221 if (rootLayer->image() == q) {
223 }
224
225 if (rootLayer->graphListener() == q) {
227 }
228
230
234 delete animationInterface;
235 }
236
238
239 quint32 lockCount = 0;
241
242 qint32 width;
243 qint32 height;
244
245 double xres = 1.0;
246 double yres = 1.0;
247
250
253 KisGroupLayerSP rootLayer; // The layers are contained in here
254 KisSelectionMaskSP targetOverlaySelectionMask; // the overlay switching stroke will try to switch into this mask
257
261
264
265 QScopedPointer<KisUndoStore> undoStore;
268
270
273
274 // filters are applied in a reversed way, from rbegin() to rend()
281
283
284 std::optional<KisRelativeContentLightLevelInformation> relativeContentLightLevelInformation;
285 std::optional<KisColorVolumeInformation> colorVolumeInformation;
286 std::optional<double> diffuseWhiteLightLevel;
287
288 QPointF axesCenter;
290
292 const QVector<QRect> &rects,
293 const QRect &cropRect,
294 KisProjectionUpdateFlags flags);
295
297
299
300 void convertImageColorSpaceImpl(const KoColorSpace *dstColorSpace,
301 bool convertLayers,
303 KoColorConversionTransformation::ConversionFlags conversionFlags);
304
306 const KoColorProfile *profile = newColorSpace->profile();
307 if (!profile) return;
308
309 if (profile->hdrReferenceWhite()) {
313 } else if (!newColorSpace->hasHighDynamicRange()) {
315 colorVolumeInformation = std::nullopt;
316 diffuseWhiteLightLevel = std::nullopt;
317 }
318 }
319
320 struct SetImageProjectionColorSpace;
321};
322
327
332
337
338KisImage::KisImage(KisUndoStore *undoStore, qint32 width, qint32 height, const KoColorSpace *colorSpace, const QString& name)
339 : QObject(0)
340 , KisShared()
341 , m_d(new KisImagePrivate(this, width, height,
342 colorSpace, undoStore,
344{
345 // make sure KisImage belongs to the GUI thread
346 moveToThread(qApp->thread());
347 connect(this, SIGNAL(sigInternalStopIsolatedModeRequested()), SLOT(stopIsolatedMode()));
348
349 setObjectName(name);
350 setRootLayer(new KisGroupLayer(this, "root", OPACITY_OPAQUE_U8));
351}
352
354{
358 waitForDone();
359
360 delete m_d;
361 disconnect(); // in case Qt gets confused
362}
363
364KisImageSP KisImage::fromQImage(const QImage &image, KisUndoStore *undoStore)
365{
366 const KoColorSpace *colorSpace = 0;
367
368 switch (image.format()) {
369 case QImage::Format_Invalid:
370 case QImage::Format_Mono:
371 case QImage::Format_MonoLSB:
373 break;
374 case QImage::Format_Indexed8:
375 case QImage::Format_RGB32:
376 case QImage::Format_ARGB32:
377 case QImage::Format_ARGB32_Premultiplied:
379 break;
380 case QImage::Format_RGB16:
382 break;
383 case QImage::Format_ARGB8565_Premultiplied:
384 case QImage::Format_RGB666:
385 case QImage::Format_ARGB6666_Premultiplied:
386 case QImage::Format_RGB555:
387 case QImage::Format_ARGB8555_Premultiplied:
388 case QImage::Format_RGB888:
389 case QImage::Format_RGB444:
390 case QImage::Format_ARGB4444_Premultiplied:
391 case QImage::Format_RGBX8888:
392 case QImage::Format_RGBA8888:
393 case QImage::Format_RGBA8888_Premultiplied:
395 break;
396 case QImage::Format_BGR30:
397 case QImage::Format_A2BGR30_Premultiplied:
398 case QImage::Format_RGB30:
399 case QImage::Format_A2RGB30_Premultiplied:
401 break;
402 case QImage::Format_Alpha8:
404 break;
405 case QImage::Format_Grayscale8:
407 break;
408 case QImage::Format_Grayscale16:
410 break;
411 case QImage::Format_RGBX64:
412 case QImage::Format_RGBA64:
413 case QImage::Format_RGBA64_Premultiplied:
415 break;
416 default:
417 colorSpace = 0;
418 }
419
420 KisImageSP img = new KisImage(undoStore, image.width(), image.height(), colorSpace, i18n("Imported Image"));
421 KisPaintLayerSP layer = new KisPaintLayer(img, img->nextLayerName(), 255);
422 layer->paintDevice()->convertFromQImage(image, 0, 0, 0);
423 img->addNode(layer.data(), img->rootLayer().data());
424
425 return img;
426}
427
428KisImage *KisImage::clone(bool exactCopy)
429{
430 return new KisImage(*this, 0, exactCopy);
431}
432
434{
436}
437
438void KisImage::copyFromImageImpl(const KisImage &rhs, int policy)
439{
440 // make sure we choose exactly one from REPLACE and CONSTRUCT
441 KIS_ASSERT_RECOVER_RETURN(bool(policy & REPLACE) != bool(policy & CONSTRUCT));
442
455 const bool sizeChanged = m_d->width != rhs.width() || m_d->height != rhs.height();
456 const bool colorSpaceChanged = *m_d->colorSpace != *rhs.colorSpace();
457 const bool resolutionChanged = m_d->xres != rhs.m_d->xres || m_d->yres != rhs.m_d->yres;
458
459 if (sizeChanged) {
460 m_d->width = rhs.width();
461 m_d->height = rhs.height();
462 }
463
464 if (colorSpaceChanged) {
465 m_d->colorSpace = rhs.colorSpace();
466 }
467
468 if (resolutionChanged) {
469 m_d->xres = rhs.m_d->xres;
470 m_d->yres = rhs.m_d->yres;
471 }
472
473 // from KisImage::KisImage(const KisImage &, KisUndoStore *, bool)
474 setObjectName(rhs.objectName());
475
476 KisNodeSP oldRoot = this->root();
477 KisNodeSP newRoot = rhs.root()->clone();
478 newRoot->setGraphListener(this);
479 newRoot->setImage(this);
480
481 m_d->rootLayer = dynamic_cast<KisGroupLayer*>(newRoot.data());
482 setRoot(newRoot);
483
484 if (oldRoot) {
485 oldRoot->setImage(0);
486 oldRoot->setGraphListener(0);
487 oldRoot->disconnect();
488 }
489
490 // only when replacing do we need to Q_EMIT signals
491#define EMIT_IF_NEEDED if (!(policy & REPLACE)) {} else emit
492
493 if (sizeChanged) {
494 EMIT_IF_NEEDED sigSizeChanged(QPointF(), QPointF());
495 }
496 if (colorSpaceChanged) {
498 }
499 if (resolutionChanged) {
501 }
502
504
505 if (rhs.m_d->proofingConfig) {
507 if (policy & REPLACE) {
508 setProofingConfiguration(proofingConfig);
509 } else {
510 m_d->proofingConfig = proofingConfig;
511 }
512 }
513
517
518 bool exactCopy = policy & EXACT_COPY;
519
520 if (exactCopy || rhs.m_d->isolationRootNode || rhs.m_d->overlaySelectionMask) {
523
524 QQueue<KisNodeSP> linearizedNodes;
526 [&linearizedNodes](KisNodeSP node) {
527 linearizedNodes.enqueue(node);
528 });
530 [&linearizedNodes, exactCopy, &rhs, this](KisNodeSP node) {
531 KisNodeSP refNode = linearizedNodes.dequeue();
532
533 if (exactCopy) {
534 node->setUuid(refNode->uuid());
535 }
536
537 if (rhs.m_d->isolationRootNode &&
538 rhs.m_d->isolationRootNode == refNode) {
539 m_d->isolationRootNode = node;
540 }
541
542 if (rhs.m_d->overlaySelectionMask &&
543 KisNodeSP(rhs.m_d->overlaySelectionMask) == refNode) {
544 m_d->targetOverlaySelectionMask = dynamic_cast<KisSelectionMask*>(node.data());
547 }
548
549
550 // Re-establish DefaultBounds Instances for Existing Nodes
551 // This is a workaround for copy-constructors failing to pass
552 // proper DefaultBounds due to either lacking image data on construction
553 // We should change the way "DefaultBounds" works to try to make it
554 // safer for threading races.
555 using KeyframeChannelContainer = QMap<QString, KisKeyframeChannel*>;
556 KeyframeChannelContainer keyframeChannels = node->keyframeChannels();
557 for (KeyframeChannelContainer::iterator i = keyframeChannels.begin();
558 i != keyframeChannels.end(); i++) {
559 keyframeChannels[i.key()]->setNode(node);
560 }
561 });
562 }
563
565 [](KisNodeSP node) {
566 dbgImage << "Node: " << (void *)node.data();
567 });
568
569
570
571 m_d->compositions.clear();
572
573 Q_FOREACH (KisLayerCompositionSP comp, rhs.m_d->compositions) {
574 m_d->compositions << toQShared(new KisLayerComposition(*comp, this));
575 }
576
578
579 vKisAnnotationSP newAnnotations;
580 Q_FOREACH (KisAnnotationSP annotation, rhs.m_d->annotations) {
581 newAnnotations << annotation->clone();
582 }
583 m_d->annotations = newAnnotations;
584
588
589#undef EMIT_IF_NEEDED
590}
591
592KisImage::KisImage(const KisImage& rhs, KisUndoStore *undoStore, bool exactCopy)
593 : KisNodeFacade(),
595 KisShared(),
596 m_d(new KisImagePrivate(this,
597 rhs.width(), rhs.height(),
598 rhs.colorSpace(),
599 undoStore ? undoStore : new KisDumbUndoStore(),
600 new KisImageAnimationInterface(*rhs.animationInterface(), this)))
601{
602 // make sure KisImage belongs to the GUI thread
603 moveToThread(qApp->thread());
604 connect(this, SIGNAL(sigInternalStopIsolatedModeRequested()), SLOT(stopIsolatedMode()));
605
606 copyFromImageImpl(rhs, CONSTRUCT | (exactCopy ? EXACT_COPY : 0));
607}
608
609void KisImage::aboutToAddANode(KisNode *parent, int index)
610{
612 SANITY_CHECK_LOCKED("aboutToAddANode");
613}
614
615void KisImage::nodeHasBeenAdded(KisNode *parent, int index, KisNodeAdditionFlags flags)
616{
617 KisNodeGraphListener::nodeHasBeenAdded(parent, index, flags);
618
620 QMap<QString, KisKeyframeChannel*> chans = node->keyframeChannels();
621 Q_FOREACH(KisKeyframeChannel* chan, chans.values()) {
622 chan->setNode(node);
623 this->keyframeChannelHasBeenAdded(node.data(), chan);
624 }
625 });
626
627 SANITY_CHECK_LOCKED("nodeHasBeenAdded");
628 m_d->signalRouter.emitNodeHasBeenAdded(parent, index, flags);
629}
630
631void KisImage::aboutToRemoveANode(KisNode *parent, int index)
632{
633 KisNodeSP deletedNode = parent->at(index);
634 if (!dynamic_cast<KisSelectionMask*>(deletedNode.data()) &&
635 deletedNode == m_d->isolationRootNode) {
636
638 }
639
641 QMap<QString, KisKeyframeChannel*> chans = node->keyframeChannels();
642 Q_FOREACH(KisKeyframeChannel* chan, chans.values()) {
643 this->keyframeChannelAboutToBeRemoved(node.data(), chan);
644 }
645 });
646
648
649 SANITY_CHECK_LOCKED("aboutToRemoveANode");
651}
652
658
663
665{
666 if (m_d->targetOverlaySelectionMask == mask) return;
667
669
670 struct UpdateOverlaySelectionStroke : public KisSimpleStrokeStrategy {
671 UpdateOverlaySelectionStroke(KisImageSP image)
672 : KisSimpleStrokeStrategy(QLatin1String("update-overlay-selection-mask"), kundo2_noi18n("update-overlay-selection-mask")),
673 m_image(image)
674 {
675 this->enableJob(JOB_INIT, true, KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE);
676 setClearsRedoOnStart(false);
677 }
678
679 void initStrokeCallback() override {
680 KisSelectionMaskSP oldMask = m_image->m_d->overlaySelectionMask;
681 KisSelectionMaskSP newMask = m_image->m_d->targetOverlaySelectionMask;
682 if (oldMask == newMask) return;
683
684 KIS_SAFE_ASSERT_RECOVER_RETURN(!newMask || static_cast<KisImage*>(newMask->graphListener()) == m_image);
685
686 m_image->m_d->overlaySelectionMask = newMask;
687
688 if (oldMask || newMask) {
689 m_image->m_d->rootLayer->notifyChildMaskChanged();
690 }
691
692 if (oldMask) {
693 const QRect oldMaskRect = oldMask->graphListener() ? oldMask->extent() : m_image->bounds();
694 m_image->m_d->rootLayer->setDirtyDontResetAnimationCache(oldMaskRect);
695 }
696
697 if (newMask) {
698 newMask->setDirty();
699 }
700
701 m_image->undoAdapter()->emitSelectionChanged();
702 }
703
704 private:
705 KisImageSP m_image;
706 };
707
708 KisStrokeId id = startStroke(new UpdateOverlaySelectionStroke(this));
709 endStroke(id);
710}
711
716
721
723{
724 KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask();
725 if (selectionMask) {
726 return selectionMask->selection();
727 } else {
728 return 0;
729 }
730}
731
736
741
742QString KisImage::nextLayerName(const QString &_baseName) const
743{
744 QString baseName = _baseName;
745
746 int numLayers = 0;
747 int maxLayerIndex = 0;
748 QRegularExpression numberedLayerRegexp(".* (\\d+)$");
750 [&numLayers, &maxLayerIndex, &numberedLayerRegexp] (KisNodeSP node) {
751 if (node->inherits("KisLayer")) {
752 QRegularExpressionMatch match = numberedLayerRegexp.match(node->name());
753
754 if (match.hasMatch()) {
755 maxLayerIndex = qMax(maxLayerIndex, match.captured(1).toInt());
756 }
757 numLayers++;
758 }
759 });
760
761 // special case if there is only root node
762 if (numLayers == 1) {
763 return i18nc("Name for the bottom-most layer in the layerstack", "Background");
764 }
765
766 if (baseName.isEmpty()) {
767 baseName = i18n("Paint Layer");
768 }
769
770 return QString("%1 %2").arg(baseName).arg(maxLayerIndex + 1);
771}
772
777
779{
780 return m_d->lockCount != 0;
781}
782
783void KisImage::barrierLock(bool readOnly)
784{
785 if (!locked()) {
790 m_d->lockedForReadOnly = readOnly;
791 } else {
792 m_d->lockedForReadOnly &= readOnly;
793 }
794
795 m_d->lockCount++;
796}
797
798bool KisImage::tryBarrierLock(bool readOnly)
799{
800 bool result = true;
801
802 if (!locked()) {
803 result = m_d->scheduler.tryBarrierLock();
804 m_d->lockedForReadOnly = readOnly;
805 }
806
807 if (result) {
808 m_d->lockCount++;
809 m_d->lockedForReadOnly &= readOnly;
810 }
811
812 return result;
813}
814
815bool KisImage::isIdle(bool allowLocked)
816{
817 return (allowLocked || !locked()) && m_d->scheduler.isIdle();
818}
819
831
833{
834 Q_ASSERT(locked());
835
836 if (locked()) {
837 m_d->lockCount--;
838
839 if (m_d->lockCount == 0) {
841 }
842 }
843}
844
849
854
855void KisImage::setSize(const QSize& size)
856{
857 m_d->width = size.width();
858 m_d->height = size.height();
859}
860
861void KisImage::resizeImageImpl(const QRect& newRect, bool cropLayers)
862{
863 if (newRect == bounds() && !cropLayers) return;
864
865 KUndo2MagicString actionName = cropLayers ?
866 kundo2_i18n("Crop Image") :
867 kundo2_i18n("Resize Image");
868
869 KisImageSignalVector emitSignals;
870 emitSignals << ComplexSizeChangedSignal(newRect, newRect.size());
871
872 KisCropSavedExtraData *extraData =
873 new KisCropSavedExtraData(cropLayers ?
876 newRect);
877
878 KisProcessingApplicator applicator(this, m_d->rootLayer,
881 emitSignals, actionName, extraData);
882
883 if (cropLayers || !newRect.topLeft().isNull()) {
884 KisProcessingVisitorSP visitor =
885 new KisCropProcessingVisitor(newRect, cropLayers, true);
886 applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT);
887 }
888 applicator.applyCommand(new KisImageResizeCommand(this, newRect.size()));
889 applicator.end();
890}
891
892void KisImage::resizeImage(const QRect& newRect)
893{
894 resizeImageImpl(newRect, false);
895}
896
897void KisImage::cropImage(const QRect& newRect)
898{
899 resizeImageImpl(newRect, true);
900}
901
902void KisImage::purgeUnusedData(bool isCancellable)
903{
910 struct PurgeUnusedDataStroke : public KisRunnableBasedStrokeStrategy {
911 PurgeUnusedDataStroke(KisImageSP image, bool isCancellable)
912 : KisRunnableBasedStrokeStrategy(QLatin1String("purge-unused-data"),
913 kundo2_i18n("Purge Unused Data")),
914 m_image(image),
915 m_finalCommand(new KUndo2Command(this->name()))
916
917 {
918 this->enableJob(JOB_INIT, true, KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE);
919 this->enableJob(JOB_DOSTROKE, true);
920 this->enableJob(JOB_FINISH, true, KisStrokeJobData::SEQUENTIAL);
921
922 setClearsRedoOnStart(!isCancellable);
923 setRequestsOtherStrokesToEnd(!isCancellable);
924 setCanForgetAboutMe(isCancellable);
925 }
926
927 void initStrokeCallback() override
928 {
929 KisPaintDeviceList paintDevicesList;
930 KisPaintDeviceList projectionsList;
932
934 [&paintDevicesList, &projectionsList, this](KisNodeSP node) {
935 KisPaintDeviceList deviceList = node->getLodCapableDevices();
936
937 Q_FOREACH (KisPaintDeviceSP dev, deviceList) {
938 if (!dev) continue;
939
940 // we do **not** strip paint devices in the forgettable
941 // mode, since we should handle transactions for them
942 if (dev == node->paintDevice() && !canForgetAboutMe()) {
943 paintDevicesList << dev;
944 } else {
945 projectionsList << dev;
946 }
947 }
948 });
949
952 KritaUtils::makeContainerUnique(paintDevicesList);
953 KritaUtils::makeContainerUnique(projectionsList);
954
955 Q_FOREACH(KisPaintDeviceSP dev, paintDevicesList) {
956 projectionsList.removeAll(dev);
957
958 // all transactions will be linked to the final command via the
959 // parent-child relationship
960 m_transactions.emplace_back(dev, m_finalCommand.get(), -1, nullptr, KisTransaction::None);
961 }
962
963 // now, when the transactions are started, we can merge the two lists
964 paintDevicesList << projectionsList;
965 projectionsList.clear();
966
967 Q_FOREACH (KisPaintDeviceSP device, paintDevicesList) {
969 [device] () {
970 const_cast<KisPaintDevice*>(device.data())->purgeDefaultPixels();
971 });
972 }
973
974 addMutatedJobs(jobsData);
975 }
976
977 void finishStrokeCallback() override {
978 for (auto it = m_transactions.begin(); it != m_transactions.end(); ++it) {
979 std::unique_ptr<KUndo2Command> cmd(it->endAndTake());
980
981 // verify the transaction command is linked to m_finalCommand,
982 // if not, just delete on return
983 KIS_SAFE_ASSERT_RECOVER(cmd->hasParent()) { continue; }
984
985 // if has a parent, release...
986 (void)cmd.release();
987 }
988
989 m_transactions.clear();
990
991 m_finalCommand->redo();
992 m_image->postExecutionUndoAdapter()->addCommand(toQShared(m_finalCommand.release()));
993
994 // now reset the thumbnail generation limitation
996 [](KisNodeSP node) {
997 if (node->preferredThumbnailBoundsMode() != KisThumbnailBoundsMode::Precise) {
998 node->setPreferredThumbnailBoundsMode(KisThumbnailBoundsMode::Precise);
999 }
1000 });
1001 }
1002
1003 private:
1004 KisImageSP m_image;
1005 std::unique_ptr<KUndo2Command> m_finalCommand;
1006 std::vector<KisTransaction> m_transactions;
1007 };
1008
1009 KisStrokeId id = startStroke(new PurgeUnusedDataStroke(this, isCancellable));
1010 endStroke(id);
1011}
1012
1013void KisImage::cropNode(KisNodeSP node, const QRect& newRect, const bool activeFrameOnly)
1014{
1015 const bool isLayer = qobject_cast<KisLayer*>(node.data());
1016 KUndo2MagicString actionName = isLayer ?
1017 kundo2_i18n("Crop Layer") :
1018 kundo2_i18n("Crop Mask");
1019
1020 KisImageSignalVector emitSignals;
1021
1022 KisCropSavedExtraData *extraData =
1024 newRect, node);
1025
1026 KisProcessingApplicator applicator(this, node,
1028 emitSignals, actionName, extraData);
1029
1030 KisProcessingVisitorSP visitor =
1031 new KisCropProcessingVisitor(newRect, true, false);
1032
1033 if (node->isAnimated() && activeFrameOnly) {
1034 // Crop active frame..
1035 applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT);
1036 } else {
1037 // Crop all frames..
1039 }
1040 applicator.end();
1041}
1042
1043void KisImage::scaleImage(const QSize &size, qreal xres, qreal yres, KisFilterStrategy *filterStrategy)
1044{
1045 bool resolutionChanged = !qFuzzyCompare(xRes(), xres) || !qFuzzyCompare(yRes(), yres);
1046 bool sizeChanged = size != this->size();
1047
1048 if (!resolutionChanged && !sizeChanged) return;
1049
1050 KisImageSignalVector emitSignals;
1051 if (resolutionChanged) emitSignals << ResolutionChangedSignal;
1052 if (sizeChanged) emitSignals << ComplexSizeChangedSignal(bounds(), size);
1053
1054 KUndo2MagicString actionName = sizeChanged ?
1055 kundo2_i18n("Scale Image") :
1056 kundo2_i18n("Change Image Resolution");
1057
1058 KisProcessingApplicator::ProcessingFlags signalFlags =
1059 (resolutionChanged || sizeChanged) ?
1062
1063 KisProcessingApplicator applicator(this, m_d->rootLayer,
1065 emitSignals, actionName);
1066
1067 qreal sx = qreal(size.width()) / this->size().width();
1068 qreal sy = qreal(size.height()) / this->size().height();
1069
1070 QTransform shapesCorrection;
1071
1072 if (resolutionChanged) {
1073 shapesCorrection = QTransform::fromScale(xRes() / xres, yRes() / yres);
1074 }
1075
1076 KisProcessingVisitorSP visitor =
1078 0, 0,
1079 0,
1080 0, 0,
1081 filterStrategy,
1082 shapesCorrection);
1083
1085
1086 if (resolutionChanged) {
1087 KUndo2Command *parent =
1089 new KisImageSetResolutionCommand(this, xres, yres, parent);
1090 applicator.applyCommand(parent);
1091 }
1092
1093 if (sizeChanged) {
1094 applicator.applyCommand(new KisImageResizeCommand(this, size));
1095 }
1096
1097 applicator.end();
1098}
1099
1100void KisImage::scaleNode(KisNodeSP node, const QPointF &center, qreal scaleX, qreal scaleY, KisFilterStrategy *filterStrategy, KisSelectionSP selection)
1101{
1102 scaleNodes(KisNodeList{node}, center, scaleX, scaleY, filterStrategy, selection);
1103}
1104void KisImage::scaleNodes(KisNodeList nodes, const QPointF &center, qreal scaleX, qreal scaleY, KisFilterStrategy *filterStrategy, KisSelectionSP selection)
1105{
1106 KUndo2MagicString actionName(kundo2_i18np("Scale Layer", "Scale %1 Layers", nodes.size()));
1107 KisImageSignalVector emitSignals;
1108
1109 QPointF offset;
1110 {
1111 KisTransformWorker worker(0,
1112 scaleX, scaleY,
1113 0, 0,
1114 0.0,
1115 0, 0, 0, 0);
1116 QTransform transform = worker.transform();
1117
1118 offset = center - transform.map(center);
1119 }
1120
1121 KisProcessingApplicator applicator(this, nodes,
1123 emitSignals, actionName);
1124
1126 new KisTransformProcessingVisitor(scaleX, scaleY,
1127 0, 0,
1128 0,
1129 offset.x(), offset.y(),
1130 filterStrategy);
1131
1132 visitor->setSelection(selection);
1133
1134 if (selection) {
1135 applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT);
1136 } else {
1138 }
1139
1140 applicator.end();
1141}
1142
1144 KisNodeSP rootNode,
1145 double radians,
1146 bool resizeImage,
1147 KisSelectionSP selection)
1148{
1149 rotateImpl(actionName, KisNodeList{rootNode}, radians, resizeImage, selection);
1150}
1152 KisNodeList nodes,
1153 double radians,
1154 bool resizeImage,
1155 KisSelectionSP selection)
1156{
1157 // we can either transform (and resize) the whole image or
1158 // transform a selection, we cannot do both at the same time
1159 KIS_SAFE_ASSERT_RECOVER(!(bool(selection) && resizeImage)) {
1160 selection = 0;
1161 }
1162
1163 QRect baseBounds;
1164 if (resizeImage) {
1165 baseBounds = bounds();
1166 }
1167 else if (selection) {
1168 baseBounds = selection->selectedExactRect();
1169 }
1170 else {
1171 Q_FOREACH(KisNodeSP node, nodes) {
1172 baseBounds = baseBounds.united(node->exactBounds());
1173 }
1174 }
1175
1176 QPointF offset;
1177 QSize newSize;
1178
1179 {
1180 KisTransformWorker worker(0,
1181 1.0, 1.0,
1182 0, 0,
1183 radians,
1184 0, 0, 0, 0);
1185 QTransform transform = worker.transform();
1186
1187 if (resizeImage) {
1188 QRect newRect = transform.mapRect(baseBounds);
1189 newSize = newRect.size();
1190 offset = -newRect.topLeft();
1191 }
1192 else {
1193 QPointF origin = QRectF(baseBounds).center();
1194
1195 newSize = size();
1196 offset = -(transform.map(origin) - origin);
1197 }
1198 }
1199
1200 bool sizeChanged = resizeImage &&
1201 (newSize.width() != baseBounds.width() ||
1202 newSize.height() != baseBounds.height());
1203
1204 // These signals will be emitted after processing is done
1205 KisImageSignalVector emitSignals;
1206 if (sizeChanged) emitSignals << ComplexSizeChangedSignal(baseBounds, newSize);
1207
1208 // These flags determine whether updates are transferred to the UI during processing
1209 KisProcessingApplicator::ProcessingFlags signalFlags =
1210 sizeChanged ?
1213
1214
1215 KisProcessingApplicator applicator(this, nodes,
1217 emitSignals, actionName);
1218
1220
1222 new KisTransformProcessingVisitor(1.0, 1.0, 0.0, 0.0,
1223 radians,
1224 offset.x(), offset.y(),
1225 filter);
1226 if (selection) {
1227 visitor->setSelection(selection);
1228 }
1229
1230 if (selection) {
1231 applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT);
1232 } else {
1234 }
1235
1236 if (sizeChanged) {
1237 applicator.applyCommand(new KisImageResizeCommand(this, newSize));
1238 }
1239 applicator.end();
1240}
1241
1242
1243void KisImage::rotateImage(double radians)
1244{
1245 rotateImpl(kundo2_i18n("Rotate Image"), root(), radians, true, 0);
1246}
1247
1248void KisImage::rotateNode(KisNodeSP node, double radians, KisSelectionSP selection)
1249{
1250 rotateNodes(KisNodeList{node}, radians, selection);
1251}
1252void KisImage::rotateNodes(KisNodeList nodes, double radians, KisSelectionSP selection)
1253{
1254 if (nodes.size() == 1 && nodes[0]->inherits("KisMask")) {
1255 rotateImpl(kundo2_i18n("Rotate Mask"), nodes, radians, false, selection);
1256 }
1257 else {
1258 rotateImpl(kundo2_i18np("Rotate Layer", "Rotate %1 Layers", nodes.size()), nodes, radians, false, selection);
1259 }
1260}
1261
1263 KisNodeSP rootNode,
1264 bool resizeImage,
1265 double angleX, double angleY,
1266 KisSelectionSP selection)
1267{
1268 shearImpl(actionName, KisNodeList{rootNode}, resizeImage, angleX, angleY, selection);
1269}
1271 KisNodeList nodes,
1272 bool resizeImage,
1273 double angleX, double angleY,
1274 KisSelectionSP selection)
1275{
1276 QRect baseBounds;
1277 if (resizeImage) {
1278 baseBounds = bounds();
1279 }
1280 else if (selection) {
1281 baseBounds = selection->selectedExactRect();
1282 }
1283 else {
1284 Q_FOREACH(KisNodeSP node, nodes) {
1285 baseBounds = baseBounds.united(node->exactBounds());
1286 }
1287 }
1288 const QPointF origin = QRectF(baseBounds).center();
1289
1290 //angleX, angleY are in degrees
1291 const qreal pi = 3.1415926535897932385;
1292 const qreal deg2rad = pi / 180.0;
1293
1294 qreal tanX = tan(angleX * deg2rad);
1295 qreal tanY = tan(angleY * deg2rad);
1296
1297 QPointF offset;
1298 QSize newSize;
1299
1300 {
1301 KisTransformWorker worker(0,
1302 1.0, 1.0,
1303 tanX, tanY,
1304 0,
1305 0, 0, 0, 0);
1306
1307 QRect newRect = worker.transform().mapRect(baseBounds);
1308 newSize = newRect.size();
1309 if (resizeImage) offset = -newRect.topLeft();
1310 else offset = origin - worker.transform().map(origin);
1311 }
1312
1313 if (newSize == baseBounds.size()) return;
1314
1315 KisImageSignalVector emitSignals;
1316 if (resizeImage) emitSignals << ComplexSizeChangedSignal(baseBounds, newSize);
1317
1318 KisProcessingApplicator::ProcessingFlags signalFlags =
1321
1322 KisProcessingApplicator applicator(this, nodes,
1323 signalFlags,
1324 emitSignals, actionName);
1325
1327
1329 new KisTransformProcessingVisitor(1.0, 1.0,
1330 tanX, tanY,
1331 0,
1332 offset.x(), offset.y(),
1333 filter);
1334
1335 if (selection) {
1336 visitor->setSelection(selection);
1337 }
1338
1339 if (selection) {
1340 applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT);
1341 } else {
1343 }
1344
1345 if (resizeImage) {
1346 applicator.applyCommand(new KisImageResizeCommand(this, newSize));
1347 }
1348
1349 applicator.end();
1350}
1351
1352void KisImage::shearNode(KisNodeSP node, double angleX, double angleY, KisSelectionSP selection)
1353{
1354 shearNodes(KisNodeList{node}, angleX, angleY, selection);
1355}
1356void KisImage::shearNodes(KisNodeList nodes, double angleX, double angleY, KisSelectionSP selection)
1357{
1358 if (nodes.size() == 1 && nodes[0]->inherits("KisMask")) {
1359 shearImpl(kundo2_i18n("Shear Mask"), nodes, false,
1360 angleX, angleY, selection);
1361 }
1362 else {
1363 shearImpl(kundo2_i18np("Shear Layer", "Shear %1 Layers", nodes.size()), nodes, false,
1364 angleX, angleY, selection);
1365 }
1366}
1367
1368void KisImage::shear(double angleX, double angleY)
1369{
1370 shearImpl(kundo2_i18n("Shear Image"), m_d->rootLayer, true,
1371 angleX, angleY, 0);
1372}
1373
1375 const KoColorSpace *dstColorSpace,
1377 KoColorConversionTransformation::ConversionFlags conversionFlags)
1378{
1379 if (!node->projectionLeaf()->isLayer()) return;
1380 // must not be an image root, use convertImageColorSpace() for that:
1381 KIS_SAFE_ASSERT_RECOVER_RETURN(!node->image() || (node.data() != node->image()->rootLayer().data()));
1382
1383 const KoColorSpace *srcColorSpace = node->colorSpace();
1384
1385 if (!dstColorSpace || *srcColorSpace == *dstColorSpace) return;
1386
1387 KUndo2MagicString actionName =
1388 kundo2_i18n("Convert Layer Color Space");
1389
1390 KisImageSignalVector emitSignals;
1391
1392 KisProcessingApplicator applicator(this, node,
1394 emitSignals, actionName);
1395
1396 applicator.applyVisitor(
1398 srcColorSpace, dstColorSpace,
1399 renderingIntent, conversionFlags),
1401
1402 applicator.end();
1403}
1404
1406{
1408 State initialState, KUndo2Command *parent = 0)
1409 : KisCommandUtils::FlipFlopCommand(initialState, parent),
1410 m_cs(cs),
1411 m_image(image)
1412 {
1413 }
1414
1415 void partA() override {
1416 KisImageSP image = m_image;
1417
1418 if (image) {
1420 }
1421 }
1422
1423private:
1426};
1427
1429 bool convertLayers,
1431 KoColorConversionTransformation::ConversionFlags conversionFlags)
1432{
1433 const KoColorSpace *srcColorSpace = this->colorSpace;
1434
1435 if (!dstColorSpace || *srcColorSpace == *dstColorSpace) return;
1436
1437 const KUndo2MagicString actionName =
1438 convertLayers ?
1439 kundo2_i18n("Convert Image Color Space") :
1440 kundo2_i18n("Convert Projection Color Space");
1441
1442 KisImageSignalVector emitSignals;
1443 emitSignals << ColorSpaceChangedSignal;
1444
1445 KisProcessingApplicator::ProcessingFlags flags = KisProcessingApplicator::NO_UI_UPDATES;
1446 if (convertLayers) {
1448 }
1449
1450 KisProcessingApplicator applicator(q, this->rootLayer,
1451 flags,
1452 emitSignals, actionName);
1453
1454 applicator.applyCommand(
1456 KisImageWSP(q),
1459
1460 applicator.applyVisitor(
1462 srcColorSpace, dstColorSpace,
1463 renderingIntent, conversionFlags),
1465
1466 applicator.applyCommand(
1468 KisImageWSP(q),
1471
1472
1473 applicator.end();
1474}
1475
1478 KoColorConversionTransformation::ConversionFlags conversionFlags)
1479{
1480 m_d->convertImageColorSpaceImpl(dstColorSpace, true, renderingIntent, conversionFlags);
1481}
1482
1489
1491{
1492 const KUndo2MagicString actionName = kundo2_i18n("Unify Layers Color Space");
1493
1494 KisImageSignalVector emitSignals;
1495
1496 KisProcessingApplicator::ProcessingFlags flags =
1498
1499 KisProcessingApplicator applicator(this, m_d->rootLayer,
1500 flags,
1501 emitSignals, actionName);
1502
1503 // src and dst color spaces coincide, since we should just unify
1504 // all our layers
1505 applicator.applyVisitor(
1511
1512 applicator.end();
1513}
1514
1516{
1517 const KoColorSpace *srcColorSpace = node->colorSpace();
1518
1519 if (!node->projectionLeaf()->isLayer()) return false;
1520 if (!profile || *srcColorSpace->profile() == *profile) return false;
1521
1522 KUndo2MagicString actionName = kundo2_i18n("Assign Profile to Layer");
1523
1524 KisImageSignalVector emitSignals;
1525
1526 const KoColorSpace *dstColorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile);
1527 if (!dstColorSpace) return false;
1528
1529 KisProcessingApplicator applicator(this, node,
1532 emitSignals, actionName);
1533
1534 applicator.applyVisitor(
1536 srcColorSpace, dstColorSpace),
1538
1539 applicator.end();
1540
1541 return true;
1542}
1543
1544
1545bool KisImage::assignImageProfile(const KoColorProfile *profile, bool blockAllUpdates)
1546{
1547 if (!profile) return false;
1548
1549 const KoColorSpace *srcColorSpace = m_d->colorSpace;
1550 bool imageProfileIsSame = *srcColorSpace->profile() == *profile;
1551
1552 imageProfileIsSame &=
1554 [profile] (KisNodeSP node) {
1555 return *node->colorSpace()->profile() != *profile;
1556 });
1557
1558 if (imageProfileIsSame) {
1559 dbgImage << "Trying to set the same image profile again" << ppVar(srcColorSpace->profile()->name()) << ppVar(profile->name());
1560 return true;
1561 }
1562
1563 KUndo2MagicString actionName = kundo2_i18n("Assign Profile");
1564
1565 KisImageSignalVector emitSignals;
1566 emitSignals << ProfileChangedSignal;
1567
1568 const KoColorSpace *dstColorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile);
1569 if (!dstColorSpace) return false;
1570
1571 KisProcessingApplicator applicator(this, m_d->rootLayer,
1573 (!blockAllUpdates ?
1576 emitSignals, actionName);
1577
1578 applicator.applyCommand(
1580 KisImageWSP(this),
1583
1584 applicator.applyVisitor(
1586 srcColorSpace, dstColorSpace),
1588
1589 applicator.applyCommand(
1591 KisImageWSP(this),
1594
1595
1596 applicator.end();
1597
1598 return true;
1599}
1600
1606
1608{
1609 return m_d->colorSpace;
1610}
1611
1613{
1614 return colorSpace()->profile();
1615}
1616
1617double KisImage::xRes() const
1618{
1619 return m_d->xres;
1620}
1621
1622double KisImage::yRes() const
1623{
1624 return m_d->yres;
1625}
1626
1627void KisImage::setResolution(double xres, double yres)
1628{
1629 if (xres > 0) {
1630 m_d->xres = xres;
1631 } else {
1632 qWarning() << "WARNING: Ignoring attempt to set image x resolution <= 0 (" << xres << ")!";
1633 }
1634
1635 if (yres > 0) {
1636 m_d->yres = yres;
1637 } else {
1638 qWarning() << "WARNING: Ignoring attempt to set image y resolution <= 0 (" << yres << ")!";
1639 }
1640}
1641
1642QPointF KisImage::documentToPixel(const QPointF &documentCoord) const
1643{
1644 return QPointF(documentCoord.x() * xRes(), documentCoord.y() * yRes());
1645}
1646
1647QPoint KisImage::documentToImagePixelFloored(const QPointF &documentCoord) const
1648{
1649 QPointF pixelCoord = documentToPixel(documentCoord);
1650 return QPoint(qFloor(pixelCoord.x()), qFloor(pixelCoord.y()));
1651}
1652
1653QRectF KisImage::documentToPixel(const QRectF &documentRect) const
1654{
1655 return QRectF(documentToPixel(documentRect.topLeft()), documentToPixel(documentRect.bottomRight()));
1656}
1657
1658QPointF KisImage::pixelToDocument(const QPointF &pixelCoord) const
1659{
1660 return QPointF(pixelCoord.x() / xRes(), pixelCoord.y() / yRes());
1661}
1662
1663QPointF KisImage::pixelToDocument(const QPoint &pixelCoord) const
1664{
1665 return QPointF((pixelCoord.x() + 0.5) / xRes(), (pixelCoord.y() + 0.5) / yRes());
1666}
1667
1668QRectF KisImage::pixelToDocument(const QRectF &pixelCoord) const
1669{
1670 return QRectF(pixelToDocument(pixelCoord.topLeft()), pixelToDocument(pixelCoord.bottomRight()));
1671}
1672
1673qint32 KisImage::width() const
1674{
1675 return m_d->width;
1676}
1677
1678qint32 KisImage::height() const
1679{
1680 return m_d->height;
1681}
1682
1684{
1685 Q_ASSERT(m_d->rootLayer);
1686 return m_d->rootLayer;
1687}
1688
1690{
1691 if (m_d->isolationRootNode) {
1692 return m_d->isolationRootNode->projection();
1693 }
1694
1695 Q_ASSERT(m_d->rootLayer);
1697 Q_ASSERT(projection);
1698 return projection;
1699}
1700
1701qint32 KisImage::nlayers() const
1702{
1703 QStringList list;
1704 list << "KisLayer";
1705
1706 KisCountVisitor visitor(list, KoProperties());
1707 m_d->rootLayer->accept(visitor);
1708 return visitor.count();
1709}
1710
1712{
1713 QStringList list;
1714 list << "KisLayer";
1715 KoProperties properties;
1716 properties.setProperty("visible", false);
1717 KisCountVisitor visitor(list, properties);
1718 m_d->rootLayer->accept(visitor);
1719
1720 return visitor.count();
1721}
1722
1724{
1725 const QStringList list = {"KisLayer"};
1726
1727 KoProperties koProperties;
1728 KisCountVisitor visitor(list, koProperties);
1729 const QList<KisNodeSP> childNodes = m_d->rootLayer->childNodes(list, koProperties);
1730 for (KisNodeSP childNode: childNodes) {
1731 childNode->accept(visitor);
1732 }
1733 return visitor.count();
1734}
1735
1737{
1738 KisLayerUtils::flattenImage(this, activeNode);
1739}
1740
1742{
1743 KisLayerUtils::mergeMultipleNodes(this, mergedNodes, putAfter);
1744}
1745
1747{
1748 KisLayerUtils::mergeDown(this, layer, strategy);
1749}
1750
1752{
1753 KisLayerUtils::flattenLayer(this, layer);
1754}
1755
1761
1762QImage KisImage::convertToQImage(QRect imageRect,
1763 const KoColorProfile * profile)
1764{
1765 qint32 x;
1766 qint32 y;
1767 qint32 w;
1768 qint32 h;
1769 imageRect.getRect(&x, &y, &w, &h);
1770 return convertToQImage(x, y, w, h, profile);
1771}
1772
1774 qint32 y,
1775 qint32 w,
1776 qint32 h,
1777 const KoColorProfile * profile)
1778{
1780 if (!dev) return QImage();
1781 QImage image = dev->convertToQImage(const_cast<KoColorProfile*>(profile), x, y, w, h,
1784
1785 return image;
1786}
1787
1788
1789
1790QImage KisImage::convertToQImage(const QSize& scaledImageSize, const KoColorProfile *profile)
1791{
1792 if (scaledImageSize.isEmpty()) {
1793 return QImage();
1794 }
1795
1797 KisPainter gc;
1798 gc.copyAreaOptimized(QPoint(0, 0), projection(), dev, bounds());
1799 gc.end();
1800 double scaleX = qreal(scaledImageSize.width()) / width();
1801 double scaleY = qreal(scaledImageSize.height()) / height();
1802
1803
1804 if (scaleX < 1.0/256 || scaleY < 1.0/256) {
1805 // quick checking if we're not trying to scale too much
1806 // convertToQImage uses KisFixedPoint values, which means that the scale cannot be smaller than 1/2^8
1807 // BUG:432182
1808 // FIXME: would be best to extend KisFixedPoint instead
1809 return convertToQImage(size(), profile).scaled(scaledImageSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
1810 }
1811
1812 KoDummyUpdaterHolder updaterHolder;
1813 QPointer<KoUpdater> updater = updaterHolder.updater();
1814
1815 KisTransformWorker worker(dev, scaleX, scaleY, 0.0, 0.0, 0.0, 0.0, 0.0, updater, KisFilterStrategyRegistry::instance()->value("Bicubic"));
1816 worker.run();
1817
1818 return dev->convertToQImage(profile);
1819}
1824
1825QRect KisImage::bounds() const
1826{
1827 return QRect(0, 0, width(), height());
1828}
1829
1831{
1832 QRect boundRect = bounds();
1833
1834 const int lod = currentLevelOfDetail();
1835 if (lod > 0) {
1836 KisLodTransform t(lod);
1837 boundRect = t.map(boundRect);
1838 }
1839
1840 return boundRect;
1841}
1842
1850
1852{
1853 return m_d->undoStore->presentCommand();
1854}
1855
1857{
1858 disconnect(m_d->undoStore.data(), SIGNAL(historyStateChanged()), &m_d->signalRouter, SLOT(emitImageModifiedNotification()));
1859
1862 m_d->undoStore.reset(undoStore);
1863
1864 connect(m_d->undoStore.data(), SIGNAL(historyStateChanged()), &m_d->signalRouter, SLOT(emitImageModifiedNotification()));
1865
1866}
1867
1869{
1870 return m_d->undoStore.data();
1871}
1872
1874{
1875 return &m_d->legacyUndoAdapter;
1876}
1877
1883
1892
1894{
1896
1898
1899 if (m_d->rootLayer) {
1901 m_d->rootLayer->setImage(0);
1902 m_d->rootLayer->disconnect();
1903
1904 KisPaintDeviceSP original = m_d->rootLayer->original();
1906 }
1907
1909 m_d->rootLayer->disconnect();
1911 m_d->rootLayer->setImage(this);
1912
1914 this->setDefaultProjectionColor(defaultProjectionColor);
1915}
1916
1918{
1919 // Find the icc annotation, if there is one
1920 vKisAnnotationSP_it it = m_d->annotations.begin();
1921 while (it != m_d->annotations.end()) {
1922 if ((*it)->type() == annotation->type()) {
1923 *it = annotation;
1925 return;
1926 }
1927 ++it;
1928 }
1929 m_d->annotations.push_back(annotation);
1931}
1932
1934{
1935 vKisAnnotationSP_it it = m_d->annotations.begin();
1936 while (it != m_d->annotations.end()) {
1937 if ((*it) && (*it)->type() == type) {
1938 return *it;
1939 }
1940 else if (!*it) {
1941 qWarning() << "Skipping deleted annotation";
1942 }
1943 ++it;
1944 }
1945 return KisAnnotationSP(0);
1946}
1947
1948void KisImage::removeAnnotation(const QString& type)
1949{
1950 vKisAnnotationSP_it it = m_d->annotations.begin();
1951 while (it != m_d->annotations.end()) {
1952 if ((*it)->type() == type) {
1953 m_d->annotations.erase(it);
1955 return;
1956 }
1957 ++it;
1958 }
1959}
1960
1965
1970
1975
1980
1988
1990{
1998 if (strokeStrategy->requestsOtherStrokesToEnd()) {
2000 }
2001
2002 return m_d->scheduler.startStroke(strokeStrategy);
2003}
2004
2006{
2007 KisImageConfig imageConfig(true);
2008 int patchWidth = imageConfig.updatePatchWidth();
2009 int patchHeight = imageConfig.updatePatchHeight();
2010
2011 for (int y = 0; y < rc.height(); y += patchHeight) {
2012 for (int x = 0; x < rc.width(); x += patchWidth) {
2013 QRect patchRect(x, y, patchWidth, patchHeight);
2014 patchRect &= rc;
2015
2016 KritaUtils::addJobConcurrent(jobs, std::bind(&KisImage::notifyProjectionUpdated, q, patchRect));
2017 }
2018 }
2019}
2020
2021bool KisImage::startIsolatedMode(KisNodeSP node, bool isolateLayer, bool isolateGroup)
2022{
2023 m_d->isolateLayer = isolateLayer;
2024 m_d->isolateGroup = isolateGroup;
2025 if ((isolateLayer || isolateGroup) == false) return false;
2026
2031 if (!node->projection()) return false;
2032
2033 struct StartIsolatedModeStroke : public KisRunnableBasedStrokeStrategy {
2034 StartIsolatedModeStroke(KisNodeSP node, KisImageSP image, bool isolateLayer, bool isolateGroup)
2035 : KisRunnableBasedStrokeStrategy(QLatin1String("start-isolated-mode"),
2036 kundo2_noi18n("start-isolated-mode")),
2037 m_newRoot(node),
2038 m_image(image),
2039 m_isolateLayer(isolateLayer),
2040 m_isolateGroup(isolateGroup)
2041 {
2042 this->enableJob(JOB_INIT, true, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE);
2043 this->enableJob(JOB_DOSTROKE, true);
2044 this->enableJob(JOB_FINISH, true, KisStrokeJobData::BARRIER);
2045 setClearsRedoOnStart(false);
2046 }
2047
2048 void initStrokeCallback() override {
2049 if (m_isolateLayer == false && m_isolateGroup == true) {
2050 // Isolate parent node unless node is the root note.
2051 m_newRoot = m_newRoot->parent() ? m_newRoot->parent() : m_newRoot;
2052 }
2053 // pass-though node don't have any projection prepared, so we should
2054 // explicitly regenerate it before activating isolated mode.
2055 m_newRoot->projectionLeaf()->explicitlyRegeneratePassThroughProjection();
2056 m_prevRoot = m_image->m_d->isolationRootNode;
2057
2058 const bool beforeVisibility = m_newRoot->projectionLeaf()->visible();
2059 const bool prevRootBeforeVisibility = m_prevRoot ? m_prevRoot->projectionLeaf()->visible() : false;
2060
2061 m_image->m_d->isolationRootNode = m_newRoot;
2062 Q_EMIT m_image->sigIsolatedModeChanged();
2063
2064 const bool afterVisibility = m_newRoot->projectionLeaf()->visible();
2065 const bool prevRootAfterVisibility = m_prevRoot ? m_prevRoot->projectionLeaf()->visible() : false;
2066
2067 m_newRootNeedsFullRefresh = beforeVisibility != afterVisibility;
2068 m_prevRootNeedsFullRefresh = prevRootBeforeVisibility != prevRootAfterVisibility;
2069 }
2070
2071 void finishStrokeCallback() override {
2072 // the GUI uses our thread to do the color space conversion so we
2073 // need to Q_EMIT this signal in multiple threads
2074
2075 if (m_prevRoot && m_prevRootNeedsFullRefresh) {
2076 m_image->refreshGraphAsync(m_prevRoot);
2077 }
2078
2079 if (m_newRootNeedsFullRefresh) {
2080 m_image->refreshGraphAsync(m_newRoot);
2081 }
2082
2083 if (!m_prevRootNeedsFullRefresh && !m_newRootNeedsFullRefresh) {
2085 m_image->m_d->notifyProjectionUpdatedInPatches(m_image->bounds(), jobs);
2086 this->runnableJobsInterface()->addRunnableJobs(jobs);
2087 }
2088
2089 m_image->invalidateAllFrames();
2090 }
2091
2092 private:
2093 KisNodeSP m_newRoot;
2094 KisNodeSP m_prevRoot;
2095 KisImageSP m_image;
2096 bool m_newRootNeedsFullRefresh = false;
2097 bool m_prevRootNeedsFullRefresh = false;
2098
2099 bool m_isolateLayer;
2100 bool m_isolateGroup;
2101 };
2102
2103 KisStrokeId id = startStroke(new StartIsolatedModeStroke(node, this, isolateLayer, isolateGroup));
2104 endStroke(id);
2105
2106 return true;
2107}
2108
2109std::optional<KisRelativeContentLightLevelInformation> KisImage::relativeContentLightLevelInformation() const
2110{
2112}
2113
2114void KisImage::setRelativeContentLightLevelInformation(const std::optional<KisRelativeContentLightLevelInformation> clli)
2115{
2117}
2118
2119std::optional<KisColorVolumeInformation> KisImage::colorVolumeInformation() const
2120{
2122}
2123
2124void KisImage::setColorVolumeInformation(const std::optional<KisColorVolumeInformation> cvi)
2125{
2127}
2128
2129std::optional<double> KisImage::hdrReferenceWhiteLightLevel() const
2130{
2132}
2133
2134void KisImage::setHdrReferenceWhiteLightLevel(const std::optional<double> value)
2135{
2137 qWarning() << "Cannot set the diffuse white metadata on the image: profile provides a different inconsistent value";
2138 return;
2139 }
2140
2142}
2143
2145{
2146 if (!m_d->isolationRootNode) return;
2147
2148 struct StopIsolatedModeStroke : public KisRunnableBasedStrokeStrategy {
2149 StopIsolatedModeStroke(KisImageSP image)
2150 : KisRunnableBasedStrokeStrategy(QLatin1String("stop-isolated-mode"), kundo2_noi18n("stop-isolated-mode")),
2151 m_image(image),
2152 m_oldRootNode(nullptr),
2153 m_oldNodeNeedsRefresh(false)
2154 {
2155 this->enableJob(JOB_INIT);
2156 this->enableJob(JOB_DOSTROKE, true);
2157 this->enableJob(JOB_FINISH, true, KisStrokeJobData::BARRIER);
2158 setClearsRedoOnStart(false);
2159 }
2160
2161 void initStrokeCallback() override {
2162 if (!m_image->m_d->isolationRootNode) return;
2163
2164 m_oldRootNode = m_image->m_d->isolationRootNode;
2165
2166 const bool beforeVisibility = m_oldRootNode->projectionLeaf()->visible();
2167 m_image->m_d->isolationRootNode = 0;
2168 m_image->m_d->isolateLayer = false;
2169 m_image->m_d->isolateGroup = false;
2170 Q_EMIT m_image->sigIsolatedModeChanged();
2171 const bool afterVisibility = m_oldRootNode->projectionLeaf()->visible();
2172
2173 m_oldNodeNeedsRefresh = (beforeVisibility != afterVisibility);
2174 }
2175
2176 void finishStrokeCallback() override {
2177
2178 m_image->invalidateAllFrames();
2179
2180 if (m_oldNodeNeedsRefresh){
2181 m_oldRootNode->setDirty(m_image->bounds());
2182 } else {
2183 // TODO: Substitute notifyProjectionUpdated() with this code
2184 // when update optimization is implemented
2185 //
2186 // QRect updateRect = bounds() | oldRootNode->extent();
2187 //oldRootNode->setDirty(updateRect);
2188
2190 m_image->m_d->notifyProjectionUpdatedInPatches(m_image->bounds(), jobs);
2191 this->runnableJobsInterface()->addRunnableJobs(jobs);
2192 }
2193 }
2194
2195 private:
2196 KisImageSP m_image;
2197 KisNodeSP m_oldRootNode;
2198 bool m_oldNodeNeedsRefresh;
2199 };
2200
2201 KisStrokeId id = startStroke(new StopIsolatedModeStroke(this));
2202 endStroke(id);
2203}
2204
2208
2210{
2211 return m_d->isolateLayer;
2212}
2213
2215{
2216 return m_d->isolateGroup;
2217}
2218
2224
2229
2231{
2232 return m_d->scheduler.cancelStroke(id);
2233}
2234
2236{
2237 return scheduler.tryCancelCurrentStrokeAsync();
2238}
2239
2244
2249
2256
2261
2267
2272
2274{
2280 refreshGraphAsync(0, bounds(), QRect());
2281 waitForDone();
2282}
2283
2284void KisImage::refreshGraphAsync(KisNodeSP root, const QVector<QRect> &rects, const QRect &cropRect, KisProjectionUpdateFlags flags)
2285{
2286 if (!root) root = m_d->rootLayer;
2287
2288 QVector<QRect> requestedRects = rects;
2289
2290 KisGroupLayer *group = dynamic_cast<KisGroupLayer*>(root.data());
2291 if (group && group->passThroughMode()) {
2302 QVector<QRect> changeRects = requestedRects;
2303 KisProjectionLeafSP leaf = root->projectionLeaf()->nextSibling();
2304 while (leaf) {
2305 if (leaf->shouldBeRendered()) {
2306 for (auto it = changeRects.begin(); it != changeRects.end(); ++it) {
2307 *it = leaf->projectionPlane()->changeRect(*it, leaf->node() == root ? KisNode::N_FILTHY : KisNode::N_ABOVE_FILTHY);
2308 }
2309 }
2310
2311 leaf = leaf->nextSibling();
2312 }
2313
2314 std::swap(requestedRects, changeRects);
2315 root = group->parent();
2316
2318 }
2319
2324 for (auto it = m_d->projectionUpdatesFilters.rbegin();
2325 it != m_d->projectionUpdatesFilters.rend();
2326 ++it) {
2327
2328 KIS_SAFE_ASSERT_RECOVER(*it) { continue; }
2329
2330 if ((*it)->filterRefreshGraph(this, root.data(), requestedRects, cropRect, flags)) {
2331 return;
2332 }
2333 }
2334
2335 if (!flags.testFlag(KisProjectionUpdateFlag::DontInvalidateFrames)) {
2336 m_d->animationInterface->notifyNodeChanged(root.data(), requestedRects, true);
2337 }
2338
2339 m_d->scheduler.fullRefreshAsync(root, requestedRects, cropRect, flags);
2340}
2341
2343{
2344 m_d->scheduler.addSpontaneousJob(spontaneousJob);
2345}
2346
2348{
2350}
2351
2360
2375
2382
2388
2394
2399
2404
2409
2411{
2413}
2414
2416{
2417 m_d->disableUIUpdateSignals.deref();
2418
2419 QRect rect;
2420 QVector<QRect> postponedUpdates;
2421
2422 while (m_d->savedDisabledUIUpdates.pop(rect)) {
2423 postponedUpdates.append(rect);
2424 }
2425
2426 return postponedUpdates;
2427}
2428
2430{
2432
2434 int lod = currentLevelOfDetail();
2435 QRect dirtyRect = !lod ? rc : KisLodTransform::upscaledRect(rc, lod);
2436
2437 if (dirtyRect.isEmpty()) return;
2438
2439 Q_EMIT sigImageUpdated(dirtyRect);
2440 } else {
2442 }
2443}
2444
2449
2451{
2452 return m_d->scheduler.threadsLimit();
2453}
2454
2475
2478 const QVector<QRect> &rects,
2479 const QRect &cropRect,
2480 KisProjectionUpdateFlags flags)
2481{
2482 if (rects.isEmpty()) return;
2483
2484 scheduler.updateProjection(node, rects, cropRect, flags);
2485}
2486
2487void KisImage::requestProjectionUpdate(KisNode *node, const QVector<QRect> &rects, KisProjectionUpdateFlags flags)
2488{
2493 for (auto it = m_d->projectionUpdatesFilters.rbegin();
2494 it != m_d->projectionUpdatesFilters.rend();
2495 ++it) {
2496
2497 KIS_SAFE_ASSERT_RECOVER(*it) { continue; }
2498
2499 if ((*it)->filter(this, node, rects, flags)) {
2500 return;
2501 }
2502 }
2503
2504 if (!flags.testFlag(KisProjectionUpdateFlag::DontInvalidateFrames)) {
2505 m_d->animationInterface->notifyNodeChanged(node, rects, false);
2506 }
2507
2517 QVector<QRect> allSplitRects;
2518
2519 const QRect boundRect = effectiveLodBounds();
2520 Q_FOREACH (const QRect &rc, rects) {
2521 KisWrappedRect splitRect(rc, boundRect, m_d->wrapAroundModeAxis);
2522 allSplitRects.append(splitRect);
2523 }
2524
2525 m_d->requestProjectionUpdateImpl(node, allSplitRects, boundRect, flags);
2526
2527 } else {
2528 m_d->requestProjectionUpdateImpl(node, rects, bounds(), flags);
2529 }
2530
2532}
2533
2534void KisImage::invalidateFrames(const KisTimeSpan &range, const QRect &rect)
2535{
2537}
2538
2543
2548
2550{
2551 Q_UNUSED(node);
2552
2553 channel->connect(channel, SIGNAL(sigAddedKeyframe(const KisKeyframeChannel*, int)), m_d->animationInterface, SIGNAL(sigKeyframeAdded(const KisKeyframeChannel*, int)), Qt::UniqueConnection);
2554 channel->connect(channel, SIGNAL(sigKeyframeHasBeenRemoved(const KisKeyframeChannel*,int)), m_d->animationInterface, SIGNAL(sigKeyframeRemoved(const KisKeyframeChannel*, int)), Qt::UniqueConnection);
2555}
2556
2558{
2559 Q_UNUSED(node);
2560
2561 channel->disconnect(channel, SIGNAL(sigAddedKeyframe(const KisKeyframeChannel*, int)), m_d->animationInterface, SIGNAL(sigKeyframeAdded(const KisKeyframeChannel*, int)));
2562 channel->disconnect(channel, SIGNAL(sigKeyframeHasBeenRemoved(const KisKeyframeChannel*, int)), m_d->animationInterface, SIGNAL(sigKeyframeRemoved(const KisKeyframeChannel*, int)));
2563}
2564
2569
2571{
2572 m_d->compositions.append(composition);
2573}
2574
2576{
2577 m_d->compositions.removeAll(composition);
2578}
2579
2581{
2582 int index = m_d->compositions.indexOf(composition);
2583 if (index <= 0) {
2584 return;
2585 }
2586 m_d->compositions.move(index, index - 1);
2587}
2588
2590{
2591 int index = m_d->compositions.indexOf(composition);
2592 if (index >= m_d->compositions.size() -1) {
2593 return;
2594 }
2595 m_d->compositions.move(index, index + 1);
2596}
2597
2599{
2600 KisSelectionMask *mask = dynamic_cast<KisSelectionMask*>(root.data());
2601 if (mask &&
2602 (!bounds.contains(mask->paintDevice()->exactBounds()) ||
2603 mask->selection()->hasShapeSelection())) {
2604
2605 return true;
2606 }
2607
2608 KisNodeSP node = root->firstChild();
2609
2610 while (node) {
2611 if (checkMasksNeedConversion(node, bounds)) {
2612 return true;
2613 }
2614
2615 node = node->nextSibling();
2616 }
2617
2618 return false;
2619}
2620
2622{
2625 }
2626
2628
2631
2632 KisProcessingApplicator applicator(this, root(),
2635 kundo2_i18n("Crop Selections"));
2636
2637 KisProcessingVisitorSP visitor =
2639
2640 applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT);
2641 applicator.end();
2642 }
2643}
2644
2649
2654
2655
2660
2666
2671
2680
2685
2690
2692{
2693 Q_UNUSED(node);
2694 Q_EMIT sigNodeCollapsedChanged();
2695}
2696
2701
2703{
2704 const bool changed = bool(m_d->proofingConfig) != bool(proofingConfig) ||
2705 (m_d->proofingConfig && proofingConfig && *m_d->proofingConfig != *proofingConfig);
2706
2707 // we still assign even when unchanged since they can be different
2708 // shared pointer objects
2709 m_d->proofingConfig = proofingConfig;
2710
2711 if (changed) {
2712 Q_EMIT sigProofingConfigChanged();
2713 }
2714}
2715
2723
2725{
2726 return m_d->axesCenter;
2727}
2728
2729void KisImage::setMirrorAxesCenter(const QPointF &value) const
2730{
2731 m_d->axesCenter = value;
2732}
2733
2738
2740{
2741 return m_d->allowMasksOnRootNode;
2742}
float value(const T *src, size_t ch)
QVector< KisImageSignalType > KisImageSignalVector
@ ColorSpaceChangedSignal
@ ProfileChangedSignal
@ LayersChangedSignal
@ ModifiedWithoutUndoSignal
@ ResolutionChangedSignal
WrapAroundAxis
@ WRAPAROUND_BOTH
const KoID Float32BitsColorDepthID("F32", ki18n("32-bit float/channel"))
const KoID RGBAColorModelID("RGBA", ki18n("RGB/Alpha"))
@ TRC_ITU_R_BT_2100_0_PQ
const quint8 OPACITY_OPAQUE_U8
PythonPluginManager * instance
void notifyWaitOnImageStarted(KisImage *image)
void notifyWaitOnImageEnded(KisImage *image)
static KisBusyWaitBroker * instance()
The KisDumbUndoStore class doesn't actually save commands, so you cannot undo or redo!
static KisFilterStrategyRegistry * instance()
void invalidateFrames(const KisTimeSpan &range, const QRect &rect)
void notifyNodeChanged(const KisNode *node, const QRect &rect, bool recursive)
void requestTimeSwitchNonGUI(int time, bool useUndo=false)
int updatePatchWidth() const
int updatePatchHeight() const
bool enableProgressReporting(bool requestDefault=false) const
KisSelectionMaskSP deselectedGlobalSelection() const
Definition kis_image.cc:328
void setDeselectedGlobalSelection(KisSelectionMaskSP selectionMask)
Definition kis_image.cc:333
KisImageGlobalSelectionManagementInterface(KisImage *image)
Definition kis_image.cc:323
void emitNotification(KisImageSignalType type)
void emitNodeHasBeenAdded(KisNode *parent, int index, KisNodeAdditionFlags flags)
void emitAboutToRemoveANode(KisNode *parent, int index)
void emitNodeChanged(KisNodeSP node)
KisCompositeProgressProxy compositeProgressProxy
Definition kis_image.cc:282
std::optional< KisRelativeContentLightLevelInformation > relativeContentLightLevelInformation
Definition kis_image.cc:284
KisLocklessStack< QRect > savedDisabledUIUpdates
Definition kis_image.cc:272
vKisAnnotationSP annotations
Definition kis_image.cc:269
KisSelectionMaskSP deselectedGlobalSelectionMask
Definition kis_image.cc:252
void notifyProjectionUpdatedInPatches(const QRect &rc, QVector< KisRunnableStrokeJobData * > &jobs)
KisImageAnimationInterface * animationInterface
Definition kis_image.cc:278
KisUpdateScheduler scheduler
Definition kis_image.cc:279
KisProofingConfigurationSP proofingConfig
Definition kis_image.cc:249
KisSelectionMaskSP targetOverlaySelectionMask
Definition kis_image.cc:254
KisImageGlobalSelectionManagementInterface globalSelectionInterface
Definition kis_image.cc:251
KisSelectionMaskSP overlaySelectionMask
Definition kis_image.cc:255
void requestProjectionUpdateImpl(KisNode *node, const QVector< QRect > &rects, const QRect &cropRect, KisProjectionUpdateFlags flags)
void updateHDRMetadataOnColorSpaceChange(const KoColorSpace *newColorSpace)
Definition kis_image.cc:305
KisPostExecutionUndoAdapter postExecutionUndoAdapter
Definition kis_image.cc:267
std::optional< double > diffuseWhiteLightLevel
Definition kis_image.cc:286
KisLegacyUndoAdapter legacyUndoAdapter
Definition kis_image.cc:266
std::optional< KisColorVolumeInformation > colorVolumeInformation
Definition kis_image.cc:285
QAtomicInt disableUIUpdateSignals
Definition kis_image.cc:271
QScopedPointer< KisUndoStore > undoStore
Definition kis_image.cc:265
WrapAroundAxis wrapAroundModeAxis
Definition kis_image.cc:263
const KoColorSpace * colorSpace
Definition kis_image.cc:248
QAtomicInt disableDirtyRequests
Definition kis_image.cc:280
QVector< KisProjectionUpdatesFilterSP > projectionUpdatesFilters
Definition kis_image.cc:275
KisImageSignalRouter signalRouter
Definition kis_image.cc:277
QStack< KisProjectionUpdatesFilterCookie > disabledUpdatesCookies
Definition kis_image.cc:276
void convertImageColorSpaceImpl(const KoColorSpace *dstColorSpace, bool convertLayers, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags)
QList< KisLayerCompositionSP > compositions
Definition kis_image.cc:256
KisGroupLayerSP rootLayer
Definition kis_image.cc:253
KisImagePrivate(KisImage *_q, qint32 w, qint32 h, const KoColorSpace *c, KisUndoStore *undo, KisImageAnimationInterface *_animationInterface)
Definition kis_image.cc:124
std::optional< KisRelativeContentLightLevelInformation > relativeContentLightLevelInformation() const
relativeContentLightLevelInformation This returns (optionally) a KisRelativeContentLightLevelInformat...
void resizeImage(const QRect &newRect)
start asynchronous operation on resizing the image
Definition kis_image.cc:892
KisUndoAdapter * undoAdapter() const
void shearNodes(KisNodeList nodes, double angleX, double angleY, KisSelectionSP selection)
vKisAnnotationSP_it endAnnotations()
void requestStrokeEndActiveNode()
QImage convertToQImage(qint32 x1, qint32 y1, qint32 width, qint32 height, const KoColorProfile *profile)
void sigLayersChangedAsync()
int workingThreadsLimit() const
void setColorVolumeInformation(const std::optional< KisColorVolumeInformation > cvi)
Set the color volume information.
void invalidateAllFrames() override
Definition kis_image.cc:659
void addAnnotation(KisAnnotationSP annotation)
bool wrapAroundModeActive() const
void sigNodeCollapsedChanged()
KisImagePrivate * m_d
Definition kis_image.h:1312
bool canReselectGlobalSelection()
Definition kis_image.cc:732
KisImageGlobalSelectionManagementInterface * globalSelectionManagementInterface() const
Definition kis_image.cc:737
void scaleNode(KisNodeSP node, const QPointF &center, qreal scaleX, qreal scaleY, KisFilterStrategy *filterStrategy, KisSelectionSP selection)
start asynchronous operation on scaling a subtree of nodes starting at node
void nodeChanged(KisNode *node) override
Definition kis_image.cc:653
bool isIsolatingLayer() const
void waitForDone()
void setLodPreferences(const KisLodPreferences &value)
bool startIsolatedMode(KisNodeSP node, bool isolateLayer, bool isolateGroup)
bool hasUpdatesRunning() const override
void disableUIUpdates() override
void setWorkingThreadsLimit(int value)
void refreshGraphAsync(KisNodeSP root, const QVector< QRect > &rects, const QRect &cropRect, KisProjectionUpdateFlags flags=KisProjectionUpdateFlag::None) override
void setHdrReferenceWhiteLightLevel(const std::optional< double > value)
Set the diffuse white light level, in cd/m²
void nodeHasBeenAdded(KisNode *parent, int index, KisNodeAdditionFlags flags) override
Definition kis_image.cc:615
void keyframeChannelAboutToBeRemoved(KisNode *node, KisKeyframeChannel *channel) override
bool cancelStroke(KisStrokeId id) override
KisGroupLayerSP rootLayer() const
void disableDirtyRequests() override
void setWrapAroundModeAxis(WrapAroundAxis value)
void shearImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, bool resizeImage, double angleX, double angleY, KisSelectionSP selection)
void cropNode(KisNodeSP node, const QRect &newRect, const bool activeFrameOnly=false)
start asynchronous operation on cropping a subtree of nodes starting at node
void enableDirtyRequests() override
QPointF documentToPixel(const QPointF &documentCoord) const
void keyframeChannelHasBeenAdded(KisNode *node, KisKeyframeChannel *channel) override
UndoResult tryUndoUnfinishedLod0Stroke()
void aboutToRemoveANode(KisNode *parent, int index) override
Definition kis_image.cc:631
qint32 nChildLayers() const
void shear(double angleX, double angleY)
start asynchronous operation on shearing the image
const KoColorSpace * colorSpace() const
QPointF mirrorAxesCenter() const
KisImageAnimationInterface * animationInterface() const
void unlock()
Definition kis_image.cc:832
void shearNode(KisNodeSP node, double angleX, double angleY, KisSelectionSP selection)
start asynchronous operation on shearing a subtree of nodes starting at node
void purgeUnusedData(bool isCancellable)
purge all pixels that have default pixel to free up memory
Definition kis_image.cc:902
KisCompositeProgressProxy * compositeProgressProxy()
Definition kis_image.cc:773
void requestUndoDuringStroke()
void removeComposition(KisLayerCompositionSP composition)
void setProjectionColorSpace(const KoColorSpace *colorSpace)
void setAllowMasksOnRootNode(bool value)
void copyFromImageImpl(const KisImage &rhs, int policy)
Definition kis_image.cc:438
void rotateImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, double radians, bool resizeImage, KisSelectionSP selection)
void sigStrokeCancellationRequested()
void blockUpdates() override
blockUpdates block updating the image projection
Definition kis_image.cc:845
void sigRedoDuringStrokeRequested()
QString nextLayerName(const QString &baseName="") const
Definition kis_image.cc:742
KisAnnotationSP annotation(const QString &type)
void flattenLayer(KisLayerSP layer)
void sigImageModified()
KisProjectionUpdatesFilterSP removeProjectionUpdatesFilter(KisProjectionUpdatesFilterCookie cookie) override
removes already installed filter from the stack of updates filers
void sigInternalStopIsolatedModeRequested()
void unifyLayersColorSpace()
bool assignImageProfile(const KoColorProfile *profile, bool blockAllUpdates=false)
QRect effectiveLodBounds() const
bool isIdle(bool allowLocked=false)
Definition kis_image.cc:815
void copyFromImage(const KisImage &rhs)
Definition kis_image.cc:433
KisProjectionUpdatesFilterCookie currentProjectionUpdatesFilter() const override
QPointF pixelToDocument(const QPointF &pixelCoord) const
void scaleNodes(KisNodeList nodes, const QPointF &center, qreal scaleX, qreal scaleY, KisFilterStrategy *filterStrategy, KisSelectionSP selection)
void convertImageColorSpace(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags)
KisSelectionMaskSP overlaySelectionMask() const
Definition kis_image.cc:712
void sigResolutionChanged(double xRes, double yRes)
void barrierLock(bool readOnly=false)
Wait until all the queued background jobs are completed and lock the image.
Definition kis_image.cc:783
KisImage * clone(bool exactCopy=false)
Definition kis_image.cc:428
void flatten(KisNodeSP activeNode)
void rotateImage(double radians)
start asynchronous operation on rotating the image
WrapAroundAxis wrapAroundModeAxis() const
void sigColorSpaceChanged(const KoColorSpace *cs)
KisPaintDeviceSP projection() const
qint32 width() const
@ REPLACE
we are replacing the current KisImage with another
Definition kis_image.h:129
@ CONSTRUCT
we are copy-constructing a new KisImage
Definition kis_image.h:128
QSize size() const
Definition kis_image.h:550
void scaleImage(const QSize &size, qreal xres, qreal yres, KisFilterStrategy *filterStrategy)
start asynchronous operation on scaling the image
void resizeImageImpl(const QRect &newRect, bool cropLayers)
Definition kis_image.cc:861
void immediateLockForReadOnly()
Definition kis_image.cc:820
void aboutToAddANode(KisNode *parent, int index) override
Definition kis_image.cc:609
void setDefaultProjectionColor(const KoColor &color)
void setProofingConfiguration(KisProofingConfigurationSP proofingConfig)
setProofingConfiguration, this sets the image's proofing configuration, and signals the proofingConfi...
void explicitRegenerateLevelOfDetail()
void notifyBatchUpdateEnded() override
void notifyBatchUpdateStarted() override
std::optional< double > hdrReferenceWhiteLightLevel() const
HDR Reference White livel.
bool allowMasksOnRootNode() const
void addComposition(KisLayerCompositionSP composition)
KisPostExecutionUndoAdapter * postExecutionUndoAdapter() const override
qint32 nHiddenLayers() const
void requestStrokeCancellation()
KisNode * graphOverlayNode() const override
void moveCompositionUp(KisLayerCompositionSP composition)
void requestTimeSwitch(int time) override
QPoint documentToImagePixelFloored(const QPointF &documentCoord) const
void addJob(KisStrokeId id, KisStrokeJobData *data) override
void setOverlaySelectionMask(KisSelectionMaskSP mask)
Definition kis_image.cc:664
KisImage(KisUndoStore *undoStore, qint32 width, qint32 height, const KoColorSpace *colorSpace, const QString &name)
colorSpace can be null. In that case, it will be initialised to a default color space.
Definition kis_image.cc:338
void cropImage(const QRect &newRect)
start asynchronous operation on cropping the image
Definition kis_image.cc:897
void setMirrorAxesCenter(const QPointF &value) const
bool assignLayerProfile(KisNodeSP node, const KoColorProfile *profile)
void initialRefreshGraph()
void sigStrokeEndRequested()
void sigUndoDuringStrokeRequested()
void stopIsolatedMode()
void setSize(const QSize &size)
Definition kis_image.cc:855
QVector< QRect > enableUIUpdates() override
void nodeCollapsedChanged(KisNode *node) override
KisStrokeId startStroke(KisStrokeStrategy *strokeStrategy) override
void notifySelectionChanged() override
bool isIsolatingGroup() const
void notifyAboutToBeDeleted()
void requestStrokeEnd()
KisLodPreferences lodPreferences() const
double xRes() const
double yRes() const
void sigAboutToBeDeleted()
qint32 height() const
void invalidateFrames(const KisTimeSpan &range, const QRect &rect) override
void mergeMultipleLayers(QList< KisNodeSP > mergedLayers, KisNodeSP putAfter)
int currentLevelOfDetail() const
KisSelectionSP globalSelection() const
Definition kis_image.cc:722
void sigStrokeEndRequestedActiveNodeFiltered()
qint32 nlayers() const
KisNodeSP isolationRootNode() const
QList< KisLayerCompositionSP > compositions()
void sigProofingConfigChanged()
void mergeDown(KisLayerSP l, const KisMetaData::MergeStrategy *strategy)
const KUndo2Command * lastExecutedCommand() const override
void removeAnnotation(const QString &type)
KisImageSignalRouter * signalRouter()
QRect bounds() const override
KoColor defaultProjectionColor() const
void convertLayerColorSpace(KisNodeSP node, const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags)
void notifyLayersChanged()
use if the layers have changed completely (eg. when flattening)
friend class KisImageResizeCommand
Definition kis_image.h:1303
bool tryBarrierLock(bool readOnly=false)
Tries to lock the image without waiting for the jobs to finish.
Definition kis_image.cc:798
bool locked() const
Definition kis_image.cc:778
KisUndoStore * undoStore()
void unblockUpdates() override
unblockUpdates unblock updating the image project. This only restarts the scheduler and does not sche...
Definition kis_image.cc:850
void setModifiedWithoutUndo()
bool hasOverlaySelectionMask() const
Definition kis_image.cc:717
KisProjectionUpdatesFilterCookie addProjectionUpdatesFilter(KisProjectionUpdatesFilterSP filter) override
void moveCompositionDown(KisLayerCompositionSP composition)
bool wrapAroundModePermitted() const
void notifyProjectionUpdated(const QRect &rc) override
void sigImageUpdated(const QRect &)
void addSpontaneousJob(KisSpontaneousJob *spontaneousJob)
void rotateNodes(KisNodeList nodes, double radians, KisSelectionSP selection)
void endStroke(KisStrokeId id) override
std::optional< KisColorVolumeInformation > colorVolumeInformation() const
colorVolumeInformation
~KisImage() override
Definition kis_image.cc:353
void notifyUIUpdateCompleted(const QRect &rc) override
const KoColorProfile * profile() const
void setRelativeContentLightLevelInformation(const std::optional< KisRelativeContentLightLevelInformation > clli)
vKisAnnotationSP_it beginAnnotations()
void requestRedoDuringStroke()
void sigSizeChanged(const QPointF &oldStillPoint, const QPointF &newStillPoint)
void setResolution(double xres, double yres)
void setRootLayer(KisGroupLayerSP rootLayer)
static KisImageSP fromQImage(const QImage &image, KisUndoStore *undoStore)
Definition kis_image.cc:364
void convertImageProjectionColorSpace(const KoColorSpace *dstColorSpace)
void setWrapAroundModePermitted(bool value)
void requestProjectionUpdate(KisNode *node, const QVector< QRect > &rects, KisProjectionUpdateFlags flags) override
void setUndoStore(KisUndoStore *undoStore)
KisProofingConfigurationSP proofingConfiguration() const
proofingConfiguration
void rotateNode(KisNodeSP node, double radians, KisSelectionSP selection)
start asynchronous operation on rotating a subtree of nodes starting at node
KisKeyframeChannel stores and manages KisKeyframes. Maps units of time to virtual keyframe values....
void setNode(KisNodeWSP node)
static QRect upscaledRect(const QRect &srcRect, int lod)
KisPaintInformation map(KisPaintInformation pi) const
QRect exactBounds() const
KoColor defaultPixel() const
QImage convertToQImage(const KoColorProfile *dstProfile, qint32 x, qint32 y, qint32 w, qint32 h, KoColorConversionTransformation::Intent renderingIntent=KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags=KoColorConversionTransformation::internalConversionFlags()) const
void convertFromQImage(const QImage &image, const KoColorProfile *profile, qint32 offsetX=0, qint32 offsetY=0)
static void copyAreaOptimized(const QPoint &dstPt, KisPaintDeviceSP src, KisPaintDeviceSP dst, const QRect &originalSrcRect)
void setUndoStore(KisUndoStore *undoStore)
void applyVisitor(KisProcessingVisitorSP visitor, KisStrokeJobData::Sequentiality sequentiality=KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::Exclusivity exclusivity=KisStrokeJobData::NORMAL)
void applyCommand(KUndo2Command *command, KisStrokeJobData::Sequentiality sequentiality=KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::Exclusivity exclusivity=KisStrokeJobData::NORMAL)
void applyVisitorAllFrames(KisProcessingVisitorSP visitor, KisStrokeJobData::Sequentiality sequentiality=KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::Exclusivity exclusivity=KisStrokeJobData::NORMAL)
The KisProofingConfiguration struct Little struct that stores the proofing configuration for a given ...
bool requestsOtherStrokesToEnd() const
static QList< KisStrokeJobData * > createSuspendJobsData(KisImageWSP image)
static QList< KisStrokeJobData * > createResumeJobsData(KisImageWSP image)
static QList< KisStrokeJobData * > createJobsData(KisImageWSP image)
static KisTimeSpan infinite(int start)
QTransform transform() const
void setUndoStore(KisUndoStore *undoStore)
virtual bool hasHighDynamicRange() const =0
virtual const KoColorProfile * profile() const =0
static KoColor createTransparent(const KoColorSpace *cs)
Definition KoColor.cpp:682
A holder for an updater that does nothing.
Definition KoUpdater.h:116
KoUpdater * updater()
const T value(const QString &id) const
QString id() const
Definition KoID.cpp:63
void setProperty(const QString &name, const QVariant &value)
static bool qFuzzyCompare(half p1, half p2)
This file is part of the Krita application in calligra.
#define KIS_ASSERT_RECOVER(cond)
Definition kis_assert.h:55
#define KIS_SAFE_ASSERT_RECOVER(cond)
Definition kis_assert.h:126
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:129
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
#define KIS_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:75
#define KIS_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:97
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
#define ppVar(var)
Definition kis_debug.h:159
#define dbgImage
Definition kis_debug.h:49
#define EMIT_IF_NEEDED
#define SANITY_CHECK_LOCKED(name)
Definition kis_image.cc:114
KIS_DECLARE_STATIC_INITIALIZER
Definition kis_image.cc:117
bool checkMasksNeedConversion(KisNodeSP root, const QRect &bounds)
static bool isLayer(KisNodeSP node)
typedef void(QOPENGLF_APIENTRYP PFNGLINVALIDATEBUFFERDATAPROC)(GLuint buffer)
QSharedPointer< T > toQShared(T *ptr)
std::pair< KisStrokeStrategy *, QList< KisStrokeJobData * > > KisSuspendResumePair
std::pair< KisStrokeStrategy *, QList< KisStrokeJobData * > > KisLodSyncPair
KisSharedPtr< KisAnnotation > KisAnnotationSP
Definition kis_types.h:179
KisWeakSharedPtr< KisImage > KisImageWSP
Definition kis_types.h:70
QSharedPointer< KisProofingConfiguration > KisProofingConfigurationSP
Definition kis_types.h:311
vKisAnnotationSP::iterator vKisAnnotationSP_it
Definition kis_types.h:181
void * KisProjectionUpdatesFilterCookie
Definition kis_types.h:285
KUndo2MagicString kundo2_i18n(const char *text)
KUndo2MagicString kundo2_noi18n(const QString &text)
KUndo2MagicString kundo2_i18np(const char *sing, const char *plur, const A1 &a1)
void flattenImage(KisImageSP image, KisNodeSP activeNode, MergeFlags flags)
KisNodeSP recursiveFindNode(KisNodeSP node, std::function< bool(KisNodeSP)> func)
void flattenLayer(KisImageSP image, KisLayerSP layer, MergeFlags flags)
void recursiveApplyNodes(NodePointer node, Functor func)
void mergeDown(KisImageSP image, KisLayerSP layer, const KisMetaData::MergeStrategy *strategy, MergeFlags flags)
void mergeMultipleNodes(KisImageSP image, KisNodeList mergedNodes, KisNodeSP putAfter, MergeFlags flags)
void addJobConcurrent(QVector< Job * > &jobs, Func func)
void makeContainerUnique(C &container)
QMap< QString, KisKeyframeChannel * > keyframeChannels
QUuid uuid() const
virtual KisPaintDeviceSP projection() const =0
virtual QRect exactBounds() const
void setUuid(const QUuid &id)
virtual const KoColorSpace * colorSpace() const =0
KisImageWSP image
bool isAnimated() const
FlipFlopCommand(State initialState, KUndo2Command *parent=0)
void setDefaultProjectionColor(KoColor color)
bool accept(KisNodeVisitor &v) override
void setImage(KisImageWSP image) override
KisPaintDeviceSP original() const override
KoColor defaultProjectionColor() const
SetImageProjectionColorSpace(const KoColorSpace *cs, KisImageWSP image, State initialState, KUndo2Command *parent=0)
KisPaintDeviceSP projection() const override
Definition kis_layer.cc:826
virtual KisSelectionMaskSP selectionMask() const
Definition kis_layer.cc:504
void notifyChildMaskChanged()
Definition kis_layer.cc:499
bool lodSupported() const
bool lodPreferred() const
KisSelectionSP selection
Definition kis_mask.cc:44
KisPaintDeviceSP paintDevice() const override
Definition kis_mask.cc:223
static KisMemoryStatisticsServer * instance()
bool addNode(KisNodeSP node, KisNodeSP parent=KisNodeSP(), KisNodeAdditionFlags flags=KisNodeAdditionFlag::None)
void setRoot(KisNodeSP root)
virtual void aboutToRemoveANode(KisNode *parent, int index)
virtual void aboutToAddANode(KisNode *parent, int index)
virtual void requestProjectionUpdate(KisNode *node, const QVector< QRect > &rects, KisProjectionUpdateFlags flags)
virtual void nodeHasBeenAdded(KisNode *parent, int index, KisNodeAdditionFlags flags)
virtual void nodeChanged(KisNode *node)
KisNodeSP firstChild() const
Definition kis_node.cpp:361
QList< KisNodeSP > childNodes(const QStringList &nodeTypes, const KoProperties &properties) const
Definition kis_node.cpp:439
void setImage(KisImageWSP newImage) override
Definition kis_node.cpp:254
KisProjectionLeafSP projectionLeaf
Definition kis_node.cpp:93
virtual KisNodeSP clone() const =0
@ N_ABOVE_FILTHY
Definition kis_node.h:59
@ N_FILTHY
Definition kis_node.h:61
KisNodeWSP parent
Definition kis_node.cpp:86
void setGraphListener(KisNodeGraphListener *graphListener)
Definition kis_node.cpp:289
KisNodeSP nextSibling() const
Definition kis_node.cpp:408
KisNodeGraphListener * graphListener
Definition kis_node.cpp:87
KisPaintDeviceSP paintDevice
QRect extent() const override
void setDirty(const QVector< QRect > &rects) override
bool hasShapeSelection() const
QRect selectedExactRect() const
Slow, but exact way of determining the rectangle that encloses the selection.
void unlock(bool resetLodLevels=true)
void addJob(KisStrokeId id, KisStrokeJobData *data) override
void setPostSyncLod0GUIPlaneRequestForResumeCallback(const std::function< void()> &callback)
void setProgressProxy(KoProgressProxy *progressProxy)
void setSuspendResumeUpdatesStrokeStrategyFactory(const KisSuspendResumeStrategyPairFactory &factory)
void addSpontaneousJob(KisSpontaneousJob *spontaneousJob)
KisStrokeId startStroke(KisStrokeStrategy *strokeStrategy) override
void setLodPreferences(const KisLodPreferences &value)
void setLod0ToNStrokeStrategyFactory(const KisLodSyncStrokeStrategyFactory &factory)
void endStroke(KisStrokeId id) override
KisLodPreferences lodPreferences() const
void setThreadsLimit(int value)
KisPostExecutionUndoAdapter * lodNPostExecutionUndoAdapter() const
void setPurgeRedoStateCallback(const std::function< void()> &callback)
bool cancelStroke(KisStrokeId id) override
void fullRefreshAsync(KisNodeSP root, const QVector< QRect > &rects, const QRect &cropRect, KisProjectionUpdateFlags flags)
static KisUpdateTimeMonitor * instance()
void reportUpdateFinished(const QRect &rect)
virtual std::optional< double > hdrReferenceWhite() const =0
hdrReferenceWhite HDR reference white is only available for Perceptual Quantizer profiles that save t...
virtual TransferCharacteristics getTransferCharacteristics() const
getTransferCharacteristics This function should be subclassed at some point so we can get the value f...
const KoColorSpace * colorSpace(const QString &colorModelId, const QString &colorDepthId, const KoColorProfile *profile)
static KoColorSpaceRegistry * instance()
const KoColorSpace * graya8(const QString &profile=QString())
const KoColorSpace * graya16(const QString &profile=QString())
const KoColorSpace * rgb8(const QString &profileName=QString())
const KoColorSpace * rgb16(const QString &profileName=QString())
const KoColorSpace * alpha8()