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