File indexing completed on 2024-06-09 04:21:58

0001 /*
0002  *  SPDX-FileCopyrightText: 2016 Dmitry Kazakov <dimula73@gmail.com>
0003  *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@amyspark.me>
0004  *
0005  *  SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 
0008 #include "kis_colorize_mask.h"
0009 
0010 #include <QCoreApplication>
0011 #include <QStack>
0012 
0013 #include <KoColorSpaceRegistry.h>
0014 #include "kis_pixel_selection.h"
0015 
0016 #include "kis_icon_utils.h"
0017 
0018 #include "kis_node_visitor.h"
0019 #include "kis_processing_visitor.h"
0020 #include "kis_painter.h"
0021 #include "kis_fill_painter.h"
0022 #include "kis_lazy_fill_tools.h"
0023 #include "kis_cached_paint_device.h"
0024 #include "kis_paint_device_debug_utils.h"
0025 #include "kis_layer_properties_icons.h"
0026 #include "kis_thread_safe_signal_compressor.h"
0027 
0028 #include "kis_colorize_stroke_strategy.h"
0029 #include "kis_multiway_cut.h"
0030 #include "kis_image.h"
0031 #include "kis_layer.h"
0032 #include "kis_macro_based_undo_store.h"
0033 #include "kis_post_execution_undo_adapter.h"
0034 #include "kis_command_utils.h"
0035 #include "kis_processing_applicator.h"
0036 #include "krita_utils.h"
0037 #include <KisFakeRunnableStrokeJobsExecutor.h>
0038 #include <KisRunnableStrokeJobData.h>
0039 #include <KisRunnableStrokeJobUtils.h>
0040 #include <kis_pointer_utils.h>
0041 
0042 
0043 using namespace KisLazyFillTools;
0044 
0045 struct KisColorizeMask::Private
0046 {
0047     Private(KisColorizeMask *_q, KisImageWSP image)
0048         : q(_q),
0049           coloringProjection(new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8())),
0050           fakePaintDevice(new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8())),
0051           filteredSource(new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8())),
0052           needAddCurrentKeyStroke(false),
0053           showKeyStrokes(true),
0054           showColoring(true),
0055           needsUpdate(true),
0056           originalSequenceNumber(-1),
0057           updateCompressor(1000, KisSignalCompressor::FIRST_ACTIVE_POSTPONE_NEXT),
0058           dirtyParentUpdateCompressor(200, KisSignalCompressor::FIRST_ACTIVE_POSTPONE_NEXT),
0059           prefilterRecalculationCompressor(1000, KisSignalCompressor::POSTPONE),
0060           updateIsRunning(false),
0061           filteringOptions(false, 4.0, 15, 0.7),
0062           limitToDeviceBounds(false)
0063     {
0064         KisDefaultBoundsSP bounds(new KisDefaultBounds(image));
0065 
0066         coloringProjection->setDefaultBounds(bounds);
0067         fakePaintDevice->setDefaultBounds(bounds);
0068         filteredSource->setDefaultBounds(bounds);
0069     }
0070 
0071     Private(const Private &rhs, KisColorizeMask *_q)
0072         : q(_q),
0073           coloringProjection(new KisPaintDevice(*rhs.coloringProjection)),
0074           fakePaintDevice(new KisPaintDevice(*rhs.fakePaintDevice)),
0075           filteredSource(new KisPaintDevice(*rhs.filteredSource)),
0076           filteredDeviceBounds(rhs.filteredDeviceBounds),
0077           needAddCurrentKeyStroke(rhs.needAddCurrentKeyStroke),
0078           showKeyStrokes(rhs.showKeyStrokes),
0079           showColoring(rhs.showColoring),
0080           needsUpdate(false),
0081           originalSequenceNumber(-1),
0082           updateCompressor(1000, KisSignalCompressor::FIRST_ACTIVE_POSTPONE_NEXT),
0083           dirtyParentUpdateCompressor(200, KisSignalCompressor::FIRST_ACTIVE_POSTPONE_NEXT),
0084           prefilterRecalculationCompressor(1000, KisSignalCompressor::POSTPONE),
0085           offset(rhs.offset),
0086           updateIsRunning(false),
0087           filteringOptions(rhs.filteringOptions),
0088           limitToDeviceBounds(rhs.limitToDeviceBounds)
0089     {
0090         Q_FOREACH (const KeyStroke &stroke, rhs.keyStrokes) {
0091             keyStrokes << KeyStroke(KisPaintDeviceSP(new KisPaintDevice(*stroke.dev)), stroke.color, stroke.isTransparent);
0092         }
0093     }
0094 
0095     KisColorizeMask *q = 0;
0096 
0097     QList<KeyStroke> keyStrokes;
0098     KisPaintDeviceSP coloringProjection;
0099     KisPaintDeviceSP fakePaintDevice;
0100     KisPaintDeviceSP filteredSource;
0101     QRect filteredDeviceBounds;
0102 
0103     KoColor currentColor;
0104     KisPaintDeviceSP currentKeyStrokeDevice;
0105     bool needAddCurrentKeyStroke;
0106 
0107     bool showKeyStrokes;
0108     bool showColoring;
0109 
0110     KisCachedSelection cachedSelection;
0111 
0112     bool needsUpdate;
0113     int originalSequenceNumber;
0114 
0115     KisThreadSafeSignalCompressor updateCompressor;
0116     KisThreadSafeSignalCompressor dirtyParentUpdateCompressor;
0117     KisThreadSafeSignalCompressor prefilterRecalculationCompressor;
0118     QPoint offset;
0119 
0120     bool updateIsRunning;
0121     QStack<QRect> extentBeforeUpdateStart;
0122 
0123     FilteringOptions filteringOptions;
0124     bool filteringDirty = true;
0125 
0126     bool limitToDeviceBounds = false;
0127 
0128     bool filteredSourceValid(KisPaintDeviceSP parentDevice) {
0129         return !filteringDirty && originalSequenceNumber == parentDevice->sequenceNumber();
0130     }
0131 
0132     void setNeedsUpdateImpl(bool value, bool requestedByUser);
0133 
0134     bool shouldShowFilteredSource() const;
0135     bool shouldShowColoring() const;
0136 };
0137 
0138 KisColorizeMask::KisColorizeMask(KisImageWSP image, const QString &name)
0139     : KisEffectMask(image, name)
0140     , m_d(new Private(this, image))
0141 {
0142     connect(&m_d->updateCompressor,
0143             SIGNAL(timeout()),
0144             SLOT(slotUpdateRegenerateFilling()));
0145 
0146     connect(this, SIGNAL(sigUpdateOnDirtyParent()),
0147             &m_d->dirtyParentUpdateCompressor, SLOT(start()));
0148 
0149     connect(&m_d->dirtyParentUpdateCompressor,
0150             SIGNAL(timeout()),
0151             SLOT(slotUpdateOnDirtyParent()));
0152 
0153     connect(&m_d->prefilterRecalculationCompressor,
0154             SIGNAL(timeout()),
0155             SLOT(slotRecalculatePrefilteredImage()));
0156 
0157 
0158     m_d->updateCompressor.moveToThread(qApp->thread());
0159 }
0160 
0161 KisColorizeMask::~KisColorizeMask()
0162 {
0163 }
0164 
0165 KisColorizeMask::KisColorizeMask(const KisColorizeMask& rhs)
0166     : KisEffectMask(rhs),
0167       m_d(new Private(*rhs.m_d, this))
0168 {
0169     connect(&m_d->updateCompressor,
0170             SIGNAL(timeout()),
0171             SLOT(slotUpdateRegenerateFilling()));
0172 
0173     connect(this, SIGNAL(sigUpdateOnDirtyParent()),
0174             &m_d->dirtyParentUpdateCompressor, SLOT(start()));
0175 
0176     connect(&m_d->dirtyParentUpdateCompressor,
0177             SIGNAL(timeout()),
0178             SLOT(slotUpdateOnDirtyParent()));
0179 
0180     m_d->updateCompressor.moveToThread(qApp->thread());
0181 }
0182 
0183 void KisColorizeMask::initializeCompositeOp()
0184 {
0185     KisLayerSP parentLayer(qobject_cast<KisLayer*>(parent().data()));
0186     if (!parentLayer || !parentLayer->original()) return;
0187 
0188     KisImageSP image = parentLayer->image();
0189     if (!image) return;
0190 
0191     const qreal samplePortion = 0.1;
0192     const qreal alphaPortion =
0193         KritaUtils::estimatePortionOfTransparentPixels(parentLayer->original(),
0194                                                        image->bounds(),
0195                                                        samplePortion);
0196 
0197     setCompositeOpId(alphaPortion > 0.3 ? COMPOSITE_BEHIND : COMPOSITE_MULT);
0198 }
0199 
0200 const KoColorSpace* KisColorizeMask::colorSpace() const
0201 {
0202     return m_d->fakePaintDevice->colorSpace();
0203 }
0204 
0205 struct SetKeyStrokesColorSpaceCommand : public KUndo2Command {
0206     SetKeyStrokesColorSpaceCommand(const KoColorSpace *dstCS,
0207                                    KoColorConversionTransformation::Intent renderingIntent,
0208                                    KoColorConversionTransformation::ConversionFlags conversionFlags,
0209                                    QList<KeyStroke> *list,
0210                                    KisColorizeMaskSP node)
0211         : m_dstCS(dstCS),
0212           m_renderingIntent(renderingIntent),
0213           m_conversionFlags(conversionFlags),
0214           m_list(list),
0215           m_node(node) {}
0216 
0217     void undo() override {
0218         KIS_ASSERT_RECOVER_RETURN(m_list->size() == m_oldColors.size());
0219 
0220         for (int i = 0; i < m_list->size(); i++) {
0221             (*m_list)[i].color = m_oldColors[i];
0222         }
0223 
0224         m_node->setNeedsUpdate(true);
0225         emit m_node->sigKeyStrokesListChanged();
0226     }
0227 
0228     void redo() override {
0229         if (m_oldColors.isEmpty()) {
0230             Q_FOREACH(const KeyStroke &stroke, *m_list) {
0231                 m_oldColors << stroke.color;
0232                 m_newColors << stroke.color;
0233                 m_newColors.last().convertTo(m_dstCS, m_renderingIntent, m_conversionFlags);
0234             }
0235         }
0236 
0237         KIS_ASSERT_RECOVER_RETURN(m_list->size() == m_newColors.size());
0238 
0239         for (int i = 0; i < m_list->size(); i++) {
0240             (*m_list)[i].color = m_newColors[i];
0241         }
0242 
0243         m_node->setNeedsUpdate(true);
0244         emit m_node->sigKeyStrokesListChanged();
0245     }
0246 
0247 private:
0248     QVector<KoColor> m_oldColors;
0249     QVector<KoColor> m_newColors;
0250 
0251     const KoColorSpace *m_dstCS;
0252     KoColorConversionTransformation::Intent m_renderingIntent;
0253     KoColorConversionTransformation::ConversionFlags m_conversionFlags;
0254     QList<KeyStroke> *m_list;
0255     KisColorizeMaskSP m_node;
0256 };
0257 
0258 
0259 void KisColorizeMask::setProfile(const KoColorProfile *profile, KUndo2Command *parentCommand)
0260 {
0261     m_d->fakePaintDevice->setProfile(profile, parentCommand);
0262     m_d->coloringProjection->setProfile(profile, parentCommand);
0263 
0264     for (auto & stroke : m_d->keyStrokes) {
0265         stroke.color.setProfile(profile);
0266     }
0267 }
0268 
0269 KUndo2Command *KisColorizeMask::setColorSpace(const KoColorSpace *dstColorSpace,
0270                                               KoColorConversionTransformation::Intent renderingIntent,
0271                                               KoColorConversionTransformation::ConversionFlags conversionFlags,
0272                                               KoUpdater *progressUpdater)
0273 {
0274     using namespace KisCommandUtils;
0275 
0276     CompositeCommand *composite = new CompositeCommand();
0277 
0278     m_d->fakePaintDevice->convertTo(dstColorSpace, renderingIntent, conversionFlags, composite, progressUpdater);
0279     m_d->coloringProjection->convertTo(dstColorSpace, renderingIntent, conversionFlags, composite, progressUpdater);
0280 
0281     KUndo2Command *strokesConversionCommand =
0282         new SetKeyStrokesColorSpaceCommand(
0283             dstColorSpace, renderingIntent, conversionFlags,
0284             &m_d->keyStrokes, KisColorizeMaskSP(this));
0285     strokesConversionCommand->redo();
0286 
0287     composite->addCommand(new SkipFirstRedoWrapper(strokesConversionCommand));
0288 
0289     return composite;
0290 }
0291 
0292 bool KisColorizeMask::needsUpdate() const
0293 {
0294     return m_d->needsUpdate;
0295 }
0296 
0297 void KisColorizeMask::setNeedsUpdate(bool value)
0298 {
0299     m_d->setNeedsUpdateImpl(value, true);
0300 }
0301 
0302 void KisColorizeMask::Private::setNeedsUpdateImpl(bool value, bool requestedByUser)
0303 {
0304     if (value != needsUpdate) {
0305         needsUpdate = value;
0306         q->baseNodeChangedCallback();
0307 
0308         if (!value && requestedByUser) {
0309             updateCompressor.start();
0310         }
0311     }
0312 }
0313 
0314 void KisColorizeMask::slotUpdateRegenerateFilling(bool prefilterOnly)
0315 {
0316     KisPaintDeviceSP src = parent()->original();
0317     KIS_ASSERT_RECOVER_RETURN(src);
0318 
0319     const bool filteredSourceValid = m_d->filteredSourceValid(src);
0320     m_d->originalSequenceNumber = src->sequenceNumber();
0321     m_d->filteringDirty = false;
0322 
0323     if (!prefilterOnly) {
0324         m_d->coloringProjection->clear();
0325     }
0326 
0327     KisLayerSP parentLayer(qobject_cast<KisLayer*>(parent().data()));
0328     if (!parentLayer) return;
0329 
0330     KisImageSP image = parentLayer->image();
0331     if (image) {
0332         m_d->updateIsRunning = true;
0333 
0334         QRect fillBounds;
0335 
0336         if (m_d->limitToDeviceBounds) {
0337             fillBounds |= src->exactBounds();
0338             Q_FOREACH (const KeyStroke &stroke, m_d->keyStrokes) {
0339                 fillBounds |= stroke.dev->exactBounds();
0340             }
0341             fillBounds &= image->bounds();
0342         } else {
0343             fillBounds = image->bounds();
0344         }
0345 
0346         m_d->filteredDeviceBounds = fillBounds;
0347 
0348         KisColorizeStrokeStrategy *strategy =
0349             new KisColorizeStrokeStrategy(src,
0350                                           m_d->coloringProjection,
0351                                           m_d->filteredSource,
0352                                           filteredSourceValid,
0353                                           fillBounds,
0354                                           this,
0355                                           prefilterOnly);
0356 
0357         strategy->setFilteringOptions(m_d->filteringOptions);
0358 
0359         Q_FOREACH (const KeyStroke &stroke, m_d->keyStrokes) {
0360             const KoColor color =
0361                 !stroke.isTransparent ?
0362                 stroke.color :
0363                 KoColor::createTransparent(stroke.color.colorSpace());
0364 
0365             strategy->addKeyStroke(stroke.dev, color);
0366         }
0367 
0368         m_d->extentBeforeUpdateStart.push(extent());
0369 
0370         connect(strategy, SIGNAL(sigFinished(bool)), SLOT(slotRegenerationFinished(bool)));
0371         connect(strategy, SIGNAL(sigCancelled()), SLOT(slotRegenerationCancelled()));
0372         KisStrokeId id = image->startStroke(strategy);
0373         image->endStroke(id);
0374     }
0375 }
0376 
0377 void KisColorizeMask::slotUpdateOnDirtyParent()
0378 {
0379     if (!parent()) {
0380         // When the colorize mask is being merged,
0381         // the update is performed for all the layers,
0382         // so the invisible areas around the canvas are included in the merged layer.
0383         // Colorize Mask gets the info that its parent is "dirty" (needs updating),
0384         // but when it arrives, the parent doesn't exist anymore and is set to null.
0385         // Colorize Mask doesn't work outside of the canvas anyway (at least in time of writing).
0386         return;
0387     }
0388     KisPaintDeviceSP src = parent()->original();
0389     KIS_ASSERT_RECOVER_RETURN(src);
0390 
0391     if (!m_d->filteredSourceValid(src)) {
0392         const QRect &oldExtent = extent();
0393 
0394         m_d->setNeedsUpdateImpl(true, false);
0395         m_d->filteringDirty = true;
0396 
0397         setDirty(oldExtent | extent());
0398     }
0399 }
0400 
0401 void KisColorizeMask::slotRecalculatePrefilteredImage()
0402 {
0403     slotUpdateRegenerateFilling(true);
0404 }
0405 
0406 void KisColorizeMask::slotRegenerationFinished(bool prefilterOnly)
0407 {
0408     m_d->updateIsRunning = false;
0409 
0410     if (!prefilterOnly) {
0411         m_d->setNeedsUpdateImpl(false, false);
0412     }
0413 
0414     QRect oldExtent;
0415 
0416     if (!m_d->extentBeforeUpdateStart.isEmpty()) {
0417         oldExtent = m_d->extentBeforeUpdateStart.pop();
0418     } else {
0419         KIS_SAFE_ASSERT_RECOVER_NOOP(!m_d->extentBeforeUpdateStart.isEmpty()); // always fail!
0420     }
0421 
0422     setDirty(oldExtent | extent());
0423 }
0424 
0425 void KisColorizeMask::slotRegenerationCancelled()
0426 {
0427     slotRegenerationFinished(true);
0428     m_d->setNeedsUpdateImpl(true, false);
0429 }
0430 
0431 KisBaseNode::PropertyList KisColorizeMask::sectionModelProperties() const
0432 {
0433     KisBaseNode::PropertyList l = KisMask::sectionModelProperties();
0434     l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::colorizeNeedsUpdate, needsUpdate());
0435     l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::colorizeEditKeyStrokes, showKeyStrokes());
0436     l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::colorizeShowColoring, showColoring());
0437 
0438     return l;
0439 }
0440 
0441 void KisColorizeMask::setSectionModelProperties(const KisBaseNode::PropertyList &properties)
0442 {
0443     KisMask::setSectionModelProperties(properties);
0444 
0445     Q_FOREACH (const KisBaseNode::Property &property, properties) {
0446         if (property.id == KisLayerPropertiesIcons::colorizeNeedsUpdate.id()) {
0447             if (m_d->needsUpdate && m_d->needsUpdate != property.state.toBool()) {
0448                 setNeedsUpdate(property.state.toBool());
0449             }
0450         }
0451         if (property.id == KisLayerPropertiesIcons::colorizeEditKeyStrokes.id()) {
0452             if (m_d->showKeyStrokes != property.state.toBool()) {
0453                 setShowKeyStrokes(property.state.toBool());
0454             }
0455         }
0456         if (property.id == KisLayerPropertiesIcons::colorizeShowColoring.id()) {
0457             if (m_d->showColoring != property.state.toBool()) {
0458                 setShowColoring(property.state.toBool());
0459             }
0460         }
0461     }
0462 }
0463 
0464 KisPaintDeviceSP KisColorizeMask::paintDevice() const
0465 {
0466     return m_d->showKeyStrokes && !m_d->updateIsRunning ? m_d->fakePaintDevice : KisPaintDeviceSP();
0467 }
0468 
0469 KisPaintDeviceSP KisColorizeMask::coloringProjection() const
0470 {
0471     return m_d->coloringProjection;
0472 }
0473 
0474 KisPaintDeviceSP KisColorizeMask::colorSampleSourceDevice() const
0475 {
0476     return
0477         m_d->shouldShowColoring() && !m_d->coloringProjection->extent().isEmpty() ?
0478             m_d->coloringProjection : projection();
0479 }
0480 
0481 QIcon KisColorizeMask::icon() const
0482 {
0483     return KisIconUtils::loadIcon("colorizeMask");
0484 }
0485 
0486 
0487 bool KisColorizeMask::accept(KisNodeVisitor &v)
0488 {
0489     return v.visit(this);
0490 }
0491 
0492 void KisColorizeMask::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter)
0493 {
0494     return visitor.visit(this, undoAdapter);
0495 }
0496 
0497 bool KisColorizeMask::Private::shouldShowFilteredSource() const
0498 {
0499     return !updateIsRunning &&
0500             showKeyStrokes &&
0501             !filteringDirty &&
0502             filteredSource &&
0503             !filteredSource->extent().isEmpty();
0504 }
0505 
0506 bool KisColorizeMask::Private::shouldShowColoring() const
0507 {
0508     return !updateIsRunning &&
0509             showColoring &&
0510             coloringProjection;
0511 }
0512 
0513 QRect KisColorizeMask::decorateRect(KisPaintDeviceSP &src,
0514                                     KisPaintDeviceSP &dst,
0515                                     const QRect &rect,
0516                                     PositionToFilthy maskPos) const
0517 {
0518     Q_UNUSED(maskPos);
0519 
0520     if (maskPos == N_ABOVE_FILTHY) {
0521         // the source layer has changed, we should update the filtered cache!
0522 
0523         if (!m_d->filteringDirty) {
0524             emit sigUpdateOnDirtyParent();
0525         }
0526     }
0527 
0528     KIS_ASSERT(dst != src);
0529 
0530     // Draw the filling and the original layer
0531     {
0532         KisPainter gc(dst);
0533 
0534         if (m_d->shouldShowFilteredSource()) {
0535             const QRect drawRect = m_d->limitToDeviceBounds ? rect & m_d->filteredDeviceBounds : rect;
0536 
0537             gc.setOpacity(128);
0538             gc.bitBlt(drawRect.topLeft(), m_d->filteredSource, drawRect);
0539         } else {
0540             gc.setOpacity(255);
0541             gc.bitBlt(rect.topLeft(), src, rect);
0542         }
0543 
0544         if (m_d->shouldShowColoring()) {
0545 
0546             gc.setOpacity(opacity());
0547             gc.setCompositeOpId(compositeOpId());
0548             gc.bitBlt(rect.topLeft(), m_d->coloringProjection, rect);
0549         }
0550     }
0551 
0552     // Draw the key strokes
0553     if (m_d->showKeyStrokes) {
0554         KisIndirectPaintingSupport::ReadLocker locker(this);
0555 
0556         KisCachedSelection::Guard s1(m_d->cachedSelection);
0557         KisCachedSelection::Guard s2(m_d->cachedSelection);
0558 
0559         KisSelectionSP selection = s1.selection();
0560         KisPixelSelectionSP tempSelection = s2.selection()->pixelSelection();
0561 
0562         KisPaintDeviceSP temporaryTarget = this->temporaryTarget();
0563         const bool isTemporaryTargetErasing = temporaryCompositeOp() == COMPOSITE_ERASE;
0564         const QRect temporaryExtent = temporaryTarget ? temporaryTarget->extent() : QRect();
0565 
0566 
0567         KisFillPainter gc(dst);
0568 
0569         QList<KeyStroke> extendedStrokes = m_d->keyStrokes;
0570 
0571         if (m_d->currentKeyStrokeDevice &&
0572             m_d->needAddCurrentKeyStroke &&
0573             !isTemporaryTargetErasing) {
0574 
0575             extendedStrokes << KeyStroke(m_d->currentKeyStrokeDevice, m_d->currentColor);
0576         }
0577 
0578         Q_FOREACH (const KeyStroke &stroke, extendedStrokes) {
0579             selection->pixelSelection()->makeCloneFromRough(stroke.dev, rect);
0580             gc.setSelection(selection);
0581 
0582             if (stroke.color == m_d->currentColor ||
0583                 (isTemporaryTargetErasing &&
0584                  temporaryExtent.intersects(selection->pixelSelection()->selectedRect()))) {
0585 
0586                 if (temporaryTarget) {
0587                     tempSelection->copyAlphaFrom(temporaryTarget, rect);
0588 
0589                     KisPainter selectionPainter(selection->pixelSelection());
0590                     setupTemporaryPainter(&selectionPainter);
0591                     selectionPainter.bitBlt(rect.topLeft(), tempSelection, rect);
0592                 }
0593             }
0594 
0595             gc.fillSelection(rect, stroke.color);
0596         }
0597     }
0598 
0599     return rect;
0600 }
0601 
0602 struct DeviceExtentPolicy
0603 {
0604     inline QRect operator() (const KisPaintDevice *dev) {
0605         return dev->extent();
0606     }
0607 };
0608 
0609 struct DeviceExactBoundsPolicy
0610 {
0611     inline QRect operator() (const KisPaintDevice *dev) {
0612         return dev->exactBounds();
0613     }
0614 };
0615 
0616 template <class DeviceMetricPolicy>
0617 QRect KisColorizeMask::calculateMaskBounds(DeviceMetricPolicy boundsPolicy) const
0618 {
0619     QRect rc;
0620 
0621     if (m_d->shouldShowFilteredSource()) {
0622         rc |= boundsPolicy(m_d->filteredSource);
0623     }
0624 
0625     if (m_d->shouldShowColoring()) {
0626         rc |= boundsPolicy(m_d->coloringProjection);
0627     }
0628 
0629     if (m_d->showKeyStrokes) {
0630         Q_FOREACH (const KeyStroke &stroke, m_d->keyStrokes) {
0631             rc |= boundsPolicy(stroke.dev);
0632         }
0633 
0634         KisIndirectPaintingSupport::ReadLocker locker(this);
0635 
0636         KisPaintDeviceSP temporaryTarget = this->temporaryTarget();
0637         if (temporaryTarget) {
0638             rc |= boundsPolicy(temporaryTarget);
0639         }
0640     }
0641 
0642     return rc;
0643 }
0644 
0645 
0646 QRect KisColorizeMask::extent() const
0647 {
0648     return calculateMaskBounds(DeviceExtentPolicy());
0649 }
0650 
0651 QRect KisColorizeMask::exactBounds() const
0652 {
0653     return calculateMaskBounds(DeviceExactBoundsPolicy());
0654 }
0655 
0656 QRect KisColorizeMask::nonDependentExtent() const
0657 {
0658     return extent();
0659 }
0660 
0661 void KisColorizeMask::setImage(KisImageWSP image)
0662 {
0663     KisDefaultBoundsSP bounds(new KisDefaultBounds(image));
0664 
0665     auto it = m_d->keyStrokes.begin();
0666     for(; it != m_d->keyStrokes.end(); ++it) {
0667         it->dev->setDefaultBounds(bounds);
0668     }
0669 
0670     m_d->coloringProjection->setDefaultBounds(bounds);
0671     m_d->fakePaintDevice->setDefaultBounds(bounds);
0672     m_d->filteredSource->setDefaultBounds(bounds);
0673 }
0674 
0675 void KisColorizeMask::setCurrentColor(const KoColor &_color)
0676 {
0677     KoColor color = _color;
0678     color.convertTo(colorSpace());
0679 
0680     WriteLocker locker(this);
0681 
0682     m_d->setNeedsUpdateImpl(true, false);
0683 
0684     QList<KeyStroke>::const_iterator it =
0685         std::find_if(m_d->keyStrokes.constBegin(),
0686                      m_d->keyStrokes.constEnd(),
0687                      kismpl::mem_equal_to(&KeyStroke::color, color));
0688 
0689     KisPaintDeviceSP activeDevice;
0690     bool newKeyStroke = false;
0691 
0692     if (it == m_d->keyStrokes.constEnd()) {
0693         activeDevice = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8());
0694         activeDevice->setParentNode(this);
0695         activeDevice->setDefaultBounds(KisDefaultBoundsBaseSP(new KisDefaultBounds(image())));
0696         newKeyStroke = true;
0697     } else {
0698         activeDevice = it->dev;
0699     }
0700 
0701     m_d->currentColor = color;
0702     m_d->currentKeyStrokeDevice = activeDevice;
0703     m_d->needAddCurrentKeyStroke = newKeyStroke;
0704 }
0705 
0706 
0707 
0708 struct KeyStrokeAddRemoveCommand : public KisCommandUtils::FlipFlopCommand {
0709     KeyStrokeAddRemoveCommand(bool add, int index, KeyStroke stroke, QList<KeyStroke> *list, KisColorizeMaskSP node, KUndo2Command *parentCommand = nullptr)
0710         : FlipFlopCommand(!add, parentCommand),
0711           m_index(index), m_stroke(stroke),
0712           m_list(list), m_node(node) {}
0713 
0714     void partA() override {
0715         m_list->insert(m_index, m_stroke);
0716         m_node->setNeedsUpdate(true);
0717         emit m_node->sigKeyStrokesListChanged();
0718     }
0719 
0720     void partB() override {
0721         KIS_ASSERT_RECOVER_RETURN((*m_list)[m_index] == m_stroke);
0722         m_list->removeAt(m_index);
0723         m_node->setNeedsUpdate(true);
0724         emit m_node->sigKeyStrokesListChanged();
0725     }
0726 
0727 private:
0728     int m_index;
0729     KeyStroke m_stroke;
0730     QList<KeyStroke> *m_list;
0731     KisColorizeMaskSP m_node;
0732 };
0733 
0734 void KisColorizeMask::mergeToLayerThreaded(KisNodeSP layer, KUndo2Command *parentCommand, const KUndo2MagicString &transactionText,int timedID, QVector<KisRunnableStrokeJobData*> *jobs)
0735 {
0736     // Just fake threaded merging. It is not supported for the colorize mask.
0737 
0738     KritaUtils::addJobSequential(*jobs,
0739         [=] () {
0740             this->mergeToLayerUnthreaded(layer, parentCommand, transactionText, timedID);
0741         }
0742     );
0743 }
0744 
0745 void KisColorizeMask::mergeToLayerUnthreaded(KisNodeSP layer, KUndo2Command *parentCommand, const KUndo2MagicString &transactionText, int timedID)
0746 {
0747     Q_UNUSED(layer);
0748 
0749     auto executeAndAdd = [parentCommand] (KUndo2Command *cmd) {
0750         cmd->redo();
0751         new KisCommandUtils::SkipFirstRedoWrapper(cmd, parentCommand);
0752     };
0753 
0754     WriteLockerSP sharedWriteLock(new WriteLocker(this));
0755 
0756     KisPaintDeviceSP temporaryTarget = this->temporaryTarget();
0757     const bool isTemporaryTargetErasing = temporaryCompositeOp() == COMPOSITE_ERASE;
0758     const QRect temporaryExtent = temporaryTarget ? temporaryTarget->extent() : QRect();
0759 
0760     /**
0761      * Add a new key stroke plane
0762      */
0763     if (m_d->needAddCurrentKeyStroke && !isTemporaryTargetErasing) {
0764         KeyStroke key(m_d->currentKeyStrokeDevice, m_d->currentColor);
0765         executeAndAdd(new KeyStrokeAddRemoveCommand(
0766             true, m_d->keyStrokes.size(), key, &m_d->keyStrokes, KisColorizeMaskSP(this), nullptr));
0767     }
0768 
0769     QVector<KisRunnableStrokeJobData*> jobs;
0770 
0771     /**
0772      * When erasing, the brush affects all the key strokes, not only
0773      * the current one.
0774      */
0775     if (!isTemporaryTargetErasing) {
0776         mergeToLayerImpl(m_d->currentKeyStrokeDevice, parentCommand, transactionText, timedID, false, sharedWriteLock, &jobs);
0777     } else {
0778         Q_FOREACH (const KeyStroke &stroke, m_d->keyStrokes) {
0779             if (temporaryExtent.intersects(stroke.dev->extent())) {
0780                 mergeToLayerImpl(stroke.dev, parentCommand, transactionText, timedID, false, sharedWriteLock, &jobs);
0781             }
0782         }
0783     }
0784 
0785     mergeToLayerImpl(m_d->fakePaintDevice, parentCommand, transactionText, timedID, false, sharedWriteLock, &jobs);
0786 
0787     /**
0788      * When merging, we use barrier jobs only for ensuring that the merge jobs
0789      * are not split by the update jobs. Merge jobs hold the shared lock, so
0790      * forcing them out of CPU will basically cause a deadlock. When running in
0791      * the fake executor, the jobs cannot be split anyway, so there is no danger
0792      * in that.
0793      */
0794     KisFakeRunnableStrokeJobsExecutor fakeExecutor(KisFakeRunnableStrokeJobsExecutor::AllowBarrierJobs);
0795     fakeExecutor.addRunnableJobs(implicitCastList<KisRunnableStrokeJobDataBase*>(jobs));
0796 
0797     m_d->currentKeyStrokeDevice = 0;
0798     m_d->currentColor = KoColor();
0799     releaseResources();
0800 
0801     /**
0802      * Try removing the key strokes that has been completely erased
0803      */
0804     if (isTemporaryTargetErasing) {
0805         for (int index = 0; index < m_d->keyStrokes.size(); /*noop*/) {
0806             const KeyStroke &stroke = m_d->keyStrokes[index];
0807 
0808             if (stroke.dev->exactBounds().isEmpty()) {
0809                 executeAndAdd(new KeyStrokeAddRemoveCommand(
0810                     false, index, stroke, &m_d->keyStrokes, KisColorizeMaskSP(this), nullptr));
0811             } else {
0812                 index++;
0813             }
0814         }
0815     }
0816 }
0817 
0818 
0819 void KisColorizeMask::writeMergeData(KisPainter *painter, KisPaintDeviceSP src, const QRect &rc)
0820 {
0821     const KoColorSpace *alpha8 = KoColorSpaceRegistry::instance()->alpha8();
0822     const bool nonAlphaDst = !(*painter->device()->colorSpace() == *alpha8);
0823 
0824     if (nonAlphaDst) {
0825         painter->bitBlt(rc.topLeft(), src, rc);
0826     } else {
0827         KisCachedSelection::Guard s1(m_d->cachedSelection);
0828         KisPixelSelectionSP tempSelection = s1.selection()->pixelSelection();
0829 
0830         tempSelection->copyAlphaFrom(src, rc);
0831         painter->bitBlt(rc.topLeft(), tempSelection, rc);
0832     }
0833 }
0834 
0835 bool KisColorizeMask::supportsNonIndirectPainting() const
0836 {
0837     return false;
0838 }
0839 
0840 bool KisColorizeMask::showColoring() const
0841 {
0842     return m_d->showColoring;
0843 }
0844 
0845 void KisColorizeMask::setShowColoring(bool value)
0846 {
0847     QRect savedExtent;
0848     if (m_d->showColoring && !value) {
0849         savedExtent = extent();
0850     }
0851 
0852     m_d->showColoring = value;
0853     baseNodeChangedCallback();
0854 
0855     if (!savedExtent.isEmpty()) {
0856         setDirty(savedExtent);
0857     }
0858 }
0859 
0860 bool KisColorizeMask::showKeyStrokes() const
0861 {
0862     return m_d->showKeyStrokes;
0863 }
0864 
0865 void KisColorizeMask::setShowKeyStrokes(bool value)
0866 {
0867     QRect savedExtent;
0868     if (m_d->showKeyStrokes && !value) {
0869         savedExtent = extent();
0870     }
0871 
0872     m_d->showKeyStrokes = value;
0873     baseNodeChangedCallback();
0874 
0875     if (!savedExtent.isEmpty()) {
0876         setDirty(savedExtent);
0877     }
0878 
0879     regeneratePrefilteredDeviceIfNeeded();
0880 }
0881 
0882 KisColorizeMask::KeyStrokeColors KisColorizeMask::keyStrokesColors() const
0883 {
0884     KeyStrokeColors colors;
0885 
0886     // TODO: thread safety!
0887     for (int i = 0; i < m_d->keyStrokes.size(); i++) {
0888         colors.colors << m_d->keyStrokes[i].color;
0889 
0890         if (m_d->keyStrokes[i].isTransparent) {
0891             colors.transparentIndex = i;
0892         }
0893     }
0894 
0895     return colors;
0896 }
0897 
0898 struct SetKeyStrokeColorsCommand : public KUndo2Command {
0899     SetKeyStrokeColorsCommand(const QList<KeyStroke> newList, QList<KeyStroke> *list, KisColorizeMaskSP node)
0900         : m_newList(newList),
0901           m_oldList(*list),
0902           m_list(list),
0903           m_node(node) {}
0904 
0905     void redo() override {
0906         *m_list = m_newList;
0907 
0908         m_node->setNeedsUpdate(true);
0909         emit m_node->sigKeyStrokesListChanged();
0910         m_node->setDirty();
0911     }
0912 
0913     void undo() override {
0914         *m_list = m_oldList;
0915 
0916         m_node->setNeedsUpdate(true);
0917         emit m_node->sigKeyStrokesListChanged();
0918         m_node->setDirty();
0919     }
0920 
0921 private:
0922     QList<KeyStroke> m_newList;
0923     QList<KeyStroke> m_oldList;
0924     QList<KeyStroke> *m_list;
0925     KisColorizeMaskSP m_node;
0926 };
0927 
0928 void KisColorizeMask::setKeyStrokesColors(KeyStrokeColors colors)
0929 {
0930     KIS_ASSERT_RECOVER_RETURN(colors.colors.size() == m_d->keyStrokes.size());
0931 
0932     QList<KeyStroke> newList = m_d->keyStrokes;
0933 
0934     for (int i = 0; i < newList.size(); i++) {
0935         newList[i].color = colors.colors[i];
0936         newList[i].color.convertTo(colorSpace());
0937         newList[i].isTransparent = colors.transparentIndex == i;
0938     }
0939 
0940     KisProcessingApplicator applicator(image(), KisNodeSP(this),
0941                                        KisProcessingApplicator::NONE,
0942                                        KisImageSignalVector(),
0943                                        kundo2_i18n("Change Key Stroke Color"));
0944     applicator.applyCommand(
0945         new SetKeyStrokeColorsCommand(
0946             newList, &m_d->keyStrokes, KisColorizeMaskSP(this)));
0947 
0948     applicator.end();
0949 }
0950 
0951 void KisColorizeMask::removeKeyStroke(const KoColor &_color)
0952 {
0953     KoColor color = _color;
0954     color.convertTo(colorSpace());
0955 
0956     QList<KeyStroke>::iterator it =
0957         std::find_if(m_d->keyStrokes.begin(),
0958                      m_d->keyStrokes.end(),
0959                      kismpl::mem_equal_to(&KeyStroke::color, color));
0960 
0961     KIS_SAFE_ASSERT_RECOVER_RETURN(it != m_d->keyStrokes.end());
0962 
0963     const int index = it - m_d->keyStrokes.begin();
0964 
0965     KisProcessingApplicator applicator(image(), KisNodeSP(this),
0966                                        KisProcessingApplicator::NONE,
0967                                        KisImageSignalVector(),
0968                                        kundo2_i18n("Remove Key Stroke"));
0969     applicator.applyCommand(
0970         new KeyStrokeAddRemoveCommand(
0971             false, index, *it, &m_d->keyStrokes, KisColorizeMaskSP(this)));
0972 
0973     applicator.end();
0974 }
0975 
0976 
0977 QVector<KisPaintDeviceSP> KisColorizeMask::allPaintDevices() const
0978 {
0979     QVector<KisPaintDeviceSP> devices;
0980 
0981     Q_FOREACH (const KeyStroke &stroke, m_d->keyStrokes) {
0982         devices << stroke.dev;
0983     }
0984 
0985     devices << m_d->coloringProjection;
0986     devices << m_d->fakePaintDevice;
0987 
0988     return devices;
0989 }
0990 
0991 void KisColorizeMask::resetCache()
0992 {
0993     m_d->filteredSource->clear();
0994     m_d->originalSequenceNumber = -1;
0995     m_d->filteringDirty = true;
0996 
0997     rerenderFakePaintDevice();
0998     slotUpdateRegenerateFilling(true);
0999 }
1000 
1001 void KisColorizeMask::setUseEdgeDetection(bool value)
1002 {
1003     m_d->filteringOptions.useEdgeDetection = value;
1004     m_d->filteringDirty = true;
1005     setNeedsUpdate(true);
1006 }
1007 
1008 bool KisColorizeMask::useEdgeDetection() const
1009 {
1010     return m_d->filteringOptions.useEdgeDetection;
1011 }
1012 
1013 void KisColorizeMask::setEdgeDetectionSize(qreal value)
1014 {
1015     m_d->filteringOptions.edgeDetectionSize = value;
1016     m_d->filteringDirty = true;
1017     setNeedsUpdate(true);
1018 }
1019 
1020 qreal KisColorizeMask::edgeDetectionSize() const
1021 {
1022     return m_d->filteringOptions.edgeDetectionSize;
1023 }
1024 
1025 void KisColorizeMask::setFuzzyRadius(qreal value)
1026 {
1027     m_d->filteringOptions.fuzzyRadius = value;
1028     m_d->filteringDirty = true;
1029     setNeedsUpdate(true);
1030 }
1031 
1032 qreal KisColorizeMask::fuzzyRadius() const
1033 {
1034     return m_d->filteringOptions.fuzzyRadius;
1035 }
1036 
1037 void KisColorizeMask::setCleanUpAmount(qreal value)
1038 {
1039     m_d->filteringOptions.cleanUpAmount = value;
1040     setNeedsUpdate(true);
1041 }
1042 
1043 qreal KisColorizeMask::cleanUpAmount() const
1044 {
1045     return m_d->filteringOptions.cleanUpAmount;
1046 }
1047 
1048 void KisColorizeMask::setLimitToDeviceBounds(bool value)
1049 {
1050     m_d->limitToDeviceBounds = value;
1051     m_d->filteringDirty = true;
1052     setNeedsUpdate(true);
1053 }
1054 
1055 bool KisColorizeMask::limitToDeviceBounds() const
1056 {
1057     return m_d->limitToDeviceBounds;
1058 }
1059 
1060 void KisColorizeMask::rerenderFakePaintDevice()
1061 {
1062     m_d->fakePaintDevice->clear();
1063     KisFillPainter gc(m_d->fakePaintDevice);
1064 
1065     KisCachedSelection::Guard s1(m_d->cachedSelection);
1066     KisSelectionSP selection = s1.selection();
1067 
1068     Q_FOREACH (const KeyStroke &stroke, m_d->keyStrokes) {
1069         const QRect rect = stroke.dev->extent();
1070 
1071         selection->pixelSelection()->makeCloneFromRough(stroke.dev, rect);
1072         gc.setSelection(selection);
1073         gc.fillSelection(rect, stroke.color);
1074     }
1075 }
1076 
1077 void KisColorizeMask::testingAddKeyStroke(KisPaintDeviceSP dev, const KoColor &color, bool isTransparent)
1078 {
1079     m_d->keyStrokes << KeyStroke(dev, color, isTransparent);
1080 }
1081 
1082 void KisColorizeMask::forceRegenerateMask()
1083 {
1084     slotUpdateRegenerateFilling();
1085     m_d->updateIsRunning = false;
1086 }
1087 
1088 KisPaintDeviceSP KisColorizeMask::testingFilteredSource() const
1089 {
1090     return m_d->filteredSource;
1091 }
1092 
1093 QList<KeyStroke> KisColorizeMask::fetchKeyStrokesDirect() const
1094 {
1095     return m_d->keyStrokes;
1096 }
1097 
1098 void KisColorizeMask::setKeyStrokesDirect(const QList<KisLazyFillTools::KeyStroke> &strokes)
1099 {
1100     m_d->keyStrokes = strokes;
1101 
1102     for (auto it = m_d->keyStrokes.begin(); it != m_d->keyStrokes.end(); ++it) {
1103         it->dev->setParentNode(this);
1104     }
1105 
1106     KIS_SAFE_ASSERT_RECOVER(image())
1107     {};
1108 }
1109 
1110 qint32 KisColorizeMask::x() const
1111 {
1112     return m_d->offset.x();
1113 }
1114 
1115 qint32 KisColorizeMask::y() const
1116 {
1117     return m_d->offset.y();
1118 }
1119 
1120 void KisColorizeMask::setX(qint32 x)
1121 {
1122     const QPoint oldOffset = m_d->offset;
1123     m_d->offset.rx() = x;
1124     moveAllInternalDevices(m_d->offset - oldOffset);
1125 }
1126 
1127 void KisColorizeMask::setY(qint32 y)
1128 {
1129     const QPoint oldOffset = m_d->offset;
1130     m_d->offset.ry() = y;
1131     moveAllInternalDevices(m_d->offset - oldOffset);
1132 }
1133 
1134 KisPaintDeviceList KisColorizeMask::getLodCapableDevices() const
1135 {
1136     KisPaintDeviceList list;
1137 
1138     auto it = m_d->keyStrokes.begin();
1139     for(; it != m_d->keyStrokes.end(); ++it) {
1140         list << it->dev;
1141     }
1142 
1143     list << m_d->coloringProjection;
1144     list << m_d->fakePaintDevice;
1145     list << m_d->filteredSource;
1146 
1147     return list;
1148 }
1149 
1150 void KisColorizeMask::regeneratePrefilteredDeviceIfNeeded()
1151 {
1152     if (!parent()) return;
1153 
1154     KisPaintDeviceSP src = parent()->original();
1155     KIS_ASSERT_RECOVER_RETURN(src);
1156 
1157     if (!m_d->filteredSourceValid(src)) {
1158         // update the prefiltered source if needed
1159         slotUpdateRegenerateFilling(true);
1160     }
1161 }
1162 
1163 void KisColorizeMask::moveAllInternalDevices(const QPoint &diff)
1164 {
1165     QVector<KisPaintDeviceSP> devices = allPaintDevices();
1166 
1167     Q_FOREACH (KisPaintDeviceSP dev, devices) {
1168         dev->moveTo(dev->offset() + diff);
1169     }
1170 }