Krita Source Code Documentation
Loading...
Searching...
No Matches
fill_processing_visitor.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2013 Dmitry Kazakov <dimula73@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
8
9#include <kis_node.h>
10#include <kis_image.h>
11#include <kis_wrapped_rect.h>
13#include <kis_assert.h>
16#include "KisAnimAutoKey.h"
17#include "kis_undo_adapter.h"
18
19
21 KisSelectionSP selection,
22 KisResourcesSnapshotSP resources)
23 : m_refPaintDevice(refPaintDevice)
24 , m_selection(selection)
25 , m_resources(resources)
26 , m_useFastMode(false)
27 , m_selectionOnly(false)
28 , m_useSelectionAsBoundary(false)
29 , m_usePattern(false)
30 , m_antiAlias(false)
31 , m_feather(0)
32 , m_sizemod(0)
33 , m_stopGrowingAtDarkestPixel(false)
34 , m_fillThreshold(8)
35 , m_opacitySpread(0)
36 , m_closeGap(0)
37 , m_regionFillingMode(KisFillPainter::RegionFillingMode_FloodFill)
38 , m_continuousFillMode(ContinuousFillMode_DoNotUse)
39 , m_continuousFillMask(nullptr)
40 , m_continuousFillReferenceColor(nullptr)
41 , m_unmerged(false)
42 , m_useBgColor(false)
43 , m_useCustomBlendingOptions(false)
44 , m_customOpacity(OPACITY_OPAQUE_F)
45 , m_customCompositeOp(COMPOSITE_OVER)
46 , m_progressHelper(nullptr)
47{}
48
50{
51 Q_UNUSED(layer);
52 Q_UNUSED(undoAdapter);
53}
54
56{
57 KisPaintDeviceSP device = node->paintDevice();
58 KIS_ASSERT(device);
59 if (!m_progressHelper) {
60 m_progressHelper.reset(new ProgressHelper(node));
61 }
62 fillPaintDevice(device, undoAdapter);
63}
64
66{
67 // we fill only the coloring project so the user can work
68 // with the mask like with a usual paint layer
69 if (!m_progressHelper) {
70 m_progressHelper.reset(new ProgressHelper(mask));
71 }
72 fillPaintDevice(mask->coloringProjection(), undoAdapter);
73}
74
76{
77 KIS_ASSERT(!m_seedPoints.isEmpty());
78
79 QRect fillRect = m_resources->image()->bounds();
80
82 if (autoKeyframeCommand) {
83 undoAdapter->addCommand(autoKeyframeCommand);
84 }
85
86 if (m_selectionOnly ||
87 (m_refPaintDevice->nonDefaultPixelArea().isEmpty() && m_sizemod == 0 && m_feather == 0 && !m_antiAlias)) {
88 if (device->defaultBounds()->wrapAroundMode()) {
89 // Always fill if wrap around mode is on
90 selectionFill(device, fillRect, undoAdapter);
91 } else {
92 // Otherwise fill only if any of the points is inside the rect
93 for (const QPoint &seedPoint : m_seedPoints) {
94 if (fillRect.contains(seedPoint)) {
95 selectionFill(device, fillRect, undoAdapter);
96 break;
97 }
98 }
99 }
100 } else {
101 for (QPoint seedPoint : m_seedPoints) {
102 if (device->defaultBounds()->wrapAroundMode()) {
103 seedPoint = KisWrappedRect::ptToWrappedPt(seedPoint, device->defaultBounds()->imageBorderRect(), device->defaultBounds()->wrapAroundModeAxis());
104 }
105
107 normalFill(device, fillRect, seedPoint, undoAdapter);
108 } else {
109 continuousFill(device, fillRect, seedPoint, undoAdapter);
110 }
111 }
112 }
113}
114
115void FillProcessingVisitor::selectionFill(KisPaintDeviceSP device, const QRect &fillRect, KisUndoAdapter *undoAdapter)
116{
117 const QRect fillRect_ = m_selection ? m_selection->selectedRect() : fillRect;
118 KisPaintDeviceSP filledDevice = device->createCompositionSourceDevice();
119 KisFillPainter fillPainter(filledDevice);
120 fillPainter.setProgress(m_progressHelper->updater());
121
122 if (m_usePattern) {
124 } else if (m_useBgColor) {
125 fillPainter.fillRect(fillRect_, m_resources->currentBgColor(), OPACITY_OPAQUE_U8);
126 } else {
127 fillPainter.fillRect(fillRect_, m_resources->currentFgColor(), OPACITY_OPAQUE_U8);
128 }
129
130 QVector<QRect> dirtyRect = fillPainter.takeDirtyRegion();
131
132 KisPainter painter(device, m_selection);
133 painter.beginTransaction();
134
135 m_resources->setupPainter(&painter);
136
140 }
141
142 Q_FOREACH (const QRect &rc, dirtyRect) {
143 painter.bitBlt(rc.topLeft(), filledDevice, rc);
144 if (m_outDirtyRect) {
145 *m_outDirtyRect = m_outDirtyRect->united(rc);
146 }
147 }
148
149 painter.endTransaction(undoAdapter);
150}
151
152void FillProcessingVisitor::normalFill(KisPaintDeviceSP device, const QRect &fillRect, const QPoint &seedPoint, KisUndoAdapter *undoAdapter)
153{
154 KisFillPainter fillPainter(device, m_selection);
155 fillPainter.beginTransaction();
156
157 m_resources->setupPainter(&fillPainter);
158
159 if (m_useBgColor) {
160 fillPainter.setPaintColor(fillPainter.backgroundColor());
161 }
162 fillPainter.setProgress(m_progressHelper->updater());
163 fillPainter.setAntiAlias(m_antiAlias);
164 fillPainter.setSizemod(m_sizemod);
166 fillPainter.setFeather(m_feather);
169 fillPainter.setCloseGap(m_closeGap);
173 }
174 fillPainter.setCareForSelection(true);
176 fillPainter.setWidth(fillRect.width());
177 fillPainter.setHeight(fillRect.height());
178 fillPainter.setUseCompositing(!m_useFastMode);
180 fillPainter.setOpacityF(m_customOpacity);
182 }
183
184 KisPaintDeviceSP sourceDevice = m_unmerged ? device : m_refPaintDevice;
185
186 if (m_usePattern) {
187 fillPainter.fillPattern(seedPoint.x(), seedPoint.y(), sourceDevice, m_resources->fillTransform());
188 } else {
189 fillPainter.fillColor(seedPoint.x(), seedPoint.y(), sourceDevice);
190 }
191
192 fillPainter.endTransaction(undoAdapter);
193
194 if (m_outDirtyRect) {
195 QVector<QRect> dirtyRects = fillPainter.takeDirtyRegion();
196 Q_FOREACH(const QRect &r, dirtyRects) {
197 *m_outDirtyRect = m_outDirtyRect->united(r);
198 }
199 }
200}
201
202void FillProcessingVisitor::continuousFill(KisPaintDeviceSP device, const QRect &fillRect, const QPoint &seedPoint, KisUndoAdapter *undoAdapter)
203{
204 // In continuous filling we use a selection mask that represents the
205 // cumulated regions already filled. Being able to discard filling
206 // operations based on if they were already filled the area under
207 // the cursor speeds up greatly the continuous fill operation
208
211
212 // The following checks are useful in the continuous fill.
214 // If we must use the current selection as boundary we must discard
215 // the continuous fill points that lie outside of it.
216 // This avoids unnecessary and expensive flood fill operations
217 // when the user drags the mouse outside the selection.
218 if (!m_selection->selectedRect().contains(seedPoint)) {
219 return;
220 }
221 const quint8 opacity = m_selection->projection()->pixel(seedPoint).opacityU8();
222 if (opacity == OPACITY_TRANSPARENT_U8) {
223 return;
224 }
225 }
226 // Filling a pixel is likely to fill nearby pixels with the same color,
227 // so this check should reduce the number of flood fill operations
228 // considerably
229 {
230 // If the color in the m_continuous fill mask under the start point
231 // equals white we return early and don't fill. This means the area
232 // under the start point was already filled
233 const quint8 opacity = m_continuousFillMask->pixelSelection()->pixel(seedPoint).opacityU8();
234 if (opacity == OPACITY_OPAQUE_U8) {
235 return;
236 }
237 }
239 // If the color in the reference device under the start point
240 // differs from the reference color we return early and don't fill
241 const KoColor referenceColor = m_continuousFillReferenceColor->convertedTo(m_refPaintDevice->colorSpace());
242 const KoColor referenceDeviceColor = m_refPaintDevice->pixel(seedPoint);
243 if (referenceColor != referenceDeviceColor) {
244 return;
245 }
246 }
247
248 KisSelectionSP newFillSelection;
249 KisPaintDeviceSP sourceDevice = m_unmerged ? device : m_refPaintDevice;
250 // First we get the new region mask
251 {
252 KisFillPainter painter;
253
254 painter.setProgress(m_progressHelper->updater());
255 painter.setSizemod(m_sizemod);
257 painter.setAntiAlias(m_antiAlias);
258 painter.setFeather(m_feather);
261 painter.setCloseGap(m_closeGap);
265 }
266 painter.setCareForSelection(true);
268 painter.setWidth(fillRect.width());
269 painter.setHeight(fillRect.height());
271
272 KisPixelSelectionSP pixelSelection = painter.createFloodSelection(seedPoint.x(),
273 seedPoint.y(),
274 sourceDevice,
276
277 newFillSelection = new KisSelection(pixelSelection->defaultBounds(),
279 newFillSelection->pixelSelection()->applySelection(pixelSelection, SELECTION_REPLACE);
280 }
281 // Now we actually fill the destination device
282 // If there is an active selection, we use a trimmed version of the mask to
283 // fill. We cannot trim the obtained mask since that would delete the not
284 // selected areas from it and those are need to be part of the continuous
285 // fill mask. This avoids unnecessary and expensive flood fill operations
286 // when the user drags the mouse outside the selection.
287 {
288 KisSelectionSP trimmedFillSelection;
289 if (m_selection) {
290 trimmedFillSelection = new KisSelection(newFillSelection->pixelSelection()->defaultBounds(), newFillSelection->resolutionProxy());
291 trimmedFillSelection->pixelSelection()->applySelection(newFillSelection->pixelSelection(), SELECTION_REPLACE);
293 } else {
294 trimmedFillSelection = newFillSelection;
295 }
296 KisSelectionSP tmpSelection = m_selection;
297 m_selection = trimmedFillSelection;
298 selectionFill(device, fillRect, undoAdapter);
299 m_selection = tmpSelection;
300 }
301 // Now we update the continuous fill mask with the new mask
302 {
304 }
305}
306
307void FillProcessingVisitor::setSeedPoint(const QPoint &seedPoint)
308{
309 m_seedPoints.clear();
310 m_seedPoints.append(seedPoint);
311}
312
314{
315 m_seedPoints = seedPoints;
316}
317
319{
320 m_useFastMode = useFastMode;
321}
322
324{
325 m_usePattern = usePattern;
326}
327
329{
330 m_selectionOnly = selectionOnly;
331}
332
334{
335 m_useSelectionAsBoundary = useSelectionAsBoundary;
336}
337
339{
340 m_antiAlias = antiAlias;
341}
342
344{
345 m_feather = feather;
346}
347
349{
350 m_sizemod = sizemod;
351}
352
353void FillProcessingVisitor::setStopGrowingAtDarkestPixel(bool stopGrowingAtDarkestPixel)
354{
355 m_stopGrowingAtDarkestPixel = stopGrowingAtDarkestPixel;
356}
357
359{
360 m_fillThreshold = fillThreshold;
361}
362
364{
365 m_opacitySpread = opacitySpread;
366}
367
369{
370 m_closeGap = gap;
371}
372
377
379{
380 m_regionFillingBoundaryColor = regionFillingBoundaryColor;
381}
382
384{
385 m_continuousFillMode = continuousFillMode;
386}
387
389{
390 m_continuousFillMask = continuousFillMask;
391}
392
394{
395 m_continuousFillReferenceColor = continuousFillReferenceColor;
396}
397
399{
400 m_unmerged = unmerged;
401}
402
404{
405 m_useBgColor = useBgColor;
406}
407
408void FillProcessingVisitor::setUseCustomBlendingOptions(bool useCustomBlendingOptions)
409{
410 m_useCustomBlendingOptions = useCustomBlendingOptions;
411}
412
414{
415 m_customOpacity = customOpacity;
416}
417
418void FillProcessingVisitor::setCustomCompositeOp(const QString &customCompositeOp)
419{
420 m_customCompositeOp = customCompositeOp;
421}
422
424{
425 m_outDirtyRect = outDirtyRect;
426}
427
@ SELECTION_REPLACE
@ SELECTION_INTERSECT
@ SELECTION_ADD
const quint8 OPACITY_TRANSPARENT_U8
const qreal OPACITY_OPAQUE_F
const quint8 OPACITY_OPAQUE_U8
const QString COMPOSITE_OVER
void setOutDirtyRect(QSharedPointer< QRect > outDirtyRect)
void setSeedPoints(const QVector< QPoint > &seedPoints)
void setStopGrowingAtDarkestPixel(bool stopGrowingAtDarkestPixel)
void setUseFastMode(bool useFastMode)
void setUseCustomBlendingOptions(bool useCustomBlendingOptions)
QSharedPointer< KoColor > m_continuousFillReferenceColor
QSharedPointer< ProgressHelper > m_progressHelper
void visitColorizeMask(KisColorizeMask *mask, KisUndoAdapter *undoAdapter) override
KisResourcesSnapshotSP m_resources
void normalFill(KisPaintDeviceSP device, const QRect &fillRect, const QPoint &seedPoint, KisUndoAdapter *undoAdapter)
void setSeedPoint(const QPoint &seedPoint)
QSharedPointer< QRect > m_outDirtyRect
void setCustomCompositeOp(const QString &customCompositeOp)
void setProgressHelper(QSharedPointer< ProgressHelper > progressHelper)
void setOpacitySpread(int opacitySpread)
void setAntiAlias(bool antiAlias)
void setSelectionOnly(bool selectionOnly)
void setUseBgColor(bool useBgColor)
void continuousFill(KisPaintDeviceSP device, const QRect &fillRect, const QPoint &seedPoint, KisUndoAdapter *undoAdapter)
void setContinuousFillMask(KisSelectionSP continuousFillMask)
void setFillThreshold(int fillThreshold)
void setRegionFillingBoundaryColor(const KoColor &regionFillingBoundaryColor)
void fillPaintDevice(KisPaintDeviceSP device, KisUndoAdapter *undoAdapter)
void visitExternalLayer(KisExternalLayer *layer, KisUndoAdapter *undoAdapter) override
void setUsePattern(bool usePattern)
void setCustomOpacity(qreal customOpacity)
void selectionFill(KisPaintDeviceSP device, const QRect &fillRect, KisUndoAdapter *undoAdapter)
void setContinuousFillReferenceColor(const QSharedPointer< KoColor > continuousFillReferenceColor)
ContinuousFillMode m_continuousFillMode
void setRegionFillingMode(KisFillPainter::RegionFillingMode regionFillingMode)
FillProcessingVisitor(KisPaintDeviceSP referencePaintDevice, KisSelectionSP selection, KisResourcesSnapshotSP resources)
void setContinuousFillMode(ContinuousFillMode continuousFillMode)
KisFillPainter::RegionFillingMode m_regionFillingMode
void setUseSelectionAsBoundary(bool useSelectionAsBoundary)
void visitNodeWithPaintDevice(KisNode *node, KisUndoAdapter *undoAdapter) override
KisPaintDeviceSP coloringProjection() const
virtual WrapAroundAxis wrapAroundModeAxis() const =0
virtual bool wrapAroundMode() const =0
virtual QRect imageBorderRect() const
void setWidth(int w)
void setSizemod(int sizemod)
void setHeight(int h)
void setFillThreshold(int threshold)
void setFeather(int feather)
void setCloseGap(int gap)
void setRegionFillingMode(RegionFillingMode regionFillingMode)
KisPixelSelectionSP createFloodSelection(int startX, int startY, KisPaintDeviceSP sourceDevice, KisPaintDeviceSP existingSelection)
void fillRect(qint32 x, qint32 y, qint32 w, qint32 h, const KoColor &c, quint8 opacity)
void setCareForSelection(bool set)
void setUseSelectionAsBoundary(bool useSelectionAsBoundary)
void setRegionFillingBoundaryColor(const KoColor &regionFillingBoundaryColor)
void setUseCompositing(bool useCompositing)
void fillColor(int startX, int startY, KisPaintDeviceSP sourceDevice)
void fillPattern(int startX, int startY, KisPaintDeviceSP sourceDevice, QTransform patternTransform=QTransform())
void setAntiAlias(bool antiAlias)
void setStopGrowingAtDarkestPixel(bool stopGrowingAtDarkestPixel)
void setOpacitySpread(int opacitySpread)
void fillRectNoCompose(const QRect &rc, const KoPatternSP pattern, const QTransform transform)
fillRect Fill a rectangle with a certain pattern. The pattern is repeated if it does not fit the enti...
static KisImageResolutionProxySP identity()
QRect bounds() const override
QRect nonDefaultPixelArea() const
KisPaintDeviceSP createCompositionSourceDevice() const
const KoColorSpace * colorSpace() const
bool pixel(qint32 x, qint32 y, QColor *c) const
KisDefaultBoundsBaseSP defaultBounds() const
void endTransaction(KisUndoAdapter *undoAdapter)
Finish the undoable paint operation.
void beginTransaction(const KUndo2MagicString &transactionName=KUndo2MagicString(), int timedID=-1)
Begin an undoable paint operation.
void setOpacityF(qreal opacity)
void bitBlt(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight)
void setProgress(KoUpdater *progressUpdater)
KoColor backgroundColor
void setPaintColor(const KoColor &color)
QVector< QRect > takeDirtyRegion()
void setCompositeOpId(const KoCompositeOp *op)
void setupPainter(KisPainter *painter)
KoPatternSP currentPattern() const
bool isNull() const
virtual void addCommand(KUndo2Command *cmd)=0
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
#define KIS_ASSERT(cond)
Definition kis_assert.h:33
KUndo2Command * tryAutoCreateDuplicatedFrame(KisPaintDeviceSP device, AutoCreateKeyframeFlags flags)
create a new duplicated keyframe if auto-keyframe mode is on
virtual KisPaintDeviceSP paintDevice() const =0
void applySelection(KisPixelSelectionSP selection, SelectionAction action)
KisPixelSelectionSP projection() const
bool hasNonEmptyPixelSelection() const
KisImageResolutionProxySP resolutionProxy
KisPixelSelectionSP pixelSelection
QRect selectedRect() const
static QPoint ptToWrappedPt(QPoint pt, const QRect &wrapRect, WrapAroundAxis wrapAxis)