/src/graphicsmagick/magick/paint.c
Line | Count | Source |
1 | | /* |
2 | | % Copyright (C) 2003-2025 GraphicsMagick Group |
3 | | % Copyright (C) 2002 ImageMagick Studio |
4 | | % Copyright 1991-1999 E. I. du Pont de Nemours and Company |
5 | | % |
6 | | % This program is covered by multiple licenses, which are described in |
7 | | % Copyright.txt. You should have received a copy of Copyright.txt with this |
8 | | % package; otherwise see http://www.graphicsmagick.org/www/Copyright.html. |
9 | | % |
10 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
11 | | % % |
12 | | % % |
13 | | % PPPP AAA IIIII N N TTTTT % |
14 | | % P P A A I NN N T % |
15 | | % PPPP AAAAA I N N N T % |
16 | | % P A A I N NN T % |
17 | | % P A A IIIII N N T % |
18 | | % % |
19 | | % % |
20 | | % Methods to Paint on an Image % |
21 | | % % |
22 | | % % |
23 | | % Software Design % |
24 | | % John Cristy % |
25 | | % July 1998 % |
26 | | % % |
27 | | % % |
28 | | % % |
29 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
30 | | % |
31 | | % |
32 | | */ |
33 | | |
34 | | /* |
35 | | Include declarations. |
36 | | */ |
37 | | #include "magick/studio.h" |
38 | | #include "magick/alpha_composite.h" |
39 | | #include "magick/color.h" |
40 | | #include "magick/monitor.h" |
41 | | #include "magick/paint.h" |
42 | | #include "magick/pixel_cache.h" |
43 | | #include "magick/pixel_iterator.h" |
44 | | #include "magick/render.h" |
45 | | #include "magick/utility.h" |
46 | | |
47 | | /* |
48 | | Define declarations. |
49 | | */ |
50 | | #define FuzzyOpacityMatch(color,target,fuzz) \ |
51 | 27.3M | (((color)->opacity == (target)->opacity) && \ |
52 | 27.3M | FuzzyColorMatch(color,target,fuzz)) |
53 | 1.72M | #define MaxStacksize (((size_t) 1) << 15) |
54 | | #define Push(up,left,right,delta) \ |
55 | 1.72M | { \ |
56 | 1.72M | if (s >= (segment_stack+MaxStacksize)) \ |
57 | 1.72M | { \ |
58 | 5 | MagickFreeResourceLimitedMemory(unsigned char *,floodplane); \ |
59 | 5 | MagickFreeResourceLimitedMemory(SegmentInfo *,segment_stack); \ |
60 | 5 | ThrowBinaryException2(DrawError,"SegmentStackOverflow", \ |
61 | 5 | image->filename); \ |
62 | 0 | } \ |
63 | 1.72M | else \ |
64 | 1.72M | { \ |
65 | 1.72M | if (((((ptrdiff_t)up)+((ptrdiff_t)delta)) >= 0) && \ |
66 | 1.72M | ((((ptrdiff_t)up)+((ptrdiff_t)delta)) < (long) image->rows)) \ |
67 | 1.72M | { \ |
68 | 1.55M | s->y1=(up); \ |
69 | 1.55M | s->x1=(left); \ |
70 | 1.55M | s->x2=(right); \ |
71 | 1.55M | s->y2=(delta); \ |
72 | 1.55M | s++; \ |
73 | 1.55M | } \ |
74 | 1.72M | } \ |
75 | 1.72M | } |
76 | | |
77 | | /* |
78 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
79 | | % % |
80 | | % % |
81 | | % % |
82 | | % C o l o r F l o o d f i l l I m a g e % |
83 | | % % |
84 | | % % |
85 | | % % |
86 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
87 | | % |
88 | | % ColorFloodfill() changes the color value of any pixel that matches |
89 | | % target and is an immediate neighbor. If the method FillToBorderMethod is |
90 | | % specified, the color value is changed for any neighbor pixel that does not |
91 | | % match the bordercolor member of image. |
92 | | % |
93 | | % By default target must match a particular pixel color exactly. |
94 | | % However, in many cases two colors may differ by a small amount. The |
95 | | % fuzz member of image defines how much tolerance is acceptable to |
96 | | % consider two colors as the same. For example, set fuzz to 10 and the |
97 | | % color red at intensities of 100 and 102 respectively are now |
98 | | % interpreted as the same color for the purposes of the floodfill. |
99 | | % |
100 | | % The format of the ColorFloodfillImage method is: |
101 | | % |
102 | | % MagickPassFail ColorFloodfillImage(Image *image, |
103 | | % const DrawInfo *draw_info, const PixelPacket target, |
104 | | % const long x_offset,const long y_offset, |
105 | | % const PaintMethod method) |
106 | | % |
107 | | % A description of each parameter follows: |
108 | | % |
109 | | % o image: The image. |
110 | | % |
111 | | % o draw_info: The draw info. |
112 | | % |
113 | | % o target: The RGB value of the target color. |
114 | | % |
115 | | % o x,y: The starting location of the operation. |
116 | | % |
117 | | % o method: Choose either FloodfillMethod or FillToBorderMethod. |
118 | | % |
119 | | % |
120 | | */ |
121 | | /* FIXME: There is a data-sharing issue (causing corruption) if OpenMP is enabled. */ |
122 | | #define ENABLE_COLORFLOODFILL_OPENMP 0 |
123 | | MagickExport MagickPassFail ColorFloodfillImage(Image *image, |
124 | | const DrawInfo *draw_info, |
125 | | const PixelPacket target, |
126 | | const long x_offset, |
127 | | const long y_offset, |
128 | | const PaintMethod method) |
129 | 2.44k | { |
130 | 2.44k | Image |
131 | 2.44k | *pattern; |
132 | | |
133 | 2.44k | int |
134 | 2.44k | skip; |
135 | | |
136 | 2.44k | long |
137 | 2.44k | offset, |
138 | 2.44k | start, |
139 | 2.44k | x1, |
140 | 2.44k | x2, |
141 | 2.44k | y; |
142 | | |
143 | 2.44k | PixelPacket |
144 | 2.44k | color; |
145 | | |
146 | 2.44k | register long |
147 | 2.44k | x; |
148 | | |
149 | 2.44k | register PixelPacket |
150 | 2.44k | *q; |
151 | | |
152 | 2.44k | register SegmentInfo |
153 | 2.44k | *s; |
154 | | |
155 | 2.44k | SegmentInfo |
156 | 2.44k | *segment_stack; |
157 | | |
158 | 2.44k | unsigned char |
159 | 2.44k | *floodplane; |
160 | | |
161 | 2.44k | MagickPassFail |
162 | 2.44k | status=MagickPass; |
163 | | |
164 | | /* |
165 | | Check boundary conditions. |
166 | | */ |
167 | 2.44k | assert(image != (Image *) NULL); |
168 | 2.44k | assert(image->signature == MagickSignature); |
169 | 2.44k | assert(draw_info != (DrawInfo *) NULL); |
170 | 2.44k | assert(draw_info->signature == MagickSignature); |
171 | 2.44k | if ((x_offset < 0) || (x_offset >= (long) image->columns)) |
172 | 101 | return(MagickFail); |
173 | 2.33k | if ((y_offset < 0) || (y_offset >= (long) image->rows)) |
174 | 74 | return(MagickFail); |
175 | | /* |
176 | | Set floodfill color. |
177 | | */ |
178 | 2.26k | if (FuzzyColorMatch(&draw_info->fill,&target,image->fuzz)) |
179 | 6 | return(MagickFail); |
180 | | /* |
181 | | The flood-fill algorithm may lock-up if a clip-mask is present |
182 | | because the pixels just written may not be what is read later. |
183 | | |
184 | | Perhaps we will update the algorithm to respect the clip-mask later. |
185 | | */ |
186 | 2.25k | if (*ImageGetClipMaskInlined(image)) |
187 | 12 | { |
188 | 12 | ThrowException(&image->exception,DrawError,UnableToFloodFillImageDueToClipMask,image->filename); |
189 | 12 | return(MagickFail); |
190 | 12 | } |
191 | 2.24k | floodplane=MagickAllocateResourceLimitedClearedArray(unsigned char *,image->columns,image->rows); |
192 | 2.24k | segment_stack=MagickAllocateResourceLimitedArray(SegmentInfo *,MaxStacksize,sizeof(SegmentInfo)); |
193 | 2.24k | if ((floodplane== (unsigned char *) NULL) || |
194 | 2.24k | (segment_stack == (SegmentInfo *) NULL)) |
195 | 1 | { |
196 | 1 | MagickFreeResourceLimitedMemory(unsigned char *, floodplane); |
197 | 1 | MagickFreeResourceLimitedMemory(SegmentInfo *, segment_stack); |
198 | 1 | ThrowBinaryException3(ResourceLimitError,MemoryAllocationFailed, |
199 | 1 | UnableToFloodfillImage); |
200 | 0 | } |
201 | | /* |
202 | | Push initial segment on stack. |
203 | | */ |
204 | 2.24k | image->storage_class=DirectClass; |
205 | 2.24k | x=x_offset; |
206 | 2.24k | y=y_offset; |
207 | 2.24k | start=0; |
208 | 2.24k | s=segment_stack; |
209 | 2.24k | Push(y,x,x,1); |
210 | 2.24k | Push((ptrdiff_t)y+1,x,x,-1); |
211 | 791k | while (s > segment_stack) |
212 | 789k | { |
213 | | /* |
214 | | Pop segment off stack. |
215 | | */ |
216 | 789k | s--; |
217 | 789k | x1=(long) s->x1; |
218 | 789k | x2=(long) s->x2; |
219 | 789k | offset=(long) s->y2; |
220 | 789k | y=(long) s->y1+offset; |
221 | | /* |
222 | | Recolor neighboring pixels. |
223 | | */ |
224 | | |
225 | | /* |
226 | | These two method loops are the same except for the fuzzy-match |
227 | | logic. |
228 | | */ |
229 | 789k | if (method == FloodfillMethod) |
230 | 651k | { |
231 | 651k | q=GetImagePixels(image,0,y,x1+1,1); |
232 | 651k | if (q == (PixelPacket *) NULL) |
233 | 0 | { |
234 | 0 | status=MagickFail; |
235 | 0 | break; |
236 | 0 | } |
237 | 651k | q+=x1; |
238 | | |
239 | 1.37M | for (x=x1; x >= 0; x--) |
240 | 890k | { |
241 | 890k | if (!FuzzyColorMatch(q,&target,image->fuzz)) |
242 | 170k | break; |
243 | 720k | floodplane[y*image->columns+x]=MagickTrue; |
244 | 720k | *q=draw_info->fill; |
245 | 720k | q--; |
246 | 720k | } |
247 | | |
248 | 651k | if (!SyncImagePixels(image)) |
249 | 0 | { |
250 | 0 | status=MagickFail; |
251 | 0 | break; |
252 | 0 | } |
253 | 651k | } |
254 | 137k | else /* method == FillToBorderMethod */ |
255 | 137k | { |
256 | 137k | q=GetImagePixels(image,0,y,x1+1,1); |
257 | 137k | if (q == (PixelPacket *) NULL) |
258 | 0 | { |
259 | 0 | status=MagickFail; |
260 | 0 | break; |
261 | 0 | } |
262 | 137k | q+=x1; |
263 | | |
264 | 612k | for (x=x1; x >= 0; x--) |
265 | 486k | { |
266 | 486k | if (FuzzyColorMatch(q,&target,image->fuzz) || |
267 | 486k | FuzzyColorMatch(q,&draw_info->fill,image->fuzz)) |
268 | 12.4k | break; |
269 | 474k | floodplane[y*image->columns+x]=MagickTrue; |
270 | 474k | *q=draw_info->fill; |
271 | 474k | q--; |
272 | 474k | } |
273 | | |
274 | 137k | if (!SyncImagePixels(image)) |
275 | 0 | { |
276 | 0 | status=MagickFail; |
277 | 0 | break; |
278 | 0 | } |
279 | 137k | } |
280 | | |
281 | 789k | skip=x >= x1; |
282 | 789k | if (!skip) |
283 | 681k | { |
284 | 681k | start=x+1; |
285 | 681k | if (start < x1) |
286 | 681k | Push(y,start,(ptrdiff_t)x1-1,-offset); |
287 | 681k | x=x1+1; |
288 | 681k | } |
289 | 789k | do |
290 | 856k | { |
291 | 856k | if (!skip) |
292 | 749k | { |
293 | 749k | if (x < (long) image->columns) |
294 | 713k | { |
295 | | /* |
296 | | These two method loops are the same except for the |
297 | | fuzzy-match logic. |
298 | | */ |
299 | 713k | if (method == FloodfillMethod) |
300 | 580k | { |
301 | 580k | q=GetImagePixels(image,x,y,image->columns-x,1); |
302 | 580k | if (q == (PixelPacket *) NULL) |
303 | 0 | { |
304 | 0 | status=MagickFail; |
305 | 0 | break; |
306 | 0 | } |
307 | | |
308 | 7.74M | for ( ; x < (long) image->columns; x++) |
309 | 7.20M | { |
310 | 7.20M | if (!FuzzyColorMatch(q,&target,image->fuzz)) |
311 | 35.4k | break; |
312 | 7.16M | floodplane[y*image->columns+x]=MagickTrue; |
313 | 7.16M | *q=draw_info->fill; |
314 | 7.16M | q++; |
315 | 7.16M | } |
316 | | |
317 | 580k | if (!SyncImagePixels(image)) |
318 | 0 | { |
319 | 0 | status=MagickFail; |
320 | 0 | break; |
321 | 0 | } |
322 | 580k | } |
323 | 133k | else /* method == FillToBorderMethod */ |
324 | 133k | { |
325 | 133k | q=GetImagePixels(image,x,y,image->columns-x,1); |
326 | 133k | if (q == (PixelPacket *) NULL) |
327 | 0 | { |
328 | 0 | status=MagickFail; |
329 | 0 | break; |
330 | 0 | } |
331 | | |
332 | 124M | for ( ; x < (long) image->columns; x++) |
333 | 124M | { |
334 | 124M | if (FuzzyColorMatch(q,&target,image->fuzz) || |
335 | 124M | FuzzyColorMatch(q,&draw_info->fill,image->fuzz)) |
336 | 9.27k | break; |
337 | 124M | floodplane[y*image->columns+x]=True; |
338 | 124M | *q=draw_info->fill; |
339 | 124M | q++; |
340 | 124M | } |
341 | | |
342 | 133k | if (!SyncImagePixels(image)) |
343 | 0 | { |
344 | 0 | status=MagickFail; |
345 | 0 | break; |
346 | 0 | } |
347 | 133k | } |
348 | 713k | } |
349 | 749k | Push(y,start, (ptrdiff_t)x-1,offset); |
350 | 749k | if (x > ((ptrdiff_t)x2+1)) |
351 | 749k | Push(y, (ptrdiff_t)x2+1, (ptrdiff_t)x-1,-offset); |
352 | 749k | } |
353 | 856k | skip=False; |
354 | 856k | x++; |
355 | 856k | if (x <= x2) |
356 | 70.4k | { |
357 | | /* |
358 | | These two method loops are the same except for the |
359 | | fuzzy-match logic. |
360 | | */ |
361 | 70.4k | if (method == FloodfillMethod) |
362 | 68.3k | { |
363 | 68.3k | q=GetImagePixels(image,x,y,x2-x+1,1); |
364 | 68.3k | if (q == (PixelPacket *) NULL) |
365 | 0 | { |
366 | 0 | status=MagickFail; |
367 | 0 | break; |
368 | 0 | } |
369 | | |
370 | 98.8k | for ( ; x <= x2; x++) |
371 | 96.5k | { |
372 | 96.5k | if (FuzzyColorMatch(q,&target,image->fuzz)) |
373 | 66.0k | break; |
374 | 30.4k | q++; |
375 | 30.4k | } |
376 | 68.3k | } |
377 | 2.15k | else /* method == FillToBorderMethod */ |
378 | 2.15k | { |
379 | 2.15k | q=GetImagePixels(image,x,y,x2-x+1,1); |
380 | 2.15k | if (q == (PixelPacket *) NULL) |
381 | 0 | { |
382 | 0 | status=MagickFail; |
383 | 0 | break; |
384 | 0 | } |
385 | | |
386 | 302k | for ( ; x <= x2; x++) |
387 | 301k | { |
388 | 301k | if (!FuzzyColorMatch(q,&target,image->fuzz) && |
389 | 301k | !FuzzyColorMatch(q,&draw_info->fill,image->fuzz)) |
390 | 1.20k | break; |
391 | 300k | q++; |
392 | 300k | } |
393 | 2.15k | } |
394 | 70.4k | } |
395 | 856k | start=x; |
396 | 856k | } while (x <= x2); |
397 | 789k | } |
398 | 2.24k | pattern=draw_info->fill_pattern; |
399 | 2.24k | if (pattern == (Image *) NULL) |
400 | 1.86k | { |
401 | | /* |
402 | | Tile fill color onto floodplane. |
403 | | */ |
404 | 1.86k | if (status == MagickPass) |
405 | 1.86k | { |
406 | | #if ENABLE_COLORFLOODFILL_OPENMP |
407 | | #if defined(HAVE_OPENMP) |
408 | | # pragma omp parallel for schedule(static,64) shared(status) |
409 | | #endif |
410 | | #endif |
411 | 286k | for (y=0; y < (long) image->rows; y++) |
412 | 284k | { |
413 | 284k | PixelPacket |
414 | 284k | * restrict p; |
415 | | |
416 | 284k | if (status == MagickFail) |
417 | 0 | continue; |
418 | | |
419 | 284k | p=GetImagePixels(image,0,y,image->columns,1); |
420 | 284k | if (p == (PixelPacket *) NULL) |
421 | 0 | { |
422 | 0 | goto tile_color_fail; |
423 | 0 | } |
424 | 311M | for (x=0; x < (long) image->columns; x++) |
425 | 311M | { |
426 | 311M | if (floodplane[y*image->columns+x]) |
427 | 130M | *p=draw_info->fill; |
428 | 311M | p++; |
429 | 311M | } |
430 | 284k | if (!SyncImagePixels(image)) |
431 | 0 | { |
432 | 0 | goto tile_color_fail; |
433 | 0 | } |
434 | | /* Continue loop processing */ |
435 | 284k | continue; |
436 | | |
437 | | /* There was a problem */ |
438 | 284k | tile_color_fail:; |
439 | 0 | status=MagickFail; |
440 | | #if ENABLE_COLORFLOODFILL_OPENMP |
441 | | #if defined(HAVE_OPENMP) |
442 | | # pragma omp flush (status) |
443 | | #endif |
444 | | #endif |
445 | 0 | } |
446 | 1.86k | } |
447 | 1.86k | } |
448 | 383 | else |
449 | 383 | { |
450 | | /* |
451 | | Tile image onto floodplane. |
452 | | */ |
453 | 383 | if (status == MagickPass) |
454 | 383 | { |
455 | | #if ENABLE_COLORFLOODFILL_OPENMP |
456 | | #if defined(HAVE_OPENMP) |
457 | | # pragma omp parallel for schedule(static,64) shared(status) |
458 | | #endif |
459 | | #endif |
460 | 4.36k | for (y=0; y < (long) image->rows; y++) |
461 | 3.98k | { |
462 | 3.98k | PixelPacket |
463 | 3.98k | * restrict p; |
464 | | |
465 | 3.98k | if (status == MagickFail) |
466 | 0 | continue; |
467 | | |
468 | 3.98k | p=GetImagePixels(image,0,y,image->columns,1); |
469 | 3.98k | if (p == (PixelPacket *) NULL) |
470 | 0 | goto tile_pattern_fail; |
471 | | |
472 | 20.2k | for (x=0; x < (long) image->columns; x++) |
473 | 16.2k | { |
474 | 16.2k | if (floodplane[y*image->columns+x]) |
475 | 10.0k | { |
476 | 10.0k | if (!AcquireOnePixelByReference(pattern, |
477 | 10.0k | &color, |
478 | 10.0k | (long) ((unsigned long) |
479 | 10.0k | (x-pattern->tile_info.x) % |
480 | 10.0k | pattern->columns), |
481 | 10.0k | (long) ((unsigned long) |
482 | 10.0k | (y-pattern->tile_info.y) % |
483 | 10.0k | pattern->rows), |
484 | 10.0k | &image->exception)) |
485 | 0 | goto tile_pattern_fail; |
486 | 10.0k | if (!pattern->matte) |
487 | 0 | color.opacity=OpaqueOpacity; |
488 | 10.0k | if (color.opacity != TransparentOpacity) |
489 | 9.63k | AlphaCompositePixel(p,&color,color.opacity,p,p->opacity); |
490 | 10.0k | } |
491 | 16.2k | p++; |
492 | 16.2k | } /* for x ... */ |
493 | 3.98k | if (!SyncImagePixels(image)) |
494 | 0 | goto tile_pattern_fail; |
495 | | |
496 | | /* Continue loop processing */ |
497 | 3.98k | continue; |
498 | | |
499 | | /* There was a problem */ |
500 | 3.98k | tile_pattern_fail:; |
501 | 0 | status=MagickFail; |
502 | | #if ENABLE_COLORFLOODFILL_OPENMP |
503 | | #if defined(HAVE_OPENMP) |
504 | | # pragma omp flush (status) |
505 | | #endif |
506 | | #endif |
507 | 0 | } /* for y ... */ |
508 | 383 | } /* if status ... */ |
509 | 383 | } |
510 | 2.24k | MagickFreeResourceLimitedMemory(SegmentInfo *, segment_stack); |
511 | 2.24k | MagickFreeResourceLimitedMemory(unsigned char *, floodplane); |
512 | 2.24k | return(status); |
513 | 2.24k | } |
514 | | |
515 | | /* |
516 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
517 | | % % |
518 | | % % |
519 | | % % |
520 | | % M a t t e F l o o d f i l l I m a g e % |
521 | | % % |
522 | | % % |
523 | | % % |
524 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
525 | | % |
526 | | % MatteFloodfill() changes the transparency value of any pixel that matches |
527 | | % target and is an immediate neighbor. If the method FillToBorderMethod |
528 | | % is specified, the transparency value is changed for any neighbor pixel |
529 | | % that does not match the bordercolor member of image. |
530 | | % |
531 | | % By default target must match a particular pixel transparency exactly. |
532 | | % However, in many cases two transparency values may differ by a |
533 | | % small amount. The fuzz member of image defines how much tolerance is |
534 | | % acceptable to consider two transparency values as the same. For example, |
535 | | % set fuzz to 10 and the opacity values of 100 and 102 respectively are |
536 | | % now interpreted as the same value for the purposes of the floodfill. |
537 | | % |
538 | | % The format of the MatteFloodfillImage method is: |
539 | | % |
540 | | % unsigned int MatteFloodfillImage(Image *image,const PixelPacket target, |
541 | | % const unsigned int opacity,const long x_offset,const long y_offset, |
542 | | % const PaintMethod method) |
543 | | % |
544 | | % A description of each parameter follows: |
545 | | % |
546 | | % o image: The image. |
547 | | % |
548 | | % o target: The RGB value of the target color. |
549 | | % |
550 | | % o opacity: The level of transparency: 0 is fully opaque and MaxRGB is |
551 | | % fully transparent. |
552 | | % |
553 | | % o x,y: The starting location of the operation. |
554 | | % |
555 | | % o method: Choose either FloodfillMethod or FillToBorderMethod. |
556 | | % |
557 | | % |
558 | | */ |
559 | | MagickExport MagickPassFail MatteFloodfillImage(Image *image, |
560 | | const PixelPacket target,const unsigned int opacity,const long x_offset, |
561 | | const long y_offset,const PaintMethod method) |
562 | 1.67k | { |
563 | 1.67k | int |
564 | 1.67k | skip; |
565 | | |
566 | 1.67k | long |
567 | 1.67k | offset, |
568 | 1.67k | start, |
569 | 1.67k | x1, |
570 | 1.67k | x2, |
571 | 1.67k | y; |
572 | | |
573 | 1.67k | register long |
574 | 1.67k | x; |
575 | | |
576 | 1.67k | register PixelPacket |
577 | 1.67k | *q; |
578 | | |
579 | 1.67k | register SegmentInfo |
580 | 1.67k | *s; |
581 | | |
582 | 1.67k | SegmentInfo |
583 | 1.67k | *segment_stack; |
584 | | |
585 | 1.67k | void |
586 | 1.67k | *floodplane = (void *) NULL; /* Only to make common Push macro happy */ |
587 | | |
588 | 1.67k | MagickPassFail |
589 | 1.67k | status=MagickPass; |
590 | | |
591 | | /* |
592 | | Check boundary conditions. |
593 | | */ |
594 | 1.67k | assert(image != (Image *) NULL); |
595 | 1.67k | assert(image->signature == MagickSignature); |
596 | 1.67k | if ((x_offset < 0) || (x_offset >= (long) image->columns)) |
597 | 63 | return(MagickFail); |
598 | 1.61k | if ((y_offset < 0) || (y_offset >= (long) image->rows)) |
599 | 69 | return(MagickFail); |
600 | 1.54k | if (target.opacity == opacity) |
601 | 5 | return(MagickFail); |
602 | 1.53k | q=GetImagePixels(image,x_offset,y_offset,1,1); |
603 | 1.53k | if (q == (PixelPacket *) NULL) |
604 | 0 | return(MagickFail); |
605 | 1.53k | if (q->opacity == opacity) |
606 | 4 | return(MagickFail); |
607 | | /* |
608 | | The flood-fill algorithm may lock-up if a clip-mask is present |
609 | | because the pixels just written may not be what is read later. |
610 | | |
611 | | Perhaps we will update the algorithm to respect the clip-mask later. |
612 | | */ |
613 | 1.53k | if (*ImageGetClipMaskInlined(image)) |
614 | 4 | { |
615 | 4 | ThrowException(&image->exception,DrawError,UnableToFloodFillImageDueToClipMask,image->filename); |
616 | 4 | return(MagickFail); |
617 | 4 | } |
618 | 1.52k | status &= SetImageType(image,TrueColorMatteType); |
619 | 1.52k | if (status == MagickFail) |
620 | 0 | return status; |
621 | | /* |
622 | | Allocate segment stack. |
623 | | */ |
624 | 1.52k | segment_stack=MagickAllocateResourceLimitedArray(SegmentInfo *,MaxStacksize,sizeof(SegmentInfo)); |
625 | 1.52k | if (segment_stack == (SegmentInfo *) NULL) |
626 | 0 | ThrowBinaryException3(ResourceLimitError,MemoryAllocationFailed, |
627 | 1.52k | UnableToFloodfillImage); |
628 | | /* |
629 | | Push initial segment on stack. |
630 | | */ |
631 | 1.52k | x=x_offset; |
632 | 1.52k | y=y_offset; |
633 | 1.52k | start=0; |
634 | 1.52k | s=segment_stack; |
635 | 1.52k | Push(y,x,x,1); |
636 | 1.52k | Push((ptrdiff_t)y+1,x,x,-1); |
637 | 600k | while (s > segment_stack) |
638 | 599k | { |
639 | | /* |
640 | | Pop segment off stack. |
641 | | */ |
642 | 599k | s--; |
643 | 599k | x1=(long) s->x1; |
644 | 599k | x2=(long) s->x2; |
645 | 599k | offset=(long) s->y2; |
646 | 599k | y=(long) s->y1+offset; |
647 | | /* |
648 | | Recolor neighboring points. |
649 | | */ |
650 | 599k | q=GetImagePixels(image,0,y,image->columns,1); |
651 | 599k | if (q == (PixelPacket *) NULL) |
652 | 0 | { |
653 | 0 | status=MagickFail; |
654 | 0 | break; |
655 | 0 | } |
656 | 599k | q+=x1; |
657 | 1.39M | for (x=x1; x >= 0; x--) |
658 | 815k | { |
659 | 815k | if (method == FloodfillMethod) |
660 | 798k | { |
661 | 798k | if (!FuzzyOpacityMatch(q,&target,image->fuzz)) |
662 | 13.1k | break; |
663 | 798k | } |
664 | 17.3k | else |
665 | 17.3k | if (FuzzyOpacityMatch(q,&target,image->fuzz) || (q->opacity == opacity)) |
666 | 5.65k | break; |
667 | 797k | q->opacity=opacity; |
668 | 797k | q--; |
669 | 797k | } |
670 | 599k | if (!SyncImagePixels(image)) |
671 | 0 | { |
672 | 0 | status=MagickFail; |
673 | 0 | break; |
674 | 0 | } |
675 | 599k | skip=x >= x1; |
676 | 599k | if (!skip) |
677 | 591k | { |
678 | 591k | start=x+1; |
679 | 591k | if (start < x1) |
680 | 591k | Push(y,start, (ptrdiff_t)x1-1,-offset); |
681 | 591k | x=x1+1; |
682 | 591k | } |
683 | 599k | do |
684 | 601k | { |
685 | 601k | if (!skip) |
686 | 594k | { |
687 | 594k | q=GetImagePixels(image,0,y,image->columns,1); |
688 | 594k | if (q == (PixelPacket *) NULL) |
689 | 0 | { |
690 | 0 | status=MagickFail; |
691 | 0 | break; |
692 | 0 | } |
693 | 594k | q+=x; |
694 | 26.4M | for ( ; x < (long) image->columns; x++) |
695 | 25.8M | { |
696 | 25.8M | if (method == FloodfillMethod) |
697 | 25.4M | { |
698 | 25.4M | if (!FuzzyOpacityMatch(q,&target,image->fuzz)) |
699 | 7.44k | break; |
700 | 25.4M | } |
701 | 438k | else |
702 | 438k | if (FuzzyOpacityMatch(q,&target,image->fuzz) || |
703 | 438k | (q->opacity == opacity)) |
704 | 4.96k | break; |
705 | 25.8M | q->opacity=opacity; |
706 | 25.8M | q++; |
707 | 25.8M | } |
708 | 594k | if (!SyncImagePixels(image)) |
709 | 0 | { |
710 | 0 | status=MagickFail; |
711 | 0 | break; |
712 | 0 | } |
713 | 594k | Push(y,start, (ptrdiff_t)x-1,offset); |
714 | 594k | if (x > ((ptrdiff_t)x2+1)) |
715 | 594k | Push(y, (ptrdiff_t)x2+1, (ptrdiff_t)x-1,-offset); |
716 | 594k | } |
717 | 601k | skip=False; |
718 | 601k | q=GetImagePixels(image,0,y,image->columns,1); |
719 | 601k | if (q == (PixelPacket *) NULL) |
720 | 0 | { |
721 | 0 | status=MagickFail; |
722 | 0 | break; |
723 | 0 | } |
724 | 601k | q+=x; |
725 | 832k | for (x++; x <= x2; x++) |
726 | 233k | { |
727 | 233k | q++; |
728 | 233k | if (method == FloodfillMethod) |
729 | 231k | { |
730 | 231k | if (FuzzyOpacityMatch(q,&target,image->fuzz)) |
731 | 2.15k | break; |
732 | 231k | } |
733 | 1.39k | else |
734 | 1.39k | if (!FuzzyOpacityMatch(q,&target,image->fuzz) && |
735 | 1.38k | (q->opacity != opacity)) |
736 | 389 | break; |
737 | 233k | } |
738 | 601k | start=x; |
739 | 601k | } while (x <= x2); |
740 | 599k | } |
741 | 1.52k | MagickFreeResourceLimitedMemory(SegmentInfo*, segment_stack); |
742 | 1.52k | return(status); |
743 | 1.52k | } |
744 | | |
745 | | /* |
746 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
747 | | % % |
748 | | % % |
749 | | % O p a q u e I m a g e % |
750 | | % % |
751 | | % % |
752 | | % % |
753 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
754 | | % |
755 | | % OpaqueImage() changes any pixel that matches color with the color |
756 | | % defined by fill. |
757 | | % |
758 | | % By default color must match a particular pixel color exactly. However, |
759 | | % in many cases two colors may differ by a small amount. Fuzz defines |
760 | | % how much tolerance is acceptable to consider two colors as the same. |
761 | | % For example, set fuzz to 10 and the color red at intensities of 100 and |
762 | | % 102 respectively are now interpreted as the same color. |
763 | | % |
764 | | % The format of the OpaqueImage method is: |
765 | | % |
766 | | % unsigned int OpaqueImage(Image *image,const PixelPacket target, |
767 | | % const PixelPacket fill) |
768 | | % |
769 | | % A description of each parameter follows: |
770 | | % |
771 | | % o image: The image. |
772 | | % |
773 | | % o target: The RGB value of the target color. |
774 | | % |
775 | | % o fill: The replacement color. |
776 | | % |
777 | | % |
778 | | */ |
779 | | typedef struct _OpaqueImageOptions_t |
780 | | { |
781 | | double fuzz; |
782 | | PixelPacket fill; |
783 | | PixelPacket target; |
784 | | } OpaqueImageOptions_t; |
785 | | static MagickPassFail |
786 | | OpaqueImageCallBack(void *mutable_data, /* User provided mutable data */ |
787 | | const void *immutable_data, /* User provided immutable data */ |
788 | | Image * restrict image, /* Modify image */ |
789 | | PixelPacket * restrict pixels, /* Pixel row */ |
790 | | IndexPacket * restrict indexes, /* Pixel row indexes */ |
791 | | const long npixels, /* Number of pixels in row */ |
792 | | ExceptionInfo *exception) /* Exception report */ |
793 | 0 | { |
794 | 0 | const OpaqueImageOptions_t |
795 | 0 | options = *((const OpaqueImageOptions_t *) immutable_data); |
796 | |
|
797 | 0 | register long |
798 | 0 | i; |
799 | |
|
800 | 0 | ARG_NOT_USED(mutable_data); |
801 | 0 | ARG_NOT_USED(image); |
802 | 0 | ARG_NOT_USED(indexes); |
803 | 0 | ARG_NOT_USED(exception); |
804 | |
|
805 | 0 | if (options.fuzz == 0.0) |
806 | 0 | { |
807 | 0 | for (i=0; i < npixels; i++) |
808 | 0 | { |
809 | 0 | if (ColorMatch(&pixels[i],&options.target)) |
810 | 0 | pixels[i]=options.fill; |
811 | 0 | } |
812 | 0 | } |
813 | 0 | else |
814 | 0 | { |
815 | 0 | for (i=0; i < npixels; i++) |
816 | 0 | { |
817 | 0 | if (FuzzyColorMatch(&pixels[i],&options.target,options.fuzz)) |
818 | 0 | pixels[i]=options.fill; |
819 | 0 | } |
820 | 0 | } |
821 | |
|
822 | 0 | return MagickPass; |
823 | 0 | } |
824 | | MagickExport MagickPassFail |
825 | | OpaqueImage(Image *image,const PixelPacket target,const PixelPacket fill) |
826 | 0 | { |
827 | 0 | #define OpaqueImageText "[%s] Setting opaque color..." |
828 | |
|
829 | 0 | OpaqueImageOptions_t |
830 | 0 | options; |
831 | |
|
832 | 0 | MagickPassFail |
833 | 0 | status=MagickPass; |
834 | |
|
835 | 0 | MagickBool |
836 | 0 | is_monochrome = image->is_monochrome, |
837 | 0 | is_grayscale = image->is_grayscale; |
838 | |
|
839 | 0 | assert(image != (Image *) NULL); |
840 | 0 | assert(image->signature == MagickSignature); |
841 | 0 | options.fuzz=image->fuzz; |
842 | 0 | options.fill=fill; |
843 | 0 | options.target=target; |
844 | | |
845 | | /* |
846 | | Assure that image type is promoted as required based on color |
847 | | */ |
848 | 0 | if ((is_grayscale || IsGrayColorspace(image->colorspace)) && !IsGray(fill)) |
849 | 0 | { |
850 | 0 | if ((is_monochrome) && !IsBlackPixel(fill) && !IsWhitePixel(fill)) |
851 | 0 | is_monochrome = MagickFalse; |
852 | 0 | is_grayscale = MagickFalse; |
853 | 0 | } |
854 | | |
855 | | /* |
856 | | Make image color opaque. |
857 | | */ |
858 | 0 | if (image->storage_class == PseudoClass) |
859 | 0 | { |
860 | 0 | assert(image->colormap != (PixelPacket *) NULL); |
861 | 0 | (void) OpaqueImageCallBack(0,&options,image,image->colormap, |
862 | 0 | (IndexPacket *) NULL,image->colors, |
863 | 0 | &image->exception); |
864 | 0 | status &= SyncImage(image); |
865 | 0 | } |
866 | 0 | else |
867 | 0 | { |
868 | 0 | status=PixelIterateMonoModify(OpaqueImageCallBack,NULL, |
869 | 0 | OpaqueImageText,NULL,&options,0,0, |
870 | 0 | image->columns,image->rows, |
871 | 0 | image,&image->exception); |
872 | 0 | } |
873 | |
|
874 | 0 | image->is_monochrome = is_monochrome; |
875 | 0 | image->is_grayscale = is_grayscale; |
876 | |
|
877 | 0 | return(status); |
878 | 0 | } |
879 | | |
880 | | /* |
881 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
882 | | % % |
883 | | % % |
884 | | % T r a n s p a r e n t I m a g e % |
885 | | % % |
886 | | % % |
887 | | % % |
888 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
889 | | % |
890 | | % TransparentImage() changes the opacity value associated with any pixel |
891 | | % that matches color to the value defined by opacity. |
892 | | % |
893 | | % By default color must match a particular pixel color exactly. However, |
894 | | % in many cases two colors may differ by a small amount. Fuzz defines |
895 | | % how much tolerance is acceptable to consider two colors as the same. |
896 | | % For example, set fuzz to 10 and the color red at intensities of 100 and |
897 | | % 102 respectively are now interpreted as the same color. |
898 | | % |
899 | | % The format of the TransparentImage method is: |
900 | | % |
901 | | % unsigned int TransparentImage(Image *image,const PixelPacket target, |
902 | | % const unsigned int opacity) |
903 | | % |
904 | | % A description of each parameter follows: |
905 | | % |
906 | | % o image: The image. |
907 | | % |
908 | | % o target: The RGB value of the target color. |
909 | | % |
910 | | % o opacity: The replacement opacity value. |
911 | | % |
912 | | % |
913 | | */ |
914 | | typedef struct _TransparentImageOptions_t |
915 | | { |
916 | | double fuzz; |
917 | | PixelPacket target; |
918 | | unsigned int opacity; |
919 | | } TransparentImageOptions_t; |
920 | | static MagickPassFail |
921 | | TransparentImageCallBack(void *mutable_data, /* User provided mutable data */ |
922 | | const void *immutable_data, /* User provided immutable data */ |
923 | | Image * restrict image, /* Modify image */ |
924 | | PixelPacket * restrict pixels, /* Pixel row */ |
925 | | IndexPacket * restrict indexes, /* Pixel row indexes */ |
926 | | const long npixels, /* Number of pixels in row */ |
927 | | ExceptionInfo *exception) /* Exception report */ |
928 | 42.9k | { |
929 | 42.9k | const TransparentImageOptions_t |
930 | 42.9k | options = *((const TransparentImageOptions_t *) immutable_data); |
931 | | |
932 | 42.9k | const MagickBool |
933 | 42.9k | clear_matte = (!image->matte); |
934 | | |
935 | 42.9k | register long |
936 | 42.9k | i; |
937 | | |
938 | 42.9k | ARG_NOT_USED(mutable_data); |
939 | 42.9k | ARG_NOT_USED(image); |
940 | 42.9k | ARG_NOT_USED(indexes); |
941 | 42.9k | ARG_NOT_USED(exception); |
942 | | |
943 | 42.9k | if (options.fuzz == 0.0) |
944 | 42.9k | { |
945 | 20.5M | for (i=0; i < npixels; i++) |
946 | 20.5M | { |
947 | 20.5M | if (ColorMatch(&pixels[i],&options.target)) |
948 | 4.63M | pixels[i].opacity=options.opacity; |
949 | 15.8M | else if (clear_matte) |
950 | 15.7M | pixels[i].opacity=OpaqueOpacity; |
951 | 20.5M | } |
952 | 42.9k | } |
953 | 0 | else |
954 | 0 | { |
955 | 0 | for (i=0; i < npixels; i++) |
956 | 0 | { |
957 | 0 | if (FuzzyColorMatch(&pixels[i],&options.target,options.fuzz)) |
958 | 0 | pixels[i].opacity=options.opacity; |
959 | 0 | else if (clear_matte) |
960 | 0 | pixels[i].opacity=OpaqueOpacity; |
961 | 0 | } |
962 | 0 | } |
963 | | |
964 | 42.9k | return MagickPass; |
965 | 42.9k | } |
966 | | MagickExport MagickPassFail |
967 | | TransparentImage(Image *image,const PixelPacket target, |
968 | | const unsigned int opacity) |
969 | 1.00k | { |
970 | 1.00k | #define TransparentImageText "[%s] Setting transparent color... " |
971 | | |
972 | 1.00k | TransparentImageOptions_t |
973 | 1.00k | options; |
974 | | |
975 | 1.00k | MagickPassFail |
976 | 1.00k | status=MagickPass; |
977 | | |
978 | | /* |
979 | | Make image color transparent. |
980 | | */ |
981 | 1.00k | assert(image != (Image *) NULL); |
982 | 1.00k | assert(image->signature == MagickSignature); |
983 | 1.00k | options.fuzz=image->fuzz; |
984 | 1.00k | options.opacity=opacity; |
985 | 1.00k | options.target=target; |
986 | 1.00k | if (image->storage_class == PseudoClass) |
987 | 485 | { |
988 | 485 | assert(image->colormap != (PixelPacket *) NULL); |
989 | 485 | (void) TransparentImageCallBack(0,&options,image,image->colormap, |
990 | 485 | (IndexPacket *) NULL,image->colors, |
991 | 485 | &image->exception); |
992 | 485 | status &= SyncImage(image); |
993 | 485 | } |
994 | 521 | else |
995 | 521 | { |
996 | 521 | status=PixelIterateMonoModify(TransparentImageCallBack,NULL, |
997 | 521 | TransparentImageText,NULL,&options,0,0, |
998 | 521 | image->columns,image->rows, |
999 | 521 | image,&image->exception); |
1000 | 521 | } |
1001 | 1.00k | image->matte=MagickTrue; |
1002 | | |
1003 | 1.00k | return(status); |
1004 | 1.00k | } |