chalk

kis_fill_painter.cpp
1/*
2 * Copyright (c) 2004 Adrian Page <adrian@pagenet.plus.com>
3 * Copyright (c) 2004 Bart Coppens <kde@bartcoppens.be>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 */
19
20#include <stdlib.h>
21#include <string.h>
22#include <cfloat>
23#include <stack>
24
25#include "tqbrush.h"
26#include "tqfontinfo.h"
27#include "tqfontmetrics.h"
28#include "tqpen.h"
29#include "tqregion.h"
30#include "tqwmatrix.h"
31#include <tqimage.h>
32#include <tqmap.h>
33#include <tqpainter.h>
34#include <tqpixmap.h>
35#include <tqpointarray.h>
36#include <tqrect.h>
37#include <tqstring.h>
38
39#include <kdebug.h>
40#include <kcommand.h>
41#include <tdelocale.h>
42
43#include "kis_brush.h"
44#include "kis_debug_areas.h"
45#include "kis_image.h"
46#include "kis_layer.h"
47#include "kis_paint_device.h"
48#include "kis_painter.h"
49#include "kis_pattern.h"
50#include "kis_rect.h"
51#include "kis_colorspace.h"
52#include "kis_transaction.h"
53#include "kis_types.h"
54#include "kis_vec.h"
55#include "kis_selection.h"
56#include "kis_fill_painter.h"
57#include "kis_iterators_pixel.h"
58#include "kis_iterator.h"
59#include "kis_color.h"
60#include "kis_selection.h"
61
62namespace {
63}
64
66 : super()
67{
68 m_width = m_height = -1;
69 m_sampleMerged = false;
70 m_careForSelection = false;
71 m_fuzzy = false;
72}
73
74KisFillPainter::KisFillPainter(KisPaintDeviceSP device) : super(device)
75{
76 m_width = m_height = -1;
77 m_sampleMerged = false;
78 m_careForSelection = false;
79 m_fuzzy = false;
80}
81
82// 'regular' filling
83// XXX: This also needs renaming, since filling ought to keep the opacity and the composite op in mind,
84// this is more eraseToColor.
85void KisFillPainter::fillRect(TQ_INT32 x1, TQ_INT32 y1, TQ_INT32 w, TQ_INT32 h, const KisColor& kc, TQ_UINT8 opacity)
86{
87 if (w > 0 && h > 0) {
88 // Make sure we're in the right colorspace
89
90 KisColor kc2(kc); // get rid of const
91 kc2.convertTo(m_device->colorSpace());
92 TQ_UINT8 * data = kc2.data();
93 m_device->colorSpace()->setAlpha(data, opacity, 1);
94
95 m_device->fill(x1, y1, w, h, data);
96
97 addDirtyRect(TQRect(x1, y1, w, h));
98 }
99}
100
101void KisFillPainter::fillRect(TQ_INT32 x1, TQ_INT32 y1, TQ_INT32 w, TQ_INT32 h, KisPattern * pattern) {
102 if (!pattern) return;
103 if (!pattern->valid()) return;
104 if (!m_device) return;
105
106
107 KisPaintDeviceSP patternLayer = pattern->image(m_device->colorSpace());
108
109 int sx, sy, sw, sh;
110
111 int y = y1;
112
113 if (y >= 0) {
114 sy = y % pattern->height();
115 } else {
116 sy = pattern->height() - (((-y - 1) % pattern->height()) + 1);
117 }
118
119 while (y < y1 + h) {
120 sh = TQMIN((y1 + h) - y, pattern->height() - sy);
121
122 int x = x1;
123
124 if (x >= 0) {
125 sx = x % pattern->width();
126 } else {
127 sx = pattern->width() - (((-x - 1) % pattern->width()) + 1);
128 }
129
130 while (x < x1 + w) {
131 sw = TQMIN((x1 + w) - x, pattern->width() - sx);
132
133 bitBlt(x, y, m_compositeOp, patternLayer.data(), m_opacity, sx, sy, sw, sh);
134 x += sw; sx = 0;
135 }
136
137 y+=sh; sy = 0;
138 }
139
140 addDirtyRect(TQRect(x1, y1, w, h));
141}
142
143// flood filling
144
145void KisFillPainter::fillColor(int startX, int startY) {
146 genericFillStart(startX, startY);
147
148 // Now create a layer and fill it
149 KisPaintDeviceSP filled = new KisPaintDevice(m_device->colorSpace(), "filled");
150 TQ_CHECK_PTR(filled);
151 KisFillPainter painter(filled.data());
152 painter.fillRect(0, 0, m_width, m_height, m_paintColor);
153 painter.end();
154
155 genericFillEnd(filled);
156}
157
158void KisFillPainter::fillPattern(int startX, int startY) {
159 genericFillStart(startX, startY);
160
161 // Now create a layer and fill it
162 KisPaintDeviceSP filled = new KisPaintDevice(m_device->colorSpace(), "filled");
163 TQ_CHECK_PTR(filled);
164 KisFillPainter painter(filled.data());
165 painter.fillRect(0, 0, m_width, m_height, m_pattern);
166 painter.end();
167
168 genericFillEnd(filled);
169}
170
171void KisFillPainter::genericFillStart(int startX, int startY) {
172 m_cancelRequested = false;
173
174 if (m_width < 0 || m_height < 0) {
175 if (m_device->image()) {
176 m_width = m_device->image()->width();
177 m_height = m_device->image()->height();
178 } else {
179 m_width = m_height = 500;
180 }
181 }
182
183 m_size = m_width * m_height;
184
185 // Create a selection from the surrounding area
186 m_selection = createFloodSelection(startX, startY);
187}
188
189void KisFillPainter::genericFillEnd(KisPaintDeviceSP filled) {
190 if (m_cancelRequested) {
191 m_width = m_height = -1;
192 return;
193 }
194
195 TQRect rc = m_selection->selectedRect();
196
197 bltSelection(rc.x(), rc.y(), m_compositeOp, filled, m_selection, m_opacity,
198 rc.x(), rc.y(), rc.width(), rc.height());
199
200 emit notifyProgressDone();
201
202 m_width = m_height = -1;
203}
204
205struct FillSegment {
206 FillSegment(int x, int y/*, FillSegment* parent*/) : x(x), y(y)/*, parent(parent)*/ {}
207 int x;
208 int y;
209// FillSegment* parent;
210};
211
212typedef enum { None = 0, Added = 1, Checked = 2 } Status;
213
214KisSelectionSP KisFillPainter::createFloodSelection(int startX, int startY) {
215 if (m_width < 0 || m_height < 0) {
216 if (m_device->hasSelection() && m_careForSelection) {
217
218 TQRect rc = m_device->selection()->selectedRect();
219 m_width = rc.width() - (startX - rc.x());
220 m_height = rc.height() - (startY - rc.y());
221
222 } else if (m_device->image()) {
223
224 m_width = m_device->image()->width();
225 m_height = m_device->image()->height();
226
227 } else {
228 m_width = m_height = 500;
229 }
230 }
231
232 // Don't try to fill if we start outside the borders, just return an empty 'fill'
233 if (startX < 0 || startY < 0 || startX >= m_width || startY >= m_height)
234 return new KisSelection(m_device);
235
236 KisPaintDeviceSP sourceDevice = 0;
237
238 // sample merged?
239 if (m_sampleMerged) {
240 if (!m_device->image()) {
241 return new KisSelection(m_device);
242 }
243 sourceDevice = m_device->image()->mergedImage();
244 } else {
245 sourceDevice = m_device;
246 }
247
248 m_size = m_width * m_height;
249
250 KisSelectionSP selection = new KisSelection(m_device);
251 KisColorSpace * colorSpace = selection->colorSpace();
252 KisColorSpace * devColorSpace = sourceDevice->colorSpace();
253
254 TQ_UINT8* source = new TQ_UINT8[sourceDevice->pixelSize()];
255 KisHLineIteratorPixel pixelIt = sourceDevice->createHLineIterator(startX, startY, startX+1, false);
256
257 memcpy(source, pixelIt.rawData(), sourceDevice->pixelSize());
258
259 std::stack<FillSegment*> stack;
260
261 stack.push(new FillSegment(startX, startY/*, 0*/));
262
263 Status* map = new Status[m_size];
264
265 memset(map, None, m_size * sizeof(Status));
266
267 int progressPercent = 0; int pixelsDone = 0; int currentPercent = 0;
268 emit notifyProgressStage(i18n("Making fill outline..."), 0);
269
270 bool hasSelection = m_careForSelection && sourceDevice->hasSelection();
271 KisSelectionSP srcSel = 0;
272 if (hasSelection)
273 srcSel = sourceDevice->selection();
274
275 while(!stack.empty()) {
276 FillSegment* segment = stack.top();
277 stack.pop();
278 if (map[m_width * segment->y + segment->x] == Checked) {
279 delete segment;
280 continue;
281 }
282 map[m_width * segment->y + segment->x] = Checked;
283
284 int x = segment->x;
285 int y = segment->y;
286
287 /* We need an iterator that is valid in the range (0,y) - (width,y). Therefore,
288 it is needed to start the iterator at the first position, and then skip to (x,y). */
289 pixelIt = sourceDevice->createHLineIterator(0, y, m_width, false);
290 pixelIt += x;
291 TQ_UINT8 diff = devColorSpace->difference(source, pixelIt.rawData());
292
293 if (diff >= m_threshold
294 || (hasSelection && srcSel->selected(pixelIt.x(), pixelIt.y()) == MIN_SELECTED)) {
295 delete segment;
296 continue;
297 }
298
299 // Here as well: start the iterator at (0,y)
300 KisHLineIteratorPixel selIt = selection->createHLineIterator(0, y, m_width, true);
301 selIt += x;
302 if (m_fuzzy)
303 colorSpace->fromTQColor(TQt::white, MAX_SELECTED - diff, selIt.rawData());
304 else
305 colorSpace->fromTQColor(TQt::white, MAX_SELECTED, selIt.rawData());
306
307 if (y > 0 && (map[m_width * (y - 1) + x] == None)) {
308 map[m_width * (y - 1) + x] = Added;
309 stack.push(new FillSegment(x, y-1));
310 }
311 if (y < (m_height - 1) && (map[m_width * (y + 1) + x] == None)) {
312 map[m_width * (y + 1) + x] = Added;
313 stack.push(new FillSegment(x, y+1));
314 }
315
316 ++pixelsDone;
317
318 bool stop = false;
319
320 --pixelIt;
321 --selIt;
322 --x;
323
324 // go to the left
325 while(!stop && x >= 0 && (map[m_width * y + x] != Checked) ) { // FIXME optimizeable?
326 map[m_width * y + x] = Checked;
327 diff = devColorSpace->difference(source, pixelIt.rawData());
328 if (diff >= m_threshold
329 || (hasSelection && srcSel->selected(pixelIt.x(), pixelIt.y()) == MIN_SELECTED)) {
330 stop = true;
331 continue;
332 }
333
334 if (m_fuzzy)
335 colorSpace->fromTQColor(TQt::white, MAX_SELECTED - diff, selIt.rawData());
336 else
337 colorSpace->fromTQColor(TQt::white, MAX_SELECTED, selIt.rawData());
338
339 if (y > 0 && (map[m_width * (y - 1) + x] == None)) {
340 map[m_width * (y - 1) + x] = Added;
341 stack.push(new FillSegment(x, y-1));
342 }
343 if (y < (m_height - 1) && (map[m_width * (y + 1) + x] == None)) {
344 map[m_width * (y + 1) + x] = Added;
345 stack.push(new FillSegment(x, y+1));
346 }
347 ++pixelsDone;
348 --pixelIt;
349 --selIt;
350 --x;
351 }
352
353 x = segment->x + 1;
354 delete segment;
355
356 if (map[m_width * y + x] == Checked)
357 continue;
358
359 // and go to the right
360 pixelIt = sourceDevice->createHLineIterator(x, y, m_width, false);
361 selIt = selection->createHLineIterator(x, y, m_width, true);
362
363 stop = false;
364 while(!stop && x < m_width && (map[m_width * y + x] != Checked) ) {
365 diff = devColorSpace->difference(source, pixelIt.rawData());
366 map[m_width * y + x] = Checked;
367
368 if (diff >= m_threshold
369 || (hasSelection && srcSel->selected(pixelIt.x(), pixelIt.y()) == MIN_SELECTED) ) {
370 stop = true;
371 continue;
372 }
373
374 if (m_fuzzy)
375 colorSpace->fromTQColor(TQt::white, MAX_SELECTED - diff, selIt.rawData());
376 else
377 colorSpace->fromTQColor(TQt::white, MAX_SELECTED, selIt.rawData());
378
379 if (y > 0 && (map[m_width * (y - 1) + x] == None)) {
380 map[m_width * (y - 1) + x] = Added;
381 stack.push(new FillSegment(x, y-1));
382 }
383 if (y < (m_height - 1) && (map[m_width * (y + 1) + x] == None)) {
384 map[m_width * (y + 1) + x] = Added;
385 stack.push(new FillSegment(x, y+1));
386 }
387 ++pixelsDone;
388 ++pixelIt;
389 ++selIt;
390 ++x;
391 }
392
393 if (m_size > 0) {
394 progressPercent = (pixelsDone * 100) / m_size;
395 if (progressPercent > currentPercent) {
396 emit notifyProgress(progressPercent);
397 currentPercent = progressPercent;
398 }
399 }
400 }
401
402
403 delete[] map;
404 delete[] source;
405
406 return selection;
407}
A colorspace is the definition of a certain color model in Chalk.
virtual void fromTQColor(const TQColor &c, TQ_UINT8 *dst, KisProfile *profile=0)=0
The fromTQColor methods take a given color defined as an RGB TQColor and fills a byte array with the ...
virtual TQ_UINT8 difference(const TQ_UINT8 *src1, const TQ_UINT8 *src2)=0
Get the difference between 2 colors, normalized in the range (0,255)
A KisColor describes a color in a certain colorspace.
Definition kis_color.h:33
void convertTo(KisColorSpace *cs)
Convert this KisColor to the specified colorspace.
TQ_UINT8 * data() const
For easy memcpy'ing etc.
Definition kis_color.h:62
This painter can be used to fill paint devices in different ways.
void fillPattern(int startX, int startY)
Fills the enclosed area around the point with the set pattern.
KisFillPainter()
Construct an empty painter.
KisSelectionSP createFloodSelection(int startX, int startY)
Returns a selection mask for the floodfill starting at the specified position.
void fillRect(TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, TQ_INT32 h, const KisColor &c)
Fill a rectangle with a certain color.
The pixel iterators are high level iterarators.
A paint device contains the actual pixel data and offers methods to read and write pixels.
KisPainter contains the graphics primitives necessary to draw on a KisPaintDevice.
Definition kis_painter.h:56
TQRect addDirtyRect(TQRect r)
Add the r to the current dirty rect, and return the dirtyRect after adding r to it.
KisPattern * pattern() const
Returns the currently set pattern.
KCommand * end()
Finish painting on the current device.
TQ_UINT8 opacity() const
Returns the opacity that is used in painting.
void bitBlt(TQ_INT32 dx, TQ_INT32 dy, const KisCompositeOp &op, KisPaintDeviceSP src, TQ_INT32 sx, TQ_INT32 sy, TQ_INT32 sw, TQ_INT32 sh)
Blast the specified region from src onto the current paint device.
KisColor fillColor() const
Returns the current fill color.
void bltSelection(TQ_INT32 dx, TQ_INT32 dy, const KisCompositeOp &op, KisPaintDeviceSP src, KisSelectionSP selMask, TQ_UINT8 opacity, TQ_INT32 sx, TQ_INT32 sy, TQ_INT32 sw, TQ_INT32 sh)
A version of bitBlt that renders using an external selection mask, ignoring the src device's own sele...
KisSelection contains a byte-map representation of a layer, where the value of a byte signifies wheth...