Commit 7748d8d1 authored by Agata Cacko's avatar Agata Cacko

Implement Fill Tool and magic wand on color labeled layers

Before this commit, Fill Tool and Contiguous Selection Tool
would have only two modes: to work on
a specific layer or to work on a projection of the whole image.
This commit adds a new mode: working on a set of layers that are
labeled with a specific color label. The user can choose it in
Tool Options.

Three modes:
- current layer
- all layers
- color labeled layers - there can be multiple labels selected
parent f138e606
......@@ -113,6 +113,7 @@ set(kritaimage_LIB_SRCS
commands_new/KisHoldUIUpdatesCommand.cpp
commands_new/KisChangeChannelFlagsCommand.cpp
commands_new/KisChangeChannelLockFlagsCommand.cpp
commands_new/KisMergeLabeledLayersCommand.cpp
processing/kis_do_nothing_processing_visitor.cpp
processing/kis_simple_processing_visitor.cpp
processing/kis_convert_color_space_processing_visitor.cpp
......
/*
* Copyright (c) 2020 Agata Cacko <cacko.azh@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "KisMergeLabeledLayersCommand.h"
#include "KoCompositeOpRegistry.h"
#include "kis_layer_utils.h"
#include "kis_node.h"
#include "kis_image.h"
#include "kis_painter.h"
#include "kis_layer.h"
KisMergeLabeledLayersCommand::KisMergeLabeledLayersCommand(KisImageSP refImage, KisPaintDeviceSP refPaintDevice,
KisNodeSP currentRoot, QList<int> selectedLabels)
: KUndo2Command(kundo2_noi18n("MERGE_LABELED_LAYERS"))
, m_refImage(refImage)
, m_refPaintDevice(refPaintDevice)
, m_currentRoot(currentRoot)
, m_selectedLabels(selectedLabels)
{
}
KisMergeLabeledLayersCommand::~KisMergeLabeledLayersCommand()
{
}
void KisMergeLabeledLayersCommand::undo()
{
KUndo2Command::undo();
}
void KisMergeLabeledLayersCommand::redo()
{
mergeLabeledLayers();
KUndo2Command::redo();
}
KisImageSP KisMergeLabeledLayersCommand::createRefImage(KisImageSP originalImage, QString name = "Reference Image")
{
return KisImageSP(new KisImage(new KisSurrogateUndoStore(), originalImage->width(), originalImage->height(),
originalImage->colorSpace(), name));
}
KisPaintDeviceSP KisMergeLabeledLayersCommand::createRefPaintDevice(KisImageSP originalImage, QString name = "Reference Result Paint Device")
{
return KisPaintDeviceSP(new KisPaintDevice(originalImage->colorSpace(), name));
}
void KisMergeLabeledLayersCommand::mergeLabeledLayers()
{
QList<KisNodeSP> nodesList;
KisImageSP refImage = m_refImage;
KisLayerUtils::recursiveApplyNodes(m_currentRoot, [&nodesList, refImage, this] (KisNodeSP node) mutable {
if (acceptNode(node)) {
KisNodeSP copy = node->clone();
if (copy.isNull()) {
return;
}
if (node->inherits("KisLayer")) {
KisLayer* layerCopy = dynamic_cast<KisLayer*>(copy.data());
layerCopy->setChannelFlags(QBitArray());
}
copy->setCompositeOpId(COMPOSITE_OVER);
bool success = refImage->addNode(copy, refImage->root());
if (!success) {
return;
}
nodesList << copy;
}
});
nodesList = KisLayerUtils::sortAndFilterAnyMergableNodesSafe(nodesList, m_refImage);
m_refImage->initialRefreshGraph();
if (m_refImage->root()->childCount() == 0) {
return;
}
m_refImage->waitForDone();
m_refImage->mergeMultipleLayers(nodesList, 0);
m_refImage->waitForDone();
KisPainter::copyAreaOptimized(QPoint(), m_refImage->projection(), m_refPaintDevice, m_refImage->bounds());
}
bool KisMergeLabeledLayersCommand::acceptNode(KisNodeSP node)
{
return m_selectedLabels.contains(node->colorLabelIndex());
}
/*
* Copyright (c) 2020 Agata Cacko <cacko.azh@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_MERGE_LABELED_LAYERS_H
#define __KIS_MERGE_LABELED_LAYERS_H
#include <QList>
#include <QString>
#include "kundo2command.h"
#include "kritaimage_export.h"
#include "kis_types.h"
#include "kis_image.h"
class KisUpdatesFacade;
class KRITAIMAGE_EXPORT KisMergeLabeledLayersCommand : public KUndo2Command
{
public:
KisMergeLabeledLayersCommand(KisImageSP refImage, KisPaintDeviceSP refPaintDevice, KisNodeSP currentRoot, QList<int> selectedLabels);
~KisMergeLabeledLayersCommand() override;
void undo() override;
void redo() override;
static KisImageSP createRefImage(KisImageSP originalImage, QString name);
static KisPaintDeviceSP createRefPaintDevice(KisImageSP originalImage, QString name);
private:
void mergeLabeledLayers();
bool acceptNode(KisNodeSP node);
private:
KisImageSP m_refImage;
KisPaintDeviceSP m_refPaintDevice;
KisNodeSP m_currentRoot;
QList<int> m_selectedLabels;
};
#endif /* __KIS_MERGE_LABELED_LAYERS_H */
......@@ -223,7 +223,8 @@ void KisFillActionFactory::run(const QString &fillSource, KisViewManager *view)
}
KisProcessingVisitorSP visitor =
new FillProcessingVisitor(QPoint(0, 0), // start position
new FillProcessingVisitor(resources->image()->projection(),
QPoint(0, 0), // start position
selection,
resources,
false, // fast mode
......@@ -232,7 +233,7 @@ void KisFillActionFactory::run(const QString &fillSource, KisViewManager *view)
0, // feathering radius
0, // sizemod
80, // threshold,
false, // unmerged
false, // use unmerged
useBgColor);
applicator.applyVisitor(visitor,
......
......@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>271</width>
<height>110</height>
<height>159</height>
</rect>
</property>
<layout class="QFormLayout" name="formLayout">
......@@ -185,6 +185,12 @@
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="KisColorFilterCombo" name="cmbColorLabels"/>
</item>
<item row="2" column="0" colspan="2">
<widget class="QComboBox" name="cmbSampleLayersMode"/>
</item>
</layout>
</widget>
<customwidgets>
......@@ -193,6 +199,11 @@
<extends>QToolButton</extends>
<header location="global">KoGroupButton.h</header>
</customwidget>
<customwidget>
<class>KisColorFilterCombo</class>
<extends>QComboBox</extends>
<header>kis_color_filter_combo.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
......
......@@ -25,7 +25,8 @@
#include "lazybrush/kis_colorize_mask.h"
FillProcessingVisitor::FillProcessingVisitor(const QPoint &startPoint,
FillProcessingVisitor::FillProcessingVisitor(KisPaintDeviceSP refPaintDevice,
const QPoint &startPoint,
KisSelectionSP selection,
KisResourcesSnapshotSP resources,
bool useFastMode,
......@@ -36,7 +37,8 @@ FillProcessingVisitor::FillProcessingVisitor(const QPoint &startPoint,
int fillThreshold,
bool unmerged,
bool useBgColor)
: m_startPoint(startPoint),
: m_refPaintDevice(refPaintDevice),
m_startPoint(startPoint),
m_selection(selection),
m_useFastMode(useFastMode),
m_selectionOnly(selectionOnly),
......@@ -125,7 +127,7 @@ void FillProcessingVisitor::fillPaintDevice(KisPaintDeviceSP device, KisUndoAdap
fillPainter.setHeight(fillRect.height());
fillPainter.setUseCompositioning(!m_useFastMode);
KisPaintDeviceSP sourceDevice = m_unmerged ? device : m_resources->image()->projection();
KisPaintDeviceSP sourceDevice = m_unmerged ? device : m_refPaintDevice;
if (m_usePattern) {
fillPainter.fillPattern(startPoint.x(), startPoint.y(), sourceDevice);
......
......@@ -30,7 +30,9 @@
class KRITAUI_EXPORT FillProcessingVisitor : public KisSimpleProcessingVisitor
{
public:
FillProcessingVisitor(const QPoint &startPoint,
FillProcessingVisitor(
KisPaintDeviceSP referencePaintDevice,
const QPoint &startPoint,
KisSelectionSP selection,
KisResourcesSnapshotSP resources,
bool useFastMode,
......@@ -50,6 +52,7 @@ private:
void fillPaintDevice(KisPaintDeviceSP device, KisUndoAdapter *undoAdapter, ProgressHelper &helper);
private:
KisPaintDeviceSP m_refPaintDevice;
QPoint m_startPoint;
KisSelectionSP m_selection;
bool m_useFastMode;
......
......@@ -70,13 +70,16 @@ public:
manager);
KisProcessingVisitorSP visitor =
new FillProcessingVisitor(QPoint(100,100),
new FillProcessingVisitor(0,
QPoint(100,100),
image->globalSelection(),
resources,
false, // useFastMode
usePattern,
selectionOnly,
10, 10, 10, true, false);
10, 10, 10,
true /* use the current device (unmerged) */,
false);
KisProcessingApplicator applicator(image, fillNode,
......
......@@ -58,9 +58,17 @@ void KisSelectionToolConfigWidgetHelper::createOptionWidget(KisCanvas2 *canvas,
connect(m_optionsWidget, &KisSelectionOptions::antiAliasSelectionChanged,
this, &KisSelectionToolConfigWidgetHelper::slotWidgetAntiAliasChanged);
connect(m_optionsWidget, &KisSelectionOptions::selectedColorLabelsChanged,
this, &KisSelectionToolConfigWidgetHelper::slotSelectedColorLabelsChanged);
connect(m_optionsWidget, &KisSelectionOptions::sampleLayersModeChanged,
this, &KisSelectionToolConfigWidgetHelper::slotSampleLayersModeChanged);
m_optionsWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
m_optionsWidget->adjustSize();
m_sampleLayersMode = m_optionsWidget->sampleLayersMode();
}
KisSelectionOptions* KisSelectionToolConfigWidgetHelper::optionWidget() const
......@@ -83,6 +91,29 @@ bool KisSelectionToolConfigWidgetHelper::antiAliasSelection() const
return m_antiAliasSelection;
}
QList<int> KisSelectionToolConfigWidgetHelper::colorLabelsSelected() const
{
return m_colorLabelsSelected;
}
QString KisSelectionToolConfigWidgetHelper::sampleLayersMode() const
{
return m_sampleLayersMode;
}
void KisSelectionToolConfigWidgetHelper::setConfigGroupForExactTool(QString toolId)
{
m_configGroupForTool = toolId;
if (m_configGroupForTool != "") {
KConfigGroup cfgToolSpecific = KSharedConfig::openConfig()->group(m_configGroupForTool);
QString newSampleMode = cfgToolSpecific.readEntry("sampleLayersMode", m_optionsWidget->SAMPLE_LAYERS_MODE_CURRENT);
if (newSampleMode != m_sampleLayersMode) {
m_optionsWidget->setSampleLayersMode(newSampleMode);
}
m_sampleLayersMode = newSampleMode;
}
}
void KisSelectionToolConfigWidgetHelper::slotWidgetActionChanged(int action)
{
if (action >= SELECTION_REPLACE && action <= SELECTION_SYMMETRICDIFFERENCE) {
......@@ -111,6 +142,18 @@ void KisSelectionToolConfigWidgetHelper::slotWidgetAntiAliasChanged(bool value)
cfg.writeEntry("antiAliasSelection", value);
}
void KisSelectionToolConfigWidgetHelper::slotSelectedColorLabelsChanged()
{
m_colorLabelsSelected = m_optionsWidget->colorLabelsSelected();
}
void KisSelectionToolConfigWidgetHelper::slotSampleLayersModeChanged(QString mode)
{
KConfigGroup cfg = KSharedConfig::openConfig()->group(m_configGroupForTool);
cfg.writeEntry("sampleLayersMode", mode);
m_sampleLayersMode = mode;
}
void KisSelectionToolConfigWidgetHelper::slotReplaceModeRequested()
{
m_optionsWidget->setAction(SELECTION_REPLACE);
......@@ -149,10 +192,15 @@ void KisSelectionToolConfigWidgetHelper::slotToolActivatedChanged(bool isActivat
m_selectionAction = (SelectionAction)cfg.readEntry("selectionAction", (int)SELECTION_REPLACE);
m_selectionMode = (SelectionMode)cfg.readEntry("selectionMode", (int)SHAPE_PROTECTION);
m_antiAliasSelection = cfg.readEntry("antiAliasSelection", true);
if (m_configGroupForTool != "")
{
KConfigGroup cfgToolSpecific = KSharedConfig::openConfig()->group(m_configGroupForTool);
m_sampleLayersMode = cfgToolSpecific.readEntry("sampleLayersMode", m_optionsWidget->SAMPLE_LAYERS_MODE_CURRENT);
}
KisSignalsBlocker b(m_optionsWidget);
m_optionsWidget->setAction(m_selectionAction);
m_optionsWidget->setMode(m_selectionMode);
m_optionsWidget->setAntiAliasSelection(m_antiAliasSelection);
m_optionsWidget->setSampleLayersMode(m_sampleLayersMode);
}
......@@ -20,10 +20,12 @@
#define __KIS_SELECTION_TOOL_CONFIG_WIDGET_HELPER_H
#include <QObject>
#include <QList>
#include "kritaui_export.h"
#include "kis_selection.h"
#include "kis_canvas_resource_provider.h"
#include "kis_image.h"
class QKeyEvent;
class KisCanvas2;
......@@ -43,8 +45,12 @@ public:
SelectionMode selectionMode() const;
SelectionAction selectionAction() const;
bool antiAliasSelection() const;
QList<int> colorLabelsSelected() const;
QString sampleLayersMode() const;
int action() const { return selectionAction(); }
void setConfigGroupForExactTool(QString toolId);
Q_SIGNALS:
void selectionActionChanged(int newAction);
......@@ -54,6 +60,8 @@ public Q_SLOTS:
void slotWidgetActionChanged(int action);
void slotWidgetModeChanged(int mode);
void slotWidgetAntiAliasChanged(bool value);
void slotSelectedColorLabelsChanged();
void slotSampleLayersModeChanged(QString mode);
void slotReplaceModeRequested();
void slotAddModeRequested();
......@@ -69,6 +77,10 @@ private:
SelectionMode m_selectionMode {SHAPE_PROTECTION};
SelectionAction m_selectionAction {SELECTION_DEFAULT};
bool m_antiAliasSelection {true};
QList<int> m_colorLabelsSelected {};
QString m_sampleLayersMode {""};
QString m_configGroupForTool {""};
};
#endif /* __KIS_SELECTION_TOOL_CONFIG_WIDGET_HELPER_H */
......@@ -37,6 +37,7 @@
#include "kis_action.h"
#include "kis_signal_auto_connection.h"
#include "kis_selection_tool_helper.h"
#include "kis_assert.h"
/**
* This is a basic template to create selection tools from basic path based drawing tools.
......@@ -98,6 +99,13 @@ public:
KisSelectionModifierMapper::instance();
}
enum SampleLayersMode
{
SampleAllLayers,
SampleCurrentLayer,
SampleColorLabeledLayers,
};
void updateActionShortcutToolTips() {
KisSelectionOptions *widget = m_widgetHelper.optionWidget();
if (widget) {
......@@ -138,8 +146,14 @@ public:
updateActionShortcutToolTips();
if (isPixelOnly() && m_widgetHelper.optionWidget()) {
m_widgetHelper.optionWidget()->enablePixelOnlySelectionMode();
if (m_widgetHelper.optionWidget()) {
m_widgetHelper.optionWidget()->activateConnectionToImage();
if (isPixelOnly()) {
m_widgetHelper.optionWidget()->enablePixelOnlySelectionMode();
}
m_widgetHelper.optionWidget()->setColorLabelsEnabled(usesColorLabels());
}
}
......@@ -147,6 +161,9 @@ public:
{
BaseClass::deactivate();
m_modeConnections.clear();
if (m_widgetHelper.optionWidget()) {
m_widgetHelper.optionWidget()->deactivateConnectionToImage();
}
}
QWidget* createOptionWidget()
......@@ -159,8 +176,11 @@ public:
this->connect(&m_widgetHelper, SIGNAL(selectionActionChanged(int)), this, SLOT(resetCursorStyle()));
updateActionShortcutToolTips();
if (isPixelOnly() && m_widgetHelper.optionWidget()) {
m_widgetHelper.optionWidget()->enablePixelOnlySelectionMode();
if (m_widgetHelper.optionWidget()) {
if (isPixelOnly()) {
m_widgetHelper.optionWidget()->enablePixelOnlySelectionMode();
}
m_widgetHelper.optionWidget()->setColorLabelsEnabled(usesColorLabels());
}
return m_widgetHelper.optionWidget();
......@@ -184,6 +204,25 @@ public:
return m_widgetHelper.antiAliasSelection();
}
QList<int> colorLabelsSelected() const
{
return m_widgetHelper.colorLabelsSelected();
}
SampleLayersMode sampleLayersMode() const
{
QString layersMode = m_widgetHelper.sampleLayersMode();
if (layersMode == m_widgetHelper.optionWidget()->SAMPLE_LAYERS_MODE_ALL) {
return SampleAllLayers;
} else if (layersMode == m_widgetHelper.optionWidget()->SAMPLE_LAYERS_MODE_CURRENT) {
return SampleCurrentLayer;
} else if (layersMode == m_widgetHelper.optionWidget()->SAMPLE_LAYERS_MODE_COLOR_LABELED) {
return SampleColorLabeledLayers;
}
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(true, SampleAllLayers);
return SampleAllLayers;
}
SelectionAction alternateSelectionAction() const
{
return m_selectionActionAlternate;
......@@ -377,6 +416,10 @@ protected:
return false;
}
virtual bool usesColorLabels() const {
return false;
}
private:
Qt::KeyboardModifiers keysAtStart;
......
......@@ -257,7 +257,9 @@ void collectAvailableLabels(KisNodeSP root, QSet<int> *labels)
void KisColorFilterCombo::updateAvailableLabels(KisNodeSP rootNode)
{
QSet<int> labels;
collectAvailableLabels(rootNode, &labels);
if (!rootNode.isNull()) {
collectAvailableLabels(rootNode, &labels);
}
updateAvailableLabels(labels);
}
......
......@@ -33,11 +33,17 @@
#include "kis_paint_device.h"
#include "canvas/kis_canvas2.h"
#include "KisViewManager.h"
#include "kis_signal_compressor.h"
#include "kis_shape_controller.h"
#include "kis_canvas2.h"
#include "KisDocument.h"
#include "kis_dummies_facade_base.h"
#include <ksharedconfig.h>
#include <kconfiggroup.h>
KisSelectionOptions::KisSelectionOptions(KisCanvas2 * /*canvas*/)
: m_colorLabelsCompressor(900, KisSignalCompressor::FIRST_INACTIVE)
{
m_page = new WdgSelectionOptions(this);
Q_CHECK_PTR(m_page);
......@@ -74,13 +80,23 @@ KisSelectionOptions::KisSelectionOptions(KisCanvas2 * /*canvas*/)
m_page->intersect->setIcon(KisIconUtils::loadIcon("selection_intersect"));
m_page->symmetricdifference->setIcon(KisIconUtils::loadIcon("selection_symmetric_difference"));
m_page->cmbSampleLayersMode->addItem(sampleLayerModeToUserString(SAMPLE_LAYERS_MODE_CURRENT), SAMPLE_LAYERS_MODE_CURRENT);
m_page->cmbSampleLayersMode->addItem(sampleLayerModeToUserString(SAMPLE_LAYERS_MODE_ALL), SAMPLE_LAYERS_MODE_ALL);
m_page->cmbSampleLayersMode->addItem(sampleLayerModeToUserString(SAMPLE_LAYERS_MODE_COLOR_LABELED), SAMPLE_LAYERS_MODE_COLOR_LABELED);
m_page->cmbSampleLayersMode->setEditable(false);
connect(m_mode, SIGNAL(buttonClicked(int)), this, SIGNAL(modeChanged(int)));
connect(m_action, SIGNAL(buttonClicked(int)), this, SIGNAL(actionChanged(int)));
connect(m_mode, SIGNAL(buttonClicked(int)), this, SLOT(hideActionsForSelectionMode(int)));
connect(m_page->chkAntiAliasing, SIGNAL(toggled(bool)), this, SIGNAL(antiAliasSelectionChanged(bool)));
connect(m_page->cmbColorLabels, SIGNAL(selectedColorsChanged()), this, SIGNAL(selectedColorLabelsChanged()));
connect(m_page->cmbSampleLayersMode, SIGNAL(currentIndexChanged(int)), this, SLOT(slotSampleLayersModeChanged(int)));
KConfigGroup cfg = KSharedConfig::openConfig()->group("KisToolSelectBase");
m_page->chkAntiAliasing->setChecked(cfg.readEntry("antiAliasSelection", true));
connect(&m_colorLabelsCompressor, SIGNAL(timeout()), this, SLOT(slotUpdateAvailableColorLabels()));
}
KisSelectionOptions::~KisSelectionOptions()
......@@ -112,12 +128,31 @@ void KisSelectionOptions::setAntiAliasSelection(bool value)
m_page->chkAntiAliasing->setChecked(value);
}
void KisSelectionOptions::setSampleLayersMode(QString mode)
{
if (mode != SAMPLE_LAYERS_MODE_ALL && mode != SAMPLE_LAYERS_MODE_COLOR_LABELED && mode != SAMPLE_LAYERS_MODE_CURRENT) {
mode = SAMPLE_LAYERS_MODE_CURRENT;
}
setCmbSampleLayersMode(mode);
}
void KisSelectionOptions::enablePixelOnlySelectionMode()
{
setMode(PIXEL_SELECTION);
disableSelectionModeOption();
}
void KisSelectionOptions::setColorLabelsEnabled(bool enabled)
{
if (enabled) {
m_page->cmbColorLabels->show();
m_page->cmbSampleLayersMode->show();
} else {
m_page->cmbColorLabels->hide();
m_page->cmbSampleLayersMode->hide();
}
}
void KisSelectionOptions::updateActionButtonToolTip(int action, const QKeySequence &shortcut)
{
const QString shortcutString = shortcut.toString(QKeySequence::NativeText);
......@@ -166,6 +201,39 @@ void KisSelectionOptions::updateActionButtonToolTip(int action, const QKeySequen
}
}
void KisSelectionOptions::attachToImage(KisImageSP image, KisCanvas2* canvas)
{
m_image = image;
m_canvas = canvas;
activateConnectionToImage();
}
void KisSelectionOptions::activateConnectionToImage()
{
if (m_image && m_canvas) {
m_page->cmbColorLabels->updateAvailableLabels(m_image->root());
KIS_SAFE_ASSERT_RECOVER_RETURN(m_canvas);
KisDocument *doc = m_canvas->imageView()->document();