blob: f6334a75038b1811dcba203997f1194c0e49a2da [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% %
cristyb56bb242014-11-25 17:12:48 +000020% Copyright 1999-2015 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{
cristycdd80122014-10-25 22:18:48 +0000114#define MaxStacksize 262144UL
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);
cristy17f11b02014-12-20 19:37:04 +0000181 if ((image->alpha_trait == UndefinedPixelTrait) &&
182 (draw_info->fill.alpha_trait != UndefinedPixelTrait))
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 }
dirkaff02c22014-12-16 19:47:33 +0000322 status=MagickTrue;
323#if defined(MAGICKCORE_OPENMP_SUPPORT)
324 #pragma omp parallel for schedule(static,4) shared(status) \
325 magick_threads(floodplane_image,image,floodplane_image->rows,1)
326#endif
cristybb503372010-05-27 20:51:26 +0000327 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000328 {
cristy4c08aed2011-07-01 19:47:50 +0000329 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000330 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000331
cristy4c08aed2011-07-01 19:47:50 +0000332 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000333 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000334
cristy14973ba2011-08-27 23:48:07 +0000335 register ssize_t
336 x;
337
cristy3ed852e2009-09-05 21:47:34 +0000338 /*
339 Tile fill color onto floodplane.
340 */
dirkaff02c22014-12-16 19:47:33 +0000341 if (status == MagickFalse)
342 continue;
cristy95f562a2012-01-01 20:49:11 +0000343 p=GetCacheViewVirtualPixels(floodplane_view,0,y,image->columns,1,exception);
cristyb0d3bb92010-09-22 14:37:58 +0000344 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000345 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
dirkaff02c22014-12-16 19:47:33 +0000346 {
347 status=MagickFalse;
348 continue;
349 }
cristybb503372010-05-27 20:51:26 +0000350 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000351 {
cristy95f562a2012-01-01 20:49:11 +0000352 if (GetPixelGray(floodplane_image,p) != 0)
cristy3ed852e2009-09-05 21:47:34 +0000353 {
cristy2ed42f62011-10-02 19:49:57 +0000354 (void) GetFillColor(draw_info,x,y,&fill_color,exception);
cristy11a06d32015-01-04 12:03:27 +0000355 SetPixelViaPixelInfo(image,&fill_color,q);
cristy3ed852e2009-09-05 21:47:34 +0000356 }
cristyed231572011-07-14 02:18:59 +0000357 p+=GetPixelChannels(floodplane_image);
358 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000359 }
cristyb0d3bb92010-09-22 14:37:58 +0000360 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
dirkaff02c22014-12-16 19:47:33 +0000361 status=MagickFalse;
cristy3ed852e2009-09-05 21:47:34 +0000362 }
cristyb0d3bb92010-09-22 14:37:58 +0000363 floodplane_view=DestroyCacheView(floodplane_view);
364 image_view=DestroyCacheView(image_view);
cristyaaaff842013-06-30 02:48:11 +0000365 segment_info=RelinquishVirtualMemory(segment_info);
cristy3ed852e2009-09-05 21:47:34 +0000366 floodplane_image=DestroyImage(floodplane_image);
dirkaff02c22014-12-16 19:47:33 +0000367 return(status);
cristy3ed852e2009-09-05 21:47:34 +0000368}
369
370/*
371%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
372% %
373% %
374% %
375+ G r a d i e n t I m a g e %
376% %
377% %
378% %
379%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
380%
cristycee97112010-05-28 00:44:52 +0000381% GradientImage() applies a continuously smooth color transitions along a
cristy3ed852e2009-09-05 21:47:34 +0000382% vector from one color to another.
383%
384% Note, the interface of this method will change in the future to support
385% more than one transistion.
386%
387% The format of the GradientImage method is:
388%
389% MagickBooleanType GradientImage(Image *image,const GradientType type,
cristy101ab702011-10-13 13:06:32 +0000390% const SpreadMethod method,const PixelInfo *start_color,
391% const PixelInfo *stop_color,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000392%
393% A description of each parameter follows:
394%
395% o image: the image.
396%
397% o type: the gradient type: linear or radial.
398%
399% o spread: the gradient spread meathod: pad, reflect, or repeat.
400%
401% o start_color: the start color.
402%
403% o stop_color: the stop color.
404%
cristy189e84c2011-08-27 18:08:53 +0000405% o exception: return any errors or warnings in this structure.
406%
cristy3ed852e2009-09-05 21:47:34 +0000407*/
408MagickExport MagickBooleanType GradientImage(Image *image,
409 const GradientType type,const SpreadMethod method,
cristy101ab702011-10-13 13:06:32 +0000410 const PixelInfo *start_color,const PixelInfo *stop_color,
cristy189e84c2011-08-27 18:08:53 +0000411 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000412{
413 DrawInfo
414 *draw_info;
415
416 GradientInfo
417 *gradient;
418
419 MagickBooleanType
420 status;
421
cristybb503372010-05-27 20:51:26 +0000422 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000423 i;
424
425 /*
426 Set gradient start-stop end points.
427 */
428 assert(image != (const Image *) NULL);
429 assert(image->signature == MagickSignature);
430 if (image->debug != MagickFalse)
431 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy101ab702011-10-13 13:06:32 +0000432 assert(start_color != (const PixelInfo *) NULL);
433 assert(stop_color != (const PixelInfo *) NULL);
cristy3ed852e2009-09-05 21:47:34 +0000434 draw_info=AcquireDrawInfo();
435 gradient=(&draw_info->gradient);
436 gradient->type=type;
437 gradient->bounding_box.width=image->columns;
438 gradient->bounding_box.height=image->rows;
439 gradient->gradient_vector.x2=(double) image->columns-1.0;
440 gradient->gradient_vector.y2=(double) image->rows-1.0;
441 if ((type == LinearGradient) && (gradient->gradient_vector.y2 != 0.0))
442 gradient->gradient_vector.x2=0.0;
443 gradient->center.x=(double) gradient->gradient_vector.x2/2.0;
444 gradient->center.y=(double) gradient->gradient_vector.y2/2.0;
445 gradient->radius=MagickMax(gradient->center.x,gradient->center.y);
446 gradient->spread=method;
447 /*
448 Define the gradient to fill between the stops.
449 */
450 gradient->number_stops=2;
451 gradient->stops=(StopInfo *) AcquireQuantumMemory(gradient->number_stops,
452 sizeof(*gradient->stops));
453 if (gradient->stops == (StopInfo *) NULL)
454 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
455 image->filename);
456 (void) ResetMagickMemory(gradient->stops,0,gradient->number_stops*
457 sizeof(*gradient->stops));
cristybb503372010-05-27 20:51:26 +0000458 for (i=0; i < (ssize_t) gradient->number_stops; i++)
cristy4c08aed2011-07-01 19:47:50 +0000459 GetPixelInfo(image,&gradient->stops[i].color);
cristy9d8c8ce2011-10-25 16:13:52 +0000460 gradient->stops[0].color=(*start_color);
cristy3ed852e2009-09-05 21:47:34 +0000461 gradient->stops[0].offset=0.0;
cristy9d8c8ce2011-10-25 16:13:52 +0000462 gradient->stops[1].color=(*stop_color);
cristy3ed852e2009-09-05 21:47:34 +0000463 gradient->stops[1].offset=1.0;
464 /*
465 Draw a gradient on the image.
466 */
cristybafdbba2012-06-20 11:50:57 +0000467 (void) SetImageColorspace(image,start_color->colorspace,exception);
cristy947cb4c2011-10-20 18:41:46 +0000468 status=DrawGradientImage(image,draw_info,exception);
cristy3ed852e2009-09-05 21:47:34 +0000469 draw_info=DestroyDrawInfo(draw_info);
cristy3ed852e2009-09-05 21:47:34 +0000470 return(status);
471}
472
473/*
474%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
475% %
476% %
477% %
478% O i l P a i n t I m a g e %
479% %
480% %
481% %
482%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
483%
484% OilPaintImage() applies a special effect filter that simulates an oil
485% painting. Each pixel is replaced by the most frequent color occurring
486% in a circular region defined by radius.
487%
488% The format of the OilPaintImage method is:
489%
490% Image *OilPaintImage(const Image *image,const double radius,
cristy14973ba2011-08-27 23:48:07 +0000491% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000492%
493% A description of each parameter follows:
494%
495% o image: the image.
496%
497% o radius: the radius of the circular neighborhood.
498%
cristy14973ba2011-08-27 23:48:07 +0000499% o sigma: the standard deviation of the Gaussian, in pixels.
500%
cristy3ed852e2009-09-05 21:47:34 +0000501% o exception: return any errors or warnings in this structure.
502%
503*/
504
cristybb503372010-05-27 20:51:26 +0000505static size_t **DestroyHistogramThreadSet(size_t **histogram)
cristy3ed852e2009-09-05 21:47:34 +0000506{
cristybb503372010-05-27 20:51:26 +0000507 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000508 i;
509
cristybb503372010-05-27 20:51:26 +0000510 assert(histogram != (size_t **) NULL);
cristyac245f82012-05-05 17:13:57 +0000511 for (i=0; i < (ssize_t) GetMagickResourceLimit(ThreadResource); i++)
cristybb503372010-05-27 20:51:26 +0000512 if (histogram[i] != (size_t *) NULL)
513 histogram[i]=(size_t *) RelinquishMagickMemory(histogram[i]);
cristyb41ee102010-10-04 16:46:15 +0000514 histogram=(size_t **) RelinquishMagickMemory(histogram);
cristy3ed852e2009-09-05 21:47:34 +0000515 return(histogram);
516}
517
cristybb503372010-05-27 20:51:26 +0000518static size_t **AcquireHistogramThreadSet(const size_t count)
cristy3ed852e2009-09-05 21:47:34 +0000519{
cristybb503372010-05-27 20:51:26 +0000520 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000521 i;
522
cristybb503372010-05-27 20:51:26 +0000523 size_t
cristy3ed852e2009-09-05 21:47:34 +0000524 **histogram,
525 number_threads;
526
cristy9357bdd2012-07-30 12:28:34 +0000527 number_threads=(size_t) GetMagickResourceLimit(ThreadResource);
cristy14973ba2011-08-27 23:48:07 +0000528 histogram=(size_t **) AcquireQuantumMemory(number_threads,sizeof(*histogram));
cristybb503372010-05-27 20:51:26 +0000529 if (histogram == (size_t **) NULL)
530 return((size_t **) NULL);
cristy3ed852e2009-09-05 21:47:34 +0000531 (void) ResetMagickMemory(histogram,0,number_threads*sizeof(*histogram));
cristybb503372010-05-27 20:51:26 +0000532 for (i=0; i < (ssize_t) number_threads; i++)
cristy3ed852e2009-09-05 21:47:34 +0000533 {
cristy14973ba2011-08-27 23:48:07 +0000534 histogram[i]=(size_t *) AcquireQuantumMemory(count,sizeof(**histogram));
cristybb503372010-05-27 20:51:26 +0000535 if (histogram[i] == (size_t *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000536 return(DestroyHistogramThreadSet(histogram));
537 }
538 return(histogram);
539}
540
541MagickExport Image *OilPaintImage(const Image *image,const double radius,
cristy14973ba2011-08-27 23:48:07 +0000542 const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000543{
544#define NumberPaintBins 256
545#define OilPaintImageTag "OilPaint/Image"
546
cristyfa112112010-01-04 17:48:07 +0000547 CacheView
548 *image_view,
549 *paint_view;
550
cristy3ed852e2009-09-05 21:47:34 +0000551 Image
cristy404d0da2012-12-20 13:04:30 +0000552 *linear_image,
cristy3ed852e2009-09-05 21:47:34 +0000553 *paint_image;
554
cristy3ed852e2009-09-05 21:47:34 +0000555 MagickBooleanType
556 status;
557
cristybb503372010-05-27 20:51:26 +0000558 MagickOffsetType
559 progress;
560
561 size_t
cristy14973ba2011-08-27 23:48:07 +0000562 **histograms,
cristy3ed852e2009-09-05 21:47:34 +0000563 width;
564
cristybb503372010-05-27 20:51:26 +0000565 ssize_t
cristyf8561542012-01-24 00:26:46 +0000566 center,
cristybb503372010-05-27 20:51:26 +0000567 y;
568
cristy3ed852e2009-09-05 21:47:34 +0000569 /*
570 Initialize painted image attributes.
571 */
572 assert(image != (const Image *) NULL);
573 assert(image->signature == MagickSignature);
574 if (image->debug != MagickFalse)
575 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
576 assert(exception != (ExceptionInfo *) NULL);
577 assert(exception->signature == MagickSignature);
cristy14973ba2011-08-27 23:48:07 +0000578 width=GetOptimalKernelWidth2D(radius,sigma);
cristy404d0da2012-12-20 13:04:30 +0000579 linear_image=CloneImage(image,0,0,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +0000580 paint_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
cristy404d0da2012-12-20 13:04:30 +0000581 if ((linear_image == (Image *) NULL) || (paint_image == (Image *) NULL))
582 {
583 if (linear_image != (Image *) NULL)
584 linear_image=DestroyImage(linear_image);
585 if (paint_image != (Image *) NULL)
586 linear_image=DestroyImage(paint_image);
587 return((Image *) NULL);
588 }
cristy574cc262011-08-05 01:23:58 +0000589 if (SetImageStorageClass(paint_image,DirectClass,exception) == MagickFalse)
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 return((Image *) NULL);
594 }
595 histograms=AcquireHistogramThreadSet(NumberPaintBins);
cristybb503372010-05-27 20:51:26 +0000596 if (histograms == (size_t **) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000597 {
cristy404d0da2012-12-20 13:04:30 +0000598 linear_image=DestroyImage(linear_image);
cristy3ed852e2009-09-05 21:47:34 +0000599 paint_image=DestroyImage(paint_image);
600 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
601 }
602 /*
603 Oil paint image.
604 */
605 status=MagickTrue;
606 progress=0;
cristy404d0da2012-12-20 13:04:30 +0000607 center=(ssize_t) GetPixelChannels(linear_image)*(linear_image->columns+width)*
608 (width/2L)+GetPixelChannels(linear_image)*(width/2L);
609 image_view=AcquireVirtualCacheView(linear_image,exception);
cristy46ff2672012-12-14 15:32:26 +0000610 paint_view=AcquireAuthenticCacheView(paint_image,exception);
cristyb5d5f722009-11-04 03:03:49 +0000611#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000612 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy404d0da2012-12-20 13:04:30 +0000613 magick_threads(linear_image,paint_image,linear_image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +0000614#endif
cristy404d0da2012-12-20 13:04:30 +0000615 for (y=0; y < (ssize_t) linear_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000616 {
cristy4c08aed2011-07-01 19:47:50 +0000617 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000618 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000619
cristy4c08aed2011-07-01 19:47:50 +0000620 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000621 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000622
cristybb503372010-05-27 20:51:26 +0000623 register size_t
cristy3ed852e2009-09-05 21:47:34 +0000624 *histogram;
625
cristy14973ba2011-08-27 23:48:07 +0000626 register ssize_t
627 x;
628
cristy3ed852e2009-09-05 21:47:34 +0000629 if (status == MagickFalse)
630 continue;
cristyfe4ba002011-02-28 14:54:12 +0000631 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
cristy404d0da2012-12-20 13:04:30 +0000632 (width/2L),linear_image->columns+width,width,exception);
cristy3ed852e2009-09-05 21:47:34 +0000633 q=QueueCacheViewAuthenticPixels(paint_view,0,y,paint_image->columns,1,
634 exception);
cristy4c08aed2011-07-01 19:47:50 +0000635 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000636 {
637 status=MagickFalse;
638 continue;
639 }
cristy3ed852e2009-09-05 21:47:34 +0000640 histogram=histograms[GetOpenMPThreadId()];
cristy404d0da2012-12-20 13:04:30 +0000641 for (x=0; x < (ssize_t) linear_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000642 {
cristybb503372010-05-27 20:51:26 +0000643 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000644 i,
645 u;
646
cristybb503372010-05-27 20:51:26 +0000647 size_t
cristy3ed852e2009-09-05 21:47:34 +0000648 count;
649
cristy9d314ff2011-03-09 01:30:28 +0000650 ssize_t
651 j,
652 k,
cristyf8561542012-01-24 00:26:46 +0000653 n,
cristy9d314ff2011-03-09 01:30:28 +0000654 v;
655
cristy3ed852e2009-09-05 21:47:34 +0000656 /*
657 Assign most frequent color.
658 */
cristyf8561542012-01-24 00:26:46 +0000659 k=0;
cristy3ed852e2009-09-05 21:47:34 +0000660 j=0;
661 count=0;
cristyf8561542012-01-24 00:26:46 +0000662 (void) ResetMagickMemory(histogram,0,NumberPaintBins* sizeof(*histogram));
cristybb503372010-05-27 20:51:26 +0000663 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +0000664 {
cristybb503372010-05-27 20:51:26 +0000665 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +0000666 {
cristy404d0da2012-12-20 13:04:30 +0000667 n=(ssize_t) ScaleQuantumToChar(ClampToQuantum(GetPixelIntensity(
668 linear_image,p+GetPixelChannels(linear_image)*(u+k))));
cristyf8561542012-01-24 00:26:46 +0000669 histogram[n]++;
670 if (histogram[n] > count)
cristy3ed852e2009-09-05 21:47:34 +0000671 {
cristyf8561542012-01-24 00:26:46 +0000672 j=k+u;
673 count=histogram[n];
cristy3ed852e2009-09-05 21:47:34 +0000674 }
675 }
cristy034ade92013-03-07 12:18:19 +0000676 k+=(ssize_t) (linear_image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +0000677 }
cristy404d0da2012-12-20 13:04:30 +0000678 for (i=0; i < (ssize_t) GetPixelChannels(linear_image); i++)
cristyf8561542012-01-24 00:26:46 +0000679 {
cristy5a23c552013-02-13 14:34:28 +0000680 PixelChannel channel=GetPixelChannelChannel(linear_image,i);
681 PixelTrait traits=GetPixelChannelTraits(linear_image,channel);
682 PixelTrait paint_traits=GetPixelChannelTraits(paint_image,channel);
cristyf8561542012-01-24 00:26:46 +0000683 if ((traits == UndefinedPixelTrait) ||
684 (paint_traits == UndefinedPixelTrait))
685 continue;
cristy1eced092012-08-10 23:10:56 +0000686 if (((paint_traits & CopyPixelTrait) != 0) ||
cristy883fde12013-04-08 00:50:13 +0000687 (GetPixelReadMask(linear_image,p) == 0))
cristyf8561542012-01-24 00:26:46 +0000688 {
689 SetPixelChannel(paint_image,channel,p[center+i],q);
690 continue;
691 }
cristy404d0da2012-12-20 13:04:30 +0000692 SetPixelChannel(paint_image,channel,p[j*GetPixelChannels(linear_image)+
693 i],q);
cristyf8561542012-01-24 00:26:46 +0000694 }
cristy404d0da2012-12-20 13:04:30 +0000695 p+=GetPixelChannels(linear_image);
cristyed231572011-07-14 02:18:59 +0000696 q+=GetPixelChannels(paint_image);
cristy3ed852e2009-09-05 21:47:34 +0000697 }
698 if (SyncCacheViewAuthenticPixels(paint_view,exception) == MagickFalse)
699 status=MagickFalse;
cristy404d0da2012-12-20 13:04:30 +0000700 if (linear_image->progress_monitor != (MagickProgressMonitor) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000701 {
702 MagickBooleanType
703 proceed;
704
cristyb5d5f722009-11-04 03:03:49 +0000705#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy95338b32012-01-22 03:34:36 +0000706 #pragma omp critical (MagickCore_OilPaintImage)
cristy3ed852e2009-09-05 21:47:34 +0000707#endif
cristy404d0da2012-12-20 13:04:30 +0000708 proceed=SetImageProgress(linear_image,OilPaintImageTag,progress++,
709 linear_image->rows);
cristy3ed852e2009-09-05 21:47:34 +0000710 if (proceed == MagickFalse)
711 status=MagickFalse;
712 }
713 }
714 paint_view=DestroyCacheView(paint_view);
715 image_view=DestroyCacheView(image_view);
716 histograms=DestroyHistogramThreadSet(histograms);
cristy404d0da2012-12-20 13:04:30 +0000717 linear_image=DestroyImage(linear_image);
cristy3ed852e2009-09-05 21:47:34 +0000718 if (status == MagickFalse)
719 paint_image=DestroyImage(paint_image);
720 return(paint_image);
721}
722
723/*
724%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
725% %
726% %
727% %
728% O p a q u e P a i n t I m a g e %
729% %
730% %
731% %
732%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
733%
734% OpaquePaintImage() changes any pixel that matches color with the color
cristy1f075552014-11-04 00:03:27 +0000735% defined by fill argument.
cristy3ed852e2009-09-05 21:47:34 +0000736%
cristy14973ba2011-08-27 23:48:07 +0000737% By default color must match a particular pixel color exactly. However, in
738% many cases two colors may differ by a small amount. Fuzz defines how much
739% tolerance is acceptable to consider two colors as the same. For example,
740% set fuzz to 10 and the color red at intensities of 100 and 102 respectively
741% are now interpreted as the same color.
cristy3ed852e2009-09-05 21:47:34 +0000742%
743% The format of the OpaquePaintImage method is:
744%
cristy1f075552014-11-04 00:03:27 +0000745% MagickBooleanType OpaquePaintImage(Image *image,const PixelInfo *target,
746% const PixelInfo *fill,const MagickBooleanType invert,
747% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000748%
749% A description of each parameter follows:
750%
751% o image: the image.
752%
cristy3ed852e2009-09-05 21:47:34 +0000753% o target: the RGB value of the target color.
754%
755% o fill: the replacement color.
756%
757% o invert: paint any pixel that does not match the target color.
758%
cristy189e84c2011-08-27 18:08:53 +0000759% o exception: return any errors or warnings in this structure.
760%
cristy3ed852e2009-09-05 21:47:34 +0000761*/
cristy3ed852e2009-09-05 21:47:34 +0000762MagickExport MagickBooleanType OpaquePaintImage(Image *image,
cristy189e84c2011-08-27 18:08:53 +0000763 const PixelInfo *target,const PixelInfo *fill,const MagickBooleanType invert,
764 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000765{
766#define OpaquePaintImageTag "Opaque/Image"
767
cristyc4c8d132010-01-07 01:58:38 +0000768 CacheView
769 *image_view;
770
cristy3ed852e2009-09-05 21:47:34 +0000771 MagickBooleanType
772 status;
773
cristybb503372010-05-27 20:51:26 +0000774 MagickOffsetType
775 progress;
776
cristy4c08aed2011-07-01 19:47:50 +0000777 PixelInfo
cristy1f075552014-11-04 00:03:27 +0000778 conform_fill,
779 conform_target,
cristy3ed852e2009-09-05 21:47:34 +0000780 zero;
781
cristybb503372010-05-27 20:51:26 +0000782 ssize_t
783 y;
784
cristy3ed852e2009-09-05 21:47:34 +0000785 assert(image != (Image *) NULL);
786 assert(image->signature == MagickSignature);
cristy4c08aed2011-07-01 19:47:50 +0000787 assert(target != (PixelInfo *) NULL);
788 assert(fill != (PixelInfo *) NULL);
cristy3ed852e2009-09-05 21:47:34 +0000789 if (image->debug != MagickFalse)
790 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy574cc262011-08-05 01:23:58 +0000791 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000792 return(MagickFalse);
dirkbfdd5bc2014-11-04 19:47:44 +0000793 ConformPixelInfo(image,fill,&conform_fill,exception);
794 ConformPixelInfo(image,target,&conform_target,exception);
cristy3ed852e2009-09-05 21:47:34 +0000795 /*
796 Make image color opaque.
797 */
798 status=MagickTrue;
799 progress=0;
cristy4c08aed2011-07-01 19:47:50 +0000800 GetPixelInfo(image,&zero);
cristy46ff2672012-12-14 15:32:26 +0000801 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +0000802#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000803 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy5e6b2592012-12-19 14:08:11 +0000804 magick_threads(image,image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +0000805#endif
cristybb503372010-05-27 20:51:26 +0000806 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000807 {
cristy4c08aed2011-07-01 19:47:50 +0000808 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000809 pixel;
810
cristy4c08aed2011-07-01 19:47:50 +0000811 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000812 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000813
cristy14973ba2011-08-27 23:48:07 +0000814 register ssize_t
815 x;
816
cristy3ed852e2009-09-05 21:47:34 +0000817 if (status == MagickFalse)
818 continue;
819 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy14973ba2011-08-27 23:48:07 +0000820 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000821 {
822 status=MagickFalse;
823 continue;
824 }
cristy3ed852e2009-09-05 21:47:34 +0000825 pixel=zero;
cristybb503372010-05-27 20:51:26 +0000826 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000827 {
cristy803640d2011-11-17 02:11:32 +0000828 GetPixelInfoPixel(image,q,&pixel);
cristy1f075552014-11-04 00:03:27 +0000829 if (IsFuzzyEquivalencePixelInfo(&pixel,&conform_target) != invert)
cristy11a06d32015-01-04 12:03:27 +0000830 SetPixelViaPixelInfo(image,&conform_fill,q);
cristyed231572011-07-14 02:18:59 +0000831 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000832 }
833 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
834 status=MagickFalse;
835 if (image->progress_monitor != (MagickProgressMonitor) NULL)
836 {
837 MagickBooleanType
838 proceed;
839
cristyb5d5f722009-11-04 03:03:49 +0000840#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy95338b32012-01-22 03:34:36 +0000841 #pragma omp critical (MagickCore_OpaquePaintImage)
cristy3ed852e2009-09-05 21:47:34 +0000842#endif
843 proceed=SetImageProgress(image,OpaquePaintImageTag,progress++,
844 image->rows);
845 if (proceed == MagickFalse)
846 status=MagickFalse;
847 }
848 }
849 image_view=DestroyCacheView(image_view);
850 return(status);
851}
852
853/*
854%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
855% %
856% %
857% %
858% T r a n s p a r e n t P a i n t I m a g e %
859% %
860% %
861% %
862%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
863%
864% TransparentPaintImage() changes the opacity value associated with any pixel
865% that matches color to the value defined by opacity.
866%
cristy14973ba2011-08-27 23:48:07 +0000867% By default color must match a particular pixel color exactly. However, in
868% many cases two colors may differ by a small amount. Fuzz defines how much
869% tolerance is acceptable to consider two colors as the same. For example,
870% set fuzz to 10 and the color red at intensities of 100 and 102 respectively
871% are now interpreted as the same color.
cristy3ed852e2009-09-05 21:47:34 +0000872%
873% The format of the TransparentPaintImage method is:
874%
875% MagickBooleanType TransparentPaintImage(Image *image,
cristy4c08aed2011-07-01 19:47:50 +0000876% const PixelInfo *target,const Quantum opacity,
cristy189e84c2011-08-27 18:08:53 +0000877% const MagickBooleanType invert,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000878%
879% A description of each parameter follows:
880%
881% o image: the image.
882%
883% o target: the target color.
884%
885% o opacity: the replacement opacity value.
886%
887% o invert: paint any pixel that does not match the target color.
888%
cristy189e84c2011-08-27 18:08:53 +0000889% o exception: return any errors or warnings in this structure.
890%
cristy3ed852e2009-09-05 21:47:34 +0000891*/
892MagickExport MagickBooleanType TransparentPaintImage(Image *image,
cristy14973ba2011-08-27 23:48:07 +0000893 const PixelInfo *target,const Quantum opacity,const MagickBooleanType invert,
894 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000895{
896#define TransparentPaintImageTag "Transparent/Image"
897
cristyc4c8d132010-01-07 01:58:38 +0000898 CacheView
899 *image_view;
900
cristy3ed852e2009-09-05 21:47:34 +0000901 MagickBooleanType
902 status;
903
cristybb503372010-05-27 20:51:26 +0000904 MagickOffsetType
905 progress;
906
cristy4c08aed2011-07-01 19:47:50 +0000907 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000908 zero;
909
cristybb503372010-05-27 20:51:26 +0000910 ssize_t
911 y;
912
cristy3ed852e2009-09-05 21:47:34 +0000913 assert(image != (Image *) NULL);
914 assert(image->signature == MagickSignature);
cristy4c08aed2011-07-01 19:47:50 +0000915 assert(target != (PixelInfo *) NULL);
cristy3ed852e2009-09-05 21:47:34 +0000916 if (image->debug != MagickFalse)
917 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy574cc262011-08-05 01:23:58 +0000918 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000919 return(MagickFalse);
cristy17f11b02014-12-20 19:37:04 +0000920 if (image->alpha_trait == UndefinedPixelTrait)
cristy63240882011-08-05 19:05:27 +0000921 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
cristy3ed852e2009-09-05 21:47:34 +0000922 /*
923 Make image color transparent.
924 */
925 status=MagickTrue;
926 progress=0;
cristy4c08aed2011-07-01 19:47:50 +0000927 GetPixelInfo(image,&zero);
cristy46ff2672012-12-14 15:32:26 +0000928 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +0000929#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000930 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy5e6b2592012-12-19 14:08:11 +0000931 magick_threads(image,image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +0000932#endif
cristybb503372010-05-27 20:51:26 +0000933 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000934 {
cristy4c08aed2011-07-01 19:47:50 +0000935 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000936 pixel;
937
cristybb503372010-05-27 20:51:26 +0000938 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000939 x;
940
cristy4c08aed2011-07-01 19:47:50 +0000941 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000942 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000943
944 if (status == MagickFalse)
945 continue;
946 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy14973ba2011-08-27 23:48:07 +0000947 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000948 {
949 status=MagickFalse;
950 continue;
951 }
cristy3ed852e2009-09-05 21:47:34 +0000952 pixel=zero;
cristybb503372010-05-27 20:51:26 +0000953 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000954 {
cristy803640d2011-11-17 02:11:32 +0000955 GetPixelInfoPixel(image,q,&pixel);
cristy4c08aed2011-07-01 19:47:50 +0000956 if (IsFuzzyEquivalencePixelInfo(&pixel,target) != invert)
957 SetPixelAlpha(image,opacity,q);
cristyed231572011-07-14 02:18:59 +0000958 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000959 }
960 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
961 status=MagickFalse;
962 if (image->progress_monitor != (MagickProgressMonitor) NULL)
963 {
964 MagickBooleanType
965 proceed;
966
cristyb5d5f722009-11-04 03:03:49 +0000967#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy95338b32012-01-22 03:34:36 +0000968 #pragma omp critical (MagickCore_TransparentPaintImage)
cristy3ed852e2009-09-05 21:47:34 +0000969#endif
970 proceed=SetImageProgress(image,TransparentPaintImageTag,progress++,
971 image->rows);
972 if (proceed == MagickFalse)
973 status=MagickFalse;
974 }
975 }
976 image_view=DestroyCacheView(image_view);
977 return(status);
978}
979
980/*
981%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
982% %
983% %
984% %
985% 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 %
986% %
987% %
988% %
989%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
990%
991% TransparentPaintImageChroma() changes the opacity value associated with any
992% pixel that matches color to the value defined by opacity.
993%
cristy14973ba2011-08-27 23:48:07 +0000994% As there is one fuzz value for the all the channels, TransparentPaintImage()
995% is not suitable for the operations like chroma, where the tolerance for
996% similarity of two color component (RGB) can be different. Thus we define
997% this method to take two target pixels (one low and one high) and all the
998% pixels of an image which are lying between these two pixels are made
999% transparent.
cristy3ed852e2009-09-05 21:47:34 +00001000%
cristy14973ba2011-08-27 23:48:07 +00001001% The format of the TransparentPaintImageChroma method is:
cristy3ed852e2009-09-05 21:47:34 +00001002%
cristy14973ba2011-08-27 23:48:07 +00001003% MagickBooleanType TransparentPaintImageChroma(Image *image,
1004% const PixelInfo *low,const PixelInfo *high,const Quantum opacity,
1005% const MagickBooleanType invert,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001006%
1007% A description of each parameter follows:
1008%
1009% o image: the image.
1010%
1011% o low: the low target color.
1012%
1013% o high: the high target color.
1014%
1015% o opacity: the replacement opacity value.
1016%
1017% o invert: paint any pixel that does not match the target color.
1018%
cristy189e84c2011-08-27 18:08:53 +00001019% o exception: return any errors or warnings in this structure.
1020%
cristy3ed852e2009-09-05 21:47:34 +00001021*/
1022MagickExport MagickBooleanType TransparentPaintImageChroma(Image *image,
cristy189e84c2011-08-27 18:08:53 +00001023 const PixelInfo *low,const PixelInfo *high,const Quantum opacity,
1024 const MagickBooleanType invert,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001025{
1026#define TransparentPaintImageTag "Transparent/Image"
1027
cristyc4c8d132010-01-07 01:58:38 +00001028 CacheView
1029 *image_view;
1030
cristy3ed852e2009-09-05 21:47:34 +00001031 MagickBooleanType
1032 status;
1033
cristybb503372010-05-27 20:51:26 +00001034 MagickOffsetType
1035 progress;
1036
1037 ssize_t
1038 y;
1039
cristy3ed852e2009-09-05 21:47:34 +00001040 assert(image != (Image *) NULL);
1041 assert(image->signature == MagickSignature);
cristy4c08aed2011-07-01 19:47:50 +00001042 assert(high != (PixelInfo *) NULL);
1043 assert(low != (PixelInfo *) NULL);
cristy3ed852e2009-09-05 21:47:34 +00001044 if (image->debug != MagickFalse)
1045 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy574cc262011-08-05 01:23:58 +00001046 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001047 return(MagickFalse);
cristy17f11b02014-12-20 19:37:04 +00001048 if (image->alpha_trait == UndefinedPixelTrait)
cristy63240882011-08-05 19:05:27 +00001049 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
cristy3ed852e2009-09-05 21:47:34 +00001050 /*
1051 Make image color transparent.
1052 */
1053 status=MagickTrue;
1054 progress=0;
cristy46ff2672012-12-14 15:32:26 +00001055 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +00001056#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001057 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy5e6b2592012-12-19 14:08:11 +00001058 magick_threads(image,image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00001059#endif
cristybb503372010-05-27 20:51:26 +00001060 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001061 {
1062 MagickBooleanType
1063 match;
1064
cristy4c08aed2011-07-01 19:47:50 +00001065 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001066 pixel;
1067
cristy4c08aed2011-07-01 19:47:50 +00001068 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001069 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001070
cristy14973ba2011-08-27 23:48:07 +00001071 register ssize_t
1072 x;
1073
cristy3ed852e2009-09-05 21:47:34 +00001074 if (status == MagickFalse)
1075 continue;
1076 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy14973ba2011-08-27 23:48:07 +00001077 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001078 {
1079 status=MagickFalse;
1080 continue;
1081 }
cristy4c08aed2011-07-01 19:47:50 +00001082 GetPixelInfo(image,&pixel);
cristybb503372010-05-27 20:51:26 +00001083 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001084 {
cristy803640d2011-11-17 02:11:32 +00001085 GetPixelInfoPixel(image,q,&pixel);
cristy3ed852e2009-09-05 21:47:34 +00001086 match=((pixel.red >= low->red) && (pixel.red <= high->red) &&
1087 (pixel.green >= low->green) && (pixel.green <= high->green) &&
cristy14973ba2011-08-27 23:48:07 +00001088 (pixel.blue >= low->blue) && (pixel.blue <= high->blue)) ? MagickTrue :
1089 MagickFalse;
cristy3ed852e2009-09-05 21:47:34 +00001090 if (match != invert)
cristy4c08aed2011-07-01 19:47:50 +00001091 SetPixelAlpha(image,opacity,q);
cristyed231572011-07-14 02:18:59 +00001092 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001093 }
1094 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1095 status=MagickFalse;
1096 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1097 {
1098 MagickBooleanType
1099 proceed;
1100
cristyb5d5f722009-11-04 03:03:49 +00001101#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy95338b32012-01-22 03:34:36 +00001102 #pragma omp critical (MagickCore_TransparentPaintImageChroma)
cristy3ed852e2009-09-05 21:47:34 +00001103#endif
1104 proceed=SetImageProgress(image,TransparentPaintImageTag,progress++,
1105 image->rows);
1106 if (proceed == MagickFalse)
1107 status=MagickFalse;
1108 }
1109 }
1110 image_view=DestroyCacheView(image_view);
1111 return(status);
1112}