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 }