blob: 1362e056daa5ddc071aa0dee6b25bdb4130f2385 [file] [log] [blame]
cristy3ed852e2009-09-05 21:47:34 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% EEEEE FFFFF FFFFF EEEEE CCCC TTTTT %
7% E F F E C T %
8% EEE FFF FFF EEE C T %
9% E F F E C T %
10% EEEEE F F EEEEE CCCC T %
11% %
12% %
13% MagickCore Image Effects Methods %
14% %
15% Software Design %
16% John Cristy %
17% October 1996 %
18% %
19% %
cristy7e41fe82010-12-04 23:12:08 +000020% Copyright 1999-2011 ImageMagick Studio LLC, a non-profit organization %
cristy3ed852e2009-09-05 21:47:34 +000021% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
26% http://www.imagemagick.org/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
36%
37%
38*/
39
40/*
41 Include declarations.
42*/
cristy4c08aed2011-07-01 19:47:50 +000043#include "MagickCore/studio.h"
44#include "MagickCore/accelerate.h"
45#include "MagickCore/blob.h"
46#include "MagickCore/cache-view.h"
47#include "MagickCore/color.h"
48#include "MagickCore/color-private.h"
49#include "MagickCore/colorspace.h"
50#include "MagickCore/constitute.h"
51#include "MagickCore/decorate.h"
52#include "MagickCore/draw.h"
53#include "MagickCore/enhance.h"
54#include "MagickCore/exception.h"
55#include "MagickCore/exception-private.h"
56#include "MagickCore/effect.h"
57#include "MagickCore/fx.h"
58#include "MagickCore/gem.h"
59#include "MagickCore/geometry.h"
60#include "MagickCore/image-private.h"
61#include "MagickCore/list.h"
62#include "MagickCore/log.h"
63#include "MagickCore/memory_.h"
64#include "MagickCore/monitor.h"
65#include "MagickCore/monitor-private.h"
66#include "MagickCore/montage.h"
67#include "MagickCore/morphology.h"
68#include "MagickCore/paint.h"
69#include "MagickCore/pixel-accessor.h"
70#include "MagickCore/property.h"
71#include "MagickCore/quantize.h"
72#include "MagickCore/quantum.h"
73#include "MagickCore/quantum-private.h"
74#include "MagickCore/random_.h"
75#include "MagickCore/random-private.h"
76#include "MagickCore/resample.h"
77#include "MagickCore/resample-private.h"
78#include "MagickCore/resize.h"
79#include "MagickCore/resource_.h"
80#include "MagickCore/segment.h"
81#include "MagickCore/shear.h"
82#include "MagickCore/signature-private.h"
83#include "MagickCore/string_.h"
84#include "MagickCore/thread-private.h"
85#include "MagickCore/transform.h"
86#include "MagickCore/threshold.h"
cristy3ed852e2009-09-05 21:47:34 +000087
88/*
89%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
90% %
91% %
92% %
93% A d a p t i v e B l u r I m a g e %
94% %
95% %
96% %
97%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
98%
99% AdaptiveBlurImage() adaptively blurs the image by blurring less
100% intensely near image edges and more intensely far from edges. We blur the
101% image with a Gaussian operator of the given radius and standard deviation
102% (sigma). For reasonable results, radius should be larger than sigma. Use a
103% radius of 0 and AdaptiveBlurImage() selects a suitable radius for you.
104%
105% The format of the AdaptiveBlurImage method is:
106%
107% Image *AdaptiveBlurImage(const Image *image,const double radius,
108% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000109%
110% A description of each parameter follows:
111%
112% o image: the image.
113%
cristy3ed852e2009-09-05 21:47:34 +0000114% o radius: the radius of the Gaussian, in pixels, not counting the center
115% pixel.
116%
117% o sigma: the standard deviation of the Laplacian, in pixels.
118%
119% o exception: return any errors or warnings in this structure.
120%
121*/
122
cristyf89cb1d2011-07-07 01:24:37 +0000123MagickExport MagickBooleanType AdaptiveLevelImage(Image *image,
124 const char *levels)
125{
126 double
127 black_point,
128 gamma,
129 white_point;
130
cristy7c0a0a42011-08-23 17:57:25 +0000131 ExceptionInfo
132 *exception;
133
cristyf89cb1d2011-07-07 01:24:37 +0000134 GeometryInfo
135 geometry_info;
136
137 MagickBooleanType
138 status;
139
140 MagickStatusType
141 flags;
142
143 /*
144 Parse levels.
145 */
146 if (levels == (char *) NULL)
147 return(MagickFalse);
cristy7c0a0a42011-08-23 17:57:25 +0000148 exception=(&image->exception);
cristyf89cb1d2011-07-07 01:24:37 +0000149 flags=ParseGeometry(levels,&geometry_info);
150 black_point=geometry_info.rho;
151 white_point=(double) QuantumRange;
152 if ((flags & SigmaValue) != 0)
153 white_point=geometry_info.sigma;
154 gamma=1.0;
155 if ((flags & XiValue) != 0)
156 gamma=geometry_info.xi;
157 if ((flags & PercentValue) != 0)
158 {
159 black_point*=(double) image->columns*image->rows/100.0;
160 white_point*=(double) image->columns*image->rows/100.0;
161 }
162 if ((flags & SigmaValue) == 0)
163 white_point=(double) QuantumRange-black_point;
164 if ((flags & AspectValue ) == 0)
cristy7c0a0a42011-08-23 17:57:25 +0000165 status=LevelImage(image,black_point,white_point,gamma,exception);
cristyf89cb1d2011-07-07 01:24:37 +0000166 else
cristy7c0a0a42011-08-23 17:57:25 +0000167 status=LevelizeImage(image,black_point,white_point,gamma,exception);
cristyf89cb1d2011-07-07 01:24:37 +0000168 return(status);
169}
170
cristyf4ad9df2011-07-08 16:49:03 +0000171MagickExport Image *AdaptiveBlurImage(const Image *image,
172 const double radius,const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000173{
174#define AdaptiveBlurImageTag "Convolve/Image"
175#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
176
cristyc4c8d132010-01-07 01:58:38 +0000177 CacheView
178 *blur_view,
179 *edge_view,
180 *image_view;
181
cristy3ed852e2009-09-05 21:47:34 +0000182 double
cristy47e00502009-12-17 19:19:57 +0000183 **kernel,
184 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000185
186 Image
187 *blur_image,
188 *edge_image,
189 *gaussian_image;
190
cristy3ed852e2009-09-05 21:47:34 +0000191 MagickBooleanType
192 status;
193
cristybb503372010-05-27 20:51:26 +0000194 MagickOffsetType
195 progress;
196
cristy4c08aed2011-07-01 19:47:50 +0000197 PixelInfo
cristyddd82202009-11-03 20:14:50 +0000198 bias;
cristy3ed852e2009-09-05 21:47:34 +0000199
cristybb503372010-05-27 20:51:26 +0000200 register ssize_t
cristy47e00502009-12-17 19:19:57 +0000201 i;
cristy3ed852e2009-09-05 21:47:34 +0000202
cristybb503372010-05-27 20:51:26 +0000203 size_t
cristy3ed852e2009-09-05 21:47:34 +0000204 width;
205
cristybb503372010-05-27 20:51:26 +0000206 ssize_t
207 j,
208 k,
209 u,
210 v,
211 y;
212
cristy3ed852e2009-09-05 21:47:34 +0000213 assert(image != (const Image *) NULL);
214 assert(image->signature == MagickSignature);
215 if (image->debug != MagickFalse)
216 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
217 assert(exception != (ExceptionInfo *) NULL);
218 assert(exception->signature == MagickSignature);
219 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
220 if (blur_image == (Image *) NULL)
221 return((Image *) NULL);
222 if (fabs(sigma) <= MagickEpsilon)
223 return(blur_image);
cristy574cc262011-08-05 01:23:58 +0000224 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000225 {
cristy3ed852e2009-09-05 21:47:34 +0000226 blur_image=DestroyImage(blur_image);
227 return((Image *) NULL);
228 }
229 /*
230 Edge detect the image brighness channel, level, blur, and level again.
231 */
232 edge_image=EdgeImage(image,radius,exception);
233 if (edge_image == (Image *) NULL)
234 {
235 blur_image=DestroyImage(blur_image);
236 return((Image *) NULL);
237 }
cristyf89cb1d2011-07-07 01:24:37 +0000238 (void) AdaptiveLevelImage(edge_image,"20%,95%");
cristy3ed852e2009-09-05 21:47:34 +0000239 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,exception);
240 if (gaussian_image != (Image *) NULL)
241 {
242 edge_image=DestroyImage(edge_image);
243 edge_image=gaussian_image;
244 }
cristyf89cb1d2011-07-07 01:24:37 +0000245 (void) AdaptiveLevelImage(edge_image,"10%,95%");
cristy3ed852e2009-09-05 21:47:34 +0000246 /*
247 Create a set of kernels from maximum (radius,sigma) to minimum.
248 */
249 width=GetOptimalKernelWidth2D(radius,sigma);
250 kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
251 if (kernel == (double **) NULL)
252 {
253 edge_image=DestroyImage(edge_image);
254 blur_image=DestroyImage(blur_image);
255 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
256 }
257 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
cristybb503372010-05-27 20:51:26 +0000258 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000259 {
260 kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)*
261 sizeof(**kernel));
262 if (kernel[i] == (double *) NULL)
263 break;
cristy47e00502009-12-17 19:19:57 +0000264 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000265 j=(ssize_t) (width-i)/2;
cristy47e00502009-12-17 19:19:57 +0000266 k=0;
267 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000268 {
cristy47e00502009-12-17 19:19:57 +0000269 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000270 {
cristy4205a3c2010-09-12 20:19:59 +0000271 kernel[i][k]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
272 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +0000273 normalize+=kernel[i][k];
274 k++;
cristy3ed852e2009-09-05 21:47:34 +0000275 }
276 }
cristy3ed852e2009-09-05 21:47:34 +0000277 if (fabs(normalize) <= MagickEpsilon)
278 normalize=1.0;
279 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000280 for (k=0; k < (j*j); k++)
281 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000282 }
cristybb503372010-05-27 20:51:26 +0000283 if (i < (ssize_t) width)
cristy3ed852e2009-09-05 21:47:34 +0000284 {
285 for (i-=2; i >= 0; i-=2)
286 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
287 kernel=(double **) RelinquishMagickMemory(kernel);
288 edge_image=DestroyImage(edge_image);
289 blur_image=DestroyImage(blur_image);
290 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
291 }
292 /*
293 Adaptively blur image.
294 */
295 status=MagickTrue;
296 progress=0;
cristy4c08aed2011-07-01 19:47:50 +0000297 GetPixelInfo(image,&bias);
298 SetPixelInfoBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000299 image_view=AcquireCacheView(image);
300 edge_view=AcquireCacheView(edge_image);
301 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000302#if defined(MAGICKCORE_OPENMP_SUPPORT)
303 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000304#endif
cristybb503372010-05-27 20:51:26 +0000305 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000306 {
cristy4c08aed2011-07-01 19:47:50 +0000307 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000308 *restrict p,
309 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000310
cristy4c08aed2011-07-01 19:47:50 +0000311 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000312 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000313
cristy117ff172010-08-15 21:35:32 +0000314 register ssize_t
315 x;
316
cristy3ed852e2009-09-05 21:47:34 +0000317 if (status == MagickFalse)
318 continue;
319 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
320 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
321 exception);
cristy4c08aed2011-07-01 19:47:50 +0000322 if ((r == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000323 {
324 status=MagickFalse;
325 continue;
326 }
cristybb503372010-05-27 20:51:26 +0000327 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000328 {
cristy4c08aed2011-07-01 19:47:50 +0000329 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000330 pixel;
331
332 MagickRealType
333 alpha,
334 gamma;
335
336 register const double
cristyc47d1f82009-11-26 01:44:43 +0000337 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000338
cristybb503372010-05-27 20:51:26 +0000339 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000340 i,
341 u,
342 v;
343
344 gamma=0.0;
cristy4c08aed2011-07-01 19:47:50 +0000345 i=(ssize_t) ceil((double) width*QuantumScale*
346 GetPixelIntensity(edge_image,r)-0.5);
cristy3ed852e2009-09-05 21:47:34 +0000347 if (i < 0)
348 i=0;
349 else
cristybb503372010-05-27 20:51:26 +0000350 if (i > (ssize_t) width)
351 i=(ssize_t) width;
cristy3ed852e2009-09-05 21:47:34 +0000352 if ((i & 0x01) != 0)
353 i--;
cristya21afde2010-07-02 00:45:40 +0000354 p=GetCacheViewVirtualPixels(image_view,x-((ssize_t) (width-i)/2L),y-
355 (ssize_t) ((width-i)/2L),width-i,width-i,exception);
cristy4c08aed2011-07-01 19:47:50 +0000356 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000357 break;
cristyddd82202009-11-03 20:14:50 +0000358 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +0000359 k=kernel[i];
cristybb503372010-05-27 20:51:26 +0000360 for (v=0; v < (ssize_t) (width-i); v++)
cristy3ed852e2009-09-05 21:47:34 +0000361 {
cristybb503372010-05-27 20:51:26 +0000362 for (u=0; u < (ssize_t) (width-i); u++)
cristy3ed852e2009-09-05 21:47:34 +0000363 {
364 alpha=1.0;
cristyed231572011-07-14 02:18:59 +0000365 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000366 (image->matte != MagickFalse))
cristy4c08aed2011-07-01 19:47:50 +0000367 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,p));
cristyed231572011-07-14 02:18:59 +0000368 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000369 pixel.red+=(*k)*alpha*GetPixelRed(image,p);
cristyed231572011-07-14 02:18:59 +0000370 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000371 pixel.green+=(*k)*alpha*GetPixelGreen(image,p);
cristyed231572011-07-14 02:18:59 +0000372 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000373 pixel.blue+=(*k)*alpha*GetPixelBlue(image,p);
cristyed231572011-07-14 02:18:59 +0000374 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000375 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +0000376 pixel.black+=(*k)*alpha*GetPixelBlack(image,p);
cristyed231572011-07-14 02:18:59 +0000377 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000378 pixel.alpha+=(*k)*GetPixelAlpha(image,p);
cristy3ed852e2009-09-05 21:47:34 +0000379 gamma+=(*k)*alpha;
380 k++;
cristyed231572011-07-14 02:18:59 +0000381 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000382 }
383 }
384 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +0000385 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000386 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +0000387 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000388 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +0000389 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000390 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +0000391 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000392 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +0000393 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristyed231572011-07-14 02:18:59 +0000394 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000395 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristyed231572011-07-14 02:18:59 +0000396 q+=GetPixelChannels(blur_image);
397 r+=GetPixelChannels(edge_image);
cristy3ed852e2009-09-05 21:47:34 +0000398 }
399 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
400 status=MagickFalse;
401 if (image->progress_monitor != (MagickProgressMonitor) NULL)
402 {
403 MagickBooleanType
404 proceed;
405
cristyb5d5f722009-11-04 03:03:49 +0000406#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy9aa95be2011-07-20 21:56:45 +0000407 #pragma omp critical (MagickCore_AdaptiveSharpenImage)
cristy3ed852e2009-09-05 21:47:34 +0000408#endif
409 proceed=SetImageProgress(image,AdaptiveBlurImageTag,progress++,
410 image->rows);
411 if (proceed == MagickFalse)
412 status=MagickFalse;
413 }
414 }
415 blur_image->type=image->type;
416 blur_view=DestroyCacheView(blur_view);
417 edge_view=DestroyCacheView(edge_view);
418 image_view=DestroyCacheView(image_view);
419 edge_image=DestroyImage(edge_image);
cristybb503372010-05-27 20:51:26 +0000420 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000421 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
422 kernel=(double **) RelinquishMagickMemory(kernel);
423 if (status == MagickFalse)
424 blur_image=DestroyImage(blur_image);
425 return(blur_image);
426}
427
428/*
429%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
430% %
431% %
432% %
433% A d a p t i v e S h a r p e n I m a g e %
434% %
435% %
436% %
437%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
438%
439% AdaptiveSharpenImage() adaptively sharpens the image by sharpening more
440% intensely near image edges and less intensely far from edges. We sharpen the
441% image with a Gaussian operator of the given radius and standard deviation
442% (sigma). For reasonable results, radius should be larger than sigma. Use a
443% radius of 0 and AdaptiveSharpenImage() selects a suitable radius for you.
444%
445% The format of the AdaptiveSharpenImage method is:
446%
447% Image *AdaptiveSharpenImage(const Image *image,const double radius,
448% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000449%
450% A description of each parameter follows:
451%
452% o image: the image.
453%
cristy3ed852e2009-09-05 21:47:34 +0000454% o radius: the radius of the Gaussian, in pixels, not counting the center
455% pixel.
456%
457% o sigma: the standard deviation of the Laplacian, in pixels.
458%
459% o exception: return any errors or warnings in this structure.
460%
461*/
cristy3ed852e2009-09-05 21:47:34 +0000462MagickExport Image *AdaptiveSharpenImage(const Image *image,const double radius,
463 const double sigma,ExceptionInfo *exception)
464{
cristy3ed852e2009-09-05 21:47:34 +0000465#define AdaptiveSharpenImageTag "Convolve/Image"
466#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
467
cristyc4c8d132010-01-07 01:58:38 +0000468 CacheView
469 *sharp_view,
470 *edge_view,
471 *image_view;
472
cristy3ed852e2009-09-05 21:47:34 +0000473 double
cristy47e00502009-12-17 19:19:57 +0000474 **kernel,
475 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000476
477 Image
478 *sharp_image,
479 *edge_image,
480 *gaussian_image;
481
cristy3ed852e2009-09-05 21:47:34 +0000482 MagickBooleanType
483 status;
484
cristybb503372010-05-27 20:51:26 +0000485 MagickOffsetType
486 progress;
487
cristy4c08aed2011-07-01 19:47:50 +0000488 PixelInfo
cristyddd82202009-11-03 20:14:50 +0000489 bias;
cristy3ed852e2009-09-05 21:47:34 +0000490
cristybb503372010-05-27 20:51:26 +0000491 register ssize_t
cristy47e00502009-12-17 19:19:57 +0000492 i;
cristy3ed852e2009-09-05 21:47:34 +0000493
cristybb503372010-05-27 20:51:26 +0000494 size_t
cristy3ed852e2009-09-05 21:47:34 +0000495 width;
496
cristybb503372010-05-27 20:51:26 +0000497 ssize_t
498 j,
499 k,
500 u,
501 v,
502 y;
503
cristy3ed852e2009-09-05 21:47:34 +0000504 assert(image != (const Image *) NULL);
505 assert(image->signature == MagickSignature);
506 if (image->debug != MagickFalse)
507 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
508 assert(exception != (ExceptionInfo *) NULL);
509 assert(exception->signature == MagickSignature);
510 sharp_image=CloneImage(image,0,0,MagickTrue,exception);
511 if (sharp_image == (Image *) NULL)
512 return((Image *) NULL);
513 if (fabs(sigma) <= MagickEpsilon)
514 return(sharp_image);
cristy574cc262011-08-05 01:23:58 +0000515 if (SetImageStorageClass(sharp_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000516 {
cristy3ed852e2009-09-05 21:47:34 +0000517 sharp_image=DestroyImage(sharp_image);
518 return((Image *) NULL);
519 }
520 /*
521 Edge detect the image brighness channel, level, sharp, and level again.
522 */
523 edge_image=EdgeImage(image,radius,exception);
524 if (edge_image == (Image *) NULL)
525 {
526 sharp_image=DestroyImage(sharp_image);
527 return((Image *) NULL);
528 }
cristyf89cb1d2011-07-07 01:24:37 +0000529 (void) AdaptiveLevelImage(edge_image,"20%,95%");
cristy3ed852e2009-09-05 21:47:34 +0000530 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,exception);
531 if (gaussian_image != (Image *) NULL)
532 {
533 edge_image=DestroyImage(edge_image);
534 edge_image=gaussian_image;
535 }
cristyf89cb1d2011-07-07 01:24:37 +0000536 (void) AdaptiveLevelImage(edge_image,"10%,95%");
cristy3ed852e2009-09-05 21:47:34 +0000537 /*
538 Create a set of kernels from maximum (radius,sigma) to minimum.
539 */
540 width=GetOptimalKernelWidth2D(radius,sigma);
541 kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
542 if (kernel == (double **) NULL)
543 {
544 edge_image=DestroyImage(edge_image);
545 sharp_image=DestroyImage(sharp_image);
546 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
547 }
548 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
cristybb503372010-05-27 20:51:26 +0000549 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000550 {
551 kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)*
552 sizeof(**kernel));
553 if (kernel[i] == (double *) NULL)
554 break;
cristy47e00502009-12-17 19:19:57 +0000555 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000556 j=(ssize_t) (width-i)/2;
cristy47e00502009-12-17 19:19:57 +0000557 k=0;
558 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000559 {
cristy47e00502009-12-17 19:19:57 +0000560 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000561 {
cristy4205a3c2010-09-12 20:19:59 +0000562 kernel[i][k]=(double) (-exp(-((double) u*u+v*v)/(2.0*MagickSigma*
563 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +0000564 normalize+=kernel[i][k];
565 k++;
cristy3ed852e2009-09-05 21:47:34 +0000566 }
567 }
cristy3ed852e2009-09-05 21:47:34 +0000568 if (fabs(normalize) <= MagickEpsilon)
569 normalize=1.0;
570 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000571 for (k=0; k < (j*j); k++)
572 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000573 }
cristybb503372010-05-27 20:51:26 +0000574 if (i < (ssize_t) width)
cristy3ed852e2009-09-05 21:47:34 +0000575 {
576 for (i-=2; i >= 0; i-=2)
577 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
578 kernel=(double **) RelinquishMagickMemory(kernel);
579 edge_image=DestroyImage(edge_image);
580 sharp_image=DestroyImage(sharp_image);
581 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
582 }
583 /*
584 Adaptively sharpen image.
585 */
586 status=MagickTrue;
587 progress=0;
cristy4c08aed2011-07-01 19:47:50 +0000588 GetPixelInfo(image,&bias);
589 SetPixelInfoBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000590 image_view=AcquireCacheView(image);
591 edge_view=AcquireCacheView(edge_image);
592 sharp_view=AcquireCacheView(sharp_image);
cristyb5d5f722009-11-04 03:03:49 +0000593#if defined(MAGICKCORE_OPENMP_SUPPORT)
594 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000595#endif
cristybb503372010-05-27 20:51:26 +0000596 for (y=0; y < (ssize_t) sharp_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000597 {
cristy4c08aed2011-07-01 19:47:50 +0000598 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000599 *restrict p,
600 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000601
cristy4c08aed2011-07-01 19:47:50 +0000602 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000603 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000604
cristy117ff172010-08-15 21:35:32 +0000605 register ssize_t
606 x;
607
cristy3ed852e2009-09-05 21:47:34 +0000608 if (status == MagickFalse)
609 continue;
610 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
611 q=QueueCacheViewAuthenticPixels(sharp_view,0,y,sharp_image->columns,1,
612 exception);
cristy4c08aed2011-07-01 19:47:50 +0000613 if ((r == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000614 {
615 status=MagickFalse;
616 continue;
617 }
cristybb503372010-05-27 20:51:26 +0000618 for (x=0; x < (ssize_t) sharp_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000619 {
cristy4c08aed2011-07-01 19:47:50 +0000620 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000621 pixel;
622
623 MagickRealType
624 alpha,
625 gamma;
626
627 register const double
cristyc47d1f82009-11-26 01:44:43 +0000628 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000629
cristybb503372010-05-27 20:51:26 +0000630 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000631 i,
632 u,
633 v;
634
635 gamma=0.0;
cristy4c08aed2011-07-01 19:47:50 +0000636 i=(ssize_t) ceil((double) width*QuantumScale*
637 GetPixelIntensity(edge_image,r)-0.5);
cristy3ed852e2009-09-05 21:47:34 +0000638 if (i < 0)
639 i=0;
640 else
cristybb503372010-05-27 20:51:26 +0000641 if (i > (ssize_t) width)
642 i=(ssize_t) width;
cristy3ed852e2009-09-05 21:47:34 +0000643 if ((i & 0x01) != 0)
644 i--;
cristy117ff172010-08-15 21:35:32 +0000645 p=GetCacheViewVirtualPixels(image_view,x-((ssize_t) (width-i)/2L),y-
646 (ssize_t) ((width-i)/2L),width-i,width-i,exception);
cristy4c08aed2011-07-01 19:47:50 +0000647 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000648 break;
cristy3ed852e2009-09-05 21:47:34 +0000649 k=kernel[i];
cristyddd82202009-11-03 20:14:50 +0000650 pixel=bias;
cristybb503372010-05-27 20:51:26 +0000651 for (v=0; v < (ssize_t) (width-i); v++)
cristy3ed852e2009-09-05 21:47:34 +0000652 {
cristybb503372010-05-27 20:51:26 +0000653 for (u=0; u < (ssize_t) (width-i); u++)
cristy3ed852e2009-09-05 21:47:34 +0000654 {
655 alpha=1.0;
cristyed231572011-07-14 02:18:59 +0000656 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000657 (image->matte != MagickFalse))
cristy4c08aed2011-07-01 19:47:50 +0000658 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,p));
cristyed231572011-07-14 02:18:59 +0000659 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000660 pixel.red+=(*k)*alpha*GetPixelRed(image,p);
cristyed231572011-07-14 02:18:59 +0000661 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000662 pixel.green+=(*k)*alpha*GetPixelGreen(image,p);
cristyed231572011-07-14 02:18:59 +0000663 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000664 pixel.blue+=(*k)*alpha*GetPixelBlue(image,p);
cristyed231572011-07-14 02:18:59 +0000665 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000666 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +0000667 pixel.black+=(*k)*alpha*GetPixelBlack(image,p);
cristyed231572011-07-14 02:18:59 +0000668 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000669 pixel.alpha+=(*k)*GetPixelAlpha(image,p);
cristy3ed852e2009-09-05 21:47:34 +0000670 gamma+=(*k)*alpha;
671 k++;
cristyed231572011-07-14 02:18:59 +0000672 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000673 }
674 }
675 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +0000676 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000677 SetPixelRed(sharp_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +0000678 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000679 SetPixelGreen(sharp_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +0000680 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000681 SetPixelBlue(sharp_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +0000682 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000683 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +0000684 SetPixelBlack(sharp_image,ClampToQuantum(gamma*pixel.black),q);
cristyed231572011-07-14 02:18:59 +0000685 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000686 SetPixelAlpha(sharp_image,ClampToQuantum(pixel.alpha),q);
cristyed231572011-07-14 02:18:59 +0000687 q+=GetPixelChannels(sharp_image);
688 r+=GetPixelChannels(edge_image);
cristy3ed852e2009-09-05 21:47:34 +0000689 }
690 if (SyncCacheViewAuthenticPixels(sharp_view,exception) == MagickFalse)
691 status=MagickFalse;
692 if (image->progress_monitor != (MagickProgressMonitor) NULL)
693 {
694 MagickBooleanType
695 proceed;
696
cristyb5d5f722009-11-04 03:03:49 +0000697#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +0000698 #pragma omp critical (MagickCore_AdaptiveSharpenImage)
cristy3ed852e2009-09-05 21:47:34 +0000699#endif
700 proceed=SetImageProgress(image,AdaptiveSharpenImageTag,progress++,
701 image->rows);
702 if (proceed == MagickFalse)
703 status=MagickFalse;
704 }
705 }
706 sharp_image->type=image->type;
707 sharp_view=DestroyCacheView(sharp_view);
708 edge_view=DestroyCacheView(edge_view);
709 image_view=DestroyCacheView(image_view);
710 edge_image=DestroyImage(edge_image);
cristybb503372010-05-27 20:51:26 +0000711 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000712 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
713 kernel=(double **) RelinquishMagickMemory(kernel);
714 if (status == MagickFalse)
715 sharp_image=DestroyImage(sharp_image);
716 return(sharp_image);
717}
718
719/*
720%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
721% %
722% %
723% %
724% B l u r I m a g e %
725% %
726% %
727% %
728%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
729%
730% BlurImage() blurs an image. We convolve the image with a Gaussian operator
731% of the given radius and standard deviation (sigma). For reasonable results,
732% the radius should be larger than sigma. Use a radius of 0 and BlurImage()
733% selects a suitable radius for you.
734%
735% BlurImage() differs from GaussianBlurImage() in that it uses a separable
736% kernel which is faster but mathematically equivalent to the non-separable
737% kernel.
738%
739% The format of the BlurImage method is:
740%
741% Image *BlurImage(const Image *image,const double radius,
742% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000743%
744% A description of each parameter follows:
745%
746% o image: the image.
747%
cristy3ed852e2009-09-05 21:47:34 +0000748% o radius: the radius of the Gaussian, in pixels, not counting the center
749% pixel.
750%
751% o sigma: the standard deviation of the Gaussian, in pixels.
752%
753% o exception: return any errors or warnings in this structure.
754%
755*/
756
cristybb503372010-05-27 20:51:26 +0000757static double *GetBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +0000758{
cristy3ed852e2009-09-05 21:47:34 +0000759 double
cristy47e00502009-12-17 19:19:57 +0000760 *kernel,
761 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000762
cristy117ff172010-08-15 21:35:32 +0000763 register ssize_t
764 i;
765
cristybb503372010-05-27 20:51:26 +0000766 ssize_t
cristy47e00502009-12-17 19:19:57 +0000767 j,
768 k;
cristy3ed852e2009-09-05 21:47:34 +0000769
cristy3ed852e2009-09-05 21:47:34 +0000770 /*
771 Generate a 1-D convolution kernel.
772 */
773 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
774 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
775 if (kernel == (double *) NULL)
776 return(0);
cristy3ed852e2009-09-05 21:47:34 +0000777 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000778 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +0000779 i=0;
780 for (k=(-j); k <= j; k++)
781 {
cristy4205a3c2010-09-12 20:19:59 +0000782 kernel[i]=(double) (exp(-((double) k*k)/(2.0*MagickSigma*MagickSigma))/
783 (MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +0000784 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +0000785 i++;
786 }
cristybb503372010-05-27 20:51:26 +0000787 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000788 kernel[i]/=normalize;
789 return(kernel);
790}
791
cristyf4ad9df2011-07-08 16:49:03 +0000792MagickExport Image *BlurImage(const Image *image,const double radius,
793 const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000794{
795#define BlurImageTag "Blur/Image"
796
cristyc4c8d132010-01-07 01:58:38 +0000797 CacheView
798 *blur_view,
799 *image_view;
800
cristy3ed852e2009-09-05 21:47:34 +0000801 double
802 *kernel;
803
804 Image
805 *blur_image;
806
cristy3ed852e2009-09-05 21:47:34 +0000807 MagickBooleanType
808 status;
809
cristybb503372010-05-27 20:51:26 +0000810 MagickOffsetType
811 progress;
812
cristy4c08aed2011-07-01 19:47:50 +0000813 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000814 bias;
815
cristybb503372010-05-27 20:51:26 +0000816 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000817 i;
818
cristybb503372010-05-27 20:51:26 +0000819 size_t
cristy3ed852e2009-09-05 21:47:34 +0000820 width;
821
cristybb503372010-05-27 20:51:26 +0000822 ssize_t
823 x,
824 y;
825
cristy3ed852e2009-09-05 21:47:34 +0000826 /*
827 Initialize blur image attributes.
828 */
829 assert(image != (Image *) NULL);
830 assert(image->signature == MagickSignature);
831 if (image->debug != MagickFalse)
832 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
833 assert(exception != (ExceptionInfo *) NULL);
834 assert(exception->signature == MagickSignature);
835 blur_image=CloneImage(image,0,0,MagickTrue,exception);
836 if (blur_image == (Image *) NULL)
837 return((Image *) NULL);
838 if (fabs(sigma) <= MagickEpsilon)
839 return(blur_image);
cristy574cc262011-08-05 01:23:58 +0000840 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000841 {
cristy3ed852e2009-09-05 21:47:34 +0000842 blur_image=DestroyImage(blur_image);
843 return((Image *) NULL);
844 }
845 width=GetOptimalKernelWidth1D(radius,sigma);
846 kernel=GetBlurKernel(width,sigma);
847 if (kernel == (double *) NULL)
848 {
849 blur_image=DestroyImage(blur_image);
850 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
851 }
852 if (image->debug != MagickFalse)
853 {
854 char
855 format[MaxTextExtent],
856 *message;
857
858 register const double
859 *k;
860
861 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +0000862 " BlurImage with %.20g kernel:",(double) width);
cristy3ed852e2009-09-05 21:47:34 +0000863 message=AcquireString("");
864 k=kernel;
cristybb503372010-05-27 20:51:26 +0000865 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000866 {
867 *message='\0';
cristyb51dff52011-05-19 16:55:47 +0000868 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) i);
cristy3ed852e2009-09-05 21:47:34 +0000869 (void) ConcatenateString(&message,format);
cristyb51dff52011-05-19 16:55:47 +0000870 (void) FormatLocaleString(format,MaxTextExtent,"%g ",*k++);
cristy3ed852e2009-09-05 21:47:34 +0000871 (void) ConcatenateString(&message,format);
872 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
873 }
874 message=DestroyString(message);
875 }
876 /*
877 Blur rows.
878 */
879 status=MagickTrue;
880 progress=0;
cristy4c08aed2011-07-01 19:47:50 +0000881 GetPixelInfo(image,&bias);
882 SetPixelInfoBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000883 image_view=AcquireCacheView(image);
884 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000885#if defined(MAGICKCORE_OPENMP_SUPPORT)
886 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000887#endif
cristybb503372010-05-27 20:51:26 +0000888 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000889 {
cristy4c08aed2011-07-01 19:47:50 +0000890 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000891 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000892
cristy4c08aed2011-07-01 19:47:50 +0000893 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000894 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000895
cristy117ff172010-08-15 21:35:32 +0000896 register ssize_t
897 x;
898
cristy3ed852e2009-09-05 21:47:34 +0000899 if (status == MagickFalse)
900 continue;
cristy117ff172010-08-15 21:35:32 +0000901 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y,
902 image->columns+width,1,exception);
cristy3ed852e2009-09-05 21:47:34 +0000903 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
904 exception);
cristy4c08aed2011-07-01 19:47:50 +0000905 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000906 {
907 status=MagickFalse;
908 continue;
909 }
cristybb503372010-05-27 20:51:26 +0000910 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000911 {
cristy4c08aed2011-07-01 19:47:50 +0000912 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000913 pixel;
914
915 register const double
cristyc47d1f82009-11-26 01:44:43 +0000916 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000917
cristy4c08aed2011-07-01 19:47:50 +0000918 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000919 *restrict kernel_pixels;
cristy3ed852e2009-09-05 21:47:34 +0000920
cristybb503372010-05-27 20:51:26 +0000921 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000922 i;
923
cristyddd82202009-11-03 20:14:50 +0000924 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +0000925 k=kernel;
926 kernel_pixels=p;
cristyed231572011-07-14 02:18:59 +0000927 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) ||
cristyf4ad9df2011-07-08 16:49:03 +0000928 (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +0000929 {
cristybb503372010-05-27 20:51:26 +0000930 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000931 {
cristy4c08aed2011-07-01 19:47:50 +0000932 pixel.red+=(*k)*GetPixelRed(image,kernel_pixels);
933 pixel.green+=(*k)*GetPixelGreen(image,kernel_pixels);
934 pixel.blue+=(*k)*GetPixelBlue(image,kernel_pixels);
935 if (image->colorspace == CMYKColorspace)
936 pixel.black+=(*k)*GetPixelBlack(image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +0000937 k++;
cristyed231572011-07-14 02:18:59 +0000938 kernel_pixels+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000939 }
cristyed231572011-07-14 02:18:59 +0000940 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000941 SetPixelRed(blur_image,ClampToQuantum(pixel.red),q);
cristyed231572011-07-14 02:18:59 +0000942 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000943 SetPixelGreen(blur_image,ClampToQuantum(pixel.green),q);
cristyed231572011-07-14 02:18:59 +0000944 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000945 SetPixelBlue(blur_image,ClampToQuantum(pixel.blue),q);
cristyed231572011-07-14 02:18:59 +0000946 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000947 (blur_image->colorspace == CMYKColorspace))
948 SetPixelBlack(blur_image,ClampToQuantum(pixel.black),q);
cristyed231572011-07-14 02:18:59 +0000949 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +0000950 {
951 k=kernel;
952 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +0000953 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000954 {
cristy4c08aed2011-07-01 19:47:50 +0000955 pixel.alpha+=(*k)*GetPixelAlpha(image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +0000956 k++;
cristyed231572011-07-14 02:18:59 +0000957 kernel_pixels+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000958 }
cristy4c08aed2011-07-01 19:47:50 +0000959 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +0000960 }
961 }
962 else
963 {
964 MagickRealType
965 alpha,
966 gamma;
967
968 gamma=0.0;
cristybb503372010-05-27 20:51:26 +0000969 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000970 {
cristy4c08aed2011-07-01 19:47:50 +0000971 alpha=(MagickRealType) (QuantumScale*
972 GetPixelAlpha(image,kernel_pixels));
973 pixel.red+=(*k)*alpha*GetPixelRed(image,kernel_pixels);
974 pixel.green+=(*k)*alpha*GetPixelGreen(image,kernel_pixels);
975 pixel.blue+=(*k)*alpha*GetPixelBlue(image,kernel_pixels);
976 if (image->colorspace == CMYKColorspace)
977 pixel.black+=(*k)*alpha*GetPixelBlack(image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +0000978 gamma+=(*k)*alpha;
979 k++;
cristyed231572011-07-14 02:18:59 +0000980 kernel_pixels+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000981 }
982 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +0000983 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000984 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +0000985 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000986 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +0000987 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000988 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +0000989 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000990 (blur_image->colorspace == CMYKColorspace))
991 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristyed231572011-07-14 02:18:59 +0000992 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +0000993 {
994 k=kernel;
995 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +0000996 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000997 {
cristy4c08aed2011-07-01 19:47:50 +0000998 pixel.alpha+=(*k)*GetPixelAlpha(image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +0000999 k++;
cristyed231572011-07-14 02:18:59 +00001000 kernel_pixels+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001001 }
cristy4c08aed2011-07-01 19:47:50 +00001002 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00001003 }
1004 }
cristyed231572011-07-14 02:18:59 +00001005 p+=GetPixelChannels(image);
1006 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001007 }
1008 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1009 status=MagickFalse;
1010 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1011 {
1012 MagickBooleanType
1013 proceed;
1014
cristyb5d5f722009-11-04 03:03:49 +00001015#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00001016 #pragma omp critical (MagickCore_BlurImage)
cristy3ed852e2009-09-05 21:47:34 +00001017#endif
1018 proceed=SetImageProgress(image,BlurImageTag,progress++,blur_image->rows+
1019 blur_image->columns);
1020 if (proceed == MagickFalse)
1021 status=MagickFalse;
1022 }
1023 }
1024 blur_view=DestroyCacheView(blur_view);
1025 image_view=DestroyCacheView(image_view);
1026 /*
1027 Blur columns.
1028 */
1029 image_view=AcquireCacheView(blur_image);
1030 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00001031#if defined(MAGICKCORE_OPENMP_SUPPORT)
1032 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001033#endif
cristybb503372010-05-27 20:51:26 +00001034 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001035 {
cristy4c08aed2011-07-01 19:47:50 +00001036 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001037 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001038
cristy4c08aed2011-07-01 19:47:50 +00001039 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001040 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001041
cristy117ff172010-08-15 21:35:32 +00001042 register ssize_t
1043 y;
1044
cristy3ed852e2009-09-05 21:47:34 +00001045 if (status == MagickFalse)
1046 continue;
cristy117ff172010-08-15 21:35:32 +00001047 p=GetCacheViewVirtualPixels(image_view,x,-((ssize_t) width/2L),1,
1048 image->rows+width,exception);
cristy3ed852e2009-09-05 21:47:34 +00001049 q=GetCacheViewAuthenticPixels(blur_view,x,0,1,blur_image->rows,exception);
cristy4c08aed2011-07-01 19:47:50 +00001050 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001051 {
1052 status=MagickFalse;
1053 continue;
1054 }
cristybb503372010-05-27 20:51:26 +00001055 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001056 {
cristy4c08aed2011-07-01 19:47:50 +00001057 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001058 pixel;
1059
1060 register const double
cristyc47d1f82009-11-26 01:44:43 +00001061 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00001062
cristy4c08aed2011-07-01 19:47:50 +00001063 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001064 *restrict kernel_pixels;
cristy3ed852e2009-09-05 21:47:34 +00001065
cristybb503372010-05-27 20:51:26 +00001066 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001067 i;
1068
cristyddd82202009-11-03 20:14:50 +00001069 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00001070 k=kernel;
1071 kernel_pixels=p;
cristyed231572011-07-14 02:18:59 +00001072 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) ||
cristyf4ad9df2011-07-08 16:49:03 +00001073 (blur_image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00001074 {
cristybb503372010-05-27 20:51:26 +00001075 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001076 {
cristy4c08aed2011-07-01 19:47:50 +00001077 pixel.red+=(*k)*GetPixelRed(blur_image,kernel_pixels);
1078 pixel.green+=(*k)*GetPixelGreen(blur_image,kernel_pixels);
1079 pixel.blue+=(*k)*GetPixelBlue(blur_image,kernel_pixels);
1080 if (blur_image->colorspace == CMYKColorspace)
1081 pixel.black+=(*k)*GetPixelBlack(blur_image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001082 k++;
cristyed231572011-07-14 02:18:59 +00001083 kernel_pixels+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001084 }
cristyed231572011-07-14 02:18:59 +00001085 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001086 SetPixelRed(blur_image,ClampToQuantum(pixel.red),q);
cristyed231572011-07-14 02:18:59 +00001087 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001088 SetPixelGreen(blur_image,ClampToQuantum(pixel.green),q);
cristyed231572011-07-14 02:18:59 +00001089 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001090 SetPixelBlue(blur_image,ClampToQuantum(pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00001091 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001092 (blur_image->colorspace == CMYKColorspace))
1093 SetPixelBlack(blur_image,ClampToQuantum(pixel.black),q);
cristyed231572011-07-14 02:18:59 +00001094 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001095 {
1096 k=kernel;
1097 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001098 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001099 {
cristy4c08aed2011-07-01 19:47:50 +00001100 pixel.alpha+=(*k)*GetPixelAlpha(blur_image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001101 k++;
cristyed231572011-07-14 02:18:59 +00001102 kernel_pixels+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001103 }
cristy4c08aed2011-07-01 19:47:50 +00001104 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00001105 }
1106 }
1107 else
1108 {
1109 MagickRealType
1110 alpha,
1111 gamma;
1112
1113 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00001114 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001115 {
cristy46f08202010-01-10 04:04:21 +00001116 alpha=(MagickRealType) (QuantumScale*
cristy4c08aed2011-07-01 19:47:50 +00001117 GetPixelAlpha(blur_image,kernel_pixels));
1118 pixel.red+=(*k)*alpha*GetPixelRed(blur_image,kernel_pixels);
1119 pixel.green+=(*k)*alpha*GetPixelGreen(blur_image,kernel_pixels);
1120 pixel.blue+=(*k)*alpha*GetPixelBlue(blur_image,kernel_pixels);
1121 if (blur_image->colorspace == CMYKColorspace)
1122 pixel.black+=(*k)*alpha*GetPixelBlack(blur_image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001123 gamma+=(*k)*alpha;
1124 k++;
cristyed231572011-07-14 02:18:59 +00001125 kernel_pixels+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001126 }
1127 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +00001128 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001129 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +00001130 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001131 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +00001132 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001133 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00001134 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001135 (blur_image->colorspace == CMYKColorspace))
1136 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristyed231572011-07-14 02:18:59 +00001137 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001138 {
1139 k=kernel;
1140 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001141 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001142 {
cristy4c08aed2011-07-01 19:47:50 +00001143 pixel.alpha+=(*k)*GetPixelAlpha(blur_image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001144 k++;
cristyed231572011-07-14 02:18:59 +00001145 kernel_pixels+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001146 }
cristy4c08aed2011-07-01 19:47:50 +00001147 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00001148 }
1149 }
cristyed231572011-07-14 02:18:59 +00001150 p+=GetPixelChannels(blur_image);
1151 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001152 }
1153 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1154 status=MagickFalse;
cristy4c08aed2011-07-01 19:47:50 +00001155 if (blur_image->progress_monitor != (MagickProgressMonitor) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001156 {
1157 MagickBooleanType
1158 proceed;
1159
cristyb5d5f722009-11-04 03:03:49 +00001160#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00001161 #pragma omp critical (MagickCore_BlurImage)
cristy3ed852e2009-09-05 21:47:34 +00001162#endif
cristy4c08aed2011-07-01 19:47:50 +00001163 proceed=SetImageProgress(blur_image,BlurImageTag,progress++,
1164 blur_image->rows+blur_image->columns);
cristy3ed852e2009-09-05 21:47:34 +00001165 if (proceed == MagickFalse)
1166 status=MagickFalse;
1167 }
1168 }
1169 blur_view=DestroyCacheView(blur_view);
1170 image_view=DestroyCacheView(image_view);
1171 kernel=(double *) RelinquishMagickMemory(kernel);
1172 if (status == MagickFalse)
1173 blur_image=DestroyImage(blur_image);
1174 blur_image->type=image->type;
1175 return(blur_image);
1176}
1177
1178/*
1179%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1180% %
1181% %
1182% %
cristyfccdab92009-11-30 16:43:57 +00001183% C o n v o l v e I m a g e %
1184% %
1185% %
1186% %
1187%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1188%
1189% ConvolveImage() applies a custom convolution kernel to the image.
1190%
1191% The format of the ConvolveImage method is:
1192%
cristy5e6be1e2011-07-16 01:23:39 +00001193% Image *ConvolveImage(const Image *image,const KernelInfo *kernel,
1194% ExceptionInfo *exception)
1195%
cristyfccdab92009-11-30 16:43:57 +00001196% A description of each parameter follows:
1197%
1198% o image: the image.
1199%
cristy5e6be1e2011-07-16 01:23:39 +00001200% o kernel: the filtering kernel.
cristyfccdab92009-11-30 16:43:57 +00001201%
1202% o exception: return any errors or warnings in this structure.
1203%
1204*/
cristy5e6be1e2011-07-16 01:23:39 +00001205MagickExport Image *ConvolveImage(const Image *image,
1206 const KernelInfo *kernel_info,ExceptionInfo *exception)
cristyfccdab92009-11-30 16:43:57 +00001207{
cristyfccdab92009-11-30 16:43:57 +00001208#define ConvolveImageTag "Convolve/Image"
1209
cristyc4c8d132010-01-07 01:58:38 +00001210 CacheView
1211 *convolve_view,
cristy105ba3c2011-07-18 02:28:38 +00001212 *image_view;
cristyc4c8d132010-01-07 01:58:38 +00001213
cristyfccdab92009-11-30 16:43:57 +00001214 Image
1215 *convolve_image;
1216
cristyfccdab92009-11-30 16:43:57 +00001217 MagickBooleanType
1218 status;
1219
cristybb503372010-05-27 20:51:26 +00001220 MagickOffsetType
1221 progress;
1222
cristybb503372010-05-27 20:51:26 +00001223 ssize_t
cristy574cc262011-08-05 01:23:58 +00001224 center,
cristybb503372010-05-27 20:51:26 +00001225 y;
1226
cristyfccdab92009-11-30 16:43:57 +00001227 /*
1228 Initialize convolve image attributes.
1229 */
1230 assert(image != (Image *) NULL);
1231 assert(image->signature == MagickSignature);
1232 if (image->debug != MagickFalse)
1233 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1234 assert(exception != (ExceptionInfo *) NULL);
1235 assert(exception->signature == MagickSignature);
cristy5e6be1e2011-07-16 01:23:39 +00001236 if ((kernel_info->width % 2) == 0)
cristyfccdab92009-11-30 16:43:57 +00001237 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
cristy08429172011-07-14 17:18:16 +00001238 convolve_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1239 exception);
cristyfccdab92009-11-30 16:43:57 +00001240 if (convolve_image == (Image *) NULL)
1241 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00001242 if (SetImageStorageClass(convolve_image,DirectClass,exception) == MagickFalse)
cristyfccdab92009-11-30 16:43:57 +00001243 {
cristyfccdab92009-11-30 16:43:57 +00001244 convolve_image=DestroyImage(convolve_image);
1245 return((Image *) NULL);
1246 }
1247 if (image->debug != MagickFalse)
1248 {
1249 char
1250 format[MaxTextExtent],
1251 *message;
1252
cristy117ff172010-08-15 21:35:32 +00001253 register const double
1254 *k;
1255
cristy4e154852011-07-14 13:28:53 +00001256 register ssize_t
1257 u;
1258
cristybb503372010-05-27 20:51:26 +00001259 ssize_t
cristyfccdab92009-11-30 16:43:57 +00001260 v;
1261
cristyfccdab92009-11-30 16:43:57 +00001262 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristy5e6be1e2011-07-16 01:23:39 +00001263 " ConvolveImage with %.20gx%.20g kernel:",(double) kernel_info->width,
1264 (double) kernel_info->height);
cristyfccdab92009-11-30 16:43:57 +00001265 message=AcquireString("");
cristy5e6be1e2011-07-16 01:23:39 +00001266 k=kernel_info->values;
1267 for (v=0; v < (ssize_t) kernel_info->width; v++)
cristyfccdab92009-11-30 16:43:57 +00001268 {
1269 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00001270 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristyfccdab92009-11-30 16:43:57 +00001271 (void) ConcatenateString(&message,format);
cristy5e6be1e2011-07-16 01:23:39 +00001272 for (u=0; u < (ssize_t) kernel_info->height; u++)
cristyfccdab92009-11-30 16:43:57 +00001273 {
cristyb51dff52011-05-19 16:55:47 +00001274 (void) FormatLocaleString(format,MaxTextExtent,"%g ",*k++);
cristyfccdab92009-11-30 16:43:57 +00001275 (void) ConcatenateString(&message,format);
1276 }
1277 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
1278 }
1279 message=DestroyString(message);
1280 }
1281 /*
cristyfccdab92009-11-30 16:43:57 +00001282 Convolve image.
1283 */
cristy574cc262011-08-05 01:23:58 +00001284 center=(ssize_t) GetPixelChannels(image)*(image->columns+kernel_info->width)*
1285 (kernel_info->height/2L)+GetPixelChannels(image)*(kernel_info->width/2);
cristyfccdab92009-11-30 16:43:57 +00001286 status=MagickTrue;
1287 progress=0;
cristyfccdab92009-11-30 16:43:57 +00001288 image_view=AcquireCacheView(image);
1289 convolve_view=AcquireCacheView(convolve_image);
1290#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy175653e2011-07-10 23:13:34 +00001291 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristyfccdab92009-11-30 16:43:57 +00001292#endif
cristybb503372010-05-27 20:51:26 +00001293 for (y=0; y < (ssize_t) image->rows; y++)
cristyfccdab92009-11-30 16:43:57 +00001294 {
cristy4c08aed2011-07-01 19:47:50 +00001295 register const Quantum
cristy105ba3c2011-07-18 02:28:38 +00001296 *restrict p;
cristyfccdab92009-11-30 16:43:57 +00001297
cristy4c08aed2011-07-01 19:47:50 +00001298 register Quantum
cristyfccdab92009-11-30 16:43:57 +00001299 *restrict q;
1300
cristy117ff172010-08-15 21:35:32 +00001301 register ssize_t
1302 x;
1303
cristyfccdab92009-11-30 16:43:57 +00001304 if (status == MagickFalse)
1305 continue;
cristy105ba3c2011-07-18 02:28:38 +00001306 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) kernel_info->width/2L),y-
1307 (ssize_t) (kernel_info->height/2L),image->columns+kernel_info->width,
1308 kernel_info->height,exception);
cristy08429172011-07-14 17:18:16 +00001309 q=QueueCacheViewAuthenticPixels(convolve_view,0,y,convolve_image->columns,1,
cristyfccdab92009-11-30 16:43:57 +00001310 exception);
cristy105ba3c2011-07-18 02:28:38 +00001311 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristyfccdab92009-11-30 16:43:57 +00001312 {
1313 status=MagickFalse;
1314 continue;
1315 }
cristybb503372010-05-27 20:51:26 +00001316 for (x=0; x < (ssize_t) image->columns; x++)
cristyfccdab92009-11-30 16:43:57 +00001317 {
cristybb503372010-05-27 20:51:26 +00001318 register ssize_t
cristyed231572011-07-14 02:18:59 +00001319 i;
cristyfccdab92009-11-30 16:43:57 +00001320
cristya30d9ba2011-07-23 21:00:48 +00001321 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristyed231572011-07-14 02:18:59 +00001322 {
cristyed231572011-07-14 02:18:59 +00001323 MagickRealType
cristy4e154852011-07-14 13:28:53 +00001324 alpha,
1325 gamma,
cristyed231572011-07-14 02:18:59 +00001326 pixel;
1327
1328 PixelChannel
1329 channel;
1330
1331 PixelTrait
1332 convolve_traits,
1333 traits;
1334
1335 register const double
1336 *restrict k;
1337
1338 register const Quantum
cristyeb52cde2011-07-17 01:52:52 +00001339 *restrict pixels;
cristyed231572011-07-14 02:18:59 +00001340
1341 register ssize_t
1342 u;
1343
1344 ssize_t
1345 v;
1346
cristy30301712011-07-18 15:06:51 +00001347 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
cristy4e154852011-07-14 13:28:53 +00001348 if (traits == UndefinedPixelTrait)
cristyed231572011-07-14 02:18:59 +00001349 continue;
cristy30301712011-07-18 15:06:51 +00001350 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
cristy4e154852011-07-14 13:28:53 +00001351 convolve_traits=GetPixelChannelMapTraits(convolve_image,channel);
1352 if (convolve_traits == UndefinedPixelTrait)
1353 continue;
1354 if ((convolve_traits & CopyPixelTrait) != 0)
1355 {
cristyf7dc44c2011-07-20 14:41:15 +00001356 q[channel]=p[center+i];
cristy4e154852011-07-14 13:28:53 +00001357 continue;
1358 }
cristy5e6be1e2011-07-16 01:23:39 +00001359 k=kernel_info->values;
cristy105ba3c2011-07-18 02:28:38 +00001360 pixels=p;
cristy0a922382011-07-16 15:30:34 +00001361 pixel=kernel_info->bias;
cristy222b19c2011-08-04 01:35:11 +00001362 if ((convolve_traits & BlendPixelTrait) == 0)
cristyfccdab92009-11-30 16:43:57 +00001363 {
cristyed231572011-07-14 02:18:59 +00001364 /*
cristy4e154852011-07-14 13:28:53 +00001365 No alpha blending.
cristyed231572011-07-14 02:18:59 +00001366 */
cristyeb52cde2011-07-17 01:52:52 +00001367 for (v=0; v < (ssize_t) kernel_info->height; v++)
cristyfccdab92009-11-30 16:43:57 +00001368 {
cristyeb52cde2011-07-17 01:52:52 +00001369 for (u=0; u < (ssize_t) kernel_info->width; u++)
cristy175653e2011-07-10 23:13:34 +00001370 {
cristyeb52cde2011-07-17 01:52:52 +00001371 pixel+=(*k)*pixels[i];
cristyed231572011-07-14 02:18:59 +00001372 k++;
cristya30d9ba2011-07-23 21:00:48 +00001373 pixels+=GetPixelChannels(image);
cristy175653e2011-07-10 23:13:34 +00001374 }
cristya30d9ba2011-07-23 21:00:48 +00001375 pixels+=image->columns*GetPixelChannels(image);
cristy175653e2011-07-10 23:13:34 +00001376 }
cristyf7dc44c2011-07-20 14:41:15 +00001377 q[channel]=ClampToQuantum(pixel);
cristy4e154852011-07-14 13:28:53 +00001378 continue;
cristyed231572011-07-14 02:18:59 +00001379 }
cristy4e154852011-07-14 13:28:53 +00001380 /*
1381 Alpha blending.
1382 */
1383 gamma=0.0;
cristyeb52cde2011-07-17 01:52:52 +00001384 for (v=0; v < (ssize_t) kernel_info->height; v++)
cristy4e154852011-07-14 13:28:53 +00001385 {
cristyeb52cde2011-07-17 01:52:52 +00001386 for (u=0; u < (ssize_t) kernel_info->width; u++)
cristy4e154852011-07-14 13:28:53 +00001387 {
cristyeb52cde2011-07-17 01:52:52 +00001388 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,pixels));
1389 pixel+=(*k)*alpha*pixels[i];
cristy4e154852011-07-14 13:28:53 +00001390 gamma+=(*k)*alpha;
1391 k++;
cristya30d9ba2011-07-23 21:00:48 +00001392 pixels+=GetPixelChannels(image);
cristy4e154852011-07-14 13:28:53 +00001393 }
cristya30d9ba2011-07-23 21:00:48 +00001394 pixels+=image->columns*GetPixelChannels(image);
cristy4e154852011-07-14 13:28:53 +00001395 }
cristy1ce96d02011-07-14 17:57:24 +00001396 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristye7a41c92011-07-20 21:31:01 +00001397 q[channel]=ClampToQuantum(gamma*pixel);
cristyed231572011-07-14 02:18:59 +00001398 }
cristya30d9ba2011-07-23 21:00:48 +00001399 p+=GetPixelChannels(image);
1400 q+=GetPixelChannels(convolve_image);
cristyfccdab92009-11-30 16:43:57 +00001401 }
cristyed231572011-07-14 02:18:59 +00001402 if (SyncCacheViewAuthenticPixels(convolve_view,exception) == MagickFalse)
cristyfccdab92009-11-30 16:43:57 +00001403 status=MagickFalse;
1404 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1405 {
1406 MagickBooleanType
1407 proceed;
1408
1409#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00001410 #pragma omp critical (MagickCore_ConvolveImage)
cristyfccdab92009-11-30 16:43:57 +00001411#endif
1412 proceed=SetImageProgress(image,ConvolveImageTag,progress++,image->rows);
1413 if (proceed == MagickFalse)
1414 status=MagickFalse;
1415 }
1416 }
1417 convolve_image->type=image->type;
1418 convolve_view=DestroyCacheView(convolve_view);
1419 image_view=DestroyCacheView(image_view);
cristyfccdab92009-11-30 16:43:57 +00001420 if (status == MagickFalse)
1421 convolve_image=DestroyImage(convolve_image);
1422 return(convolve_image);
1423}
1424
1425/*
1426%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1427% %
1428% %
1429% %
cristy3ed852e2009-09-05 21:47:34 +00001430% D e s p e c k l e I m a g e %
1431% %
1432% %
1433% %
1434%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1435%
1436% DespeckleImage() reduces the speckle noise in an image while perserving the
1437% edges of the original image.
1438%
1439% The format of the DespeckleImage method is:
1440%
1441% Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1442%
1443% A description of each parameter follows:
1444%
1445% o image: the image.
1446%
1447% o exception: return any errors or warnings in this structure.
1448%
1449*/
1450
cristybb503372010-05-27 20:51:26 +00001451static void Hull(const ssize_t x_offset,const ssize_t y_offset,
1452 const size_t columns,const size_t rows,Quantum *f,Quantum *g,
cristy3ed852e2009-09-05 21:47:34 +00001453 const int polarity)
1454{
cristy3ed852e2009-09-05 21:47:34 +00001455 MagickRealType
1456 v;
1457
cristy3ed852e2009-09-05 21:47:34 +00001458 register Quantum
1459 *p,
1460 *q,
1461 *r,
1462 *s;
1463
cristy117ff172010-08-15 21:35:32 +00001464 register ssize_t
1465 x;
1466
1467 ssize_t
1468 y;
1469
cristy3ed852e2009-09-05 21:47:34 +00001470 assert(f != (Quantum *) NULL);
1471 assert(g != (Quantum *) NULL);
1472 p=f+(columns+2);
1473 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001474 r=p+(y_offset*((ssize_t) columns+2)+x_offset);
1475 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001476 {
1477 p++;
1478 q++;
1479 r++;
1480 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001481 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001482 {
1483 v=(MagickRealType) (*p);
1484 if ((MagickRealType) *r >= (v+(MagickRealType) ScaleCharToQuantum(2)))
1485 v+=ScaleCharToQuantum(1);
1486 *q=(Quantum) v;
1487 p++;
1488 q++;
1489 r++;
1490 }
1491 else
cristybb503372010-05-27 20:51:26 +00001492 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001493 {
1494 v=(MagickRealType) (*p);
1495 if ((MagickRealType) *r <= (v-(MagickRealType) ScaleCharToQuantum(2)))
cristybb503372010-05-27 20:51:26 +00001496 v-=(ssize_t) ScaleCharToQuantum(1);
cristy3ed852e2009-09-05 21:47:34 +00001497 *q=(Quantum) v;
1498 p++;
1499 q++;
1500 r++;
1501 }
1502 p++;
1503 q++;
1504 r++;
1505 }
1506 p=f+(columns+2);
1507 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001508 r=q+(y_offset*((ssize_t) columns+2)+x_offset);
1509 s=q-(y_offset*((ssize_t) columns+2)+x_offset);
1510 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001511 {
1512 p++;
1513 q++;
1514 r++;
1515 s++;
1516 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001517 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001518 {
1519 v=(MagickRealType) (*q);
1520 if (((MagickRealType) *s >=
1521 (v+(MagickRealType) ScaleCharToQuantum(2))) &&
1522 ((MagickRealType) *r > v))
1523 v+=ScaleCharToQuantum(1);
1524 *p=(Quantum) v;
1525 p++;
1526 q++;
1527 r++;
1528 s++;
1529 }
1530 else
cristybb503372010-05-27 20:51:26 +00001531 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001532 {
1533 v=(MagickRealType) (*q);
1534 if (((MagickRealType) *s <=
1535 (v-(MagickRealType) ScaleCharToQuantum(2))) &&
1536 ((MagickRealType) *r < v))
1537 v-=(MagickRealType) ScaleCharToQuantum(1);
1538 *p=(Quantum) v;
1539 p++;
1540 q++;
1541 r++;
1542 s++;
1543 }
1544 p++;
1545 q++;
1546 r++;
1547 s++;
1548 }
1549}
1550
1551MagickExport Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1552{
1553#define DespeckleImageTag "Despeckle/Image"
1554
cristy2407fc22009-09-11 00:55:25 +00001555 CacheView
1556 *despeckle_view,
1557 *image_view;
1558
cristy3ed852e2009-09-05 21:47:34 +00001559 Image
1560 *despeckle_image;
1561
cristy3ed852e2009-09-05 21:47:34 +00001562 MagickBooleanType
1563 status;
1564
cristya58c3172011-02-19 19:23:11 +00001565 register ssize_t
1566 i;
1567
cristy3ed852e2009-09-05 21:47:34 +00001568 Quantum
cristy65b9f392011-02-22 14:22:54 +00001569 *restrict buffers,
1570 *restrict pixels;
cristy3ed852e2009-09-05 21:47:34 +00001571
1572 size_t
cristya58c3172011-02-19 19:23:11 +00001573 length,
1574 number_channels;
cristy117ff172010-08-15 21:35:32 +00001575
cristybb503372010-05-27 20:51:26 +00001576 static const ssize_t
cristy691a29e2009-09-11 00:44:10 +00001577 X[4] = {0, 1, 1,-1},
1578 Y[4] = {1, 0, 1, 1};
cristy3ed852e2009-09-05 21:47:34 +00001579
cristy3ed852e2009-09-05 21:47:34 +00001580 /*
1581 Allocate despeckled image.
1582 */
1583 assert(image != (const Image *) NULL);
1584 assert(image->signature == MagickSignature);
1585 if (image->debug != MagickFalse)
1586 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1587 assert(exception != (ExceptionInfo *) NULL);
1588 assert(exception->signature == MagickSignature);
1589 despeckle_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1590 exception);
1591 if (despeckle_image == (Image *) NULL)
1592 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00001593 if (SetImageStorageClass(despeckle_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001594 {
cristy3ed852e2009-09-05 21:47:34 +00001595 despeckle_image=DestroyImage(despeckle_image);
1596 return((Image *) NULL);
1597 }
1598 /*
1599 Allocate image buffers.
1600 */
1601 length=(size_t) ((image->columns+2)*(image->rows+2));
cristy65b9f392011-02-22 14:22:54 +00001602 pixels=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1603 buffers=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1604 if ((pixels == (Quantum *) NULL) || (buffers == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001605 {
cristy65b9f392011-02-22 14:22:54 +00001606 if (buffers != (Quantum *) NULL)
1607 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1608 if (pixels != (Quantum *) NULL)
1609 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001610 despeckle_image=DestroyImage(despeckle_image);
1611 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1612 }
1613 /*
1614 Reduce speckle in the image.
1615 */
1616 status=MagickTrue;
cristy109695a2011-02-19 19:38:14 +00001617 number_channels=(size_t) (image->colorspace == CMYKColorspace ? 5 : 4);
cristy3ed852e2009-09-05 21:47:34 +00001618 image_view=AcquireCacheView(image);
1619 despeckle_view=AcquireCacheView(despeckle_image);
cristy8df3d002011-02-19 19:40:59 +00001620 for (i=0; i < (ssize_t) number_channels; i++)
cristy3ed852e2009-09-05 21:47:34 +00001621 {
cristy3ed852e2009-09-05 21:47:34 +00001622 register Quantum
1623 *buffer,
1624 *pixel;
1625
cristyc1488b52011-02-19 18:54:15 +00001626 register ssize_t
cristya58c3172011-02-19 19:23:11 +00001627 k,
cristyc1488b52011-02-19 18:54:15 +00001628 x;
1629
cristy117ff172010-08-15 21:35:32 +00001630 ssize_t
1631 j,
1632 y;
1633
cristy3ed852e2009-09-05 21:47:34 +00001634 if (status == MagickFalse)
1635 continue;
cristy65b9f392011-02-22 14:22:54 +00001636 pixel=pixels;
cristy3ed852e2009-09-05 21:47:34 +00001637 (void) ResetMagickMemory(pixel,0,length*sizeof(*pixel));
cristy65b9f392011-02-22 14:22:54 +00001638 buffer=buffers;
cristybb503372010-05-27 20:51:26 +00001639 j=(ssize_t) image->columns+2;
1640 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001641 {
cristy4c08aed2011-07-01 19:47:50 +00001642 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001643 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001644
1645 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001646 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001647 break;
1648 j++;
cristybb503372010-05-27 20:51:26 +00001649 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001650 {
cristya58c3172011-02-19 19:23:11 +00001651 switch (i)
cristy3ed852e2009-09-05 21:47:34 +00001652 {
cristy4c08aed2011-07-01 19:47:50 +00001653 case 0: pixel[j]=GetPixelRed(image,p); break;
1654 case 1: pixel[j]=GetPixelGreen(image,p); break;
1655 case 2: pixel[j]=GetPixelBlue(image,p); break;
1656 case 3: pixel[j]=GetPixelAlpha(image,p); break;
1657 case 4: pixel[j]=GetPixelBlack(image,p); break;
cristy3ed852e2009-09-05 21:47:34 +00001658 default: break;
1659 }
cristyed231572011-07-14 02:18:59 +00001660 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001661 j++;
1662 }
1663 j++;
1664 }
cristy3ed852e2009-09-05 21:47:34 +00001665 (void) ResetMagickMemory(buffer,0,length*sizeof(*buffer));
cristya58c3172011-02-19 19:23:11 +00001666 for (k=0; k < 4; k++)
cristy3ed852e2009-09-05 21:47:34 +00001667 {
cristya58c3172011-02-19 19:23:11 +00001668 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,1);
1669 Hull(-X[k],-Y[k],image->columns,image->rows,pixel,buffer,1);
1670 Hull(-X[k],-Y[k],image->columns,image->rows,pixel,buffer,-1);
1671 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,-1);
cristy3ed852e2009-09-05 21:47:34 +00001672 }
cristybb503372010-05-27 20:51:26 +00001673 j=(ssize_t) image->columns+2;
1674 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001675 {
1676 MagickBooleanType
1677 sync;
1678
cristy4c08aed2011-07-01 19:47:50 +00001679 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001680 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001681
1682 q=GetCacheViewAuthenticPixels(despeckle_view,0,y,despeckle_image->columns,
1683 1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001684 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001685 break;
1686 j++;
cristybb503372010-05-27 20:51:26 +00001687 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001688 {
cristya58c3172011-02-19 19:23:11 +00001689 switch (i)
cristy3ed852e2009-09-05 21:47:34 +00001690 {
cristy4c08aed2011-07-01 19:47:50 +00001691 case 0: SetPixelRed(despeckle_image,pixel[j],q); break;
1692 case 1: SetPixelGreen(despeckle_image,pixel[j],q); break;
1693 case 2: SetPixelBlue(despeckle_image,pixel[j],q); break;
1694 case 3: SetPixelAlpha(despeckle_image,pixel[j],q); break;
1695 case 4: SetPixelBlack(despeckle_image,pixel[j],q); break;
cristy3ed852e2009-09-05 21:47:34 +00001696 default: break;
1697 }
cristyed231572011-07-14 02:18:59 +00001698 q+=GetPixelChannels(despeckle_image);
cristy3ed852e2009-09-05 21:47:34 +00001699 j++;
1700 }
1701 sync=SyncCacheViewAuthenticPixels(despeckle_view,exception);
1702 if (sync == MagickFalse)
1703 {
1704 status=MagickFalse;
1705 break;
1706 }
1707 j++;
1708 }
1709 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1710 {
1711 MagickBooleanType
1712 proceed;
1713
cristya58c3172011-02-19 19:23:11 +00001714 proceed=SetImageProgress(image,DespeckleImageTag,(MagickOffsetType) i,
1715 number_channels);
cristy3ed852e2009-09-05 21:47:34 +00001716 if (proceed == MagickFalse)
1717 status=MagickFalse;
1718 }
1719 }
1720 despeckle_view=DestroyCacheView(despeckle_view);
1721 image_view=DestroyCacheView(image_view);
cristy65b9f392011-02-22 14:22:54 +00001722 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1723 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001724 despeckle_image->type=image->type;
1725 if (status == MagickFalse)
1726 despeckle_image=DestroyImage(despeckle_image);
1727 return(despeckle_image);
1728}
1729
1730/*
1731%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1732% %
1733% %
1734% %
1735% E d g e I m a g e %
1736% %
1737% %
1738% %
1739%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1740%
1741% EdgeImage() finds edges in an image. Radius defines the radius of the
1742% convolution filter. Use a radius of 0 and EdgeImage() selects a suitable
1743% radius for you.
1744%
1745% The format of the EdgeImage method is:
1746%
1747% Image *EdgeImage(const Image *image,const double radius,
1748% ExceptionInfo *exception)
1749%
1750% A description of each parameter follows:
1751%
1752% o image: the image.
1753%
1754% o radius: the radius of the pixel neighborhood.
1755%
1756% o exception: return any errors or warnings in this structure.
1757%
1758*/
1759MagickExport Image *EdgeImage(const Image *image,const double radius,
1760 ExceptionInfo *exception)
1761{
1762 Image
1763 *edge_image;
1764
cristy41cbe682011-07-15 19:12:37 +00001765 KernelInfo
1766 *kernel_info;
cristy3ed852e2009-09-05 21:47:34 +00001767
cristybb503372010-05-27 20:51:26 +00001768 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001769 i;
1770
cristybb503372010-05-27 20:51:26 +00001771 size_t
cristy3ed852e2009-09-05 21:47:34 +00001772 width;
1773
cristy41cbe682011-07-15 19:12:37 +00001774 ssize_t
1775 j,
1776 u,
1777 v;
1778
cristy3ed852e2009-09-05 21:47:34 +00001779 assert(image != (const Image *) NULL);
1780 assert(image->signature == MagickSignature);
1781 if (image->debug != MagickFalse)
1782 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1783 assert(exception != (ExceptionInfo *) NULL);
1784 assert(exception->signature == MagickSignature);
1785 width=GetOptimalKernelWidth1D(radius,0.5);
cristy5e6be1e2011-07-16 01:23:39 +00001786 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00001787 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001788 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00001789 kernel_info->width=width;
1790 kernel_info->height=width;
cristy41cbe682011-07-15 19:12:37 +00001791 kernel_info->values=(double *) AcquireAlignedMemory(kernel_info->width,
1792 kernel_info->width*sizeof(*kernel_info->values));
1793 if (kernel_info->values == (double *) NULL)
1794 {
1795 kernel_info=DestroyKernelInfo(kernel_info);
1796 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1797 }
1798 j=(ssize_t) kernel_info->width/2;
1799 i=0;
1800 for (v=(-j); v <= j; v++)
1801 {
1802 for (u=(-j); u <= j; u++)
1803 {
1804 kernel_info->values[i]=(-1.0);
1805 i++;
1806 }
1807 }
1808 kernel_info->values[i/2]=(double) (width*width-1.0);
cristy0a922382011-07-16 15:30:34 +00001809 kernel_info->bias=image->bias;
cristy5e6be1e2011-07-16 01:23:39 +00001810 edge_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00001811 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00001812 return(edge_image);
1813}
1814
1815/*
1816%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1817% %
1818% %
1819% %
1820% E m b o s s I m a g e %
1821% %
1822% %
1823% %
1824%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1825%
1826% EmbossImage() returns a grayscale image with a three-dimensional effect.
1827% We convolve the image with a Gaussian operator of the given radius and
1828% standard deviation (sigma). For reasonable results, radius should be
1829% larger than sigma. Use a radius of 0 and Emboss() selects a suitable
1830% radius for you.
1831%
1832% The format of the EmbossImage method is:
1833%
1834% Image *EmbossImage(const Image *image,const double radius,
1835% const double sigma,ExceptionInfo *exception)
1836%
1837% A description of each parameter follows:
1838%
1839% o image: the image.
1840%
1841% o radius: the radius of the pixel neighborhood.
1842%
1843% o sigma: the standard deviation of the Gaussian, in pixels.
1844%
1845% o exception: return any errors or warnings in this structure.
1846%
1847*/
1848MagickExport Image *EmbossImage(const Image *image,const double radius,
1849 const double sigma,ExceptionInfo *exception)
1850{
cristy3ed852e2009-09-05 21:47:34 +00001851 Image
1852 *emboss_image;
1853
cristy41cbe682011-07-15 19:12:37 +00001854 KernelInfo
1855 *kernel_info;
1856
cristybb503372010-05-27 20:51:26 +00001857 register ssize_t
cristy47e00502009-12-17 19:19:57 +00001858 i;
1859
cristybb503372010-05-27 20:51:26 +00001860 size_t
cristy3ed852e2009-09-05 21:47:34 +00001861 width;
1862
cristy117ff172010-08-15 21:35:32 +00001863 ssize_t
1864 j,
1865 k,
1866 u,
1867 v;
1868
cristy41cbe682011-07-15 19:12:37 +00001869 assert(image != (const Image *) NULL);
cristy3ed852e2009-09-05 21:47:34 +00001870 assert(image->signature == MagickSignature);
1871 if (image->debug != MagickFalse)
1872 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1873 assert(exception != (ExceptionInfo *) NULL);
1874 assert(exception->signature == MagickSignature);
1875 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00001876 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00001877 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001878 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00001879 kernel_info->width=width;
1880 kernel_info->height=width;
cristy41cbe682011-07-15 19:12:37 +00001881 kernel_info->values=(double *) AcquireAlignedMemory(kernel_info->width,
1882 kernel_info->width*sizeof(*kernel_info->values));
1883 if (kernel_info->values == (double *) NULL)
1884 {
1885 kernel_info=DestroyKernelInfo(kernel_info);
1886 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1887 }
1888 j=(ssize_t) kernel_info->width/2;
cristy47e00502009-12-17 19:19:57 +00001889 k=j;
1890 i=0;
1891 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00001892 {
cristy47e00502009-12-17 19:19:57 +00001893 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00001894 {
cristy41cbe682011-07-15 19:12:37 +00001895 kernel_info->values[i]=(double) (((u < 0) || (v < 0) ? -8.0 : 8.0)*
cristy47e00502009-12-17 19:19:57 +00001896 exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
cristy4205a3c2010-09-12 20:19:59 +00001897 (2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +00001898 if (u != k)
cristy41cbe682011-07-15 19:12:37 +00001899 kernel_info->values[i]=0.0;
cristy3ed852e2009-09-05 21:47:34 +00001900 i++;
1901 }
cristy47e00502009-12-17 19:19:57 +00001902 k--;
cristy3ed852e2009-09-05 21:47:34 +00001903 }
cristy0a922382011-07-16 15:30:34 +00001904 kernel_info->bias=image->bias;
cristy5e6be1e2011-07-16 01:23:39 +00001905 emboss_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00001906 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00001907 if (emboss_image != (Image *) NULL)
cristy6d8c3d72011-08-22 01:20:01 +00001908 (void) EqualizeImage(emboss_image,exception);
cristy3ed852e2009-09-05 21:47:34 +00001909 return(emboss_image);
1910}
1911
1912/*
1913%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1914% %
1915% %
1916% %
1917% G a u s s i a n B l u r I m a g e %
1918% %
1919% %
1920% %
1921%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1922%
1923% GaussianBlurImage() blurs an image. We convolve the image with a
1924% Gaussian operator of the given radius and standard deviation (sigma).
1925% For reasonable results, the radius should be larger than sigma. Use a
1926% radius of 0 and GaussianBlurImage() selects a suitable radius for you
1927%
1928% The format of the GaussianBlurImage method is:
1929%
1930% Image *GaussianBlurImage(const Image *image,onst double radius,
1931% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001932%
1933% A description of each parameter follows:
1934%
1935% o image: the image.
1936%
cristy3ed852e2009-09-05 21:47:34 +00001937% o radius: the radius of the Gaussian, in pixels, not counting the center
1938% pixel.
1939%
1940% o sigma: the standard deviation of the Gaussian, in pixels.
1941%
1942% o exception: return any errors or warnings in this structure.
1943%
1944*/
cristy41cbe682011-07-15 19:12:37 +00001945MagickExport Image *GaussianBlurImage(const Image *image,const double radius,
1946 const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001947{
cristy3ed852e2009-09-05 21:47:34 +00001948 Image
1949 *blur_image;
1950
cristy41cbe682011-07-15 19:12:37 +00001951 KernelInfo
1952 *kernel_info;
1953
cristybb503372010-05-27 20:51:26 +00001954 register ssize_t
cristy47e00502009-12-17 19:19:57 +00001955 i;
1956
cristybb503372010-05-27 20:51:26 +00001957 size_t
cristy3ed852e2009-09-05 21:47:34 +00001958 width;
1959
cristy117ff172010-08-15 21:35:32 +00001960 ssize_t
1961 j,
1962 u,
1963 v;
1964
cristy3ed852e2009-09-05 21:47:34 +00001965 assert(image != (const Image *) NULL);
1966 assert(image->signature == MagickSignature);
1967 if (image->debug != MagickFalse)
1968 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1969 assert(exception != (ExceptionInfo *) NULL);
1970 assert(exception->signature == MagickSignature);
1971 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00001972 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00001973 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001974 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00001975 (void) ResetMagickMemory(kernel_info,0,sizeof(*kernel_info));
1976 kernel_info->width=width;
1977 kernel_info->height=width;
1978 kernel_info->signature=MagickSignature;
1979 kernel_info->values=(double *) AcquireAlignedMemory(kernel_info->width,
1980 kernel_info->width*sizeof(*kernel_info->values));
1981 if (kernel_info->values == (double *) NULL)
1982 {
1983 kernel_info=DestroyKernelInfo(kernel_info);
1984 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1985 }
1986 j=(ssize_t) kernel_info->width/2;
cristy3ed852e2009-09-05 21:47:34 +00001987 i=0;
cristy47e00502009-12-17 19:19:57 +00001988 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00001989 {
cristy47e00502009-12-17 19:19:57 +00001990 for (u=(-j); u <= j; u++)
cristy41cbe682011-07-15 19:12:37 +00001991 {
1992 kernel_info->values[i]=(double) (exp(-((double) u*u+v*v)/(2.0*
1993 MagickSigma*MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
1994 i++;
1995 }
cristy3ed852e2009-09-05 21:47:34 +00001996 }
cristy0a922382011-07-16 15:30:34 +00001997 kernel_info->bias=image->bias;
cristy5e6be1e2011-07-16 01:23:39 +00001998 blur_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00001999 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00002000 return(blur_image);
2001}
2002
2003/*
2004%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2005% %
2006% %
2007% %
cristy3ed852e2009-09-05 21:47:34 +00002008% M o t i o n B l u r I m a g e %
2009% %
2010% %
2011% %
2012%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2013%
2014% MotionBlurImage() simulates motion blur. We convolve the image with a
2015% Gaussian operator of the given radius and standard deviation (sigma).
2016% For reasonable results, radius should be larger than sigma. Use a
2017% radius of 0 and MotionBlurImage() selects a suitable radius for you.
2018% Angle gives the angle of the blurring motion.
2019%
2020% Andrew Protano contributed this effect.
2021%
2022% The format of the MotionBlurImage method is:
2023%
2024% Image *MotionBlurImage(const Image *image,const double radius,
2025% const double sigma,const double angle,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002026%
2027% A description of each parameter follows:
2028%
2029% o image: the image.
2030%
cristy3ed852e2009-09-05 21:47:34 +00002031% o radius: the radius of the Gaussian, in pixels, not counting
2032% the center pixel.
2033%
2034% o sigma: the standard deviation of the Gaussian, in pixels.
2035%
cristycee97112010-05-28 00:44:52 +00002036% o angle: Apply the effect along this angle.
cristy3ed852e2009-09-05 21:47:34 +00002037%
2038% o exception: return any errors or warnings in this structure.
2039%
2040*/
2041
cristybb503372010-05-27 20:51:26 +00002042static double *GetMotionBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +00002043{
cristy3ed852e2009-09-05 21:47:34 +00002044 double
cristy47e00502009-12-17 19:19:57 +00002045 *kernel,
cristy3ed852e2009-09-05 21:47:34 +00002046 normalize;
2047
cristybb503372010-05-27 20:51:26 +00002048 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002049 i;
2050
2051 /*
cristy47e00502009-12-17 19:19:57 +00002052 Generate a 1-D convolution kernel.
cristy3ed852e2009-09-05 21:47:34 +00002053 */
2054 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
2055 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
2056 if (kernel == (double *) NULL)
2057 return(kernel);
cristy3ed852e2009-09-05 21:47:34 +00002058 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00002059 for (i=0; i < (ssize_t) width; i++)
cristy47e00502009-12-17 19:19:57 +00002060 {
cristy4205a3c2010-09-12 20:19:59 +00002061 kernel[i]=(double) (exp((-((double) i*i)/(double) (2.0*MagickSigma*
2062 MagickSigma)))/(MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00002063 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +00002064 }
cristybb503372010-05-27 20:51:26 +00002065 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002066 kernel[i]/=normalize;
2067 return(kernel);
2068}
2069
cristyf4ad9df2011-07-08 16:49:03 +00002070MagickExport Image *MotionBlurImage(const Image *image,
2071 const double radius,const double sigma,const double angle,
2072 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002073{
cristyc4c8d132010-01-07 01:58:38 +00002074 CacheView
2075 *blur_view,
2076 *image_view;
2077
cristy3ed852e2009-09-05 21:47:34 +00002078 double
2079 *kernel;
2080
2081 Image
2082 *blur_image;
2083
cristy3ed852e2009-09-05 21:47:34 +00002084 MagickBooleanType
2085 status;
2086
cristybb503372010-05-27 20:51:26 +00002087 MagickOffsetType
2088 progress;
2089
cristy4c08aed2011-07-01 19:47:50 +00002090 PixelInfo
cristyddd82202009-11-03 20:14:50 +00002091 bias;
cristy3ed852e2009-09-05 21:47:34 +00002092
2093 OffsetInfo
2094 *offset;
2095
2096 PointInfo
2097 point;
2098
cristybb503372010-05-27 20:51:26 +00002099 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002100 i;
2101
cristybb503372010-05-27 20:51:26 +00002102 size_t
cristy3ed852e2009-09-05 21:47:34 +00002103 width;
2104
cristybb503372010-05-27 20:51:26 +00002105 ssize_t
2106 y;
2107
cristy3ed852e2009-09-05 21:47:34 +00002108 assert(image != (Image *) NULL);
2109 assert(image->signature == MagickSignature);
2110 if (image->debug != MagickFalse)
2111 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2112 assert(exception != (ExceptionInfo *) NULL);
2113 width=GetOptimalKernelWidth1D(radius,sigma);
2114 kernel=GetMotionBlurKernel(width,sigma);
2115 if (kernel == (double *) NULL)
2116 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2117 offset=(OffsetInfo *) AcquireQuantumMemory(width,sizeof(*offset));
2118 if (offset == (OffsetInfo *) NULL)
2119 {
2120 kernel=(double *) RelinquishMagickMemory(kernel);
2121 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2122 }
2123 blur_image=CloneImage(image,0,0,MagickTrue,exception);
2124 if (blur_image == (Image *) NULL)
2125 {
2126 kernel=(double *) RelinquishMagickMemory(kernel);
2127 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2128 return((Image *) NULL);
2129 }
cristy574cc262011-08-05 01:23:58 +00002130 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00002131 {
2132 kernel=(double *) RelinquishMagickMemory(kernel);
2133 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
cristy3ed852e2009-09-05 21:47:34 +00002134 blur_image=DestroyImage(blur_image);
2135 return((Image *) NULL);
2136 }
2137 point.x=(double) width*sin(DegreesToRadians(angle));
2138 point.y=(double) width*cos(DegreesToRadians(angle));
cristybb503372010-05-27 20:51:26 +00002139 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002140 {
cristybb503372010-05-27 20:51:26 +00002141 offset[i].x=(ssize_t) ceil((double) (i*point.y)/hypot(point.x,point.y)-0.5);
2142 offset[i].y=(ssize_t) ceil((double) (i*point.x)/hypot(point.x,point.y)-0.5);
cristy3ed852e2009-09-05 21:47:34 +00002143 }
2144 /*
2145 Motion blur image.
2146 */
2147 status=MagickTrue;
2148 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00002149 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00002150 image_view=AcquireCacheView(image);
2151 blur_view=AcquireCacheView(blur_image);
cristyb557a152011-02-22 12:14:30 +00002152#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00002153 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00002154#endif
cristybb503372010-05-27 20:51:26 +00002155 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002156 {
cristy4c08aed2011-07-01 19:47:50 +00002157 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002158 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002159
cristy117ff172010-08-15 21:35:32 +00002160 register ssize_t
2161 x;
2162
cristy3ed852e2009-09-05 21:47:34 +00002163 if (status == MagickFalse)
2164 continue;
2165 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
2166 exception);
cristy4c08aed2011-07-01 19:47:50 +00002167 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002168 {
2169 status=MagickFalse;
2170 continue;
2171 }
cristybb503372010-05-27 20:51:26 +00002172 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002173 {
cristy4c08aed2011-07-01 19:47:50 +00002174 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002175 qixel;
2176
2177 PixelPacket
2178 pixel;
2179
2180 register double
cristyc47d1f82009-11-26 01:44:43 +00002181 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00002182
cristybb503372010-05-27 20:51:26 +00002183 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002184 i;
2185
cristy3ed852e2009-09-05 21:47:34 +00002186 k=kernel;
cristyddd82202009-11-03 20:14:50 +00002187 qixel=bias;
cristyed231572011-07-14 02:18:59 +00002188 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00002189 {
cristybb503372010-05-27 20:51:26 +00002190 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002191 {
2192 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
2193 offset[i].y,&pixel,exception);
2194 qixel.red+=(*k)*pixel.red;
2195 qixel.green+=(*k)*pixel.green;
2196 qixel.blue+=(*k)*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00002197 qixel.alpha+=(*k)*pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00002198 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00002199 qixel.black+=(*k)*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00002200 k++;
2201 }
cristyed231572011-07-14 02:18:59 +00002202 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002203 SetPixelRed(blur_image,
2204 ClampToQuantum(qixel.red),q);
cristyed231572011-07-14 02:18:59 +00002205 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002206 SetPixelGreen(blur_image,
2207 ClampToQuantum(qixel.green),q);
cristyed231572011-07-14 02:18:59 +00002208 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002209 SetPixelBlue(blur_image,
2210 ClampToQuantum(qixel.blue),q);
cristyed231572011-07-14 02:18:59 +00002211 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002212 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002213 SetPixelBlack(blur_image,
2214 ClampToQuantum(qixel.black),q);
cristyed231572011-07-14 02:18:59 +00002215 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002216 SetPixelAlpha(blur_image,
2217 ClampToQuantum(qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00002218 }
2219 else
2220 {
2221 MagickRealType
2222 alpha,
2223 gamma;
2224
2225 alpha=0.0;
2226 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00002227 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002228 {
2229 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
2230 offset[i].y,&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00002231 alpha=(MagickRealType) (QuantumScale*pixel.alpha);
cristy3ed852e2009-09-05 21:47:34 +00002232 qixel.red+=(*k)*alpha*pixel.red;
2233 qixel.green+=(*k)*alpha*pixel.green;
2234 qixel.blue+=(*k)*alpha*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00002235 qixel.alpha+=(*k)*pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00002236 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00002237 qixel.black+=(*k)*alpha*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00002238 gamma+=(*k)*alpha;
2239 k++;
2240 }
2241 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +00002242 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002243 SetPixelRed(blur_image,
2244 ClampToQuantum(gamma*qixel.red),q);
cristyed231572011-07-14 02:18:59 +00002245 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002246 SetPixelGreen(blur_image,
2247 ClampToQuantum(gamma*qixel.green),q);
cristyed231572011-07-14 02:18:59 +00002248 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002249 SetPixelBlue(blur_image,
2250 ClampToQuantum(gamma*qixel.blue),q);
cristyed231572011-07-14 02:18:59 +00002251 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002252 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002253 SetPixelBlack(blur_image,
2254 ClampToQuantum(gamma*qixel.black),q);
cristyed231572011-07-14 02:18:59 +00002255 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002256 SetPixelAlpha(blur_image,
2257 ClampToQuantum(qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00002258 }
cristyed231572011-07-14 02:18:59 +00002259 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00002260 }
2261 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
2262 status=MagickFalse;
2263 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2264 {
2265 MagickBooleanType
2266 proceed;
2267
cristyb557a152011-02-22 12:14:30 +00002268#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00002269 #pragma omp critical (MagickCore_MotionBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00002270#endif
2271 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
2272 if (proceed == MagickFalse)
2273 status=MagickFalse;
2274 }
2275 }
2276 blur_view=DestroyCacheView(blur_view);
2277 image_view=DestroyCacheView(image_view);
2278 kernel=(double *) RelinquishMagickMemory(kernel);
2279 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2280 if (status == MagickFalse)
2281 blur_image=DestroyImage(blur_image);
2282 return(blur_image);
2283}
2284
2285/*
2286%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2287% %
2288% %
2289% %
2290% P r e v i e w I m a g e %
2291% %
2292% %
2293% %
2294%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2295%
2296% PreviewImage() tiles 9 thumbnails of the specified image with an image
2297% processing operation applied with varying parameters. This may be helpful
2298% pin-pointing an appropriate parameter for a particular image processing
2299% operation.
2300%
2301% The format of the PreviewImages method is:
2302%
2303% Image *PreviewImages(const Image *image,const PreviewType preview,
2304% ExceptionInfo *exception)
2305%
2306% A description of each parameter follows:
2307%
2308% o image: the image.
2309%
2310% o preview: the image processing operation.
2311%
2312% o exception: return any errors or warnings in this structure.
2313%
2314*/
2315MagickExport Image *PreviewImage(const Image *image,const PreviewType preview,
2316 ExceptionInfo *exception)
2317{
2318#define NumberTiles 9
2319#define PreviewImageTag "Preview/Image"
2320#define DefaultPreviewGeometry "204x204+10+10"
2321
2322 char
2323 factor[MaxTextExtent],
2324 label[MaxTextExtent];
2325
2326 double
2327 degrees,
2328 gamma,
2329 percentage,
2330 radius,
2331 sigma,
2332 threshold;
2333
2334 Image
2335 *images,
2336 *montage_image,
2337 *preview_image,
2338 *thumbnail;
2339
2340 ImageInfo
2341 *preview_info;
2342
cristy3ed852e2009-09-05 21:47:34 +00002343 MagickBooleanType
2344 proceed;
2345
2346 MontageInfo
2347 *montage_info;
2348
2349 QuantizeInfo
2350 quantize_info;
2351
2352 RectangleInfo
2353 geometry;
2354
cristybb503372010-05-27 20:51:26 +00002355 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002356 i,
2357 x;
2358
cristybb503372010-05-27 20:51:26 +00002359 size_t
cristy3ed852e2009-09-05 21:47:34 +00002360 colors;
2361
cristy117ff172010-08-15 21:35:32 +00002362 ssize_t
2363 y;
2364
cristy3ed852e2009-09-05 21:47:34 +00002365 /*
2366 Open output image file.
2367 */
2368 assert(image != (Image *) NULL);
2369 assert(image->signature == MagickSignature);
2370 if (image->debug != MagickFalse)
2371 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2372 colors=2;
2373 degrees=0.0;
2374 gamma=(-0.2f);
2375 preview_info=AcquireImageInfo();
2376 SetGeometry(image,&geometry);
2377 (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y,
2378 &geometry.width,&geometry.height);
2379 images=NewImageList();
2380 percentage=12.5;
2381 GetQuantizeInfo(&quantize_info);
2382 radius=0.0;
2383 sigma=1.0;
2384 threshold=0.0;
2385 x=0;
2386 y=0;
2387 for (i=0; i < NumberTiles; i++)
2388 {
2389 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
2390 if (thumbnail == (Image *) NULL)
2391 break;
2392 (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL,
2393 (void *) NULL);
2394 (void) SetImageProperty(thumbnail,"label",DefaultTileLabel);
2395 if (i == (NumberTiles/2))
2396 {
2397 (void) QueryColorDatabase("#dfdfdf",&thumbnail->matte_color,exception);
2398 AppendImageToList(&images,thumbnail);
2399 continue;
2400 }
2401 switch (preview)
2402 {
2403 case RotatePreview:
2404 {
2405 degrees+=45.0;
2406 preview_image=RotateImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002407 (void) FormatLocaleString(label,MaxTextExtent,"rotate %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002408 break;
2409 }
2410 case ShearPreview:
2411 {
2412 degrees+=5.0;
2413 preview_image=ShearImage(thumbnail,degrees,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002414 (void) FormatLocaleString(label,MaxTextExtent,"shear %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002415 degrees,2.0*degrees);
2416 break;
2417 }
2418 case RollPreview:
2419 {
cristybb503372010-05-27 20:51:26 +00002420 x=(ssize_t) ((i+1)*thumbnail->columns)/NumberTiles;
2421 y=(ssize_t) ((i+1)*thumbnail->rows)/NumberTiles;
cristy3ed852e2009-09-05 21:47:34 +00002422 preview_image=RollImage(thumbnail,x,y,exception);
cristyb51dff52011-05-19 16:55:47 +00002423 (void) FormatLocaleString(label,MaxTextExtent,"roll %+.20gx%+.20g",
cristye8c25f92010-06-03 00:53:06 +00002424 (double) x,(double) y);
cristy3ed852e2009-09-05 21:47:34 +00002425 break;
2426 }
2427 case HuePreview:
2428 {
2429 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2430 if (preview_image == (Image *) NULL)
2431 break;
cristyb51dff52011-05-19 16:55:47 +00002432 (void) FormatLocaleString(factor,MaxTextExtent,"100,100,%g",
cristy3ed852e2009-09-05 21:47:34 +00002433 2.0*percentage);
cristy33bd5152011-08-24 01:42:24 +00002434 (void) ModulateImage(preview_image,factor,exception);
cristyb51dff52011-05-19 16:55:47 +00002435 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002436 break;
2437 }
2438 case SaturationPreview:
2439 {
2440 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2441 if (preview_image == (Image *) NULL)
2442 break;
cristyb51dff52011-05-19 16:55:47 +00002443 (void) FormatLocaleString(factor,MaxTextExtent,"100,%g",
cristy8cd5b312010-01-07 01:10:24 +00002444 2.0*percentage);
cristy33bd5152011-08-24 01:42:24 +00002445 (void) ModulateImage(preview_image,factor,exception);
cristyb51dff52011-05-19 16:55:47 +00002446 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002447 break;
2448 }
2449 case BrightnessPreview:
2450 {
2451 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2452 if (preview_image == (Image *) NULL)
2453 break;
cristyb51dff52011-05-19 16:55:47 +00002454 (void) FormatLocaleString(factor,MaxTextExtent,"%g",2.0*percentage);
cristy33bd5152011-08-24 01:42:24 +00002455 (void) ModulateImage(preview_image,factor,exception);
cristyb51dff52011-05-19 16:55:47 +00002456 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002457 break;
2458 }
2459 case GammaPreview:
2460 default:
2461 {
2462 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2463 if (preview_image == (Image *) NULL)
2464 break;
2465 gamma+=0.4f;
cristyb3e7c6c2011-07-24 01:43:55 +00002466 (void) GammaImage(preview_image,gamma,exception);
cristyb51dff52011-05-19 16:55:47 +00002467 (void) FormatLocaleString(label,MaxTextExtent,"gamma %g",gamma);
cristy3ed852e2009-09-05 21:47:34 +00002468 break;
2469 }
2470 case SpiffPreview:
2471 {
2472 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2473 if (preview_image != (Image *) NULL)
2474 for (x=0; x < i; x++)
cristye23ec9d2011-08-16 18:15:40 +00002475 (void) ContrastImage(preview_image,MagickTrue,exception);
cristyb51dff52011-05-19 16:55:47 +00002476 (void) FormatLocaleString(label,MaxTextExtent,"contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002477 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002478 break;
2479 }
2480 case DullPreview:
2481 {
2482 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2483 if (preview_image == (Image *) NULL)
2484 break;
2485 for (x=0; x < i; x++)
cristye23ec9d2011-08-16 18:15:40 +00002486 (void) ContrastImage(preview_image,MagickFalse,exception);
cristyb51dff52011-05-19 16:55:47 +00002487 (void) FormatLocaleString(label,MaxTextExtent,"+contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002488 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002489 break;
2490 }
2491 case GrayscalePreview:
2492 {
2493 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2494 if (preview_image == (Image *) NULL)
2495 break;
2496 colors<<=1;
2497 quantize_info.number_colors=colors;
2498 quantize_info.colorspace=GRAYColorspace;
2499 (void) QuantizeImage(&quantize_info,preview_image);
cristyb51dff52011-05-19 16:55:47 +00002500 (void) FormatLocaleString(label,MaxTextExtent,
cristye8c25f92010-06-03 00:53:06 +00002501 "-colorspace gray -colors %.20g",(double) colors);
cristy3ed852e2009-09-05 21:47:34 +00002502 break;
2503 }
2504 case QuantizePreview:
2505 {
2506 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2507 if (preview_image == (Image *) NULL)
2508 break;
2509 colors<<=1;
2510 quantize_info.number_colors=colors;
2511 (void) QuantizeImage(&quantize_info,preview_image);
cristyb51dff52011-05-19 16:55:47 +00002512 (void) FormatLocaleString(label,MaxTextExtent,"colors %.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00002513 colors);
cristy3ed852e2009-09-05 21:47:34 +00002514 break;
2515 }
2516 case DespecklePreview:
2517 {
2518 for (x=0; x < (i-1); x++)
2519 {
2520 preview_image=DespeckleImage(thumbnail,exception);
2521 if (preview_image == (Image *) NULL)
2522 break;
2523 thumbnail=DestroyImage(thumbnail);
2524 thumbnail=preview_image;
2525 }
2526 preview_image=DespeckleImage(thumbnail,exception);
2527 if (preview_image == (Image *) NULL)
2528 break;
cristyb51dff52011-05-19 16:55:47 +00002529 (void) FormatLocaleString(label,MaxTextExtent,"despeckle (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002530 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002531 break;
2532 }
2533 case ReduceNoisePreview:
2534 {
cristy95c38342011-03-18 22:39:51 +00002535 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) radius,
2536 (size_t) radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002537 (void) FormatLocaleString(label,MaxTextExtent,"noise %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002538 break;
2539 }
2540 case AddNoisePreview:
2541 {
2542 switch ((int) i)
2543 {
2544 case 0:
2545 {
2546 (void) CopyMagickString(factor,"uniform",MaxTextExtent);
2547 break;
2548 }
2549 case 1:
2550 {
2551 (void) CopyMagickString(factor,"gaussian",MaxTextExtent);
2552 break;
2553 }
2554 case 2:
2555 {
2556 (void) CopyMagickString(factor,"multiplicative",MaxTextExtent);
2557 break;
2558 }
2559 case 3:
2560 {
2561 (void) CopyMagickString(factor,"impulse",MaxTextExtent);
2562 break;
2563 }
2564 case 4:
2565 {
2566 (void) CopyMagickString(factor,"laplacian",MaxTextExtent);
2567 break;
2568 }
2569 case 5:
2570 {
2571 (void) CopyMagickString(factor,"Poisson",MaxTextExtent);
2572 break;
2573 }
2574 default:
2575 {
2576 (void) CopyMagickString(thumbnail->magick,"NULL",MaxTextExtent);
2577 break;
2578 }
2579 }
cristyd76c51e2011-03-26 00:21:26 +00002580 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) i,
2581 (size_t) i,exception);
cristyb51dff52011-05-19 16:55:47 +00002582 (void) FormatLocaleString(label,MaxTextExtent,"+noise %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002583 break;
2584 }
2585 case SharpenPreview:
2586 {
2587 preview_image=SharpenImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00002588 (void) FormatLocaleString(label,MaxTextExtent,"sharpen %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002589 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002590 break;
2591 }
2592 case BlurPreview:
2593 {
2594 preview_image=BlurImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00002595 (void) FormatLocaleString(label,MaxTextExtent,"blur %gx%g",radius,
cristy3ed852e2009-09-05 21:47:34 +00002596 sigma);
2597 break;
2598 }
2599 case ThresholdPreview:
2600 {
2601 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2602 if (preview_image == (Image *) NULL)
2603 break;
2604 (void) BilevelImage(thumbnail,
2605 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
cristyb51dff52011-05-19 16:55:47 +00002606 (void) FormatLocaleString(label,MaxTextExtent,"threshold %g",
cristy3ed852e2009-09-05 21:47:34 +00002607 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
2608 break;
2609 }
2610 case EdgeDetectPreview:
2611 {
2612 preview_image=EdgeImage(thumbnail,radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002613 (void) FormatLocaleString(label,MaxTextExtent,"edge %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002614 break;
2615 }
2616 case SpreadPreview:
2617 {
2618 preview_image=SpreadImage(thumbnail,radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002619 (void) FormatLocaleString(label,MaxTextExtent,"spread %g",
cristy8cd5b312010-01-07 01:10:24 +00002620 radius+0.5);
cristy3ed852e2009-09-05 21:47:34 +00002621 break;
2622 }
2623 case SolarizePreview:
2624 {
2625 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2626 if (preview_image == (Image *) NULL)
2627 break;
2628 (void) SolarizeImage(preview_image,(double) QuantumRange*
2629 percentage/100.0);
cristyb51dff52011-05-19 16:55:47 +00002630 (void) FormatLocaleString(label,MaxTextExtent,"solarize %g",
cristy3ed852e2009-09-05 21:47:34 +00002631 (QuantumRange*percentage)/100.0);
2632 break;
2633 }
2634 case ShadePreview:
2635 {
2636 degrees+=10.0;
2637 preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees,
2638 exception);
cristyb51dff52011-05-19 16:55:47 +00002639 (void) FormatLocaleString(label,MaxTextExtent,"shade %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002640 degrees,degrees);
cristy3ed852e2009-09-05 21:47:34 +00002641 break;
2642 }
2643 case RaisePreview:
2644 {
2645 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2646 if (preview_image == (Image *) NULL)
2647 break;
cristybb503372010-05-27 20:51:26 +00002648 geometry.width=(size_t) (2*i+2);
2649 geometry.height=(size_t) (2*i+2);
cristy3ed852e2009-09-05 21:47:34 +00002650 geometry.x=i/2;
2651 geometry.y=i/2;
cristy6170ac32011-08-28 14:15:37 +00002652 (void) RaiseImage(preview_image,&geometry,MagickTrue,exception);
cristyb51dff52011-05-19 16:55:47 +00002653 (void) FormatLocaleString(label,MaxTextExtent,
cristy6d8abba2010-06-03 01:10:47 +00002654 "raise %.20gx%.20g%+.20g%+.20g",(double) geometry.width,(double)
cristye8c25f92010-06-03 00:53:06 +00002655 geometry.height,(double) geometry.x,(double) geometry.y);
cristy3ed852e2009-09-05 21:47:34 +00002656 break;
2657 }
2658 case SegmentPreview:
2659 {
2660 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2661 if (preview_image == (Image *) NULL)
2662 break;
2663 threshold+=0.4f;
2664 (void) SegmentImage(preview_image,RGBColorspace,MagickFalse,threshold,
2665 threshold);
cristyb51dff52011-05-19 16:55:47 +00002666 (void) FormatLocaleString(label,MaxTextExtent,"segment %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002667 threshold,threshold);
2668 break;
2669 }
2670 case SwirlPreview:
2671 {
2672 preview_image=SwirlImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002673 (void) FormatLocaleString(label,MaxTextExtent,"swirl %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002674 degrees+=45.0;
2675 break;
2676 }
2677 case ImplodePreview:
2678 {
2679 degrees+=0.1f;
2680 preview_image=ImplodeImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002681 (void) FormatLocaleString(label,MaxTextExtent,"implode %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002682 break;
2683 }
2684 case WavePreview:
2685 {
2686 degrees+=5.0f;
2687 preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002688 (void) FormatLocaleString(label,MaxTextExtent,"wave %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002689 0.5*degrees,2.0*degrees);
cristy3ed852e2009-09-05 21:47:34 +00002690 break;
2691 }
2692 case OilPaintPreview:
2693 {
cristy14973ba2011-08-27 23:48:07 +00002694 preview_image=OilPaintImage(thumbnail,(double) radius,(double) sigma,
2695 exception);
2696 (void) FormatLocaleString(label,MaxTextExtent,"charcoal %gx%g",
2697 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002698 break;
2699 }
2700 case CharcoalDrawingPreview:
2701 {
2702 preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma,
2703 exception);
cristyb51dff52011-05-19 16:55:47 +00002704 (void) FormatLocaleString(label,MaxTextExtent,"charcoal %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002705 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002706 break;
2707 }
2708 case JPEGPreview:
2709 {
2710 char
2711 filename[MaxTextExtent];
2712
2713 int
2714 file;
2715
2716 MagickBooleanType
2717 status;
2718
2719 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2720 if (preview_image == (Image *) NULL)
2721 break;
cristybb503372010-05-27 20:51:26 +00002722 preview_info->quality=(size_t) percentage;
cristyb51dff52011-05-19 16:55:47 +00002723 (void) FormatLocaleString(factor,MaxTextExtent,"%.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00002724 preview_info->quality);
cristy3ed852e2009-09-05 21:47:34 +00002725 file=AcquireUniqueFileResource(filename);
2726 if (file != -1)
2727 file=close(file)-1;
cristyb51dff52011-05-19 16:55:47 +00002728 (void) FormatLocaleString(preview_image->filename,MaxTextExtent,
cristy3ed852e2009-09-05 21:47:34 +00002729 "jpeg:%s",filename);
cristy6f9e0d32011-08-28 16:32:09 +00002730 status=WriteImage(preview_info,preview_image,exception);
cristy3ed852e2009-09-05 21:47:34 +00002731 if (status != MagickFalse)
2732 {
2733 Image
2734 *quality_image;
2735
2736 (void) CopyMagickString(preview_info->filename,
2737 preview_image->filename,MaxTextExtent);
2738 quality_image=ReadImage(preview_info,exception);
2739 if (quality_image != (Image *) NULL)
2740 {
2741 preview_image=DestroyImage(preview_image);
2742 preview_image=quality_image;
2743 }
2744 }
2745 (void) RelinquishUniqueFileResource(preview_image->filename);
2746 if ((GetBlobSize(preview_image)/1024) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00002747 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%gmb ",
cristy3ed852e2009-09-05 21:47:34 +00002748 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
2749 1024.0/1024.0);
2750 else
2751 if (GetBlobSize(preview_image) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00002752 (void) FormatLocaleString(label,MaxTextExtent,
cristye7f51092010-01-17 00:39:37 +00002753 "quality %s\n%gkb ",factor,(double) ((MagickOffsetType)
cristy8cd5b312010-01-07 01:10:24 +00002754 GetBlobSize(preview_image))/1024.0);
cristy3ed852e2009-09-05 21:47:34 +00002755 else
cristyb51dff52011-05-19 16:55:47 +00002756 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%.20gb ",
cristy54ea5732011-06-10 12:39:53 +00002757 factor,(double) ((MagickOffsetType) GetBlobSize(thumbnail)));
cristy3ed852e2009-09-05 21:47:34 +00002758 break;
2759 }
2760 }
2761 thumbnail=DestroyImage(thumbnail);
2762 percentage+=12.5;
2763 radius+=0.5;
2764 sigma+=0.25;
2765 if (preview_image == (Image *) NULL)
2766 break;
2767 (void) DeleteImageProperty(preview_image,"label");
2768 (void) SetImageProperty(preview_image,"label",label);
2769 AppendImageToList(&images,preview_image);
cristybb503372010-05-27 20:51:26 +00002770 proceed=SetImageProgress(image,PreviewImageTag,(MagickOffsetType) i,
2771 NumberTiles);
cristy3ed852e2009-09-05 21:47:34 +00002772 if (proceed == MagickFalse)
2773 break;
2774 }
2775 if (images == (Image *) NULL)
2776 {
2777 preview_info=DestroyImageInfo(preview_info);
2778 return((Image *) NULL);
2779 }
2780 /*
2781 Create the montage.
2782 */
2783 montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL);
2784 (void) CopyMagickString(montage_info->filename,image->filename,MaxTextExtent);
2785 montage_info->shadow=MagickTrue;
2786 (void) CloneString(&montage_info->tile,"3x3");
2787 (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry);
2788 (void) CloneString(&montage_info->frame,DefaultTileFrame);
2789 montage_image=MontageImages(images,montage_info,exception);
2790 montage_info=DestroyMontageInfo(montage_info);
2791 images=DestroyImageList(images);
2792 if (montage_image == (Image *) NULL)
2793 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2794 if (montage_image->montage != (char *) NULL)
2795 {
2796 /*
2797 Free image directory.
2798 */
2799 montage_image->montage=(char *) RelinquishMagickMemory(
2800 montage_image->montage);
2801 if (image->directory != (char *) NULL)
2802 montage_image->directory=(char *) RelinquishMagickMemory(
2803 montage_image->directory);
2804 }
2805 preview_info=DestroyImageInfo(preview_info);
2806 return(montage_image);
2807}
2808
2809/*
2810%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2811% %
2812% %
2813% %
2814% R a d i a l B l u r I m a g e %
2815% %
2816% %
2817% %
2818%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2819%
2820% RadialBlurImage() applies a radial blur to the image.
2821%
2822% Andrew Protano contributed this effect.
2823%
2824% The format of the RadialBlurImage method is:
2825%
2826% Image *RadialBlurImage(const Image *image,const double angle,
2827% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002828%
2829% A description of each parameter follows:
2830%
2831% o image: the image.
2832%
cristy3ed852e2009-09-05 21:47:34 +00002833% o angle: the angle of the radial blur.
2834%
2835% o exception: return any errors or warnings in this structure.
2836%
2837*/
cristyf4ad9df2011-07-08 16:49:03 +00002838MagickExport Image *RadialBlurImage(const Image *image,
2839 const double angle,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002840{
cristyc4c8d132010-01-07 01:58:38 +00002841 CacheView
2842 *blur_view,
2843 *image_view;
2844
cristy3ed852e2009-09-05 21:47:34 +00002845 Image
2846 *blur_image;
2847
cristy3ed852e2009-09-05 21:47:34 +00002848 MagickBooleanType
2849 status;
2850
cristybb503372010-05-27 20:51:26 +00002851 MagickOffsetType
2852 progress;
2853
cristy4c08aed2011-07-01 19:47:50 +00002854 PixelInfo
cristyddd82202009-11-03 20:14:50 +00002855 bias;
cristy3ed852e2009-09-05 21:47:34 +00002856
2857 MagickRealType
2858 blur_radius,
2859 *cos_theta,
2860 offset,
2861 *sin_theta,
2862 theta;
2863
2864 PointInfo
2865 blur_center;
2866
cristybb503372010-05-27 20:51:26 +00002867 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002868 i;
2869
cristybb503372010-05-27 20:51:26 +00002870 size_t
cristy3ed852e2009-09-05 21:47:34 +00002871 n;
2872
cristybb503372010-05-27 20:51:26 +00002873 ssize_t
2874 y;
2875
cristy3ed852e2009-09-05 21:47:34 +00002876 /*
2877 Allocate blur image.
2878 */
2879 assert(image != (Image *) NULL);
2880 assert(image->signature == MagickSignature);
2881 if (image->debug != MagickFalse)
2882 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2883 assert(exception != (ExceptionInfo *) NULL);
2884 assert(exception->signature == MagickSignature);
2885 blur_image=CloneImage(image,0,0,MagickTrue,exception);
2886 if (blur_image == (Image *) NULL)
2887 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00002888 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00002889 {
cristy3ed852e2009-09-05 21:47:34 +00002890 blur_image=DestroyImage(blur_image);
2891 return((Image *) NULL);
2892 }
2893 blur_center.x=(double) image->columns/2.0;
2894 blur_center.y=(double) image->rows/2.0;
2895 blur_radius=hypot(blur_center.x,blur_center.y);
cristy117ff172010-08-15 21:35:32 +00002896 n=(size_t) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+2UL);
cristy3ed852e2009-09-05 21:47:34 +00002897 theta=DegreesToRadians(angle)/(MagickRealType) (n-1);
2898 cos_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
2899 sizeof(*cos_theta));
2900 sin_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
2901 sizeof(*sin_theta));
2902 if ((cos_theta == (MagickRealType *) NULL) ||
2903 (sin_theta == (MagickRealType *) NULL))
2904 {
2905 blur_image=DestroyImage(blur_image);
2906 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2907 }
2908 offset=theta*(MagickRealType) (n-1)/2.0;
cristybb503372010-05-27 20:51:26 +00002909 for (i=0; i < (ssize_t) n; i++)
cristy3ed852e2009-09-05 21:47:34 +00002910 {
2911 cos_theta[i]=cos((double) (theta*i-offset));
2912 sin_theta[i]=sin((double) (theta*i-offset));
2913 }
2914 /*
2915 Radial blur image.
2916 */
2917 status=MagickTrue;
2918 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00002919 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00002920 image_view=AcquireCacheView(image);
2921 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00002922#if defined(MAGICKCORE_OPENMP_SUPPORT)
2923 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002924#endif
cristybb503372010-05-27 20:51:26 +00002925 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002926 {
cristy4c08aed2011-07-01 19:47:50 +00002927 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002928 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002929
cristy117ff172010-08-15 21:35:32 +00002930 register ssize_t
2931 x;
2932
cristy3ed852e2009-09-05 21:47:34 +00002933 if (status == MagickFalse)
2934 continue;
2935 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
2936 exception);
cristy4c08aed2011-07-01 19:47:50 +00002937 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002938 {
2939 status=MagickFalse;
2940 continue;
2941 }
cristybb503372010-05-27 20:51:26 +00002942 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002943 {
cristy4c08aed2011-07-01 19:47:50 +00002944 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002945 qixel;
2946
2947 MagickRealType
2948 normalize,
2949 radius;
2950
2951 PixelPacket
2952 pixel;
2953
2954 PointInfo
2955 center;
2956
cristybb503372010-05-27 20:51:26 +00002957 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002958 i;
2959
cristybb503372010-05-27 20:51:26 +00002960 size_t
cristy3ed852e2009-09-05 21:47:34 +00002961 step;
2962
2963 center.x=(double) x-blur_center.x;
2964 center.y=(double) y-blur_center.y;
2965 radius=hypot((double) center.x,center.y);
2966 if (radius == 0)
2967 step=1;
2968 else
2969 {
cristybb503372010-05-27 20:51:26 +00002970 step=(size_t) (blur_radius/radius);
cristy3ed852e2009-09-05 21:47:34 +00002971 if (step == 0)
2972 step=1;
2973 else
2974 if (step >= n)
2975 step=n-1;
2976 }
2977 normalize=0.0;
cristyddd82202009-11-03 20:14:50 +00002978 qixel=bias;
cristyed231572011-07-14 02:18:59 +00002979 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00002980 {
cristyeaedf062010-05-29 22:36:02 +00002981 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00002982 {
cristyeaedf062010-05-29 22:36:02 +00002983 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
2984 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
2985 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
2986 cos_theta[i]+0.5),&pixel,exception);
cristy3ed852e2009-09-05 21:47:34 +00002987 qixel.red+=pixel.red;
2988 qixel.green+=pixel.green;
2989 qixel.blue+=pixel.blue;
cristy3ed852e2009-09-05 21:47:34 +00002990 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00002991 qixel.black+=pixel.black;
2992 qixel.alpha+=pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00002993 normalize+=1.0;
2994 }
2995 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
2996 normalize);
cristyed231572011-07-14 02:18:59 +00002997 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002998 SetPixelRed(blur_image,
2999 ClampToQuantum(normalize*qixel.red),q);
cristyed231572011-07-14 02:18:59 +00003000 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003001 SetPixelGreen(blur_image,
3002 ClampToQuantum(normalize*qixel.green),q);
cristyed231572011-07-14 02:18:59 +00003003 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003004 SetPixelBlue(blur_image,
3005 ClampToQuantum(normalize*qixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003006 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003007 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003008 SetPixelBlack(blur_image,
3009 ClampToQuantum(normalize*qixel.black),q);
cristyed231572011-07-14 02:18:59 +00003010 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003011 SetPixelAlpha(blur_image,
3012 ClampToQuantum(normalize*qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003013 }
3014 else
3015 {
3016 MagickRealType
3017 alpha,
3018 gamma;
3019
3020 alpha=1.0;
3021 gamma=0.0;
cristyeaedf062010-05-29 22:36:02 +00003022 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00003023 {
cristyeaedf062010-05-29 22:36:02 +00003024 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
3025 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
3026 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
3027 cos_theta[i]+0.5),&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00003028 alpha=(MagickRealType) (QuantumScale*pixel.alpha);
cristy3ed852e2009-09-05 21:47:34 +00003029 qixel.red+=alpha*pixel.red;
3030 qixel.green+=alpha*pixel.green;
3031 qixel.blue+=alpha*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00003032 qixel.alpha+=pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00003033 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00003034 qixel.black+=alpha*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00003035 gamma+=alpha;
3036 normalize+=1.0;
3037 }
3038 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3039 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
3040 normalize);
cristyed231572011-07-14 02:18:59 +00003041 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003042 SetPixelRed(blur_image,
3043 ClampToQuantum(gamma*qixel.red),q);
cristyed231572011-07-14 02:18:59 +00003044 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003045 SetPixelGreen(blur_image,
3046 ClampToQuantum(gamma*qixel.green),q);
cristyed231572011-07-14 02:18:59 +00003047 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003048 SetPixelBlue(blur_image,
3049 ClampToQuantum(gamma*qixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003050 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003051 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003052 SetPixelBlack(blur_image,
3053 ClampToQuantum(gamma*qixel.black),q);
cristyed231572011-07-14 02:18:59 +00003054 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003055 SetPixelAlpha(blur_image,
3056 ClampToQuantum(normalize*qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003057 }
cristyed231572011-07-14 02:18:59 +00003058 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003059 }
3060 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
3061 status=MagickFalse;
3062 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3063 {
3064 MagickBooleanType
3065 proceed;
3066
cristyb5d5f722009-11-04 03:03:49 +00003067#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00003068 #pragma omp critical (MagickCore_RadialBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00003069#endif
3070 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
3071 if (proceed == MagickFalse)
3072 status=MagickFalse;
3073 }
3074 }
3075 blur_view=DestroyCacheView(blur_view);
3076 image_view=DestroyCacheView(image_view);
3077 cos_theta=(MagickRealType *) RelinquishMagickMemory(cos_theta);
3078 sin_theta=(MagickRealType *) RelinquishMagickMemory(sin_theta);
3079 if (status == MagickFalse)
3080 blur_image=DestroyImage(blur_image);
3081 return(blur_image);
3082}
3083
3084/*
3085%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3086% %
3087% %
3088% %
cristy3ed852e2009-09-05 21:47:34 +00003089% S e l e c t i v e B l u r I m a g e %
3090% %
3091% %
3092% %
3093%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3094%
3095% SelectiveBlurImage() selectively blur pixels within a contrast threshold.
3096% It is similar to the unsharpen mask that sharpens everything with contrast
3097% above a certain threshold.
3098%
3099% The format of the SelectiveBlurImage method is:
3100%
3101% Image *SelectiveBlurImage(const Image *image,const double radius,
3102% const double sigma,const double threshold,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003103%
3104% A description of each parameter follows:
3105%
3106% o image: the image.
3107%
cristy3ed852e2009-09-05 21:47:34 +00003108% o radius: the radius of the Gaussian, in pixels, not counting the center
3109% pixel.
3110%
3111% o sigma: the standard deviation of the Gaussian, in pixels.
3112%
3113% o threshold: only pixels within this contrast threshold are included
3114% in the blur operation.
3115%
3116% o exception: return any errors or warnings in this structure.
3117%
3118*/
cristyf4ad9df2011-07-08 16:49:03 +00003119MagickExport Image *SelectiveBlurImage(const Image *image,
3120 const double radius,const double sigma,const double threshold,
3121 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003122{
3123#define SelectiveBlurImageTag "SelectiveBlur/Image"
3124
cristy47e00502009-12-17 19:19:57 +00003125 CacheView
3126 *blur_view,
3127 *image_view;
3128
cristy3ed852e2009-09-05 21:47:34 +00003129 double
cristy3ed852e2009-09-05 21:47:34 +00003130 *kernel;
3131
3132 Image
3133 *blur_image;
3134
cristy3ed852e2009-09-05 21:47:34 +00003135 MagickBooleanType
3136 status;
3137
cristybb503372010-05-27 20:51:26 +00003138 MagickOffsetType
3139 progress;
3140
cristy4c08aed2011-07-01 19:47:50 +00003141 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003142 bias;
3143
cristybb503372010-05-27 20:51:26 +00003144 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003145 i;
cristy3ed852e2009-09-05 21:47:34 +00003146
cristybb503372010-05-27 20:51:26 +00003147 size_t
cristy3ed852e2009-09-05 21:47:34 +00003148 width;
3149
cristybb503372010-05-27 20:51:26 +00003150 ssize_t
3151 j,
3152 u,
3153 v,
3154 y;
3155
cristy3ed852e2009-09-05 21:47:34 +00003156 /*
3157 Initialize blur image attributes.
3158 */
3159 assert(image != (Image *) NULL);
3160 assert(image->signature == MagickSignature);
3161 if (image->debug != MagickFalse)
3162 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3163 assert(exception != (ExceptionInfo *) NULL);
3164 assert(exception->signature == MagickSignature);
3165 width=GetOptimalKernelWidth1D(radius,sigma);
3166 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
3167 if (kernel == (double *) NULL)
3168 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00003169 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00003170 i=0;
cristy47e00502009-12-17 19:19:57 +00003171 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00003172 {
cristy47e00502009-12-17 19:19:57 +00003173 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00003174 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
3175 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00003176 }
3177 if (image->debug != MagickFalse)
3178 {
3179 char
3180 format[MaxTextExtent],
3181 *message;
3182
cristy117ff172010-08-15 21:35:32 +00003183 register const double
3184 *k;
3185
cristybb503372010-05-27 20:51:26 +00003186 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003187 u,
3188 v;
3189
cristy3ed852e2009-09-05 21:47:34 +00003190 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00003191 " SelectiveBlurImage with %.20gx%.20g kernel:",(double) width,(double)
3192 width);
cristy3ed852e2009-09-05 21:47:34 +00003193 message=AcquireString("");
3194 k=kernel;
cristybb503372010-05-27 20:51:26 +00003195 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003196 {
3197 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00003198 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy3ed852e2009-09-05 21:47:34 +00003199 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00003200 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003201 {
cristyb51dff52011-05-19 16:55:47 +00003202 (void) FormatLocaleString(format,MaxTextExtent,"%+f ",*k++);
cristy3ed852e2009-09-05 21:47:34 +00003203 (void) ConcatenateString(&message,format);
3204 }
3205 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
3206 }
3207 message=DestroyString(message);
3208 }
3209 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3210 if (blur_image == (Image *) NULL)
3211 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00003212 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003213 {
cristy3ed852e2009-09-05 21:47:34 +00003214 blur_image=DestroyImage(blur_image);
3215 return((Image *) NULL);
3216 }
3217 /*
3218 Threshold blur image.
3219 */
3220 status=MagickTrue;
3221 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00003222 GetPixelInfo(image,&bias);
3223 SetPixelInfoBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003224 image_view=AcquireCacheView(image);
3225 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003226#if defined(MAGICKCORE_OPENMP_SUPPORT)
3227 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003228#endif
cristybb503372010-05-27 20:51:26 +00003229 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003230 {
cristy4c08aed2011-07-01 19:47:50 +00003231 double
3232 contrast;
3233
cristy3ed852e2009-09-05 21:47:34 +00003234 MagickBooleanType
3235 sync;
3236
3237 MagickRealType
3238 gamma;
3239
cristy4c08aed2011-07-01 19:47:50 +00003240 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003241 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003242
cristy4c08aed2011-07-01 19:47:50 +00003243 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003244 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003245
cristy117ff172010-08-15 21:35:32 +00003246 register ssize_t
3247 x;
3248
cristy3ed852e2009-09-05 21:47:34 +00003249 if (status == MagickFalse)
3250 continue;
cristy117ff172010-08-15 21:35:32 +00003251 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
3252 (width/2L),image->columns+width,width,exception);
cristy3ed852e2009-09-05 21:47:34 +00003253 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3254 exception);
cristy4c08aed2011-07-01 19:47:50 +00003255 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003256 {
3257 status=MagickFalse;
3258 continue;
3259 }
cristybb503372010-05-27 20:51:26 +00003260 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003261 {
cristy4c08aed2011-07-01 19:47:50 +00003262 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003263 pixel;
3264
3265 register const double
cristyc47d1f82009-11-26 01:44:43 +00003266 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00003267
cristybb503372010-05-27 20:51:26 +00003268 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003269 u;
3270
cristy117ff172010-08-15 21:35:32 +00003271 ssize_t
3272 j,
3273 v;
3274
cristyddd82202009-11-03 20:14:50 +00003275 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00003276 k=kernel;
3277 gamma=0.0;
3278 j=0;
cristyed231572011-07-14 02:18:59 +00003279 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00003280 {
cristybb503372010-05-27 20:51:26 +00003281 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003282 {
cristybb503372010-05-27 20:51:26 +00003283 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003284 {
cristyed231572011-07-14 02:18:59 +00003285 contrast=GetPixelIntensity(image,p+(u+j)*GetPixelChannels(image))-
cristy4c08aed2011-07-01 19:47:50 +00003286 (double) GetPixelIntensity(blur_image,q);
3287 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003288 {
cristy4c08aed2011-07-01 19:47:50 +00003289 pixel.red+=(*k)*
cristyed231572011-07-14 02:18:59 +00003290 GetPixelRed(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003291 pixel.green+=(*k)*
cristyed231572011-07-14 02:18:59 +00003292 GetPixelGreen(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003293 pixel.blue+=(*k)*
cristyed231572011-07-14 02:18:59 +00003294 GetPixelBlue(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003295 if (image->colorspace == CMYKColorspace)
3296 pixel.black+=(*k)*
cristyed231572011-07-14 02:18:59 +00003297 GetPixelBlack(image,p+(u+j)*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003298 gamma+=(*k);
3299 k++;
3300 }
3301 }
cristyd99b0962010-05-29 23:14:26 +00003302 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003303 }
3304 if (gamma != 0.0)
3305 {
3306 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +00003307 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003308 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +00003309 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003310 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +00003311 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003312 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003313 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00003314 (image->colorspace == CMYKColorspace))
3315 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00003316 }
cristyed231572011-07-14 02:18:59 +00003317 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003318 {
3319 gamma=0.0;
3320 j=0;
cristybb503372010-05-27 20:51:26 +00003321 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003322 {
cristybb503372010-05-27 20:51:26 +00003323 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003324 {
cristy4c08aed2011-07-01 19:47:50 +00003325 contrast=GetPixelIntensity(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003326 GetPixelChannels(image))-(double)
cristy4c08aed2011-07-01 19:47:50 +00003327 GetPixelIntensity(blur_image,q);
3328 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003329 {
cristy4c08aed2011-07-01 19:47:50 +00003330 pixel.alpha+=(*k)*
cristyed231572011-07-14 02:18:59 +00003331 GetPixelAlpha(image,p+(u+j)*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003332 gamma+=(*k);
3333 k++;
3334 }
3335 }
cristyeaedf062010-05-29 22:36:02 +00003336 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003337 }
3338 if (gamma != 0.0)
3339 {
3340 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
3341 gamma);
cristy4c08aed2011-07-01 19:47:50 +00003342 SetPixelAlpha(blur_image,ClampToQuantum(gamma*pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003343 }
3344 }
3345 }
3346 else
3347 {
3348 MagickRealType
3349 alpha;
3350
cristybb503372010-05-27 20:51:26 +00003351 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003352 {
cristybb503372010-05-27 20:51:26 +00003353 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003354 {
cristy4c08aed2011-07-01 19:47:50 +00003355 contrast=GetPixelIntensity(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003356 GetPixelChannels(image))-(double)
cristy4c08aed2011-07-01 19:47:50 +00003357 GetPixelIntensity(blur_image,q);
3358 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003359 {
cristy4c08aed2011-07-01 19:47:50 +00003360 alpha=(MagickRealType) (QuantumScale*
cristyed231572011-07-14 02:18:59 +00003361 GetPixelAlpha(image,p+(u+j)*GetPixelChannels(image)));
cristy4c08aed2011-07-01 19:47:50 +00003362 pixel.red+=(*k)*alpha*
cristyed231572011-07-14 02:18:59 +00003363 GetPixelRed(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003364 pixel.green+=(*k)*alpha*GetPixelGreen(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003365 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003366 pixel.blue+=(*k)*alpha*GetPixelBlue(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003367 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003368 pixel.alpha+=(*k)*GetPixelAlpha(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003369 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003370 if (image->colorspace == CMYKColorspace)
3371 pixel.black+=(*k)*GetPixelBlack(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003372 GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003373 gamma+=(*k)*alpha;
3374 k++;
3375 }
3376 }
cristyeaedf062010-05-29 22:36:02 +00003377 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003378 }
3379 if (gamma != 0.0)
3380 {
3381 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +00003382 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003383 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +00003384 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003385 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +00003386 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003387 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003388 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00003389 (image->colorspace == CMYKColorspace))
3390 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00003391 }
cristyed231572011-07-14 02:18:59 +00003392 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003393 {
3394 gamma=0.0;
3395 j=0;
cristybb503372010-05-27 20:51:26 +00003396 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003397 {
cristybb503372010-05-27 20:51:26 +00003398 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003399 {
cristy4c08aed2011-07-01 19:47:50 +00003400 contrast=GetPixelIntensity(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003401 GetPixelChannels(image))-(double)
cristy4c08aed2011-07-01 19:47:50 +00003402 GetPixelIntensity(blur_image,q);
3403 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003404 {
cristy4c08aed2011-07-01 19:47:50 +00003405 pixel.alpha+=(*k)*
cristyed231572011-07-14 02:18:59 +00003406 GetPixelAlpha(image,p+(u+j)*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003407 gamma+=(*k);
3408 k++;
3409 }
3410 }
cristyeaedf062010-05-29 22:36:02 +00003411 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003412 }
3413 if (gamma != 0.0)
3414 {
3415 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
3416 gamma);
cristy4c08aed2011-07-01 19:47:50 +00003417 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003418 }
3419 }
3420 }
cristyed231572011-07-14 02:18:59 +00003421 p+=GetPixelChannels(image);
3422 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003423 }
3424 sync=SyncCacheViewAuthenticPixels(blur_view,exception);
3425 if (sync == MagickFalse)
3426 status=MagickFalse;
3427 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3428 {
3429 MagickBooleanType
3430 proceed;
3431
cristyb5d5f722009-11-04 03:03:49 +00003432#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00003433 #pragma omp critical (MagickCore_SelectiveBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00003434#endif
3435 proceed=SetImageProgress(image,SelectiveBlurImageTag,progress++,
3436 image->rows);
3437 if (proceed == MagickFalse)
3438 status=MagickFalse;
3439 }
3440 }
3441 blur_image->type=image->type;
3442 blur_view=DestroyCacheView(blur_view);
3443 image_view=DestroyCacheView(image_view);
3444 kernel=(double *) RelinquishMagickMemory(kernel);
3445 if (status == MagickFalse)
3446 blur_image=DestroyImage(blur_image);
3447 return(blur_image);
3448}
3449
3450/*
3451%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3452% %
3453% %
3454% %
3455% S h a d e I m a g e %
3456% %
3457% %
3458% %
3459%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3460%
3461% ShadeImage() shines a distant light on an image to create a
3462% three-dimensional effect. You control the positioning of the light with
3463% azimuth and elevation; azimuth is measured in degrees off the x axis
3464% and elevation is measured in pixels above the Z axis.
3465%
3466% The format of the ShadeImage method is:
3467%
3468% Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3469% const double azimuth,const double elevation,ExceptionInfo *exception)
3470%
3471% A description of each parameter follows:
3472%
3473% o image: the image.
3474%
3475% o gray: A value other than zero shades the intensity of each pixel.
3476%
3477% o azimuth, elevation: Define the light source direction.
3478%
3479% o exception: return any errors or warnings in this structure.
3480%
3481*/
3482MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3483 const double azimuth,const double elevation,ExceptionInfo *exception)
3484{
3485#define ShadeImageTag "Shade/Image"
3486
cristyc4c8d132010-01-07 01:58:38 +00003487 CacheView
3488 *image_view,
3489 *shade_view;
3490
cristy3ed852e2009-09-05 21:47:34 +00003491 Image
3492 *shade_image;
3493
cristy3ed852e2009-09-05 21:47:34 +00003494 MagickBooleanType
3495 status;
3496
cristybb503372010-05-27 20:51:26 +00003497 MagickOffsetType
3498 progress;
3499
cristy3ed852e2009-09-05 21:47:34 +00003500 PrimaryInfo
3501 light;
3502
cristybb503372010-05-27 20:51:26 +00003503 ssize_t
3504 y;
3505
cristy3ed852e2009-09-05 21:47:34 +00003506 /*
3507 Initialize shaded image attributes.
3508 */
3509 assert(image != (const Image *) NULL);
3510 assert(image->signature == MagickSignature);
3511 if (image->debug != MagickFalse)
3512 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3513 assert(exception != (ExceptionInfo *) NULL);
3514 assert(exception->signature == MagickSignature);
3515 shade_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
3516 if (shade_image == (Image *) NULL)
3517 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00003518 if (SetImageStorageClass(shade_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003519 {
cristy3ed852e2009-09-05 21:47:34 +00003520 shade_image=DestroyImage(shade_image);
3521 return((Image *) NULL);
3522 }
3523 /*
3524 Compute the light vector.
3525 */
3526 light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))*
3527 cos(DegreesToRadians(elevation));
3528 light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))*
3529 cos(DegreesToRadians(elevation));
3530 light.z=(double) QuantumRange*sin(DegreesToRadians(elevation));
3531 /*
3532 Shade image.
3533 */
3534 status=MagickTrue;
3535 progress=0;
3536 image_view=AcquireCacheView(image);
3537 shade_view=AcquireCacheView(shade_image);
cristyb5d5f722009-11-04 03:03:49 +00003538#if defined(MAGICKCORE_OPENMP_SUPPORT)
3539 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003540#endif
cristybb503372010-05-27 20:51:26 +00003541 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003542 {
3543 MagickRealType
3544 distance,
3545 normal_distance,
3546 shade;
3547
3548 PrimaryInfo
3549 normal;
3550
cristy4c08aed2011-07-01 19:47:50 +00003551 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003552 *restrict p,
3553 *restrict s0,
3554 *restrict s1,
3555 *restrict s2;
cristy3ed852e2009-09-05 21:47:34 +00003556
cristy4c08aed2011-07-01 19:47:50 +00003557 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003558 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003559
cristy117ff172010-08-15 21:35:32 +00003560 register ssize_t
3561 x;
3562
cristy3ed852e2009-09-05 21:47:34 +00003563 if (status == MagickFalse)
3564 continue;
3565 p=GetCacheViewVirtualPixels(image_view,-1,y-1,image->columns+2,3,exception);
3566 q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1,
3567 exception);
cristy4c08aed2011-07-01 19:47:50 +00003568 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003569 {
3570 status=MagickFalse;
3571 continue;
3572 }
3573 /*
3574 Shade this row of pixels.
3575 */
3576 normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */
cristyed231572011-07-14 02:18:59 +00003577 s0=p+GetPixelChannels(image);
3578 s1=s0+(image->columns+2)*GetPixelChannels(image);
3579 s2=s1+(image->columns+2)*GetPixelChannels(image);
cristybb503372010-05-27 20:51:26 +00003580 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003581 {
3582 /*
3583 Determine the surface normal and compute shading.
3584 */
cristyed231572011-07-14 02:18:59 +00003585 normal.x=(double) (GetPixelIntensity(image,s0-GetPixelChannels(image))+
3586 GetPixelIntensity(image,s1-GetPixelChannels(image))+
3587 GetPixelIntensity(image,s2-GetPixelChannels(image))-
3588 GetPixelIntensity(image,s0+GetPixelChannels(image))-
3589 GetPixelIntensity(image,s1+GetPixelChannels(image))-
3590 GetPixelIntensity(image,s2+GetPixelChannels(image)));
3591 normal.y=(double) (GetPixelIntensity(image,s2-GetPixelChannels(image))+
cristy4c08aed2011-07-01 19:47:50 +00003592 GetPixelIntensity(image,s2)+
cristyed231572011-07-14 02:18:59 +00003593 GetPixelIntensity(image,s2+GetPixelChannels(image))-
3594 GetPixelIntensity(image,s0-GetPixelChannels(image))-
cristy4c08aed2011-07-01 19:47:50 +00003595 GetPixelIntensity(image,s0)-
cristyed231572011-07-14 02:18:59 +00003596 GetPixelIntensity(image,s0+GetPixelChannels(image)));
cristy3ed852e2009-09-05 21:47:34 +00003597 if ((normal.x == 0.0) && (normal.y == 0.0))
3598 shade=light.z;
3599 else
3600 {
3601 shade=0.0;
3602 distance=normal.x*light.x+normal.y*light.y+normal.z*light.z;
3603 if (distance > MagickEpsilon)
3604 {
3605 normal_distance=
3606 normal.x*normal.x+normal.y*normal.y+normal.z*normal.z;
3607 if (normal_distance > (MagickEpsilon*MagickEpsilon))
3608 shade=distance/sqrt((double) normal_distance);
3609 }
3610 }
3611 if (gray != MagickFalse)
3612 {
cristy4c08aed2011-07-01 19:47:50 +00003613 SetPixelRed(shade_image,ClampToQuantum(shade),q);
3614 SetPixelGreen(shade_image,ClampToQuantum(shade),q);
3615 SetPixelBlue(shade_image,ClampToQuantum(shade),q);
cristy3ed852e2009-09-05 21:47:34 +00003616 }
3617 else
3618 {
cristy4c08aed2011-07-01 19:47:50 +00003619 SetPixelRed(shade_image,ClampToQuantum(QuantumScale*shade*
3620 GetPixelRed(image,s1)),q);
3621 SetPixelGreen(shade_image,ClampToQuantum(QuantumScale*shade*
3622 GetPixelGreen(image,s1)),q);
3623 SetPixelBlue(shade_image,ClampToQuantum(QuantumScale*shade*
3624 GetPixelBlue(image,s1)),q);
cristy3ed852e2009-09-05 21:47:34 +00003625 }
cristy4c08aed2011-07-01 19:47:50 +00003626 SetPixelAlpha(shade_image,GetPixelAlpha(image,s1),q);
cristyed231572011-07-14 02:18:59 +00003627 s0+=GetPixelChannels(image);
3628 s1+=GetPixelChannels(image);
3629 s2+=GetPixelChannels(image);
3630 q+=GetPixelChannels(shade_image);
cristy3ed852e2009-09-05 21:47:34 +00003631 }
3632 if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
3633 status=MagickFalse;
3634 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3635 {
3636 MagickBooleanType
3637 proceed;
3638
cristyb5d5f722009-11-04 03:03:49 +00003639#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003640 #pragma omp critical (MagickCore_ShadeImage)
3641#endif
3642 proceed=SetImageProgress(image,ShadeImageTag,progress++,image->rows);
3643 if (proceed == MagickFalse)
3644 status=MagickFalse;
3645 }
3646 }
3647 shade_view=DestroyCacheView(shade_view);
3648 image_view=DestroyCacheView(image_view);
3649 if (status == MagickFalse)
3650 shade_image=DestroyImage(shade_image);
3651 return(shade_image);
3652}
3653
3654/*
3655%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3656% %
3657% %
3658% %
3659% S h a r p e n I m a g e %
3660% %
3661% %
3662% %
3663%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3664%
3665% SharpenImage() sharpens the image. We convolve the image with a Gaussian
3666% operator of the given radius and standard deviation (sigma). For
3667% reasonable results, radius should be larger than sigma. Use a radius of 0
3668% and SharpenImage() selects a suitable radius for you.
3669%
3670% Using a separable kernel would be faster, but the negative weights cancel
3671% out on the corners of the kernel producing often undesirable ringing in the
3672% filtered result; this can be avoided by using a 2D gaussian shaped image
3673% sharpening kernel instead.
3674%
3675% The format of the SharpenImage method is:
3676%
3677% Image *SharpenImage(const Image *image,const double radius,
3678% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003679%
3680% A description of each parameter follows:
3681%
3682% o image: the image.
3683%
cristy3ed852e2009-09-05 21:47:34 +00003684% o radius: the radius of the Gaussian, in pixels, not counting the center
3685% pixel.
3686%
3687% o sigma: the standard deviation of the Laplacian, in pixels.
3688%
3689% o exception: return any errors or warnings in this structure.
3690%
3691*/
cristy3ed852e2009-09-05 21:47:34 +00003692MagickExport Image *SharpenImage(const Image *image,const double radius,
3693 const double sigma,ExceptionInfo *exception)
3694{
cristy3ed852e2009-09-05 21:47:34 +00003695 double
cristy47e00502009-12-17 19:19:57 +00003696 normalize;
cristy3ed852e2009-09-05 21:47:34 +00003697
3698 Image
3699 *sharp_image;
3700
cristy41cbe682011-07-15 19:12:37 +00003701 KernelInfo
3702 *kernel_info;
3703
cristybb503372010-05-27 20:51:26 +00003704 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003705 i;
3706
cristybb503372010-05-27 20:51:26 +00003707 size_t
cristy3ed852e2009-09-05 21:47:34 +00003708 width;
3709
cristy117ff172010-08-15 21:35:32 +00003710 ssize_t
3711 j,
3712 u,
3713 v;
3714
cristy3ed852e2009-09-05 21:47:34 +00003715 assert(image != (const Image *) NULL);
3716 assert(image->signature == MagickSignature);
3717 if (image->debug != MagickFalse)
3718 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3719 assert(exception != (ExceptionInfo *) NULL);
3720 assert(exception->signature == MagickSignature);
3721 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00003722 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00003723 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003724 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00003725 (void) ResetMagickMemory(kernel_info,0,sizeof(*kernel_info));
3726 kernel_info->width=width;
3727 kernel_info->height=width;
3728 kernel_info->signature=MagickSignature;
3729 kernel_info->values=(double *) AcquireAlignedMemory(kernel_info->width,
3730 kernel_info->width*sizeof(*kernel_info->values));
3731 if (kernel_info->values == (double *) NULL)
3732 {
3733 kernel_info=DestroyKernelInfo(kernel_info);
3734 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3735 }
cristy3ed852e2009-09-05 21:47:34 +00003736 normalize=0.0;
cristy41cbe682011-07-15 19:12:37 +00003737 j=(ssize_t) kernel_info->width/2;
cristy47e00502009-12-17 19:19:57 +00003738 i=0;
3739 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00003740 {
cristy47e00502009-12-17 19:19:57 +00003741 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00003742 {
cristy41cbe682011-07-15 19:12:37 +00003743 kernel_info->values[i]=(double) (-exp(-((double) u*u+v*v)/(2.0*
3744 MagickSigma*MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
3745 normalize+=kernel_info->values[i];
cristy3ed852e2009-09-05 21:47:34 +00003746 i++;
3747 }
3748 }
cristy41cbe682011-07-15 19:12:37 +00003749 kernel_info->values[i/2]=(double) ((-2.0)*normalize);
cristy0a922382011-07-16 15:30:34 +00003750 kernel_info->bias=image->bias;
cristy5e6be1e2011-07-16 01:23:39 +00003751 sharp_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00003752 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00003753 return(sharp_image);
3754}
3755
3756/*
3757%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3758% %
3759% %
3760% %
3761% S p r e a d I m a g e %
3762% %
3763% %
3764% %
3765%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3766%
3767% SpreadImage() is a special effects method that randomly displaces each
3768% pixel in a block defined by the radius parameter.
3769%
3770% The format of the SpreadImage method is:
3771%
3772% Image *SpreadImage(const Image *image,const double radius,
3773% ExceptionInfo *exception)
3774%
3775% A description of each parameter follows:
3776%
3777% o image: the image.
3778%
3779% o radius: Choose a random pixel in a neighborhood of this extent.
3780%
3781% o exception: return any errors or warnings in this structure.
3782%
3783*/
3784MagickExport Image *SpreadImage(const Image *image,const double radius,
3785 ExceptionInfo *exception)
3786{
3787#define SpreadImageTag "Spread/Image"
3788
cristyfa112112010-01-04 17:48:07 +00003789 CacheView
cristy9f7e7cb2011-03-26 00:49:57 +00003790 *image_view,
3791 *spread_view;
cristyfa112112010-01-04 17:48:07 +00003792
cristy3ed852e2009-09-05 21:47:34 +00003793 Image
3794 *spread_image;
3795
cristy3ed852e2009-09-05 21:47:34 +00003796 MagickBooleanType
3797 status;
3798
cristybb503372010-05-27 20:51:26 +00003799 MagickOffsetType
3800 progress;
3801
cristy4c08aed2011-07-01 19:47:50 +00003802 PixelInfo
cristyddd82202009-11-03 20:14:50 +00003803 bias;
cristy3ed852e2009-09-05 21:47:34 +00003804
3805 RandomInfo
cristyfa112112010-01-04 17:48:07 +00003806 **restrict random_info;
cristy3ed852e2009-09-05 21:47:34 +00003807
cristybb503372010-05-27 20:51:26 +00003808 size_t
cristy3ed852e2009-09-05 21:47:34 +00003809 width;
3810
cristybb503372010-05-27 20:51:26 +00003811 ssize_t
3812 y;
3813
cristy3ed852e2009-09-05 21:47:34 +00003814 /*
3815 Initialize spread image attributes.
3816 */
3817 assert(image != (Image *) NULL);
3818 assert(image->signature == MagickSignature);
3819 if (image->debug != MagickFalse)
3820 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3821 assert(exception != (ExceptionInfo *) NULL);
3822 assert(exception->signature == MagickSignature);
3823 spread_image=CloneImage(image,image->columns,image->rows,MagickTrue,
3824 exception);
3825 if (spread_image == (Image *) NULL)
3826 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00003827 if (SetImageStorageClass(spread_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003828 {
cristy3ed852e2009-09-05 21:47:34 +00003829 spread_image=DestroyImage(spread_image);
3830 return((Image *) NULL);
3831 }
3832 /*
3833 Spread image.
3834 */
3835 status=MagickTrue;
3836 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00003837 GetPixelInfo(spread_image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003838 width=GetOptimalKernelWidth1D(radius,0.5);
cristy3ed852e2009-09-05 21:47:34 +00003839 random_info=AcquireRandomInfoThreadSet();
cristy9f7e7cb2011-03-26 00:49:57 +00003840 image_view=AcquireCacheView(image);
3841 spread_view=AcquireCacheView(spread_image);
cristyb557a152011-02-22 12:14:30 +00003842#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00003843 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00003844#endif
cristybb503372010-05-27 20:51:26 +00003845 for (y=0; y < (ssize_t) spread_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003846 {
cristy5c9e6f22010-09-17 17:31:01 +00003847 const int
3848 id = GetOpenMPThreadId();
cristy6ebe97c2010-07-03 01:17:28 +00003849
cristy4c08aed2011-07-01 19:47:50 +00003850 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003851 pixel;
3852
cristy4c08aed2011-07-01 19:47:50 +00003853 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003854 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003855
cristy117ff172010-08-15 21:35:32 +00003856 register ssize_t
3857 x;
3858
cristy3ed852e2009-09-05 21:47:34 +00003859 if (status == MagickFalse)
3860 continue;
cristy9f7e7cb2011-03-26 00:49:57 +00003861 q=QueueCacheViewAuthenticPixels(spread_view,0,y,spread_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +00003862 exception);
cristy4c08aed2011-07-01 19:47:50 +00003863 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003864 {
3865 status=MagickFalse;
3866 continue;
3867 }
cristyddd82202009-11-03 20:14:50 +00003868 pixel=bias;
cristybb503372010-05-27 20:51:26 +00003869 for (x=0; x < (ssize_t) spread_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003870 {
cristy4c08aed2011-07-01 19:47:50 +00003871 (void) InterpolatePixelInfo(image,image_view,
cristy8a7c3e82011-03-26 02:10:53 +00003872 UndefinedInterpolatePixel,(double) x+width*(GetPseudoRandomValue(
3873 random_info[id])-0.5),(double) y+width*(GetPseudoRandomValue(
3874 random_info[id])-0.5),&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00003875 SetPixelPixelInfo(spread_image,&pixel,q);
cristyed231572011-07-14 02:18:59 +00003876 q+=GetPixelChannels(spread_image);
cristy3ed852e2009-09-05 21:47:34 +00003877 }
cristy9f7e7cb2011-03-26 00:49:57 +00003878 if (SyncCacheViewAuthenticPixels(spread_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003879 status=MagickFalse;
3880 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3881 {
3882 MagickBooleanType
3883 proceed;
3884
cristyb557a152011-02-22 12:14:30 +00003885#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003886 #pragma omp critical (MagickCore_SpreadImage)
3887#endif
3888 proceed=SetImageProgress(image,SpreadImageTag,progress++,image->rows);
3889 if (proceed == MagickFalse)
3890 status=MagickFalse;
3891 }
3892 }
cristy9f7e7cb2011-03-26 00:49:57 +00003893 spread_view=DestroyCacheView(spread_view);
cristy3ed852e2009-09-05 21:47:34 +00003894 image_view=DestroyCacheView(image_view);
3895 random_info=DestroyRandomInfoThreadSet(random_info);
cristy3ed852e2009-09-05 21:47:34 +00003896 return(spread_image);
3897}
3898
3899/*
3900%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3901% %
3902% %
3903% %
cristy0834d642011-03-18 18:26:08 +00003904% S t a t i s t i c I m a g e %
3905% %
3906% %
3907% %
3908%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3909%
3910% StatisticImage() makes each pixel the min / max / median / mode / etc. of
cristy8d752042011-03-19 01:00:36 +00003911% the neighborhood of the specified width and height.
cristy0834d642011-03-18 18:26:08 +00003912%
3913% The format of the StatisticImage method is:
3914%
3915% Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00003916% const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00003917%
3918% A description of each parameter follows:
3919%
3920% o image: the image.
3921%
cristy0834d642011-03-18 18:26:08 +00003922% o type: the statistic type (median, mode, etc.).
3923%
cristy95c38342011-03-18 22:39:51 +00003924% o width: the width of the pixel neighborhood.
3925%
3926% o height: the height of the pixel neighborhood.
cristy0834d642011-03-18 18:26:08 +00003927%
3928% o exception: return any errors or warnings in this structure.
3929%
3930*/
3931
cristy733678d2011-03-18 21:29:28 +00003932#define ListChannels 5
3933
3934typedef struct _ListNode
3935{
3936 size_t
3937 next[9],
3938 count,
3939 signature;
3940} ListNode;
3941
3942typedef struct _SkipList
3943{
3944 ssize_t
3945 level;
3946
3947 ListNode
3948 *nodes;
3949} SkipList;
3950
3951typedef struct _PixelList
3952{
3953 size_t
cristy6fc86bb2011-03-18 23:45:16 +00003954 length,
cristy733678d2011-03-18 21:29:28 +00003955 seed,
3956 signature;
3957
3958 SkipList
3959 lists[ListChannels];
3960} PixelList;
3961
3962static PixelList *DestroyPixelList(PixelList *pixel_list)
3963{
3964 register ssize_t
3965 i;
3966
3967 if (pixel_list == (PixelList *) NULL)
3968 return((PixelList *) NULL);
3969 for (i=0; i < ListChannels; i++)
3970 if (pixel_list->lists[i].nodes != (ListNode *) NULL)
3971 pixel_list->lists[i].nodes=(ListNode *) RelinquishMagickMemory(
3972 pixel_list->lists[i].nodes);
3973 pixel_list=(PixelList *) RelinquishMagickMemory(pixel_list);
3974 return(pixel_list);
3975}
3976
3977static PixelList **DestroyPixelListThreadSet(PixelList **pixel_list)
3978{
3979 register ssize_t
3980 i;
3981
3982 assert(pixel_list != (PixelList **) NULL);
3983 for (i=0; i < (ssize_t) GetOpenMPMaximumThreads(); i++)
3984 if (pixel_list[i] != (PixelList *) NULL)
3985 pixel_list[i]=DestroyPixelList(pixel_list[i]);
3986 pixel_list=(PixelList **) RelinquishMagickMemory(pixel_list);
3987 return(pixel_list);
3988}
3989
cristy6fc86bb2011-03-18 23:45:16 +00003990static PixelList *AcquirePixelList(const size_t width,const size_t height)
cristy733678d2011-03-18 21:29:28 +00003991{
3992 PixelList
3993 *pixel_list;
3994
3995 register ssize_t
3996 i;
3997
3998 pixel_list=(PixelList *) AcquireMagickMemory(sizeof(*pixel_list));
3999 if (pixel_list == (PixelList *) NULL)
4000 return(pixel_list);
4001 (void) ResetMagickMemory((void *) pixel_list,0,sizeof(*pixel_list));
cristy6fc86bb2011-03-18 23:45:16 +00004002 pixel_list->length=width*height;
cristy733678d2011-03-18 21:29:28 +00004003 for (i=0; i < ListChannels; i++)
4004 {
4005 pixel_list->lists[i].nodes=(ListNode *) AcquireQuantumMemory(65537UL,
4006 sizeof(*pixel_list->lists[i].nodes));
4007 if (pixel_list->lists[i].nodes == (ListNode *) NULL)
4008 return(DestroyPixelList(pixel_list));
4009 (void) ResetMagickMemory(pixel_list->lists[i].nodes,0,65537UL*
4010 sizeof(*pixel_list->lists[i].nodes));
4011 }
4012 pixel_list->signature=MagickSignature;
4013 return(pixel_list);
4014}
4015
cristy6fc86bb2011-03-18 23:45:16 +00004016static PixelList **AcquirePixelListThreadSet(const size_t width,
4017 const size_t height)
cristy733678d2011-03-18 21:29:28 +00004018{
4019 PixelList
4020 **pixel_list;
4021
4022 register ssize_t
4023 i;
4024
4025 size_t
4026 number_threads;
4027
4028 number_threads=GetOpenMPMaximumThreads();
4029 pixel_list=(PixelList **) AcquireQuantumMemory(number_threads,
4030 sizeof(*pixel_list));
4031 if (pixel_list == (PixelList **) NULL)
4032 return((PixelList **) NULL);
4033 (void) ResetMagickMemory(pixel_list,0,number_threads*sizeof(*pixel_list));
4034 for (i=0; i < (ssize_t) number_threads; i++)
4035 {
cristy6fc86bb2011-03-18 23:45:16 +00004036 pixel_list[i]=AcquirePixelList(width,height);
cristy733678d2011-03-18 21:29:28 +00004037 if (pixel_list[i] == (PixelList *) NULL)
4038 return(DestroyPixelListThreadSet(pixel_list));
4039 }
4040 return(pixel_list);
4041}
4042
4043static void AddNodePixelList(PixelList *pixel_list,const ssize_t channel,
4044 const size_t color)
4045{
4046 register SkipList
4047 *list;
4048
4049 register ssize_t
4050 level;
4051
4052 size_t
4053 search,
4054 update[9];
4055
4056 /*
4057 Initialize the node.
4058 */
4059 list=pixel_list->lists+channel;
4060 list->nodes[color].signature=pixel_list->signature;
4061 list->nodes[color].count=1;
4062 /*
4063 Determine where it belongs in the list.
4064 */
4065 search=65536UL;
4066 for (level=list->level; level >= 0; level--)
4067 {
4068 while (list->nodes[search].next[level] < color)
4069 search=list->nodes[search].next[level];
4070 update[level]=search;
4071 }
4072 /*
4073 Generate a pseudo-random level for this node.
4074 */
4075 for (level=0; ; level++)
4076 {
4077 pixel_list->seed=(pixel_list->seed*42893621L)+1L;
4078 if ((pixel_list->seed & 0x300) != 0x300)
4079 break;
4080 }
4081 if (level > 8)
4082 level=8;
4083 if (level > (list->level+2))
4084 level=list->level+2;
4085 /*
4086 If we're raising the list's level, link back to the root node.
4087 */
4088 while (level > list->level)
4089 {
4090 list->level++;
4091 update[list->level]=65536UL;
4092 }
4093 /*
4094 Link the node into the skip-list.
4095 */
4096 do
4097 {
4098 list->nodes[color].next[level]=list->nodes[update[level]].next[level];
4099 list->nodes[update[level]].next[level]=color;
cristy3cba8ca2011-03-19 01:29:12 +00004100 } while (level-- > 0);
cristy733678d2011-03-18 21:29:28 +00004101}
4102
cristy4c08aed2011-07-01 19:47:50 +00004103static PixelInfo GetMaximumPixelList(PixelList *pixel_list)
cristy6fc86bb2011-03-18 23:45:16 +00004104{
cristy4c08aed2011-07-01 19:47:50 +00004105 PixelInfo
cristy6fc86bb2011-03-18 23:45:16 +00004106 pixel;
4107
4108 register SkipList
4109 *list;
4110
4111 register ssize_t
4112 channel;
4113
4114 size_t
cristyd76c51e2011-03-26 00:21:26 +00004115 color,
4116 maximum;
cristy49f37242011-03-22 18:18:23 +00004117
4118 ssize_t
cristy6fc86bb2011-03-18 23:45:16 +00004119 count;
4120
4121 unsigned short
cristyd76c51e2011-03-26 00:21:26 +00004122 channels[ListChannels];
cristy6fc86bb2011-03-18 23:45:16 +00004123
4124 /*
4125 Find the maximum value for each of the color.
4126 */
4127 for (channel=0; channel < 5; channel++)
4128 {
4129 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004130 color=65536L;
cristy6fc86bb2011-03-18 23:45:16 +00004131 count=0;
cristy49f37242011-03-22 18:18:23 +00004132 maximum=list->nodes[color].next[0];
cristy6fc86bb2011-03-18 23:45:16 +00004133 do
4134 {
4135 color=list->nodes[color].next[0];
cristy49f37242011-03-22 18:18:23 +00004136 if (color > maximum)
4137 maximum=color;
cristy6fc86bb2011-03-18 23:45:16 +00004138 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004139 } while (count < (ssize_t) pixel_list->length);
cristy49f37242011-03-22 18:18:23 +00004140 channels[channel]=(unsigned short) maximum;
4141 }
cristy4c08aed2011-07-01 19:47:50 +00004142 GetPixelInfo((const Image *) NULL,&pixel);
cristy49f37242011-03-22 18:18:23 +00004143 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4144 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4145 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004146 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4147 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy49f37242011-03-22 18:18:23 +00004148 return(pixel);
4149}
4150
cristy4c08aed2011-07-01 19:47:50 +00004151static PixelInfo GetMeanPixelList(PixelList *pixel_list)
cristy49f37242011-03-22 18:18:23 +00004152{
cristy4c08aed2011-07-01 19:47:50 +00004153 PixelInfo
cristy49f37242011-03-22 18:18:23 +00004154 pixel;
4155
cristy80a99a32011-03-30 01:30:23 +00004156 MagickRealType
4157 sum;
4158
cristy49f37242011-03-22 18:18:23 +00004159 register SkipList
4160 *list;
4161
4162 register ssize_t
4163 channel;
4164
4165 size_t
cristy80a99a32011-03-30 01:30:23 +00004166 color;
cristy49f37242011-03-22 18:18:23 +00004167
4168 ssize_t
4169 count;
4170
4171 unsigned short
4172 channels[ListChannels];
4173
4174 /*
4175 Find the mean value for each of the color.
4176 */
4177 for (channel=0; channel < 5; channel++)
4178 {
4179 list=pixel_list->lists+channel;
4180 color=65536L;
4181 count=0;
cristy80a99a32011-03-30 01:30:23 +00004182 sum=0.0;
cristy49f37242011-03-22 18:18:23 +00004183 do
4184 {
4185 color=list->nodes[color].next[0];
cristy80a99a32011-03-30 01:30:23 +00004186 sum+=(MagickRealType) list->nodes[color].count*color;
cristy49f37242011-03-22 18:18:23 +00004187 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004188 } while (count < (ssize_t) pixel_list->length);
cristy80a99a32011-03-30 01:30:23 +00004189 sum/=pixel_list->length;
4190 channels[channel]=(unsigned short) sum;
cristy6fc86bb2011-03-18 23:45:16 +00004191 }
cristy4c08aed2011-07-01 19:47:50 +00004192 GetPixelInfo((const Image *) NULL,&pixel);
cristy6fc86bb2011-03-18 23:45:16 +00004193 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4194 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4195 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004196 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4197 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy6fc86bb2011-03-18 23:45:16 +00004198 return(pixel);
4199}
4200
cristy4c08aed2011-07-01 19:47:50 +00004201static PixelInfo GetMedianPixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004202{
cristy4c08aed2011-07-01 19:47:50 +00004203 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004204 pixel;
4205
4206 register SkipList
4207 *list;
4208
4209 register ssize_t
4210 channel;
4211
4212 size_t
cristy49f37242011-03-22 18:18:23 +00004213 color;
4214
4215 ssize_t
cristy733678d2011-03-18 21:29:28 +00004216 count;
4217
4218 unsigned short
4219 channels[ListChannels];
4220
4221 /*
4222 Find the median value for each of the color.
4223 */
cristy733678d2011-03-18 21:29:28 +00004224 for (channel=0; channel < 5; channel++)
4225 {
4226 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004227 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004228 count=0;
4229 do
4230 {
4231 color=list->nodes[color].next[0];
4232 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004233 } while (count <= (ssize_t) (pixel_list->length >> 1));
cristy6fc86bb2011-03-18 23:45:16 +00004234 channels[channel]=(unsigned short) color;
4235 }
cristy4c08aed2011-07-01 19:47:50 +00004236 GetPixelInfo((const Image *) NULL,&pixel);
cristy6fc86bb2011-03-18 23:45:16 +00004237 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4238 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4239 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004240 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4241 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy6fc86bb2011-03-18 23:45:16 +00004242 return(pixel);
4243}
4244
cristy4c08aed2011-07-01 19:47:50 +00004245static PixelInfo GetMinimumPixelList(PixelList *pixel_list)
cristy6fc86bb2011-03-18 23:45:16 +00004246{
cristy4c08aed2011-07-01 19:47:50 +00004247 PixelInfo
cristy6fc86bb2011-03-18 23:45:16 +00004248 pixel;
4249
4250 register SkipList
4251 *list;
4252
4253 register ssize_t
4254 channel;
4255
4256 size_t
cristyd76c51e2011-03-26 00:21:26 +00004257 color,
4258 minimum;
cristy6fc86bb2011-03-18 23:45:16 +00004259
cristy49f37242011-03-22 18:18:23 +00004260 ssize_t
4261 count;
4262
cristy6fc86bb2011-03-18 23:45:16 +00004263 unsigned short
cristyd76c51e2011-03-26 00:21:26 +00004264 channels[ListChannels];
cristy6fc86bb2011-03-18 23:45:16 +00004265
4266 /*
4267 Find the minimum value for each of the color.
4268 */
4269 for (channel=0; channel < 5; channel++)
4270 {
4271 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004272 count=0;
cristy6fc86bb2011-03-18 23:45:16 +00004273 color=65536UL;
cristy49f37242011-03-22 18:18:23 +00004274 minimum=list->nodes[color].next[0];
4275 do
4276 {
4277 color=list->nodes[color].next[0];
4278 if (color < minimum)
4279 minimum=color;
4280 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004281 } while (count < (ssize_t) pixel_list->length);
cristy49f37242011-03-22 18:18:23 +00004282 channels[channel]=(unsigned short) minimum;
cristy733678d2011-03-18 21:29:28 +00004283 }
cristy4c08aed2011-07-01 19:47:50 +00004284 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004285 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4286 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4287 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004288 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4289 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy733678d2011-03-18 21:29:28 +00004290 return(pixel);
4291}
4292
cristy4c08aed2011-07-01 19:47:50 +00004293static PixelInfo GetModePixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004294{
cristy4c08aed2011-07-01 19:47:50 +00004295 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004296 pixel;
4297
4298 register SkipList
4299 *list;
4300
4301 register ssize_t
4302 channel;
4303
4304 size_t
4305 color,
cristy733678d2011-03-18 21:29:28 +00004306 max_count,
cristy6fc86bb2011-03-18 23:45:16 +00004307 mode;
cristy733678d2011-03-18 21:29:28 +00004308
cristy49f37242011-03-22 18:18:23 +00004309 ssize_t
4310 count;
4311
cristy733678d2011-03-18 21:29:28 +00004312 unsigned short
4313 channels[5];
4314
4315 /*
glennrp30d2dc62011-06-25 03:17:16 +00004316 Make each pixel the 'predominant color' of the specified neighborhood.
cristy733678d2011-03-18 21:29:28 +00004317 */
cristy733678d2011-03-18 21:29:28 +00004318 for (channel=0; channel < 5; channel++)
4319 {
4320 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004321 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004322 mode=color;
4323 max_count=list->nodes[mode].count;
4324 count=0;
4325 do
4326 {
4327 color=list->nodes[color].next[0];
4328 if (list->nodes[color].count > max_count)
4329 {
4330 mode=color;
4331 max_count=list->nodes[mode].count;
4332 }
4333 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004334 } while (count < (ssize_t) pixel_list->length);
cristy733678d2011-03-18 21:29:28 +00004335 channels[channel]=(unsigned short) mode;
4336 }
cristy4c08aed2011-07-01 19:47:50 +00004337 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004338 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4339 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4340 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004341 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4342 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy733678d2011-03-18 21:29:28 +00004343 return(pixel);
4344}
4345
cristy4c08aed2011-07-01 19:47:50 +00004346static PixelInfo GetNonpeakPixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004347{
cristy4c08aed2011-07-01 19:47:50 +00004348 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004349 pixel;
4350
4351 register SkipList
4352 *list;
4353
4354 register ssize_t
4355 channel;
4356
4357 size_t
cristy733678d2011-03-18 21:29:28 +00004358 color,
cristy733678d2011-03-18 21:29:28 +00004359 next,
4360 previous;
4361
cristy49f37242011-03-22 18:18:23 +00004362 ssize_t
4363 count;
4364
cristy733678d2011-03-18 21:29:28 +00004365 unsigned short
4366 channels[5];
4367
4368 /*
cristy49f37242011-03-22 18:18:23 +00004369 Finds the non peak value for each of the colors.
cristy733678d2011-03-18 21:29:28 +00004370 */
cristy733678d2011-03-18 21:29:28 +00004371 for (channel=0; channel < 5; channel++)
4372 {
4373 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004374 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004375 next=list->nodes[color].next[0];
4376 count=0;
4377 do
4378 {
4379 previous=color;
4380 color=next;
4381 next=list->nodes[color].next[0];
4382 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004383 } while (count <= (ssize_t) (pixel_list->length >> 1));
cristy733678d2011-03-18 21:29:28 +00004384 if ((previous == 65536UL) && (next != 65536UL))
4385 color=next;
4386 else
4387 if ((previous != 65536UL) && (next == 65536UL))
4388 color=previous;
4389 channels[channel]=(unsigned short) color;
4390 }
cristy4c08aed2011-07-01 19:47:50 +00004391 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004392 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4393 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4394 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004395 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4396 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy733678d2011-03-18 21:29:28 +00004397 return(pixel);
4398}
4399
cristy4c08aed2011-07-01 19:47:50 +00004400static PixelInfo GetStandardDeviationPixelList(PixelList *pixel_list)
cristy9a68cbb2011-03-29 00:51:23 +00004401{
cristy4c08aed2011-07-01 19:47:50 +00004402 PixelInfo
cristy9a68cbb2011-03-29 00:51:23 +00004403 pixel;
4404
cristy80a99a32011-03-30 01:30:23 +00004405 MagickRealType
4406 sum,
4407 sum_squared;
4408
cristy9a68cbb2011-03-29 00:51:23 +00004409 register SkipList
4410 *list;
4411
4412 register ssize_t
4413 channel;
4414
4415 size_t
cristy80a99a32011-03-30 01:30:23 +00004416 color;
cristy9a68cbb2011-03-29 00:51:23 +00004417
4418 ssize_t
4419 count;
4420
4421 unsigned short
4422 channels[ListChannels];
4423
4424 /*
cristy80a99a32011-03-30 01:30:23 +00004425 Find the standard-deviation value for each of the color.
cristy9a68cbb2011-03-29 00:51:23 +00004426 */
4427 for (channel=0; channel < 5; channel++)
4428 {
4429 list=pixel_list->lists+channel;
4430 color=65536L;
4431 count=0;
cristy80a99a32011-03-30 01:30:23 +00004432 sum=0.0;
4433 sum_squared=0.0;
cristy9a68cbb2011-03-29 00:51:23 +00004434 do
4435 {
cristy80a99a32011-03-30 01:30:23 +00004436 register ssize_t
4437 i;
4438
cristy9a68cbb2011-03-29 00:51:23 +00004439 color=list->nodes[color].next[0];
cristy80a99a32011-03-30 01:30:23 +00004440 sum+=(MagickRealType) list->nodes[color].count*color;
4441 for (i=0; i < (ssize_t) list->nodes[color].count; i++)
4442 sum_squared+=((MagickRealType) color)*((MagickRealType) color);
cristy9a68cbb2011-03-29 00:51:23 +00004443 count+=list->nodes[color].count;
4444 } while (count < (ssize_t) pixel_list->length);
cristy80a99a32011-03-30 01:30:23 +00004445 sum/=pixel_list->length;
4446 sum_squared/=pixel_list->length;
4447 channels[channel]=(unsigned short) sqrt(sum_squared-(sum*sum));
cristy9a68cbb2011-03-29 00:51:23 +00004448 }
cristy4c08aed2011-07-01 19:47:50 +00004449 GetPixelInfo((const Image *) NULL,&pixel);
cristy9a68cbb2011-03-29 00:51:23 +00004450 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4451 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4452 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004453 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4454 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy9a68cbb2011-03-29 00:51:23 +00004455 return(pixel);
4456}
4457
cristy4c08aed2011-07-01 19:47:50 +00004458static inline void InsertPixelList(const Image *image,const Quantum *pixel,
4459 PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004460{
4461 size_t
4462 signature;
4463
4464 unsigned short
4465 index;
4466
cristy4c08aed2011-07-01 19:47:50 +00004467 index=ScaleQuantumToShort(GetPixelRed(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004468 signature=pixel_list->lists[0].nodes[index].signature;
4469 if (signature == pixel_list->signature)
4470 pixel_list->lists[0].nodes[index].count++;
4471 else
4472 AddNodePixelList(pixel_list,0,index);
cristy4c08aed2011-07-01 19:47:50 +00004473 index=ScaleQuantumToShort(GetPixelGreen(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004474 signature=pixel_list->lists[1].nodes[index].signature;
4475 if (signature == pixel_list->signature)
4476 pixel_list->lists[1].nodes[index].count++;
4477 else
4478 AddNodePixelList(pixel_list,1,index);
cristy4c08aed2011-07-01 19:47:50 +00004479 index=ScaleQuantumToShort(GetPixelBlue(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004480 signature=pixel_list->lists[2].nodes[index].signature;
4481 if (signature == pixel_list->signature)
4482 pixel_list->lists[2].nodes[index].count++;
4483 else
4484 AddNodePixelList(pixel_list,2,index);
cristy4c08aed2011-07-01 19:47:50 +00004485 index=ScaleQuantumToShort(GetPixelAlpha(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004486 signature=pixel_list->lists[3].nodes[index].signature;
4487 if (signature == pixel_list->signature)
4488 pixel_list->lists[3].nodes[index].count++;
4489 else
4490 AddNodePixelList(pixel_list,3,index);
4491 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00004492 index=ScaleQuantumToShort(GetPixelBlack(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004493 signature=pixel_list->lists[4].nodes[index].signature;
4494 if (signature == pixel_list->signature)
4495 pixel_list->lists[4].nodes[index].count++;
4496 else
4497 AddNodePixelList(pixel_list,4,index);
4498}
4499
cristy80c99742011-04-04 14:46:39 +00004500static inline MagickRealType MagickAbsoluteValue(const MagickRealType x)
4501{
4502 if (x < 0)
4503 return(-x);
4504 return(x);
4505}
4506
cristy733678d2011-03-18 21:29:28 +00004507static void ResetPixelList(PixelList *pixel_list)
4508{
4509 int
4510 level;
4511
4512 register ListNode
4513 *root;
4514
4515 register SkipList
4516 *list;
4517
4518 register ssize_t
4519 channel;
4520
4521 /*
4522 Reset the skip-list.
4523 */
4524 for (channel=0; channel < 5; channel++)
4525 {
4526 list=pixel_list->lists+channel;
4527 root=list->nodes+65536UL;
4528 list->level=0;
4529 for (level=0; level < 9; level++)
4530 root->next[level]=65536UL;
4531 }
4532 pixel_list->seed=pixel_list->signature++;
4533}
4534
cristy0834d642011-03-18 18:26:08 +00004535MagickExport Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00004536 const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00004537{
cristy3cba8ca2011-03-19 01:29:12 +00004538#define StatisticWidth \
cristyd76c51e2011-03-26 00:21:26 +00004539 (width == 0 ? GetOptimalKernelWidth2D((double) width,0.5) : width)
cristy3cba8ca2011-03-19 01:29:12 +00004540#define StatisticHeight \
cristyd76c51e2011-03-26 00:21:26 +00004541 (height == 0 ? GetOptimalKernelWidth2D((double) height,0.5) : height)
cristy0834d642011-03-18 18:26:08 +00004542#define StatisticImageTag "Statistic/Image"
4543
4544 CacheView
4545 *image_view,
4546 *statistic_view;
4547
4548 Image
4549 *statistic_image;
4550
4551 MagickBooleanType
4552 status;
4553
4554 MagickOffsetType
4555 progress;
4556
4557 PixelList
4558 **restrict pixel_list;
4559
cristy0834d642011-03-18 18:26:08 +00004560 ssize_t
4561 y;
4562
4563 /*
4564 Initialize statistics image attributes.
4565 */
4566 assert(image != (Image *) NULL);
4567 assert(image->signature == MagickSignature);
4568 if (image->debug != MagickFalse)
4569 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4570 assert(exception != (ExceptionInfo *) NULL);
4571 assert(exception->signature == MagickSignature);
cristy0834d642011-03-18 18:26:08 +00004572 statistic_image=CloneImage(image,image->columns,image->rows,MagickTrue,
4573 exception);
4574 if (statistic_image == (Image *) NULL)
4575 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00004576 if (SetImageStorageClass(statistic_image,DirectClass,exception) == MagickFalse)
cristy0834d642011-03-18 18:26:08 +00004577 {
cristy0834d642011-03-18 18:26:08 +00004578 statistic_image=DestroyImage(statistic_image);
4579 return((Image *) NULL);
4580 }
cristy6fc86bb2011-03-18 23:45:16 +00004581 pixel_list=AcquirePixelListThreadSet(StatisticWidth,StatisticHeight);
cristy0834d642011-03-18 18:26:08 +00004582 if (pixel_list == (PixelList **) NULL)
4583 {
4584 statistic_image=DestroyImage(statistic_image);
4585 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
4586 }
4587 /*
cristy8d752042011-03-19 01:00:36 +00004588 Make each pixel the min / max / median / mode / etc. of the neighborhood.
cristy0834d642011-03-18 18:26:08 +00004589 */
4590 status=MagickTrue;
4591 progress=0;
4592 image_view=AcquireCacheView(image);
4593 statistic_view=AcquireCacheView(statistic_image);
4594#if defined(MAGICKCORE_OPENMP_SUPPORT)
4595 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
4596#endif
4597 for (y=0; y < (ssize_t) statistic_image->rows; y++)
4598 {
4599 const int
4600 id = GetOpenMPThreadId();
4601
cristy4c08aed2011-07-01 19:47:50 +00004602 register const Quantum
cristy0834d642011-03-18 18:26:08 +00004603 *restrict p;
4604
cristy4c08aed2011-07-01 19:47:50 +00004605 register Quantum
cristy0834d642011-03-18 18:26:08 +00004606 *restrict q;
4607
4608 register ssize_t
4609 x;
4610
4611 if (status == MagickFalse)
4612 continue;
cristy6fc86bb2011-03-18 23:45:16 +00004613 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) StatisticWidth/2L),y-
4614 (ssize_t) (StatisticHeight/2L),image->columns+StatisticWidth,
4615 StatisticHeight,exception);
cristy3cba8ca2011-03-19 01:29:12 +00004616 q=QueueCacheViewAuthenticPixels(statistic_view,0,y,statistic_image->columns, 1,exception);
cristy4c08aed2011-07-01 19:47:50 +00004617 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy0834d642011-03-18 18:26:08 +00004618 {
4619 status=MagickFalse;
4620 continue;
4621 }
cristy0834d642011-03-18 18:26:08 +00004622 for (x=0; x < (ssize_t) statistic_image->columns; x++)
4623 {
cristy4c08aed2011-07-01 19:47:50 +00004624 PixelInfo
cristy0834d642011-03-18 18:26:08 +00004625 pixel;
4626
cristy4c08aed2011-07-01 19:47:50 +00004627 register const Quantum
cristy6e3026a2011-03-19 00:54:38 +00004628 *restrict r;
4629
cristy0834d642011-03-18 18:26:08 +00004630 register ssize_t
4631 u,
4632 v;
4633
4634 r=p;
cristy0834d642011-03-18 18:26:08 +00004635 ResetPixelList(pixel_list[id]);
cristy6e4c3292011-03-19 00:53:55 +00004636 for (v=0; v < (ssize_t) StatisticHeight; v++)
cristy0834d642011-03-18 18:26:08 +00004637 {
cristy6e4c3292011-03-19 00:53:55 +00004638 for (u=0; u < (ssize_t) StatisticWidth; u++)
cristyed231572011-07-14 02:18:59 +00004639 InsertPixelList(image,r+u*GetPixelChannels(image),pixel_list[id]);
4640 r+=(image->columns+StatisticWidth)*GetPixelChannels(image);
cristy0834d642011-03-18 18:26:08 +00004641 }
cristy4c08aed2011-07-01 19:47:50 +00004642 GetPixelInfo(image,&pixel);
cristy490408a2011-07-07 14:42:05 +00004643 SetPixelInfo(image,p+(StatisticWidth*StatisticHeight/2)*
cristyed231572011-07-14 02:18:59 +00004644 GetPixelChannels(image),&pixel);
cristy0834d642011-03-18 18:26:08 +00004645 switch (type)
4646 {
cristy80c99742011-04-04 14:46:39 +00004647 case GradientStatistic:
4648 {
cristy4c08aed2011-07-01 19:47:50 +00004649 PixelInfo
cristy80c99742011-04-04 14:46:39 +00004650 maximum,
4651 minimum;
4652
4653 minimum=GetMinimumPixelList(pixel_list[id]);
4654 maximum=GetMaximumPixelList(pixel_list[id]);
4655 pixel.red=MagickAbsoluteValue(maximum.red-minimum.red);
4656 pixel.green=MagickAbsoluteValue(maximum.green-minimum.green);
4657 pixel.blue=MagickAbsoluteValue(maximum.blue-minimum.blue);
cristy4c08aed2011-07-01 19:47:50 +00004658 pixel.alpha=MagickAbsoluteValue(maximum.alpha-minimum.alpha);
cristy80c99742011-04-04 14:46:39 +00004659 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00004660 pixel.black=MagickAbsoluteValue(maximum.black-minimum.black);
cristy80c99742011-04-04 14:46:39 +00004661 break;
4662 }
cristy6fc86bb2011-03-18 23:45:16 +00004663 case MaximumStatistic:
4664 {
4665 pixel=GetMaximumPixelList(pixel_list[id]);
4666 break;
4667 }
cristy49f37242011-03-22 18:18:23 +00004668 case MeanStatistic:
4669 {
4670 pixel=GetMeanPixelList(pixel_list[id]);
4671 break;
4672 }
cristyf2ad14a2011-03-18 18:57:25 +00004673 case MedianStatistic:
cristy6fc86bb2011-03-18 23:45:16 +00004674 default:
cristyf2ad14a2011-03-18 18:57:25 +00004675 {
4676 pixel=GetMedianPixelList(pixel_list[id]);
4677 break;
4678 }
cristy6fc86bb2011-03-18 23:45:16 +00004679 case MinimumStatistic:
4680 {
4681 pixel=GetMinimumPixelList(pixel_list[id]);
4682 break;
4683 }
cristyf2ad14a2011-03-18 18:57:25 +00004684 case ModeStatistic:
4685 {
4686 pixel=GetModePixelList(pixel_list[id]);
4687 break;
4688 }
4689 case NonpeakStatistic:
4690 {
4691 pixel=GetNonpeakPixelList(pixel_list[id]);
4692 break;
4693 }
cristy9a68cbb2011-03-29 00:51:23 +00004694 case StandardDeviationStatistic:
4695 {
4696 pixel=GetStandardDeviationPixelList(pixel_list[id]);
4697 break;
4698 }
cristy0834d642011-03-18 18:26:08 +00004699 }
cristyed231572011-07-14 02:18:59 +00004700 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00004701 SetPixelRed(statistic_image,ClampToQuantum(pixel.red),q);
cristyed231572011-07-14 02:18:59 +00004702 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00004703 SetPixelGreen(statistic_image,ClampToQuantum(pixel.green),q);
cristyed231572011-07-14 02:18:59 +00004704 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00004705 SetPixelBlue(statistic_image,ClampToQuantum(pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00004706 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy0834d642011-03-18 18:26:08 +00004707 (image->colorspace == CMYKColorspace))
cristy490408a2011-07-07 14:42:05 +00004708 SetPixelBlack(statistic_image,ClampToQuantum(pixel.black),q);
cristyed231572011-07-14 02:18:59 +00004709 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00004710 (image->matte != MagickFalse))
cristy490408a2011-07-07 14:42:05 +00004711 SetPixelAlpha(statistic_image,ClampToQuantum(pixel.alpha),q);
cristyed231572011-07-14 02:18:59 +00004712 p+=GetPixelChannels(image);
4713 q+=GetPixelChannels(statistic_image);
cristy0834d642011-03-18 18:26:08 +00004714 }
4715 if (SyncCacheViewAuthenticPixels(statistic_view,exception) == MagickFalse)
4716 status=MagickFalse;
4717 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4718 {
4719 MagickBooleanType
4720 proceed;
4721
4722#if defined(MAGICKCORE_OPENMP_SUPPORT)
4723 #pragma omp critical (MagickCore_StatisticImage)
4724#endif
4725 proceed=SetImageProgress(image,StatisticImageTag,progress++,
4726 image->rows);
4727 if (proceed == MagickFalse)
4728 status=MagickFalse;
4729 }
4730 }
4731 statistic_view=DestroyCacheView(statistic_view);
4732 image_view=DestroyCacheView(image_view);
4733 pixel_list=DestroyPixelListThreadSet(pixel_list);
4734 return(statistic_image);
4735}
4736
4737/*
4738%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4739% %
4740% %
4741% %
cristy3ed852e2009-09-05 21:47:34 +00004742% U n s h a r p M a s k I m a g e %
4743% %
4744% %
4745% %
4746%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4747%
4748% UnsharpMaskImage() sharpens one or more image channels. We convolve the
4749% image with a Gaussian operator of the given radius and standard deviation
4750% (sigma). For reasonable results, radius should be larger than sigma. Use a
4751% radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
4752%
4753% The format of the UnsharpMaskImage method is:
4754%
4755% Image *UnsharpMaskImage(const Image *image,const double radius,
4756% const double sigma,const double amount,const double threshold,
4757% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00004758%
4759% A description of each parameter follows:
4760%
4761% o image: the image.
4762%
cristy3ed852e2009-09-05 21:47:34 +00004763% o radius: the radius of the Gaussian, in pixels, not counting the center
4764% pixel.
4765%
4766% o sigma: the standard deviation of the Gaussian, in pixels.
4767%
4768% o amount: the percentage of the difference between the original and the
4769% blur image that is added back into the original.
4770%
4771% o threshold: the threshold in pixels needed to apply the diffence amount.
4772%
4773% o exception: return any errors or warnings in this structure.
4774%
4775*/
cristyf4ad9df2011-07-08 16:49:03 +00004776MagickExport Image *UnsharpMaskImage(const Image *image,
4777 const double radius,const double sigma,const double amount,
4778 const double threshold,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00004779{
4780#define SharpenImageTag "Sharpen/Image"
4781
cristyc4c8d132010-01-07 01:58:38 +00004782 CacheView
4783 *image_view,
4784 *unsharp_view;
4785
cristy3ed852e2009-09-05 21:47:34 +00004786 Image
4787 *unsharp_image;
4788
cristy3ed852e2009-09-05 21:47:34 +00004789 MagickBooleanType
4790 status;
4791
cristybb503372010-05-27 20:51:26 +00004792 MagickOffsetType
4793 progress;
4794
cristy4c08aed2011-07-01 19:47:50 +00004795 PixelInfo
cristyddd82202009-11-03 20:14:50 +00004796 bias;
cristy3ed852e2009-09-05 21:47:34 +00004797
4798 MagickRealType
4799 quantum_threshold;
4800
cristybb503372010-05-27 20:51:26 +00004801 ssize_t
4802 y;
4803
cristy3ed852e2009-09-05 21:47:34 +00004804 assert(image != (const Image *) NULL);
4805 assert(image->signature == MagickSignature);
4806 if (image->debug != MagickFalse)
4807 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4808 assert(exception != (ExceptionInfo *) NULL);
cristyf4ad9df2011-07-08 16:49:03 +00004809 unsharp_image=BlurImage(image,radius,sigma,exception);
cristy3ed852e2009-09-05 21:47:34 +00004810 if (unsharp_image == (Image *) NULL)
4811 return((Image *) NULL);
4812 quantum_threshold=(MagickRealType) QuantumRange*threshold;
4813 /*
4814 Unsharp-mask image.
4815 */
4816 status=MagickTrue;
4817 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00004818 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00004819 image_view=AcquireCacheView(image);
4820 unsharp_view=AcquireCacheView(unsharp_image);
cristyb5d5f722009-11-04 03:03:49 +00004821#if defined(MAGICKCORE_OPENMP_SUPPORT)
4822 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004823#endif
cristybb503372010-05-27 20:51:26 +00004824 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00004825 {
cristy4c08aed2011-07-01 19:47:50 +00004826 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00004827 pixel;
4828
cristy4c08aed2011-07-01 19:47:50 +00004829 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00004830 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00004831
cristy4c08aed2011-07-01 19:47:50 +00004832 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00004833 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004834
cristy117ff172010-08-15 21:35:32 +00004835 register ssize_t
4836 x;
4837
cristy3ed852e2009-09-05 21:47:34 +00004838 if (status == MagickFalse)
4839 continue;
4840 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
4841 q=GetCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
4842 exception);
cristy4c08aed2011-07-01 19:47:50 +00004843 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00004844 {
4845 status=MagickFalse;
4846 continue;
4847 }
cristyddd82202009-11-03 20:14:50 +00004848 pixel=bias;
cristybb503372010-05-27 20:51:26 +00004849 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004850 {
cristyed231572011-07-14 02:18:59 +00004851 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00004852 {
cristy4c08aed2011-07-01 19:47:50 +00004853 pixel.red=GetPixelRed(image,p)-(MagickRealType) GetPixelRed(image,q);
cristy3ed852e2009-09-05 21:47:34 +00004854 if (fabs(2.0*pixel.red) < quantum_threshold)
cristy4c08aed2011-07-01 19:47:50 +00004855 pixel.red=(MagickRealType) GetPixelRed(image,p);
cristy3ed852e2009-09-05 21:47:34 +00004856 else
cristy4c08aed2011-07-01 19:47:50 +00004857 pixel.red=(MagickRealType) GetPixelRed(image,p)+(pixel.red*amount);
4858 SetPixelRed(unsharp_image,ClampToQuantum(pixel.red),q);
cristy3ed852e2009-09-05 21:47:34 +00004859 }
cristyed231572011-07-14 02:18:59 +00004860 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00004861 {
cristy4c08aed2011-07-01 19:47:50 +00004862 pixel.green=GetPixelGreen(image,p)-
4863 (MagickRealType) GetPixelGreen(image,q);
cristy3ed852e2009-09-05 21:47:34 +00004864 if (fabs(2.0*pixel.green) < quantum_threshold)
cristy4c08aed2011-07-01 19:47:50 +00004865 pixel.green=(MagickRealType)
4866 GetPixelGreen(image,p);
cristy3ed852e2009-09-05 21:47:34 +00004867 else
cristy4c08aed2011-07-01 19:47:50 +00004868 pixel.green=(MagickRealType)
4869 GetPixelGreen(image,p)+
4870 (pixel.green*amount);
4871 SetPixelGreen(unsharp_image,
4872 ClampToQuantum(pixel.green),q);
cristy3ed852e2009-09-05 21:47:34 +00004873 }
cristyed231572011-07-14 02:18:59 +00004874 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00004875 {
cristy4c08aed2011-07-01 19:47:50 +00004876 pixel.blue=GetPixelBlue(image,p)-
4877 (MagickRealType) GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00004878 if (fabs(2.0*pixel.blue) < quantum_threshold)
cristy4c08aed2011-07-01 19:47:50 +00004879 pixel.blue=(MagickRealType)
4880 GetPixelBlue(image,p);
cristy3ed852e2009-09-05 21:47:34 +00004881 else
cristy4c08aed2011-07-01 19:47:50 +00004882 pixel.blue=(MagickRealType)
4883 GetPixelBlue(image,p)+(pixel.blue*amount);
4884 SetPixelBlue(unsharp_image,
4885 ClampToQuantum(pixel.blue),q);
cristy3ed852e2009-09-05 21:47:34 +00004886 }
cristyed231572011-07-14 02:18:59 +00004887 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00004888 (image->colorspace == CMYKColorspace))
4889 {
cristy4c08aed2011-07-01 19:47:50 +00004890 pixel.black=GetPixelBlack(image,p)-
4891 (MagickRealType) GetPixelBlack(image,q);
4892 if (fabs(2.0*pixel.black) < quantum_threshold)
4893 pixel.black=(MagickRealType)
4894 GetPixelBlack(image,p);
cristy3ed852e2009-09-05 21:47:34 +00004895 else
cristy4c08aed2011-07-01 19:47:50 +00004896 pixel.black=(MagickRealType)
4897 GetPixelBlack(image,p)+(pixel.black*
4898 amount);
4899 SetPixelBlack(unsharp_image,
4900 ClampToQuantum(pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00004901 }
cristyed231572011-07-14 02:18:59 +00004902 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00004903 {
4904 pixel.alpha=GetPixelAlpha(image,p)-
4905 (MagickRealType) GetPixelAlpha(image,q);
4906 if (fabs(2.0*pixel.alpha) < quantum_threshold)
4907 pixel.alpha=(MagickRealType)
4908 GetPixelAlpha(image,p);
4909 else
4910 pixel.alpha=GetPixelAlpha(image,p)+
4911 (pixel.alpha*amount);
4912 SetPixelAlpha(unsharp_image,
4913 ClampToQuantum(pixel.alpha),q);
4914 }
cristyed231572011-07-14 02:18:59 +00004915 p+=GetPixelChannels(image);
4916 q+=GetPixelChannels(unsharp_image);
cristy3ed852e2009-09-05 21:47:34 +00004917 }
4918 if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
4919 status=MagickFalse;
4920 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4921 {
4922 MagickBooleanType
4923 proceed;
4924
cristyb5d5f722009-11-04 03:03:49 +00004925#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00004926 #pragma omp critical (MagickCore_UnsharpMaskImage)
cristy3ed852e2009-09-05 21:47:34 +00004927#endif
4928 proceed=SetImageProgress(image,SharpenImageTag,progress++,image->rows);
4929 if (proceed == MagickFalse)
4930 status=MagickFalse;
4931 }
4932 }
4933 unsharp_image->type=image->type;
4934 unsharp_view=DestroyCacheView(unsharp_view);
4935 image_view=DestroyCacheView(image_view);
4936 if (status == MagickFalse)
4937 unsharp_image=DestroyImage(unsharp_image);
4938 return(unsharp_image);
4939}