blob: 526f6268ef08ab548952f8273528bb9b89a66c32 [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% %
cristy45ef08f2012-12-07 13:13:34 +000020% Copyright 1999-2013 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"
cristy6a2180c2013-05-27 10:28:36 +000043#include "MagickCore/channel.h"
cristy4c08aed2011-07-01 19:47:50 +000044#include "MagickCore/color.h"
45#include "MagickCore/color-private.h"
46#include "MagickCore/colorspace-private.h"
47#include "MagickCore/composite.h"
48#include "MagickCore/composite-private.h"
49#include "MagickCore/draw.h"
50#include "MagickCore/draw-private.h"
51#include "MagickCore/exception.h"
52#include "MagickCore/exception-private.h"
53#include "MagickCore/gem.h"
cristy8ea81222011-09-04 10:33:32 +000054#include "MagickCore/gem-private.h"
cristy4c08aed2011-07-01 19:47:50 +000055#include "MagickCore/monitor.h"
56#include "MagickCore/monitor-private.h"
57#include "MagickCore/paint.h"
58#include "MagickCore/pixel-accessor.h"
cristyac245f82012-05-05 17:13:57 +000059#include "MagickCore/resource_.h"
cristy95f562a2012-01-01 20:49:11 +000060#include "MagickCore/statistic.h"
cristy4c08aed2011-07-01 19:47:50 +000061#include "MagickCore/string_.h"
62#include "MagickCore/thread-private.h"
cristy3ed852e2009-09-05 21:47:34 +000063
cristy3ed852e2009-09-05 21:47:34 +000064/*
65%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
66% %
67% %
68% %
69% F l o o d f i l l P a i n t I m a g e %
70% %
71% %
72% %
73%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
74%
75% FloodfillPaintImage() changes the color value of any pixel that matches
76% target and is an immediate neighbor. If the method FillToBorderMethod is
77% specified, the color value is changed for any neighbor pixel that does not
78% match the bordercolor member of image.
79%
cristy908a0002011-08-28 00:13:39 +000080% By default target must match a particular pixel color exactly. However,
81% in many cases two colors may differ by a small amount. The fuzz member of
82% image defines how much tolerance is acceptable to consider two colors as
83% the same. For example, set fuzz to 10 and the color red at intensities of
84% 100 and 102 respectively are now interpreted as the same color for the
85% purposes of the floodfill.
cristy3ed852e2009-09-05 21:47:34 +000086%
87% The format of the FloodfillPaintImage method is:
88%
89% MagickBooleanType FloodfillPaintImage(Image *image,
cristyd42d9952011-07-08 14:21:50 +000090% const DrawInfo *draw_info,const PixelInfo target,
91% const ssize_t x_offset,const ssize_t y_offset,
cristy189e84c2011-08-27 18:08:53 +000092% const MagickBooleanType invert,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +000093%
94% A description of each parameter follows:
95%
96% o image: the image.
97%
cristy3ed852e2009-09-05 21:47:34 +000098% o draw_info: the draw info.
99%
100% o target: the RGB value of the target color.
101%
102% o x_offset,y_offset: the starting location of the operation.
103%
104% o invert: paint any pixel that does not match the target color.
105%
cristy189e84c2011-08-27 18:08:53 +0000106% o exception: return any errors or warnings in this structure.
107%
cristy3ed852e2009-09-05 21:47:34 +0000108*/
109MagickExport MagickBooleanType FloodfillPaintImage(Image *image,
cristyd42d9952011-07-08 14:21:50 +0000110 const DrawInfo *draw_info,const PixelInfo *target,const ssize_t x_offset,
cristy189e84c2011-08-27 18:08:53 +0000111 const ssize_t y_offset,const MagickBooleanType invert,
112 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000113{
cristyf9e2a082013-01-26 22:16:32 +0000114#define MaxStacksize 131072UL
cristy3ed852e2009-09-05 21:47:34 +0000115#define PushSegmentStack(up,left,right,delta) \
116{ \
117 if (s >= (segment_stack+MaxStacksize)) \
118 ThrowBinaryException(DrawError,"SegmentStackOverflow",image->filename) \
119 else \
120 { \
cristybb503372010-05-27 20:51:26 +0000121 if ((((up)+(delta)) >= 0) && (((up)+(delta)) < (ssize_t) image->rows)) \
cristy3ed852e2009-09-05 21:47:34 +0000122 { \
123 s->x1=(double) (left); \
124 s->y1=(double) (up); \
125 s->x2=(double) (right); \
126 s->y2=(double) (delta); \
127 s++; \
128 } \
129 } \
130}
131
cristyb0d3bb92010-09-22 14:37:58 +0000132 CacheView
133 *floodplane_view,
134 *image_view;
135
cristy3ed852e2009-09-05 21:47:34 +0000136 Image
137 *floodplane_image;
138
cristy3ed852e2009-09-05 21:47:34 +0000139 MagickBooleanType
cristy14973ba2011-08-27 23:48:07 +0000140 skip,
141 status;
cristy3ed852e2009-09-05 21:47:34 +0000142
cristy4c08aed2011-07-01 19:47:50 +0000143 PixelInfo
cristyfdcc0182011-12-26 19:33:56 +0000144 fill_color,
cristy3ed852e2009-09-05 21:47:34 +0000145 pixel;
146
cristy3ed852e2009-09-05 21:47:34 +0000147 register SegmentInfo
148 *s;
149
150 SegmentInfo
151 *segment_stack;
152
cristy9d314ff2011-03-09 01:30:28 +0000153 ssize_t
154 offset,
155 start,
156 x,
157 x1,
158 x2,
159 y;
160
cristy3ed852e2009-09-05 21:47:34 +0000161 /*
162 Check boundary conditions.
163 */
164 assert(image != (Image *) NULL);
165 assert(image->signature == MagickSignature);
166 if (image->debug != MagickFalse)
167 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
168 assert(draw_info != (DrawInfo *) NULL);
169 assert(draw_info->signature == MagickSignature);
cristybb503372010-05-27 20:51:26 +0000170 if ((x_offset < 0) || (x_offset >= (ssize_t) image->columns))
cristy3ed852e2009-09-05 21:47:34 +0000171 return(MagickFalse);
cristybb503372010-05-27 20:51:26 +0000172 if ((y_offset < 0) || (y_offset >= (ssize_t) image->rows))
cristy3ed852e2009-09-05 21:47:34 +0000173 return(MagickFalse);
cristy574cc262011-08-05 01:23:58 +0000174 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000175 return(MagickFalse);
cristya6400b12013-03-15 12:20:18 +0000176 if (IsGrayColorspace(image->colorspace) != MagickFalse)
cristy0c81d062013-04-21 15:22:02 +0000177 (void) SetImageColorspace(image,sRGBColorspace,exception);
cristy400275a2013-03-13 00:34:24 +0000178 if ((image->alpha_trait != BlendPixelTrait) &&
179 (draw_info->fill.alpha_trait == BlendPixelTrait))
cristy5b67d4e2012-02-07 19:43:53 +0000180 (void) SetImageAlpha(image,OpaqueAlpha,exception);
cristy3ed852e2009-09-05 21:47:34 +0000181 /*
182 Set floodfill state.
183 */
cristy95f562a2012-01-01 20:49:11 +0000184 floodplane_image=CloneImage(image,image->columns,image->rows,MagickTrue,
185 exception);
cristy3ed852e2009-09-05 21:47:34 +0000186 if (floodplane_image == (Image *) NULL)
187 return(MagickFalse);
cristy8a46d822012-08-28 23:32:39 +0000188 floodplane_image->alpha_trait=UndefinedPixelTrait;
cristy20990102012-05-16 18:10:49 +0000189 floodplane_image->colorspace=GRAYColorspace;
190 (void) QueryColorCompliance("#000",AllCompliance,
191 &floodplane_image->background_color,exception);
192 (void) SetImageBackgroundColor(floodplane_image,exception);
cristy3ed852e2009-09-05 21:47:34 +0000193 segment_stack=(SegmentInfo *) AcquireQuantumMemory(MaxStacksize,
194 sizeof(*segment_stack));
195 if (segment_stack == (SegmentInfo *) NULL)
196 {
197 floodplane_image=DestroyImage(floodplane_image);
198 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
199 image->filename);
200 }
201 /*
202 Push initial segment on stack.
203 */
cristy14973ba2011-08-27 23:48:07 +0000204 status=MagickTrue;
cristy3ed852e2009-09-05 21:47:34 +0000205 x=x_offset;
206 y=y_offset;
207 start=0;
208 s=segment_stack;
209 PushSegmentStack(y,x,x,1);
210 PushSegmentStack(y+1,x,x,-1);
cristy4c08aed2011-07-01 19:47:50 +0000211 GetPixelInfo(image,&pixel);
cristy46ff2672012-12-14 15:32:26 +0000212 image_view=AcquireVirtualCacheView(image,exception);
213 floodplane_view=AcquireAuthenticCacheView(floodplane_image,exception);
cristy3ed852e2009-09-05 21:47:34 +0000214 while (s > segment_stack)
215 {
cristy4c08aed2011-07-01 19:47:50 +0000216 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000217 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000218
cristy4c08aed2011-07-01 19:47:50 +0000219 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000220 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000221
cristy14973ba2011-08-27 23:48:07 +0000222 register ssize_t
223 x;
224
cristy3ed852e2009-09-05 21:47:34 +0000225 /*
226 Pop segment off stack.
227 */
228 s--;
cristybb503372010-05-27 20:51:26 +0000229 x1=(ssize_t) s->x1;
230 x2=(ssize_t) s->x2;
231 offset=(ssize_t) s->y2;
232 y=(ssize_t) s->y1+offset;
cristy3ed852e2009-09-05 21:47:34 +0000233 /*
234 Recolor neighboring pixels.
235 */
cristyb0d3bb92010-09-22 14:37:58 +0000236 p=GetCacheViewVirtualPixels(image_view,0,y,(size_t) (x1+1),1,exception);
237 q=GetCacheViewAuthenticPixels(floodplane_view,0,y,(size_t) (x1+1),1,
cristy3ed852e2009-09-05 21:47:34 +0000238 exception);
cristy4c08aed2011-07-01 19:47:50 +0000239 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000240 break;
cristyed231572011-07-14 02:18:59 +0000241 p+=x1*GetPixelChannels(image);
242 q+=x1*GetPixelChannels(floodplane_image);
cristy3ed852e2009-09-05 21:47:34 +0000243 for (x=x1; x >= 0; x--)
244 {
cristy95f562a2012-01-01 20:49:11 +0000245 if (GetPixelGray(floodplane_image,q) != 0)
cristy3ed852e2009-09-05 21:47:34 +0000246 break;
cristy803640d2011-11-17 02:11:32 +0000247 GetPixelInfoPixel(image,p,&pixel);
cristy4c08aed2011-07-01 19:47:50 +0000248 if (IsFuzzyEquivalencePixelInfo(&pixel,target) == invert)
cristy3ed852e2009-09-05 21:47:34 +0000249 break;
cristy95f562a2012-01-01 20:49:11 +0000250 SetPixelGray(floodplane_image,QuantumRange,q);
cristyed231572011-07-14 02:18:59 +0000251 p-=GetPixelChannels(image);
252 q-=GetPixelChannels(floodplane_image);
cristy3ed852e2009-09-05 21:47:34 +0000253 }
cristyb0d3bb92010-09-22 14:37:58 +0000254 if (SyncCacheViewAuthenticPixels(floodplane_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000255 break;
256 skip=x >= x1 ? MagickTrue : MagickFalse;
257 if (skip == MagickFalse)
258 {
259 start=x+1;
260 if (start < x1)
261 PushSegmentStack(y,start,x1-1,-offset);
262 x=x1+1;
263 }
264 do
265 {
266 if (skip == MagickFalse)
267 {
cristybb503372010-05-27 20:51:26 +0000268 if (x < (ssize_t) image->columns)
cristy3ed852e2009-09-05 21:47:34 +0000269 {
cristyb0d3bb92010-09-22 14:37:58 +0000270 p=GetCacheViewVirtualPixels(image_view,x,y,image->columns-x,1,
cristy3ed852e2009-09-05 21:47:34 +0000271 exception);
cristyfdcc0182011-12-26 19:33:56 +0000272 q=GetCacheViewAuthenticPixels(floodplane_view,x,y,image->columns-
273 x,1,exception);
cristy636dcb52011-08-26 13:23:49 +0000274 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000275 break;
cristybb503372010-05-27 20:51:26 +0000276 for ( ; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000277 {
cristy95f562a2012-01-01 20:49:11 +0000278 if (GetPixelGray(floodplane_image,q) != 0)
cristy3ed852e2009-09-05 21:47:34 +0000279 break;
cristy803640d2011-11-17 02:11:32 +0000280 GetPixelInfoPixel(image,p,&pixel);
cristy4c08aed2011-07-01 19:47:50 +0000281 if (IsFuzzyEquivalencePixelInfo(&pixel,target) == invert)
cristy3ed852e2009-09-05 21:47:34 +0000282 break;
cristy95f562a2012-01-01 20:49:11 +0000283 SetPixelGray(floodplane_image,QuantumRange,q);
cristyed231572011-07-14 02:18:59 +0000284 p+=GetPixelChannels(image);
285 q+=GetPixelChannels(floodplane_image);
cristy3ed852e2009-09-05 21:47:34 +0000286 }
cristy14973ba2011-08-27 23:48:07 +0000287 status=SyncCacheViewAuthenticPixels(floodplane_view,exception);
288 if (status == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000289 break;
290 }
291 PushSegmentStack(y,start,x-1,offset);
292 if (x > (x2+1))
293 PushSegmentStack(y,x2+1,x-1,-offset);
294 }
295 skip=MagickFalse;
296 x++;
297 if (x <= x2)
298 {
cristyb0d3bb92010-09-22 14:37:58 +0000299 p=GetCacheViewVirtualPixels(image_view,x,y,(size_t) (x2-x+1),1,
300 exception);
301 q=GetCacheViewAuthenticPixels(floodplane_view,x,y,(size_t) (x2-x+1),1,
cristy3ed852e2009-09-05 21:47:34 +0000302 exception);
cristy636dcb52011-08-26 13:23:49 +0000303 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000304 break;
cristy3ed852e2009-09-05 21:47:34 +0000305 for ( ; x <= x2; x++)
306 {
cristy95f562a2012-01-01 20:49:11 +0000307 if (GetPixelGray(floodplane_image,q) != 0)
cristy3ed852e2009-09-05 21:47:34 +0000308 break;
cristy803640d2011-11-17 02:11:32 +0000309 GetPixelInfoPixel(image,p,&pixel);
cristy4c08aed2011-07-01 19:47:50 +0000310 if (IsFuzzyEquivalencePixelInfo(&pixel,target) != invert)
cristy3ed852e2009-09-05 21:47:34 +0000311 break;
cristyed231572011-07-14 02:18:59 +0000312 p+=GetPixelChannels(image);
313 q+=GetPixelChannels(floodplane_image);
cristy3ed852e2009-09-05 21:47:34 +0000314 }
315 }
316 start=x;
317 } while (x <= x2);
318 }
cristybb503372010-05-27 20:51:26 +0000319 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000320 {
cristy4c08aed2011-07-01 19:47:50 +0000321 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000322 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000323
cristy4c08aed2011-07-01 19:47:50 +0000324 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000325 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000326
cristy14973ba2011-08-27 23:48:07 +0000327 register ssize_t
328 x;
329
cristy3ed852e2009-09-05 21:47:34 +0000330 /*
331 Tile fill color onto floodplane.
332 */
cristy95f562a2012-01-01 20:49:11 +0000333 p=GetCacheViewVirtualPixels(floodplane_view,0,y,image->columns,1,exception);
cristyb0d3bb92010-09-22 14:37:58 +0000334 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000335 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000336 break;
cristybb503372010-05-27 20:51:26 +0000337 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000338 {
cristy95f562a2012-01-01 20:49:11 +0000339 if (GetPixelGray(floodplane_image,p) != 0)
cristy3ed852e2009-09-05 21:47:34 +0000340 {
cristy2ed42f62011-10-02 19:49:57 +0000341 (void) GetFillColor(draw_info,x,y,&fill_color,exception);
cristy8a20fa02011-12-27 15:54:31 +0000342 SetPixelInfoPixel(image,&fill_color,q);
cristy3ed852e2009-09-05 21:47:34 +0000343 }
cristyed231572011-07-14 02:18:59 +0000344 p+=GetPixelChannels(floodplane_image);
345 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000346 }
cristyb0d3bb92010-09-22 14:37:58 +0000347 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000348 break;
349 }
cristyb0d3bb92010-09-22 14:37:58 +0000350 floodplane_view=DestroyCacheView(floodplane_view);
351 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +0000352 segment_stack=(SegmentInfo *) RelinquishMagickMemory(segment_stack);
353 floodplane_image=DestroyImage(floodplane_image);
cristybb503372010-05-27 20:51:26 +0000354 return(y == (ssize_t) image->rows ? MagickTrue : MagickFalse);
cristy3ed852e2009-09-05 21:47:34 +0000355}
356
357/*
358%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
359% %
360% %
361% %
362+ G r a d i e n t I m a g e %
363% %
364% %
365% %
366%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
367%
cristycee97112010-05-28 00:44:52 +0000368% GradientImage() applies a continuously smooth color transitions along a
cristy3ed852e2009-09-05 21:47:34 +0000369% vector from one color to another.
370%
371% Note, the interface of this method will change in the future to support
372% more than one transistion.
373%
374% The format of the GradientImage method is:
375%
376% MagickBooleanType GradientImage(Image *image,const GradientType type,
cristy101ab702011-10-13 13:06:32 +0000377% const SpreadMethod method,const PixelInfo *start_color,
378% const PixelInfo *stop_color,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000379%
380% A description of each parameter follows:
381%
382% o image: the image.
383%
384% o type: the gradient type: linear or radial.
385%
386% o spread: the gradient spread meathod: pad, reflect, or repeat.
387%
388% o start_color: the start color.
389%
390% o stop_color: the stop color.
391%
cristy189e84c2011-08-27 18:08:53 +0000392% o exception: return any errors or warnings in this structure.
393%
cristy3ed852e2009-09-05 21:47:34 +0000394*/
cristy117ff172010-08-15 21:35:32 +0000395
396static inline double MagickMax(const double x,const double y)
397{
398 return(x > y ? x : y);
399}
400
cristy3ed852e2009-09-05 21:47:34 +0000401MagickExport MagickBooleanType GradientImage(Image *image,
402 const GradientType type,const SpreadMethod method,
cristy101ab702011-10-13 13:06:32 +0000403 const PixelInfo *start_color,const PixelInfo *stop_color,
cristy189e84c2011-08-27 18:08:53 +0000404 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000405{
406 DrawInfo
407 *draw_info;
408
409 GradientInfo
410 *gradient;
411
412 MagickBooleanType
413 status;
414
cristybb503372010-05-27 20:51:26 +0000415 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000416 i;
417
418 /*
419 Set gradient start-stop end points.
420 */
421 assert(image != (const Image *) NULL);
422 assert(image->signature == MagickSignature);
423 if (image->debug != MagickFalse)
424 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy101ab702011-10-13 13:06:32 +0000425 assert(start_color != (const PixelInfo *) NULL);
426 assert(stop_color != (const PixelInfo *) NULL);
cristy3ed852e2009-09-05 21:47:34 +0000427 draw_info=AcquireDrawInfo();
428 gradient=(&draw_info->gradient);
429 gradient->type=type;
430 gradient->bounding_box.width=image->columns;
431 gradient->bounding_box.height=image->rows;
432 gradient->gradient_vector.x2=(double) image->columns-1.0;
433 gradient->gradient_vector.y2=(double) image->rows-1.0;
434 if ((type == LinearGradient) && (gradient->gradient_vector.y2 != 0.0))
435 gradient->gradient_vector.x2=0.0;
436 gradient->center.x=(double) gradient->gradient_vector.x2/2.0;
437 gradient->center.y=(double) gradient->gradient_vector.y2/2.0;
438 gradient->radius=MagickMax(gradient->center.x,gradient->center.y);
439 gradient->spread=method;
440 /*
441 Define the gradient to fill between the stops.
442 */
443 gradient->number_stops=2;
444 gradient->stops=(StopInfo *) AcquireQuantumMemory(gradient->number_stops,
445 sizeof(*gradient->stops));
446 if (gradient->stops == (StopInfo *) NULL)
447 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
448 image->filename);
449 (void) ResetMagickMemory(gradient->stops,0,gradient->number_stops*
450 sizeof(*gradient->stops));
cristybb503372010-05-27 20:51:26 +0000451 for (i=0; i < (ssize_t) gradient->number_stops; i++)
cristy4c08aed2011-07-01 19:47:50 +0000452 GetPixelInfo(image,&gradient->stops[i].color);
cristy9d8c8ce2011-10-25 16:13:52 +0000453 gradient->stops[0].color=(*start_color);
cristy3ed852e2009-09-05 21:47:34 +0000454 gradient->stops[0].offset=0.0;
cristy9d8c8ce2011-10-25 16:13:52 +0000455 gradient->stops[1].color=(*stop_color);
cristy3ed852e2009-09-05 21:47:34 +0000456 gradient->stops[1].offset=1.0;
457 /*
458 Draw a gradient on the image.
459 */
cristybafdbba2012-06-20 11:50:57 +0000460 (void) SetImageColorspace(image,start_color->colorspace,exception);
cristy947cb4c2011-10-20 18:41:46 +0000461 status=DrawGradientImage(image,draw_info,exception);
cristy3ed852e2009-09-05 21:47:34 +0000462 draw_info=DestroyDrawInfo(draw_info);
cristy3ed852e2009-09-05 21:47:34 +0000463 return(status);
464}
465
466/*
467%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
468% %
469% %
470% %
471% O i l P a i n t I m a g e %
472% %
473% %
474% %
475%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
476%
477% OilPaintImage() applies a special effect filter that simulates an oil
478% painting. Each pixel is replaced by the most frequent color occurring
479% in a circular region defined by radius.
480%
481% The format of the OilPaintImage method is:
482%
483% Image *OilPaintImage(const Image *image,const double radius,
cristy14973ba2011-08-27 23:48:07 +0000484% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000485%
486% A description of each parameter follows:
487%
488% o image: the image.
489%
490% o radius: the radius of the circular neighborhood.
491%
cristy14973ba2011-08-27 23:48:07 +0000492% o sigma: the standard deviation of the Gaussian, in pixels.
493%
cristy3ed852e2009-09-05 21:47:34 +0000494% o exception: return any errors or warnings in this structure.
495%
496*/
497
cristybb503372010-05-27 20:51:26 +0000498static size_t **DestroyHistogramThreadSet(size_t **histogram)
cristy3ed852e2009-09-05 21:47:34 +0000499{
cristybb503372010-05-27 20:51:26 +0000500 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000501 i;
502
cristybb503372010-05-27 20:51:26 +0000503 assert(histogram != (size_t **) NULL);
cristyac245f82012-05-05 17:13:57 +0000504 for (i=0; i < (ssize_t) GetMagickResourceLimit(ThreadResource); i++)
cristybb503372010-05-27 20:51:26 +0000505 if (histogram[i] != (size_t *) NULL)
506 histogram[i]=(size_t *) RelinquishMagickMemory(histogram[i]);
cristyb41ee102010-10-04 16:46:15 +0000507 histogram=(size_t **) RelinquishMagickMemory(histogram);
cristy3ed852e2009-09-05 21:47:34 +0000508 return(histogram);
509}
510
cristybb503372010-05-27 20:51:26 +0000511static size_t **AcquireHistogramThreadSet(const size_t count)
cristy3ed852e2009-09-05 21:47:34 +0000512{
cristybb503372010-05-27 20:51:26 +0000513 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000514 i;
515
cristybb503372010-05-27 20:51:26 +0000516 size_t
cristy3ed852e2009-09-05 21:47:34 +0000517 **histogram,
518 number_threads;
519
cristy9357bdd2012-07-30 12:28:34 +0000520 number_threads=(size_t) GetMagickResourceLimit(ThreadResource);
cristy14973ba2011-08-27 23:48:07 +0000521 histogram=(size_t **) AcquireQuantumMemory(number_threads,sizeof(*histogram));
cristybb503372010-05-27 20:51:26 +0000522 if (histogram == (size_t **) NULL)
523 return((size_t **) NULL);
cristy3ed852e2009-09-05 21:47:34 +0000524 (void) ResetMagickMemory(histogram,0,number_threads*sizeof(*histogram));
cristybb503372010-05-27 20:51:26 +0000525 for (i=0; i < (ssize_t) number_threads; i++)
cristy3ed852e2009-09-05 21:47:34 +0000526 {
cristy14973ba2011-08-27 23:48:07 +0000527 histogram[i]=(size_t *) AcquireQuantumMemory(count,sizeof(**histogram));
cristybb503372010-05-27 20:51:26 +0000528 if (histogram[i] == (size_t *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000529 return(DestroyHistogramThreadSet(histogram));
530 }
531 return(histogram);
532}
533
534MagickExport Image *OilPaintImage(const Image *image,const double radius,
cristy14973ba2011-08-27 23:48:07 +0000535 const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000536{
537#define NumberPaintBins 256
538#define OilPaintImageTag "OilPaint/Image"
539
cristyfa112112010-01-04 17:48:07 +0000540 CacheView
541 *image_view,
542 *paint_view;
543
cristy3ed852e2009-09-05 21:47:34 +0000544 Image
cristy404d0da2012-12-20 13:04:30 +0000545 *linear_image,
cristy3ed852e2009-09-05 21:47:34 +0000546 *paint_image;
547
cristy3ed852e2009-09-05 21:47:34 +0000548 MagickBooleanType
549 status;
550
cristybb503372010-05-27 20:51:26 +0000551 MagickOffsetType
552 progress;
553
554 size_t
cristy14973ba2011-08-27 23:48:07 +0000555 **histograms,
cristy3ed852e2009-09-05 21:47:34 +0000556 width;
557
cristybb503372010-05-27 20:51:26 +0000558 ssize_t
cristyf8561542012-01-24 00:26:46 +0000559 center,
cristybb503372010-05-27 20:51:26 +0000560 y;
561
cristy3ed852e2009-09-05 21:47:34 +0000562 /*
563 Initialize painted image attributes.
564 */
565 assert(image != (const Image *) NULL);
566 assert(image->signature == MagickSignature);
567 if (image->debug != MagickFalse)
568 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
569 assert(exception != (ExceptionInfo *) NULL);
570 assert(exception->signature == MagickSignature);
cristy14973ba2011-08-27 23:48:07 +0000571 width=GetOptimalKernelWidth2D(radius,sigma);
cristy404d0da2012-12-20 13:04:30 +0000572 linear_image=CloneImage(image,0,0,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +0000573 paint_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
cristy404d0da2012-12-20 13:04:30 +0000574 if ((linear_image == (Image *) NULL) || (paint_image == (Image *) NULL))
575 {
576 if (linear_image != (Image *) NULL)
577 linear_image=DestroyImage(linear_image);
578 if (paint_image != (Image *) NULL)
579 linear_image=DestroyImage(paint_image);
580 return((Image *) NULL);
581 }
cristy574cc262011-08-05 01:23:58 +0000582 if (SetImageStorageClass(paint_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000583 {
cristy404d0da2012-12-20 13:04:30 +0000584 linear_image=DestroyImage(linear_image);
cristy3ed852e2009-09-05 21:47:34 +0000585 paint_image=DestroyImage(paint_image);
586 return((Image *) NULL);
587 }
588 histograms=AcquireHistogramThreadSet(NumberPaintBins);
cristybb503372010-05-27 20:51:26 +0000589 if (histograms == (size_t **) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000590 {
cristy404d0da2012-12-20 13:04:30 +0000591 linear_image=DestroyImage(linear_image);
cristy3ed852e2009-09-05 21:47:34 +0000592 paint_image=DestroyImage(paint_image);
593 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
594 }
595 /*
596 Oil paint image.
597 */
598 status=MagickTrue;
599 progress=0;
cristy404d0da2012-12-20 13:04:30 +0000600 center=(ssize_t) GetPixelChannels(linear_image)*(linear_image->columns+width)*
601 (width/2L)+GetPixelChannels(linear_image)*(width/2L);
602 image_view=AcquireVirtualCacheView(linear_image,exception);
cristy46ff2672012-12-14 15:32:26 +0000603 paint_view=AcquireAuthenticCacheView(paint_image,exception);
cristyb5d5f722009-11-04 03:03:49 +0000604#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000605 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy404d0da2012-12-20 13:04:30 +0000606 magick_threads(linear_image,paint_image,linear_image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +0000607#endif
cristy404d0da2012-12-20 13:04:30 +0000608 for (y=0; y < (ssize_t) linear_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000609 {
cristy4c08aed2011-07-01 19:47:50 +0000610 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000611 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000612
cristy4c08aed2011-07-01 19:47:50 +0000613 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000614 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000615
cristybb503372010-05-27 20:51:26 +0000616 register size_t
cristy3ed852e2009-09-05 21:47:34 +0000617 *histogram;
618
cristy14973ba2011-08-27 23:48:07 +0000619 register ssize_t
620 x;
621
cristy3ed852e2009-09-05 21:47:34 +0000622 if (status == MagickFalse)
623 continue;
cristyfe4ba002011-02-28 14:54:12 +0000624 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
cristy404d0da2012-12-20 13:04:30 +0000625 (width/2L),linear_image->columns+width,width,exception);
cristy3ed852e2009-09-05 21:47:34 +0000626 q=QueueCacheViewAuthenticPixels(paint_view,0,y,paint_image->columns,1,
627 exception);
cristy4c08aed2011-07-01 19:47:50 +0000628 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000629 {
630 status=MagickFalse;
631 continue;
632 }
cristy3ed852e2009-09-05 21:47:34 +0000633 histogram=histograms[GetOpenMPThreadId()];
cristy404d0da2012-12-20 13:04:30 +0000634 for (x=0; x < (ssize_t) linear_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000635 {
cristybb503372010-05-27 20:51:26 +0000636 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000637 i,
638 u;
639
cristybb503372010-05-27 20:51:26 +0000640 size_t
cristy3ed852e2009-09-05 21:47:34 +0000641 count;
642
cristy9d314ff2011-03-09 01:30:28 +0000643 ssize_t
644 j,
645 k,
cristyf8561542012-01-24 00:26:46 +0000646 n,
cristy9d314ff2011-03-09 01:30:28 +0000647 v;
648
cristy3ed852e2009-09-05 21:47:34 +0000649 /*
650 Assign most frequent color.
651 */
cristyf8561542012-01-24 00:26:46 +0000652 k=0;
cristy3ed852e2009-09-05 21:47:34 +0000653 j=0;
654 count=0;
cristyf8561542012-01-24 00:26:46 +0000655 (void) ResetMagickMemory(histogram,0,NumberPaintBins* sizeof(*histogram));
cristybb503372010-05-27 20:51:26 +0000656 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +0000657 {
cristybb503372010-05-27 20:51:26 +0000658 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +0000659 {
cristy404d0da2012-12-20 13:04:30 +0000660 n=(ssize_t) ScaleQuantumToChar(ClampToQuantum(GetPixelIntensity(
661 linear_image,p+GetPixelChannels(linear_image)*(u+k))));
cristyf8561542012-01-24 00:26:46 +0000662 histogram[n]++;
663 if (histogram[n] > count)
cristy3ed852e2009-09-05 21:47:34 +0000664 {
cristyf8561542012-01-24 00:26:46 +0000665 j=k+u;
666 count=histogram[n];
cristy3ed852e2009-09-05 21:47:34 +0000667 }
668 }
cristy034ade92013-03-07 12:18:19 +0000669 k+=(ssize_t) (linear_image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +0000670 }
cristy404d0da2012-12-20 13:04:30 +0000671 for (i=0; i < (ssize_t) GetPixelChannels(linear_image); i++)
cristyf8561542012-01-24 00:26:46 +0000672 {
cristy5a23c552013-02-13 14:34:28 +0000673 PixelChannel channel=GetPixelChannelChannel(linear_image,i);
674 PixelTrait traits=GetPixelChannelTraits(linear_image,channel);
675 PixelTrait paint_traits=GetPixelChannelTraits(paint_image,channel);
cristyf8561542012-01-24 00:26:46 +0000676 if ((traits == UndefinedPixelTrait) ||
677 (paint_traits == UndefinedPixelTrait))
678 continue;
cristy1eced092012-08-10 23:10:56 +0000679 if (((paint_traits & CopyPixelTrait) != 0) ||
cristy883fde12013-04-08 00:50:13 +0000680 (GetPixelReadMask(linear_image,p) == 0))
cristyf8561542012-01-24 00:26:46 +0000681 {
682 SetPixelChannel(paint_image,channel,p[center+i],q);
683 continue;
684 }
cristy404d0da2012-12-20 13:04:30 +0000685 SetPixelChannel(paint_image,channel,p[j*GetPixelChannels(linear_image)+
686 i],q);
cristyf8561542012-01-24 00:26:46 +0000687 }
cristy404d0da2012-12-20 13:04:30 +0000688 p+=GetPixelChannels(linear_image);
cristyed231572011-07-14 02:18:59 +0000689 q+=GetPixelChannels(paint_image);
cristy3ed852e2009-09-05 21:47:34 +0000690 }
691 if (SyncCacheViewAuthenticPixels(paint_view,exception) == MagickFalse)
692 status=MagickFalse;
cristy404d0da2012-12-20 13:04:30 +0000693 if (linear_image->progress_monitor != (MagickProgressMonitor) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000694 {
695 MagickBooleanType
696 proceed;
697
cristyb5d5f722009-11-04 03:03:49 +0000698#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy95338b32012-01-22 03:34:36 +0000699 #pragma omp critical (MagickCore_OilPaintImage)
cristy3ed852e2009-09-05 21:47:34 +0000700#endif
cristy404d0da2012-12-20 13:04:30 +0000701 proceed=SetImageProgress(linear_image,OilPaintImageTag,progress++,
702 linear_image->rows);
cristy3ed852e2009-09-05 21:47:34 +0000703 if (proceed == MagickFalse)
704 status=MagickFalse;
705 }
706 }
707 paint_view=DestroyCacheView(paint_view);
708 image_view=DestroyCacheView(image_view);
709 histograms=DestroyHistogramThreadSet(histograms);
cristy404d0da2012-12-20 13:04:30 +0000710 linear_image=DestroyImage(linear_image);
cristy3ed852e2009-09-05 21:47:34 +0000711 if (status == MagickFalse)
712 paint_image=DestroyImage(paint_image);
713 return(paint_image);
714}
715
716/*
717%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
718% %
719% %
720% %
721% O p a q u e P a i n t I m a g e %
722% %
723% %
724% %
725%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
726%
727% OpaquePaintImage() changes any pixel that matches color with the color
728% defined by fill.
729%
cristy14973ba2011-08-27 23:48:07 +0000730% By default color must match a particular pixel color exactly. However, in
731% many cases two colors may differ by a small amount. Fuzz defines how much
732% tolerance is acceptable to consider two colors as the same. For example,
733% set fuzz to 10 and the color red at intensities of 100 and 102 respectively
734% are now interpreted as the same color.
cristy3ed852e2009-09-05 21:47:34 +0000735%
736% The format of the OpaquePaintImage method is:
737%
738% MagickBooleanType OpaquePaintImage(Image *image,
cristy101ab702011-10-13 13:06:32 +0000739% const PixelInfo *target,const PixelInfo *fill,
cristy189e84c2011-08-27 18:08:53 +0000740% const MagickBooleanType invert,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000741%
742% A description of each parameter follows:
743%
744% o image: the image.
745%
cristy3ed852e2009-09-05 21:47:34 +0000746% o target: the RGB value of the target color.
747%
748% o fill: the replacement color.
749%
750% o invert: paint any pixel that does not match the target color.
751%
cristy189e84c2011-08-27 18:08:53 +0000752% o exception: return any errors or warnings in this structure.
753%
cristy3ed852e2009-09-05 21:47:34 +0000754*/
cristy3ed852e2009-09-05 21:47:34 +0000755MagickExport MagickBooleanType OpaquePaintImage(Image *image,
cristy189e84c2011-08-27 18:08:53 +0000756 const PixelInfo *target,const PixelInfo *fill,const MagickBooleanType invert,
757 ExceptionInfo *exception)
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 MagickBooleanType
765 status;
766
cristybb503372010-05-27 20:51:26 +0000767 MagickOffsetType
768 progress;
769
cristy4c08aed2011-07-01 19:47:50 +0000770 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000771 zero;
772
cristybb503372010-05-27 20:51:26 +0000773 ssize_t
774 y;
775
cristy3ed852e2009-09-05 21:47:34 +0000776 assert(image != (Image *) NULL);
777 assert(image->signature == MagickSignature);
cristy4c08aed2011-07-01 19:47:50 +0000778 assert(target != (PixelInfo *) NULL);
779 assert(fill != (PixelInfo *) NULL);
cristy3ed852e2009-09-05 21:47:34 +0000780 if (image->debug != MagickFalse)
781 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy574cc262011-08-05 01:23:58 +0000782 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000783 return(MagickFalse);
cristya6400b12013-03-15 12:20:18 +0000784 if ((IsGrayColorspace(image->colorspace) != MagickFalse) &&
785 (IsPixelInfoGray(fill) == MagickFalse))
cristy0c81d062013-04-21 15:22:02 +0000786 (void) SetImageColorspace(image,sRGBColorspace,exception);
cristy400275a2013-03-13 00:34:24 +0000787 if ((fill->alpha_trait == BlendPixelTrait) &&
788 (image->alpha_trait != BlendPixelTrait))
cristy09250192012-02-07 18:53:31 +0000789 (void) SetImageAlpha(image,OpaqueAlpha,exception);
cristy3ed852e2009-09-05 21:47:34 +0000790 /*
791 Make image color opaque.
792 */
793 status=MagickTrue;
794 progress=0;
cristy4c08aed2011-07-01 19:47:50 +0000795 GetPixelInfo(image,&zero);
cristy46ff2672012-12-14 15:32:26 +0000796 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +0000797#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000798 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy5e6b2592012-12-19 14:08:11 +0000799 magick_threads(image,image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +0000800#endif
cristybb503372010-05-27 20:51:26 +0000801 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000802 {
cristy4c08aed2011-07-01 19:47:50 +0000803 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000804 pixel;
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
cristy14973ba2011-08-27 23:48:07 +0000809 register ssize_t
810 x;
811
cristy3ed852e2009-09-05 21:47:34 +0000812 if (status == MagickFalse)
813 continue;
814 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy14973ba2011-08-27 23:48:07 +0000815 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000816 {
817 status=MagickFalse;
818 continue;
819 }
cristy3ed852e2009-09-05 21:47:34 +0000820 pixel=zero;
cristybb503372010-05-27 20:51:26 +0000821 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000822 {
cristy803640d2011-11-17 02:11:32 +0000823 GetPixelInfoPixel(image,q,&pixel);
cristy4c08aed2011-07-01 19:47:50 +0000824 if (IsFuzzyEquivalencePixelInfo(&pixel,target) != invert)
cristy95f562a2012-01-01 20:49:11 +0000825 SetPixelInfoPixel(image,fill,q);
cristyed231572011-07-14 02:18:59 +0000826 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000827 }
828 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
829 status=MagickFalse;
830 if (image->progress_monitor != (MagickProgressMonitor) NULL)
831 {
832 MagickBooleanType
833 proceed;
834
cristyb5d5f722009-11-04 03:03:49 +0000835#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy95338b32012-01-22 03:34:36 +0000836 #pragma omp critical (MagickCore_OpaquePaintImage)
cristy3ed852e2009-09-05 21:47:34 +0000837#endif
838 proceed=SetImageProgress(image,OpaquePaintImageTag,progress++,
839 image->rows);
840 if (proceed == MagickFalse)
841 status=MagickFalse;
842 }
843 }
844 image_view=DestroyCacheView(image_view);
845 return(status);
846}
847
848/*
849%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
850% %
851% %
852% %
853% T r a n s p a r e n t P a i n t I m a g e %
854% %
855% %
856% %
857%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
858%
859% TransparentPaintImage() changes the opacity value associated with any pixel
860% that matches color to the value defined by opacity.
861%
cristy14973ba2011-08-27 23:48:07 +0000862% By default color must match a particular pixel color exactly. However, in
863% many cases two colors may differ by a small amount. Fuzz defines how much
864% tolerance is acceptable to consider two colors as the same. For example,
865% set fuzz to 10 and the color red at intensities of 100 and 102 respectively
866% are now interpreted as the same color.
cristy3ed852e2009-09-05 21:47:34 +0000867%
868% The format of the TransparentPaintImage method is:
869%
870% MagickBooleanType TransparentPaintImage(Image *image,
cristy4c08aed2011-07-01 19:47:50 +0000871% const PixelInfo *target,const Quantum opacity,
cristy189e84c2011-08-27 18:08:53 +0000872% const MagickBooleanType invert,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000873%
874% A description of each parameter follows:
875%
876% o image: the image.
877%
878% o target: the target color.
879%
880% o opacity: the replacement opacity value.
881%
882% o invert: paint any pixel that does not match the target color.
883%
cristy189e84c2011-08-27 18:08:53 +0000884% o exception: return any errors or warnings in this structure.
885%
cristy3ed852e2009-09-05 21:47:34 +0000886*/
887MagickExport MagickBooleanType TransparentPaintImage(Image *image,
cristy14973ba2011-08-27 23:48:07 +0000888 const PixelInfo *target,const Quantum opacity,const MagickBooleanType invert,
889 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000890{
891#define TransparentPaintImageTag "Transparent/Image"
892
cristyc4c8d132010-01-07 01:58:38 +0000893 CacheView
894 *image_view;
895
cristy3ed852e2009-09-05 21:47:34 +0000896 MagickBooleanType
897 status;
898
cristybb503372010-05-27 20:51:26 +0000899 MagickOffsetType
900 progress;
901
cristy4c08aed2011-07-01 19:47:50 +0000902 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000903 zero;
904
cristybb503372010-05-27 20:51:26 +0000905 ssize_t
906 y;
907
cristy3ed852e2009-09-05 21:47:34 +0000908 assert(image != (Image *) NULL);
909 assert(image->signature == MagickSignature);
cristy4c08aed2011-07-01 19:47:50 +0000910 assert(target != (PixelInfo *) NULL);
cristy3ed852e2009-09-05 21:47:34 +0000911 if (image->debug != MagickFalse)
912 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy574cc262011-08-05 01:23:58 +0000913 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000914 return(MagickFalse);
cristy8a46d822012-08-28 23:32:39 +0000915 if (image->alpha_trait != BlendPixelTrait)
cristy63240882011-08-05 19:05:27 +0000916 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
cristy3ed852e2009-09-05 21:47:34 +0000917 /*
918 Make image color transparent.
919 */
920 status=MagickTrue;
921 progress=0;
cristy4c08aed2011-07-01 19:47:50 +0000922 GetPixelInfo(image,&zero);
cristy46ff2672012-12-14 15:32:26 +0000923 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +0000924#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000925 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy5e6b2592012-12-19 14:08:11 +0000926 magick_threads(image,image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +0000927#endif
cristybb503372010-05-27 20:51:26 +0000928 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000929 {
cristy4c08aed2011-07-01 19:47:50 +0000930 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000931 pixel;
932
cristybb503372010-05-27 20:51:26 +0000933 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000934 x;
935
cristy4c08aed2011-07-01 19:47:50 +0000936 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000937 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000938
939 if (status == MagickFalse)
940 continue;
941 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy14973ba2011-08-27 23:48:07 +0000942 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000943 {
944 status=MagickFalse;
945 continue;
946 }
cristy3ed852e2009-09-05 21:47:34 +0000947 pixel=zero;
cristybb503372010-05-27 20:51:26 +0000948 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000949 {
cristy803640d2011-11-17 02:11:32 +0000950 GetPixelInfoPixel(image,q,&pixel);
cristy4c08aed2011-07-01 19:47:50 +0000951 if (IsFuzzyEquivalencePixelInfo(&pixel,target) != invert)
952 SetPixelAlpha(image,opacity,q);
cristyed231572011-07-14 02:18:59 +0000953 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000954 }
955 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
956 status=MagickFalse;
957 if (image->progress_monitor != (MagickProgressMonitor) NULL)
958 {
959 MagickBooleanType
960 proceed;
961
cristyb5d5f722009-11-04 03:03:49 +0000962#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy95338b32012-01-22 03:34:36 +0000963 #pragma omp critical (MagickCore_TransparentPaintImage)
cristy3ed852e2009-09-05 21:47:34 +0000964#endif
965 proceed=SetImageProgress(image,TransparentPaintImageTag,progress++,
966 image->rows);
967 if (proceed == MagickFalse)
968 status=MagickFalse;
969 }
970 }
971 image_view=DestroyCacheView(image_view);
972 return(status);
973}
974
975/*
976%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
977% %
978% %
979% %
980% 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 %
981% %
982% %
983% %
984%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
985%
986% TransparentPaintImageChroma() changes the opacity value associated with any
987% pixel that matches color to the value defined by opacity.
988%
cristy14973ba2011-08-27 23:48:07 +0000989% As there is one fuzz value for the all the channels, TransparentPaintImage()
990% is not suitable for the operations like chroma, where the tolerance for
991% similarity of two color component (RGB) can be different. Thus we define
992% this method to take two target pixels (one low and one high) and all the
993% pixels of an image which are lying between these two pixels are made
994% transparent.
cristy3ed852e2009-09-05 21:47:34 +0000995%
cristy14973ba2011-08-27 23:48:07 +0000996% The format of the TransparentPaintImageChroma method is:
cristy3ed852e2009-09-05 21:47:34 +0000997%
cristy14973ba2011-08-27 23:48:07 +0000998% MagickBooleanType TransparentPaintImageChroma(Image *image,
999% const PixelInfo *low,const PixelInfo *high,const Quantum opacity,
1000% const MagickBooleanType invert,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001001%
1002% A description of each parameter follows:
1003%
1004% o image: the image.
1005%
1006% o low: the low target color.
1007%
1008% o high: the high target color.
1009%
1010% o opacity: the replacement opacity value.
1011%
1012% o invert: paint any pixel that does not match the target color.
1013%
cristy189e84c2011-08-27 18:08:53 +00001014% o exception: return any errors or warnings in this structure.
1015%
cristy3ed852e2009-09-05 21:47:34 +00001016*/
1017MagickExport MagickBooleanType TransparentPaintImageChroma(Image *image,
cristy189e84c2011-08-27 18:08:53 +00001018 const PixelInfo *low,const PixelInfo *high,const Quantum opacity,
1019 const MagickBooleanType invert,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001020{
1021#define TransparentPaintImageTag "Transparent/Image"
1022
cristyc4c8d132010-01-07 01:58:38 +00001023 CacheView
1024 *image_view;
1025
cristy3ed852e2009-09-05 21:47:34 +00001026 MagickBooleanType
1027 status;
1028
cristybb503372010-05-27 20:51:26 +00001029 MagickOffsetType
1030 progress;
1031
1032 ssize_t
1033 y;
1034
cristy3ed852e2009-09-05 21:47:34 +00001035 assert(image != (Image *) NULL);
1036 assert(image->signature == MagickSignature);
cristy4c08aed2011-07-01 19:47:50 +00001037 assert(high != (PixelInfo *) NULL);
1038 assert(low != (PixelInfo *) NULL);
cristy3ed852e2009-09-05 21:47:34 +00001039 if (image->debug != MagickFalse)
1040 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy574cc262011-08-05 01:23:58 +00001041 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001042 return(MagickFalse);
cristy8a46d822012-08-28 23:32:39 +00001043 if (image->alpha_trait != BlendPixelTrait)
cristy63240882011-08-05 19:05:27 +00001044 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
cristy3ed852e2009-09-05 21:47:34 +00001045 /*
1046 Make image color transparent.
1047 */
1048 status=MagickTrue;
1049 progress=0;
cristy46ff2672012-12-14 15:32:26 +00001050 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +00001051#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001052 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy5e6b2592012-12-19 14:08:11 +00001053 magick_threads(image,image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00001054#endif
cristybb503372010-05-27 20:51:26 +00001055 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001056 {
1057 MagickBooleanType
1058 match;
1059
cristy4c08aed2011-07-01 19:47:50 +00001060 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001061 pixel;
1062
cristy4c08aed2011-07-01 19:47:50 +00001063 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001064 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001065
cristy14973ba2011-08-27 23:48:07 +00001066 register ssize_t
1067 x;
1068
cristy3ed852e2009-09-05 21:47:34 +00001069 if (status == MagickFalse)
1070 continue;
1071 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy14973ba2011-08-27 23:48:07 +00001072 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001073 {
1074 status=MagickFalse;
1075 continue;
1076 }
cristy4c08aed2011-07-01 19:47:50 +00001077 GetPixelInfo(image,&pixel);
cristybb503372010-05-27 20:51:26 +00001078 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001079 {
cristy803640d2011-11-17 02:11:32 +00001080 GetPixelInfoPixel(image,q,&pixel);
cristy3ed852e2009-09-05 21:47:34 +00001081 match=((pixel.red >= low->red) && (pixel.red <= high->red) &&
1082 (pixel.green >= low->green) && (pixel.green <= high->green) &&
cristy14973ba2011-08-27 23:48:07 +00001083 (pixel.blue >= low->blue) && (pixel.blue <= high->blue)) ? MagickTrue :
1084 MagickFalse;
cristy3ed852e2009-09-05 21:47:34 +00001085 if (match != invert)
cristy4c08aed2011-07-01 19:47:50 +00001086 SetPixelAlpha(image,opacity,q);
cristyed231572011-07-14 02:18:59 +00001087 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001088 }
1089 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1090 status=MagickFalse;
1091 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1092 {
1093 MagickBooleanType
1094 proceed;
1095
cristyb5d5f722009-11-04 03:03:49 +00001096#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy95338b32012-01-22 03:34:36 +00001097 #pragma omp critical (MagickCore_TransparentPaintImageChroma)
cristy3ed852e2009-09-05 21:47:34 +00001098#endif
1099 proceed=SetImageProgress(image,TransparentPaintImageTag,progress++,
1100 image->rows);
1101 if (proceed == MagickFalse)
1102 status=MagickFalse;
1103 }
1104 }
1105 image_view=DestroyCacheView(image_view);
1106 return(status);
1107}