diff --git a/libs/image/kis_fill_painter.cc b/libs/image/kis_fill_painter.cc index 67db897851..7c461a97af 100644 --- a/libs/image/kis_fill_painter.cc +++ b/libs/image/kis_fill_painter.cc @@ -1,358 +1,358 @@ /* * 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()); } 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) +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(), QTransform()); + 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); 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 newSelection = new KisPixelSelection(new KisSelectionDefaultBounds(device())); return createFloodSelection(newSelection, startX, startY, sourceDevice); } KisPixelSelectionSP KisFillPainter::createFloodSelection(KisPixelSelectionSP pixelSelection, int startX, int startY, KisPaintDeviceSP sourceDevice) { 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_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 cb074ab7a1..4235e8283c 100644 --- a/libs/image/kis_fill_painter.h +++ b/libs/image/kis_fill_painter.h @@ -1,318 +1,319 @@ /* * 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); + 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); /** * 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); /** * 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/libs/ui/processing/fill_processing_visitor.cpp b/libs/ui/processing/fill_processing_visitor.cpp index 3fc5cf9d8b..a32b635baf 100644 --- a/libs/ui/processing/fill_processing_visitor.cpp +++ b/libs/ui/processing/fill_processing_visitor.cpp @@ -1,148 +1,148 @@ /* * 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, 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_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(), QTransform()); + 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()->bounds()); } 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.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); + 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/tool/kis_resources_snapshot.cpp b/libs/ui/tool/kis_resources_snapshot.cpp index 7cda8546fe..d42043ebab 100644 --- a/libs/ui/tool/kis_resources_snapshot.cpp +++ b/libs/ui/tool/kis_resources_snapshot.cpp @@ -1,441 +1,446 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_resources_snapshot.h" #include #include #include #include #include #include #include #include #include "kis_canvas_resource_provider.h" #include "filter/kis_filter_configuration.h" #include "kis_image.h" #include "kis_paint_device.h" #include "kis_paint_layer.h" #include "kis_selection.h" #include "kis_selection_mask.h" #include "kis_algebra_2d.h" struct KisResourcesSnapshot::Private { Private() : currentPattern(0) , currentGradient(0) , currentGenerator(0) , compositeOp(0) { } KisImageSP image; KisDefaultBoundsBaseSP bounds; KoColor currentFgColor; KoColor currentBgColor; KoPatternSP currentPattern; KoAbstractGradientSP currentGradient; KisPaintOpPresetSP currentPaintOpPreset; KisNodeSP currentNode; qreal currentExposure; KisFilterConfigurationSP currentGenerator; QPointF axesCenter; bool mirrorMaskHorizontal = false; bool mirrorMaskVertical = false; quint8 opacity = OPACITY_OPAQUE_U8; QString compositeOpId = COMPOSITE_OVER; const KoCompositeOp *compositeOp; KisPainter::StrokeStyle strokeStyle = KisPainter::StrokeStyleBrush; KisPainter::FillStyle fillStyle = KisPainter::FillStyleForegroundColor; QTransform fillTransform = QTransform(); bool globalAlphaLock = false; qreal effectiveZoom = 1.0; bool presetAllowsLod = false; KisSelectionSP selectionOverride; bool hasOverrideSelection = false; }; KisResourcesSnapshot::KisResourcesSnapshot(KisImageSP image, KisNodeSP currentNode, KoCanvasResourceProvider *resourceManager, KisDefaultBoundsBaseSP bounds) : m_d(new Private()) { m_d->image = image; if (!bounds) { bounds = new KisDefaultBounds(m_d->image); } m_d->bounds = bounds; m_d->currentFgColor = resourceManager->resource(KoCanvasResourceProvider::ForegroundColor).value(); m_d->currentBgColor = resourceManager->resource(KoCanvasResourceProvider::BackgroundColor).value(); m_d->currentPattern = resourceManager->resource(KisCanvasResourceProvider::CurrentPattern).value(); m_d->currentGradient = resourceManager->resource(KisCanvasResourceProvider::CurrentGradient).value(); /** * We should deep-copy the preset, so that long-running actions * will have correct brush parameters. Theoretically this cloning * can be expensive, but according to measurements, it takes * something like 0.1 ms for an average preset. */ KisPaintOpPresetSP p = resourceManager->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value(); if (p) { m_d->currentPaintOpPreset = resourceManager->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value()->cloneWithResourcesSnapshot(); } #ifdef HAVE_THREADED_TEXT_RENDERING_WORKAROUND KisPaintOpRegistry::instance()->preinitializePaintOpIfNeeded(m_d->currentPaintOpPreset); #endif /* HAVE_THREADED_TEXT_RENDERING_WORKAROUND */ m_d->currentExposure = resourceManager->resource(KisCanvasResourceProvider::HdrExposure).toDouble(); m_d->currentGenerator = resourceManager->resource(KisCanvasResourceProvider::CurrentGeneratorConfiguration).value(); if (m_d->currentGenerator) { m_d->currentGenerator = m_d->currentGenerator->cloneWithResourcesSnapshot(); } QPointF relativeAxesCenter(0.5, 0.5); if (m_d->image) { relativeAxesCenter = m_d->image->mirrorAxesCenter(); } m_d->axesCenter = KisAlgebra2D::relativeToAbsolute(relativeAxesCenter, m_d->bounds->bounds()); m_d->mirrorMaskHorizontal = resourceManager->resource(KisCanvasResourceProvider::MirrorHorizontal).toBool(); m_d->mirrorMaskVertical = resourceManager->resource(KisCanvasResourceProvider::MirrorVertical).toBool(); qreal normOpacity = resourceManager->resource(KisCanvasResourceProvider::Opacity).toDouble(); m_d->opacity = quint8(normOpacity * OPACITY_OPAQUE_U8); m_d->compositeOpId = resourceManager->resource(KisCanvasResourceProvider::CurrentEffectiveCompositeOp).toString(); setCurrentNode(currentNode); /** * Fill and Stroke styles are not a part of the resource manager * so the tools should set them manually * TODO: port stroke and fill styles to be a part * of the resource manager */ m_d->strokeStyle = KisPainter::StrokeStyleBrush; m_d->fillStyle = KisPainter::FillStyleNone; m_d->globalAlphaLock = resourceManager->resource(KisCanvasResourceProvider::GlobalAlphaLock).toBool(); m_d->effectiveZoom = resourceManager->resource(KisCanvasResourceProvider::EffectiveZoom).toDouble(); m_d->presetAllowsLod = resourceManager->resource(KisCanvasResourceProvider::EffectiveLodAvailablility).toBool(); } KisResourcesSnapshot::KisResourcesSnapshot(KisImageSP image, KisNodeSP currentNode, KisDefaultBoundsBaseSP bounds) : m_d(new Private()) { m_d->image = image; if (!bounds) { bounds = new KisDefaultBounds(m_d->image); } m_d->bounds = bounds; #ifdef HAVE_THREADED_TEXT_RENDERING_WORKAROUND KisPaintOpRegistry::instance()->preinitializePaintOpIfNeeded(m_d->currentPaintOpPreset); #endif /* HAVE_THREADED_TEXT_RENDERING_WORKAROUND */ QPointF relativeAxesCenter(0.5, 0.5); if (m_d->image) { relativeAxesCenter = m_d->image->mirrorAxesCenter(); } m_d->axesCenter = KisAlgebra2D::relativeToAbsolute(relativeAxesCenter, m_d->bounds->bounds()); m_d->opacity = OPACITY_OPAQUE_U8; setCurrentNode(currentNode); /** * Fill and Stroke styles are not a part of the resource manager * so the tools should set them manually * TODO: port stroke and fill styles to be a part * of the resource manager */ m_d->strokeStyle = KisPainter::StrokeStyleBrush; m_d->fillStyle = KisPainter::FillStyleNone; } KisResourcesSnapshot::~KisResourcesSnapshot() { delete m_d; } void KisResourcesSnapshot::setupPainter(KisPainter* painter) { painter->setPaintColor(m_d->currentFgColor); painter->setBackgroundColor(m_d->currentBgColor); painter->setGenerator(m_d->currentGenerator); painter->setPattern(m_d->currentPattern); painter->setGradient(m_d->currentGradient); QBitArray lockflags = channelLockFlags(); if (lockflags.size() > 0) { painter->setChannelFlags(lockflags); } painter->setOpacity(m_d->opacity); painter->setCompositeOp(m_d->compositeOp); painter->setMirrorInformation(m_d->axesCenter, m_d->mirrorMaskHorizontal, m_d->mirrorMaskVertical); painter->setStrokeStyle(m_d->strokeStyle); painter->setFillStyle(m_d->fillStyle); painter->setPatternTransform(m_d->fillTransform); /** * The paintOp should be initialized the last, because it may * ask the painter for some options while initialization */ painter->setPaintOpPreset(m_d->currentPaintOpPreset, m_d->currentNode, m_d->image); } void KisResourcesSnapshot::setupMaskingBrushPainter(KisPainter *painter) { KIS_SAFE_ASSERT_RECOVER_RETURN(painter->device()); KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->currentPaintOpPreset->hasMaskingPreset()); painter->setPaintColor(KoColor(Qt::white, painter->device()->colorSpace())); painter->setBackgroundColor(KoColor(Qt::black, painter->device()->colorSpace())); painter->setOpacity(OPACITY_OPAQUE_U8); painter->setChannelFlags(QBitArray()); // masked brush always paints in indirect mode painter->setCompositeOp(COMPOSITE_ALPHA_DARKEN); painter->setMirrorInformation(m_d->axesCenter, m_d->mirrorMaskHorizontal, m_d->mirrorMaskVertical); painter->setStrokeStyle(m_d->strokeStyle); /** * The paintOp should be initialized the last, because it may * ask the painter for some options while initialization */ painter->setPaintOpPreset(m_d->currentPaintOpPreset->createMaskingPreset(), m_d->currentNode, m_d->image); } KisPostExecutionUndoAdapter* KisResourcesSnapshot::postExecutionUndoAdapter() const { return m_d->image ? m_d->image->postExecutionUndoAdapter() : 0; } void KisResourcesSnapshot::setCurrentNode(KisNodeSP node) { m_d->currentNode = node; KisPaintDeviceSP device; if(m_d->currentNode && (device = m_d->currentNode->paintDevice())) { m_d->compositeOp = device->colorSpace()->compositeOp(m_d->compositeOpId); if(!m_d->compositeOp) { m_d->compositeOp = device->colorSpace()->compositeOp(COMPOSITE_OVER); } } } void KisResourcesSnapshot::setStrokeStyle(KisPainter::StrokeStyle strokeStyle) { m_d->strokeStyle = strokeStyle; } void KisResourcesSnapshot::setFillStyle(KisPainter::FillStyle fillStyle) { m_d->fillStyle = fillStyle; } void KisResourcesSnapshot::setFillTransform(QTransform transform) { m_d->fillTransform = transform; } KisNodeSP KisResourcesSnapshot::currentNode() const { return m_d->currentNode; } KisImageSP KisResourcesSnapshot::image() const { return m_d->image; } bool KisResourcesSnapshot::needsIndirectPainting() const { return !m_d->currentPaintOpPreset->settings()->paintIncremental(); } QString KisResourcesSnapshot::indirectPaintingCompositeOp() const { return m_d->currentPaintOpPreset ? m_d->currentPaintOpPreset->settings()->indirectPaintingCompositeOp() : COMPOSITE_ALPHA_DARKEN; } bool KisResourcesSnapshot::needsMaskingBrushRendering() const { return m_d->currentPaintOpPreset && m_d->currentPaintOpPreset->hasMaskingPreset(); } KisSelectionSP KisResourcesSnapshot::activeSelection() const { /** * It is possible to have/use the snapshot without the image. Such * usecase is present for example in the scratchpad. */ if (m_d->hasOverrideSelection) { return m_d->selectionOverride; } KisSelectionSP selection = m_d->image ? m_d->image->globalSelection() : 0; KisLayerSP layer = qobject_cast(m_d->currentNode.data()); KisSelectionMaskSP mask; if((layer = qobject_cast(m_d->currentNode.data()))) { selection = layer->selection(); } else if ((mask = dynamic_cast(m_d->currentNode.data())) && mask->selection() == selection) { selection = 0; } return selection; } bool KisResourcesSnapshot::needsAirbrushing() const { return ( m_d->currentPaintOpPreset && m_d->currentPaintOpPreset->settings() && m_d->currentPaintOpPreset->settings()->isAirbrushing()); } qreal KisResourcesSnapshot::airbrushingInterval() const { return ( m_d->currentPaintOpPreset && m_d->currentPaintOpPreset->settings() && m_d->currentPaintOpPreset->settings()->airbrushInterval()); } bool KisResourcesSnapshot::needsSpacingUpdates() const { return ( m_d->currentPaintOpPreset && m_d->currentPaintOpPreset->settings() && m_d->currentPaintOpPreset->settings()->useSpacingUpdates()); } void KisResourcesSnapshot::setOpacity(qreal opacity) { m_d->opacity = opacity * OPACITY_OPAQUE_U8; } quint8 KisResourcesSnapshot::opacity() const { return m_d->opacity; } const KoCompositeOp* KisResourcesSnapshot::compositeOp() const { return m_d->compositeOp; } QString KisResourcesSnapshot::compositeOpId() const { return m_d->compositeOpId; } KoPatternSP KisResourcesSnapshot::currentPattern() const { return m_d->currentPattern; } KoColor KisResourcesSnapshot::currentFgColor() const { return m_d->currentFgColor; } KoColor KisResourcesSnapshot::currentBgColor() const { return m_d->currentBgColor; } KisPaintOpPresetSP KisResourcesSnapshot::currentPaintOpPreset() const { return m_d->currentPaintOpPreset; } +QTransform KisResourcesSnapshot::fillTransform() const +{ + return m_d->fillTransform; +} + QBitArray KisResourcesSnapshot::channelLockFlags() const { QBitArray channelFlags; KisPaintLayer *paintLayer; if ((paintLayer = dynamic_cast(m_d->currentNode.data()))) { channelFlags = paintLayer->channelLockFlags(); if (m_d->globalAlphaLock) { if (channelFlags.isEmpty()) { channelFlags = paintLayer->colorSpace()->channelFlags(true, true); } channelFlags &= paintLayer->colorSpace()->channelFlags(true, false); } } return channelFlags; } qreal KisResourcesSnapshot::effectiveZoom() const { return m_d->effectiveZoom; } bool KisResourcesSnapshot::presetAllowsLod() const { return m_d->presetAllowsLod; } bool KisResourcesSnapshot::presetNeedsAsynchronousUpdates() const { return m_d->currentPaintOpPreset && m_d->currentPaintOpPreset->settings()->needsAsynchronousUpdates(); } void KisResourcesSnapshot::setFGColorOverride(const KoColor &color) { m_d->currentFgColor = color; } void KisResourcesSnapshot::setBGColorOverride(const KoColor &color) { m_d->currentBgColor = color; } void KisResourcesSnapshot::setSelectionOverride(KisSelectionSP selection) { m_d->selectionOverride = selection; m_d->hasOverrideSelection = true; // needed if selection passed is null to ignore selection } void KisResourcesSnapshot::setBrush(const KisPaintOpPresetSP &brush) { m_d->currentPaintOpPreset = brush->cloneWithResourcesSnapshot(); #ifdef HAVE_THREADED_TEXT_RENDERING_WORKAROUND KisPaintOpRegistry::instance()->preinitializePaintOpIfNeeded(m_d->currentPaintOpPreset); #endif /* HAVE_THREADED_TEXT_RENDERING_WORKAROUND */ } diff --git a/libs/ui/tool/kis_resources_snapshot.h b/libs/ui/tool/kis_resources_snapshot.h index cadcbe85e1..8dbf87fe6a 100644 --- a/libs/ui/tool/kis_resources_snapshot.h +++ b/libs/ui/tool/kis_resources_snapshot.h @@ -1,107 +1,109 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_RESOURCES_SNAPSHOT_H #define __KIS_RESOURCES_SNAPSHOT_H #include "kis_shared.h" #include "kis_shared_ptr.h" #include "kis_types.h" #include "kritaui_export.h" #include "kis_painter.h" #include "kis_default_bounds.h" class KoCanvasResourceProvider; class KoCompositeOp; class KisPainter; class KisPostExecutionUndoAdapter; class KoPattern; /** * @brief The KisResourcesSnapshot class takes a snapshot of the various resources * like colors and settings used at the begin of a stroke so subsequent * changes don't impact the running stroke. The main reason for the snapshot is that the * user can *change* the options while the stroke is being executed in the background. */ class KRITAUI_EXPORT KisResourcesSnapshot : public KisShared { public: KisResourcesSnapshot(KisImageSP image, KisNodeSP currentNode, KoCanvasResourceProvider *resourceManager, KisDefaultBoundsBaseSP bounds = 0); KisResourcesSnapshot(KisImageSP image, KisNodeSP currentNode, KisDefaultBoundsBaseSP bounds = 0); ~KisResourcesSnapshot(); void setupPainter(KisPainter *painter); void setupMaskingBrushPainter(KisPainter *painter); KisPostExecutionUndoAdapter* postExecutionUndoAdapter() const; void setCurrentNode(KisNodeSP node); void setStrokeStyle(KisPainter::StrokeStyle strokeStyle); void setFillStyle(KisPainter::FillStyle fillStyle); void setFillTransform(QTransform transform); KisNodeSP currentNode() const; KisImageSP image() const; bool needsIndirectPainting() const; QString indirectPaintingCompositeOp() const; bool needsMaskingBrushRendering() const; /** * \return currently active selection. Note that it will return * null if current node *is* the current selection. This * is done to avoid recursive selection application when * painting on selectgion masks. */ KisSelectionSP activeSelection() const; bool needsAirbrushing() const; qreal airbrushingInterval() const; bool needsSpacingUpdates() const; void setOpacity(qreal opacity); quint8 opacity() const; const KoCompositeOp* compositeOp() const; QString compositeOpId() const; KoPatternSP currentPattern() const; KoColor currentFgColor() const; KoColor currentBgColor() const; KisPaintOpPresetSP currentPaintOpPreset() const; + QTransform fillTransform() const; + /// @return the channel lock flags of the current node with the global override applied QBitArray channelLockFlags() const; qreal effectiveZoom() const; bool presetAllowsLod() const; bool presetNeedsAsynchronousUpdates() const; void setFGColorOverride(const KoColor &color); void setBGColorOverride(const KoColor &color); void setSelectionOverride(KisSelectionSP selection); void setBrush(const KisPaintOpPresetSP &brush); private: struct Private; Private * const m_d; }; typedef KisSharedPtr KisResourcesSnapshotSP; #endif /* __KIS_RESOURCES_SNAPSHOT_H */ diff --git a/plugins/tools/basictools/kis_tool_fill.cc b/plugins/tools/basictools/kis_tool_fill.cc index e9ce96c7f3..ac883bf975 100644 --- a/plugins/tools/basictools/kis_tool_fill.cc +++ b/plugins/tools/basictools/kis_tool_fill.cc @@ -1,445 +1,490 @@ /* * 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) { 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_usePattern, m_fillOnlySelection, 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( 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))); 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_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_slThreshold, lbl_threshold); addOptionWidgetOption(m_sizemodWidget , lbl_sizemod); addOptionWidgetOption(m_featherWidget , lbl_feather); addOptionWidgetOption(m_checkFillSelection, lbl_fillSelection); 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_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_sldPatternRotate->setValue(m_configGroup.readEntry("patternRotate", 0.0)); + m_sldPatternScale->setValue(m_configGroup.readEntry("patternScale", 100.0)); + activateConnectionsToImage(); m_widgetsInitialized = true; return widget; } void KisToolFill::updateGUI() { bool useAdvancedMode = !m_useFastMode->isChecked(); bool selectionOnly = m_checkFillSelection->isChecked(); m_useFastMode->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); 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) { 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::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 64bd54cdd2..3370614637 100644 --- a/plugins/tools/basictools/kis_tool_fill.h +++ b/plugins/tools/basictools/kis_tool_fill.h @@ -1,147 +1,154 @@ /* * 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 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; QString m_sampleLayersMode; QList m_selectedColors; + qreal m_patternRotation; + qreal m_patternScale; bool m_widgetsInitialized {false}; QCheckBox *m_useFastMode; KisSliderSpinBox *m_slThreshold; KisSliderSpinBox *m_sizemodWidget; KisSliderSpinBox *m_featherWidget; + KisDoubleSliderSpinBox *m_sldPatternRotate; + KisDoubleSliderSpinBox *m_sldPatternScale; QCheckBox *m_checkUsePattern; QCheckBox *m_checkFillSelection; 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__