diff --git a/benchmarks/kis_floodfill_benchmark.cpp b/benchmarks/kis_floodfill_benchmark.cpp index f0a2f91b85..e2fc4cf2ba 100644 --- a/benchmarks/kis_floodfill_benchmark.cpp +++ b/benchmarks/kis_floodfill_benchmark.cpp @@ -1,112 +1,189 @@ /* * Copyright (c) 2010 Lukáš Tvrdý * * 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 #include #include "kis_benchmark_values.h" #include "kis_random_accessor_ng.h" #include #include #include #include #include #include "kis_floodfill_benchmark.h" #include void KisFloodFillBenchmark::initTestCase() { m_colorSpace = KoColorSpaceRegistry::instance()->rgb8(); - m_device = new KisPaintDevice(m_colorSpace); + m_deviceStandardFloodFill = new KisPaintDevice(m_colorSpace); m_color = KoColor(m_colorSpace); QColor qcolor(Qt::red); srand(31524744); int tilew = 38; int tileh = 56; m_color.fromQColor(QColor(0,0,0,0)); // default pixel - m_device->fill( 0,0,GMP_IMAGE_WIDTH, GMP_IMAGE_HEIGHT,m_color.data() ); + m_deviceStandardFloodFill->fill( 0,0,GMP_IMAGE_WIDTH, GMP_IMAGE_HEIGHT,m_color.data() ); // fill the image with red ellipses (like some random dabs) m_color.fromQColor(Qt::red); - KisPainter painter(m_device); + KisPainter painter(m_deviceStandardFloodFill); painter.setFillStyle(KisPainter::FillStyleForegroundColor); painter.setPaintColor(m_color); int x = 0; int y = 0; for (int i = 0; i < 100;i++){ x = rand() % GMP_IMAGE_WIDTH; y = rand() % GMP_IMAGE_HEIGHT; // plus 10 so that we don't fill the ellipse painter.paintEllipse(x+ 10, y+ 10, tilew, tileh); } + // copy to other tests + m_deviceWithoutSelectionAsBoundary = new KisPaintDevice(m_colorSpace); + m_deviceWithSelectionAsBoundary = new KisPaintDevice(m_colorSpace); + + + KisPainter::copyAreaOptimized(QPoint(), m_deviceStandardFloodFill, + m_deviceWithoutSelectionAsBoundary, m_deviceWithoutSelectionAsBoundary->exactBounds()); + KisPainter::copyAreaOptimized(QPoint(), m_deviceStandardFloodFill, + m_deviceWithSelectionAsBoundary, m_deviceWithSelectionAsBoundary->exactBounds()); + + //m_deviceWithoutSelectionAsBoundary = m_deviceStandardFloodFill-> + + const KoColorSpace* alphacs = KoColorSpaceRegistry::instance()->alpha8(); + KoColor defaultSelected = KoColor(alphacs); + defaultSelected.fromQColor(QColor(255, 255, 255)); + m_existingSelection = new KisPaintDevice(alphacs); + m_existingSelection->fill(0, 0, GMP_IMAGE_WIDTH, GMP_IMAGE_HEIGHT, defaultSelected.data()); } void KisFloodFillBenchmark::benchmarkFlood() { KoColor fg(m_colorSpace); KoColor bg(m_colorSpace); fg.fromQColor(Qt::blue); bg.fromQColor(Qt::black); QBENCHMARK { - KisFillPainter fillPainter(m_device); + KisFillPainter fillPainter(m_deviceStandardFloodFill); //setupPainter(&fillPainter); fillPainter.setPaintColor( fg ); fillPainter.setBackgroundColor( bg ); fillPainter.beginTransaction(kundo2_noi18n("Flood Fill")); //fillPainter.setProgress(updater->startSubtask()); fillPainter.setOpacity(OPACITY_OPAQUE_U8); // default fillPainter.setFillThreshold(15); fillPainter.setCompositeOp(COMPOSITE_OVER); fillPainter.setCareForSelection(true); fillPainter.setWidth(GMP_IMAGE_WIDTH); fillPainter.setHeight(GMP_IMAGE_HEIGHT); // fill twice - fillPainter.fillColor(1, 1, m_device); + fillPainter.fillColor(1, 1, m_deviceStandardFloodFill); fillPainter.deleteTransaction(); } // uncomment this to see the output //QImage out = m_device->convertToQImage(m_colorSpace->profile(),0,0,GMP_IMAGE_WIDTH,GMP_IMAGE_HEIGHT); //out.save("fill_output.png"); } +void KisFloodFillBenchmark::benchmarkFloodWithoutSelectionAsBoundary() +{ + KoColor fg(m_colorSpace); + KoColor bg(m_colorSpace); + fg.fromQColor(Qt::blue); + bg.fromQColor(Qt::black); + + QBENCHMARK + { + KisFillPainter fillPainter(m_deviceWithoutSelectionAsBoundary); + fillPainter.setPaintColor( fg ); + fillPainter.setBackgroundColor( bg ); + + fillPainter.beginTransaction(kundo2_noi18n("Flood Fill")); + + fillPainter.setOpacity(OPACITY_OPAQUE_U8); + // default + fillPainter.setFillThreshold(15); + fillPainter.setCompositeOp(COMPOSITE_OVER); + fillPainter.setCareForSelection(true); + fillPainter.setWidth(GMP_IMAGE_WIDTH); + fillPainter.setHeight(GMP_IMAGE_HEIGHT); + fillPainter.setUseSelectionAsBoundary(false); + + fillPainter.createFloodSelection(1, 1, m_deviceWithoutSelectionAsBoundary, m_existingSelection); + + fillPainter.deleteTransaction(); + } +} + +void KisFloodFillBenchmark::benchmarkFloodWithSelectionAsBoundary() +{ + KoColor fg(m_colorSpace); + KoColor bg(m_colorSpace); + fg.fromQColor(Qt::blue); + bg.fromQColor(Qt::black); + + QBENCHMARK + { + KisFillPainter fillPainter(m_deviceWithSelectionAsBoundary); + fillPainter.setPaintColor( fg ); + fillPainter.setBackgroundColor( bg ); + + fillPainter.beginTransaction(kundo2_noi18n("Flood Fill")); + + fillPainter.setOpacity(OPACITY_OPAQUE_U8); + // default + fillPainter.setFillThreshold(15); + fillPainter.setCompositeOp(COMPOSITE_OVER); + fillPainter.setCareForSelection(true); + fillPainter.setWidth(GMP_IMAGE_WIDTH); + fillPainter.setHeight(GMP_IMAGE_HEIGHT); + fillPainter.setUseSelectionAsBoundary(true); + + fillPainter.createFloodSelection(1, 1, m_deviceWithSelectionAsBoundary, m_existingSelection); + + fillPainter.deleteTransaction(); + } +} + void KisFloodFillBenchmark::cleanupTestCase() { } QTEST_MAIN(KisFloodFillBenchmark) diff --git a/benchmarks/kis_floodfill_benchmark.h b/benchmarks/kis_floodfill_benchmark.h index 522a41a38e..7b804eb229 100644 --- a/benchmarks/kis_floodfill_benchmark.h +++ b/benchmarks/kis_floodfill_benchmark.h @@ -1,50 +1,56 @@ /* * Copyright (c) 2010 Lukáš Tvrdý * * 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_FLOODFILL_BENCHMARK_H #define KIS_FLOODFILL_BENCHMARK_H #include #include #include #include class KoColor; class KisFloodFillBenchmark : public QObject { Q_OBJECT private: const KoColorSpace * m_colorSpace; KoColor m_color; - KisPaintDeviceSP m_device; + KisPaintDeviceSP m_deviceStandardFloodFill; + KisPaintDeviceSP m_deviceWithSelectionAsBoundary; + KisPaintDeviceSP m_deviceWithoutSelectionAsBoundary; + KisPaintDeviceSP m_existingSelection; int m_startX; int m_startY; private Q_SLOTS: void initTestCase(); void cleanupTestCase(); void benchmarkFlood(); + void benchmarkFloodWithoutSelectionAsBoundary(); + void benchmarkFloodWithSelectionAsBoundary(); + }; #endif diff --git a/libs/image/floodfill/kis_scanline_fill.cpp b/libs/image/floodfill/kis_scanline_fill.cpp index a65204ccca..f9e9c109ad 100644 --- a/libs/image/floodfill/kis_scanline_fill.cpp +++ b/libs/image/floodfill/kis_scanline_fill.cpp @@ -1,726 +1,842 @@ /* * Copyright (c) 2014 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_scanline_fill.h" #include #include #include #include #include #include "kis_image.h" #include "kis_fill_interval_map.h" #include "kis_pixel_selection.h" #include "kis_random_accessor_ng.h" #include "kis_fill_sanity_checks.h" template class CopyToSelection : public BaseClass { public: typedef KisRandomConstAccessorSP SourceAccessorType; SourceAccessorType createSourceDeviceAccessor(KisPaintDeviceSP device) { return device->createRandomConstAccessorNG(); } public: void setDestinationSelection(KisPaintDeviceSP pixelSelection) { m_pixelSelection = pixelSelection; m_it = m_pixelSelection->createRandomAccessorNG(); } ALWAYS_INLINE void fillPixel(quint8 *dstPtr, quint8 opacity, int x, int y) { Q_UNUSED(dstPtr); m_it->moveTo(x, y); *m_it->rawData() = opacity; } private: KisPaintDeviceSP m_pixelSelection; KisRandomAccessorSP m_it; }; template class FillWithColor : public BaseClass { public: typedef KisRandomAccessorSP SourceAccessorType; SourceAccessorType createSourceDeviceAccessor(KisPaintDeviceSP device) { return device->createRandomAccessorNG(); } public: FillWithColor() : m_pixelSize(0) {} void setFillColor(const KoColor &sourceColor) { m_sourceColor = sourceColor; m_pixelSize = sourceColor.colorSpace()->pixelSize(); m_data = m_sourceColor.data(); } ALWAYS_INLINE void fillPixel(quint8 *dstPtr, quint8 opacity, int x, int y) { Q_UNUSED(x); Q_UNUSED(y); if (opacity == MAX_SELECTED) { memcpy(dstPtr, m_data, m_pixelSize); } } private: KoColor m_sourceColor; const quint8 *m_data; int m_pixelSize; }; template class FillWithColorExternal : public BaseClass { public: typedef KisRandomConstAccessorSP SourceAccessorType; SourceAccessorType createSourceDeviceAccessor(KisPaintDeviceSP device) { return device->createRandomConstAccessorNG(); } public: void setDestinationDevice(KisPaintDeviceSP device) { m_externalDevice = device; m_it = m_externalDevice->createRandomAccessorNG(); } void setFillColor(const KoColor &sourceColor) { m_sourceColor = sourceColor; m_pixelSize = sourceColor.colorSpace()->pixelSize(); m_data = m_sourceColor.data(); } ALWAYS_INLINE void fillPixel(quint8 *dstPtr, quint8 opacity, int x, int y) { Q_UNUSED(dstPtr); m_it->moveTo(x, y); if (opacity == MAX_SELECTED) { memcpy(m_it->rawData(), m_data, m_pixelSize); } } private: KisPaintDeviceSP m_externalDevice; KisRandomAccessorSP m_it; KoColor m_sourceColor; const quint8 *m_data {0}; int m_pixelSize {0}; }; class DifferencePolicySlow { public: ALWAYS_INLINE void initDifferences(KisPaintDeviceSP device, const KoColor &srcPixel, int threshold) { m_colorSpace = device->colorSpace(); m_srcPixel = srcPixel; m_srcPixelPtr = m_srcPixel.data(); m_threshold = threshold; } ALWAYS_INLINE quint8 calculateDifference(quint8* pixelPtr) { if (m_threshold == 1) { if (memcmp(m_srcPixelPtr, pixelPtr, m_colorSpace->pixelSize()) == 0) { return 0; } return quint8_MAX; } else { return m_colorSpace->differenceA(m_srcPixelPtr, pixelPtr); } } private: const KoColorSpace *m_colorSpace; KoColor m_srcPixel; const quint8 *m_srcPixelPtr; int m_threshold; }; template class DifferencePolicyOptimized { typedef SrcPixelType HashKeyType; typedef QHash HashType; public: ALWAYS_INLINE void initDifferences(KisPaintDeviceSP device, const KoColor &srcPixel, int threshold) { m_colorSpace = device->colorSpace(); m_srcPixel = srcPixel; m_srcPixelPtr = m_srcPixel.data(); m_threshold = threshold; } ALWAYS_INLINE quint8 calculateDifference(quint8* pixelPtr) { HashKeyType key = *reinterpret_cast(pixelPtr); quint8 result; typename HashType::iterator it = m_differences.find(key); if (it != m_differences.end()) { result = *it; } else { if (m_threshold == 1) { if (memcmp(m_srcPixelPtr, pixelPtr, m_colorSpace->pixelSize()) == 0) { result = 0; } else { result = quint8_MAX; } } else { result = m_colorSpace->differenceA(m_srcPixelPtr, pixelPtr); } m_differences.insert(key, result); } return result; } private: HashType m_differences; const KoColorSpace *m_colorSpace; KoColor m_srcPixel; const quint8 *m_srcPixelPtr; int m_threshold; }; +class SelectednessPolicyOptimized +{ + typedef quint8 HashKeyType; + typedef QHash HashType; + + KisRandomConstAccessorSP m_selectionIt; + +public: + + ALWAYS_INLINE void initSelectedness(KisPaintDeviceSP device, int threshold) { + m_colorSpace = device->colorSpace(); + m_threshold = threshold; + m_selectionIt = device->createRandomConstAccessorNG(); + } + + ALWAYS_INLINE quint8 calculateSelectedness(int x, int y) { + m_selectionIt->moveTo(x, y); + const quint8* pixelPtr = m_selectionIt->rawDataConst(); + return *pixelPtr; + } + +private: + HashType m_selectedness; + + const KoColorSpace *m_colorSpace; + int m_threshold; +}; + + template class PixelFiller> class SelectionPolicy : public PixelFiller { public: typename PixelFiller::SourceAccessorType m_srcIt; public: SelectionPolicy(KisPaintDeviceSP device, const KoColor &srcPixel, int threshold) : m_threshold(threshold) { this->initDifferences(device, srcPixel, threshold); m_srcIt = this->createSourceDeviceAccessor(device); } - ALWAYS_INLINE quint8 calculateOpacity(quint8* pixelPtr) { + ALWAYS_INLINE quint8 calculateOpacity(quint8* pixelPtr, int x, int y) { quint8 diff = this->calculateDifference(pixelPtr); + Q_UNUSED(x); + Q_UNUSED(y); if (!useSmoothSelection) { return diff <= m_threshold ? MAX_SELECTED : MIN_SELECTED; } else { quint8 selectionValue = qMax(0, m_threshold - diff); quint8 result = MIN_SELECTED; if (selectionValue > 0) { qreal selectionNorm = qreal(selectionValue) / m_threshold; result = MAX_SELECTED * selectionNorm; } + return result; + } + } + +private: + int m_threshold; +}; + + + +template class PixelFiller, + class SelectednessCheckPolicy> +class SelectionPolicyExtended : public SelectionPolicy + , public SelectednessCheckPolicy +{ + +public: + + SelectionPolicyExtended(KisPaintDeviceSP mainDevice, KisPaintDeviceSP selectionDevice, const KoColor &srcPixel, int threshold) + : SelectionPolicy(mainDevice, srcPixel, threshold) + { + m_threshold = threshold; + this->initSelectedness(selectionDevice, threshold); + } + +public: + ALWAYS_INLINE quint8 calculateOpacity(quint8* pixelPtr, int x, int y) { + quint8 diff = this->calculateDifference(pixelPtr); + quint8 selectedness = this->calculateSelectedness(x, y); + + if (!useSmoothSelection) { + return (diff <= m_threshold && selectedness > 0) ? MAX_SELECTED : MIN_SELECTED; + } else { + quint8 selectionValue = qMax(0, m_threshold - diff); + quint8 result = MIN_SELECTED; + + if (selectionValue > 0 && selectedness > 0) { + qreal selectionNorm = qreal(selectionValue) / m_threshold; + result = MAX_SELECTED * selectionNorm; + } return result; } } private: int m_threshold; + }; + + + + + class IsNonNullPolicySlow { public: ALWAYS_INLINE void initDifferences(KisPaintDeviceSP device, const KoColor &srcPixel, int /*threshold*/) { Q_UNUSED(srcPixel); m_pixelSize = device->pixelSize(); m_testPixel.resize(m_pixelSize); } ALWAYS_INLINE quint8 calculateDifference(quint8* pixelPtr) { if (memcmp(m_testPixel.data(), pixelPtr, m_pixelSize) == 0) { return 0; } return quint8_MAX; } private: int m_pixelSize {0}; QByteArray m_testPixel; }; template class IsNonNullPolicyOptimized { public: ALWAYS_INLINE void initDifferences(KisPaintDeviceSP device, const KoColor &srcPixel, int /*threshold*/) { Q_UNUSED(device); Q_UNUSED(srcPixel); } ALWAYS_INLINE quint8 calculateDifference(quint8* pixelPtr) { SrcPixelType *pixel = reinterpret_cast(pixelPtr); return *pixel == 0; } }; class GroupSplitPolicy { public: typedef KisRandomConstAccessorSP SourceAccessorType; SourceAccessorType m_srcIt; public: GroupSplitPolicy(KisPaintDeviceSP scribbleDevice, KisPaintDeviceSP groupMapDevice, qint32 groupIndex, quint8 referenceValue, int threshold) : m_threshold(threshold), m_groupIndex(groupIndex), m_referenceValue(referenceValue) { KIS_SAFE_ASSERT_RECOVER_NOOP(m_groupIndex > 0); m_srcIt = scribbleDevice->createRandomConstAccessorNG(); m_groupMapIt = groupMapDevice->createRandomAccessorNG(); } - ALWAYS_INLINE quint8 calculateOpacity(quint8* pixelPtr) { + ALWAYS_INLINE quint8 calculateOpacity(quint8* pixelPtr, int x, int y) { // TODO: either threshold should always be null, or there should be a special // case for *pixelPtr == 0, which is different from all the other groups, // whatever the threshold is + Q_UNUSED(x); + Q_UNUSED(y); int diff = qAbs(int(*pixelPtr) - m_referenceValue); return diff <= m_threshold ? MAX_SELECTED : MIN_SELECTED; } ALWAYS_INLINE void fillPixel(quint8 *dstPtr, quint8 opacity, int x, int y) { Q_UNUSED(opacity); // erase the scribble *dstPtr = 0; // write group index into the map m_groupMapIt->moveTo(x, y); qint32 *groupMapPtr = reinterpret_cast(m_groupMapIt->rawData()); if (*groupMapPtr != 0) { dbgImage << ppVar(*groupMapPtr) << ppVar(m_groupIndex); } KIS_SAFE_ASSERT_RECOVER_NOOP(*groupMapPtr == 0); *groupMapPtr = m_groupIndex; } private: int m_threshold; qint32 m_groupIndex; quint8 m_referenceValue; KisRandomAccessorSP m_groupMapIt; }; struct Q_DECL_HIDDEN KisScanlineFill::Private { KisPaintDeviceSP device; QPoint startPoint; QRect boundingRect; int threshold; int rowIncrement; KisFillIntervalMap backwardMap; QStack forwardStack; inline void swapDirection() { rowIncrement *= -1; KIS_SAFE_ASSERT_RECOVER_NOOP(forwardStack.isEmpty() && "FATAL: the forward stack must be empty " "on a direction swap"); forwardStack = QStack(backwardMap.fetchAllIntervals(rowIncrement)); backwardMap.clear(); } }; KisScanlineFill::KisScanlineFill(KisPaintDeviceSP device, const QPoint &startPoint, const QRect &boundingRect) : m_d(new Private) { m_d->device = device; m_d->startPoint = startPoint; m_d->boundingRect = boundingRect; m_d->rowIncrement = 1; m_d->threshold = 0; } KisScanlineFill::~KisScanlineFill() { } void KisScanlineFill::setThreshold(int threshold) { m_d->threshold = threshold; } template void KisScanlineFill::extendedPass(KisFillInterval *currentInterval, int srcRow, bool extendRight, T &pixelPolicy) { int x; int endX; int columnIncrement; int *intervalBorder; int *backwardIntervalBorder; KisFillInterval backwardInterval(currentInterval->start, currentInterval->end, srcRow); if (extendRight) { x = currentInterval->end; endX = m_d->boundingRect.right(); if (x >= endX) return; columnIncrement = 1; intervalBorder = ¤tInterval->end; backwardInterval.start = currentInterval->end + 1; backwardIntervalBorder = &backwardInterval.end; } else { x = currentInterval->start; endX = m_d->boundingRect.left(); if (x <= endX) return; columnIncrement = -1; intervalBorder = ¤tInterval->start; backwardInterval.end = currentInterval->start - 1; backwardIntervalBorder = &backwardInterval.start; } do { x += columnIncrement; pixelPolicy.m_srcIt->moveTo(x, srcRow); quint8 *pixelPtr = const_cast(pixelPolicy.m_srcIt->rawDataConst()); // TODO: avoid doing const_cast - quint8 opacity = pixelPolicy.calculateOpacity(pixelPtr); + quint8 opacity = pixelPolicy.calculateOpacity(pixelPtr, x, srcRow); if (opacity) { *intervalBorder = x; *backwardIntervalBorder = x; pixelPolicy.fillPixel(pixelPtr, opacity, x, srcRow); } else { break; } } while (x != endX); if (backwardInterval.isValid()) { m_d->backwardMap.insertInterval(backwardInterval); } } template void KisScanlineFill::processLine(KisFillInterval interval, const int rowIncrement, T &pixelPolicy) { m_d->backwardMap.cropInterval(&interval); if (!interval.isValid()) return; int firstX = interval.start; int lastX = interval.end; int x = firstX; int row = interval.row; int nextRow = row + rowIncrement; KisFillInterval currentForwardInterval; int numPixelsLeft = 0; quint8 *dataPtr = 0; const int pixelSize = m_d->device->pixelSize(); while(x <= lastX) { // a bit of optimzation for not calling slow random accessor // methods too often if (numPixelsLeft <= 0) { pixelPolicy.m_srcIt->moveTo(x, row); numPixelsLeft = pixelPolicy.m_srcIt->numContiguousColumns(x) - 1; dataPtr = const_cast(pixelPolicy.m_srcIt->rawDataConst()); } else { numPixelsLeft--; dataPtr += pixelSize; } quint8 *pixelPtr = dataPtr; - quint8 opacity = pixelPolicy.calculateOpacity(pixelPtr); + quint8 opacity = pixelPolicy.calculateOpacity(pixelPtr, x, row); if (opacity) { if (!currentForwardInterval.isValid()) { currentForwardInterval.start = x; currentForwardInterval.end = x; currentForwardInterval.row = nextRow; } else { currentForwardInterval.end = x; } pixelPolicy.fillPixel(pixelPtr, opacity, x, row); if (x == firstX) { extendedPass(¤tForwardInterval, row, false, pixelPolicy); } if (x == lastX) { extendedPass(¤tForwardInterval, row, true, pixelPolicy); } } else { if (currentForwardInterval.isValid()) { m_d->forwardStack.push(currentForwardInterval); currentForwardInterval.invalidate(); } } x++; } if (currentForwardInterval.isValid()) { m_d->forwardStack.push(currentForwardInterval); } } template void KisScanlineFill::runImpl(T &pixelPolicy) { KIS_ASSERT_RECOVER_RETURN(m_d->forwardStack.isEmpty()); KisFillInterval startInterval(m_d->startPoint.x(), m_d->startPoint.x(), m_d->startPoint.y()); m_d->forwardStack.push(startInterval); /** * In the end of the first pass we should add an interval * containing the starting pixel, but directed into the opposite * direction. We cannot do it in the very beginning because the * intervals are offset by 1 pixel during every swap operation. */ bool firstPass = true; while (!m_d->forwardStack.isEmpty()) { while (!m_d->forwardStack.isEmpty()) { KisFillInterval interval = m_d->forwardStack.pop(); if (interval.row > m_d->boundingRect.bottom() || interval.row < m_d->boundingRect.top()) { continue; } processLine(interval, m_d->rowIncrement, pixelPolicy); } m_d->swapDirection(); if (firstPass) { startInterval.row--; m_d->forwardStack.push(startInterval); firstPass = false; } } } void KisScanlineFill::fillColor(const KoColor &originalFillColor) { KoColor srcColor(m_d->device->pixel(m_d->startPoint)); KoColor fillColor(originalFillColor); fillColor.convertTo(m_d->device->colorSpace()); const int pixelSize = m_d->device->pixelSize(); if (pixelSize == 1) { SelectionPolicy, FillWithColor> policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(fillColor); runImpl(policy); } else if (pixelSize == 2) { SelectionPolicy, FillWithColor> policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(fillColor); runImpl(policy); } else if (pixelSize == 4) { SelectionPolicy, FillWithColor> policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(fillColor); runImpl(policy); } else if (pixelSize == 8) { SelectionPolicy, FillWithColor> policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(fillColor); runImpl(policy); } else { SelectionPolicy policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(fillColor); runImpl(policy); } } void KisScanlineFill::fillColor(const KoColor &originalFillColor, KisPaintDeviceSP externalDevice) { KoColor srcColor(m_d->device->pixel(m_d->startPoint)); KoColor fillColor(originalFillColor); fillColor.convertTo(m_d->device->colorSpace()); const int pixelSize = m_d->device->pixelSize(); if (pixelSize == 1) { SelectionPolicy, FillWithColorExternal> policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationDevice(externalDevice); policy.setFillColor(fillColor); runImpl(policy); } else if (pixelSize == 2) { SelectionPolicy, FillWithColorExternal> policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationDevice(externalDevice); policy.setFillColor(fillColor); runImpl(policy); } else if (pixelSize == 4) { SelectionPolicy, FillWithColorExternal> policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationDevice(externalDevice); policy.setFillColor(fillColor); runImpl(policy); } else if (pixelSize == 8) { SelectionPolicy, FillWithColorExternal> policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationDevice(externalDevice); policy.setFillColor(fillColor); runImpl(policy); } else { SelectionPolicy policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationDevice(externalDevice); policy.setFillColor(fillColor); runImpl(policy); } } +void KisScanlineFill::fillSelectionWithBoundary(KisPixelSelectionSP pixelSelection, KisPaintDeviceSP existingSelection) +{ + KoColor srcColor(m_d->device->pixel(m_d->startPoint)); + + const int pixelSize = m_d->device->pixelSize(); + + if (pixelSize == 1) { + SelectionPolicyExtended, CopyToSelection, SelectednessPolicyOptimized> + policy(m_d->device, existingSelection, srcColor, m_d->threshold); + policy.setDestinationSelection(pixelSelection); + runImpl(policy); + } else if (pixelSize == 2) { + SelectionPolicyExtended, CopyToSelection, SelectednessPolicyOptimized> + policy(m_d->device, existingSelection, srcColor, m_d->threshold); + policy.setDestinationSelection(pixelSelection); + runImpl(policy); + } else if (pixelSize == 4) { + SelectionPolicyExtended, CopyToSelection, SelectednessPolicyOptimized> + policy(m_d->device, existingSelection, srcColor, m_d->threshold); + policy.setDestinationSelection(pixelSelection); + runImpl(policy); + + } else if (pixelSize == 8) { + SelectionPolicyExtended, CopyToSelection, SelectednessPolicyOptimized> + policy(m_d->device, existingSelection, srcColor, m_d->threshold); + policy.setDestinationSelection(pixelSelection); + runImpl(policy); + } else { + SelectionPolicyExtended + policy(m_d->device, existingSelection, srcColor, m_d->threshold); + policy.setDestinationSelection(pixelSelection); + runImpl(policy); + } +} + void KisScanlineFill::fillSelection(KisPixelSelectionSP pixelSelection) { KoColor srcColor(m_d->device->pixel(m_d->startPoint)); const int pixelSize = m_d->device->pixelSize(); if (pixelSize == 1) { SelectionPolicy, CopyToSelection> policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationSelection(pixelSelection); runImpl(policy); } else if (pixelSize == 2) { SelectionPolicy, CopyToSelection> policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationSelection(pixelSelection); runImpl(policy); } else if (pixelSize == 4) { SelectionPolicy, CopyToSelection> policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationSelection(pixelSelection); runImpl(policy); } else if (pixelSize == 8) { SelectionPolicy, CopyToSelection> policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationSelection(pixelSelection); runImpl(policy); } else { SelectionPolicy policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationSelection(pixelSelection); runImpl(policy); } } void KisScanlineFill::clearNonZeroComponent() { const int pixelSize = m_d->device->pixelSize(); KoColor srcColor(Qt::transparent, m_d->device->colorSpace()); if (pixelSize == 1) { SelectionPolicy, FillWithColor> policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(srcColor); runImpl(policy); } else if (pixelSize == 2) { SelectionPolicy, FillWithColor> policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(srcColor); runImpl(policy); } else if (pixelSize == 4) { SelectionPolicy, FillWithColor> policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(srcColor); runImpl(policy); } else if (pixelSize == 8) { SelectionPolicy, FillWithColor> policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(srcColor); runImpl(policy); } else { SelectionPolicy policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(srcColor); runImpl(policy); } } void KisScanlineFill::fillContiguousGroup(KisPaintDeviceSP groupMapDevice, qint32 groupIndex) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->device->pixelSize() == 1); KIS_SAFE_ASSERT_RECOVER_RETURN(groupMapDevice->pixelSize() == 4); const quint8 referenceValue = *m_d->device->pixel(m_d->startPoint).data(); GroupSplitPolicy policy(m_d->device, groupMapDevice, groupIndex, referenceValue, m_d->threshold); runImpl(policy); } void KisScanlineFill::testingProcessLine(const KisFillInterval &processInterval) { KoColor srcColor(QColor(0,0,0,0), m_d->device->colorSpace()); KoColor fillColor(QColor(200,200,200,200), m_d->device->colorSpace()); SelectionPolicy, FillWithColor> policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(fillColor); processLine(processInterval, 1, policy); } QVector KisScanlineFill::testingGetForwardIntervals() const { return QVector(m_d->forwardStack); } KisFillIntervalMap* KisScanlineFill::testingGetBackwardIntervals() const { return &m_d->backwardMap; } diff --git a/libs/image/floodfill/kis_scanline_fill.h b/libs/image/floodfill/kis_scanline_fill.h index db752e1745..5d06ffcb2b 100644 --- a/libs/image/floodfill/kis_scanline_fill.h +++ b/libs/image/floodfill/kis_scanline_fill.h @@ -1,100 +1,106 @@ /* * Copyright (c) 2014 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_SCANLINE_FILL_H #define __KIS_SCANLINE_FILL_H #include #include #include #include class KisFillInterval; class KisFillIntervalMap; class KRITAIMAGE_EXPORT KisScanlineFill { public: KisScanlineFill(KisPaintDeviceSP device, const QPoint &startPoint, const QRect &boundingRect); ~KisScanlineFill(); /** * Fill the source device with \p fillColor */ void fillColor(const KoColor &fillColor); /** * Fill \p externalDevice with \p fillColor basing on the contents * of the source device. */ void fillColor(const KoColor &fillColor, KisPaintDeviceSP externalDevice); + /** + * Fill \p pixelSelection with the opacity of the contiguous area. + * This method uses an existing selection as boundary for the flood fill. + */ + void fillSelectionWithBoundary(KisPixelSelectionSP pixelSelection, KisPaintDeviceSP existingSelection); + /** * Fill \p pixelSelection with the opacity of the contiguous area */ void fillSelection(KisPixelSelectionSP pixelSelection); /** * Clear the contiguous non-zero area of the device * * WARNING: the threshold parameter is not counted! */ void clearNonZeroComponent(); /** * A special filler algorithm for the Watershed initialization routine: * * 1) Clear the contiguous area in the destination device * 2) At the same time, fill the corresponding area of \p groupMapDevice with * value \p groupIndex * 3) \p groupMapDevice **must** store 4 bytes per pixel */ void fillContiguousGroup(KisPaintDeviceSP groupMapDevice, qint32 groupIndex); /** * Set the threshold of the filling operation * * Used in all functions except clearNonZeroComponent() */ void setThreshold(int threshold); private: friend class KisScanlineFillTest; Q_DISABLE_COPY(KisScanlineFill) template void processLine(KisFillInterval interval, const int rowIncrement, T &pixelPolicy); template void extendedPass(KisFillInterval *currentInterval, int srcRow, bool extendRight, T &pixelPolicy); template void runImpl(T &pixelPolicy); private: void testingProcessLine(const KisFillInterval &processInterval); QVector testingGetForwardIntervals() const; KisFillIntervalMap* testingGetBackwardIntervals() const; private: struct Private; const QScopedPointer m_d; }; #endif /* __KIS_SCANLINE_FILL_H */ diff --git a/libs/image/kis_fill_painter.cc b/libs/image/kis_fill_painter.cc index 7c461a97af..55a38ccbd6 100644 --- a/libs/image/kis_fill_painter.cc +++ b/libs/image/kis_fill_painter.cc @@ -1,358 +1,365 @@ /* * Copyright (c) 2004 Adrian Page * Copyright (c) 2004 Bart Coppens * Copyright (c) 2010 Lukáš Tvrdý * * 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_fill_painter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "generator/kis_generator.h" #include "filter/kis_filter_configuration.h" #include "generator/kis_generator_registry.h" #include "kis_processing_information.h" #include "kis_debug.h" #include "kis_image.h" #include "kis_layer.h" #include "kis_paint_device.h" #include #include "KoColorSpace.h" #include "kis_transaction.h" #include "kis_pixel_selection.h" #include #include #include "kis_selection_filters.h" #include KisFillPainter::KisFillPainter() : KisPainter() { initFillPainter(); } KisFillPainter::KisFillPainter(KisPaintDeviceSP device) : KisPainter(device) { initFillPainter(); } KisFillPainter::KisFillPainter(KisPaintDeviceSP device, KisSelectionSP selection) : KisPainter(device, selection) { initFillPainter(); } void KisFillPainter::initFillPainter() { m_width = m_height = -1; m_careForSelection = false; m_sizemod = 0; m_feather = 0; m_useCompositioning = false; m_threshold = 0; + m_useSelectionAsBoundary = false; } void KisFillPainter::fillSelection(const QRect &rc, const KoColor &color) { KisPaintDeviceSP fillDevice = new KisPaintDevice(device()->colorSpace()); fillDevice->setDefaultPixel(color); bitBlt(rc.topLeft(), fillDevice, rc); } // 'regular' filling // XXX: This also needs renaming, since filling ought to keep the opacity and the composite op in mind, // this is more eraseToColor. void KisFillPainter::fillRect(qint32 x1, qint32 y1, qint32 w, qint32 h, const KoColor& kc, quint8 opacity) { if (w > 0 && h > 0) { // Make sure we're in the right colorspace KoColor kc2(kc); // get rid of const kc2.convertTo(device()->colorSpace()); quint8 * data = kc2.data(); device()->colorSpace()->setOpacity(data, opacity, 1); device()->fill(x1, y1, w, h, data); addDirtyRect(QRect(x1, y1, w, h)); } } void KisFillPainter::fillRect(const QRect &rc, const KoPatternSP pattern, const QPoint &offset) { fillRect(rc.x(), rc.y(), rc.width(), rc.height(), pattern, offset); } void KisFillPainter::fillRect(qint32 x1, qint32 y1, qint32 w, qint32 h, const KoPatternSP pattern, const QPoint &offset) { if (!pattern) return; if (!pattern->valid()) return; if (!device()) return; if (w < 1) return; if (h < 1) return; KisPaintDeviceSP patternLayer = new KisPaintDevice(device()->compositionSourceColorSpace(), pattern->name()); patternLayer->convertFromQImage(pattern->pattern(), 0); if (!offset.isNull()) { patternLayer->moveTo(offset); } fillRect(x1, y1, w, h, patternLayer, QRect(offset.x(), offset.y(), pattern->width(), pattern->height())); } void KisFillPainter::fillRect(const QRect &rc, const KoPatternSP pattern, const QTransform transform) { if (!pattern) return; if (!pattern->valid()) return; if (!device()) return; if (rc.width() < 1) return; if (rc.height() < 1) return; KisPaintDeviceSP patternLayer = new KisPaintDevice(device()->compositionSourceColorSpace(), pattern->name()); patternLayer->convertFromQImage(pattern->pattern(), 0); fillRect(rc.x(), rc.y(), rc.width(), rc.height(), patternLayer, QRect(0, 0, pattern->width(), pattern->height()), transform); } void KisFillPainter::fillRect(qint32 x1, qint32 y1, qint32 w, qint32 h, const KisPaintDeviceSP device, const QRect& deviceRect, const QTransform transform) { KisPaintDeviceSP wrapped = device; wrapped->setDefaultBounds(new KisWrapAroundBoundsWrapper(wrapped->defaultBounds(), deviceRect)); KisPerspectiveTransformWorker worker = KisPerspectiveTransformWorker(this->device(), transform, this->progressUpdater()); worker.runPartialDst(device, this->device(), QRect(x1, y1, w, h)); addDirtyRect(QRect(x1, y1, w, h)); } void KisFillPainter::fillRect(qint32 x1, qint32 y1, qint32 w, qint32 h, const KisPaintDeviceSP device, const QRect& deviceRect) { const QRect &patternRect = deviceRect; const QRect fillRect(x1, y1, w, h); auto toPatternLocal = [](int value, int offset, int width) { const int normalizedValue = value - offset; return offset + (normalizedValue >= 0 ? normalizedValue % width : width - (-normalizedValue - 1) % width - 1); }; int dstY = fillRect.y(); while (dstY <= fillRect.bottom()) { const int dstRowsRemaining = fillRect.bottom() - dstY + 1; const int srcY = toPatternLocal(dstY, patternRect.y(), patternRect.height()); const int height = qMin(patternRect.height() - srcY + patternRect.y(), dstRowsRemaining); int dstX = fillRect.x(); while (dstX <= fillRect.right()) { const int dstColumnsRemaining = fillRect.right() - dstX + 1; const int srcX = toPatternLocal(dstX, patternRect.x(), patternRect.width()); const int width = qMin(patternRect.width() - srcX + patternRect.x(), dstColumnsRemaining); bitBlt(dstX, dstY, device, srcX, srcY, width, height); dstX += width; } dstY += height; } addDirtyRect(QRect(x1, y1, w, h)); } void KisFillPainter::fillRect(qint32 x1, qint32 y1, qint32 w, qint32 h, const KisFilterConfigurationSP generator) { if (!generator) return; KisGeneratorSP g = KisGeneratorRegistry::instance()->value(generator->name()); if (!device()) return; if (w < 1) return; if (h < 1) return; QRect tmpRc(x1, y1, w, h); KisProcessingInformation dstCfg(device(), tmpRc.topLeft(), 0); g->generate(dstCfg, tmpRc.size(), generator); addDirtyRect(tmpRc); } // flood filling void KisFillPainter::fillColor(int startX, int startY, KisPaintDeviceSP sourceDevice) { if (!m_useCompositioning) { if (m_sizemod || m_feather || compositeOp()->id() != COMPOSITE_OVER || opacity() != MAX_SELECTED || sourceDevice != device()) { warnKrita << "WARNING: Fast Flood Fill (no compositioning mode)" << "does not support compositeOps, opacity, " << "selection enhancements and separate source " << "devices"; } QRect fillBoundsRect(0, 0, m_width, m_height); QPoint startPoint(startX, startY); if (!fillBoundsRect.contains(startPoint)) return; KisScanlineFill gc(device(), startPoint, fillBoundsRect); gc.setThreshold(m_threshold); gc.fillColor(paintColor()); } else { genericFillStart(startX, startY, sourceDevice); // Now create a layer and fill it KisPaintDeviceSP filled = device()->createCompositionSourceDevice(); Q_CHECK_PTR(filled); KisFillPainter painter(filled); painter.fillRect(0, 0, m_width, m_height, paintColor()); painter.end(); genericFillEnd(filled); } } void KisFillPainter::fillPattern(int startX, int startY, KisPaintDeviceSP sourceDevice, QTransform patternTransform) { genericFillStart(startX, startY, sourceDevice); // Now create a layer and fill it KisPaintDeviceSP filled = device()->createCompositionSourceDevice(); Q_CHECK_PTR(filled); KisFillPainter painter(filled); painter.fillRect(QRect(0, 0, m_width, m_height), pattern(), patternTransform); painter.end(); genericFillEnd(filled); } void KisFillPainter::genericFillStart(int startX, int startY, KisPaintDeviceSP sourceDevice) { Q_ASSERT(m_width > 0); Q_ASSERT(m_height > 0); // Create a selection from the surrounding area - KisPixelSelectionSP pixelSelection = createFloodSelection(startX, startY, sourceDevice); + KisPixelSelectionSP pixelSelection = createFloodSelection(startX, startY, sourceDevice, selection()->pixelSelection()); KisSelectionSP newSelection = new KisSelection(pixelSelection->defaultBounds()); newSelection->pixelSelection()->applySelection(pixelSelection, SELECTION_REPLACE); m_fillSelection = newSelection; } void KisFillPainter::genericFillEnd(KisPaintDeviceSP filled) { if (progressUpdater() && progressUpdater()->interrupted()) { m_width = m_height = -1; return; } // TODO: filling using the correct bound of the selection would be better, *but* // the selection is limited to the exact bound of a layer, while in reality, we don't // want that, since we want a transparent layer to be completely filled // QRect rc = m_fillSelection->selectedExactRect(); /** * Apply the real selection to a filled one */ KisSelectionSP realSelection = selection(); if (realSelection) { m_fillSelection->pixelSelection()->applySelection( realSelection->projection(), SELECTION_INTERSECT); } setSelection(m_fillSelection); bitBlt(0, 0, filled, 0, 0, m_width, m_height); setSelection(realSelection); if (progressUpdater()) progressUpdater()->setProgress(100); m_width = m_height = -1; } -KisPixelSelectionSP KisFillPainter::createFloodSelection(int startX, int startY, KisPaintDeviceSP sourceDevice) +KisPixelSelectionSP KisFillPainter::createFloodSelection(int startX, int startY, KisPaintDeviceSP sourceDevice, + KisPaintDeviceSP existingSelection) { KisPixelSelectionSP newSelection = new KisPixelSelection(new KisSelectionDefaultBounds(device())); - return createFloodSelection(newSelection, startX, startY, sourceDevice); + return createFloodSelection(newSelection, startX, startY, sourceDevice, existingSelection); } -KisPixelSelectionSP KisFillPainter::createFloodSelection(KisPixelSelectionSP pixelSelection, int startX, int startY, KisPaintDeviceSP sourceDevice) +KisPixelSelectionSP KisFillPainter::createFloodSelection(KisPixelSelectionSP pixelSelection, int startX, int startY, + KisPaintDeviceSP sourceDevice, KisPaintDeviceSP existingSelection) { if (m_width < 0 || m_height < 0) { if (selection() && m_careForSelection) { QRect rc = selection()->selectedExactRect(); m_width = rc.width() - (startX - rc.x()); m_height = rc.height() - (startY - rc.y()); } } dbgImage << "Width: " << m_width << " Height: " << m_height; // Otherwise the width and height should have been set Q_ASSERT(m_width > 0 && m_height > 0); QRect fillBoundsRect(0, 0, m_width, m_height); QPoint startPoint(startX, startY); if (!fillBoundsRect.contains(startPoint)) { return pixelSelection; } KisScanlineFill gc(sourceDevice, startPoint, fillBoundsRect); gc.setThreshold(m_threshold); - gc.fillSelection(pixelSelection); + if (m_useSelectionAsBoundary) { + gc.fillSelectionWithBoundary(pixelSelection, existingSelection); + } else { + gc.fillSelection(pixelSelection); + } if (m_sizemod > 0) { KisGrowSelectionFilter biggy(m_sizemod, m_sizemod); biggy.process(pixelSelection, pixelSelection->selectedRect().adjusted(-m_sizemod, -m_sizemod, m_sizemod, m_sizemod)); } else if (m_sizemod < 0) { KisShrinkSelectionFilter tiny(-m_sizemod, -m_sizemod, false); tiny.process(pixelSelection, pixelSelection->selectedRect()); } if (m_feather > 0) { KisFeatherSelectionFilter feathery(m_feather); feathery.process(pixelSelection, pixelSelection->selectedRect().adjusted(-m_feather, -m_feather, m_feather, m_feather)); } return pixelSelection; } diff --git a/libs/image/kis_fill_painter.h b/libs/image/kis_fill_painter.h index 4235e8283c..1e041d3e0e 100644 --- a/libs/image/kis_fill_painter.h +++ b/libs/image/kis_fill_painter.h @@ -1,319 +1,332 @@ /* * Copyright (c) 2004 Adrian Page * Copyright (c) 2004 Bart Coppens * * 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_FILL_PAINTER_H_ #define KIS_FILL_PAINTER_H_ #include #include #include #include #include "kis_painter.h" #include "kis_types.h" #include "kis_selection.h" #include class KisFilterConfiguration; // XXX: Filling should set dirty rect. /** * This painter can be used to fill paint devices in different ways. This can also be used * for flood filling related operations. */ class KRITAIMAGE_EXPORT KisFillPainter : public KisPainter { public: /** * Construct an empty painter. Use the begin(KisPaintDeviceSP) method to attach * to a paint device */ KisFillPainter(); /** * Start painting on the specified paint device */ KisFillPainter(KisPaintDeviceSP device); KisFillPainter(KisPaintDeviceSP device, KisSelectionSP selection); private: void initFillPainter(); public: /** * Fill a rectangle with black transparent pixels (0, 0, 0, 0 for RGBA). */ void eraseRect(qint32 x1, qint32 y1, qint32 w, qint32 h); /** * Overloaded version of the above function. */ void eraseRect(const QRect& rc); /** * Fill current selection of KisPainter with a specified \p color. * * The filling rect is limited by \p rc to allow multithreaded * filling/processing. */ void fillSelection(const QRect &rc, const KoColor &color); /** * Fill a rectangle with a certain color. */ void fillRect(qint32 x, qint32 y, qint32 w, qint32 h, const KoColor& c); /** * Overloaded version of the above function. */ void fillRect(const QRect& rc, const KoColor& c); /** * Fill a rectangle with a certain color and opacity. */ void fillRect(qint32 x, qint32 y, qint32 w, qint32 h, const KoColor& c, quint8 opacity); /** * Overloaded version of the above function. */ void fillRect(const QRect& rc, const KoColor& c, quint8 opacity); /** * Fill a rectangle with a certain pattern. The pattern is repeated if it does not fit the * entire rectangle. */ void fillRect(qint32 x1, qint32 y1, qint32 w, qint32 h, const KoPatternSP pattern, const QPoint &offset = QPoint()); /** * Fill a rectangle with a certain pattern. The pattern is repeated if it does not fit the * entire rectangle. * * This one uses blitting and thus makes use of proper composition. */ void fillRect(qint32 x1, qint32 y1, qint32 w, qint32 h, const KisPaintDeviceSP device, const QRect& deviceRect); /** * Overloaded version of the above function. */ void fillRect(const QRect& rc, const KoPatternSP pattern, const QPoint &offset = QPoint()); /** * @brief fillRect * Fill a rectangle with a certain pattern. The pattern is repeated if it does not fit the * entire rectangle. Differs from other functions that it uses a transform, does not support * composite ops in turn. * @param rc rectangle to fill. * @param pattern pattern to use. * @param transform transformation to apply to the pattern. */ void fillRect(const QRect& rc, const KoPatternSP pattern, const QTransform transform); /** * Fill a rectangle with a certain pattern. The pattern is repeated if it does not fit the * entire rectangle. * * This one supports transforms, but does not use blitting. */ void fillRect(qint32 x1, qint32 y1, qint32 w, qint32 h, const KisPaintDeviceSP device, const QRect& deviceRect, const QTransform transform); /** * Fill the specified area with the output of the generator plugin that is configured * in the generator parameter */ void fillRect(qint32 x1, qint32 y1, qint32 w, qint32 h, const KisFilterConfigurationSP generator); /** * Fills the enclosed area around the point with the set color. If * there is a selection, the whole selection is filled. Note that * you must have set the width and height on the painter if you * don't have a selection. * * @param startX the X position where the floodfill starts * @param startY the Y position where the floodfill starts * @param sourceDevice the sourceDevice that determines the area that * is floodfilled if sampleMerged is on */ void fillColor(int startX, int startY, KisPaintDeviceSP sourceDevice); /** * Fills the enclosed area around the point with the set pattern. * If there is a selection, the whole selection is filled. Note * that you must have set the width and height on the painter if * you don't have a selection. * * @param startX the X position where the floodfill starts * @param startY the Y position where the floodfill starts * @param sourceDevice the sourceDevice that determines the area that * is floodfilled if sampleMerged is on * @param patternTransform transform applied to the pattern; */ void fillPattern(int startX, int startY, KisPaintDeviceSP sourceDevice, QTransform patternTransform = QTransform()); /** * Returns a selection mask for the floodfill starting at the specified position. * This variant basically creates a new selection object and passes it down * to the other variant of the function. * * @param startX the X position where the floodfill starts * @param startY the Y position where the floodfill starts * @param sourceDevice the sourceDevice that determines the area that * is floodfilled if sampleMerged is on */ - KisPixelSelectionSP createFloodSelection(int startX, int startY, KisPaintDeviceSP sourceDevice); + KisPixelSelectionSP createFloodSelection(int startX, int startY, + KisPaintDeviceSP sourceDevice, KisPaintDeviceSP existingSelection); /** * Returns a selection mask for the floodfill starting at the specified position. * This variant requires an empty selection object. It is used in cases where the pointer * to the selection must be known beforehand, for example when the selection is filled * in a stroke and then the pointer to the pixel selection is needed later. * * @param selection empty new selection object * @param startX the X position where the floodfill starts * @param startY the Y position where the floodfill starts * @param sourceDevice the sourceDevice that determines the area that * is floodfilled if sampleMerged is on */ - KisPixelSelectionSP createFloodSelection(KisPixelSelectionSP newSelection, int startX, int startY, KisPaintDeviceSP sourceDevice); + KisPixelSelectionSP createFloodSelection(KisPixelSelectionSP newSelection, int startX, int startY, + KisPaintDeviceSP sourceDevice, KisPaintDeviceSP existingSelection); /** * Set the threshold for floodfill. The range is 0-255: 0 means the fill will only * fill parts that are the exact same color, 255 means anything will be filled */ void setFillThreshold(int threshold); /** Returns the fill threshold, see setFillThreshold for details */ int fillThreshold() const { return m_threshold; } bool useCompositioning() const { return m_useCompositioning; } void setUseCompositioning(bool useCompositioning) { m_useCompositioning = useCompositioning; } /** Sets the width of the paint device */ void setWidth(int w) { m_width = w; } /** Sets the height of the paint device */ void setHeight(int h) { m_height = h; } /** If true, floodfill doesn't fill outside the selected area of a layer */ bool careForSelection() const { return m_careForSelection; } /** Set caring for selection. See careForSelection for details */ void setCareForSelection(bool set) { m_careForSelection = set; } /** Sets the auto growth/shrinking radius */ void setSizemod(int sizemod) { m_sizemod = sizemod; } /** Sets how much to auto-grow or shrink (if @p sizemod is negative) the selection flood before painting, this affects every fill operation except fillRect */ int sizemod() { return m_sizemod; } /** Sets feathering radius */ void setFeather(int feather) { m_feather = feather; } /** defines the feathering radius for selection flood operations, this affects every fill operation except fillRect */ uint feather() { return m_feather; } + /** Sets selection borders being treated as boundary */ + void setUseSelectionAsBoundary(bool useSelectionAsBoundary) { + m_useSelectionAsBoundary = useSelectionAsBoundary; + } + + /** defines if the selection borders are treated as boundary in flood fill or not */ + uint useSelectionAsBoundary() { + return m_useSelectionAsBoundary; + } + private: // for floodfill void genericFillStart(int startX, int startY, KisPaintDeviceSP sourceDevice); void genericFillEnd(KisPaintDeviceSP filled); KisSelectionSP m_fillSelection; int m_feather; int m_sizemod; int m_threshold; int m_width, m_height; QRect m_rect; bool m_careForSelection; bool m_useCompositioning; + bool m_useSelectionAsBoundary; }; inline void KisFillPainter::fillRect(qint32 x, qint32 y, qint32 w, qint32 h, const KoColor& c) { fillRect(x, y, w, h, c, OPACITY_OPAQUE_U8); } inline void KisFillPainter::fillRect(const QRect& rc, const KoColor& c) { fillRect(rc.x(), rc.y(), rc.width(), rc.height(), c, OPACITY_OPAQUE_U8); } inline void KisFillPainter::eraseRect(qint32 x1, qint32 y1, qint32 w, qint32 h) { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KoColor c(Qt::black, cs); fillRect(x1, y1, w, h, c, OPACITY_TRANSPARENT_U8); } inline void KisFillPainter::eraseRect(const QRect& rc) { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KoColor c(Qt::black, cs); fillRect(rc.x(), rc.y(), rc.width(), rc.height(), c, OPACITY_TRANSPARENT_U8); } inline void KisFillPainter::fillRect(const QRect& rc, const KoColor& c, quint8 opacity) { fillRect(rc.x(), rc.y(), rc.width(), rc.height(), c, opacity); } inline void KisFillPainter::setFillThreshold(int threshold) { m_threshold = threshold; } #endif //KIS_FILL_PAINTER_H_ diff --git a/libs/ui/actions/kis_selection_action_factories.cpp b/libs/ui/actions/kis_selection_action_factories.cpp index 32ebdd0ee1..1ebfe130fd 100644 --- a/libs/ui/actions/kis_selection_action_factories.cpp +++ b/libs/ui/actions/kis_selection_action_factories.cpp @@ -1,624 +1,625 @@ /* * Copyright (c) 2012 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_action_factories.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisViewManager.h" #include "kis_canvas_resource_provider.h" #include "kis_clipboard.h" #include "kis_pixel_selection.h" #include "kis_paint_layer.h" #include "kis_image.h" #include "kis_image_barrier_locker.h" #include "kis_fill_painter.h" #include "kis_transaction.h" #include "kis_iterator_ng.h" #include "kis_processing_applicator.h" #include "kis_group_layer.h" #include "commands/kis_selection_commands.h" #include "commands/kis_image_layer_add_command.h" #include "kis_tool_proxy.h" #include "kis_canvas2.h" #include "kis_canvas_controller.h" #include "kis_selection_manager.h" #include "commands_new/kis_transaction_based_command.h" #include "kis_selection_filters.h" #include "kis_shape_selection.h" #include "kis_shape_layer.h" #include #include "kis_image_animation_interface.h" #include "kis_time_range.h" #include "kis_keyframe_channel.h" #include #include #include "kis_figure_painting_tool_helper.h" #include "kis_update_outline_job.h" namespace ActionHelper { void copyFromDevice(KisViewManager *view, KisPaintDeviceSP device, bool makeSharpClip = false, const KisTimeRange &range = KisTimeRange()) { KisImageWSP image = view->image(); if (!image) return; KisSelectionSP selection = view->selection(); QRect rc = (selection) ? selection->selectedExactRect() : image->bounds(); KisPaintDeviceSP clip = new KisPaintDevice(device->colorSpace()); Q_CHECK_PTR(clip); const KoColorSpace *cs = clip->colorSpace(); // TODO if the source is linked... copy from all linked layers?!? // Copy image data KisPainter::copyAreaOptimized(QPoint(), device, clip, rc); if (selection) { // Apply selection mask. KisPaintDeviceSP selectionProjection = selection->projection(); KisHLineIteratorSP layerIt = clip->createHLineIteratorNG(0, 0, rc.width()); KisHLineConstIteratorSP selectionIt = selectionProjection->createHLineIteratorNG(rc.x(), rc.y(), rc.width()); const KoColorSpace *selCs = selection->projection()->colorSpace(); for (qint32 y = 0; y < rc.height(); y++) { for (qint32 x = 0; x < rc.width(); x++) { /** * Sharp method is an exact reverse of COMPOSITE_OVER * so if you cover the cut/copied piece over its source * you get an exactly the same image without any seams */ if (makeSharpClip) { 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); } else { cs->applyAlphaU8Mask(layerIt->rawData(), selectionIt->oldRawData(), 1); } layerIt->nextPixel(); selectionIt->nextPixel(); } layerIt->nextRow(); selectionIt->nextRow(); } } KisClipboard::instance()->setClip(clip, rc.topLeft(), range); } } void KisSelectAllActionFactory::run(KisViewManager *view) { KisImageWSP image = view->image(); if (!image) return; KisProcessingApplicator *ap = beginAction(view, kundo2_i18n("Select All")); if (!image->globalSelection()) { ap->applyCommand(new KisSetEmptyGlobalSelectionCommand(image), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); } struct SelectAll : public KisTransactionBasedCommand { SelectAll(KisImageSP image) : m_image(image) {} KisImageSP m_image; KUndo2Command* paint() override { KisSelectionSP selection = m_image->globalSelection(); KisSelectionTransaction transaction(selection->pixelSelection()); selection->pixelSelection()->clear(); selection->pixelSelection()->select(m_image->bounds()); return transaction.endAndTake(); } }; ap->applyCommand(new SelectAll(image), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); endAction(ap, KisOperationConfiguration(id()).toXML()); } void KisDeselectActionFactory::run(KisViewManager *view) { KisImageWSP image = view->image(); if (!image) return; KUndo2Command *cmd = new KisDeselectActiveSelectionCommand(view->selection(), image); KisProcessingApplicator *ap = beginAction(view, cmd->text()); ap->applyCommand(cmd, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); endAction(ap, KisOperationConfiguration(id()).toXML()); } void KisReselectActionFactory::run(KisViewManager *view) { KisImageWSP image = view->image(); if (!image) return; KUndo2Command *cmd = new KisReselectActiveSelectionCommand(view->activeNode(), image); KisProcessingApplicator *ap = beginAction(view, cmd->text()); ap->applyCommand(cmd, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); endAction(ap, KisOperationConfiguration(id()).toXML()); } void KisFillActionFactory::run(const QString &fillSource, KisViewManager *view) { KisNodeSP node = view->activeNode(); if (!node || !node->hasEditablePaintDevice()) return; KisSelectionSP selection = view->selection(); QRect selectedRect = selection ? selection->selectedRect() : view->image()->bounds(); Q_UNUSED(selectedRect); KisPaintDeviceSP filled = node->paintDevice()->createCompositionSourceDevice(); Q_UNUSED(filled); bool usePattern = false; bool useBgColor = false; if (fillSource.contains("pattern")) { usePattern = true; } else if (fillSource.contains("bg")) { useBgColor = true; } KisProcessingApplicator applicator(view->image(), node, KisProcessingApplicator::NONE, KisImageSignalVector() << ModifiedSignal, kundo2_i18n("Flood Fill Layer")); KisResourcesSnapshotSP resources = new KisResourcesSnapshot(view->image(), node, view->canvasResourceProvider()->resourceManager()); if (!fillSource.contains("opacity")) { resources->setOpacity(1.0); } KisProcessingVisitorSP visitor = new FillProcessingVisitor(resources->image()->projection(), QPoint(0, 0), // start position selection, resources, false, // fast mode usePattern, true, // fill only selection, + false, 0, // feathering radius 0, // sizemod 80, // threshold, false, // use unmerged useBgColor); applicator.applyVisitor(visitor, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.end(); view->canvasResourceProvider()->slotPainting(); } void KisClearActionFactory::run(KisViewManager *view) { // XXX: "Add saving of XML data for Clear action" view->canvasBase()->toolProxy()->deleteSelection(); } void KisImageResizeToSelectionActionFactory::run(KisViewManager *view) { // XXX: "Add saving of XML data for Image Resize To Selection action" KisSelectionSP selection = view->selection(); if (!selection) return; view->image()->cropImage(selection->selectedExactRect()); } void KisCutCopyActionFactory::run(bool willCut, bool makeSharpClip, KisViewManager *view) { KisImageSP image = view->image(); if (!image) return; bool haveShapesSelected = view->selectionManager()->haveShapesSelected(); if (haveShapesSelected) { // XXX: "Add saving of XML data for Cut/Copy of shapes" KisImageBarrierLocker locker(image); if (willCut) { view->canvasBase()->toolProxy()->cut(); } else { view->canvasBase()->toolProxy()->copy(); } } else { KisNodeSP node = view->activeNode(); if (!node) return; KisSelectionSP selection = view->selection(); if (selection.isNull()) return; { KisImageBarrierLocker locker(image); KisPaintDeviceSP dev = node->paintDevice(); if (!dev) { dev = node->projection(); } if (!dev) { view->showFloatingMessage( i18nc("floating message when cannot copy from a node", "Cannot copy pixels from this type of layer "), QIcon(), 3000, KisFloatingMessage::Medium); return; } if (dev->exactBounds().isEmpty()) { view->showFloatingMessage( i18nc("floating message when copying empty selection", "Selection is empty: no pixels were copied "), QIcon(), 3000, KisFloatingMessage::Medium); return; } KisTimeRange range; KisKeyframeChannel *channel = node->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (channel) { const int currentTime = image->animationInterface()->currentTime(); range = channel->affectedFrames(currentTime); } ActionHelper::copyFromDevice(view, dev, makeSharpClip, range); } KUndo2Command *command = 0; if (willCut && node->hasEditablePaintDevice()) { struct ClearSelection : public KisTransactionBasedCommand { ClearSelection(KisNodeSP node, KisSelectionSP sel) : m_node(node), m_sel(sel) {} KisNodeSP m_node; KisSelectionSP m_sel; KUndo2Command* paint() override { KisSelectionSP cutSelection = m_sel; // Shrinking the cutting area was previously used // for getting seamless cut-paste. Now we use makeSharpClip // instead. // QRect originalRect = cutSelection->selectedExactRect(); // static const int preciseSelectionThreshold = 16; // // if (originalRect.width() > preciseSelectionThreshold || // originalRect.height() > preciseSelectionThreshold) { // cutSelection = new KisSelection(*m_sel); // delete cutSelection->flatten(); // // KisSelectionFilter* filter = new KisShrinkSelectionFilter(1, 1, false); // // QRect processingRect = filter->changeRect(originalRect); // filter->process(cutSelection->pixelSelection(), processingRect); // } KisTransaction transaction(m_node->paintDevice()); m_node->paintDevice()->clearSelection(cutSelection); m_node->setDirty(cutSelection->selectedRect()); return transaction.endAndTake(); } }; command = new ClearSelection(node, selection); } KUndo2MagicString actionName = willCut ? kundo2_i18n("Cut") : kundo2_i18n("Copy"); KisProcessingApplicator *ap = beginAction(view, actionName); if (command) { ap->applyCommand(command, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL); } KisOperationConfiguration config(id()); config.setProperty("will-cut", willCut); endAction(ap, config.toXML()); } } void KisCopyMergedActionFactory::run(KisViewManager *view) { KisImageWSP image = view->image(); if (!image) return; if (!view->blockUntilOperationsFinished(image)) return; image->barrierLock(); KisPaintDeviceSP dev = image->root()->projection(); ActionHelper::copyFromDevice(view, dev); image->unlock(); KisProcessingApplicator *ap = beginAction(view, kundo2_i18n("Copy Merged")); endAction(ap, KisOperationConfiguration(id()).toXML()); } void KisInvertSelectionOperation::runFromXML(KisViewManager* view, const KisOperationConfiguration& config) { KisSelectionFilter* filter = new KisInvertSelectionFilter(); runFilter(filter, view, config); } void KisSelectionToVectorActionFactory::run(KisViewManager *view) { KisSelectionSP selection = view->selection(); if (selection->hasShapeSelection()) { view->showFloatingMessage(i18nc("floating message", "Selection is already in a vector format "), QIcon(), 2000, KisFloatingMessage::Low); return; } if (!selection->outlineCacheValid()) { view->image()->addSpontaneousJob(new KisUpdateOutlineJob(selection, false, Qt::transparent)); if (!view->blockUntilOperationsFinished(view->image())) { return; } } QPainterPath selectionOutline = selection->outlineCache(); QTransform transform = view->canvasBase()->coordinatesConverter()->imageToDocumentTransform(); KoShape *shape = KoPathShape::createShapeFromPainterPath(transform.map(selectionOutline)); shape->setShapeId(KoPathShapeId); /** * Mark a shape that it belongs to a shape selection */ if(!shape->userData()) { shape->setUserData(new KisShapeSelectionMarker); } KisProcessingApplicator *ap = beginAction(view, kundo2_i18n("Convert to Vector Selection")); ap->applyCommand(view->canvasBase()->shapeController()->addShape(shape, 0), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); endAction(ap, KisOperationConfiguration(id()).toXML()); } void KisSelectionToRasterActionFactory::run(KisViewManager *view) { KisSelectionSP selection = view->selection(); if (!selection->hasShapeSelection()) { view->showFloatingMessage(i18nc("floating message", "Selection is already in a raster format "), QIcon(), 2000, KisFloatingMessage::Low); return; } KisProcessingApplicator *ap = beginAction(view, kundo2_i18n("Convert to Vector Selection")); struct RasterizeSelection : public KisTransactionBasedCommand { RasterizeSelection(KisSelectionSP sel) : m_sel(sel) {} KisSelectionSP m_sel; KUndo2Command* paint() override { // just create an empty transaction: it will rasterize the // selection and emit the necessary signals KisTransaction transaction(m_sel->pixelSelection()); return transaction.endAndTake(); } }; ap->applyCommand(new RasterizeSelection(selection), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); endAction(ap, KisOperationConfiguration(id()).toXML()); } void KisShapesToVectorSelectionActionFactory::run(KisViewManager* view) { const QList originalShapes = view->canvasBase()->shapeManager()->selection()->selectedShapes(); bool hasSelectionShapes = false; QList clonedShapes; Q_FOREACH (KoShape *shape, originalShapes) { if (dynamic_cast(shape->userData())) { hasSelectionShapes = true; continue; } clonedShapes << shape->cloneShape(); } if (clonedShapes.isEmpty()) { if (hasSelectionShapes) { view->showFloatingMessage(i18nc("floating message", "The shape already belongs to a selection"), QIcon(), 2000, KisFloatingMessage::Low); } return; } KisSelectionToolHelper helper(view->canvasBase(), kundo2_i18n("Convert shapes to vector selection")); helper.addSelectionShapes(clonedShapes); } void KisSelectionToShapeActionFactory::run(KisViewManager *view) { KisSelectionSP selection = view->selection(); if (!selection->outlineCacheValid()) { return; } QPainterPath selectionOutline = selection->outlineCache(); QTransform transform = view->canvasBase()->coordinatesConverter()->imageToDocumentTransform(); KoShape *shape = KoPathShape::createShapeFromPainterPath(transform.map(selectionOutline)); shape->setShapeId(KoPathShapeId); KoColor fgColor = view->canvasBase()->resourceManager()->resource(KoCanvasResourceProvider::ForegroundColor).value(); KoShapeStrokeSP border(new KoShapeStroke(1.0, fgColor.toQColor())); shape->setStroke(border); KUndo2Command *cmd = view->canvasBase()->shapeController()->addShapeDirect(shape, 0); KisProcessingApplicator::runSingleCommandStroke(view->image(), cmd); } void KisStrokeSelectionActionFactory::run(KisViewManager *view, StrokeSelectionOptions params) { KisImageWSP image = view->image(); if (!image) { return; } KisSelectionSP selection = view->selection(); if (!selection) { return; } int size = params.lineSize; KisPixelSelectionSP pixelSelection = selection->projection(); if (!pixelSelection->outlineCacheValid()) { pixelSelection->recalculateOutlineCache(); } QPainterPath outline = pixelSelection->outlineCache(); QColor color = params.color.toQColor(); KisNodeSP currentNode = view->canvasResourceProvider()->resourceManager()->resource(KisCanvasResourceProvider::CurrentKritaNode).value(); if (!currentNode->inherits("KisShapeLayer") && currentNode->paintDevice()) { KoCanvasResourceProvider * rManager = view->canvasResourceProvider()->resourceManager(); KisToolShapeUtils::StrokeStyle strokeStyle = KisToolShapeUtils::StrokeStyleForeground; KisToolShapeUtils::FillStyle fillStyle = params.fillStyle(); KisFigurePaintingToolHelper helper(kundo2_i18n("Draw Polyline"), image, currentNode, rManager , strokeStyle, fillStyle); helper.setFGColorOverride(params.color); helper.setSelectionOverride(0); QPen pen(Qt::red, size); pen.setJoinStyle(Qt::RoundJoin); if (fillStyle != KisToolShapeUtils::FillStyleNone) { helper.paintPainterPathQPenFill(outline, pen, params.fillColor); } else { helper.paintPainterPathQPen(outline, pen, params.fillColor); } } else if (currentNode->inherits("KisShapeLayer")) { QTransform transform = view->canvasBase()->coordinatesConverter()->imageToDocumentTransform(); KoShape *shape = KoPathShape::createShapeFromPainterPath(transform.map(outline)); shape->setShapeId(KoPathShapeId); KoShapeStrokeSP border(new KoShapeStroke(size, color)); shape->setStroke(border); KUndo2Command *cmd = view->canvasBase()->shapeController()->addShapeDirect(shape, 0); KisProcessingApplicator::runSingleCommandStroke(view->image(), cmd); } image->setModified(); } void KisStrokeBrushSelectionActionFactory::run(KisViewManager *view, StrokeSelectionOptions params) { KisImageWSP image = view->image(); if (!image) { return; } KisSelectionSP selection = view->selection(); if (!selection) { return; } KisPixelSelectionSP pixelSelection = selection->projection(); if (!pixelSelection->outlineCacheValid()) { pixelSelection->recalculateOutlineCache(); } KisNodeSP currentNode = view->canvasResourceProvider()->resourceManager()->resource(KisCanvasResourceProvider::CurrentKritaNode).value(); if (!currentNode->inherits("KisShapeLayer") && currentNode->paintDevice()) { KoCanvasResourceProvider * rManager = view->canvasResourceProvider()->resourceManager(); QPainterPath outline = pixelSelection->outlineCache(); KisToolShapeUtils::StrokeStyle strokeStyle = KisToolShapeUtils::StrokeStyleForeground; KisToolShapeUtils::FillStyle fillStyle = KisToolShapeUtils::FillStyleNone; KoColor color = params.color; KisFigurePaintingToolHelper helper(kundo2_i18n("Draw Polyline"), image, currentNode, rManager, strokeStyle, fillStyle); helper.setFGColorOverride(color); helper.setSelectionOverride(0); helper.paintPainterPath(outline); image->setModified(); } } diff --git a/libs/ui/processing/fill_processing_visitor.cpp b/libs/ui/processing/fill_processing_visitor.cpp index 4711ca2fa1..5e100d8fdc 100644 --- a/libs/ui/processing/fill_processing_visitor.cpp +++ b/libs/ui/processing/fill_processing_visitor.cpp @@ -1,148 +1,151 @@ /* * Copyright (c) 2013 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 "fill_processing_visitor.h" #include #include #include #include #include "lazybrush/kis_colorize_mask.h" FillProcessingVisitor::FillProcessingVisitor(KisPaintDeviceSP refPaintDevice, const QPoint &startPoint, KisSelectionSP selection, KisResourcesSnapshotSP resources, bool useFastMode, bool usePattern, bool selectionOnly, + bool useSelectionAsBoundary, int feather, int sizemod, int fillThreshold, bool unmerged, bool useBgColor) : m_refPaintDevice(refPaintDevice), m_startPoint(startPoint), m_selection(selection), m_useFastMode(useFastMode), m_selectionOnly(selectionOnly), + m_useSelectionAsBoundary(useSelectionAsBoundary), m_usePattern(usePattern), m_resources(resources), m_feather(feather), m_sizemod(sizemod), m_fillThreshold(fillThreshold), m_unmerged(unmerged), m_useBgColor(useBgColor) { } void FillProcessingVisitor::visitExternalLayer(KisExternalLayer *layer, KisUndoAdapter *undoAdapter) { Q_UNUSED(layer); Q_UNUSED(undoAdapter); } void FillProcessingVisitor::visitNodeWithPaintDevice(KisNode *node, KisUndoAdapter *undoAdapter) { KisPaintDeviceSP device = node->paintDevice(); Q_ASSERT(device); ProgressHelper helper(node); fillPaintDevice(device, undoAdapter, helper); } void FillProcessingVisitor::fillPaintDevice(KisPaintDeviceSP device, KisUndoAdapter *undoAdapter, ProgressHelper &helper) { QRect fillRect = m_resources->image()->bounds(); if (!device->defaultBounds()->wrapAroundMode() && !fillRect.contains(m_startPoint)) { return; } if (m_selectionOnly) { KisPaintDeviceSP filledDevice = device->createCompositionSourceDevice(); KisFillPainter fillPainter(filledDevice); fillPainter.setProgress(helper.updater()); if (m_usePattern) { fillPainter.fillRect(fillRect, m_resources->currentPattern(), m_resources->fillTransform()); } else if (m_useBgColor) { fillPainter.fillRect(fillRect, m_resources->currentBgColor(), m_resources->opacity()); } else { fillPainter.fillRect(fillRect, m_resources->currentFgColor(), m_resources->opacity()); } QVector dirtyRect = fillPainter.takeDirtyRegion(); KisPainter painter(device, m_selection); painter.beginTransaction(); m_resources->setupPainter(&painter); Q_FOREACH (const QRect &rc, dirtyRect) { painter.bitBlt(rc.topLeft(), filledDevice, rc); } painter.endTransaction(undoAdapter); } else { QPoint startPoint = m_startPoint; if (device->defaultBounds()->wrapAroundMode()) { startPoint = KisWrappedRect::ptToWrappedPt(startPoint, device->defaultBounds()->imageBorderRect()); } KisFillPainter fillPainter(device, m_selection); fillPainter.beginTransaction(); m_resources->setupPainter(&fillPainter); fillPainter.setProgress(helper.updater()); fillPainter.setSizemod(m_sizemod); fillPainter.setFeather(m_feather); fillPainter.setFillThreshold(m_fillThreshold); fillPainter.setCareForSelection(true); + fillPainter.setUseSelectionAsBoundary((m_selection.isNull() || m_selection->hasNonEmptyPixelSelection()) ? m_useSelectionAsBoundary : false); fillPainter.setWidth(fillRect.width()); fillPainter.setHeight(fillRect.height()); fillPainter.setUseCompositioning(!m_useFastMode); KisPaintDeviceSP sourceDevice = m_unmerged ? device : m_refPaintDevice; if (m_usePattern) { fillPainter.fillPattern(startPoint.x(), startPoint.y(), sourceDevice, m_resources->fillTransform()); } else { fillPainter.fillColor(startPoint.x(), startPoint.y(), sourceDevice); } fillPainter.endTransaction(undoAdapter); } } void FillProcessingVisitor::visitColorizeMask(KisColorizeMask *mask, KisUndoAdapter *undoAdapter) { // we fill only the coloring project so the user can work // with the mask like with a usual paint layer ProgressHelper helper(mask); fillPaintDevice(mask->coloringProjection(), undoAdapter, helper); } diff --git a/libs/ui/processing/fill_processing_visitor.h b/libs/ui/processing/fill_processing_visitor.h index 62f8820346..f4a7cbb575 100644 --- a/libs/ui/processing/fill_processing_visitor.h +++ b/libs/ui/processing/fill_processing_visitor.h @@ -1,70 +1,72 @@ /* * Copyright (c) 2013 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 __FILL_PROCESSING_VISITOR_H #define __FILL_PROCESSING_VISITOR_H #include #include #include #include #include class KRITAUI_EXPORT FillProcessingVisitor : public KisSimpleProcessingVisitor { public: FillProcessingVisitor( KisPaintDeviceSP referencePaintDevice, const QPoint &startPoint, KisSelectionSP selection, KisResourcesSnapshotSP resources, bool useFastMode, bool usePattern, bool selectionOnly, + bool useSelectionAsBoundary, int feather, int sizemod, int fillThreshold, bool unmerged, bool m_useBgColor); private: void visitNodeWithPaintDevice(KisNode *node, KisUndoAdapter *undoAdapter) override; void visitExternalLayer(KisExternalLayer *layer, KisUndoAdapter *undoAdapter) override; void visitColorizeMask(KisColorizeMask *mask, KisUndoAdapter *undoAdapter) override; void fillPaintDevice(KisPaintDeviceSP device, KisUndoAdapter *undoAdapter, ProgressHelper &helper); private: KisPaintDeviceSP m_refPaintDevice; QPoint m_startPoint; KisSelectionSP m_selection; bool m_useFastMode; bool m_selectionOnly; + bool m_useSelectionAsBoundary; bool m_usePattern; KisResourcesSnapshotSP m_resources; int m_feather; int m_sizemod; int m_fillThreshold; bool m_unmerged; bool m_useBgColor; }; #endif /* __FILL_PROCESSING_VISITOR_H */ diff --git a/libs/ui/tests/fill_processing_visitor_test.cpp b/libs/ui/tests/fill_processing_visitor_test.cpp index be1f4f5284..f6d64beccd 100644 --- a/libs/ui/tests/fill_processing_visitor_test.cpp +++ b/libs/ui/tests/fill_processing_visitor_test.cpp @@ -1,149 +1,150 @@ /* * Copyright (c) 2013 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 "fill_processing_visitor_test.h" #include #include "kis_undo_stores.h" #include "kis_processing_applicator.h" #include "testutil.h" #include "qimage_based_test.h" #include "stroke_testing_utils.h" #include #include "kis_canvas_resource_provider.h" #include #include class FillProcessingVisitorTester : public TestUtil::QImageBasedTest { public: FillProcessingVisitorTester() : QImageBasedTest("fill_processing") { } void test(const QString &testname, bool haveSelection, bool usePattern, bool selectionOnly) { KisSurrogateUndoStore *undoStore = new KisSurrogateUndoStore(); KisImageSP image = createImage(undoStore); if (haveSelection) { addGlobalSelection(image); } image->initialRefreshGraph(); QVERIFY(checkLayersInitial(image)); KisNodeSP fillNode = findNode(image->root(), "paint1"); KoCanvasResourceProvider *manager = utils::createResourceManager(image, fillNode); KoPatternSP newPattern(new KoPattern(TestUtil::fetchDataFileLazy("HR_SketchPaper_01.pat"))); newPattern->load(KisGlobalResourcesInterface::instance()); Q_ASSERT(newPattern->valid()); QVariant v; v.setValue(newPattern); manager->setResource(KisCanvasResourceProvider::CurrentPattern, v); KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image, fillNode, manager); KisProcessingVisitorSP visitor = new FillProcessingVisitor(0, QPoint(100,100), image->globalSelection(), resources, false, // useFastMode usePattern, selectionOnly, + false, 10, 10, 10, true /* use the current device (unmerged) */, false); KisProcessingApplicator applicator(image, fillNode, KisProcessingApplicator::NONE); applicator.applyVisitor(visitor); applicator.end(); image->waitForDone(); QVERIFY(checkOneLayer(image, fillNode, testname, 500)); undoStore->undo(); image->waitForDone(); QVERIFY(checkLayersInitial(image)); } }; void FillProcessingVisitorTest::testFillColorNoSelection() { FillProcessingVisitorTester tester; tester.test("fill_color_no_selection", false, false, false); } void FillProcessingVisitorTest::testFillPatternNoSelection() { FillProcessingVisitorTester tester; tester.test("fill_pattern_no_selection", false, true, false); } void FillProcessingVisitorTest::testFillColorHaveSelection() { FillProcessingVisitorTester tester; tester.test("fill_color_have_selection", true, false, false); } void FillProcessingVisitorTest::testFillPatternHaveSelection() { FillProcessingVisitorTester tester; tester.test("fill_pattern_have_selection", true, true, false); } void FillProcessingVisitorTest::testFillColorNoSelectionSelectionOnly() { FillProcessingVisitorTester tester; tester.test("fill_color_no_selection_selection_only", false, false, true); } void FillProcessingVisitorTest::testFillPatternNoSelectionSelectionOnly() { FillProcessingVisitorTester tester; tester.test("fill_pattern_no_selection_selection_only", false, true, true); } void FillProcessingVisitorTest::testFillColorHaveSelectionSelectionOnly() { FillProcessingVisitorTester tester; tester.test("fill_color_have_selection_selection_only", true, false, true); } void FillProcessingVisitorTest::testFillPatternHaveSelectionSelectionOnly() { FillProcessingVisitorTester tester; tester.test("fill_pattern_have_selection_selection_only", true, true, true); } QTEST_MAIN(FillProcessingVisitorTest) diff --git a/plugins/tools/basictools/kis_tool_fill.cc b/plugins/tools/basictools/kis_tool_fill.cc index 7f62504f12..52d42db6b0 100644 --- a/plugins/tools/basictools/kis_tool_fill.cc +++ b/plugins/tools/basictools/kis_tool_fill.cc @@ -1,490 +1,525 @@ /* * kis_tool_fill.cc - part of Krayon * * Copyright (c) 2000 John Califf * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2004 Bart Coppens * * 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_tool_fill.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_resources_snapshot.h" #include "commands_new/KisMergeLabeledLayersCommand.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "KoCompositeOpRegistry.h" KisToolFill::KisToolFill(KoCanvasBase * canvas) : KisToolPaint(canvas, KisCursor::load("tool_fill_cursor.png", 6, 6)) - , m_colorLabelCompressor(900, KisSignalCompressor::FIRST_INACTIVE) + , m_colorLabelCompressor(500, KisSignalCompressor::FIRST_INACTIVE) { setObjectName("tool_fill"); m_feather = 0; m_sizemod = 0; m_threshold = 80; m_usePattern = false; m_fillOnlySelection = false; connect(&m_colorLabelCompressor, SIGNAL(timeout()), SLOT(slotUpdateAvailableColorLabels())); } KisToolFill::~KisToolFill() { } void KisToolFill::resetCursorStyle() { KisToolPaint::resetCursorStyle(); overrideCursorIfNotEditable(); } void KisToolFill::slotUpdateAvailableColorLabels() { if (m_widgetsInitialized && m_cmbSelectedLabels) { m_cmbSelectedLabels->updateAvailableLabels(currentImage()->root()); } } void KisToolFill::activate(ToolActivation toolActivation, const QSet &shapes) { KisToolPaint::activate(toolActivation, shapes); m_configGroup = KSharedConfig::openConfig()->group(toolId()); if (m_widgetsInitialized && m_imageConnections.isEmpty()) { activateConnectionsToImage(); } } void KisToolFill::deactivate() { KisToolPaint::deactivate(); m_imageConnections.clear(); } void KisToolFill::beginPrimaryAction(KoPointerEvent *event) { // cannot use fill tool on non-painting layers. // this logic triggers with multiple layer types like vector layer, clone layer, file layer, group layer if ( currentNode().isNull() || currentNode()->inherits("KisShapeLayer") || nodePaintAbility()!=NodePaintAbility::PAINT ) { KisCanvas2 * kiscanvas = static_cast(canvas()); kiscanvas->viewManager()-> showFloatingMessage( i18n("You cannot use this tool with the selected layer type"), QIcon(), 2000, KisFloatingMessage::Medium, Qt::AlignCenter); event->ignore(); return; } if (!nodeEditable()) { event->ignore(); return; } setMode(KisTool::PAINT_MODE); m_startPos = convertToImagePixelCoordFloored(event); keysAtStart = event->modifiers(); } void KisToolFill::endPrimaryAction(KoPointerEvent *event) { Q_UNUSED(event); CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); setMode(KisTool::HOVER_MODE); if (!currentNode() || (!image()->wrapAroundModePermitted() && !image()->bounds().contains(m_startPos))) { return; } - bool useFastMode = m_useFastMode->isChecked(); + Qt::KeyboardModifiers fillOnlySelectionModifier = Qt::AltModifier; // Not sure where to keep it if (keysAtStart == fillOnlySelectionModifier) { m_fillOnlySelection = true; } keysAtStart = Qt::NoModifier; // libs/ui/tool/kis_tool_select_base.h cleans it up in endPrimaryAction so i do it too KisProcessingApplicator applicator(currentImage(), currentNode(), KisProcessingApplicator::SUPPORTS_WRAPAROUND_MODE, KisImageSignalVector() << ModifiedSignal, kundo2_i18n("Flood Fill")); KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image(), currentNode(), this->canvas()->resourceManager()); KisPaintDeviceSP refPaintDevice = 0; KisImageWSP currentImageWSP = currentImage(); KisNodeSP currentRoot = currentImageWSP->root(); KisImageSP refImage = KisMergeLabeledLayersCommand::createRefImage(image(), "Fill Tool Reference Image"); if (m_sampleLayersMode == SAMPLE_LAYERS_MODE_ALL) { refPaintDevice = currentImage()->projection(); } else if (m_sampleLayersMode == SAMPLE_LAYERS_MODE_CURRENT) { refPaintDevice = currentNode()->paintDevice(); } else if (m_sampleLayersMode == SAMPLE_LAYERS_MODE_COLOR_LABELED) { refPaintDevice = KisMergeLabeledLayersCommand::createRefPaintDevice(image(), "Fill Tool Reference Result Paint Device"); applicator.applyCommand(new KisMergeLabeledLayersCommand(refImage, refPaintDevice, currentRoot, m_selectedColors), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); } KIS_ASSERT(refPaintDevice); QTransform transform; transform.rotate(m_patternRotation); transform.scale(m_patternScale, m_patternScale); resources->setFillTransform(transform); KisProcessingVisitorSP visitor = new FillProcessingVisitor(refPaintDevice, m_startPos, resources->activeSelection(), resources, - useFastMode, + m_useFastMode, m_usePattern, m_fillOnlySelection, + m_useSelectionAsBoundary, m_feather, m_sizemod, m_threshold, false, /* use the current device (unmerged) */ false); applicator.applyVisitor(visitor, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.end(); m_fillOnlySelection = m_checkFillSelection->isChecked(); } QWidget* KisToolFill::createOptionWidget() { QWidget *widget = KisToolPaint::createOptionWidget(); widget->setObjectName(toolId() + " option widget"); QLabel *lbl_fastMode = new QLabel(i18n("Fast mode: "), widget); - m_useFastMode = new QCheckBox(QString(), widget); - m_useFastMode->setToolTip( + m_checkUseFastMode = new QCheckBox(QString(), widget); + m_checkUseFastMode->setToolTip( i18n("Fills area faster, but does not take composition " "mode into account. Selections and other extended " "features will also be disabled.")); QLabel *lbl_threshold = new QLabel(i18n("Threshold: "), widget); m_slThreshold = new KisSliderSpinBox(widget); m_slThreshold->setObjectName("int_widget"); m_slThreshold->setRange(1, 100); m_slThreshold->setPageStep(3); QLabel *lbl_sizemod = new QLabel(i18n("Grow selection: "), widget); m_sizemodWidget = new KisSliderSpinBox(widget); m_sizemodWidget->setObjectName("sizemod"); m_sizemodWidget->setRange(-40, 40); m_sizemodWidget->setSingleStep(1); m_sizemodWidget->setSuffix(i18n(" px")); QLabel *lbl_feather = new QLabel(i18n("Feathering radius: "), widget); m_featherWidget = new KisSliderSpinBox(widget); m_featherWidget->setObjectName("feather"); m_featherWidget->setRange(0, 40); m_featherWidget->setSingleStep(1); m_featherWidget->setSuffix(i18n(" px")); QLabel *lbl_usePattern = new QLabel(i18n("Use pattern:"), widget); m_checkUsePattern = new QCheckBox(QString(), widget); m_checkUsePattern->setToolTip(i18n("When checked do not use the foreground color, but the pattern selected to fill with")); QLabel *lbl_patternRotation = new QLabel(i18n("Rotate:"), widget); m_sldPatternRotate = new KisDoubleSliderSpinBox(widget); m_sldPatternRotate->setObjectName("patternrotate"); m_sldPatternRotate->setRange(0, 360, 2); m_sldPatternRotate->setSingleStep(1.0); m_sldPatternRotate->setSuffix(QChar(Qt::Key_degree)); QLabel *lbl_patternScale = new QLabel(i18n("Scale:"), widget); m_sldPatternScale = new KisDoubleSliderSpinBox(widget); m_sldPatternScale->setObjectName("patternscale"); m_sldPatternScale->setRange(0, 500, 2); m_sldPatternScale->setSingleStep(1.0); m_sldPatternScale->setSuffix(QChar(Qt::Key_Percent)); QLabel *lbl_sampleLayers = new QLabel(i18nc("This is a label before a combobox with different choices regarding which layers " "to take into considerationg when calculating the area to fill. " "Options together with the label are: /Sample current layer/ /Sample all layers/ " "/Sample color labeled layers/. Sample is a verb here and means something akin to 'take into account'.", "Sample:"), widget); m_cmbSampleLayersMode = new QComboBox(widget); m_cmbSampleLayersMode->addItem(sampleLayerModeToUserString(SAMPLE_LAYERS_MODE_CURRENT), SAMPLE_LAYERS_MODE_CURRENT); m_cmbSampleLayersMode->addItem(sampleLayerModeToUserString(SAMPLE_LAYERS_MODE_ALL), SAMPLE_LAYERS_MODE_ALL); m_cmbSampleLayersMode->addItem(sampleLayerModeToUserString(SAMPLE_LAYERS_MODE_COLOR_LABELED), SAMPLE_LAYERS_MODE_COLOR_LABELED); m_cmbSampleLayersMode->setEditable(false); QLabel *lbl_cmbLabel = new QLabel(i18nc("This is a string in tool options for Fill Tool to describe a combobox about " "a choice of color labels that a layer can be marked with. Those color labels " "will be used for calculating the area to fill.", "Labels used:"), widget); m_cmbSelectedLabels = new KisColorFilterCombo(widget, false, false); m_cmbSelectedLabels->updateAvailableLabels(currentImage().isNull() ? KisNodeSP() : currentImage()->root()); QLabel *lbl_fillSelection = new QLabel(i18n("Fill entire selection:"), widget); m_checkFillSelection = new QCheckBox(QString(), widget); m_checkFillSelection->setToolTip(i18n("When checked do not look at the current layer colors, but just fill all of the selected area")); - connect (m_useFastMode , SIGNAL(toggled(bool)) , this, SLOT(slotSetUseFastMode(bool))); + QLabel *lbl_useSelectionAsBoundary = new QLabel(i18nc("Description for a checkbox in a Fill Tool to use selection borders as boundary when filling", "Use selection as boundary:"), widget); + m_checkUseSelectionAsBoundary = new QCheckBox(QString(), widget); + m_checkUseSelectionAsBoundary->setToolTip(i18nc("Tooltip for 'Use selection as boundary' checkbox", "When checked, use selection borders as boundary when filling")); + + + + connect (m_checkUseFastMode , SIGNAL(toggled(bool)) , this, SLOT(slotSetUseFastMode(bool))); connect (m_slThreshold , SIGNAL(valueChanged(int)), this, SLOT(slotSetThreshold(int))); connect (m_sizemodWidget , SIGNAL(valueChanged(int)), this, SLOT(slotSetSizemod(int))); connect (m_featherWidget , SIGNAL(valueChanged(int)), this, SLOT(slotSetFeather(int))); connect (m_checkUsePattern , SIGNAL(toggled(bool)) , this, SLOT(slotSetUsePattern(bool))); connect (m_checkFillSelection, SIGNAL(toggled(bool)) , this, SLOT(slotSetFillSelection(bool))); + connect (m_checkUseSelectionAsBoundary, SIGNAL(toggled(bool)) , this, SLOT(slotSetUseSelectionAsBoundary(bool))); + connect (m_cmbSampleLayersMode , SIGNAL(currentIndexChanged(int)), this, SLOT(slotSetSampleLayers(int))); connect (m_cmbSelectedLabels , SIGNAL(selectedColorsChanged()), this, SLOT(slotSetSelectedColorLabels())); connect (m_sldPatternRotate , SIGNAL(valueChanged(qreal)), this, SLOT(slotSetPatternRotation(qreal))); connect (m_sldPatternScale , SIGNAL(valueChanged(qreal)), this, SLOT(slotSetPatternScale(qreal))); - addOptionWidgetOption(m_useFastMode, lbl_fastMode); + addOptionWidgetOption(m_checkUseFastMode, lbl_fastMode); addOptionWidgetOption(m_slThreshold, lbl_threshold); addOptionWidgetOption(m_sizemodWidget , lbl_sizemod); addOptionWidgetOption(m_featherWidget , lbl_feather); addOptionWidgetOption(m_checkFillSelection, lbl_fillSelection); + addOptionWidgetOption(m_checkUseSelectionAsBoundary, lbl_useSelectionAsBoundary); addOptionWidgetOption(m_cmbSampleLayersMode, lbl_sampleLayers); addOptionWidgetOption(m_cmbSelectedLabels, lbl_cmbLabel); addOptionWidgetOption(m_checkUsePattern, lbl_usePattern); addOptionWidgetOption(m_sldPatternRotate, lbl_patternRotation); addOptionWidgetOption(m_sldPatternScale, lbl_patternScale); updateGUI(); widget->setFixedHeight(widget->sizeHint().height()); // load configuration options - m_useFastMode->setChecked(m_configGroup.readEntry("useFastMode", false)); + m_checkUseFastMode->setChecked(m_configGroup.readEntry("useFastMode", false)); m_slThreshold->setValue(m_configGroup.readEntry("thresholdAmount", 80)); m_sizemodWidget->setValue(m_configGroup.readEntry("growSelection", 0)); m_featherWidget->setValue(m_configGroup.readEntry("featherAmount", 0)); m_checkUsePattern->setChecked(m_configGroup.readEntry("usePattern", false)); if (m_configGroup.hasKey("sampleLayersMode")) { m_sampleLayersMode = m_configGroup.readEntry("sampleLayersMode", SAMPLE_LAYERS_MODE_CURRENT); setCmbSampleLayersMode(m_sampleLayersMode); } else { // if neither option is present in the configuration, it will fall back to CURRENT bool sampleMerged = m_configGroup.readEntry("sampleMerged", false); m_sampleLayersMode = sampleMerged ? SAMPLE_LAYERS_MODE_ALL : SAMPLE_LAYERS_MODE_CURRENT; setCmbSampleLayersMode(m_sampleLayersMode); } m_checkFillSelection->setChecked(m_configGroup.readEntry("fillSelection", false)); + m_checkUseSelectionAsBoundary->setChecked(m_configGroup.readEntry("useSelectionAsBoundary", false)); m_sldPatternRotate->setValue(m_configGroup.readEntry("patternRotate", 0.0)); m_sldPatternScale->setValue(m_configGroup.readEntry("patternScale", 100.0)); + // manually set up all variables in case there were no signals when setting value + m_feather = m_featherWidget->value(); + m_sizemod = m_sizemodWidget->value(); + m_threshold = m_slThreshold->value(); + m_useFastMode = m_checkUseFastMode->isChecked(); + m_fillOnlySelection = m_checkFillSelection->isChecked(); + m_useSelectionAsBoundary = m_checkUseSelectionAsBoundary->isChecked(); + m_patternRotation = m_sldPatternRotate->value(); + m_patternScale = m_sldPatternScale->value(); + m_usePattern = m_checkUsePattern->isChecked(); + // m_sampleLayersMode is set manually above + // selectedColors are also set manually + + activateConnectionsToImage(); m_widgetsInitialized = true; return widget; } void KisToolFill::updateGUI() { - bool useAdvancedMode = !m_useFastMode->isChecked(); + bool useAdvancedMode = !m_checkUseFastMode->isChecked(); bool selectionOnly = m_checkFillSelection->isChecked(); - m_useFastMode->setEnabled(!selectionOnly); + m_checkUseFastMode->setEnabled(!selectionOnly); m_slThreshold->setEnabled(!selectionOnly); m_sizemodWidget->setEnabled(!selectionOnly && useAdvancedMode); m_featherWidget->setEnabled(!selectionOnly && useAdvancedMode); m_checkUsePattern->setEnabled(useAdvancedMode); m_sldPatternRotate->setEnabled((m_checkUsePattern->isChecked() && useAdvancedMode)); m_sldPatternScale->setEnabled((m_checkUsePattern->isChecked() && useAdvancedMode)); m_cmbSampleLayersMode->setEnabled(!selectionOnly && useAdvancedMode); + m_checkUseSelectionAsBoundary->setEnabled(!selectionOnly && useAdvancedMode); + bool sampleLayersModeIsColorLabeledLayers = m_cmbSampleLayersMode->currentData().toString() == SAMPLE_LAYERS_MODE_COLOR_LABELED; m_cmbSelectedLabels->setEnabled(!selectionOnly && useAdvancedMode && sampleLayersModeIsColorLabeledLayers); } QString KisToolFill::sampleLayerModeToUserString(QString sampleLayersModeId) { QString currentLayer = i18nc("Option in fill tool: take only the current layer into account when calculating the area to fill", "Current Layer"); if (sampleLayersModeId == SAMPLE_LAYERS_MODE_CURRENT) { return currentLayer; } else if (sampleLayersModeId == SAMPLE_LAYERS_MODE_ALL) { return i18nc("Option in fill tool: take all layers (merged) into account when calculating the area to fill", "All Layers"); } else if (sampleLayersModeId == SAMPLE_LAYERS_MODE_COLOR_LABELED) { return i18nc("Option in fill tool: take all layers that were labeled with a color label (more precisely: all those layers merged)" " into account when calculating the area to fill", "Color Labeled Layers"); } KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(false, currentLayer); return currentLayer; } void KisToolFill::setCmbSampleLayersMode(QString sampleLayersModeId) { for (int i = 0; i < m_cmbSampleLayersMode->count(); i++) { if (m_cmbSampleLayersMode->itemData(i).toString() == sampleLayersModeId) { m_cmbSampleLayersMode->setCurrentIndex(i); break; } } m_sampleLayersMode = sampleLayersModeId; updateGUI(); } void KisToolFill::activateConnectionsToImage() { auto *kisCanvas = dynamic_cast(canvas()); KIS_SAFE_ASSERT_RECOVER_RETURN(kisCanvas); KisDocument *doc = kisCanvas->imageView()->document(); KisShapeController *kritaShapeController = dynamic_cast(doc->shapeController()); m_dummiesFacade = static_cast(kritaShapeController); if (m_dummiesFacade) { m_imageConnections.addConnection(m_dummiesFacade, SIGNAL(sigEndInsertDummy(KisNodeDummy*)), &m_colorLabelCompressor, SLOT(start())); m_imageConnections.addConnection(m_dummiesFacade, SIGNAL(sigEndRemoveDummy()), &m_colorLabelCompressor, SLOT(start())); m_imageConnections.addConnection(m_dummiesFacade, SIGNAL(sigDummyChanged(KisNodeDummy*)), &m_colorLabelCompressor, SLOT(start())); } } void KisToolFill::deactivateConnectionsToImage() { m_imageConnections.clear(); } void KisToolFill::slotSetUseFastMode(bool value) { + m_useFastMode = value; updateGUI(); m_configGroup.writeEntry("useFastMode", value); } void KisToolFill::slotSetThreshold(int threshold) { m_threshold = threshold; m_configGroup.writeEntry("thresholdAmount", threshold); } void KisToolFill::slotSetUsePattern(bool state) { m_usePattern = state; m_sldPatternScale->setEnabled(state); m_sldPatternRotate->setEnabled(state); m_configGroup.writeEntry("usePattern", state); } void KisToolFill::slotSetSampleLayers(int index) { Q_UNUSED(index); m_sampleLayersMode = m_cmbSampleLayersMode->currentData(Qt::UserRole).toString(); updateGUI(); m_configGroup.writeEntry("sampleLayersMode", m_sampleLayersMode); } void KisToolFill::slotSetSelectedColorLabels() { m_selectedColors = m_cmbSelectedLabels->selectedColors(); } void KisToolFill::slotSetPatternScale(qreal scale) { m_patternScale = scale*0.01; m_configGroup.writeEntry("patternScale", scale); } void KisToolFill::slotSetPatternRotation(qreal rotate) { m_patternRotation = rotate; m_configGroup.writeEntry("patternRotate", rotate); } void KisToolFill::slotSetFillSelection(bool state) { m_fillOnlySelection = state; m_configGroup.writeEntry("fillSelection", state); updateGUI(); } +void KisToolFill::slotSetUseSelectionAsBoundary(bool state) +{ + m_useSelectionAsBoundary = state; + m_configGroup.writeEntry("useSelectionAsBoundary", state); + updateGUI(); +} + void KisToolFill::slotSetSizemod(int sizemod) { m_sizemod = sizemod; m_configGroup.writeEntry("growSelection", sizemod); } void KisToolFill::slotSetFeather(int feather) { m_feather = feather; m_configGroup.writeEntry("featherAmount", feather); } diff --git a/plugins/tools/basictools/kis_tool_fill.h b/plugins/tools/basictools/kis_tool_fill.h index 3370614637..9a64f7d90f 100644 --- a/plugins/tools/basictools/kis_tool_fill.h +++ b/plugins/tools/basictools/kis_tool_fill.h @@ -1,154 +1,158 @@ /* * kis_tool_fill.h - part of Krayon^Krita * * Copyright (c) 2004 Bart Coppens * * 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_TOOL_FILL_H_ #define KIS_TOOL_FILL_H_ #include "kis_tool_paint.h" #include #include #include #include #include #include #include #include #include class QWidget; class QCheckBox; class KisSliderSpinBox; class KisDoubleSliderSpinBox; class KoCanvasBase; class KisColorFilterCombo; class KisDummiesFacadeBase; class KisToolFill : public KisToolPaint { Q_OBJECT public: KisToolFill(KoCanvasBase * canvas); ~KisToolFill() override; void beginPrimaryAction(KoPointerEvent *event) override; void endPrimaryAction(KoPointerEvent *event) override; QWidget * createOptionWidget() override; public Q_SLOTS: void activate(ToolActivation toolActivation, const QSet &shapes) override; void deactivate() override; void slotSetUseFastMode(bool); void slotSetThreshold(int); void slotSetUsePattern(bool); void slotSetFillSelection(bool); + void slotSetUseSelectionAsBoundary(bool); void slotSetSizemod(int); void slotSetFeather(int); void slotSetSampleLayers(int index); void slotSetSelectedColorLabels(); void slotSetPatternScale(qreal scale); void slotSetPatternRotation(qreal rotate); protected Q_SLOTS: void resetCursorStyle() override; void slotUpdateAvailableColorLabels(); protected: bool wantsAutoScroll() const override { return false; } private: void updateGUI(); QString sampleLayerModeToUserString(QString sampleLayersModeId); void setCmbSampleLayersMode(QString sampleLayersModeId); void activateConnectionsToImage(); void deactivateConnectionsToImage(); private: QString SAMPLE_LAYERS_MODE_CURRENT = {"currentLayer"}; QString SAMPLE_LAYERS_MODE_ALL = {"allLayers"}; QString SAMPLE_LAYERS_MODE_COLOR_LABELED = {"colorLabeledLayers"}; private: Qt::KeyboardModifiers keysAtStart; int m_feather; int m_sizemod; QPoint m_startPos; int m_threshold; bool m_usePattern; bool m_fillOnlySelection; + bool m_useSelectionAsBoundary; + bool m_useFastMode; QString m_sampleLayersMode; QList m_selectedColors; qreal m_patternRotation; qreal m_patternScale; bool m_widgetsInitialized {false}; - QCheckBox *m_useFastMode; + QCheckBox *m_checkUseFastMode; KisSliderSpinBox *m_slThreshold; KisSliderSpinBox *m_sizemodWidget; KisSliderSpinBox *m_featherWidget; KisDoubleSliderSpinBox *m_sldPatternRotate; KisDoubleSliderSpinBox *m_sldPatternScale; QCheckBox *m_checkUsePattern; QCheckBox *m_checkFillSelection; + QCheckBox *m_checkUseSelectionAsBoundary; QComboBox *m_cmbSampleLayersMode; KisColorFilterCombo *m_cmbSelectedLabels; KisSignalCompressor m_colorLabelCompressor; KisDummiesFacadeBase* m_dummiesFacade; KisSignalAutoConnectionsStore m_imageConnections; KConfigGroup m_configGroup; }; #include "KisToolPaintFactoryBase.h" class KisToolFillFactory : public KisToolPaintFactoryBase { public: KisToolFillFactory() : KisToolPaintFactoryBase("KritaFill/KisToolFill") { setToolTip(i18n("Fill Tool")); setSection(TOOL_TYPE_FILL); setPriority(0); setActivationShapeId(KRITA_TOOL_ACTIVATION_ID); setIconName(koIconNameCStr("krita_tool_color_fill")); setShortcut( QKeySequence( Qt::Key_F ) ); setPriority(14); } ~KisToolFillFactory() override {} KoToolBase * createTool(KoCanvasBase *canvas) override { return new KisToolFill(canvas); } }; #endif //__filltool_h__ diff --git a/plugins/tools/selectiontools/kis_tool_select_contiguous.cc b/plugins/tools/selectiontools/kis_tool_select_contiguous.cc index 3deba3a300..42267b858c 100644 --- a/plugins/tools/selectiontools/kis_tool_select_contiguous.cc +++ b/plugins/tools/selectiontools/kis_tool_select_contiguous.cc @@ -1,278 +1,307 @@ /* * kis_tool_select_contiguous - part of Krayon^WKrita * * Copyright (c) 1999 Michael Koch * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2012 José Luis Vergara * Copyright (c) 2015 Michael Abrahams * * 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_tool_select_contiguous.h" #include #include #include #include #include #include #include #include #include #include "KoPointerEvent.h" #include "KoViewConverter.h" #include "kis_cursor.h" #include "kis_selection_manager.h" #include "kis_image.h" #include "canvas/kis_canvas2.h" #include "kis_layer.h" #include "kis_selection_options.h" #include "kis_paint_device.h" #include "kis_fill_painter.h" #include "kis_pixel_selection.h" #include "kis_selection_tool_helper.h" #include "kis_slider_spin_box.h" #include "tiles3/kis_hline_iterator.h" #include "commands_new/KisMergeLabeledLayersCommand.h" #include "kis_image.h" #include "kis_undo_stores.h" #include "kis_resources_snapshot.h" #include "kis_processing_applicator.h" #include #include "kis_command_utils.h" KisToolSelectContiguous::KisToolSelectContiguous(KoCanvasBase *canvas) : KisToolSelect(canvas, KisCursor::load("tool_contiguous_selection_cursor.png", 6, 6), i18n("Contiguous Area Selection")), m_fuzziness(20), m_sizemod(0), m_feather(0) { setObjectName("tool_select_contiguous"); } KisToolSelectContiguous::~KisToolSelectContiguous() { } void KisToolSelectContiguous::activate(ToolActivation toolActivation, const QSet &shapes) { KisToolSelect::activate(toolActivation, shapes); m_configGroup = KSharedConfig::openConfig()->group(toolId()); } void KisToolSelectContiguous::beginPrimaryAction(KoPointerEvent *event) { KisToolSelectBase::beginPrimaryAction(event); KisPaintDeviceSP dev; if (!currentNode() || !(dev = currentNode()->paintDevice()) || !currentNode()->visible() || !selectionEditable()) { event->ignore(); return; } if (KisToolSelect::selectionDidMove()) { return; } QApplication::setOverrideCursor(KisCursor::waitCursor()); // ------------------------------- KisProcessingApplicator applicator(currentImage(), currentNode(), KisProcessingApplicator::NONE, KisImageSignalVector() << ModifiedSignal, kundo2_i18n("Select Contiguous Area")); QPoint pos = convertToImagePixelCoordFloored(event); QRect rc = currentImage()->bounds(); KisImageSP image = currentImage(); KisPaintDeviceSP sourceDevice; if (sampleLayersMode() == SampleAllLayers) { sourceDevice = image->projection(); } else if (sampleLayersMode() == SampleColorLabeledLayers) { KisImageSP refImage = KisMergeLabeledLayersCommand::createRefImage(image, "Contiguous Selection Tool Reference Image"); sourceDevice = KisMergeLabeledLayersCommand::createRefPaintDevice( image, "Contiguous Selection Tool Reference Result Paint Device"); KisMergeLabeledLayersCommand* command = new KisMergeLabeledLayersCommand(refImage, sourceDevice, image->root(), colorLabelsSelected()); applicator.applyCommand(command, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); } else { // Sample Current Layer sourceDevice = dev; } KisPixelSelectionSP selection = KisPixelSelectionSP(new KisPixelSelection(new KisSelectionDefaultBounds(dev))); + bool antiAlias = antiAliasSelection(); int fuzziness = m_fuzziness; int feather = m_feather; int sizemod = m_sizemod; + bool useSelectionAsBoundary = m_useSelectionAsBoundary; + + KisCanvas2 * kisCanvas = dynamic_cast(canvas()); + KIS_SAFE_ASSERT_RECOVER(kisCanvas) { + applicator.cancel(); + QApplication::restoreOverrideCursor(); + return; + }; + + KisPixelSelectionSP existingSelection; + if (kisCanvas->imageView() && kisCanvas->imageView()->selection()) + { + existingSelection = kisCanvas->imageView()->selection()->pixelSelection(); + } KUndo2Command* cmd = new KisCommandUtils::LambdaCommand( - [dev, rc, fuzziness, feather, sizemod, selection, pos, sourceDevice, antiAlias] () mutable -> KUndo2Command* { + [dev, rc, fuzziness, feather, sizemod, useSelectionAsBoundary, + selection, pos, sourceDevice, antiAlias, existingSelection] () mutable -> KUndo2Command* { KisFillPainter fillpainter(dev); fillpainter.setHeight(rc.height()); fillpainter.setWidth(rc.width()); fillpainter.setFillThreshold(fuzziness); fillpainter.setFeather(feather); fillpainter.setSizemod(sizemod); + fillpainter.setUseSelectionAsBoundary((existingSelection.isNull() || existingSelection->isEmpty()) ? false : useSelectionAsBoundary); - fillpainter.createFloodSelection(selection, pos.x(), pos.y(), sourceDevice); + fillpainter.createFloodSelection(selection, pos.x(), pos.y(), sourceDevice, existingSelection); // If we're not antialiasing, threshold the entire selection if (!antiAlias) { const QRect r = selection->selectedExactRect(); KisSequentialIterator it (selection, r); while(it.nextPixel()) { if (*it.rawData() > 0) { *it.rawData() = OPACITY_OPAQUE_U8; } } } selection->invalidateOutlineCache(); return 0; }); applicator.applyCommand(cmd, KisStrokeJobData::SEQUENTIAL); - KisCanvas2 * kisCanvas = dynamic_cast(canvas()); - KIS_SAFE_ASSERT_RECOVER(kisCanvas) { - applicator.cancel(); - QApplication::restoreOverrideCursor(); - return; - }; + KisSelectionToolHelper helper(kisCanvas, kundo2_i18n("Select Contiguous Area")); helper.selectPixelSelection(applicator, selection, selectionAction()); applicator.end(); QApplication::restoreOverrideCursor(); } void KisToolSelectContiguous::paint(QPainter &painter, const KoViewConverter &converter) { Q_UNUSED(painter); Q_UNUSED(converter); } void KisToolSelectContiguous::slotSetFuzziness(int fuzziness) { m_fuzziness = fuzziness; m_configGroup.writeEntry("fuzziness", fuzziness); } void KisToolSelectContiguous::slotSetSizemod(int sizemod) { m_sizemod = sizemod; m_configGroup.writeEntry("sizemod", sizemod); } void KisToolSelectContiguous::slotSetFeather(int feather) { m_feather = feather; m_configGroup.writeEntry("feather", feather); } +void KisToolSelectContiguous::slotSetUseSelectionAsBoundary(bool useSelectionAsBoundary) +{ + m_useSelectionAsBoundary = useSelectionAsBoundary; + m_configGroup.writeEntry("useSelectionAsBoundary", useSelectionAsBoundary); +} + QWidget* KisToolSelectContiguous::createOptionWidget() { KisToolSelectBase::createOptionWidget(); KisSelectionOptions *selectionWidget = selectionOptionWidget(); QVBoxLayout * l = dynamic_cast(selectionWidget->layout()); Q_ASSERT(l); if (l) { QGridLayout * gridLayout = new QGridLayout(); l->insertLayout(1, gridLayout); QLabel * lbl = new QLabel(i18n("Fuzziness: "), selectionWidget); gridLayout->addWidget(lbl, 0, 0, 1, 1); KisSliderSpinBox *input = new KisSliderSpinBox(selectionWidget); Q_CHECK_PTR(input); input->setObjectName("fuzziness"); input->setRange(1, 100); input->setSingleStep(1); input->setExponentRatio(2); gridLayout->addWidget(input, 0, 1, 1, 1); lbl = new QLabel(i18n("Grow/shrink selection: "), selectionWidget); gridLayout->addWidget(lbl, 1, 0, 1, 1); KisSliderSpinBox *sizemod = new KisSliderSpinBox(selectionWidget); Q_CHECK_PTR(sizemod); sizemod->setObjectName("sizemod"); //grow/shrink selection sizemod->setRange(-40, 40); sizemod->setSingleStep(1); gridLayout->addWidget(sizemod, 1, 1, 1, 1); lbl = new QLabel(i18n("Feathering radius: "), selectionWidget); gridLayout->addWidget(lbl, 2, 0, 1, 1); KisSliderSpinBox *feather = new KisSliderSpinBox(selectionWidget); Q_CHECK_PTR(feather); feather->setObjectName("feathering"); feather->setRange(0, 40); feather->setSingleStep(1); gridLayout->addWidget(feather, 2, 1, 1, 1); + lbl = new QLabel(i18n("Use selection as boundary: "), selectionWidget); + gridLayout->addWidget(lbl, 3, 0, 1, 1); + + QCheckBox *useSelectionAsBoundary = new QCheckBox(selectionWidget); + Q_CHECK_PTR(useSelectionAsBoundary); + gridLayout->addWidget(useSelectionAsBoundary, 3, 1, 1, 1); + connect (input , SIGNAL(valueChanged(int)), this, SLOT(slotSetFuzziness(int))); connect (sizemod, SIGNAL(valueChanged(int)), this, SLOT(slotSetSizemod(int))); connect (feather, SIGNAL(valueChanged(int)), this, SLOT(slotSetFeather(int))); + connect (useSelectionAsBoundary, SIGNAL(toggled(bool)), this, SLOT(slotSetUseSelectionAsBoundary(bool))); + selectionWidget->attachToImage(image(), dynamic_cast(canvas())); m_widgetHelper.setConfigGroupForExactTool(toolId()); // load configuration settings into tool options input->setValue(m_configGroup.readEntry("fuzziness", 20)); // fuzziness sizemod->setValue( m_configGroup.readEntry("sizemod", 0)); //grow/shrink sizemod->setSuffix(i18n(" px")); feather->setValue(m_configGroup.readEntry("feather", 0)); feather->setSuffix(i18n(" px")); + useSelectionAsBoundary->setChecked(m_configGroup.readEntry("useSelectionAsBoundary", false)); + } return selectionWidget; } void KisToolSelectContiguous::resetCursorStyle() { if (selectionAction() == SELECTION_ADD) { useCursor(KisCursor::load("tool_contiguous_selection_cursor_add.png", 6, 6)); } else if (selectionAction() == SELECTION_SUBTRACT) { useCursor(KisCursor::load("tool_contiguous_selection_cursor_sub.png", 6, 6)); } else { KisToolSelect::resetCursorStyle(); } } diff --git a/plugins/tools/selectiontools/kis_tool_select_contiguous.h b/plugins/tools/selectiontools/kis_tool_select_contiguous.h index 5163e48ac0..6aca20b3f2 100644 --- a/plugins/tools/selectiontools/kis_tool_select_contiguous.h +++ b/plugins/tools/selectiontools/kis_tool_select_contiguous.h @@ -1,96 +1,98 @@ /* * kis_tool_select_contiguous.h - part of KImageShop^WKrayon^Krita * * Copyright (c) 1999 Michael Koch * Copyright (c) 2002 Patrick Julien * Copyright (c) 2015 Michael Abrahams * * 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_TOOL_SELECT_CONTIGUOUS_H__ #define __KIS_TOOL_SELECT_CONTIGUOUS_H__ #include "KisSelectionToolFactoryBase.h" #include "kis_tool_select_base.h" #include #include #include /** * The 'magic wand' selection tool -- in fact just * a floodfill that only creates a selection. */ class KisToolSelectContiguous : public KisToolSelect { Q_OBJECT public: KisToolSelectContiguous(KoCanvasBase *canvas); ~KisToolSelectContiguous() override; QWidget* createOptionWidget() override; void paint(QPainter &painter, const KoViewConverter &converter) override; void beginPrimaryAction(KoPointerEvent *event) override; void resetCursorStyle() override; protected: bool wantsAutoScroll() const override { return false; } bool isPixelOnly() const override { return true; } bool usesColorLabels() const override { return true; } public Q_SLOTS: void activate(ToolActivation toolActivation, const QSet &shapes) override; virtual void slotSetFuzziness(int); virtual void slotSetSizemod(int); virtual void slotSetFeather(int); + virtual void slotSetUseSelectionAsBoundary(bool); //virtual bool antiAliasSelection(); protected: using KisToolSelectBase::m_widgetHelper; private: int m_fuzziness; int m_sizemod; int m_feather; + bool m_useSelectionAsBoundary; KConfigGroup m_configGroup; }; class KisToolSelectContiguousFactory : public KisSelectionToolFactoryBase { public: KisToolSelectContiguousFactory() : KisSelectionToolFactoryBase("KisToolSelectContiguous") { setToolTip(i18n("Contiguous Selection Tool")); setSection(TOOL_TYPE_SELECTION); setIconName(koIconNameCStr("tool_contiguous_selection")); setPriority(4); setActivationShapeId(KRITA_TOOL_ACTIVATION_ID); } ~KisToolSelectContiguousFactory() override {} KoToolBase * createTool(KoCanvasBase *canvas) override { return new KisToolSelectContiguous(canvas); } }; #endif //__KIS_TOOL_SELECT_CONTIGUOUS_H__