blob: 5cd7bbdd5ba0324b88891bfd2c3ac88c45d0f60d [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);
2434 (void) ModulateImage(preview_image,factor);
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);
cristy3ed852e2009-09-05 21:47:34 +00002445 (void) ModulateImage(preview_image,factor);
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);
cristy3ed852e2009-09-05 21:47:34 +00002455 (void) ModulateImage(preview_image,factor);
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;
2652 (void) RaiseImage(preview_image,&geometry,MagickTrue);
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 {
2694 preview_image=OilPaintImage(thumbnail,(double) radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002695 (void) FormatLocaleString(label,MaxTextExtent,"paint %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002696 break;
2697 }
2698 case CharcoalDrawingPreview:
2699 {
2700 preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma,
2701 exception);
cristyb51dff52011-05-19 16:55:47 +00002702 (void) FormatLocaleString(label,MaxTextExtent,"charcoal %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002703 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002704 break;
2705 }
2706 case JPEGPreview:
2707 {
2708 char
2709 filename[MaxTextExtent];
2710
2711 int
2712 file;
2713
2714 MagickBooleanType
2715 status;
2716
2717 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2718 if (preview_image == (Image *) NULL)
2719 break;
cristybb503372010-05-27 20:51:26 +00002720 preview_info->quality=(size_t) percentage;
cristyb51dff52011-05-19 16:55:47 +00002721 (void) FormatLocaleString(factor,MaxTextExtent,"%.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00002722 preview_info->quality);
cristy3ed852e2009-09-05 21:47:34 +00002723 file=AcquireUniqueFileResource(filename);
2724 if (file != -1)
2725 file=close(file)-1;
cristyb51dff52011-05-19 16:55:47 +00002726 (void) FormatLocaleString(preview_image->filename,MaxTextExtent,
cristy3ed852e2009-09-05 21:47:34 +00002727 "jpeg:%s",filename);
2728 status=WriteImage(preview_info,preview_image);
2729 if (status != MagickFalse)
2730 {
2731 Image
2732 *quality_image;
2733
2734 (void) CopyMagickString(preview_info->filename,
2735 preview_image->filename,MaxTextExtent);
2736 quality_image=ReadImage(preview_info,exception);
2737 if (quality_image != (Image *) NULL)
2738 {
2739 preview_image=DestroyImage(preview_image);
2740 preview_image=quality_image;
2741 }
2742 }
2743 (void) RelinquishUniqueFileResource(preview_image->filename);
2744 if ((GetBlobSize(preview_image)/1024) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00002745 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%gmb ",
cristy3ed852e2009-09-05 21:47:34 +00002746 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
2747 1024.0/1024.0);
2748 else
2749 if (GetBlobSize(preview_image) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00002750 (void) FormatLocaleString(label,MaxTextExtent,
cristye7f51092010-01-17 00:39:37 +00002751 "quality %s\n%gkb ",factor,(double) ((MagickOffsetType)
cristy8cd5b312010-01-07 01:10:24 +00002752 GetBlobSize(preview_image))/1024.0);
cristy3ed852e2009-09-05 21:47:34 +00002753 else
cristyb51dff52011-05-19 16:55:47 +00002754 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%.20gb ",
cristy54ea5732011-06-10 12:39:53 +00002755 factor,(double) ((MagickOffsetType) GetBlobSize(thumbnail)));
cristy3ed852e2009-09-05 21:47:34 +00002756 break;
2757 }
2758 }
2759 thumbnail=DestroyImage(thumbnail);
2760 percentage+=12.5;
2761 radius+=0.5;
2762 sigma+=0.25;
2763 if (preview_image == (Image *) NULL)
2764 break;
2765 (void) DeleteImageProperty(preview_image,"label");
2766 (void) SetImageProperty(preview_image,"label",label);
2767 AppendImageToList(&images,preview_image);
cristybb503372010-05-27 20:51:26 +00002768 proceed=SetImageProgress(image,PreviewImageTag,(MagickOffsetType) i,
2769 NumberTiles);
cristy3ed852e2009-09-05 21:47:34 +00002770 if (proceed == MagickFalse)
2771 break;
2772 }
2773 if (images == (Image *) NULL)
2774 {
2775 preview_info=DestroyImageInfo(preview_info);
2776 return((Image *) NULL);
2777 }
2778 /*
2779 Create the montage.
2780 */
2781 montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL);
2782 (void) CopyMagickString(montage_info->filename,image->filename,MaxTextExtent);
2783 montage_info->shadow=MagickTrue;
2784 (void) CloneString(&montage_info->tile,"3x3");
2785 (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry);
2786 (void) CloneString(&montage_info->frame,DefaultTileFrame);
2787 montage_image=MontageImages(images,montage_info,exception);
2788 montage_info=DestroyMontageInfo(montage_info);
2789 images=DestroyImageList(images);
2790 if (montage_image == (Image *) NULL)
2791 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2792 if (montage_image->montage != (char *) NULL)
2793 {
2794 /*
2795 Free image directory.
2796 */
2797 montage_image->montage=(char *) RelinquishMagickMemory(
2798 montage_image->montage);
2799 if (image->directory != (char *) NULL)
2800 montage_image->directory=(char *) RelinquishMagickMemory(
2801 montage_image->directory);
2802 }
2803 preview_info=DestroyImageInfo(preview_info);
2804 return(montage_image);
2805}
2806
2807/*
2808%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2809% %
2810% %
2811% %
2812% R a d i a l B l u r I m a g e %
2813% %
2814% %
2815% %
2816%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2817%
2818% RadialBlurImage() applies a radial blur to the image.
2819%
2820% Andrew Protano contributed this effect.
2821%
2822% The format of the RadialBlurImage method is:
2823%
2824% Image *RadialBlurImage(const Image *image,const double angle,
2825% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002826%
2827% A description of each parameter follows:
2828%
2829% o image: the image.
2830%
cristy3ed852e2009-09-05 21:47:34 +00002831% o angle: the angle of the radial blur.
2832%
2833% o exception: return any errors or warnings in this structure.
2834%
2835*/
cristyf4ad9df2011-07-08 16:49:03 +00002836MagickExport Image *RadialBlurImage(const Image *image,
2837 const double angle,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002838{
cristyc4c8d132010-01-07 01:58:38 +00002839 CacheView
2840 *blur_view,
2841 *image_view;
2842
cristy3ed852e2009-09-05 21:47:34 +00002843 Image
2844 *blur_image;
2845
cristy3ed852e2009-09-05 21:47:34 +00002846 MagickBooleanType
2847 status;
2848
cristybb503372010-05-27 20:51:26 +00002849 MagickOffsetType
2850 progress;
2851
cristy4c08aed2011-07-01 19:47:50 +00002852 PixelInfo
cristyddd82202009-11-03 20:14:50 +00002853 bias;
cristy3ed852e2009-09-05 21:47:34 +00002854
2855 MagickRealType
2856 blur_radius,
2857 *cos_theta,
2858 offset,
2859 *sin_theta,
2860 theta;
2861
2862 PointInfo
2863 blur_center;
2864
cristybb503372010-05-27 20:51:26 +00002865 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002866 i;
2867
cristybb503372010-05-27 20:51:26 +00002868 size_t
cristy3ed852e2009-09-05 21:47:34 +00002869 n;
2870
cristybb503372010-05-27 20:51:26 +00002871 ssize_t
2872 y;
2873
cristy3ed852e2009-09-05 21:47:34 +00002874 /*
2875 Allocate blur image.
2876 */
2877 assert(image != (Image *) NULL);
2878 assert(image->signature == MagickSignature);
2879 if (image->debug != MagickFalse)
2880 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2881 assert(exception != (ExceptionInfo *) NULL);
2882 assert(exception->signature == MagickSignature);
2883 blur_image=CloneImage(image,0,0,MagickTrue,exception);
2884 if (blur_image == (Image *) NULL)
2885 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00002886 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00002887 {
cristy3ed852e2009-09-05 21:47:34 +00002888 blur_image=DestroyImage(blur_image);
2889 return((Image *) NULL);
2890 }
2891 blur_center.x=(double) image->columns/2.0;
2892 blur_center.y=(double) image->rows/2.0;
2893 blur_radius=hypot(blur_center.x,blur_center.y);
cristy117ff172010-08-15 21:35:32 +00002894 n=(size_t) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+2UL);
cristy3ed852e2009-09-05 21:47:34 +00002895 theta=DegreesToRadians(angle)/(MagickRealType) (n-1);
2896 cos_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
2897 sizeof(*cos_theta));
2898 sin_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
2899 sizeof(*sin_theta));
2900 if ((cos_theta == (MagickRealType *) NULL) ||
2901 (sin_theta == (MagickRealType *) NULL))
2902 {
2903 blur_image=DestroyImage(blur_image);
2904 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2905 }
2906 offset=theta*(MagickRealType) (n-1)/2.0;
cristybb503372010-05-27 20:51:26 +00002907 for (i=0; i < (ssize_t) n; i++)
cristy3ed852e2009-09-05 21:47:34 +00002908 {
2909 cos_theta[i]=cos((double) (theta*i-offset));
2910 sin_theta[i]=sin((double) (theta*i-offset));
2911 }
2912 /*
2913 Radial blur image.
2914 */
2915 status=MagickTrue;
2916 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00002917 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00002918 image_view=AcquireCacheView(image);
2919 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00002920#if defined(MAGICKCORE_OPENMP_SUPPORT)
2921 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002922#endif
cristybb503372010-05-27 20:51:26 +00002923 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002924 {
cristy4c08aed2011-07-01 19:47:50 +00002925 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002926 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002927
cristy117ff172010-08-15 21:35:32 +00002928 register ssize_t
2929 x;
2930
cristy3ed852e2009-09-05 21:47:34 +00002931 if (status == MagickFalse)
2932 continue;
2933 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
2934 exception);
cristy4c08aed2011-07-01 19:47:50 +00002935 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002936 {
2937 status=MagickFalse;
2938 continue;
2939 }
cristybb503372010-05-27 20:51:26 +00002940 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002941 {
cristy4c08aed2011-07-01 19:47:50 +00002942 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002943 qixel;
2944
2945 MagickRealType
2946 normalize,
2947 radius;
2948
2949 PixelPacket
2950 pixel;
2951
2952 PointInfo
2953 center;
2954
cristybb503372010-05-27 20:51:26 +00002955 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002956 i;
2957
cristybb503372010-05-27 20:51:26 +00002958 size_t
cristy3ed852e2009-09-05 21:47:34 +00002959 step;
2960
2961 center.x=(double) x-blur_center.x;
2962 center.y=(double) y-blur_center.y;
2963 radius=hypot((double) center.x,center.y);
2964 if (radius == 0)
2965 step=1;
2966 else
2967 {
cristybb503372010-05-27 20:51:26 +00002968 step=(size_t) (blur_radius/radius);
cristy3ed852e2009-09-05 21:47:34 +00002969 if (step == 0)
2970 step=1;
2971 else
2972 if (step >= n)
2973 step=n-1;
2974 }
2975 normalize=0.0;
cristyddd82202009-11-03 20:14:50 +00002976 qixel=bias;
cristyed231572011-07-14 02:18:59 +00002977 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00002978 {
cristyeaedf062010-05-29 22:36:02 +00002979 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00002980 {
cristyeaedf062010-05-29 22:36:02 +00002981 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
2982 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
2983 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
2984 cos_theta[i]+0.5),&pixel,exception);
cristy3ed852e2009-09-05 21:47:34 +00002985 qixel.red+=pixel.red;
2986 qixel.green+=pixel.green;
2987 qixel.blue+=pixel.blue;
cristy3ed852e2009-09-05 21:47:34 +00002988 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00002989 qixel.black+=pixel.black;
2990 qixel.alpha+=pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00002991 normalize+=1.0;
2992 }
2993 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
2994 normalize);
cristyed231572011-07-14 02:18:59 +00002995 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002996 SetPixelRed(blur_image,
2997 ClampToQuantum(normalize*qixel.red),q);
cristyed231572011-07-14 02:18:59 +00002998 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002999 SetPixelGreen(blur_image,
3000 ClampToQuantum(normalize*qixel.green),q);
cristyed231572011-07-14 02:18:59 +00003001 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003002 SetPixelBlue(blur_image,
3003 ClampToQuantum(normalize*qixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003004 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003005 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003006 SetPixelBlack(blur_image,
3007 ClampToQuantum(normalize*qixel.black),q);
cristyed231572011-07-14 02:18:59 +00003008 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003009 SetPixelAlpha(blur_image,
3010 ClampToQuantum(normalize*qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003011 }
3012 else
3013 {
3014 MagickRealType
3015 alpha,
3016 gamma;
3017
3018 alpha=1.0;
3019 gamma=0.0;
cristyeaedf062010-05-29 22:36:02 +00003020 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00003021 {
cristyeaedf062010-05-29 22:36:02 +00003022 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
3023 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
3024 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
3025 cos_theta[i]+0.5),&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00003026 alpha=(MagickRealType) (QuantumScale*pixel.alpha);
cristy3ed852e2009-09-05 21:47:34 +00003027 qixel.red+=alpha*pixel.red;
3028 qixel.green+=alpha*pixel.green;
3029 qixel.blue+=alpha*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00003030 qixel.alpha+=pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00003031 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00003032 qixel.black+=alpha*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00003033 gamma+=alpha;
3034 normalize+=1.0;
3035 }
3036 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3037 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
3038 normalize);
cristyed231572011-07-14 02:18:59 +00003039 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003040 SetPixelRed(blur_image,
3041 ClampToQuantum(gamma*qixel.red),q);
cristyed231572011-07-14 02:18:59 +00003042 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003043 SetPixelGreen(blur_image,
3044 ClampToQuantum(gamma*qixel.green),q);
cristyed231572011-07-14 02:18:59 +00003045 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003046 SetPixelBlue(blur_image,
3047 ClampToQuantum(gamma*qixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003048 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003049 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003050 SetPixelBlack(blur_image,
3051 ClampToQuantum(gamma*qixel.black),q);
cristyed231572011-07-14 02:18:59 +00003052 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003053 SetPixelAlpha(blur_image,
3054 ClampToQuantum(normalize*qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003055 }
cristyed231572011-07-14 02:18:59 +00003056 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003057 }
3058 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
3059 status=MagickFalse;
3060 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3061 {
3062 MagickBooleanType
3063 proceed;
3064
cristyb5d5f722009-11-04 03:03:49 +00003065#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00003066 #pragma omp critical (MagickCore_RadialBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00003067#endif
3068 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
3069 if (proceed == MagickFalse)
3070 status=MagickFalse;
3071 }
3072 }
3073 blur_view=DestroyCacheView(blur_view);
3074 image_view=DestroyCacheView(image_view);
3075 cos_theta=(MagickRealType *) RelinquishMagickMemory(cos_theta);
3076 sin_theta=(MagickRealType *) RelinquishMagickMemory(sin_theta);
3077 if (status == MagickFalse)
3078 blur_image=DestroyImage(blur_image);
3079 return(blur_image);
3080}
3081
3082/*
3083%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3084% %
3085% %
3086% %
cristy3ed852e2009-09-05 21:47:34 +00003087% S e l e c t i v e B l u r I m a g e %
3088% %
3089% %
3090% %
3091%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3092%
3093% SelectiveBlurImage() selectively blur pixels within a contrast threshold.
3094% It is similar to the unsharpen mask that sharpens everything with contrast
3095% above a certain threshold.
3096%
3097% The format of the SelectiveBlurImage method is:
3098%
3099% Image *SelectiveBlurImage(const Image *image,const double radius,
3100% const double sigma,const double threshold,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003101%
3102% A description of each parameter follows:
3103%
3104% o image: the image.
3105%
cristy3ed852e2009-09-05 21:47:34 +00003106% o radius: the radius of the Gaussian, in pixels, not counting the center
3107% pixel.
3108%
3109% o sigma: the standard deviation of the Gaussian, in pixels.
3110%
3111% o threshold: only pixels within this contrast threshold are included
3112% in the blur operation.
3113%
3114% o exception: return any errors or warnings in this structure.
3115%
3116*/
cristyf4ad9df2011-07-08 16:49:03 +00003117MagickExport Image *SelectiveBlurImage(const Image *image,
3118 const double radius,const double sigma,const double threshold,
3119 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003120{
3121#define SelectiveBlurImageTag "SelectiveBlur/Image"
3122
cristy47e00502009-12-17 19:19:57 +00003123 CacheView
3124 *blur_view,
3125 *image_view;
3126
cristy3ed852e2009-09-05 21:47:34 +00003127 double
cristy3ed852e2009-09-05 21:47:34 +00003128 *kernel;
3129
3130 Image
3131 *blur_image;
3132
cristy3ed852e2009-09-05 21:47:34 +00003133 MagickBooleanType
3134 status;
3135
cristybb503372010-05-27 20:51:26 +00003136 MagickOffsetType
3137 progress;
3138
cristy4c08aed2011-07-01 19:47:50 +00003139 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003140 bias;
3141
cristybb503372010-05-27 20:51:26 +00003142 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003143 i;
cristy3ed852e2009-09-05 21:47:34 +00003144
cristybb503372010-05-27 20:51:26 +00003145 size_t
cristy3ed852e2009-09-05 21:47:34 +00003146 width;
3147
cristybb503372010-05-27 20:51:26 +00003148 ssize_t
3149 j,
3150 u,
3151 v,
3152 y;
3153
cristy3ed852e2009-09-05 21:47:34 +00003154 /*
3155 Initialize blur image attributes.
3156 */
3157 assert(image != (Image *) NULL);
3158 assert(image->signature == MagickSignature);
3159 if (image->debug != MagickFalse)
3160 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3161 assert(exception != (ExceptionInfo *) NULL);
3162 assert(exception->signature == MagickSignature);
3163 width=GetOptimalKernelWidth1D(radius,sigma);
3164 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
3165 if (kernel == (double *) NULL)
3166 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00003167 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00003168 i=0;
cristy47e00502009-12-17 19:19:57 +00003169 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00003170 {
cristy47e00502009-12-17 19:19:57 +00003171 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00003172 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
3173 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00003174 }
3175 if (image->debug != MagickFalse)
3176 {
3177 char
3178 format[MaxTextExtent],
3179 *message;
3180
cristy117ff172010-08-15 21:35:32 +00003181 register const double
3182 *k;
3183
cristybb503372010-05-27 20:51:26 +00003184 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003185 u,
3186 v;
3187
cristy3ed852e2009-09-05 21:47:34 +00003188 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00003189 " SelectiveBlurImage with %.20gx%.20g kernel:",(double) width,(double)
3190 width);
cristy3ed852e2009-09-05 21:47:34 +00003191 message=AcquireString("");
3192 k=kernel;
cristybb503372010-05-27 20:51:26 +00003193 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003194 {
3195 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00003196 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy3ed852e2009-09-05 21:47:34 +00003197 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00003198 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003199 {
cristyb51dff52011-05-19 16:55:47 +00003200 (void) FormatLocaleString(format,MaxTextExtent,"%+f ",*k++);
cristy3ed852e2009-09-05 21:47:34 +00003201 (void) ConcatenateString(&message,format);
3202 }
3203 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
3204 }
3205 message=DestroyString(message);
3206 }
3207 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3208 if (blur_image == (Image *) NULL)
3209 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00003210 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003211 {
cristy3ed852e2009-09-05 21:47:34 +00003212 blur_image=DestroyImage(blur_image);
3213 return((Image *) NULL);
3214 }
3215 /*
3216 Threshold blur image.
3217 */
3218 status=MagickTrue;
3219 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00003220 GetPixelInfo(image,&bias);
3221 SetPixelInfoBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003222 image_view=AcquireCacheView(image);
3223 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003224#if defined(MAGICKCORE_OPENMP_SUPPORT)
3225 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003226#endif
cristybb503372010-05-27 20:51:26 +00003227 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003228 {
cristy4c08aed2011-07-01 19:47:50 +00003229 double
3230 contrast;
3231
cristy3ed852e2009-09-05 21:47:34 +00003232 MagickBooleanType
3233 sync;
3234
3235 MagickRealType
3236 gamma;
3237
cristy4c08aed2011-07-01 19:47:50 +00003238 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003239 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003240
cristy4c08aed2011-07-01 19:47:50 +00003241 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003242 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003243
cristy117ff172010-08-15 21:35:32 +00003244 register ssize_t
3245 x;
3246
cristy3ed852e2009-09-05 21:47:34 +00003247 if (status == MagickFalse)
3248 continue;
cristy117ff172010-08-15 21:35:32 +00003249 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
3250 (width/2L),image->columns+width,width,exception);
cristy3ed852e2009-09-05 21:47:34 +00003251 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3252 exception);
cristy4c08aed2011-07-01 19:47:50 +00003253 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003254 {
3255 status=MagickFalse;
3256 continue;
3257 }
cristybb503372010-05-27 20:51:26 +00003258 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003259 {
cristy4c08aed2011-07-01 19:47:50 +00003260 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003261 pixel;
3262
3263 register const double
cristyc47d1f82009-11-26 01:44:43 +00003264 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00003265
cristybb503372010-05-27 20:51:26 +00003266 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003267 u;
3268
cristy117ff172010-08-15 21:35:32 +00003269 ssize_t
3270 j,
3271 v;
3272
cristyddd82202009-11-03 20:14:50 +00003273 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00003274 k=kernel;
3275 gamma=0.0;
3276 j=0;
cristyed231572011-07-14 02:18:59 +00003277 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00003278 {
cristybb503372010-05-27 20:51:26 +00003279 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003280 {
cristybb503372010-05-27 20:51:26 +00003281 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003282 {
cristyed231572011-07-14 02:18:59 +00003283 contrast=GetPixelIntensity(image,p+(u+j)*GetPixelChannels(image))-
cristy4c08aed2011-07-01 19:47:50 +00003284 (double) GetPixelIntensity(blur_image,q);
3285 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003286 {
cristy4c08aed2011-07-01 19:47:50 +00003287 pixel.red+=(*k)*
cristyed231572011-07-14 02:18:59 +00003288 GetPixelRed(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003289 pixel.green+=(*k)*
cristyed231572011-07-14 02:18:59 +00003290 GetPixelGreen(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003291 pixel.blue+=(*k)*
cristyed231572011-07-14 02:18:59 +00003292 GetPixelBlue(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003293 if (image->colorspace == CMYKColorspace)
3294 pixel.black+=(*k)*
cristyed231572011-07-14 02:18:59 +00003295 GetPixelBlack(image,p+(u+j)*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003296 gamma+=(*k);
3297 k++;
3298 }
3299 }
cristyd99b0962010-05-29 23:14:26 +00003300 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003301 }
3302 if (gamma != 0.0)
3303 {
3304 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +00003305 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003306 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +00003307 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003308 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +00003309 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003310 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003311 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00003312 (image->colorspace == CMYKColorspace))
3313 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00003314 }
cristyed231572011-07-14 02:18:59 +00003315 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003316 {
3317 gamma=0.0;
3318 j=0;
cristybb503372010-05-27 20:51:26 +00003319 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003320 {
cristybb503372010-05-27 20:51:26 +00003321 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003322 {
cristy4c08aed2011-07-01 19:47:50 +00003323 contrast=GetPixelIntensity(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003324 GetPixelChannels(image))-(double)
cristy4c08aed2011-07-01 19:47:50 +00003325 GetPixelIntensity(blur_image,q);
3326 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003327 {
cristy4c08aed2011-07-01 19:47:50 +00003328 pixel.alpha+=(*k)*
cristyed231572011-07-14 02:18:59 +00003329 GetPixelAlpha(image,p+(u+j)*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003330 gamma+=(*k);
3331 k++;
3332 }
3333 }
cristyeaedf062010-05-29 22:36:02 +00003334 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003335 }
3336 if (gamma != 0.0)
3337 {
3338 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
3339 gamma);
cristy4c08aed2011-07-01 19:47:50 +00003340 SetPixelAlpha(blur_image,ClampToQuantum(gamma*pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003341 }
3342 }
3343 }
3344 else
3345 {
3346 MagickRealType
3347 alpha;
3348
cristybb503372010-05-27 20:51:26 +00003349 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003350 {
cristybb503372010-05-27 20:51:26 +00003351 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003352 {
cristy4c08aed2011-07-01 19:47:50 +00003353 contrast=GetPixelIntensity(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003354 GetPixelChannels(image))-(double)
cristy4c08aed2011-07-01 19:47:50 +00003355 GetPixelIntensity(blur_image,q);
3356 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003357 {
cristy4c08aed2011-07-01 19:47:50 +00003358 alpha=(MagickRealType) (QuantumScale*
cristyed231572011-07-14 02:18:59 +00003359 GetPixelAlpha(image,p+(u+j)*GetPixelChannels(image)));
cristy4c08aed2011-07-01 19:47:50 +00003360 pixel.red+=(*k)*alpha*
cristyed231572011-07-14 02:18:59 +00003361 GetPixelRed(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003362 pixel.green+=(*k)*alpha*GetPixelGreen(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003363 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003364 pixel.blue+=(*k)*alpha*GetPixelBlue(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003365 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003366 pixel.alpha+=(*k)*GetPixelAlpha(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003367 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003368 if (image->colorspace == CMYKColorspace)
3369 pixel.black+=(*k)*GetPixelBlack(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003370 GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003371 gamma+=(*k)*alpha;
3372 k++;
3373 }
3374 }
cristyeaedf062010-05-29 22:36:02 +00003375 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003376 }
3377 if (gamma != 0.0)
3378 {
3379 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +00003380 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003381 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +00003382 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003383 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +00003384 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003385 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003386 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00003387 (image->colorspace == CMYKColorspace))
3388 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00003389 }
cristyed231572011-07-14 02:18:59 +00003390 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003391 {
3392 gamma=0.0;
3393 j=0;
cristybb503372010-05-27 20:51:26 +00003394 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003395 {
cristybb503372010-05-27 20:51:26 +00003396 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003397 {
cristy4c08aed2011-07-01 19:47:50 +00003398 contrast=GetPixelIntensity(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003399 GetPixelChannels(image))-(double)
cristy4c08aed2011-07-01 19:47:50 +00003400 GetPixelIntensity(blur_image,q);
3401 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003402 {
cristy4c08aed2011-07-01 19:47:50 +00003403 pixel.alpha+=(*k)*
cristyed231572011-07-14 02:18:59 +00003404 GetPixelAlpha(image,p+(u+j)*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003405 gamma+=(*k);
3406 k++;
3407 }
3408 }
cristyeaedf062010-05-29 22:36:02 +00003409 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003410 }
3411 if (gamma != 0.0)
3412 {
3413 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
3414 gamma);
cristy4c08aed2011-07-01 19:47:50 +00003415 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003416 }
3417 }
3418 }
cristyed231572011-07-14 02:18:59 +00003419 p+=GetPixelChannels(image);
3420 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003421 }
3422 sync=SyncCacheViewAuthenticPixels(blur_view,exception);
3423 if (sync == MagickFalse)
3424 status=MagickFalse;
3425 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3426 {
3427 MagickBooleanType
3428 proceed;
3429
cristyb5d5f722009-11-04 03:03:49 +00003430#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00003431 #pragma omp critical (MagickCore_SelectiveBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00003432#endif
3433 proceed=SetImageProgress(image,SelectiveBlurImageTag,progress++,
3434 image->rows);
3435 if (proceed == MagickFalse)
3436 status=MagickFalse;
3437 }
3438 }
3439 blur_image->type=image->type;
3440 blur_view=DestroyCacheView(blur_view);
3441 image_view=DestroyCacheView(image_view);
3442 kernel=(double *) RelinquishMagickMemory(kernel);
3443 if (status == MagickFalse)
3444 blur_image=DestroyImage(blur_image);
3445 return(blur_image);
3446}
3447
3448/*
3449%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3450% %
3451% %
3452% %
3453% S h a d e I m a g e %
3454% %
3455% %
3456% %
3457%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3458%
3459% ShadeImage() shines a distant light on an image to create a
3460% three-dimensional effect. You control the positioning of the light with
3461% azimuth and elevation; azimuth is measured in degrees off the x axis
3462% and elevation is measured in pixels above the Z axis.
3463%
3464% The format of the ShadeImage method is:
3465%
3466% Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3467% const double azimuth,const double elevation,ExceptionInfo *exception)
3468%
3469% A description of each parameter follows:
3470%
3471% o image: the image.
3472%
3473% o gray: A value other than zero shades the intensity of each pixel.
3474%
3475% o azimuth, elevation: Define the light source direction.
3476%
3477% o exception: return any errors or warnings in this structure.
3478%
3479*/
3480MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3481 const double azimuth,const double elevation,ExceptionInfo *exception)
3482{
3483#define ShadeImageTag "Shade/Image"
3484
cristyc4c8d132010-01-07 01:58:38 +00003485 CacheView
3486 *image_view,
3487 *shade_view;
3488
cristy3ed852e2009-09-05 21:47:34 +00003489 Image
3490 *shade_image;
3491
cristy3ed852e2009-09-05 21:47:34 +00003492 MagickBooleanType
3493 status;
3494
cristybb503372010-05-27 20:51:26 +00003495 MagickOffsetType
3496 progress;
3497
cristy3ed852e2009-09-05 21:47:34 +00003498 PrimaryInfo
3499 light;
3500
cristybb503372010-05-27 20:51:26 +00003501 ssize_t
3502 y;
3503
cristy3ed852e2009-09-05 21:47:34 +00003504 /*
3505 Initialize shaded image attributes.
3506 */
3507 assert(image != (const Image *) NULL);
3508 assert(image->signature == MagickSignature);
3509 if (image->debug != MagickFalse)
3510 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3511 assert(exception != (ExceptionInfo *) NULL);
3512 assert(exception->signature == MagickSignature);
3513 shade_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
3514 if (shade_image == (Image *) NULL)
3515 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00003516 if (SetImageStorageClass(shade_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003517 {
cristy3ed852e2009-09-05 21:47:34 +00003518 shade_image=DestroyImage(shade_image);
3519 return((Image *) NULL);
3520 }
3521 /*
3522 Compute the light vector.
3523 */
3524 light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))*
3525 cos(DegreesToRadians(elevation));
3526 light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))*
3527 cos(DegreesToRadians(elevation));
3528 light.z=(double) QuantumRange*sin(DegreesToRadians(elevation));
3529 /*
3530 Shade image.
3531 */
3532 status=MagickTrue;
3533 progress=0;
3534 image_view=AcquireCacheView(image);
3535 shade_view=AcquireCacheView(shade_image);
cristyb5d5f722009-11-04 03:03:49 +00003536#if defined(MAGICKCORE_OPENMP_SUPPORT)
3537 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003538#endif
cristybb503372010-05-27 20:51:26 +00003539 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003540 {
3541 MagickRealType
3542 distance,
3543 normal_distance,
3544 shade;
3545
3546 PrimaryInfo
3547 normal;
3548
cristy4c08aed2011-07-01 19:47:50 +00003549 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003550 *restrict p,
3551 *restrict s0,
3552 *restrict s1,
3553 *restrict s2;
cristy3ed852e2009-09-05 21:47:34 +00003554
cristy4c08aed2011-07-01 19:47:50 +00003555 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003556 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003557
cristy117ff172010-08-15 21:35:32 +00003558 register ssize_t
3559 x;
3560
cristy3ed852e2009-09-05 21:47:34 +00003561 if (status == MagickFalse)
3562 continue;
3563 p=GetCacheViewVirtualPixels(image_view,-1,y-1,image->columns+2,3,exception);
3564 q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1,
3565 exception);
cristy4c08aed2011-07-01 19:47:50 +00003566 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003567 {
3568 status=MagickFalse;
3569 continue;
3570 }
3571 /*
3572 Shade this row of pixels.
3573 */
3574 normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */
cristyed231572011-07-14 02:18:59 +00003575 s0=p+GetPixelChannels(image);
3576 s1=s0+(image->columns+2)*GetPixelChannels(image);
3577 s2=s1+(image->columns+2)*GetPixelChannels(image);
cristybb503372010-05-27 20:51:26 +00003578 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003579 {
3580 /*
3581 Determine the surface normal and compute shading.
3582 */
cristyed231572011-07-14 02:18:59 +00003583 normal.x=(double) (GetPixelIntensity(image,s0-GetPixelChannels(image))+
3584 GetPixelIntensity(image,s1-GetPixelChannels(image))+
3585 GetPixelIntensity(image,s2-GetPixelChannels(image))-
3586 GetPixelIntensity(image,s0+GetPixelChannels(image))-
3587 GetPixelIntensity(image,s1+GetPixelChannels(image))-
3588 GetPixelIntensity(image,s2+GetPixelChannels(image)));
3589 normal.y=(double) (GetPixelIntensity(image,s2-GetPixelChannels(image))+
cristy4c08aed2011-07-01 19:47:50 +00003590 GetPixelIntensity(image,s2)+
cristyed231572011-07-14 02:18:59 +00003591 GetPixelIntensity(image,s2+GetPixelChannels(image))-
3592 GetPixelIntensity(image,s0-GetPixelChannels(image))-
cristy4c08aed2011-07-01 19:47:50 +00003593 GetPixelIntensity(image,s0)-
cristyed231572011-07-14 02:18:59 +00003594 GetPixelIntensity(image,s0+GetPixelChannels(image)));
cristy3ed852e2009-09-05 21:47:34 +00003595 if ((normal.x == 0.0) && (normal.y == 0.0))
3596 shade=light.z;
3597 else
3598 {
3599 shade=0.0;
3600 distance=normal.x*light.x+normal.y*light.y+normal.z*light.z;
3601 if (distance > MagickEpsilon)
3602 {
3603 normal_distance=
3604 normal.x*normal.x+normal.y*normal.y+normal.z*normal.z;
3605 if (normal_distance > (MagickEpsilon*MagickEpsilon))
3606 shade=distance/sqrt((double) normal_distance);
3607 }
3608 }
3609 if (gray != MagickFalse)
3610 {
cristy4c08aed2011-07-01 19:47:50 +00003611 SetPixelRed(shade_image,ClampToQuantum(shade),q);
3612 SetPixelGreen(shade_image,ClampToQuantum(shade),q);
3613 SetPixelBlue(shade_image,ClampToQuantum(shade),q);
cristy3ed852e2009-09-05 21:47:34 +00003614 }
3615 else
3616 {
cristy4c08aed2011-07-01 19:47:50 +00003617 SetPixelRed(shade_image,ClampToQuantum(QuantumScale*shade*
3618 GetPixelRed(image,s1)),q);
3619 SetPixelGreen(shade_image,ClampToQuantum(QuantumScale*shade*
3620 GetPixelGreen(image,s1)),q);
3621 SetPixelBlue(shade_image,ClampToQuantum(QuantumScale*shade*
3622 GetPixelBlue(image,s1)),q);
cristy3ed852e2009-09-05 21:47:34 +00003623 }
cristy4c08aed2011-07-01 19:47:50 +00003624 SetPixelAlpha(shade_image,GetPixelAlpha(image,s1),q);
cristyed231572011-07-14 02:18:59 +00003625 s0+=GetPixelChannels(image);
3626 s1+=GetPixelChannels(image);
3627 s2+=GetPixelChannels(image);
3628 q+=GetPixelChannels(shade_image);
cristy3ed852e2009-09-05 21:47:34 +00003629 }
3630 if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
3631 status=MagickFalse;
3632 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3633 {
3634 MagickBooleanType
3635 proceed;
3636
cristyb5d5f722009-11-04 03:03:49 +00003637#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003638 #pragma omp critical (MagickCore_ShadeImage)
3639#endif
3640 proceed=SetImageProgress(image,ShadeImageTag,progress++,image->rows);
3641 if (proceed == MagickFalse)
3642 status=MagickFalse;
3643 }
3644 }
3645 shade_view=DestroyCacheView(shade_view);
3646 image_view=DestroyCacheView(image_view);
3647 if (status == MagickFalse)
3648 shade_image=DestroyImage(shade_image);
3649 return(shade_image);
3650}
3651
3652/*
3653%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3654% %
3655% %
3656% %
3657% S h a r p e n I m a g e %
3658% %
3659% %
3660% %
3661%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3662%
3663% SharpenImage() sharpens the image. We convolve the image with a Gaussian
3664% operator of the given radius and standard deviation (sigma). For
3665% reasonable results, radius should be larger than sigma. Use a radius of 0
3666% and SharpenImage() selects a suitable radius for you.
3667%
3668% Using a separable kernel would be faster, but the negative weights cancel
3669% out on the corners of the kernel producing often undesirable ringing in the
3670% filtered result; this can be avoided by using a 2D gaussian shaped image
3671% sharpening kernel instead.
3672%
3673% The format of the SharpenImage method is:
3674%
3675% Image *SharpenImage(const Image *image,const double radius,
3676% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003677%
3678% A description of each parameter follows:
3679%
3680% o image: the image.
3681%
cristy3ed852e2009-09-05 21:47:34 +00003682% o radius: the radius of the Gaussian, in pixels, not counting the center
3683% pixel.
3684%
3685% o sigma: the standard deviation of the Laplacian, in pixels.
3686%
3687% o exception: return any errors or warnings in this structure.
3688%
3689*/
cristy3ed852e2009-09-05 21:47:34 +00003690MagickExport Image *SharpenImage(const Image *image,const double radius,
3691 const double sigma,ExceptionInfo *exception)
3692{
cristy3ed852e2009-09-05 21:47:34 +00003693 double
cristy47e00502009-12-17 19:19:57 +00003694 normalize;
cristy3ed852e2009-09-05 21:47:34 +00003695
3696 Image
3697 *sharp_image;
3698
cristy41cbe682011-07-15 19:12:37 +00003699 KernelInfo
3700 *kernel_info;
3701
cristybb503372010-05-27 20:51:26 +00003702 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003703 i;
3704
cristybb503372010-05-27 20:51:26 +00003705 size_t
cristy3ed852e2009-09-05 21:47:34 +00003706 width;
3707
cristy117ff172010-08-15 21:35:32 +00003708 ssize_t
3709 j,
3710 u,
3711 v;
3712
cristy3ed852e2009-09-05 21:47:34 +00003713 assert(image != (const Image *) NULL);
3714 assert(image->signature == MagickSignature);
3715 if (image->debug != MagickFalse)
3716 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3717 assert(exception != (ExceptionInfo *) NULL);
3718 assert(exception->signature == MagickSignature);
3719 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00003720 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00003721 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003722 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00003723 (void) ResetMagickMemory(kernel_info,0,sizeof(*kernel_info));
3724 kernel_info->width=width;
3725 kernel_info->height=width;
3726 kernel_info->signature=MagickSignature;
3727 kernel_info->values=(double *) AcquireAlignedMemory(kernel_info->width,
3728 kernel_info->width*sizeof(*kernel_info->values));
3729 if (kernel_info->values == (double *) NULL)
3730 {
3731 kernel_info=DestroyKernelInfo(kernel_info);
3732 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3733 }
cristy3ed852e2009-09-05 21:47:34 +00003734 normalize=0.0;
cristy41cbe682011-07-15 19:12:37 +00003735 j=(ssize_t) kernel_info->width/2;
cristy47e00502009-12-17 19:19:57 +00003736 i=0;
3737 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00003738 {
cristy47e00502009-12-17 19:19:57 +00003739 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00003740 {
cristy41cbe682011-07-15 19:12:37 +00003741 kernel_info->values[i]=(double) (-exp(-((double) u*u+v*v)/(2.0*
3742 MagickSigma*MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
3743 normalize+=kernel_info->values[i];
cristy3ed852e2009-09-05 21:47:34 +00003744 i++;
3745 }
3746 }
cristy41cbe682011-07-15 19:12:37 +00003747 kernel_info->values[i/2]=(double) ((-2.0)*normalize);
cristy0a922382011-07-16 15:30:34 +00003748 kernel_info->bias=image->bias;
cristy5e6be1e2011-07-16 01:23:39 +00003749 sharp_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00003750 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00003751 return(sharp_image);
3752}
3753
3754/*
3755%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3756% %
3757% %
3758% %
3759% S p r e a d I m a g e %
3760% %
3761% %
3762% %
3763%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3764%
3765% SpreadImage() is a special effects method that randomly displaces each
3766% pixel in a block defined by the radius parameter.
3767%
3768% The format of the SpreadImage method is:
3769%
3770% Image *SpreadImage(const Image *image,const double radius,
3771% ExceptionInfo *exception)
3772%
3773% A description of each parameter follows:
3774%
3775% o image: the image.
3776%
3777% o radius: Choose a random pixel in a neighborhood of this extent.
3778%
3779% o exception: return any errors or warnings in this structure.
3780%
3781*/
3782MagickExport Image *SpreadImage(const Image *image,const double radius,
3783 ExceptionInfo *exception)
3784{
3785#define SpreadImageTag "Spread/Image"
3786
cristyfa112112010-01-04 17:48:07 +00003787 CacheView
cristy9f7e7cb2011-03-26 00:49:57 +00003788 *image_view,
3789 *spread_view;
cristyfa112112010-01-04 17:48:07 +00003790
cristy3ed852e2009-09-05 21:47:34 +00003791 Image
3792 *spread_image;
3793
cristy3ed852e2009-09-05 21:47:34 +00003794 MagickBooleanType
3795 status;
3796
cristybb503372010-05-27 20:51:26 +00003797 MagickOffsetType
3798 progress;
3799
cristy4c08aed2011-07-01 19:47:50 +00003800 PixelInfo
cristyddd82202009-11-03 20:14:50 +00003801 bias;
cristy3ed852e2009-09-05 21:47:34 +00003802
3803 RandomInfo
cristyfa112112010-01-04 17:48:07 +00003804 **restrict random_info;
cristy3ed852e2009-09-05 21:47:34 +00003805
cristybb503372010-05-27 20:51:26 +00003806 size_t
cristy3ed852e2009-09-05 21:47:34 +00003807 width;
3808
cristybb503372010-05-27 20:51:26 +00003809 ssize_t
3810 y;
3811
cristy3ed852e2009-09-05 21:47:34 +00003812 /*
3813 Initialize spread image attributes.
3814 */
3815 assert(image != (Image *) NULL);
3816 assert(image->signature == MagickSignature);
3817 if (image->debug != MagickFalse)
3818 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3819 assert(exception != (ExceptionInfo *) NULL);
3820 assert(exception->signature == MagickSignature);
3821 spread_image=CloneImage(image,image->columns,image->rows,MagickTrue,
3822 exception);
3823 if (spread_image == (Image *) NULL)
3824 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00003825 if (SetImageStorageClass(spread_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003826 {
cristy3ed852e2009-09-05 21:47:34 +00003827 spread_image=DestroyImage(spread_image);
3828 return((Image *) NULL);
3829 }
3830 /*
3831 Spread image.
3832 */
3833 status=MagickTrue;
3834 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00003835 GetPixelInfo(spread_image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003836 width=GetOptimalKernelWidth1D(radius,0.5);
cristy3ed852e2009-09-05 21:47:34 +00003837 random_info=AcquireRandomInfoThreadSet();
cristy9f7e7cb2011-03-26 00:49:57 +00003838 image_view=AcquireCacheView(image);
3839 spread_view=AcquireCacheView(spread_image);
cristyb557a152011-02-22 12:14:30 +00003840#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00003841 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00003842#endif
cristybb503372010-05-27 20:51:26 +00003843 for (y=0; y < (ssize_t) spread_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003844 {
cristy5c9e6f22010-09-17 17:31:01 +00003845 const int
3846 id = GetOpenMPThreadId();
cristy6ebe97c2010-07-03 01:17:28 +00003847
cristy4c08aed2011-07-01 19:47:50 +00003848 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003849 pixel;
3850
cristy4c08aed2011-07-01 19:47:50 +00003851 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003852 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003853
cristy117ff172010-08-15 21:35:32 +00003854 register ssize_t
3855 x;
3856
cristy3ed852e2009-09-05 21:47:34 +00003857 if (status == MagickFalse)
3858 continue;
cristy9f7e7cb2011-03-26 00:49:57 +00003859 q=QueueCacheViewAuthenticPixels(spread_view,0,y,spread_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +00003860 exception);
cristy4c08aed2011-07-01 19:47:50 +00003861 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003862 {
3863 status=MagickFalse;
3864 continue;
3865 }
cristyddd82202009-11-03 20:14:50 +00003866 pixel=bias;
cristybb503372010-05-27 20:51:26 +00003867 for (x=0; x < (ssize_t) spread_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003868 {
cristy4c08aed2011-07-01 19:47:50 +00003869 (void) InterpolatePixelInfo(image,image_view,
cristy8a7c3e82011-03-26 02:10:53 +00003870 UndefinedInterpolatePixel,(double) x+width*(GetPseudoRandomValue(
3871 random_info[id])-0.5),(double) y+width*(GetPseudoRandomValue(
3872 random_info[id])-0.5),&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00003873 SetPixelPixelInfo(spread_image,&pixel,q);
cristyed231572011-07-14 02:18:59 +00003874 q+=GetPixelChannels(spread_image);
cristy3ed852e2009-09-05 21:47:34 +00003875 }
cristy9f7e7cb2011-03-26 00:49:57 +00003876 if (SyncCacheViewAuthenticPixels(spread_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003877 status=MagickFalse;
3878 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3879 {
3880 MagickBooleanType
3881 proceed;
3882
cristyb557a152011-02-22 12:14:30 +00003883#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003884 #pragma omp critical (MagickCore_SpreadImage)
3885#endif
3886 proceed=SetImageProgress(image,SpreadImageTag,progress++,image->rows);
3887 if (proceed == MagickFalse)
3888 status=MagickFalse;
3889 }
3890 }
cristy9f7e7cb2011-03-26 00:49:57 +00003891 spread_view=DestroyCacheView(spread_view);
cristy3ed852e2009-09-05 21:47:34 +00003892 image_view=DestroyCacheView(image_view);
3893 random_info=DestroyRandomInfoThreadSet(random_info);
cristy3ed852e2009-09-05 21:47:34 +00003894 return(spread_image);
3895}
3896
3897/*
3898%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3899% %
3900% %
3901% %
cristy0834d642011-03-18 18:26:08 +00003902% S t a t i s t i c I m a g e %
3903% %
3904% %
3905% %
3906%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3907%
3908% StatisticImage() makes each pixel the min / max / median / mode / etc. of
cristy8d752042011-03-19 01:00:36 +00003909% the neighborhood of the specified width and height.
cristy0834d642011-03-18 18:26:08 +00003910%
3911% The format of the StatisticImage method is:
3912%
3913% Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00003914% const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00003915%
3916% A description of each parameter follows:
3917%
3918% o image: the image.
3919%
cristy0834d642011-03-18 18:26:08 +00003920% o type: the statistic type (median, mode, etc.).
3921%
cristy95c38342011-03-18 22:39:51 +00003922% o width: the width of the pixel neighborhood.
3923%
3924% o height: the height of the pixel neighborhood.
cristy0834d642011-03-18 18:26:08 +00003925%
3926% o exception: return any errors or warnings in this structure.
3927%
3928*/
3929
cristy733678d2011-03-18 21:29:28 +00003930#define ListChannels 5
3931
3932typedef struct _ListNode
3933{
3934 size_t
3935 next[9],
3936 count,
3937 signature;
3938} ListNode;
3939
3940typedef struct _SkipList
3941{
3942 ssize_t
3943 level;
3944
3945 ListNode
3946 *nodes;
3947} SkipList;
3948
3949typedef struct _PixelList
3950{
3951 size_t
cristy6fc86bb2011-03-18 23:45:16 +00003952 length,
cristy733678d2011-03-18 21:29:28 +00003953 seed,
3954 signature;
3955
3956 SkipList
3957 lists[ListChannels];
3958} PixelList;
3959
3960static PixelList *DestroyPixelList(PixelList *pixel_list)
3961{
3962 register ssize_t
3963 i;
3964
3965 if (pixel_list == (PixelList *) NULL)
3966 return((PixelList *) NULL);
3967 for (i=0; i < ListChannels; i++)
3968 if (pixel_list->lists[i].nodes != (ListNode *) NULL)
3969 pixel_list->lists[i].nodes=(ListNode *) RelinquishMagickMemory(
3970 pixel_list->lists[i].nodes);
3971 pixel_list=(PixelList *) RelinquishMagickMemory(pixel_list);
3972 return(pixel_list);
3973}
3974
3975static PixelList **DestroyPixelListThreadSet(PixelList **pixel_list)
3976{
3977 register ssize_t
3978 i;
3979
3980 assert(pixel_list != (PixelList **) NULL);
3981 for (i=0; i < (ssize_t) GetOpenMPMaximumThreads(); i++)
3982 if (pixel_list[i] != (PixelList *) NULL)
3983 pixel_list[i]=DestroyPixelList(pixel_list[i]);
3984 pixel_list=(PixelList **) RelinquishMagickMemory(pixel_list);
3985 return(pixel_list);
3986}
3987
cristy6fc86bb2011-03-18 23:45:16 +00003988static PixelList *AcquirePixelList(const size_t width,const size_t height)
cristy733678d2011-03-18 21:29:28 +00003989{
3990 PixelList
3991 *pixel_list;
3992
3993 register ssize_t
3994 i;
3995
3996 pixel_list=(PixelList *) AcquireMagickMemory(sizeof(*pixel_list));
3997 if (pixel_list == (PixelList *) NULL)
3998 return(pixel_list);
3999 (void) ResetMagickMemory((void *) pixel_list,0,sizeof(*pixel_list));
cristy6fc86bb2011-03-18 23:45:16 +00004000 pixel_list->length=width*height;
cristy733678d2011-03-18 21:29:28 +00004001 for (i=0; i < ListChannels; i++)
4002 {
4003 pixel_list->lists[i].nodes=(ListNode *) AcquireQuantumMemory(65537UL,
4004 sizeof(*pixel_list->lists[i].nodes));
4005 if (pixel_list->lists[i].nodes == (ListNode *) NULL)
4006 return(DestroyPixelList(pixel_list));
4007 (void) ResetMagickMemory(pixel_list->lists[i].nodes,0,65537UL*
4008 sizeof(*pixel_list->lists[i].nodes));
4009 }
4010 pixel_list->signature=MagickSignature;
4011 return(pixel_list);
4012}
4013
cristy6fc86bb2011-03-18 23:45:16 +00004014static PixelList **AcquirePixelListThreadSet(const size_t width,
4015 const size_t height)
cristy733678d2011-03-18 21:29:28 +00004016{
4017 PixelList
4018 **pixel_list;
4019
4020 register ssize_t
4021 i;
4022
4023 size_t
4024 number_threads;
4025
4026 number_threads=GetOpenMPMaximumThreads();
4027 pixel_list=(PixelList **) AcquireQuantumMemory(number_threads,
4028 sizeof(*pixel_list));
4029 if (pixel_list == (PixelList **) NULL)
4030 return((PixelList **) NULL);
4031 (void) ResetMagickMemory(pixel_list,0,number_threads*sizeof(*pixel_list));
4032 for (i=0; i < (ssize_t) number_threads; i++)
4033 {
cristy6fc86bb2011-03-18 23:45:16 +00004034 pixel_list[i]=AcquirePixelList(width,height);
cristy733678d2011-03-18 21:29:28 +00004035 if (pixel_list[i] == (PixelList *) NULL)
4036 return(DestroyPixelListThreadSet(pixel_list));
4037 }
4038 return(pixel_list);
4039}
4040
4041static void AddNodePixelList(PixelList *pixel_list,const ssize_t channel,
4042 const size_t color)
4043{
4044 register SkipList
4045 *list;
4046
4047 register ssize_t
4048 level;
4049
4050 size_t
4051 search,
4052 update[9];
4053
4054 /*
4055 Initialize the node.
4056 */
4057 list=pixel_list->lists+channel;
4058 list->nodes[color].signature=pixel_list->signature;
4059 list->nodes[color].count=1;
4060 /*
4061 Determine where it belongs in the list.
4062 */
4063 search=65536UL;
4064 for (level=list->level; level >= 0; level--)
4065 {
4066 while (list->nodes[search].next[level] < color)
4067 search=list->nodes[search].next[level];
4068 update[level]=search;
4069 }
4070 /*
4071 Generate a pseudo-random level for this node.
4072 */
4073 for (level=0; ; level++)
4074 {
4075 pixel_list->seed=(pixel_list->seed*42893621L)+1L;
4076 if ((pixel_list->seed & 0x300) != 0x300)
4077 break;
4078 }
4079 if (level > 8)
4080 level=8;
4081 if (level > (list->level+2))
4082 level=list->level+2;
4083 /*
4084 If we're raising the list's level, link back to the root node.
4085 */
4086 while (level > list->level)
4087 {
4088 list->level++;
4089 update[list->level]=65536UL;
4090 }
4091 /*
4092 Link the node into the skip-list.
4093 */
4094 do
4095 {
4096 list->nodes[color].next[level]=list->nodes[update[level]].next[level];
4097 list->nodes[update[level]].next[level]=color;
cristy3cba8ca2011-03-19 01:29:12 +00004098 } while (level-- > 0);
cristy733678d2011-03-18 21:29:28 +00004099}
4100
cristy4c08aed2011-07-01 19:47:50 +00004101static PixelInfo GetMaximumPixelList(PixelList *pixel_list)
cristy6fc86bb2011-03-18 23:45:16 +00004102{
cristy4c08aed2011-07-01 19:47:50 +00004103 PixelInfo
cristy6fc86bb2011-03-18 23:45:16 +00004104 pixel;
4105
4106 register SkipList
4107 *list;
4108
4109 register ssize_t
4110 channel;
4111
4112 size_t
cristyd76c51e2011-03-26 00:21:26 +00004113 color,
4114 maximum;
cristy49f37242011-03-22 18:18:23 +00004115
4116 ssize_t
cristy6fc86bb2011-03-18 23:45:16 +00004117 count;
4118
4119 unsigned short
cristyd76c51e2011-03-26 00:21:26 +00004120 channels[ListChannels];
cristy6fc86bb2011-03-18 23:45:16 +00004121
4122 /*
4123 Find the maximum value for each of the color.
4124 */
4125 for (channel=0; channel < 5; channel++)
4126 {
4127 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004128 color=65536L;
cristy6fc86bb2011-03-18 23:45:16 +00004129 count=0;
cristy49f37242011-03-22 18:18:23 +00004130 maximum=list->nodes[color].next[0];
cristy6fc86bb2011-03-18 23:45:16 +00004131 do
4132 {
4133 color=list->nodes[color].next[0];
cristy49f37242011-03-22 18:18:23 +00004134 if (color > maximum)
4135 maximum=color;
cristy6fc86bb2011-03-18 23:45:16 +00004136 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004137 } while (count < (ssize_t) pixel_list->length);
cristy49f37242011-03-22 18:18:23 +00004138 channels[channel]=(unsigned short) maximum;
4139 }
cristy4c08aed2011-07-01 19:47:50 +00004140 GetPixelInfo((const Image *) NULL,&pixel);
cristy49f37242011-03-22 18:18:23 +00004141 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4142 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4143 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004144 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4145 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy49f37242011-03-22 18:18:23 +00004146 return(pixel);
4147}
4148
cristy4c08aed2011-07-01 19:47:50 +00004149static PixelInfo GetMeanPixelList(PixelList *pixel_list)
cristy49f37242011-03-22 18:18:23 +00004150{
cristy4c08aed2011-07-01 19:47:50 +00004151 PixelInfo
cristy49f37242011-03-22 18:18:23 +00004152 pixel;
4153
cristy80a99a32011-03-30 01:30:23 +00004154 MagickRealType
4155 sum;
4156
cristy49f37242011-03-22 18:18:23 +00004157 register SkipList
4158 *list;
4159
4160 register ssize_t
4161 channel;
4162
4163 size_t
cristy80a99a32011-03-30 01:30:23 +00004164 color;
cristy49f37242011-03-22 18:18:23 +00004165
4166 ssize_t
4167 count;
4168
4169 unsigned short
4170 channels[ListChannels];
4171
4172 /*
4173 Find the mean value for each of the color.
4174 */
4175 for (channel=0; channel < 5; channel++)
4176 {
4177 list=pixel_list->lists+channel;
4178 color=65536L;
4179 count=0;
cristy80a99a32011-03-30 01:30:23 +00004180 sum=0.0;
cristy49f37242011-03-22 18:18:23 +00004181 do
4182 {
4183 color=list->nodes[color].next[0];
cristy80a99a32011-03-30 01:30:23 +00004184 sum+=(MagickRealType) list->nodes[color].count*color;
cristy49f37242011-03-22 18:18:23 +00004185 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004186 } while (count < (ssize_t) pixel_list->length);
cristy80a99a32011-03-30 01:30:23 +00004187 sum/=pixel_list->length;
4188 channels[channel]=(unsigned short) sum;
cristy6fc86bb2011-03-18 23:45:16 +00004189 }
cristy4c08aed2011-07-01 19:47:50 +00004190 GetPixelInfo((const Image *) NULL,&pixel);
cristy6fc86bb2011-03-18 23:45:16 +00004191 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4192 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4193 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004194 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4195 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy6fc86bb2011-03-18 23:45:16 +00004196 return(pixel);
4197}
4198
cristy4c08aed2011-07-01 19:47:50 +00004199static PixelInfo GetMedianPixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004200{
cristy4c08aed2011-07-01 19:47:50 +00004201 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004202 pixel;
4203
4204 register SkipList
4205 *list;
4206
4207 register ssize_t
4208 channel;
4209
4210 size_t
cristy49f37242011-03-22 18:18:23 +00004211 color;
4212
4213 ssize_t
cristy733678d2011-03-18 21:29:28 +00004214 count;
4215
4216 unsigned short
4217 channels[ListChannels];
4218
4219 /*
4220 Find the median value for each of the color.
4221 */
cristy733678d2011-03-18 21:29:28 +00004222 for (channel=0; channel < 5; channel++)
4223 {
4224 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004225 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004226 count=0;
4227 do
4228 {
4229 color=list->nodes[color].next[0];
4230 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004231 } while (count <= (ssize_t) (pixel_list->length >> 1));
cristy6fc86bb2011-03-18 23:45:16 +00004232 channels[channel]=(unsigned short) color;
4233 }
cristy4c08aed2011-07-01 19:47:50 +00004234 GetPixelInfo((const Image *) NULL,&pixel);
cristy6fc86bb2011-03-18 23:45:16 +00004235 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4236 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4237 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004238 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4239 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy6fc86bb2011-03-18 23:45:16 +00004240 return(pixel);
4241}
4242
cristy4c08aed2011-07-01 19:47:50 +00004243static PixelInfo GetMinimumPixelList(PixelList *pixel_list)
cristy6fc86bb2011-03-18 23:45:16 +00004244{
cristy4c08aed2011-07-01 19:47:50 +00004245 PixelInfo
cristy6fc86bb2011-03-18 23:45:16 +00004246 pixel;
4247
4248 register SkipList
4249 *list;
4250
4251 register ssize_t
4252 channel;
4253
4254 size_t
cristyd76c51e2011-03-26 00:21:26 +00004255 color,
4256 minimum;
cristy6fc86bb2011-03-18 23:45:16 +00004257
cristy49f37242011-03-22 18:18:23 +00004258 ssize_t
4259 count;
4260
cristy6fc86bb2011-03-18 23:45:16 +00004261 unsigned short
cristyd76c51e2011-03-26 00:21:26 +00004262 channels[ListChannels];
cristy6fc86bb2011-03-18 23:45:16 +00004263
4264 /*
4265 Find the minimum value for each of the color.
4266 */
4267 for (channel=0; channel < 5; channel++)
4268 {
4269 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004270 count=0;
cristy6fc86bb2011-03-18 23:45:16 +00004271 color=65536UL;
cristy49f37242011-03-22 18:18:23 +00004272 minimum=list->nodes[color].next[0];
4273 do
4274 {
4275 color=list->nodes[color].next[0];
4276 if (color < minimum)
4277 minimum=color;
4278 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004279 } while (count < (ssize_t) pixel_list->length);
cristy49f37242011-03-22 18:18:23 +00004280 channels[channel]=(unsigned short) minimum;
cristy733678d2011-03-18 21:29:28 +00004281 }
cristy4c08aed2011-07-01 19:47:50 +00004282 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004283 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4284 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4285 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004286 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4287 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy733678d2011-03-18 21:29:28 +00004288 return(pixel);
4289}
4290
cristy4c08aed2011-07-01 19:47:50 +00004291static PixelInfo GetModePixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004292{
cristy4c08aed2011-07-01 19:47:50 +00004293 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004294 pixel;
4295
4296 register SkipList
4297 *list;
4298
4299 register ssize_t
4300 channel;
4301
4302 size_t
4303 color,
cristy733678d2011-03-18 21:29:28 +00004304 max_count,
cristy6fc86bb2011-03-18 23:45:16 +00004305 mode;
cristy733678d2011-03-18 21:29:28 +00004306
cristy49f37242011-03-22 18:18:23 +00004307 ssize_t
4308 count;
4309
cristy733678d2011-03-18 21:29:28 +00004310 unsigned short
4311 channels[5];
4312
4313 /*
glennrp30d2dc62011-06-25 03:17:16 +00004314 Make each pixel the 'predominant color' of the specified neighborhood.
cristy733678d2011-03-18 21:29:28 +00004315 */
cristy733678d2011-03-18 21:29:28 +00004316 for (channel=0; channel < 5; channel++)
4317 {
4318 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004319 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004320 mode=color;
4321 max_count=list->nodes[mode].count;
4322 count=0;
4323 do
4324 {
4325 color=list->nodes[color].next[0];
4326 if (list->nodes[color].count > max_count)
4327 {
4328 mode=color;
4329 max_count=list->nodes[mode].count;
4330 }
4331 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004332 } while (count < (ssize_t) pixel_list->length);
cristy733678d2011-03-18 21:29:28 +00004333 channels[channel]=(unsigned short) mode;
4334 }
cristy4c08aed2011-07-01 19:47:50 +00004335 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004336 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4337 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4338 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004339 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4340 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy733678d2011-03-18 21:29:28 +00004341 return(pixel);
4342}
4343
cristy4c08aed2011-07-01 19:47:50 +00004344static PixelInfo GetNonpeakPixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004345{
cristy4c08aed2011-07-01 19:47:50 +00004346 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004347 pixel;
4348
4349 register SkipList
4350 *list;
4351
4352 register ssize_t
4353 channel;
4354
4355 size_t
cristy733678d2011-03-18 21:29:28 +00004356 color,
cristy733678d2011-03-18 21:29:28 +00004357 next,
4358 previous;
4359
cristy49f37242011-03-22 18:18:23 +00004360 ssize_t
4361 count;
4362
cristy733678d2011-03-18 21:29:28 +00004363 unsigned short
4364 channels[5];
4365
4366 /*
cristy49f37242011-03-22 18:18:23 +00004367 Finds the non peak value for each of the colors.
cristy733678d2011-03-18 21:29:28 +00004368 */
cristy733678d2011-03-18 21:29:28 +00004369 for (channel=0; channel < 5; channel++)
4370 {
4371 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004372 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004373 next=list->nodes[color].next[0];
4374 count=0;
4375 do
4376 {
4377 previous=color;
4378 color=next;
4379 next=list->nodes[color].next[0];
4380 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004381 } while (count <= (ssize_t) (pixel_list->length >> 1));
cristy733678d2011-03-18 21:29:28 +00004382 if ((previous == 65536UL) && (next != 65536UL))
4383 color=next;
4384 else
4385 if ((previous != 65536UL) && (next == 65536UL))
4386 color=previous;
4387 channels[channel]=(unsigned short) color;
4388 }
cristy4c08aed2011-07-01 19:47:50 +00004389 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004390 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4391 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4392 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004393 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4394 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy733678d2011-03-18 21:29:28 +00004395 return(pixel);
4396}
4397
cristy4c08aed2011-07-01 19:47:50 +00004398static PixelInfo GetStandardDeviationPixelList(PixelList *pixel_list)
cristy9a68cbb2011-03-29 00:51:23 +00004399{
cristy4c08aed2011-07-01 19:47:50 +00004400 PixelInfo
cristy9a68cbb2011-03-29 00:51:23 +00004401 pixel;
4402
cristy80a99a32011-03-30 01:30:23 +00004403 MagickRealType
4404 sum,
4405 sum_squared;
4406
cristy9a68cbb2011-03-29 00:51:23 +00004407 register SkipList
4408 *list;
4409
4410 register ssize_t
4411 channel;
4412
4413 size_t
cristy80a99a32011-03-30 01:30:23 +00004414 color;
cristy9a68cbb2011-03-29 00:51:23 +00004415
4416 ssize_t
4417 count;
4418
4419 unsigned short
4420 channels[ListChannels];
4421
4422 /*
cristy80a99a32011-03-30 01:30:23 +00004423 Find the standard-deviation value for each of the color.
cristy9a68cbb2011-03-29 00:51:23 +00004424 */
4425 for (channel=0; channel < 5; channel++)
4426 {
4427 list=pixel_list->lists+channel;
4428 color=65536L;
4429 count=0;
cristy80a99a32011-03-30 01:30:23 +00004430 sum=0.0;
4431 sum_squared=0.0;
cristy9a68cbb2011-03-29 00:51:23 +00004432 do
4433 {
cristy80a99a32011-03-30 01:30:23 +00004434 register ssize_t
4435 i;
4436
cristy9a68cbb2011-03-29 00:51:23 +00004437 color=list->nodes[color].next[0];
cristy80a99a32011-03-30 01:30:23 +00004438 sum+=(MagickRealType) list->nodes[color].count*color;
4439 for (i=0; i < (ssize_t) list->nodes[color].count; i++)
4440 sum_squared+=((MagickRealType) color)*((MagickRealType) color);
cristy9a68cbb2011-03-29 00:51:23 +00004441 count+=list->nodes[color].count;
4442 } while (count < (ssize_t) pixel_list->length);
cristy80a99a32011-03-30 01:30:23 +00004443 sum/=pixel_list->length;
4444 sum_squared/=pixel_list->length;
4445 channels[channel]=(unsigned short) sqrt(sum_squared-(sum*sum));
cristy9a68cbb2011-03-29 00:51:23 +00004446 }
cristy4c08aed2011-07-01 19:47:50 +00004447 GetPixelInfo((const Image *) NULL,&pixel);
cristy9a68cbb2011-03-29 00:51:23 +00004448 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4449 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4450 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004451 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4452 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy9a68cbb2011-03-29 00:51:23 +00004453 return(pixel);
4454}
4455
cristy4c08aed2011-07-01 19:47:50 +00004456static inline void InsertPixelList(const Image *image,const Quantum *pixel,
4457 PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004458{
4459 size_t
4460 signature;
4461
4462 unsigned short
4463 index;
4464
cristy4c08aed2011-07-01 19:47:50 +00004465 index=ScaleQuantumToShort(GetPixelRed(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004466 signature=pixel_list->lists[0].nodes[index].signature;
4467 if (signature == pixel_list->signature)
4468 pixel_list->lists[0].nodes[index].count++;
4469 else
4470 AddNodePixelList(pixel_list,0,index);
cristy4c08aed2011-07-01 19:47:50 +00004471 index=ScaleQuantumToShort(GetPixelGreen(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004472 signature=pixel_list->lists[1].nodes[index].signature;
4473 if (signature == pixel_list->signature)
4474 pixel_list->lists[1].nodes[index].count++;
4475 else
4476 AddNodePixelList(pixel_list,1,index);
cristy4c08aed2011-07-01 19:47:50 +00004477 index=ScaleQuantumToShort(GetPixelBlue(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004478 signature=pixel_list->lists[2].nodes[index].signature;
4479 if (signature == pixel_list->signature)
4480 pixel_list->lists[2].nodes[index].count++;
4481 else
4482 AddNodePixelList(pixel_list,2,index);
cristy4c08aed2011-07-01 19:47:50 +00004483 index=ScaleQuantumToShort(GetPixelAlpha(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004484 signature=pixel_list->lists[3].nodes[index].signature;
4485 if (signature == pixel_list->signature)
4486 pixel_list->lists[3].nodes[index].count++;
4487 else
4488 AddNodePixelList(pixel_list,3,index);
4489 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00004490 index=ScaleQuantumToShort(GetPixelBlack(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004491 signature=pixel_list->lists[4].nodes[index].signature;
4492 if (signature == pixel_list->signature)
4493 pixel_list->lists[4].nodes[index].count++;
4494 else
4495 AddNodePixelList(pixel_list,4,index);
4496}
4497
cristy80c99742011-04-04 14:46:39 +00004498static inline MagickRealType MagickAbsoluteValue(const MagickRealType x)
4499{
4500 if (x < 0)
4501 return(-x);
4502 return(x);
4503}
4504
cristy733678d2011-03-18 21:29:28 +00004505static void ResetPixelList(PixelList *pixel_list)
4506{
4507 int
4508 level;
4509
4510 register ListNode
4511 *root;
4512
4513 register SkipList
4514 *list;
4515
4516 register ssize_t
4517 channel;
4518
4519 /*
4520 Reset the skip-list.
4521 */
4522 for (channel=0; channel < 5; channel++)
4523 {
4524 list=pixel_list->lists+channel;
4525 root=list->nodes+65536UL;
4526 list->level=0;
4527 for (level=0; level < 9; level++)
4528 root->next[level]=65536UL;
4529 }
4530 pixel_list->seed=pixel_list->signature++;
4531}
4532
cristy0834d642011-03-18 18:26:08 +00004533MagickExport Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00004534 const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00004535{
cristy3cba8ca2011-03-19 01:29:12 +00004536#define StatisticWidth \
cristyd76c51e2011-03-26 00:21:26 +00004537 (width == 0 ? GetOptimalKernelWidth2D((double) width,0.5) : width)
cristy3cba8ca2011-03-19 01:29:12 +00004538#define StatisticHeight \
cristyd76c51e2011-03-26 00:21:26 +00004539 (height == 0 ? GetOptimalKernelWidth2D((double) height,0.5) : height)
cristy0834d642011-03-18 18:26:08 +00004540#define StatisticImageTag "Statistic/Image"
4541
4542 CacheView
4543 *image_view,
4544 *statistic_view;
4545
4546 Image
4547 *statistic_image;
4548
4549 MagickBooleanType
4550 status;
4551
4552 MagickOffsetType
4553 progress;
4554
4555 PixelList
4556 **restrict pixel_list;
4557
cristy0834d642011-03-18 18:26:08 +00004558 ssize_t
4559 y;
4560
4561 /*
4562 Initialize statistics image attributes.
4563 */
4564 assert(image != (Image *) NULL);
4565 assert(image->signature == MagickSignature);
4566 if (image->debug != MagickFalse)
4567 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4568 assert(exception != (ExceptionInfo *) NULL);
4569 assert(exception->signature == MagickSignature);
cristy0834d642011-03-18 18:26:08 +00004570 statistic_image=CloneImage(image,image->columns,image->rows,MagickTrue,
4571 exception);
4572 if (statistic_image == (Image *) NULL)
4573 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00004574 if (SetImageStorageClass(statistic_image,DirectClass,exception) == MagickFalse)
cristy0834d642011-03-18 18:26:08 +00004575 {
cristy0834d642011-03-18 18:26:08 +00004576 statistic_image=DestroyImage(statistic_image);
4577 return((Image *) NULL);
4578 }
cristy6fc86bb2011-03-18 23:45:16 +00004579 pixel_list=AcquirePixelListThreadSet(StatisticWidth,StatisticHeight);
cristy0834d642011-03-18 18:26:08 +00004580 if (pixel_list == (PixelList **) NULL)
4581 {
4582 statistic_image=DestroyImage(statistic_image);
4583 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
4584 }
4585 /*
cristy8d752042011-03-19 01:00:36 +00004586 Make each pixel the min / max / median / mode / etc. of the neighborhood.
cristy0834d642011-03-18 18:26:08 +00004587 */
4588 status=MagickTrue;
4589 progress=0;
4590 image_view=AcquireCacheView(image);
4591 statistic_view=AcquireCacheView(statistic_image);
4592#if defined(MAGICKCORE_OPENMP_SUPPORT)
4593 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
4594#endif
4595 for (y=0; y < (ssize_t) statistic_image->rows; y++)
4596 {
4597 const int
4598 id = GetOpenMPThreadId();
4599
cristy4c08aed2011-07-01 19:47:50 +00004600 register const Quantum
cristy0834d642011-03-18 18:26:08 +00004601 *restrict p;
4602
cristy4c08aed2011-07-01 19:47:50 +00004603 register Quantum
cristy0834d642011-03-18 18:26:08 +00004604 *restrict q;
4605
4606 register ssize_t
4607 x;
4608
4609 if (status == MagickFalse)
4610 continue;
cristy6fc86bb2011-03-18 23:45:16 +00004611 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) StatisticWidth/2L),y-
4612 (ssize_t) (StatisticHeight/2L),image->columns+StatisticWidth,
4613 StatisticHeight,exception);
cristy3cba8ca2011-03-19 01:29:12 +00004614 q=QueueCacheViewAuthenticPixels(statistic_view,0,y,statistic_image->columns, 1,exception);
cristy4c08aed2011-07-01 19:47:50 +00004615 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy0834d642011-03-18 18:26:08 +00004616 {
4617 status=MagickFalse;
4618 continue;
4619 }
cristy0834d642011-03-18 18:26:08 +00004620 for (x=0; x < (ssize_t) statistic_image->columns; x++)
4621 {
cristy4c08aed2011-07-01 19:47:50 +00004622 PixelInfo
cristy0834d642011-03-18 18:26:08 +00004623 pixel;
4624
cristy4c08aed2011-07-01 19:47:50 +00004625 register const Quantum
cristy6e3026a2011-03-19 00:54:38 +00004626 *restrict r;
4627
cristy0834d642011-03-18 18:26:08 +00004628 register ssize_t
4629 u,
4630 v;
4631
4632 r=p;
cristy0834d642011-03-18 18:26:08 +00004633 ResetPixelList(pixel_list[id]);
cristy6e4c3292011-03-19 00:53:55 +00004634 for (v=0; v < (ssize_t) StatisticHeight; v++)
cristy0834d642011-03-18 18:26:08 +00004635 {
cristy6e4c3292011-03-19 00:53:55 +00004636 for (u=0; u < (ssize_t) StatisticWidth; u++)
cristyed231572011-07-14 02:18:59 +00004637 InsertPixelList(image,r+u*GetPixelChannels(image),pixel_list[id]);
4638 r+=(image->columns+StatisticWidth)*GetPixelChannels(image);
cristy0834d642011-03-18 18:26:08 +00004639 }
cristy4c08aed2011-07-01 19:47:50 +00004640 GetPixelInfo(image,&pixel);
cristy490408a2011-07-07 14:42:05 +00004641 SetPixelInfo(image,p+(StatisticWidth*StatisticHeight/2)*
cristyed231572011-07-14 02:18:59 +00004642 GetPixelChannels(image),&pixel);
cristy0834d642011-03-18 18:26:08 +00004643 switch (type)
4644 {
cristy80c99742011-04-04 14:46:39 +00004645 case GradientStatistic:
4646 {
cristy4c08aed2011-07-01 19:47:50 +00004647 PixelInfo
cristy80c99742011-04-04 14:46:39 +00004648 maximum,
4649 minimum;
4650
4651 minimum=GetMinimumPixelList(pixel_list[id]);
4652 maximum=GetMaximumPixelList(pixel_list[id]);
4653 pixel.red=MagickAbsoluteValue(maximum.red-minimum.red);
4654 pixel.green=MagickAbsoluteValue(maximum.green-minimum.green);
4655 pixel.blue=MagickAbsoluteValue(maximum.blue-minimum.blue);
cristy4c08aed2011-07-01 19:47:50 +00004656 pixel.alpha=MagickAbsoluteValue(maximum.alpha-minimum.alpha);
cristy80c99742011-04-04 14:46:39 +00004657 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00004658 pixel.black=MagickAbsoluteValue(maximum.black-minimum.black);
cristy80c99742011-04-04 14:46:39 +00004659 break;
4660 }
cristy6fc86bb2011-03-18 23:45:16 +00004661 case MaximumStatistic:
4662 {
4663 pixel=GetMaximumPixelList(pixel_list[id]);
4664 break;
4665 }
cristy49f37242011-03-22 18:18:23 +00004666 case MeanStatistic:
4667 {
4668 pixel=GetMeanPixelList(pixel_list[id]);
4669 break;
4670 }
cristyf2ad14a2011-03-18 18:57:25 +00004671 case MedianStatistic:
cristy6fc86bb2011-03-18 23:45:16 +00004672 default:
cristyf2ad14a2011-03-18 18:57:25 +00004673 {
4674 pixel=GetMedianPixelList(pixel_list[id]);
4675 break;
4676 }
cristy6fc86bb2011-03-18 23:45:16 +00004677 case MinimumStatistic:
4678 {
4679 pixel=GetMinimumPixelList(pixel_list[id]);
4680 break;
4681 }
cristyf2ad14a2011-03-18 18:57:25 +00004682 case ModeStatistic:
4683 {
4684 pixel=GetModePixelList(pixel_list[id]);
4685 break;
4686 }
4687 case NonpeakStatistic:
4688 {
4689 pixel=GetNonpeakPixelList(pixel_list[id]);
4690 break;
4691 }
cristy9a68cbb2011-03-29 00:51:23 +00004692 case StandardDeviationStatistic:
4693 {
4694 pixel=GetStandardDeviationPixelList(pixel_list[id]);
4695 break;
4696 }
cristy0834d642011-03-18 18:26:08 +00004697 }
cristyed231572011-07-14 02:18:59 +00004698 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00004699 SetPixelRed(statistic_image,ClampToQuantum(pixel.red),q);
cristyed231572011-07-14 02:18:59 +00004700 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00004701 SetPixelGreen(statistic_image,ClampToQuantum(pixel.green),q);
cristyed231572011-07-14 02:18:59 +00004702 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00004703 SetPixelBlue(statistic_image,ClampToQuantum(pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00004704 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy0834d642011-03-18 18:26:08 +00004705 (image->colorspace == CMYKColorspace))
cristy490408a2011-07-07 14:42:05 +00004706 SetPixelBlack(statistic_image,ClampToQuantum(pixel.black),q);
cristyed231572011-07-14 02:18:59 +00004707 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00004708 (image->matte != MagickFalse))
cristy490408a2011-07-07 14:42:05 +00004709 SetPixelAlpha(statistic_image,ClampToQuantum(pixel.alpha),q);
cristyed231572011-07-14 02:18:59 +00004710 p+=GetPixelChannels(image);
4711 q+=GetPixelChannels(statistic_image);
cristy0834d642011-03-18 18:26:08 +00004712 }
4713 if (SyncCacheViewAuthenticPixels(statistic_view,exception) == MagickFalse)
4714 status=MagickFalse;
4715 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4716 {
4717 MagickBooleanType
4718 proceed;
4719
4720#if defined(MAGICKCORE_OPENMP_SUPPORT)
4721 #pragma omp critical (MagickCore_StatisticImage)
4722#endif
4723 proceed=SetImageProgress(image,StatisticImageTag,progress++,
4724 image->rows);
4725 if (proceed == MagickFalse)
4726 status=MagickFalse;
4727 }
4728 }
4729 statistic_view=DestroyCacheView(statistic_view);
4730 image_view=DestroyCacheView(image_view);
4731 pixel_list=DestroyPixelListThreadSet(pixel_list);
4732 return(statistic_image);
4733}
4734
4735/*
4736%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4737% %
4738% %
4739% %
cristy3ed852e2009-09-05 21:47:34 +00004740% U n s h a r p M a s k I m a g e %
4741% %
4742% %
4743% %
4744%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4745%
4746% UnsharpMaskImage() sharpens one or more image channels. We convolve the
4747% image with a Gaussian operator of the given radius and standard deviation
4748% (sigma). For reasonable results, radius should be larger than sigma. Use a
4749% radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
4750%
4751% The format of the UnsharpMaskImage method is:
4752%
4753% Image *UnsharpMaskImage(const Image *image,const double radius,
4754% const double sigma,const double amount,const double threshold,
4755% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00004756%
4757% A description of each parameter follows:
4758%
4759% o image: the image.
4760%
cristy3ed852e2009-09-05 21:47:34 +00004761% o radius: the radius of the Gaussian, in pixels, not counting the center
4762% pixel.
4763%
4764% o sigma: the standard deviation of the Gaussian, in pixels.
4765%
4766% o amount: the percentage of the difference between the original and the
4767% blur image that is added back into the original.
4768%
4769% o threshold: the threshold in pixels needed to apply the diffence amount.
4770%
4771% o exception: return any errors or warnings in this structure.
4772%
4773*/
cristyf4ad9df2011-07-08 16:49:03 +00004774MagickExport Image *UnsharpMaskImage(const Image *image,
4775 const double radius,const double sigma,const double amount,
4776 const double threshold,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00004777{
4778#define SharpenImageTag "Sharpen/Image"
4779
cristyc4c8d132010-01-07 01:58:38 +00004780 CacheView
4781 *image_view,
4782 *unsharp_view;
4783
cristy3ed852e2009-09-05 21:47:34 +00004784 Image
4785 *unsharp_image;
4786
cristy3ed852e2009-09-05 21:47:34 +00004787 MagickBooleanType
4788 status;
4789
cristybb503372010-05-27 20:51:26 +00004790 MagickOffsetType
4791 progress;
4792
cristy4c08aed2011-07-01 19:47:50 +00004793 PixelInfo
cristyddd82202009-11-03 20:14:50 +00004794 bias;
cristy3ed852e2009-09-05 21:47:34 +00004795
4796 MagickRealType
4797 quantum_threshold;
4798
cristybb503372010-05-27 20:51:26 +00004799 ssize_t
4800 y;
4801
cristy3ed852e2009-09-05 21:47:34 +00004802 assert(image != (const Image *) NULL);
4803 assert(image->signature == MagickSignature);
4804 if (image->debug != MagickFalse)
4805 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4806 assert(exception != (ExceptionInfo *) NULL);
cristyf4ad9df2011-07-08 16:49:03 +00004807 unsharp_image=BlurImage(image,radius,sigma,exception);
cristy3ed852e2009-09-05 21:47:34 +00004808 if (unsharp_image == (Image *) NULL)
4809 return((Image *) NULL);
4810 quantum_threshold=(MagickRealType) QuantumRange*threshold;
4811 /*
4812 Unsharp-mask image.
4813 */
4814 status=MagickTrue;
4815 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00004816 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00004817 image_view=AcquireCacheView(image);
4818 unsharp_view=AcquireCacheView(unsharp_image);
cristyb5d5f722009-11-04 03:03:49 +00004819#if defined(MAGICKCORE_OPENMP_SUPPORT)
4820 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004821#endif
cristybb503372010-05-27 20:51:26 +00004822 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00004823 {
cristy4c08aed2011-07-01 19:47:50 +00004824 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00004825 pixel;
4826
cristy4c08aed2011-07-01 19:47:50 +00004827 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00004828 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00004829
cristy4c08aed2011-07-01 19:47:50 +00004830 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00004831 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004832
cristy117ff172010-08-15 21:35:32 +00004833 register ssize_t
4834 x;
4835
cristy3ed852e2009-09-05 21:47:34 +00004836 if (status == MagickFalse)
4837 continue;
4838 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
4839 q=GetCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
4840 exception);
cristy4c08aed2011-07-01 19:47:50 +00004841 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00004842 {
4843 status=MagickFalse;
4844 continue;
4845 }
cristyddd82202009-11-03 20:14:50 +00004846 pixel=bias;
cristybb503372010-05-27 20:51:26 +00004847 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004848 {
cristyed231572011-07-14 02:18:59 +00004849 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00004850 {
cristy4c08aed2011-07-01 19:47:50 +00004851 pixel.red=GetPixelRed(image,p)-(MagickRealType) GetPixelRed(image,q);
cristy3ed852e2009-09-05 21:47:34 +00004852 if (fabs(2.0*pixel.red) < quantum_threshold)
cristy4c08aed2011-07-01 19:47:50 +00004853 pixel.red=(MagickRealType) GetPixelRed(image,p);
cristy3ed852e2009-09-05 21:47:34 +00004854 else
cristy4c08aed2011-07-01 19:47:50 +00004855 pixel.red=(MagickRealType) GetPixelRed(image,p)+(pixel.red*amount);
4856 SetPixelRed(unsharp_image,ClampToQuantum(pixel.red),q);
cristy3ed852e2009-09-05 21:47:34 +00004857 }
cristyed231572011-07-14 02:18:59 +00004858 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00004859 {
cristy4c08aed2011-07-01 19:47:50 +00004860 pixel.green=GetPixelGreen(image,p)-
4861 (MagickRealType) GetPixelGreen(image,q);
cristy3ed852e2009-09-05 21:47:34 +00004862 if (fabs(2.0*pixel.green) < quantum_threshold)
cristy4c08aed2011-07-01 19:47:50 +00004863 pixel.green=(MagickRealType)
4864 GetPixelGreen(image,p);
cristy3ed852e2009-09-05 21:47:34 +00004865 else
cristy4c08aed2011-07-01 19:47:50 +00004866 pixel.green=(MagickRealType)
4867 GetPixelGreen(image,p)+
4868 (pixel.green*amount);
4869 SetPixelGreen(unsharp_image,
4870 ClampToQuantum(pixel.green),q);
cristy3ed852e2009-09-05 21:47:34 +00004871 }
cristyed231572011-07-14 02:18:59 +00004872 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00004873 {
cristy4c08aed2011-07-01 19:47:50 +00004874 pixel.blue=GetPixelBlue(image,p)-
4875 (MagickRealType) GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00004876 if (fabs(2.0*pixel.blue) < quantum_threshold)
cristy4c08aed2011-07-01 19:47:50 +00004877 pixel.blue=(MagickRealType)
4878 GetPixelBlue(image,p);
cristy3ed852e2009-09-05 21:47:34 +00004879 else
cristy4c08aed2011-07-01 19:47:50 +00004880 pixel.blue=(MagickRealType)
4881 GetPixelBlue(image,p)+(pixel.blue*amount);
4882 SetPixelBlue(unsharp_image,
4883 ClampToQuantum(pixel.blue),q);
cristy3ed852e2009-09-05 21:47:34 +00004884 }
cristyed231572011-07-14 02:18:59 +00004885 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00004886 (image->colorspace == CMYKColorspace))
4887 {
cristy4c08aed2011-07-01 19:47:50 +00004888 pixel.black=GetPixelBlack(image,p)-
4889 (MagickRealType) GetPixelBlack(image,q);
4890 if (fabs(2.0*pixel.black) < quantum_threshold)
4891 pixel.black=(MagickRealType)
4892 GetPixelBlack(image,p);
cristy3ed852e2009-09-05 21:47:34 +00004893 else
cristy4c08aed2011-07-01 19:47:50 +00004894 pixel.black=(MagickRealType)
4895 GetPixelBlack(image,p)+(pixel.black*
4896 amount);
4897 SetPixelBlack(unsharp_image,
4898 ClampToQuantum(pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00004899 }
cristyed231572011-07-14 02:18:59 +00004900 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00004901 {
4902 pixel.alpha=GetPixelAlpha(image,p)-
4903 (MagickRealType) GetPixelAlpha(image,q);
4904 if (fabs(2.0*pixel.alpha) < quantum_threshold)
4905 pixel.alpha=(MagickRealType)
4906 GetPixelAlpha(image,p);
4907 else
4908 pixel.alpha=GetPixelAlpha(image,p)+
4909 (pixel.alpha*amount);
4910 SetPixelAlpha(unsharp_image,
4911 ClampToQuantum(pixel.alpha),q);
4912 }
cristyed231572011-07-14 02:18:59 +00004913 p+=GetPixelChannels(image);
4914 q+=GetPixelChannels(unsharp_image);
cristy3ed852e2009-09-05 21:47:34 +00004915 }
4916 if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
4917 status=MagickFalse;
4918 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4919 {
4920 MagickBooleanType
4921 proceed;
4922
cristyb5d5f722009-11-04 03:03:49 +00004923#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00004924 #pragma omp critical (MagickCore_UnsharpMaskImage)
cristy3ed852e2009-09-05 21:47:34 +00004925#endif
4926 proceed=SetImageProgress(image,SharpenImageTag,progress++,image->rows);
4927 if (proceed == MagickFalse)
4928 status=MagickFalse;
4929 }
4930 }
4931 unsharp_image->type=image->type;
4932 unsharp_view=DestroyCacheView(unsharp_view);
4933 image_view=DestroyCacheView(image_view);
4934 if (status == MagickFalse)
4935 unsharp_image=DestroyImage(unsharp_image);
4936 return(unsharp_image);
4937}