Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_tool_select_contiguous.cc
Go to the documentation of this file.
1/*
2 * kis_tool_select_contiguous - part of Krayon^WKrita
3 *
4 * SPDX-FileCopyrightText: 1999 Michael Koch <koch@kde.org>
5 * SPDX-FileCopyrightText: 2002 Patrick Julien <freak@codepimps.org>
6 * SPDX-FileCopyrightText: 2004 Boudewijn Rempt <boud@valdyas.org>
7 * SPDX-FileCopyrightText: 2012 José Luis Vergara <pentalis@gmail.com>
8 * SPDX-FileCopyrightText: 2015 Michael Abrahams <miabraha@gmail.com>
9 *
10 * SPDX-License-Identifier: GPL-2.0-or-later
11 */
12
14#include <QPainter>
15#include <QLayout>
16#include <QApplication>
17#include <QCheckBox>
18#include <QVBoxLayout>
19
22#include <KoGroupButton.h>
24#include <kis_color_button.h>
25
26#include <kis_debug.h>
27#include <klocalizedstring.h>
28#include <ksharedconfig.h>
29
30#include "KoPointerEvent.h"
31#include "KoViewConverter.h"
32
33#include "kis_cursor.h"
35#include "kis_image.h"
36#include "canvas/kis_canvas2.h"
37#include "kis_layer.h"
39#include "kis_paint_device.h"
40#include "kis_fill_painter.h"
41#include "kis_pixel_selection.h"
43#include "kis_slider_spin_box.h"
45#include "kis_image.h"
46#include "kis_undo_stores.h"
52
53#include "kis_command_utils.h"
54
57 canvas,
58 KisCursor::load("tool_contiguous_selection_cursor.png", 6, 6),
59 i18n("Contiguous Area Selection"))
60 , m_threshold(8)
61 , m_opacitySpread(100)
62 , m_useSelectionAsBoundary(false)
63 , m_previousTime(0)
64{
65 setObjectName("tool_select_contiguous");
66}
67
71
72void KisToolSelectContiguous::activate(const QSet<KoShape*> &shapes)
73{
75 m_configGroup = KSharedConfig::openConfig()->group(toolId());
76}
77
84
86{
88 if (isMovingSelection()) {
89 return;
90 }
91
93
94 if (!currentNode() ||
95 !(dev = currentNode()->projection()) ||
96 !selectionEditable()) {
97 event->ignore();
98 return;
99 }
100
102
103 KisCursorOverrideLock cursorLock(KisCursor::waitCursor());
104
105 // -------------------------------
106
107 KisProcessingApplicator applicator(currentImage(), currentNode(),
110 kundo2_i18n("Select Contiguous Area"));
111
112 QPoint pos = convertToImagePixelCoordFloored(event);
113 QRect rc = currentImage()->bounds();
114
115 KisPaintDeviceSP sourceDevice;
116
118 sourceDevice = m_referencePaintDevice = dev;
119 } else if (sampleLayersMode() == SampleAllLayers) {
120 sourceDevice = m_referencePaintDevice = currentImage()->projection();
122 if (!m_referenceNodeList) {
123 m_referencePaintDevice = KisMergeLabeledLayersCommand::createRefPaintDevice(image(), "Contiguous Selection Tool Reference Result Paint Device");
125 }
126 KisPaintDeviceSP newReferencePaintDevice = KisMergeLabeledLayersCommand::createRefPaintDevice(image(), "Contiguous Selection Tool Reference Result Paint Device");
128 const int currentTime = image()->animationInterface()->currentTime();
129 applicator.applyCommand(
131 image(),
133 newReferenceNodeList,
135 newReferencePaintDevice,
138 m_previousTime != currentTime
139 ),
142 );
143 sourceDevice = m_referencePaintDevice = newReferencePaintDevice;
144 m_referenceNodeList = newReferenceNodeList;
145 m_previousTime = currentTime;
146 }
147
149 // Reset this so that the device from color labeled layers gets
150 // regenerated when that mode is selected again
151 m_referenceNodeList.reset();
152 }
153
154 KisPixelSelectionSP selection =
156
157 ContiguousSelectionMode contiguousSelectionMode = m_contiguousSelectionMode;
158 KoColor contiguousSelectionBoundaryColor = m_contiguousSelectionBoundaryColor;
159 int threshold = m_threshold;
160 int opacitySpread = m_opacitySpread;
161 int closeGap = m_closeGap;
162 bool useSelectionAsBoundary = m_useSelectionAsBoundary;
163 bool antiAlias = antiAliasSelection();
164 int grow = growSelection();
166 int feather = featherSelection();
167
168 KisCanvas2 * kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
169 KIS_SAFE_ASSERT_RECOVER(kisCanvas) {
170 applicator.cancel();
171 return;
172 };
173
174 KisPixelSelectionSP existingSelection;
175 if (kisCanvas->imageView() && kisCanvas->imageView()->selection())
176 {
177 existingSelection = kisCanvas->imageView()->selection()->pixelSelection();
178 }
179
181 *cmd =
183 [dev,
184 rc,
185 contiguousSelectionMode,
186 contiguousSelectionBoundaryColor,
187 threshold,
188 opacitySpread,
189 closeGap,
190 antiAlias,
191 feather,
192 grow,
194 useSelectionAsBoundary,
195 selection,
196 pos,
197 sourceDevice,
198 existingSelection]() mutable -> KUndo2Command * {
199 KisFillPainter fillpainter(dev);
200 fillpainter.setHeight(rc.height());
201 fillpainter.setWidth(rc.width());
202 fillpainter.setRegionFillingMode(
203 contiguousSelectionMode == FloodFill
206 );
207 if (contiguousSelectionMode == BoundaryFill) {
208 fillpainter.setRegionFillingBoundaryColor(contiguousSelectionBoundaryColor);
209 }
210 fillpainter.setFillThreshold(threshold);
211 fillpainter.setOpacitySpread(opacitySpread);
212 fillpainter.setAntiAlias(antiAlias);
213 fillpainter.setFeather(feather);
214 fillpainter.setCloseGap(closeGap);
215 fillpainter.setSizemod(grow);
217 fillpainter.setUseCompositing(true);
218
219 useSelectionAsBoundary &=
220 existingSelection &&
221 !existingSelection->isEmpty() &&
222 existingSelection->pixel(pos).opacityU8() != OPACITY_TRANSPARENT_U8;
223
224 fillpainter.setUseSelectionAsBoundary(useSelectionAsBoundary);
225 fillpainter.createFloodSelection(selection, pos.x(), pos.y(), sourceDevice, existingSelection);
226
227 selection->invalidateOutlineCache();
228
229 return 0;
230 });
232
233
234
235 KisSelectionToolHelper helper(kisCanvas, kundo2_i18n("Select Contiguous Area"));
236
237 helper.selectPixelSelection(applicator, selection, selectionAction());
238
239 applicator.end();
240
241}
242
252
253void KisToolSelectContiguous::paint(QPainter &painter, const KoViewConverter &converter)
254{
255 Q_UNUSED(painter);
256 Q_UNUSED(converter);
257}
258
260 ContiguousSelectionMode contiguousSelectionMode)
261{
262 if (contiguousSelectionMode == m_contiguousSelectionMode) {
263 return;
264 }
265 m_contiguousSelectionMode = contiguousSelectionMode;
266 m_configGroup.writeEntry(
267 "contiguousSelectionMode",
268 contiguousSelectionMode == FloodFill
269 ? "floodFill"
270 : "boundaryFill"
271 );
272}
273
275 const KoColor &color)
276{
278 return;
279 }
281 m_configGroup.writeEntry("contiguousSelectionBoundaryColor", color.toXML());
282}
283
285{
286 m_threshold = threshold;
287 m_configGroup.writeEntry("threshold", threshold);
288}
289
291{
292 m_opacitySpread = opacitySpread;
293 m_configGroup.writeEntry("opacitySpread", opacitySpread);
294}
295
297{
298 m_closeGap = closeGap;
299 m_configGroup.writeEntry("closeGapAmount", closeGap);
300}
301
303{
304 m_useSelectionAsBoundary = useSelectionAsBoundary;
305 m_configGroup.writeEntry("useSelectionAsBoundary", useSelectionAsBoundary);
306}
307
310 bool checked)
311{
312 if (!checked) {
313 return;
314 }
315
316 KisOptionCollectionWidgetWithHeader *sectionSelectionExtent =
318 "sectionSelectionExtent"
319 );
320 const KoGroupButton *buttonContiguousSelectionModeBoundaryFill =
321 sectionSelectionExtent->primaryWidgetAs<KisOptionButtonStrip*>()->button(1);
322 const bool visible = button == buttonContiguousSelectionModeBoundaryFill;
323 sectionSelectionExtent->setWidgetVisible(
324 "buttonContiguousSelectionBoundaryColor", visible
325 );
326
328 button == buttonContiguousSelectionModeBoundaryFill
330 : FloodFill
331 );
332}
333
335{
336 const QString xmlColor =
337 m_configGroup.readEntry("contiguousSelectionBoundaryColor", QString());
338 QDomDocument doc;
339 if (doc.setContent(xmlColor)) {
340 QDomElement e = doc.documentElement().firstChild().toElement();
341 QString channelDepthID =
342 doc.documentElement().attribute("channeldepth",
344 bool ok;
345 if (e.hasAttribute("space") || e.tagName().toLower() == "srgb") {
346 return KoColor::fromXML(e, channelDepthID, &ok);
347 } else if (doc.documentElement().hasAttribute("space") ||
348 doc.documentElement().tagName().toLower() == "srgb") {
349 return KoColor::fromXML(doc.documentElement(), channelDepthID, &ok);
350 }
351 }
352 return KoColor();
353}
354
356{
358 KisSelectionOptions *selectionWidget = selectionOptionWidget();
359
360 selectionWidget->setStopGrowingAtDarkestPixelButtonVisible(true);
361
362 // Create widgets
363 KisOptionButtonStrip *optionButtonStripContiguousSelectionMode =
365 KoGroupButton *buttonContiguousSelectionModeFloodFill =
366 optionButtonStripContiguousSelectionMode->addButton(
367 KisIconUtils::loadIcon("region-filling-flood-fill")
368 );
369 KoGroupButton *buttonContiguousSelectionModeBoundaryFill =
370 optionButtonStripContiguousSelectionMode->addButton(
371 KisIconUtils::loadIcon("region-filling-boundary-fill")
372 );
373 buttonContiguousSelectionModeFloodFill->setChecked(true);
374 KisColorButton *buttonContiguousSelectionBoundaryColor = new KisColorButton;
375 KisSliderSpinBox *sliderThreshold = new KisSliderSpinBox;
376 sliderThreshold->setPrefix(i18nc(
377 "The 'threshold' spinbox prefix in contiguous selection tool options",
378 "Threshold: "));
379 sliderThreshold->setRange(1, 100);
380 KisSliderSpinBox *sliderSpread = new KisSliderSpinBox;
381 sliderSpread->setRange(0, 100);
383 sliderSpread,
384 i18nc(
385 "The 'spread' spinbox in contiguous selection tool options; {n} is the number value, % is the percent sign",
386 "Spread: {n}%"));
387 QCheckBox *checkBoxSelectionAsBoundary = new QCheckBox(i18nc(
388 "The 'use selection as boundary' checkbox in contiguous selection tool "
389 "to use selection borders as boundary when filling",
390 "Use selection as boundary"));
391 checkBoxSelectionAsBoundary->setSizePolicy(QSizePolicy::Ignored,
392 QSizePolicy::Preferred);
393
394 // Set the tooltips
395 buttonContiguousSelectionModeFloodFill->setToolTip(
396 i18n("Select regions similar in color to the clicked region"));
397 buttonContiguousSelectionModeBoundaryFill->setToolTip(
398 i18n("Select all regions until a specific boundary color"));
399 buttonContiguousSelectionBoundaryColor->setToolTip(i18n("Boundary color"));
400 sliderThreshold->setToolTip(
401 i18n("Set the color similarity tolerance of the selection. "
402 "Increasing threshold increases the range of similar colors to be selected."));
403 sliderSpread->setToolTip(i18n(
404 "Set the extent of the opaque portion of the selection. "
405 "Decreasing spread decreases opacity of selection areas depending on color similarity."));
406 checkBoxSelectionAsBoundary->setToolTip(
407 i18n("Set if the contour of the active selection should be treated as "
408 "a boundary when making a new selection"));
409
410
411 KisSliderSpinBox *sliderCloseGap = new KisSliderSpinBox;
412 sliderCloseGap->setPrefix(i18nc("The 'close gap' spinbox prefix in contiguous selection tool options", "Close Gap: "));
413 sliderCloseGap->setRange(0, 32);
414 sliderCloseGap->setSuffix(i18n(" px"));
415
416 // Construct the option widget
417 KisOptionCollectionWidgetWithHeader *sectionSelectionExtent =
419 i18nc("The 'selection extent' section label in contiguous "
420 "selection tool options",
421 "Selection extent"));
422 sectionSelectionExtent->setPrimaryWidget(
423 optionButtonStripContiguousSelectionMode
424 );
425 sectionSelectionExtent->appendWidget(
426 "buttonContiguousSelectionBoundaryColor",
427 buttonContiguousSelectionBoundaryColor
428 );
429 sectionSelectionExtent->setWidgetVisible(
430 "buttonContiguousSelectionBoundaryColor",
431 false
432 );
433 sectionSelectionExtent->appendWidget("sliderThreshold", sliderThreshold);
434 sectionSelectionExtent->appendWidget("sliderSpread", sliderSpread);
435 sectionSelectionExtent->appendWidget("sliderCloseGap", sliderCloseGap);
436 sectionSelectionExtent->appendWidget("checkBoxSelectionAsBoundary",
437 checkBoxSelectionAsBoundary);
438
439 selectionWidget->insertWidget(3,
440 "sectionSelectionExtent",
441 sectionSelectionExtent);
442
443 // Load configuration settings into tool options
444 const QString contiguousSelectionModeStr =
445 m_configGroup.readEntry<QString>("contiguousSelectionMode", "");
447 contiguousSelectionModeStr == "boundaryFill"
449 : FloodFill;
452 if (m_configGroup.hasKey("threshold")) {
453 m_threshold = m_configGroup.readEntry("threshold", 8);
454 } else {
455 m_threshold = m_configGroup.readEntry("fuzziness", 8);
456 }
457 m_opacitySpread = m_configGroup.readEntry("opacitySpread", 100);
458 m_closeGap = m_configGroup.readEntry("closeGapAmount", 0);
460 m_configGroup.readEntry("useSelectionAsBoundary", false);
461
463 buttonContiguousSelectionModeBoundaryFill->setChecked(true);
464 sectionSelectionExtent->setWidgetVisible(
465 "buttonContiguousSelectionBoundaryColor",
466 true
467 );
468 }
469 buttonContiguousSelectionBoundaryColor->setColor(
471 );
472 sliderThreshold->setValue(m_threshold);
473 sliderSpread->setValue(m_opacitySpread);
474 sliderCloseGap->setValue(m_closeGap);
475 checkBoxSelectionAsBoundary->setChecked(m_useSelectionAsBoundary);
476
477 // Make connections
478 connect(optionButtonStripContiguousSelectionMode,
479 SIGNAL(buttonToggled(KoGroupButton*, bool)),
481 KoGroupButton*, bool)));
482 connect(buttonContiguousSelectionBoundaryColor,
483 SIGNAL(changed(const KoColor&)),
485 connect(sliderThreshold,
486 SIGNAL(valueChanged(int)),
487 this,
488 SLOT(slotSetThreshold(int)));
489 connect(sliderSpread,
490 SIGNAL(valueChanged(int)),
491 this,
492 SLOT(slotSetOpacitySpread(int)));
493 connect(sliderCloseGap,
494 SIGNAL(valueChanged(int)),
495 this,
496 SLOT(slotSetCloseGap(int)));
497 connect(checkBoxSelectionAsBoundary,
498 SIGNAL(toggled(bool)),
499 this,
501
502 return selectionWidget;
503}
504
506{
508 useCursor(KisCursor::load("tool_contiguous_selection_cursor_add.png", 6, 6));
509 } else if (selectionAction() == SELECTION_SUBTRACT) {
510 useCursor(KisCursor::load("tool_contiguous_selection_cursor_sub.png", 6, 6));
511 } else if (selectionAction() == SELECTION_INTERSECT) {
512 useCursor(KisCursor::load("tool_contiguous_selection_cursor_inter.png", 6, 6));
514 useCursor(KisCursor::load("tool_contiguous_selection_cursor_symdiff.png", 6, 6));
515 } else {
516 KisToolSelect::resetCursorStyle();
517 }
518}
QVector< KisImageSignalType > KisImageSignalVector
@ SELECTION_INTERSECT
@ SELECTION_SYMMETRICDIFFERENCE
@ SELECTION_SUBTRACT
@ SELECTION_ADD
const KoID Integer16BitsColorDepthID("U16", ki18n("16-bit integer/channel"))
const quint8 OPACITY_TRANSPARENT_U8
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
QPointer< KisView > imageView() const
A pushbutton to display or allow user selection of a color.
void setColor(const KoColor &c)
static QCursor load(const QString &cursorName, int hotspotX=-1, int hotspotY=-1)
static QCursor waitCursor()
Definition kis_cursor.cc:54
void setWidth(int w)
void setSizemod(int sizemod)
void setHeight(int h)
void setFillThreshold(int threshold)
void setFeather(int feather)
void setCloseGap(int gap)
void setRegionFillingMode(RegionFillingMode regionFillingMode)
KisPixelSelectionSP createFloodSelection(int startX, int startY, KisPaintDeviceSP sourceDevice, KisPaintDeviceSP existingSelection)
void setUseSelectionAsBoundary(bool useSelectionAsBoundary)
void setRegionFillingBoundaryColor(const KoColor &regionFillingBoundaryColor)
void setUseCompositing(bool useCompositing)
void setAntiAlias(bool antiAlias)
void setStopGrowingAtDarkestPixel(bool stopGrowingAtDarkestPixel)
void setOpacitySpread(int opacitySpread)
static KisPaintDeviceSP createRefPaintDevice(KisImageSP originalImage, QString name="Merge Labeled Layers Reference Paint Device")
@ GroupSelectionPolicy_SelectIfColorLabeled
Groups will be taken into account only if they have set an explicit color label. This ignores groups ...
Provides a list of consecutive tool buttons.
KoGroupButton * addButton(const QIcon &icon, const QString &text=QString())
Wrapper class around a KisOptionCollectionWidget that also provide a header with a title label and an...
void setPrimaryWidget(QWidget *widget)
Set the primary widget. The list widget takes ownership of it.
void appendWidget(const QString &id, QWidget *widget)
Insert the given widget with the given id at the end of the list. The list widget takes ownership of ...
T primaryWidgetAs() const
Get the primary widget casted to some other class.
void setWidgetVisible(int index, bool visible)
Set the visibility of the widget that is at the given position.
void insertWidget(int index, const QString &id, QWidget *widget)
Insert the given widget with the given id at the given position. The list widget takes ownership of t...
T widgetAs(int index) const
Get the widget that is at the given position casted to some other class.
bool pixel(qint32 x, qint32 y, QColor *c) const
void applyCommand(KUndo2Command *command, KisStrokeJobData::Sequentiality sequentiality=KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::Exclusivity exclusivity=KisStrokeJobData::NORMAL)
void setStopGrowingAtDarkestPixelButtonVisible(bool visible)
void selectPixelSelection(KisProcessingApplicator &applicator, KisPixelSelectionSP selection, SelectionAction action)
This class is a spinbox in which you can click and drag to set the value. A slider like bar is displa...
void setValue(int newValue)
void setRange(int newMinimum, int newMaximum, bool computeNewFastSliderStep=true)
Set the minimum and the maximum values of the range, computing a new "fast slider step" based on the ...
KisSelectionOptions * selectionOptionWidget()
SampleLayersMode sampleLayersMode() const
SelectionAction selectionAction() const
void beginPrimaryAction(KoPointerEvent *event) override
QList< int > colorLabelsSelected() const
void activate(const QSet< KoShape * > &shapes) override
void endPrimaryAction(KoPointerEvent *event) override
bool stopGrowingAtDarkestPixel() const
void deactivate() override
QWidget * createOptionWidget() override
bool antiAliasSelection() const
KisMergeLabeledLayersCommand::ReferenceNodeInfoListSP m_referenceNodeList
KisToolSelectContiguous(KoCanvasBase *canvas)
void slotSetContiguousSelectionMode(ContiguousSelectionMode)
void activate(const QSet< KoShape * > &shapes) override
void paint(QPainter &painter, const KoViewConverter &converter) override
void beginPrimaryAction(KoPointerEvent *event) override
ContiguousSelectionMode m_contiguousSelectionMode
void slotSetContiguousSelectionBoundaryColor(const KoColor &)
void endPrimaryAction(KoPointerEvent *event) override
void slot_optionButtonStripContiguousSelectionMode_buttonToggled(KoGroupButton *, bool)
static KoColor fromXML(const QDomElement &elt, const QString &channelDepthId)
Definition KoColor.cpp:350
void toXML(QDomDocument &doc, QDomElement &colorElt) const
Definition KoColor.cpp:304
QString id() const
Definition KoID.cpp:63
#define KIS_SAFE_ASSERT_RECOVER(cond)
Definition kis_assert.h:126
QString button(const QWheelEvent &ev)
KUndo2MagicString kundo2_i18n(const char *text)
QIcon loadIcon(const QString &name)
void setText(QSpinBox *spinBox, const QStringView textTemplate)
The LambdaCommand struct is a shorthand for creation of AggregateCommand commands using C++ lambda fe...
bool isEmpty() const override