blob: e61431f2bc37769af9d0ad376f669385e3208f8b [file] [log] [blame]
cristy3ed852e2009-09-05 21:47:34 +00001/*
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% John Cristy %
17% July 1998 %
18% %
19% %
cristy7e41fe82010-12-04 23:12:08 +000020% Copyright 1999-2011 ImageMagick Studio LLC, a non-profit organization %
cristy3ed852e2009-09-05 21:47:34 +000021% 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*/
cristy4c08aed2011-07-01 19:47:50 +000042#include "MagickCore/studio.h"
43#include "MagickCore/color.h"
44#include "MagickCore/color-private.h"
45#include "MagickCore/colorspace-private.h"
46#include "MagickCore/composite.h"
47#include "MagickCore/composite-private.h"
48#include "MagickCore/draw.h"
49#include "MagickCore/draw-private.h"
50#include "MagickCore/exception.h"
51#include "MagickCore/exception-private.h"
52#include "MagickCore/gem.h"
53#include "MagickCore/monitor.h"
54#include "MagickCore/monitor-private.h"
55#include "MagickCore/paint.h"
56#include "MagickCore/pixel-accessor.h"
57#include "MagickCore/string_.h"
58#include "MagickCore/thread-private.h"
cristy3ed852e2009-09-05 21:47:34 +000059
cristy3ed852e2009-09-05 21:47:34 +000060/*
61%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
62% %
63% %
64% %
65% F l o o d f i l l P a i n t I m a g e %
66% %
67% %
68% %
69%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
70%
71% FloodfillPaintImage() changes the color value of any pixel that matches
72% target and is an immediate neighbor. If the method FillToBorderMethod is
73% specified, the color value is changed for any neighbor pixel that does not
74% match the bordercolor member of image.
75%
76% By default target must match a particular pixel color exactly.
77% However, in many cases two colors may differ by a small amount. The
78% fuzz member of image defines how much tolerance is acceptable to
79% consider two colors as the same. For example, set fuzz to 10 and the
80% color red at intensities of 100 and 102 respectively are now
81% interpreted as the same color for the purposes of the floodfill.
82%
83% The format of the FloodfillPaintImage method is:
84%
85% MagickBooleanType FloodfillPaintImage(Image *image,
86% const ChannelType channel,const DrawInfo *draw_info,
cristy4c08aed2011-07-01 19:47:50 +000087% const PixelInfo target,const ssize_t x_offset,
cristy178edaf2011-01-23 03:42:39 +000088% const ssize_t y_offset,const MagickBooleanType invert)
cristy3ed852e2009-09-05 21:47:34 +000089%
90% A description of each parameter follows:
91%
92% o image: the image.
93%
94% o channel: the channel(s).
95%
96% o draw_info: the draw info.
97%
98% o target: the RGB value of the target color.
99%
100% o x_offset,y_offset: the starting location of the operation.
101%
102% o invert: paint any pixel that does not match the target color.
103%
104*/
105MagickExport MagickBooleanType FloodfillPaintImage(Image *image,
106 const ChannelType channel,const DrawInfo *draw_info,
cristy4c08aed2011-07-01 19:47:50 +0000107 const PixelInfo *target,const ssize_t x_offset,const ssize_t y_offset,
cristy3ed852e2009-09-05 21:47:34 +0000108 const MagickBooleanType invert)
109{
110#define MaxStacksize (1UL << 15)
111#define PushSegmentStack(up,left,right,delta) \
112{ \
113 if (s >= (segment_stack+MaxStacksize)) \
114 ThrowBinaryException(DrawError,"SegmentStackOverflow",image->filename) \
115 else \
116 { \
cristybb503372010-05-27 20:51:26 +0000117 if ((((up)+(delta)) >= 0) && (((up)+(delta)) < (ssize_t) image->rows)) \
cristy3ed852e2009-09-05 21:47:34 +0000118 { \
119 s->x1=(double) (left); \
120 s->y1=(double) (up); \
121 s->x2=(double) (right); \
122 s->y2=(double) (delta); \
123 s++; \
124 } \
125 } \
126}
127
cristyb0d3bb92010-09-22 14:37:58 +0000128 CacheView
129 *floodplane_view,
130 *image_view;
131
cristy3ed852e2009-09-05 21:47:34 +0000132 ExceptionInfo
133 *exception;
134
135 Image
136 *floodplane_image;
137
cristy3ed852e2009-09-05 21:47:34 +0000138 MagickBooleanType
139 skip;
140
cristy4c08aed2011-07-01 19:47:50 +0000141 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000142 fill,
143 pixel;
144
145 PixelPacket
146 fill_color;
147
148 register SegmentInfo
149 *s;
150
151 SegmentInfo
152 *segment_stack;
153
cristy9d314ff2011-03-09 01:30:28 +0000154 ssize_t
155 offset,
156 start,
157 x,
158 x1,
159 x2,
160 y;
161
cristy3ed852e2009-09-05 21:47:34 +0000162 /*
163 Check boundary conditions.
164 */
165 assert(image != (Image *) NULL);
166 assert(image->signature == MagickSignature);
167 if (image->debug != MagickFalse)
168 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
169 assert(draw_info != (DrawInfo *) NULL);
170 assert(draw_info->signature == MagickSignature);
cristybb503372010-05-27 20:51:26 +0000171 if ((x_offset < 0) || (x_offset >= (ssize_t) image->columns))
cristy3ed852e2009-09-05 21:47:34 +0000172 return(MagickFalse);
cristybb503372010-05-27 20:51:26 +0000173 if ((y_offset < 0) || (y_offset >= (ssize_t) image->rows))
cristy3ed852e2009-09-05 21:47:34 +0000174 return(MagickFalse);
175 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
176 return(MagickFalse);
177 if (image->matte == MagickFalse)
178 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel);
179 /*
180 Set floodfill state.
181 */
182 floodplane_image=CloneImage(image,0,0,MagickTrue,&image->exception);
183 if (floodplane_image == (Image *) NULL)
184 return(MagickFalse);
185 (void) SetImageAlphaChannel(floodplane_image,OpaqueAlphaChannel);
186 segment_stack=(SegmentInfo *) AcquireQuantumMemory(MaxStacksize,
187 sizeof(*segment_stack));
188 if (segment_stack == (SegmentInfo *) NULL)
189 {
190 floodplane_image=DestroyImage(floodplane_image);
191 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
192 image->filename);
193 }
194 /*
195 Push initial segment on stack.
196 */
197 exception=(&image->exception);
198 x=x_offset;
199 y=y_offset;
200 start=0;
201 s=segment_stack;
202 PushSegmentStack(y,x,x,1);
203 PushSegmentStack(y+1,x,x,-1);
cristy4c08aed2011-07-01 19:47:50 +0000204 GetPixelInfo(image,&fill);
205 GetPixelInfo(image,&pixel);
cristyb0d3bb92010-09-22 14:37:58 +0000206 image_view=AcquireCacheView(image);
207 floodplane_view=AcquireCacheView(floodplane_image);
cristy3ed852e2009-09-05 21:47:34 +0000208 while (s > segment_stack)
209 {
cristy4c08aed2011-07-01 19:47:50 +0000210 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000211 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000212
cristybb503372010-05-27 20:51:26 +0000213 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000214 x;
215
cristy4c08aed2011-07-01 19:47:50 +0000216 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000217 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000218
219 /*
220 Pop segment off stack.
221 */
222 s--;
cristybb503372010-05-27 20:51:26 +0000223 x1=(ssize_t) s->x1;
224 x2=(ssize_t) s->x2;
225 offset=(ssize_t) s->y2;
226 y=(ssize_t) s->y1+offset;
cristy3ed852e2009-09-05 21:47:34 +0000227 /*
228 Recolor neighboring pixels.
229 */
cristyb0d3bb92010-09-22 14:37:58 +0000230 p=GetCacheViewVirtualPixels(image_view,0,y,(size_t) (x1+1),1,exception);
231 q=GetCacheViewAuthenticPixels(floodplane_view,0,y,(size_t) (x1+1),1,
cristy3ed852e2009-09-05 21:47:34 +0000232 exception);
cristy4c08aed2011-07-01 19:47:50 +0000233 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000234 break;
cristy4c08aed2011-07-01 19:47:50 +0000235 p+=x1*GetPixelChannels(image);
236 q+=x1*GetPixelChannels(floodplane_image);
cristy3ed852e2009-09-05 21:47:34 +0000237 for (x=x1; x >= 0; x--)
238 {
cristy4c08aed2011-07-01 19:47:50 +0000239 if (GetPixelAlpha(image,q) == TransparentAlpha)
cristy3ed852e2009-09-05 21:47:34 +0000240 break;
cristy4c08aed2011-07-01 19:47:50 +0000241 SetPixelInfo(image,p,&pixel);
242 if (IsFuzzyEquivalencePixelInfo(&pixel,target) == invert)
cristy3ed852e2009-09-05 21:47:34 +0000243 break;
cristy4c08aed2011-07-01 19:47:50 +0000244 SetPixelAlpha(floodplane_image,TransparentAlpha,q);
245 p-=GetPixelChannels(image);
246 q-=GetPixelChannels(floodplane_image);
cristy3ed852e2009-09-05 21:47:34 +0000247 }
cristyb0d3bb92010-09-22 14:37:58 +0000248 if (SyncCacheViewAuthenticPixels(floodplane_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000249 break;
250 skip=x >= x1 ? MagickTrue : MagickFalse;
251 if (skip == MagickFalse)
252 {
253 start=x+1;
254 if (start < x1)
255 PushSegmentStack(y,start,x1-1,-offset);
256 x=x1+1;
257 }
258 do
259 {
260 if (skip == MagickFalse)
261 {
cristybb503372010-05-27 20:51:26 +0000262 if (x < (ssize_t) image->columns)
cristy3ed852e2009-09-05 21:47:34 +0000263 {
cristyb0d3bb92010-09-22 14:37:58 +0000264 p=GetCacheViewVirtualPixels(image_view,x,y,image->columns-x,1,
cristy3ed852e2009-09-05 21:47:34 +0000265 exception);
cristyb0d3bb92010-09-22 14:37:58 +0000266 q=GetCacheViewAuthenticPixels(floodplane_view,x,y,
267 image->columns-x,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000268 if ((p == (const Quantum *) NULL) ||
269 (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000270 break;
cristybb503372010-05-27 20:51:26 +0000271 for ( ; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000272 {
cristy4c08aed2011-07-01 19:47:50 +0000273 if (GetPixelAlpha(image,q) == TransparentAlpha)
cristy3ed852e2009-09-05 21:47:34 +0000274 break;
cristy4c08aed2011-07-01 19:47:50 +0000275 SetPixelInfo(image,p,&pixel);
276 if (IsFuzzyEquivalencePixelInfo(&pixel,target) == invert)
cristy3ed852e2009-09-05 21:47:34 +0000277 break;
cristy4c08aed2011-07-01 19:47:50 +0000278 SetPixelAlpha(floodplane_image,
279 TransparentAlpha,q);
280 p+=GetPixelChannels(image);
281 q+=GetPixelChannels(floodplane_image);
cristy3ed852e2009-09-05 21:47:34 +0000282 }
cristyb0d3bb92010-09-22 14:37:58 +0000283 if (SyncCacheViewAuthenticPixels(floodplane_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000284 break;
285 }
286 PushSegmentStack(y,start,x-1,offset);
287 if (x > (x2+1))
288 PushSegmentStack(y,x2+1,x-1,-offset);
289 }
290 skip=MagickFalse;
291 x++;
292 if (x <= x2)
293 {
cristyb0d3bb92010-09-22 14:37:58 +0000294 p=GetCacheViewVirtualPixels(image_view,x,y,(size_t) (x2-x+1),1,
295 exception);
296 q=GetCacheViewAuthenticPixels(floodplane_view,x,y,(size_t) (x2-x+1),1,
cristy3ed852e2009-09-05 21:47:34 +0000297 exception);
cristy4c08aed2011-07-01 19:47:50 +0000298 if ((p == (const Quantum *) NULL) ||
299 (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000300 break;
cristy3ed852e2009-09-05 21:47:34 +0000301 for ( ; x <= x2; x++)
302 {
cristy4c08aed2011-07-01 19:47:50 +0000303 if (GetPixelAlpha(image,q) == TransparentAlpha)
cristy3ed852e2009-09-05 21:47:34 +0000304 break;
cristy4c08aed2011-07-01 19:47:50 +0000305 SetPixelInfo(image,p,&pixel);
306 if (IsFuzzyEquivalencePixelInfo(&pixel,target) != invert)
cristy3ed852e2009-09-05 21:47:34 +0000307 break;
cristy4c08aed2011-07-01 19:47:50 +0000308 p+=GetPixelChannels(image);
309 q+=GetPixelChannels(floodplane_image);
cristy3ed852e2009-09-05 21:47:34 +0000310 }
311 }
312 start=x;
313 } while (x <= x2);
314 }
cristybb503372010-05-27 20:51:26 +0000315 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000316 {
cristy4c08aed2011-07-01 19:47:50 +0000317 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000318 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000319
cristybb503372010-05-27 20:51:26 +0000320 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000321 x;
322
cristy4c08aed2011-07-01 19:47:50 +0000323 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000324 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000325
326 /*
327 Tile fill color onto floodplane.
328 */
cristyb0d3bb92010-09-22 14:37:58 +0000329 p=GetCacheViewVirtualPixels(floodplane_view,0,y,image->columns,1,
330 exception);
331 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000332 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000333 break;
cristybb503372010-05-27 20:51:26 +0000334 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000335 {
cristy4c08aed2011-07-01 19:47:50 +0000336 if (GetPixelAlpha(floodplane_image,p) != OpaqueAlpha)
cristy3ed852e2009-09-05 21:47:34 +0000337 {
338 (void) GetFillColor(draw_info,x,y,&fill_color);
cristy4c08aed2011-07-01 19:47:50 +0000339 SetPixelInfoPacket(image,&fill_color,&fill);
cristy3ed852e2009-09-05 21:47:34 +0000340 if (image->colorspace == CMYKColorspace)
341 ConvertRGBToCMYK(&fill);
342 if ((channel & RedChannel) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000343 SetPixelRed(image,ClampToQuantum(fill.red),q);
cristy3ed852e2009-09-05 21:47:34 +0000344 if ((channel & GreenChannel) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000345 SetPixelGreen(image,ClampToQuantum(fill.green),q);
cristy3ed852e2009-09-05 21:47:34 +0000346 if ((channel & BlueChannel) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000347 SetPixelBlue(image,ClampToQuantum(fill.blue),q);
348 if (((channel & BlackChannel) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000349 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +0000350 SetPixelBlack(image,ClampToQuantum(fill.black),q);
351 if ((channel & AlphaChannel) != 0)
352 SetPixelAlpha(image,ClampToQuantum(fill.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +0000353 }
cristy4c08aed2011-07-01 19:47:50 +0000354 p+=GetPixelChannels(floodplane_image);
355 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000356 }
cristyb0d3bb92010-09-22 14:37:58 +0000357 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000358 break;
359 }
cristyb0d3bb92010-09-22 14:37:58 +0000360 floodplane_view=DestroyCacheView(floodplane_view);
361 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +0000362 segment_stack=(SegmentInfo *) RelinquishMagickMemory(segment_stack);
363 floodplane_image=DestroyImage(floodplane_image);
cristybb503372010-05-27 20:51:26 +0000364 return(y == (ssize_t) image->rows ? MagickTrue : MagickFalse);
cristy3ed852e2009-09-05 21:47:34 +0000365}
366
367/*
368%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
369% %
370% %
371% %
372+ G r a d i e n t I m a g e %
373% %
374% %
375% %
376%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
377%
cristycee97112010-05-28 00:44:52 +0000378% GradientImage() applies a continuously smooth color transitions along a
cristy3ed852e2009-09-05 21:47:34 +0000379% vector from one color to another.
380%
381% Note, the interface of this method will change in the future to support
382% more than one transistion.
383%
384% The format of the GradientImage method is:
385%
386% MagickBooleanType GradientImage(Image *image,const GradientType type,
387% const SpreadMethod method,const PixelPacket *start_color,
388% const PixelPacket *stop_color)
389%
390% A description of each parameter follows:
391%
392% o image: the image.
393%
394% o type: the gradient type: linear or radial.
395%
396% o spread: the gradient spread meathod: pad, reflect, or repeat.
397%
398% o start_color: the start color.
399%
400% o stop_color: the stop color.
401%
cristy3ed852e2009-09-05 21:47:34 +0000402*/
cristy117ff172010-08-15 21:35:32 +0000403
404static inline double MagickMax(const double x,const double y)
405{
406 return(x > y ? x : y);
407}
408
cristy3ed852e2009-09-05 21:47:34 +0000409MagickExport MagickBooleanType GradientImage(Image *image,
410 const GradientType type,const SpreadMethod method,
411 const PixelPacket *start_color,const PixelPacket *stop_color)
412{
413 DrawInfo
414 *draw_info;
415
416 GradientInfo
417 *gradient;
418
419 MagickBooleanType
420 status;
421
cristybb503372010-05-27 20:51:26 +0000422 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000423 i;
424
425 /*
426 Set gradient start-stop end points.
427 */
428 assert(image != (const Image *) NULL);
429 assert(image->signature == MagickSignature);
430 if (image->debug != MagickFalse)
431 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
432 assert(start_color != (const PixelPacket *) NULL);
433 assert(stop_color != (const PixelPacket *) NULL);
434 draw_info=AcquireDrawInfo();
435 gradient=(&draw_info->gradient);
436 gradient->type=type;
437 gradient->bounding_box.width=image->columns;
438 gradient->bounding_box.height=image->rows;
439 gradient->gradient_vector.x2=(double) image->columns-1.0;
440 gradient->gradient_vector.y2=(double) image->rows-1.0;
441 if ((type == LinearGradient) && (gradient->gradient_vector.y2 != 0.0))
442 gradient->gradient_vector.x2=0.0;
443 gradient->center.x=(double) gradient->gradient_vector.x2/2.0;
444 gradient->center.y=(double) gradient->gradient_vector.y2/2.0;
445 gradient->radius=MagickMax(gradient->center.x,gradient->center.y);
446 gradient->spread=method;
447 /*
448 Define the gradient to fill between the stops.
449 */
450 gradient->number_stops=2;
451 gradient->stops=(StopInfo *) AcquireQuantumMemory(gradient->number_stops,
452 sizeof(*gradient->stops));
453 if (gradient->stops == (StopInfo *) NULL)
454 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
455 image->filename);
456 (void) ResetMagickMemory(gradient->stops,0,gradient->number_stops*
457 sizeof(*gradient->stops));
cristybb503372010-05-27 20:51:26 +0000458 for (i=0; i < (ssize_t) gradient->number_stops; i++)
cristy4c08aed2011-07-01 19:47:50 +0000459 GetPixelInfo(image,&gradient->stops[i].color);
460 SetPixelInfoPacket(image,start_color,&gradient->stops[0].color);
cristy3ed852e2009-09-05 21:47:34 +0000461 gradient->stops[0].offset=0.0;
cristy4c08aed2011-07-01 19:47:50 +0000462 SetPixelInfoPacket(image,stop_color,&gradient->stops[1].color);
cristy3ed852e2009-09-05 21:47:34 +0000463 gradient->stops[1].offset=1.0;
464 /*
465 Draw a gradient on the image.
466 */
467 status=DrawGradientImage(image,draw_info);
468 draw_info=DestroyDrawInfo(draw_info);
cristy4c08aed2011-07-01 19:47:50 +0000469 if ((start_color->alpha == OpaqueAlpha) && (stop_color->alpha == OpaqueAlpha))
cristy3ed852e2009-09-05 21:47:34 +0000470 image->matte=MagickFalse;
cristy4c08aed2011-07-01 19:47:50 +0000471 if ((IsPixelPacketGray(start_color) != MagickFalse) &&
472 (IsPixelPacketGray(stop_color) != MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +0000473 image->type=GrayscaleType;
474 return(status);
475}
476
477/*
478%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
479% %
480% %
481% %
482% O i l P a i n t I m a g e %
483% %
484% %
485% %
486%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
487%
488% OilPaintImage() applies a special effect filter that simulates an oil
489% painting. Each pixel is replaced by the most frequent color occurring
490% in a circular region defined by radius.
491%
492% The format of the OilPaintImage method is:
493%
494% Image *OilPaintImage(const Image *image,const double radius,
495% ExceptionInfo *exception)
496%
497% A description of each parameter follows:
498%
499% o image: the image.
500%
501% o radius: the radius of the circular neighborhood.
502%
503% o exception: return any errors or warnings in this structure.
504%
505*/
506
cristybb503372010-05-27 20:51:26 +0000507static size_t **DestroyHistogramThreadSet(size_t **histogram)
cristy3ed852e2009-09-05 21:47:34 +0000508{
cristybb503372010-05-27 20:51:26 +0000509 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000510 i;
511
cristybb503372010-05-27 20:51:26 +0000512 assert(histogram != (size_t **) NULL);
513 for (i=0; i < (ssize_t) GetOpenMPMaximumThreads(); i++)
514 if (histogram[i] != (size_t *) NULL)
515 histogram[i]=(size_t *) RelinquishMagickMemory(histogram[i]);
cristyb41ee102010-10-04 16:46:15 +0000516 histogram=(size_t **) RelinquishMagickMemory(histogram);
cristy3ed852e2009-09-05 21:47:34 +0000517 return(histogram);
518}
519
cristybb503372010-05-27 20:51:26 +0000520static size_t **AcquireHistogramThreadSet(const size_t count)
cristy3ed852e2009-09-05 21:47:34 +0000521{
cristybb503372010-05-27 20:51:26 +0000522 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000523 i;
524
cristybb503372010-05-27 20:51:26 +0000525 size_t
cristy3ed852e2009-09-05 21:47:34 +0000526 **histogram,
527 number_threads;
528
529 number_threads=GetOpenMPMaximumThreads();
cristyb41ee102010-10-04 16:46:15 +0000530 histogram=(size_t **) AcquireQuantumMemory(number_threads,
cristy3ed852e2009-09-05 21:47:34 +0000531 sizeof(*histogram));
cristybb503372010-05-27 20:51:26 +0000532 if (histogram == (size_t **) NULL)
533 return((size_t **) NULL);
cristy3ed852e2009-09-05 21:47:34 +0000534 (void) ResetMagickMemory(histogram,0,number_threads*sizeof(*histogram));
cristybb503372010-05-27 20:51:26 +0000535 for (i=0; i < (ssize_t) number_threads; i++)
cristy3ed852e2009-09-05 21:47:34 +0000536 {
cristybb503372010-05-27 20:51:26 +0000537 histogram[i]=(size_t *) AcquireQuantumMemory(count,
cristy3ed852e2009-09-05 21:47:34 +0000538 sizeof(**histogram));
cristybb503372010-05-27 20:51:26 +0000539 if (histogram[i] == (size_t *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000540 return(DestroyHistogramThreadSet(histogram));
541 }
542 return(histogram);
543}
544
545MagickExport Image *OilPaintImage(const Image *image,const double radius,
546 ExceptionInfo *exception)
547{
548#define NumberPaintBins 256
549#define OilPaintImageTag "OilPaint/Image"
550
cristyfa112112010-01-04 17:48:07 +0000551 CacheView
552 *image_view,
553 *paint_view;
554
cristy3ed852e2009-09-05 21:47:34 +0000555 Image
556 *paint_image;
557
cristy3ed852e2009-09-05 21:47:34 +0000558 MagickBooleanType
559 status;
560
cristybb503372010-05-27 20:51:26 +0000561 MagickOffsetType
562 progress;
563
564 size_t
cristyfa112112010-01-04 17:48:07 +0000565 **restrict histograms,
cristy3ed852e2009-09-05 21:47:34 +0000566 width;
567
cristybb503372010-05-27 20:51:26 +0000568 ssize_t
569 y;
570
cristy3ed852e2009-09-05 21:47:34 +0000571 /*
572 Initialize painted image attributes.
573 */
574 assert(image != (const Image *) NULL);
575 assert(image->signature == MagickSignature);
576 if (image->debug != MagickFalse)
577 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
578 assert(exception != (ExceptionInfo *) NULL);
579 assert(exception->signature == MagickSignature);
580 width=GetOptimalKernelWidth2D(radius,0.5);
cristy3ed852e2009-09-05 21:47:34 +0000581 paint_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
582 if (paint_image == (Image *) NULL)
583 return((Image *) NULL);
584 if (SetImageStorageClass(paint_image,DirectClass) == MagickFalse)
585 {
586 InheritException(exception,&paint_image->exception);
587 paint_image=DestroyImage(paint_image);
588 return((Image *) NULL);
589 }
590 histograms=AcquireHistogramThreadSet(NumberPaintBins);
cristybb503372010-05-27 20:51:26 +0000591 if (histograms == (size_t **) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000592 {
593 paint_image=DestroyImage(paint_image);
594 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
595 }
596 /*
597 Oil paint image.
598 */
599 status=MagickTrue;
600 progress=0;
601 image_view=AcquireCacheView(image);
602 paint_view=AcquireCacheView(paint_image);
cristyb5d5f722009-11-04 03:03:49 +0000603#if defined(MAGICKCORE_OPENMP_SUPPORT)
604 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000605#endif
cristybb503372010-05-27 20:51:26 +0000606 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000607 {
cristy4c08aed2011-07-01 19:47:50 +0000608 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000609 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000610
cristybb503372010-05-27 20:51:26 +0000611 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000612 x;
613
cristy4c08aed2011-07-01 19:47:50 +0000614 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000615 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000616
cristybb503372010-05-27 20:51:26 +0000617 register size_t
cristy3ed852e2009-09-05 21:47:34 +0000618 *histogram;
619
620 if (status == MagickFalse)
621 continue;
cristyfe4ba002011-02-28 14:54:12 +0000622 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
623 (width/2L),image->columns+width,width,exception);
cristy3ed852e2009-09-05 21:47:34 +0000624 q=QueueCacheViewAuthenticPixels(paint_view,0,y,paint_image->columns,1,
625 exception);
cristy4c08aed2011-07-01 19:47:50 +0000626 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000627 {
628 status=MagickFalse;
629 continue;
630 }
cristy3ed852e2009-09-05 21:47:34 +0000631 histogram=histograms[GetOpenMPThreadId()];
cristybb503372010-05-27 20:51:26 +0000632 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000633 {
cristybb503372010-05-27 20:51:26 +0000634 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000635 i,
636 u;
637
cristybb503372010-05-27 20:51:26 +0000638 size_t
cristy3ed852e2009-09-05 21:47:34 +0000639 count;
640
cristy9d314ff2011-03-09 01:30:28 +0000641 ssize_t
642 j,
643 k,
644 v;
645
cristy3ed852e2009-09-05 21:47:34 +0000646 /*
647 Assign most frequent color.
648 */
649 i=0;
650 j=0;
651 count=0;
652 (void) ResetMagickMemory(histogram,0,NumberPaintBins*sizeof(*histogram));
cristybb503372010-05-27 20:51:26 +0000653 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +0000654 {
cristybb503372010-05-27 20:51:26 +0000655 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +0000656 {
cristy4c08aed2011-07-01 19:47:50 +0000657 k=(ssize_t) ScaleQuantumToChar(GetPixelIntensity(image,p+u+i));
cristy3ed852e2009-09-05 21:47:34 +0000658 histogram[k]++;
659 if (histogram[k] > count)
660 {
661 j=i+u;
662 count=histogram[k];
663 }
664 }
cristyd99b0962010-05-29 23:14:26 +0000665 i+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +0000666 }
cristy4c08aed2011-07-01 19:47:50 +0000667 SetPixelRed(paint_image,GetPixelRed(image,p+j*
668 GetPixelChannels(image)),q);
669 SetPixelGreen(paint_image,GetPixelGreen(image,p+j*
670 GetPixelChannels(image)),q);
671 SetPixelBlue(paint_image,GetPixelBlue(image,p+j*
672 GetPixelChannels(image)),q);
cristy3ed852e2009-09-05 21:47:34 +0000673 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +0000674 SetPixelBlack(paint_image,GetPixelBlack(image,p+j*
675 GetPixelChannels(image)),q);
676 if (image->matte != MagickFalse)
677 SetPixelAlpha(paint_image,GetPixelAlpha(image,p+j*
678 GetPixelChannels(image)),q);
679 p+=GetPixelChannels(image);
680 q+=GetPixelChannels(paint_image);
cristy3ed852e2009-09-05 21:47:34 +0000681 }
682 if (SyncCacheViewAuthenticPixels(paint_view,exception) == MagickFalse)
683 status=MagickFalse;
684 if (image->progress_monitor != (MagickProgressMonitor) NULL)
685 {
686 MagickBooleanType
687 proceed;
688
cristyb5d5f722009-11-04 03:03:49 +0000689#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000690 #pragma omp critical (MagickCore_OilPaintImage)
691#endif
692 proceed=SetImageProgress(image,OilPaintImageTag,progress++,image->rows);
693 if (proceed == MagickFalse)
694 status=MagickFalse;
695 }
696 }
697 paint_view=DestroyCacheView(paint_view);
698 image_view=DestroyCacheView(image_view);
699 histograms=DestroyHistogramThreadSet(histograms);
700 if (status == MagickFalse)
701 paint_image=DestroyImage(paint_image);
702 return(paint_image);
703}
704
705/*
706%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
707% %
708% %
709% %
710% O p a q u e P a i n t I m a g e %
711% %
712% %
713% %
714%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
715%
716% OpaquePaintImage() changes any pixel that matches color with the color
717% defined by fill.
718%
719% By default color must match a particular pixel color exactly. However,
720% in many cases two colors may differ by a small amount. Fuzz defines
721% how much tolerance is acceptable to consider two colors as the same.
722% For example, set fuzz to 10 and the color red at intensities of 100 and
723% 102 respectively are now interpreted as the same color.
724%
725% The format of the OpaquePaintImage method is:
726%
727% MagickBooleanType OpaquePaintImage(Image *image,
728% const PixelPacket *target,const PixelPacket *fill,
729% const MagickBooleanType invert)
730% MagickBooleanType OpaquePaintImageChannel(Image *image,
731% const ChannelType channel,const PixelPacket *target,
732% const PixelPacket *fill,const MagickBooleanType invert)
733%
734% A description of each parameter follows:
735%
736% o image: the image.
737%
738% o channel: the channel(s).
739%
740% o target: the RGB value of the target color.
741%
742% o fill: the replacement color.
743%
744% o invert: paint any pixel that does not match the target color.
745%
746*/
747
748MagickExport MagickBooleanType OpaquePaintImage(Image *image,
cristy4c08aed2011-07-01 19:47:50 +0000749 const PixelInfo *target,const PixelInfo *fill,
cristy3ed852e2009-09-05 21:47:34 +0000750 const MagickBooleanType invert)
751{
cristy9a9230e2011-04-26 14:56:14 +0000752 return(OpaquePaintImageChannel(image,CompositeChannels,target,fill,invert));
cristy3ed852e2009-09-05 21:47:34 +0000753}
754
755MagickExport MagickBooleanType OpaquePaintImageChannel(Image *image,
cristy4c08aed2011-07-01 19:47:50 +0000756 const ChannelType channel,const PixelInfo *target,
757 const PixelInfo *fill,const MagickBooleanType invert)
cristy3ed852e2009-09-05 21:47:34 +0000758{
759#define OpaquePaintImageTag "Opaque/Image"
760
cristyc4c8d132010-01-07 01:58:38 +0000761 CacheView
762 *image_view;
763
cristy3ed852e2009-09-05 21:47:34 +0000764 ExceptionInfo
765 *exception;
766
cristy3ed852e2009-09-05 21:47:34 +0000767 MagickBooleanType
768 status;
769
cristybb503372010-05-27 20:51:26 +0000770 MagickOffsetType
771 progress;
772
cristy4c08aed2011-07-01 19:47:50 +0000773 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000774 zero;
775
cristybb503372010-05-27 20:51:26 +0000776 ssize_t
777 y;
778
cristy3ed852e2009-09-05 21:47:34 +0000779 assert(image != (Image *) NULL);
780 assert(image->signature == MagickSignature);
cristy4c08aed2011-07-01 19:47:50 +0000781 assert(target != (PixelInfo *) NULL);
782 assert(fill != (PixelInfo *) NULL);
cristy3ed852e2009-09-05 21:47:34 +0000783 if (image->debug != MagickFalse)
784 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
785 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
786 return(MagickFalse);
787 /*
788 Make image color opaque.
789 */
790 status=MagickTrue;
791 progress=0;
792 exception=(&image->exception);
cristy4c08aed2011-07-01 19:47:50 +0000793 GetPixelInfo(image,&zero);
cristy3ed852e2009-09-05 21:47:34 +0000794 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000795#if defined(MAGICKCORE_OPENMP_SUPPORT)
796 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000797#endif
cristybb503372010-05-27 20:51:26 +0000798 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000799 {
cristy4c08aed2011-07-01 19:47:50 +0000800 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000801 pixel;
802
cristybb503372010-05-27 20:51:26 +0000803 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000804 x;
805
cristy4c08aed2011-07-01 19:47:50 +0000806 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000807 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000808
809 if (status == MagickFalse)
810 continue;
811 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000812 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000813 {
814 status=MagickFalse;
815 continue;
816 }
cristy3ed852e2009-09-05 21:47:34 +0000817 pixel=zero;
cristybb503372010-05-27 20:51:26 +0000818 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000819 {
cristy4c08aed2011-07-01 19:47:50 +0000820 SetPixelInfo(image,q,&pixel);
821 if (IsFuzzyEquivalencePixelInfo(&pixel,target) != invert)
cristy3ed852e2009-09-05 21:47:34 +0000822 {
823 if ((channel & RedChannel) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000824 SetPixelRed(image,ClampToQuantum(fill->red),q);
cristy3ed852e2009-09-05 21:47:34 +0000825 if ((channel & GreenChannel) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000826 SetPixelGreen(image,ClampToQuantum(fill->green),q);
cristy3ed852e2009-09-05 21:47:34 +0000827 if ((channel & BlueChannel) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000828 SetPixelBlue(image,ClampToQuantum(fill->blue),q);
829 if (((channel & BlackChannel) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000830 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +0000831 SetPixelBlack(image,ClampToQuantum(fill->black),q);
832 if ((channel & AlphaChannel) != 0)
833 SetPixelAlpha(image,ClampToQuantum(fill->alpha),q);
cristy3ed852e2009-09-05 21:47:34 +0000834 }
cristy4c08aed2011-07-01 19:47:50 +0000835 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000836 }
837 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
838 status=MagickFalse;
839 if (image->progress_monitor != (MagickProgressMonitor) NULL)
840 {
841 MagickBooleanType
842 proceed;
843
cristyb5d5f722009-11-04 03:03:49 +0000844#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000845 #pragma omp critical (MagickCore_OpaquePaintImageChannel)
846#endif
847 proceed=SetImageProgress(image,OpaquePaintImageTag,progress++,
848 image->rows);
849 if (proceed == MagickFalse)
850 status=MagickFalse;
851 }
852 }
853 image_view=DestroyCacheView(image_view);
854 return(status);
855}
856
857/*
858%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
859% %
860% %
861% %
862% T r a n s p a r e n t P a i n t I m a g e %
863% %
864% %
865% %
866%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
867%
868% TransparentPaintImage() changes the opacity value associated with any pixel
869% that matches color to the value defined by opacity.
870%
871% By default color must match a particular pixel color exactly. However,
872% in many cases two colors may differ by a small amount. Fuzz defines
873% how much tolerance is acceptable to consider two colors as the same.
874% For example, set fuzz to 10 and the color red at intensities of 100 and
875% 102 respectively are now interpreted as the same color.
876%
877% The format of the TransparentPaintImage method is:
878%
879% MagickBooleanType TransparentPaintImage(Image *image,
cristy4c08aed2011-07-01 19:47:50 +0000880% const PixelInfo *target,const Quantum opacity,
cristy3ed852e2009-09-05 21:47:34 +0000881% const MagickBooleanType invert)
882%
883% A description of each parameter follows:
884%
885% o image: the image.
886%
887% o target: the target color.
888%
889% o opacity: the replacement opacity value.
890%
891% o invert: paint any pixel that does not match the target color.
892%
893*/
894MagickExport MagickBooleanType TransparentPaintImage(Image *image,
cristy4c08aed2011-07-01 19:47:50 +0000895 const PixelInfo *target,const Quantum opacity,
cristy3ed852e2009-09-05 21:47:34 +0000896 const MagickBooleanType invert)
897{
898#define TransparentPaintImageTag "Transparent/Image"
899
cristyc4c8d132010-01-07 01:58:38 +0000900 CacheView
901 *image_view;
902
cristy3ed852e2009-09-05 21:47:34 +0000903 ExceptionInfo
904 *exception;
905
cristy3ed852e2009-09-05 21:47:34 +0000906 MagickBooleanType
907 status;
908
cristybb503372010-05-27 20:51:26 +0000909 MagickOffsetType
910 progress;
911
cristy4c08aed2011-07-01 19:47:50 +0000912 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000913 zero;
914
cristybb503372010-05-27 20:51:26 +0000915 ssize_t
916 y;
917
cristy3ed852e2009-09-05 21:47:34 +0000918 assert(image != (Image *) NULL);
919 assert(image->signature == MagickSignature);
cristy4c08aed2011-07-01 19:47:50 +0000920 assert(target != (PixelInfo *) NULL);
cristy3ed852e2009-09-05 21:47:34 +0000921 if (image->debug != MagickFalse)
922 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
923 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
924 return(MagickFalse);
925 if (image->matte == MagickFalse)
926 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel);
927 /*
928 Make image color transparent.
929 */
930 status=MagickTrue;
931 progress=0;
932 exception=(&image->exception);
cristy4c08aed2011-07-01 19:47:50 +0000933 GetPixelInfo(image,&zero);
cristy3ed852e2009-09-05 21:47:34 +0000934 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000935#if defined(MAGICKCORE_OPENMP_SUPPORT)
936 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000937#endif
cristybb503372010-05-27 20:51:26 +0000938 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000939 {
cristy4c08aed2011-07-01 19:47:50 +0000940 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000941 pixel;
942
cristybb503372010-05-27 20:51:26 +0000943 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000944 x;
945
cristy4c08aed2011-07-01 19:47:50 +0000946 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000947 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000948
949 if (status == MagickFalse)
950 continue;
951 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000952 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000953 {
954 status=MagickFalse;
955 continue;
956 }
cristy3ed852e2009-09-05 21:47:34 +0000957 pixel=zero;
cristybb503372010-05-27 20:51:26 +0000958 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000959 {
cristy4c08aed2011-07-01 19:47:50 +0000960 SetPixelInfo(image,q,&pixel);
961 if (IsFuzzyEquivalencePixelInfo(&pixel,target) != invert)
962 SetPixelAlpha(image,opacity,q);
963 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000964 }
965 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
966 status=MagickFalse;
967 if (image->progress_monitor != (MagickProgressMonitor) NULL)
968 {
969 MagickBooleanType
970 proceed;
971
cristyb5d5f722009-11-04 03:03:49 +0000972#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000973 #pragma omp critical (MagickCore_TransparentPaintImage)
974#endif
975 proceed=SetImageProgress(image,TransparentPaintImageTag,progress++,
976 image->rows);
977 if (proceed == MagickFalse)
978 status=MagickFalse;
979 }
980 }
981 image_view=DestroyCacheView(image_view);
982 return(status);
983}
984
985/*
986%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
987% %
988% %
989% %
990% 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 %
991% %
992% %
993% %
994%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
995%
996% TransparentPaintImageChroma() changes the opacity value associated with any
997% pixel that matches color to the value defined by opacity.
998%
999% As there is one fuzz value for the all the channels, the
1000% TransparentPaintImage() API is not suitable for the operations like chroma,
1001% where the tolerance for similarity of two color component (RGB) can be
1002% different, Thus we define this method take two target pixels (one
1003% low and one hight) and all the pixels of an image which are lying between
1004% these two pixels are made transparent.
1005%
1006% The format of the TransparentPaintImage method is:
1007%
1008% MagickBooleanType TransparentPaintImage(Image *image,
cristy4c08aed2011-07-01 19:47:50 +00001009% const PixelInfo *low,const PixelInfo *hight,
cristy3ed852e2009-09-05 21:47:34 +00001010% const Quantum opacity,const MagickBooleanType invert)
1011%
1012% A description of each parameter follows:
1013%
1014% o image: the image.
1015%
1016% o low: the low target color.
1017%
1018% o high: the high target color.
1019%
1020% o opacity: the replacement opacity value.
1021%
1022% o invert: paint any pixel that does not match the target color.
1023%
1024*/
1025MagickExport MagickBooleanType TransparentPaintImageChroma(Image *image,
cristy4c08aed2011-07-01 19:47:50 +00001026 const PixelInfo *low,const PixelInfo *high,
cristy3ed852e2009-09-05 21:47:34 +00001027 const Quantum opacity,const MagickBooleanType invert)
1028{
1029#define TransparentPaintImageTag "Transparent/Image"
1030
cristyc4c8d132010-01-07 01:58:38 +00001031 CacheView
1032 *image_view;
1033
cristy3ed852e2009-09-05 21:47:34 +00001034 ExceptionInfo
1035 *exception;
1036
cristy3ed852e2009-09-05 21:47:34 +00001037 MagickBooleanType
1038 status;
1039
cristybb503372010-05-27 20:51:26 +00001040 MagickOffsetType
1041 progress;
1042
1043 ssize_t
1044 y;
1045
cristy3ed852e2009-09-05 21:47:34 +00001046 assert(image != (Image *) NULL);
1047 assert(image->signature == MagickSignature);
cristy4c08aed2011-07-01 19:47:50 +00001048 assert(high != (PixelInfo *) NULL);
1049 assert(low != (PixelInfo *) NULL);
cristy3ed852e2009-09-05 21:47:34 +00001050 if (image->debug != MagickFalse)
1051 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1052 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
1053 return(MagickFalse);
1054 if (image->matte == MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +00001055 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel);
cristy3ed852e2009-09-05 21:47:34 +00001056 /*
1057 Make image color transparent.
1058 */
1059 status=MagickTrue;
1060 progress=0;
1061 exception=(&image->exception);
1062 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00001063#if defined(MAGICKCORE_OPENMP_SUPPORT)
1064 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001065#endif
cristybb503372010-05-27 20:51:26 +00001066 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001067 {
1068 MagickBooleanType
1069 match;
1070
cristy4c08aed2011-07-01 19:47:50 +00001071 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001072 pixel;
1073
cristybb503372010-05-27 20:51:26 +00001074 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001075 x;
1076
cristy4c08aed2011-07-01 19:47:50 +00001077 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001078 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001079
1080 if (status == MagickFalse)
1081 continue;
1082 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001083 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001084 {
1085 status=MagickFalse;
1086 continue;
1087 }
cristy4c08aed2011-07-01 19:47:50 +00001088 GetPixelInfo(image,&pixel);
cristybb503372010-05-27 20:51:26 +00001089 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001090 {
cristy4c08aed2011-07-01 19:47:50 +00001091 SetPixelInfo(image,q,&pixel);
cristy3ed852e2009-09-05 21:47:34 +00001092 match=((pixel.red >= low->red) && (pixel.red <= high->red) &&
1093 (pixel.green >= low->green) && (pixel.green <= high->green) &&
1094 (pixel.blue >= low->blue) && (pixel.blue <= high->blue)) ?
1095 MagickTrue : MagickFalse;
1096 if (match != invert)
cristy4c08aed2011-07-01 19:47:50 +00001097 SetPixelAlpha(image,opacity,q);
1098 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001099 }
1100 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1101 status=MagickFalse;
1102 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1103 {
1104 MagickBooleanType
1105 proceed;
1106
cristyb5d5f722009-11-04 03:03:49 +00001107#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001108 #pragma omp critical (MagickCore_TransparentPaintImageChroma)
1109#endif
1110 proceed=SetImageProgress(image,TransparentPaintImageTag,progress++,
1111 image->rows);
1112 if (proceed == MagickFalse)
1113 status=MagickFalse;
1114 }
1115 }
1116 image_view=DestroyCacheView(image_view);
1117 return(status);
1118}