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