File indexing completed on 2024-05-12 15:58:42

0001 /*
0002  *  SPDX-FileCopyrightText: 2006 Boudewijn Rempt <boud@valdyas.org>
0003  *
0004  *
0005  *  SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 
0008 #include "kis_selection_mask.h"
0009 
0010 #include "kis_image.h"
0011 #include "kis_layer.h"
0012 #include "kis_selection.h"
0013 #include <KoColorSpaceRegistry.h>
0014 #include <KoColorSpace.h>
0015 #include <KoProperties.h>
0016 #include "kis_fill_painter.h"
0017 #include <KoCompositeOp.h>
0018 #include "kis_node_visitor.h"
0019 #include "kis_processing_visitor.h"
0020 #include "kis_pixel_selection.h"
0021 #include "kis_undo_adapter.h"
0022 #include <KoIcon.h>
0023 #include <kis_icon.h>
0024 #include "kis_thread_safe_signal_compressor.h"
0025 #include "kis_layer_properties_icons.h"
0026 #include "kis_cached_paint_device.h"
0027 
0028 #include "kis_image_config.h"
0029 #include "KisImageConfigNotifier.h"
0030 
0031 
0032 struct Q_DECL_HIDDEN KisSelectionMask::Private
0033 {
0034 public:
0035     Private(KisSelectionMask *_q)
0036         : q(_q)
0037         , updatesCompressor(0)
0038         , maskColor(Qt::green, KoColorSpaceRegistry::instance()->rgb8())
0039     {}
0040     KisSelectionMask *q;
0041     KisCachedPaintDevice paintDeviceCache;
0042     KisCachedSelection cachedSelection;
0043     KisThreadSafeSignalCompressor *updatesCompressor;
0044     KoColor maskColor;
0045 
0046     void slotSelectionChangedCompressed();
0047     void slotConfigChangedImpl(bool blockUpdates);
0048     void slotConfigChanged();
0049 };
0050 
0051 KisSelectionMask::KisSelectionMask(KisImageWSP image, const QString &name)
0052     : KisEffectMask(image, name)
0053     , m_d(new Private(this))
0054 {
0055     setActive(false);
0056     setSupportsLodMoves(false);
0057 
0058     m_d->updatesCompressor =
0059             new KisThreadSafeSignalCompressor(50, KisSignalCompressor::FIRST_ACTIVE);
0060 
0061     connect(m_d->updatesCompressor, SIGNAL(timeout()), SLOT(slotSelectionChangedCompressed()));
0062 
0063     connect(KisImageConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
0064     m_d->slotConfigChangedImpl(false);
0065 }
0066 
0067 KisSelectionMask::KisSelectionMask(const KisSelectionMask& rhs)
0068     : KisEffectMask(rhs)
0069     , m_d(new Private(this))
0070 {
0071     m_d->updatesCompressor =
0072             new KisThreadSafeSignalCompressor(300, KisSignalCompressor::POSTPONE);
0073 
0074     connect(m_d->updatesCompressor, SIGNAL(timeout()), SLOT(slotSelectionChangedCompressed()));
0075 
0076     connect(KisImageConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
0077     m_d->slotConfigChangedImpl(false);
0078 }
0079 
0080 KisSelectionMask::~KisSelectionMask()
0081 {
0082     m_d->updatesCompressor->deleteLater();
0083     delete m_d;
0084 }
0085 
0086 QIcon KisSelectionMask::icon() const {
0087     return KisIconUtils::loadIcon("selectionMask");
0088 }
0089 
0090 void KisSelectionMask::mergeInMaskInternal(KisPaintDeviceSP projection,
0091                                            KisSelectionSP effectiveSelection,
0092                                            const QRect &applyRect,
0093                                            const QRect &preparedNeedRect,
0094                                            KisNode::PositionToFilthy maskPos) const
0095 {
0096     Q_UNUSED(maskPos);
0097     Q_UNUSED(preparedNeedRect);
0098     if (!effectiveSelection) return;
0099 
0100     {
0101         KisSelectionSP mainMaskSelection = this->selection();
0102         if (mainMaskSelection &&
0103             (!mainMaskSelection->isVisible() ||
0104              mainMaskSelection->pixelSelection()->defaultBounds()->externalFrameActive())) {
0105 
0106             return;
0107         }
0108     }
0109 
0110     KisCachedPaintDevice::Guard d1(projection, m_d->paintDeviceCache);
0111     KisPaintDeviceSP fillDevice = d1.device();
0112     fillDevice->setDefaultPixel(m_d->maskColor);
0113 
0114     const QRect selectionExtent = effectiveSelection->selectedRect();
0115 
0116     if (selectionExtent.contains(applyRect) || selectionExtent.intersects(applyRect)) {
0117         KisCachedSelection::Guard s1(m_d->cachedSelection);
0118         KisSelectionSP invertedSelection = s1.selection();
0119 
0120         invertedSelection->pixelSelection()->makeCloneFromRough(effectiveSelection->pixelSelection(), applyRect);
0121         invertedSelection->pixelSelection()->invert();
0122 
0123         KisPainter gc(projection);
0124         gc.setSelection(invertedSelection);
0125         gc.bitBlt(applyRect.topLeft(), fillDevice, applyRect);
0126 
0127     } else {
0128         KisPainter gc(projection);
0129         gc.bitBlt(applyRect.topLeft(), fillDevice, applyRect);
0130     }
0131 }
0132 
0133 bool KisSelectionMask::paintsOutsideSelection() const
0134 {
0135     return true;
0136 }
0137 
0138 void KisSelectionMask::setSelection(KisSelectionSP selection)
0139 {
0140     if (selection) {
0141         KisEffectMask::setSelection(selection);
0142     } else {
0143         KisEffectMask::setSelection(new KisSelection());
0144 
0145         const KoColorSpace * cs = KoColorSpaceRegistry::instance()->alpha8();
0146         KisFillPainter gc(KisPaintDeviceSP(this->selection()->pixelSelection().data()));
0147         gc.fillRect(image()->bounds(), KoColor(Qt::white, cs), MAX_SELECTED);
0148         gc.end();
0149     }
0150     setDirty();
0151 }
0152 
0153 bool KisSelectionMask::accept(KisNodeVisitor &v)
0154 {
0155     return v.visit(this);
0156 }
0157 
0158 void KisSelectionMask::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter)
0159 {
0160     return visitor.visit(this, undoAdapter);
0161 }
0162 
0163 KisBaseNode::PropertyList KisSelectionMask::sectionModelProperties() const
0164 {
0165     KisBaseNode::PropertyList l = KisBaseNode::sectionModelProperties();
0166     l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::selectionActive, active());
0167     return l;
0168 }
0169 
0170 void KisSelectionMask::setSectionModelProperties(const KisBaseNode::PropertyList &properties)
0171 {
0172     KisEffectMask::setSectionModelProperties(properties);
0173     setActive(properties.at(2).state.toBool());
0174 }
0175 
0176 void KisSelectionMask::setVisible(bool visible, bool isLoading)
0177 {
0178     const bool oldVisible = this->visible(false);
0179     setNodeProperty("visible", visible);
0180 
0181     if (!isLoading && visible != oldVisible) {
0182         if (selection())
0183             selection()->setVisible(visible);
0184     }
0185 }
0186 
0187 bool KisSelectionMask::active() const
0188 {
0189     return nodeProperties().boolProperty("active", true);
0190 }
0191 
0192 void KisSelectionMask::setActive(bool active)
0193 {
0194     KisImageSP image = this->image();
0195     KisLayerSP parentLayer = qobject_cast<KisLayer*>(parent().data());
0196 
0197     if (active && parentLayer) {
0198         KisSelectionMaskSP activeMask = parentLayer->selectionMask();
0199         if (activeMask && activeMask != this) {
0200             activeMask->setActive(false);
0201         }
0202     }
0203 
0204     const bool oldActive = this->active();
0205     setNodeProperty("active", active);
0206 
0207 
0208     /**
0209      * WARNING: we have a direct link to the image here, but we
0210      * must not use it for notification until we are a part of
0211      * the node graph! Notifications should be emitted iff we
0212      * have graph listener link set up.
0213      */
0214     if (graphListener() &&
0215         image && oldActive != active) {
0216 
0217         baseNodeChangedCallback();
0218         image->undoAdapter()->emitSelectionChanged();
0219     }
0220 }
0221 
0222 QRect KisSelectionMask::needRect(const QRect &rect, KisNode::PositionToFilthy pos) const
0223 {
0224     Q_UNUSED(pos);
0225     
0226     // selection masks just add an overlay, so the needed rect is simply passed through
0227     return rect;
0228 }
0229 
0230 QRect KisSelectionMask::changeRect(const QRect &rect, KisNode::PositionToFilthy pos) const
0231 {
0232     Q_UNUSED(pos);
0233 
0234     // selection masks just add an overlay, so the changed rect is simply passed through
0235     return rect;
0236 }
0237 
0238 QRect KisSelectionMask::extent() const
0239 {
0240     // since mask overlay is inverted, the mask paints over
0241     // the entire image bounds
0242     QRect resultRect;
0243     KisSelectionSP selection = this->selection();
0244     
0245     if (selection) {
0246         resultRect = selection->pixelSelection()->defaultBounds()->bounds();
0247         
0248         if (KisNodeSP parent = this->parent()) {
0249             resultRect |= parent->extent();
0250         }
0251         
0252     } else if (KisNodeSP parent = this->parent()) {
0253         KisPaintDeviceSP dev = parent->projection();
0254         if (dev) {
0255             resultRect = dev->defaultBounds()->bounds();
0256         }
0257     }
0258 
0259     return resultRect;
0260 }
0261 
0262 QRect KisSelectionMask::exactBounds() const
0263 {
0264     return extent();
0265 }
0266 
0267 void KisSelectionMask::notifySelectionChangedCompressed()
0268 {
0269     m_d->updatesCompressor->start();
0270 }
0271 
0272 bool KisSelectionMask::decorationsVisible() const
0273 {
0274     return selection()->isVisible();
0275 }
0276 
0277 void KisSelectionMask::setDecorationsVisible(bool value, bool update)
0278 {
0279     if (value == decorationsVisible()) return;
0280 
0281     const QRect oldExtent = extent();
0282 
0283     selection()->setVisible(value);
0284 
0285     if (update) {
0286         setDirty(oldExtent | extent());
0287     }
0288 }
0289 
0290 void KisSelectionMask::setDirty(const QVector<QRect> &rects)
0291 {
0292     KisImageSP image = this->image();
0293 
0294     if (image && image->overlaySelectionMask() == this) {
0295         KisEffectMask::setDirty(rects);
0296     }
0297 }
0298 
0299 void KisSelectionMask::flattenSelectionProjection(KisSelectionSP selection, const QRect &dirtyRect) const
0300 {
0301     Q_UNUSED(selection);
0302     Q_UNUSED(dirtyRect);
0303 }
0304 
0305 void KisSelectionMask::Private::slotSelectionChangedCompressed()
0306 {
0307     KisSelectionSP currentSelection = q->selection();
0308     if (!currentSelection) return;
0309 
0310     currentSelection->notifySelectionChanged();
0311 }
0312 
0313 void KisSelectionMask::Private::slotConfigChangedImpl(bool doUpdates)
0314 {
0315     KisImageSP image = q->image();
0316 
0317     const KoColorSpace *cs = image ?
0318         image->colorSpace() :
0319         KoColorSpaceRegistry::instance()->rgb8();
0320 
0321     KisImageConfig cfg(true);
0322 
0323     maskColor = KoColor(cfg.selectionOverlayMaskColor(), cs);
0324 
0325     if (doUpdates && image && image->overlaySelectionMask() == q) {
0326         q->setDirty();
0327     }
0328 }
0329 
0330 void KisSelectionMask::Private::slotConfigChanged()
0331 {
0332     slotConfigChangedImpl(true);
0333 }
0334 
0335 #include "moc_kis_selection_mask.cpp"