blob: 75dc1259197ed5823328ea82e77e2eb15877ed5b [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 %
cristyde984cd2013-12-01 14:49:27 +000016% Cristy %
cristy3ed852e2009-09-05 21:47:34 +000017% July 1998 %
18% %
19% %
cristyfe676ee2013-11-18 13:03:38 +000020% Copyright 1999-2014 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
cristyaaaff842013-06-30 02:48:11 +0000143 MemoryInfo
144 *segment_info;
145
cristy4c08aed2011-07-01 19:47:50 +0000146 PixelInfo
cristyfdcc0182011-12-26 19:33:56 +0000147 fill_color,
cristy3ed852e2009-09-05 21:47:34 +0000148 pixel;
149
cristy3ed852e2009-09-05 21:47:34 +0000150 register SegmentInfo
151 *s;
152
153 SegmentInfo
154 *segment_stack;
155
cristy9d314ff2011-03-09 01:30:28 +0000156 ssize_t
157 offset,
158 start,
159 x,
160 x1,
161 x2,
162 y;
163
cristy3ed852e2009-09-05 21:47:34 +0000164 /*
165 Check boundary conditions.
166 */
167 assert(image != (Image *) NULL);
168 assert(image->signature == MagickSignature);
169 if (image->debug != MagickFalse)
170 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
171 assert(draw_info != (DrawInfo *) NULL);
172 assert(draw_info->signature == MagickSignature);
cristybb503372010-05-27 20:51:26 +0000173 if ((x_offset < 0) || (x_offset >= (ssize_t) image->columns))
cristy3ed852e2009-09-05 21:47:34 +0000174 return(MagickFalse);
cristybb503372010-05-27 20:51:26 +0000175 if ((y_offset < 0) || (y_offset >= (ssize_t) image->rows))
cristy3ed852e2009-09-05 21:47:34 +0000176 return(MagickFalse);
cristy574cc262011-08-05 01:23:58 +0000177 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000178 return(MagickFalse);
cristya6400b12013-03-15 12:20:18 +0000179 if (IsGrayColorspace(image->colorspace) != MagickFalse)
cristy0c81d062013-04-21 15:22:02 +0000180 (void) SetImageColorspace(image,sRGBColorspace,exception);
cristy400275a2013-03-13 00:34:24 +0000181 if ((image->alpha_trait != BlendPixelTrait) &&
182 (draw_info->fill.alpha_trait == BlendPixelTrait))
cristy5b67d4e2012-02-07 19:43:53 +0000183 (void) SetImageAlpha(image,OpaqueAlpha,exception);
cristy3ed852e2009-09-05 21:47:34 +0000184 /*
185 Set floodfill state.
186 */
cristy95f562a2012-01-01 20:49:11 +0000187 floodplane_image=CloneImage(image,image->columns,image->rows,MagickTrue,
188 exception);
cristy3ed852e2009-09-05 21:47:34 +0000189 if (floodplane_image == (Image *) NULL)
190 return(MagickFalse);
cristy8a46d822012-08-28 23:32:39 +0000191 floodplane_image->alpha_trait=UndefinedPixelTrait;
cristy20990102012-05-16 18:10:49 +0000192 floodplane_image->colorspace=GRAYColorspace;
193 (void) QueryColorCompliance("#000",AllCompliance,
194 &floodplane_image->background_color,exception);
195 (void) SetImageBackgroundColor(floodplane_image,exception);
cristyaaaff842013-06-30 02:48:11 +0000196 segment_info=AcquireVirtualMemory(MaxStacksize,sizeof(*segment_stack));
197 if (segment_info == (MemoryInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000198 {
199 floodplane_image=DestroyImage(floodplane_image);
200 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
201 image->filename);
202 }
cristyaaaff842013-06-30 02:48:11 +0000203 segment_stack=(SegmentInfo *) GetVirtualMemoryBlob(segment_info);
cristy3ed852e2009-09-05 21:47:34 +0000204 /*
205 Push initial segment on stack.
206 */
cristy14973ba2011-08-27 23:48:07 +0000207 status=MagickTrue;
cristy3ed852e2009-09-05 21:47:34 +0000208 x=x_offset;
209 y=y_offset;
210 start=0;
211 s=segment_stack;
212 PushSegmentStack(y,x,x,1);
213 PushSegmentStack(y+1,x,x,-1);
cristy4c08aed2011-07-01 19:47:50 +0000214 GetPixelInfo(image,&pixel);
cristy46ff2672012-12-14 15:32:26 +0000215 image_view=AcquireVirtualCacheView(image,exception);
216 floodplane_view=AcquireAuthenticCacheView(floodplane_image,exception);
cristy3ed852e2009-09-05 21:47:34 +0000217 while (s > segment_stack)
218 {
cristy4c08aed2011-07-01 19:47:50 +0000219 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000220 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000221
cristy4c08aed2011-07-01 19:47:50 +0000222 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000223 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000224
cristy14973ba2011-08-27 23:48:07 +0000225 register ssize_t
226 x;
227
cristy3ed852e2009-09-05 21:47:34 +0000228 /*
229 Pop segment off stack.
230 */
231 s--;
cristybb503372010-05-27 20:51:26 +0000232 x1=(ssize_t) s->x1;
233 x2=(ssize_t) s->x2;
234 offset=(ssize_t) s->y2;
235 y=(ssize_t) s->y1+offset;
cristy3ed852e2009-09-05 21:47:34 +0000236 /*
237 Recolor neighboring pixels.
238 */
cristyb0d3bb92010-09-22 14:37:58 +0000239 p=GetCacheViewVirtualPixels(image_view,0,y,(size_t) (x1+1),1,exception);
240 q=GetCacheViewAuthenticPixels(floodplane_view,0,y,(size_t) (x1+1),1,
cristy3ed852e2009-09-05 21:47:34 +0000241 exception);
cristy4c08aed2011-07-01 19:47:50 +0000242 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000243 break;
cristyed231572011-07-14 02:18:59 +0000244 p+=x1*GetPixelChannels(image);
245 q+=x1*GetPixelChannels(floodplane_image);
cristy3ed852e2009-09-05 21:47:34 +0000246 for (x=x1; x >= 0; x--)
247 {
cristy95f562a2012-01-01 20:49:11 +0000248 if (GetPixelGray(floodplane_image,q) != 0)
cristy3ed852e2009-09-05 21:47:34 +0000249 break;
cristy803640d2011-11-17 02:11:32 +0000250 GetPixelInfoPixel(image,p,&pixel);
cristy4c08aed2011-07-01 19:47:50 +0000251 if (IsFuzzyEquivalencePixelInfo(&pixel,target) == invert)
cristy3ed852e2009-09-05 21:47:34 +0000252 break;
cristy95f562a2012-01-01 20:49:11 +0000253 SetPixelGray(floodplane_image,QuantumRange,q);
cristyed231572011-07-14 02:18:59 +0000254 p-=GetPixelChannels(image);
255 q-=GetPixelChannels(floodplane_image);
cristy3ed852e2009-09-05 21:47:34 +0000256 }
cristyb0d3bb92010-09-22 14:37:58 +0000257 if (SyncCacheViewAuthenticPixels(floodplane_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000258 break;
259 skip=x >= x1 ? MagickTrue : MagickFalse;
260 if (skip == MagickFalse)
261 {
262 start=x+1;
263 if (start < x1)
264 PushSegmentStack(y,start,x1-1,-offset);
265 x=x1+1;
266 }
267 do
268 {
269 if (skip == MagickFalse)
270 {
cristybb503372010-05-27 20:51:26 +0000271 if (x < (ssize_t) image->columns)
cristy3ed852e2009-09-05 21:47:34 +0000272 {
cristyb0d3bb92010-09-22 14:37:58 +0000273 p=GetCacheViewVirtualPixels(image_view,x,y,image->columns-x,1,
cristy3ed852e2009-09-05 21:47:34 +0000274 exception);
cristyfdcc0182011-12-26 19:33:56 +0000275 q=GetCacheViewAuthenticPixels(floodplane_view,x,y,image->columns-
276 x,1,exception);
cristy636dcb52011-08-26 13:23:49 +0000277 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000278 break;
cristybb503372010-05-27 20:51:26 +0000279 for ( ; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000280 {
cristy95f562a2012-01-01 20:49:11 +0000281 if (GetPixelGray(floodplane_image,q) != 0)
cristy3ed852e2009-09-05 21:47:34 +0000282 break;
cristy803640d2011-11-17 02:11:32 +0000283 GetPixelInfoPixel(image,p,&pixel);
cristy4c08aed2011-07-01 19:47:50 +0000284 if (IsFuzzyEquivalencePixelInfo(&pixel,target) == invert)
cristy3ed852e2009-09-05 21:47:34 +0000285 break;
cristy95f562a2012-01-01 20:49:11 +0000286 SetPixelGray(floodplane_image,QuantumRange,q);
cristyed231572011-07-14 02:18:59 +0000287 p+=GetPixelChannels(image);
288 q+=GetPixelChannels(floodplane_image);
cristy3ed852e2009-09-05 21:47:34 +0000289 }
cristy14973ba2011-08-27 23:48:07 +0000290 status=SyncCacheViewAuthenticPixels(floodplane_view,exception);
291 if (status == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000292 break;
293 }
294 PushSegmentStack(y,start,x-1,offset);
295 if (x > (x2+1))
296 PushSegmentStack(y,x2+1,x-1,-offset);
297 }
298 skip=MagickFalse;
299 x++;
300 if (x <= x2)
301 {
cristyb0d3bb92010-09-22 14:37:58 +0000302 p=GetCacheViewVirtualPixels(image_view,x,y,(size_t) (x2-x+1),1,
303 exception);
304 q=GetCacheViewAuthenticPixels(floodplane_view,x,y,(size_t) (x2-x+1),1,
cristy3ed852e2009-09-05 21:47:34 +0000305 exception);
cristy636dcb52011-08-26 13:23:49 +0000306 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000307 break;
cristy3ed852e2009-09-05 21:47:34 +0000308 for ( ; x <= x2; x++)
309 {
cristy95f562a2012-01-01 20:49:11 +0000310 if (GetPixelGray(floodplane_image,q) != 0)
cristy3ed852e2009-09-05 21:47:34 +0000311 break;
cristy803640d2011-11-17 02:11:32 +0000312 GetPixelInfoPixel(image,p,&pixel);
cristy4c08aed2011-07-01 19:47:50 +0000313 if (IsFuzzyEquivalencePixelInfo(&pixel,target) != invert)
cristy3ed852e2009-09-05 21:47:34 +0000314 break;
cristyed231572011-07-14 02:18:59 +0000315 p+=GetPixelChannels(image);
316 q+=GetPixelChannels(floodplane_image);
cristy3ed852e2009-09-05 21:47:34 +0000317 }
318 }
319 start=x;
320 } while (x <= x2);
321 }
cristybb503372010-05-27 20:51:26 +0000322 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000323 {
cristy4c08aed2011-07-01 19:47:50 +0000324 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000325 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000326
cristy4c08aed2011-07-01 19:47:50 +0000327 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000328 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000329
cristy14973ba2011-08-27 23:48:07 +0000330 register ssize_t
331 x;
332
cristy3ed852e2009-09-05 21:47:34 +0000333 /*
334 Tile fill color onto floodplane.
335 */
cristy95f562a2012-01-01 20:49:11 +0000336 p=GetCacheViewVirtualPixels(floodplane_view,0,y,image->columns,1,exception);
cristyb0d3bb92010-09-22 14:37:58 +0000337 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000338 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000339 break;
cristybb503372010-05-27 20:51:26 +0000340 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000341 {
cristy95f562a2012-01-01 20:49:11 +0000342 if (GetPixelGray(floodplane_image,p) != 0)
cristy3ed852e2009-09-05 21:47:34 +0000343 {
cristy2ed42f62011-10-02 19:49:57 +0000344 (void) GetFillColor(draw_info,x,y,&fill_color,exception);
cristy8a20fa02011-12-27 15:54:31 +0000345 SetPixelInfoPixel(image,&fill_color,q);
cristy3ed852e2009-09-05 21:47:34 +0000346 }
cristyed231572011-07-14 02:18:59 +0000347 p+=GetPixelChannels(floodplane_image);
348 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000349 }
cristyb0d3bb92010-09-22 14:37:58 +0000350 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000351 break;
352 }
cristyb0d3bb92010-09-22 14:37:58 +0000353 floodplane_view=DestroyCacheView(floodplane_view);
354 image_view=DestroyCacheView(image_view);
cristyaaaff842013-06-30 02:48:11 +0000355 segment_info=RelinquishVirtualMemory(segment_info);
cristy3ed852e2009-09-05 21:47:34 +0000356 floodplane_image=DestroyImage(floodplane_image);
cristybb503372010-05-27 20:51:26 +0000357 return(y == (ssize_t) image->rows ? MagickTrue : MagickFalse);
cristy3ed852e2009-09-05 21:47:34 +0000358}
359
360/*
361%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
362% %
363% %
364% %
365+ G r a d i e n t I m a g e %
366% %
367% %
368% %
369%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
370%
cristycee97112010-05-28 00:44:52 +0000371% GradientImage() applies a continuously smooth color transitions along a
cristy3ed852e2009-09-05 21:47:34 +0000372% vector from one color to another.
373%
374% Note, the interface of this method will change in the future to support
375% more than one transistion.
376%
377% The format of the GradientImage method is:
378%
379% MagickBooleanType GradientImage(Image *image,const GradientType type,
cristy101ab702011-10-13 13:06:32 +0000380% const SpreadMethod method,const PixelInfo *start_color,
381% const PixelInfo *stop_color,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000382%
383% A description of each parameter follows:
384%
385% o image: the image.
386%
387% o type: the gradient type: linear or radial.
388%
389% o spread: the gradient spread meathod: pad, reflect, or repeat.
390%
391% o start_color: the start color.
392%
393% o stop_color: the stop color.
394%
cristy189e84c2011-08-27 18:08:53 +0000395% o exception: return any errors or warnings in this structure.
396%
cristy3ed852e2009-09-05 21:47:34 +0000397*/
cristy117ff172010-08-15 21:35:32 +0000398
399static inline double MagickMax(const double x,const double y)
400{
401 return(x > y ? x : y);
402}
403
cristy3ed852e2009-09-05 21:47:34 +0000404MagickExport MagickBooleanType GradientImage(Image *image,
405 const GradientType type,const SpreadMethod method,
cristy101ab702011-10-13 13:06:32 +0000406 const PixelInfo *start_color,const PixelInfo *stop_color,
cristy189e84c2011-08-27 18:08:53 +0000407 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000408{
409 DrawInfo
410 *draw_info;
411
412 GradientInfo
413 *gradient;
414
415 MagickBooleanType
416 status;
417
cristybb503372010-05-27 20:51:26 +0000418 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000419 i;
420
421 /*
422 Set gradient start-stop end points.
423 */
424 assert(image != (const Image *) NULL);
425 assert(image->signature == MagickSignature);
426 if (image->debug != MagickFalse)
427 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy101ab702011-10-13 13:06:32 +0000428 assert(start_color != (const PixelInfo *) NULL);
429 assert(stop_color != (const PixelInfo *) NULL);
cristy3ed852e2009-09-05 21:47:34 +0000430 draw_info=AcquireDrawInfo();
431 gradient=(&draw_info->gradient);
432 gradient->type=type;
433 gradient->bounding_box.width=image->columns;
434 gradient->bounding_box.height=image->rows;
435 gradient->gradient_vector.x2=(double) image->columns-1.0;
436 gradient->gradient_vector.y2=(double) image->rows-1.0;
437 if ((type == LinearGradient) && (gradient->gradient_vector.y2 != 0.0))
438 gradient->gradient_vector.x2=0.0;
439 gradient->center.x=(double) gradient->gradient_vector.x2/2.0;
440 gradient->center.y=(double) gradient->gradient_vector.y2/2.0;
441 gradient->radius=MagickMax(gradient->center.x,gradient->center.y);
442 gradient->spread=method;
443 /*
444 Define the gradient to fill between the stops.
445 */
446 gradient->number_stops=2;
447 gradient->stops=(StopInfo *) AcquireQuantumMemory(gradient->number_stops,
448 sizeof(*gradient->stops));
449 if (gradient->stops == (StopInfo *) NULL)
450 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
451 image->filename);
452 (void) ResetMagickMemory(gradient->stops,0,gradient->number_stops*
453 sizeof(*gradient->stops));
cristybb503372010-05-27 20:51:26 +0000454 for (i=0; i < (ssize_t) gradient->number_stops; i++)
cristy4c08aed2011-07-01 19:47:50 +0000455 GetPixelInfo(image,&gradient->stops[i].color);
cristy9d8c8ce2011-10-25 16:13:52 +0000456 gradient->stops[0].color=(*start_color);
cristy3ed852e2009-09-05 21:47:34 +0000457 gradient->stops[0].offset=0.0;
cristy9d8c8ce2011-10-25 16:13:52 +0000458 gradient->stops[1].color=(*stop_color);
cristy3ed852e2009-09-05 21:47:34 +0000459 gradient->stops[1].offset=1.0;
460 /*
461 Draw a gradient on the image.
462 */
cristybafdbba2012-06-20 11:50:57 +0000463 (void) SetImageColorspace(image,start_color->colorspace,exception);
cristy947cb4c2011-10-20 18:41:46 +0000464 status=DrawGradientImage(image,draw_info,exception);
cristy3ed852e2009-09-05 21:47:34 +0000465 draw_info=DestroyDrawInfo(draw_info);
cristy3ed852e2009-09-05 21:47:34 +0000466 return(status);
467}
468
469/*
470%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
471% %
472% %
473% %
474% O i l P a i n t I m a g e %
475% %
476% %
477% %
478%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
479%
480% OilPaintImage() applies a special effect filter that simulates an oil
481% painting. Each pixel is replaced by the most frequent color occurring
482% in a circular region defined by radius.
483%
484% The format of the OilPaintImage method is:
485%
486% Image *OilPaintImage(const Image *image,const double radius,
cristy14973ba2011-08-27 23:48:07 +0000487% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000488%
489% A description of each parameter follows:
490%
491% o image: the image.
492%
493% o radius: the radius of the circular neighborhood.
494%
cristy14973ba2011-08-27 23:48:07 +0000495% o sigma: the standard deviation of the Gaussian, in pixels.
496%
cristy3ed852e2009-09-05 21:47:34 +0000497% o exception: return any errors or warnings in this structure.
498%
499*/
500
cristybb503372010-05-27 20:51:26 +0000501static size_t **DestroyHistogramThreadSet(size_t **histogram)
cristy3ed852e2009-09-05 21:47:34 +0000502{
cristybb503372010-05-27 20:51:26 +0000503 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000504 i;
505
cristybb503372010-05-27 20:51:26 +0000506 assert(histogram != (size_t **) NULL);
cristyac245f82012-05-05 17:13:57 +0000507 for (i=0; i < (ssize_t) GetMagickResourceLimit(ThreadResource); i++)
cristybb503372010-05-27 20:51:26 +0000508 if (histogram[i] != (size_t *) NULL)
509 histogram[i]=(size_t *) RelinquishMagickMemory(histogram[i]);
cristyb41ee102010-10-04 16:46:15 +0000510 histogram=(size_t **) RelinquishMagickMemory(histogram);
cristy3ed852e2009-09-05 21:47:34 +0000511 return(histogram);
512}
513
cristybb503372010-05-27 20:51:26 +0000514static size_t **AcquireHistogramThreadSet(const size_t count)
cristy3ed852e2009-09-05 21:47:34 +0000515{
cristybb503372010-05-27 20:51:26 +0000516 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000517 i;
518
cristybb503372010-05-27 20:51:26 +0000519 size_t
cristy3ed852e2009-09-05 21:47:34 +0000520 **histogram,
521 number_threads;
522
cristy9357bdd2012-07-30 12:28:34 +0000523 number_threads=(size_t) GetMagickResourceLimit(ThreadResource);
cristy14973ba2011-08-27 23:48:07 +0000524 histogram=(size_t **) AcquireQuantumMemory(number_threads,sizeof(*histogram));
cristybb503372010-05-27 20:51:26 +0000525 if (histogram == (size_t **) NULL)
526 return((size_t **) NULL);
cristy3ed852e2009-09-05 21:47:34 +0000527 (void) ResetMagickMemory(histogram,0,number_threads*sizeof(*histogram));
cristybb503372010-05-27 20:51:26 +0000528 for (i=0; i < (ssize_t) number_threads; i++)
cristy3ed852e2009-09-05 21:47:34 +0000529 {
cristy14973ba2011-08-27 23:48:07 +0000530 histogram[i]=(size_t *) AcquireQuantumMemory(count,sizeof(**histogram));
cristybb503372010-05-27 20:51:26 +0000531 if (histogram[i] == (size_t *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000532 return(DestroyHistogramThreadSet(histogram));
533 }
534 return(histogram);
535}
536
537MagickExport Image *OilPaintImage(const Image *image,const double radius,
cristy14973ba2011-08-27 23:48:07 +0000538 const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000539{
540#define NumberPaintBins 256
541#define OilPaintImageTag "OilPaint/Image"
542
cristyfa112112010-01-04 17:48:07 +0000543 CacheView
544 *image_view,
545 *paint_view;
546
cristy3ed852e2009-09-05 21:47:34 +0000547 Image
cristy404d0da2012-12-20 13:04:30 +0000548 *linear_image,
cristy3ed852e2009-09-05 21:47:34 +0000549 *paint_image;
550
cristy3ed852e2009-09-05 21:47:34 +0000551 MagickBooleanType
552 status;
553
cristybb503372010-05-27 20:51:26 +0000554 MagickOffsetType
555 progress;
556
557 size_t
cristy14973ba2011-08-27 23:48:07 +0000558 **histograms,
cristy3ed852e2009-09-05 21:47:34 +0000559 width;
560
cristybb503372010-05-27 20:51:26 +0000561 ssize_t
cristyf8561542012-01-24 00:26:46 +0000562 center,
cristybb503372010-05-27 20:51:26 +0000563 y;
564
cristy3ed852e2009-09-05 21:47:34 +0000565 /*
566 Initialize painted image attributes.
567 */
568 assert(image != (const Image *) NULL);
569 assert(image->signature == MagickSignature);
570 if (image->debug != MagickFalse)
571 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
572 assert(exception != (ExceptionInfo *) NULL);
573 assert(exception->signature == MagickSignature);
cristy14973ba2011-08-27 23:48:07 +0000574 width=GetOptimalKernelWidth2D(radius,sigma);
cristy404d0da2012-12-20 13:04:30 +0000575 linear_image=CloneImage(image,0,0,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +0000576 paint_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
cristy404d0da2012-12-20 13:04:30 +0000577 if ((linear_image == (Image *) NULL) || (paint_image == (Image *) NULL))
578 {
579 if (linear_image != (Image *) NULL)
580 linear_image=DestroyImage(linear_image);
581 if (paint_image != (Image *) NULL)
582 linear_image=DestroyImage(paint_image);
583 return((Image *) NULL);
584 }
cristy574cc262011-08-05 01:23:58 +0000585 if (SetImageStorageClass(paint_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000586 {
cristy404d0da2012-12-20 13:04:30 +0000587 linear_image=DestroyImage(linear_image);
cristy3ed852e2009-09-05 21:47:34 +0000588 paint_image=DestroyImage(paint_image);
589 return((Image *) NULL);
590 }
591 histograms=AcquireHistogramThreadSet(NumberPaintBins);
cristybb503372010-05-27 20:51:26 +0000592 if (histograms == (size_t **) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000593 {
cristy404d0da2012-12-20 13:04:30 +0000594 linear_image=DestroyImage(linear_image);
cristy3ed852e2009-09-05 21:47:34 +0000595 paint_image=DestroyImage(paint_image);
596 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
597 }
598 /*
599 Oil paint image.
600 */
601 status=MagickTrue;
602 progress=0;
cristy404d0da2012-12-20 13:04:30 +0000603 center=(ssize_t) GetPixelChannels(linear_image)*(linear_image->columns+width)*
604 (width/2L)+GetPixelChannels(linear_image)*(width/2L);
605 image_view=AcquireVirtualCacheView(linear_image,exception);
cristy46ff2672012-12-14 15:32:26 +0000606 paint_view=AcquireAuthenticCacheView(paint_image,exception);
cristyb5d5f722009-11-04 03:03:49 +0000607#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000608 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy404d0da2012-12-20 13:04:30 +0000609 magick_threads(linear_image,paint_image,linear_image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +0000610#endif
cristy404d0da2012-12-20 13:04:30 +0000611 for (y=0; y < (ssize_t) linear_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000612 {
cristy4c08aed2011-07-01 19:47:50 +0000613 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000614 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000615
cristy4c08aed2011-07-01 19:47:50 +0000616 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000617 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000618
cristybb503372010-05-27 20:51:26 +0000619 register size_t
cristy3ed852e2009-09-05 21:47:34 +0000620 *histogram;
621
cristy14973ba2011-08-27 23:48:07 +0000622 register ssize_t
623 x;
624
cristy3ed852e2009-09-05 21:47:34 +0000625 if (status == MagickFalse)
626 continue;
cristyfe4ba002011-02-28 14:54:12 +0000627 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
cristy404d0da2012-12-20 13:04:30 +0000628 (width/2L),linear_image->columns+width,width,exception);
cristy3ed852e2009-09-05 21:47:34 +0000629 q=QueueCacheViewAuthenticPixels(paint_view,0,y,paint_image->columns,1,
630 exception);
cristy4c08aed2011-07-01 19:47:50 +0000631 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000632 {
633 status=MagickFalse;
634 continue;
635 }
cristy3ed852e2009-09-05 21:47:34 +0000636 histogram=histograms[GetOpenMPThreadId()];
cristy404d0da2012-12-20 13:04:30 +0000637 for (x=0; x < (ssize_t) linear_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000638 {
cristybb503372010-05-27 20:51:26 +0000639 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000640 i,
641 u;
642
cristybb503372010-05-27 20:51:26 +0000643 size_t
cristy3ed852e2009-09-05 21:47:34 +0000644 count;
645
cristy9d314ff2011-03-09 01:30:28 +0000646 ssize_t
647 j,
648 k,
cristyf8561542012-01-24 00:26:46 +0000649 n,
cristy9d314ff2011-03-09 01:30:28 +0000650 v;
651
cristy3ed852e2009-09-05 21:47:34 +0000652 /*
653 Assign most frequent color.
654 */
cristyf8561542012-01-24 00:26:46 +0000655 k=0;
cristy3ed852e2009-09-05 21:47:34 +0000656 j=0;
657 count=0;
cristyf8561542012-01-24 00:26:46 +0000658 (void) ResetMagickMemory(histogram,0,NumberPaintBins* sizeof(*histogram));
cristybb503372010-05-27 20:51:26 +0000659 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +0000660 {
cristybb503372010-05-27 20:51:26 +0000661 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +0000662 {
cristy404d0da2012-12-20 13:04:30 +0000663 n=(ssize_t) ScaleQuantumToChar(ClampToQuantum(GetPixelIntensity(
664 linear_image,p+GetPixelChannels(linear_image)*(u+k))));
cristyf8561542012-01-24 00:26:46 +0000665 histogram[n]++;
666 if (histogram[n] > count)
cristy3ed852e2009-09-05 21:47:34 +0000667 {
cristyf8561542012-01-24 00:26:46 +0000668 j=k+u;
669 count=histogram[n];
cristy3ed852e2009-09-05 21:47:34 +0000670 }
671 }
cristy034ade92013-03-07 12:18:19 +0000672 k+=(ssize_t) (linear_image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +0000673 }
cristy404d0da2012-12-20 13:04:30 +0000674 for (i=0; i < (ssize_t) GetPixelChannels(linear_image); i++)
cristyf8561542012-01-24 00:26:46 +0000675 {
cristy5a23c552013-02-13 14:34:28 +0000676 PixelChannel channel=GetPixelChannelChannel(linear_image,i);
677 PixelTrait traits=GetPixelChannelTraits(linear_image,channel);
678 PixelTrait paint_traits=GetPixelChannelTraits(paint_image,channel);
cristyf8561542012-01-24 00:26:46 +0000679 if ((traits == UndefinedPixelTrait) ||
680 (paint_traits == UndefinedPixelTrait))
681 continue;
cristy1eced092012-08-10 23:10:56 +0000682 if (((paint_traits & CopyPixelTrait) != 0) ||
cristy883fde12013-04-08 00:50:13 +0000683 (GetPixelReadMask(linear_image,p) == 0))
cristyf8561542012-01-24 00:26:46 +0000684 {
685 SetPixelChannel(paint_image,channel,p[center+i],q);
686 continue;
687 }
cristy404d0da2012-12-20 13:04:30 +0000688 SetPixelChannel(paint_image,channel,p[j*GetPixelChannels(linear_image)+
689 i],q);
cristyf8561542012-01-24 00:26:46 +0000690 }
cristy404d0da2012-12-20 13:04:30 +0000691 p+=GetPixelChannels(linear_image);
cristyed231572011-07-14 02:18:59 +0000692 q+=GetPixelChannels(paint_image);
cristy3ed852e2009-09-05 21:47:34 +0000693 }
694 if (SyncCacheViewAuthenticPixels(paint_view,exception) == MagickFalse)
695 status=MagickFalse;
cristy404d0da2012-12-20 13:04:30 +0000696 if (linear_image->progress_monitor != (MagickProgressMonitor) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000697 {
698 MagickBooleanType
699 proceed;
700
cristyb5d5f722009-11-04 03:03:49 +0000701#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy95338b32012-01-22 03:34:36 +0000702 #pragma omp critical (MagickCore_OilPaintImage)
cristy3ed852e2009-09-05 21:47:34 +0000703#endif
cristy404d0da2012-12-20 13:04:30 +0000704 proceed=SetImageProgress(linear_image,OilPaintImageTag,progress++,
705 linear_image->rows);
cristy3ed852e2009-09-05 21:47:34 +0000706 if (proceed == MagickFalse)
707 status=MagickFalse;
708 }
709 }
710 paint_view=DestroyCacheView(paint_view);
711 image_view=DestroyCacheView(image_view);
712 histograms=DestroyHistogramThreadSet(histograms);
cristy404d0da2012-12-20 13:04:30 +0000713 linear_image=DestroyImage(linear_image);
cristy3ed852e2009-09-05 21:47:34 +0000714 if (status == MagickFalse)
715 paint_image=DestroyImage(paint_image);
716 return(paint_image);
717}
718
719/*
720%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
721% %
722% %
723% %
724% O p a q u e P a i n t I m a g e %
725% %
726% %
727% %
728%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
729%
730% OpaquePaintImage() changes any pixel that matches color with the color
731% defined by fill.
732%
cristy14973ba2011-08-27 23:48:07 +0000733% By default color must match a particular pixel color exactly. However, in
734% many cases two colors may differ by a small amount. Fuzz defines how much
735% tolerance is acceptable to consider two colors as the same. For example,
736% set fuzz to 10 and the color red at intensities of 100 and 102 respectively
737% are now interpreted as the same color.
cristy3ed852e2009-09-05 21:47:34 +0000738%
739% The format of the OpaquePaintImage method is:
740%
741% MagickBooleanType OpaquePaintImage(Image *image,
cristy101ab702011-10-13 13:06:32 +0000742% const PixelInfo *target,const PixelInfo *fill,
cristy189e84c2011-08-27 18:08:53 +0000743% const MagickBooleanType invert,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000744%
745% A description of each parameter follows:
746%
747% o image: the image.
748%
cristy3ed852e2009-09-05 21:47:34 +0000749% o target: the RGB value of the target color.
750%
751% o fill: the replacement color.
752%
753% o invert: paint any pixel that does not match the target color.
754%
cristy189e84c2011-08-27 18:08:53 +0000755% o exception: return any errors or warnings in this structure.
756%
cristy3ed852e2009-09-05 21:47:34 +0000757*/
cristy3ed852e2009-09-05 21:47:34 +0000758MagickExport MagickBooleanType OpaquePaintImage(Image *image,
cristy189e84c2011-08-27 18:08:53 +0000759 const PixelInfo *target,const PixelInfo *fill,const MagickBooleanType invert,
760 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000761{
762#define OpaquePaintImageTag "Opaque/Image"
763
cristyc4c8d132010-01-07 01:58:38 +0000764 CacheView
765 *image_view;
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);
cristy574cc262011-08-05 01:23:58 +0000785 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000786 return(MagickFalse);
cristya6400b12013-03-15 12:20:18 +0000787 if ((IsGrayColorspace(image->colorspace) != MagickFalse) &&
788 (IsPixelInfoGray(fill) == MagickFalse))
cristy0c81d062013-04-21 15:22:02 +0000789 (void) SetImageColorspace(image,sRGBColorspace,exception);
cristy400275a2013-03-13 00:34:24 +0000790 if ((fill->alpha_trait == BlendPixelTrait) &&
791 (image->alpha_trait != BlendPixelTrait))
cristy09250192012-02-07 18:53:31 +0000792 (void) SetImageAlpha(image,OpaqueAlpha,exception);
cristy3ed852e2009-09-05 21:47:34 +0000793 /*
794 Make image color opaque.
795 */
796 status=MagickTrue;
797 progress=0;
cristy4c08aed2011-07-01 19:47:50 +0000798 GetPixelInfo(image,&zero);
cristy46ff2672012-12-14 15:32:26 +0000799 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +0000800#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000801 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy5e6b2592012-12-19 14:08:11 +0000802 magick_threads(image,image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +0000803#endif
cristybb503372010-05-27 20:51:26 +0000804 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000805 {
cristy4c08aed2011-07-01 19:47:50 +0000806 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000807 pixel;
808
cristy4c08aed2011-07-01 19:47:50 +0000809 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000810 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000811
cristy14973ba2011-08-27 23:48:07 +0000812 register ssize_t
813 x;
814
cristy3ed852e2009-09-05 21:47:34 +0000815 if (status == MagickFalse)
816 continue;
817 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy14973ba2011-08-27 23:48:07 +0000818 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000819 {
820 status=MagickFalse;
821 continue;
822 }
cristy3ed852e2009-09-05 21:47:34 +0000823 pixel=zero;
cristybb503372010-05-27 20:51:26 +0000824 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000825 {
cristy803640d2011-11-17 02:11:32 +0000826 GetPixelInfoPixel(image,q,&pixel);
cristy4c08aed2011-07-01 19:47:50 +0000827 if (IsFuzzyEquivalencePixelInfo(&pixel,target) != invert)
cristy95f562a2012-01-01 20:49:11 +0000828 SetPixelInfoPixel(image,fill,q);
cristyed231572011-07-14 02:18:59 +0000829 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000830 }
831 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
832 status=MagickFalse;
833 if (image->progress_monitor != (MagickProgressMonitor) NULL)
834 {
835 MagickBooleanType
836 proceed;
837
cristyb5d5f722009-11-04 03:03:49 +0000838#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy95338b32012-01-22 03:34:36 +0000839 #pragma omp critical (MagickCore_OpaquePaintImage)
cristy3ed852e2009-09-05 21:47:34 +0000840#endif
841 proceed=SetImageProgress(image,OpaquePaintImageTag,progress++,
842 image->rows);
843 if (proceed == MagickFalse)
844 status=MagickFalse;
845 }
846 }
847 image_view=DestroyCacheView(image_view);
848 return(status);
849}
850
851/*
852%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
853% %
854% %
855% %
856% T r a n s p a r e n t P a i n t I m a g e %
857% %
858% %
859% %
860%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
861%
862% TransparentPaintImage() changes the opacity value associated with any pixel
863% that matches color to the value defined by opacity.
864%
cristy14973ba2011-08-27 23:48:07 +0000865% By default color must match a particular pixel color exactly. However, in
866% many cases two colors may differ by a small amount. Fuzz defines how much
867% tolerance is acceptable to consider two colors as the same. For example,
868% set fuzz to 10 and the color red at intensities of 100 and 102 respectively
869% are now interpreted as the same color.
cristy3ed852e2009-09-05 21:47:34 +0000870%
871% The format of the TransparentPaintImage method is:
872%
873% MagickBooleanType TransparentPaintImage(Image *image,
cristy4c08aed2011-07-01 19:47:50 +0000874% const PixelInfo *target,const Quantum opacity,
cristy189e84c2011-08-27 18:08:53 +0000875% const MagickBooleanType invert,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000876%
877% A description of each parameter follows:
878%
879% o image: the image.
880%
881% o target: the target color.
882%
883% o opacity: the replacement opacity value.
884%
885% o invert: paint any pixel that does not match the target color.
886%
cristy189e84c2011-08-27 18:08:53 +0000887% o exception: return any errors or warnings in this structure.
888%
cristy3ed852e2009-09-05 21:47:34 +0000889*/
890MagickExport MagickBooleanType TransparentPaintImage(Image *image,
cristy14973ba2011-08-27 23:48:07 +0000891 const PixelInfo *target,const Quantum opacity,const MagickBooleanType invert,
892 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000893{
894#define TransparentPaintImageTag "Transparent/Image"
895
cristyc4c8d132010-01-07 01:58:38 +0000896 CacheView
897 *image_view;
898
cristy3ed852e2009-09-05 21:47:34 +0000899 MagickBooleanType
900 status;
901
cristybb503372010-05-27 20:51:26 +0000902 MagickOffsetType
903 progress;
904
cristy4c08aed2011-07-01 19:47:50 +0000905 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000906 zero;
907
cristybb503372010-05-27 20:51:26 +0000908 ssize_t
909 y;
910
cristy3ed852e2009-09-05 21:47:34 +0000911 assert(image != (Image *) NULL);
912 assert(image->signature == MagickSignature);
cristy4c08aed2011-07-01 19:47:50 +0000913 assert(target != (PixelInfo *) NULL);
cristy3ed852e2009-09-05 21:47:34 +0000914 if (image->debug != MagickFalse)
915 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy574cc262011-08-05 01:23:58 +0000916 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000917 return(MagickFalse);
cristy8a46d822012-08-28 23:32:39 +0000918 if (image->alpha_trait != BlendPixelTrait)
cristy63240882011-08-05 19:05:27 +0000919 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
cristy3ed852e2009-09-05 21:47:34 +0000920 /*
921 Make image color transparent.
922 */
923 status=MagickTrue;
924 progress=0;
cristy4c08aed2011-07-01 19:47:50 +0000925 GetPixelInfo(image,&zero);
cristy46ff2672012-12-14 15:32:26 +0000926 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +0000927#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000928 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy5e6b2592012-12-19 14:08:11 +0000929 magick_threads(image,image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +0000930#endif
cristybb503372010-05-27 20:51:26 +0000931 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000932 {
cristy4c08aed2011-07-01 19:47:50 +0000933 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000934 pixel;
935
cristybb503372010-05-27 20:51:26 +0000936 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000937 x;
938
cristy4c08aed2011-07-01 19:47:50 +0000939 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000940 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000941
942 if (status == MagickFalse)
943 continue;
944 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy14973ba2011-08-27 23:48:07 +0000945 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000946 {
947 status=MagickFalse;
948 continue;
949 }
cristy3ed852e2009-09-05 21:47:34 +0000950 pixel=zero;
cristybb503372010-05-27 20:51:26 +0000951 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000952 {
cristy803640d2011-11-17 02:11:32 +0000953 GetPixelInfoPixel(image,q,&pixel);
cristy4c08aed2011-07-01 19:47:50 +0000954 if (IsFuzzyEquivalencePixelInfo(&pixel,target) != invert)
955 SetPixelAlpha(image,opacity,q);
cristyed231572011-07-14 02:18:59 +0000956 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000957 }
958 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
959 status=MagickFalse;
960 if (image->progress_monitor != (MagickProgressMonitor) NULL)
961 {
962 MagickBooleanType
963 proceed;
964
cristyb5d5f722009-11-04 03:03:49 +0000965#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy95338b32012-01-22 03:34:36 +0000966 #pragma omp critical (MagickCore_TransparentPaintImage)
cristy3ed852e2009-09-05 21:47:34 +0000967#endif
968 proceed=SetImageProgress(image,TransparentPaintImageTag,progress++,
969 image->rows);
970 if (proceed == MagickFalse)
971 status=MagickFalse;
972 }
973 }
974 image_view=DestroyCacheView(image_view);
975 return(status);
976}
977
978/*
979%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
980% %
981% %
982% %
983% 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 %
984% %
985% %
986% %
987%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
988%
989% TransparentPaintImageChroma() changes the opacity value associated with any
990% pixel that matches color to the value defined by opacity.
991%
cristy14973ba2011-08-27 23:48:07 +0000992% As there is one fuzz value for the all the channels, TransparentPaintImage()
993% is not suitable for the operations like chroma, where the tolerance for
994% similarity of two color component (RGB) can be different. Thus we define
995% this method to take two target pixels (one low and one high) and all the
996% pixels of an image which are lying between these two pixels are made
997% transparent.
cristy3ed852e2009-09-05 21:47:34 +0000998%
cristy14973ba2011-08-27 23:48:07 +0000999% The format of the TransparentPaintImageChroma method is:
cristy3ed852e2009-09-05 21:47:34 +00001000%
cristy14973ba2011-08-27 23:48:07 +00001001% MagickBooleanType TransparentPaintImageChroma(Image *image,
1002% const PixelInfo *low,const PixelInfo *high,const Quantum opacity,
1003% const MagickBooleanType invert,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001004%
1005% A description of each parameter follows:
1006%
1007% o image: the image.
1008%
1009% o low: the low target color.
1010%
1011% o high: the high target color.
1012%
1013% o opacity: the replacement opacity value.
1014%
1015% o invert: paint any pixel that does not match the target color.
1016%
cristy189e84c2011-08-27 18:08:53 +00001017% o exception: return any errors or warnings in this structure.
1018%
cristy3ed852e2009-09-05 21:47:34 +00001019*/
1020MagickExport MagickBooleanType TransparentPaintImageChroma(Image *image,
cristy189e84c2011-08-27 18:08:53 +00001021 const PixelInfo *low,const PixelInfo *high,const Quantum opacity,
1022 const MagickBooleanType invert,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001023{
1024#define TransparentPaintImageTag "Transparent/Image"
1025
cristyc4c8d132010-01-07 01:58:38 +00001026 CacheView
1027 *image_view;
1028
cristy3ed852e2009-09-05 21:47:34 +00001029 MagickBooleanType
1030 status;
1031
cristybb503372010-05-27 20:51:26 +00001032 MagickOffsetType
1033 progress;
1034
1035 ssize_t
1036 y;
1037
cristy3ed852e2009-09-05 21:47:34 +00001038 assert(image != (Image *) NULL);
1039 assert(image->signature == MagickSignature);
cristy4c08aed2011-07-01 19:47:50 +00001040 assert(high != (PixelInfo *) NULL);
1041 assert(low != (PixelInfo *) NULL);
cristy3ed852e2009-09-05 21:47:34 +00001042 if (image->debug != MagickFalse)
1043 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy574cc262011-08-05 01:23:58 +00001044 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001045 return(MagickFalse);
cristy8a46d822012-08-28 23:32:39 +00001046 if (image->alpha_trait != BlendPixelTrait)
cristy63240882011-08-05 19:05:27 +00001047 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
cristy3ed852e2009-09-05 21:47:34 +00001048 /*
1049 Make image color transparent.
1050 */
1051 status=MagickTrue;
1052 progress=0;
cristy46ff2672012-12-14 15:32:26 +00001053 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +00001054#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001055 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy5e6b2592012-12-19 14:08:11 +00001056 magick_threads(image,image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00001057#endif
cristybb503372010-05-27 20:51:26 +00001058 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001059 {
1060 MagickBooleanType
1061 match;
1062
cristy4c08aed2011-07-01 19:47:50 +00001063 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001064 pixel;
1065
cristy4c08aed2011-07-01 19:47:50 +00001066 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001067 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001068
cristy14973ba2011-08-27 23:48:07 +00001069 register ssize_t
1070 x;
1071
cristy3ed852e2009-09-05 21:47:34 +00001072 if (status == MagickFalse)
1073 continue;
1074 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy14973ba2011-08-27 23:48:07 +00001075 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001076 {
1077 status=MagickFalse;
1078 continue;
1079 }
cristy4c08aed2011-07-01 19:47:50 +00001080 GetPixelInfo(image,&pixel);
cristybb503372010-05-27 20:51:26 +00001081 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001082 {
cristy803640d2011-11-17 02:11:32 +00001083 GetPixelInfoPixel(image,q,&pixel);
cristy3ed852e2009-09-05 21:47:34 +00001084 match=((pixel.red >= low->red) && (pixel.red <= high->red) &&
1085 (pixel.green >= low->green) && (pixel.green <= high->green) &&
cristy14973ba2011-08-27 23:48:07 +00001086 (pixel.blue >= low->blue) && (pixel.blue <= high->blue)) ? MagickTrue :
1087 MagickFalse;
cristy3ed852e2009-09-05 21:47:34 +00001088 if (match != invert)
cristy4c08aed2011-07-01 19:47:50 +00001089 SetPixelAlpha(image,opacity,q);
cristyed231572011-07-14 02:18:59 +00001090 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001091 }
1092 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1093 status=MagickFalse;
1094 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1095 {
1096 MagickBooleanType
1097 proceed;
1098
cristyb5d5f722009-11-04 03:03:49 +00001099#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy95338b32012-01-22 03:34:36 +00001100 #pragma omp critical (MagickCore_TransparentPaintImageChroma)
cristy3ed852e2009-09-05 21:47:34 +00001101#endif
1102 proceed=SetImageProgress(image,TransparentPaintImageTag,progress++,
1103 image->rows);
1104 if (proceed == MagickFalse)
1105 status=MagickFalse;
1106 }
1107 }
1108 image_view=DestroyCacheView(image_view);
1109 return(status);
1110}