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