MagickCore 6.9.13
All Data Structures
paint.c
1/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% PPPP AAA IIIII N N TTTTT %
7% P P A A I NN N T %
8% PPPP AAAAA I N N N T %
9% P A A I N NN T %
10% P A A IIIII N N T %
11% %
12% %
13% Methods to Paint on an Image %
14% %
15% Software Design %
16% Cristy %
17% July 1998 %
18% %
19% %
20% Copyright 1999 ImageMagick Studio LLC, a non-profit organization %
21% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
26% https://imagemagick.org/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
36%
37*/
38
39/*
40 Include declarations.
41*/
42#include "magick/studio.h"
43#include "magick/artifact.h"
44#include "magick/cache.h"
45#include "magick/channel.h"
46#include "magick/color-private.h"
47#include "magick/colorspace-private.h"
48#include "magick/composite.h"
49#include "magick/composite-private.h"
50#include "magick/draw.h"
51#include "magick/draw-private.h"
52#include "magick/exception.h"
53#include "magick/exception-private.h"
54#include "magick/gem.h"
55#include "magick/monitor.h"
56#include "magick/monitor-private.h"
57#include "magick/option.h"
58#include "magick/paint.h"
59#include "magick/pixel-private.h"
60#include "magick/resource_.h"
61#include "magick/string_.h"
62#include "magick/string-private.h"
63#include "magick/thread-private.h"
64
65/*
66%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
67% %
68% %
69% %
70% F l o o d f i l l P a i n t I m a g e %
71% %
72% %
73% %
74%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
75%
76% FloodfillPaintImage() changes the color value of any pixel that matches
77% target and is an immediate neighbor. If the method FillToBorderMethod is
78% specified, the color value is changed for any neighbor pixel that does not
79% match the bordercolor member of image.
80%
81% By default target must match a particular pixel color exactly.
82% However, in many cases two colors may differ by a small amount. The
83% fuzz member of image defines how much tolerance is acceptable to
84% consider two colors as the same. For example, set fuzz to 10 and the
85% color red at intensities of 100 and 102 respectively are now
86% interpreted as the same color for the purposes of the floodfill.
87%
88% The format of the FloodfillPaintImage method is:
89%
90% MagickBooleanType FloodfillPaintImage(Image *image,
91% const ChannelType channel,const DrawInfo *draw_info,
92% const MagickPixelPacket target,const ssize_t x_offset,
93% const ssize_t y_offset,const MagickBooleanType invert)
94%
95% A description of each parameter follows:
96%
97% o image: the image.
98%
99% o channel: the channel(s).
100%
101% o draw_info: the draw info.
102%
103% o target: the RGB value of the target color.
104%
105% o x_offset,y_offset: the starting location of the operation.
106%
107% o invert: paint any pixel that does not match the target color.
108%
109*/
110MagickExport MagickBooleanType FloodfillPaintImage(Image *image,
111 const ChannelType channel,const DrawInfo *draw_info,
112 const MagickPixelPacket *target,const ssize_t x_offset,const ssize_t y_offset,
113 const MagickBooleanType invert)
114{
115#define MaxStacksize 524288UL
116#define PushSegmentStack(up,left,right,delta) \
117{ \
118 if (s >= (segment_stack+MaxStacksize)) \
119 { \
120 segment_info=RelinquishVirtualMemory(segment_info); \
121 image_view=DestroyCacheView(image_view); \
122 floodplane_view=DestroyCacheView(floodplane_view); \
123 floodplane_image=DestroyImage(floodplane_image); \
124 ThrowBinaryException(DrawError,"SegmentStackOverflow",image->filename) \
125 } \
126 else \
127 { \
128 if ((((up)+(delta)) >= 0) && (((up)+(delta)) < (ssize_t) image->rows)) \
129 { \
130 s->x1=(double) (left); \
131 s->y1=(double) (up); \
132 s->x2=(double) (right); \
133 s->y2=(double) (delta); \
134 s++; \
135 } \
136 } \
137}
138
140 *floodplane_view,
141 *image_view;
142
144 *exception;
145
146 Image
147 *floodplane_image;
148
149 MagickBooleanType
150 skip,
151 status;
152
154 pixel;
155
157 *segment_info;
158
160 *s,
161 *segment_stack;
162
163 ssize_t
164 offset,
165 start,
166 x,
167 x1,
168 x2,
169 y;
170
171 /*
172 Check boundary conditions.
173 */
174 assert(image != (Image *) NULL);
175 assert(image->signature == MagickCoreSignature);
176 assert(draw_info != (DrawInfo *) NULL);
177 assert(draw_info->signature == MagickCoreSignature);
178 if (IsEventLogging() != MagickFalse)
179 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
180 if ((x_offset < 0) || (x_offset >= (ssize_t) image->columns))
181 return(MagickFalse);
182 if ((y_offset < 0) || (y_offset >= (ssize_t) image->rows))
183 return(MagickFalse);
184 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
185 return(MagickFalse);
186 exception=(&image->exception);
187 if (IsGrayColorspace(image->colorspace) != MagickFalse)
188 (void) SetImageColorspace(image,sRGBColorspace);
189 if ((image->matte == MagickFalse) &&
190 (draw_info->fill.opacity != OpaqueOpacity))
191 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel);
192 /*
193 Set floodfill state.
194 */
195 floodplane_image=CloneImage(image,0,0,MagickTrue,&image->exception);
196 if (floodplane_image == (Image *) NULL)
197 return(MagickFalse);
198 floodplane_image->matte=MagickFalse;
199 floodplane_image->colorspace=GRAYColorspace;
200 (void) QueryColorCompliance("#000",AllCompliance,
201 &floodplane_image->background_color,exception);
202 (void) SetImageBackgroundColor(floodplane_image);
203 segment_info=AcquireVirtualMemory(MaxStacksize,sizeof(*segment_stack));
204 if (segment_info == (MemoryInfo *) NULL)
205 {
206 floodplane_image=DestroyImage(floodplane_image);
207 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
208 image->filename);
209 }
210 segment_stack=(SegmentInfo *) GetVirtualMemoryBlob(segment_info);
211 /*
212 Push initial segment on stack.
213 */
214 x=x_offset;
215 y=y_offset;
216 start=0;
217 s=segment_stack;
218 GetMagickPixelPacket(image,&pixel);
219 image_view=AcquireVirtualCacheView(image,exception);
220 floodplane_view=AcquireAuthenticCacheView(floodplane_image,exception);
221 PushSegmentStack(y,x,x,1);
222 PushSegmentStack(y+1,x,x,-1);
223 while (s > segment_stack)
224 {
225 const IndexPacket
226 *magick_restrict indexes;
227
228 const PixelPacket
229 *magick_restrict p;
230
232 *magick_restrict q;
233
234 ssize_t
235 x;
236
237 /*
238 Pop segment off stack.
239 */
240 s--;
241 x1=(ssize_t) s->x1;
242 x2=(ssize_t) s->x2;
243 offset=(ssize_t) s->y2;
244 y=(ssize_t) s->y1+offset;
245 /*
246 Recolor neighboring pixels.
247 */
248 p=GetCacheViewVirtualPixels(image_view,0,y,(size_t) (x1+1),1,exception);
249 q=GetCacheViewAuthenticPixels(floodplane_view,0,y,(size_t) (x1+1),1,
250 exception);
251 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
252 break;
253 indexes=GetCacheViewVirtualIndexQueue(image_view);
254 p+=(ptrdiff_t) x1;
255 q+=(ptrdiff_t) x1;
256 for (x=x1; x >= 0; x--)
257 {
258 if (GetPixelGray(q) != 0)
259 break;
260 SetMagickPixelPacket(image,p,indexes+x,&pixel);
261 if (IsMagickColorSimilar(&pixel,target) == invert)
262 break;
263 SetPixelGray(q,QuantumRange);
264 p--;
265 q--;
266 }
267 if (SyncCacheViewAuthenticPixels(floodplane_view,exception) == MagickFalse)
268 break;
269 skip=x >= x1 ? MagickTrue : MagickFalse;
270 if (skip == MagickFalse)
271 {
272 start=x+1;
273 if (start < x1)
274 PushSegmentStack(y,start,x1-1,-offset);
275 x=x1+1;
276 }
277 do
278 {
279 if (skip == MagickFalse)
280 {
281 if (x < (ssize_t) image->columns)
282 {
283 p=GetCacheViewVirtualPixels(image_view,x,y,image->columns-x,1,
284 exception);
285 q=GetCacheViewAuthenticPixels(floodplane_view,x,y,
286 image->columns-x,1,exception);
287 if ((p == (const PixelPacket *) NULL) ||
288 (q == (PixelPacket *) NULL))
289 break;
290 indexes=GetCacheViewVirtualIndexQueue(image_view);
291 for ( ; x < (ssize_t) image->columns; x++)
292 {
293 if (GetPixelGray(q) != 0)
294 break;
295 SetMagickPixelPacket(image,p,indexes+x,&pixel);
296 if (IsMagickColorSimilar(&pixel,target) == invert)
297 break;
298 SetPixelGray(q,QuantumRange);
299 p++;
300 q++;
301 }
302 if (SyncCacheViewAuthenticPixels(floodplane_view,exception) == MagickFalse)
303 break;
304 }
305 PushSegmentStack(y,start,x-1,offset);
306 if (x > (x2+1))
307 PushSegmentStack(y,x2+1,x-1,-offset);
308 }
309 skip=MagickFalse;
310 x++;
311 if (x <= x2)
312 {
313 p=GetCacheViewVirtualPixels(image_view,x,y,(size_t) (x2-x+1),1,
314 exception);
315 q=GetCacheViewAuthenticPixels(floodplane_view,x,y,(size_t) (x2-x+1),1,
316 exception);
317 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
318 break;
319 indexes=GetCacheViewVirtualIndexQueue(image_view);
320 for ( ; x <= x2; x++)
321 {
322 if (GetPixelGray(q) != 0)
323 break;
324 SetMagickPixelPacket(image,p,indexes+x,&pixel);
325 if (IsMagickColorSimilar(&pixel,target) != invert)
326 break;
327 p++;
328 q++;
329 }
330 }
331 start=x;
332 } while (x <= x2);
333 }
334 status=MagickTrue;
335#if defined(MMAGICKCORE_OPENMP_SUPPORT)
336 #pragma omp parallel for schedule(static) shared(status) \
337 magick_number_threads(floodplane_image,image,image->rows,2)
338#endif
339 for (y=0; y < (ssize_t) image->rows; y++)
340 {
341 const PixelPacket
342 *magick_restrict p;
343
344 IndexPacket
345 *magick_restrict indexes;
346
348 *magick_restrict q;
349
350 ssize_t
351 x;
352
353 /*
354 Tile fill color onto floodplane.
355 */
356 if (status == MagickFalse)
357 continue;
358 p=GetCacheViewVirtualPixels(floodplane_view,0,y,image->columns,1,exception);
359 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
360 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
361 {
362 status=MagickFalse;
363 continue;
364 }
365 indexes=GetCacheViewAuthenticIndexQueue(image_view);
366 for (x=0; x < (ssize_t) image->columns; x++)
367 {
368 if (GetPixelGray(p) != 0)
369 {
371 fill;
372
374 fill_color;
375
376 (void) GetFillColor(draw_info,x,y,&fill_color);
377 GetMagickPixelPacket(image,&fill);
378 SetMagickPixelPacket(image,&fill_color,(IndexPacket *) NULL,&fill);
379 if (image->colorspace == CMYKColorspace)
380 ConvertRGBToCMYK(&fill);
381 if ((channel & RedChannel) != 0)
382 SetPixelRed(q,ClampToQuantum(fill.red));
383 if ((channel & GreenChannel) != 0)
384 SetPixelGreen(q,ClampToQuantum(fill.green));
385 if ((channel & BlueChannel) != 0)
386 SetPixelBlue(q,ClampToQuantum(fill.blue));
387 if (((channel & OpacityChannel) != 0) ||
388 (draw_info->fill.opacity != OpaqueOpacity))
389 SetPixelOpacity(q,ClampToQuantum(fill.opacity));
390 if (((channel & IndexChannel) != 0) &&
391 (image->colorspace == CMYKColorspace))
392 SetPixelIndex(indexes+x,ClampToQuantum(fill.index));
393 }
394 p++;
395 q++;
396 }
397 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
398 status=MagickFalse;
399 }
400 floodplane_view=DestroyCacheView(floodplane_view);
401 image_view=DestroyCacheView(image_view);
402 segment_info=RelinquishVirtualMemory(segment_info);
403 floodplane_image=DestroyImage(floodplane_image);
404 return(y == (ssize_t) image->rows ? MagickTrue : MagickFalse);
405}
406
407/*
408%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
409% %
410% %
411% %
412+ G r a d i e n t I m a g e %
413% %
414% %
415% %
416%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
417%
418% GradientImage() applies a continuously smooth color transitions along a
419% vector from one color to another.
420%
421% Note, the interface of this method will change in the future to support
422% more than one transition.
423%
424% The format of the GradientImage method is:
425%
426% MagickBooleanType GradientImage(Image *image,const GradientType type,
427% const SpreadMethod method,const PixelPacket *start_color,
428% const PixelPacket *stop_color)
429%
430% A description of each parameter follows:
431%
432% o image: the image.
433%
434% o type: the gradient type: linear or radial.
435%
436% o spread: the gradient spread method: pad, reflect, or repeat.
437%
438% o start_color: the start color.
439%
440% o stop_color: the stop color.
441%
442% This provides a good example of making use of the DrawGradientImage
443% function and the gradient structure in draw_info.
444%
445*/
446MagickExport MagickBooleanType GradientImage(Image *image,
447 const GradientType type,const SpreadMethod method,
448 const PixelPacket *start_color,const PixelPacket *stop_color)
449{
450 const char
451 *artifact;
452
454 *draw_info;
455
457 *gradient;
458
459 MagickBooleanType
460 status;
461
462 ssize_t
463 i;
464
465 /*
466 Set gradient start-stop end points.
467 */
468 assert(image != (const Image *) NULL);
469 assert(image->signature == MagickCoreSignature);
470 assert(start_color != (const PixelPacket *) NULL);
471 assert(stop_color != (const PixelPacket *) NULL);
472 if (IsEventLogging() != MagickFalse)
473 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
474 draw_info=AcquireDrawInfo();
475 gradient=(&draw_info->gradient);
476 gradient->type=type;
477 gradient->bounding_box.width=image->columns;
478 gradient->bounding_box.height=image->rows;
479 artifact=GetImageArtifact(image,"gradient:bounding-box");
480 if (artifact != (const char *) NULL)
481 (void) ParseAbsoluteGeometry(artifact,&gradient->bounding_box);
482 gradient->gradient_vector.x2=(double) image->columns-1;
483 gradient->gradient_vector.y2=(double) image->rows-1;
484 artifact=GetImageArtifact(image,"gradient:direction");
485 if (artifact != (const char *) NULL)
486 {
487 GravityType
488 direction;
489
490 direction=(GravityType) ParseCommandOption(MagickGravityOptions,
491 MagickFalse,artifact);
492 switch (direction)
493 {
494 case NorthWestGravity:
495 {
496 gradient->gradient_vector.x1=(double) image->columns-1;
497 gradient->gradient_vector.y1=(double) image->rows-1;
498 gradient->gradient_vector.x2=0.0;
499 gradient->gradient_vector.y2=0.0;
500 break;
501 }
502 case NorthGravity:
503 {
504 gradient->gradient_vector.x1=0.0;
505 gradient->gradient_vector.y1=(double) image->rows-1;
506 gradient->gradient_vector.x2=0.0;
507 gradient->gradient_vector.y2=0.0;
508 break;
509 }
510 case NorthEastGravity:
511 {
512 gradient->gradient_vector.x1=0.0;
513 gradient->gradient_vector.y1=(double) image->rows-1;
514 gradient->gradient_vector.x2=(double) image->columns-1;
515 gradient->gradient_vector.y2=0.0;
516 break;
517 }
518 case WestGravity:
519 {
520 gradient->gradient_vector.x1=(double) image->columns-1;
521 gradient->gradient_vector.y1=0.0;
522 gradient->gradient_vector.x2=0.0;
523 gradient->gradient_vector.y2=0.0;
524 break;
525 }
526 case EastGravity:
527 {
528 gradient->gradient_vector.x1=0.0;
529 gradient->gradient_vector.y1=0.0;
530 gradient->gradient_vector.x2=(double) image->columns-1;
531 gradient->gradient_vector.y2=0.0;
532 break;
533 }
534 case SouthWestGravity:
535 {
536 gradient->gradient_vector.x1=(double) image->columns-1;
537 gradient->gradient_vector.y1=0.0;
538 gradient->gradient_vector.x2=0.0;
539 gradient->gradient_vector.y2=(double) image->rows-1;
540 break;
541 }
542 case SouthGravity:
543 {
544 gradient->gradient_vector.x1=0.0;
545 gradient->gradient_vector.y1=0.0;
546 gradient->gradient_vector.x2=0.0;
547 gradient->gradient_vector.y2=(double) image->columns-1;
548 break;
549 }
550 case SouthEastGravity:
551 {
552 gradient->gradient_vector.x1=0.0;
553 gradient->gradient_vector.y1=0.0;
554 gradient->gradient_vector.x2=(double) image->columns-1;
555 gradient->gradient_vector.y2=(double) image->rows-1;
556 break;
557 }
558 default:
559 break;
560 }
561 }
562 artifact=GetImageArtifact(image,"gradient:angle");
563 if (artifact != (const char *) NULL)
564 gradient->angle=(MagickRealType) StringToDouble(artifact,(char **) NULL);
565 artifact=GetImageArtifact(image,"gradient:vector");
566 if (artifact != (const char *) NULL)
567 (void) sscanf(artifact,"%lf%*[ ,]%lf%*[ ,]%lf%*[ ,]%lf",
568 &gradient->gradient_vector.x1,&gradient->gradient_vector.y1,
569 &gradient->gradient_vector.x2,&gradient->gradient_vector.y2);
570 if ((GetImageArtifact(image,"gradient:angle") == (const char *) NULL) &&
571 (GetImageArtifact(image,"gradient:direction") == (const char *) NULL) &&
572 (GetImageArtifact(image,"gradient:extent") == (const char *) NULL) &&
573 (GetImageArtifact(image,"gradient:vector") == (const char *) NULL))
574 if ((type == LinearGradient) && (gradient->gradient_vector.y2 != 0.0))
575 gradient->gradient_vector.x2=0.0;
576 gradient->center.x=(double) gradient->gradient_vector.x2/2.0;
577 gradient->center.y=(double) gradient->gradient_vector.y2/2.0;
578 artifact=GetImageArtifact(image,"gradient:center");
579 if (artifact != (const char *) NULL)
580 (void) sscanf(artifact,"%lf%*[ ,]%lf",&gradient->center.x,
581 &gradient->center.y);
582 artifact=GetImageArtifact(image,"gradient:angle");
583 if ((type == LinearGradient) && (artifact != (const char *) NULL))
584 {
585 double
586 sine,
587 cosine,
588 distance;
589
590 /*
591 Reference https://drafts.csswg.org/css-images-3/#linear-gradients.
592 */
593 sine=sin((double) DegreesToRadians(gradient->angle-90.0));
594 cosine=cos((double) DegreesToRadians(gradient->angle-90.0));
595 distance=fabs((double) (image->columns-1)*cosine)+
596 fabs((double) (image->rows-1)*sine);
597 gradient->gradient_vector.x1=0.5*((image->columns-1)-distance*cosine);
598 gradient->gradient_vector.y1=0.5*((image->rows-1)-distance*sine);
599 gradient->gradient_vector.x2=0.5*((image->columns-1)+distance*cosine);
600 gradient->gradient_vector.y2=0.5*((image->rows-1)+distance*sine);
601 }
602 gradient->radii.x=(double) MagickMax((image->columns-1),(image->rows-1))/2.0;
603 gradient->radii.y=gradient->radii.x;
604 artifact=GetImageArtifact(image,"gradient:extent");
605 if (artifact != (const char *) NULL)
606 {
607 if (LocaleCompare(artifact,"Circle") == 0)
608 {
609 gradient->radii.x=(double) (MagickMax((image->columns-1),
610 (image->rows-1)))/2.0;
611 gradient->radii.y=gradient->radii.x;
612 }
613 if (LocaleCompare(artifact,"Diagonal") == 0)
614 {
615 gradient->radii.x=(double) (sqrt((double) (image->columns-1)*
616 (image->columns-1)+(image->rows-1)*(image->rows-1)))/2.0;
617 gradient->radii.y=gradient->radii.x;
618 }
619 if (LocaleCompare(artifact,"Ellipse") == 0)
620 {
621 gradient->radii.x=(double) (image->columns-1)/2.0;
622 gradient->radii.y=(double) (image->rows-1)/2.0;
623 }
624 if (LocaleCompare(artifact,"Maximum") == 0)
625 {
626 gradient->radii.x=(double) MagickMax((image->columns-1),
627 (image->rows-1))/2.0;
628 gradient->radii.y=gradient->radii.x;
629 }
630 if (LocaleCompare(artifact,"Minimum") == 0)
631 {
632 gradient->radii.x=(double) MagickMin((image->columns-1),
633 (image->rows-1))/2.0;
634 gradient->radii.y=gradient->radii.x;
635 }
636 }
637 artifact=GetImageArtifact(image,"gradient:radii");
638 if (artifact != (const char *) NULL)
639 (void) sscanf(artifact,"%lf%*[ ,]%lf",&gradient->radii.x,
640 &gradient->radii.y);
641 gradient->radius=MagickMax(gradient->radii.x,gradient->radii.y);
642 gradient->spread=method;
643 /*
644 Define the gradient to fill between the stops.
645 */
646 gradient->number_stops=2;
647 gradient->stops=(StopInfo *) AcquireQuantumMemory(gradient->number_stops,
648 sizeof(*gradient->stops));
649 if (gradient->stops == (StopInfo *) NULL)
650 ThrowBinaryImageException(ResourceLimitError,"MemoryAllocationFailed",
651 image->filename);
652 (void) memset(gradient->stops,0,gradient->number_stops*
653 sizeof(*gradient->stops));
654 for (i=0; i < (ssize_t) gradient->number_stops; i++)
655 GetMagickPixelPacket(image,&gradient->stops[i].color);
656 SetMagickPixelPacket(image,start_color,(IndexPacket *) NULL,
657 &gradient->stops[0].color);
658 gradient->stops[0].offset=0.0;
659 SetMagickPixelPacket(image,stop_color,(IndexPacket *) NULL,
660 &gradient->stops[1].color);
661 gradient->stops[1].offset=1.0;
662 /*
663 Draw a gradient on the image.
664 */
665 status=DrawGradientImage(image,draw_info);
666 draw_info=DestroyDrawInfo(draw_info);
667 return(status);
668}
669
670/*
671%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
672% %
673% %
674% %
675% O i l P a i n t I m a g e %
676% %
677% %
678% %
679%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
680%
681% OilPaintImage() applies a special effect filter that simulates an oil
682% painting. Each pixel is replaced by the most frequent color occurring
683% in a circular region defined by radius.
684%
685% The format of the OilPaintImage method is:
686%
687% Image *OilPaintImage(const Image *image,const double radius,
688% ExceptionInfo *exception)
689%
690% A description of each parameter follows:
691%
692% o image: the image.
693%
694% o radius: the radius of the circular neighborhood.
695%
696% o exception: return any errors or warnings in this structure.
697%
698*/
699
700static size_t **DestroyHistogramTLS(size_t **histogram)
701{
702 ssize_t
703 i;
704
705 assert(histogram != (size_t **) NULL);
706 for (i=0; i < (ssize_t) GetMagickResourceLimit(ThreadResource); i++)
707 if (histogram[i] != (size_t *) NULL)
708 histogram[i]=(size_t *) RelinquishMagickMemory(histogram[i]);
709 histogram=(size_t **) RelinquishMagickMemory(histogram);
710 return(histogram);
711}
712
713static size_t **AcquireHistogramTLS(const size_t count)
714{
715 ssize_t
716 i;
717
718 size_t
719 **histogram,
720 number_threads;
721
722 number_threads=(size_t) GetMagickResourceLimit(ThreadResource);
723 histogram=(size_t **) AcquireQuantumMemory(number_threads,
724 sizeof(*histogram));
725 if (histogram == (size_t **) NULL)
726 return((size_t **) NULL);
727 (void) memset(histogram,0,number_threads*sizeof(*histogram));
728 for (i=0; i < (ssize_t) number_threads; i++)
729 {
730 histogram[i]=(size_t *) AcquireQuantumMemory(count,
731 sizeof(**histogram));
732 if (histogram[i] == (size_t *) NULL)
733 return(DestroyHistogramTLS(histogram));
734 }
735 return(histogram);
736}
737
738MagickExport Image *OilPaintImage(const Image *image,const double radius,
739 ExceptionInfo *exception)
740{
741#define NumberPaintBins 256
742#define OilPaintImageTag "OilPaint/Image"
743
745 *image_view,
746 *paint_view;
747
748 Image
749 *linear_image,
750 *paint_image;
751
752 MagickBooleanType
753 status;
754
755 MagickOffsetType
756 progress;
757
758 size_t
759 **magick_restrict histograms,
760 width;
761
762 ssize_t
763 y;
764
765 /*
766 Initialize painted image attributes.
767 */
768 assert(image != (const Image *) NULL);
769 assert(image->signature == MagickCoreSignature);
770 assert(exception != (ExceptionInfo *) NULL);
771 assert(exception->signature == MagickCoreSignature);
772 if (IsEventLogging() != MagickFalse)
773 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
774 width=GetOptimalKernelWidth2D(radius,0.5);
775 linear_image=CloneImage(image,0,0,MagickTrue,exception);
776 paint_image=CloneImage(image,0,0,MagickTrue,exception);
777 if ((linear_image == (Image *) NULL) || (paint_image == (Image *) NULL))
778 {
779 if (linear_image != (Image *) NULL)
780 linear_image=DestroyImage(linear_image);
781 if (paint_image != (Image *) NULL)
782 linear_image=DestroyImage(paint_image);
783 return((Image *) NULL);
784 }
785 if (SetImageStorageClass(paint_image,DirectClass) == MagickFalse)
786 {
787 InheritException(exception,&paint_image->exception);
788 linear_image=DestroyImage(linear_image);
789 paint_image=DestroyImage(paint_image);
790 return((Image *) NULL);
791 }
792 histograms=AcquireHistogramTLS(NumberPaintBins);
793 if (histograms == (size_t **) NULL)
794 {
795 linear_image=DestroyImage(linear_image);
796 paint_image=DestroyImage(paint_image);
797 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
798 }
799 /*
800 Oil paint image.
801 */
802 status=MagickTrue;
803 progress=0;
804 image_view=AcquireVirtualCacheView(linear_image,exception);
805 paint_view=AcquireAuthenticCacheView(paint_image,exception);
806#if defined(MAGICKCORE_OPENMP_SUPPORT)
807 #pragma omp parallel for schedule(static) shared(progress,status) \
808 magick_number_threads(linear_image,paint_image,linear_image->rows,1)
809#endif
810 for (y=0; y < (ssize_t) linear_image->rows; y++)
811 {
812 const IndexPacket
813 *magick_restrict indexes;
814
815 const PixelPacket
816 *magick_restrict p;
817
818 IndexPacket
819 *magick_restrict paint_indexes;
820
821 ssize_t
822 x;
823
825 *magick_restrict q;
826
827 size_t
828 *histogram;
829
830 if (status == MagickFalse)
831 continue;
832 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
833 (width/2L),linear_image->columns+width,width,exception);
834 q=QueueCacheViewAuthenticPixels(paint_view,0,y,paint_image->columns,1,
835 exception);
836 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
837 {
838 status=MagickFalse;
839 continue;
840 }
841 indexes=GetCacheViewVirtualIndexQueue(image_view);
842 paint_indexes=GetCacheViewAuthenticIndexQueue(paint_view);
843 histogram=histograms[GetOpenMPThreadId()];
844 for (x=0; x < (ssize_t) linear_image->columns; x++)
845 {
846 ssize_t
847 i,
848 u;
849
850 size_t
851 count;
852
853 ssize_t
854 j,
855 k,
856 v;
857
858 /*
859 Assign most frequent color.
860 */
861 i=0;
862 j=0;
863 count=0;
864 (void) memset(histogram,0,NumberPaintBins*sizeof(*histogram));
865 for (v=0; v < (ssize_t) width; v++)
866 {
867 for (u=0; u < (ssize_t) width; u++)
868 {
869 k=(ssize_t) ScaleQuantumToChar(ClampToQuantum(GetPixelIntensity(
870 linear_image,p+u+i)));
871 histogram[k]++;
872 if (histogram[k] > count)
873 {
874 j=i+u;
875 count=histogram[k];
876 }
877 }
878 i+=(ssize_t) (linear_image->columns+width);
879 }
880 *q=(*(p+j));
881 if (linear_image->colorspace == CMYKColorspace)
882 SetPixelIndex(paint_indexes+x,GetPixelIndex(indexes+x+j));
883 p++;
884 q++;
885 }
886 if (SyncCacheViewAuthenticPixels(paint_view,exception) == MagickFalse)
887 status=MagickFalse;
888 if (image->progress_monitor != (MagickProgressMonitor) NULL)
889 {
890 MagickBooleanType
891 proceed;
892
893#if defined(MAGICKCORE_OPENMP_SUPPORT)
894 #pragma omp atomic
895#endif
896 progress++;
897 proceed=SetImageProgress(image,OilPaintImageTag,progress,image->rows);
898 if (proceed == MagickFalse)
899 status=MagickFalse;
900 }
901 }
902 paint_view=DestroyCacheView(paint_view);
903 image_view=DestroyCacheView(image_view);
904 histograms=DestroyHistogramTLS(histograms);
905 linear_image=DestroyImage(linear_image);
906 if (status == MagickFalse)
907 paint_image=DestroyImage(paint_image);
908 return(paint_image);
909}
910
911/*
912%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
913% %
914% %
915% %
916% O p a q u e P a i n t I m a g e %
917% %
918% %
919% %
920%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
921%
922% OpaquePaintImage() changes any pixel that matches color with the color
923% defined by fill.
924%
925% By default color must match a particular pixel color exactly. However,
926% in many cases two colors may differ by a small amount. Fuzz defines
927% how much tolerance is acceptable to consider two colors as the same.
928% For example, set fuzz to 10 and the color red at intensities of 100 and
929% 102 respectively are now interpreted as the same color.
930%
931% The format of the OpaquePaintImage method is:
932%
933% MagickBooleanType OpaquePaintImage(Image *image,
934% const PixelPacket *target,const PixelPacket *fill,
935% const MagickBooleanType invert)
936% MagickBooleanType OpaquePaintImageChannel(Image *image,
937% const ChannelType channel,const PixelPacket *target,
938% const PixelPacket *fill,const MagickBooleanType invert)
939%
940% A description of each parameter follows:
941%
942% o image: the image.
943%
944% o channel: the channel(s).
945%
946% o target: the RGB value of the target color.
947%
948% o fill: the replacement color.
949%
950% o invert: paint any pixel that does not match the target color.
951%
952*/
953
954MagickExport MagickBooleanType OpaquePaintImage(Image *image,
955 const MagickPixelPacket *target,const MagickPixelPacket *fill,
956 const MagickBooleanType invert)
957{
958 return(OpaquePaintImageChannel(image,CompositeChannels,target,fill,invert));
959}
960
961MagickExport MagickBooleanType OpaquePaintImageChannel(Image *image,
962 const ChannelType channel,const MagickPixelPacket *target,
963 const MagickPixelPacket *fill,const MagickBooleanType invert)
964{
965#define OpaquePaintImageTag "Opaque/Image"
966
968 *image_view;
969
971 *exception;
972
973 MagickBooleanType
974 status;
975
976 MagickOffsetType
977 progress;
978
980 conform_fill,
981 conform_target,
982 zero;
983
984 ssize_t
985 y;
986
987 assert(image != (Image *) NULL);
988 assert(image->signature == MagickCoreSignature);
989 assert(target != (MagickPixelPacket *) NULL);
990 assert(fill != (MagickPixelPacket *) NULL);
991 if (IsEventLogging() != MagickFalse)
992 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
993 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
994 return(MagickFalse);
995 exception=(&image->exception);
996 ConformMagickPixelPacket(image,fill,&conform_fill,exception);
997 ConformMagickPixelPacket(image,target,&conform_target,exception);
998 /*
999 Make image color opaque.
1000 */
1001 status=MagickTrue;
1002 progress=0;
1003 GetMagickPixelPacket(image,&zero);
1004 image_view=AcquireAuthenticCacheView(image,exception);
1005#if defined(MAGICKCORE_OPENMP_SUPPORT)
1006 #pragma omp parallel for schedule(static) shared(progress,status) \
1007 magick_number_threads(image,image,image->rows,1)
1008#endif
1009 for (y=0; y < (ssize_t) image->rows; y++)
1010 {
1012 pixel;
1013
1014 IndexPacket
1015 *magick_restrict indexes;
1016
1017 ssize_t
1018 x;
1019
1021 *magick_restrict q;
1022
1023 if (status == MagickFalse)
1024 continue;
1025 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1026 if (q == (PixelPacket *) NULL)
1027 {
1028 status=MagickFalse;
1029 continue;
1030 }
1031 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1032 pixel=zero;
1033 for (x=0; x < (ssize_t) image->columns; x++)
1034 {
1035 SetMagickPixelPacket(image,q,indexes+x,&pixel);
1036 if (IsMagickColorSimilar(&pixel,&conform_target) != invert)
1037 {
1038 if ((channel & RedChannel) != 0)
1039 SetPixelRed(q,ClampToQuantum(conform_fill.red));
1040 if ((channel & GreenChannel) != 0)
1041 SetPixelGreen(q,ClampToQuantum(conform_fill.green));
1042 if ((channel & BlueChannel) != 0)
1043 SetPixelBlue(q,ClampToQuantum(conform_fill.blue));
1044 if ((channel & OpacityChannel) != 0)
1045 SetPixelOpacity(q,ClampToQuantum(conform_fill.opacity));
1046 if (((channel & IndexChannel) != 0) &&
1047 (image->colorspace == CMYKColorspace))
1048 SetPixelIndex(indexes+x,ClampToQuantum(conform_fill.index));
1049 }
1050 q++;
1051 }
1052 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1053 status=MagickFalse;
1054 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1055 {
1056 MagickBooleanType
1057 proceed;
1058
1059#if defined(MAGICKCORE_OPENMP_SUPPORT)
1060 #pragma omp atomic
1061#endif
1062 progress++;
1063 proceed=SetImageProgress(image,OpaquePaintImageTag,progress,
1064 image->rows);
1065 if (proceed == MagickFalse)
1066 status=MagickFalse;
1067 }
1068 }
1069 image_view=DestroyCacheView(image_view);
1070 return(status);
1071}
1072
1073/*
1074%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1075% %
1076% %
1077% %
1078% T r a n s p a r e n t P a i n t I m a g e %
1079% %
1080% %
1081% %
1082%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1083%
1084% TransparentPaintImage() changes the opacity value associated with any pixel
1085% that matches color to the value defined by opacity.
1086%
1087% By default color must match a particular pixel color exactly. However,
1088% in many cases two colors may differ by a small amount. Fuzz defines
1089% how much tolerance is acceptable to consider two colors as the same.
1090% For example, set fuzz to 10 and the color red at intensities of 100 and
1091% 102 respectively are now interpreted as the same color.
1092%
1093% The format of the TransparentPaintImage method is:
1094%
1095% MagickBooleanType TransparentPaintImage(Image *image,
1096% const MagickPixelPacket *target,const Quantum opacity,
1097% const MagickBooleanType invert)
1098%
1099% A description of each parameter follows:
1100%
1101% o image: the image.
1102%
1103% o target: the target color.
1104%
1105% o opacity: the replacement opacity value.
1106%
1107% o invert: paint any pixel that does not match the target color.
1108%
1109*/
1110MagickExport MagickBooleanType TransparentPaintImage(Image *image,
1111 const MagickPixelPacket *target,const Quantum opacity,
1112 const MagickBooleanType invert)
1113{
1114#define TransparentPaintImageTag "Transparent/Image"
1115
1116 CacheView
1117 *image_view;
1118
1120 *exception;
1121
1122 MagickBooleanType
1123 status;
1124
1125 MagickOffsetType
1126 progress;
1127
1129 zero;
1130
1131 ssize_t
1132 y;
1133
1134 assert(image != (Image *) NULL);
1135 assert(image->signature == MagickCoreSignature);
1136 assert(target != (MagickPixelPacket *) NULL);
1137 if (IsEventLogging() != MagickFalse)
1138 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1139 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
1140 return(MagickFalse);
1141 if (image->matte == MagickFalse)
1142 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel);
1143 /*
1144 Make image color transparent.
1145 */
1146 status=MagickTrue;
1147 progress=0;
1148 exception=(&image->exception);
1149 GetMagickPixelPacket(image,&zero);
1150 image_view=AcquireAuthenticCacheView(image,exception);
1151#if defined(MAGICKCORE_OPENMP_SUPPORT)
1152 #pragma omp parallel for schedule(static) shared(progress,status) \
1153 magick_number_threads(image,image,image->rows,1)
1154#endif
1155 for (y=0; y < (ssize_t) image->rows; y++)
1156 {
1158 pixel;
1159
1160 IndexPacket
1161 *magick_restrict indexes;
1162
1163 ssize_t
1164 x;
1165
1167 *magick_restrict q;
1168
1169 if (status == MagickFalse)
1170 continue;
1171 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1172 if (q == (PixelPacket *) NULL)
1173 {
1174 status=MagickFalse;
1175 continue;
1176 }
1177 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1178 pixel=zero;
1179 for (x=0; x < (ssize_t) image->columns; x++)
1180 {
1181 SetMagickPixelPacket(image,q,indexes+x,&pixel);
1182 if (IsMagickColorSimilar(&pixel,target) != invert)
1183 q->opacity=opacity;
1184 q++;
1185 }
1186 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1187 status=MagickFalse;
1188 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1189 {
1190 MagickBooleanType
1191 proceed;
1192
1193#if defined(MAGICKCORE_OPENMP_SUPPORT)
1194 #pragma omp atomic
1195#endif
1196 progress++;
1197 proceed=SetImageProgress(image,TransparentPaintImageTag,progress,
1198 image->rows);
1199 if (proceed == MagickFalse)
1200 status=MagickFalse;
1201 }
1202 }
1203 image_view=DestroyCacheView(image_view);
1204 return(status);
1205}
1206
1207/*
1208%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1209% %
1210% %
1211% %
1212% T r a n s p a r e n t P a i n t I m a g e C h r o m a %
1213% %
1214% %
1215% %
1216%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1217%
1218% TransparentPaintImageChroma() changes the opacity value associated with any
1219% pixel that matches color to the value defined by opacity.
1220%
1221% As there is one fuzz value for the all the channels, the
1222% TransparentPaintImage() API is not suitable for the operations like chroma,
1223% where the tolerance for similarity of two color component (RGB) can be
1224% different, Thus we define this method take two target pixels (one
1225% low and one hight) and all the pixels of an image which are lying between
1226% these two pixels are made transparent.
1227%
1228% The format of the TransparentPaintImage method is:
1229%
1230% MagickBooleanType TransparentPaintImage(Image *image,
1231% const MagickPixelPacket *low,const MagickPixelPacket *hight,
1232% const Quantum opacity,const MagickBooleanType invert)
1233%
1234% A description of each parameter follows:
1235%
1236% o image: the image.
1237%
1238% o low: the low target color.
1239%
1240% o high: the high target color.
1241%
1242% o opacity: the replacement opacity value.
1243%
1244% o invert: paint any pixel that does not match the target color.
1245%
1246*/
1247MagickExport MagickBooleanType TransparentPaintImageChroma(Image *image,
1248 const MagickPixelPacket *low,const MagickPixelPacket *high,
1249 const Quantum opacity,const MagickBooleanType invert)
1250{
1251#define TransparentPaintImageTag "Transparent/Image"
1252
1253 CacheView
1254 *image_view;
1255
1257 *exception;
1258
1259 MagickBooleanType
1260 status;
1261
1262 MagickOffsetType
1263 progress;
1264
1265 ssize_t
1266 y;
1267
1268 assert(image != (Image *) NULL);
1269 assert(image->signature == MagickCoreSignature);
1270 assert(high != (MagickPixelPacket *) NULL);
1271 assert(low != (MagickPixelPacket *) NULL);
1272 if (IsEventLogging() != MagickFalse)
1273 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1274 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
1275 return(MagickFalse);
1276 if (image->matte == MagickFalse)
1277 (void) SetImageAlphaChannel(image,ResetAlphaChannel);
1278 /*
1279 Make image color transparent.
1280 */
1281 status=MagickTrue;
1282 progress=0;
1283 exception=(&image->exception);
1284 image_view=AcquireAuthenticCacheView(image,exception);
1285#if defined(MAGICKCORE_OPENMP_SUPPORT)
1286 #pragma omp parallel for schedule(static) shared(progress,status) \
1287 magick_number_threads(image,image,image->rows,1)
1288#endif
1289 for (y=0; y < (ssize_t) image->rows; y++)
1290 {
1291 MagickBooleanType
1292 match;
1293
1295 pixel;
1296
1297 IndexPacket
1298 *magick_restrict indexes;
1299
1300 ssize_t
1301 x;
1302
1304 *magick_restrict q;
1305
1306 if (status == MagickFalse)
1307 continue;
1308 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1309 if (q == (PixelPacket *) NULL)
1310 {
1311 status=MagickFalse;
1312 continue;
1313 }
1314 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1315 GetMagickPixelPacket(image,&pixel);
1316 for (x=0; x < (ssize_t) image->columns; x++)
1317 {
1318 SetMagickPixelPacket(image,q,indexes+x,&pixel);
1319 match=((pixel.red >= low->red) && (pixel.red <= high->red) &&
1320 (pixel.green >= low->green) && (pixel.green <= high->green) &&
1321 (pixel.blue >= low->blue) && (pixel.blue <= high->blue)) ? MagickTrue : MagickFalse;
1322 if (match != invert)
1323 q->opacity=opacity;
1324 q++;
1325 }
1326 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1327 status=MagickFalse;
1328 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1329 {
1330 MagickBooleanType
1331 proceed;
1332
1333#if defined(MAGICKCORE_OPENMP_SUPPORT)
1334 #pragma omp atomic
1335#endif
1336 progress++;
1337 proceed=SetImageProgress(image,TransparentPaintImageTag,progress,
1338 image->rows);
1339 if (proceed == MagickFalse)
1340 status=MagickFalse;
1341 }
1342 }
1343 image_view=DestroyCacheView(image_view);
1344 return(status);
1345}