blob: a48c64964eb7eea16306dabe53477aedb5f2cc72 [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)
109% Image *AdaptiveBlurImageChannel(const Image *image,
110% const ChannelType channel,double radius,const double sigma,
111% ExceptionInfo *exception)
112%
113% A description of each parameter follows:
114%
115% o image: the image.
116%
117% o channel: the channel type.
118%
119% o radius: the radius of the Gaussian, in pixels, not counting the center
120% pixel.
121%
122% o sigma: the standard deviation of the Laplacian, in pixels.
123%
124% o exception: return any errors or warnings in this structure.
125%
126*/
127
128MagickExport Image *AdaptiveBlurImage(const Image *image,const double radius,
129 const double sigma,ExceptionInfo *exception)
130{
131 Image
132 *blur_image;
133
134 blur_image=AdaptiveBlurImageChannel(image,DefaultChannels,radius,sigma,
135 exception);
136 return(blur_image);
137}
138
cristyf89cb1d2011-07-07 01:24:37 +0000139MagickExport MagickBooleanType AdaptiveLevelImage(Image *image,
140 const char *levels)
141{
142 double
143 black_point,
144 gamma,
145 white_point;
146
147 GeometryInfo
148 geometry_info;
149
150 MagickBooleanType
151 status;
152
153 MagickStatusType
154 flags;
155
156 /*
157 Parse levels.
158 */
159 if (levels == (char *) NULL)
160 return(MagickFalse);
161 flags=ParseGeometry(levels,&geometry_info);
162 black_point=geometry_info.rho;
163 white_point=(double) QuantumRange;
164 if ((flags & SigmaValue) != 0)
165 white_point=geometry_info.sigma;
166 gamma=1.0;
167 if ((flags & XiValue) != 0)
168 gamma=geometry_info.xi;
169 if ((flags & PercentValue) != 0)
170 {
171 black_point*=(double) image->columns*image->rows/100.0;
172 white_point*=(double) image->columns*image->rows/100.0;
173 }
174 if ((flags & SigmaValue) == 0)
175 white_point=(double) QuantumRange-black_point;
176 if ((flags & AspectValue ) == 0)
177 status=LevelImage(image,black_point,white_point,gamma);
178 else
179 status=LevelizeImage(image,black_point,white_point,gamma);
180 return(status);
181}
182
cristy3ed852e2009-09-05 21:47:34 +0000183MagickExport Image *AdaptiveBlurImageChannel(const Image *image,
184 const ChannelType channel,const double radius,const double sigma,
185 ExceptionInfo *exception)
186{
187#define AdaptiveBlurImageTag "Convolve/Image"
188#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
189
cristyc4c8d132010-01-07 01:58:38 +0000190 CacheView
191 *blur_view,
192 *edge_view,
193 *image_view;
194
cristy3ed852e2009-09-05 21:47:34 +0000195 double
cristy47e00502009-12-17 19:19:57 +0000196 **kernel,
197 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000198
199 Image
200 *blur_image,
201 *edge_image,
202 *gaussian_image;
203
cristy3ed852e2009-09-05 21:47:34 +0000204 MagickBooleanType
205 status;
206
cristybb503372010-05-27 20:51:26 +0000207 MagickOffsetType
208 progress;
209
cristy4c08aed2011-07-01 19:47:50 +0000210 PixelInfo
cristyddd82202009-11-03 20:14:50 +0000211 bias;
cristy3ed852e2009-09-05 21:47:34 +0000212
cristybb503372010-05-27 20:51:26 +0000213 register ssize_t
cristy47e00502009-12-17 19:19:57 +0000214 i;
cristy3ed852e2009-09-05 21:47:34 +0000215
cristybb503372010-05-27 20:51:26 +0000216 size_t
cristy3ed852e2009-09-05 21:47:34 +0000217 width;
218
cristybb503372010-05-27 20:51:26 +0000219 ssize_t
220 j,
221 k,
222 u,
223 v,
224 y;
225
cristy3ed852e2009-09-05 21:47:34 +0000226 assert(image != (const Image *) NULL);
227 assert(image->signature == MagickSignature);
228 if (image->debug != MagickFalse)
229 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
230 assert(exception != (ExceptionInfo *) NULL);
231 assert(exception->signature == MagickSignature);
232 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
233 if (blur_image == (Image *) NULL)
234 return((Image *) NULL);
235 if (fabs(sigma) <= MagickEpsilon)
236 return(blur_image);
237 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
238 {
239 InheritException(exception,&blur_image->exception);
240 blur_image=DestroyImage(blur_image);
241 return((Image *) NULL);
242 }
243 /*
244 Edge detect the image brighness channel, level, blur, and level again.
245 */
246 edge_image=EdgeImage(image,radius,exception);
247 if (edge_image == (Image *) NULL)
248 {
249 blur_image=DestroyImage(blur_image);
250 return((Image *) NULL);
251 }
cristyf89cb1d2011-07-07 01:24:37 +0000252 (void) AdaptiveLevelImage(edge_image,"20%,95%");
cristy3ed852e2009-09-05 21:47:34 +0000253 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,exception);
254 if (gaussian_image != (Image *) NULL)
255 {
256 edge_image=DestroyImage(edge_image);
257 edge_image=gaussian_image;
258 }
cristyf89cb1d2011-07-07 01:24:37 +0000259 (void) AdaptiveLevelImage(edge_image,"10%,95%");
cristy3ed852e2009-09-05 21:47:34 +0000260 /*
261 Create a set of kernels from maximum (radius,sigma) to minimum.
262 */
263 width=GetOptimalKernelWidth2D(radius,sigma);
264 kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
265 if (kernel == (double **) NULL)
266 {
267 edge_image=DestroyImage(edge_image);
268 blur_image=DestroyImage(blur_image);
269 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
270 }
271 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
cristybb503372010-05-27 20:51:26 +0000272 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000273 {
274 kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)*
275 sizeof(**kernel));
276 if (kernel[i] == (double *) NULL)
277 break;
cristy47e00502009-12-17 19:19:57 +0000278 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000279 j=(ssize_t) (width-i)/2;
cristy47e00502009-12-17 19:19:57 +0000280 k=0;
281 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000282 {
cristy47e00502009-12-17 19:19:57 +0000283 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000284 {
cristy4205a3c2010-09-12 20:19:59 +0000285 kernel[i][k]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
286 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +0000287 normalize+=kernel[i][k];
288 k++;
cristy3ed852e2009-09-05 21:47:34 +0000289 }
290 }
cristy3ed852e2009-09-05 21:47:34 +0000291 if (fabs(normalize) <= MagickEpsilon)
292 normalize=1.0;
293 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000294 for (k=0; k < (j*j); k++)
295 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000296 }
cristybb503372010-05-27 20:51:26 +0000297 if (i < (ssize_t) width)
cristy3ed852e2009-09-05 21:47:34 +0000298 {
299 for (i-=2; i >= 0; i-=2)
300 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
301 kernel=(double **) RelinquishMagickMemory(kernel);
302 edge_image=DestroyImage(edge_image);
303 blur_image=DestroyImage(blur_image);
304 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
305 }
306 /*
307 Adaptively blur image.
308 */
309 status=MagickTrue;
310 progress=0;
cristy4c08aed2011-07-01 19:47:50 +0000311 GetPixelInfo(image,&bias);
312 SetPixelInfoBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000313 image_view=AcquireCacheView(image);
314 edge_view=AcquireCacheView(edge_image);
315 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000316#if defined(MAGICKCORE_OPENMP_SUPPORT)
317 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000318#endif
cristybb503372010-05-27 20:51:26 +0000319 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000320 {
cristy4c08aed2011-07-01 19:47:50 +0000321 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000322 *restrict p,
323 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000324
cristy4c08aed2011-07-01 19:47:50 +0000325 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000326 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000327
cristy117ff172010-08-15 21:35:32 +0000328 register ssize_t
329 x;
330
cristy3ed852e2009-09-05 21:47:34 +0000331 if (status == MagickFalse)
332 continue;
333 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
334 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
335 exception);
cristy4c08aed2011-07-01 19:47:50 +0000336 if ((r == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000337 {
338 status=MagickFalse;
339 continue;
340 }
cristybb503372010-05-27 20:51:26 +0000341 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000342 {
cristy4c08aed2011-07-01 19:47:50 +0000343 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000344 pixel;
345
346 MagickRealType
347 alpha,
348 gamma;
349
350 register const double
cristyc47d1f82009-11-26 01:44:43 +0000351 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000352
cristybb503372010-05-27 20:51:26 +0000353 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000354 i,
355 u,
356 v;
357
358 gamma=0.0;
cristy4c08aed2011-07-01 19:47:50 +0000359 i=(ssize_t) ceil((double) width*QuantumScale*
360 GetPixelIntensity(edge_image,r)-0.5);
cristy3ed852e2009-09-05 21:47:34 +0000361 if (i < 0)
362 i=0;
363 else
cristybb503372010-05-27 20:51:26 +0000364 if (i > (ssize_t) width)
365 i=(ssize_t) width;
cristy3ed852e2009-09-05 21:47:34 +0000366 if ((i & 0x01) != 0)
367 i--;
cristya21afde2010-07-02 00:45:40 +0000368 p=GetCacheViewVirtualPixels(image_view,x-((ssize_t) (width-i)/2L),y-
369 (ssize_t) ((width-i)/2L),width-i,width-i,exception);
cristy4c08aed2011-07-01 19:47:50 +0000370 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000371 break;
cristyddd82202009-11-03 20:14:50 +0000372 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +0000373 k=kernel[i];
cristybb503372010-05-27 20:51:26 +0000374 for (v=0; v < (ssize_t) (width-i); v++)
cristy3ed852e2009-09-05 21:47:34 +0000375 {
cristybb503372010-05-27 20:51:26 +0000376 for (u=0; u < (ssize_t) (width-i); u++)
cristy3ed852e2009-09-05 21:47:34 +0000377 {
378 alpha=1.0;
cristy2b9582a2011-07-04 17:38:56 +0000379 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000380 (image->matte != MagickFalse))
cristy4c08aed2011-07-01 19:47:50 +0000381 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,p));
cristy2b9582a2011-07-04 17:38:56 +0000382 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000383 pixel.red+=(*k)*alpha*GetPixelRed(image,p);
cristy2b9582a2011-07-04 17:38:56 +0000384 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000385 pixel.green+=(*k)*alpha*GetPixelGreen(image,p);
cristy2b9582a2011-07-04 17:38:56 +0000386 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000387 pixel.blue+=(*k)*alpha*GetPixelBlue(image,p);
cristy2b9582a2011-07-04 17:38:56 +0000388 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000389 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +0000390 pixel.black+=(*k)*alpha*GetPixelBlack(image,p);
cristy2b9582a2011-07-04 17:38:56 +0000391 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000392 pixel.alpha+=(*k)*GetPixelAlpha(image,p);
cristy3ed852e2009-09-05 21:47:34 +0000393 gamma+=(*k)*alpha;
394 k++;
cristy4c08aed2011-07-01 19:47:50 +0000395 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000396 }
397 }
398 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy2b9582a2011-07-04 17:38:56 +0000399 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000400 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +0000401 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000402 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +0000403 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000404 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +0000405 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000406 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +0000407 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +0000408 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000409 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
410 q+=GetPixelChannels(blur_image);
411 r+=GetPixelChannels(edge_image);
cristy3ed852e2009-09-05 21:47:34 +0000412 }
413 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
414 status=MagickFalse;
415 if (image->progress_monitor != (MagickProgressMonitor) NULL)
416 {
417 MagickBooleanType
418 proceed;
419
cristyb5d5f722009-11-04 03:03:49 +0000420#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000421 #pragma omp critical (MagickCore_AdaptiveBlurImageChannel)
422#endif
423 proceed=SetImageProgress(image,AdaptiveBlurImageTag,progress++,
424 image->rows);
425 if (proceed == MagickFalse)
426 status=MagickFalse;
427 }
428 }
429 blur_image->type=image->type;
430 blur_view=DestroyCacheView(blur_view);
431 edge_view=DestroyCacheView(edge_view);
432 image_view=DestroyCacheView(image_view);
433 edge_image=DestroyImage(edge_image);
cristybb503372010-05-27 20:51:26 +0000434 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000435 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
436 kernel=(double **) RelinquishMagickMemory(kernel);
437 if (status == MagickFalse)
438 blur_image=DestroyImage(blur_image);
439 return(blur_image);
440}
441
442/*
443%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
444% %
445% %
446% %
447% A d a p t i v e S h a r p e n I m a g e %
448% %
449% %
450% %
451%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
452%
453% AdaptiveSharpenImage() adaptively sharpens the image by sharpening more
454% intensely near image edges and less intensely far from edges. We sharpen the
455% image with a Gaussian operator of the given radius and standard deviation
456% (sigma). For reasonable results, radius should be larger than sigma. Use a
457% radius of 0 and AdaptiveSharpenImage() selects a suitable radius for you.
458%
459% The format of the AdaptiveSharpenImage method is:
460%
461% Image *AdaptiveSharpenImage(const Image *image,const double radius,
462% const double sigma,ExceptionInfo *exception)
463% Image *AdaptiveSharpenImageChannel(const Image *image,
464% const ChannelType channel,double radius,const double sigma,
465% ExceptionInfo *exception)
466%
467% A description of each parameter follows:
468%
469% o image: the image.
470%
471% o channel: the channel type.
472%
473% o radius: the radius of the Gaussian, in pixels, not counting the center
474% pixel.
475%
476% o sigma: the standard deviation of the Laplacian, in pixels.
477%
478% o exception: return any errors or warnings in this structure.
479%
480*/
481
482MagickExport Image *AdaptiveSharpenImage(const Image *image,const double radius,
483 const double sigma,ExceptionInfo *exception)
484{
485 Image
486 *sharp_image;
487
488 sharp_image=AdaptiveSharpenImageChannel(image,DefaultChannels,radius,sigma,
489 exception);
490 return(sharp_image);
491}
492
493MagickExport Image *AdaptiveSharpenImageChannel(const Image *image,
494 const ChannelType channel,const double radius,const double sigma,
495 ExceptionInfo *exception)
496{
497#define AdaptiveSharpenImageTag "Convolve/Image"
498#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
499
cristyc4c8d132010-01-07 01:58:38 +0000500 CacheView
501 *sharp_view,
502 *edge_view,
503 *image_view;
504
cristy3ed852e2009-09-05 21:47:34 +0000505 double
cristy47e00502009-12-17 19:19:57 +0000506 **kernel,
507 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000508
509 Image
510 *sharp_image,
511 *edge_image,
512 *gaussian_image;
513
cristy3ed852e2009-09-05 21:47:34 +0000514 MagickBooleanType
515 status;
516
cristybb503372010-05-27 20:51:26 +0000517 MagickOffsetType
518 progress;
519
cristy4c08aed2011-07-01 19:47:50 +0000520 PixelInfo
cristyddd82202009-11-03 20:14:50 +0000521 bias;
cristy3ed852e2009-09-05 21:47:34 +0000522
cristybb503372010-05-27 20:51:26 +0000523 register ssize_t
cristy47e00502009-12-17 19:19:57 +0000524 i;
cristy3ed852e2009-09-05 21:47:34 +0000525
cristybb503372010-05-27 20:51:26 +0000526 size_t
cristy3ed852e2009-09-05 21:47:34 +0000527 width;
528
cristybb503372010-05-27 20:51:26 +0000529 ssize_t
530 j,
531 k,
532 u,
533 v,
534 y;
535
cristy3ed852e2009-09-05 21:47:34 +0000536 assert(image != (const Image *) NULL);
537 assert(image->signature == MagickSignature);
538 if (image->debug != MagickFalse)
539 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
540 assert(exception != (ExceptionInfo *) NULL);
541 assert(exception->signature == MagickSignature);
542 sharp_image=CloneImage(image,0,0,MagickTrue,exception);
543 if (sharp_image == (Image *) NULL)
544 return((Image *) NULL);
545 if (fabs(sigma) <= MagickEpsilon)
546 return(sharp_image);
547 if (SetImageStorageClass(sharp_image,DirectClass) == MagickFalse)
548 {
549 InheritException(exception,&sharp_image->exception);
550 sharp_image=DestroyImage(sharp_image);
551 return((Image *) NULL);
552 }
553 /*
554 Edge detect the image brighness channel, level, sharp, and level again.
555 */
556 edge_image=EdgeImage(image,radius,exception);
557 if (edge_image == (Image *) NULL)
558 {
559 sharp_image=DestroyImage(sharp_image);
560 return((Image *) NULL);
561 }
cristyf89cb1d2011-07-07 01:24:37 +0000562 (void) AdaptiveLevelImage(edge_image,"20%,95%");
cristy3ed852e2009-09-05 21:47:34 +0000563 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,exception);
564 if (gaussian_image != (Image *) NULL)
565 {
566 edge_image=DestroyImage(edge_image);
567 edge_image=gaussian_image;
568 }
cristyf89cb1d2011-07-07 01:24:37 +0000569 (void) AdaptiveLevelImage(edge_image,"10%,95%");
cristy3ed852e2009-09-05 21:47:34 +0000570 /*
571 Create a set of kernels from maximum (radius,sigma) to minimum.
572 */
573 width=GetOptimalKernelWidth2D(radius,sigma);
574 kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
575 if (kernel == (double **) NULL)
576 {
577 edge_image=DestroyImage(edge_image);
578 sharp_image=DestroyImage(sharp_image);
579 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
580 }
581 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
cristybb503372010-05-27 20:51:26 +0000582 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000583 {
584 kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)*
585 sizeof(**kernel));
586 if (kernel[i] == (double *) NULL)
587 break;
cristy47e00502009-12-17 19:19:57 +0000588 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000589 j=(ssize_t) (width-i)/2;
cristy47e00502009-12-17 19:19:57 +0000590 k=0;
591 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000592 {
cristy47e00502009-12-17 19:19:57 +0000593 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000594 {
cristy4205a3c2010-09-12 20:19:59 +0000595 kernel[i][k]=(double) (-exp(-((double) u*u+v*v)/(2.0*MagickSigma*
596 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +0000597 normalize+=kernel[i][k];
598 k++;
cristy3ed852e2009-09-05 21:47:34 +0000599 }
600 }
cristy3ed852e2009-09-05 21:47:34 +0000601 if (fabs(normalize) <= MagickEpsilon)
602 normalize=1.0;
603 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000604 for (k=0; k < (j*j); k++)
605 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000606 }
cristybb503372010-05-27 20:51:26 +0000607 if (i < (ssize_t) width)
cristy3ed852e2009-09-05 21:47:34 +0000608 {
609 for (i-=2; i >= 0; i-=2)
610 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
611 kernel=(double **) RelinquishMagickMemory(kernel);
612 edge_image=DestroyImage(edge_image);
613 sharp_image=DestroyImage(sharp_image);
614 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
615 }
616 /*
617 Adaptively sharpen image.
618 */
619 status=MagickTrue;
620 progress=0;
cristy4c08aed2011-07-01 19:47:50 +0000621 GetPixelInfo(image,&bias);
622 SetPixelInfoBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000623 image_view=AcquireCacheView(image);
624 edge_view=AcquireCacheView(edge_image);
625 sharp_view=AcquireCacheView(sharp_image);
cristyb5d5f722009-11-04 03:03:49 +0000626#if defined(MAGICKCORE_OPENMP_SUPPORT)
627 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000628#endif
cristybb503372010-05-27 20:51:26 +0000629 for (y=0; y < (ssize_t) sharp_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000630 {
cristy4c08aed2011-07-01 19:47:50 +0000631 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000632 *restrict p,
633 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000634
cristy4c08aed2011-07-01 19:47:50 +0000635 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000636 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000637
cristy117ff172010-08-15 21:35:32 +0000638 register ssize_t
639 x;
640
cristy3ed852e2009-09-05 21:47:34 +0000641 if (status == MagickFalse)
642 continue;
643 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
644 q=QueueCacheViewAuthenticPixels(sharp_view,0,y,sharp_image->columns,1,
645 exception);
cristy4c08aed2011-07-01 19:47:50 +0000646 if ((r == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000647 {
648 status=MagickFalse;
649 continue;
650 }
cristybb503372010-05-27 20:51:26 +0000651 for (x=0; x < (ssize_t) sharp_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000652 {
cristy4c08aed2011-07-01 19:47:50 +0000653 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000654 pixel;
655
656 MagickRealType
657 alpha,
658 gamma;
659
660 register const double
cristyc47d1f82009-11-26 01:44:43 +0000661 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000662
cristybb503372010-05-27 20:51:26 +0000663 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000664 i,
665 u,
666 v;
667
668 gamma=0.0;
cristy4c08aed2011-07-01 19:47:50 +0000669 i=(ssize_t) ceil((double) width*QuantumScale*
670 GetPixelIntensity(edge_image,r)-0.5);
cristy3ed852e2009-09-05 21:47:34 +0000671 if (i < 0)
672 i=0;
673 else
cristybb503372010-05-27 20:51:26 +0000674 if (i > (ssize_t) width)
675 i=(ssize_t) width;
cristy3ed852e2009-09-05 21:47:34 +0000676 if ((i & 0x01) != 0)
677 i--;
cristy117ff172010-08-15 21:35:32 +0000678 p=GetCacheViewVirtualPixels(image_view,x-((ssize_t) (width-i)/2L),y-
679 (ssize_t) ((width-i)/2L),width-i,width-i,exception);
cristy4c08aed2011-07-01 19:47:50 +0000680 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000681 break;
cristy3ed852e2009-09-05 21:47:34 +0000682 k=kernel[i];
cristyddd82202009-11-03 20:14:50 +0000683 pixel=bias;
cristybb503372010-05-27 20:51:26 +0000684 for (v=0; v < (ssize_t) (width-i); v++)
cristy3ed852e2009-09-05 21:47:34 +0000685 {
cristybb503372010-05-27 20:51:26 +0000686 for (u=0; u < (ssize_t) (width-i); u++)
cristy3ed852e2009-09-05 21:47:34 +0000687 {
688 alpha=1.0;
cristy2b9582a2011-07-04 17:38:56 +0000689 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000690 (image->matte != MagickFalse))
cristy4c08aed2011-07-01 19:47:50 +0000691 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,p));
cristy2b9582a2011-07-04 17:38:56 +0000692 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000693 pixel.red+=(*k)*alpha*GetPixelRed(image,p);
cristy2b9582a2011-07-04 17:38:56 +0000694 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000695 pixel.green+=(*k)*alpha*GetPixelGreen(image,p);
cristy2b9582a2011-07-04 17:38:56 +0000696 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000697 pixel.blue+=(*k)*alpha*GetPixelBlue(image,p);
cristy2b9582a2011-07-04 17:38:56 +0000698 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000699 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +0000700 pixel.black+=(*k)*alpha*GetPixelBlack(image,p);
cristy2b9582a2011-07-04 17:38:56 +0000701 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000702 pixel.alpha+=(*k)*GetPixelAlpha(image,p);
cristy3ed852e2009-09-05 21:47:34 +0000703 gamma+=(*k)*alpha;
704 k++;
cristy4c08aed2011-07-01 19:47:50 +0000705 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000706 }
707 }
708 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy2b9582a2011-07-04 17:38:56 +0000709 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000710 SetPixelRed(sharp_image,ClampToQuantum(gamma*pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +0000711 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000712 SetPixelGreen(sharp_image,ClampToQuantum(gamma*pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +0000713 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000714 SetPixelBlue(sharp_image,ClampToQuantum(gamma*pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +0000715 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000716 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +0000717 SetPixelBlack(sharp_image,ClampToQuantum(gamma*pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +0000718 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000719 SetPixelAlpha(sharp_image,ClampToQuantum(pixel.alpha),q);
720 q+=GetPixelChannels(sharp_image);
721 r+=GetPixelChannels(edge_image);
cristy3ed852e2009-09-05 21:47:34 +0000722 }
723 if (SyncCacheViewAuthenticPixels(sharp_view,exception) == MagickFalse)
724 status=MagickFalse;
725 if (image->progress_monitor != (MagickProgressMonitor) NULL)
726 {
727 MagickBooleanType
728 proceed;
729
cristyb5d5f722009-11-04 03:03:49 +0000730#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000731 #pragma omp critical (MagickCore_AdaptiveSharpenImageChannel)
732#endif
733 proceed=SetImageProgress(image,AdaptiveSharpenImageTag,progress++,
734 image->rows);
735 if (proceed == MagickFalse)
736 status=MagickFalse;
737 }
738 }
739 sharp_image->type=image->type;
740 sharp_view=DestroyCacheView(sharp_view);
741 edge_view=DestroyCacheView(edge_view);
742 image_view=DestroyCacheView(image_view);
743 edge_image=DestroyImage(edge_image);
cristybb503372010-05-27 20:51:26 +0000744 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000745 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
746 kernel=(double **) RelinquishMagickMemory(kernel);
747 if (status == MagickFalse)
748 sharp_image=DestroyImage(sharp_image);
749 return(sharp_image);
750}
751
752/*
753%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
754% %
755% %
756% %
757% B l u r I m a g e %
758% %
759% %
760% %
761%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
762%
763% BlurImage() blurs an image. We convolve the image with a Gaussian operator
764% of the given radius and standard deviation (sigma). For reasonable results,
765% the radius should be larger than sigma. Use a radius of 0 and BlurImage()
766% selects a suitable radius for you.
767%
768% BlurImage() differs from GaussianBlurImage() in that it uses a separable
769% kernel which is faster but mathematically equivalent to the non-separable
770% kernel.
771%
772% The format of the BlurImage method is:
773%
774% Image *BlurImage(const Image *image,const double radius,
775% const double sigma,ExceptionInfo *exception)
776% Image *BlurImageChannel(const Image *image,const ChannelType channel,
777% const double radius,const double sigma,ExceptionInfo *exception)
778%
779% A description of each parameter follows:
780%
781% o image: the image.
782%
783% o channel: the channel type.
784%
785% o radius: the radius of the Gaussian, in pixels, not counting the center
786% pixel.
787%
788% o sigma: the standard deviation of the Gaussian, in pixels.
789%
790% o exception: return any errors or warnings in this structure.
791%
792*/
793
794MagickExport Image *BlurImage(const Image *image,const double radius,
795 const double sigma,ExceptionInfo *exception)
796{
797 Image
798 *blur_image;
799
800 blur_image=BlurImageChannel(image,DefaultChannels,radius,sigma,exception);
801 return(blur_image);
802}
803
cristybb503372010-05-27 20:51:26 +0000804static double *GetBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +0000805{
cristy3ed852e2009-09-05 21:47:34 +0000806 double
cristy47e00502009-12-17 19:19:57 +0000807 *kernel,
808 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000809
cristy117ff172010-08-15 21:35:32 +0000810 register ssize_t
811 i;
812
cristybb503372010-05-27 20:51:26 +0000813 ssize_t
cristy47e00502009-12-17 19:19:57 +0000814 j,
815 k;
cristy3ed852e2009-09-05 21:47:34 +0000816
cristy3ed852e2009-09-05 21:47:34 +0000817 /*
818 Generate a 1-D convolution kernel.
819 */
820 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
821 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
822 if (kernel == (double *) NULL)
823 return(0);
cristy3ed852e2009-09-05 21:47:34 +0000824 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000825 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +0000826 i=0;
827 for (k=(-j); k <= j; k++)
828 {
cristy4205a3c2010-09-12 20:19:59 +0000829 kernel[i]=(double) (exp(-((double) k*k)/(2.0*MagickSigma*MagickSigma))/
830 (MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +0000831 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +0000832 i++;
833 }
cristybb503372010-05-27 20:51:26 +0000834 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000835 kernel[i]/=normalize;
836 return(kernel);
837}
838
839MagickExport Image *BlurImageChannel(const Image *image,
840 const ChannelType channel,const double radius,const double sigma,
841 ExceptionInfo *exception)
842{
843#define BlurImageTag "Blur/Image"
844
cristyc4c8d132010-01-07 01:58:38 +0000845 CacheView
846 *blur_view,
847 *image_view;
848
cristy3ed852e2009-09-05 21:47:34 +0000849 double
850 *kernel;
851
852 Image
853 *blur_image;
854
cristy3ed852e2009-09-05 21:47:34 +0000855 MagickBooleanType
856 status;
857
cristybb503372010-05-27 20:51:26 +0000858 MagickOffsetType
859 progress;
860
cristy4c08aed2011-07-01 19:47:50 +0000861 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000862 bias;
863
cristybb503372010-05-27 20:51:26 +0000864 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000865 i;
866
cristybb503372010-05-27 20:51:26 +0000867 size_t
cristy3ed852e2009-09-05 21:47:34 +0000868 width;
869
cristybb503372010-05-27 20:51:26 +0000870 ssize_t
871 x,
872 y;
873
cristy3ed852e2009-09-05 21:47:34 +0000874 /*
875 Initialize blur image attributes.
876 */
877 assert(image != (Image *) NULL);
878 assert(image->signature == MagickSignature);
879 if (image->debug != MagickFalse)
880 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
881 assert(exception != (ExceptionInfo *) NULL);
882 assert(exception->signature == MagickSignature);
883 blur_image=CloneImage(image,0,0,MagickTrue,exception);
884 if (blur_image == (Image *) NULL)
885 return((Image *) NULL);
886 if (fabs(sigma) <= MagickEpsilon)
887 return(blur_image);
888 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
889 {
890 InheritException(exception,&blur_image->exception);
891 blur_image=DestroyImage(blur_image);
892 return((Image *) NULL);
893 }
894 width=GetOptimalKernelWidth1D(radius,sigma);
895 kernel=GetBlurKernel(width,sigma);
896 if (kernel == (double *) NULL)
897 {
898 blur_image=DestroyImage(blur_image);
899 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
900 }
901 if (image->debug != MagickFalse)
902 {
903 char
904 format[MaxTextExtent],
905 *message;
906
907 register const double
908 *k;
909
910 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +0000911 " BlurImage with %.20g kernel:",(double) width);
cristy3ed852e2009-09-05 21:47:34 +0000912 message=AcquireString("");
913 k=kernel;
cristybb503372010-05-27 20:51:26 +0000914 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000915 {
916 *message='\0';
cristyb51dff52011-05-19 16:55:47 +0000917 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) i);
cristy3ed852e2009-09-05 21:47:34 +0000918 (void) ConcatenateString(&message,format);
cristyb51dff52011-05-19 16:55:47 +0000919 (void) FormatLocaleString(format,MaxTextExtent,"%g ",*k++);
cristy3ed852e2009-09-05 21:47:34 +0000920 (void) ConcatenateString(&message,format);
921 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
922 }
923 message=DestroyString(message);
924 }
925 /*
926 Blur rows.
927 */
928 status=MagickTrue;
929 progress=0;
cristy4c08aed2011-07-01 19:47:50 +0000930 GetPixelInfo(image,&bias);
931 SetPixelInfoBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000932 image_view=AcquireCacheView(image);
933 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000934#if defined(MAGICKCORE_OPENMP_SUPPORT)
935 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000936#endif
cristybb503372010-05-27 20:51:26 +0000937 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000938 {
cristy4c08aed2011-07-01 19:47:50 +0000939 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000940 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000941
cristy4c08aed2011-07-01 19:47:50 +0000942 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000943 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000944
cristy117ff172010-08-15 21:35:32 +0000945 register ssize_t
946 x;
947
cristy3ed852e2009-09-05 21:47:34 +0000948 if (status == MagickFalse)
949 continue;
cristy117ff172010-08-15 21:35:32 +0000950 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y,
951 image->columns+width,1,exception);
cristy3ed852e2009-09-05 21:47:34 +0000952 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
953 exception);
cristy4c08aed2011-07-01 19:47:50 +0000954 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000955 {
956 status=MagickFalse;
957 continue;
958 }
cristybb503372010-05-27 20:51:26 +0000959 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000960 {
cristy4c08aed2011-07-01 19:47:50 +0000961 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000962 pixel;
963
964 register const double
cristyc47d1f82009-11-26 01:44:43 +0000965 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000966
cristy4c08aed2011-07-01 19:47:50 +0000967 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000968 *restrict kernel_pixels;
cristy3ed852e2009-09-05 21:47:34 +0000969
cristybb503372010-05-27 20:51:26 +0000970 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000971 i;
972
cristyddd82202009-11-03 20:14:50 +0000973 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +0000974 k=kernel;
975 kernel_pixels=p;
cristy2b9582a2011-07-04 17:38:56 +0000976 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +0000977 {
cristybb503372010-05-27 20:51:26 +0000978 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000979 {
cristy4c08aed2011-07-01 19:47:50 +0000980 pixel.red+=(*k)*GetPixelRed(image,kernel_pixels);
981 pixel.green+=(*k)*GetPixelGreen(image,kernel_pixels);
982 pixel.blue+=(*k)*GetPixelBlue(image,kernel_pixels);
983 if (image->colorspace == CMYKColorspace)
984 pixel.black+=(*k)*GetPixelBlack(image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +0000985 k++;
cristy4c08aed2011-07-01 19:47:50 +0000986 kernel_pixels+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000987 }
cristy2b9582a2011-07-04 17:38:56 +0000988 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000989 SetPixelRed(blur_image,ClampToQuantum(pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +0000990 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000991 SetPixelGreen(blur_image,ClampToQuantum(pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +0000992 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000993 SetPixelBlue(blur_image,ClampToQuantum(pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +0000994 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000995 (blur_image->colorspace == CMYKColorspace))
996 SetPixelBlack(blur_image,ClampToQuantum(pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +0000997 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +0000998 {
999 k=kernel;
1000 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001001 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001002 {
cristy4c08aed2011-07-01 19:47:50 +00001003 pixel.alpha+=(*k)*GetPixelAlpha(image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001004 k++;
cristy4c08aed2011-07-01 19:47:50 +00001005 kernel_pixels+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001006 }
cristy4c08aed2011-07-01 19:47:50 +00001007 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00001008 }
1009 }
1010 else
1011 {
1012 MagickRealType
1013 alpha,
1014 gamma;
1015
1016 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00001017 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001018 {
cristy4c08aed2011-07-01 19:47:50 +00001019 alpha=(MagickRealType) (QuantumScale*
1020 GetPixelAlpha(image,kernel_pixels));
1021 pixel.red+=(*k)*alpha*GetPixelRed(image,kernel_pixels);
1022 pixel.green+=(*k)*alpha*GetPixelGreen(image,kernel_pixels);
1023 pixel.blue+=(*k)*alpha*GetPixelBlue(image,kernel_pixels);
1024 if (image->colorspace == CMYKColorspace)
1025 pixel.black+=(*k)*alpha*GetPixelBlack(image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001026 gamma+=(*k)*alpha;
1027 k++;
cristy4c08aed2011-07-01 19:47:50 +00001028 kernel_pixels+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001029 }
1030 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy2b9582a2011-07-04 17:38:56 +00001031 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001032 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00001033 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001034 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00001035 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001036 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00001037 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001038 (blur_image->colorspace == CMYKColorspace))
1039 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00001040 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001041 {
1042 k=kernel;
1043 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001044 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001045 {
cristy4c08aed2011-07-01 19:47:50 +00001046 pixel.alpha+=(*k)*GetPixelAlpha(image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001047 k++;
cristy4c08aed2011-07-01 19:47:50 +00001048 kernel_pixels+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001049 }
cristy4c08aed2011-07-01 19:47:50 +00001050 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00001051 }
1052 }
cristy4c08aed2011-07-01 19:47:50 +00001053 p+=GetPixelChannels(image);
1054 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001055 }
1056 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1057 status=MagickFalse;
1058 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1059 {
1060 MagickBooleanType
1061 proceed;
1062
cristyb5d5f722009-11-04 03:03:49 +00001063#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001064 #pragma omp critical (MagickCore_BlurImageChannel)
1065#endif
1066 proceed=SetImageProgress(image,BlurImageTag,progress++,blur_image->rows+
1067 blur_image->columns);
1068 if (proceed == MagickFalse)
1069 status=MagickFalse;
1070 }
1071 }
1072 blur_view=DestroyCacheView(blur_view);
1073 image_view=DestroyCacheView(image_view);
1074 /*
1075 Blur columns.
1076 */
1077 image_view=AcquireCacheView(blur_image);
1078 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00001079#if defined(MAGICKCORE_OPENMP_SUPPORT)
1080 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001081#endif
cristybb503372010-05-27 20:51:26 +00001082 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001083 {
cristy4c08aed2011-07-01 19:47:50 +00001084 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001085 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001086
cristy4c08aed2011-07-01 19:47:50 +00001087 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001088 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001089
cristy117ff172010-08-15 21:35:32 +00001090 register ssize_t
1091 y;
1092
cristy3ed852e2009-09-05 21:47:34 +00001093 if (status == MagickFalse)
1094 continue;
cristy117ff172010-08-15 21:35:32 +00001095 p=GetCacheViewVirtualPixels(image_view,x,-((ssize_t) width/2L),1,
1096 image->rows+width,exception);
cristy3ed852e2009-09-05 21:47:34 +00001097 q=GetCacheViewAuthenticPixels(blur_view,x,0,1,blur_image->rows,exception);
cristy4c08aed2011-07-01 19:47:50 +00001098 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001099 {
1100 status=MagickFalse;
1101 continue;
1102 }
cristybb503372010-05-27 20:51:26 +00001103 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001104 {
cristy4c08aed2011-07-01 19:47:50 +00001105 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001106 pixel;
1107
1108 register const double
cristyc47d1f82009-11-26 01:44:43 +00001109 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00001110
cristy4c08aed2011-07-01 19:47:50 +00001111 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001112 *restrict kernel_pixels;
cristy3ed852e2009-09-05 21:47:34 +00001113
cristybb503372010-05-27 20:51:26 +00001114 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001115 i;
1116
cristyddd82202009-11-03 20:14:50 +00001117 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00001118 k=kernel;
1119 kernel_pixels=p;
cristy2b9582a2011-07-04 17:38:56 +00001120 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) == 0) || (blur_image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00001121 {
cristybb503372010-05-27 20:51:26 +00001122 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001123 {
cristy4c08aed2011-07-01 19:47:50 +00001124 pixel.red+=(*k)*GetPixelRed(blur_image,kernel_pixels);
1125 pixel.green+=(*k)*GetPixelGreen(blur_image,kernel_pixels);
1126 pixel.blue+=(*k)*GetPixelBlue(blur_image,kernel_pixels);
1127 if (blur_image->colorspace == CMYKColorspace)
1128 pixel.black+=(*k)*GetPixelBlack(blur_image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001129 k++;
cristy4c08aed2011-07-01 19:47:50 +00001130 kernel_pixels+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001131 }
cristy2b9582a2011-07-04 17:38:56 +00001132 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001133 SetPixelRed(blur_image,ClampToQuantum(pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00001134 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001135 SetPixelGreen(blur_image,ClampToQuantum(pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00001136 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001137 SetPixelBlue(blur_image,ClampToQuantum(pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00001138 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001139 (blur_image->colorspace == CMYKColorspace))
1140 SetPixelBlack(blur_image,ClampToQuantum(pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00001141 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001142 {
1143 k=kernel;
1144 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001145 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001146 {
cristy4c08aed2011-07-01 19:47:50 +00001147 pixel.alpha+=(*k)*GetPixelAlpha(blur_image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001148 k++;
cristy4c08aed2011-07-01 19:47:50 +00001149 kernel_pixels+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001150 }
cristy4c08aed2011-07-01 19:47:50 +00001151 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00001152 }
1153 }
1154 else
1155 {
1156 MagickRealType
1157 alpha,
1158 gamma;
1159
1160 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00001161 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001162 {
cristy46f08202010-01-10 04:04:21 +00001163 alpha=(MagickRealType) (QuantumScale*
cristy4c08aed2011-07-01 19:47:50 +00001164 GetPixelAlpha(blur_image,kernel_pixels));
1165 pixel.red+=(*k)*alpha*GetPixelRed(blur_image,kernel_pixels);
1166 pixel.green+=(*k)*alpha*GetPixelGreen(blur_image,kernel_pixels);
1167 pixel.blue+=(*k)*alpha*GetPixelBlue(blur_image,kernel_pixels);
1168 if (blur_image->colorspace == CMYKColorspace)
1169 pixel.black+=(*k)*alpha*GetPixelBlack(blur_image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001170 gamma+=(*k)*alpha;
1171 k++;
cristy4c08aed2011-07-01 19:47:50 +00001172 kernel_pixels+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001173 }
1174 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy2b9582a2011-07-04 17:38:56 +00001175 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001176 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00001177 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001178 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00001179 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001180 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00001181 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001182 (blur_image->colorspace == CMYKColorspace))
1183 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00001184 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001185 {
1186 k=kernel;
1187 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001188 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001189 {
cristy4c08aed2011-07-01 19:47:50 +00001190 pixel.alpha+=(*k)*GetPixelAlpha(blur_image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001191 k++;
cristy4c08aed2011-07-01 19:47:50 +00001192 kernel_pixels+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001193 }
cristy4c08aed2011-07-01 19:47:50 +00001194 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00001195 }
1196 }
cristy4c08aed2011-07-01 19:47:50 +00001197 p+=GetPixelChannels(blur_image);
1198 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001199 }
1200 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1201 status=MagickFalse;
cristy4c08aed2011-07-01 19:47:50 +00001202 if (blur_image->progress_monitor != (MagickProgressMonitor) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001203 {
1204 MagickBooleanType
1205 proceed;
1206
cristyb5d5f722009-11-04 03:03:49 +00001207#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001208 #pragma omp critical (MagickCore_BlurImageChannel)
1209#endif
cristy4c08aed2011-07-01 19:47:50 +00001210 proceed=SetImageProgress(blur_image,BlurImageTag,progress++,
1211 blur_image->rows+blur_image->columns);
cristy3ed852e2009-09-05 21:47:34 +00001212 if (proceed == MagickFalse)
1213 status=MagickFalse;
1214 }
1215 }
1216 blur_view=DestroyCacheView(blur_view);
1217 image_view=DestroyCacheView(image_view);
1218 kernel=(double *) RelinquishMagickMemory(kernel);
1219 if (status == MagickFalse)
1220 blur_image=DestroyImage(blur_image);
1221 blur_image->type=image->type;
1222 return(blur_image);
1223}
1224
1225/*
1226%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1227% %
1228% %
1229% %
cristyfccdab92009-11-30 16:43:57 +00001230% C o n v o l v e I m a g e %
1231% %
1232% %
1233% %
1234%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1235%
1236% ConvolveImage() applies a custom convolution kernel to the image.
1237%
1238% The format of the ConvolveImage method is:
1239%
cristybb503372010-05-27 20:51:26 +00001240% Image *ConvolveImage(const Image *image,const size_t order,
cristyfccdab92009-11-30 16:43:57 +00001241% const double *kernel,ExceptionInfo *exception)
1242% Image *ConvolveImageChannel(const Image *image,const ChannelType channel,
cristy117ff172010-08-15 21:35:32 +00001243% const size_t order,const double *kernel,ExceptionInfo *exception)
cristyfccdab92009-11-30 16:43:57 +00001244%
1245% A description of each parameter follows:
1246%
1247% o image: the image.
1248%
1249% o channel: the channel type.
1250%
1251% o order: the number of columns and rows in the filter kernel.
1252%
1253% o kernel: An array of double representing the convolution kernel.
1254%
1255% o exception: return any errors or warnings in this structure.
1256%
1257*/
1258
cristybb503372010-05-27 20:51:26 +00001259MagickExport Image *ConvolveImage(const Image *image,const size_t order,
cristyfccdab92009-11-30 16:43:57 +00001260 const double *kernel,ExceptionInfo *exception)
1261{
1262 Image
1263 *convolve_image;
1264
1265 convolve_image=ConvolveImageChannel(image,DefaultChannels,order,kernel,
1266 exception);
1267 return(convolve_image);
1268}
1269
1270MagickExport Image *ConvolveImageChannel(const Image *image,
cristybb503372010-05-27 20:51:26 +00001271 const ChannelType channel,const size_t order,const double *kernel,
cristyfccdab92009-11-30 16:43:57 +00001272 ExceptionInfo *exception)
1273{
1274#define ConvolveImageTag "Convolve/Image"
1275
cristyc4c8d132010-01-07 01:58:38 +00001276 CacheView
1277 *convolve_view,
1278 *image_view;
1279
cristyfccdab92009-11-30 16:43:57 +00001280 double
1281 *normal_kernel;
1282
1283 Image
1284 *convolve_image;
1285
cristyfccdab92009-11-30 16:43:57 +00001286 MagickBooleanType
1287 status;
1288
cristybb503372010-05-27 20:51:26 +00001289 MagickOffsetType
1290 progress;
1291
cristy4c08aed2011-07-01 19:47:50 +00001292 PixelInfo
cristyfccdab92009-11-30 16:43:57 +00001293 bias;
1294
1295 MagickRealType
1296 gamma;
1297
cristybb503372010-05-27 20:51:26 +00001298 register ssize_t
cristyfccdab92009-11-30 16:43:57 +00001299 i;
1300
cristybb503372010-05-27 20:51:26 +00001301 size_t
cristyfccdab92009-11-30 16:43:57 +00001302 width;
1303
cristybb503372010-05-27 20:51:26 +00001304 ssize_t
1305 y;
1306
cristyfccdab92009-11-30 16:43:57 +00001307 /*
1308 Initialize convolve image attributes.
1309 */
1310 assert(image != (Image *) NULL);
1311 assert(image->signature == MagickSignature);
1312 if (image->debug != MagickFalse)
1313 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1314 assert(exception != (ExceptionInfo *) NULL);
1315 assert(exception->signature == MagickSignature);
1316 width=order;
1317 if ((width % 2) == 0)
1318 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
1319 convolve_image=CloneImage(image,0,0,MagickTrue,exception);
1320 if (convolve_image == (Image *) NULL)
1321 return((Image *) NULL);
1322 if (SetImageStorageClass(convolve_image,DirectClass) == MagickFalse)
1323 {
1324 InheritException(exception,&convolve_image->exception);
1325 convolve_image=DestroyImage(convolve_image);
1326 return((Image *) NULL);
1327 }
1328 if (image->debug != MagickFalse)
1329 {
1330 char
1331 format[MaxTextExtent],
1332 *message;
1333
cristy117ff172010-08-15 21:35:32 +00001334 register const double
1335 *k;
1336
cristybb503372010-05-27 20:51:26 +00001337 ssize_t
cristyfccdab92009-11-30 16:43:57 +00001338 u,
1339 v;
1340
cristyfccdab92009-11-30 16:43:57 +00001341 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00001342 " ConvolveImage with %.20gx%.20g kernel:",(double) width,(double)
1343 width);
cristyfccdab92009-11-30 16:43:57 +00001344 message=AcquireString("");
1345 k=kernel;
cristybb503372010-05-27 20:51:26 +00001346 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001347 {
1348 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00001349 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristyfccdab92009-11-30 16:43:57 +00001350 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00001351 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001352 {
cristyb51dff52011-05-19 16:55:47 +00001353 (void) FormatLocaleString(format,MaxTextExtent,"%g ",*k++);
cristyfccdab92009-11-30 16:43:57 +00001354 (void) ConcatenateString(&message,format);
1355 }
1356 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
1357 }
1358 message=DestroyString(message);
1359 }
1360 /*
1361 Normalize kernel.
1362 */
1363 normal_kernel=(double *) AcquireQuantumMemory(width*width,
1364 sizeof(*normal_kernel));
1365 if (normal_kernel == (double *) NULL)
1366 {
1367 convolve_image=DestroyImage(convolve_image);
1368 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1369 }
1370 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00001371 for (i=0; i < (ssize_t) (width*width); i++)
cristyfccdab92009-11-30 16:43:57 +00001372 gamma+=kernel[i];
1373 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristybb503372010-05-27 20:51:26 +00001374 for (i=0; i < (ssize_t) (width*width); i++)
cristyfccdab92009-11-30 16:43:57 +00001375 normal_kernel[i]=gamma*kernel[i];
1376 /*
1377 Convolve image.
1378 */
1379 status=MagickTrue;
1380 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00001381 GetPixelInfo(image,&bias);
1382 SetPixelInfoBias(image,&bias);
cristyfccdab92009-11-30 16:43:57 +00001383 image_view=AcquireCacheView(image);
1384 convolve_view=AcquireCacheView(convolve_image);
1385#if defined(MAGICKCORE_OPENMP_SUPPORT)
1386 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1387#endif
cristybb503372010-05-27 20:51:26 +00001388 for (y=0; y < (ssize_t) image->rows; y++)
cristyfccdab92009-11-30 16:43:57 +00001389 {
1390 MagickBooleanType
1391 sync;
1392
cristy4c08aed2011-07-01 19:47:50 +00001393 register const Quantum
cristyfccdab92009-11-30 16:43:57 +00001394 *restrict p;
1395
cristy4c08aed2011-07-01 19:47:50 +00001396 register Quantum
cristyfccdab92009-11-30 16:43:57 +00001397 *restrict q;
1398
cristy117ff172010-08-15 21:35:32 +00001399 register ssize_t
1400 x;
1401
cristyfccdab92009-11-30 16:43:57 +00001402 if (status == MagickFalse)
1403 continue;
cristyce889302010-06-30 19:16:36 +00001404 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
1405 (width/2L),image->columns+width,width,exception);
cristyfccdab92009-11-30 16:43:57 +00001406 q=GetCacheViewAuthenticPixels(convolve_view,0,y,convolve_image->columns,1,
1407 exception);
cristy4c08aed2011-07-01 19:47:50 +00001408 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristyfccdab92009-11-30 16:43:57 +00001409 {
1410 status=MagickFalse;
1411 continue;
1412 }
cristybb503372010-05-27 20:51:26 +00001413 for (x=0; x < (ssize_t) image->columns; x++)
cristyfccdab92009-11-30 16:43:57 +00001414 {
cristy4c08aed2011-07-01 19:47:50 +00001415 PixelInfo
cristyfccdab92009-11-30 16:43:57 +00001416 pixel;
1417
1418 register const double
1419 *restrict k;
1420
cristy4c08aed2011-07-01 19:47:50 +00001421 register const Quantum
cristyfccdab92009-11-30 16:43:57 +00001422 *restrict kernel_pixels;
1423
cristybb503372010-05-27 20:51:26 +00001424 register ssize_t
cristyfccdab92009-11-30 16:43:57 +00001425 u;
1426
cristy117ff172010-08-15 21:35:32 +00001427 ssize_t
1428 v;
1429
cristyfccdab92009-11-30 16:43:57 +00001430 pixel=bias;
1431 k=normal_kernel;
1432 kernel_pixels=p;
cristy2b9582a2011-07-04 17:38:56 +00001433 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) == 0) || (image->matte == MagickFalse))
cristyfccdab92009-11-30 16:43:57 +00001434 {
cristybb503372010-05-27 20:51:26 +00001435 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001436 {
cristybb503372010-05-27 20:51:26 +00001437 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001438 {
cristy4c08aed2011-07-01 19:47:50 +00001439 pixel.red+=(*k)*GetPixelRed(image,kernel_pixels+u*
1440 GetPixelChannels(image));
1441 pixel.green+=(*k)*GetPixelGreen(image,kernel_pixels+u*
1442 GetPixelChannels(image));
1443 pixel.blue+=(*k)*GetPixelBlue(image,kernel_pixels+u*
1444 GetPixelChannels(image));
1445 if (image->colorspace == CMYKColorspace)
1446 pixel.black+=(*k)*GetPixelBlack(image,kernel_pixels+u*
1447 GetPixelChannels(image));
cristyfccdab92009-11-30 16:43:57 +00001448 k++;
1449 }
cristy4c08aed2011-07-01 19:47:50 +00001450 kernel_pixels+=(image->columns+width)*GetPixelChannels(image);
cristyfccdab92009-11-30 16:43:57 +00001451 }
cristy2b9582a2011-07-04 17:38:56 +00001452 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001453 SetPixelRed(convolve_image,ClampToQuantum(pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00001454 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001455 SetPixelGreen(convolve_image,ClampToQuantum(pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00001456 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001457 SetPixelBlue(convolve_image,ClampToQuantum(pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00001458 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001459 (image->colorspace == CMYKColorspace))
1460 SetPixelBlack(convolve_image,ClampToQuantum(pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00001461 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristyfccdab92009-11-30 16:43:57 +00001462 {
1463 k=normal_kernel;
1464 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001465 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001466 {
cristybb503372010-05-27 20:51:26 +00001467 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001468 {
cristy4c08aed2011-07-01 19:47:50 +00001469 pixel.alpha+=(*k)*GetPixelAlpha(image,kernel_pixels+u*
1470 GetPixelChannels(image));
cristyfccdab92009-11-30 16:43:57 +00001471 k++;
1472 }
cristy4c08aed2011-07-01 19:47:50 +00001473 kernel_pixels+=(image->columns+width)*
1474 GetPixelChannels(image);
cristyfccdab92009-11-30 16:43:57 +00001475 }
cristy4c08aed2011-07-01 19:47:50 +00001476 SetPixelAlpha(convolve_image,ClampToQuantum(pixel.alpha),q);
cristyfccdab92009-11-30 16:43:57 +00001477 }
1478 }
1479 else
1480 {
1481 MagickRealType
1482 alpha,
1483 gamma;
1484
1485 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00001486 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001487 {
cristybb503372010-05-27 20:51:26 +00001488 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001489 {
cristy4c08aed2011-07-01 19:47:50 +00001490 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,
1491 kernel_pixels+u*GetPixelChannels(image)));
1492 pixel.red+=(*k)*alpha*GetPixelRed(image,kernel_pixels+u*
1493 GetPixelChannels(image));
1494 pixel.green+=(*k)*alpha*GetPixelGreen(image,kernel_pixels+u*
1495 GetPixelChannels(image));
1496 pixel.blue+=(*k)*alpha*GetPixelBlue(image,kernel_pixels+u*
1497 GetPixelChannels(image));
1498 if (image->colorspace == CMYKColorspace)
1499 pixel.black+=(*k)*alpha*GetPixelBlack(image,kernel_pixels+u*
1500 GetPixelChannels(image));
cristyfccdab92009-11-30 16:43:57 +00001501 gamma+=(*k)*alpha;
1502 k++;
1503 }
cristy4c08aed2011-07-01 19:47:50 +00001504 kernel_pixels+=(image->columns+width)*GetPixelChannels(image);
cristyfccdab92009-11-30 16:43:57 +00001505 }
1506 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy2b9582a2011-07-04 17:38:56 +00001507 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001508 SetPixelRed(convolve_image,ClampToQuantum(gamma*pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00001509 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001510 SetPixelGreen(convolve_image,ClampToQuantum(gamma*pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00001511 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001512 SetPixelBlue(convolve_image,ClampToQuantum(gamma*pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00001513 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristyfccdab92009-11-30 16:43:57 +00001514 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00001515 SetPixelBlack(convolve_image,ClampToQuantum(gamma*pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00001516 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristyfccdab92009-11-30 16:43:57 +00001517 {
cristyfccdab92009-11-30 16:43:57 +00001518 k=normal_kernel;
1519 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001520 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001521 {
cristybb503372010-05-27 20:51:26 +00001522 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001523 {
cristy4c08aed2011-07-01 19:47:50 +00001524 pixel.alpha+=(*k)*GetPixelAlpha(image,kernel_pixels+u);
cristyfccdab92009-11-30 16:43:57 +00001525 k++;
1526 }
cristy4c08aed2011-07-01 19:47:50 +00001527 kernel_pixels+=(image->columns+width)*
1528 GetPixelChannels(image);
cristyfccdab92009-11-30 16:43:57 +00001529 }
cristy4c08aed2011-07-01 19:47:50 +00001530 SetPixelAlpha(convolve_image,ClampToQuantum(pixel.alpha),q);
cristyfccdab92009-11-30 16:43:57 +00001531 }
1532 }
cristy4c08aed2011-07-01 19:47:50 +00001533 p+=GetPixelChannels(image);
1534 q+=GetPixelChannels(convolve_image);
cristyfccdab92009-11-30 16:43:57 +00001535 }
1536 sync=SyncCacheViewAuthenticPixels(convolve_view,exception);
1537 if (sync == MagickFalse)
1538 status=MagickFalse;
1539 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1540 {
1541 MagickBooleanType
1542 proceed;
1543
1544#if defined(MAGICKCORE_OPENMP_SUPPORT)
1545 #pragma omp critical (MagickCore_ConvolveImageChannel)
1546#endif
1547 proceed=SetImageProgress(image,ConvolveImageTag,progress++,image->rows);
1548 if (proceed == MagickFalse)
1549 status=MagickFalse;
1550 }
1551 }
1552 convolve_image->type=image->type;
1553 convolve_view=DestroyCacheView(convolve_view);
1554 image_view=DestroyCacheView(image_view);
1555 normal_kernel=(double *) RelinquishMagickMemory(normal_kernel);
1556 if (status == MagickFalse)
1557 convolve_image=DestroyImage(convolve_image);
1558 return(convolve_image);
1559}
1560
1561/*
1562%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1563% %
1564% %
1565% %
cristy3ed852e2009-09-05 21:47:34 +00001566% D e s p e c k l e I m a g e %
1567% %
1568% %
1569% %
1570%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1571%
1572% DespeckleImage() reduces the speckle noise in an image while perserving the
1573% edges of the original image.
1574%
1575% The format of the DespeckleImage method is:
1576%
1577% Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1578%
1579% A description of each parameter follows:
1580%
1581% o image: the image.
1582%
1583% o exception: return any errors or warnings in this structure.
1584%
1585*/
1586
cristybb503372010-05-27 20:51:26 +00001587static void Hull(const ssize_t x_offset,const ssize_t y_offset,
1588 const size_t columns,const size_t rows,Quantum *f,Quantum *g,
cristy3ed852e2009-09-05 21:47:34 +00001589 const int polarity)
1590{
cristy3ed852e2009-09-05 21:47:34 +00001591 MagickRealType
1592 v;
1593
cristy3ed852e2009-09-05 21:47:34 +00001594 register Quantum
1595 *p,
1596 *q,
1597 *r,
1598 *s;
1599
cristy117ff172010-08-15 21:35:32 +00001600 register ssize_t
1601 x;
1602
1603 ssize_t
1604 y;
1605
cristy3ed852e2009-09-05 21:47:34 +00001606 assert(f != (Quantum *) NULL);
1607 assert(g != (Quantum *) NULL);
1608 p=f+(columns+2);
1609 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001610 r=p+(y_offset*((ssize_t) columns+2)+x_offset);
1611 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001612 {
1613 p++;
1614 q++;
1615 r++;
1616 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001617 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001618 {
1619 v=(MagickRealType) (*p);
1620 if ((MagickRealType) *r >= (v+(MagickRealType) ScaleCharToQuantum(2)))
1621 v+=ScaleCharToQuantum(1);
1622 *q=(Quantum) v;
1623 p++;
1624 q++;
1625 r++;
1626 }
1627 else
cristybb503372010-05-27 20:51:26 +00001628 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001629 {
1630 v=(MagickRealType) (*p);
1631 if ((MagickRealType) *r <= (v-(MagickRealType) ScaleCharToQuantum(2)))
cristybb503372010-05-27 20:51:26 +00001632 v-=(ssize_t) ScaleCharToQuantum(1);
cristy3ed852e2009-09-05 21:47:34 +00001633 *q=(Quantum) v;
1634 p++;
1635 q++;
1636 r++;
1637 }
1638 p++;
1639 q++;
1640 r++;
1641 }
1642 p=f+(columns+2);
1643 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001644 r=q+(y_offset*((ssize_t) columns+2)+x_offset);
1645 s=q-(y_offset*((ssize_t) columns+2)+x_offset);
1646 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001647 {
1648 p++;
1649 q++;
1650 r++;
1651 s++;
1652 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001653 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001654 {
1655 v=(MagickRealType) (*q);
1656 if (((MagickRealType) *s >=
1657 (v+(MagickRealType) ScaleCharToQuantum(2))) &&
1658 ((MagickRealType) *r > v))
1659 v+=ScaleCharToQuantum(1);
1660 *p=(Quantum) v;
1661 p++;
1662 q++;
1663 r++;
1664 s++;
1665 }
1666 else
cristybb503372010-05-27 20:51:26 +00001667 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001668 {
1669 v=(MagickRealType) (*q);
1670 if (((MagickRealType) *s <=
1671 (v-(MagickRealType) ScaleCharToQuantum(2))) &&
1672 ((MagickRealType) *r < v))
1673 v-=(MagickRealType) ScaleCharToQuantum(1);
1674 *p=(Quantum) v;
1675 p++;
1676 q++;
1677 r++;
1678 s++;
1679 }
1680 p++;
1681 q++;
1682 r++;
1683 s++;
1684 }
1685}
1686
1687MagickExport Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1688{
1689#define DespeckleImageTag "Despeckle/Image"
1690
cristy2407fc22009-09-11 00:55:25 +00001691 CacheView
1692 *despeckle_view,
1693 *image_view;
1694
cristy3ed852e2009-09-05 21:47:34 +00001695 Image
1696 *despeckle_image;
1697
cristy3ed852e2009-09-05 21:47:34 +00001698 MagickBooleanType
1699 status;
1700
cristya58c3172011-02-19 19:23:11 +00001701 register ssize_t
1702 i;
1703
cristy3ed852e2009-09-05 21:47:34 +00001704 Quantum
cristy65b9f392011-02-22 14:22:54 +00001705 *restrict buffers,
1706 *restrict pixels;
cristy3ed852e2009-09-05 21:47:34 +00001707
1708 size_t
cristya58c3172011-02-19 19:23:11 +00001709 length,
1710 number_channels;
cristy117ff172010-08-15 21:35:32 +00001711
cristybb503372010-05-27 20:51:26 +00001712 static const ssize_t
cristy691a29e2009-09-11 00:44:10 +00001713 X[4] = {0, 1, 1,-1},
1714 Y[4] = {1, 0, 1, 1};
cristy3ed852e2009-09-05 21:47:34 +00001715
cristy3ed852e2009-09-05 21:47:34 +00001716 /*
1717 Allocate despeckled image.
1718 */
1719 assert(image != (const Image *) NULL);
1720 assert(image->signature == MagickSignature);
1721 if (image->debug != MagickFalse)
1722 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1723 assert(exception != (ExceptionInfo *) NULL);
1724 assert(exception->signature == MagickSignature);
1725 despeckle_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1726 exception);
1727 if (despeckle_image == (Image *) NULL)
1728 return((Image *) NULL);
1729 if (SetImageStorageClass(despeckle_image,DirectClass) == MagickFalse)
1730 {
1731 InheritException(exception,&despeckle_image->exception);
1732 despeckle_image=DestroyImage(despeckle_image);
1733 return((Image *) NULL);
1734 }
1735 /*
1736 Allocate image buffers.
1737 */
1738 length=(size_t) ((image->columns+2)*(image->rows+2));
cristy65b9f392011-02-22 14:22:54 +00001739 pixels=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1740 buffers=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1741 if ((pixels == (Quantum *) NULL) || (buffers == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001742 {
cristy65b9f392011-02-22 14:22:54 +00001743 if (buffers != (Quantum *) NULL)
1744 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1745 if (pixels != (Quantum *) NULL)
1746 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001747 despeckle_image=DestroyImage(despeckle_image);
1748 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1749 }
1750 /*
1751 Reduce speckle in the image.
1752 */
1753 status=MagickTrue;
cristy109695a2011-02-19 19:38:14 +00001754 number_channels=(size_t) (image->colorspace == CMYKColorspace ? 5 : 4);
cristy3ed852e2009-09-05 21:47:34 +00001755 image_view=AcquireCacheView(image);
1756 despeckle_view=AcquireCacheView(despeckle_image);
cristy8df3d002011-02-19 19:40:59 +00001757 for (i=0; i < (ssize_t) number_channels; i++)
cristy3ed852e2009-09-05 21:47:34 +00001758 {
cristy3ed852e2009-09-05 21:47:34 +00001759 register Quantum
1760 *buffer,
1761 *pixel;
1762
cristyc1488b52011-02-19 18:54:15 +00001763 register ssize_t
cristya58c3172011-02-19 19:23:11 +00001764 k,
cristyc1488b52011-02-19 18:54:15 +00001765 x;
1766
cristy117ff172010-08-15 21:35:32 +00001767 ssize_t
1768 j,
1769 y;
1770
cristy3ed852e2009-09-05 21:47:34 +00001771 if (status == MagickFalse)
1772 continue;
cristy65b9f392011-02-22 14:22:54 +00001773 pixel=pixels;
cristy3ed852e2009-09-05 21:47:34 +00001774 (void) ResetMagickMemory(pixel,0,length*sizeof(*pixel));
cristy65b9f392011-02-22 14:22:54 +00001775 buffer=buffers;
cristybb503372010-05-27 20:51:26 +00001776 j=(ssize_t) image->columns+2;
1777 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001778 {
cristy4c08aed2011-07-01 19:47:50 +00001779 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001780 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001781
1782 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001783 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001784 break;
1785 j++;
cristybb503372010-05-27 20:51:26 +00001786 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001787 {
cristya58c3172011-02-19 19:23:11 +00001788 switch (i)
cristy3ed852e2009-09-05 21:47:34 +00001789 {
cristy4c08aed2011-07-01 19:47:50 +00001790 case 0: pixel[j]=GetPixelRed(image,p); break;
1791 case 1: pixel[j]=GetPixelGreen(image,p); break;
1792 case 2: pixel[j]=GetPixelBlue(image,p); break;
1793 case 3: pixel[j]=GetPixelAlpha(image,p); break;
1794 case 4: pixel[j]=GetPixelBlack(image,p); break;
cristy3ed852e2009-09-05 21:47:34 +00001795 default: break;
1796 }
cristy4c08aed2011-07-01 19:47:50 +00001797 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001798 j++;
1799 }
1800 j++;
1801 }
cristy3ed852e2009-09-05 21:47:34 +00001802 (void) ResetMagickMemory(buffer,0,length*sizeof(*buffer));
cristya58c3172011-02-19 19:23:11 +00001803 for (k=0; k < 4; k++)
cristy3ed852e2009-09-05 21:47:34 +00001804 {
cristya58c3172011-02-19 19:23:11 +00001805 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,1);
1806 Hull(-X[k],-Y[k],image->columns,image->rows,pixel,buffer,1);
1807 Hull(-X[k],-Y[k],image->columns,image->rows,pixel,buffer,-1);
1808 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,-1);
cristy3ed852e2009-09-05 21:47:34 +00001809 }
cristybb503372010-05-27 20:51:26 +00001810 j=(ssize_t) image->columns+2;
1811 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001812 {
1813 MagickBooleanType
1814 sync;
1815
cristy4c08aed2011-07-01 19:47:50 +00001816 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001817 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001818
1819 q=GetCacheViewAuthenticPixels(despeckle_view,0,y,despeckle_image->columns,
1820 1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001821 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001822 break;
1823 j++;
cristybb503372010-05-27 20:51:26 +00001824 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001825 {
cristya58c3172011-02-19 19:23:11 +00001826 switch (i)
cristy3ed852e2009-09-05 21:47:34 +00001827 {
cristy4c08aed2011-07-01 19:47:50 +00001828 case 0: SetPixelRed(despeckle_image,pixel[j],q); break;
1829 case 1: SetPixelGreen(despeckle_image,pixel[j],q); break;
1830 case 2: SetPixelBlue(despeckle_image,pixel[j],q); break;
1831 case 3: SetPixelAlpha(despeckle_image,pixel[j],q); break;
1832 case 4: SetPixelBlack(despeckle_image,pixel[j],q); break;
cristy3ed852e2009-09-05 21:47:34 +00001833 default: break;
1834 }
cristy4c08aed2011-07-01 19:47:50 +00001835 q+=GetPixelChannels(despeckle_image);
cristy3ed852e2009-09-05 21:47:34 +00001836 j++;
1837 }
1838 sync=SyncCacheViewAuthenticPixels(despeckle_view,exception);
1839 if (sync == MagickFalse)
1840 {
1841 status=MagickFalse;
1842 break;
1843 }
1844 j++;
1845 }
1846 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1847 {
1848 MagickBooleanType
1849 proceed;
1850
cristya58c3172011-02-19 19:23:11 +00001851 proceed=SetImageProgress(image,DespeckleImageTag,(MagickOffsetType) i,
1852 number_channels);
cristy3ed852e2009-09-05 21:47:34 +00001853 if (proceed == MagickFalse)
1854 status=MagickFalse;
1855 }
1856 }
1857 despeckle_view=DestroyCacheView(despeckle_view);
1858 image_view=DestroyCacheView(image_view);
cristy65b9f392011-02-22 14:22:54 +00001859 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1860 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001861 despeckle_image->type=image->type;
1862 if (status == MagickFalse)
1863 despeckle_image=DestroyImage(despeckle_image);
1864 return(despeckle_image);
1865}
1866
1867/*
1868%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1869% %
1870% %
1871% %
1872% E d g e I m a g e %
1873% %
1874% %
1875% %
1876%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1877%
1878% EdgeImage() finds edges in an image. Radius defines the radius of the
1879% convolution filter. Use a radius of 0 and EdgeImage() selects a suitable
1880% radius for you.
1881%
1882% The format of the EdgeImage method is:
1883%
1884% Image *EdgeImage(const Image *image,const double radius,
1885% ExceptionInfo *exception)
1886%
1887% A description of each parameter follows:
1888%
1889% o image: the image.
1890%
1891% o radius: the radius of the pixel neighborhood.
1892%
1893% o exception: return any errors or warnings in this structure.
1894%
1895*/
1896MagickExport Image *EdgeImage(const Image *image,const double radius,
1897 ExceptionInfo *exception)
1898{
1899 Image
1900 *edge_image;
1901
1902 double
1903 *kernel;
1904
cristybb503372010-05-27 20:51:26 +00001905 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001906 i;
1907
cristybb503372010-05-27 20:51:26 +00001908 size_t
cristy3ed852e2009-09-05 21:47:34 +00001909 width;
1910
1911 assert(image != (const Image *) NULL);
1912 assert(image->signature == MagickSignature);
1913 if (image->debug != MagickFalse)
1914 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1915 assert(exception != (ExceptionInfo *) NULL);
1916 assert(exception->signature == MagickSignature);
1917 width=GetOptimalKernelWidth1D(radius,0.5);
1918 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
1919 if (kernel == (double *) NULL)
1920 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00001921 for (i=0; i < (ssize_t) (width*width); i++)
cristy3ed852e2009-09-05 21:47:34 +00001922 kernel[i]=(-1.0);
1923 kernel[i/2]=(double) (width*width-1.0);
1924 edge_image=ConvolveImage(image,width,kernel,exception);
1925 kernel=(double *) RelinquishMagickMemory(kernel);
1926 return(edge_image);
1927}
1928
1929/*
1930%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1931% %
1932% %
1933% %
1934% E m b o s s I m a g e %
1935% %
1936% %
1937% %
1938%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1939%
1940% EmbossImage() returns a grayscale image with a three-dimensional effect.
1941% We convolve the image with a Gaussian operator of the given radius and
1942% standard deviation (sigma). For reasonable results, radius should be
1943% larger than sigma. Use a radius of 0 and Emboss() selects a suitable
1944% radius for you.
1945%
1946% The format of the EmbossImage method is:
1947%
1948% Image *EmbossImage(const Image *image,const double radius,
1949% const double sigma,ExceptionInfo *exception)
1950%
1951% A description of each parameter follows:
1952%
1953% o image: the image.
1954%
1955% o radius: the radius of the pixel neighborhood.
1956%
1957% o sigma: the standard deviation of the Gaussian, in pixels.
1958%
1959% o exception: return any errors or warnings in this structure.
1960%
1961*/
1962MagickExport Image *EmbossImage(const Image *image,const double radius,
1963 const double sigma,ExceptionInfo *exception)
1964{
1965 double
1966 *kernel;
1967
1968 Image
1969 *emboss_image;
1970
cristybb503372010-05-27 20:51:26 +00001971 register ssize_t
cristy47e00502009-12-17 19:19:57 +00001972 i;
1973
cristybb503372010-05-27 20:51:26 +00001974 size_t
cristy3ed852e2009-09-05 21:47:34 +00001975 width;
1976
cristy117ff172010-08-15 21:35:32 +00001977 ssize_t
1978 j,
1979 k,
1980 u,
1981 v;
1982
cristy3ed852e2009-09-05 21:47:34 +00001983 assert(image != (Image *) NULL);
1984 assert(image->signature == MagickSignature);
1985 if (image->debug != MagickFalse)
1986 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1987 assert(exception != (ExceptionInfo *) NULL);
1988 assert(exception->signature == MagickSignature);
1989 width=GetOptimalKernelWidth2D(radius,sigma);
1990 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
1991 if (kernel == (double *) NULL)
1992 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00001993 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +00001994 k=j;
1995 i=0;
1996 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00001997 {
cristy47e00502009-12-17 19:19:57 +00001998 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00001999 {
cristy4205a3c2010-09-12 20:19:59 +00002000 kernel[i]=(double) (((u < 0) || (v < 0) ? -8.0 : 8.0)*
cristy47e00502009-12-17 19:19:57 +00002001 exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
cristy4205a3c2010-09-12 20:19:59 +00002002 (2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +00002003 if (u != k)
cristy3ed852e2009-09-05 21:47:34 +00002004 kernel[i]=0.0;
2005 i++;
2006 }
cristy47e00502009-12-17 19:19:57 +00002007 k--;
cristy3ed852e2009-09-05 21:47:34 +00002008 }
2009 emboss_image=ConvolveImage(image,width,kernel,exception);
2010 if (emboss_image != (Image *) NULL)
2011 (void) EqualizeImage(emboss_image);
2012 kernel=(double *) RelinquishMagickMemory(kernel);
2013 return(emboss_image);
2014}
2015
2016/*
2017%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2018% %
2019% %
2020% %
cristy56a9e512010-01-06 18:18:55 +00002021% F i l t e r I m a g e %
2022% %
2023% %
2024% %
2025%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2026%
2027% FilterImage() applies a custom convolution kernel to the image.
2028%
2029% The format of the FilterImage method is:
2030%
cristy2be15382010-01-21 02:38:03 +00002031% Image *FilterImage(const Image *image,const KernelInfo *kernel,
cristy56a9e512010-01-06 18:18:55 +00002032% ExceptionInfo *exception)
2033% Image *FilterImageChannel(const Image *image,const ChannelType channel,
cristy2be15382010-01-21 02:38:03 +00002034% const KernelInfo *kernel,ExceptionInfo *exception)
cristy56a9e512010-01-06 18:18:55 +00002035%
2036% A description of each parameter follows:
2037%
2038% o image: the image.
2039%
2040% o channel: the channel type.
2041%
2042% o kernel: the filtering kernel.
2043%
2044% o exception: return any errors or warnings in this structure.
2045%
2046*/
2047
cristy2be15382010-01-21 02:38:03 +00002048MagickExport Image *FilterImage(const Image *image,const KernelInfo *kernel,
cristy56a9e512010-01-06 18:18:55 +00002049 ExceptionInfo *exception)
2050{
2051 Image
2052 *filter_image;
2053
2054 filter_image=FilterImageChannel(image,DefaultChannels,kernel,exception);
2055 return(filter_image);
2056}
2057
2058MagickExport Image *FilterImageChannel(const Image *image,
cristy2be15382010-01-21 02:38:03 +00002059 const ChannelType channel,const KernelInfo *kernel,ExceptionInfo *exception)
cristy56a9e512010-01-06 18:18:55 +00002060{
2061#define FilterImageTag "Filter/Image"
2062
2063 CacheView
2064 *filter_view,
2065 *image_view;
2066
cristy56a9e512010-01-06 18:18:55 +00002067 Image
2068 *filter_image;
2069
cristy56a9e512010-01-06 18:18:55 +00002070 MagickBooleanType
2071 status;
2072
cristybb503372010-05-27 20:51:26 +00002073 MagickOffsetType
2074 progress;
2075
cristy4c08aed2011-07-01 19:47:50 +00002076 PixelInfo
cristy56a9e512010-01-06 18:18:55 +00002077 bias;
2078
cristybb503372010-05-27 20:51:26 +00002079 ssize_t
2080 y;
2081
cristy56a9e512010-01-06 18:18:55 +00002082 /*
2083 Initialize filter image attributes.
2084 */
2085 assert(image != (Image *) NULL);
2086 assert(image->signature == MagickSignature);
2087 if (image->debug != MagickFalse)
2088 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2089 assert(exception != (ExceptionInfo *) NULL);
2090 assert(exception->signature == MagickSignature);
2091 if ((kernel->width % 2) == 0)
2092 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
2093 filter_image=CloneImage(image,0,0,MagickTrue,exception);
2094 if (filter_image == (Image *) NULL)
2095 return((Image *) NULL);
2096 if (SetImageStorageClass(filter_image,DirectClass) == MagickFalse)
2097 {
2098 InheritException(exception,&filter_image->exception);
2099 filter_image=DestroyImage(filter_image);
2100 return((Image *) NULL);
2101 }
2102 if (image->debug != MagickFalse)
2103 {
2104 char
2105 format[MaxTextExtent],
2106 *message;
2107
cristy117ff172010-08-15 21:35:32 +00002108 register const double
2109 *k;
2110
cristybb503372010-05-27 20:51:26 +00002111 ssize_t
cristy56a9e512010-01-06 18:18:55 +00002112 u,
2113 v;
2114
cristy56a9e512010-01-06 18:18:55 +00002115 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00002116 " FilterImage with %.20gx%.20g kernel:",(double) kernel->width,(double)
2117 kernel->height);
cristy56a9e512010-01-06 18:18:55 +00002118 message=AcquireString("");
2119 k=kernel->values;
cristybb503372010-05-27 20:51:26 +00002120 for (v=0; v < (ssize_t) kernel->height; v++)
cristy56a9e512010-01-06 18:18:55 +00002121 {
2122 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00002123 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy56a9e512010-01-06 18:18:55 +00002124 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00002125 for (u=0; u < (ssize_t) kernel->width; u++)
cristy56a9e512010-01-06 18:18:55 +00002126 {
cristyb51dff52011-05-19 16:55:47 +00002127 (void) FormatLocaleString(format,MaxTextExtent,"%g ",*k++);
cristy56a9e512010-01-06 18:18:55 +00002128 (void) ConcatenateString(&message,format);
2129 }
2130 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
2131 }
2132 message=DestroyString(message);
2133 }
cristy36826ab2010-03-06 01:29:30 +00002134 status=AccelerateConvolveImage(image,kernel,filter_image,exception);
cristyd43a46b2010-01-21 02:13:41 +00002135 if (status == MagickTrue)
2136 return(filter_image);
cristy56a9e512010-01-06 18:18:55 +00002137 /*
2138 Filter image.
2139 */
2140 status=MagickTrue;
2141 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00002142 GetPixelInfo(image,&bias);
2143 SetPixelInfoBias(image,&bias);
cristy56a9e512010-01-06 18:18:55 +00002144 image_view=AcquireCacheView(image);
2145 filter_view=AcquireCacheView(filter_image);
2146#if defined(MAGICKCORE_OPENMP_SUPPORT)
2147 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2148#endif
cristybb503372010-05-27 20:51:26 +00002149 for (y=0; y < (ssize_t) image->rows; y++)
cristy56a9e512010-01-06 18:18:55 +00002150 {
2151 MagickBooleanType
2152 sync;
2153
cristy4c08aed2011-07-01 19:47:50 +00002154 register const Quantum
cristy56a9e512010-01-06 18:18:55 +00002155 *restrict p;
2156
cristy4c08aed2011-07-01 19:47:50 +00002157 register Quantum
cristy56a9e512010-01-06 18:18:55 +00002158 *restrict q;
2159
cristy117ff172010-08-15 21:35:32 +00002160 register ssize_t
2161 x;
2162
cristy56a9e512010-01-06 18:18:55 +00002163 if (status == MagickFalse)
2164 continue;
cristybb503372010-05-27 20:51:26 +00002165 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) kernel->width/2L),
cristy117ff172010-08-15 21:35:32 +00002166 y-(ssize_t) (kernel->height/2L),image->columns+kernel->width,
2167 kernel->height,exception);
cristy56a9e512010-01-06 18:18:55 +00002168 q=GetCacheViewAuthenticPixels(filter_view,0,y,filter_image->columns,1,
2169 exception);
cristy4c08aed2011-07-01 19:47:50 +00002170 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy56a9e512010-01-06 18:18:55 +00002171 {
2172 status=MagickFalse;
2173 continue;
2174 }
cristybb503372010-05-27 20:51:26 +00002175 for (x=0; x < (ssize_t) image->columns; x++)
cristy56a9e512010-01-06 18:18:55 +00002176 {
cristy4c08aed2011-07-01 19:47:50 +00002177 PixelInfo
cristy56a9e512010-01-06 18:18:55 +00002178 pixel;
2179
2180 register const double
2181 *restrict k;
2182
cristy4c08aed2011-07-01 19:47:50 +00002183 register const Quantum
cristy56a9e512010-01-06 18:18:55 +00002184 *restrict kernel_pixels;
2185
cristybb503372010-05-27 20:51:26 +00002186 register ssize_t
cristy56a9e512010-01-06 18:18:55 +00002187 u;
2188
cristy117ff172010-08-15 21:35:32 +00002189 ssize_t
2190 v;
2191
cristy56a9e512010-01-06 18:18:55 +00002192 pixel=bias;
cristy36826ab2010-03-06 01:29:30 +00002193 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002194 kernel_pixels=p;
cristy2b9582a2011-07-04 17:38:56 +00002195 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) == 0) || (image->matte == MagickFalse))
cristy56a9e512010-01-06 18:18:55 +00002196 {
cristybb503372010-05-27 20:51:26 +00002197 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002198 {
cristybb503372010-05-27 20:51:26 +00002199 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002200 {
cristy4c08aed2011-07-01 19:47:50 +00002201 pixel.red+=(*k)*GetPixelRed(image,kernel_pixels+u*
2202 GetPixelChannels(image));
2203 pixel.green+=(*k)*GetPixelGreen(image,kernel_pixels+u*
2204 GetPixelChannels(image));
2205 pixel.blue+=(*k)*GetPixelBlue(image,kernel_pixels+u*
2206 GetPixelChannels(image));
2207 if (image->colorspace == CMYKColorspace)
2208 pixel.black+=(*k)*GetPixelBlack(image,kernel_pixels+u*
2209 GetPixelChannels(image));
cristy56a9e512010-01-06 18:18:55 +00002210 k++;
2211 }
cristy4c08aed2011-07-01 19:47:50 +00002212 kernel_pixels+=(image->columns+kernel->width)*
2213 GetPixelChannels(image);
cristy56a9e512010-01-06 18:18:55 +00002214 }
cristy2b9582a2011-07-04 17:38:56 +00002215 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002216 SetPixelRed(filter_image,ClampToQuantum(pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00002217 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002218 SetPixelGreen(filter_image,ClampToQuantum(pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00002219 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002220 SetPixelBlue(filter_image,ClampToQuantum(pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00002221 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002222 (image->colorspace == CMYKColorspace))
2223 SetPixelBlack(filter_image,ClampToQuantum(pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00002224 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy56a9e512010-01-06 18:18:55 +00002225 {
cristy36826ab2010-03-06 01:29:30 +00002226 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002227 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00002228 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002229 {
cristybb503372010-05-27 20:51:26 +00002230 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002231 {
cristy4c08aed2011-07-01 19:47:50 +00002232 pixel.alpha+=(*k)*GetPixelRed(image,kernel_pixels+u*
2233 GetPixelChannels(image));
cristy56a9e512010-01-06 18:18:55 +00002234 k++;
2235 }
cristy4c08aed2011-07-01 19:47:50 +00002236 kernel_pixels+=(image->columns+kernel->width)*
2237 GetPixelChannels(image);
cristy56a9e512010-01-06 18:18:55 +00002238 }
cristy4c08aed2011-07-01 19:47:50 +00002239 SetPixelAlpha(filter_image,ClampToQuantum(pixel.alpha),q);
cristy56a9e512010-01-06 18:18:55 +00002240 }
2241 }
2242 else
2243 {
2244 MagickRealType
2245 alpha,
2246 gamma;
2247
2248 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00002249 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002250 {
cristybb503372010-05-27 20:51:26 +00002251 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002252 {
cristy4c08aed2011-07-01 19:47:50 +00002253 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,
2254 kernel_pixels+u*GetPixelChannels(image)));
2255 pixel.red+=(*k)*alpha*GetPixelRed(image,kernel_pixels+u*
2256 GetPixelChannels(image));
2257 pixel.green+=(*k)*alpha*GetPixelGreen(image,kernel_pixels+u*
2258 GetPixelChannels(image));
2259 pixel.blue+=(*k)*alpha*GetPixelBlue(image,kernel_pixels+u*
2260 GetPixelChannels(image));
2261 if (image->colorspace == CMYKColorspace)
2262 pixel.black+=(*k)*alpha*GetPixelBlack(image,kernel_pixels+u*
2263 GetPixelChannels(image));
cristy56a9e512010-01-06 18:18:55 +00002264 gamma+=(*k)*alpha;
2265 k++;
2266 }
cristy4c08aed2011-07-01 19:47:50 +00002267 kernel_pixels+=(image->columns+kernel->width);
2268 GetPixelChannels(image);
cristy56a9e512010-01-06 18:18:55 +00002269 }
2270 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy2b9582a2011-07-04 17:38:56 +00002271 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002272 SetPixelRed(filter_image,ClampToQuantum(gamma*pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00002273 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002274 SetPixelGreen(filter_image,ClampToQuantum(gamma*pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00002275 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002276 SetPixelBlue(filter_image,ClampToQuantum(gamma*pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00002277 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy56a9e512010-01-06 18:18:55 +00002278 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002279 SetPixelBlack(filter_image,ClampToQuantum(gamma*pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00002280 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy56a9e512010-01-06 18:18:55 +00002281 {
cristy36826ab2010-03-06 01:29:30 +00002282 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002283 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00002284 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002285 {
cristybb503372010-05-27 20:51:26 +00002286 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002287 {
cristy4c08aed2011-07-01 19:47:50 +00002288 pixel.alpha+=(*k)*GetPixelAlpha(image,kernel_pixels+u*
2289 GetPixelChannels(image));
cristy56a9e512010-01-06 18:18:55 +00002290 k++;
2291 }
cristy4c08aed2011-07-01 19:47:50 +00002292 kernel_pixels+=(image->columns+kernel->width)*
2293 GetPixelChannels(image);
cristy56a9e512010-01-06 18:18:55 +00002294 }
cristy4c08aed2011-07-01 19:47:50 +00002295 SetPixelAlpha(filter_image,ClampToQuantum(pixel.alpha),q);
cristy56a9e512010-01-06 18:18:55 +00002296 }
2297 }
cristy4c08aed2011-07-01 19:47:50 +00002298 p+=GetPixelChannels(image);
2299 q+=GetPixelChannels(filter_image);
cristy56a9e512010-01-06 18:18:55 +00002300 }
2301 sync=SyncCacheViewAuthenticPixels(filter_view,exception);
2302 if (sync == MagickFalse)
2303 status=MagickFalse;
2304 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2305 {
2306 MagickBooleanType
2307 proceed;
2308
2309#if defined(MAGICKCORE_OPENMP_SUPPORT)
2310 #pragma omp critical (MagickCore_FilterImageChannel)
2311#endif
2312 proceed=SetImageProgress(image,FilterImageTag,progress++,image->rows);
2313 if (proceed == MagickFalse)
2314 status=MagickFalse;
2315 }
2316 }
2317 filter_image->type=image->type;
2318 filter_view=DestroyCacheView(filter_view);
2319 image_view=DestroyCacheView(image_view);
cristy56a9e512010-01-06 18:18:55 +00002320 if (status == MagickFalse)
2321 filter_image=DestroyImage(filter_image);
2322 return(filter_image);
2323}
2324
2325/*
2326%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2327% %
2328% %
2329% %
cristy3ed852e2009-09-05 21:47:34 +00002330% G a u s s i a n B l u r I m a g e %
2331% %
2332% %
2333% %
2334%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2335%
2336% GaussianBlurImage() blurs an image. We convolve the image with a
2337% Gaussian operator of the given radius and standard deviation (sigma).
2338% For reasonable results, the radius should be larger than sigma. Use a
2339% radius of 0 and GaussianBlurImage() selects a suitable radius for you
2340%
2341% The format of the GaussianBlurImage method is:
2342%
2343% Image *GaussianBlurImage(const Image *image,onst double radius,
2344% const double sigma,ExceptionInfo *exception)
2345% Image *GaussianBlurImageChannel(const Image *image,
2346% const ChannelType channel,const double radius,const double sigma,
2347% ExceptionInfo *exception)
2348%
2349% A description of each parameter follows:
2350%
2351% o image: the image.
2352%
2353% o channel: the channel type.
2354%
2355% o radius: the radius of the Gaussian, in pixels, not counting the center
2356% pixel.
2357%
2358% o sigma: the standard deviation of the Gaussian, in pixels.
2359%
2360% o exception: return any errors or warnings in this structure.
2361%
2362*/
2363
2364MagickExport Image *GaussianBlurImage(const Image *image,const double radius,
2365 const double sigma,ExceptionInfo *exception)
2366{
2367 Image
2368 *blur_image;
2369
2370 blur_image=GaussianBlurImageChannel(image,DefaultChannels,radius,sigma,
2371 exception);
2372 return(blur_image);
2373}
2374
2375MagickExport Image *GaussianBlurImageChannel(const Image *image,
2376 const ChannelType channel,const double radius,const double sigma,
2377 ExceptionInfo *exception)
2378{
2379 double
2380 *kernel;
2381
2382 Image
2383 *blur_image;
2384
cristybb503372010-05-27 20:51:26 +00002385 register ssize_t
cristy47e00502009-12-17 19:19:57 +00002386 i;
2387
cristybb503372010-05-27 20:51:26 +00002388 size_t
cristy3ed852e2009-09-05 21:47:34 +00002389 width;
2390
cristy117ff172010-08-15 21:35:32 +00002391 ssize_t
2392 j,
2393 u,
2394 v;
2395
cristy3ed852e2009-09-05 21:47:34 +00002396 assert(image != (const Image *) NULL);
2397 assert(image->signature == MagickSignature);
2398 if (image->debug != MagickFalse)
2399 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2400 assert(exception != (ExceptionInfo *) NULL);
2401 assert(exception->signature == MagickSignature);
2402 width=GetOptimalKernelWidth2D(radius,sigma);
2403 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2404 if (kernel == (double *) NULL)
2405 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00002406 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00002407 i=0;
cristy47e00502009-12-17 19:19:57 +00002408 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002409 {
cristy47e00502009-12-17 19:19:57 +00002410 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00002411 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
2412 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00002413 }
2414 blur_image=ConvolveImageChannel(image,channel,width,kernel,exception);
2415 kernel=(double *) RelinquishMagickMemory(kernel);
2416 return(blur_image);
2417}
2418
2419/*
2420%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2421% %
2422% %
2423% %
cristy3ed852e2009-09-05 21:47:34 +00002424% M o t i o n B l u r I m a g e %
2425% %
2426% %
2427% %
2428%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2429%
2430% MotionBlurImage() simulates motion blur. We convolve the image with a
2431% Gaussian operator of the given radius and standard deviation (sigma).
2432% For reasonable results, radius should be larger than sigma. Use a
2433% radius of 0 and MotionBlurImage() selects a suitable radius for you.
2434% Angle gives the angle of the blurring motion.
2435%
2436% Andrew Protano contributed this effect.
2437%
2438% The format of the MotionBlurImage method is:
2439%
2440% Image *MotionBlurImage(const Image *image,const double radius,
2441% const double sigma,const double angle,ExceptionInfo *exception)
2442% Image *MotionBlurImageChannel(const Image *image,const ChannelType channel,
2443% const double radius,const double sigma,const double angle,
2444% ExceptionInfo *exception)
2445%
2446% A description of each parameter follows:
2447%
2448% o image: the image.
2449%
2450% o channel: the channel type.
2451%
2452% o radius: the radius of the Gaussian, in pixels, not counting the center
2453% o radius: the radius of the Gaussian, in pixels, not counting
2454% the center pixel.
2455%
2456% o sigma: the standard deviation of the Gaussian, in pixels.
2457%
cristycee97112010-05-28 00:44:52 +00002458% o angle: Apply the effect along this angle.
cristy3ed852e2009-09-05 21:47:34 +00002459%
2460% o exception: return any errors or warnings in this structure.
2461%
2462*/
2463
cristybb503372010-05-27 20:51:26 +00002464static double *GetMotionBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +00002465{
cristy3ed852e2009-09-05 21:47:34 +00002466 double
cristy47e00502009-12-17 19:19:57 +00002467 *kernel,
cristy3ed852e2009-09-05 21:47:34 +00002468 normalize;
2469
cristybb503372010-05-27 20:51:26 +00002470 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002471 i;
2472
2473 /*
cristy47e00502009-12-17 19:19:57 +00002474 Generate a 1-D convolution kernel.
cristy3ed852e2009-09-05 21:47:34 +00002475 */
2476 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
2477 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
2478 if (kernel == (double *) NULL)
2479 return(kernel);
cristy3ed852e2009-09-05 21:47:34 +00002480 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00002481 for (i=0; i < (ssize_t) width; i++)
cristy47e00502009-12-17 19:19:57 +00002482 {
cristy4205a3c2010-09-12 20:19:59 +00002483 kernel[i]=(double) (exp((-((double) i*i)/(double) (2.0*MagickSigma*
2484 MagickSigma)))/(MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00002485 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +00002486 }
cristybb503372010-05-27 20:51:26 +00002487 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002488 kernel[i]/=normalize;
2489 return(kernel);
2490}
2491
2492MagickExport Image *MotionBlurImage(const Image *image,const double radius,
2493 const double sigma,const double angle,ExceptionInfo *exception)
2494{
2495 Image
2496 *motion_blur;
2497
2498 motion_blur=MotionBlurImageChannel(image,DefaultChannels,radius,sigma,angle,
2499 exception);
2500 return(motion_blur);
2501}
2502
2503MagickExport Image *MotionBlurImageChannel(const Image *image,
2504 const ChannelType channel,const double radius,const double sigma,
2505 const double angle,ExceptionInfo *exception)
2506{
cristyc4c8d132010-01-07 01:58:38 +00002507 CacheView
2508 *blur_view,
2509 *image_view;
2510
cristy3ed852e2009-09-05 21:47:34 +00002511 double
2512 *kernel;
2513
2514 Image
2515 *blur_image;
2516
cristy3ed852e2009-09-05 21:47:34 +00002517 MagickBooleanType
2518 status;
2519
cristybb503372010-05-27 20:51:26 +00002520 MagickOffsetType
2521 progress;
2522
cristy4c08aed2011-07-01 19:47:50 +00002523 PixelInfo
cristyddd82202009-11-03 20:14:50 +00002524 bias;
cristy3ed852e2009-09-05 21:47:34 +00002525
2526 OffsetInfo
2527 *offset;
2528
2529 PointInfo
2530 point;
2531
cristybb503372010-05-27 20:51:26 +00002532 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002533 i;
2534
cristybb503372010-05-27 20:51:26 +00002535 size_t
cristy3ed852e2009-09-05 21:47:34 +00002536 width;
2537
cristybb503372010-05-27 20:51:26 +00002538 ssize_t
2539 y;
2540
cristy3ed852e2009-09-05 21:47:34 +00002541 assert(image != (Image *) NULL);
2542 assert(image->signature == MagickSignature);
2543 if (image->debug != MagickFalse)
2544 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2545 assert(exception != (ExceptionInfo *) NULL);
2546 width=GetOptimalKernelWidth1D(radius,sigma);
2547 kernel=GetMotionBlurKernel(width,sigma);
2548 if (kernel == (double *) NULL)
2549 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2550 offset=(OffsetInfo *) AcquireQuantumMemory(width,sizeof(*offset));
2551 if (offset == (OffsetInfo *) NULL)
2552 {
2553 kernel=(double *) RelinquishMagickMemory(kernel);
2554 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2555 }
2556 blur_image=CloneImage(image,0,0,MagickTrue,exception);
2557 if (blur_image == (Image *) NULL)
2558 {
2559 kernel=(double *) RelinquishMagickMemory(kernel);
2560 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2561 return((Image *) NULL);
2562 }
2563 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
2564 {
2565 kernel=(double *) RelinquishMagickMemory(kernel);
2566 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2567 InheritException(exception,&blur_image->exception);
2568 blur_image=DestroyImage(blur_image);
2569 return((Image *) NULL);
2570 }
2571 point.x=(double) width*sin(DegreesToRadians(angle));
2572 point.y=(double) width*cos(DegreesToRadians(angle));
cristybb503372010-05-27 20:51:26 +00002573 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002574 {
cristybb503372010-05-27 20:51:26 +00002575 offset[i].x=(ssize_t) ceil((double) (i*point.y)/hypot(point.x,point.y)-0.5);
2576 offset[i].y=(ssize_t) ceil((double) (i*point.x)/hypot(point.x,point.y)-0.5);
cristy3ed852e2009-09-05 21:47:34 +00002577 }
2578 /*
2579 Motion blur image.
2580 */
2581 status=MagickTrue;
2582 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00002583 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00002584 image_view=AcquireCacheView(image);
2585 blur_view=AcquireCacheView(blur_image);
cristyb557a152011-02-22 12:14:30 +00002586#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00002587 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00002588#endif
cristybb503372010-05-27 20:51:26 +00002589 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002590 {
cristy4c08aed2011-07-01 19:47:50 +00002591 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002592 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002593
cristy117ff172010-08-15 21:35:32 +00002594 register ssize_t
2595 x;
2596
cristy3ed852e2009-09-05 21:47:34 +00002597 if (status == MagickFalse)
2598 continue;
2599 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
2600 exception);
cristy4c08aed2011-07-01 19:47:50 +00002601 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002602 {
2603 status=MagickFalse;
2604 continue;
2605 }
cristybb503372010-05-27 20:51:26 +00002606 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002607 {
cristy4c08aed2011-07-01 19:47:50 +00002608 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002609 qixel;
2610
2611 PixelPacket
2612 pixel;
2613
2614 register double
cristyc47d1f82009-11-26 01:44:43 +00002615 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00002616
cristybb503372010-05-27 20:51:26 +00002617 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002618 i;
2619
cristy3ed852e2009-09-05 21:47:34 +00002620 k=kernel;
cristyddd82202009-11-03 20:14:50 +00002621 qixel=bias;
cristy2b9582a2011-07-04 17:38:56 +00002622 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00002623 {
cristybb503372010-05-27 20:51:26 +00002624 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002625 {
2626 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
2627 offset[i].y,&pixel,exception);
2628 qixel.red+=(*k)*pixel.red;
2629 qixel.green+=(*k)*pixel.green;
2630 qixel.blue+=(*k)*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00002631 qixel.alpha+=(*k)*pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00002632 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00002633 qixel.black+=(*k)*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00002634 k++;
2635 }
cristy2b9582a2011-07-04 17:38:56 +00002636 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002637 SetPixelRed(blur_image,
2638 ClampToQuantum(qixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00002639 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002640 SetPixelGreen(blur_image,
2641 ClampToQuantum(qixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00002642 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002643 SetPixelBlue(blur_image,
2644 ClampToQuantum(qixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00002645 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002646 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002647 SetPixelBlack(blur_image,
2648 ClampToQuantum(qixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00002649 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002650 SetPixelAlpha(blur_image,
2651 ClampToQuantum(qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00002652 }
2653 else
2654 {
2655 MagickRealType
2656 alpha,
2657 gamma;
2658
2659 alpha=0.0;
2660 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00002661 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002662 {
2663 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
2664 offset[i].y,&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00002665 alpha=(MagickRealType) (QuantumScale*pixel.alpha);
cristy3ed852e2009-09-05 21:47:34 +00002666 qixel.red+=(*k)*alpha*pixel.red;
2667 qixel.green+=(*k)*alpha*pixel.green;
2668 qixel.blue+=(*k)*alpha*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00002669 qixel.alpha+=(*k)*pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00002670 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00002671 qixel.black+=(*k)*alpha*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00002672 gamma+=(*k)*alpha;
2673 k++;
2674 }
2675 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy2b9582a2011-07-04 17:38:56 +00002676 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002677 SetPixelRed(blur_image,
2678 ClampToQuantum(gamma*qixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00002679 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002680 SetPixelGreen(blur_image,
2681 ClampToQuantum(gamma*qixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00002682 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002683 SetPixelBlue(blur_image,
2684 ClampToQuantum(gamma*qixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00002685 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002686 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002687 SetPixelBlack(blur_image,
2688 ClampToQuantum(gamma*qixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00002689 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002690 SetPixelAlpha(blur_image,
2691 ClampToQuantum(qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00002692 }
cristy4c08aed2011-07-01 19:47:50 +00002693 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00002694 }
2695 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
2696 status=MagickFalse;
2697 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2698 {
2699 MagickBooleanType
2700 proceed;
2701
cristyb557a152011-02-22 12:14:30 +00002702#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002703 #pragma omp critical (MagickCore_MotionBlurImageChannel)
2704#endif
2705 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
2706 if (proceed == MagickFalse)
2707 status=MagickFalse;
2708 }
2709 }
2710 blur_view=DestroyCacheView(blur_view);
2711 image_view=DestroyCacheView(image_view);
2712 kernel=(double *) RelinquishMagickMemory(kernel);
2713 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2714 if (status == MagickFalse)
2715 blur_image=DestroyImage(blur_image);
2716 return(blur_image);
2717}
2718
2719/*
2720%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2721% %
2722% %
2723% %
2724% P r e v i e w I m a g e %
2725% %
2726% %
2727% %
2728%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2729%
2730% PreviewImage() tiles 9 thumbnails of the specified image with an image
2731% processing operation applied with varying parameters. This may be helpful
2732% pin-pointing an appropriate parameter for a particular image processing
2733% operation.
2734%
2735% The format of the PreviewImages method is:
2736%
2737% Image *PreviewImages(const Image *image,const PreviewType preview,
2738% ExceptionInfo *exception)
2739%
2740% A description of each parameter follows:
2741%
2742% o image: the image.
2743%
2744% o preview: the image processing operation.
2745%
2746% o exception: return any errors or warnings in this structure.
2747%
2748*/
2749MagickExport Image *PreviewImage(const Image *image,const PreviewType preview,
2750 ExceptionInfo *exception)
2751{
2752#define NumberTiles 9
2753#define PreviewImageTag "Preview/Image"
2754#define DefaultPreviewGeometry "204x204+10+10"
2755
2756 char
2757 factor[MaxTextExtent],
2758 label[MaxTextExtent];
2759
2760 double
2761 degrees,
2762 gamma,
2763 percentage,
2764 radius,
2765 sigma,
2766 threshold;
2767
2768 Image
2769 *images,
2770 *montage_image,
2771 *preview_image,
2772 *thumbnail;
2773
2774 ImageInfo
2775 *preview_info;
2776
cristy3ed852e2009-09-05 21:47:34 +00002777 MagickBooleanType
2778 proceed;
2779
2780 MontageInfo
2781 *montage_info;
2782
2783 QuantizeInfo
2784 quantize_info;
2785
2786 RectangleInfo
2787 geometry;
2788
cristybb503372010-05-27 20:51:26 +00002789 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002790 i,
2791 x;
2792
cristybb503372010-05-27 20:51:26 +00002793 size_t
cristy3ed852e2009-09-05 21:47:34 +00002794 colors;
2795
cristy117ff172010-08-15 21:35:32 +00002796 ssize_t
2797 y;
2798
cristy3ed852e2009-09-05 21:47:34 +00002799 /*
2800 Open output image file.
2801 */
2802 assert(image != (Image *) NULL);
2803 assert(image->signature == MagickSignature);
2804 if (image->debug != MagickFalse)
2805 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2806 colors=2;
2807 degrees=0.0;
2808 gamma=(-0.2f);
2809 preview_info=AcquireImageInfo();
2810 SetGeometry(image,&geometry);
2811 (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y,
2812 &geometry.width,&geometry.height);
2813 images=NewImageList();
2814 percentage=12.5;
2815 GetQuantizeInfo(&quantize_info);
2816 radius=0.0;
2817 sigma=1.0;
2818 threshold=0.0;
2819 x=0;
2820 y=0;
2821 for (i=0; i < NumberTiles; i++)
2822 {
2823 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
2824 if (thumbnail == (Image *) NULL)
2825 break;
2826 (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL,
2827 (void *) NULL);
2828 (void) SetImageProperty(thumbnail,"label",DefaultTileLabel);
2829 if (i == (NumberTiles/2))
2830 {
2831 (void) QueryColorDatabase("#dfdfdf",&thumbnail->matte_color,exception);
2832 AppendImageToList(&images,thumbnail);
2833 continue;
2834 }
2835 switch (preview)
2836 {
2837 case RotatePreview:
2838 {
2839 degrees+=45.0;
2840 preview_image=RotateImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002841 (void) FormatLocaleString(label,MaxTextExtent,"rotate %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002842 break;
2843 }
2844 case ShearPreview:
2845 {
2846 degrees+=5.0;
2847 preview_image=ShearImage(thumbnail,degrees,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002848 (void) FormatLocaleString(label,MaxTextExtent,"shear %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002849 degrees,2.0*degrees);
2850 break;
2851 }
2852 case RollPreview:
2853 {
cristybb503372010-05-27 20:51:26 +00002854 x=(ssize_t) ((i+1)*thumbnail->columns)/NumberTiles;
2855 y=(ssize_t) ((i+1)*thumbnail->rows)/NumberTiles;
cristy3ed852e2009-09-05 21:47:34 +00002856 preview_image=RollImage(thumbnail,x,y,exception);
cristyb51dff52011-05-19 16:55:47 +00002857 (void) FormatLocaleString(label,MaxTextExtent,"roll %+.20gx%+.20g",
cristye8c25f92010-06-03 00:53:06 +00002858 (double) x,(double) y);
cristy3ed852e2009-09-05 21:47:34 +00002859 break;
2860 }
2861 case HuePreview:
2862 {
2863 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2864 if (preview_image == (Image *) NULL)
2865 break;
cristyb51dff52011-05-19 16:55:47 +00002866 (void) FormatLocaleString(factor,MaxTextExtent,"100,100,%g",
cristy3ed852e2009-09-05 21:47:34 +00002867 2.0*percentage);
2868 (void) ModulateImage(preview_image,factor);
cristyb51dff52011-05-19 16:55:47 +00002869 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002870 break;
2871 }
2872 case SaturationPreview:
2873 {
2874 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2875 if (preview_image == (Image *) NULL)
2876 break;
cristyb51dff52011-05-19 16:55:47 +00002877 (void) FormatLocaleString(factor,MaxTextExtent,"100,%g",
cristy8cd5b312010-01-07 01:10:24 +00002878 2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00002879 (void) ModulateImage(preview_image,factor);
cristyb51dff52011-05-19 16:55:47 +00002880 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002881 break;
2882 }
2883 case BrightnessPreview:
2884 {
2885 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2886 if (preview_image == (Image *) NULL)
2887 break;
cristyb51dff52011-05-19 16:55:47 +00002888 (void) FormatLocaleString(factor,MaxTextExtent,"%g",2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00002889 (void) ModulateImage(preview_image,factor);
cristyb51dff52011-05-19 16:55:47 +00002890 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002891 break;
2892 }
2893 case GammaPreview:
2894 default:
2895 {
2896 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2897 if (preview_image == (Image *) NULL)
2898 break;
2899 gamma+=0.4f;
cristy50fbc382011-07-07 02:19:17 +00002900 (void) GammaImage(preview_image,gamma);
cristyb51dff52011-05-19 16:55:47 +00002901 (void) FormatLocaleString(label,MaxTextExtent,"gamma %g",gamma);
cristy3ed852e2009-09-05 21:47:34 +00002902 break;
2903 }
2904 case SpiffPreview:
2905 {
2906 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2907 if (preview_image != (Image *) NULL)
2908 for (x=0; x < i; x++)
2909 (void) ContrastImage(preview_image,MagickTrue);
cristyb51dff52011-05-19 16:55:47 +00002910 (void) FormatLocaleString(label,MaxTextExtent,"contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002911 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002912 break;
2913 }
2914 case DullPreview:
2915 {
2916 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2917 if (preview_image == (Image *) NULL)
2918 break;
2919 for (x=0; x < i; x++)
2920 (void) ContrastImage(preview_image,MagickFalse);
cristyb51dff52011-05-19 16:55:47 +00002921 (void) FormatLocaleString(label,MaxTextExtent,"+contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002922 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002923 break;
2924 }
2925 case GrayscalePreview:
2926 {
2927 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2928 if (preview_image == (Image *) NULL)
2929 break;
2930 colors<<=1;
2931 quantize_info.number_colors=colors;
2932 quantize_info.colorspace=GRAYColorspace;
2933 (void) QuantizeImage(&quantize_info,preview_image);
cristyb51dff52011-05-19 16:55:47 +00002934 (void) FormatLocaleString(label,MaxTextExtent,
cristye8c25f92010-06-03 00:53:06 +00002935 "-colorspace gray -colors %.20g",(double) colors);
cristy3ed852e2009-09-05 21:47:34 +00002936 break;
2937 }
2938 case QuantizePreview:
2939 {
2940 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2941 if (preview_image == (Image *) NULL)
2942 break;
2943 colors<<=1;
2944 quantize_info.number_colors=colors;
2945 (void) QuantizeImage(&quantize_info,preview_image);
cristyb51dff52011-05-19 16:55:47 +00002946 (void) FormatLocaleString(label,MaxTextExtent,"colors %.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00002947 colors);
cristy3ed852e2009-09-05 21:47:34 +00002948 break;
2949 }
2950 case DespecklePreview:
2951 {
2952 for (x=0; x < (i-1); x++)
2953 {
2954 preview_image=DespeckleImage(thumbnail,exception);
2955 if (preview_image == (Image *) NULL)
2956 break;
2957 thumbnail=DestroyImage(thumbnail);
2958 thumbnail=preview_image;
2959 }
2960 preview_image=DespeckleImage(thumbnail,exception);
2961 if (preview_image == (Image *) NULL)
2962 break;
cristyb51dff52011-05-19 16:55:47 +00002963 (void) FormatLocaleString(label,MaxTextExtent,"despeckle (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002964 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002965 break;
2966 }
2967 case ReduceNoisePreview:
2968 {
cristy95c38342011-03-18 22:39:51 +00002969 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) radius,
2970 (size_t) radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002971 (void) FormatLocaleString(label,MaxTextExtent,"noise %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002972 break;
2973 }
2974 case AddNoisePreview:
2975 {
2976 switch ((int) i)
2977 {
2978 case 0:
2979 {
2980 (void) CopyMagickString(factor,"uniform",MaxTextExtent);
2981 break;
2982 }
2983 case 1:
2984 {
2985 (void) CopyMagickString(factor,"gaussian",MaxTextExtent);
2986 break;
2987 }
2988 case 2:
2989 {
2990 (void) CopyMagickString(factor,"multiplicative",MaxTextExtent);
2991 break;
2992 }
2993 case 3:
2994 {
2995 (void) CopyMagickString(factor,"impulse",MaxTextExtent);
2996 break;
2997 }
2998 case 4:
2999 {
3000 (void) CopyMagickString(factor,"laplacian",MaxTextExtent);
3001 break;
3002 }
3003 case 5:
3004 {
3005 (void) CopyMagickString(factor,"Poisson",MaxTextExtent);
3006 break;
3007 }
3008 default:
3009 {
3010 (void) CopyMagickString(thumbnail->magick,"NULL",MaxTextExtent);
3011 break;
3012 }
3013 }
cristyd76c51e2011-03-26 00:21:26 +00003014 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) i,
3015 (size_t) i,exception);
cristyb51dff52011-05-19 16:55:47 +00003016 (void) FormatLocaleString(label,MaxTextExtent,"+noise %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00003017 break;
3018 }
3019 case SharpenPreview:
3020 {
3021 preview_image=SharpenImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00003022 (void) FormatLocaleString(label,MaxTextExtent,"sharpen %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003023 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00003024 break;
3025 }
3026 case BlurPreview:
3027 {
3028 preview_image=BlurImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00003029 (void) FormatLocaleString(label,MaxTextExtent,"blur %gx%g",radius,
cristy3ed852e2009-09-05 21:47:34 +00003030 sigma);
3031 break;
3032 }
3033 case ThresholdPreview:
3034 {
3035 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3036 if (preview_image == (Image *) NULL)
3037 break;
3038 (void) BilevelImage(thumbnail,
3039 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
cristyb51dff52011-05-19 16:55:47 +00003040 (void) FormatLocaleString(label,MaxTextExtent,"threshold %g",
cristy3ed852e2009-09-05 21:47:34 +00003041 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
3042 break;
3043 }
3044 case EdgeDetectPreview:
3045 {
3046 preview_image=EdgeImage(thumbnail,radius,exception);
cristyb51dff52011-05-19 16:55:47 +00003047 (void) FormatLocaleString(label,MaxTextExtent,"edge %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003048 break;
3049 }
3050 case SpreadPreview:
3051 {
3052 preview_image=SpreadImage(thumbnail,radius,exception);
cristyb51dff52011-05-19 16:55:47 +00003053 (void) FormatLocaleString(label,MaxTextExtent,"spread %g",
cristy8cd5b312010-01-07 01:10:24 +00003054 radius+0.5);
cristy3ed852e2009-09-05 21:47:34 +00003055 break;
3056 }
3057 case SolarizePreview:
3058 {
3059 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3060 if (preview_image == (Image *) NULL)
3061 break;
3062 (void) SolarizeImage(preview_image,(double) QuantumRange*
3063 percentage/100.0);
cristyb51dff52011-05-19 16:55:47 +00003064 (void) FormatLocaleString(label,MaxTextExtent,"solarize %g",
cristy3ed852e2009-09-05 21:47:34 +00003065 (QuantumRange*percentage)/100.0);
3066 break;
3067 }
3068 case ShadePreview:
3069 {
3070 degrees+=10.0;
3071 preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees,
3072 exception);
cristyb51dff52011-05-19 16:55:47 +00003073 (void) FormatLocaleString(label,MaxTextExtent,"shade %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003074 degrees,degrees);
cristy3ed852e2009-09-05 21:47:34 +00003075 break;
3076 }
3077 case RaisePreview:
3078 {
3079 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3080 if (preview_image == (Image *) NULL)
3081 break;
cristybb503372010-05-27 20:51:26 +00003082 geometry.width=(size_t) (2*i+2);
3083 geometry.height=(size_t) (2*i+2);
cristy3ed852e2009-09-05 21:47:34 +00003084 geometry.x=i/2;
3085 geometry.y=i/2;
3086 (void) RaiseImage(preview_image,&geometry,MagickTrue);
cristyb51dff52011-05-19 16:55:47 +00003087 (void) FormatLocaleString(label,MaxTextExtent,
cristy6d8abba2010-06-03 01:10:47 +00003088 "raise %.20gx%.20g%+.20g%+.20g",(double) geometry.width,(double)
cristye8c25f92010-06-03 00:53:06 +00003089 geometry.height,(double) geometry.x,(double) geometry.y);
cristy3ed852e2009-09-05 21:47:34 +00003090 break;
3091 }
3092 case SegmentPreview:
3093 {
3094 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3095 if (preview_image == (Image *) NULL)
3096 break;
3097 threshold+=0.4f;
3098 (void) SegmentImage(preview_image,RGBColorspace,MagickFalse,threshold,
3099 threshold);
cristyb51dff52011-05-19 16:55:47 +00003100 (void) FormatLocaleString(label,MaxTextExtent,"segment %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00003101 threshold,threshold);
3102 break;
3103 }
3104 case SwirlPreview:
3105 {
3106 preview_image=SwirlImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00003107 (void) FormatLocaleString(label,MaxTextExtent,"swirl %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003108 degrees+=45.0;
3109 break;
3110 }
3111 case ImplodePreview:
3112 {
3113 degrees+=0.1f;
3114 preview_image=ImplodeImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00003115 (void) FormatLocaleString(label,MaxTextExtent,"implode %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003116 break;
3117 }
3118 case WavePreview:
3119 {
3120 degrees+=5.0f;
3121 preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00003122 (void) FormatLocaleString(label,MaxTextExtent,"wave %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003123 0.5*degrees,2.0*degrees);
cristy3ed852e2009-09-05 21:47:34 +00003124 break;
3125 }
3126 case OilPaintPreview:
3127 {
3128 preview_image=OilPaintImage(thumbnail,(double) radius,exception);
cristyb51dff52011-05-19 16:55:47 +00003129 (void) FormatLocaleString(label,MaxTextExtent,"paint %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003130 break;
3131 }
3132 case CharcoalDrawingPreview:
3133 {
3134 preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma,
3135 exception);
cristyb51dff52011-05-19 16:55:47 +00003136 (void) FormatLocaleString(label,MaxTextExtent,"charcoal %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003137 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00003138 break;
3139 }
3140 case JPEGPreview:
3141 {
3142 char
3143 filename[MaxTextExtent];
3144
3145 int
3146 file;
3147
3148 MagickBooleanType
3149 status;
3150
3151 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3152 if (preview_image == (Image *) NULL)
3153 break;
cristybb503372010-05-27 20:51:26 +00003154 preview_info->quality=(size_t) percentage;
cristyb51dff52011-05-19 16:55:47 +00003155 (void) FormatLocaleString(factor,MaxTextExtent,"%.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00003156 preview_info->quality);
cristy3ed852e2009-09-05 21:47:34 +00003157 file=AcquireUniqueFileResource(filename);
3158 if (file != -1)
3159 file=close(file)-1;
cristyb51dff52011-05-19 16:55:47 +00003160 (void) FormatLocaleString(preview_image->filename,MaxTextExtent,
cristy3ed852e2009-09-05 21:47:34 +00003161 "jpeg:%s",filename);
3162 status=WriteImage(preview_info,preview_image);
3163 if (status != MagickFalse)
3164 {
3165 Image
3166 *quality_image;
3167
3168 (void) CopyMagickString(preview_info->filename,
3169 preview_image->filename,MaxTextExtent);
3170 quality_image=ReadImage(preview_info,exception);
3171 if (quality_image != (Image *) NULL)
3172 {
3173 preview_image=DestroyImage(preview_image);
3174 preview_image=quality_image;
3175 }
3176 }
3177 (void) RelinquishUniqueFileResource(preview_image->filename);
3178 if ((GetBlobSize(preview_image)/1024) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00003179 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%gmb ",
cristy3ed852e2009-09-05 21:47:34 +00003180 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
3181 1024.0/1024.0);
3182 else
3183 if (GetBlobSize(preview_image) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00003184 (void) FormatLocaleString(label,MaxTextExtent,
cristye7f51092010-01-17 00:39:37 +00003185 "quality %s\n%gkb ",factor,(double) ((MagickOffsetType)
cristy8cd5b312010-01-07 01:10:24 +00003186 GetBlobSize(preview_image))/1024.0);
cristy3ed852e2009-09-05 21:47:34 +00003187 else
cristyb51dff52011-05-19 16:55:47 +00003188 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%.20gb ",
cristy54ea5732011-06-10 12:39:53 +00003189 factor,(double) ((MagickOffsetType) GetBlobSize(thumbnail)));
cristy3ed852e2009-09-05 21:47:34 +00003190 break;
3191 }
3192 }
3193 thumbnail=DestroyImage(thumbnail);
3194 percentage+=12.5;
3195 radius+=0.5;
3196 sigma+=0.25;
3197 if (preview_image == (Image *) NULL)
3198 break;
3199 (void) DeleteImageProperty(preview_image,"label");
3200 (void) SetImageProperty(preview_image,"label",label);
3201 AppendImageToList(&images,preview_image);
cristybb503372010-05-27 20:51:26 +00003202 proceed=SetImageProgress(image,PreviewImageTag,(MagickOffsetType) i,
3203 NumberTiles);
cristy3ed852e2009-09-05 21:47:34 +00003204 if (proceed == MagickFalse)
3205 break;
3206 }
3207 if (images == (Image *) NULL)
3208 {
3209 preview_info=DestroyImageInfo(preview_info);
3210 return((Image *) NULL);
3211 }
3212 /*
3213 Create the montage.
3214 */
3215 montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL);
3216 (void) CopyMagickString(montage_info->filename,image->filename,MaxTextExtent);
3217 montage_info->shadow=MagickTrue;
3218 (void) CloneString(&montage_info->tile,"3x3");
3219 (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry);
3220 (void) CloneString(&montage_info->frame,DefaultTileFrame);
3221 montage_image=MontageImages(images,montage_info,exception);
3222 montage_info=DestroyMontageInfo(montage_info);
3223 images=DestroyImageList(images);
3224 if (montage_image == (Image *) NULL)
3225 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3226 if (montage_image->montage != (char *) NULL)
3227 {
3228 /*
3229 Free image directory.
3230 */
3231 montage_image->montage=(char *) RelinquishMagickMemory(
3232 montage_image->montage);
3233 if (image->directory != (char *) NULL)
3234 montage_image->directory=(char *) RelinquishMagickMemory(
3235 montage_image->directory);
3236 }
3237 preview_info=DestroyImageInfo(preview_info);
3238 return(montage_image);
3239}
3240
3241/*
3242%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3243% %
3244% %
3245% %
3246% R a d i a l B l u r I m a g e %
3247% %
3248% %
3249% %
3250%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3251%
3252% RadialBlurImage() applies a radial blur to the image.
3253%
3254% Andrew Protano contributed this effect.
3255%
3256% The format of the RadialBlurImage method is:
3257%
3258% Image *RadialBlurImage(const Image *image,const double angle,
3259% ExceptionInfo *exception)
3260% Image *RadialBlurImageChannel(const Image *image,const ChannelType channel,
3261% const double angle,ExceptionInfo *exception)
3262%
3263% A description of each parameter follows:
3264%
3265% o image: the image.
3266%
3267% o channel: the channel type.
3268%
3269% o angle: the angle of the radial blur.
3270%
3271% o exception: return any errors or warnings in this structure.
3272%
3273*/
3274
3275MagickExport Image *RadialBlurImage(const Image *image,const double angle,
3276 ExceptionInfo *exception)
3277{
3278 Image
3279 *blur_image;
3280
3281 blur_image=RadialBlurImageChannel(image,DefaultChannels,angle,exception);
3282 return(blur_image);
3283}
3284
3285MagickExport Image *RadialBlurImageChannel(const Image *image,
3286 const ChannelType channel,const double angle,ExceptionInfo *exception)
3287{
cristyc4c8d132010-01-07 01:58:38 +00003288 CacheView
3289 *blur_view,
3290 *image_view;
3291
cristy3ed852e2009-09-05 21:47:34 +00003292 Image
3293 *blur_image;
3294
cristy3ed852e2009-09-05 21:47:34 +00003295 MagickBooleanType
3296 status;
3297
cristybb503372010-05-27 20:51:26 +00003298 MagickOffsetType
3299 progress;
3300
cristy4c08aed2011-07-01 19:47:50 +00003301 PixelInfo
cristyddd82202009-11-03 20:14:50 +00003302 bias;
cristy3ed852e2009-09-05 21:47:34 +00003303
3304 MagickRealType
3305 blur_radius,
3306 *cos_theta,
3307 offset,
3308 *sin_theta,
3309 theta;
3310
3311 PointInfo
3312 blur_center;
3313
cristybb503372010-05-27 20:51:26 +00003314 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003315 i;
3316
cristybb503372010-05-27 20:51:26 +00003317 size_t
cristy3ed852e2009-09-05 21:47:34 +00003318 n;
3319
cristybb503372010-05-27 20:51:26 +00003320 ssize_t
3321 y;
3322
cristy3ed852e2009-09-05 21:47:34 +00003323 /*
3324 Allocate blur image.
3325 */
3326 assert(image != (Image *) NULL);
3327 assert(image->signature == MagickSignature);
3328 if (image->debug != MagickFalse)
3329 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3330 assert(exception != (ExceptionInfo *) NULL);
3331 assert(exception->signature == MagickSignature);
3332 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3333 if (blur_image == (Image *) NULL)
3334 return((Image *) NULL);
3335 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3336 {
3337 InheritException(exception,&blur_image->exception);
3338 blur_image=DestroyImage(blur_image);
3339 return((Image *) NULL);
3340 }
3341 blur_center.x=(double) image->columns/2.0;
3342 blur_center.y=(double) image->rows/2.0;
3343 blur_radius=hypot(blur_center.x,blur_center.y);
cristy117ff172010-08-15 21:35:32 +00003344 n=(size_t) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+2UL);
cristy3ed852e2009-09-05 21:47:34 +00003345 theta=DegreesToRadians(angle)/(MagickRealType) (n-1);
3346 cos_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3347 sizeof(*cos_theta));
3348 sin_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3349 sizeof(*sin_theta));
3350 if ((cos_theta == (MagickRealType *) NULL) ||
3351 (sin_theta == (MagickRealType *) NULL))
3352 {
3353 blur_image=DestroyImage(blur_image);
3354 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3355 }
3356 offset=theta*(MagickRealType) (n-1)/2.0;
cristybb503372010-05-27 20:51:26 +00003357 for (i=0; i < (ssize_t) n; i++)
cristy3ed852e2009-09-05 21:47:34 +00003358 {
3359 cos_theta[i]=cos((double) (theta*i-offset));
3360 sin_theta[i]=sin((double) (theta*i-offset));
3361 }
3362 /*
3363 Radial blur image.
3364 */
3365 status=MagickTrue;
3366 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00003367 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003368 image_view=AcquireCacheView(image);
3369 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003370#if defined(MAGICKCORE_OPENMP_SUPPORT)
3371 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003372#endif
cristybb503372010-05-27 20:51:26 +00003373 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003374 {
cristy4c08aed2011-07-01 19:47:50 +00003375 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003376 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003377
cristy117ff172010-08-15 21:35:32 +00003378 register ssize_t
3379 x;
3380
cristy3ed852e2009-09-05 21:47:34 +00003381 if (status == MagickFalse)
3382 continue;
3383 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3384 exception);
cristy4c08aed2011-07-01 19:47:50 +00003385 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003386 {
3387 status=MagickFalse;
3388 continue;
3389 }
cristybb503372010-05-27 20:51:26 +00003390 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003391 {
cristy4c08aed2011-07-01 19:47:50 +00003392 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003393 qixel;
3394
3395 MagickRealType
3396 normalize,
3397 radius;
3398
3399 PixelPacket
3400 pixel;
3401
3402 PointInfo
3403 center;
3404
cristybb503372010-05-27 20:51:26 +00003405 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003406 i;
3407
cristybb503372010-05-27 20:51:26 +00003408 size_t
cristy3ed852e2009-09-05 21:47:34 +00003409 step;
3410
3411 center.x=(double) x-blur_center.x;
3412 center.y=(double) y-blur_center.y;
3413 radius=hypot((double) center.x,center.y);
3414 if (radius == 0)
3415 step=1;
3416 else
3417 {
cristybb503372010-05-27 20:51:26 +00003418 step=(size_t) (blur_radius/radius);
cristy3ed852e2009-09-05 21:47:34 +00003419 if (step == 0)
3420 step=1;
3421 else
3422 if (step >= n)
3423 step=n-1;
3424 }
3425 normalize=0.0;
cristyddd82202009-11-03 20:14:50 +00003426 qixel=bias;
cristy2b9582a2011-07-04 17:38:56 +00003427 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00003428 {
cristyeaedf062010-05-29 22:36:02 +00003429 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00003430 {
cristyeaedf062010-05-29 22:36:02 +00003431 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
3432 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
3433 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
3434 cos_theta[i]+0.5),&pixel,exception);
cristy3ed852e2009-09-05 21:47:34 +00003435 qixel.red+=pixel.red;
3436 qixel.green+=pixel.green;
3437 qixel.blue+=pixel.blue;
cristy3ed852e2009-09-05 21:47:34 +00003438 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00003439 qixel.black+=pixel.black;
3440 qixel.alpha+=pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00003441 normalize+=1.0;
3442 }
3443 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
3444 normalize);
cristy2b9582a2011-07-04 17:38:56 +00003445 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003446 SetPixelRed(blur_image,
3447 ClampToQuantum(normalize*qixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00003448 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003449 SetPixelGreen(blur_image,
3450 ClampToQuantum(normalize*qixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00003451 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003452 SetPixelBlue(blur_image,
3453 ClampToQuantum(normalize*qixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00003454 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003455 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003456 SetPixelBlack(blur_image,
3457 ClampToQuantum(normalize*qixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00003458 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003459 SetPixelAlpha(blur_image,
3460 ClampToQuantum(normalize*qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003461 }
3462 else
3463 {
3464 MagickRealType
3465 alpha,
3466 gamma;
3467
3468 alpha=1.0;
3469 gamma=0.0;
cristyeaedf062010-05-29 22:36:02 +00003470 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00003471 {
cristyeaedf062010-05-29 22:36:02 +00003472 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
3473 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
3474 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
3475 cos_theta[i]+0.5),&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00003476 alpha=(MagickRealType) (QuantumScale*pixel.alpha);
cristy3ed852e2009-09-05 21:47:34 +00003477 qixel.red+=alpha*pixel.red;
3478 qixel.green+=alpha*pixel.green;
3479 qixel.blue+=alpha*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00003480 qixel.alpha+=pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00003481 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00003482 qixel.black+=alpha*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00003483 gamma+=alpha;
3484 normalize+=1.0;
3485 }
3486 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3487 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
3488 normalize);
cristy2b9582a2011-07-04 17:38:56 +00003489 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003490 SetPixelRed(blur_image,
3491 ClampToQuantum(gamma*qixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00003492 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003493 SetPixelGreen(blur_image,
3494 ClampToQuantum(gamma*qixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00003495 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003496 SetPixelBlue(blur_image,
3497 ClampToQuantum(gamma*qixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00003498 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003499 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003500 SetPixelBlack(blur_image,
3501 ClampToQuantum(gamma*qixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00003502 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003503 SetPixelAlpha(blur_image,
3504 ClampToQuantum(normalize*qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003505 }
cristy4c08aed2011-07-01 19:47:50 +00003506 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003507 }
3508 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
3509 status=MagickFalse;
3510 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3511 {
3512 MagickBooleanType
3513 proceed;
3514
cristyb5d5f722009-11-04 03:03:49 +00003515#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003516 #pragma omp critical (MagickCore_RadialBlurImageChannel)
3517#endif
3518 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
3519 if (proceed == MagickFalse)
3520 status=MagickFalse;
3521 }
3522 }
3523 blur_view=DestroyCacheView(blur_view);
3524 image_view=DestroyCacheView(image_view);
3525 cos_theta=(MagickRealType *) RelinquishMagickMemory(cos_theta);
3526 sin_theta=(MagickRealType *) RelinquishMagickMemory(sin_theta);
3527 if (status == MagickFalse)
3528 blur_image=DestroyImage(blur_image);
3529 return(blur_image);
3530}
3531
3532/*
3533%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3534% %
3535% %
3536% %
cristy3ed852e2009-09-05 21:47:34 +00003537% S e l e c t i v e B l u r I m a g e %
3538% %
3539% %
3540% %
3541%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3542%
3543% SelectiveBlurImage() selectively blur pixels within a contrast threshold.
3544% It is similar to the unsharpen mask that sharpens everything with contrast
3545% above a certain threshold.
3546%
3547% The format of the SelectiveBlurImage method is:
3548%
3549% Image *SelectiveBlurImage(const Image *image,const double radius,
3550% const double sigma,const double threshold,ExceptionInfo *exception)
3551% Image *SelectiveBlurImageChannel(const Image *image,
3552% const ChannelType channel,const double radius,const double sigma,
3553% const double threshold,ExceptionInfo *exception)
3554%
3555% A description of each parameter follows:
3556%
3557% o image: the image.
3558%
3559% o channel: the channel type.
3560%
3561% o radius: the radius of the Gaussian, in pixels, not counting the center
3562% pixel.
3563%
3564% o sigma: the standard deviation of the Gaussian, in pixels.
3565%
3566% o threshold: only pixels within this contrast threshold are included
3567% in the blur operation.
3568%
3569% o exception: return any errors or warnings in this structure.
3570%
3571*/
3572
cristy3ed852e2009-09-05 21:47:34 +00003573MagickExport Image *SelectiveBlurImage(const Image *image,const double radius,
3574 const double sigma,const double threshold,ExceptionInfo *exception)
3575{
3576 Image
3577 *blur_image;
3578
3579 blur_image=SelectiveBlurImageChannel(image,DefaultChannels,radius,sigma,
3580 threshold,exception);
3581 return(blur_image);
3582}
3583
3584MagickExport Image *SelectiveBlurImageChannel(const Image *image,
3585 const ChannelType channel,const double radius,const double sigma,
3586 const double threshold,ExceptionInfo *exception)
3587{
3588#define SelectiveBlurImageTag "SelectiveBlur/Image"
3589
cristy47e00502009-12-17 19:19:57 +00003590 CacheView
3591 *blur_view,
3592 *image_view;
3593
cristy3ed852e2009-09-05 21:47:34 +00003594 double
cristy3ed852e2009-09-05 21:47:34 +00003595 *kernel;
3596
3597 Image
3598 *blur_image;
3599
cristy3ed852e2009-09-05 21:47:34 +00003600 MagickBooleanType
3601 status;
3602
cristybb503372010-05-27 20:51:26 +00003603 MagickOffsetType
3604 progress;
3605
cristy4c08aed2011-07-01 19:47:50 +00003606 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003607 bias;
3608
cristybb503372010-05-27 20:51:26 +00003609 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003610 i;
cristy3ed852e2009-09-05 21:47:34 +00003611
cristybb503372010-05-27 20:51:26 +00003612 size_t
cristy3ed852e2009-09-05 21:47:34 +00003613 width;
3614
cristybb503372010-05-27 20:51:26 +00003615 ssize_t
3616 j,
3617 u,
3618 v,
3619 y;
3620
cristy3ed852e2009-09-05 21:47:34 +00003621 /*
3622 Initialize blur image attributes.
3623 */
3624 assert(image != (Image *) NULL);
3625 assert(image->signature == MagickSignature);
3626 if (image->debug != MagickFalse)
3627 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3628 assert(exception != (ExceptionInfo *) NULL);
3629 assert(exception->signature == MagickSignature);
3630 width=GetOptimalKernelWidth1D(radius,sigma);
3631 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
3632 if (kernel == (double *) NULL)
3633 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00003634 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00003635 i=0;
cristy47e00502009-12-17 19:19:57 +00003636 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00003637 {
cristy47e00502009-12-17 19:19:57 +00003638 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00003639 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
3640 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00003641 }
3642 if (image->debug != MagickFalse)
3643 {
3644 char
3645 format[MaxTextExtent],
3646 *message;
3647
cristy117ff172010-08-15 21:35:32 +00003648 register const double
3649 *k;
3650
cristybb503372010-05-27 20:51:26 +00003651 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003652 u,
3653 v;
3654
cristy3ed852e2009-09-05 21:47:34 +00003655 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00003656 " SelectiveBlurImage with %.20gx%.20g kernel:",(double) width,(double)
3657 width);
cristy3ed852e2009-09-05 21:47:34 +00003658 message=AcquireString("");
3659 k=kernel;
cristybb503372010-05-27 20:51:26 +00003660 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003661 {
3662 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00003663 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy3ed852e2009-09-05 21:47:34 +00003664 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00003665 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003666 {
cristyb51dff52011-05-19 16:55:47 +00003667 (void) FormatLocaleString(format,MaxTextExtent,"%+f ",*k++);
cristy3ed852e2009-09-05 21:47:34 +00003668 (void) ConcatenateString(&message,format);
3669 }
3670 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
3671 }
3672 message=DestroyString(message);
3673 }
3674 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3675 if (blur_image == (Image *) NULL)
3676 return((Image *) NULL);
3677 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3678 {
3679 InheritException(exception,&blur_image->exception);
3680 blur_image=DestroyImage(blur_image);
3681 return((Image *) NULL);
3682 }
3683 /*
3684 Threshold blur image.
3685 */
3686 status=MagickTrue;
3687 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00003688 GetPixelInfo(image,&bias);
3689 SetPixelInfoBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003690 image_view=AcquireCacheView(image);
3691 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003692#if defined(MAGICKCORE_OPENMP_SUPPORT)
3693 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003694#endif
cristybb503372010-05-27 20:51:26 +00003695 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003696 {
cristy4c08aed2011-07-01 19:47:50 +00003697 double
3698 contrast;
3699
cristy3ed852e2009-09-05 21:47:34 +00003700 MagickBooleanType
3701 sync;
3702
3703 MagickRealType
3704 gamma;
3705
cristy4c08aed2011-07-01 19:47:50 +00003706 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003707 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003708
cristy4c08aed2011-07-01 19:47:50 +00003709 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003710 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003711
cristy117ff172010-08-15 21:35:32 +00003712 register ssize_t
3713 x;
3714
cristy3ed852e2009-09-05 21:47:34 +00003715 if (status == MagickFalse)
3716 continue;
cristy117ff172010-08-15 21:35:32 +00003717 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
3718 (width/2L),image->columns+width,width,exception);
cristy3ed852e2009-09-05 21:47:34 +00003719 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3720 exception);
cristy4c08aed2011-07-01 19:47:50 +00003721 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003722 {
3723 status=MagickFalse;
3724 continue;
3725 }
cristybb503372010-05-27 20:51:26 +00003726 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003727 {
cristy4c08aed2011-07-01 19:47:50 +00003728 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003729 pixel;
3730
3731 register const double
cristyc47d1f82009-11-26 01:44:43 +00003732 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00003733
cristybb503372010-05-27 20:51:26 +00003734 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003735 u;
3736
cristy117ff172010-08-15 21:35:32 +00003737 ssize_t
3738 j,
3739 v;
3740
cristyddd82202009-11-03 20:14:50 +00003741 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00003742 k=kernel;
3743 gamma=0.0;
3744 j=0;
cristy2b9582a2011-07-04 17:38:56 +00003745 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00003746 {
cristybb503372010-05-27 20:51:26 +00003747 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003748 {
cristybb503372010-05-27 20:51:26 +00003749 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003750 {
cristy4c08aed2011-07-01 19:47:50 +00003751 contrast=GetPixelIntensity(image,p+(u+j)*GetPixelChannels(image))-
3752 (double) GetPixelIntensity(blur_image,q);
3753 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003754 {
cristy4c08aed2011-07-01 19:47:50 +00003755 pixel.red+=(*k)*
3756 GetPixelRed(image,p+(u+j)*GetPixelChannels(image));
3757 pixel.green+=(*k)*
3758 GetPixelGreen(image,p+(u+j)*GetPixelChannels(image));
3759 pixel.blue+=(*k)*
3760 GetPixelBlue(image,p+(u+j)*GetPixelChannels(image));
3761 if (image->colorspace == CMYKColorspace)
3762 pixel.black+=(*k)*
3763 GetPixelBlack(image,p+(u+j)*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003764 gamma+=(*k);
3765 k++;
3766 }
3767 }
cristyd99b0962010-05-29 23:14:26 +00003768 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003769 }
3770 if (gamma != 0.0)
3771 {
3772 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy2b9582a2011-07-04 17:38:56 +00003773 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003774 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00003775 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003776 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00003777 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003778 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00003779 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00003780 (image->colorspace == CMYKColorspace))
3781 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00003782 }
cristy2b9582a2011-07-04 17:38:56 +00003783 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003784 {
3785 gamma=0.0;
3786 j=0;
cristybb503372010-05-27 20:51:26 +00003787 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003788 {
cristybb503372010-05-27 20:51:26 +00003789 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003790 {
cristy4c08aed2011-07-01 19:47:50 +00003791 contrast=GetPixelIntensity(image,p+(u+j)*
3792 GetPixelChannels(image))-(double)
3793 GetPixelIntensity(blur_image,q);
3794 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003795 {
cristy4c08aed2011-07-01 19:47:50 +00003796 pixel.alpha+=(*k)*
3797 GetPixelAlpha(image,p+(u+j)*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003798 gamma+=(*k);
3799 k++;
3800 }
3801 }
cristyeaedf062010-05-29 22:36:02 +00003802 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003803 }
3804 if (gamma != 0.0)
3805 {
3806 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
3807 gamma);
cristy4c08aed2011-07-01 19:47:50 +00003808 SetPixelAlpha(blur_image,ClampToQuantum(gamma*pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003809 }
3810 }
3811 }
3812 else
3813 {
3814 MagickRealType
3815 alpha;
3816
cristybb503372010-05-27 20:51:26 +00003817 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003818 {
cristybb503372010-05-27 20:51:26 +00003819 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003820 {
cristy4c08aed2011-07-01 19:47:50 +00003821 contrast=GetPixelIntensity(image,p+(u+j)*
3822 GetPixelChannels(image))-(double)
3823 GetPixelIntensity(blur_image,q);
3824 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003825 {
cristy4c08aed2011-07-01 19:47:50 +00003826 alpha=(MagickRealType) (QuantumScale*
3827 GetPixelAlpha(image,p+(u+j)*GetPixelChannels(image)));
3828 pixel.red+=(*k)*alpha*
3829 GetPixelRed(image,p+(u+j)*GetPixelChannels(image));
3830 pixel.green+=(*k)*alpha*GetPixelGreen(image,p+(u+j)*
3831 GetPixelChannels(image));
3832 pixel.blue+=(*k)*alpha*GetPixelBlue(image,p+(u+j)*
3833 GetPixelChannels(image));
3834 pixel.alpha+=(*k)*GetPixelAlpha(image,p+(u+j)*
3835 GetPixelChannels(image));
3836 if (image->colorspace == CMYKColorspace)
3837 pixel.black+=(*k)*GetPixelBlack(image,p+(u+j)*
3838 GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003839 gamma+=(*k)*alpha;
3840 k++;
3841 }
3842 }
cristyeaedf062010-05-29 22:36:02 +00003843 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003844 }
3845 if (gamma != 0.0)
3846 {
3847 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy2b9582a2011-07-04 17:38:56 +00003848 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003849 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00003850 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003851 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00003852 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003853 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00003854 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00003855 (image->colorspace == CMYKColorspace))
3856 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00003857 }
cristy2b9582a2011-07-04 17:38:56 +00003858 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003859 {
3860 gamma=0.0;
3861 j=0;
cristybb503372010-05-27 20:51:26 +00003862 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003863 {
cristybb503372010-05-27 20:51:26 +00003864 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003865 {
cristy4c08aed2011-07-01 19:47:50 +00003866 contrast=GetPixelIntensity(image,p+(u+j)*
3867 GetPixelChannels(image))-(double)
3868 GetPixelIntensity(blur_image,q);
3869 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003870 {
cristy4c08aed2011-07-01 19:47:50 +00003871 pixel.alpha+=(*k)*
3872 GetPixelAlpha(image,p+(u+j)*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003873 gamma+=(*k);
3874 k++;
3875 }
3876 }
cristyeaedf062010-05-29 22:36:02 +00003877 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003878 }
3879 if (gamma != 0.0)
3880 {
3881 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
3882 gamma);
cristy4c08aed2011-07-01 19:47:50 +00003883 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003884 }
3885 }
3886 }
cristy4c08aed2011-07-01 19:47:50 +00003887 p+=GetPixelChannels(image);
3888 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003889 }
3890 sync=SyncCacheViewAuthenticPixels(blur_view,exception);
3891 if (sync == MagickFalse)
3892 status=MagickFalse;
3893 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3894 {
3895 MagickBooleanType
3896 proceed;
3897
cristyb5d5f722009-11-04 03:03:49 +00003898#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003899 #pragma omp critical (MagickCore_SelectiveBlurImageChannel)
3900#endif
3901 proceed=SetImageProgress(image,SelectiveBlurImageTag,progress++,
3902 image->rows);
3903 if (proceed == MagickFalse)
3904 status=MagickFalse;
3905 }
3906 }
3907 blur_image->type=image->type;
3908 blur_view=DestroyCacheView(blur_view);
3909 image_view=DestroyCacheView(image_view);
3910 kernel=(double *) RelinquishMagickMemory(kernel);
3911 if (status == MagickFalse)
3912 blur_image=DestroyImage(blur_image);
3913 return(blur_image);
3914}
3915
3916/*
3917%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3918% %
3919% %
3920% %
3921% S h a d e I m a g e %
3922% %
3923% %
3924% %
3925%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3926%
3927% ShadeImage() shines a distant light on an image to create a
3928% three-dimensional effect. You control the positioning of the light with
3929% azimuth and elevation; azimuth is measured in degrees off the x axis
3930% and elevation is measured in pixels above the Z axis.
3931%
3932% The format of the ShadeImage method is:
3933%
3934% Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3935% const double azimuth,const double elevation,ExceptionInfo *exception)
3936%
3937% A description of each parameter follows:
3938%
3939% o image: the image.
3940%
3941% o gray: A value other than zero shades the intensity of each pixel.
3942%
3943% o azimuth, elevation: Define the light source direction.
3944%
3945% o exception: return any errors or warnings in this structure.
3946%
3947*/
3948MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3949 const double azimuth,const double elevation,ExceptionInfo *exception)
3950{
3951#define ShadeImageTag "Shade/Image"
3952
cristyc4c8d132010-01-07 01:58:38 +00003953 CacheView
3954 *image_view,
3955 *shade_view;
3956
cristy3ed852e2009-09-05 21:47:34 +00003957 Image
3958 *shade_image;
3959
cristy3ed852e2009-09-05 21:47:34 +00003960 MagickBooleanType
3961 status;
3962
cristybb503372010-05-27 20:51:26 +00003963 MagickOffsetType
3964 progress;
3965
cristy3ed852e2009-09-05 21:47:34 +00003966 PrimaryInfo
3967 light;
3968
cristybb503372010-05-27 20:51:26 +00003969 ssize_t
3970 y;
3971
cristy3ed852e2009-09-05 21:47:34 +00003972 /*
3973 Initialize shaded image attributes.
3974 */
3975 assert(image != (const Image *) NULL);
3976 assert(image->signature == MagickSignature);
3977 if (image->debug != MagickFalse)
3978 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3979 assert(exception != (ExceptionInfo *) NULL);
3980 assert(exception->signature == MagickSignature);
3981 shade_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
3982 if (shade_image == (Image *) NULL)
3983 return((Image *) NULL);
3984 if (SetImageStorageClass(shade_image,DirectClass) == MagickFalse)
3985 {
3986 InheritException(exception,&shade_image->exception);
3987 shade_image=DestroyImage(shade_image);
3988 return((Image *) NULL);
3989 }
3990 /*
3991 Compute the light vector.
3992 */
3993 light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))*
3994 cos(DegreesToRadians(elevation));
3995 light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))*
3996 cos(DegreesToRadians(elevation));
3997 light.z=(double) QuantumRange*sin(DegreesToRadians(elevation));
3998 /*
3999 Shade image.
4000 */
4001 status=MagickTrue;
4002 progress=0;
4003 image_view=AcquireCacheView(image);
4004 shade_view=AcquireCacheView(shade_image);
cristyb5d5f722009-11-04 03:03:49 +00004005#if defined(MAGICKCORE_OPENMP_SUPPORT)
4006 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004007#endif
cristybb503372010-05-27 20:51:26 +00004008 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00004009 {
4010 MagickRealType
4011 distance,
4012 normal_distance,
4013 shade;
4014
4015 PrimaryInfo
4016 normal;
4017
cristy4c08aed2011-07-01 19:47:50 +00004018 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00004019 *restrict p,
4020 *restrict s0,
4021 *restrict s1,
4022 *restrict s2;
cristy3ed852e2009-09-05 21:47:34 +00004023
cristy4c08aed2011-07-01 19:47:50 +00004024 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00004025 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004026
cristy117ff172010-08-15 21:35:32 +00004027 register ssize_t
4028 x;
4029
cristy3ed852e2009-09-05 21:47:34 +00004030 if (status == MagickFalse)
4031 continue;
4032 p=GetCacheViewVirtualPixels(image_view,-1,y-1,image->columns+2,3,exception);
4033 q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1,
4034 exception);
cristy4c08aed2011-07-01 19:47:50 +00004035 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00004036 {
4037 status=MagickFalse;
4038 continue;
4039 }
4040 /*
4041 Shade this row of pixels.
4042 */
4043 normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */
cristy4c08aed2011-07-01 19:47:50 +00004044 s0=p+GetPixelChannels(image);
4045 s1=s0+(image->columns+2)*GetPixelChannels(image);
4046 s2=s1+(image->columns+2)*GetPixelChannels(image);
cristybb503372010-05-27 20:51:26 +00004047 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004048 {
4049 /*
4050 Determine the surface normal and compute shading.
4051 */
cristy4c08aed2011-07-01 19:47:50 +00004052 normal.x=(double) (GetPixelIntensity(image,s0-GetPixelChannels(image))+
4053 GetPixelIntensity(image,s1-GetPixelChannels(image))+
4054 GetPixelIntensity(image,s2-GetPixelChannels(image))-
4055 GetPixelIntensity(image,s0+GetPixelChannels(image))-
4056 GetPixelIntensity(image,s1+GetPixelChannels(image))-
4057 GetPixelIntensity(image,s2+GetPixelChannels(image)));
4058 normal.y=(double) (GetPixelIntensity(image,s2-GetPixelChannels(image))+
4059 GetPixelIntensity(image,s2)+
4060 GetPixelIntensity(image,s2+GetPixelChannels(image))-
4061 GetPixelIntensity(image,s0-GetPixelChannels(image))-
4062 GetPixelIntensity(image,s0)-
4063 GetPixelIntensity(image,s0+GetPixelChannels(image)));
cristy3ed852e2009-09-05 21:47:34 +00004064 if ((normal.x == 0.0) && (normal.y == 0.0))
4065 shade=light.z;
4066 else
4067 {
4068 shade=0.0;
4069 distance=normal.x*light.x+normal.y*light.y+normal.z*light.z;
4070 if (distance > MagickEpsilon)
4071 {
4072 normal_distance=
4073 normal.x*normal.x+normal.y*normal.y+normal.z*normal.z;
4074 if (normal_distance > (MagickEpsilon*MagickEpsilon))
4075 shade=distance/sqrt((double) normal_distance);
4076 }
4077 }
4078 if (gray != MagickFalse)
4079 {
cristy4c08aed2011-07-01 19:47:50 +00004080 SetPixelRed(shade_image,ClampToQuantum(shade),q);
4081 SetPixelGreen(shade_image,ClampToQuantum(shade),q);
4082 SetPixelBlue(shade_image,ClampToQuantum(shade),q);
cristy3ed852e2009-09-05 21:47:34 +00004083 }
4084 else
4085 {
cristy4c08aed2011-07-01 19:47:50 +00004086 SetPixelRed(shade_image,ClampToQuantum(QuantumScale*shade*
4087 GetPixelRed(image,s1)),q);
4088 SetPixelGreen(shade_image,ClampToQuantum(QuantumScale*shade*
4089 GetPixelGreen(image,s1)),q);
4090 SetPixelBlue(shade_image,ClampToQuantum(QuantumScale*shade*
4091 GetPixelBlue(image,s1)),q);
cristy3ed852e2009-09-05 21:47:34 +00004092 }
cristy4c08aed2011-07-01 19:47:50 +00004093 SetPixelAlpha(shade_image,GetPixelAlpha(image,s1),q);
4094 s0+=GetPixelChannels(image);
4095 s1+=GetPixelChannels(image);
4096 s2+=GetPixelChannels(image);
4097 q+=GetPixelChannels(shade_image);
cristy3ed852e2009-09-05 21:47:34 +00004098 }
4099 if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
4100 status=MagickFalse;
4101 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4102 {
4103 MagickBooleanType
4104 proceed;
4105
cristyb5d5f722009-11-04 03:03:49 +00004106#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004107 #pragma omp critical (MagickCore_ShadeImage)
4108#endif
4109 proceed=SetImageProgress(image,ShadeImageTag,progress++,image->rows);
4110 if (proceed == MagickFalse)
4111 status=MagickFalse;
4112 }
4113 }
4114 shade_view=DestroyCacheView(shade_view);
4115 image_view=DestroyCacheView(image_view);
4116 if (status == MagickFalse)
4117 shade_image=DestroyImage(shade_image);
4118 return(shade_image);
4119}
4120
4121/*
4122%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4123% %
4124% %
4125% %
4126% S h a r p e n I m a g e %
4127% %
4128% %
4129% %
4130%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4131%
4132% SharpenImage() sharpens the image. We convolve the image with a Gaussian
4133% operator of the given radius and standard deviation (sigma). For
4134% reasonable results, radius should be larger than sigma. Use a radius of 0
4135% and SharpenImage() selects a suitable radius for you.
4136%
4137% Using a separable kernel would be faster, but the negative weights cancel
4138% out on the corners of the kernel producing often undesirable ringing in the
4139% filtered result; this can be avoided by using a 2D gaussian shaped image
4140% sharpening kernel instead.
4141%
4142% The format of the SharpenImage method is:
4143%
4144% Image *SharpenImage(const Image *image,const double radius,
4145% const double sigma,ExceptionInfo *exception)
4146% Image *SharpenImageChannel(const Image *image,const ChannelType channel,
4147% const double radius,const double sigma,ExceptionInfo *exception)
4148%
4149% A description of each parameter follows:
4150%
4151% o image: the image.
4152%
4153% o channel: the channel type.
4154%
4155% o radius: the radius of the Gaussian, in pixels, not counting the center
4156% pixel.
4157%
4158% o sigma: the standard deviation of the Laplacian, in pixels.
4159%
4160% o exception: return any errors or warnings in this structure.
4161%
4162*/
4163
4164MagickExport Image *SharpenImage(const Image *image,const double radius,
4165 const double sigma,ExceptionInfo *exception)
4166{
4167 Image
4168 *sharp_image;
4169
4170 sharp_image=SharpenImageChannel(image,DefaultChannels,radius,sigma,exception);
4171 return(sharp_image);
4172}
4173
4174MagickExport Image *SharpenImageChannel(const Image *image,
4175 const ChannelType channel,const double radius,const double sigma,
4176 ExceptionInfo *exception)
4177{
4178 double
cristy47e00502009-12-17 19:19:57 +00004179 *kernel,
4180 normalize;
cristy3ed852e2009-09-05 21:47:34 +00004181
4182 Image
4183 *sharp_image;
4184
cristybb503372010-05-27 20:51:26 +00004185 register ssize_t
cristy47e00502009-12-17 19:19:57 +00004186 i;
4187
cristybb503372010-05-27 20:51:26 +00004188 size_t
cristy3ed852e2009-09-05 21:47:34 +00004189 width;
4190
cristy117ff172010-08-15 21:35:32 +00004191 ssize_t
4192 j,
4193 u,
4194 v;
4195
cristy3ed852e2009-09-05 21:47:34 +00004196 assert(image != (const Image *) NULL);
4197 assert(image->signature == MagickSignature);
4198 if (image->debug != MagickFalse)
4199 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4200 assert(exception != (ExceptionInfo *) NULL);
4201 assert(exception->signature == MagickSignature);
4202 width=GetOptimalKernelWidth2D(radius,sigma);
4203 kernel=(double *) AcquireQuantumMemory((size_t) width*width,sizeof(*kernel));
4204 if (kernel == (double *) NULL)
4205 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy3ed852e2009-09-05 21:47:34 +00004206 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00004207 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +00004208 i=0;
4209 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00004210 {
cristy47e00502009-12-17 19:19:57 +00004211 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00004212 {
cristy4205a3c2010-09-12 20:19:59 +00004213 kernel[i]=(double) (-exp(-((double) u*u+v*v)/(2.0*MagickSigma*
4214 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00004215 normalize+=kernel[i];
4216 i++;
4217 }
4218 }
4219 kernel[i/2]=(double) ((-2.0)*normalize);
4220 sharp_image=ConvolveImageChannel(image,channel,width,kernel,exception);
4221 kernel=(double *) RelinquishMagickMemory(kernel);
4222 return(sharp_image);
4223}
4224
4225/*
4226%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4227% %
4228% %
4229% %
4230% S p r e a d I m a g e %
4231% %
4232% %
4233% %
4234%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4235%
4236% SpreadImage() is a special effects method that randomly displaces each
4237% pixel in a block defined by the radius parameter.
4238%
4239% The format of the SpreadImage method is:
4240%
4241% Image *SpreadImage(const Image *image,const double radius,
4242% ExceptionInfo *exception)
4243%
4244% A description of each parameter follows:
4245%
4246% o image: the image.
4247%
4248% o radius: Choose a random pixel in a neighborhood of this extent.
4249%
4250% o exception: return any errors or warnings in this structure.
4251%
4252*/
4253MagickExport Image *SpreadImage(const Image *image,const double radius,
4254 ExceptionInfo *exception)
4255{
4256#define SpreadImageTag "Spread/Image"
4257
cristyfa112112010-01-04 17:48:07 +00004258 CacheView
cristy9f7e7cb2011-03-26 00:49:57 +00004259 *image_view,
4260 *spread_view;
cristyfa112112010-01-04 17:48:07 +00004261
cristy3ed852e2009-09-05 21:47:34 +00004262 Image
4263 *spread_image;
4264
cristy3ed852e2009-09-05 21:47:34 +00004265 MagickBooleanType
4266 status;
4267
cristybb503372010-05-27 20:51:26 +00004268 MagickOffsetType
4269 progress;
4270
cristy4c08aed2011-07-01 19:47:50 +00004271 PixelInfo
cristyddd82202009-11-03 20:14:50 +00004272 bias;
cristy3ed852e2009-09-05 21:47:34 +00004273
4274 RandomInfo
cristyfa112112010-01-04 17:48:07 +00004275 **restrict random_info;
cristy3ed852e2009-09-05 21:47:34 +00004276
cristybb503372010-05-27 20:51:26 +00004277 size_t
cristy3ed852e2009-09-05 21:47:34 +00004278 width;
4279
cristybb503372010-05-27 20:51:26 +00004280 ssize_t
4281 y;
4282
cristy3ed852e2009-09-05 21:47:34 +00004283 /*
4284 Initialize spread image attributes.
4285 */
4286 assert(image != (Image *) NULL);
4287 assert(image->signature == MagickSignature);
4288 if (image->debug != MagickFalse)
4289 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4290 assert(exception != (ExceptionInfo *) NULL);
4291 assert(exception->signature == MagickSignature);
4292 spread_image=CloneImage(image,image->columns,image->rows,MagickTrue,
4293 exception);
4294 if (spread_image == (Image *) NULL)
4295 return((Image *) NULL);
4296 if (SetImageStorageClass(spread_image,DirectClass) == MagickFalse)
4297 {
4298 InheritException(exception,&spread_image->exception);
4299 spread_image=DestroyImage(spread_image);
4300 return((Image *) NULL);
4301 }
4302 /*
4303 Spread image.
4304 */
4305 status=MagickTrue;
4306 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00004307 GetPixelInfo(spread_image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00004308 width=GetOptimalKernelWidth1D(radius,0.5);
cristy3ed852e2009-09-05 21:47:34 +00004309 random_info=AcquireRandomInfoThreadSet();
cristy9f7e7cb2011-03-26 00:49:57 +00004310 image_view=AcquireCacheView(image);
4311 spread_view=AcquireCacheView(spread_image);
cristyb557a152011-02-22 12:14:30 +00004312#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00004313 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00004314#endif
cristybb503372010-05-27 20:51:26 +00004315 for (y=0; y < (ssize_t) spread_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00004316 {
cristy5c9e6f22010-09-17 17:31:01 +00004317 const int
4318 id = GetOpenMPThreadId();
cristy6ebe97c2010-07-03 01:17:28 +00004319
cristy4c08aed2011-07-01 19:47:50 +00004320 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00004321 pixel;
4322
cristy4c08aed2011-07-01 19:47:50 +00004323 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00004324 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004325
cristy117ff172010-08-15 21:35:32 +00004326 register ssize_t
4327 x;
4328
cristy3ed852e2009-09-05 21:47:34 +00004329 if (status == MagickFalse)
4330 continue;
cristy9f7e7cb2011-03-26 00:49:57 +00004331 q=QueueCacheViewAuthenticPixels(spread_view,0,y,spread_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +00004332 exception);
cristy4c08aed2011-07-01 19:47:50 +00004333 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00004334 {
4335 status=MagickFalse;
4336 continue;
4337 }
cristyddd82202009-11-03 20:14:50 +00004338 pixel=bias;
cristybb503372010-05-27 20:51:26 +00004339 for (x=0; x < (ssize_t) spread_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004340 {
cristy4c08aed2011-07-01 19:47:50 +00004341 (void) InterpolatePixelInfo(image,image_view,
cristy8a7c3e82011-03-26 02:10:53 +00004342 UndefinedInterpolatePixel,(double) x+width*(GetPseudoRandomValue(
4343 random_info[id])-0.5),(double) y+width*(GetPseudoRandomValue(
4344 random_info[id])-0.5),&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00004345 SetPixelPixelInfo(spread_image,&pixel,q);
4346 q+=GetPixelChannels(spread_image);
cristy3ed852e2009-09-05 21:47:34 +00004347 }
cristy9f7e7cb2011-03-26 00:49:57 +00004348 if (SyncCacheViewAuthenticPixels(spread_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00004349 status=MagickFalse;
4350 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4351 {
4352 MagickBooleanType
4353 proceed;
4354
cristyb557a152011-02-22 12:14:30 +00004355#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004356 #pragma omp critical (MagickCore_SpreadImage)
4357#endif
4358 proceed=SetImageProgress(image,SpreadImageTag,progress++,image->rows);
4359 if (proceed == MagickFalse)
4360 status=MagickFalse;
4361 }
4362 }
cristy9f7e7cb2011-03-26 00:49:57 +00004363 spread_view=DestroyCacheView(spread_view);
cristy3ed852e2009-09-05 21:47:34 +00004364 image_view=DestroyCacheView(image_view);
4365 random_info=DestroyRandomInfoThreadSet(random_info);
cristy3ed852e2009-09-05 21:47:34 +00004366 return(spread_image);
4367}
4368
4369/*
4370%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4371% %
4372% %
4373% %
cristy0834d642011-03-18 18:26:08 +00004374% S t a t i s t i c I m a g e %
4375% %
4376% %
4377% %
4378%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4379%
4380% StatisticImage() makes each pixel the min / max / median / mode / etc. of
cristy8d752042011-03-19 01:00:36 +00004381% the neighborhood of the specified width and height.
cristy0834d642011-03-18 18:26:08 +00004382%
4383% The format of the StatisticImage method is:
4384%
4385% Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00004386% const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00004387% Image *StatisticImageChannel(const Image *image,
4388% const ChannelType channel,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00004389% const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00004390%
4391% A description of each parameter follows:
4392%
4393% o image: the image.
4394%
4395% o channel: the image channel.
4396%
4397% o type: the statistic type (median, mode, etc.).
4398%
cristy95c38342011-03-18 22:39:51 +00004399% o width: the width of the pixel neighborhood.
4400%
4401% o height: the height of the pixel neighborhood.
cristy0834d642011-03-18 18:26:08 +00004402%
4403% o exception: return any errors or warnings in this structure.
4404%
4405*/
4406
cristy733678d2011-03-18 21:29:28 +00004407#define ListChannels 5
4408
4409typedef struct _ListNode
4410{
4411 size_t
4412 next[9],
4413 count,
4414 signature;
4415} ListNode;
4416
4417typedef struct _SkipList
4418{
4419 ssize_t
4420 level;
4421
4422 ListNode
4423 *nodes;
4424} SkipList;
4425
4426typedef struct _PixelList
4427{
4428 size_t
cristy6fc86bb2011-03-18 23:45:16 +00004429 length,
cristy733678d2011-03-18 21:29:28 +00004430 seed,
4431 signature;
4432
4433 SkipList
4434 lists[ListChannels];
4435} PixelList;
4436
4437static PixelList *DestroyPixelList(PixelList *pixel_list)
4438{
4439 register ssize_t
4440 i;
4441
4442 if (pixel_list == (PixelList *) NULL)
4443 return((PixelList *) NULL);
4444 for (i=0; i < ListChannels; i++)
4445 if (pixel_list->lists[i].nodes != (ListNode *) NULL)
4446 pixel_list->lists[i].nodes=(ListNode *) RelinquishMagickMemory(
4447 pixel_list->lists[i].nodes);
4448 pixel_list=(PixelList *) RelinquishMagickMemory(pixel_list);
4449 return(pixel_list);
4450}
4451
4452static PixelList **DestroyPixelListThreadSet(PixelList **pixel_list)
4453{
4454 register ssize_t
4455 i;
4456
4457 assert(pixel_list != (PixelList **) NULL);
4458 for (i=0; i < (ssize_t) GetOpenMPMaximumThreads(); i++)
4459 if (pixel_list[i] != (PixelList *) NULL)
4460 pixel_list[i]=DestroyPixelList(pixel_list[i]);
4461 pixel_list=(PixelList **) RelinquishMagickMemory(pixel_list);
4462 return(pixel_list);
4463}
4464
cristy6fc86bb2011-03-18 23:45:16 +00004465static PixelList *AcquirePixelList(const size_t width,const size_t height)
cristy733678d2011-03-18 21:29:28 +00004466{
4467 PixelList
4468 *pixel_list;
4469
4470 register ssize_t
4471 i;
4472
4473 pixel_list=(PixelList *) AcquireMagickMemory(sizeof(*pixel_list));
4474 if (pixel_list == (PixelList *) NULL)
4475 return(pixel_list);
4476 (void) ResetMagickMemory((void *) pixel_list,0,sizeof(*pixel_list));
cristy6fc86bb2011-03-18 23:45:16 +00004477 pixel_list->length=width*height;
cristy733678d2011-03-18 21:29:28 +00004478 for (i=0; i < ListChannels; i++)
4479 {
4480 pixel_list->lists[i].nodes=(ListNode *) AcquireQuantumMemory(65537UL,
4481 sizeof(*pixel_list->lists[i].nodes));
4482 if (pixel_list->lists[i].nodes == (ListNode *) NULL)
4483 return(DestroyPixelList(pixel_list));
4484 (void) ResetMagickMemory(pixel_list->lists[i].nodes,0,65537UL*
4485 sizeof(*pixel_list->lists[i].nodes));
4486 }
4487 pixel_list->signature=MagickSignature;
4488 return(pixel_list);
4489}
4490
cristy6fc86bb2011-03-18 23:45:16 +00004491static PixelList **AcquirePixelListThreadSet(const size_t width,
4492 const size_t height)
cristy733678d2011-03-18 21:29:28 +00004493{
4494 PixelList
4495 **pixel_list;
4496
4497 register ssize_t
4498 i;
4499
4500 size_t
4501 number_threads;
4502
4503 number_threads=GetOpenMPMaximumThreads();
4504 pixel_list=(PixelList **) AcquireQuantumMemory(number_threads,
4505 sizeof(*pixel_list));
4506 if (pixel_list == (PixelList **) NULL)
4507 return((PixelList **) NULL);
4508 (void) ResetMagickMemory(pixel_list,0,number_threads*sizeof(*pixel_list));
4509 for (i=0; i < (ssize_t) number_threads; i++)
4510 {
cristy6fc86bb2011-03-18 23:45:16 +00004511 pixel_list[i]=AcquirePixelList(width,height);
cristy733678d2011-03-18 21:29:28 +00004512 if (pixel_list[i] == (PixelList *) NULL)
4513 return(DestroyPixelListThreadSet(pixel_list));
4514 }
4515 return(pixel_list);
4516}
4517
4518static void AddNodePixelList(PixelList *pixel_list,const ssize_t channel,
4519 const size_t color)
4520{
4521 register SkipList
4522 *list;
4523
4524 register ssize_t
4525 level;
4526
4527 size_t
4528 search,
4529 update[9];
4530
4531 /*
4532 Initialize the node.
4533 */
4534 list=pixel_list->lists+channel;
4535 list->nodes[color].signature=pixel_list->signature;
4536 list->nodes[color].count=1;
4537 /*
4538 Determine where it belongs in the list.
4539 */
4540 search=65536UL;
4541 for (level=list->level; level >= 0; level--)
4542 {
4543 while (list->nodes[search].next[level] < color)
4544 search=list->nodes[search].next[level];
4545 update[level]=search;
4546 }
4547 /*
4548 Generate a pseudo-random level for this node.
4549 */
4550 for (level=0; ; level++)
4551 {
4552 pixel_list->seed=(pixel_list->seed*42893621L)+1L;
4553 if ((pixel_list->seed & 0x300) != 0x300)
4554 break;
4555 }
4556 if (level > 8)
4557 level=8;
4558 if (level > (list->level+2))
4559 level=list->level+2;
4560 /*
4561 If we're raising the list's level, link back to the root node.
4562 */
4563 while (level > list->level)
4564 {
4565 list->level++;
4566 update[list->level]=65536UL;
4567 }
4568 /*
4569 Link the node into the skip-list.
4570 */
4571 do
4572 {
4573 list->nodes[color].next[level]=list->nodes[update[level]].next[level];
4574 list->nodes[update[level]].next[level]=color;
cristy3cba8ca2011-03-19 01:29:12 +00004575 } while (level-- > 0);
cristy733678d2011-03-18 21:29:28 +00004576}
4577
cristy4c08aed2011-07-01 19:47:50 +00004578static PixelInfo GetMaximumPixelList(PixelList *pixel_list)
cristy6fc86bb2011-03-18 23:45:16 +00004579{
cristy4c08aed2011-07-01 19:47:50 +00004580 PixelInfo
cristy6fc86bb2011-03-18 23:45:16 +00004581 pixel;
4582
4583 register SkipList
4584 *list;
4585
4586 register ssize_t
4587 channel;
4588
4589 size_t
cristyd76c51e2011-03-26 00:21:26 +00004590 color,
4591 maximum;
cristy49f37242011-03-22 18:18:23 +00004592
4593 ssize_t
cristy6fc86bb2011-03-18 23:45:16 +00004594 count;
4595
4596 unsigned short
cristyd76c51e2011-03-26 00:21:26 +00004597 channels[ListChannels];
cristy6fc86bb2011-03-18 23:45:16 +00004598
4599 /*
4600 Find the maximum value for each of the color.
4601 */
4602 for (channel=0; channel < 5; channel++)
4603 {
4604 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004605 color=65536L;
cristy6fc86bb2011-03-18 23:45:16 +00004606 count=0;
cristy49f37242011-03-22 18:18:23 +00004607 maximum=list->nodes[color].next[0];
cristy6fc86bb2011-03-18 23:45:16 +00004608 do
4609 {
4610 color=list->nodes[color].next[0];
cristy49f37242011-03-22 18:18:23 +00004611 if (color > maximum)
4612 maximum=color;
cristy6fc86bb2011-03-18 23:45:16 +00004613 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004614 } while (count < (ssize_t) pixel_list->length);
cristy49f37242011-03-22 18:18:23 +00004615 channels[channel]=(unsigned short) maximum;
4616 }
cristy4c08aed2011-07-01 19:47:50 +00004617 GetPixelInfo((const Image *) NULL,&pixel);
cristy49f37242011-03-22 18:18:23 +00004618 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4619 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4620 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004621 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4622 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy49f37242011-03-22 18:18:23 +00004623 return(pixel);
4624}
4625
cristy4c08aed2011-07-01 19:47:50 +00004626static PixelInfo GetMeanPixelList(PixelList *pixel_list)
cristy49f37242011-03-22 18:18:23 +00004627{
cristy4c08aed2011-07-01 19:47:50 +00004628 PixelInfo
cristy49f37242011-03-22 18:18:23 +00004629 pixel;
4630
cristy80a99a32011-03-30 01:30:23 +00004631 MagickRealType
4632 sum;
4633
cristy49f37242011-03-22 18:18:23 +00004634 register SkipList
4635 *list;
4636
4637 register ssize_t
4638 channel;
4639
4640 size_t
cristy80a99a32011-03-30 01:30:23 +00004641 color;
cristy49f37242011-03-22 18:18:23 +00004642
4643 ssize_t
4644 count;
4645
4646 unsigned short
4647 channels[ListChannels];
4648
4649 /*
4650 Find the mean value for each of the color.
4651 */
4652 for (channel=0; channel < 5; channel++)
4653 {
4654 list=pixel_list->lists+channel;
4655 color=65536L;
4656 count=0;
cristy80a99a32011-03-30 01:30:23 +00004657 sum=0.0;
cristy49f37242011-03-22 18:18:23 +00004658 do
4659 {
4660 color=list->nodes[color].next[0];
cristy80a99a32011-03-30 01:30:23 +00004661 sum+=(MagickRealType) list->nodes[color].count*color;
cristy49f37242011-03-22 18:18:23 +00004662 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004663 } while (count < (ssize_t) pixel_list->length);
cristy80a99a32011-03-30 01:30:23 +00004664 sum/=pixel_list->length;
4665 channels[channel]=(unsigned short) sum;
cristy6fc86bb2011-03-18 23:45:16 +00004666 }
cristy4c08aed2011-07-01 19:47:50 +00004667 GetPixelInfo((const Image *) NULL,&pixel);
cristy6fc86bb2011-03-18 23:45:16 +00004668 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4669 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4670 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004671 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4672 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy6fc86bb2011-03-18 23:45:16 +00004673 return(pixel);
4674}
4675
cristy4c08aed2011-07-01 19:47:50 +00004676static PixelInfo GetMedianPixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004677{
cristy4c08aed2011-07-01 19:47:50 +00004678 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004679 pixel;
4680
4681 register SkipList
4682 *list;
4683
4684 register ssize_t
4685 channel;
4686
4687 size_t
cristy49f37242011-03-22 18:18:23 +00004688 color;
4689
4690 ssize_t
cristy733678d2011-03-18 21:29:28 +00004691 count;
4692
4693 unsigned short
4694 channels[ListChannels];
4695
4696 /*
4697 Find the median value for each of the color.
4698 */
cristy733678d2011-03-18 21:29:28 +00004699 for (channel=0; channel < 5; channel++)
4700 {
4701 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004702 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004703 count=0;
4704 do
4705 {
4706 color=list->nodes[color].next[0];
4707 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004708 } while (count <= (ssize_t) (pixel_list->length >> 1));
cristy6fc86bb2011-03-18 23:45:16 +00004709 channels[channel]=(unsigned short) color;
4710 }
cristy4c08aed2011-07-01 19:47:50 +00004711 GetPixelInfo((const Image *) NULL,&pixel);
cristy6fc86bb2011-03-18 23:45:16 +00004712 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4713 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4714 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004715 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4716 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy6fc86bb2011-03-18 23:45:16 +00004717 return(pixel);
4718}
4719
cristy4c08aed2011-07-01 19:47:50 +00004720static PixelInfo GetMinimumPixelList(PixelList *pixel_list)
cristy6fc86bb2011-03-18 23:45:16 +00004721{
cristy4c08aed2011-07-01 19:47:50 +00004722 PixelInfo
cristy6fc86bb2011-03-18 23:45:16 +00004723 pixel;
4724
4725 register SkipList
4726 *list;
4727
4728 register ssize_t
4729 channel;
4730
4731 size_t
cristyd76c51e2011-03-26 00:21:26 +00004732 color,
4733 minimum;
cristy6fc86bb2011-03-18 23:45:16 +00004734
cristy49f37242011-03-22 18:18:23 +00004735 ssize_t
4736 count;
4737
cristy6fc86bb2011-03-18 23:45:16 +00004738 unsigned short
cristyd76c51e2011-03-26 00:21:26 +00004739 channels[ListChannels];
cristy6fc86bb2011-03-18 23:45:16 +00004740
4741 /*
4742 Find the minimum value for each of the color.
4743 */
4744 for (channel=0; channel < 5; channel++)
4745 {
4746 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004747 count=0;
cristy6fc86bb2011-03-18 23:45:16 +00004748 color=65536UL;
cristy49f37242011-03-22 18:18:23 +00004749 minimum=list->nodes[color].next[0];
4750 do
4751 {
4752 color=list->nodes[color].next[0];
4753 if (color < minimum)
4754 minimum=color;
4755 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004756 } while (count < (ssize_t) pixel_list->length);
cristy49f37242011-03-22 18:18:23 +00004757 channels[channel]=(unsigned short) minimum;
cristy733678d2011-03-18 21:29:28 +00004758 }
cristy4c08aed2011-07-01 19:47:50 +00004759 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004760 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4761 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4762 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004763 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4764 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy733678d2011-03-18 21:29:28 +00004765 return(pixel);
4766}
4767
cristy4c08aed2011-07-01 19:47:50 +00004768static PixelInfo GetModePixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004769{
cristy4c08aed2011-07-01 19:47:50 +00004770 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004771 pixel;
4772
4773 register SkipList
4774 *list;
4775
4776 register ssize_t
4777 channel;
4778
4779 size_t
4780 color,
cristy733678d2011-03-18 21:29:28 +00004781 max_count,
cristy6fc86bb2011-03-18 23:45:16 +00004782 mode;
cristy733678d2011-03-18 21:29:28 +00004783
cristy49f37242011-03-22 18:18:23 +00004784 ssize_t
4785 count;
4786
cristy733678d2011-03-18 21:29:28 +00004787 unsigned short
4788 channels[5];
4789
4790 /*
glennrp30d2dc62011-06-25 03:17:16 +00004791 Make each pixel the 'predominant color' of the specified neighborhood.
cristy733678d2011-03-18 21:29:28 +00004792 */
cristy733678d2011-03-18 21:29:28 +00004793 for (channel=0; channel < 5; channel++)
4794 {
4795 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004796 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004797 mode=color;
4798 max_count=list->nodes[mode].count;
4799 count=0;
4800 do
4801 {
4802 color=list->nodes[color].next[0];
4803 if (list->nodes[color].count > max_count)
4804 {
4805 mode=color;
4806 max_count=list->nodes[mode].count;
4807 }
4808 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004809 } while (count < (ssize_t) pixel_list->length);
cristy733678d2011-03-18 21:29:28 +00004810 channels[channel]=(unsigned short) mode;
4811 }
cristy4c08aed2011-07-01 19:47:50 +00004812 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004813 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4814 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4815 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004816 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4817 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy733678d2011-03-18 21:29:28 +00004818 return(pixel);
4819}
4820
cristy4c08aed2011-07-01 19:47:50 +00004821static PixelInfo GetNonpeakPixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004822{
cristy4c08aed2011-07-01 19:47:50 +00004823 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004824 pixel;
4825
4826 register SkipList
4827 *list;
4828
4829 register ssize_t
4830 channel;
4831
4832 size_t
cristy733678d2011-03-18 21:29:28 +00004833 color,
cristy733678d2011-03-18 21:29:28 +00004834 next,
4835 previous;
4836
cristy49f37242011-03-22 18:18:23 +00004837 ssize_t
4838 count;
4839
cristy733678d2011-03-18 21:29:28 +00004840 unsigned short
4841 channels[5];
4842
4843 /*
cristy49f37242011-03-22 18:18:23 +00004844 Finds the non peak value for each of the colors.
cristy733678d2011-03-18 21:29:28 +00004845 */
cristy733678d2011-03-18 21:29:28 +00004846 for (channel=0; channel < 5; channel++)
4847 {
4848 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004849 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004850 next=list->nodes[color].next[0];
4851 count=0;
4852 do
4853 {
4854 previous=color;
4855 color=next;
4856 next=list->nodes[color].next[0];
4857 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004858 } while (count <= (ssize_t) (pixel_list->length >> 1));
cristy733678d2011-03-18 21:29:28 +00004859 if ((previous == 65536UL) && (next != 65536UL))
4860 color=next;
4861 else
4862 if ((previous != 65536UL) && (next == 65536UL))
4863 color=previous;
4864 channels[channel]=(unsigned short) color;
4865 }
cristy4c08aed2011-07-01 19:47:50 +00004866 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004867 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4868 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4869 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004870 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4871 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy733678d2011-03-18 21:29:28 +00004872 return(pixel);
4873}
4874
cristy4c08aed2011-07-01 19:47:50 +00004875static PixelInfo GetStandardDeviationPixelList(PixelList *pixel_list)
cristy9a68cbb2011-03-29 00:51:23 +00004876{
cristy4c08aed2011-07-01 19:47:50 +00004877 PixelInfo
cristy9a68cbb2011-03-29 00:51:23 +00004878 pixel;
4879
cristy80a99a32011-03-30 01:30:23 +00004880 MagickRealType
4881 sum,
4882 sum_squared;
4883
cristy9a68cbb2011-03-29 00:51:23 +00004884 register SkipList
4885 *list;
4886
4887 register ssize_t
4888 channel;
4889
4890 size_t
cristy80a99a32011-03-30 01:30:23 +00004891 color;
cristy9a68cbb2011-03-29 00:51:23 +00004892
4893 ssize_t
4894 count;
4895
4896 unsigned short
4897 channels[ListChannels];
4898
4899 /*
cristy80a99a32011-03-30 01:30:23 +00004900 Find the standard-deviation value for each of the color.
cristy9a68cbb2011-03-29 00:51:23 +00004901 */
4902 for (channel=0; channel < 5; channel++)
4903 {
4904 list=pixel_list->lists+channel;
4905 color=65536L;
4906 count=0;
cristy80a99a32011-03-30 01:30:23 +00004907 sum=0.0;
4908 sum_squared=0.0;
cristy9a68cbb2011-03-29 00:51:23 +00004909 do
4910 {
cristy80a99a32011-03-30 01:30:23 +00004911 register ssize_t
4912 i;
4913
cristy9a68cbb2011-03-29 00:51:23 +00004914 color=list->nodes[color].next[0];
cristy80a99a32011-03-30 01:30:23 +00004915 sum+=(MagickRealType) list->nodes[color].count*color;
4916 for (i=0; i < (ssize_t) list->nodes[color].count; i++)
4917 sum_squared+=((MagickRealType) color)*((MagickRealType) color);
cristy9a68cbb2011-03-29 00:51:23 +00004918 count+=list->nodes[color].count;
4919 } while (count < (ssize_t) pixel_list->length);
cristy80a99a32011-03-30 01:30:23 +00004920 sum/=pixel_list->length;
4921 sum_squared/=pixel_list->length;
4922 channels[channel]=(unsigned short) sqrt(sum_squared-(sum*sum));
cristy9a68cbb2011-03-29 00:51:23 +00004923 }
cristy4c08aed2011-07-01 19:47:50 +00004924 GetPixelInfo((const Image *) NULL,&pixel);
cristy9a68cbb2011-03-29 00:51:23 +00004925 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4926 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4927 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004928 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4929 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy9a68cbb2011-03-29 00:51:23 +00004930 return(pixel);
4931}
4932
cristy4c08aed2011-07-01 19:47:50 +00004933static inline void InsertPixelList(const Image *image,const Quantum *pixel,
4934 PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004935{
4936 size_t
4937 signature;
4938
4939 unsigned short
4940 index;
4941
cristy4c08aed2011-07-01 19:47:50 +00004942 index=ScaleQuantumToShort(GetPixelRed(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004943 signature=pixel_list->lists[0].nodes[index].signature;
4944 if (signature == pixel_list->signature)
4945 pixel_list->lists[0].nodes[index].count++;
4946 else
4947 AddNodePixelList(pixel_list,0,index);
cristy4c08aed2011-07-01 19:47:50 +00004948 index=ScaleQuantumToShort(GetPixelGreen(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004949 signature=pixel_list->lists[1].nodes[index].signature;
4950 if (signature == pixel_list->signature)
4951 pixel_list->lists[1].nodes[index].count++;
4952 else
4953 AddNodePixelList(pixel_list,1,index);
cristy4c08aed2011-07-01 19:47:50 +00004954 index=ScaleQuantumToShort(GetPixelBlue(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004955 signature=pixel_list->lists[2].nodes[index].signature;
4956 if (signature == pixel_list->signature)
4957 pixel_list->lists[2].nodes[index].count++;
4958 else
4959 AddNodePixelList(pixel_list,2,index);
cristy4c08aed2011-07-01 19:47:50 +00004960 index=ScaleQuantumToShort(GetPixelAlpha(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004961 signature=pixel_list->lists[3].nodes[index].signature;
4962 if (signature == pixel_list->signature)
4963 pixel_list->lists[3].nodes[index].count++;
4964 else
4965 AddNodePixelList(pixel_list,3,index);
4966 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00004967 index=ScaleQuantumToShort(GetPixelBlack(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004968 signature=pixel_list->lists[4].nodes[index].signature;
4969 if (signature == pixel_list->signature)
4970 pixel_list->lists[4].nodes[index].count++;
4971 else
4972 AddNodePixelList(pixel_list,4,index);
4973}
4974
cristy80c99742011-04-04 14:46:39 +00004975static inline MagickRealType MagickAbsoluteValue(const MagickRealType x)
4976{
4977 if (x < 0)
4978 return(-x);
4979 return(x);
4980}
4981
cristy733678d2011-03-18 21:29:28 +00004982static void ResetPixelList(PixelList *pixel_list)
4983{
4984 int
4985 level;
4986
4987 register ListNode
4988 *root;
4989
4990 register SkipList
4991 *list;
4992
4993 register ssize_t
4994 channel;
4995
4996 /*
4997 Reset the skip-list.
4998 */
4999 for (channel=0; channel < 5; channel++)
5000 {
5001 list=pixel_list->lists+channel;
5002 root=list->nodes+65536UL;
5003 list->level=0;
5004 for (level=0; level < 9; level++)
5005 root->next[level]=65536UL;
5006 }
5007 pixel_list->seed=pixel_list->signature++;
5008}
5009
cristy0834d642011-03-18 18:26:08 +00005010MagickExport Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00005011 const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00005012{
cristy95c38342011-03-18 22:39:51 +00005013 Image
5014 *statistic_image;
5015
5016 statistic_image=StatisticImageChannel(image,DefaultChannels,type,width,
5017 height,exception);
5018 return(statistic_image);
cristy0834d642011-03-18 18:26:08 +00005019}
5020
5021MagickExport Image *StatisticImageChannel(const Image *image,
cristy95c38342011-03-18 22:39:51 +00005022 const ChannelType channel,const StatisticType type,const size_t width,
5023 const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00005024{
cristy3cba8ca2011-03-19 01:29:12 +00005025#define StatisticWidth \
cristyd76c51e2011-03-26 00:21:26 +00005026 (width == 0 ? GetOptimalKernelWidth2D((double) width,0.5) : width)
cristy3cba8ca2011-03-19 01:29:12 +00005027#define StatisticHeight \
cristyd76c51e2011-03-26 00:21:26 +00005028 (height == 0 ? GetOptimalKernelWidth2D((double) height,0.5) : height)
cristy0834d642011-03-18 18:26:08 +00005029#define StatisticImageTag "Statistic/Image"
5030
5031 CacheView
5032 *image_view,
5033 *statistic_view;
5034
5035 Image
5036 *statistic_image;
5037
5038 MagickBooleanType
5039 status;
5040
5041 MagickOffsetType
5042 progress;
5043
5044 PixelList
5045 **restrict pixel_list;
5046
cristy0834d642011-03-18 18:26:08 +00005047 ssize_t
5048 y;
5049
5050 /*
5051 Initialize statistics image attributes.
5052 */
5053 assert(image != (Image *) NULL);
5054 assert(image->signature == MagickSignature);
5055 if (image->debug != MagickFalse)
5056 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5057 assert(exception != (ExceptionInfo *) NULL);
5058 assert(exception->signature == MagickSignature);
cristy0834d642011-03-18 18:26:08 +00005059 statistic_image=CloneImage(image,image->columns,image->rows,MagickTrue,
5060 exception);
5061 if (statistic_image == (Image *) NULL)
5062 return((Image *) NULL);
5063 if (SetImageStorageClass(statistic_image,DirectClass) == MagickFalse)
5064 {
5065 InheritException(exception,&statistic_image->exception);
5066 statistic_image=DestroyImage(statistic_image);
5067 return((Image *) NULL);
5068 }
cristy6fc86bb2011-03-18 23:45:16 +00005069 pixel_list=AcquirePixelListThreadSet(StatisticWidth,StatisticHeight);
cristy0834d642011-03-18 18:26:08 +00005070 if (pixel_list == (PixelList **) NULL)
5071 {
5072 statistic_image=DestroyImage(statistic_image);
5073 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
5074 }
5075 /*
cristy8d752042011-03-19 01:00:36 +00005076 Make each pixel the min / max / median / mode / etc. of the neighborhood.
cristy0834d642011-03-18 18:26:08 +00005077 */
5078 status=MagickTrue;
5079 progress=0;
5080 image_view=AcquireCacheView(image);
5081 statistic_view=AcquireCacheView(statistic_image);
5082#if defined(MAGICKCORE_OPENMP_SUPPORT)
5083 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
5084#endif
5085 for (y=0; y < (ssize_t) statistic_image->rows; y++)
5086 {
5087 const int
5088 id = GetOpenMPThreadId();
5089
cristy4c08aed2011-07-01 19:47:50 +00005090 register const Quantum
cristy0834d642011-03-18 18:26:08 +00005091 *restrict p;
5092
cristy4c08aed2011-07-01 19:47:50 +00005093 register Quantum
cristy0834d642011-03-18 18:26:08 +00005094 *restrict q;
5095
5096 register ssize_t
5097 x;
5098
5099 if (status == MagickFalse)
5100 continue;
cristy6fc86bb2011-03-18 23:45:16 +00005101 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) StatisticWidth/2L),y-
5102 (ssize_t) (StatisticHeight/2L),image->columns+StatisticWidth,
5103 StatisticHeight,exception);
cristy3cba8ca2011-03-19 01:29:12 +00005104 q=QueueCacheViewAuthenticPixels(statistic_view,0,y,statistic_image->columns, 1,exception);
cristy4c08aed2011-07-01 19:47:50 +00005105 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy0834d642011-03-18 18:26:08 +00005106 {
5107 status=MagickFalse;
5108 continue;
5109 }
cristy0834d642011-03-18 18:26:08 +00005110 for (x=0; x < (ssize_t) statistic_image->columns; x++)
5111 {
cristy4c08aed2011-07-01 19:47:50 +00005112 PixelInfo
cristy0834d642011-03-18 18:26:08 +00005113 pixel;
5114
cristy4c08aed2011-07-01 19:47:50 +00005115 register const Quantum
cristy6e3026a2011-03-19 00:54:38 +00005116 *restrict r;
5117
cristy0834d642011-03-18 18:26:08 +00005118 register ssize_t
5119 u,
5120 v;
5121
5122 r=p;
cristy0834d642011-03-18 18:26:08 +00005123 ResetPixelList(pixel_list[id]);
cristy6e4c3292011-03-19 00:53:55 +00005124 for (v=0; v < (ssize_t) StatisticHeight; v++)
cristy0834d642011-03-18 18:26:08 +00005125 {
cristy6e4c3292011-03-19 00:53:55 +00005126 for (u=0; u < (ssize_t) StatisticWidth; u++)
cristy4c08aed2011-07-01 19:47:50 +00005127 InsertPixelList(image,r+u*GetPixelChannels(image),pixel_list[id]);
5128 r+=(image->columns+StatisticWidth)*GetPixelChannels(image);
cristy0834d642011-03-18 18:26:08 +00005129 }
cristy4c08aed2011-07-01 19:47:50 +00005130 GetPixelInfo(image,&pixel);
cristy490408a2011-07-07 14:42:05 +00005131 SetPixelInfo(image,p+(StatisticWidth*StatisticHeight/2)*
5132 GetPixelChannels(image),&pixel);
cristy0834d642011-03-18 18:26:08 +00005133 switch (type)
5134 {
cristy80c99742011-04-04 14:46:39 +00005135 case GradientStatistic:
5136 {
cristy4c08aed2011-07-01 19:47:50 +00005137 PixelInfo
cristy80c99742011-04-04 14:46:39 +00005138 maximum,
5139 minimum;
5140
5141 minimum=GetMinimumPixelList(pixel_list[id]);
5142 maximum=GetMaximumPixelList(pixel_list[id]);
5143 pixel.red=MagickAbsoluteValue(maximum.red-minimum.red);
5144 pixel.green=MagickAbsoluteValue(maximum.green-minimum.green);
5145 pixel.blue=MagickAbsoluteValue(maximum.blue-minimum.blue);
cristy4c08aed2011-07-01 19:47:50 +00005146 pixel.alpha=MagickAbsoluteValue(maximum.alpha-minimum.alpha);
cristy80c99742011-04-04 14:46:39 +00005147 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00005148 pixel.black=MagickAbsoluteValue(maximum.black-minimum.black);
cristy80c99742011-04-04 14:46:39 +00005149 break;
5150 }
cristy6fc86bb2011-03-18 23:45:16 +00005151 case MaximumStatistic:
5152 {
5153 pixel=GetMaximumPixelList(pixel_list[id]);
5154 break;
5155 }
cristy49f37242011-03-22 18:18:23 +00005156 case MeanStatistic:
5157 {
5158 pixel=GetMeanPixelList(pixel_list[id]);
5159 break;
5160 }
cristyf2ad14a2011-03-18 18:57:25 +00005161 case MedianStatistic:
cristy6fc86bb2011-03-18 23:45:16 +00005162 default:
cristyf2ad14a2011-03-18 18:57:25 +00005163 {
5164 pixel=GetMedianPixelList(pixel_list[id]);
5165 break;
5166 }
cristy6fc86bb2011-03-18 23:45:16 +00005167 case MinimumStatistic:
5168 {
5169 pixel=GetMinimumPixelList(pixel_list[id]);
5170 break;
5171 }
cristyf2ad14a2011-03-18 18:57:25 +00005172 case ModeStatistic:
5173 {
5174 pixel=GetModePixelList(pixel_list[id]);
5175 break;
5176 }
5177 case NonpeakStatistic:
5178 {
5179 pixel=GetNonpeakPixelList(pixel_list[id]);
5180 break;
5181 }
cristy9a68cbb2011-03-29 00:51:23 +00005182 case StandardDeviationStatistic:
5183 {
5184 pixel=GetStandardDeviationPixelList(pixel_list[id]);
5185 break;
5186 }
cristy0834d642011-03-18 18:26:08 +00005187 }
cristy2b9582a2011-07-04 17:38:56 +00005188 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00005189 SetPixelRed(statistic_image,ClampToQuantum(pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00005190 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00005191 SetPixelGreen(statistic_image,ClampToQuantum(pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00005192 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00005193 SetPixelBlue(statistic_image,ClampToQuantum(pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00005194 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy0834d642011-03-18 18:26:08 +00005195 (image->colorspace == CMYKColorspace))
cristy490408a2011-07-07 14:42:05 +00005196 SetPixelBlack(statistic_image,ClampToQuantum(pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00005197 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00005198 (image->matte != MagickFalse))
cristy490408a2011-07-07 14:42:05 +00005199 SetPixelAlpha(statistic_image,ClampToQuantum(pixel.alpha),q);
cristy4c08aed2011-07-01 19:47:50 +00005200 p+=GetPixelChannels(image);
5201 q+=GetPixelChannels(statistic_image);
cristy0834d642011-03-18 18:26:08 +00005202 }
5203 if (SyncCacheViewAuthenticPixels(statistic_view,exception) == MagickFalse)
5204 status=MagickFalse;
5205 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5206 {
5207 MagickBooleanType
5208 proceed;
5209
5210#if defined(MAGICKCORE_OPENMP_SUPPORT)
5211 #pragma omp critical (MagickCore_StatisticImage)
5212#endif
5213 proceed=SetImageProgress(image,StatisticImageTag,progress++,
5214 image->rows);
5215 if (proceed == MagickFalse)
5216 status=MagickFalse;
5217 }
5218 }
5219 statistic_view=DestroyCacheView(statistic_view);
5220 image_view=DestroyCacheView(image_view);
5221 pixel_list=DestroyPixelListThreadSet(pixel_list);
5222 return(statistic_image);
5223}
5224
5225/*
5226%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5227% %
5228% %
5229% %
cristy3ed852e2009-09-05 21:47:34 +00005230% U n s h a r p M a s k I m a g e %
5231% %
5232% %
5233% %
5234%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5235%
5236% UnsharpMaskImage() sharpens one or more image channels. We convolve the
5237% image with a Gaussian operator of the given radius and standard deviation
5238% (sigma). For reasonable results, radius should be larger than sigma. Use a
5239% radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
5240%
5241% The format of the UnsharpMaskImage method is:
5242%
5243% Image *UnsharpMaskImage(const Image *image,const double radius,
5244% const double sigma,const double amount,const double threshold,
5245% ExceptionInfo *exception)
5246% Image *UnsharpMaskImageChannel(const Image *image,
5247% const ChannelType channel,const double radius,const double sigma,
5248% const double amount,const double threshold,ExceptionInfo *exception)
5249%
5250% A description of each parameter follows:
5251%
5252% o image: the image.
5253%
5254% o channel: the channel type.
5255%
5256% o radius: the radius of the Gaussian, in pixels, not counting the center
5257% pixel.
5258%
5259% o sigma: the standard deviation of the Gaussian, in pixels.
5260%
5261% o amount: the percentage of the difference between the original and the
5262% blur image that is added back into the original.
5263%
5264% o threshold: the threshold in pixels needed to apply the diffence amount.
5265%
5266% o exception: return any errors or warnings in this structure.
5267%
5268*/
5269
5270MagickExport Image *UnsharpMaskImage(const Image *image,const double radius,
5271 const double sigma,const double amount,const double threshold,
5272 ExceptionInfo *exception)
5273{
5274 Image
5275 *sharp_image;
5276
5277 sharp_image=UnsharpMaskImageChannel(image,DefaultChannels,radius,sigma,amount,
5278 threshold,exception);
5279 return(sharp_image);
5280}
5281
5282MagickExport Image *UnsharpMaskImageChannel(const Image *image,
5283 const ChannelType channel,const double radius,const double sigma,
5284 const double amount,const double threshold,ExceptionInfo *exception)
5285{
5286#define SharpenImageTag "Sharpen/Image"
5287
cristyc4c8d132010-01-07 01:58:38 +00005288 CacheView
5289 *image_view,
5290 *unsharp_view;
5291
cristy3ed852e2009-09-05 21:47:34 +00005292 Image
5293 *unsharp_image;
5294
cristy3ed852e2009-09-05 21:47:34 +00005295 MagickBooleanType
5296 status;
5297
cristybb503372010-05-27 20:51:26 +00005298 MagickOffsetType
5299 progress;
5300
cristy4c08aed2011-07-01 19:47:50 +00005301 PixelInfo
cristyddd82202009-11-03 20:14:50 +00005302 bias;
cristy3ed852e2009-09-05 21:47:34 +00005303
5304 MagickRealType
5305 quantum_threshold;
5306
cristybb503372010-05-27 20:51:26 +00005307 ssize_t
5308 y;
5309
cristy3ed852e2009-09-05 21:47:34 +00005310 assert(image != (const Image *) NULL);
5311 assert(image->signature == MagickSignature);
5312 if (image->debug != MagickFalse)
5313 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5314 assert(exception != (ExceptionInfo *) NULL);
5315 unsharp_image=BlurImageChannel(image,channel,radius,sigma,exception);
5316 if (unsharp_image == (Image *) NULL)
5317 return((Image *) NULL);
5318 quantum_threshold=(MagickRealType) QuantumRange*threshold;
5319 /*
5320 Unsharp-mask image.
5321 */
5322 status=MagickTrue;
5323 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00005324 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00005325 image_view=AcquireCacheView(image);
5326 unsharp_view=AcquireCacheView(unsharp_image);
cristyb5d5f722009-11-04 03:03:49 +00005327#if defined(MAGICKCORE_OPENMP_SUPPORT)
5328 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00005329#endif
cristybb503372010-05-27 20:51:26 +00005330 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00005331 {
cristy4c08aed2011-07-01 19:47:50 +00005332 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00005333 pixel;
5334
cristy4c08aed2011-07-01 19:47:50 +00005335 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00005336 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00005337
cristy4c08aed2011-07-01 19:47:50 +00005338 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00005339 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00005340
cristy117ff172010-08-15 21:35:32 +00005341 register ssize_t
5342 x;
5343
cristy3ed852e2009-09-05 21:47:34 +00005344 if (status == MagickFalse)
5345 continue;
5346 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
5347 q=GetCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
5348 exception);
cristy4c08aed2011-07-01 19:47:50 +00005349 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00005350 {
5351 status=MagickFalse;
5352 continue;
5353 }
cristyddd82202009-11-03 20:14:50 +00005354 pixel=bias;
cristybb503372010-05-27 20:51:26 +00005355 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00005356 {
cristy2b9582a2011-07-04 17:38:56 +00005357 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00005358 {
cristy4c08aed2011-07-01 19:47:50 +00005359 pixel.red=GetPixelRed(image,p)-(MagickRealType) GetPixelRed(image,q);
cristy3ed852e2009-09-05 21:47:34 +00005360 if (fabs(2.0*pixel.red) < quantum_threshold)
cristy4c08aed2011-07-01 19:47:50 +00005361 pixel.red=(MagickRealType) GetPixelRed(image,p);
cristy3ed852e2009-09-05 21:47:34 +00005362 else
cristy4c08aed2011-07-01 19:47:50 +00005363 pixel.red=(MagickRealType) GetPixelRed(image,p)+(pixel.red*amount);
5364 SetPixelRed(unsharp_image,ClampToQuantum(pixel.red),q);
cristy3ed852e2009-09-05 21:47:34 +00005365 }
cristy2b9582a2011-07-04 17:38:56 +00005366 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00005367 {
cristy4c08aed2011-07-01 19:47:50 +00005368 pixel.green=GetPixelGreen(image,p)-
5369 (MagickRealType) GetPixelGreen(image,q);
cristy3ed852e2009-09-05 21:47:34 +00005370 if (fabs(2.0*pixel.green) < quantum_threshold)
cristy4c08aed2011-07-01 19:47:50 +00005371 pixel.green=(MagickRealType)
5372 GetPixelGreen(image,p);
cristy3ed852e2009-09-05 21:47:34 +00005373 else
cristy4c08aed2011-07-01 19:47:50 +00005374 pixel.green=(MagickRealType)
5375 GetPixelGreen(image,p)+
5376 (pixel.green*amount);
5377 SetPixelGreen(unsharp_image,
5378 ClampToQuantum(pixel.green),q);
cristy3ed852e2009-09-05 21:47:34 +00005379 }
cristy2b9582a2011-07-04 17:38:56 +00005380 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00005381 {
cristy4c08aed2011-07-01 19:47:50 +00005382 pixel.blue=GetPixelBlue(image,p)-
5383 (MagickRealType) GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00005384 if (fabs(2.0*pixel.blue) < quantum_threshold)
cristy4c08aed2011-07-01 19:47:50 +00005385 pixel.blue=(MagickRealType)
5386 GetPixelBlue(image,p);
cristy3ed852e2009-09-05 21:47:34 +00005387 else
cristy4c08aed2011-07-01 19:47:50 +00005388 pixel.blue=(MagickRealType)
5389 GetPixelBlue(image,p)+(pixel.blue*amount);
5390 SetPixelBlue(unsharp_image,
5391 ClampToQuantum(pixel.blue),q);
cristy3ed852e2009-09-05 21:47:34 +00005392 }
cristy2b9582a2011-07-04 17:38:56 +00005393 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00005394 (image->colorspace == CMYKColorspace))
5395 {
cristy4c08aed2011-07-01 19:47:50 +00005396 pixel.black=GetPixelBlack(image,p)-
5397 (MagickRealType) GetPixelBlack(image,q);
5398 if (fabs(2.0*pixel.black) < quantum_threshold)
5399 pixel.black=(MagickRealType)
5400 GetPixelBlack(image,p);
cristy3ed852e2009-09-05 21:47:34 +00005401 else
cristy4c08aed2011-07-01 19:47:50 +00005402 pixel.black=(MagickRealType)
5403 GetPixelBlack(image,p)+(pixel.black*
5404 amount);
5405 SetPixelBlack(unsharp_image,
5406 ClampToQuantum(pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00005407 }
cristy2b9582a2011-07-04 17:38:56 +00005408 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00005409 {
5410 pixel.alpha=GetPixelAlpha(image,p)-
5411 (MagickRealType) GetPixelAlpha(image,q);
5412 if (fabs(2.0*pixel.alpha) < quantum_threshold)
5413 pixel.alpha=(MagickRealType)
5414 GetPixelAlpha(image,p);
5415 else
5416 pixel.alpha=GetPixelAlpha(image,p)+
5417 (pixel.alpha*amount);
5418 SetPixelAlpha(unsharp_image,
5419 ClampToQuantum(pixel.alpha),q);
5420 }
5421 p+=GetPixelChannels(image);
5422 q+=GetPixelChannels(unsharp_image);
cristy3ed852e2009-09-05 21:47:34 +00005423 }
5424 if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
5425 status=MagickFalse;
5426 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5427 {
5428 MagickBooleanType
5429 proceed;
5430
cristyb5d5f722009-11-04 03:03:49 +00005431#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00005432 #pragma omp critical (MagickCore_UnsharpMaskImageChannel)
5433#endif
5434 proceed=SetImageProgress(image,SharpenImageTag,progress++,image->rows);
5435 if (proceed == MagickFalse)
5436 status=MagickFalse;
5437 }
5438 }
5439 unsharp_image->type=image->type;
5440 unsharp_view=DestroyCacheView(unsharp_view);
5441 image_view=DestroyCacheView(image_view);
5442 if (status == MagickFalse)
5443 unsharp_image=DestroyImage(unsharp_image);
5444 return(unsharp_image);
5445}