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