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