diff --git a/krita/krita.qrc b/krita/krita.qrc --- a/krita/krita.qrc +++ b/krita/krita.qrc @@ -53,7 +53,7 @@ pics/select_pixel.png pics/select_shape.png pics/selection_add.png - pics/selection_exclude.png + pics/selection_symmetric_difference.png pics/selection_intersect.png pics/selection_replace.png pics/selection_subtract.png diff --git a/krita/pics/selection_exclude.png b/krita/pics/selection_symmetric_difference.png rename from krita/pics/selection_exclude.png rename to krita/pics/selection_symmetric_difference.png diff --git a/libs/image/KisSelectionTags.h b/libs/image/KisSelectionTags.h --- a/libs/image/KisSelectionTags.h +++ b/libs/image/KisSelectionTags.h @@ -30,6 +30,7 @@ SELECTION_ADD, SELECTION_SUBTRACT, SELECTION_INTERSECT, + SELECTION_SYMMETRICDIFFERENCE, SELECTION_DEFAULT }; diff --git a/libs/image/kis_pixel_selection.h b/libs/image/kis_pixel_selection.h --- a/libs/image/kis_pixel_selection.h +++ b/libs/image/kis_pixel_selection.h @@ -158,6 +158,11 @@ */ void intersectSelection(KisPixelSelectionSP selection); + /** + * Invert a selection or intersect with the inverse of a selection + */ + void symmetricdifferenceSelection(KisPixelSelectionSP selection); + private: // We don't want these methods to be used on selections: using KisPaintDevice::extent; diff --git a/libs/image/kis_pixel_selection.cpp b/libs/image/kis_pixel_selection.cpp --- a/libs/image/kis_pixel_selection.cpp +++ b/libs/image/kis_pixel_selection.cpp @@ -162,6 +162,9 @@ case SELECTION_INTERSECT: intersectSelection(selection); break; + case SELECTION_SYMMETRICDIFFERENCE: + symmetricdifferenceSelection(selection); + break; default: break; } @@ -267,6 +270,32 @@ m_d->invalidateThumbnailImage(); } +void KisPixelSelection::symmetricdifferenceSelection(KisPixelSelectionSP selection) +{ + QRect r = selection->selectedRect().united(selectedRect()); + if (r.isEmpty()) return; + + KisHLineIteratorSP dst = createHLineIteratorNG(r.x(), r.y(), r.width()); + KisHLineConstIteratorSP src = selection->createHLineConstIteratorNG(r.x(), r.y(), r.width()); + for (int i = 0; i < r.height(); ++i) { + + do { + *dst->rawData() = abs(*dst->rawData() - *src->oldRawData()); + } while (src->nextPixel() && dst->nextPixel()); + + dst->nextRow(); + src->nextRow(); + } + + m_d->outlineCacheValid &= selection->outlineCacheValid(); + + if (m_d->outlineCacheValid) { + m_d->outlineCache = (m_d->outlineCache | selection->outlineCache()) - (m_d->outlineCache & selection->outlineCache()); + } + + m_d->invalidateThumbnailImage(); +} + void KisPixelSelection::clear(const QRect & r) { if (*defaultPixel().data() != MIN_SELECTED) { diff --git a/libs/libkis/Selection.h b/libs/libkis/Selection.h --- a/libs/libkis/Selection.h +++ b/libs/libkis/Selection.h @@ -196,6 +196,11 @@ */ void intersect(Selection *selection); + /** + * Intersect with the inverse of the given selection with this selection. + */ + void symmetricdifference(Selection *selection); + /** * @brief pixelData reads the given rectangle from the Selection's mask and returns it as a * byte array. The pixel data starts top-left, and is ordered row-first. diff --git a/libs/libkis/Selection.cpp b/libs/libkis/Selection.cpp --- a/libs/libkis/Selection.cpp +++ b/libs/libkis/Selection.cpp @@ -293,6 +293,12 @@ d->selection->pixelSelection()->applySelection(selection->selection()->pixelSelection(), SELECTION_INTERSECT); } +void Selection::symmetricdifference(Selection *selection) +{ + if (!d->selection) return; + d->selection->pixelSelection()->applySelection(selection->selection()->pixelSelection(), SELECTION_SYMMETRICDIFFERENCE); +} + QByteArray Selection::pixelData(int x, int y, int w, int h) const { diff --git a/libs/libqml/qml/panels/SelectPanel.qml b/libs/libqml/qml/panels/SelectPanel.qml --- a/libs/libqml/qml/panels/SelectPanel.qml +++ b/libs/libqml/qml/panels/SelectPanel.qml @@ -145,6 +145,15 @@ checked: (toolManager.currentTool && toolManager.currentTool.selectionAction === 2) ? true : false; onClicked: if (toolManager.currentTool && toolManager.currentTool.selectionAction !== undefined) toolManager.currentTool.selectionAction = 2; } + Button { + id: selectSymmetricDifference; + anchors.left: selectSub.right; + image: Settings.theme.icon("select-symmetric-difference"); + width: Constants.ToolbarButtonSize * 0.8; + height: width; + checked: (toolManager.currentTool && toolManager.currentTool.selectionAction === 4) ? true : false; + onClicked: if (toolManager.currentTool && toolManager.currentTool.selectionAction !== undefined) toolManager.currentTool.selectionAction = 4; + } } Item { width: childrenRect.width; diff --git a/libs/ui/forms/wdgselectionoptions.ui b/libs/ui/forms/wdgselectionoptions.ui --- a/libs/ui/forms/wdgselectionoptions.ui +++ b/libs/ui/forms/wdgselectionoptions.ui @@ -7,25 +7,10 @@ 0 0 271 - 106 + 110 - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - + @@ -156,9 +141,28 @@ + + + + Symmetric Difference + + + -1 + + + ... + + + true + + + false + + + - + Qt::Horizontal diff --git a/libs/ui/kis_selection_manager.cc b/libs/ui/kis_selection_manager.cc --- a/libs/ui/kis_selection_manager.cc +++ b/libs/ui/kis_selection_manager.cc @@ -715,6 +715,9 @@ case SELECTION_INTERSECT: actionName = kundo2_i18n("Select Opaque (Intersect)"); break; + case SELECTION_SYMMETRICDIFFERENCE: + actionName = kundo2_i18n("Select Opaque (Symmetric Difference)"); + break; default: actionName = kundo2_i18n("Select Opaque"); break; diff --git a/libs/ui/tool/kis_selection_tool_config_widget_helper.h b/libs/ui/tool/kis_selection_tool_config_widget_helper.h --- a/libs/ui/tool/kis_selection_tool_config_widget_helper.h +++ b/libs/ui/tool/kis_selection_tool_config_widget_helper.h @@ -59,6 +59,7 @@ void slotAddModeRequested(); void slotSubtractModeRequested(); void slotIntersectModeRequested(); + void slotSymmetricDifferenceModeRequested(); private: KisSelectionOptions* m_optionsWidget; diff --git a/libs/ui/tool/kis_selection_tool_config_widget_helper.cpp b/libs/ui/tool/kis_selection_tool_config_widget_helper.cpp --- a/libs/ui/tool/kis_selection_tool_config_widget_helper.cpp +++ b/libs/ui/tool/kis_selection_tool_config_widget_helper.cpp @@ -85,7 +85,7 @@ void KisSelectionToolConfigWidgetHelper::slotWidgetActionChanged(int action) { - if (action >= SELECTION_REPLACE && action <= SELECTION_INTERSECT) { + if (action >= SELECTION_REPLACE && action <= SELECTION_SYMMETRICDIFFERENCE) { m_selectionAction = (SelectionAction)action; KConfigGroup cfg = KSharedConfig::openConfig()->group("KisToolSelectBase"); @@ -135,6 +135,12 @@ slotWidgetActionChanged(m_optionsWidget->action()); } +void KisSelectionToolConfigWidgetHelper::slotSymmetricDifferenceModeRequested() +{ + m_optionsWidget->setAction(SELECTION_SYMMETRICDIFFERENCE); + slotWidgetActionChanged(m_optionsWidget->action()); +} + void KisSelectionToolConfigWidgetHelper::slotToolActivatedChanged(bool isActivated) { if (!isActivated) return; diff --git a/libs/ui/tool/kis_selection_tool_helper.cpp b/libs/ui/tool/kis_selection_tool_helper.cpp --- a/libs/ui/tool/kis_selection_tool_helper.cpp +++ b/libs/ui/tool/kis_selection_tool_helper.cpp @@ -105,14 +105,14 @@ KisSelectionTransaction transaction(pixelSelection); - if (!hasSelection && m_action == SELECTION_SUBTRACT) { + if (!hasSelection && m_action == SELECTION_SYMMETRICDIFFERENCE) { pixelSelection->invert(); } pixelSelection->applySelection(m_selection, m_action); QRect dirtyRect = m_view->image()->bounds(); - if (hasSelection && m_action != SELECTION_REPLACE && m_action != SELECTION_INTERSECT) { + if (hasSelection && m_action != SELECTION_REPLACE && m_action != SELECTION_SYMMETRICDIFFERENCE) { dirtyRect = m_selection->selectedRect(); } m_view->selection()->updateProjection(dirtyRect); @@ -226,6 +226,9 @@ case SELECTION_SUBTRACT: path = path1 - path2; break; + case SELECTION_SYMMETRICDIFFERENCE: + path = (path1 | path2) - (path1 & path2); + break; } KoShape *newShape = KoPathShape::createShapeFromPainterPath(path); @@ -279,7 +282,7 @@ bool result = false; if (KisAlgebra2D::maxDimension(selectionViewRect) < KisConfig(true).selectionViewSizeMinimum() && - (action == SELECTION_INTERSECT || action == SELECTION_REPLACE)) { + (action == SELECTION_SYMMETRICDIFFERENCE || action == SELECTION_REPLACE)) { // Queueing this action to ensure we avoid a race condition when unlocking the node system QTimer::singleShot(0, m_canvas->viewManager()->selectionManager(), SLOT(deselect())); diff --git a/libs/ui/widgets/kis_selection_options.cc b/libs/ui/widgets/kis_selection_options.cc --- a/libs/ui/widgets/kis_selection_options.cc +++ b/libs/ui/widgets/kis_selection_options.cc @@ -53,6 +53,7 @@ m_action->addButton(m_page->subtract, SELECTION_SUBTRACT); m_action->addButton(m_page->replace, SELECTION_REPLACE); m_action->addButton(m_page->intersect, SELECTION_INTERSECT); + m_action->addButton(m_page->symmetricdifference, SELECTION_SYMMETRICDIFFERENCE); m_page->pixel->setGroupPosition(KoGroupButton::GroupLeft); m_page->shape->setGroupPosition(KoGroupButton::GroupRight); @@ -63,10 +64,12 @@ m_page->subtract->setGroupPosition(KoGroupButton::GroupRight); m_page->replace->setGroupPosition(KoGroupButton::GroupLeft); m_page->intersect->setGroupPosition(KoGroupButton::GroupCenter); + m_page->symmetricdifference->setGroupPosition(KoGroupButton::GroupRight); m_page->add->setIcon(KisIconUtils::loadIcon("selection_add")); m_page->subtract->setIcon(KisIconUtils::loadIcon("selection_subtract")); m_page->replace->setIcon(KisIconUtils::loadIcon("selection_replace")); m_page->intersect->setIcon(KisIconUtils::loadIcon("selection_intersect")); + m_page->symmetricdifference->setIcon(KisIconUtils::loadIcon("selection_symmetric_difference")); connect(m_mode, SIGNAL(buttonClicked(int)), this, SIGNAL(modeChanged(int))); connect(m_action, SIGNAL(buttonClicked(int)), this, SIGNAL(actionChanged(int))); @@ -138,6 +141,15 @@ m_action->button(SELECTION_INTERSECT)->setToolTip(toolTipText); + break; + + case SELECTION_SYMMETRICDIFFERENCE: + toolTipText = shortcutString.isEmpty() ? + i18nc("@info:tooltip", "Symmetric Difference") : + i18nc("@info:tooltip", "Symmetric Difference (%1)", shortcutString); + + m_action->button(SELECTION_SYMMETRICDIFFERENCE)->setToolTip(toolTipText); + break; } } diff --git a/plugins/extensions/pykrita/sip/krita/Selection.sip b/plugins/extensions/pykrita/sip/krita/Selection.sip --- a/plugins/extensions/pykrita/sip/krita/Selection.sip +++ b/plugins/extensions/pykrita/sip/krita/Selection.sip @@ -35,6 +35,7 @@ void add(Selection *selection); void subtract(Selection *selection); void intersect(Selection *selection); + void symmetricdifference(Selection *selection); QByteArray pixelData(int x, int y, int w, int h) const; void setPixelData(QByteArray value, int x, int y, int w, int h); private: diff --git a/plugins/tools/selectiontools/kis_selection_modifier_mapper.cc b/plugins/tools/selectiontools/kis_selection_modifier_mapper.cc --- a/plugins/tools/selectiontools/kis_selection_modifier_mapper.cc +++ b/plugins/tools/selectiontools/kis_selection_modifier_mapper.cc @@ -59,6 +59,7 @@ Qt::KeyboardModifiers intersectModifiers; Qt::KeyboardModifiers addModifiers; Qt::KeyboardModifiers subtractModifiers; + Qt::KeyboardModifiers symmetricdifferenceModifiers; }; @@ -94,10 +95,12 @@ replaceModifiers = Qt::ControlModifier; intersectModifiers = (Qt::KeyboardModifiers)(Qt::AltModifier | Qt::ShiftModifier); subtractModifiers = Qt::AltModifier; + symmetricdifferenceModifiers = (Qt::KeyboardModifiers)(Qt::ControlModifier | Qt::AltModifier); } else { replaceModifiers = Qt::AltModifier; intersectModifiers = (Qt::KeyboardModifiers)(Qt::ControlModifier | Qt::ShiftModifier); subtractModifiers = Qt::ControlModifier; + symmetricdifferenceModifiers = (Qt::KeyboardModifiers)(Qt::ShiftModifier | Qt::ControlModifier); } addModifiers = Qt::ShiftModifier; @@ -119,6 +122,9 @@ newAction = SELECTION_ADD; } else if (m == subtractModifiers) { newAction = SELECTION_SUBTRACT; + } else if (m == symmetricdifferenceModifiers) { + newAction = SELECTION_SYMMETRICDIFFERENCE; } + return newAction; }