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}
362
363KisImageSP KisImage::fromQImage(const QImage &image, KisUndoStore *undoStore)
364{
365 const KoColorSpace *colorSpace = 0;
366
367 switch (image.format()) {
368 case QImage::Format_Invalid:
369 case QImage::Format_Mono:
370 case QImage::Format_MonoLSB:
372 break;
373 case QImage::Format_Indexed8:
374 case QImage::Format_RGB32:
375 case QImage::Format_ARGB32:
376 case QImage::Format_ARGB32_Premultiplied:
378 break;
379 case QImage::Format_RGB16:
381 break;
382 case QImage::Format_ARGB8565_Premultiplied:
383 case QImage::Format_RGB666:
384 case QImage::Format_ARGB6666_Premultiplied:
385 case QImage::Format_RGB555:
386 case QImage::Format_ARGB8555_Premultiplied:
387 case QImage::Format_RGB888:
388 case QImage::Format_RGB444:
389 case QImage::Format_ARGB4444_Premultiplied:
390 case QImage::Format_RGBX8888:
391 case QImage::Format_RGBA8888:
392 case QImage::Format_RGBA8888_Premultiplied:
394 break;
395 case QImage::Format_BGR30:
396 case QImage::Format_A2BGR30_Premultiplied:
397 case QImage::Format_RGB30:
398 case QImage::Format_A2RGB30_Premultiplied:
400 break;
401 case QImage::Format_Alpha8:
403 break;
404 case QImage::Format_Grayscale8:
406 break;
407 case QImage::Format_Grayscale16:
409 break;
410 case QImage::Format_RGBX64:
411 case QImage::Format_RGBA64:
412 case QImage::Format_RGBA64_Premultiplied:
414 break;
415 default:
416 colorSpace = 0;
417 }
418
419 KisImageSP img = new KisImage(undoStore, image.width(), image.height(), colorSpace, i18n("Imported Image"));
420 KisPaintLayerSP layer = new KisPaintLayer(img, img->nextLayerName(), 255);
421 layer->paintDevice()->convertFromQImage(image, 0, 0, 0);
422 img->addNode(layer.data(), img->rootLayer().data());
423
424 return img;
425}
426
427KisImage *KisImage::clone(bool exactCopy)
428{
429 return new KisImage(*this, 0, exactCopy);
430}
431
433{
435}
436
437void KisImage::copyFromImageImpl(const KisImage &rhs, int policy)
438{
439 // make sure we choose exactly one from REPLACE and CONSTRUCT
440 KIS_ASSERT_RECOVER_RETURN(bool(policy & REPLACE) != bool(policy & CONSTRUCT));
441
454 const bool sizeChanged = m_d->width != rhs.width() || m_d->height != rhs.height();
455 const bool colorSpaceChanged = *m_d->colorSpace != *rhs.colorSpace();
456 const bool resolutionChanged = m_d->xres != rhs.m_d->xres || m_d->yres != rhs.m_d->yres;
457
458 if (sizeChanged) {
459 m_d->width = rhs.width();
460 m_d->height = rhs.height();
461 }
462
463 if (colorSpaceChanged) {
464 m_d->colorSpace = rhs.colorSpace();
465 }
466
467 if (resolutionChanged) {
468 m_d->xres = rhs.m_d->xres;
469 m_d->yres = rhs.m_d->yres;
470 }
471
472 // from KisImage::KisImage(const KisImage &, KisUndoStore *, bool)
473 setObjectName(rhs.objectName());
474
475 KisNodeSP oldRoot = this->root();
476 KisNodeSP newRoot = rhs.root()->clone();
477 newRoot->setGraphListener(this);
478 newRoot->setImage(this);
479
480 m_d->rootLayer = dynamic_cast<KisGroupLayer*>(newRoot.data());
481 setRoot(newRoot);
482
483 if (oldRoot) {
484 oldRoot->setImage(0);
485 oldRoot->setGraphListener(0);
486 oldRoot->disconnect();
487 }
488
489 // only when replacing do we need to Q_EMIT signals
490#define EMIT_IF_NEEDED if (!(policy & REPLACE)) {} else emit
491
492 if (sizeChanged) {
493 EMIT_IF_NEEDED sigSizeChanged(QPointF(), QPointF());
494 }
495 if (colorSpaceChanged) {
497 }
498 if (resolutionChanged) {
500 }
501
503
504 if (rhs.m_d->proofingConfig) {
506 if (policy & REPLACE) {
507 setProofingConfiguration(proofingConfig);
508 } else {
509 m_d->proofingConfig = proofingConfig;
510 }
511 }
512
516
517 bool exactCopy = policy & EXACT_COPY;
518
519 if (exactCopy || rhs.m_d->isolationRootNode || rhs.m_d->overlaySelectionMask) {
522
523 QQueue<KisNodeSP> linearizedNodes;
525 [&linearizedNodes](KisNodeSP node) {
526 linearizedNodes.enqueue(node);
527 });
529 [&linearizedNodes, exactCopy, &rhs, this](KisNodeSP node) {
530 KisNodeSP refNode = linearizedNodes.dequeue();
531
532 if (exactCopy) {
533 node->setUuid(refNode->uuid());
534 }
535
536 if (rhs.m_d->isolationRootNode &&
537 rhs.m_d->isolationRootNode == refNode) {
538 m_d->isolationRootNode = node;
539 }
540
541 if (rhs.m_d->overlaySelectionMask &&
542 KisNodeSP(rhs.m_d->overlaySelectionMask) == refNode) {
543 m_d->targetOverlaySelectionMask = dynamic_cast<KisSelectionMask*>(node.data());
546 }
547
548
549 // Re-establish DefaultBounds Instances for Existing Nodes
550 // This is a workaround for copy-constructors failing to pass
551 // proper DefaultBounds due to either lacking image data on construction
552 // We should change the way "DefaultBounds" works to try to make it
553 // safer for threading races.
554 using KeyframeChannelContainer = QMap<QString, KisKeyframeChannel*>;
555 KeyframeChannelContainer keyframeChannels = node->keyframeChannels();
556 for (KeyframeChannelContainer::iterator i = keyframeChannels.begin();
557 i != keyframeChannels.end(); i++) {
558 keyframeChannels[i.key()]->setNode(node);
559 }
560 });
561 }
562
564 [](KisNodeSP node) {
565 dbgImage << "Node: " << (void *)node.data();
566 });
567
568
569
570 m_d->compositions.clear();
571
572 Q_FOREACH (KisLayerCompositionSP comp, rhs.m_d->compositions) {
573 m_d->compositions << toQShared(new KisLayerComposition(*comp, this));
574 }
575
577
578 vKisAnnotationSP newAnnotations;
579 Q_FOREACH (KisAnnotationSP annotation, rhs.m_d->annotations) {
580 newAnnotations << annotation->clone();
581 }
582 m_d->annotations = newAnnotations;
583
587
588#undef EMIT_IF_NEEDED
589}
590
591KisImage::KisImage(const KisImage& rhs, KisUndoStore *undoStore, bool exactCopy)
592 : KisNodeFacade(),
594 KisShared(),
595 m_d(new KisImagePrivate(this,
596 rhs.width(), rhs.height(),
597 rhs.colorSpace(),
598 undoStore ? undoStore : new KisDumbUndoStore(),
599 new KisImageAnimationInterface(*rhs.animationInterface(), this)))
600{
601 // make sure KisImage belongs to the GUI thread
602 moveToThread(qApp->thread());
603 connect(this, SIGNAL(sigInternalStopIsolatedModeRequested()), SLOT(stopIsolatedMode()));
604
605 copyFromImageImpl(rhs, CONSTRUCT | (exactCopy ? EXACT_COPY : 0));
606}
607
608void KisImage::aboutToAddANode(KisNode *parent, int index)
609{
611 SANITY_CHECK_LOCKED("aboutToAddANode");
612}
613
614void KisImage::nodeHasBeenAdded(KisNode *parent, int index, KisNodeAdditionFlags flags)
615{
616 KisNodeGraphListener::nodeHasBeenAdded(parent, index, flags);
617
619 QMap<QString, KisKeyframeChannel*> chans = node->keyframeChannels();
620 Q_FOREACH(KisKeyframeChannel* chan, chans.values()) {
621 chan->setNode(node);
622 this->keyframeChannelHasBeenAdded(node.data(), chan);
623 }
624 });
625
626 SANITY_CHECK_LOCKED("nodeHasBeenAdded");
627 m_d->signalRouter.emitNodeHasBeenAdded(parent, index, flags);
628}
629
630void KisImage::aboutToRemoveANode(KisNode *parent, int index)
631{
632 KisNodeSP deletedNode = parent->at(index);
633 if (!dynamic_cast<KisSelectionMask*>(deletedNode.data()) &&
634 deletedNode == m_d->isolationRootNode) {
635
637 }
638
640 QMap<QString, KisKeyframeChannel*> chans = node->keyframeChannels();
641 Q_FOREACH(KisKeyframeChannel* chan, chans.values()) {
642 this->keyframeChannelAboutToBeRemoved(node.data(), chan);
643 }
644 });
645
647
648 SANITY_CHECK_LOCKED("aboutToRemoveANode");
650}
651
657
662
664{
665 if (m_d->targetOverlaySelectionMask == mask) return;
666
668
669 struct UpdateOverlaySelectionStroke : public KisSimpleStrokeStrategy {
670 UpdateOverlaySelectionStroke(KisImageSP image)
671 : KisSimpleStrokeStrategy(QLatin1String("update-overlay-selection-mask"), kundo2_noi18n("update-overlay-selection-mask")),
672 m_image(image)
673 {
674 this->enableJob(JOB_INIT, true, KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE);
675 setClearsRedoOnStart(false);
676 }
677
678 void initStrokeCallback() override {
679 KisSelectionMaskSP oldMask = m_image->m_d->overlaySelectionMask;
680 KisSelectionMaskSP newMask = m_image->m_d->targetOverlaySelectionMask;
681 if (oldMask == newMask) return;
682
683 KIS_SAFE_ASSERT_RECOVER_RETURN(!newMask || static_cast<KisImage*>(newMask->graphListener()) == m_image);
684
685 m_image->m_d->overlaySelectionMask = newMask;
686
687 if (oldMask || newMask) {
688 m_image->m_d->rootLayer->notifyChildMaskChanged();
689 }
690
691 if (oldMask) {
692 const QRect oldMaskRect = oldMask->graphListener() ? oldMask->extent() : m_image->bounds();
693 m_image->m_d->rootLayer->setDirtyDontResetAnimationCache(oldMaskRect);
694 }
695
696 if (newMask) {
697 newMask->setDirty();
698 }
699
700 m_image->undoAdapter()->emitSelectionChanged();
701 }
702
703 private:
704 KisImageSP m_image;
705 };
706
707 KisStrokeId id = startStroke(new UpdateOverlaySelectionStroke(this));
708 endStroke(id);
709}
710
715
720
722{
723 KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask();
724 if (selectionMask) {
725 return selectionMask->selection();
726 } else {
727 return 0;
728 }
729}
730
735
740
741QString KisImage::nextLayerName(const QString &_baseName) const
742{
743 QString baseName = _baseName;
744
745 int numLayers = 0;
746 int maxLayerIndex = 0;
747 QRegularExpression numberedLayerRegexp(".* (\\d+)$");
749 [&numLayers, &maxLayerIndex, &numberedLayerRegexp] (KisNodeSP node) {
750 if (node->inherits("KisLayer")) {
751 QRegularExpressionMatch match = numberedLayerRegexp.match(node->name());
752
753 if (match.hasMatch()) {
754 maxLayerIndex = qMax(maxLayerIndex, match.captured(1).toInt());
755 }
756 numLayers++;
757 }
758 });
759
760 // special case if there is only root node
761 if (numLayers == 1) {
762 return i18nc("Name for the bottom-most layer in the layerstack", "Background");
763 }
764
765 if (baseName.isEmpty()) {
766 baseName = i18n("Paint Layer");
767 }
768
769 return QString("%1 %2").arg(baseName).arg(maxLayerIndex + 1);
770}
771
776
778{
779 return m_d->lockCount != 0;
780}
781
782void KisImage::barrierLock(bool readOnly)
783{
784 if (!locked()) {
789 m_d->lockedForReadOnly = readOnly;
790 } else {
791 m_d->lockedForReadOnly &= readOnly;
792 }
793
794 m_d->lockCount++;
795}
796
797bool KisImage::tryBarrierLock(bool readOnly)
798{
799 bool result = true;
800
801 if (!locked()) {
802 result = m_d->scheduler.tryBarrierLock();
803 m_d->lockedForReadOnly = readOnly;
804 }
805
806 if (result) {
807 m_d->lockCount++;
808 m_d->lockedForReadOnly &= readOnly;
809 }
810
811 return result;
812}
813
814bool KisImage::isIdle(bool allowLocked)
815{
816 return (allowLocked || !locked()) && m_d->scheduler.isIdle();
817}
818
830
832{
833 Q_ASSERT(locked());
834
835 if (locked()) {
836 m_d->lockCount--;
837
838 if (m_d->lockCount == 0) {
840 }
841 }
842}
843
848
853
854void KisImage::setSize(const QSize& size)
855{
856 m_d->width = size.width();
857 m_d->height = size.height();
858}
859
860void KisImage::resizeImageImpl(const QRect& newRect, bool cropLayers)
861{
862 if (newRect == bounds() && !cropLayers) return;
863
864 KUndo2MagicString actionName = cropLayers ?
865 kundo2_i18n("Crop Image") :
866 kundo2_i18n("Resize Image");
867
868 KisImageSignalVector emitSignals;
869 emitSignals << ComplexSizeChangedSignal(newRect, newRect.size());
870
871 KisCropSavedExtraData *extraData =
872 new KisCropSavedExtraData(cropLayers ?
875 newRect);
876
877 KisProcessingApplicator applicator(this, m_d->rootLayer,
880 emitSignals, actionName, extraData);
881
882 if (cropLayers || !newRect.topLeft().isNull()) {
883 KisProcessingVisitorSP visitor =
884 new KisCropProcessingVisitor(newRect, cropLayers, true);
885 applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT);
886 }
887 applicator.applyCommand(new KisImageResizeCommand(this, newRect.size()));
888 applicator.end();
889}
890
891void KisImage::resizeImage(const QRect& newRect)
892{
893 resizeImageImpl(newRect, false);
894}
895
896void KisImage::cropImage(const QRect& newRect)
897{
898 resizeImageImpl(newRect, true);
899}
900
901void KisImage::purgeUnusedData(bool isCancellable)
902{
909 struct PurgeUnusedDataStroke : public KisRunnableBasedStrokeStrategy {
910 PurgeUnusedDataStroke(KisImageSP image, bool isCancellable)
911 : KisRunnableBasedStrokeStrategy(QLatin1String("purge-unused-data"),
912 kundo2_i18n("Purge Unused Data")),
913 m_image(image),
914 m_finalCommand(new KUndo2Command(this->name()))
915
916 {
917 this->enableJob(JOB_INIT, true, KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE);
918 this->enableJob(JOB_DOSTROKE, true);
919 this->enableJob(JOB_FINISH, true, KisStrokeJobData::SEQUENTIAL);
920
921 setClearsRedoOnStart(!isCancellable);
922 setRequestsOtherStrokesToEnd(!isCancellable);
923 setCanForgetAboutMe(isCancellable);
924 }
925
926 void initStrokeCallback() override
927 {
928 KisPaintDeviceList paintDevicesList;
929 KisPaintDeviceList projectionsList;
931
933 [&paintDevicesList, &projectionsList, this](KisNodeSP node) {
934 KisPaintDeviceList deviceList = node->getLodCapableDevices();
935
936 Q_FOREACH (KisPaintDeviceSP dev, deviceList) {
937 if (!dev) continue;
938
939 // we do **not** strip paint devices in the forgettable
940 // mode, since we should handle transactions for them
941 if (dev == node->paintDevice() && !canForgetAboutMe()) {
942 paintDevicesList << dev;
943 } else {
944 projectionsList << dev;
945 }
946 }
947 });
948
951 KritaUtils::makeContainerUnique(paintDevicesList);
952 KritaUtils::makeContainerUnique(projectionsList);
953
954 Q_FOREACH(KisPaintDeviceSP dev, paintDevicesList) {
955 projectionsList.removeAll(dev);
956
957 // all transactions will be linked to the final command via the
958 // parent-child relationship
959 m_transactions.emplace_back(dev, m_finalCommand.get(), -1, nullptr, KisTransaction::None);
960 }
961
962 // now, when the transactions are started, we can merge the two lists
963 paintDevicesList << projectionsList;
964 projectionsList.clear();
965
966 Q_FOREACH (KisPaintDeviceSP device, paintDevicesList) {
968 [device] () {
969 const_cast<KisPaintDevice*>(device.data())->purgeDefaultPixels();
970 });
971 }
972
973 addMutatedJobs(jobsData);
974 }
975
976 void finishStrokeCallback() override {
977 for (auto it = m_transactions.begin(); it != m_transactions.end(); ++it) {
978 std::unique_ptr<KUndo2Command> cmd(it->endAndTake());
979
980 // verify the transaction command is linked to m_finalCommand,
981 // if not, just delete on return
982 KIS_SAFE_ASSERT_RECOVER(cmd->hasParent()) { continue; }
983
984 // if has a parent, release...
985 (void)cmd.release();
986 }
987
988 m_transactions.clear();
989
990 m_finalCommand->redo();
991 m_image->postExecutionUndoAdapter()->addCommand(toQShared(m_finalCommand.release()));
992
993 // now reset the thumbnail generation limitation
995 [](KisNodeSP node) {
996 if (node->preferredThumbnailBoundsMode() != KisThumbnailBoundsMode::Precise) {
997 node->setPreferredThumbnailBoundsMode(KisThumbnailBoundsMode::Precise);
998 }
999 });
1000 }
1001
1002 private:
1003 KisImageSP m_image;
1004 std::unique_ptr<KUndo2Command> m_finalCommand;
1005 std::vector<KisTransaction> m_transactions;
1006 };
1007
1008 KisStrokeId id = startStroke(new PurgeUnusedDataStroke(this, isCancellable));
1009 endStroke(id);
1010}
1011
1012void KisImage::cropNode(KisNodeSP node, const QRect& newRect, const bool activeFrameOnly)
1013{
1014 const bool isLayer = qobject_cast<KisLayer*>(node.data());
1015 KUndo2MagicString actionName = isLayer ?
1016 kundo2_i18n("Crop Layer") :
1017 kundo2_i18n("Crop Mask");
1018
1019 KisImageSignalVector emitSignals;
1020
1021 KisCropSavedExtraData *extraData =
1023 newRect, node);
1024
1025 KisProcessingApplicator applicator(this, node,
1027 emitSignals, actionName, extraData);
1028
1029 KisProcessingVisitorSP visitor =
1030 new KisCropProcessingVisitor(newRect, true, false);
1031
1032 if (node->isAnimated() && activeFrameOnly) {
1033 // Crop active frame..
1034 applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT);
1035 } else {
1036 // Crop all frames..
1038 }
1039 applicator.end();
1040}
1041
1042void KisImage::scaleImage(const QSize &size, qreal xres, qreal yres, KisFilterStrategy *filterStrategy)
1043{
1044 bool resolutionChanged = !qFuzzyCompare(xRes(), xres) || !qFuzzyCompare(yRes(), yres);
1045 bool sizeChanged = size != this->size();
1046
1047 if (!resolutionChanged && !sizeChanged) return;
1048
1049 KisImageSignalVector emitSignals;
1050 if (resolutionChanged) emitSignals << ResolutionChangedSignal;
1051 if (sizeChanged) emitSignals << ComplexSizeChangedSignal(bounds(), size);
1052
1053 KUndo2MagicString actionName = sizeChanged ?
1054 kundo2_i18n("Scale Image") :
1055 kundo2_i18n("Change Image Resolution");
1056
1057 KisProcessingApplicator::ProcessingFlags signalFlags =
1058 (resolutionChanged || sizeChanged) ?
1061
1062 KisProcessingApplicator applicator(this, m_d->rootLayer,
1064 emitSignals, actionName);
1065
1066 qreal sx = qreal(size.width()) / this->size().width();
1067 qreal sy = qreal(size.height()) / this->size().height();
1068
1069 QTransform shapesCorrection;
1070
1071 if (resolutionChanged) {
1072 shapesCorrection = QTransform::fromScale(xRes() / xres, yRes() / yres);
1073 }
1074
1075 KisProcessingVisitorSP visitor =
1077 0, 0,
1078 0,
1079 0, 0,
1080 filterStrategy,
1081 shapesCorrection);
1082
1084
1085 if (resolutionChanged) {
1086 KUndo2Command *parent =
1088 new KisImageSetResolutionCommand(this, xres, yres, parent);
1089 applicator.applyCommand(parent);
1090 }
1091
1092 if (sizeChanged) {
1093 applicator.applyCommand(new KisImageResizeCommand(this, size));
1094 }
1095
1096 applicator.end();
1097}
1098
1099void KisImage::scaleNode(KisNodeSP node, const QPointF &center, qreal scaleX, qreal scaleY, KisFilterStrategy *filterStrategy, KisSelectionSP selection)
1100{
1101 scaleNodes(KisNodeList{node}, center, scaleX, scaleY, filterStrategy, selection);
1102}
1103void KisImage::scaleNodes(KisNodeList nodes, const QPointF &center, qreal scaleX, qreal scaleY, KisFilterStrategy *filterStrategy, KisSelectionSP selection)
1104{
1105 KUndo2MagicString actionName(kundo2_i18np("Scale Layer", "Scale %1 Layers", nodes.size()));
1106 KisImageSignalVector emitSignals;
1107
1108 QPointF offset;
1109 {
1110 KisTransformWorker worker(0,
1111 scaleX, scaleY,
1112 0, 0,
1113 0.0,
1114 0, 0, 0, 0);
1115 QTransform transform = worker.transform();
1116
1117 offset = center - transform.map(center);
1118 }
1119
1120 KisProcessingApplicator applicator(this, nodes,
1122 emitSignals, actionName);
1123
1125 new KisTransformProcessingVisitor(scaleX, scaleY,
1126 0, 0,
1127 0,
1128 offset.x(), offset.y(),
1129 filterStrategy);
1130
1131 visitor->setSelection(selection);
1132
1133 if (selection) {
1134 applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT);
1135 } else {
1137 }
1138
1139 applicator.end();
1140}
1141
1143 KisNodeSP rootNode,
1144 double radians,
1145 bool resizeImage,
1146 KisSelectionSP selection)
1147{
1148 rotateImpl(actionName, KisNodeList{rootNode}, radians, resizeImage, selection);
1149}
1151 KisNodeList nodes,
1152 double radians,
1153 bool resizeImage,
1154 KisSelectionSP selection)
1155{
1156 // we can either transform (and resize) the whole image or
1157 // transform a selection, we cannot do both at the same time
1158 KIS_SAFE_ASSERT_RECOVER(!(bool(selection) && resizeImage)) {
1159 selection = 0;
1160 }
1161
1162 QRect baseBounds;
1163 if (resizeImage) {
1164 baseBounds = bounds();
1165 }
1166 else if (selection) {
1167 baseBounds = selection->selectedExactRect();
1168 }
1169 else {
1170 Q_FOREACH(KisNodeSP node, nodes) {
1171 baseBounds = baseBounds.united(node->exactBounds());
1172 }
1173 }
1174
1175 QPointF offset;
1176 QSize newSize;
1177
1178 {
1179 KisTransformWorker worker(0,
1180 1.0, 1.0,
1181 0, 0,
1182 radians,
1183 0, 0, 0, 0);
1184 QTransform transform = worker.transform();
1185
1186 if (resizeImage) {
1187 QRect newRect = transform.mapRect(baseBounds);
1188 newSize = newRect.size();
1189 offset = -newRect.topLeft();
1190 }
1191 else {
1192 QPointF origin = QRectF(baseBounds).center();
1193
1194 newSize = size();
1195 offset = -(transform.map(origin) - origin);
1196 }
1197 }
1198
1199 bool sizeChanged = resizeImage &&
1200 (newSize.width() != baseBounds.width() ||
1201 newSize.height() != baseBounds.height());
1202
1203 // These signals will be emitted after processing is done
1204 KisImageSignalVector emitSignals;
1205 if (sizeChanged) emitSignals << ComplexSizeChangedSignal(baseBounds, newSize);
1206
1207 // These flags determine whether updates are transferred to the UI during processing
1208 KisProcessingApplicator::ProcessingFlags signalFlags =
1209 sizeChanged ?
1212
1213
1214 KisProcessingApplicator applicator(this, nodes,
1216 emitSignals, actionName);
1217
1219
1221 new KisTransformProcessingVisitor(1.0, 1.0, 0.0, 0.0,
1222 radians,
1223 offset.x(), offset.y(),
1224 filter);
1225 if (selection) {
1226 visitor->setSelection(selection);
1227 }
1228
1229 if (selection) {
1230 applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT);
1231 } else {
1233 }
1234
1235 if (sizeChanged) {
1236 applicator.applyCommand(new KisImageResizeCommand(this, newSize));
1237 }
1238 applicator.end();
1239}
1240
1241
1242void KisImage::rotateImage(double radians)
1243{
1244 rotateImpl(kundo2_i18n("Rotate Image"), root(), radians, true, 0);
1245}
1246
1247void KisImage::rotateNode(KisNodeSP node, double radians, KisSelectionSP selection)
1248{
1249 rotateNodes(KisNodeList{node}, radians, selection);
1250}
1251void KisImage::rotateNodes(KisNodeList nodes, double radians, KisSelectionSP selection)
1252{
1253 if (nodes.size() == 1 && nodes[0]->inherits("KisMask")) {
1254 rotateImpl(kundo2_i18n("Rotate Mask"), nodes, radians, false, selection);
1255 }
1256 else {
1257 rotateImpl(kundo2_i18np("Rotate Layer", "Rotate %1 Layers", nodes.size()), nodes, radians, false, selection);
1258 }
1259}
1260
1262 KisNodeSP rootNode,
1263 bool resizeImage,
1264 double angleX, double angleY,
1265 KisSelectionSP selection)
1266{
1267 shearImpl(actionName, KisNodeList{rootNode}, resizeImage, angleX, angleY, selection);
1268}
1270 KisNodeList nodes,
1271 bool resizeImage,
1272 double angleX, double angleY,
1273 KisSelectionSP selection)
1274{
1275 QRect baseBounds;
1276 if (resizeImage) {
1277 baseBounds = bounds();
1278 }
1279 else if (selection) {
1280 baseBounds = selection->selectedExactRect();
1281 }
1282 else {
1283 Q_FOREACH(KisNodeSP node, nodes) {
1284 baseBounds = baseBounds.united(node->exactBounds());
1285 }
1286 }
1287 const QPointF origin = QRectF(baseBounds).center();
1288
1289 //angleX, angleY are in degrees
1290 const qreal pi = 3.1415926535897932385;
1291 const qreal deg2rad = pi / 180.0;
1292
1293 qreal tanX = tan(angleX * deg2rad);
1294 qreal tanY = tan(angleY * deg2rad);
1295
1296 QPointF offset;
1297 QSize newSize;
1298
1299 {
1300 KisTransformWorker worker(0,
1301 1.0, 1.0,
1302 tanX, tanY,
1303 0,
1304 0, 0, 0, 0);
1305
1306 QRect newRect = worker.transform().mapRect(baseBounds);
1307 newSize = newRect.size();
1308 if (resizeImage) offset = -newRect.topLeft();
1309 else offset = origin - worker.transform().map(origin);
1310 }
1311
1312 if (newSize == baseBounds.size()) return;
1313
1314 KisImageSignalVector emitSignals;
1315 if (resizeImage) emitSignals << ComplexSizeChangedSignal(baseBounds, newSize);
1316
1317 KisProcessingApplicator::ProcessingFlags signalFlags =
1320
1321 KisProcessingApplicator applicator(this, nodes,
1322 signalFlags,
1323 emitSignals, actionName);
1324
1326
1328 new KisTransformProcessingVisitor(1.0, 1.0,
1329 tanX, tanY,
1330 0,
1331 offset.x(), offset.y(),
1332 filter);
1333
1334 if (selection) {
1335 visitor->setSelection(selection);
1336 }
1337
1338 if (selection) {
1339 applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT);
1340 } else {
1342 }
1343
1344 if (resizeImage) {
1345 applicator.applyCommand(new KisImageResizeCommand(this, newSize));
1346 }
1347
1348 applicator.end();
1349}
1350
1351void KisImage::shearNode(KisNodeSP node, double angleX, double angleY, KisSelectionSP selection)
1352{
1353 shearNodes(KisNodeList{node}, angleX, angleY, selection);
1354}
1355void KisImage::shearNodes(KisNodeList nodes, double angleX, double angleY, KisSelectionSP selection)
1356{
1357 if (nodes.size() == 1 && nodes[0]->inherits("KisMask")) {
1358 shearImpl(kundo2_i18n("Shear Mask"), nodes, false,
1359 angleX, angleY, selection);
1360 }
1361 else {
1362 shearImpl(kundo2_i18np("Shear Layer", "Shear %1 Layers", nodes.size()), nodes, false,
1363 angleX, angleY, selection);
1364 }
1365}
1366
1367void KisImage::shear(double angleX, double angleY)
1368{
1369 shearImpl(kundo2_i18n("Shear Image"), m_d->rootLayer, true,
1370 angleX, angleY, 0);
1371}
1372
1374 const KoColorSpace *dstColorSpace,
1376 KoColorConversionTransformation::ConversionFlags conversionFlags)
1377{
1378 if (!node->projectionLeaf()->isLayer()) return;
1379 // must not be an image root, use convertImageColorSpace() for that:
1380 KIS_SAFE_ASSERT_RECOVER_RETURN(!node->image() || (node.data() != node->image()->rootLayer().data()));
1381
1382 const KoColorSpace *srcColorSpace = node->colorSpace();
1383
1384 if (!dstColorSpace || *srcColorSpace == *dstColorSpace) return;
1385
1386 KUndo2MagicString actionName =
1387 kundo2_i18n("Convert Layer Color Space");
1388
1389 KisImageSignalVector emitSignals;
1390
1391 KisProcessingApplicator applicator(this, node,
1393 emitSignals, actionName);
1394
1395 applicator.applyVisitor(
1397 srcColorSpace, dstColorSpace,
1398 renderingIntent, conversionFlags),
1400
1401 applicator.end();
1402}
1403
1405{
1407 State initialState, KUndo2Command *parent = 0)
1408 : KisCommandUtils::FlipFlopCommand(initialState, parent),
1409 m_cs(cs),
1410 m_image(image)
1411 {
1412 }
1413
1414 void partA() override {
1415 KisImageSP image = m_image;
1416
1417 if (image) {
1419 }
1420 }
1421
1422private:
1425};
1426
1428 bool convertLayers,
1430 KoColorConversionTransformation::ConversionFlags conversionFlags)
1431{
1432 const KoColorSpace *srcColorSpace = this->colorSpace;
1433
1434 if (!dstColorSpace || *srcColorSpace == *dstColorSpace) return;
1435
1436 const KUndo2MagicString actionName =
1437 convertLayers ?
1438 kundo2_i18n("Convert Image Color Space") :
1439 kundo2_i18n("Convert Projection Color Space");
1440
1441 KisImageSignalVector emitSignals;
1442 emitSignals << ColorSpaceChangedSignal;
1443
1444 KisProcessingApplicator::ProcessingFlags flags = KisProcessingApplicator::NO_UI_UPDATES;
1445 if (convertLayers) {
1447 }
1448
1449 KisProcessingApplicator applicator(q, this->rootLayer,
1450 flags,
1451 emitSignals, actionName);
1452
1453 applicator.applyCommand(
1455 KisImageWSP(q),
1458
1459 applicator.applyVisitor(
1461 srcColorSpace, dstColorSpace,
1462 renderingIntent, conversionFlags),
1464
1465 applicator.applyCommand(
1467 KisImageWSP(q),
1470
1471
1472 applicator.end();
1473}
1474
1477 KoColorConversionTransformation::ConversionFlags conversionFlags)
1478{
1479 m_d->convertImageColorSpaceImpl(dstColorSpace, true, renderingIntent, conversionFlags);
1480}
1481
1488
1490{
1491 const KUndo2MagicString actionName = kundo2_i18n("Unify Layers Color Space");
1492
1493 KisImageSignalVector emitSignals;
1494
1495 KisProcessingApplicator::ProcessingFlags flags =
1497
1498 KisProcessingApplicator applicator(this, m_d->rootLayer,
1499 flags,
1500 emitSignals, actionName);
1501
1502 // src and dst color spaces coincide, since we should just unify
1503 // all our layers
1504 applicator.applyVisitor(
1510
1511 applicator.end();
1512}
1513
1515{
1516 const KoColorSpace *srcColorSpace = node->colorSpace();
1517
1518 if (!node->projectionLeaf()->isLayer()) return false;
1519 if (!profile || *srcColorSpace->profile() == *profile) return false;
1520
1521 KUndo2MagicString actionName = kundo2_i18n("Assign Profile to Layer");
1522
1523 KisImageSignalVector emitSignals;
1524
1525 const KoColorSpace *dstColorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile);
1526 if (!dstColorSpace) return false;
1527
1528 KisProcessingApplicator applicator(this, node,
1531 emitSignals, actionName);
1532
1533 applicator.applyVisitor(
1535 srcColorSpace, dstColorSpace),
1537
1538 applicator.end();
1539
1540 return true;
1541}
1542
1543
1544bool KisImage::assignImageProfile(const KoColorProfile *profile, bool blockAllUpdates)
1545{
1546 if (!profile) return false;
1547
1548 const KoColorSpace *srcColorSpace = m_d->colorSpace;
1549 bool imageProfileIsSame = *srcColorSpace->profile() == *profile;
1550
1551 imageProfileIsSame &=
1553 [profile] (KisNodeSP node) {
1554 return *node->colorSpace()->profile() != *profile;
1555 });
1556
1557 if (imageProfileIsSame) {
1558 dbgImage << "Trying to set the same image profile again" << ppVar(srcColorSpace->profile()->name()) << ppVar(profile->name());
1559 return true;
1560 }
1561
1562 KUndo2MagicString actionName = kundo2_i18n("Assign Profile");
1563
1564 KisImageSignalVector emitSignals;
1565 emitSignals << ProfileChangedSignal;
1566
1567 const KoColorSpace *dstColorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile);
1568 if (!dstColorSpace) return false;
1569
1570 KisProcessingApplicator applicator(this, m_d->rootLayer,
1572 (!blockAllUpdates ?
1575 emitSignals, actionName);
1576
1577 applicator.applyCommand(
1579 KisImageWSP(this),
1582
1583 applicator.applyVisitor(
1585 srcColorSpace, dstColorSpace),
1587
1588 applicator.applyCommand(
1590 KisImageWSP(this),
1593
1594
1595 applicator.end();
1596
1597 return true;
1598}
1599
1605
1607{
1608 return m_d->colorSpace;
1609}
1610
1612{
1613 return colorSpace()->profile();
1614}
1615
1616double KisImage::xRes() const
1617{
1618 return m_d->xres;
1619}
1620
1621double KisImage::yRes() const
1622{
1623 return m_d->yres;
1624}
1625
1626void KisImage::setResolution(double xres, double yres)
1627{
1628 if (xres > 0) {
1629 m_d->xres = xres;
1630 } else {
1631 qWarning() << "WARNING: Ignoring attempt to set image x resolution <= 0 (" << xres << ")!";
1632 }
1633
1634 if (yres > 0) {
1635 m_d->yres = yres;
1636 } else {
1637 qWarning() << "WARNING: Ignoring attempt to set image y resolution <= 0 (" << yres << ")!";
1638 }
1639}
1640
1641QPointF KisImage::documentToPixel(const QPointF &documentCoord) const
1642{
1643 return QPointF(documentCoord.x() * xRes(), documentCoord.y() * yRes());
1644}
1645
1646QPoint KisImage::documentToImagePixelFloored(const QPointF &documentCoord) const
1647{
1648 QPointF pixelCoord = documentToPixel(documentCoord);
1649 return QPoint(qFloor(pixelCoord.x()), qFloor(pixelCoord.y()));
1650}
1651
1652QRectF KisImage::documentToPixel(const QRectF &documentRect) const
1653{
1654 return QRectF(documentToPixel(documentRect.topLeft()), documentToPixel(documentRect.bottomRight()));
1655}
1656
1657QPointF KisImage::pixelToDocument(const QPointF &pixelCoord) const
1658{
1659 return QPointF(pixelCoord.x() / xRes(), pixelCoord.y() / yRes());
1660}
1661
1662QPointF KisImage::pixelToDocument(const QPoint &pixelCoord) const
1663{
1664 return QPointF((pixelCoord.x() + 0.5) / xRes(), (pixelCoord.y() + 0.5) / yRes());
1665}
1666
1667QRectF KisImage::pixelToDocument(const QRectF &pixelCoord) const
1668{
1669 return QRectF(pixelToDocument(pixelCoord.topLeft()), pixelToDocument(pixelCoord.bottomRight()));
1670}
1671
1672qint32 KisImage::width() const
1673{
1674 return m_d->width;
1675}
1676
1677qint32 KisImage::height() const
1678{
1679 return m_d->height;
1680}
1681
1683{
1684 Q_ASSERT(m_d->rootLayer);
1685 return m_d->rootLayer;
1686}
1687
1689{
1690 if (m_d->isolationRootNode) {
1691 return m_d->isolationRootNode->projection();
1692 }
1693
1694 Q_ASSERT(m_d->rootLayer);
1696 Q_ASSERT(projection);
1697 return projection;
1698}
1699
1700qint32 KisImage::nlayers() const
1701{
1702 QStringList list;
1703 list << "KisLayer";
1704
1705 KisCountVisitor visitor(list, KoProperties());
1706 m_d->rootLayer->accept(visitor);
1707 return visitor.count();
1708}
1709
1711{
1712 QStringList list;
1713 list << "KisLayer";
1714 KoProperties properties;
1715 properties.setProperty("visible", false);
1716 KisCountVisitor visitor(list, properties);
1717 m_d->rootLayer->accept(visitor);
1718
1719 return visitor.count();
1720}
1721
1723{
1724 const QStringList list = {"KisLayer"};
1725
1726 KoProperties koProperties;
1727 KisCountVisitor visitor(list, koProperties);
1728 const QList<KisNodeSP> childNodes = m_d->rootLayer->childNodes(list, koProperties);
1729 for (KisNodeSP childNode: childNodes) {
1730 childNode->accept(visitor);
1731 }
1732 return visitor.count();
1733}
1734
1736{
1737 KisLayerUtils::flattenImage(this, activeNode);
1738}
1739
1741{
1742 KisLayerUtils::mergeMultipleNodes(this, mergedNodes, putAfter);
1743}
1744
1746{
1747 KisLayerUtils::mergeDown(this, layer, strategy);
1748}
1749
1751{
1752 KisLayerUtils::flattenLayer(this, layer);
1753}
1754
1760
1761QImage KisImage::convertToQImage(QRect imageRect,
1762 const KoColorProfile * profile)
1763{
1764 qint32 x;
1765 qint32 y;
1766 qint32 w;
1767 qint32 h;
1768 imageRect.getRect(&x, &y, &w, &h);
1769 return convertToQImage(x, y, w, h, profile);
1770}
1771
1773 qint32 y,
1774 qint32 w,
1775 qint32 h,
1776 const KoColorProfile * profile)
1777{
1779 if (!dev) return QImage();
1780 QImage image = dev->convertToQImage(const_cast<KoColorProfile*>(profile), x, y, w, h,
1783
1784 return image;
1785}
1786
1787
1788
1789QImage KisImage::convertToQImage(const QSize& scaledImageSize, const KoColorProfile *profile)
1790{
1791 if (scaledImageSize.isEmpty()) {
1792 return QImage();
1793 }
1794
1796 KisPainter gc;
1797 gc.copyAreaOptimized(QPoint(0, 0), projection(), dev, bounds());
1798 gc.end();
1799 double scaleX = qreal(scaledImageSize.width()) / width();
1800 double scaleY = qreal(scaledImageSize.height()) / height();
1801
1802
1803 if (scaleX < 1.0/256 || scaleY < 1.0/256) {
1804 // quick checking if we're not trying to scale too much
1805 // convertToQImage uses KisFixedPoint values, which means that the scale cannot be smaller than 1/2^8
1806 // BUG:432182
1807 // FIXME: would be best to extend KisFixedPoint instead
1808 return convertToQImage(size(), profile).scaled(scaledImageSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
1809 }
1810
1811 KoDummyUpdaterHolder updaterHolder;
1812 QPointer<KoUpdater> updater = updaterHolder.updater();
1813
1814 KisTransformWorker worker(dev, scaleX, scaleY, 0.0, 0.0, 0.0, 0.0, 0.0, updater, KisFilterStrategyRegistry::instance()->value("Bicubic"));
1815 worker.run();
1816
1817 return dev->convertToQImage(profile);
1818}
1823
1824QRect KisImage::bounds() const
1825{
1826 return QRect(0, 0, width(), height());
1827}
1828
1830{
1831 QRect boundRect = bounds();
1832
1833 const int lod = currentLevelOfDetail();
1834 if (lod > 0) {
1835 KisLodTransform t(lod);
1836 boundRect = t.map(boundRect);
1837 }
1838
1839 return boundRect;
1840}
1841
1849
1851{
1852 return m_d->undoStore->presentCommand();
1853}
1854
1856{
1857 disconnect(m_d->undoStore.data(), SIGNAL(historyStateChanged()), &m_d->signalRouter, SLOT(emitImageModifiedNotification()));
1858
1861 m_d->undoStore.reset(undoStore);
1862
1863 connect(m_d->undoStore.data(), SIGNAL(historyStateChanged()), &m_d->signalRouter, SLOT(emitImageModifiedNotification()));
1864
1865}
1866
1868{
1869 return m_d->undoStore.data();
1870}
1871
1873{
1874 return &m_d->legacyUndoAdapter;
1875}
1876
1882
1891
1893{
1895
1897
1898 if (m_d->rootLayer) {
1900 m_d->rootLayer->setImage(0);
1901 m_d->rootLayer->disconnect();
1902
1903 KisPaintDeviceSP original = m_d->rootLayer->original();
1905 }
1906
1908 m_d->rootLayer->disconnect();
1910 m_d->rootLayer->setImage(this);
1911
1913 this->setDefaultProjectionColor(defaultProjectionColor);
1914}
1915
1917{
1918 // Find the icc annotation, if there is one
1919 vKisAnnotationSP_it it = m_d->annotations.begin();
1920 while (it != m_d->annotations.end()) {
1921 if ((*it)->type() == annotation->type()) {
1922 *it = annotation;
1924 return;
1925 }
1926 ++it;
1927 }
1928 m_d->annotations.push_back(annotation);
1930}
1931
1933{
1934 vKisAnnotationSP_it it = m_d->annotations.begin();
1935 while (it != m_d->annotations.end()) {
1936 if ((*it) && (*it)->type() == type) {
1937 return *it;
1938 }
1939 else if (!*it) {
1940 qWarning() << "Skipping deleted annotation";
1941 }
1942 ++it;
1943 }
1944 return KisAnnotationSP(0);
1945}
1946
1947void KisImage::removeAnnotation(const QString& type)
1948{
1949 vKisAnnotationSP_it it = m_d->annotations.begin();
1950 while (it != m_d->annotations.end()) {
1951 if ((*it)->type() == type) {
1952 m_d->annotations.erase(it);
1954 return;
1955 }
1956 ++it;
1957 }
1958}
1959
1964
1969
1974
1979
1987
1989{
1997 if (strokeStrategy->requestsOtherStrokesToEnd()) {
1999 }
2000
2001 return m_d->scheduler.startStroke(strokeStrategy);
2002}
2003
2005{
2006 KisImageConfig imageConfig(true);
2007 int patchWidth = imageConfig.updatePatchWidth();
2008 int patchHeight = imageConfig.updatePatchHeight();
2009
2010 for (int y = 0; y < rc.height(); y += patchHeight) {
2011 for (int x = 0; x < rc.width(); x += patchWidth) {
2012 QRect patchRect(x, y, patchWidth, patchHeight);
2013 patchRect &= rc;
2014
2015 KritaUtils::addJobConcurrent(jobs, std::bind(&KisImage::notifyProjectionUpdated, q, patchRect));
2016 }
2017 }
2018}
2019
2020bool KisImage::startIsolatedMode(KisNodeSP node, bool isolateLayer, bool isolateGroup)
2021{
2022 m_d->isolateLayer = isolateLayer;
2023 m_d->isolateGroup = isolateGroup;
2024 if ((isolateLayer || isolateGroup) == false) return false;
2025
2030 if (!node->projection()) return false;
2031
2032 struct StartIsolatedModeStroke : public KisRunnableBasedStrokeStrategy {
2033 StartIsolatedModeStroke(KisNodeSP node, KisImageSP image, bool isolateLayer, bool isolateGroup)
2034 : KisRunnableBasedStrokeStrategy(QLatin1String("start-isolated-mode"),
2035 kundo2_noi18n("start-isolated-mode")),
2036 m_newRoot(node),
2037 m_image(image),
2038 m_isolateLayer(isolateLayer),
2039 m_isolateGroup(isolateGroup)
2040 {
2041 this->enableJob(JOB_INIT, true, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE);
2042 this->enableJob(JOB_DOSTROKE, true);
2043 this->enableJob(JOB_FINISH, true, KisStrokeJobData::BARRIER);
2044 setClearsRedoOnStart(false);
2045 }
2046
2047 void initStrokeCallback() override {
2048 if (m_isolateLayer == false && m_isolateGroup == true) {
2049 // Isolate parent node unless node is the root note.
2050 m_newRoot = m_newRoot->parent() ? m_newRoot->parent() : m_newRoot;
2051 }
2052 // pass-though node don't have any projection prepared, so we should
2053 // explicitly regenerate it before activating isolated mode.
2054 m_newRoot->projectionLeaf()->explicitlyRegeneratePassThroughProjection();
2055 m_prevRoot = m_image->m_d->isolationRootNode;
2056
2057 const bool beforeVisibility = m_newRoot->projectionLeaf()->visible();
2058 const bool prevRootBeforeVisibility = m_prevRoot ? m_prevRoot->projectionLeaf()->visible() : false;
2059
2060 m_image->m_d->isolationRootNode = m_newRoot;
2061 Q_EMIT m_image->sigIsolatedModeChanged();
2062
2063 const bool afterVisibility = m_newRoot->projectionLeaf()->visible();
2064 const bool prevRootAfterVisibility = m_prevRoot ? m_prevRoot->projectionLeaf()->visible() : false;
2065
2066 m_newRootNeedsFullRefresh = beforeVisibility != afterVisibility;
2067 m_prevRootNeedsFullRefresh = prevRootBeforeVisibility != prevRootAfterVisibility;
2068 }
2069
2070 void finishStrokeCallback() override {
2071 // the GUI uses our thread to do the color space conversion so we
2072 // need to Q_EMIT this signal in multiple threads
2073
2074 if (m_prevRoot && m_prevRootNeedsFullRefresh) {
2075 m_image->refreshGraphAsync(m_prevRoot);
2076 }
2077
2078 if (m_newRootNeedsFullRefresh) {
2079 m_image->refreshGraphAsync(m_newRoot);
2080 }
2081
2082 if (!m_prevRootNeedsFullRefresh && !m_newRootNeedsFullRefresh) {
2084 m_image->m_d->notifyProjectionUpdatedInPatches(m_image->bounds(), jobs);
2085 this->runnableJobsInterface()->addRunnableJobs(jobs);
2086 }
2087
2088 m_image->invalidateAllFrames();
2089 }
2090
2091 private:
2092 KisNodeSP m_newRoot;
2093 KisNodeSP m_prevRoot;
2094 KisImageSP m_image;
2095 bool m_newRootNeedsFullRefresh = false;
2096 bool m_prevRootNeedsFullRefresh = false;
2097
2098 bool m_isolateLayer;
2099 bool m_isolateGroup;
2100 };
2101
2102 KisStrokeId id = startStroke(new StartIsolatedModeStroke(node, this, isolateLayer, isolateGroup));
2103 endStroke(id);
2104
2105 return true;
2106}
2107
2108std::optional<KisRelativeContentLightLevelInformation> KisImage::relativeContentLightLevelInformation() const
2109{
2111}
2112
2113void KisImage::setRelativeContentLightLevelInformation(const std::optional<KisRelativeContentLightLevelInformation> clli)
2114{
2116}
2117
2118std::optional<KisColorVolumeInformation> KisImage::colorVolumeInformation() const
2119{
2121}
2122
2123void KisImage::setColorVolumeInformation(const std::optional<KisColorVolumeInformation> cvi)
2124{
2126}
2127
2128std::optional<double> KisImage::hdrReferenceWhiteLightLevel() const
2129{
2131}
2132
2133void KisImage::setHdrReferenceWhiteLightLevel(const std::optional<double> value)
2134{
2136 qWarning() << "Cannot set the diffuse white metadata on the image: profile provides a different inconsistent value";
2137 return;
2138 }
2139
2141}
2142
2144{
2145 if (!m_d->isolationRootNode) return;
2146
2147 struct StopIsolatedModeStroke : public KisRunnableBasedStrokeStrategy {
2148 StopIsolatedModeStroke(KisImageSP image)
2149 : KisRunnableBasedStrokeStrategy(QLatin1String("stop-isolated-mode"), kundo2_noi18n("stop-isolated-mode")),
2150 m_image(image),
2151 m_oldRootNode(nullptr),
2152 m_oldNodeNeedsRefresh(false)
2153 {
2154 this->enableJob(JOB_INIT);
2155 this->enableJob(JOB_DOSTROKE, true);
2156 this->enableJob(JOB_FINISH, true, KisStrokeJobData::BARRIER);
2157 setClearsRedoOnStart(false);
2158 }
2159
2160 void initStrokeCallback() override {
2161 if (!m_image->m_d->isolationRootNode) return;
2162
2163 m_oldRootNode = m_image->m_d->isolationRootNode;
2164
2165 const bool beforeVisibility = m_oldRootNode->projectionLeaf()->visible();
2166 m_image->m_d->isolationRootNode = 0;
2167 m_image->m_d->isolateLayer = false;
2168 m_image->m_d->isolateGroup = false;
2169 Q_EMIT m_image->sigIsolatedModeChanged();
2170 const bool afterVisibility = m_oldRootNode->projectionLeaf()->visible();
2171
2172 m_oldNodeNeedsRefresh = (beforeVisibility != afterVisibility);
2173 }
2174
2175 void finishStrokeCallback() override {
2176
2177 m_image->invalidateAllFrames();
2178
2179 if (m_oldNodeNeedsRefresh){
2180 m_oldRootNode->setDirty(m_image->bounds());
2181 } else {
2182 // TODO: Substitute notifyProjectionUpdated() with this code
2183 // when update optimization is implemented
2184 //
2185 // QRect updateRect = bounds() | oldRootNode->extent();
2186 //oldRootNode->setDirty(updateRect);
2187
2189 m_image->m_d->notifyProjectionUpdatedInPatches(m_image->bounds(), jobs);
2190 this->runnableJobsInterface()->addRunnableJobs(jobs);
2191 }
2192 }
2193
2194 private:
2195 KisImageSP m_image;
2196 KisNodeSP m_oldRootNode;
2197 bool m_oldNodeNeedsRefresh;
2198 };
2199
2200 KisStrokeId id = startStroke(new StopIsolatedModeStroke(this));
2201 endStroke(id);
2202}
2203
2207
2209{
2210 return m_d->isolateLayer;
2211}
2212
2214{
2215 return m_d->isolateGroup;
2216}
2217
2223
2228
2230{
2231 return m_d->scheduler.cancelStroke(id);
2232}
2233
2235{
2236 return scheduler.tryCancelCurrentStrokeAsync();
2237}
2238
2243
2248
2255
2260
2266
2271
2273{
2279 refreshGraphAsync(0, bounds(), QRect());
2280 waitForDone();
2281}
2282
2283void KisImage::refreshGraphAsync(KisNodeSP root, const QVector<QRect> &rects, const QRect &cropRect, KisProjectionUpdateFlags flags)
2284{
2285 if (!root) root = m_d->rootLayer;
2286
2287 QVector<QRect> requestedRects = rects;
2288
2289 KisGroupLayer *group = dynamic_cast<KisGroupLayer*>(root.data());
2290 if (group && group->passThroughMode()) {
2301 QVector<QRect> changeRects = requestedRects;
2302 KisProjectionLeafSP leaf = root->projectionLeaf()->nextSibling();
2303 while (leaf) {
2304 if (leaf->shouldBeRendered()) {
2305 for (auto it = changeRects.begin(); it != changeRects.end(); ++it) {
2306 *it = leaf->projectionPlane()->changeRect(*it, leaf->node() == root ? KisNode::N_FILTHY : KisNode::N_ABOVE_FILTHY);
2307 }
2308 }
2309
2310 leaf = leaf->nextSibling();
2311 }
2312
2313 std::swap(requestedRects, changeRects);
2314 root = group->parent();
2315
2317 }
2318
2323 for (auto it = m_d->projectionUpdatesFilters.rbegin();
2324 it != m_d->projectionUpdatesFilters.rend();
2325 ++it) {
2326
2327 KIS_SAFE_ASSERT_RECOVER(*it) { continue; }
2328
2329 if ((*it)->filterRefreshGraph(this, root.data(), requestedRects, cropRect, flags)) {
2330 return;
2331 }
2332 }
2333
2334 if (!flags.testFlag(KisProjectionUpdateFlag::DontInvalidateFrames)) {
2335 m_d->animationInterface->notifyNodeChanged(root.data(), requestedRects, true);
2336 }
2337
2338 m_d->scheduler.fullRefreshAsync(root, requestedRects, cropRect, flags);
2339}
2340
2342{
2343 m_d->scheduler.addSpontaneousJob(spontaneousJob);
2344}
2345
2347{
2349}
2350
2359
2374
2381
2387
2393
2398
2403
2408
2410{
2412}
2413
2415{
2416 m_d->disableUIUpdateSignals.deref();
2417
2418 QRect rect;
2419 QVector<QRect> postponedUpdates;
2420
2421 while (m_d->savedDisabledUIUpdates.pop(rect)) {
2422 postponedUpdates.append(rect);
2423 }
2424
2425 return postponedUpdates;
2426}
2427
2429{
2431
2433 int lod = currentLevelOfDetail();
2434 QRect dirtyRect = !lod ? rc : KisLodTransform::upscaledRect(rc, lod);
2435
2436 if (dirtyRect.isEmpty()) return;
2437
2438 Q_EMIT sigImageUpdated(dirtyRect);
2439 } else {
2441 }
2442}
2443
2448
2450{
2451 return m_d->scheduler.threadsLimit();
2452}
2453
2474
2477 const QVector<QRect> &rects,
2478 const QRect &cropRect,
2479 KisProjectionUpdateFlags flags)
2480{
2481 if (rects.isEmpty()) return;
2482
2483 scheduler.updateProjection(node, rects, cropRect, flags);
2484}
2485
2486void KisImage::requestProjectionUpdate(KisNode *node, const QVector<QRect> &rects, KisProjectionUpdateFlags flags)
2487{
2492 for (auto it = m_d->projectionUpdatesFilters.rbegin();
2493 it != m_d->projectionUpdatesFilters.rend();
2494 ++it) {
2495
2496 KIS_SAFE_ASSERT_RECOVER(*it) { continue; }
2497
2498 if ((*it)->filter(this, node, rects, flags)) {
2499 return;
2500 }
2501 }
2502
2503 if (!flags.testFlag(KisProjectionUpdateFlag::DontInvalidateFrames)) {
2504 m_d->animationInterface->notifyNodeChanged(node, rects, false);
2505 }
2506
2516 QVector<QRect> allSplitRects;
2517
2518 const QRect boundRect = effectiveLodBounds();
2519 Q_FOREACH (const QRect &rc, rects) {
2520 KisWrappedRect splitRect(rc, boundRect, m_d->wrapAroundModeAxis);
2521 allSplitRects.append(splitRect);
2522 }
2523
2524 m_d->requestProjectionUpdateImpl(node, allSplitRects, boundRect, flags);
2525
2526 } else {
2527 m_d->requestProjectionUpdateImpl(node, rects, bounds(), flags);
2528 }
2529
2531}
2532
2533void KisImage::invalidateFrames(const KisTimeSpan &range, const QRect &rect)
2534{
2536}
2537
2542
2547
2549{
2550 Q_UNUSED(node);
2551
2552 channel->connect(channel, SIGNAL(sigAddedKeyframe(const KisKeyframeChannel*, int)), m_d->animationInterface, SIGNAL(sigKeyframeAdded(const KisKeyframeChannel*, int)), Qt::UniqueConnection);
2553 channel->connect(channel, SIGNAL(sigKeyframeHasBeenRemoved(const KisKeyframeChannel*,int)), m_d->animationInterface, SIGNAL(sigKeyframeRemoved(const KisKeyframeChannel*, int)), Qt::UniqueConnection);
2554}
2555
2557{
2558 Q_UNUSED(node);
2559
2560 channel->disconnect(channel, SIGNAL(sigAddedKeyframe(const KisKeyframeChannel*, int)), m_d->animationInterface, SIGNAL(sigKeyframeAdded(const KisKeyframeChannel*, int)));
2561 channel->disconnect(channel, SIGNAL(sigKeyframeHasBeenRemoved(const KisKeyframeChannel*, int)), m_d->animationInterface, SIGNAL(sigKeyframeRemoved(const KisKeyframeChannel*, int)));
2562}
2563
2568
2570{
2571 m_d->compositions.append(composition);
2572}
2573
2575{
2576 m_d->compositions.removeAll(composition);
2577}
2578
2580{
2581 int index = m_d->compositions.indexOf(composition);
2582 if (index <= 0) {
2583 return;
2584 }
2585 m_d->compositions.move(index, index - 1);
2586}
2587
2589{
2590 int index = m_d->compositions.indexOf(composition);
2591 if (index >= m_d->compositions.size() -1) {
2592 return;
2593 }
2594 m_d->compositions.move(index, index + 1);
2595}
2596
2598{
2599 KisSelectionMask *mask = dynamic_cast<KisSelectionMask*>(root.data());
2600 if (mask &&
2601 (!bounds.contains(mask->paintDevice()->exactBounds()) ||
2602 mask->selection()->hasShapeSelection())) {
2603
2604 return true;
2605 }
2606
2607 KisNodeSP node = root->firstChild();
2608
2609 while (node) {
2610 if (checkMasksNeedConversion(node, bounds)) {
2611 return true;
2612 }
2613
2614 node = node->nextSibling();
2615 }
2616
2617 return false;
2618}
2619
2621{
2624 }
2625
2627
2630
2631 KisProcessingApplicator applicator(this, root(),
2634 kundo2_i18n("Crop Selections"));
2635
2636 KisProcessingVisitorSP visitor =
2638
2639 applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT);
2640 applicator.end();
2641 }
2642}
2643
2648
2653
2654
2659
2665
2670
2679
2684
2689
2691{
2692 Q_UNUSED(node);
2693 Q_EMIT sigNodeCollapsedChanged();
2694}
2695
2700
2702{
2703 const bool changed = bool(m_d->proofingConfig) != bool(proofingConfig) ||
2704 (m_d->proofingConfig && proofingConfig && *m_d->proofingConfig != *proofingConfig);
2705
2706 // we still assign even when unchanged since they can be different
2707 // shared pointer objects
2708 m_d->proofingConfig = proofingConfig;
2709
2710 if (changed) {
2711 Q_EMIT sigProofingConfigChanged();
2712 }
2713}
2714
2722
2724{
2725 return m_d->axesCenter;
2726}
2727
2728void KisImage::setMirrorAxesCenter(const QPointF &value) const
2729{
2730 m_d->axesCenter = value;
2731}
2732
2737
2739{
2740 return m_d->allowMasksOnRootNode;
2741}
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:891
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:658
void addAnnotation(KisAnnotationSP annotation)
bool wrapAroundModeActive() const
void sigNodeCollapsedChanged()
KisImagePrivate * m_d
Definition kis_image.h:1312
bool canReselectGlobalSelection()
Definition kis_image.cc:731
KisImageGlobalSelectionManagementInterface * globalSelectionManagementInterface() const
Definition kis_image.cc:736
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:652
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:614
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:630
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:831
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:901
KisCompositeProgressProxy * compositeProgressProxy()
Definition kis_image.cc:772
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:437
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:844
void sigRedoDuringStrokeRequested()
QString nextLayerName(const QString &baseName="") const
Definition kis_image.cc:741
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:814
void copyFromImage(const KisImage &rhs)
Definition kis_image.cc:432
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:711
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:782
KisImage * clone(bool exactCopy=false)
Definition kis_image.cc:427
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:860
void immediateLockForReadOnly()
Definition kis_image.cc:819
void aboutToAddANode(KisNode *parent, int index) override
Definition kis_image.cc:608
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:663
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:896
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:854
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:721
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:797
bool locked() const
Definition kis_image.cc:777
KisUndoStore * undoStore()
void unblockUpdates() override
unblockUpdates unblock updating the image project. This only restarts the scheduler and does not sche...
Definition kis_image.cc:849
void setModifiedWithoutUndo()
bool hasOverlaySelectionMask() const
Definition kis_image.cc:716
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:363
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:163
#define dbgImage
Definition kis_debug.h:50
#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()