blob: bec60a5cf497fcb1135c1c4941aa21494f56713c [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
139MagickExport Image *AdaptiveBlurImageChannel(const Image *image,
140 const ChannelType channel,const double radius,const double sigma,
141 ExceptionInfo *exception)
142{
143#define AdaptiveBlurImageTag "Convolve/Image"
144#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
145
cristyc4c8d132010-01-07 01:58:38 +0000146 CacheView
147 *blur_view,
148 *edge_view,
149 *image_view;
150
cristy3ed852e2009-09-05 21:47:34 +0000151 double
cristy47e00502009-12-17 19:19:57 +0000152 **kernel,
153 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000154
155 Image
156 *blur_image,
157 *edge_image,
158 *gaussian_image;
159
cristy3ed852e2009-09-05 21:47:34 +0000160 MagickBooleanType
161 status;
162
cristybb503372010-05-27 20:51:26 +0000163 MagickOffsetType
164 progress;
165
cristy4c08aed2011-07-01 19:47:50 +0000166 PixelInfo
cristyddd82202009-11-03 20:14:50 +0000167 bias;
cristy3ed852e2009-09-05 21:47:34 +0000168
cristybb503372010-05-27 20:51:26 +0000169 register ssize_t
cristy47e00502009-12-17 19:19:57 +0000170 i;
cristy3ed852e2009-09-05 21:47:34 +0000171
cristybb503372010-05-27 20:51:26 +0000172 size_t
cristy3ed852e2009-09-05 21:47:34 +0000173 width;
174
cristybb503372010-05-27 20:51:26 +0000175 ssize_t
176 j,
177 k,
178 u,
179 v,
180 y;
181
cristy3ed852e2009-09-05 21:47:34 +0000182 assert(image != (const Image *) NULL);
183 assert(image->signature == MagickSignature);
184 if (image->debug != MagickFalse)
185 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
186 assert(exception != (ExceptionInfo *) NULL);
187 assert(exception->signature == MagickSignature);
188 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
189 if (blur_image == (Image *) NULL)
190 return((Image *) NULL);
191 if (fabs(sigma) <= MagickEpsilon)
192 return(blur_image);
193 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
194 {
195 InheritException(exception,&blur_image->exception);
196 blur_image=DestroyImage(blur_image);
197 return((Image *) NULL);
198 }
199 /*
200 Edge detect the image brighness channel, level, blur, and level again.
201 */
202 edge_image=EdgeImage(image,radius,exception);
203 if (edge_image == (Image *) NULL)
204 {
205 blur_image=DestroyImage(blur_image);
206 return((Image *) NULL);
207 }
208 (void) LevelImage(edge_image,"20%,95%");
209 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,exception);
210 if (gaussian_image != (Image *) NULL)
211 {
212 edge_image=DestroyImage(edge_image);
213 edge_image=gaussian_image;
214 }
215 (void) LevelImage(edge_image,"10%,95%");
216 /*
217 Create a set of kernels from maximum (radius,sigma) to minimum.
218 */
219 width=GetOptimalKernelWidth2D(radius,sigma);
220 kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
221 if (kernel == (double **) NULL)
222 {
223 edge_image=DestroyImage(edge_image);
224 blur_image=DestroyImage(blur_image);
225 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
226 }
227 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
cristybb503372010-05-27 20:51:26 +0000228 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000229 {
230 kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)*
231 sizeof(**kernel));
232 if (kernel[i] == (double *) NULL)
233 break;
cristy47e00502009-12-17 19:19:57 +0000234 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000235 j=(ssize_t) (width-i)/2;
cristy47e00502009-12-17 19:19:57 +0000236 k=0;
237 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000238 {
cristy47e00502009-12-17 19:19:57 +0000239 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000240 {
cristy4205a3c2010-09-12 20:19:59 +0000241 kernel[i][k]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
242 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +0000243 normalize+=kernel[i][k];
244 k++;
cristy3ed852e2009-09-05 21:47:34 +0000245 }
246 }
cristy3ed852e2009-09-05 21:47:34 +0000247 if (fabs(normalize) <= MagickEpsilon)
248 normalize=1.0;
249 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000250 for (k=0; k < (j*j); k++)
251 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000252 }
cristybb503372010-05-27 20:51:26 +0000253 if (i < (ssize_t) width)
cristy3ed852e2009-09-05 21:47:34 +0000254 {
255 for (i-=2; i >= 0; i-=2)
256 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
257 kernel=(double **) RelinquishMagickMemory(kernel);
258 edge_image=DestroyImage(edge_image);
259 blur_image=DestroyImage(blur_image);
260 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
261 }
262 /*
263 Adaptively blur image.
264 */
265 status=MagickTrue;
266 progress=0;
cristy4c08aed2011-07-01 19:47:50 +0000267 GetPixelInfo(image,&bias);
268 SetPixelInfoBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000269 image_view=AcquireCacheView(image);
270 edge_view=AcquireCacheView(edge_image);
271 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000272#if defined(MAGICKCORE_OPENMP_SUPPORT)
273 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000274#endif
cristybb503372010-05-27 20:51:26 +0000275 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000276 {
cristy4c08aed2011-07-01 19:47:50 +0000277 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000278 *restrict p,
279 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000280
cristy4c08aed2011-07-01 19:47:50 +0000281 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000282 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000283
cristy117ff172010-08-15 21:35:32 +0000284 register ssize_t
285 x;
286
cristy3ed852e2009-09-05 21:47:34 +0000287 if (status == MagickFalse)
288 continue;
289 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
290 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
291 exception);
cristy4c08aed2011-07-01 19:47:50 +0000292 if ((r == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000293 {
294 status=MagickFalse;
295 continue;
296 }
cristybb503372010-05-27 20:51:26 +0000297 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000298 {
cristy4c08aed2011-07-01 19:47:50 +0000299 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000300 pixel;
301
302 MagickRealType
303 alpha,
304 gamma;
305
306 register const double
cristyc47d1f82009-11-26 01:44:43 +0000307 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000308
cristybb503372010-05-27 20:51:26 +0000309 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000310 i,
311 u,
312 v;
313
314 gamma=0.0;
cristy4c08aed2011-07-01 19:47:50 +0000315 i=(ssize_t) ceil((double) width*QuantumScale*
316 GetPixelIntensity(edge_image,r)-0.5);
cristy3ed852e2009-09-05 21:47:34 +0000317 if (i < 0)
318 i=0;
319 else
cristybb503372010-05-27 20:51:26 +0000320 if (i > (ssize_t) width)
321 i=(ssize_t) width;
cristy3ed852e2009-09-05 21:47:34 +0000322 if ((i & 0x01) != 0)
323 i--;
cristya21afde2010-07-02 00:45:40 +0000324 p=GetCacheViewVirtualPixels(image_view,x-((ssize_t) (width-i)/2L),y-
325 (ssize_t) ((width-i)/2L),width-i,width-i,exception);
cristy4c08aed2011-07-01 19:47:50 +0000326 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000327 break;
cristyddd82202009-11-03 20:14:50 +0000328 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +0000329 k=kernel[i];
cristybb503372010-05-27 20:51:26 +0000330 for (v=0; v < (ssize_t) (width-i); v++)
cristy3ed852e2009-09-05 21:47:34 +0000331 {
cristybb503372010-05-27 20:51:26 +0000332 for (u=0; u < (ssize_t) (width-i); u++)
cristy3ed852e2009-09-05 21:47:34 +0000333 {
334 alpha=1.0;
cristy2b9582a2011-07-04 17:38:56 +0000335 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000336 (image->matte != MagickFalse))
cristy4c08aed2011-07-01 19:47:50 +0000337 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,p));
cristy2b9582a2011-07-04 17:38:56 +0000338 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000339 pixel.red+=(*k)*alpha*GetPixelRed(image,p);
cristy2b9582a2011-07-04 17:38:56 +0000340 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000341 pixel.green+=(*k)*alpha*GetPixelGreen(image,p);
cristy2b9582a2011-07-04 17:38:56 +0000342 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000343 pixel.blue+=(*k)*alpha*GetPixelBlue(image,p);
cristy2b9582a2011-07-04 17:38:56 +0000344 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000345 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +0000346 pixel.black+=(*k)*alpha*GetPixelBlack(image,p);
cristy2b9582a2011-07-04 17:38:56 +0000347 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000348 pixel.alpha+=(*k)*GetPixelAlpha(image,p);
cristy3ed852e2009-09-05 21:47:34 +0000349 gamma+=(*k)*alpha;
350 k++;
cristy4c08aed2011-07-01 19:47:50 +0000351 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000352 }
353 }
354 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy2b9582a2011-07-04 17:38:56 +0000355 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000356 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +0000357 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000358 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +0000359 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000360 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +0000361 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000362 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +0000363 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +0000364 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000365 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
366 q+=GetPixelChannels(blur_image);
367 r+=GetPixelChannels(edge_image);
cristy3ed852e2009-09-05 21:47:34 +0000368 }
369 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
370 status=MagickFalse;
371 if (image->progress_monitor != (MagickProgressMonitor) NULL)
372 {
373 MagickBooleanType
374 proceed;
375
cristyb5d5f722009-11-04 03:03:49 +0000376#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000377 #pragma omp critical (MagickCore_AdaptiveBlurImageChannel)
378#endif
379 proceed=SetImageProgress(image,AdaptiveBlurImageTag,progress++,
380 image->rows);
381 if (proceed == MagickFalse)
382 status=MagickFalse;
383 }
384 }
385 blur_image->type=image->type;
386 blur_view=DestroyCacheView(blur_view);
387 edge_view=DestroyCacheView(edge_view);
388 image_view=DestroyCacheView(image_view);
389 edge_image=DestroyImage(edge_image);
cristybb503372010-05-27 20:51:26 +0000390 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000391 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
392 kernel=(double **) RelinquishMagickMemory(kernel);
393 if (status == MagickFalse)
394 blur_image=DestroyImage(blur_image);
395 return(blur_image);
396}
397
398/*
399%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
400% %
401% %
402% %
403% A d a p t i v e S h a r p e n I m a g e %
404% %
405% %
406% %
407%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
408%
409% AdaptiveSharpenImage() adaptively sharpens the image by sharpening more
410% intensely near image edges and less intensely far from edges. We sharpen the
411% image with a Gaussian operator of the given radius and standard deviation
412% (sigma). For reasonable results, radius should be larger than sigma. Use a
413% radius of 0 and AdaptiveSharpenImage() selects a suitable radius for you.
414%
415% The format of the AdaptiveSharpenImage method is:
416%
417% Image *AdaptiveSharpenImage(const Image *image,const double radius,
418% const double sigma,ExceptionInfo *exception)
419% Image *AdaptiveSharpenImageChannel(const Image *image,
420% const ChannelType channel,double radius,const double sigma,
421% ExceptionInfo *exception)
422%
423% A description of each parameter follows:
424%
425% o image: the image.
426%
427% o channel: the channel type.
428%
429% o radius: the radius of the Gaussian, in pixels, not counting the center
430% pixel.
431%
432% o sigma: the standard deviation of the Laplacian, in pixels.
433%
434% o exception: return any errors or warnings in this structure.
435%
436*/
437
438MagickExport Image *AdaptiveSharpenImage(const Image *image,const double radius,
439 const double sigma,ExceptionInfo *exception)
440{
441 Image
442 *sharp_image;
443
444 sharp_image=AdaptiveSharpenImageChannel(image,DefaultChannels,radius,sigma,
445 exception);
446 return(sharp_image);
447}
448
449MagickExport Image *AdaptiveSharpenImageChannel(const Image *image,
450 const ChannelType channel,const double radius,const double sigma,
451 ExceptionInfo *exception)
452{
453#define AdaptiveSharpenImageTag "Convolve/Image"
454#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
455
cristyc4c8d132010-01-07 01:58:38 +0000456 CacheView
457 *sharp_view,
458 *edge_view,
459 *image_view;
460
cristy3ed852e2009-09-05 21:47:34 +0000461 double
cristy47e00502009-12-17 19:19:57 +0000462 **kernel,
463 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000464
465 Image
466 *sharp_image,
467 *edge_image,
468 *gaussian_image;
469
cristy3ed852e2009-09-05 21:47:34 +0000470 MagickBooleanType
471 status;
472
cristybb503372010-05-27 20:51:26 +0000473 MagickOffsetType
474 progress;
475
cristy4c08aed2011-07-01 19:47:50 +0000476 PixelInfo
cristyddd82202009-11-03 20:14:50 +0000477 bias;
cristy3ed852e2009-09-05 21:47:34 +0000478
cristybb503372010-05-27 20:51:26 +0000479 register ssize_t
cristy47e00502009-12-17 19:19:57 +0000480 i;
cristy3ed852e2009-09-05 21:47:34 +0000481
cristybb503372010-05-27 20:51:26 +0000482 size_t
cristy3ed852e2009-09-05 21:47:34 +0000483 width;
484
cristybb503372010-05-27 20:51:26 +0000485 ssize_t
486 j,
487 k,
488 u,
489 v,
490 y;
491
cristy3ed852e2009-09-05 21:47:34 +0000492 assert(image != (const Image *) NULL);
493 assert(image->signature == MagickSignature);
494 if (image->debug != MagickFalse)
495 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
496 assert(exception != (ExceptionInfo *) NULL);
497 assert(exception->signature == MagickSignature);
498 sharp_image=CloneImage(image,0,0,MagickTrue,exception);
499 if (sharp_image == (Image *) NULL)
500 return((Image *) NULL);
501 if (fabs(sigma) <= MagickEpsilon)
502 return(sharp_image);
503 if (SetImageStorageClass(sharp_image,DirectClass) == MagickFalse)
504 {
505 InheritException(exception,&sharp_image->exception);
506 sharp_image=DestroyImage(sharp_image);
507 return((Image *) NULL);
508 }
509 /*
510 Edge detect the image brighness channel, level, sharp, and level again.
511 */
512 edge_image=EdgeImage(image,radius,exception);
513 if (edge_image == (Image *) NULL)
514 {
515 sharp_image=DestroyImage(sharp_image);
516 return((Image *) NULL);
517 }
518 (void) LevelImage(edge_image,"20%,95%");
519 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,exception);
520 if (gaussian_image != (Image *) NULL)
521 {
522 edge_image=DestroyImage(edge_image);
523 edge_image=gaussian_image;
524 }
525 (void) LevelImage(edge_image,"10%,95%");
526 /*
527 Create a set of kernels from maximum (radius,sigma) to minimum.
528 */
529 width=GetOptimalKernelWidth2D(radius,sigma);
530 kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
531 if (kernel == (double **) NULL)
532 {
533 edge_image=DestroyImage(edge_image);
534 sharp_image=DestroyImage(sharp_image);
535 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
536 }
537 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
cristybb503372010-05-27 20:51:26 +0000538 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000539 {
540 kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)*
541 sizeof(**kernel));
542 if (kernel[i] == (double *) NULL)
543 break;
cristy47e00502009-12-17 19:19:57 +0000544 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000545 j=(ssize_t) (width-i)/2;
cristy47e00502009-12-17 19:19:57 +0000546 k=0;
547 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000548 {
cristy47e00502009-12-17 19:19:57 +0000549 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000550 {
cristy4205a3c2010-09-12 20:19:59 +0000551 kernel[i][k]=(double) (-exp(-((double) u*u+v*v)/(2.0*MagickSigma*
552 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +0000553 normalize+=kernel[i][k];
554 k++;
cristy3ed852e2009-09-05 21:47:34 +0000555 }
556 }
cristy3ed852e2009-09-05 21:47:34 +0000557 if (fabs(normalize) <= MagickEpsilon)
558 normalize=1.0;
559 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000560 for (k=0; k < (j*j); k++)
561 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000562 }
cristybb503372010-05-27 20:51:26 +0000563 if (i < (ssize_t) width)
cristy3ed852e2009-09-05 21:47:34 +0000564 {
565 for (i-=2; i >= 0; i-=2)
566 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
567 kernel=(double **) RelinquishMagickMemory(kernel);
568 edge_image=DestroyImage(edge_image);
569 sharp_image=DestroyImage(sharp_image);
570 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
571 }
572 /*
573 Adaptively sharpen image.
574 */
575 status=MagickTrue;
576 progress=0;
cristy4c08aed2011-07-01 19:47:50 +0000577 GetPixelInfo(image,&bias);
578 SetPixelInfoBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000579 image_view=AcquireCacheView(image);
580 edge_view=AcquireCacheView(edge_image);
581 sharp_view=AcquireCacheView(sharp_image);
cristyb5d5f722009-11-04 03:03:49 +0000582#if defined(MAGICKCORE_OPENMP_SUPPORT)
583 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000584#endif
cristybb503372010-05-27 20:51:26 +0000585 for (y=0; y < (ssize_t) sharp_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000586 {
cristy4c08aed2011-07-01 19:47:50 +0000587 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000588 *restrict p,
589 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000590
cristy4c08aed2011-07-01 19:47:50 +0000591 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000592 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000593
cristy117ff172010-08-15 21:35:32 +0000594 register ssize_t
595 x;
596
cristy3ed852e2009-09-05 21:47:34 +0000597 if (status == MagickFalse)
598 continue;
599 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
600 q=QueueCacheViewAuthenticPixels(sharp_view,0,y,sharp_image->columns,1,
601 exception);
cristy4c08aed2011-07-01 19:47:50 +0000602 if ((r == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000603 {
604 status=MagickFalse;
605 continue;
606 }
cristybb503372010-05-27 20:51:26 +0000607 for (x=0; x < (ssize_t) sharp_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000608 {
cristy4c08aed2011-07-01 19:47:50 +0000609 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000610 pixel;
611
612 MagickRealType
613 alpha,
614 gamma;
615
616 register const double
cristyc47d1f82009-11-26 01:44:43 +0000617 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000618
cristybb503372010-05-27 20:51:26 +0000619 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000620 i,
621 u,
622 v;
623
624 gamma=0.0;
cristy4c08aed2011-07-01 19:47:50 +0000625 i=(ssize_t) ceil((double) width*QuantumScale*
626 GetPixelIntensity(edge_image,r)-0.5);
cristy3ed852e2009-09-05 21:47:34 +0000627 if (i < 0)
628 i=0;
629 else
cristybb503372010-05-27 20:51:26 +0000630 if (i > (ssize_t) width)
631 i=(ssize_t) width;
cristy3ed852e2009-09-05 21:47:34 +0000632 if ((i & 0x01) != 0)
633 i--;
cristy117ff172010-08-15 21:35:32 +0000634 p=GetCacheViewVirtualPixels(image_view,x-((ssize_t) (width-i)/2L),y-
635 (ssize_t) ((width-i)/2L),width-i,width-i,exception);
cristy4c08aed2011-07-01 19:47:50 +0000636 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000637 break;
cristy3ed852e2009-09-05 21:47:34 +0000638 k=kernel[i];
cristyddd82202009-11-03 20:14:50 +0000639 pixel=bias;
cristybb503372010-05-27 20:51:26 +0000640 for (v=0; v < (ssize_t) (width-i); v++)
cristy3ed852e2009-09-05 21:47:34 +0000641 {
cristybb503372010-05-27 20:51:26 +0000642 for (u=0; u < (ssize_t) (width-i); u++)
cristy3ed852e2009-09-05 21:47:34 +0000643 {
644 alpha=1.0;
cristy2b9582a2011-07-04 17:38:56 +0000645 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000646 (image->matte != MagickFalse))
cristy4c08aed2011-07-01 19:47:50 +0000647 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,p));
cristy2b9582a2011-07-04 17:38:56 +0000648 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000649 pixel.red+=(*k)*alpha*GetPixelRed(image,p);
cristy2b9582a2011-07-04 17:38:56 +0000650 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000651 pixel.green+=(*k)*alpha*GetPixelGreen(image,p);
cristy2b9582a2011-07-04 17:38:56 +0000652 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000653 pixel.blue+=(*k)*alpha*GetPixelBlue(image,p);
cristy2b9582a2011-07-04 17:38:56 +0000654 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000655 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +0000656 pixel.black+=(*k)*alpha*GetPixelBlack(image,p);
cristy2b9582a2011-07-04 17:38:56 +0000657 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000658 pixel.alpha+=(*k)*GetPixelAlpha(image,p);
cristy3ed852e2009-09-05 21:47:34 +0000659 gamma+=(*k)*alpha;
660 k++;
cristy4c08aed2011-07-01 19:47:50 +0000661 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000662 }
663 }
664 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy2b9582a2011-07-04 17:38:56 +0000665 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000666 SetPixelRed(sharp_image,ClampToQuantum(gamma*pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +0000667 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000668 SetPixelGreen(sharp_image,ClampToQuantum(gamma*pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +0000669 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000670 SetPixelBlue(sharp_image,ClampToQuantum(gamma*pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +0000671 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000672 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +0000673 SetPixelBlack(sharp_image,ClampToQuantum(gamma*pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +0000674 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000675 SetPixelAlpha(sharp_image,ClampToQuantum(pixel.alpha),q);
676 q+=GetPixelChannels(sharp_image);
677 r+=GetPixelChannels(edge_image);
cristy3ed852e2009-09-05 21:47:34 +0000678 }
679 if (SyncCacheViewAuthenticPixels(sharp_view,exception) == MagickFalse)
680 status=MagickFalse;
681 if (image->progress_monitor != (MagickProgressMonitor) NULL)
682 {
683 MagickBooleanType
684 proceed;
685
cristyb5d5f722009-11-04 03:03:49 +0000686#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000687 #pragma omp critical (MagickCore_AdaptiveSharpenImageChannel)
688#endif
689 proceed=SetImageProgress(image,AdaptiveSharpenImageTag,progress++,
690 image->rows);
691 if (proceed == MagickFalse)
692 status=MagickFalse;
693 }
694 }
695 sharp_image->type=image->type;
696 sharp_view=DestroyCacheView(sharp_view);
697 edge_view=DestroyCacheView(edge_view);
698 image_view=DestroyCacheView(image_view);
699 edge_image=DestroyImage(edge_image);
cristybb503372010-05-27 20:51:26 +0000700 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000701 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
702 kernel=(double **) RelinquishMagickMemory(kernel);
703 if (status == MagickFalse)
704 sharp_image=DestroyImage(sharp_image);
705 return(sharp_image);
706}
707
708/*
709%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
710% %
711% %
712% %
713% B l u r I m a g e %
714% %
715% %
716% %
717%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
718%
719% BlurImage() blurs an image. We convolve the image with a Gaussian operator
720% of the given radius and standard deviation (sigma). For reasonable results,
721% the radius should be larger than sigma. Use a radius of 0 and BlurImage()
722% selects a suitable radius for you.
723%
724% BlurImage() differs from GaussianBlurImage() in that it uses a separable
725% kernel which is faster but mathematically equivalent to the non-separable
726% kernel.
727%
728% The format of the BlurImage method is:
729%
730% Image *BlurImage(const Image *image,const double radius,
731% const double sigma,ExceptionInfo *exception)
732% Image *BlurImageChannel(const Image *image,const ChannelType channel,
733% const double radius,const double sigma,ExceptionInfo *exception)
734%
735% A description of each parameter follows:
736%
737% o image: the image.
738%
739% o channel: the channel type.
740%
741% o radius: the radius of the Gaussian, in pixels, not counting the center
742% pixel.
743%
744% o sigma: the standard deviation of the Gaussian, in pixels.
745%
746% o exception: return any errors or warnings in this structure.
747%
748*/
749
750MagickExport Image *BlurImage(const Image *image,const double radius,
751 const double sigma,ExceptionInfo *exception)
752{
753 Image
754 *blur_image;
755
756 blur_image=BlurImageChannel(image,DefaultChannels,radius,sigma,exception);
757 return(blur_image);
758}
759
cristybb503372010-05-27 20:51:26 +0000760static double *GetBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +0000761{
cristy3ed852e2009-09-05 21:47:34 +0000762 double
cristy47e00502009-12-17 19:19:57 +0000763 *kernel,
764 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000765
cristy117ff172010-08-15 21:35:32 +0000766 register ssize_t
767 i;
768
cristybb503372010-05-27 20:51:26 +0000769 ssize_t
cristy47e00502009-12-17 19:19:57 +0000770 j,
771 k;
cristy3ed852e2009-09-05 21:47:34 +0000772
cristy3ed852e2009-09-05 21:47:34 +0000773 /*
774 Generate a 1-D convolution kernel.
775 */
776 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
777 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
778 if (kernel == (double *) NULL)
779 return(0);
cristy3ed852e2009-09-05 21:47:34 +0000780 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000781 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +0000782 i=0;
783 for (k=(-j); k <= j; k++)
784 {
cristy4205a3c2010-09-12 20:19:59 +0000785 kernel[i]=(double) (exp(-((double) k*k)/(2.0*MagickSigma*MagickSigma))/
786 (MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +0000787 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +0000788 i++;
789 }
cristybb503372010-05-27 20:51:26 +0000790 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000791 kernel[i]/=normalize;
792 return(kernel);
793}
794
795MagickExport Image *BlurImageChannel(const Image *image,
796 const ChannelType channel,const double radius,const double sigma,
797 ExceptionInfo *exception)
798{
799#define BlurImageTag "Blur/Image"
800
cristyc4c8d132010-01-07 01:58:38 +0000801 CacheView
802 *blur_view,
803 *image_view;
804
cristy3ed852e2009-09-05 21:47:34 +0000805 double
806 *kernel;
807
808 Image
809 *blur_image;
810
cristy3ed852e2009-09-05 21:47:34 +0000811 MagickBooleanType
812 status;
813
cristybb503372010-05-27 20:51:26 +0000814 MagickOffsetType
815 progress;
816
cristy4c08aed2011-07-01 19:47:50 +0000817 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000818 bias;
819
cristybb503372010-05-27 20:51:26 +0000820 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000821 i;
822
cristybb503372010-05-27 20:51:26 +0000823 size_t
cristy3ed852e2009-09-05 21:47:34 +0000824 width;
825
cristybb503372010-05-27 20:51:26 +0000826 ssize_t
827 x,
828 y;
829
cristy3ed852e2009-09-05 21:47:34 +0000830 /*
831 Initialize blur image attributes.
832 */
833 assert(image != (Image *) NULL);
834 assert(image->signature == MagickSignature);
835 if (image->debug != MagickFalse)
836 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
837 assert(exception != (ExceptionInfo *) NULL);
838 assert(exception->signature == MagickSignature);
839 blur_image=CloneImage(image,0,0,MagickTrue,exception);
840 if (blur_image == (Image *) NULL)
841 return((Image *) NULL);
842 if (fabs(sigma) <= MagickEpsilon)
843 return(blur_image);
844 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
845 {
846 InheritException(exception,&blur_image->exception);
847 blur_image=DestroyImage(blur_image);
848 return((Image *) NULL);
849 }
850 width=GetOptimalKernelWidth1D(radius,sigma);
851 kernel=GetBlurKernel(width,sigma);
852 if (kernel == (double *) NULL)
853 {
854 blur_image=DestroyImage(blur_image);
855 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
856 }
857 if (image->debug != MagickFalse)
858 {
859 char
860 format[MaxTextExtent],
861 *message;
862
863 register const double
864 *k;
865
866 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +0000867 " BlurImage with %.20g kernel:",(double) width);
cristy3ed852e2009-09-05 21:47:34 +0000868 message=AcquireString("");
869 k=kernel;
cristybb503372010-05-27 20:51:26 +0000870 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000871 {
872 *message='\0';
cristyb51dff52011-05-19 16:55:47 +0000873 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) i);
cristy3ed852e2009-09-05 21:47:34 +0000874 (void) ConcatenateString(&message,format);
cristyb51dff52011-05-19 16:55:47 +0000875 (void) FormatLocaleString(format,MaxTextExtent,"%g ",*k++);
cristy3ed852e2009-09-05 21:47:34 +0000876 (void) ConcatenateString(&message,format);
877 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
878 }
879 message=DestroyString(message);
880 }
881 /*
882 Blur rows.
883 */
884 status=MagickTrue;
885 progress=0;
cristy4c08aed2011-07-01 19:47:50 +0000886 GetPixelInfo(image,&bias);
887 SetPixelInfoBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000888 image_view=AcquireCacheView(image);
889 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000890#if defined(MAGICKCORE_OPENMP_SUPPORT)
891 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000892#endif
cristybb503372010-05-27 20:51:26 +0000893 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000894 {
cristy4c08aed2011-07-01 19:47:50 +0000895 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000896 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000897
cristy4c08aed2011-07-01 19:47:50 +0000898 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000899 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000900
cristy117ff172010-08-15 21:35:32 +0000901 register ssize_t
902 x;
903
cristy3ed852e2009-09-05 21:47:34 +0000904 if (status == MagickFalse)
905 continue;
cristy117ff172010-08-15 21:35:32 +0000906 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y,
907 image->columns+width,1,exception);
cristy3ed852e2009-09-05 21:47:34 +0000908 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
909 exception);
cristy4c08aed2011-07-01 19:47:50 +0000910 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000911 {
912 status=MagickFalse;
913 continue;
914 }
cristybb503372010-05-27 20:51:26 +0000915 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000916 {
cristy4c08aed2011-07-01 19:47:50 +0000917 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000918 pixel;
919
920 register const double
cristyc47d1f82009-11-26 01:44:43 +0000921 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000922
cristy4c08aed2011-07-01 19:47:50 +0000923 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000924 *restrict kernel_pixels;
cristy3ed852e2009-09-05 21:47:34 +0000925
cristybb503372010-05-27 20:51:26 +0000926 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000927 i;
928
cristyddd82202009-11-03 20:14:50 +0000929 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +0000930 k=kernel;
931 kernel_pixels=p;
cristy2b9582a2011-07-04 17:38:56 +0000932 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +0000933 {
cristybb503372010-05-27 20:51:26 +0000934 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000935 {
cristy4c08aed2011-07-01 19:47:50 +0000936 pixel.red+=(*k)*GetPixelRed(image,kernel_pixels);
937 pixel.green+=(*k)*GetPixelGreen(image,kernel_pixels);
938 pixel.blue+=(*k)*GetPixelBlue(image,kernel_pixels);
939 if (image->colorspace == CMYKColorspace)
940 pixel.black+=(*k)*GetPixelBlack(image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +0000941 k++;
cristy4c08aed2011-07-01 19:47:50 +0000942 kernel_pixels+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000943 }
cristy2b9582a2011-07-04 17:38:56 +0000944 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000945 SetPixelRed(blur_image,ClampToQuantum(pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +0000946 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000947 SetPixelGreen(blur_image,ClampToQuantum(pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +0000948 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000949 SetPixelBlue(blur_image,ClampToQuantum(pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +0000950 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000951 (blur_image->colorspace == CMYKColorspace))
952 SetPixelBlack(blur_image,ClampToQuantum(pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +0000953 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +0000954 {
955 k=kernel;
956 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +0000957 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000958 {
cristy4c08aed2011-07-01 19:47:50 +0000959 pixel.alpha+=(*k)*GetPixelAlpha(image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +0000960 k++;
cristy4c08aed2011-07-01 19:47:50 +0000961 kernel_pixels+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000962 }
cristy4c08aed2011-07-01 19:47:50 +0000963 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +0000964 }
965 }
966 else
967 {
968 MagickRealType
969 alpha,
970 gamma;
971
972 gamma=0.0;
cristybb503372010-05-27 20:51:26 +0000973 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000974 {
cristy4c08aed2011-07-01 19:47:50 +0000975 alpha=(MagickRealType) (QuantumScale*
976 GetPixelAlpha(image,kernel_pixels));
977 pixel.red+=(*k)*alpha*GetPixelRed(image,kernel_pixels);
978 pixel.green+=(*k)*alpha*GetPixelGreen(image,kernel_pixels);
979 pixel.blue+=(*k)*alpha*GetPixelBlue(image,kernel_pixels);
980 if (image->colorspace == CMYKColorspace)
981 pixel.black+=(*k)*alpha*GetPixelBlack(image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +0000982 gamma+=(*k)*alpha;
983 k++;
cristy4c08aed2011-07-01 19:47:50 +0000984 kernel_pixels+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000985 }
986 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy2b9582a2011-07-04 17:38:56 +0000987 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000988 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +0000989 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000990 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +0000991 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000992 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +0000993 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000994 (blur_image->colorspace == CMYKColorspace))
995 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +0000996 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +0000997 {
998 k=kernel;
999 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001000 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001001 {
cristy4c08aed2011-07-01 19:47:50 +00001002 pixel.alpha+=(*k)*GetPixelAlpha(image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001003 k++;
cristy4c08aed2011-07-01 19:47:50 +00001004 kernel_pixels+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001005 }
cristy4c08aed2011-07-01 19:47:50 +00001006 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00001007 }
1008 }
cristy4c08aed2011-07-01 19:47:50 +00001009 p+=GetPixelChannels(image);
1010 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001011 }
1012 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1013 status=MagickFalse;
1014 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1015 {
1016 MagickBooleanType
1017 proceed;
1018
cristyb5d5f722009-11-04 03:03:49 +00001019#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001020 #pragma omp critical (MagickCore_BlurImageChannel)
1021#endif
1022 proceed=SetImageProgress(image,BlurImageTag,progress++,blur_image->rows+
1023 blur_image->columns);
1024 if (proceed == MagickFalse)
1025 status=MagickFalse;
1026 }
1027 }
1028 blur_view=DestroyCacheView(blur_view);
1029 image_view=DestroyCacheView(image_view);
1030 /*
1031 Blur columns.
1032 */
1033 image_view=AcquireCacheView(blur_image);
1034 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00001035#if defined(MAGICKCORE_OPENMP_SUPPORT)
1036 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001037#endif
cristybb503372010-05-27 20:51:26 +00001038 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001039 {
cristy4c08aed2011-07-01 19:47:50 +00001040 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001041 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001042
cristy4c08aed2011-07-01 19:47:50 +00001043 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001044 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001045
cristy117ff172010-08-15 21:35:32 +00001046 register ssize_t
1047 y;
1048
cristy3ed852e2009-09-05 21:47:34 +00001049 if (status == MagickFalse)
1050 continue;
cristy117ff172010-08-15 21:35:32 +00001051 p=GetCacheViewVirtualPixels(image_view,x,-((ssize_t) width/2L),1,
1052 image->rows+width,exception);
cristy3ed852e2009-09-05 21:47:34 +00001053 q=GetCacheViewAuthenticPixels(blur_view,x,0,1,blur_image->rows,exception);
cristy4c08aed2011-07-01 19:47:50 +00001054 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001055 {
1056 status=MagickFalse;
1057 continue;
1058 }
cristybb503372010-05-27 20:51:26 +00001059 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001060 {
cristy4c08aed2011-07-01 19:47:50 +00001061 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001062 pixel;
1063
1064 register const double
cristyc47d1f82009-11-26 01:44:43 +00001065 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00001066
cristy4c08aed2011-07-01 19:47:50 +00001067 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001068 *restrict kernel_pixels;
cristy3ed852e2009-09-05 21:47:34 +00001069
cristybb503372010-05-27 20:51:26 +00001070 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001071 i;
1072
cristyddd82202009-11-03 20:14:50 +00001073 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00001074 k=kernel;
1075 kernel_pixels=p;
cristy2b9582a2011-07-04 17:38:56 +00001076 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) == 0) || (blur_image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00001077 {
cristybb503372010-05-27 20:51:26 +00001078 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001079 {
cristy4c08aed2011-07-01 19:47:50 +00001080 pixel.red+=(*k)*GetPixelRed(blur_image,kernel_pixels);
1081 pixel.green+=(*k)*GetPixelGreen(blur_image,kernel_pixels);
1082 pixel.blue+=(*k)*GetPixelBlue(blur_image,kernel_pixels);
1083 if (blur_image->colorspace == CMYKColorspace)
1084 pixel.black+=(*k)*GetPixelBlack(blur_image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001085 k++;
cristy4c08aed2011-07-01 19:47:50 +00001086 kernel_pixels+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001087 }
cristy2b9582a2011-07-04 17:38:56 +00001088 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001089 SetPixelRed(blur_image,ClampToQuantum(pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00001090 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001091 SetPixelGreen(blur_image,ClampToQuantum(pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00001092 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001093 SetPixelBlue(blur_image,ClampToQuantum(pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00001094 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001095 (blur_image->colorspace == CMYKColorspace))
1096 SetPixelBlack(blur_image,ClampToQuantum(pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00001097 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001098 {
1099 k=kernel;
1100 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001101 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001102 {
cristy4c08aed2011-07-01 19:47:50 +00001103 pixel.alpha+=(*k)*GetPixelAlpha(blur_image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001104 k++;
cristy4c08aed2011-07-01 19:47:50 +00001105 kernel_pixels+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001106 }
cristy4c08aed2011-07-01 19:47:50 +00001107 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00001108 }
1109 }
1110 else
1111 {
1112 MagickRealType
1113 alpha,
1114 gamma;
1115
1116 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00001117 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001118 {
cristy46f08202010-01-10 04:04:21 +00001119 alpha=(MagickRealType) (QuantumScale*
cristy4c08aed2011-07-01 19:47:50 +00001120 GetPixelAlpha(blur_image,kernel_pixels));
1121 pixel.red+=(*k)*alpha*GetPixelRed(blur_image,kernel_pixels);
1122 pixel.green+=(*k)*alpha*GetPixelGreen(blur_image,kernel_pixels);
1123 pixel.blue+=(*k)*alpha*GetPixelBlue(blur_image,kernel_pixels);
1124 if (blur_image->colorspace == CMYKColorspace)
1125 pixel.black+=(*k)*alpha*GetPixelBlack(blur_image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001126 gamma+=(*k)*alpha;
1127 k++;
cristy4c08aed2011-07-01 19:47:50 +00001128 kernel_pixels+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001129 }
1130 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy2b9582a2011-07-04 17:38:56 +00001131 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001132 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00001133 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001134 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00001135 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001136 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00001137 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001138 (blur_image->colorspace == CMYKColorspace))
1139 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00001140 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001141 {
1142 k=kernel;
1143 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001144 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001145 {
cristy4c08aed2011-07-01 19:47:50 +00001146 pixel.alpha+=(*k)*GetPixelAlpha(blur_image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001147 k++;
cristy4c08aed2011-07-01 19:47:50 +00001148 kernel_pixels+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001149 }
cristy4c08aed2011-07-01 19:47:50 +00001150 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00001151 }
1152 }
cristy4c08aed2011-07-01 19:47:50 +00001153 p+=GetPixelChannels(blur_image);
1154 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001155 }
1156 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1157 status=MagickFalse;
cristy4c08aed2011-07-01 19:47:50 +00001158 if (blur_image->progress_monitor != (MagickProgressMonitor) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001159 {
1160 MagickBooleanType
1161 proceed;
1162
cristyb5d5f722009-11-04 03:03:49 +00001163#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001164 #pragma omp critical (MagickCore_BlurImageChannel)
1165#endif
cristy4c08aed2011-07-01 19:47:50 +00001166 proceed=SetImageProgress(blur_image,BlurImageTag,progress++,
1167 blur_image->rows+blur_image->columns);
cristy3ed852e2009-09-05 21:47:34 +00001168 if (proceed == MagickFalse)
1169 status=MagickFalse;
1170 }
1171 }
1172 blur_view=DestroyCacheView(blur_view);
1173 image_view=DestroyCacheView(image_view);
1174 kernel=(double *) RelinquishMagickMemory(kernel);
1175 if (status == MagickFalse)
1176 blur_image=DestroyImage(blur_image);
1177 blur_image->type=image->type;
1178 return(blur_image);
1179}
1180
1181/*
1182%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1183% %
1184% %
1185% %
cristyfccdab92009-11-30 16:43:57 +00001186% C o n v o l v e I m a g e %
1187% %
1188% %
1189% %
1190%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1191%
1192% ConvolveImage() applies a custom convolution kernel to the image.
1193%
1194% The format of the ConvolveImage method is:
1195%
cristybb503372010-05-27 20:51:26 +00001196% Image *ConvolveImage(const Image *image,const size_t order,
cristyfccdab92009-11-30 16:43:57 +00001197% const double *kernel,ExceptionInfo *exception)
1198% Image *ConvolveImageChannel(const Image *image,const ChannelType channel,
cristy117ff172010-08-15 21:35:32 +00001199% const size_t order,const double *kernel,ExceptionInfo *exception)
cristyfccdab92009-11-30 16:43:57 +00001200%
1201% A description of each parameter follows:
1202%
1203% o image: the image.
1204%
1205% o channel: the channel type.
1206%
1207% o order: the number of columns and rows in the filter kernel.
1208%
1209% o kernel: An array of double representing the convolution kernel.
1210%
1211% o exception: return any errors or warnings in this structure.
1212%
1213*/
1214
cristybb503372010-05-27 20:51:26 +00001215MagickExport Image *ConvolveImage(const Image *image,const size_t order,
cristyfccdab92009-11-30 16:43:57 +00001216 const double *kernel,ExceptionInfo *exception)
1217{
1218 Image
1219 *convolve_image;
1220
1221 convolve_image=ConvolveImageChannel(image,DefaultChannels,order,kernel,
1222 exception);
1223 return(convolve_image);
1224}
1225
1226MagickExport Image *ConvolveImageChannel(const Image *image,
cristybb503372010-05-27 20:51:26 +00001227 const ChannelType channel,const size_t order,const double *kernel,
cristyfccdab92009-11-30 16:43:57 +00001228 ExceptionInfo *exception)
1229{
1230#define ConvolveImageTag "Convolve/Image"
1231
cristyc4c8d132010-01-07 01:58:38 +00001232 CacheView
1233 *convolve_view,
1234 *image_view;
1235
cristyfccdab92009-11-30 16:43:57 +00001236 double
1237 *normal_kernel;
1238
1239 Image
1240 *convolve_image;
1241
cristyfccdab92009-11-30 16:43:57 +00001242 MagickBooleanType
1243 status;
1244
cristybb503372010-05-27 20:51:26 +00001245 MagickOffsetType
1246 progress;
1247
cristy4c08aed2011-07-01 19:47:50 +00001248 PixelInfo
cristyfccdab92009-11-30 16:43:57 +00001249 bias;
1250
1251 MagickRealType
1252 gamma;
1253
cristybb503372010-05-27 20:51:26 +00001254 register ssize_t
cristyfccdab92009-11-30 16:43:57 +00001255 i;
1256
cristybb503372010-05-27 20:51:26 +00001257 size_t
cristyfccdab92009-11-30 16:43:57 +00001258 width;
1259
cristybb503372010-05-27 20:51:26 +00001260 ssize_t
1261 y;
1262
cristyfccdab92009-11-30 16:43:57 +00001263 /*
1264 Initialize convolve image attributes.
1265 */
1266 assert(image != (Image *) NULL);
1267 assert(image->signature == MagickSignature);
1268 if (image->debug != MagickFalse)
1269 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1270 assert(exception != (ExceptionInfo *) NULL);
1271 assert(exception->signature == MagickSignature);
1272 width=order;
1273 if ((width % 2) == 0)
1274 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
1275 convolve_image=CloneImage(image,0,0,MagickTrue,exception);
1276 if (convolve_image == (Image *) NULL)
1277 return((Image *) NULL);
1278 if (SetImageStorageClass(convolve_image,DirectClass) == MagickFalse)
1279 {
1280 InheritException(exception,&convolve_image->exception);
1281 convolve_image=DestroyImage(convolve_image);
1282 return((Image *) NULL);
1283 }
1284 if (image->debug != MagickFalse)
1285 {
1286 char
1287 format[MaxTextExtent],
1288 *message;
1289
cristy117ff172010-08-15 21:35:32 +00001290 register const double
1291 *k;
1292
cristybb503372010-05-27 20:51:26 +00001293 ssize_t
cristyfccdab92009-11-30 16:43:57 +00001294 u,
1295 v;
1296
cristyfccdab92009-11-30 16:43:57 +00001297 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00001298 " ConvolveImage with %.20gx%.20g kernel:",(double) width,(double)
1299 width);
cristyfccdab92009-11-30 16:43:57 +00001300 message=AcquireString("");
1301 k=kernel;
cristybb503372010-05-27 20:51:26 +00001302 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001303 {
1304 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00001305 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristyfccdab92009-11-30 16:43:57 +00001306 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00001307 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001308 {
cristyb51dff52011-05-19 16:55:47 +00001309 (void) FormatLocaleString(format,MaxTextExtent,"%g ",*k++);
cristyfccdab92009-11-30 16:43:57 +00001310 (void) ConcatenateString(&message,format);
1311 }
1312 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
1313 }
1314 message=DestroyString(message);
1315 }
1316 /*
1317 Normalize kernel.
1318 */
1319 normal_kernel=(double *) AcquireQuantumMemory(width*width,
1320 sizeof(*normal_kernel));
1321 if (normal_kernel == (double *) NULL)
1322 {
1323 convolve_image=DestroyImage(convolve_image);
1324 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1325 }
1326 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00001327 for (i=0; i < (ssize_t) (width*width); i++)
cristyfccdab92009-11-30 16:43:57 +00001328 gamma+=kernel[i];
1329 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristybb503372010-05-27 20:51:26 +00001330 for (i=0; i < (ssize_t) (width*width); i++)
cristyfccdab92009-11-30 16:43:57 +00001331 normal_kernel[i]=gamma*kernel[i];
1332 /*
1333 Convolve image.
1334 */
1335 status=MagickTrue;
1336 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00001337 GetPixelInfo(image,&bias);
1338 SetPixelInfoBias(image,&bias);
cristyfccdab92009-11-30 16:43:57 +00001339 image_view=AcquireCacheView(image);
1340 convolve_view=AcquireCacheView(convolve_image);
1341#if defined(MAGICKCORE_OPENMP_SUPPORT)
1342 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1343#endif
cristybb503372010-05-27 20:51:26 +00001344 for (y=0; y < (ssize_t) image->rows; y++)
cristyfccdab92009-11-30 16:43:57 +00001345 {
1346 MagickBooleanType
1347 sync;
1348
cristy4c08aed2011-07-01 19:47:50 +00001349 register const Quantum
cristyfccdab92009-11-30 16:43:57 +00001350 *restrict p;
1351
cristy4c08aed2011-07-01 19:47:50 +00001352 register Quantum
cristyfccdab92009-11-30 16:43:57 +00001353 *restrict q;
1354
cristy117ff172010-08-15 21:35:32 +00001355 register ssize_t
1356 x;
1357
cristyfccdab92009-11-30 16:43:57 +00001358 if (status == MagickFalse)
1359 continue;
cristyce889302010-06-30 19:16:36 +00001360 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
1361 (width/2L),image->columns+width,width,exception);
cristyfccdab92009-11-30 16:43:57 +00001362 q=GetCacheViewAuthenticPixels(convolve_view,0,y,convolve_image->columns,1,
1363 exception);
cristy4c08aed2011-07-01 19:47:50 +00001364 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristyfccdab92009-11-30 16:43:57 +00001365 {
1366 status=MagickFalse;
1367 continue;
1368 }
cristybb503372010-05-27 20:51:26 +00001369 for (x=0; x < (ssize_t) image->columns; x++)
cristyfccdab92009-11-30 16:43:57 +00001370 {
cristy4c08aed2011-07-01 19:47:50 +00001371 PixelInfo
cristyfccdab92009-11-30 16:43:57 +00001372 pixel;
1373
1374 register const double
1375 *restrict k;
1376
cristy4c08aed2011-07-01 19:47:50 +00001377 register const Quantum
cristyfccdab92009-11-30 16:43:57 +00001378 *restrict kernel_pixels;
1379
cristybb503372010-05-27 20:51:26 +00001380 register ssize_t
cristyfccdab92009-11-30 16:43:57 +00001381 u;
1382
cristy117ff172010-08-15 21:35:32 +00001383 ssize_t
1384 v;
1385
cristyfccdab92009-11-30 16:43:57 +00001386 pixel=bias;
1387 k=normal_kernel;
1388 kernel_pixels=p;
cristy2b9582a2011-07-04 17:38:56 +00001389 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) == 0) || (image->matte == MagickFalse))
cristyfccdab92009-11-30 16:43:57 +00001390 {
cristybb503372010-05-27 20:51:26 +00001391 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001392 {
cristybb503372010-05-27 20:51:26 +00001393 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001394 {
cristy4c08aed2011-07-01 19:47:50 +00001395 pixel.red+=(*k)*GetPixelRed(image,kernel_pixels+u*
1396 GetPixelChannels(image));
1397 pixel.green+=(*k)*GetPixelGreen(image,kernel_pixels+u*
1398 GetPixelChannels(image));
1399 pixel.blue+=(*k)*GetPixelBlue(image,kernel_pixels+u*
1400 GetPixelChannels(image));
1401 if (image->colorspace == CMYKColorspace)
1402 pixel.black+=(*k)*GetPixelBlack(image,kernel_pixels+u*
1403 GetPixelChannels(image));
cristyfccdab92009-11-30 16:43:57 +00001404 k++;
1405 }
cristy4c08aed2011-07-01 19:47:50 +00001406 kernel_pixels+=(image->columns+width)*GetPixelChannels(image);
cristyfccdab92009-11-30 16:43:57 +00001407 }
cristy2b9582a2011-07-04 17:38:56 +00001408 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001409 SetPixelRed(convolve_image,ClampToQuantum(pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00001410 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001411 SetPixelGreen(convolve_image,ClampToQuantum(pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00001412 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001413 SetPixelBlue(convolve_image,ClampToQuantum(pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00001414 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001415 (image->colorspace == CMYKColorspace))
1416 SetPixelBlack(convolve_image,ClampToQuantum(pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00001417 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristyfccdab92009-11-30 16:43:57 +00001418 {
1419 k=normal_kernel;
1420 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001421 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001422 {
cristybb503372010-05-27 20:51:26 +00001423 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001424 {
cristy4c08aed2011-07-01 19:47:50 +00001425 pixel.alpha+=(*k)*GetPixelAlpha(image,kernel_pixels+u*
1426 GetPixelChannels(image));
cristyfccdab92009-11-30 16:43:57 +00001427 k++;
1428 }
cristy4c08aed2011-07-01 19:47:50 +00001429 kernel_pixels+=(image->columns+width)*
1430 GetPixelChannels(image);
cristyfccdab92009-11-30 16:43:57 +00001431 }
cristy4c08aed2011-07-01 19:47:50 +00001432 SetPixelAlpha(convolve_image,ClampToQuantum(pixel.alpha),q);
cristyfccdab92009-11-30 16:43:57 +00001433 }
1434 }
1435 else
1436 {
1437 MagickRealType
1438 alpha,
1439 gamma;
1440
1441 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00001442 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001443 {
cristybb503372010-05-27 20:51:26 +00001444 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001445 {
cristy4c08aed2011-07-01 19:47:50 +00001446 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,
1447 kernel_pixels+u*GetPixelChannels(image)));
1448 pixel.red+=(*k)*alpha*GetPixelRed(image,kernel_pixels+u*
1449 GetPixelChannels(image));
1450 pixel.green+=(*k)*alpha*GetPixelGreen(image,kernel_pixels+u*
1451 GetPixelChannels(image));
1452 pixel.blue+=(*k)*alpha*GetPixelBlue(image,kernel_pixels+u*
1453 GetPixelChannels(image));
1454 if (image->colorspace == CMYKColorspace)
1455 pixel.black+=(*k)*alpha*GetPixelBlack(image,kernel_pixels+u*
1456 GetPixelChannels(image));
cristyfccdab92009-11-30 16:43:57 +00001457 gamma+=(*k)*alpha;
1458 k++;
1459 }
cristy4c08aed2011-07-01 19:47:50 +00001460 kernel_pixels+=(image->columns+width)*GetPixelChannels(image);
cristyfccdab92009-11-30 16:43:57 +00001461 }
1462 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy2b9582a2011-07-04 17:38:56 +00001463 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001464 SetPixelRed(convolve_image,ClampToQuantum(gamma*pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00001465 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001466 SetPixelGreen(convolve_image,ClampToQuantum(gamma*pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00001467 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001468 SetPixelBlue(convolve_image,ClampToQuantum(gamma*pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00001469 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristyfccdab92009-11-30 16:43:57 +00001470 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00001471 SetPixelBlack(convolve_image,ClampToQuantum(gamma*pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00001472 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristyfccdab92009-11-30 16:43:57 +00001473 {
cristyfccdab92009-11-30 16:43:57 +00001474 k=normal_kernel;
1475 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001476 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001477 {
cristybb503372010-05-27 20:51:26 +00001478 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001479 {
cristy4c08aed2011-07-01 19:47:50 +00001480 pixel.alpha+=(*k)*GetPixelAlpha(image,kernel_pixels+u);
cristyfccdab92009-11-30 16:43:57 +00001481 k++;
1482 }
cristy4c08aed2011-07-01 19:47:50 +00001483 kernel_pixels+=(image->columns+width)*
1484 GetPixelChannels(image);
cristyfccdab92009-11-30 16:43:57 +00001485 }
cristy4c08aed2011-07-01 19:47:50 +00001486 SetPixelAlpha(convolve_image,ClampToQuantum(pixel.alpha),q);
cristyfccdab92009-11-30 16:43:57 +00001487 }
1488 }
cristy4c08aed2011-07-01 19:47:50 +00001489 p+=GetPixelChannels(image);
1490 q+=GetPixelChannels(convolve_image);
cristyfccdab92009-11-30 16:43:57 +00001491 }
1492 sync=SyncCacheViewAuthenticPixels(convolve_view,exception);
1493 if (sync == MagickFalse)
1494 status=MagickFalse;
1495 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1496 {
1497 MagickBooleanType
1498 proceed;
1499
1500#if defined(MAGICKCORE_OPENMP_SUPPORT)
1501 #pragma omp critical (MagickCore_ConvolveImageChannel)
1502#endif
1503 proceed=SetImageProgress(image,ConvolveImageTag,progress++,image->rows);
1504 if (proceed == MagickFalse)
1505 status=MagickFalse;
1506 }
1507 }
1508 convolve_image->type=image->type;
1509 convolve_view=DestroyCacheView(convolve_view);
1510 image_view=DestroyCacheView(image_view);
1511 normal_kernel=(double *) RelinquishMagickMemory(normal_kernel);
1512 if (status == MagickFalse)
1513 convolve_image=DestroyImage(convolve_image);
1514 return(convolve_image);
1515}
1516
1517/*
1518%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1519% %
1520% %
1521% %
cristy3ed852e2009-09-05 21:47:34 +00001522% D e s p e c k l e I m a g e %
1523% %
1524% %
1525% %
1526%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1527%
1528% DespeckleImage() reduces the speckle noise in an image while perserving the
1529% edges of the original image.
1530%
1531% The format of the DespeckleImage method is:
1532%
1533% Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1534%
1535% A description of each parameter follows:
1536%
1537% o image: the image.
1538%
1539% o exception: return any errors or warnings in this structure.
1540%
1541*/
1542
cristybb503372010-05-27 20:51:26 +00001543static void Hull(const ssize_t x_offset,const ssize_t y_offset,
1544 const size_t columns,const size_t rows,Quantum *f,Quantum *g,
cristy3ed852e2009-09-05 21:47:34 +00001545 const int polarity)
1546{
cristy3ed852e2009-09-05 21:47:34 +00001547 MagickRealType
1548 v;
1549
cristy3ed852e2009-09-05 21:47:34 +00001550 register Quantum
1551 *p,
1552 *q,
1553 *r,
1554 *s;
1555
cristy117ff172010-08-15 21:35:32 +00001556 register ssize_t
1557 x;
1558
1559 ssize_t
1560 y;
1561
cristy3ed852e2009-09-05 21:47:34 +00001562 assert(f != (Quantum *) NULL);
1563 assert(g != (Quantum *) NULL);
1564 p=f+(columns+2);
1565 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001566 r=p+(y_offset*((ssize_t) columns+2)+x_offset);
1567 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001568 {
1569 p++;
1570 q++;
1571 r++;
1572 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001573 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001574 {
1575 v=(MagickRealType) (*p);
1576 if ((MagickRealType) *r >= (v+(MagickRealType) ScaleCharToQuantum(2)))
1577 v+=ScaleCharToQuantum(1);
1578 *q=(Quantum) v;
1579 p++;
1580 q++;
1581 r++;
1582 }
1583 else
cristybb503372010-05-27 20:51:26 +00001584 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001585 {
1586 v=(MagickRealType) (*p);
1587 if ((MagickRealType) *r <= (v-(MagickRealType) ScaleCharToQuantum(2)))
cristybb503372010-05-27 20:51:26 +00001588 v-=(ssize_t) ScaleCharToQuantum(1);
cristy3ed852e2009-09-05 21:47:34 +00001589 *q=(Quantum) v;
1590 p++;
1591 q++;
1592 r++;
1593 }
1594 p++;
1595 q++;
1596 r++;
1597 }
1598 p=f+(columns+2);
1599 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001600 r=q+(y_offset*((ssize_t) columns+2)+x_offset);
1601 s=q-(y_offset*((ssize_t) columns+2)+x_offset);
1602 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001603 {
1604 p++;
1605 q++;
1606 r++;
1607 s++;
1608 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001609 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001610 {
1611 v=(MagickRealType) (*q);
1612 if (((MagickRealType) *s >=
1613 (v+(MagickRealType) ScaleCharToQuantum(2))) &&
1614 ((MagickRealType) *r > v))
1615 v+=ScaleCharToQuantum(1);
1616 *p=(Quantum) v;
1617 p++;
1618 q++;
1619 r++;
1620 s++;
1621 }
1622 else
cristybb503372010-05-27 20:51:26 +00001623 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001624 {
1625 v=(MagickRealType) (*q);
1626 if (((MagickRealType) *s <=
1627 (v-(MagickRealType) ScaleCharToQuantum(2))) &&
1628 ((MagickRealType) *r < v))
1629 v-=(MagickRealType) ScaleCharToQuantum(1);
1630 *p=(Quantum) v;
1631 p++;
1632 q++;
1633 r++;
1634 s++;
1635 }
1636 p++;
1637 q++;
1638 r++;
1639 s++;
1640 }
1641}
1642
1643MagickExport Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1644{
1645#define DespeckleImageTag "Despeckle/Image"
1646
cristy2407fc22009-09-11 00:55:25 +00001647 CacheView
1648 *despeckle_view,
1649 *image_view;
1650
cristy3ed852e2009-09-05 21:47:34 +00001651 Image
1652 *despeckle_image;
1653
cristy3ed852e2009-09-05 21:47:34 +00001654 MagickBooleanType
1655 status;
1656
cristya58c3172011-02-19 19:23:11 +00001657 register ssize_t
1658 i;
1659
cristy3ed852e2009-09-05 21:47:34 +00001660 Quantum
cristy65b9f392011-02-22 14:22:54 +00001661 *restrict buffers,
1662 *restrict pixels;
cristy3ed852e2009-09-05 21:47:34 +00001663
1664 size_t
cristya58c3172011-02-19 19:23:11 +00001665 length,
1666 number_channels;
cristy117ff172010-08-15 21:35:32 +00001667
cristybb503372010-05-27 20:51:26 +00001668 static const ssize_t
cristy691a29e2009-09-11 00:44:10 +00001669 X[4] = {0, 1, 1,-1},
1670 Y[4] = {1, 0, 1, 1};
cristy3ed852e2009-09-05 21:47:34 +00001671
cristy3ed852e2009-09-05 21:47:34 +00001672 /*
1673 Allocate despeckled image.
1674 */
1675 assert(image != (const Image *) NULL);
1676 assert(image->signature == MagickSignature);
1677 if (image->debug != MagickFalse)
1678 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1679 assert(exception != (ExceptionInfo *) NULL);
1680 assert(exception->signature == MagickSignature);
1681 despeckle_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1682 exception);
1683 if (despeckle_image == (Image *) NULL)
1684 return((Image *) NULL);
1685 if (SetImageStorageClass(despeckle_image,DirectClass) == MagickFalse)
1686 {
1687 InheritException(exception,&despeckle_image->exception);
1688 despeckle_image=DestroyImage(despeckle_image);
1689 return((Image *) NULL);
1690 }
1691 /*
1692 Allocate image buffers.
1693 */
1694 length=(size_t) ((image->columns+2)*(image->rows+2));
cristy65b9f392011-02-22 14:22:54 +00001695 pixels=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1696 buffers=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1697 if ((pixels == (Quantum *) NULL) || (buffers == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001698 {
cristy65b9f392011-02-22 14:22:54 +00001699 if (buffers != (Quantum *) NULL)
1700 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1701 if (pixels != (Quantum *) NULL)
1702 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001703 despeckle_image=DestroyImage(despeckle_image);
1704 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1705 }
1706 /*
1707 Reduce speckle in the image.
1708 */
1709 status=MagickTrue;
cristy109695a2011-02-19 19:38:14 +00001710 number_channels=(size_t) (image->colorspace == CMYKColorspace ? 5 : 4);
cristy3ed852e2009-09-05 21:47:34 +00001711 image_view=AcquireCacheView(image);
1712 despeckle_view=AcquireCacheView(despeckle_image);
cristy8df3d002011-02-19 19:40:59 +00001713 for (i=0; i < (ssize_t) number_channels; i++)
cristy3ed852e2009-09-05 21:47:34 +00001714 {
cristy3ed852e2009-09-05 21:47:34 +00001715 register Quantum
1716 *buffer,
1717 *pixel;
1718
cristyc1488b52011-02-19 18:54:15 +00001719 register ssize_t
cristya58c3172011-02-19 19:23:11 +00001720 k,
cristyc1488b52011-02-19 18:54:15 +00001721 x;
1722
cristy117ff172010-08-15 21:35:32 +00001723 ssize_t
1724 j,
1725 y;
1726
cristy3ed852e2009-09-05 21:47:34 +00001727 if (status == MagickFalse)
1728 continue;
cristy65b9f392011-02-22 14:22:54 +00001729 pixel=pixels;
cristy3ed852e2009-09-05 21:47:34 +00001730 (void) ResetMagickMemory(pixel,0,length*sizeof(*pixel));
cristy65b9f392011-02-22 14:22:54 +00001731 buffer=buffers;
cristybb503372010-05-27 20:51:26 +00001732 j=(ssize_t) image->columns+2;
1733 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001734 {
cristy4c08aed2011-07-01 19:47:50 +00001735 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001736 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001737
1738 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001739 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001740 break;
1741 j++;
cristybb503372010-05-27 20:51:26 +00001742 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001743 {
cristya58c3172011-02-19 19:23:11 +00001744 switch (i)
cristy3ed852e2009-09-05 21:47:34 +00001745 {
cristy4c08aed2011-07-01 19:47:50 +00001746 case 0: pixel[j]=GetPixelRed(image,p); break;
1747 case 1: pixel[j]=GetPixelGreen(image,p); break;
1748 case 2: pixel[j]=GetPixelBlue(image,p); break;
1749 case 3: pixel[j]=GetPixelAlpha(image,p); break;
1750 case 4: pixel[j]=GetPixelBlack(image,p); break;
cristy3ed852e2009-09-05 21:47:34 +00001751 default: break;
1752 }
cristy4c08aed2011-07-01 19:47:50 +00001753 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001754 j++;
1755 }
1756 j++;
1757 }
cristy3ed852e2009-09-05 21:47:34 +00001758 (void) ResetMagickMemory(buffer,0,length*sizeof(*buffer));
cristya58c3172011-02-19 19:23:11 +00001759 for (k=0; k < 4; k++)
cristy3ed852e2009-09-05 21:47:34 +00001760 {
cristya58c3172011-02-19 19:23:11 +00001761 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,1);
1762 Hull(-X[k],-Y[k],image->columns,image->rows,pixel,buffer,1);
1763 Hull(-X[k],-Y[k],image->columns,image->rows,pixel,buffer,-1);
1764 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,-1);
cristy3ed852e2009-09-05 21:47:34 +00001765 }
cristybb503372010-05-27 20:51:26 +00001766 j=(ssize_t) image->columns+2;
1767 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001768 {
1769 MagickBooleanType
1770 sync;
1771
cristy4c08aed2011-07-01 19:47:50 +00001772 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001773 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001774
1775 q=GetCacheViewAuthenticPixels(despeckle_view,0,y,despeckle_image->columns,
1776 1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001777 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001778 break;
1779 j++;
cristybb503372010-05-27 20:51:26 +00001780 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001781 {
cristya58c3172011-02-19 19:23:11 +00001782 switch (i)
cristy3ed852e2009-09-05 21:47:34 +00001783 {
cristy4c08aed2011-07-01 19:47:50 +00001784 case 0: SetPixelRed(despeckle_image,pixel[j],q); break;
1785 case 1: SetPixelGreen(despeckle_image,pixel[j],q); break;
1786 case 2: SetPixelBlue(despeckle_image,pixel[j],q); break;
1787 case 3: SetPixelAlpha(despeckle_image,pixel[j],q); break;
1788 case 4: SetPixelBlack(despeckle_image,pixel[j],q); break;
cristy3ed852e2009-09-05 21:47:34 +00001789 default: break;
1790 }
cristy4c08aed2011-07-01 19:47:50 +00001791 q+=GetPixelChannels(despeckle_image);
cristy3ed852e2009-09-05 21:47:34 +00001792 j++;
1793 }
1794 sync=SyncCacheViewAuthenticPixels(despeckle_view,exception);
1795 if (sync == MagickFalse)
1796 {
1797 status=MagickFalse;
1798 break;
1799 }
1800 j++;
1801 }
1802 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1803 {
1804 MagickBooleanType
1805 proceed;
1806
cristya58c3172011-02-19 19:23:11 +00001807 proceed=SetImageProgress(image,DespeckleImageTag,(MagickOffsetType) i,
1808 number_channels);
cristy3ed852e2009-09-05 21:47:34 +00001809 if (proceed == MagickFalse)
1810 status=MagickFalse;
1811 }
1812 }
1813 despeckle_view=DestroyCacheView(despeckle_view);
1814 image_view=DestroyCacheView(image_view);
cristy65b9f392011-02-22 14:22:54 +00001815 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1816 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001817 despeckle_image->type=image->type;
1818 if (status == MagickFalse)
1819 despeckle_image=DestroyImage(despeckle_image);
1820 return(despeckle_image);
1821}
1822
1823/*
1824%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1825% %
1826% %
1827% %
1828% E d g e I m a g e %
1829% %
1830% %
1831% %
1832%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1833%
1834% EdgeImage() finds edges in an image. Radius defines the radius of the
1835% convolution filter. Use a radius of 0 and EdgeImage() selects a suitable
1836% radius for you.
1837%
1838% The format of the EdgeImage method is:
1839%
1840% Image *EdgeImage(const Image *image,const double radius,
1841% ExceptionInfo *exception)
1842%
1843% A description of each parameter follows:
1844%
1845% o image: the image.
1846%
1847% o radius: the radius of the pixel neighborhood.
1848%
1849% o exception: return any errors or warnings in this structure.
1850%
1851*/
1852MagickExport Image *EdgeImage(const Image *image,const double radius,
1853 ExceptionInfo *exception)
1854{
1855 Image
1856 *edge_image;
1857
1858 double
1859 *kernel;
1860
cristybb503372010-05-27 20:51:26 +00001861 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001862 i;
1863
cristybb503372010-05-27 20:51:26 +00001864 size_t
cristy3ed852e2009-09-05 21:47:34 +00001865 width;
1866
1867 assert(image != (const Image *) NULL);
1868 assert(image->signature == MagickSignature);
1869 if (image->debug != MagickFalse)
1870 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1871 assert(exception != (ExceptionInfo *) NULL);
1872 assert(exception->signature == MagickSignature);
1873 width=GetOptimalKernelWidth1D(radius,0.5);
1874 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
1875 if (kernel == (double *) NULL)
1876 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00001877 for (i=0; i < (ssize_t) (width*width); i++)
cristy3ed852e2009-09-05 21:47:34 +00001878 kernel[i]=(-1.0);
1879 kernel[i/2]=(double) (width*width-1.0);
1880 edge_image=ConvolveImage(image,width,kernel,exception);
1881 kernel=(double *) RelinquishMagickMemory(kernel);
1882 return(edge_image);
1883}
1884
1885/*
1886%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1887% %
1888% %
1889% %
1890% E m b o s s I m a g e %
1891% %
1892% %
1893% %
1894%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1895%
1896% EmbossImage() returns a grayscale image with a three-dimensional effect.
1897% We convolve the image with a Gaussian operator of the given radius and
1898% standard deviation (sigma). For reasonable results, radius should be
1899% larger than sigma. Use a radius of 0 and Emboss() selects a suitable
1900% radius for you.
1901%
1902% The format of the EmbossImage method is:
1903%
1904% Image *EmbossImage(const Image *image,const double radius,
1905% const double sigma,ExceptionInfo *exception)
1906%
1907% A description of each parameter follows:
1908%
1909% o image: the image.
1910%
1911% o radius: the radius of the pixel neighborhood.
1912%
1913% o sigma: the standard deviation of the Gaussian, in pixels.
1914%
1915% o exception: return any errors or warnings in this structure.
1916%
1917*/
1918MagickExport Image *EmbossImage(const Image *image,const double radius,
1919 const double sigma,ExceptionInfo *exception)
1920{
1921 double
1922 *kernel;
1923
1924 Image
1925 *emboss_image;
1926
cristybb503372010-05-27 20:51:26 +00001927 register ssize_t
cristy47e00502009-12-17 19:19:57 +00001928 i;
1929
cristybb503372010-05-27 20:51:26 +00001930 size_t
cristy3ed852e2009-09-05 21:47:34 +00001931 width;
1932
cristy117ff172010-08-15 21:35:32 +00001933 ssize_t
1934 j,
1935 k,
1936 u,
1937 v;
1938
cristy3ed852e2009-09-05 21:47:34 +00001939 assert(image != (Image *) NULL);
1940 assert(image->signature == MagickSignature);
1941 if (image->debug != MagickFalse)
1942 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1943 assert(exception != (ExceptionInfo *) NULL);
1944 assert(exception->signature == MagickSignature);
1945 width=GetOptimalKernelWidth2D(radius,sigma);
1946 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
1947 if (kernel == (double *) NULL)
1948 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00001949 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +00001950 k=j;
1951 i=0;
1952 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00001953 {
cristy47e00502009-12-17 19:19:57 +00001954 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00001955 {
cristy4205a3c2010-09-12 20:19:59 +00001956 kernel[i]=(double) (((u < 0) || (v < 0) ? -8.0 : 8.0)*
cristy47e00502009-12-17 19:19:57 +00001957 exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
cristy4205a3c2010-09-12 20:19:59 +00001958 (2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +00001959 if (u != k)
cristy3ed852e2009-09-05 21:47:34 +00001960 kernel[i]=0.0;
1961 i++;
1962 }
cristy47e00502009-12-17 19:19:57 +00001963 k--;
cristy3ed852e2009-09-05 21:47:34 +00001964 }
1965 emboss_image=ConvolveImage(image,width,kernel,exception);
1966 if (emboss_image != (Image *) NULL)
1967 (void) EqualizeImage(emboss_image);
1968 kernel=(double *) RelinquishMagickMemory(kernel);
1969 return(emboss_image);
1970}
1971
1972/*
1973%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1974% %
1975% %
1976% %
cristy56a9e512010-01-06 18:18:55 +00001977% F i l t e r I m a g e %
1978% %
1979% %
1980% %
1981%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1982%
1983% FilterImage() applies a custom convolution kernel to the image.
1984%
1985% The format of the FilterImage method is:
1986%
cristy2be15382010-01-21 02:38:03 +00001987% Image *FilterImage(const Image *image,const KernelInfo *kernel,
cristy56a9e512010-01-06 18:18:55 +00001988% ExceptionInfo *exception)
1989% Image *FilterImageChannel(const Image *image,const ChannelType channel,
cristy2be15382010-01-21 02:38:03 +00001990% const KernelInfo *kernel,ExceptionInfo *exception)
cristy56a9e512010-01-06 18:18:55 +00001991%
1992% A description of each parameter follows:
1993%
1994% o image: the image.
1995%
1996% o channel: the channel type.
1997%
1998% o kernel: the filtering kernel.
1999%
2000% o exception: return any errors or warnings in this structure.
2001%
2002*/
2003
cristy2be15382010-01-21 02:38:03 +00002004MagickExport Image *FilterImage(const Image *image,const KernelInfo *kernel,
cristy56a9e512010-01-06 18:18:55 +00002005 ExceptionInfo *exception)
2006{
2007 Image
2008 *filter_image;
2009
2010 filter_image=FilterImageChannel(image,DefaultChannels,kernel,exception);
2011 return(filter_image);
2012}
2013
2014MagickExport Image *FilterImageChannel(const Image *image,
cristy2be15382010-01-21 02:38:03 +00002015 const ChannelType channel,const KernelInfo *kernel,ExceptionInfo *exception)
cristy56a9e512010-01-06 18:18:55 +00002016{
2017#define FilterImageTag "Filter/Image"
2018
2019 CacheView
2020 *filter_view,
2021 *image_view;
2022
cristy56a9e512010-01-06 18:18:55 +00002023 Image
2024 *filter_image;
2025
cristy56a9e512010-01-06 18:18:55 +00002026 MagickBooleanType
2027 status;
2028
cristybb503372010-05-27 20:51:26 +00002029 MagickOffsetType
2030 progress;
2031
cristy4c08aed2011-07-01 19:47:50 +00002032 PixelInfo
cristy56a9e512010-01-06 18:18:55 +00002033 bias;
2034
cristybb503372010-05-27 20:51:26 +00002035 ssize_t
2036 y;
2037
cristy56a9e512010-01-06 18:18:55 +00002038 /*
2039 Initialize filter image attributes.
2040 */
2041 assert(image != (Image *) NULL);
2042 assert(image->signature == MagickSignature);
2043 if (image->debug != MagickFalse)
2044 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2045 assert(exception != (ExceptionInfo *) NULL);
2046 assert(exception->signature == MagickSignature);
2047 if ((kernel->width % 2) == 0)
2048 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
2049 filter_image=CloneImage(image,0,0,MagickTrue,exception);
2050 if (filter_image == (Image *) NULL)
2051 return((Image *) NULL);
2052 if (SetImageStorageClass(filter_image,DirectClass) == MagickFalse)
2053 {
2054 InheritException(exception,&filter_image->exception);
2055 filter_image=DestroyImage(filter_image);
2056 return((Image *) NULL);
2057 }
2058 if (image->debug != MagickFalse)
2059 {
2060 char
2061 format[MaxTextExtent],
2062 *message;
2063
cristy117ff172010-08-15 21:35:32 +00002064 register const double
2065 *k;
2066
cristybb503372010-05-27 20:51:26 +00002067 ssize_t
cristy56a9e512010-01-06 18:18:55 +00002068 u,
2069 v;
2070
cristy56a9e512010-01-06 18:18:55 +00002071 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00002072 " FilterImage with %.20gx%.20g kernel:",(double) kernel->width,(double)
2073 kernel->height);
cristy56a9e512010-01-06 18:18:55 +00002074 message=AcquireString("");
2075 k=kernel->values;
cristybb503372010-05-27 20:51:26 +00002076 for (v=0; v < (ssize_t) kernel->height; v++)
cristy56a9e512010-01-06 18:18:55 +00002077 {
2078 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00002079 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy56a9e512010-01-06 18:18:55 +00002080 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00002081 for (u=0; u < (ssize_t) kernel->width; u++)
cristy56a9e512010-01-06 18:18:55 +00002082 {
cristyb51dff52011-05-19 16:55:47 +00002083 (void) FormatLocaleString(format,MaxTextExtent,"%g ",*k++);
cristy56a9e512010-01-06 18:18:55 +00002084 (void) ConcatenateString(&message,format);
2085 }
2086 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
2087 }
2088 message=DestroyString(message);
2089 }
cristy36826ab2010-03-06 01:29:30 +00002090 status=AccelerateConvolveImage(image,kernel,filter_image,exception);
cristyd43a46b2010-01-21 02:13:41 +00002091 if (status == MagickTrue)
2092 return(filter_image);
cristy56a9e512010-01-06 18:18:55 +00002093 /*
2094 Filter image.
2095 */
2096 status=MagickTrue;
2097 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00002098 GetPixelInfo(image,&bias);
2099 SetPixelInfoBias(image,&bias);
cristy56a9e512010-01-06 18:18:55 +00002100 image_view=AcquireCacheView(image);
2101 filter_view=AcquireCacheView(filter_image);
2102#if defined(MAGICKCORE_OPENMP_SUPPORT)
2103 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2104#endif
cristybb503372010-05-27 20:51:26 +00002105 for (y=0; y < (ssize_t) image->rows; y++)
cristy56a9e512010-01-06 18:18:55 +00002106 {
2107 MagickBooleanType
2108 sync;
2109
cristy4c08aed2011-07-01 19:47:50 +00002110 register const Quantum
cristy56a9e512010-01-06 18:18:55 +00002111 *restrict p;
2112
cristy4c08aed2011-07-01 19:47:50 +00002113 register Quantum
cristy56a9e512010-01-06 18:18:55 +00002114 *restrict q;
2115
cristy117ff172010-08-15 21:35:32 +00002116 register ssize_t
2117 x;
2118
cristy56a9e512010-01-06 18:18:55 +00002119 if (status == MagickFalse)
2120 continue;
cristybb503372010-05-27 20:51:26 +00002121 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) kernel->width/2L),
cristy117ff172010-08-15 21:35:32 +00002122 y-(ssize_t) (kernel->height/2L),image->columns+kernel->width,
2123 kernel->height,exception);
cristy56a9e512010-01-06 18:18:55 +00002124 q=GetCacheViewAuthenticPixels(filter_view,0,y,filter_image->columns,1,
2125 exception);
cristy4c08aed2011-07-01 19:47:50 +00002126 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy56a9e512010-01-06 18:18:55 +00002127 {
2128 status=MagickFalse;
2129 continue;
2130 }
cristybb503372010-05-27 20:51:26 +00002131 for (x=0; x < (ssize_t) image->columns; x++)
cristy56a9e512010-01-06 18:18:55 +00002132 {
cristy4c08aed2011-07-01 19:47:50 +00002133 PixelInfo
cristy56a9e512010-01-06 18:18:55 +00002134 pixel;
2135
2136 register const double
2137 *restrict k;
2138
cristy4c08aed2011-07-01 19:47:50 +00002139 register const Quantum
cristy56a9e512010-01-06 18:18:55 +00002140 *restrict kernel_pixels;
2141
cristybb503372010-05-27 20:51:26 +00002142 register ssize_t
cristy56a9e512010-01-06 18:18:55 +00002143 u;
2144
cristy117ff172010-08-15 21:35:32 +00002145 ssize_t
2146 v;
2147
cristy56a9e512010-01-06 18:18:55 +00002148 pixel=bias;
cristy36826ab2010-03-06 01:29:30 +00002149 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002150 kernel_pixels=p;
cristy2b9582a2011-07-04 17:38:56 +00002151 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) == 0) || (image->matte == MagickFalse))
cristy56a9e512010-01-06 18:18:55 +00002152 {
cristybb503372010-05-27 20:51:26 +00002153 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002154 {
cristybb503372010-05-27 20:51:26 +00002155 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002156 {
cristy4c08aed2011-07-01 19:47:50 +00002157 pixel.red+=(*k)*GetPixelRed(image,kernel_pixels+u*
2158 GetPixelChannels(image));
2159 pixel.green+=(*k)*GetPixelGreen(image,kernel_pixels+u*
2160 GetPixelChannels(image));
2161 pixel.blue+=(*k)*GetPixelBlue(image,kernel_pixels+u*
2162 GetPixelChannels(image));
2163 if (image->colorspace == CMYKColorspace)
2164 pixel.black+=(*k)*GetPixelBlack(image,kernel_pixels+u*
2165 GetPixelChannels(image));
cristy56a9e512010-01-06 18:18:55 +00002166 k++;
2167 }
cristy4c08aed2011-07-01 19:47:50 +00002168 kernel_pixels+=(image->columns+kernel->width)*
2169 GetPixelChannels(image);
cristy56a9e512010-01-06 18:18:55 +00002170 }
cristy2b9582a2011-07-04 17:38:56 +00002171 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002172 SetPixelRed(filter_image,ClampToQuantum(pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00002173 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002174 SetPixelGreen(filter_image,ClampToQuantum(pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00002175 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002176 SetPixelBlue(filter_image,ClampToQuantum(pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00002177 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002178 (image->colorspace == CMYKColorspace))
2179 SetPixelBlack(filter_image,ClampToQuantum(pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00002180 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy56a9e512010-01-06 18:18:55 +00002181 {
cristy36826ab2010-03-06 01:29:30 +00002182 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002183 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00002184 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002185 {
cristybb503372010-05-27 20:51:26 +00002186 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002187 {
cristy4c08aed2011-07-01 19:47:50 +00002188 pixel.alpha+=(*k)*GetPixelRed(image,kernel_pixels+u*
2189 GetPixelChannels(image));
cristy56a9e512010-01-06 18:18:55 +00002190 k++;
2191 }
cristy4c08aed2011-07-01 19:47:50 +00002192 kernel_pixels+=(image->columns+kernel->width)*
2193 GetPixelChannels(image);
cristy56a9e512010-01-06 18:18:55 +00002194 }
cristy4c08aed2011-07-01 19:47:50 +00002195 SetPixelAlpha(filter_image,ClampToQuantum(pixel.alpha),q);
cristy56a9e512010-01-06 18:18:55 +00002196 }
2197 }
2198 else
2199 {
2200 MagickRealType
2201 alpha,
2202 gamma;
2203
2204 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00002205 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002206 {
cristybb503372010-05-27 20:51:26 +00002207 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002208 {
cristy4c08aed2011-07-01 19:47:50 +00002209 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,
2210 kernel_pixels+u*GetPixelChannels(image)));
2211 pixel.red+=(*k)*alpha*GetPixelRed(image,kernel_pixels+u*
2212 GetPixelChannels(image));
2213 pixel.green+=(*k)*alpha*GetPixelGreen(image,kernel_pixels+u*
2214 GetPixelChannels(image));
2215 pixel.blue+=(*k)*alpha*GetPixelBlue(image,kernel_pixels+u*
2216 GetPixelChannels(image));
2217 if (image->colorspace == CMYKColorspace)
2218 pixel.black+=(*k)*alpha*GetPixelBlack(image,kernel_pixels+u*
2219 GetPixelChannels(image));
cristy56a9e512010-01-06 18:18:55 +00002220 gamma+=(*k)*alpha;
2221 k++;
2222 }
cristy4c08aed2011-07-01 19:47:50 +00002223 kernel_pixels+=(image->columns+kernel->width);
2224 GetPixelChannels(image);
cristy56a9e512010-01-06 18:18:55 +00002225 }
2226 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy2b9582a2011-07-04 17:38:56 +00002227 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002228 SetPixelRed(filter_image,ClampToQuantum(gamma*pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00002229 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002230 SetPixelGreen(filter_image,ClampToQuantum(gamma*pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00002231 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002232 SetPixelBlue(filter_image,ClampToQuantum(gamma*pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00002233 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy56a9e512010-01-06 18:18:55 +00002234 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002235 SetPixelBlack(filter_image,ClampToQuantum(gamma*pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00002236 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy56a9e512010-01-06 18:18:55 +00002237 {
cristy36826ab2010-03-06 01:29:30 +00002238 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002239 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00002240 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002241 {
cristybb503372010-05-27 20:51:26 +00002242 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002243 {
cristy4c08aed2011-07-01 19:47:50 +00002244 pixel.alpha+=(*k)*GetPixelAlpha(image,kernel_pixels+u*
2245 GetPixelChannels(image));
cristy56a9e512010-01-06 18:18:55 +00002246 k++;
2247 }
cristy4c08aed2011-07-01 19:47:50 +00002248 kernel_pixels+=(image->columns+kernel->width)*
2249 GetPixelChannels(image);
cristy56a9e512010-01-06 18:18:55 +00002250 }
cristy4c08aed2011-07-01 19:47:50 +00002251 SetPixelAlpha(filter_image,ClampToQuantum(pixel.alpha),q);
cristy56a9e512010-01-06 18:18:55 +00002252 }
2253 }
cristy4c08aed2011-07-01 19:47:50 +00002254 p+=GetPixelChannels(image);
2255 q+=GetPixelChannels(filter_image);
cristy56a9e512010-01-06 18:18:55 +00002256 }
2257 sync=SyncCacheViewAuthenticPixels(filter_view,exception);
2258 if (sync == MagickFalse)
2259 status=MagickFalse;
2260 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2261 {
2262 MagickBooleanType
2263 proceed;
2264
2265#if defined(MAGICKCORE_OPENMP_SUPPORT)
2266 #pragma omp critical (MagickCore_FilterImageChannel)
2267#endif
2268 proceed=SetImageProgress(image,FilterImageTag,progress++,image->rows);
2269 if (proceed == MagickFalse)
2270 status=MagickFalse;
2271 }
2272 }
2273 filter_image->type=image->type;
2274 filter_view=DestroyCacheView(filter_view);
2275 image_view=DestroyCacheView(image_view);
cristy56a9e512010-01-06 18:18:55 +00002276 if (status == MagickFalse)
2277 filter_image=DestroyImage(filter_image);
2278 return(filter_image);
2279}
2280
2281/*
2282%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2283% %
2284% %
2285% %
cristy3ed852e2009-09-05 21:47:34 +00002286% G a u s s i a n B l u r I m a g e %
2287% %
2288% %
2289% %
2290%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2291%
2292% GaussianBlurImage() blurs an image. We convolve the image with a
2293% Gaussian operator of the given radius and standard deviation (sigma).
2294% For reasonable results, the radius should be larger than sigma. Use a
2295% radius of 0 and GaussianBlurImage() selects a suitable radius for you
2296%
2297% The format of the GaussianBlurImage method is:
2298%
2299% Image *GaussianBlurImage(const Image *image,onst double radius,
2300% const double sigma,ExceptionInfo *exception)
2301% Image *GaussianBlurImageChannel(const Image *image,
2302% const ChannelType channel,const double radius,const double sigma,
2303% ExceptionInfo *exception)
2304%
2305% A description of each parameter follows:
2306%
2307% o image: the image.
2308%
2309% o channel: the channel type.
2310%
2311% o radius: the radius of the Gaussian, in pixels, not counting the center
2312% pixel.
2313%
2314% o sigma: the standard deviation of the Gaussian, in pixels.
2315%
2316% o exception: return any errors or warnings in this structure.
2317%
2318*/
2319
2320MagickExport Image *GaussianBlurImage(const Image *image,const double radius,
2321 const double sigma,ExceptionInfo *exception)
2322{
2323 Image
2324 *blur_image;
2325
2326 blur_image=GaussianBlurImageChannel(image,DefaultChannels,radius,sigma,
2327 exception);
2328 return(blur_image);
2329}
2330
2331MagickExport Image *GaussianBlurImageChannel(const Image *image,
2332 const ChannelType channel,const double radius,const double sigma,
2333 ExceptionInfo *exception)
2334{
2335 double
2336 *kernel;
2337
2338 Image
2339 *blur_image;
2340
cristybb503372010-05-27 20:51:26 +00002341 register ssize_t
cristy47e00502009-12-17 19:19:57 +00002342 i;
2343
cristybb503372010-05-27 20:51:26 +00002344 size_t
cristy3ed852e2009-09-05 21:47:34 +00002345 width;
2346
cristy117ff172010-08-15 21:35:32 +00002347 ssize_t
2348 j,
2349 u,
2350 v;
2351
cristy3ed852e2009-09-05 21:47:34 +00002352 assert(image != (const Image *) NULL);
2353 assert(image->signature == MagickSignature);
2354 if (image->debug != MagickFalse)
2355 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2356 assert(exception != (ExceptionInfo *) NULL);
2357 assert(exception->signature == MagickSignature);
2358 width=GetOptimalKernelWidth2D(radius,sigma);
2359 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2360 if (kernel == (double *) NULL)
2361 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00002362 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00002363 i=0;
cristy47e00502009-12-17 19:19:57 +00002364 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002365 {
cristy47e00502009-12-17 19:19:57 +00002366 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00002367 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
2368 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00002369 }
2370 blur_image=ConvolveImageChannel(image,channel,width,kernel,exception);
2371 kernel=(double *) RelinquishMagickMemory(kernel);
2372 return(blur_image);
2373}
2374
2375/*
2376%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2377% %
2378% %
2379% %
cristy3ed852e2009-09-05 21:47:34 +00002380% M o t i o n B l u r I m a g e %
2381% %
2382% %
2383% %
2384%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2385%
2386% MotionBlurImage() simulates motion blur. We convolve the image with a
2387% Gaussian operator of the given radius and standard deviation (sigma).
2388% For reasonable results, radius should be larger than sigma. Use a
2389% radius of 0 and MotionBlurImage() selects a suitable radius for you.
2390% Angle gives the angle of the blurring motion.
2391%
2392% Andrew Protano contributed this effect.
2393%
2394% The format of the MotionBlurImage method is:
2395%
2396% Image *MotionBlurImage(const Image *image,const double radius,
2397% const double sigma,const double angle,ExceptionInfo *exception)
2398% Image *MotionBlurImageChannel(const Image *image,const ChannelType channel,
2399% const double radius,const double sigma,const double angle,
2400% ExceptionInfo *exception)
2401%
2402% A description of each parameter follows:
2403%
2404% o image: the image.
2405%
2406% o channel: the channel type.
2407%
2408% o radius: the radius of the Gaussian, in pixels, not counting the center
2409% o radius: the radius of the Gaussian, in pixels, not counting
2410% the center pixel.
2411%
2412% o sigma: the standard deviation of the Gaussian, in pixels.
2413%
cristycee97112010-05-28 00:44:52 +00002414% o angle: Apply the effect along this angle.
cristy3ed852e2009-09-05 21:47:34 +00002415%
2416% o exception: return any errors or warnings in this structure.
2417%
2418*/
2419
cristybb503372010-05-27 20:51:26 +00002420static double *GetMotionBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +00002421{
cristy3ed852e2009-09-05 21:47:34 +00002422 double
cristy47e00502009-12-17 19:19:57 +00002423 *kernel,
cristy3ed852e2009-09-05 21:47:34 +00002424 normalize;
2425
cristybb503372010-05-27 20:51:26 +00002426 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002427 i;
2428
2429 /*
cristy47e00502009-12-17 19:19:57 +00002430 Generate a 1-D convolution kernel.
cristy3ed852e2009-09-05 21:47:34 +00002431 */
2432 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
2433 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
2434 if (kernel == (double *) NULL)
2435 return(kernel);
cristy3ed852e2009-09-05 21:47:34 +00002436 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00002437 for (i=0; i < (ssize_t) width; i++)
cristy47e00502009-12-17 19:19:57 +00002438 {
cristy4205a3c2010-09-12 20:19:59 +00002439 kernel[i]=(double) (exp((-((double) i*i)/(double) (2.0*MagickSigma*
2440 MagickSigma)))/(MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00002441 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +00002442 }
cristybb503372010-05-27 20:51:26 +00002443 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002444 kernel[i]/=normalize;
2445 return(kernel);
2446}
2447
2448MagickExport Image *MotionBlurImage(const Image *image,const double radius,
2449 const double sigma,const double angle,ExceptionInfo *exception)
2450{
2451 Image
2452 *motion_blur;
2453
2454 motion_blur=MotionBlurImageChannel(image,DefaultChannels,radius,sigma,angle,
2455 exception);
2456 return(motion_blur);
2457}
2458
2459MagickExport Image *MotionBlurImageChannel(const Image *image,
2460 const ChannelType channel,const double radius,const double sigma,
2461 const double angle,ExceptionInfo *exception)
2462{
cristyc4c8d132010-01-07 01:58:38 +00002463 CacheView
2464 *blur_view,
2465 *image_view;
2466
cristy3ed852e2009-09-05 21:47:34 +00002467 double
2468 *kernel;
2469
2470 Image
2471 *blur_image;
2472
cristy3ed852e2009-09-05 21:47:34 +00002473 MagickBooleanType
2474 status;
2475
cristybb503372010-05-27 20:51:26 +00002476 MagickOffsetType
2477 progress;
2478
cristy4c08aed2011-07-01 19:47:50 +00002479 PixelInfo
cristyddd82202009-11-03 20:14:50 +00002480 bias;
cristy3ed852e2009-09-05 21:47:34 +00002481
2482 OffsetInfo
2483 *offset;
2484
2485 PointInfo
2486 point;
2487
cristybb503372010-05-27 20:51:26 +00002488 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002489 i;
2490
cristybb503372010-05-27 20:51:26 +00002491 size_t
cristy3ed852e2009-09-05 21:47:34 +00002492 width;
2493
cristybb503372010-05-27 20:51:26 +00002494 ssize_t
2495 y;
2496
cristy3ed852e2009-09-05 21:47:34 +00002497 assert(image != (Image *) NULL);
2498 assert(image->signature == MagickSignature);
2499 if (image->debug != MagickFalse)
2500 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2501 assert(exception != (ExceptionInfo *) NULL);
2502 width=GetOptimalKernelWidth1D(radius,sigma);
2503 kernel=GetMotionBlurKernel(width,sigma);
2504 if (kernel == (double *) NULL)
2505 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2506 offset=(OffsetInfo *) AcquireQuantumMemory(width,sizeof(*offset));
2507 if (offset == (OffsetInfo *) NULL)
2508 {
2509 kernel=(double *) RelinquishMagickMemory(kernel);
2510 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2511 }
2512 blur_image=CloneImage(image,0,0,MagickTrue,exception);
2513 if (blur_image == (Image *) NULL)
2514 {
2515 kernel=(double *) RelinquishMagickMemory(kernel);
2516 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2517 return((Image *) NULL);
2518 }
2519 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
2520 {
2521 kernel=(double *) RelinquishMagickMemory(kernel);
2522 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2523 InheritException(exception,&blur_image->exception);
2524 blur_image=DestroyImage(blur_image);
2525 return((Image *) NULL);
2526 }
2527 point.x=(double) width*sin(DegreesToRadians(angle));
2528 point.y=(double) width*cos(DegreesToRadians(angle));
cristybb503372010-05-27 20:51:26 +00002529 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002530 {
cristybb503372010-05-27 20:51:26 +00002531 offset[i].x=(ssize_t) ceil((double) (i*point.y)/hypot(point.x,point.y)-0.5);
2532 offset[i].y=(ssize_t) ceil((double) (i*point.x)/hypot(point.x,point.y)-0.5);
cristy3ed852e2009-09-05 21:47:34 +00002533 }
2534 /*
2535 Motion blur image.
2536 */
2537 status=MagickTrue;
2538 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00002539 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00002540 image_view=AcquireCacheView(image);
2541 blur_view=AcquireCacheView(blur_image);
cristyb557a152011-02-22 12:14:30 +00002542#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00002543 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00002544#endif
cristybb503372010-05-27 20:51:26 +00002545 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002546 {
cristy4c08aed2011-07-01 19:47:50 +00002547 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002548 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002549
cristy117ff172010-08-15 21:35:32 +00002550 register ssize_t
2551 x;
2552
cristy3ed852e2009-09-05 21:47:34 +00002553 if (status == MagickFalse)
2554 continue;
2555 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
2556 exception);
cristy4c08aed2011-07-01 19:47:50 +00002557 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002558 {
2559 status=MagickFalse;
2560 continue;
2561 }
cristybb503372010-05-27 20:51:26 +00002562 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002563 {
cristy4c08aed2011-07-01 19:47:50 +00002564 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002565 qixel;
2566
2567 PixelPacket
2568 pixel;
2569
2570 register double
cristyc47d1f82009-11-26 01:44:43 +00002571 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00002572
cristybb503372010-05-27 20:51:26 +00002573 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002574 i;
2575
cristy3ed852e2009-09-05 21:47:34 +00002576 k=kernel;
cristyddd82202009-11-03 20:14:50 +00002577 qixel=bias;
cristy2b9582a2011-07-04 17:38:56 +00002578 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00002579 {
cristybb503372010-05-27 20:51:26 +00002580 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002581 {
2582 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
2583 offset[i].y,&pixel,exception);
2584 qixel.red+=(*k)*pixel.red;
2585 qixel.green+=(*k)*pixel.green;
2586 qixel.blue+=(*k)*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00002587 qixel.alpha+=(*k)*pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00002588 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00002589 qixel.black+=(*k)*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00002590 k++;
2591 }
cristy2b9582a2011-07-04 17:38:56 +00002592 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002593 SetPixelRed(blur_image,
2594 ClampToQuantum(qixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00002595 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002596 SetPixelGreen(blur_image,
2597 ClampToQuantum(qixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00002598 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002599 SetPixelBlue(blur_image,
2600 ClampToQuantum(qixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00002601 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002602 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002603 SetPixelBlack(blur_image,
2604 ClampToQuantum(qixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00002605 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002606 SetPixelAlpha(blur_image,
2607 ClampToQuantum(qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00002608 }
2609 else
2610 {
2611 MagickRealType
2612 alpha,
2613 gamma;
2614
2615 alpha=0.0;
2616 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00002617 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002618 {
2619 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
2620 offset[i].y,&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00002621 alpha=(MagickRealType) (QuantumScale*pixel.alpha);
cristy3ed852e2009-09-05 21:47:34 +00002622 qixel.red+=(*k)*alpha*pixel.red;
2623 qixel.green+=(*k)*alpha*pixel.green;
2624 qixel.blue+=(*k)*alpha*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00002625 qixel.alpha+=(*k)*pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00002626 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00002627 qixel.black+=(*k)*alpha*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00002628 gamma+=(*k)*alpha;
2629 k++;
2630 }
2631 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy2b9582a2011-07-04 17:38:56 +00002632 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002633 SetPixelRed(blur_image,
2634 ClampToQuantum(gamma*qixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00002635 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002636 SetPixelGreen(blur_image,
2637 ClampToQuantum(gamma*qixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00002638 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002639 SetPixelBlue(blur_image,
2640 ClampToQuantum(gamma*qixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00002641 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002642 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002643 SetPixelBlack(blur_image,
2644 ClampToQuantum(gamma*qixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00002645 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002646 SetPixelAlpha(blur_image,
2647 ClampToQuantum(qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00002648 }
cristy4c08aed2011-07-01 19:47:50 +00002649 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00002650 }
2651 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
2652 status=MagickFalse;
2653 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2654 {
2655 MagickBooleanType
2656 proceed;
2657
cristyb557a152011-02-22 12:14:30 +00002658#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002659 #pragma omp critical (MagickCore_MotionBlurImageChannel)
2660#endif
2661 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
2662 if (proceed == MagickFalse)
2663 status=MagickFalse;
2664 }
2665 }
2666 blur_view=DestroyCacheView(blur_view);
2667 image_view=DestroyCacheView(image_view);
2668 kernel=(double *) RelinquishMagickMemory(kernel);
2669 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2670 if (status == MagickFalse)
2671 blur_image=DestroyImage(blur_image);
2672 return(blur_image);
2673}
2674
2675/*
2676%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2677% %
2678% %
2679% %
2680% P r e v i e w I m a g e %
2681% %
2682% %
2683% %
2684%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2685%
2686% PreviewImage() tiles 9 thumbnails of the specified image with an image
2687% processing operation applied with varying parameters. This may be helpful
2688% pin-pointing an appropriate parameter for a particular image processing
2689% operation.
2690%
2691% The format of the PreviewImages method is:
2692%
2693% Image *PreviewImages(const Image *image,const PreviewType preview,
2694% ExceptionInfo *exception)
2695%
2696% A description of each parameter follows:
2697%
2698% o image: the image.
2699%
2700% o preview: the image processing operation.
2701%
2702% o exception: return any errors or warnings in this structure.
2703%
2704*/
2705MagickExport Image *PreviewImage(const Image *image,const PreviewType preview,
2706 ExceptionInfo *exception)
2707{
2708#define NumberTiles 9
2709#define PreviewImageTag "Preview/Image"
2710#define DefaultPreviewGeometry "204x204+10+10"
2711
2712 char
2713 factor[MaxTextExtent],
2714 label[MaxTextExtent];
2715
2716 double
2717 degrees,
2718 gamma,
2719 percentage,
2720 radius,
2721 sigma,
2722 threshold;
2723
2724 Image
2725 *images,
2726 *montage_image,
2727 *preview_image,
2728 *thumbnail;
2729
2730 ImageInfo
2731 *preview_info;
2732
cristy3ed852e2009-09-05 21:47:34 +00002733 MagickBooleanType
2734 proceed;
2735
2736 MontageInfo
2737 *montage_info;
2738
2739 QuantizeInfo
2740 quantize_info;
2741
2742 RectangleInfo
2743 geometry;
2744
cristybb503372010-05-27 20:51:26 +00002745 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002746 i,
2747 x;
2748
cristybb503372010-05-27 20:51:26 +00002749 size_t
cristy3ed852e2009-09-05 21:47:34 +00002750 colors;
2751
cristy117ff172010-08-15 21:35:32 +00002752 ssize_t
2753 y;
2754
cristy3ed852e2009-09-05 21:47:34 +00002755 /*
2756 Open output image file.
2757 */
2758 assert(image != (Image *) NULL);
2759 assert(image->signature == MagickSignature);
2760 if (image->debug != MagickFalse)
2761 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2762 colors=2;
2763 degrees=0.0;
2764 gamma=(-0.2f);
2765 preview_info=AcquireImageInfo();
2766 SetGeometry(image,&geometry);
2767 (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y,
2768 &geometry.width,&geometry.height);
2769 images=NewImageList();
2770 percentage=12.5;
2771 GetQuantizeInfo(&quantize_info);
2772 radius=0.0;
2773 sigma=1.0;
2774 threshold=0.0;
2775 x=0;
2776 y=0;
2777 for (i=0; i < NumberTiles; i++)
2778 {
2779 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
2780 if (thumbnail == (Image *) NULL)
2781 break;
2782 (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL,
2783 (void *) NULL);
2784 (void) SetImageProperty(thumbnail,"label",DefaultTileLabel);
2785 if (i == (NumberTiles/2))
2786 {
2787 (void) QueryColorDatabase("#dfdfdf",&thumbnail->matte_color,exception);
2788 AppendImageToList(&images,thumbnail);
2789 continue;
2790 }
2791 switch (preview)
2792 {
2793 case RotatePreview:
2794 {
2795 degrees+=45.0;
2796 preview_image=RotateImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002797 (void) FormatLocaleString(label,MaxTextExtent,"rotate %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002798 break;
2799 }
2800 case ShearPreview:
2801 {
2802 degrees+=5.0;
2803 preview_image=ShearImage(thumbnail,degrees,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002804 (void) FormatLocaleString(label,MaxTextExtent,"shear %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002805 degrees,2.0*degrees);
2806 break;
2807 }
2808 case RollPreview:
2809 {
cristybb503372010-05-27 20:51:26 +00002810 x=(ssize_t) ((i+1)*thumbnail->columns)/NumberTiles;
2811 y=(ssize_t) ((i+1)*thumbnail->rows)/NumberTiles;
cristy3ed852e2009-09-05 21:47:34 +00002812 preview_image=RollImage(thumbnail,x,y,exception);
cristyb51dff52011-05-19 16:55:47 +00002813 (void) FormatLocaleString(label,MaxTextExtent,"roll %+.20gx%+.20g",
cristye8c25f92010-06-03 00:53:06 +00002814 (double) x,(double) y);
cristy3ed852e2009-09-05 21:47:34 +00002815 break;
2816 }
2817 case HuePreview:
2818 {
2819 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2820 if (preview_image == (Image *) NULL)
2821 break;
cristyb51dff52011-05-19 16:55:47 +00002822 (void) FormatLocaleString(factor,MaxTextExtent,"100,100,%g",
cristy3ed852e2009-09-05 21:47:34 +00002823 2.0*percentage);
2824 (void) ModulateImage(preview_image,factor);
cristyb51dff52011-05-19 16:55:47 +00002825 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002826 break;
2827 }
2828 case SaturationPreview:
2829 {
2830 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2831 if (preview_image == (Image *) NULL)
2832 break;
cristyb51dff52011-05-19 16:55:47 +00002833 (void) FormatLocaleString(factor,MaxTextExtent,"100,%g",
cristy8cd5b312010-01-07 01:10:24 +00002834 2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00002835 (void) ModulateImage(preview_image,factor);
cristyb51dff52011-05-19 16:55:47 +00002836 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002837 break;
2838 }
2839 case BrightnessPreview:
2840 {
2841 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2842 if (preview_image == (Image *) NULL)
2843 break;
cristyb51dff52011-05-19 16:55:47 +00002844 (void) FormatLocaleString(factor,MaxTextExtent,"%g",2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00002845 (void) ModulateImage(preview_image,factor);
cristyb51dff52011-05-19 16:55:47 +00002846 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002847 break;
2848 }
2849 case GammaPreview:
2850 default:
2851 {
2852 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2853 if (preview_image == (Image *) NULL)
2854 break;
2855 gamma+=0.4f;
2856 (void) GammaImageChannel(preview_image,DefaultChannels,gamma);
cristyb51dff52011-05-19 16:55:47 +00002857 (void) FormatLocaleString(label,MaxTextExtent,"gamma %g",gamma);
cristy3ed852e2009-09-05 21:47:34 +00002858 break;
2859 }
2860 case SpiffPreview:
2861 {
2862 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2863 if (preview_image != (Image *) NULL)
2864 for (x=0; x < i; x++)
2865 (void) ContrastImage(preview_image,MagickTrue);
cristyb51dff52011-05-19 16:55:47 +00002866 (void) FormatLocaleString(label,MaxTextExtent,"contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002867 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002868 break;
2869 }
2870 case DullPreview:
2871 {
2872 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2873 if (preview_image == (Image *) NULL)
2874 break;
2875 for (x=0; x < i; x++)
2876 (void) ContrastImage(preview_image,MagickFalse);
cristyb51dff52011-05-19 16:55:47 +00002877 (void) FormatLocaleString(label,MaxTextExtent,"+contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002878 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002879 break;
2880 }
2881 case GrayscalePreview:
2882 {
2883 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2884 if (preview_image == (Image *) NULL)
2885 break;
2886 colors<<=1;
2887 quantize_info.number_colors=colors;
2888 quantize_info.colorspace=GRAYColorspace;
2889 (void) QuantizeImage(&quantize_info,preview_image);
cristyb51dff52011-05-19 16:55:47 +00002890 (void) FormatLocaleString(label,MaxTextExtent,
cristye8c25f92010-06-03 00:53:06 +00002891 "-colorspace gray -colors %.20g",(double) colors);
cristy3ed852e2009-09-05 21:47:34 +00002892 break;
2893 }
2894 case QuantizePreview:
2895 {
2896 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2897 if (preview_image == (Image *) NULL)
2898 break;
2899 colors<<=1;
2900 quantize_info.number_colors=colors;
2901 (void) QuantizeImage(&quantize_info,preview_image);
cristyb51dff52011-05-19 16:55:47 +00002902 (void) FormatLocaleString(label,MaxTextExtent,"colors %.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00002903 colors);
cristy3ed852e2009-09-05 21:47:34 +00002904 break;
2905 }
2906 case DespecklePreview:
2907 {
2908 for (x=0; x < (i-1); x++)
2909 {
2910 preview_image=DespeckleImage(thumbnail,exception);
2911 if (preview_image == (Image *) NULL)
2912 break;
2913 thumbnail=DestroyImage(thumbnail);
2914 thumbnail=preview_image;
2915 }
2916 preview_image=DespeckleImage(thumbnail,exception);
2917 if (preview_image == (Image *) NULL)
2918 break;
cristyb51dff52011-05-19 16:55:47 +00002919 (void) FormatLocaleString(label,MaxTextExtent,"despeckle (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002920 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002921 break;
2922 }
2923 case ReduceNoisePreview:
2924 {
cristy95c38342011-03-18 22:39:51 +00002925 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) radius,
2926 (size_t) radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002927 (void) FormatLocaleString(label,MaxTextExtent,"noise %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002928 break;
2929 }
2930 case AddNoisePreview:
2931 {
2932 switch ((int) i)
2933 {
2934 case 0:
2935 {
2936 (void) CopyMagickString(factor,"uniform",MaxTextExtent);
2937 break;
2938 }
2939 case 1:
2940 {
2941 (void) CopyMagickString(factor,"gaussian",MaxTextExtent);
2942 break;
2943 }
2944 case 2:
2945 {
2946 (void) CopyMagickString(factor,"multiplicative",MaxTextExtent);
2947 break;
2948 }
2949 case 3:
2950 {
2951 (void) CopyMagickString(factor,"impulse",MaxTextExtent);
2952 break;
2953 }
2954 case 4:
2955 {
2956 (void) CopyMagickString(factor,"laplacian",MaxTextExtent);
2957 break;
2958 }
2959 case 5:
2960 {
2961 (void) CopyMagickString(factor,"Poisson",MaxTextExtent);
2962 break;
2963 }
2964 default:
2965 {
2966 (void) CopyMagickString(thumbnail->magick,"NULL",MaxTextExtent);
2967 break;
2968 }
2969 }
cristyd76c51e2011-03-26 00:21:26 +00002970 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) i,
2971 (size_t) i,exception);
cristyb51dff52011-05-19 16:55:47 +00002972 (void) FormatLocaleString(label,MaxTextExtent,"+noise %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002973 break;
2974 }
2975 case SharpenPreview:
2976 {
2977 preview_image=SharpenImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00002978 (void) FormatLocaleString(label,MaxTextExtent,"sharpen %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002979 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002980 break;
2981 }
2982 case BlurPreview:
2983 {
2984 preview_image=BlurImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00002985 (void) FormatLocaleString(label,MaxTextExtent,"blur %gx%g",radius,
cristy3ed852e2009-09-05 21:47:34 +00002986 sigma);
2987 break;
2988 }
2989 case ThresholdPreview:
2990 {
2991 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2992 if (preview_image == (Image *) NULL)
2993 break;
2994 (void) BilevelImage(thumbnail,
2995 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
cristyb51dff52011-05-19 16:55:47 +00002996 (void) FormatLocaleString(label,MaxTextExtent,"threshold %g",
cristy3ed852e2009-09-05 21:47:34 +00002997 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
2998 break;
2999 }
3000 case EdgeDetectPreview:
3001 {
3002 preview_image=EdgeImage(thumbnail,radius,exception);
cristyb51dff52011-05-19 16:55:47 +00003003 (void) FormatLocaleString(label,MaxTextExtent,"edge %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003004 break;
3005 }
3006 case SpreadPreview:
3007 {
3008 preview_image=SpreadImage(thumbnail,radius,exception);
cristyb51dff52011-05-19 16:55:47 +00003009 (void) FormatLocaleString(label,MaxTextExtent,"spread %g",
cristy8cd5b312010-01-07 01:10:24 +00003010 radius+0.5);
cristy3ed852e2009-09-05 21:47:34 +00003011 break;
3012 }
3013 case SolarizePreview:
3014 {
3015 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3016 if (preview_image == (Image *) NULL)
3017 break;
3018 (void) SolarizeImage(preview_image,(double) QuantumRange*
3019 percentage/100.0);
cristyb51dff52011-05-19 16:55:47 +00003020 (void) FormatLocaleString(label,MaxTextExtent,"solarize %g",
cristy3ed852e2009-09-05 21:47:34 +00003021 (QuantumRange*percentage)/100.0);
3022 break;
3023 }
3024 case ShadePreview:
3025 {
3026 degrees+=10.0;
3027 preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees,
3028 exception);
cristyb51dff52011-05-19 16:55:47 +00003029 (void) FormatLocaleString(label,MaxTextExtent,"shade %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003030 degrees,degrees);
cristy3ed852e2009-09-05 21:47:34 +00003031 break;
3032 }
3033 case RaisePreview:
3034 {
3035 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3036 if (preview_image == (Image *) NULL)
3037 break;
cristybb503372010-05-27 20:51:26 +00003038 geometry.width=(size_t) (2*i+2);
3039 geometry.height=(size_t) (2*i+2);
cristy3ed852e2009-09-05 21:47:34 +00003040 geometry.x=i/2;
3041 geometry.y=i/2;
3042 (void) RaiseImage(preview_image,&geometry,MagickTrue);
cristyb51dff52011-05-19 16:55:47 +00003043 (void) FormatLocaleString(label,MaxTextExtent,
cristy6d8abba2010-06-03 01:10:47 +00003044 "raise %.20gx%.20g%+.20g%+.20g",(double) geometry.width,(double)
cristye8c25f92010-06-03 00:53:06 +00003045 geometry.height,(double) geometry.x,(double) geometry.y);
cristy3ed852e2009-09-05 21:47:34 +00003046 break;
3047 }
3048 case SegmentPreview:
3049 {
3050 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3051 if (preview_image == (Image *) NULL)
3052 break;
3053 threshold+=0.4f;
3054 (void) SegmentImage(preview_image,RGBColorspace,MagickFalse,threshold,
3055 threshold);
cristyb51dff52011-05-19 16:55:47 +00003056 (void) FormatLocaleString(label,MaxTextExtent,"segment %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00003057 threshold,threshold);
3058 break;
3059 }
3060 case SwirlPreview:
3061 {
3062 preview_image=SwirlImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00003063 (void) FormatLocaleString(label,MaxTextExtent,"swirl %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003064 degrees+=45.0;
3065 break;
3066 }
3067 case ImplodePreview:
3068 {
3069 degrees+=0.1f;
3070 preview_image=ImplodeImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00003071 (void) FormatLocaleString(label,MaxTextExtent,"implode %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003072 break;
3073 }
3074 case WavePreview:
3075 {
3076 degrees+=5.0f;
3077 preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00003078 (void) FormatLocaleString(label,MaxTextExtent,"wave %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003079 0.5*degrees,2.0*degrees);
cristy3ed852e2009-09-05 21:47:34 +00003080 break;
3081 }
3082 case OilPaintPreview:
3083 {
3084 preview_image=OilPaintImage(thumbnail,(double) radius,exception);
cristyb51dff52011-05-19 16:55:47 +00003085 (void) FormatLocaleString(label,MaxTextExtent,"paint %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003086 break;
3087 }
3088 case CharcoalDrawingPreview:
3089 {
3090 preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma,
3091 exception);
cristyb51dff52011-05-19 16:55:47 +00003092 (void) FormatLocaleString(label,MaxTextExtent,"charcoal %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003093 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00003094 break;
3095 }
3096 case JPEGPreview:
3097 {
3098 char
3099 filename[MaxTextExtent];
3100
3101 int
3102 file;
3103
3104 MagickBooleanType
3105 status;
3106
3107 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3108 if (preview_image == (Image *) NULL)
3109 break;
cristybb503372010-05-27 20:51:26 +00003110 preview_info->quality=(size_t) percentage;
cristyb51dff52011-05-19 16:55:47 +00003111 (void) FormatLocaleString(factor,MaxTextExtent,"%.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00003112 preview_info->quality);
cristy3ed852e2009-09-05 21:47:34 +00003113 file=AcquireUniqueFileResource(filename);
3114 if (file != -1)
3115 file=close(file)-1;
cristyb51dff52011-05-19 16:55:47 +00003116 (void) FormatLocaleString(preview_image->filename,MaxTextExtent,
cristy3ed852e2009-09-05 21:47:34 +00003117 "jpeg:%s",filename);
3118 status=WriteImage(preview_info,preview_image);
3119 if (status != MagickFalse)
3120 {
3121 Image
3122 *quality_image;
3123
3124 (void) CopyMagickString(preview_info->filename,
3125 preview_image->filename,MaxTextExtent);
3126 quality_image=ReadImage(preview_info,exception);
3127 if (quality_image != (Image *) NULL)
3128 {
3129 preview_image=DestroyImage(preview_image);
3130 preview_image=quality_image;
3131 }
3132 }
3133 (void) RelinquishUniqueFileResource(preview_image->filename);
3134 if ((GetBlobSize(preview_image)/1024) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00003135 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%gmb ",
cristy3ed852e2009-09-05 21:47:34 +00003136 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
3137 1024.0/1024.0);
3138 else
3139 if (GetBlobSize(preview_image) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00003140 (void) FormatLocaleString(label,MaxTextExtent,
cristye7f51092010-01-17 00:39:37 +00003141 "quality %s\n%gkb ",factor,(double) ((MagickOffsetType)
cristy8cd5b312010-01-07 01:10:24 +00003142 GetBlobSize(preview_image))/1024.0);
cristy3ed852e2009-09-05 21:47:34 +00003143 else
cristyb51dff52011-05-19 16:55:47 +00003144 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%.20gb ",
cristy54ea5732011-06-10 12:39:53 +00003145 factor,(double) ((MagickOffsetType) GetBlobSize(thumbnail)));
cristy3ed852e2009-09-05 21:47:34 +00003146 break;
3147 }
3148 }
3149 thumbnail=DestroyImage(thumbnail);
3150 percentage+=12.5;
3151 radius+=0.5;
3152 sigma+=0.25;
3153 if (preview_image == (Image *) NULL)
3154 break;
3155 (void) DeleteImageProperty(preview_image,"label");
3156 (void) SetImageProperty(preview_image,"label",label);
3157 AppendImageToList(&images,preview_image);
cristybb503372010-05-27 20:51:26 +00003158 proceed=SetImageProgress(image,PreviewImageTag,(MagickOffsetType) i,
3159 NumberTiles);
cristy3ed852e2009-09-05 21:47:34 +00003160 if (proceed == MagickFalse)
3161 break;
3162 }
3163 if (images == (Image *) NULL)
3164 {
3165 preview_info=DestroyImageInfo(preview_info);
3166 return((Image *) NULL);
3167 }
3168 /*
3169 Create the montage.
3170 */
3171 montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL);
3172 (void) CopyMagickString(montage_info->filename,image->filename,MaxTextExtent);
3173 montage_info->shadow=MagickTrue;
3174 (void) CloneString(&montage_info->tile,"3x3");
3175 (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry);
3176 (void) CloneString(&montage_info->frame,DefaultTileFrame);
3177 montage_image=MontageImages(images,montage_info,exception);
3178 montage_info=DestroyMontageInfo(montage_info);
3179 images=DestroyImageList(images);
3180 if (montage_image == (Image *) NULL)
3181 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3182 if (montage_image->montage != (char *) NULL)
3183 {
3184 /*
3185 Free image directory.
3186 */
3187 montage_image->montage=(char *) RelinquishMagickMemory(
3188 montage_image->montage);
3189 if (image->directory != (char *) NULL)
3190 montage_image->directory=(char *) RelinquishMagickMemory(
3191 montage_image->directory);
3192 }
3193 preview_info=DestroyImageInfo(preview_info);
3194 return(montage_image);
3195}
3196
3197/*
3198%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3199% %
3200% %
3201% %
3202% R a d i a l B l u r I m a g e %
3203% %
3204% %
3205% %
3206%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3207%
3208% RadialBlurImage() applies a radial blur to the image.
3209%
3210% Andrew Protano contributed this effect.
3211%
3212% The format of the RadialBlurImage method is:
3213%
3214% Image *RadialBlurImage(const Image *image,const double angle,
3215% ExceptionInfo *exception)
3216% Image *RadialBlurImageChannel(const Image *image,const ChannelType channel,
3217% const double angle,ExceptionInfo *exception)
3218%
3219% A description of each parameter follows:
3220%
3221% o image: the image.
3222%
3223% o channel: the channel type.
3224%
3225% o angle: the angle of the radial blur.
3226%
3227% o exception: return any errors or warnings in this structure.
3228%
3229*/
3230
3231MagickExport Image *RadialBlurImage(const Image *image,const double angle,
3232 ExceptionInfo *exception)
3233{
3234 Image
3235 *blur_image;
3236
3237 blur_image=RadialBlurImageChannel(image,DefaultChannels,angle,exception);
3238 return(blur_image);
3239}
3240
3241MagickExport Image *RadialBlurImageChannel(const Image *image,
3242 const ChannelType channel,const double angle,ExceptionInfo *exception)
3243{
cristyc4c8d132010-01-07 01:58:38 +00003244 CacheView
3245 *blur_view,
3246 *image_view;
3247
cristy3ed852e2009-09-05 21:47:34 +00003248 Image
3249 *blur_image;
3250
cristy3ed852e2009-09-05 21:47:34 +00003251 MagickBooleanType
3252 status;
3253
cristybb503372010-05-27 20:51:26 +00003254 MagickOffsetType
3255 progress;
3256
cristy4c08aed2011-07-01 19:47:50 +00003257 PixelInfo
cristyddd82202009-11-03 20:14:50 +00003258 bias;
cristy3ed852e2009-09-05 21:47:34 +00003259
3260 MagickRealType
3261 blur_radius,
3262 *cos_theta,
3263 offset,
3264 *sin_theta,
3265 theta;
3266
3267 PointInfo
3268 blur_center;
3269
cristybb503372010-05-27 20:51:26 +00003270 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003271 i;
3272
cristybb503372010-05-27 20:51:26 +00003273 size_t
cristy3ed852e2009-09-05 21:47:34 +00003274 n;
3275
cristybb503372010-05-27 20:51:26 +00003276 ssize_t
3277 y;
3278
cristy3ed852e2009-09-05 21:47:34 +00003279 /*
3280 Allocate blur image.
3281 */
3282 assert(image != (Image *) NULL);
3283 assert(image->signature == MagickSignature);
3284 if (image->debug != MagickFalse)
3285 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3286 assert(exception != (ExceptionInfo *) NULL);
3287 assert(exception->signature == MagickSignature);
3288 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3289 if (blur_image == (Image *) NULL)
3290 return((Image *) NULL);
3291 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3292 {
3293 InheritException(exception,&blur_image->exception);
3294 blur_image=DestroyImage(blur_image);
3295 return((Image *) NULL);
3296 }
3297 blur_center.x=(double) image->columns/2.0;
3298 blur_center.y=(double) image->rows/2.0;
3299 blur_radius=hypot(blur_center.x,blur_center.y);
cristy117ff172010-08-15 21:35:32 +00003300 n=(size_t) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+2UL);
cristy3ed852e2009-09-05 21:47:34 +00003301 theta=DegreesToRadians(angle)/(MagickRealType) (n-1);
3302 cos_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3303 sizeof(*cos_theta));
3304 sin_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3305 sizeof(*sin_theta));
3306 if ((cos_theta == (MagickRealType *) NULL) ||
3307 (sin_theta == (MagickRealType *) NULL))
3308 {
3309 blur_image=DestroyImage(blur_image);
3310 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3311 }
3312 offset=theta*(MagickRealType) (n-1)/2.0;
cristybb503372010-05-27 20:51:26 +00003313 for (i=0; i < (ssize_t) n; i++)
cristy3ed852e2009-09-05 21:47:34 +00003314 {
3315 cos_theta[i]=cos((double) (theta*i-offset));
3316 sin_theta[i]=sin((double) (theta*i-offset));
3317 }
3318 /*
3319 Radial blur image.
3320 */
3321 status=MagickTrue;
3322 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00003323 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003324 image_view=AcquireCacheView(image);
3325 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003326#if defined(MAGICKCORE_OPENMP_SUPPORT)
3327 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003328#endif
cristybb503372010-05-27 20:51:26 +00003329 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003330 {
cristy4c08aed2011-07-01 19:47:50 +00003331 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003332 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003333
cristy117ff172010-08-15 21:35:32 +00003334 register ssize_t
3335 x;
3336
cristy3ed852e2009-09-05 21:47:34 +00003337 if (status == MagickFalse)
3338 continue;
3339 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3340 exception);
cristy4c08aed2011-07-01 19:47:50 +00003341 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003342 {
3343 status=MagickFalse;
3344 continue;
3345 }
cristybb503372010-05-27 20:51:26 +00003346 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003347 {
cristy4c08aed2011-07-01 19:47:50 +00003348 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003349 qixel;
3350
3351 MagickRealType
3352 normalize,
3353 radius;
3354
3355 PixelPacket
3356 pixel;
3357
3358 PointInfo
3359 center;
3360
cristybb503372010-05-27 20:51:26 +00003361 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003362 i;
3363
cristybb503372010-05-27 20:51:26 +00003364 size_t
cristy3ed852e2009-09-05 21:47:34 +00003365 step;
3366
3367 center.x=(double) x-blur_center.x;
3368 center.y=(double) y-blur_center.y;
3369 radius=hypot((double) center.x,center.y);
3370 if (radius == 0)
3371 step=1;
3372 else
3373 {
cristybb503372010-05-27 20:51:26 +00003374 step=(size_t) (blur_radius/radius);
cristy3ed852e2009-09-05 21:47:34 +00003375 if (step == 0)
3376 step=1;
3377 else
3378 if (step >= n)
3379 step=n-1;
3380 }
3381 normalize=0.0;
cristyddd82202009-11-03 20:14:50 +00003382 qixel=bias;
cristy2b9582a2011-07-04 17:38:56 +00003383 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00003384 {
cristyeaedf062010-05-29 22:36:02 +00003385 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00003386 {
cristyeaedf062010-05-29 22:36:02 +00003387 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
3388 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
3389 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
3390 cos_theta[i]+0.5),&pixel,exception);
cristy3ed852e2009-09-05 21:47:34 +00003391 qixel.red+=pixel.red;
3392 qixel.green+=pixel.green;
3393 qixel.blue+=pixel.blue;
cristy3ed852e2009-09-05 21:47:34 +00003394 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00003395 qixel.black+=pixel.black;
3396 qixel.alpha+=pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00003397 normalize+=1.0;
3398 }
3399 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
3400 normalize);
cristy2b9582a2011-07-04 17:38:56 +00003401 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003402 SetPixelRed(blur_image,
3403 ClampToQuantum(normalize*qixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00003404 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003405 SetPixelGreen(blur_image,
3406 ClampToQuantum(normalize*qixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00003407 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003408 SetPixelBlue(blur_image,
3409 ClampToQuantum(normalize*qixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00003410 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003411 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003412 SetPixelBlack(blur_image,
3413 ClampToQuantum(normalize*qixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00003414 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003415 SetPixelAlpha(blur_image,
3416 ClampToQuantum(normalize*qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003417 }
3418 else
3419 {
3420 MagickRealType
3421 alpha,
3422 gamma;
3423
3424 alpha=1.0;
3425 gamma=0.0;
cristyeaedf062010-05-29 22:36:02 +00003426 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00003427 {
cristyeaedf062010-05-29 22:36:02 +00003428 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
3429 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
3430 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
3431 cos_theta[i]+0.5),&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00003432 alpha=(MagickRealType) (QuantumScale*pixel.alpha);
cristy3ed852e2009-09-05 21:47:34 +00003433 qixel.red+=alpha*pixel.red;
3434 qixel.green+=alpha*pixel.green;
3435 qixel.blue+=alpha*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00003436 qixel.alpha+=pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00003437 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00003438 qixel.black+=alpha*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00003439 gamma+=alpha;
3440 normalize+=1.0;
3441 }
3442 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
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(gamma*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(gamma*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(gamma*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(gamma*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 }
cristy4c08aed2011-07-01 19:47:50 +00003462 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003463 }
3464 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
3465 status=MagickFalse;
3466 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3467 {
3468 MagickBooleanType
3469 proceed;
3470
cristyb5d5f722009-11-04 03:03:49 +00003471#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003472 #pragma omp critical (MagickCore_RadialBlurImageChannel)
3473#endif
3474 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
3475 if (proceed == MagickFalse)
3476 status=MagickFalse;
3477 }
3478 }
3479 blur_view=DestroyCacheView(blur_view);
3480 image_view=DestroyCacheView(image_view);
3481 cos_theta=(MagickRealType *) RelinquishMagickMemory(cos_theta);
3482 sin_theta=(MagickRealType *) RelinquishMagickMemory(sin_theta);
3483 if (status == MagickFalse)
3484 blur_image=DestroyImage(blur_image);
3485 return(blur_image);
3486}
3487
3488/*
3489%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3490% %
3491% %
3492% %
cristy3ed852e2009-09-05 21:47:34 +00003493% S e l e c t i v e B l u r I m a g e %
3494% %
3495% %
3496% %
3497%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3498%
3499% SelectiveBlurImage() selectively blur pixels within a contrast threshold.
3500% It is similar to the unsharpen mask that sharpens everything with contrast
3501% above a certain threshold.
3502%
3503% The format of the SelectiveBlurImage method is:
3504%
3505% Image *SelectiveBlurImage(const Image *image,const double radius,
3506% const double sigma,const double threshold,ExceptionInfo *exception)
3507% Image *SelectiveBlurImageChannel(const Image *image,
3508% const ChannelType channel,const double radius,const double sigma,
3509% const double threshold,ExceptionInfo *exception)
3510%
3511% A description of each parameter follows:
3512%
3513% o image: the image.
3514%
3515% o channel: the channel type.
3516%
3517% o radius: the radius of the Gaussian, in pixels, not counting the center
3518% pixel.
3519%
3520% o sigma: the standard deviation of the Gaussian, in pixels.
3521%
3522% o threshold: only pixels within this contrast threshold are included
3523% in the blur operation.
3524%
3525% o exception: return any errors or warnings in this structure.
3526%
3527*/
3528
cristy3ed852e2009-09-05 21:47:34 +00003529MagickExport Image *SelectiveBlurImage(const Image *image,const double radius,
3530 const double sigma,const double threshold,ExceptionInfo *exception)
3531{
3532 Image
3533 *blur_image;
3534
3535 blur_image=SelectiveBlurImageChannel(image,DefaultChannels,radius,sigma,
3536 threshold,exception);
3537 return(blur_image);
3538}
3539
3540MagickExport Image *SelectiveBlurImageChannel(const Image *image,
3541 const ChannelType channel,const double radius,const double sigma,
3542 const double threshold,ExceptionInfo *exception)
3543{
3544#define SelectiveBlurImageTag "SelectiveBlur/Image"
3545
cristy47e00502009-12-17 19:19:57 +00003546 CacheView
3547 *blur_view,
3548 *image_view;
3549
cristy3ed852e2009-09-05 21:47:34 +00003550 double
cristy3ed852e2009-09-05 21:47:34 +00003551 *kernel;
3552
3553 Image
3554 *blur_image;
3555
cristy3ed852e2009-09-05 21:47:34 +00003556 MagickBooleanType
3557 status;
3558
cristybb503372010-05-27 20:51:26 +00003559 MagickOffsetType
3560 progress;
3561
cristy4c08aed2011-07-01 19:47:50 +00003562 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003563 bias;
3564
cristybb503372010-05-27 20:51:26 +00003565 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003566 i;
cristy3ed852e2009-09-05 21:47:34 +00003567
cristybb503372010-05-27 20:51:26 +00003568 size_t
cristy3ed852e2009-09-05 21:47:34 +00003569 width;
3570
cristybb503372010-05-27 20:51:26 +00003571 ssize_t
3572 j,
3573 u,
3574 v,
3575 y;
3576
cristy3ed852e2009-09-05 21:47:34 +00003577 /*
3578 Initialize blur image attributes.
3579 */
3580 assert(image != (Image *) NULL);
3581 assert(image->signature == MagickSignature);
3582 if (image->debug != MagickFalse)
3583 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3584 assert(exception != (ExceptionInfo *) NULL);
3585 assert(exception->signature == MagickSignature);
3586 width=GetOptimalKernelWidth1D(radius,sigma);
3587 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
3588 if (kernel == (double *) NULL)
3589 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00003590 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00003591 i=0;
cristy47e00502009-12-17 19:19:57 +00003592 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00003593 {
cristy47e00502009-12-17 19:19:57 +00003594 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00003595 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
3596 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00003597 }
3598 if (image->debug != MagickFalse)
3599 {
3600 char
3601 format[MaxTextExtent],
3602 *message;
3603
cristy117ff172010-08-15 21:35:32 +00003604 register const double
3605 *k;
3606
cristybb503372010-05-27 20:51:26 +00003607 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003608 u,
3609 v;
3610
cristy3ed852e2009-09-05 21:47:34 +00003611 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00003612 " SelectiveBlurImage with %.20gx%.20g kernel:",(double) width,(double)
3613 width);
cristy3ed852e2009-09-05 21:47:34 +00003614 message=AcquireString("");
3615 k=kernel;
cristybb503372010-05-27 20:51:26 +00003616 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003617 {
3618 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00003619 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy3ed852e2009-09-05 21:47:34 +00003620 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00003621 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003622 {
cristyb51dff52011-05-19 16:55:47 +00003623 (void) FormatLocaleString(format,MaxTextExtent,"%+f ",*k++);
cristy3ed852e2009-09-05 21:47:34 +00003624 (void) ConcatenateString(&message,format);
3625 }
3626 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
3627 }
3628 message=DestroyString(message);
3629 }
3630 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3631 if (blur_image == (Image *) NULL)
3632 return((Image *) NULL);
3633 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3634 {
3635 InheritException(exception,&blur_image->exception);
3636 blur_image=DestroyImage(blur_image);
3637 return((Image *) NULL);
3638 }
3639 /*
3640 Threshold blur image.
3641 */
3642 status=MagickTrue;
3643 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00003644 GetPixelInfo(image,&bias);
3645 SetPixelInfoBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003646 image_view=AcquireCacheView(image);
3647 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003648#if defined(MAGICKCORE_OPENMP_SUPPORT)
3649 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003650#endif
cristybb503372010-05-27 20:51:26 +00003651 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003652 {
cristy4c08aed2011-07-01 19:47:50 +00003653 double
3654 contrast;
3655
cristy3ed852e2009-09-05 21:47:34 +00003656 MagickBooleanType
3657 sync;
3658
3659 MagickRealType
3660 gamma;
3661
cristy4c08aed2011-07-01 19:47:50 +00003662 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003663 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003664
cristy4c08aed2011-07-01 19:47:50 +00003665 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003666 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003667
cristy117ff172010-08-15 21:35:32 +00003668 register ssize_t
3669 x;
3670
cristy3ed852e2009-09-05 21:47:34 +00003671 if (status == MagickFalse)
3672 continue;
cristy117ff172010-08-15 21:35:32 +00003673 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
3674 (width/2L),image->columns+width,width,exception);
cristy3ed852e2009-09-05 21:47:34 +00003675 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3676 exception);
cristy4c08aed2011-07-01 19:47:50 +00003677 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003678 {
3679 status=MagickFalse;
3680 continue;
3681 }
cristybb503372010-05-27 20:51:26 +00003682 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003683 {
cristy4c08aed2011-07-01 19:47:50 +00003684 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003685 pixel;
3686
3687 register const double
cristyc47d1f82009-11-26 01:44:43 +00003688 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00003689
cristybb503372010-05-27 20:51:26 +00003690 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003691 u;
3692
cristy117ff172010-08-15 21:35:32 +00003693 ssize_t
3694 j,
3695 v;
3696
cristyddd82202009-11-03 20:14:50 +00003697 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00003698 k=kernel;
3699 gamma=0.0;
3700 j=0;
cristy2b9582a2011-07-04 17:38:56 +00003701 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00003702 {
cristybb503372010-05-27 20:51:26 +00003703 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003704 {
cristybb503372010-05-27 20:51:26 +00003705 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003706 {
cristy4c08aed2011-07-01 19:47:50 +00003707 contrast=GetPixelIntensity(image,p+(u+j)*GetPixelChannels(image))-
3708 (double) GetPixelIntensity(blur_image,q);
3709 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003710 {
cristy4c08aed2011-07-01 19:47:50 +00003711 pixel.red+=(*k)*
3712 GetPixelRed(image,p+(u+j)*GetPixelChannels(image));
3713 pixel.green+=(*k)*
3714 GetPixelGreen(image,p+(u+j)*GetPixelChannels(image));
3715 pixel.blue+=(*k)*
3716 GetPixelBlue(image,p+(u+j)*GetPixelChannels(image));
3717 if (image->colorspace == CMYKColorspace)
3718 pixel.black+=(*k)*
3719 GetPixelBlack(image,p+(u+j)*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003720 gamma+=(*k);
3721 k++;
3722 }
3723 }
cristyd99b0962010-05-29 23:14:26 +00003724 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003725 }
3726 if (gamma != 0.0)
3727 {
3728 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy2b9582a2011-07-04 17:38:56 +00003729 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003730 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00003731 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003732 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00003733 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003734 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00003735 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00003736 (image->colorspace == CMYKColorspace))
3737 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00003738 }
cristy2b9582a2011-07-04 17:38:56 +00003739 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003740 {
3741 gamma=0.0;
3742 j=0;
cristybb503372010-05-27 20:51:26 +00003743 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003744 {
cristybb503372010-05-27 20:51:26 +00003745 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003746 {
cristy4c08aed2011-07-01 19:47:50 +00003747 contrast=GetPixelIntensity(image,p+(u+j)*
3748 GetPixelChannels(image))-(double)
3749 GetPixelIntensity(blur_image,q);
3750 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003751 {
cristy4c08aed2011-07-01 19:47:50 +00003752 pixel.alpha+=(*k)*
3753 GetPixelAlpha(image,p+(u+j)*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003754 gamma+=(*k);
3755 k++;
3756 }
3757 }
cristyeaedf062010-05-29 22:36:02 +00003758 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003759 }
3760 if (gamma != 0.0)
3761 {
3762 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
3763 gamma);
cristy4c08aed2011-07-01 19:47:50 +00003764 SetPixelAlpha(blur_image,ClampToQuantum(gamma*pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003765 }
3766 }
3767 }
3768 else
3769 {
3770 MagickRealType
3771 alpha;
3772
cristybb503372010-05-27 20:51:26 +00003773 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003774 {
cristybb503372010-05-27 20:51:26 +00003775 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003776 {
cristy4c08aed2011-07-01 19:47:50 +00003777 contrast=GetPixelIntensity(image,p+(u+j)*
3778 GetPixelChannels(image))-(double)
3779 GetPixelIntensity(blur_image,q);
3780 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003781 {
cristy4c08aed2011-07-01 19:47:50 +00003782 alpha=(MagickRealType) (QuantumScale*
3783 GetPixelAlpha(image,p+(u+j)*GetPixelChannels(image)));
3784 pixel.red+=(*k)*alpha*
3785 GetPixelRed(image,p+(u+j)*GetPixelChannels(image));
3786 pixel.green+=(*k)*alpha*GetPixelGreen(image,p+(u+j)*
3787 GetPixelChannels(image));
3788 pixel.blue+=(*k)*alpha*GetPixelBlue(image,p+(u+j)*
3789 GetPixelChannels(image));
3790 pixel.alpha+=(*k)*GetPixelAlpha(image,p+(u+j)*
3791 GetPixelChannels(image));
3792 if (image->colorspace == CMYKColorspace)
3793 pixel.black+=(*k)*GetPixelBlack(image,p+(u+j)*
3794 GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003795 gamma+=(*k)*alpha;
3796 k++;
3797 }
3798 }
cristyeaedf062010-05-29 22:36:02 +00003799 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003800 }
3801 if (gamma != 0.0)
3802 {
3803 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy2b9582a2011-07-04 17:38:56 +00003804 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003805 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00003806 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003807 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00003808 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003809 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00003810 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00003811 (image->colorspace == CMYKColorspace))
3812 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00003813 }
cristy2b9582a2011-07-04 17:38:56 +00003814 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003815 {
3816 gamma=0.0;
3817 j=0;
cristybb503372010-05-27 20:51:26 +00003818 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003819 {
cristybb503372010-05-27 20:51:26 +00003820 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003821 {
cristy4c08aed2011-07-01 19:47:50 +00003822 contrast=GetPixelIntensity(image,p+(u+j)*
3823 GetPixelChannels(image))-(double)
3824 GetPixelIntensity(blur_image,q);
3825 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003826 {
cristy4c08aed2011-07-01 19:47:50 +00003827 pixel.alpha+=(*k)*
3828 GetPixelAlpha(image,p+(u+j)*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003829 gamma+=(*k);
3830 k++;
3831 }
3832 }
cristyeaedf062010-05-29 22:36:02 +00003833 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003834 }
3835 if (gamma != 0.0)
3836 {
3837 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
3838 gamma);
cristy4c08aed2011-07-01 19:47:50 +00003839 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003840 }
3841 }
3842 }
cristy4c08aed2011-07-01 19:47:50 +00003843 p+=GetPixelChannels(image);
3844 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003845 }
3846 sync=SyncCacheViewAuthenticPixels(blur_view,exception);
3847 if (sync == MagickFalse)
3848 status=MagickFalse;
3849 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3850 {
3851 MagickBooleanType
3852 proceed;
3853
cristyb5d5f722009-11-04 03:03:49 +00003854#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003855 #pragma omp critical (MagickCore_SelectiveBlurImageChannel)
3856#endif
3857 proceed=SetImageProgress(image,SelectiveBlurImageTag,progress++,
3858 image->rows);
3859 if (proceed == MagickFalse)
3860 status=MagickFalse;
3861 }
3862 }
3863 blur_image->type=image->type;
3864 blur_view=DestroyCacheView(blur_view);
3865 image_view=DestroyCacheView(image_view);
3866 kernel=(double *) RelinquishMagickMemory(kernel);
3867 if (status == MagickFalse)
3868 blur_image=DestroyImage(blur_image);
3869 return(blur_image);
3870}
3871
3872/*
3873%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3874% %
3875% %
3876% %
3877% S h a d e I m a g e %
3878% %
3879% %
3880% %
3881%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3882%
3883% ShadeImage() shines a distant light on an image to create a
3884% three-dimensional effect. You control the positioning of the light with
3885% azimuth and elevation; azimuth is measured in degrees off the x axis
3886% and elevation is measured in pixels above the Z axis.
3887%
3888% The format of the ShadeImage method is:
3889%
3890% Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3891% const double azimuth,const double elevation,ExceptionInfo *exception)
3892%
3893% A description of each parameter follows:
3894%
3895% o image: the image.
3896%
3897% o gray: A value other than zero shades the intensity of each pixel.
3898%
3899% o azimuth, elevation: Define the light source direction.
3900%
3901% o exception: return any errors or warnings in this structure.
3902%
3903*/
3904MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3905 const double azimuth,const double elevation,ExceptionInfo *exception)
3906{
3907#define ShadeImageTag "Shade/Image"
3908
cristyc4c8d132010-01-07 01:58:38 +00003909 CacheView
3910 *image_view,
3911 *shade_view;
3912
cristy3ed852e2009-09-05 21:47:34 +00003913 Image
3914 *shade_image;
3915
cristy3ed852e2009-09-05 21:47:34 +00003916 MagickBooleanType
3917 status;
3918
cristybb503372010-05-27 20:51:26 +00003919 MagickOffsetType
3920 progress;
3921
cristy3ed852e2009-09-05 21:47:34 +00003922 PrimaryInfo
3923 light;
3924
cristybb503372010-05-27 20:51:26 +00003925 ssize_t
3926 y;
3927
cristy3ed852e2009-09-05 21:47:34 +00003928 /*
3929 Initialize shaded image attributes.
3930 */
3931 assert(image != (const Image *) NULL);
3932 assert(image->signature == MagickSignature);
3933 if (image->debug != MagickFalse)
3934 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3935 assert(exception != (ExceptionInfo *) NULL);
3936 assert(exception->signature == MagickSignature);
3937 shade_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
3938 if (shade_image == (Image *) NULL)
3939 return((Image *) NULL);
3940 if (SetImageStorageClass(shade_image,DirectClass) == MagickFalse)
3941 {
3942 InheritException(exception,&shade_image->exception);
3943 shade_image=DestroyImage(shade_image);
3944 return((Image *) NULL);
3945 }
3946 /*
3947 Compute the light vector.
3948 */
3949 light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))*
3950 cos(DegreesToRadians(elevation));
3951 light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))*
3952 cos(DegreesToRadians(elevation));
3953 light.z=(double) QuantumRange*sin(DegreesToRadians(elevation));
3954 /*
3955 Shade image.
3956 */
3957 status=MagickTrue;
3958 progress=0;
3959 image_view=AcquireCacheView(image);
3960 shade_view=AcquireCacheView(shade_image);
cristyb5d5f722009-11-04 03:03:49 +00003961#if defined(MAGICKCORE_OPENMP_SUPPORT)
3962 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003963#endif
cristybb503372010-05-27 20:51:26 +00003964 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003965 {
3966 MagickRealType
3967 distance,
3968 normal_distance,
3969 shade;
3970
3971 PrimaryInfo
3972 normal;
3973
cristy4c08aed2011-07-01 19:47:50 +00003974 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003975 *restrict p,
3976 *restrict s0,
3977 *restrict s1,
3978 *restrict s2;
cristy3ed852e2009-09-05 21:47:34 +00003979
cristy4c08aed2011-07-01 19:47:50 +00003980 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003981 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003982
cristy117ff172010-08-15 21:35:32 +00003983 register ssize_t
3984 x;
3985
cristy3ed852e2009-09-05 21:47:34 +00003986 if (status == MagickFalse)
3987 continue;
3988 p=GetCacheViewVirtualPixels(image_view,-1,y-1,image->columns+2,3,exception);
3989 q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1,
3990 exception);
cristy4c08aed2011-07-01 19:47:50 +00003991 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003992 {
3993 status=MagickFalse;
3994 continue;
3995 }
3996 /*
3997 Shade this row of pixels.
3998 */
3999 normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */
cristy4c08aed2011-07-01 19:47:50 +00004000 s0=p+GetPixelChannels(image);
4001 s1=s0+(image->columns+2)*GetPixelChannels(image);
4002 s2=s1+(image->columns+2)*GetPixelChannels(image);
cristybb503372010-05-27 20:51:26 +00004003 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004004 {
4005 /*
4006 Determine the surface normal and compute shading.
4007 */
cristy4c08aed2011-07-01 19:47:50 +00004008 normal.x=(double) (GetPixelIntensity(image,s0-GetPixelChannels(image))+
4009 GetPixelIntensity(image,s1-GetPixelChannels(image))+
4010 GetPixelIntensity(image,s2-GetPixelChannels(image))-
4011 GetPixelIntensity(image,s0+GetPixelChannels(image))-
4012 GetPixelIntensity(image,s1+GetPixelChannels(image))-
4013 GetPixelIntensity(image,s2+GetPixelChannels(image)));
4014 normal.y=(double) (GetPixelIntensity(image,s2-GetPixelChannels(image))+
4015 GetPixelIntensity(image,s2)+
4016 GetPixelIntensity(image,s2+GetPixelChannels(image))-
4017 GetPixelIntensity(image,s0-GetPixelChannels(image))-
4018 GetPixelIntensity(image,s0)-
4019 GetPixelIntensity(image,s0+GetPixelChannels(image)));
cristy3ed852e2009-09-05 21:47:34 +00004020 if ((normal.x == 0.0) && (normal.y == 0.0))
4021 shade=light.z;
4022 else
4023 {
4024 shade=0.0;
4025 distance=normal.x*light.x+normal.y*light.y+normal.z*light.z;
4026 if (distance > MagickEpsilon)
4027 {
4028 normal_distance=
4029 normal.x*normal.x+normal.y*normal.y+normal.z*normal.z;
4030 if (normal_distance > (MagickEpsilon*MagickEpsilon))
4031 shade=distance/sqrt((double) normal_distance);
4032 }
4033 }
4034 if (gray != MagickFalse)
4035 {
cristy4c08aed2011-07-01 19:47:50 +00004036 SetPixelRed(shade_image,ClampToQuantum(shade),q);
4037 SetPixelGreen(shade_image,ClampToQuantum(shade),q);
4038 SetPixelBlue(shade_image,ClampToQuantum(shade),q);
cristy3ed852e2009-09-05 21:47:34 +00004039 }
4040 else
4041 {
cristy4c08aed2011-07-01 19:47:50 +00004042 SetPixelRed(shade_image,ClampToQuantum(QuantumScale*shade*
4043 GetPixelRed(image,s1)),q);
4044 SetPixelGreen(shade_image,ClampToQuantum(QuantumScale*shade*
4045 GetPixelGreen(image,s1)),q);
4046 SetPixelBlue(shade_image,ClampToQuantum(QuantumScale*shade*
4047 GetPixelBlue(image,s1)),q);
cristy3ed852e2009-09-05 21:47:34 +00004048 }
cristy4c08aed2011-07-01 19:47:50 +00004049 SetPixelAlpha(shade_image,GetPixelAlpha(image,s1),q);
4050 s0+=GetPixelChannels(image);
4051 s1+=GetPixelChannels(image);
4052 s2+=GetPixelChannels(image);
4053 q+=GetPixelChannels(shade_image);
cristy3ed852e2009-09-05 21:47:34 +00004054 }
4055 if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
4056 status=MagickFalse;
4057 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4058 {
4059 MagickBooleanType
4060 proceed;
4061
cristyb5d5f722009-11-04 03:03:49 +00004062#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004063 #pragma omp critical (MagickCore_ShadeImage)
4064#endif
4065 proceed=SetImageProgress(image,ShadeImageTag,progress++,image->rows);
4066 if (proceed == MagickFalse)
4067 status=MagickFalse;
4068 }
4069 }
4070 shade_view=DestroyCacheView(shade_view);
4071 image_view=DestroyCacheView(image_view);
4072 if (status == MagickFalse)
4073 shade_image=DestroyImage(shade_image);
4074 return(shade_image);
4075}
4076
4077/*
4078%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4079% %
4080% %
4081% %
4082% S h a r p e n I m a g e %
4083% %
4084% %
4085% %
4086%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4087%
4088% SharpenImage() sharpens the image. We convolve the image with a Gaussian
4089% operator of the given radius and standard deviation (sigma). For
4090% reasonable results, radius should be larger than sigma. Use a radius of 0
4091% and SharpenImage() selects a suitable radius for you.
4092%
4093% Using a separable kernel would be faster, but the negative weights cancel
4094% out on the corners of the kernel producing often undesirable ringing in the
4095% filtered result; this can be avoided by using a 2D gaussian shaped image
4096% sharpening kernel instead.
4097%
4098% The format of the SharpenImage method is:
4099%
4100% Image *SharpenImage(const Image *image,const double radius,
4101% const double sigma,ExceptionInfo *exception)
4102% Image *SharpenImageChannel(const Image *image,const ChannelType channel,
4103% const double radius,const double sigma,ExceptionInfo *exception)
4104%
4105% A description of each parameter follows:
4106%
4107% o image: the image.
4108%
4109% o channel: the channel type.
4110%
4111% o radius: the radius of the Gaussian, in pixels, not counting the center
4112% pixel.
4113%
4114% o sigma: the standard deviation of the Laplacian, in pixels.
4115%
4116% o exception: return any errors or warnings in this structure.
4117%
4118*/
4119
4120MagickExport Image *SharpenImage(const Image *image,const double radius,
4121 const double sigma,ExceptionInfo *exception)
4122{
4123 Image
4124 *sharp_image;
4125
4126 sharp_image=SharpenImageChannel(image,DefaultChannels,radius,sigma,exception);
4127 return(sharp_image);
4128}
4129
4130MagickExport Image *SharpenImageChannel(const Image *image,
4131 const ChannelType channel,const double radius,const double sigma,
4132 ExceptionInfo *exception)
4133{
4134 double
cristy47e00502009-12-17 19:19:57 +00004135 *kernel,
4136 normalize;
cristy3ed852e2009-09-05 21:47:34 +00004137
4138 Image
4139 *sharp_image;
4140
cristybb503372010-05-27 20:51:26 +00004141 register ssize_t
cristy47e00502009-12-17 19:19:57 +00004142 i;
4143
cristybb503372010-05-27 20:51:26 +00004144 size_t
cristy3ed852e2009-09-05 21:47:34 +00004145 width;
4146
cristy117ff172010-08-15 21:35:32 +00004147 ssize_t
4148 j,
4149 u,
4150 v;
4151
cristy3ed852e2009-09-05 21:47:34 +00004152 assert(image != (const Image *) NULL);
4153 assert(image->signature == MagickSignature);
4154 if (image->debug != MagickFalse)
4155 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4156 assert(exception != (ExceptionInfo *) NULL);
4157 assert(exception->signature == MagickSignature);
4158 width=GetOptimalKernelWidth2D(radius,sigma);
4159 kernel=(double *) AcquireQuantumMemory((size_t) width*width,sizeof(*kernel));
4160 if (kernel == (double *) NULL)
4161 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy3ed852e2009-09-05 21:47:34 +00004162 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00004163 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +00004164 i=0;
4165 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00004166 {
cristy47e00502009-12-17 19:19:57 +00004167 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00004168 {
cristy4205a3c2010-09-12 20:19:59 +00004169 kernel[i]=(double) (-exp(-((double) u*u+v*v)/(2.0*MagickSigma*
4170 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00004171 normalize+=kernel[i];
4172 i++;
4173 }
4174 }
4175 kernel[i/2]=(double) ((-2.0)*normalize);
4176 sharp_image=ConvolveImageChannel(image,channel,width,kernel,exception);
4177 kernel=(double *) RelinquishMagickMemory(kernel);
4178 return(sharp_image);
4179}
4180
4181/*
4182%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4183% %
4184% %
4185% %
4186% S p r e a d I m a g e %
4187% %
4188% %
4189% %
4190%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4191%
4192% SpreadImage() is a special effects method that randomly displaces each
4193% pixel in a block defined by the radius parameter.
4194%
4195% The format of the SpreadImage method is:
4196%
4197% Image *SpreadImage(const Image *image,const double radius,
4198% ExceptionInfo *exception)
4199%
4200% A description of each parameter follows:
4201%
4202% o image: the image.
4203%
4204% o radius: Choose a random pixel in a neighborhood of this extent.
4205%
4206% o exception: return any errors or warnings in this structure.
4207%
4208*/
4209MagickExport Image *SpreadImage(const Image *image,const double radius,
4210 ExceptionInfo *exception)
4211{
4212#define SpreadImageTag "Spread/Image"
4213
cristyfa112112010-01-04 17:48:07 +00004214 CacheView
cristy9f7e7cb2011-03-26 00:49:57 +00004215 *image_view,
4216 *spread_view;
cristyfa112112010-01-04 17:48:07 +00004217
cristy3ed852e2009-09-05 21:47:34 +00004218 Image
4219 *spread_image;
4220
cristy3ed852e2009-09-05 21:47:34 +00004221 MagickBooleanType
4222 status;
4223
cristybb503372010-05-27 20:51:26 +00004224 MagickOffsetType
4225 progress;
4226
cristy4c08aed2011-07-01 19:47:50 +00004227 PixelInfo
cristyddd82202009-11-03 20:14:50 +00004228 bias;
cristy3ed852e2009-09-05 21:47:34 +00004229
4230 RandomInfo
cristyfa112112010-01-04 17:48:07 +00004231 **restrict random_info;
cristy3ed852e2009-09-05 21:47:34 +00004232
cristybb503372010-05-27 20:51:26 +00004233 size_t
cristy3ed852e2009-09-05 21:47:34 +00004234 width;
4235
cristybb503372010-05-27 20:51:26 +00004236 ssize_t
4237 y;
4238
cristy3ed852e2009-09-05 21:47:34 +00004239 /*
4240 Initialize spread image attributes.
4241 */
4242 assert(image != (Image *) NULL);
4243 assert(image->signature == MagickSignature);
4244 if (image->debug != MagickFalse)
4245 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4246 assert(exception != (ExceptionInfo *) NULL);
4247 assert(exception->signature == MagickSignature);
4248 spread_image=CloneImage(image,image->columns,image->rows,MagickTrue,
4249 exception);
4250 if (spread_image == (Image *) NULL)
4251 return((Image *) NULL);
4252 if (SetImageStorageClass(spread_image,DirectClass) == MagickFalse)
4253 {
4254 InheritException(exception,&spread_image->exception);
4255 spread_image=DestroyImage(spread_image);
4256 return((Image *) NULL);
4257 }
4258 /*
4259 Spread image.
4260 */
4261 status=MagickTrue;
4262 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00004263 GetPixelInfo(spread_image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00004264 width=GetOptimalKernelWidth1D(radius,0.5);
cristy3ed852e2009-09-05 21:47:34 +00004265 random_info=AcquireRandomInfoThreadSet();
cristy9f7e7cb2011-03-26 00:49:57 +00004266 image_view=AcquireCacheView(image);
4267 spread_view=AcquireCacheView(spread_image);
cristyb557a152011-02-22 12:14:30 +00004268#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00004269 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00004270#endif
cristybb503372010-05-27 20:51:26 +00004271 for (y=0; y < (ssize_t) spread_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00004272 {
cristy5c9e6f22010-09-17 17:31:01 +00004273 const int
4274 id = GetOpenMPThreadId();
cristy6ebe97c2010-07-03 01:17:28 +00004275
cristy4c08aed2011-07-01 19:47:50 +00004276 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00004277 pixel;
4278
cristy4c08aed2011-07-01 19:47:50 +00004279 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00004280 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004281
cristy117ff172010-08-15 21:35:32 +00004282 register ssize_t
4283 x;
4284
cristy3ed852e2009-09-05 21:47:34 +00004285 if (status == MagickFalse)
4286 continue;
cristy9f7e7cb2011-03-26 00:49:57 +00004287 q=QueueCacheViewAuthenticPixels(spread_view,0,y,spread_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +00004288 exception);
cristy4c08aed2011-07-01 19:47:50 +00004289 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00004290 {
4291 status=MagickFalse;
4292 continue;
4293 }
cristyddd82202009-11-03 20:14:50 +00004294 pixel=bias;
cristybb503372010-05-27 20:51:26 +00004295 for (x=0; x < (ssize_t) spread_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004296 {
cristy4c08aed2011-07-01 19:47:50 +00004297 (void) InterpolatePixelInfo(image,image_view,
cristy8a7c3e82011-03-26 02:10:53 +00004298 UndefinedInterpolatePixel,(double) x+width*(GetPseudoRandomValue(
4299 random_info[id])-0.5),(double) y+width*(GetPseudoRandomValue(
4300 random_info[id])-0.5),&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00004301 SetPixelPixelInfo(spread_image,&pixel,q);
4302 q+=GetPixelChannels(spread_image);
cristy3ed852e2009-09-05 21:47:34 +00004303 }
cristy9f7e7cb2011-03-26 00:49:57 +00004304 if (SyncCacheViewAuthenticPixels(spread_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00004305 status=MagickFalse;
4306 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4307 {
4308 MagickBooleanType
4309 proceed;
4310
cristyb557a152011-02-22 12:14:30 +00004311#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004312 #pragma omp critical (MagickCore_SpreadImage)
4313#endif
4314 proceed=SetImageProgress(image,SpreadImageTag,progress++,image->rows);
4315 if (proceed == MagickFalse)
4316 status=MagickFalse;
4317 }
4318 }
cristy9f7e7cb2011-03-26 00:49:57 +00004319 spread_view=DestroyCacheView(spread_view);
cristy3ed852e2009-09-05 21:47:34 +00004320 image_view=DestroyCacheView(image_view);
4321 random_info=DestroyRandomInfoThreadSet(random_info);
cristy3ed852e2009-09-05 21:47:34 +00004322 return(spread_image);
4323}
4324
4325/*
4326%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4327% %
4328% %
4329% %
cristy0834d642011-03-18 18:26:08 +00004330% S t a t i s t i c I m a g e %
4331% %
4332% %
4333% %
4334%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4335%
4336% StatisticImage() makes each pixel the min / max / median / mode / etc. of
cristy8d752042011-03-19 01:00:36 +00004337% the neighborhood of the specified width and height.
cristy0834d642011-03-18 18:26:08 +00004338%
4339% The format of the StatisticImage method is:
4340%
4341% Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00004342% const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00004343% Image *StatisticImageChannel(const Image *image,
4344% const ChannelType channel,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00004345% const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00004346%
4347% A description of each parameter follows:
4348%
4349% o image: the image.
4350%
4351% o channel: the image channel.
4352%
4353% o type: the statistic type (median, mode, etc.).
4354%
cristy95c38342011-03-18 22:39:51 +00004355% o width: the width of the pixel neighborhood.
4356%
4357% o height: the height of the pixel neighborhood.
cristy0834d642011-03-18 18:26:08 +00004358%
4359% o exception: return any errors or warnings in this structure.
4360%
4361*/
4362
cristy733678d2011-03-18 21:29:28 +00004363#define ListChannels 5
4364
4365typedef struct _ListNode
4366{
4367 size_t
4368 next[9],
4369 count,
4370 signature;
4371} ListNode;
4372
4373typedef struct _SkipList
4374{
4375 ssize_t
4376 level;
4377
4378 ListNode
4379 *nodes;
4380} SkipList;
4381
4382typedef struct _PixelList
4383{
4384 size_t
cristy6fc86bb2011-03-18 23:45:16 +00004385 length,
cristy733678d2011-03-18 21:29:28 +00004386 seed,
4387 signature;
4388
4389 SkipList
4390 lists[ListChannels];
4391} PixelList;
4392
4393static PixelList *DestroyPixelList(PixelList *pixel_list)
4394{
4395 register ssize_t
4396 i;
4397
4398 if (pixel_list == (PixelList *) NULL)
4399 return((PixelList *) NULL);
4400 for (i=0; i < ListChannels; i++)
4401 if (pixel_list->lists[i].nodes != (ListNode *) NULL)
4402 pixel_list->lists[i].nodes=(ListNode *) RelinquishMagickMemory(
4403 pixel_list->lists[i].nodes);
4404 pixel_list=(PixelList *) RelinquishMagickMemory(pixel_list);
4405 return(pixel_list);
4406}
4407
4408static PixelList **DestroyPixelListThreadSet(PixelList **pixel_list)
4409{
4410 register ssize_t
4411 i;
4412
4413 assert(pixel_list != (PixelList **) NULL);
4414 for (i=0; i < (ssize_t) GetOpenMPMaximumThreads(); i++)
4415 if (pixel_list[i] != (PixelList *) NULL)
4416 pixel_list[i]=DestroyPixelList(pixel_list[i]);
4417 pixel_list=(PixelList **) RelinquishMagickMemory(pixel_list);
4418 return(pixel_list);
4419}
4420
cristy6fc86bb2011-03-18 23:45:16 +00004421static PixelList *AcquirePixelList(const size_t width,const size_t height)
cristy733678d2011-03-18 21:29:28 +00004422{
4423 PixelList
4424 *pixel_list;
4425
4426 register ssize_t
4427 i;
4428
4429 pixel_list=(PixelList *) AcquireMagickMemory(sizeof(*pixel_list));
4430 if (pixel_list == (PixelList *) NULL)
4431 return(pixel_list);
4432 (void) ResetMagickMemory((void *) pixel_list,0,sizeof(*pixel_list));
cristy6fc86bb2011-03-18 23:45:16 +00004433 pixel_list->length=width*height;
cristy733678d2011-03-18 21:29:28 +00004434 for (i=0; i < ListChannels; i++)
4435 {
4436 pixel_list->lists[i].nodes=(ListNode *) AcquireQuantumMemory(65537UL,
4437 sizeof(*pixel_list->lists[i].nodes));
4438 if (pixel_list->lists[i].nodes == (ListNode *) NULL)
4439 return(DestroyPixelList(pixel_list));
4440 (void) ResetMagickMemory(pixel_list->lists[i].nodes,0,65537UL*
4441 sizeof(*pixel_list->lists[i].nodes));
4442 }
4443 pixel_list->signature=MagickSignature;
4444 return(pixel_list);
4445}
4446
cristy6fc86bb2011-03-18 23:45:16 +00004447static PixelList **AcquirePixelListThreadSet(const size_t width,
4448 const size_t height)
cristy733678d2011-03-18 21:29:28 +00004449{
4450 PixelList
4451 **pixel_list;
4452
4453 register ssize_t
4454 i;
4455
4456 size_t
4457 number_threads;
4458
4459 number_threads=GetOpenMPMaximumThreads();
4460 pixel_list=(PixelList **) AcquireQuantumMemory(number_threads,
4461 sizeof(*pixel_list));
4462 if (pixel_list == (PixelList **) NULL)
4463 return((PixelList **) NULL);
4464 (void) ResetMagickMemory(pixel_list,0,number_threads*sizeof(*pixel_list));
4465 for (i=0; i < (ssize_t) number_threads; i++)
4466 {
cristy6fc86bb2011-03-18 23:45:16 +00004467 pixel_list[i]=AcquirePixelList(width,height);
cristy733678d2011-03-18 21:29:28 +00004468 if (pixel_list[i] == (PixelList *) NULL)
4469 return(DestroyPixelListThreadSet(pixel_list));
4470 }
4471 return(pixel_list);
4472}
4473
4474static void AddNodePixelList(PixelList *pixel_list,const ssize_t channel,
4475 const size_t color)
4476{
4477 register SkipList
4478 *list;
4479
4480 register ssize_t
4481 level;
4482
4483 size_t
4484 search,
4485 update[9];
4486
4487 /*
4488 Initialize the node.
4489 */
4490 list=pixel_list->lists+channel;
4491 list->nodes[color].signature=pixel_list->signature;
4492 list->nodes[color].count=1;
4493 /*
4494 Determine where it belongs in the list.
4495 */
4496 search=65536UL;
4497 for (level=list->level; level >= 0; level--)
4498 {
4499 while (list->nodes[search].next[level] < color)
4500 search=list->nodes[search].next[level];
4501 update[level]=search;
4502 }
4503 /*
4504 Generate a pseudo-random level for this node.
4505 */
4506 for (level=0; ; level++)
4507 {
4508 pixel_list->seed=(pixel_list->seed*42893621L)+1L;
4509 if ((pixel_list->seed & 0x300) != 0x300)
4510 break;
4511 }
4512 if (level > 8)
4513 level=8;
4514 if (level > (list->level+2))
4515 level=list->level+2;
4516 /*
4517 If we're raising the list's level, link back to the root node.
4518 */
4519 while (level > list->level)
4520 {
4521 list->level++;
4522 update[list->level]=65536UL;
4523 }
4524 /*
4525 Link the node into the skip-list.
4526 */
4527 do
4528 {
4529 list->nodes[color].next[level]=list->nodes[update[level]].next[level];
4530 list->nodes[update[level]].next[level]=color;
cristy3cba8ca2011-03-19 01:29:12 +00004531 } while (level-- > 0);
cristy733678d2011-03-18 21:29:28 +00004532}
4533
cristy4c08aed2011-07-01 19:47:50 +00004534static PixelInfo GetMaximumPixelList(PixelList *pixel_list)
cristy6fc86bb2011-03-18 23:45:16 +00004535{
cristy4c08aed2011-07-01 19:47:50 +00004536 PixelInfo
cristy6fc86bb2011-03-18 23:45:16 +00004537 pixel;
4538
4539 register SkipList
4540 *list;
4541
4542 register ssize_t
4543 channel;
4544
4545 size_t
cristyd76c51e2011-03-26 00:21:26 +00004546 color,
4547 maximum;
cristy49f37242011-03-22 18:18:23 +00004548
4549 ssize_t
cristy6fc86bb2011-03-18 23:45:16 +00004550 count;
4551
4552 unsigned short
cristyd76c51e2011-03-26 00:21:26 +00004553 channels[ListChannels];
cristy6fc86bb2011-03-18 23:45:16 +00004554
4555 /*
4556 Find the maximum value for each of the color.
4557 */
4558 for (channel=0; channel < 5; channel++)
4559 {
4560 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004561 color=65536L;
cristy6fc86bb2011-03-18 23:45:16 +00004562 count=0;
cristy49f37242011-03-22 18:18:23 +00004563 maximum=list->nodes[color].next[0];
cristy6fc86bb2011-03-18 23:45:16 +00004564 do
4565 {
4566 color=list->nodes[color].next[0];
cristy49f37242011-03-22 18:18:23 +00004567 if (color > maximum)
4568 maximum=color;
cristy6fc86bb2011-03-18 23:45:16 +00004569 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004570 } while (count < (ssize_t) pixel_list->length);
cristy49f37242011-03-22 18:18:23 +00004571 channels[channel]=(unsigned short) maximum;
4572 }
cristy4c08aed2011-07-01 19:47:50 +00004573 GetPixelInfo((const Image *) NULL,&pixel);
cristy49f37242011-03-22 18:18:23 +00004574 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4575 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4576 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004577 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4578 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy49f37242011-03-22 18:18:23 +00004579 return(pixel);
4580}
4581
cristy4c08aed2011-07-01 19:47:50 +00004582static PixelInfo GetMeanPixelList(PixelList *pixel_list)
cristy49f37242011-03-22 18:18:23 +00004583{
cristy4c08aed2011-07-01 19:47:50 +00004584 PixelInfo
cristy49f37242011-03-22 18:18:23 +00004585 pixel;
4586
cristy80a99a32011-03-30 01:30:23 +00004587 MagickRealType
4588 sum;
4589
cristy49f37242011-03-22 18:18:23 +00004590 register SkipList
4591 *list;
4592
4593 register ssize_t
4594 channel;
4595
4596 size_t
cristy80a99a32011-03-30 01:30:23 +00004597 color;
cristy49f37242011-03-22 18:18:23 +00004598
4599 ssize_t
4600 count;
4601
4602 unsigned short
4603 channels[ListChannels];
4604
4605 /*
4606 Find the mean value for each of the color.
4607 */
4608 for (channel=0; channel < 5; channel++)
4609 {
4610 list=pixel_list->lists+channel;
4611 color=65536L;
4612 count=0;
cristy80a99a32011-03-30 01:30:23 +00004613 sum=0.0;
cristy49f37242011-03-22 18:18:23 +00004614 do
4615 {
4616 color=list->nodes[color].next[0];
cristy80a99a32011-03-30 01:30:23 +00004617 sum+=(MagickRealType) list->nodes[color].count*color;
cristy49f37242011-03-22 18:18:23 +00004618 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004619 } while (count < (ssize_t) pixel_list->length);
cristy80a99a32011-03-30 01:30:23 +00004620 sum/=pixel_list->length;
4621 channels[channel]=(unsigned short) sum;
cristy6fc86bb2011-03-18 23:45:16 +00004622 }
cristy4c08aed2011-07-01 19:47:50 +00004623 GetPixelInfo((const Image *) NULL,&pixel);
cristy6fc86bb2011-03-18 23:45:16 +00004624 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4625 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4626 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004627 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4628 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy6fc86bb2011-03-18 23:45:16 +00004629 return(pixel);
4630}
4631
cristy4c08aed2011-07-01 19:47:50 +00004632static PixelInfo GetMedianPixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004633{
cristy4c08aed2011-07-01 19:47:50 +00004634 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004635 pixel;
4636
4637 register SkipList
4638 *list;
4639
4640 register ssize_t
4641 channel;
4642
4643 size_t
cristy49f37242011-03-22 18:18:23 +00004644 color;
4645
4646 ssize_t
cristy733678d2011-03-18 21:29:28 +00004647 count;
4648
4649 unsigned short
4650 channels[ListChannels];
4651
4652 /*
4653 Find the median value for each of the color.
4654 */
cristy733678d2011-03-18 21:29:28 +00004655 for (channel=0; channel < 5; channel++)
4656 {
4657 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004658 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004659 count=0;
4660 do
4661 {
4662 color=list->nodes[color].next[0];
4663 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004664 } while (count <= (ssize_t) (pixel_list->length >> 1));
cristy6fc86bb2011-03-18 23:45:16 +00004665 channels[channel]=(unsigned short) color;
4666 }
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 GetMinimumPixelList(PixelList *pixel_list)
cristy6fc86bb2011-03-18 23:45:16 +00004677{
cristy4c08aed2011-07-01 19:47:50 +00004678 PixelInfo
cristy6fc86bb2011-03-18 23:45:16 +00004679 pixel;
4680
4681 register SkipList
4682 *list;
4683
4684 register ssize_t
4685 channel;
4686
4687 size_t
cristyd76c51e2011-03-26 00:21:26 +00004688 color,
4689 minimum;
cristy6fc86bb2011-03-18 23:45:16 +00004690
cristy49f37242011-03-22 18:18:23 +00004691 ssize_t
4692 count;
4693
cristy6fc86bb2011-03-18 23:45:16 +00004694 unsigned short
cristyd76c51e2011-03-26 00:21:26 +00004695 channels[ListChannels];
cristy6fc86bb2011-03-18 23:45:16 +00004696
4697 /*
4698 Find the minimum value for each of the color.
4699 */
4700 for (channel=0; channel < 5; channel++)
4701 {
4702 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004703 count=0;
cristy6fc86bb2011-03-18 23:45:16 +00004704 color=65536UL;
cristy49f37242011-03-22 18:18:23 +00004705 minimum=list->nodes[color].next[0];
4706 do
4707 {
4708 color=list->nodes[color].next[0];
4709 if (color < minimum)
4710 minimum=color;
4711 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004712 } while (count < (ssize_t) pixel_list->length);
cristy49f37242011-03-22 18:18:23 +00004713 channels[channel]=(unsigned short) minimum;
cristy733678d2011-03-18 21:29:28 +00004714 }
cristy4c08aed2011-07-01 19:47:50 +00004715 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004716 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4717 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4718 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004719 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4720 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy733678d2011-03-18 21:29:28 +00004721 return(pixel);
4722}
4723
cristy4c08aed2011-07-01 19:47:50 +00004724static PixelInfo GetModePixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004725{
cristy4c08aed2011-07-01 19:47:50 +00004726 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004727 pixel;
4728
4729 register SkipList
4730 *list;
4731
4732 register ssize_t
4733 channel;
4734
4735 size_t
4736 color,
cristy733678d2011-03-18 21:29:28 +00004737 max_count,
cristy6fc86bb2011-03-18 23:45:16 +00004738 mode;
cristy733678d2011-03-18 21:29:28 +00004739
cristy49f37242011-03-22 18:18:23 +00004740 ssize_t
4741 count;
4742
cristy733678d2011-03-18 21:29:28 +00004743 unsigned short
4744 channels[5];
4745
4746 /*
glennrp30d2dc62011-06-25 03:17:16 +00004747 Make each pixel the 'predominant color' of the specified neighborhood.
cristy733678d2011-03-18 21:29:28 +00004748 */
cristy733678d2011-03-18 21:29:28 +00004749 for (channel=0; channel < 5; channel++)
4750 {
4751 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004752 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004753 mode=color;
4754 max_count=list->nodes[mode].count;
4755 count=0;
4756 do
4757 {
4758 color=list->nodes[color].next[0];
4759 if (list->nodes[color].count > max_count)
4760 {
4761 mode=color;
4762 max_count=list->nodes[mode].count;
4763 }
4764 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004765 } while (count < (ssize_t) pixel_list->length);
cristy733678d2011-03-18 21:29:28 +00004766 channels[channel]=(unsigned short) mode;
4767 }
cristy4c08aed2011-07-01 19:47:50 +00004768 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004769 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4770 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4771 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004772 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4773 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy733678d2011-03-18 21:29:28 +00004774 return(pixel);
4775}
4776
cristy4c08aed2011-07-01 19:47:50 +00004777static PixelInfo GetNonpeakPixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004778{
cristy4c08aed2011-07-01 19:47:50 +00004779 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004780 pixel;
4781
4782 register SkipList
4783 *list;
4784
4785 register ssize_t
4786 channel;
4787
4788 size_t
cristy733678d2011-03-18 21:29:28 +00004789 color,
cristy733678d2011-03-18 21:29:28 +00004790 next,
4791 previous;
4792
cristy49f37242011-03-22 18:18:23 +00004793 ssize_t
4794 count;
4795
cristy733678d2011-03-18 21:29:28 +00004796 unsigned short
4797 channels[5];
4798
4799 /*
cristy49f37242011-03-22 18:18:23 +00004800 Finds the non peak value for each of the colors.
cristy733678d2011-03-18 21:29:28 +00004801 */
cristy733678d2011-03-18 21:29:28 +00004802 for (channel=0; channel < 5; channel++)
4803 {
4804 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004805 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004806 next=list->nodes[color].next[0];
4807 count=0;
4808 do
4809 {
4810 previous=color;
4811 color=next;
4812 next=list->nodes[color].next[0];
4813 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004814 } while (count <= (ssize_t) (pixel_list->length >> 1));
cristy733678d2011-03-18 21:29:28 +00004815 if ((previous == 65536UL) && (next != 65536UL))
4816 color=next;
4817 else
4818 if ((previous != 65536UL) && (next == 65536UL))
4819 color=previous;
4820 channels[channel]=(unsigned short) color;
4821 }
cristy4c08aed2011-07-01 19:47:50 +00004822 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004823 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4824 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4825 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004826 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4827 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy733678d2011-03-18 21:29:28 +00004828 return(pixel);
4829}
4830
cristy4c08aed2011-07-01 19:47:50 +00004831static PixelInfo GetStandardDeviationPixelList(PixelList *pixel_list)
cristy9a68cbb2011-03-29 00:51:23 +00004832{
cristy4c08aed2011-07-01 19:47:50 +00004833 PixelInfo
cristy9a68cbb2011-03-29 00:51:23 +00004834 pixel;
4835
cristy80a99a32011-03-30 01:30:23 +00004836 MagickRealType
4837 sum,
4838 sum_squared;
4839
cristy9a68cbb2011-03-29 00:51:23 +00004840 register SkipList
4841 *list;
4842
4843 register ssize_t
4844 channel;
4845
4846 size_t
cristy80a99a32011-03-30 01:30:23 +00004847 color;
cristy9a68cbb2011-03-29 00:51:23 +00004848
4849 ssize_t
4850 count;
4851
4852 unsigned short
4853 channels[ListChannels];
4854
4855 /*
cristy80a99a32011-03-30 01:30:23 +00004856 Find the standard-deviation value for each of the color.
cristy9a68cbb2011-03-29 00:51:23 +00004857 */
4858 for (channel=0; channel < 5; channel++)
4859 {
4860 list=pixel_list->lists+channel;
4861 color=65536L;
4862 count=0;
cristy80a99a32011-03-30 01:30:23 +00004863 sum=0.0;
4864 sum_squared=0.0;
cristy9a68cbb2011-03-29 00:51:23 +00004865 do
4866 {
cristy80a99a32011-03-30 01:30:23 +00004867 register ssize_t
4868 i;
4869
cristy9a68cbb2011-03-29 00:51:23 +00004870 color=list->nodes[color].next[0];
cristy80a99a32011-03-30 01:30:23 +00004871 sum+=(MagickRealType) list->nodes[color].count*color;
4872 for (i=0; i < (ssize_t) list->nodes[color].count; i++)
4873 sum_squared+=((MagickRealType) color)*((MagickRealType) color);
cristy9a68cbb2011-03-29 00:51:23 +00004874 count+=list->nodes[color].count;
4875 } while (count < (ssize_t) pixel_list->length);
cristy80a99a32011-03-30 01:30:23 +00004876 sum/=pixel_list->length;
4877 sum_squared/=pixel_list->length;
4878 channels[channel]=(unsigned short) sqrt(sum_squared-(sum*sum));
cristy9a68cbb2011-03-29 00:51:23 +00004879 }
cristy4c08aed2011-07-01 19:47:50 +00004880 GetPixelInfo((const Image *) NULL,&pixel);
cristy9a68cbb2011-03-29 00:51:23 +00004881 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4882 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4883 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004884 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4885 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy9a68cbb2011-03-29 00:51:23 +00004886 return(pixel);
4887}
4888
cristy4c08aed2011-07-01 19:47:50 +00004889static inline void InsertPixelList(const Image *image,const Quantum *pixel,
4890 PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004891{
4892 size_t
4893 signature;
4894
4895 unsigned short
4896 index;
4897
cristy4c08aed2011-07-01 19:47:50 +00004898 index=ScaleQuantumToShort(GetPixelRed(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004899 signature=pixel_list->lists[0].nodes[index].signature;
4900 if (signature == pixel_list->signature)
4901 pixel_list->lists[0].nodes[index].count++;
4902 else
4903 AddNodePixelList(pixel_list,0,index);
cristy4c08aed2011-07-01 19:47:50 +00004904 index=ScaleQuantumToShort(GetPixelGreen(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004905 signature=pixel_list->lists[1].nodes[index].signature;
4906 if (signature == pixel_list->signature)
4907 pixel_list->lists[1].nodes[index].count++;
4908 else
4909 AddNodePixelList(pixel_list,1,index);
cristy4c08aed2011-07-01 19:47:50 +00004910 index=ScaleQuantumToShort(GetPixelBlue(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004911 signature=pixel_list->lists[2].nodes[index].signature;
4912 if (signature == pixel_list->signature)
4913 pixel_list->lists[2].nodes[index].count++;
4914 else
4915 AddNodePixelList(pixel_list,2,index);
cristy4c08aed2011-07-01 19:47:50 +00004916 index=ScaleQuantumToShort(GetPixelAlpha(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004917 signature=pixel_list->lists[3].nodes[index].signature;
4918 if (signature == pixel_list->signature)
4919 pixel_list->lists[3].nodes[index].count++;
4920 else
4921 AddNodePixelList(pixel_list,3,index);
4922 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00004923 index=ScaleQuantumToShort(GetPixelBlack(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004924 signature=pixel_list->lists[4].nodes[index].signature;
4925 if (signature == pixel_list->signature)
4926 pixel_list->lists[4].nodes[index].count++;
4927 else
4928 AddNodePixelList(pixel_list,4,index);
4929}
4930
cristy80c99742011-04-04 14:46:39 +00004931static inline MagickRealType MagickAbsoluteValue(const MagickRealType x)
4932{
4933 if (x < 0)
4934 return(-x);
4935 return(x);
4936}
4937
cristy733678d2011-03-18 21:29:28 +00004938static void ResetPixelList(PixelList *pixel_list)
4939{
4940 int
4941 level;
4942
4943 register ListNode
4944 *root;
4945
4946 register SkipList
4947 *list;
4948
4949 register ssize_t
4950 channel;
4951
4952 /*
4953 Reset the skip-list.
4954 */
4955 for (channel=0; channel < 5; channel++)
4956 {
4957 list=pixel_list->lists+channel;
4958 root=list->nodes+65536UL;
4959 list->level=0;
4960 for (level=0; level < 9; level++)
4961 root->next[level]=65536UL;
4962 }
4963 pixel_list->seed=pixel_list->signature++;
4964}
4965
cristy0834d642011-03-18 18:26:08 +00004966MagickExport Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00004967 const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00004968{
cristy95c38342011-03-18 22:39:51 +00004969 Image
4970 *statistic_image;
4971
4972 statistic_image=StatisticImageChannel(image,DefaultChannels,type,width,
4973 height,exception);
4974 return(statistic_image);
cristy0834d642011-03-18 18:26:08 +00004975}
4976
4977MagickExport Image *StatisticImageChannel(const Image *image,
cristy95c38342011-03-18 22:39:51 +00004978 const ChannelType channel,const StatisticType type,const size_t width,
4979 const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00004980{
cristy3cba8ca2011-03-19 01:29:12 +00004981#define StatisticWidth \
cristyd76c51e2011-03-26 00:21:26 +00004982 (width == 0 ? GetOptimalKernelWidth2D((double) width,0.5) : width)
cristy3cba8ca2011-03-19 01:29:12 +00004983#define StatisticHeight \
cristyd76c51e2011-03-26 00:21:26 +00004984 (height == 0 ? GetOptimalKernelWidth2D((double) height,0.5) : height)
cristy0834d642011-03-18 18:26:08 +00004985#define StatisticImageTag "Statistic/Image"
4986
4987 CacheView
4988 *image_view,
4989 *statistic_view;
4990
4991 Image
4992 *statistic_image;
4993
4994 MagickBooleanType
4995 status;
4996
4997 MagickOffsetType
4998 progress;
4999
5000 PixelList
5001 **restrict pixel_list;
5002
cristy0834d642011-03-18 18:26:08 +00005003 ssize_t
5004 y;
5005
5006 /*
5007 Initialize statistics image attributes.
5008 */
5009 assert(image != (Image *) NULL);
5010 assert(image->signature == MagickSignature);
5011 if (image->debug != MagickFalse)
5012 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5013 assert(exception != (ExceptionInfo *) NULL);
5014 assert(exception->signature == MagickSignature);
cristy0834d642011-03-18 18:26:08 +00005015 statistic_image=CloneImage(image,image->columns,image->rows,MagickTrue,
5016 exception);
5017 if (statistic_image == (Image *) NULL)
5018 return((Image *) NULL);
5019 if (SetImageStorageClass(statistic_image,DirectClass) == MagickFalse)
5020 {
5021 InheritException(exception,&statistic_image->exception);
5022 statistic_image=DestroyImage(statistic_image);
5023 return((Image *) NULL);
5024 }
cristy6fc86bb2011-03-18 23:45:16 +00005025 pixel_list=AcquirePixelListThreadSet(StatisticWidth,StatisticHeight);
cristy0834d642011-03-18 18:26:08 +00005026 if (pixel_list == (PixelList **) NULL)
5027 {
5028 statistic_image=DestroyImage(statistic_image);
5029 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
5030 }
5031 /*
cristy8d752042011-03-19 01:00:36 +00005032 Make each pixel the min / max / median / mode / etc. of the neighborhood.
cristy0834d642011-03-18 18:26:08 +00005033 */
5034 status=MagickTrue;
5035 progress=0;
5036 image_view=AcquireCacheView(image);
5037 statistic_view=AcquireCacheView(statistic_image);
5038#if defined(MAGICKCORE_OPENMP_SUPPORT)
5039 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
5040#endif
5041 for (y=0; y < (ssize_t) statistic_image->rows; y++)
5042 {
5043 const int
5044 id = GetOpenMPThreadId();
5045
cristy4c08aed2011-07-01 19:47:50 +00005046 register const Quantum
cristy0834d642011-03-18 18:26:08 +00005047 *restrict p;
5048
cristy4c08aed2011-07-01 19:47:50 +00005049 register Quantum
cristy0834d642011-03-18 18:26:08 +00005050 *restrict q;
5051
5052 register ssize_t
5053 x;
5054
5055 if (status == MagickFalse)
5056 continue;
cristy6fc86bb2011-03-18 23:45:16 +00005057 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) StatisticWidth/2L),y-
5058 (ssize_t) (StatisticHeight/2L),image->columns+StatisticWidth,
5059 StatisticHeight,exception);
cristy3cba8ca2011-03-19 01:29:12 +00005060 q=QueueCacheViewAuthenticPixels(statistic_view,0,y,statistic_image->columns, 1,exception);
cristy4c08aed2011-07-01 19:47:50 +00005061 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy0834d642011-03-18 18:26:08 +00005062 {
5063 status=MagickFalse;
5064 continue;
5065 }
cristy0834d642011-03-18 18:26:08 +00005066 for (x=0; x < (ssize_t) statistic_image->columns; x++)
5067 {
cristy4c08aed2011-07-01 19:47:50 +00005068 PixelInfo
cristy0834d642011-03-18 18:26:08 +00005069 pixel;
5070
cristy4c08aed2011-07-01 19:47:50 +00005071 register const Quantum
cristy6e3026a2011-03-19 00:54:38 +00005072 *restrict r;
5073
cristy0834d642011-03-18 18:26:08 +00005074 register ssize_t
5075 u,
5076 v;
5077
5078 r=p;
cristy0834d642011-03-18 18:26:08 +00005079 ResetPixelList(pixel_list[id]);
cristy6e4c3292011-03-19 00:53:55 +00005080 for (v=0; v < (ssize_t) StatisticHeight; v++)
cristy0834d642011-03-18 18:26:08 +00005081 {
cristy6e4c3292011-03-19 00:53:55 +00005082 for (u=0; u < (ssize_t) StatisticWidth; u++)
cristy4c08aed2011-07-01 19:47:50 +00005083 InsertPixelList(image,r+u*GetPixelChannels(image),pixel_list[id]);
5084 r+=(image->columns+StatisticWidth)*GetPixelChannels(image);
cristy0834d642011-03-18 18:26:08 +00005085 }
cristy4c08aed2011-07-01 19:47:50 +00005086 GetPixelInfo(image,&pixel);
5087 SetPixelInfo(image,p+StatisticWidth*StatisticHeight/2,&pixel);
cristy0834d642011-03-18 18:26:08 +00005088 switch (type)
5089 {
cristy80c99742011-04-04 14:46:39 +00005090 case GradientStatistic:
5091 {
cristy4c08aed2011-07-01 19:47:50 +00005092 PixelInfo
cristy80c99742011-04-04 14:46:39 +00005093 maximum,
5094 minimum;
5095
5096 minimum=GetMinimumPixelList(pixel_list[id]);
5097 maximum=GetMaximumPixelList(pixel_list[id]);
5098 pixel.red=MagickAbsoluteValue(maximum.red-minimum.red);
5099 pixel.green=MagickAbsoluteValue(maximum.green-minimum.green);
5100 pixel.blue=MagickAbsoluteValue(maximum.blue-minimum.blue);
cristy4c08aed2011-07-01 19:47:50 +00005101 pixel.alpha=MagickAbsoluteValue(maximum.alpha-minimum.alpha);
cristy80c99742011-04-04 14:46:39 +00005102 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00005103 pixel.black=MagickAbsoluteValue(maximum.black-minimum.black);
cristy80c99742011-04-04 14:46:39 +00005104 break;
5105 }
cristy6fc86bb2011-03-18 23:45:16 +00005106 case MaximumStatistic:
5107 {
5108 pixel=GetMaximumPixelList(pixel_list[id]);
5109 break;
5110 }
cristy49f37242011-03-22 18:18:23 +00005111 case MeanStatistic:
5112 {
5113 pixel=GetMeanPixelList(pixel_list[id]);
5114 break;
5115 }
cristyf2ad14a2011-03-18 18:57:25 +00005116 case MedianStatistic:
cristy6fc86bb2011-03-18 23:45:16 +00005117 default:
cristyf2ad14a2011-03-18 18:57:25 +00005118 {
5119 pixel=GetMedianPixelList(pixel_list[id]);
5120 break;
5121 }
cristy6fc86bb2011-03-18 23:45:16 +00005122 case MinimumStatistic:
5123 {
5124 pixel=GetMinimumPixelList(pixel_list[id]);
5125 break;
5126 }
cristyf2ad14a2011-03-18 18:57:25 +00005127 case ModeStatistic:
5128 {
5129 pixel=GetModePixelList(pixel_list[id]);
5130 break;
5131 }
5132 case NonpeakStatistic:
5133 {
5134 pixel=GetNonpeakPixelList(pixel_list[id]);
5135 break;
5136 }
cristy9a68cbb2011-03-29 00:51:23 +00005137 case StandardDeviationStatistic:
5138 {
5139 pixel=GetStandardDeviationPixelList(pixel_list[id]);
5140 break;
5141 }
cristy0834d642011-03-18 18:26:08 +00005142 }
cristy2b9582a2011-07-04 17:38:56 +00005143 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00005144 SetPixelRed(statistic_image,
5145 ClampToQuantum(pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00005146 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00005147 SetPixelGreen(statistic_image,
5148 ClampToQuantum(pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00005149 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00005150 SetPixelBlue(statistic_image,
5151 ClampToQuantum(pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00005152 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy0834d642011-03-18 18:26:08 +00005153 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00005154 SetPixelBlack(statistic_image,
5155 ClampToQuantum(pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00005156 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00005157 (image->matte != MagickFalse))
5158 SetPixelAlpha(statistic_image,
5159 ClampToQuantum(pixel.alpha),q);
5160 p+=GetPixelChannels(image);
5161 q+=GetPixelChannels(statistic_image);
cristy0834d642011-03-18 18:26:08 +00005162 }
5163 if (SyncCacheViewAuthenticPixels(statistic_view,exception) == MagickFalse)
5164 status=MagickFalse;
5165 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5166 {
5167 MagickBooleanType
5168 proceed;
5169
5170#if defined(MAGICKCORE_OPENMP_SUPPORT)
5171 #pragma omp critical (MagickCore_StatisticImage)
5172#endif
5173 proceed=SetImageProgress(image,StatisticImageTag,progress++,
5174 image->rows);
5175 if (proceed == MagickFalse)
5176 status=MagickFalse;
5177 }
5178 }
5179 statistic_view=DestroyCacheView(statistic_view);
5180 image_view=DestroyCacheView(image_view);
5181 pixel_list=DestroyPixelListThreadSet(pixel_list);
5182 return(statistic_image);
5183}
5184
5185/*
5186%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5187% %
5188% %
5189% %
cristy3ed852e2009-09-05 21:47:34 +00005190% U n s h a r p M a s k I m a g e %
5191% %
5192% %
5193% %
5194%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5195%
5196% UnsharpMaskImage() sharpens one or more image channels. We convolve the
5197% image with a Gaussian operator of the given radius and standard deviation
5198% (sigma). For reasonable results, radius should be larger than sigma. Use a
5199% radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
5200%
5201% The format of the UnsharpMaskImage method is:
5202%
5203% Image *UnsharpMaskImage(const Image *image,const double radius,
5204% const double sigma,const double amount,const double threshold,
5205% ExceptionInfo *exception)
5206% Image *UnsharpMaskImageChannel(const Image *image,
5207% const ChannelType channel,const double radius,const double sigma,
5208% const double amount,const double threshold,ExceptionInfo *exception)
5209%
5210% A description of each parameter follows:
5211%
5212% o image: the image.
5213%
5214% o channel: the channel type.
5215%
5216% o radius: the radius of the Gaussian, in pixels, not counting the center
5217% pixel.
5218%
5219% o sigma: the standard deviation of the Gaussian, in pixels.
5220%
5221% o amount: the percentage of the difference between the original and the
5222% blur image that is added back into the original.
5223%
5224% o threshold: the threshold in pixels needed to apply the diffence amount.
5225%
5226% o exception: return any errors or warnings in this structure.
5227%
5228*/
5229
5230MagickExport Image *UnsharpMaskImage(const Image *image,const double radius,
5231 const double sigma,const double amount,const double threshold,
5232 ExceptionInfo *exception)
5233{
5234 Image
5235 *sharp_image;
5236
5237 sharp_image=UnsharpMaskImageChannel(image,DefaultChannels,radius,sigma,amount,
5238 threshold,exception);
5239 return(sharp_image);
5240}
5241
5242MagickExport Image *UnsharpMaskImageChannel(const Image *image,
5243 const ChannelType channel,const double radius,const double sigma,
5244 const double amount,const double threshold,ExceptionInfo *exception)
5245{
5246#define SharpenImageTag "Sharpen/Image"
5247
cristyc4c8d132010-01-07 01:58:38 +00005248 CacheView
5249 *image_view,
5250 *unsharp_view;
5251
cristy3ed852e2009-09-05 21:47:34 +00005252 Image
5253 *unsharp_image;
5254
cristy3ed852e2009-09-05 21:47:34 +00005255 MagickBooleanType
5256 status;
5257
cristybb503372010-05-27 20:51:26 +00005258 MagickOffsetType
5259 progress;
5260
cristy4c08aed2011-07-01 19:47:50 +00005261 PixelInfo
cristyddd82202009-11-03 20:14:50 +00005262 bias;
cristy3ed852e2009-09-05 21:47:34 +00005263
5264 MagickRealType
5265 quantum_threshold;
5266
cristybb503372010-05-27 20:51:26 +00005267 ssize_t
5268 y;
5269
cristy3ed852e2009-09-05 21:47:34 +00005270 assert(image != (const Image *) NULL);
5271 assert(image->signature == MagickSignature);
5272 if (image->debug != MagickFalse)
5273 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5274 assert(exception != (ExceptionInfo *) NULL);
5275 unsharp_image=BlurImageChannel(image,channel,radius,sigma,exception);
5276 if (unsharp_image == (Image *) NULL)
5277 return((Image *) NULL);
5278 quantum_threshold=(MagickRealType) QuantumRange*threshold;
5279 /*
5280 Unsharp-mask image.
5281 */
5282 status=MagickTrue;
5283 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00005284 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00005285 image_view=AcquireCacheView(image);
5286 unsharp_view=AcquireCacheView(unsharp_image);
cristyb5d5f722009-11-04 03:03:49 +00005287#if defined(MAGICKCORE_OPENMP_SUPPORT)
5288 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00005289#endif
cristybb503372010-05-27 20:51:26 +00005290 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00005291 {
cristy4c08aed2011-07-01 19:47:50 +00005292 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00005293 pixel;
5294
cristy4c08aed2011-07-01 19:47:50 +00005295 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00005296 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00005297
cristy4c08aed2011-07-01 19:47:50 +00005298 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00005299 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00005300
cristy117ff172010-08-15 21:35:32 +00005301 register ssize_t
5302 x;
5303
cristy3ed852e2009-09-05 21:47:34 +00005304 if (status == MagickFalse)
5305 continue;
5306 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
5307 q=GetCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
5308 exception);
cristy4c08aed2011-07-01 19:47:50 +00005309 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00005310 {
5311 status=MagickFalse;
5312 continue;
5313 }
cristyddd82202009-11-03 20:14:50 +00005314 pixel=bias;
cristybb503372010-05-27 20:51:26 +00005315 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00005316 {
cristy2b9582a2011-07-04 17:38:56 +00005317 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00005318 {
cristy4c08aed2011-07-01 19:47:50 +00005319 pixel.red=GetPixelRed(image,p)-(MagickRealType) GetPixelRed(image,q);
cristy3ed852e2009-09-05 21:47:34 +00005320 if (fabs(2.0*pixel.red) < quantum_threshold)
cristy4c08aed2011-07-01 19:47:50 +00005321 pixel.red=(MagickRealType) GetPixelRed(image,p);
cristy3ed852e2009-09-05 21:47:34 +00005322 else
cristy4c08aed2011-07-01 19:47:50 +00005323 pixel.red=(MagickRealType) GetPixelRed(image,p)+(pixel.red*amount);
5324 SetPixelRed(unsharp_image,ClampToQuantum(pixel.red),q);
cristy3ed852e2009-09-05 21:47:34 +00005325 }
cristy2b9582a2011-07-04 17:38:56 +00005326 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00005327 {
cristy4c08aed2011-07-01 19:47:50 +00005328 pixel.green=GetPixelGreen(image,p)-
5329 (MagickRealType) GetPixelGreen(image,q);
cristy3ed852e2009-09-05 21:47:34 +00005330 if (fabs(2.0*pixel.green) < quantum_threshold)
cristy4c08aed2011-07-01 19:47:50 +00005331 pixel.green=(MagickRealType)
5332 GetPixelGreen(image,p);
cristy3ed852e2009-09-05 21:47:34 +00005333 else
cristy4c08aed2011-07-01 19:47:50 +00005334 pixel.green=(MagickRealType)
5335 GetPixelGreen(image,p)+
5336 (pixel.green*amount);
5337 SetPixelGreen(unsharp_image,
5338 ClampToQuantum(pixel.green),q);
cristy3ed852e2009-09-05 21:47:34 +00005339 }
cristy2b9582a2011-07-04 17:38:56 +00005340 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00005341 {
cristy4c08aed2011-07-01 19:47:50 +00005342 pixel.blue=GetPixelBlue(image,p)-
5343 (MagickRealType) GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00005344 if (fabs(2.0*pixel.blue) < quantum_threshold)
cristy4c08aed2011-07-01 19:47:50 +00005345 pixel.blue=(MagickRealType)
5346 GetPixelBlue(image,p);
cristy3ed852e2009-09-05 21:47:34 +00005347 else
cristy4c08aed2011-07-01 19:47:50 +00005348 pixel.blue=(MagickRealType)
5349 GetPixelBlue(image,p)+(pixel.blue*amount);
5350 SetPixelBlue(unsharp_image,
5351 ClampToQuantum(pixel.blue),q);
cristy3ed852e2009-09-05 21:47:34 +00005352 }
cristy2b9582a2011-07-04 17:38:56 +00005353 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00005354 (image->colorspace == CMYKColorspace))
5355 {
cristy4c08aed2011-07-01 19:47:50 +00005356 pixel.black=GetPixelBlack(image,p)-
5357 (MagickRealType) GetPixelBlack(image,q);
5358 if (fabs(2.0*pixel.black) < quantum_threshold)
5359 pixel.black=(MagickRealType)
5360 GetPixelBlack(image,p);
cristy3ed852e2009-09-05 21:47:34 +00005361 else
cristy4c08aed2011-07-01 19:47:50 +00005362 pixel.black=(MagickRealType)
5363 GetPixelBlack(image,p)+(pixel.black*
5364 amount);
5365 SetPixelBlack(unsharp_image,
5366 ClampToQuantum(pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00005367 }
cristy2b9582a2011-07-04 17:38:56 +00005368 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00005369 {
5370 pixel.alpha=GetPixelAlpha(image,p)-
5371 (MagickRealType) GetPixelAlpha(image,q);
5372 if (fabs(2.0*pixel.alpha) < quantum_threshold)
5373 pixel.alpha=(MagickRealType)
5374 GetPixelAlpha(image,p);
5375 else
5376 pixel.alpha=GetPixelAlpha(image,p)+
5377 (pixel.alpha*amount);
5378 SetPixelAlpha(unsharp_image,
5379 ClampToQuantum(pixel.alpha),q);
5380 }
5381 p+=GetPixelChannels(image);
5382 q+=GetPixelChannels(unsharp_image);
cristy3ed852e2009-09-05 21:47:34 +00005383 }
5384 if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
5385 status=MagickFalse;
5386 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5387 {
5388 MagickBooleanType
5389 proceed;
5390
cristyb5d5f722009-11-04 03:03:49 +00005391#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00005392 #pragma omp critical (MagickCore_UnsharpMaskImageChannel)
5393#endif
5394 proceed=SetImageProgress(image,SharpenImageTag,progress++,image->rows);
5395 if (proceed == MagickFalse)
5396 status=MagickFalse;
5397 }
5398 }
5399 unsharp_image->type=image->type;
5400 unsharp_view=DestroyCacheView(unsharp_view);
5401 image_view=DestroyCacheView(image_view);
5402 if (status == MagickFalse)
5403 unsharp_image=DestroyImage(unsharp_image);
5404 return(unsharp_image);
5405}