File indexing completed on 2024-06-16 04:17:50
0001 /* 0002 * SPDX-FileCopyrightText: 1999 Matthias Elter <me@kde.org> 0003 * SPDX-FileCopyrightText: 2002 Patrick Julien <freak@codepimps.org> 0004 * SPDX-FileCopyrightText: 2005 Boudewijn Rempt <boud@valdyas.org> 0005 * SPDX-FileCopyrightText: 2015 Michael Abrahams <miabraha@gmail.com> 0006 * 0007 * SPDX-License-Identifier: GPL-2.0-or-later 0008 */ 0009 0010 #include "kis_tool_select_similar.h" 0011 0012 #include <QApplication> 0013 #include <QHBoxLayout> 0014 #include <QLabel> 0015 #include <QVBoxLayout> 0016 0017 #include <ksharedconfig.h> 0018 0019 #include <KoColorSpace.h> 0020 #include <KisCursorOverrideLock.h> 0021 #include <KisSpinBoxI18nHelper.h> 0022 0023 #include "kis_canvas2.h" 0024 #include "kis_command_utils.h" 0025 #include "kis_image.h" 0026 #include "kis_iterator_ng.h" 0027 #include "kis_selection_tool_helper.h" 0028 #include "kis_slider_spin_box.h" 0029 #include "krita_utils.h" 0030 #include <KoPointerEvent.h> 0031 #include <kis_cursor.h> 0032 #include <kis_paint_device.h> 0033 #include <kis_pixel_selection.h> 0034 #include <kis_selection_filters.h> 0035 #include <kis_selection_options.h> 0036 #include <kis_image_animation_interface.h> 0037 #include <kis_default_bounds.h> 0038 #include <kis_fill_painter.h> 0039 0040 KisToolSelectSimilar::KisToolSelectSimilar(KoCanvasBase *canvas) 0041 : KisToolSelect(canvas, 0042 KisCursor::load("tool_similar_selection_cursor.png", 6, 6), 0043 i18n("Similar Color Selection")) 0044 , m_threshold(20) 0045 , m_opacitySpread(100) 0046 , m_previousTime(0) 0047 { 0048 } 0049 0050 void KisToolSelectSimilar::activate(const QSet<KoShape*> &shapes) 0051 { 0052 KisToolSelect::activate(shapes); 0053 m_configGroup = KSharedConfig::openConfig()->group(toolId()); 0054 } 0055 0056 void KisToolSelectSimilar::deactivate() 0057 { 0058 m_referencePaintDevice = nullptr; 0059 m_referenceNodeList = nullptr; 0060 KisToolSelect::deactivate(); 0061 } 0062 0063 void KisToolSelectSimilar::beginPrimaryAction(KoPointerEvent *event) 0064 { 0065 KisToolSelectBase::beginPrimaryAction(event); 0066 if (isMovingSelection()) { 0067 return; 0068 } 0069 0070 KisPaintDeviceSP dev; 0071 0072 if (!currentNode() || 0073 !(currentNode()->projection()) || 0074 !selectionEditable()) { 0075 0076 event->ignore(); 0077 return; 0078 } 0079 0080 KisCanvas2 * kisCanvas = dynamic_cast<KisCanvas2*>(canvas()); 0081 KIS_SAFE_ASSERT_RECOVER_RETURN(kisCanvas); 0082 0083 beginSelectInteraction(); 0084 0085 KisCursorOverrideLock cursorLock(KisCursor::waitCursor()); 0086 0087 // Create the stroke 0088 KisStrokeStrategyUndoCommandBased *strategy = 0089 new KisStrokeStrategyUndoCommandBased(kundo2_i18n("Select Similar Color"), false, image().data()); 0090 strategy->setSupportsWrapAroundMode(false); 0091 KisStrokeId strokeId = image()->startStroke(strategy); 0092 0093 // Construct the reference device 0094 if (sampleLayersMode() == SampleCurrentLayer) { 0095 m_referencePaintDevice = currentNode()->projection(); 0096 } else if (sampleLayersMode() == SampleAllLayers) { 0097 m_referencePaintDevice = currentImage()->projection(); 0098 } else if (sampleLayersMode() == SampleColorLabeledLayers) { 0099 if (!m_referenceNodeList) { 0100 m_referencePaintDevice = KisMergeLabeledLayersCommand::createRefPaintDevice(image(), "Similar Colors Selection Tool Reference Result Paint Device"); 0101 m_referenceNodeList.reset(new KisMergeLabeledLayersCommand::ReferenceNodeInfoList); 0102 } 0103 KisPaintDeviceSP newReferencePaintDevice = KisMergeLabeledLayersCommand::createRefPaintDevice(image(), "Similar Colors Selection Tool Reference Result Paint Device"); 0104 KisMergeLabeledLayersCommand::ReferenceNodeInfoListSP newReferenceNodeList(new KisMergeLabeledLayersCommand::ReferenceNodeInfoList); 0105 const int currentTime = image()->animationInterface()->currentTime(); 0106 0107 image()->addJob( 0108 strokeId, 0109 new KisStrokeStrategyUndoCommandBased::Data( 0110 KUndo2CommandSP(new KisMergeLabeledLayersCommand( 0111 image(), 0112 m_referenceNodeList, 0113 newReferenceNodeList, 0114 m_referencePaintDevice, 0115 newReferencePaintDevice, 0116 colorLabelsSelected(), 0117 KisMergeLabeledLayersCommand::GroupSelectionPolicy_SelectIfColorLabeled, 0118 m_previousTime != currentTime 0119 )), 0120 false, 0121 KisStrokeJobData::SEQUENTIAL, 0122 KisStrokeJobData::EXCLUSIVE 0123 ) 0124 ); 0125 0126 m_referencePaintDevice = newReferencePaintDevice; 0127 m_referenceNodeList = newReferenceNodeList; 0128 m_previousTime = currentTime; 0129 } 0130 0131 // Get the color of the pixel where the user clicked 0132 KisPaintDeviceSP sourceDevice = m_referencePaintDevice; 0133 const QPoint pos = convertToImagePixelCoordFloored(event); 0134 QSharedPointer<KoColor> referenceColor = QSharedPointer<KoColor>(new KoColor(sourceDevice->colorSpace())); 0135 // We need to obtain the reference color from the reference paint 0136 // device, but it is produced in a stroke, so we must get the color 0137 // after the device is ready. So we get it in the stroke 0138 image()->addJob( 0139 strokeId, 0140 new KisStrokeStrategyUndoCommandBased::Data( 0141 KUndo2CommandSP(new KisCommandUtils::LambdaCommand( 0142 [sourceDevice, referenceColor, pos]() -> KUndo2Command* 0143 { 0144 *referenceColor = sourceDevice->pixel(pos); 0145 return 0; 0146 } 0147 )), 0148 false, 0149 KisStrokeJobData::SEQUENTIAL, 0150 KisStrokeJobData::EXCLUSIVE 0151 ) 0152 ); 0153 0154 // Get the similar colors selection 0155 KisFillPainter painter; 0156 QRect bounds = currentImage()->bounds(); 0157 QSharedPointer<KisProcessingVisitor::ProgressHelper> 0158 progressHelper(new KisProcessingVisitor::ProgressHelper(currentNode())); 0159 KisPixelSelectionSP tmpSel = new KisPixelSelection(new KisSelectionDefaultBounds(currentNode()->projection())); 0160 0161 painter.setFillThreshold(m_threshold); 0162 painter.setOpacitySpread(m_opacitySpread); 0163 painter.setAntiAlias(antiAliasSelection()); 0164 painter.setSizemod(growSelection()); 0165 painter.setStopGrowingAtDarkestPixel(this->stopGrowingAtDarkestPixel()); 0166 painter.setFeather(featherSelection()); 0167 0168 QVector<KisStrokeJobData*> jobs = 0169 painter.createSimilarColorsSelectionJobs( 0170 tmpSel, referenceColor, sourceDevice, 0171 bounds, nullptr, progressHelper 0172 ); 0173 0174 for (KisStrokeJobData *job : jobs) { 0175 image()->addJob(strokeId, job); 0176 } 0177 0178 image()->addJob( 0179 strokeId, 0180 new KisStrokeStrategyUndoCommandBased::Data( 0181 KUndo2CommandSP(new KisCommandUtils::LambdaCommand( 0182 [tmpSel]() mutable -> KUndo2Command* 0183 { 0184 tmpSel->invalidateOutlineCache(); 0185 return 0; 0186 } 0187 )), 0188 false, 0189 KisStrokeJobData::SEQUENTIAL, 0190 KisStrokeJobData::EXCLUSIVE 0191 ) 0192 ); 0193 0194 image()->endStroke(strokeId); 0195 0196 // Apply selection 0197 KisSelectionToolHelper helper(kisCanvas, kundo2_i18n("Select Similar Color")); 0198 helper.selectPixelSelection(tmpSel, selectionAction()); 0199 } 0200 0201 void KisToolSelectSimilar::endPrimaryAction(KoPointerEvent *event) 0202 { 0203 if (isMovingSelection()) { 0204 KisToolSelectBase::endPrimaryAction(event); 0205 return; 0206 } 0207 0208 endSelectInteraction(); 0209 } 0210 0211 void KisToolSelectSimilar::slotSetThreshold(int threshold) 0212 { 0213 m_threshold = threshold; 0214 m_configGroup.writeEntry("threshold", threshold); 0215 } 0216 0217 void KisToolSelectSimilar::slotSetOpacitySpread(int opacitySpread) 0218 { 0219 m_opacitySpread = opacitySpread; 0220 m_configGroup.writeEntry("opacitySpread", opacitySpread); 0221 } 0222 0223 QWidget* KisToolSelectSimilar::createOptionWidget() 0224 { 0225 KisToolSelectBase::createOptionWidget(); 0226 KisSelectionOptions *selectionWidget = selectionOptionWidget(); 0227 0228 selectionWidget->setStopGrowingAtDarkestPixelButtonVisible(true); 0229 0230 // Create widgets 0231 KisSliderSpinBox *sliderThreshold = new KisSliderSpinBox; 0232 sliderThreshold->setPrefix(i18nc( 0233 "The 'threshold' spinbox prefix in similar selection tool options", 0234 "Threshold: ")); 0235 sliderThreshold->setRange(1, 100); 0236 sliderThreshold->setSingleStep(1); 0237 sliderThreshold->setToolTip( 0238 i18n("Set the color similarity tolerance of the selection. " 0239 "Increasing threshold increases the range of similar colors to be selected.")); 0240 0241 KisSliderSpinBox *sliderSpread = new KisSliderSpinBox; 0242 sliderSpread->setRange(0, 100); 0243 KisSpinBoxI18nHelper::setText(sliderSpread, 0244 i18nc("The 'spread' spinbox in similar color selection tool options; {n} is the " 0245 "number value, % is the percent sign", 0246 "Spread: {n}%")); 0247 0248 // Set the tooltips 0249 sliderThreshold->setToolTip( 0250 i18n("Set the color similarity tolerance of the selection. " 0251 "Increasing threshold increases the range of similar colors to be selected.")); 0252 sliderSpread->setToolTip( 0253 i18n("Set the extent of the opaque portion of the selection. " 0254 "Decreasing spread decreases opacity of selection areas depending on color similarity.")); 0255 0256 // Construct the option widget 0257 KisOptionCollectionWidgetWithHeader *sectionSelectionExtent = 0258 new KisOptionCollectionWidgetWithHeader( 0259 i18nc("The 'selection extent' section label in similar selection " 0260 "tool options", 0261 "Selection extent")); 0262 sectionSelectionExtent->appendWidget("sliderThreshold", sliderThreshold); 0263 sectionSelectionExtent->appendWidget("sliderSpread", sliderSpread); 0264 selectionWidget->insertWidget(3, "sectionSelectionExtent", sectionSelectionExtent); 0265 0266 // load setting from config 0267 if (m_configGroup.hasKey("threshold")) { 0268 m_threshold = m_configGroup.readEntry("threshold", 20); 0269 } else { 0270 m_threshold = m_configGroup.readEntry("fuzziness", 20); 0271 } 0272 sliderThreshold->setValue(m_threshold); 0273 0274 m_opacitySpread = m_configGroup.readEntry("opacitySpread", 100); 0275 sliderSpread->setValue(m_opacitySpread); 0276 0277 // Make connections 0278 connect(sliderThreshold, 0279 SIGNAL(valueChanged(int)), 0280 this, 0281 SLOT(slotSetThreshold(int))); 0282 connect(sliderSpread, 0283 SIGNAL(valueChanged(int)), 0284 this, 0285 SLOT(slotSetOpacitySpread(int))); 0286 0287 return selectionWidget; 0288 } 0289 0290 void KisToolSelectSimilar::resetCursorStyle() 0291 { 0292 if (selectionAction() == SELECTION_ADD) { 0293 useCursor(KisCursor::load("tool_similar_selection_cursor_add.png", 6, 6)); 0294 } else if (selectionAction() == SELECTION_SUBTRACT) { 0295 useCursor(KisCursor::load("tool_similar_selection_cursor_sub.png", 6, 6)); 0296 } else if (selectionAction() == SELECTION_INTERSECT) { 0297 useCursor(KisCursor::load("tool_similar_selection_cursor_inter.png", 6, 6)); 0298 } else if (selectionAction() == SELECTION_SYMMETRICDIFFERENCE) { 0299 useCursor(KisCursor::load("tool_similar_selection_cursor_symdiff.png", 6, 6)); 0300 } else { 0301 KisToolSelect::resetCursorStyle(); 0302 } 0303 }