diff --git a/libs/image/kis_fill_painter.cc b/libs/image/kis_fill_painter.cc index 824fa49070..142e51de3c 100644 --- a/libs/image/kis_fill_painter.cc +++ b/libs/image/kis_fill_painter.cc @@ -1,385 +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); 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, 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, KisPaintDeviceSP existingSelection) { KisPixelSelectionSP newSelection = new KisPixelSelection(new KisSelectionDefaultBounds(device())); return createFloodSelection(newSelection, startX, startY, sourceDevice, existingSelection); } 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); - if (!qgetenv("SELBOUND").isEmpty()) { + if (!qgetenv("SELBOUND").isEmpty() || 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)); } 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 2dcb495302..1e041d3e0e 100644 --- a/libs/image/kis_fill_painter.h +++ b/libs/image/kis_fill_painter.h @@ -1,321 +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, 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, 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_