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