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