diff --git a/krita/krita.qrc b/krita/krita.qrc index 0479a25e9f..4e55340c89 100644 --- a/krita/krita.qrc +++ b/krita/krita.qrc @@ -1,75 +1,75 @@ data/kritarc krita4.xmlgui pics/broken-preset.png pics/dark_layer-locked.png pics/dark_layer-unlocked.png pics/dark_novisible.svg pics/dark_passthrough-disabled.png pics/dark_passthrough-enabled.png pics/dark_selection-mode_ants.png pics/dark_selection-mode_invisible.png pics/dark_selection-mode_mask.png pics/dark_transparency-disabled.png pics/dark_transparency-enabled.png pics/dark_trim-to-image.png pics/dark_visible.svg pics/delete.png pics/dirty-preset.svg pics/height.png pics/height_icon.png pics/hi16-add_dialog.png pics/hi16-palette_library.png pics/icon-kritasketch-256.png pics/layer-style-disabled.png pics/layer-style-enabled.png pics/light_layer-locked.png pics/light_layer-unlocked.png pics/light_novisible.svg pics/light_passthrough-disabled.png pics/light_passthrough-enabled.png pics/light_selection-mode_ants.png pics/light_selection-mode_invisible.png pics/light_selection-mode_mask.png pics/light_transparency-disabled.png pics/light_transparency-enabled.png pics/light_trim-to-image.png pics/light_visible.svg pics/linked.png pics/local_selection_active.png pics/local_selection_inactive.png pics/mirrorAxis-HorizontalMove.png pics/mirrorAxis-VerticalMove.png pics/novisible.svg pics/offset_horizontal.png pics/offset_vertical.png pics/ratio_icon.png 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 pics/shade.png pics/shear_horizontal.png pics/shear_vertical.png pics/sidebaricon.png pics/tablet.png pics/tool_screenshot.png pics/transparency-locked.png pics/transparency-unlocked.png pics/unlinked.png pics/visible.svg pics/width.png pics/width_icon.png pics/workspace-chooser.png diff --git a/krita/pics/selection_exclude.png b/krita/pics/selection_symmetric_difference.png similarity index 100% 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 index eab45119d2..a4021da2ce 100644 --- a/libs/image/KisSelectionTags.h +++ b/libs/image/KisSelectionTags.h @@ -1,36 +1,37 @@ /* * Copyright (c) 2018 Dmitry Kazakov * * 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 KISSELECTIONTAGS_H #define KISSELECTIONTAGS_H enum SelectionMode { PIXEL_SELECTION = 0, SHAPE_PROTECTION }; enum SelectionAction { SELECTION_REPLACE = 0, SELECTION_ADD, SELECTION_SUBTRACT, SELECTION_INTERSECT, + SELECTION_SYMMETRICDIFFERENCE, SELECTION_DEFAULT }; #endif // KISSELECTIONTAGS_H diff --git a/libs/image/kis_pixel_selection.cpp b/libs/image/kis_pixel_selection.cpp index d887afb362..b6bf7ecc2e 100644 --- a/libs/image/kis_pixel_selection.cpp +++ b/libs/image/kis_pixel_selection.cpp @@ -1,568 +1,597 @@ /* * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2007 Sven Langkamp * * 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 "kis_pixel_selection.h" #include #include #include #include #include #include #include #include #include #include #include "kis_layer.h" #include "kis_debug.h" #include "kis_image.h" #include "kis_fill_painter.h" #include "kis_outline_generator.h" #include #include "kis_lod_transform.h" struct Q_DECL_HIDDEN KisPixelSelection::Private { KisSelectionWSP parentSelection; QPainterPath outlineCache; bool outlineCacheValid; QMutex outlineCacheMutex; bool thumbnailImageValid; QImage thumbnailImage; QTransform thumbnailImageTransform; QPoint lod0CachesOffset; void invalidateThumbnailImage() { thumbnailImageValid = false; thumbnailImage = QImage(); thumbnailImageTransform = QTransform(); } }; KisPixelSelection::KisPixelSelection(KisDefaultBoundsBaseSP defaultBounds, KisSelectionWSP parentSelection) : KisPaintDevice(0, KoColorSpaceRegistry::instance()->alpha8(), defaultBounds) , m_d(new Private) { m_d->outlineCacheValid = true; m_d->invalidateThumbnailImage(); m_d->parentSelection = parentSelection; } KisPixelSelection::KisPixelSelection(const KisPixelSelection& rhs, KritaUtils::DeviceCopyMode copyMode) : KisPaintDevice(rhs, copyMode) , KisSelectionComponent(rhs) , m_d(new Private) { // parent selection is not supposed to be shared m_d->outlineCache = rhs.m_d->outlineCache; m_d->outlineCacheValid = rhs.m_d->outlineCacheValid; m_d->thumbnailImageValid = rhs.m_d->thumbnailImageValid; m_d->thumbnailImage = rhs.m_d->thumbnailImage; m_d->thumbnailImageTransform = rhs.m_d->thumbnailImageTransform; } KisPixelSelection::KisPixelSelection(const KisPaintDeviceSP copySource, KritaUtils::DeviceCopyMode copyMode, KisSelectionWSP parentSelection) : KisPaintDevice(*copySource.data(), copyMode) , m_d(new Private) { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->alpha8(); convertTo(cs); m_d->parentSelection = parentSelection; m_d->outlineCacheValid = true; m_d->invalidateThumbnailImage(); } KisSelectionComponent* KisPixelSelection::clone(KisSelection*) { return new KisPixelSelection(*this); } KisPixelSelection::~KisPixelSelection() { delete m_d; } const KoColorSpace *KisPixelSelection::compositionSourceColorSpace() const { return KoColorSpaceRegistry::instance()-> colorSpace(GrayAColorModelID.id(), Integer8BitsColorDepthID.id(), QString()); } bool KisPixelSelection::read(QIODevice *stream) { bool retval = KisPaintDevice::read(stream); m_d->outlineCacheValid = false; m_d->invalidateThumbnailImage(); return retval; } void KisPixelSelection::select(const QRect & rc, quint8 selectedness) { QRect r = rc.normalized(); if (r.isEmpty()) return; KisFillPainter painter(KisPaintDeviceSP(this)); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); painter.fillRect(r, KoColor(Qt::white, cs), selectedness); if (m_d->outlineCacheValid) { QPainterPath path; path.addRect(r); if (selectedness != MIN_SELECTED) { m_d->outlineCache += path; } else { m_d->outlineCache -= path; } } m_d->invalidateThumbnailImage(); } void KisPixelSelection::applySelection(KisPixelSelectionSP selection, SelectionAction action) { switch (action) { case SELECTION_REPLACE: clear(); addSelection(selection); break; case SELECTION_ADD: addSelection(selection); break; case SELECTION_SUBTRACT: subtractSelection(selection); break; case SELECTION_INTERSECT: intersectSelection(selection); break; + case SELECTION_SYMMETRICDIFFERENCE: + symmetricdifferenceSelection(selection); + break; default: break; } } void KisPixelSelection::copyAlphaFrom(KisPaintDeviceSP src, const QRect &processRect) { const KoColorSpace *srcCS = src->colorSpace(); KisSequentialConstIterator srcIt(src, processRect); KisSequentialIterator dstIt(this, processRect); while (srcIt.nextPixel() && dstIt.nextPixel()) { const quint8 *srcPtr = srcIt.rawDataConst(); quint8 *alpha8Ptr = dstIt.rawData(); *alpha8Ptr = srcCS->opacityU8(srcPtr); } m_d->outlineCacheValid = false; m_d->outlineCache = QPainterPath(); m_d->invalidateThumbnailImage(); } void KisPixelSelection::addSelection(KisPixelSelectionSP selection) { QRect r = selection->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 { if (*src->oldRawData() + *dst->rawData() < MAX_SELECTED) *dst->rawData() = *src->oldRawData() + *dst->rawData(); else *dst->rawData() = MAX_SELECTED; } while (src->nextPixel() && dst->nextPixel()); dst->nextRow(); src->nextRow(); } m_d->outlineCacheValid &= selection->outlineCacheValid(); if (m_d->outlineCacheValid) { m_d->outlineCache += selection->outlineCache(); } m_d->invalidateThumbnailImage(); } void KisPixelSelection::subtractSelection(KisPixelSelectionSP selection) { QRect r = selection->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 { if (*dst->rawData() - *src->oldRawData() > MIN_SELECTED) *dst->rawData() = *dst->rawData() - *src->oldRawData(); else *dst->rawData() = MIN_SELECTED; } while (src->nextPixel() && dst->nextPixel()); dst->nextRow(); src->nextRow(); } m_d->outlineCacheValid &= selection->outlineCacheValid(); if (m_d->outlineCacheValid) { m_d->outlineCache -= selection->outlineCache(); } m_d->invalidateThumbnailImage(); } void KisPixelSelection::intersectSelection(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() = qMin(*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 &= selection->outlineCache(); } 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) { KisFillPainter painter(KisPaintDeviceSP(this)); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); painter.fillRect(r, KoColor(Qt::white, cs), MIN_SELECTED); } else { KisPaintDevice::clear(r); } if (m_d->outlineCacheValid) { QPainterPath path; path.addRect(r); m_d->outlineCache -= path; } m_d->invalidateThumbnailImage(); } void KisPixelSelection::clear() { setDefaultPixel(KoColor(Qt::transparent, colorSpace())); KisPaintDevice::clear(); m_d->outlineCacheValid = true; m_d->outlineCache = QPainterPath(); // Empty the thumbnail image. It is a valid state. m_d->invalidateThumbnailImage(); m_d->thumbnailImageValid = true; } void KisPixelSelection::invert() { // Region is needed here (not exactBounds or extent), because // unselected but existing pixels need to be inverted too QRect rc = region().boundingRect(); if (!rc.isEmpty()) { KisSequentialIterator it(this, rc); while(it.nextPixel()) { *(it.rawData()) = MAX_SELECTED - *(it.rawData()); } } quint8 defPixel = MAX_SELECTED - *defaultPixel().data(); setDefaultPixel(KoColor(&defPixel, colorSpace())); if (m_d->outlineCacheValid) { QPainterPath path; path.addRect(defaultBounds()->bounds()); m_d->outlineCache = path - m_d->outlineCache; } m_d->invalidateThumbnailImage(); } void KisPixelSelection::moveTo(const QPoint &pt) { const int lod = defaultBounds()->currentLevelOfDetail(); const QPoint lod0Point = !lod ? pt : pt * KisLodTransform::lodToInvScale(lod); const QPoint offset = lod0Point - m_d->lod0CachesOffset; if (m_d->outlineCacheValid) { m_d->outlineCache.translate(offset); } if (m_d->thumbnailImageValid) { m_d->thumbnailImageTransform = QTransform::fromTranslate(offset.x(), offset.y()) * m_d->thumbnailImageTransform; } m_d->lod0CachesOffset = lod0Point; KisPaintDevice::moveTo(pt); } bool KisPixelSelection::isTotallyUnselected(const QRect & r) const { if (*defaultPixel().data() != MIN_SELECTED) return false; QRect sr = selectedExactRect(); return ! r.intersects(sr); } QRect KisPixelSelection::selectedRect() const { return extent(); } QRect KisPixelSelection::selectedExactRect() const { return exactBounds(); } QVector KisPixelSelection::outline() const { QRect selectionExtent = selectedExactRect(); /** * When the default pixel is not fully transarent, the * exactBounds() return extent of the device instead. To make this * value sane we should limit the calculated area by the bounds of * the image. */ if (*defaultPixel().data() != MIN_SELECTED) { selectionExtent &= defaultBounds()->bounds(); } qint32 xOffset = selectionExtent.x(); qint32 yOffset = selectionExtent.y(); qint32 width = selectionExtent.width(); qint32 height = selectionExtent.height(); KisOutlineGenerator generator(colorSpace(), MIN_SELECTED); // If the selection is small using a buffer is much faster try { quint8* buffer = new quint8[width*height]; readBytes(buffer, xOffset, yOffset, width, height); QVector paths = generator.outline(buffer, xOffset, yOffset, width, height); delete[] buffer; return paths; } catch(const std::bad_alloc&) { // Allocating so much memory failed, so we fall through to the slow option. warnKrita << "KisPixelSelection::outline ran out of memory allocating" << width << "*" << height << "bytes."; } return generator.outline(this, xOffset, yOffset, width, height); } bool KisPixelSelection::isEmpty() const { return *defaultPixel().data() == MIN_SELECTED && selectedRect().isEmpty(); } QPainterPath KisPixelSelection::outlineCache() const { QMutexLocker locker(&m_d->outlineCacheMutex); return m_d->outlineCache; } void KisPixelSelection::setOutlineCache(const QPainterPath &cache) { QMutexLocker locker(&m_d->outlineCacheMutex); m_d->outlineCache = cache; m_d->outlineCacheValid = true; m_d->thumbnailImageValid = false; } bool KisPixelSelection::outlineCacheValid() const { QMutexLocker locker(&m_d->outlineCacheMutex); return m_d->outlineCacheValid; } void KisPixelSelection::invalidateOutlineCache() { QMutexLocker locker(&m_d->outlineCacheMutex); m_d->outlineCacheValid = false; m_d->thumbnailImageValid = false; } void KisPixelSelection::recalculateOutlineCache() { QMutexLocker locker(&m_d->outlineCacheMutex); m_d->outlineCache = QPainterPath(); Q_FOREACH (const QPolygon &polygon, outline()) { m_d->outlineCache.addPolygon(polygon); /** * The outline generation algorithm has a small bug, which * results in the starting point be repeated twice in the * beginning of the path, instead of being put to the * end. Here we just explicitly close the path to workaround * it. * * \see KisSelectionTest::testOutlineGeneration() */ m_d->outlineCache.closeSubpath(); } m_d->outlineCacheValid = true; } bool KisPixelSelection::thumbnailImageValid() const { return m_d->thumbnailImageValid; } QImage KisPixelSelection::thumbnailImage() const { return m_d->thumbnailImage; } QTransform KisPixelSelection::thumbnailImageTransform() const { return m_d->thumbnailImageTransform; } QImage deviceToQImage(KisPaintDeviceSP device, const QRect &rc, const QColor &maskColor) { QImage image(rc.size(), QImage::Format_ARGB32); QColor color = maskColor; const qreal alphaScale = maskColor.alphaF(); KisSequentialConstIterator it(device, rc); while(it.nextPixel()) { quint8 value = (MAX_SELECTED - *(it.rawDataConst())) * alphaScale; color.setAlpha(value); QPoint pt(it.x(), it.y()); pt -= rc.topLeft(); image.setPixel(pt.x(), pt.y(), color.rgba()); } return image; } void KisPixelSelection::recalculateThumbnailImage(const QColor &maskColor) { QRect rc = selectedExactRect(); const int maxPreviewSize = 2000; if (rc.isEmpty()) { m_d->thumbnailImageTransform = QTransform(); m_d->thumbnailImage = QImage(); return; } if (rc.width() > maxPreviewSize || rc.height() > maxPreviewSize) { qreal factor = 1.0; if (rc.width() > rc.height()) { factor = qreal(maxPreviewSize) / rc.width(); } else { factor = qreal(maxPreviewSize) / rc.height(); } int newWidth = qRound(rc.width() * factor); int newHeight = qRound(rc.height() * factor); m_d->thumbnailImageTransform = QTransform::fromScale(qreal(rc.width()) / newWidth, qreal(rc.height()) / newHeight) * QTransform::fromTranslate(rc.x(), rc.y()); KisPaintDeviceSP thumbDevice = createThumbnailDevice(newWidth, newHeight, rc); QRect thumbRect(0, 0, newWidth, newHeight); m_d->thumbnailImage = deviceToQImage(thumbDevice, thumbRect, maskColor); } else { m_d->thumbnailImageTransform = QTransform::fromTranslate(rc.x(), rc.y()); m_d->thumbnailImage = deviceToQImage(this, rc, maskColor); } m_d->thumbnailImageValid = true; } void KisPixelSelection::setParentSelection(KisSelectionWSP selection) { m_d->parentSelection = selection; } KisSelectionWSP KisPixelSelection::parentSelection() const { return m_d->parentSelection; } void KisPixelSelection::renderToProjection(KisPaintDeviceSP projection) { renderToProjection(projection, selectedExactRect()); } void KisPixelSelection::renderToProjection(KisPaintDeviceSP projection, const QRect& rc) { QRect updateRect = rc & selectedExactRect(); if (updateRect.isValid()) { KisPainter::copyAreaOptimized(updateRect.topLeft(), KisPaintDeviceSP(this), projection, updateRect); } } diff --git a/libs/image/kis_pixel_selection.h b/libs/image/kis_pixel_selection.h index f65e773a6c..aab880c871 100644 --- a/libs/image/kis_pixel_selection.h +++ b/libs/image/kis_pixel_selection.h @@ -1,172 +1,177 @@ /* * Copyright (c) 2004 Boudewijn Rempt * * 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_PIXEL_SELECTION_H_ #define KIS_PIXEL_SELECTION_H_ #include #include #include "kis_types.h" #include "kis_global.h" #include "kis_paint_device.h" #include "kis_selection_component.h" #include "kis_selection.h" #include /** * KisPixelSelection contains a byte-map representation of a layer, where * the value of a byte signifies whether a corresponding pixel is selected, or not. */ class KRITAIMAGE_EXPORT KisPixelSelection : public KisPaintDevice, public KisSelectionComponent { public: /** * Create a new KisPixelSelection. This selection will not have a * parent paint device. */ KisPixelSelection(KisDefaultBoundsBaseSP defaultBounds = KisDefaultBoundsBaseSP(), KisSelectionWSP parentSelection = KisSelectionWSP()); /** * Copy the selection */ KisPixelSelection(const KisPixelSelection& rhs, KritaUtils::DeviceCopyMode copyMode = KritaUtils::CopySnapshot); /** * Create a new selection using the content of copySource as the mask. */ KisPixelSelection(const KisPaintDeviceSP copySource, KritaUtils::DeviceCopyMode copyMode = KritaUtils::CopySnapshot, KisSelectionWSP parentSelection = KisSelectionWSP()); ~KisPixelSelection() override; KisSelectionComponent* clone(KisSelection*) override; const KoColorSpace* compositionSourceColorSpace() const override; bool read(QIODevice *stream); /** * Fill the specified rect with the specified selectedness. */ void select(const QRect & r, quint8 selectedness = MAX_SELECTED); /** * Invert the total selection. This will also invert the default value * of the selection paint device, from MIN_SELECTED to MAX_SELECTED or * back. */ void invert(); /** * Set the specified rect to MIN_SELECTED. */ void clear(const QRect & r); /** * Reset the entire selection. The selectedRect and selectedExactRect * will be empty. The selection will be completely deselected. */ void clear() override; /** * Copies alpha channel form the specified \p src device */ void copyAlphaFrom(KisPaintDeviceSP src, const QRect &processRect); /** * Apply a selection to the selection using the specified selection mode */ void applySelection(KisPixelSelectionSP selection, SelectionAction action); /// Tests if the rect is totally outside the selection bool isTotallyUnselected(const QRect & r) const; /** * Rough, but fastish way of determining the area * of the tiles used by the selection. */ QRect selectedRect() const; /** * Slow, but exact way of determining the rectangle * that encloses the selection. */ QRect selectedExactRect() const; /** * @brief outline returns the outline of the current selection * @return a vector of polygons that can be used to draw the outline */ QVector outline() const; /** * Overridden from KisPaintDevice to handle outline cache moves */ void moveTo(const QPoint& pt) override; using KisPaintDevice::moveTo; bool isEmpty() const override; QPainterPath outlineCache() const override; bool outlineCacheValid() const override; void recalculateOutlineCache() override; void setOutlineCache(const QPainterPath &cache); void invalidateOutlineCache(); bool thumbnailImageValid() const; QImage thumbnailImage() const; QTransform thumbnailImageTransform() const; void recalculateThumbnailImage(const QColor &maskColor); void setParentSelection(KisSelectionWSP selection); KisSelectionWSP parentSelection() const; void renderToProjection(KisPaintDeviceSP projection) override; void renderToProjection(KisPaintDeviceSP projection, const QRect& r) override; private: /** * Add a selection */ void addSelection(KisPixelSelectionSP selection); /** * Subtracts a selection */ void subtractSelection(KisPixelSelectionSP selection); /** * Intersects a selection using min-T-norm for this. */ 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; using KisPaintDevice::exactBounds; private: struct Private; Private * const m_d; }; #endif // KIS_PIXEL_SELECTION_H_ diff --git a/libs/libkis/Selection.cpp b/libs/libkis/Selection.cpp index 28783df6c9..150b971f34 100644 --- a/libs/libkis/Selection.cpp +++ b/libs/libkis/Selection.cpp @@ -1,322 +1,328 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser 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 Lesser 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 "Selection.h" #include #include "kis_iterator_ng.h" #include #include #include #include #include #include #include #include struct Selection::Private { Private() {} KisSelectionSP selection; }; Selection::Selection(KisSelectionSP selection, QObject *parent) : QObject(parent) , d(new Private) { d->selection = selection; } Selection::Selection(QObject *parent) : QObject(parent) , d(new Private) { d->selection = new KisSelection(); } Selection::~Selection() { delete d; } bool Selection::operator==(const Selection &other) const { return (d->selection == other.d->selection); } bool Selection::operator!=(const Selection &other) const { return !(operator==(other)); } int Selection::width() const { if (!d->selection) return 0; return d->selection->selectedExactRect().width(); } int Selection::height() const { if (!d->selection) return 0; return d->selection->selectedExactRect().height(); } int Selection::x() const { if (!d->selection) return 0; int xPos = d->selection->x(); if (d->selection->hasPixelSelection()) { xPos = d->selection->selectedExactRect().x(); } return xPos; } int Selection::y() const { if (!d->selection) return 0; int yPos = d->selection->y(); if (d->selection->hasPixelSelection()) { yPos = d->selection->selectedExactRect().y(); } return yPos; } void Selection::move(int x, int y) { if (!d->selection) return; d->selection->pixelSelection()->moveTo(QPoint(x, y)); } void Selection::clear() { if (!d->selection) return; d->selection->clear(); } void Selection::contract(int value) { if (!d->selection) return; d->selection->pixelSelection()->select(QRect(x(), y(), width() - value, height() - value)); } void Selection::copy(Node *node) { if (!node) return; if (!d->selection) return; if (node->node()->exactBounds().isEmpty()) return; if (!node->node()->hasEditablePaintDevice()) return; KisPaintDeviceSP dev = node->node()->paintDevice(); KisPaintDeviceSP clip = new KisPaintDevice(dev->colorSpace()); KisPaintDeviceSP selectionProjection = d->selection->projection(); const KoColorSpace *cs = clip->colorSpace(); const KoColorSpace *selCs = d->selection->projection()->colorSpace(); QRect rc = d->selection->selectedExactRect(); KisPainter::copyAreaOptimized(QPoint(), dev, clip, rc); KisHLineIteratorSP layerIt = clip->createHLineIteratorNG(0, 0, rc.width()); KisHLineConstIteratorSP selectionIt = selectionProjection->createHLineIteratorNG(rc.x(), rc.y(), rc.width()); for (qint32 y = 0; y < rc.height(); y++) { for (qint32 x = 0; x < rc.width(); x++) { qreal dstAlpha = cs->opacityF(layerIt->rawData()); qreal sel = selCs->opacityF(selectionIt->oldRawData()); qreal newAlpha = sel * dstAlpha / (1.0 - dstAlpha + sel * dstAlpha); float mask = newAlpha / dstAlpha; cs->applyAlphaNormedFloatMask(layerIt->rawData(), &mask, 1); layerIt->nextPixel(); selectionIt->nextPixel(); } layerIt->nextRow(); selectionIt->nextRow(); } KisClipboard::instance()->setClip(clip, rc.topLeft()); } void Selection::cut(Node* node) { if (!node) return; if (!d->selection) return; if (node->node()->exactBounds().isEmpty()) return; if (!node->node()->hasEditablePaintDevice()) return; KisPaintDeviceSP dev = node->node()->paintDevice(); copy(node); dev->clearSelection(d->selection); node->node()->setDirty(d->selection->selectedExactRect()); } void Selection::paste(Node *destination, int x, int y) { if (!destination) return; if (!d->selection) return; if (!KisClipboard::instance()->hasClip()) return; KisPaintDeviceSP src = KisClipboard::instance()->clip(QRect(), false); KisPaintDeviceSP dst = destination->node()->paintDevice(); if (!dst) return; KisPainter::copyAreaOptimized(QPoint(x, y), src, dst, src->exactBounds(), d->selection); destination->node()->setDirty(); } void Selection::erode() { if (!d->selection) return; KisErodeSelectionFilter esf; QRect rc = esf.changeRect(d->selection->selectedExactRect()); esf.process(d->selection->pixelSelection(), rc); } void Selection::dilate() { if (!d->selection) return; KisDilateSelectionFilter dsf; QRect rc = dsf.changeRect(d->selection->selectedExactRect()); dsf.process(d->selection->pixelSelection(), rc); } void Selection::border(int xRadius, int yRadius) { if (!d->selection) return; KisBorderSelectionFilter sf(xRadius, yRadius); QRect rc = sf.changeRect(d->selection->selectedExactRect()); sf.process(d->selection->pixelSelection(), rc); } void Selection::feather(int radius) { if (!d->selection) return; KisFeatherSelectionFilter fsf(radius); QRect rc = fsf.changeRect(d->selection->selectedExactRect()); fsf.process(d->selection->pixelSelection(), rc); } void Selection::grow(int xradius, int yradius) { if (!d->selection) return; KisGrowSelectionFilter gsf(xradius, yradius); QRect rc = gsf.changeRect(d->selection->selectedExactRect()); gsf.process(d->selection->pixelSelection(), rc); } void Selection::shrink(int xRadius, int yRadius, bool edgeLock) { if (!d->selection) return; KisShrinkSelectionFilter sf(xRadius, yRadius, edgeLock); QRect rc = sf.changeRect(d->selection->selectedExactRect()); sf.process(d->selection->pixelSelection(), rc); } void Selection::smooth() { if (!d->selection) return; KisSmoothSelectionFilter sf; QRect rc = sf.changeRect(d->selection->selectedExactRect()); sf.process(d->selection->pixelSelection(), rc); } void Selection::invert() { if (!d->selection) return; KisInvertSelectionFilter sf; QRect rc = sf.changeRect(d->selection->selectedExactRect()); sf.process(d->selection->pixelSelection(), rc); } void Selection::resize(int w, int h) { if (!d->selection) return; d->selection->pixelSelection()->select(QRect(x(), y(), w, h)); } void Selection::select(int x, int y, int w, int h, int value) { if (!d->selection) return; d->selection->pixelSelection()->select(QRect(x, y, w, h), value); } void Selection::selectAll(Node *node, int value) { if (!d->selection) return; d->selection->pixelSelection()->select(node->node()->exactBounds(), value); } void Selection::replace(Selection *selection) { if (!d->selection) return; d->selection->pixelSelection()->applySelection(selection->selection()->pixelSelection(), SELECTION_REPLACE); } void Selection::add(Selection *selection) { if (!d->selection) return; d->selection->pixelSelection()->applySelection(selection->selection()->pixelSelection(), SELECTION_ADD); } void Selection::subtract(Selection *selection) { if (!d->selection) return; d->selection->pixelSelection()->applySelection(selection->selection()->pixelSelection(), SELECTION_SUBTRACT); } void Selection::intersect(Selection *selection) { if (!d->selection) return; 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 { QByteArray ba; if (!d->selection) return ba; KisPaintDeviceSP dev = d->selection->projection(); quint8 *data = new quint8[w * h]; dev->readBytes(data, x, y, w, h); ba = QByteArray((const char*)data, (int)(w * h)); delete[] data; return ba; } void Selection::setPixelData(QByteArray value, int x, int y, int w, int h) { if (!d->selection) return; KisPixelSelectionSP dev = d->selection->pixelSelection(); if (!dev) return; dev->writeBytes((const quint8*)value.constData(), x, y, w, h); } KisSelectionSP Selection::selection() const { return d->selection; } diff --git a/libs/libkis/Selection.h b/libs/libkis/Selection.h index 961463b6df..ffbf96e2a4 100644 --- a/libs/libkis/Selection.h +++ b/libs/libkis/Selection.h @@ -1,245 +1,250 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser 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 Lesser 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 LIBKIS_SELECTION_H #define LIBKIS_SELECTION_H #include #include "kritalibkis_export.h" #include "libkis.h" #include /** * Selection represents a selection on Krita. A selection is * not necessarily associated with a particular Node or Image. * * @code * from krita import * * * d = Application.activeDocument() * n = d.activeNode() * r = n.bounds() * s = Selection() * s.select(r.width() / 3, r.height() / 3, r.width() / 3, r.height() / 3, 255) * s.cut(n) * @endcode */ class KRITALIBKIS_EXPORT Selection : public QObject { Q_OBJECT public: /** * For internal use only. */ Selection(KisSelectionSP selection, QObject *parent = 0); /** * Create a new, empty selection object. */ explicit Selection(QObject *parent = 0); ~Selection() override; bool operator==(const Selection &other) const; bool operator!=(const Selection &other) const; public Q_SLOTS: /** * @return the width of the selection */ int width() const; /** * @return the height of the selection */ int height() const; /** * @return the left-hand position of the selection. */ int x() const; /** * @return the top position of the selection. */ int y() const; /** * Move the selection's top-left corner to the given coordinates. */ void move(int x, int y); /** * Make the selection entirely unselected. */ void clear(); /** * Make the selection's width and height smaller by the given value. * This will not move the selection's top-left position. */ void contract(int value); /** * @brief copy copies the area defined by the selection from the node to the clipboard. * @param node the node from where the pixels will be copied. */ void copy(Node *node); /** * @brief cut erases the area defined by the selection from the node and puts a copy on the clipboard. * @param node the node from which the selection will be cut. */ void cut(Node *node); /** * @brief paste pastes the content of the clipboard to the given node, limited by the area of the current * selection. * @param destination the node where the pixels will be written * @param x: the x position at which the clip will be written * @param y: the y position at which the clip will be written */ void paste(Node *destination, int x, int y); /** * Erode the selection with a radius of 1 pixel. */ void erode(); /** * Dilate the selection with a radius of 1 pixel. */ void dilate(); /** * Border the selection with the given radius. */ void border(int xRadius, int yRadius); /** * Feather the selection with the given radius. */ void feather(int radius); /** * Grow the selection with the given radius. */ void grow(int xradius, int yradius); /** * Shrink the selection with the given radius. */ void shrink(int xRadius, int yRadius, bool edgeLock); /** * Smooth the selection. */ void smooth(); /** * Invert the selection. */ void invert(); /** * Resize the selection to the given width and height. The top-left position will not be moved. */ void resize(int w, int h); /** * Select the given area. The value can be between 0 and 255; 0 is * totally unselected, 255 is totally selected. */ void select(int x, int y, int w, int h, int value); /** * Select all pixels in the given node. The value can be between 0 and 255; 0 is * totally unselected, 255 is totally selected. */ void selectAll(Node *node, int value); /** * Replace the current selection's selection with the one of the given selection. */ void replace(Selection *selection); /** * Add the given selection's selected pixels to the current selection. */ void add(Selection *selection); /** * Subtract the given selection's selected pixels from the current selection. */ void subtract(Selection *selection); /** * Intersect the given selection with this selection. */ 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. * * The byte array will contain one byte for every pixel, representing the selectedness. 0 * is totally unselected, 255 is fully selected. * * You can read outside the Selection's boundaries; those pixels will be unselected. * * The byte array is a copy of the original selection data. * @param x x position from where to start reading * @param y y position from where to start reading * @param w row length to read * @param h number of rows to read * @return a QByteArray with the pixel data. The byte array may be empty. */ QByteArray pixelData(int x, int y, int w, int h) const; /** * @brief setPixelData writes the given bytes, of which there must be enough, into the * Selection. * * @param value the byte array representing the pixels. There must be enough bytes available. * Krita will take the raw pointer from the QByteArray and start reading, not stopping before * (w * h) bytes are read. * * @param x the x position to start writing from * @param y the y position to start writing from * @param w the width of each row * @param h the number of rows to write */ void setPixelData(QByteArray value, int x, int y, int w, int h); private: friend class Document; friend class FilterLayer; friend class FillLayer; friend class SelectionMask; KisSelectionSP selection() const; struct Private; Private *const d; }; #endif // LIBKIS_SELECTION_H diff --git a/libs/libqml/qml/panels/SelectPanel.qml b/libs/libqml/qml/panels/SelectPanel.qml index 21d6e1f946..3e0f659380 100644 --- a/libs/libqml/qml/panels/SelectPanel.qml +++ b/libs/libqml/qml/panels/SelectPanel.qml @@ -1,520 +1,529 @@ /* This file is part of the KDE project * Copyright (C) 2012 Arjen Hiemstra * * 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. */ import QtQuick 2.3 import org.krita.sketch 1.0 import org.krita.sketch.components 1.0 Panel { id: base; name: "Select"; colorSet: "selection"; actions: [ Button { id: deselectButton; width: height; height: Constants.ToolbarButtonSize image: Settings.theme.icon("select-deselect"); enabled: sketchView.selectionManager ? sketchView.selectionManager.havePixelsSelected : false; onClicked: if (sketchView.selectionManager) sketchView.selectionManager.deselect(); }, Button { id: reselectButton; width: height; height: Constants.ToolbarButtonSize image: Settings.theme.icon("select-reselect"); onClicked: sketchView.selectionManager.reselect(); }, Item { width: base.width - Constants.DefaultMargin - (Constants.ToolbarButtonSize * 3) height: Constants.ToolbarButtonSize; }, Button { id: toggleShowSelectionButton; property bool showSelection: sketchView.selectionManager ? sketchView.selectionManager.displaySelection : false; width: height; height: Constants.ToolbarButtonSize image: showSelection ? Settings.theme.icon("select-show") : Settings.theme.icon("select-hide"); onClicked: sketchView.selectionManager.toggleDisplaySelection(); } ] peekContents: Item { id: peekItem; anchors.fill: parent; Item { width: childrenRect.width; height: peekItem.height / 3; anchors { horizontalCenter: parent.horizontalCenter; top: parent.top; } Button { id: selectRectangle; anchors.verticalCenter: parent.verticalCenter; image: Settings.theme.icon("select-rectangle"); width: Constants.ToolbarButtonSize * 0.8; height: width; onClicked: toolManager.requestToolChange("KisToolSelectRectangular"); checked: toolManager.currentTool !== null ? toolManager.currentTool.toolId() === "KisToolSelectRectangular" : false; } Button { id: selectPolygon; anchors.verticalCenter: parent.verticalCenter; anchors.left: selectRectangle.right; image: Settings.theme.icon("select-polygon"); width: Constants.ToolbarButtonSize * 0.8; height: width; onClicked: toolManager.requestToolChange("KisToolSelectPolygonal"); checked: toolManager.currentTool !== null ? toolManager.currentTool.toolId() === "KisToolSelectPolygonal" : false; } Button { id: selectArea; anchors.verticalCenter: parent.verticalCenter; anchors.left: selectPolygon.right; image: Settings.theme.icon("select-area"); width: Constants.ToolbarButtonSize * 0.8; height: width; onClicked: toolManager.requestToolChange("KisToolSelectContiguous"); checked: toolManager.currentTool !== null ? toolManager.currentTool.toolId() === "KisToolSelectContiguous" : false; } Button { id: selectColor; anchors.verticalCenter: parent.verticalCenter; anchors.left: selectArea.right; image: Settings.theme.icon("select-color"); width: Constants.ToolbarButtonSize * 0.8; height: width; onClicked: toolManager.requestToolChange("KisToolSelectSimilar"); checked: toolManager.currentTool !== null ? toolManager.currentTool.toolId() === "KisToolSelectSimilar" : false; } } Item { width: childrenRect.width; height: childrenRect.height; anchors.centerIn: parent; Button { id: selectReplace; image: Settings.theme.icon("select-replace"); width: Constants.ToolbarButtonSize * 0.8; height: width; checked: (toolManager.currentTool && toolManager.currentTool.selectionAction === 0) ? true : false; onClicked: if (toolManager.currentTool && toolManager.currentTool.selectionAction !== undefined) toolManager.currentTool.selectionAction = 0; } Button { id: selectIntersect; anchors.left: selectReplace.right; image: Settings.theme.icon("select-intersect"); width: Constants.ToolbarButtonSize * 0.8; height: width; checked: (toolManager.currentTool && toolManager.currentTool.selectionAction === 3) ? true : false; onClicked: if (toolManager.currentTool && toolManager.currentTool.selectionAction !== undefined) toolManager.currentTool.selectionAction = 3; } Button { id: selectAdd; anchors.left: selectIntersect.right; image: Settings.theme.icon("select-add"); width: Constants.ToolbarButtonSize * 0.8; height: width; checked: (toolManager.currentTool && toolManager.currentTool.selectionAction === 1) ? true : false; onClicked: if (toolManager.currentTool && toolManager.currentTool.selectionAction !== undefined) toolManager.currentTool.selectionAction = 1; } Button { id: selectSub; anchors.left: selectAdd.right; image: Settings.theme.icon("select-sub"); width: Constants.ToolbarButtonSize * 0.8; height: width; 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; height: peekItem.height / 3; anchors { horizontalCenter: parent.horizontalCenter; bottom: parent.bottom; } Button { id: selectAll; anchors.verticalCenter: parent.verticalCenter; anchors.margins: Constants.DefaultMargin; text: "All"; textColor: Settings.theme.color("panels/selection/buttons/text"); color: Settings.theme.color("panels/selection/buttons/color"); border.width: 1; border.color: Settings.theme.color("panels/selection/buttons/border"); radius: Constants.DefaultMargin; width: Constants.IsLandscape ? Constants.GridWidth : (Constants.GridWidth * 2 / 3);// - Constants.DefaultMargin; height: textSize + Constants.DefaultMargin * 2; onClicked: sketchView.selectionManager.selectAll(); } Button { id: selectInvert; anchors.verticalCenter: parent.verticalCenter; anchors.left: selectAll.right; anchors.margins: Constants.DefaultMargin; text: "Invert"; textColor: Settings.theme.color("panels/selection/buttons/text"); color: Settings.theme.color("panels/selection/buttons/color"); border.width: 1; border.color: Settings.theme.color("panels/selection/buttons/border"); radius: Constants.DefaultMargin; width: Constants.IsLandscape ? Constants.GridWidth : (Constants.GridWidth * 2 / 3);// - Constants.DefaultMargin; height: textSize + Constants.DefaultMargin * 2 onClicked: sketchView.selectionManager.invert(); } /*Button { id: selectOpaque; anchors.verticalCenter: parent.verticalCenter; anchors.left: selectInvert.right; anchors.margins: Constants.DefaultMargin; text: "Opaque"; textColor: "black"; color: "#63ffffff"; border.width: 1; border.color: "silver"; radius: Constants.DefaultMargin; width: (Constants.GridWidth * 2 / 3) - Constants.DefaultMargin; height: textSize + Constants.DefaultMargin * 2 }*/ } } fullContents: Item { anchors.fill: parent; Flickable { id: selectOptionsFullFlickable; anchors.fill: parent contentHeight: fullItem.height; Item { id: fullItem; width: parent.width; height: Constants.GridHeight * 9.5; Item { id: fullTopRow; width: childrenRect.width; height: Constants.GridHeight; anchors { horizontalCenter: parent.horizontalCenter; top: parent.top; } Button { id: selectRectangleFull; anchors.verticalCenter: parent.verticalCenter; image: Settings.theme.icon("select-rectangle"); width: Constants.ToolbarButtonSize * 0.8; height: width; onClicked: toolManager.requestToolChange("KisToolSelectRectangular"); checked: toolManager.currentTool !== null ? toolManager.currentTool.toolId() === "KisToolSelectRectangular" : false; } Button { id: selectPolygonFull; anchors.verticalCenter: parent.verticalCenter; anchors.left: selectRectangleFull.right; image: Settings.theme.icon("select-polygon") width: Constants.ToolbarButtonSize * 0.8; height: width; onClicked: toolManager.requestToolChange("KisToolSelectPolygonal"); checked: toolManager.currentTool !== null ? toolManager.currentTool.toolId() === "KisToolSelectPolygonal" : false; } Button { id: selectAreaFull; anchors.verticalCenter: parent.verticalCenter; anchors.left: selectPolygonFull.right; image: Settings.theme.icon("select-area") width: Constants.ToolbarButtonSize * 0.8; height: width; onClicked: toolManager.requestToolChange("KisToolSelectContiguous"); checked: toolManager.currentTool !== null ? toolManager.currentTool.toolId() === "KisToolSelectContiguous" : false; } Button { id: selectColorFull; anchors.verticalCenter: parent.verticalCenter; anchors.left: selectAreaFull.right; image: Settings.theme.icon("select-color") width: Constants.ToolbarButtonSize * 0.8; height: width; onClicked: toolManager.requestToolChange("KisToolSelectSimilar"); checked: toolManager.currentTool !== null ? toolManager.currentTool.toolId() === "KisToolSelectSimilar" : false; } } Label { id: modeLabel anchors { left: parent.left; leftMargin: Constants.DefaultMargin; top: fullTopRow.bottom; } height: Constants.GridHeight / 2; text: "Mode:"; font: Settings.theme.font("panelSection"); } Item { id: fullModeRow; width: childrenRect.width; height: Constants.GridHeight; anchors.horizontalCenter: parent.horizontalCenter; anchors.top: modeLabel.bottom; Button { id: selectReplaceFull; image: Settings.theme.icon("select-replace") width: Constants.ToolbarButtonSize * 0.8; height: width; checked: (toolManager.currentTool && toolManager.currentTool.selectionAction === 0) ? true : false; onClicked: if (toolManager.currentTool && toolManager.currentTool.selectionAction !== undefined) toolManager.currentTool.selectionAction = 0; } Button { id: selectIntersectFull; anchors.left: selectReplaceFull.right; image: Settings.theme.icon("select-intersect") width: Constants.ToolbarButtonSize * 0.8; height: width; checked: (toolManager.currentTool && toolManager.currentTool.selectionAction === 3) ? true : false; onClicked: if (toolManager.currentTool && toolManager.currentTool.selectionAction !== undefined) toolManager.currentTool.selectionAction = 3; } Button { id: selectAddFull; anchors.left: selectIntersectFull.right; image: Settings.theme.icon("select-add") width: Constants.ToolbarButtonSize * 0.8; height: width; checked: (toolManager.currentTool && toolManager.currentTool.selectionAction === 1) ? true : false; onClicked: if (toolManager.currentTool && toolManager.currentTool.selectionAction !== undefined) toolManager.currentTool.selectionAction = 1; } Button { id: selectSubFull; anchors.left: selectAddFull.right; image: Settings.theme.icon("select-sub") width: Constants.ToolbarButtonSize * 0.8; height: width; checked: (toolManager.currentTool && toolManager.currentTool.selectionAction === 2) ? true : false; onClicked: if (toolManager.currentTool && toolManager.currentTool.selectionAction !== undefined) toolManager.currentTool.selectionAction = 2; } } Label { id: fullSelectCommandsLabel; anchors { left: parent.left; leftMargin: Constants.DefaultMargin; top: fullModeRow.bottom; } height: Constants.GridHeight / 2; text: "Select:"; font: Settings.theme.font("panelSection"); } Item { id: fullSelectCommands; width: childrenRect.width; height: Constants.GridHeight; anchors { horizontalCenter: parent.horizontalCenter; top: fullSelectCommandsLabel.bottom; } Button { id: selectAllFull; anchors.verticalCenter: parent.verticalCenter; anchors.margins: Constants.DefaultMargin; text: "All"; textColor: Settings.theme.color("panels/selection/buttons/text"); color: Settings.theme.color("panels/selection/buttons/color"); border.width: 1; border.color: Settings.theme.color("panels/selection/buttons/border"); radius: Constants.DefaultMargin; width: Constants.IsLandscape ? Constants.GridWidth : (Constants.GridWidth * 2 / 3);// - Constants.DefaultMargin; height: textSize + Constants.DefaultMargin * 2 onClicked: sketchView.selectionManager.selectAll(); } Button { id: selectInvertFull; anchors.verticalCenter: parent.verticalCenter; anchors.left: selectAllFull.right; anchors.margins: Constants.DefaultMargin; text: "Invert"; textColor: Settings.theme.color("panels/selection/buttons/text"); color: Settings.theme.color("panels/selection/buttons/color"); border.width: 1; border.color: Settings.theme.color("panels/selection/buttons/border"); radius: Constants.DefaultMargin; width: Constants.IsLandscape ? Constants.GridWidth : (Constants.GridWidth * 2 / 3);// - Constants.DefaultMargin; height: textSize + Constants.DefaultMargin * 2 onClicked: sketchView.selectionManager.invert(); } /*Button { id: selectOpaqueFull; anchors.verticalCenter: parent.verticalCenter; anchors.left: selectInvertFull.right; anchors.margins: Constants.DefaultMargin; text: "Opaque"; textColor: "black"; color: "#63ffffff"; border.width: 1; border.color: "silver"; radius: Constants.DefaultMargin; width: (Constants.GridWidth * 2 / 3) - Constants.DefaultMargin; height: textSize + Constants.DefaultMargin * 2 }*/ } Label { id: fullEditingLabel; anchors { left: parent.left; leftMargin: Constants.DefaultMargin; top: fullSelectCommands.bottom; } height: Constants.GridHeight / 2; text: "Edit:"; font: Settings.theme.font("panelSection"); } Column { anchors { top: fullEditingLabel.bottom; left: parent.left; right: parent.right; } spacing: Constants.DefaultMargin; Item { width: fullItem.width; height: featherTxt.height; RangeInput { id: featherTxt; anchors { left: parent.left; right: featherImg.left; } placeholder: "Feather"; min: 1; max: 500; decimals: 0; useExponentialValue: true; } Image { id: featherImg; anchors { right: parent.right; rightMargin: Constants.DefaultMargin; verticalCenter: featherTxt.verticalCenter; } height: parent.height - (Constants.DefaultMargin * 6); width: height; source: Settings.theme.icon("select-apply"); smooth: true; MouseArea { anchors.fill: parent; onClicked: sketchView.selectionExtras.feather(featherTxt.value); } } } Item { width: fullItem.width; height: growTxt.height; RangeInput { id: growTxt; anchors { left: parent.left; right: growImg.left; } placeholder: "Grow"; min: 1; max: 500; decimals: 0; useExponentialValue: true; } Image { id: growImg; anchors { right: parent.right; rightMargin: Constants.DefaultMargin; verticalCenter: growTxt.verticalCenter; } height: parent.height - (Constants.DefaultMargin * 6); width: height; source: Settings.theme.icon("select-apply"); smooth: true; MouseArea { anchors.fill: parent; onClicked: sketchView.selectionExtras.grow(growTxt.value, growTxt.value); } } } Item { width: fullItem.width; height: borderTxt.height; RangeInput { id: borderTxt; anchors { left: parent.left; right: borderImg.left; } placeholder: "Border"; min: 1; max: 500; decimals: 0; useExponentialValue: true; } Image { id: borderImg; anchors { right: parent.right; rightMargin: Constants.DefaultMargin; verticalCenter: borderTxt.verticalCenter; } height: parent.height - (Constants.DefaultMargin * 6); width: height; source: Settings.theme.icon("select-apply"); smooth: true; MouseArea { anchors.fill: parent; onClicked: sketchView.selectionExtras.border(borderTxt.value, borderTxt.value); } } } Item { width: fullItem.width; height: shrinkTxt.height; RangeInput { id: shrinkTxt; anchors { left: parent.left; right: shrinkImg.left; } placeholder: "Shrink"; min: 1; max: 500; decimals: 0; useExponentialValue: true; } Image { id: shrinkImg; anchors { right: parent.right; rightMargin: Constants.DefaultMargin; verticalCenter: shrinkTxt.verticalCenter; } height: parent.height - (Constants.DefaultMargin * 6); width: height; source: Settings.theme.icon("select-apply"); smooth: true; MouseArea { anchors.fill: parent; onClicked: sketchView.selectionExtras.shrink(shrinkTxt.value, shrinkTxt.value, false); } } } } } } ScrollDecorator { flickableItem: selectOptionsFullFlickable; } } } diff --git a/libs/ui/forms/wdgselectionoptions.ui b/libs/ui/forms/wdgselectionoptions.ui index 7231b4c46d..3cc3ce3820 100644 --- a/libs/ui/forms/wdgselectionoptions.ui +++ b/libs/ui/forms/wdgselectionoptions.ui @@ -1,195 +1,199 @@ WdgSelectionOptions 0 0 271 - 106 + 110 - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - + 0 Intersect ... true false Vector Selection ... true false Replace ... true true false Add ... true false CrossCursor Mode: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Action: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Pixel Selection ... true true false Subtract ... true false + + + + Symmetric Difference + + + -1 + + + ... + + + true + + + false + + + - + Qt::Horizontal 0 10 Anti-aliasing true KoGroupButton QToolButton
KoGroupButton.h
diff --git a/libs/ui/kis_selection_manager.cc b/libs/ui/kis_selection_manager.cc index d7dc874755..0f963f5ee5 100644 --- a/libs/ui/kis_selection_manager.cc +++ b/libs/ui/kis_selection_manager.cc @@ -1,743 +1,746 @@ /* * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2007 Sven Langkamp * * The outline algorithm uses the limn algorithm of fontutils by * Karl Berry and Kathryn Hargreaves * * 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 "kis_selection_manager.h" #include #include #include #include #include #include #include #include #include #include #include "KoCanvasController.h" #include "KoChannelInfo.h" #include "KoIntegerMaths.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_adjustment_layer.h" #include "kis_node_manager.h" #include "canvas/kis_canvas2.h" #include "kis_config.h" #include "kis_convolution_painter.h" #include "kis_convolution_kernel.h" #include "kis_debug.h" #include "kis_fill_painter.h" #include "kis_group_layer.h" #include "kis_layer.h" #include "kis_statusbar.h" #include "kis_paint_device.h" #include "kis_paint_layer.h" #include "kis_painter.h" #include "kis_transaction.h" #include "kis_selection.h" #include "kis_types.h" #include "kis_canvas_resource_provider.h" #include "kis_undo_adapter.h" #include "kis_pixel_selection.h" #include "flake/kis_shape_selection.h" #include "commands/kis_selection_commands.h" #include "kis_selection_mask.h" #include "flake/kis_shape_layer.h" #include "kis_selection_decoration.h" #include "canvas/kis_canvas_decoration.h" #include "kis_node_commands_adapter.h" #include "kis_iterator_ng.h" #include "kis_clipboard.h" #include "KisViewManager.h" #include "kis_selection_filters.h" #include "kis_figure_painting_tool_helper.h" #include "KisView.h" #include "dialogs/kis_dlg_stroke_selection_properties.h" #include "actions/kis_selection_action_factories.h" #include "actions/KisPasteActionFactory.h" #include "kis_action.h" #include "kis_action_manager.h" #include "operations/kis_operation_configuration.h" //new #include "kis_node_query_path.h" #include "kis_tool_shape.h" KisSelectionManager::KisSelectionManager(KisViewManager * view) : m_view(view), m_doc(0), m_imageView(0), m_adapter(new KisNodeCommandsAdapter(view)), m_copy(0), m_copyMerged(0), m_cut(0), m_paste(0), m_pasteNew(0), m_cutToNewLayer(0), m_selectAll(0), m_deselect(0), m_clear(0), m_reselect(0), m_invert(0), m_copyToNewLayer(0), m_fillForegroundColor(0), m_fillBackgroundColor(0), m_fillPattern(0), m_imageResizeToSelection(0), m_selectionDecoration(0) { m_clipboard = KisClipboard::instance(); } KisSelectionManager::~KisSelectionManager() { } void KisSelectionManager::setup(KisActionManager* actionManager) { m_cut = actionManager->createStandardAction(KStandardAction::Cut, this, SLOT(cut())); m_copy = actionManager->createStandardAction(KStandardAction::Copy, this, SLOT(copy())); m_paste = actionManager->createStandardAction(KStandardAction::Paste, this, SLOT(paste())); KisAction *action = actionManager->createAction("copy_sharp"); connect(action, SIGNAL(triggered()), this, SLOT(copySharp())); action = actionManager->createAction("cut_sharp"); connect(action, SIGNAL(triggered()), this, SLOT(cutSharp())); m_pasteNew = actionManager->createAction("paste_new"); connect(m_pasteNew, SIGNAL(triggered()), this, SLOT(pasteNew())); m_pasteAt = actionManager->createAction("paste_at"); connect(m_pasteAt, SIGNAL(triggered()), this, SLOT(pasteAt())); m_copyMerged = actionManager->createAction("copy_merged"); connect(m_copyMerged, SIGNAL(triggered()), this, SLOT(copyMerged())); m_selectAll = actionManager->createAction("select_all"); connect(m_selectAll, SIGNAL(triggered()), this, SLOT(selectAll())); m_deselect = actionManager->createAction("deselect"); connect(m_deselect, SIGNAL(triggered()), this, SLOT(deselect())); m_clear = actionManager->createAction("clear"); connect(m_clear, SIGNAL(triggered()), SLOT(clear())); m_reselect = actionManager->createAction("reselect"); connect(m_reselect, SIGNAL(triggered()), this, SLOT(reselect())); m_invert = actionManager->createAction("invert_selection"); m_invert->setOperationID("invertselection"); actionManager->registerOperation(new KisInvertSelectionOperation); m_copyToNewLayer = actionManager->createAction("copy_selection_to_new_layer"); connect(m_copyToNewLayer, SIGNAL(triggered()), this, SLOT(copySelectionToNewLayer())); m_cutToNewLayer = actionManager->createAction("cut_selection_to_new_layer"); connect(m_cutToNewLayer, SIGNAL(triggered()), this, SLOT(cutToNewLayer())); m_fillForegroundColor = actionManager->createAction("fill_selection_foreground_color"); connect(m_fillForegroundColor, SIGNAL(triggered()), this, SLOT(fillForegroundColor())); m_fillBackgroundColor = actionManager->createAction("fill_selection_background_color"); connect(m_fillBackgroundColor, SIGNAL(triggered()), this, SLOT(fillBackgroundColor())); m_fillPattern = actionManager->createAction("fill_selection_pattern"); connect(m_fillPattern, SIGNAL(triggered()), this, SLOT(fillPattern())); m_fillForegroundColorOpacity = actionManager->createAction("fill_selection_foreground_color_opacity"); connect(m_fillForegroundColorOpacity, SIGNAL(triggered()), this, SLOT(fillForegroundColorOpacity())); m_fillBackgroundColorOpacity = actionManager->createAction("fill_selection_background_color_opacity"); connect(m_fillBackgroundColorOpacity, SIGNAL(triggered()), this, SLOT(fillBackgroundColorOpacity())); m_fillPatternOpacity = actionManager->createAction("fill_selection_pattern_opacity"); connect(m_fillPatternOpacity, SIGNAL(triggered()), this, SLOT(fillPatternOpacity())); m_strokeShapes = actionManager->createAction("stroke_shapes"); connect(m_strokeShapes, SIGNAL(triggered()), this, SLOT(paintSelectedShapes())); m_toggleDisplaySelection = actionManager->createAction("toggle_display_selection"); connect(m_toggleDisplaySelection, SIGNAL(triggered()), this, SLOT(toggleDisplaySelection())); m_toggleDisplaySelection->setChecked(true); m_imageResizeToSelection = actionManager->createAction("resizeimagetoselection"); connect(m_imageResizeToSelection, SIGNAL(triggered()), this, SLOT(imageResizeToSelection())); action = actionManager->createAction("edit_selection"); connect(action, SIGNAL(triggered()), SLOT(editSelection())); action = actionManager->createAction("convert_to_vector_selection"); connect(action, SIGNAL(triggered()), SLOT(convertToVectorSelection())); action = actionManager->createAction("convert_to_raster_selection"); connect(action, SIGNAL(triggered()), SLOT(convertToRasterSelection())); action = actionManager->createAction("convert_shapes_to_vector_selection"); connect(action, SIGNAL(triggered()), SLOT(convertShapesToVectorSelection())); action = actionManager->createAction("convert_selection_to_shape"); connect(action, SIGNAL(triggered()), SLOT(convertToShape())); m_toggleSelectionOverlayMode = actionManager->createAction("toggle-selection-overlay-mode"); connect(m_toggleSelectionOverlayMode, SIGNAL(triggered()), SLOT(slotToggleSelectionDecoration())); m_strokeSelected = actionManager->createAction("stroke_selection"); connect(m_strokeSelected, SIGNAL(triggered()), SLOT(slotStrokeSelection())); QClipboard *cb = QApplication::clipboard(); connect(cb, SIGNAL(dataChanged()), SLOT(clipboardDataChanged())); } void KisSelectionManager::setView(QPointerimageView) { if (m_imageView && m_imageView->canvasBase()) { disconnect(m_imageView->canvasBase()->toolProxy(), SIGNAL(toolChanged(const QString&)), this, SLOT(clipboardDataChanged())); KoSelection *selection = m_imageView->canvasBase()->globalShapeManager()->selection(); selection->disconnect(this, SLOT(shapeSelectionChanged())); KisSelectionDecoration *decoration = qobject_cast(m_imageView->canvasBase()->decoration("selection").data()); if (decoration) { disconnect(SIGNAL(currentSelectionChanged()), decoration); } m_imageView->image()->undoAdapter()->disconnect(this); m_selectionDecoration = 0; } m_imageView = imageView; if (m_imageView) { connect(m_imageView->canvasBase()->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SLOT(shapeSelectionChanged())); KisSelectionDecoration* decoration = qobject_cast(m_imageView->canvasBase()->decoration("selection").data()); if (!decoration) { decoration = new KisSelectionDecoration(m_imageView); decoration->setVisible(true); m_imageView->canvasBase()->addDecoration(decoration); } m_selectionDecoration = decoration; connect(this, SIGNAL(currentSelectionChanged()), decoration, SLOT(selectionChanged())); connect(m_imageView->image()->undoAdapter(), SIGNAL(selectionChanged()), SLOT(selectionChanged())); connect(m_imageView->canvasBase()->toolProxy(), SIGNAL(toolChanged(const QString&)), SLOT(clipboardDataChanged())); } } void KisSelectionManager::clipboardDataChanged() { m_view->updateGUI(); } bool KisSelectionManager::havePixelsSelected() { KisSelectionSP activeSelection = m_view->selection(); return activeSelection && !activeSelection->selectedRect().isEmpty(); } bool KisSelectionManager::havePixelsInClipboard() { return m_clipboard->hasClip(); } bool KisSelectionManager::haveShapesSelected() { if (m_view && m_view->canvasBase()) { return m_view->canvasBase()->selectedShapesProxy()->selection()->count() > 0; } return false; } bool KisSelectionManager::haveShapesInClipboard() { KoSvgPaste paste; return paste.hasShapes(); } bool KisSelectionManager::haveAnySelectionWithPixels() { KisSelectionSP selection = m_view->selection(); return selection && selection->hasPixelSelection(); } bool KisSelectionManager::haveShapeSelectionWithShapes() { KisSelectionSP selection = m_view->selection(); return selection && selection->hasShapeSelection(); } bool KisSelectionManager::haveRasterSelectionWithPixels() { KisSelectionSP selection = m_view->selection(); return selection && selection->hasPixelSelection() && !selection->hasShapeSelection(); } void KisSelectionManager::updateGUI() { Q_ASSERT(m_view); Q_ASSERT(m_clipboard); if (!m_view || !m_clipboard) return; bool havePixelsSelected = this->havePixelsSelected(); bool havePixelsInClipboard = this->havePixelsInClipboard(); bool haveShapesSelected = this->haveShapesSelected(); bool haveShapesInClipboard = this->haveShapesInClipboard(); bool haveDevice = m_view->activeDevice(); KisLayerSP activeLayer = m_view->activeLayer(); KisImageWSP image = activeLayer ? activeLayer->image() : 0; bool canReselect = image && image->canReselectGlobalSelection(); bool canDeselect = image && image->globalSelection(); m_clear->setEnabled(haveDevice || havePixelsSelected || haveShapesSelected); m_cut->setEnabled(havePixelsSelected || haveShapesSelected); m_copy->setEnabled(havePixelsSelected || haveShapesSelected); m_paste->setEnabled(havePixelsInClipboard || haveShapesInClipboard); m_pasteAt->setEnabled(havePixelsInClipboard || haveShapesInClipboard); // FIXME: how about pasting shapes? m_pasteNew->setEnabled(havePixelsInClipboard); m_selectAll->setEnabled(true); m_deselect->setEnabled(canDeselect); m_reselect->setEnabled(canReselect); // m_load->setEnabled(true); // m_save->setEnabled(havePixelsSelected); updateStatusBar(); emit signalUpdateGUI(); } void KisSelectionManager::updateStatusBar() { if (m_view && m_view->statusBar()) { m_view->statusBar()->setSelection(m_view->image()); } } void KisSelectionManager::selectionChanged() { m_view->updateGUI(); emit currentSelectionChanged(); } void KisSelectionManager::cut() { KisCutCopyActionFactory factory; factory.run(true, false, m_view); } void KisSelectionManager::copy() { KisCutCopyActionFactory factory; factory.run(false, false, m_view); } void KisSelectionManager::cutSharp() { KisCutCopyActionFactory factory; factory.run(true, true, m_view); } void KisSelectionManager::copySharp() { KisCutCopyActionFactory factory; factory.run(false, true, m_view); } void KisSelectionManager::copyMerged() { KisCopyMergedActionFactory factory; factory.run(m_view); } void KisSelectionManager::paste() { KisPasteActionFactory factory; factory.run(false, m_view); } void KisSelectionManager::pasteAt() { KisPasteActionFactory factory; factory.run(true, m_view); } void KisSelectionManager::pasteNew() { KisPasteNewActionFactory factory; factory.run(m_view); } void KisSelectionManager::selectAll() { KisSelectAllActionFactory factory; factory.run(m_view); } void KisSelectionManager::deselect() { KisDeselectActionFactory factory; factory.run(m_view); } void KisSelectionManager::invert() { if(m_invert) m_invert->trigger(); } void KisSelectionManager::reselect() { KisReselectActionFactory factory; factory.run(m_view); } #include #include void KisSelectionManager::editSelection() { KisSelectionSP selection = m_view->selection(); if (!selection) return; KisAction *action = m_view->actionManager()->actionByName("show-global-selection-mask"); KIS_SAFE_ASSERT_RECOVER_RETURN(action); if (!action->isChecked()) { action->setChecked(true); emit action->toggled(true); emit action->triggered(true); } KisNodeSP node = selection->parentNode(); KIS_SAFE_ASSERT_RECOVER_RETURN(node); m_view->nodeManager()->slotNonUiActivatedNode(node); if (selection->hasShapeSelection()) { KisShapeSelection *shapeSelection = dynamic_cast(selection->shapeSelection()); KIS_SAFE_ASSERT_RECOVER_RETURN(shapeSelection); KoToolManager::instance()->switchToolRequested(KoInteractionTool_ID); QList shapes = shapeSelection->shapes(); if (shapes.isEmpty()) { KIS_SAFE_ASSERT_RECOVER_NOOP(0 && "no shapes"); return; } Q_FOREACH (KoShape *shape, shapes) { m_view->canvasBase()->selectedShapesProxy()->selection()->select(shape); } } else { KoToolManager::instance()->switchToolRequested("KisToolTransform"); } } void KisSelectionManager::convertToVectorSelection() { KisSelectionToVectorActionFactory factory; factory.run(m_view); } void KisSelectionManager::convertToRasterSelection() { KisSelectionToRasterActionFactory factory; factory.run(m_view); } void KisSelectionManager::convertShapesToVectorSelection() { KisShapesToVectorSelectionActionFactory factory; factory.run(m_view); } void KisSelectionManager::convertToShape() { KisSelectionToShapeActionFactory factory; factory.run(m_view); } void KisSelectionManager::clear() { KisClearActionFactory factory; factory.run(m_view); } void KisSelectionManager::fillForegroundColor() { KisFillActionFactory factory; factory.run("fg", m_view); } void KisSelectionManager::fillBackgroundColor() { KisFillActionFactory factory; factory.run("bg", m_view); } void KisSelectionManager::fillPattern() { KisFillActionFactory factory; factory.run("pattern", m_view); } void KisSelectionManager::fillForegroundColorOpacity() { KisFillActionFactory factory; factory.run("fg_opacity", m_view); } void KisSelectionManager::fillBackgroundColorOpacity() { KisFillActionFactory factory; factory.run("bg_opacity", m_view); } void KisSelectionManager::fillPatternOpacity() { KisFillActionFactory factory; factory.run("pattern_opacity", m_view); } void KisSelectionManager::copySelectionToNewLayer() { copy(); paste(); } void KisSelectionManager::cutToNewLayer() { cut(); paste(); } void KisSelectionManager::toggleDisplaySelection() { KIS_ASSERT_RECOVER_RETURN(m_selectionDecoration); m_selectionDecoration->toggleVisibility(); m_toggleDisplaySelection->blockSignals(true); m_toggleDisplaySelection->setChecked(m_selectionDecoration->visible()); m_toggleDisplaySelection->blockSignals(false); emit displaySelectionChanged(); } bool KisSelectionManager::displaySelection() { return m_toggleDisplaySelection->isChecked(); } void KisSelectionManager::shapeSelectionChanged() { KoShapeManager* shapeManager = m_view->canvasBase()->globalShapeManager(); KoSelection * selection = shapeManager->selection(); QList selectedShapes = selection->selectedShapes(); KoShapeStrokeSP border(new KoShapeStroke(0, Qt::lightGray)); Q_FOREACH (KoShape* shape, shapeManager->shapes()) { if (dynamic_cast(shape->parent())) { if (selectedShapes.contains(shape)) shape->setStroke(border); else shape->setStroke(KoShapeStrokeSP()); } } m_view->updateGUI(); } void KisSelectionManager::imageResizeToSelection() { KisImageResizeToSelectionActionFactory factory; factory.run(m_view); } void KisSelectionManager::paintSelectedShapes() { KisImageWSP image = m_view->image(); if (!image) return; KisLayerSP layer = m_view->activeLayer(); if (!layer) return; QList shapes = m_view->canvasBase()->shapeManager()->selection()->selectedShapes(); KisPaintLayerSP paintLayer = new KisPaintLayer(image, i18n("Stroked Shapes"), OPACITY_OPAQUE_U8); KUndo2MagicString actionName = kundo2_i18n("Stroke Shapes"); m_adapter->beginMacro(actionName); m_adapter->addNode(paintLayer.data(), layer->parent().data(), layer.data()); KisFigurePaintingToolHelper helper(actionName, image, paintLayer.data(), m_view->resourceProvider()->resourceManager(), KisPainter::StrokeStyleBrush, KisPainter::FillStyleNone); Q_FOREACH (KoShape* shape, shapes) { QTransform matrix = shape->absoluteTransformation(0) * QTransform::fromScale(image->xRes(), image->yRes()); QPainterPath mapedOutline = matrix.map(shape->outline()); helper.paintPainterPath(mapedOutline); } m_adapter->endMacro(); } void KisSelectionManager::slotToggleSelectionDecoration() { KIS_ASSERT_RECOVER_RETURN(m_selectionDecoration); KisSelectionDecoration::Mode mode = m_selectionDecoration->mode() ? KisSelectionDecoration::Ants : KisSelectionDecoration::Mask; m_selectionDecoration->setMode(mode); emit displaySelectionChanged(); } bool KisSelectionManager::showSelectionAsMask() const { if (m_selectionDecoration) { return m_selectionDecoration->mode() == KisSelectionDecoration::Mask; } return false; } void KisSelectionManager::slotStrokeSelection() { KisImageWSP image = m_view->image(); if (!image ) { return; } KisNodeSP currentNode = m_view->resourceProvider()->resourceManager()->resource(KisCanvasResourceProvider::CurrentKritaNode).value(); bool isVectorLayer = false; if (currentNode->inherits("KisShapeLayer")) { isVectorLayer = true; } QPointer dlg = new KisDlgStrokeSelection(image, m_view, isVectorLayer); if (dlg->exec() == QDialog::Accepted) { StrokeSelectionOptions params = dlg->getParams(); if (params.brushSelected){ KisStrokeBrushSelectionActionFactory factory; factory.run(m_view, params); } else { KisStrokeSelectionActionFactory factory; factory.run(m_view, params); } } delete dlg; } #include "kis_image_barrier_locker.h" #include "kis_selection_tool_helper.h" void KisSelectionManager::selectOpaqueOnNode(KisNodeSP node, SelectionAction action) { KisImageSP image = m_view->image(); if (!m_view->blockUntilOperationsFinished(image)) { return; } KUndo2MagicString actionName; KisPixelSelectionSP tmpSel = KisPixelSelectionSP(new KisPixelSelection()); KisCanvas2 *canvas = m_view->canvasBase(); { KisImageBarrierLocker locker(image); KisPaintDeviceSP device = node->projection(); if (!device) device = node->paintDevice(); if (!device) device = node->original(); KIS_ASSERT_RECOVER_RETURN(canvas && device); QRect rc = device->exactBounds(); if (rc.isEmpty()) return; /** * If there is nothing selected, just create a new selection */ if (!canvas->imageView()->selection()) { action = SELECTION_REPLACE; } switch (action) { case SELECTION_ADD: actionName = kundo2_i18n("Select Opaque (Add)"); break; case SELECTION_SUBTRACT: actionName = kundo2_i18n("Select Opaque (Subtract)"); break; 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; } qint32 x, y, w, h; rc.getRect(&x, &y, &w, &h); const KoColorSpace * cs = device->colorSpace(); KisHLineConstIteratorSP deviter = device->createHLineConstIteratorNG(x, y, w); KisHLineIteratorSP selIter = tmpSel ->createHLineIteratorNG(x, y, w); for (int row = y; row < h + y; ++row) { do { *selIter->rawData() = cs->opacityU8(deviter->oldRawData()); } while (deviter->nextPixel() && selIter->nextPixel()); deviter->nextRow(); selIter->nextRow(); } } KisSelectionToolHelper helper(canvas, actionName); tmpSel->invalidateOutlineCache(); helper.selectPixelSelection(tmpSel, action); } diff --git a/libs/ui/tool/kis_selection_tool_config_widget_helper.cpp b/libs/ui/tool/kis_selection_tool_config_widget_helper.cpp index c1f7022eb4..d9e8d84145 100644 --- a/libs/ui/tool/kis_selection_tool_config_widget_helper.cpp +++ b/libs/ui/tool/kis_selection_tool_config_widget_helper.cpp @@ -1,152 +1,158 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * 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 "kis_selection_tool_config_widget_helper.h" #include #include "kis_selection_options.h" #include "kis_canvas2.h" #include "KisViewManager.h" #include "kis_canvas_resource_provider.h" #include "kis_signals_blocker.h" #include #include KisSelectionToolConfigWidgetHelper::KisSelectionToolConfigWidgetHelper(const QString &windowTitle) : m_optionsWidget(0), m_windowTitle(windowTitle) { } void KisSelectionToolConfigWidgetHelper::createOptionWidget(KisCanvas2 *canvas, const QString &toolId) { m_optionsWidget = new KisSelectionOptions(canvas); Q_CHECK_PTR(m_optionsWidget); m_optionsWidget->setObjectName(toolId + "option widget"); m_optionsWidget->setWindowTitle(m_windowTitle); slotToolActivatedChanged(true); // See https://bugs.kde.org/show_bug.cgi?id=316896 QWidget *specialSpacer = new QWidget(m_optionsWidget); specialSpacer->setObjectName("SpecialSpacer"); specialSpacer->setFixedSize(0, 0); m_optionsWidget->layout()->addWidget(specialSpacer); connect(m_optionsWidget, &KisSelectionOptions::actionChanged, this, &KisSelectionToolConfigWidgetHelper::slotWidgetActionChanged); connect(m_optionsWidget, &KisSelectionOptions::modeChanged, this, &KisSelectionToolConfigWidgetHelper::slotWidgetModeChanged); connect(m_optionsWidget, &KisSelectionOptions::antiAliasSelectionChanged, this, &KisSelectionToolConfigWidgetHelper::slotWidgetAntiAliasChanged); m_optionsWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); m_optionsWidget->adjustSize(); } KisSelectionOptions* KisSelectionToolConfigWidgetHelper::optionWidget() const { return m_optionsWidget; } SelectionMode KisSelectionToolConfigWidgetHelper::selectionMode() const { return m_selectionMode; } SelectionAction KisSelectionToolConfigWidgetHelper::selectionAction() const { return m_selectionAction; } bool KisSelectionToolConfigWidgetHelper::antiAliasSelection() const { return m_antiAliasSelection; } 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"); cfg.writeEntry("selectionAction", action); emit selectionActionChanged(action); } } void KisSelectionToolConfigWidgetHelper::slotWidgetModeChanged(int mode) { m_selectionMode = (SelectionMode)mode; KConfigGroup cfg = KSharedConfig::openConfig()->group("KisToolSelectBase"); cfg.writeEntry("selectionMode", mode); } void KisSelectionToolConfigWidgetHelper::slotWidgetAntiAliasChanged(bool value) { m_antiAliasSelection = value; KConfigGroup cfg = KSharedConfig::openConfig()->group("KisToolSelectBase"); cfg.writeEntry("antiAliasSelection", value); } void KisSelectionToolConfigWidgetHelper::slotReplaceModeRequested() { m_optionsWidget->setAction(SELECTION_REPLACE); slotWidgetActionChanged(m_optionsWidget->action()); } void KisSelectionToolConfigWidgetHelper::slotAddModeRequested() { m_optionsWidget->setAction(SELECTION_ADD); slotWidgetActionChanged(m_optionsWidget->action()); } void KisSelectionToolConfigWidgetHelper::slotSubtractModeRequested() { m_optionsWidget->setAction(SELECTION_SUBTRACT); slotWidgetActionChanged(m_optionsWidget->action()); } void KisSelectionToolConfigWidgetHelper::slotIntersectModeRequested() { m_optionsWidget->setAction(SELECTION_INTERSECT); slotWidgetActionChanged(m_optionsWidget->action()); } +void KisSelectionToolConfigWidgetHelper::slotSymmetricDifferenceModeRequested() +{ + m_optionsWidget->setAction(SELECTION_SYMMETRICDIFFERENCE); + slotWidgetActionChanged(m_optionsWidget->action()); +} + void KisSelectionToolConfigWidgetHelper::slotToolActivatedChanged(bool isActivated) { if (!isActivated) return; KConfigGroup cfg = KSharedConfig::openConfig()->group("KisToolSelectBase"); m_selectionAction = (SelectionAction)cfg.readEntry("selectionAction", (int)SELECTION_REPLACE); m_selectionMode = (SelectionMode)cfg.readEntry("selectionMode", (int)SHAPE_PROTECTION); m_antiAliasSelection = cfg.readEntry("antiAliasSelection", true); KisSignalsBlocker b(m_optionsWidget); m_optionsWidget->setAction(m_selectionAction); m_optionsWidget->setMode(m_selectionMode); m_optionsWidget->setAntiAliasSelection(m_antiAliasSelection); } diff --git a/libs/ui/tool/kis_selection_tool_config_widget_helper.h b/libs/ui/tool/kis_selection_tool_config_widget_helper.h index acb631c905..1b0fa09793 100644 --- a/libs/ui/tool/kis_selection_tool_config_widget_helper.h +++ b/libs/ui/tool/kis_selection_tool_config_widget_helper.h @@ -1,73 +1,74 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * 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_SELECTION_TOOL_CONFIG_WIDGET_HELPER_H #define __KIS_SELECTION_TOOL_CONFIG_WIDGET_HELPER_H #include #include "kritaui_export.h" #include "kis_selection.h" #include "kis_canvas_resource_provider.h" class QKeyEvent; class KisCanvas2; class KisSelectionOptions; class KoCanvasResourceProvider; class KRITAUI_EXPORT KisSelectionToolConfigWidgetHelper : public QObject { Q_OBJECT public: KisSelectionToolConfigWidgetHelper(const QString &windowTitle); void createOptionWidget(KisCanvas2 *canvas, const QString &toolId); KisSelectionOptions* optionWidget() const; SelectionMode selectionMode() const; SelectionAction selectionAction() const; bool antiAliasSelection() const; int action() const { return selectionAction(); } Q_SIGNALS: void selectionActionChanged(int newAction); public Q_SLOTS: void slotToolActivatedChanged(bool isActivated); void slotWidgetActionChanged(int action); void slotWidgetModeChanged(int mode); void slotWidgetAntiAliasChanged(bool value); void slotReplaceModeRequested(); void slotAddModeRequested(); void slotSubtractModeRequested(); void slotIntersectModeRequested(); + void slotSymmetricDifferenceModeRequested(); private: KisSelectionOptions* m_optionsWidget; QString m_windowTitle; SelectionMode m_selectionMode; SelectionAction m_selectionAction; bool m_antiAliasSelection = true; }; #endif /* __KIS_SELECTION_TOOL_CONFIG_WIDGET_HELPER_H */ diff --git a/libs/ui/tool/kis_selection_tool_helper.cpp b/libs/ui/tool/kis_selection_tool_helper.cpp index 109e9c4b10..2db9542a17 100644 --- a/libs/ui/tool/kis_selection_tool_helper.cpp +++ b/libs/ui/tool/kis_selection_tool_helper.cpp @@ -1,351 +1,354 @@ /* * Copyright (c) 2007 Sven Langkamp * * 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 "kis_selection_tool_helper.h" #include #include #include #include "kis_pixel_selection.h" #include "kis_shape_selection.h" #include "kis_image.h" #include "canvas/kis_canvas2.h" #include "KisViewManager.h" #include "kis_selection_manager.h" #include "kis_transaction.h" #include "commands/kis_selection_commands.h" #include "kis_shape_controller.h" #include #include "kis_processing_applicator.h" #include "commands_new/kis_transaction_based_command.h" #include "kis_gui_context_command.h" #include "kis_command_utils.h" #include "commands/kis_deselect_global_selection_command.h" #include "kis_algebra_2d.h" #include "kis_config.h" #include "kis_action_manager.h" #include "kis_action.h" #include KisSelectionToolHelper::KisSelectionToolHelper(KisCanvas2* canvas, const KUndo2MagicString& name) : m_canvas(canvas) , m_name(name) { m_image = m_canvas->viewManager()->image(); } KisSelectionToolHelper::~KisSelectionToolHelper() { } struct LazyInitGlobalSelection : public KisTransactionBasedCommand { LazyInitGlobalSelection(KisViewManager *view) : m_view(view) {} KisViewManager *m_view; KUndo2Command* paint() override { return !m_view->selection() ? new KisSetEmptyGlobalSelectionCommand(m_view->image()) : 0; } }; void KisSelectionToolHelper::selectPixelSelection(KisPixelSelectionSP selection, SelectionAction action) { KisViewManager* view = m_canvas->viewManager(); if (selection->selectedExactRect().isEmpty()) { m_canvas->viewManager()->selectionManager()->deselect(); return; } KisProcessingApplicator applicator(view->image(), 0 /* we need no automatic updates */, KisProcessingApplicator::SUPPORTS_WRAPAROUND_MODE, KisImageSignalVector() << ModifiedSignal, m_name); applicator.applyCommand(new LazyInitGlobalSelection(view)); struct ApplyToPixelSelection : public KisTransactionBasedCommand { ApplyToPixelSelection(KisViewManager *view, KisPixelSelectionSP selection, SelectionAction action) : m_view(view), m_selection(selection), m_action(action) {} KisViewManager *m_view; KisPixelSelectionSP m_selection; SelectionAction m_action; KUndo2Command* paint() override { KisPixelSelectionSP pixelSelection = m_view->selection()->pixelSelection(); KIS_ASSERT_RECOVER(pixelSelection) { return 0; } bool hasSelection = !pixelSelection->isEmpty(); 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); KUndo2Command *savedCommand = transaction.endAndTake(); pixelSelection->setDirty(dirtyRect); if (m_view->selection()->selectedExactRect().isEmpty()) { KisCommandUtils::CompositeCommand *cmd = new KisCommandUtils::CompositeCommand(); cmd->addCommand(savedCommand); cmd->addCommand(new KisDeselectGlobalSelectionCommand(m_view->image())); savedCommand = cmd; } return savedCommand; } }; applicator.applyCommand(new ApplyToPixelSelection(view, selection, action)); applicator.end(); } void KisSelectionToolHelper::addSelectionShape(KoShape* shape, SelectionAction action) { QList shapes; shapes.append(shape); addSelectionShapes(shapes, action); } void KisSelectionToolHelper::addSelectionShapes(QList< KoShape* > shapes, SelectionAction action) { KisViewManager* view = m_canvas->viewManager(); if (view->image()->wrapAroundModePermitted()) { view->showFloatingMessage( i18n("Shape selection does not fully " "support wraparound mode. Please " "use pixel selection instead"), KisIconUtils::loadIcon("selection-info")); } KisProcessingApplicator applicator(view->image(), 0 /* we need no automatic updates */, KisProcessingApplicator::NONE, KisImageSignalVector() << ModifiedSignal, m_name); applicator.applyCommand(new LazyInitGlobalSelection(view)); struct ClearPixelSelection : public KisTransactionBasedCommand { ClearPixelSelection(KisViewManager *view) : m_view(view) {} KisViewManager *m_view; KUndo2Command* paint() override { KisPixelSelectionSP pixelSelection = m_view->selection()->pixelSelection(); KIS_ASSERT_RECOVER(pixelSelection) { return 0; } KisSelectionTransaction transaction(pixelSelection); pixelSelection->clear(); return transaction.endAndTake(); } }; if (action == SELECTION_REPLACE || action == SELECTION_DEFAULT) { applicator.applyCommand(new ClearPixelSelection(view)); } struct AddSelectionShape : public KisTransactionBasedCommand { AddSelectionShape(KisViewManager *view, KoShape* shape, SelectionAction action) : m_view(view), m_shape(shape), m_action(action) {} KisViewManager *m_view; KoShape* m_shape; SelectionAction m_action; KUndo2Command* paint() override { KUndo2Command *resultCommand = 0; KisSelectionSP selection = m_view->selection(); if (selection) { KisShapeSelection * shapeSelection = static_cast(selection->shapeSelection()); if (shapeSelection) { QList existingShapes = shapeSelection->shapes(); if (existingShapes.size() == 1) { KoShape *currentShape = existingShapes.first(); QPainterPath path1 = currentShape->absoluteTransformation(0).map(currentShape->outline()); QPainterPath path2 = m_shape->absoluteTransformation(0).map(m_shape->outline()); QPainterPath path = path2; switch (m_action) { case SELECTION_DEFAULT: case SELECTION_REPLACE: path = path2; break; case SELECTION_INTERSECT: path = path1 & path2; break; case SELECTION_ADD: path = path1 | path2; break; case SELECTION_SUBTRACT: path = path1 - path2; break; + case SELECTION_SYMMETRICDIFFERENCE: + path = (path1 | path2) - (path1 & path2); + break; } KoShape *newShape = KoPathShape::createShapeFromPainterPath(path); newShape->setUserData(new KisShapeSelectionMarker); KUndo2Command *parentCommand = new KUndo2Command(); m_view->canvasBase()->shapeController()->removeShape(currentShape, parentCommand); m_view->canvasBase()->shapeController()->addShape(newShape, 0, parentCommand); resultCommand = parentCommand; } } } if (!resultCommand) { /** * Mark a shape that it belongs to a shape selection */ if(!m_shape->userData()) { m_shape->setUserData(new KisShapeSelectionMarker); } resultCommand = m_view->canvasBase()->shapeController()->addShape(m_shape, 0); } return resultCommand; } }; Q_FOREACH (KoShape* shape, shapes) { applicator.applyCommand( new KisGuiContextCommand(new AddSelectionShape(view, shape, action), view)); } applicator.end(); } bool KisSelectionToolHelper::canShortcutToDeselect(const QRect &rect, SelectionAction action) { return rect.isEmpty() && (action == SELECTION_INTERSECT || action == SELECTION_REPLACE); } bool KisSelectionToolHelper::canShortcutToNoop(const QRect &rect, SelectionAction action) { return rect.isEmpty() && action == SELECTION_ADD; } bool KisSelectionToolHelper::tryDeselectCurrentSelection(const QRectF selectionViewRect, SelectionAction action) { 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())); result = true; } return result; } QMenu* KisSelectionToolHelper::getSelectionContextMenu(KisCanvas2* canvas) { QMenu *m_contextMenu = new QMenu(); KisActionManager * actionMan = canvas->viewManager()->actionManager(); m_contextMenu->addAction(actionMan->actionByName("deselect")); m_contextMenu->addAction(actionMan->actionByName("invert")); m_contextMenu->addAction(actionMan->actionByName("select_all")); m_contextMenu->addSeparator(); m_contextMenu->addAction(actionMan->actionByName("cut_selection_to_new_layer")); m_contextMenu->addAction(actionMan->actionByName("copy_selection_to_new_layer")); m_contextMenu->addSeparator(); KisSelectionSP selection = canvas->viewManager()->selection(); if (selection && canvas->viewManager()->selectionEditable()) { m_contextMenu->addAction(actionMan->actionByName("edit_selection")); if (!selection->hasShapeSelection()) { m_contextMenu->addAction(actionMan->actionByName("convert_to_vector_selection")); } else { m_contextMenu->addAction(actionMan->actionByName("convert_to_raster_selection")); } QMenu *transformMenu = m_contextMenu->addMenu(i18n("Transform")); transformMenu->addAction(actionMan->actionByName("selectionscale")); transformMenu->addAction(actionMan->actionByName("growselection")); transformMenu->addAction(actionMan->actionByName("shrinkselection")); transformMenu->addAction(actionMan->actionByName("borderselection")); transformMenu->addAction(actionMan->actionByName("smoothselection")); transformMenu->addAction(actionMan->actionByName("featherselection")); transformMenu->addAction(actionMan->actionByName("stroke_selection")); m_contextMenu->addSeparator(); } m_contextMenu->addAction(actionMan->actionByName("resizeimagetoselection")); m_contextMenu->addSeparator(); m_contextMenu->addAction(actionMan->actionByName("toggle_display_selection")); m_contextMenu->addAction(actionMan->actionByName("show-global-selection-mask")); return m_contextMenu; } SelectionMode KisSelectionToolHelper::tryOverrideSelectionMode(KisSelectionSP activeSelection, SelectionMode currentMode, SelectionAction currentAction) const { if (currentAction != SELECTION_DEFAULT && currentAction != SELECTION_REPLACE) { if (activeSelection) { currentMode = activeSelection->hasShapeSelection() ? SHAPE_PROTECTION : PIXEL_SELECTION; } } return currentMode; } diff --git a/libs/ui/widgets/kis_selection_options.cc b/libs/ui/widgets/kis_selection_options.cc index beda02c1f2..6d8ac5fb9b 100644 --- a/libs/ui/widgets/kis_selection_options.cc +++ b/libs/ui/widgets/kis_selection_options.cc @@ -1,169 +1,181 @@ /* * Copyright (c) 2005 Boudewijn Rempt * * 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 "kis_selection_options.h" #include #include #include #include #include #include #include #include "kis_types.h" #include "kis_layer.h" #include "kis_image.h" #include "kis_selection.h" #include "kis_paint_device.h" #include "canvas/kis_canvas2.h" #include "KisViewManager.h" KisSelectionOptions::KisSelectionOptions(KisCanvas2 * /*canvas*/) { m_page = new WdgSelectionOptions(this); Q_CHECK_PTR(m_page); QVBoxLayout * l = new QVBoxLayout(this); l->addWidget(m_page); l->addSpacerItem(new QSpacerItem(0,0, QSizePolicy::Preferred, QSizePolicy::Expanding)); l->setContentsMargins(0,0,0,0); m_mode = new QButtonGroup(this); m_mode->addButton(m_page->pixel, PIXEL_SELECTION); m_mode->addButton(m_page->shape, SHAPE_PROTECTION); m_action = new QButtonGroup(this); m_action->addButton(m_page->add, SELECTION_ADD); 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); m_page->pixel->setIcon(KisIconUtils::loadIcon("select_pixel")); m_page->shape->setIcon(KisIconUtils::loadIcon("select_shape")); m_page->add->setGroupPosition(KoGroupButton::GroupCenter); 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))); connect(m_mode, SIGNAL(buttonClicked(int)), this, SLOT(hideActionsForSelectionMode(int))); connect(m_page->chkAntiAliasing, SIGNAL(toggled(bool)), this, SIGNAL(antiAliasSelectionChanged(bool))); } KisSelectionOptions::~KisSelectionOptions() { } int KisSelectionOptions::action() { return m_action->checkedId(); } void KisSelectionOptions::setAction(int action) { QAbstractButton* button = m_action->button(action); KIS_SAFE_ASSERT_RECOVER_RETURN(button); button->setChecked(true); } void KisSelectionOptions::setMode(int mode) { QAbstractButton* button = m_mode->button(mode); KIS_SAFE_ASSERT_RECOVER_RETURN(button); button->setChecked(true); hideActionsForSelectionMode(mode); } void KisSelectionOptions::setAntiAliasSelection(bool value) { m_page->chkAntiAliasing->setChecked(value); } void KisSelectionOptions::updateActionButtonToolTip(int action, const QKeySequence &shortcut) { const QString shortcutString = shortcut.toString(QKeySequence::NativeText); QString toolTipText; switch ((SelectionAction)action) { case SELECTION_DEFAULT: case SELECTION_REPLACE: toolTipText = shortcutString.isEmpty() ? i18nc("@info:tooltip", "Replace") : i18nc("@info:tooltip", "Replace (%1)", shortcutString); m_action->button(SELECTION_REPLACE)->setToolTip(toolTipText); break; case SELECTION_ADD: toolTipText = shortcutString.isEmpty() ? i18nc("@info:tooltip", "Add") : i18nc("@info:tooltip", "Add (%1)", shortcutString); m_action->button(SELECTION_ADD)->setToolTip(toolTipText); break; case SELECTION_SUBTRACT: toolTipText = shortcutString.isEmpty() ? i18nc("@info:tooltip", "Subtract") : i18nc("@info:tooltip", "Subtract (%1)", shortcutString); m_action->button(SELECTION_SUBTRACT)->setToolTip(toolTipText); break; case SELECTION_INTERSECT: toolTipText = shortcutString.isEmpty() ? i18nc("@info:tooltip", "Intersect") : i18nc("@info:tooltip", "Intersect (%1)", shortcutString); 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; } } //hide action buttons and antialiasing, if shape selection is active (actions currently don't work on shape selection) void KisSelectionOptions::hideActionsForSelectionMode(int mode) { const bool isPixelSelection = (mode == (int)PIXEL_SELECTION); m_page->chkAntiAliasing->setVisible(isPixelSelection); } bool KisSelectionOptions::antiAliasSelection() { return m_page->chkAntiAliasing->isChecked(); } void KisSelectionOptions::disableAntiAliasSelectionOption() { m_page->chkAntiAliasing->hide(); disconnect(m_page->pixel, SIGNAL(clicked()), m_page->chkAntiAliasing, SLOT(show())); } void KisSelectionOptions::disableSelectionModeOption() { m_page->lblMode->hide(); m_page->pixel->hide(); m_page->shape->hide(); } diff --git a/plugins/extensions/pykrita/sip/krita/Selection.sip b/plugins/extensions/pykrita/sip/krita/Selection.sip index c429b8e61e..7ea9cbca44 100644 --- a/plugins/extensions/pykrita/sip/krita/Selection.sip +++ b/plugins/extensions/pykrita/sip/krita/Selection.sip @@ -1,41 +1,42 @@ class Selection : QObject { %TypeHeaderCode #include "Selection.h" %End Selection(const Selection & __0); public: Selection(QObject* parent /TransferThis/ = 0); virtual ~Selection(); bool operator==(const Selection &other) const; bool operator!=(const Selection &other) const; public Q_SLOTS: int width() const; int height() const; int x() const; int y() const; void move(int x, int y); void clear(); void contract(int value); void copy(Node *node); void cut(Node *node); void paste(Node *destination, int x, int y); void erode(); void dilate(); void border(int xRadius, int yRadius); void feather(int radius); void grow(int xradius, int yradius); void shrink(int xRadius, int yRadius, bool edgeLock); void smooth(); void invert(); void resize(int w, int h); void select(int x, int y, int w, int h, int value); void selectAll(Node* node, int value); void replace(Selection *selection); 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 index 419383b111..8896faffc5 100644 --- a/plugins/tools/selectiontools/kis_selection_modifier_mapper.cc +++ b/plugins/tools/selectiontools/kis_selection_modifier_mapper.cc @@ -1,124 +1,130 @@ /* This file is part of the KDE project * Copyright (C) 2016 Michael Abrahams * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * This is a basic template to create selection tools from basic path based drawing tools. * The template overrides the ability to execute alternate actions correctly. * Modifier keys are overridden with the following behavior: * * Shift: add to selection * Alt: subtract from selection * Shift+Alt: intersect current selection * Ctrl: replace selection * * Certain tools also use modifier keys to alter their behavior, e.g. forcing square proportions with the rectangle tool. * The template enables the following rules for forwarding keys: * 1) Any modifier keys held *when the tool is first activated* will determine the new selection method. * 2) If the underlying tool *does not take modifier keys*, pressing modifier keys in the middle of a stroke will change the selection method. This applies to the lasso tool and polygon tool. * 3) If the underlying tool *takes modifier keys,* they will always be forwarded to the underlying tool, and it is not possible to change the selection method in the middle of a stroke. */ #include "kis_selection.h" #include "kis_selection_modifier_mapper.h" #include "kis_config_notifier.h" #include "kis_config.h" Q_GLOBAL_STATIC(KisSelectionModifierMapper, s_instance); // This numerically serializes modifier flags... let's keep it around for later. #if 0 #include QString QMOD_BINARY(Qt::KeyboardModifiers m) { return QString(std::bitset(m).to_string().c_str()); }; #endif struct Q_DECL_HIDDEN KisSelectionModifierMapper::Private { SelectionAction map(Qt::KeyboardModifiers m); void slotConfigChanged(); Qt::KeyboardModifiers replaceModifiers; Qt::KeyboardModifiers intersectModifiers; Qt::KeyboardModifiers addModifiers; Qt::KeyboardModifiers subtractModifiers; + Qt::KeyboardModifiers symmetricdifferenceModifiers; }; KisSelectionModifierMapper::KisSelectionModifierMapper() : m_d(new Private) { connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); slotConfigChanged(); } KisSelectionModifierMapper::~KisSelectionModifierMapper() { } KisSelectionModifierMapper *KisSelectionModifierMapper::instance() { return s_instance; } void KisSelectionModifierMapper::slotConfigChanged() { m_d->slotConfigChanged(); } void KisSelectionModifierMapper::Private::slotConfigChanged() { KisConfig cfg(true); if (!cfg.switchSelectionCtrlAlt()) { 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; } SelectionAction KisSelectionModifierMapper::map(Qt::KeyboardModifiers m) { return s_instance->m_d->map(m); } SelectionAction KisSelectionModifierMapper::Private::map(Qt::KeyboardModifiers m) { SelectionAction newAction = SELECTION_DEFAULT; if (m == replaceModifiers) { newAction = SELECTION_REPLACE; } else if (m == intersectModifiers) { newAction = SELECTION_INTERSECT; } else if (m == addModifiers) { newAction = SELECTION_ADD; } else if (m == subtractModifiers) { newAction = SELECTION_SUBTRACT; + } else if (m == symmetricdifferenceModifiers) { + newAction = SELECTION_SYMMETRICDIFFERENCE; } + return newAction; }