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"