diff --git a/libs/image/floodfill/kis_scanline_fill.cpp b/libs/image/floodfill/kis_scanline_fill.cpp index 56d21657e7..62388a382e 100644 --- a/libs/image/floodfill/kis_scanline_fill.cpp +++ b/libs/image/floodfill/kis_scanline_fill.cpp @@ -1,827 +1,899 @@ /* * 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); quint8* pixelPtr = const_cast(m_selectionIt->rawDataConst()); HashKeyType key = *reinterpret_cast(pixelPtr); quint8 result; typename HashType::iterator it = m_selectedness.find(key); if (it != m_selectedness.end()) { result = *it; } else { result = *pixelPtr; // if (m_threshold == 1) { // result = ; // if (memcmp(m_srcPixelPtr, pixelPtr, m_colorSpace->pixelSize()) == 0) { // result = 0; // } // else { // result = quint8_MAX; // } // } // else { // result = m_colorSpace->difference(m_srcPixelPtr, pixelPtr); // } m_selectedness.insert(key, result); } return result; } 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; } + qCritical() << "(" << x << "," << y << ") | " << result << " out of <" << MAX_SELECTED << MIN_SELECTED << "> | " + << selectionValue << m_threshold; + 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); + //qCritical() << "( " << x << ", " << y << ") |" << ppVar(diff) << ppVar(selectedness); + if (!useSmoothSelection) { + qCritical() << "result = " << ((diff <= m_threshold && selectedness > 0) ? MAX_SELECTED : MIN_SELECTED); + qCritical() << ppVar(diff <= m_threshold && selectedness > 0) + << ppVar(diff) << ppVar(m_threshold) << ppVar(selectedness); return (diff <= m_threshold && selectedness > 0) ? MAX_SELECTED : MIN_SELECTED; } else { quint8 selectionValue = qMax(0, m_threshold - diff); + //qCritical() << ppVar(selectionValue); + + quint8 result = MIN_SELECTED; + //qCritical() << ppVar(result); if (selectionValue > 0 && selectedness > 0) { qreal selectionNorm = qreal(selectionValue) / m_threshold; + //qCritical() << "if//" << ppVar(selectionNorm); result = MAX_SELECTED * selectionNorm; + //qCritical() << ppVar(result); } + //qCritical() << ppVar(result); + qCritical() << "(" << x << "," << y << ") | " << result << " out of " << ppVar(MAX_SELECTED) << ppVar(MIN_SELECTED); + if (result == 1) { + //result = MAX_SELECTED; + } 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(); + qCritical() << "void KisScanlineFill::fillColor(1) | " << ppVar(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(); + qCritical() << "void KisScanlineFill::fillSelection(KisPixelSelectionSP pixelSelection) | " << ppVar(pixelSize); + + if (pixelSize == 1) { + qCritical() << "void KisScanlineFill::fillSelection(KisPixelSelectionSP pixelSelection) | Using my police!!!\n"; + SelectionPolicyExtended, CopyToSelection, SelectednessPolicyOptimized> + policy(m_d->device, existingSelection, 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) { + qCritical() << "void KisScanlineFill::fillSelection(KisPixelSelectionSP pixelSelection) | Using my police!!!\n"; + SelectionPolicyExtended, CopyToSelection, SelectednessPolicyOptimized> + policy(m_d->device, existingSelection, srcColor, m_d->threshold); + policy.setDestinationSelection(pixelSelection); + runImpl(policy); + qCritical() << "DONE"; + qCritical() << ppVar(m_d->device->exactBounds()); + qCritical() << ppVar(pixelSelection->selectedExactRect()); + qCritical() << ppVar(existingSelection->exactBounds()); + qCritical() << "###"; + + } 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::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..824fa49070 100644 --- a/libs/image/kis_fill_painter.cc +++ b/libs/image/kis_fill_painter.cc @@ -1,358 +1,385 @@ /* * 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; } 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()); + ENTER_FUNCTION() << "here!!!"; + gc.fillColor(paintColor(), device()); } 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()); + qCritical() << "void KisFillPainter::genericFillStart" << ppVar(pixelSelection->selectedExactRect()); + KisSelectionSP newSelection = new KisSelection(pixelSelection->defaultBounds()); + qCritical() << "void KisFillPainter::genericFillStart" << ppVar(newSelection->selectedExactRect()); newSelection->pixelSelection()->applySelection(pixelSelection, SELECTION_REPLACE); + qCritical() << "void KisFillPainter::genericFillStart" << ppVar(newSelection->selectedExactRect()); m_fillSelection = newSelection; + qCritical() << "void KisFillPainter::genericFillStart" << ppVar(m_fillSelection->selectedExactRect()); } void KisFillPainter::genericFillEnd(KisPaintDeviceSP filled) { if (progressUpdater() && progressUpdater()->interrupted()) { + qCritical() << "interrupted!\n"; 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(); + qCritical() << "void KisFillPainter::genericFillEnd | " << ppVar(realSelection->selectedExactRect()); + qCritical() << "void KisFillPainter::genericFillEnd | " << ppVar(filled->exactBounds()); + qCritical() << "void KisFillPainter::genericFillEnd | " << ppVar(m_fillSelection->selectedExactRect()); + qCritical() << "void KisFillPainter::genericFillEnd | " << ppVar(m_fillSelection->pixelSelection()->selectedExactRect()); + + if (realSelection) { m_fillSelection->pixelSelection()->applySelection( realSelection->projection(), SELECTION_INTERSECT); + qCritical() << "void KisFillPainter::genericFillEnd | + " << ppVar(m_fillSelection->selectedExactRect()); + qCritical() << "void KisFillPainter::genericFillEnd | + " << ppVar(m_fillSelection->pixelSelection()->selectedExactRect()); } + qCritical() << "void KisFillPainter::genericFillEnd | " << ppVar(m_fillSelection->selectedExactRect()); + setSelection(m_fillSelection); bitBlt(0, 0, filled, 0, 0, m_width, m_height); setSelection(realSelection); + qCritical() << "void KisFillPainter::genericFillEnd | " << ppVar(this->device()->exactBounds()); + 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 (!qgetenv("SELBOUND").isEmpty()) { + 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)); } + qCritical() << "KisPixelSelectionSP KisFillPainter::createFloodSelection | " << ppVar(pixelSelection->selectedExactRect()); + return pixelSelection; } diff --git a/libs/image/kis_fill_painter.h b/libs/image/kis_fill_painter.h index 4235e8283c..2dcb495302 100644 --- a/libs/image/kis_fill_painter.h +++ b/libs/image/kis_fill_painter.h @@ -1,319 +1,321 @@ /* * 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; } 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; }; 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/plugins/tools/selectiontools/kis_tool_select_contiguous.cc b/plugins/tools/selectiontools/kis_tool_select_contiguous.cc index 3deba3a300..5021e88d51 100644 --- a/plugins/tools/selectiontools/kis_tool_select_contiguous.cc +++ b/plugins/tools/selectiontools/kis_tool_select_contiguous.cc @@ -1,278 +1,283 @@ /* * 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; + KisCanvas2 * kisCanvas = dynamic_cast(canvas()); + KIS_SAFE_ASSERT_RECOVER(kisCanvas) { + applicator.cancel(); + QApplication::restoreOverrideCursor(); + return; + }; + + KisPixelSelectionSP 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, 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.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); } 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); 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))); 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")); } 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(); } }