blob: 977a4f46523803b5ccd7461fb42f203d48dac569 [file] [log] [blame]
cristy3ed852e2009-09-05 21:47:34 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% EEEEE FFFFF FFFFF EEEEE CCCC TTTTT %
7% E F F E C T %
8% EEE FFF FFF EEE C T %
9% E F F E C T %
10% EEEEE F F EEEEE CCCC T %
11% %
12% %
13% MagickCore Image Effects Methods %
14% %
15% Software Design %
16% John Cristy %
17% October 1996 %
18% %
19% %
cristy7e41fe82010-12-04 23:12:08 +000020% Copyright 1999-2011 ImageMagick Studio LLC, a non-profit organization %
cristy3ed852e2009-09-05 21:47:34 +000021% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
26% http://www.imagemagick.org/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
36%
37%
38*/
39
40/*
41 Include declarations.
42*/
cristy4c08aed2011-07-01 19:47:50 +000043#include "MagickCore/studio.h"
44#include "MagickCore/accelerate.h"
45#include "MagickCore/blob.h"
46#include "MagickCore/cache-view.h"
47#include "MagickCore/color.h"
48#include "MagickCore/color-private.h"
49#include "MagickCore/colorspace.h"
50#include "MagickCore/constitute.h"
51#include "MagickCore/decorate.h"
52#include "MagickCore/draw.h"
53#include "MagickCore/enhance.h"
54#include "MagickCore/exception.h"
55#include "MagickCore/exception-private.h"
56#include "MagickCore/effect.h"
57#include "MagickCore/fx.h"
58#include "MagickCore/gem.h"
59#include "MagickCore/geometry.h"
60#include "MagickCore/image-private.h"
61#include "MagickCore/list.h"
62#include "MagickCore/log.h"
63#include "MagickCore/memory_.h"
64#include "MagickCore/monitor.h"
65#include "MagickCore/monitor-private.h"
66#include "MagickCore/montage.h"
67#include "MagickCore/morphology.h"
68#include "MagickCore/paint.h"
69#include "MagickCore/pixel-accessor.h"
70#include "MagickCore/property.h"
71#include "MagickCore/quantize.h"
72#include "MagickCore/quantum.h"
73#include "MagickCore/quantum-private.h"
74#include "MagickCore/random_.h"
75#include "MagickCore/random-private.h"
76#include "MagickCore/resample.h"
77#include "MagickCore/resample-private.h"
78#include "MagickCore/resize.h"
79#include "MagickCore/resource_.h"
80#include "MagickCore/segment.h"
81#include "MagickCore/shear.h"
82#include "MagickCore/signature-private.h"
83#include "MagickCore/string_.h"
84#include "MagickCore/thread-private.h"
85#include "MagickCore/transform.h"
86#include "MagickCore/threshold.h"
cristy3ed852e2009-09-05 21:47:34 +000087
88/*
89%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
90% %
91% %
92% %
93% A d a p t i v e B l u r I m a g e %
94% %
95% %
96% %
97%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
98%
99% AdaptiveBlurImage() adaptively blurs the image by blurring less
100% intensely near image edges and more intensely far from edges. We blur the
101% image with a Gaussian operator of the given radius and standard deviation
102% (sigma). For reasonable results, radius should be larger than sigma. Use a
103% radius of 0 and AdaptiveBlurImage() selects a suitable radius for you.
104%
105% The format of the AdaptiveBlurImage method is:
106%
107% Image *AdaptiveBlurImage(const Image *image,const double radius,
108% const double sigma,ExceptionInfo *exception)
109% Image *AdaptiveBlurImageChannel(const Image *image,
110% const ChannelType channel,double radius,const double sigma,
111% ExceptionInfo *exception)
112%
113% A description of each parameter follows:
114%
115% o image: the image.
116%
117% o channel: the channel type.
118%
119% o radius: the radius of the Gaussian, in pixels, not counting the center
120% pixel.
121%
122% o sigma: the standard deviation of the Laplacian, in pixels.
123%
124% o exception: return any errors or warnings in this structure.
125%
126*/
127
128MagickExport Image *AdaptiveBlurImage(const Image *image,const double radius,
129 const double sigma,ExceptionInfo *exception)
130{
131 Image
132 *blur_image;
133
134 blur_image=AdaptiveBlurImageChannel(image,DefaultChannels,radius,sigma,
135 exception);
136 return(blur_image);
137}
138
cristyf89cb1d2011-07-07 01:24:37 +0000139MagickExport MagickBooleanType AdaptiveLevelImage(Image *image,
140 const char *levels)
141{
142 double
143 black_point,
144 gamma,
145 white_point;
146
147 GeometryInfo
148 geometry_info;
149
150 MagickBooleanType
151 status;
152
153 MagickStatusType
154 flags;
155
156 /*
157 Parse levels.
158 */
159 if (levels == (char *) NULL)
160 return(MagickFalse);
161 flags=ParseGeometry(levels,&geometry_info);
162 black_point=geometry_info.rho;
163 white_point=(double) QuantumRange;
164 if ((flags & SigmaValue) != 0)
165 white_point=geometry_info.sigma;
166 gamma=1.0;
167 if ((flags & XiValue) != 0)
168 gamma=geometry_info.xi;
169 if ((flags & PercentValue) != 0)
170 {
171 black_point*=(double) image->columns*image->rows/100.0;
172 white_point*=(double) image->columns*image->rows/100.0;
173 }
174 if ((flags & SigmaValue) == 0)
175 white_point=(double) QuantumRange-black_point;
176 if ((flags & AspectValue ) == 0)
177 status=LevelImage(image,black_point,white_point,gamma);
178 else
179 status=LevelizeImage(image,black_point,white_point,gamma);
180 return(status);
181}
182
cristy3ed852e2009-09-05 21:47:34 +0000183MagickExport Image *AdaptiveBlurImageChannel(const Image *image,
184 const ChannelType channel,const double radius,const double sigma,
185 ExceptionInfo *exception)
186{
187#define AdaptiveBlurImageTag "Convolve/Image"
188#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
189
cristyc4c8d132010-01-07 01:58:38 +0000190 CacheView
191 *blur_view,
192 *edge_view,
193 *image_view;
194
cristy3ed852e2009-09-05 21:47:34 +0000195 double
cristy47e00502009-12-17 19:19:57 +0000196 **kernel,
197 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000198
199 Image
200 *blur_image,
201 *edge_image,
202 *gaussian_image;
203
cristy3ed852e2009-09-05 21:47:34 +0000204 MagickBooleanType
205 status;
206
cristybb503372010-05-27 20:51:26 +0000207 MagickOffsetType
208 progress;
209
cristy4c08aed2011-07-01 19:47:50 +0000210 PixelInfo
cristyddd82202009-11-03 20:14:50 +0000211 bias;
cristy3ed852e2009-09-05 21:47:34 +0000212
cristybb503372010-05-27 20:51:26 +0000213 register ssize_t
cristy47e00502009-12-17 19:19:57 +0000214 i;
cristy3ed852e2009-09-05 21:47:34 +0000215
cristybb503372010-05-27 20:51:26 +0000216 size_t
cristy3ed852e2009-09-05 21:47:34 +0000217 width;
218
cristybb503372010-05-27 20:51:26 +0000219 ssize_t
220 j,
221 k,
222 u,
223 v,
224 y;
225
cristy3ed852e2009-09-05 21:47:34 +0000226 assert(image != (const Image *) NULL);
227 assert(image->signature == MagickSignature);
228 if (image->debug != MagickFalse)
229 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
230 assert(exception != (ExceptionInfo *) NULL);
231 assert(exception->signature == MagickSignature);
232 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
233 if (blur_image == (Image *) NULL)
234 return((Image *) NULL);
235 if (fabs(sigma) <= MagickEpsilon)
236 return(blur_image);
237 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
238 {
239 InheritException(exception,&blur_image->exception);
240 blur_image=DestroyImage(blur_image);
241 return((Image *) NULL);
242 }
243 /*
244 Edge detect the image brighness channel, level, blur, and level again.
245 */
246 edge_image=EdgeImage(image,radius,exception);
247 if (edge_image == (Image *) NULL)
248 {
249 blur_image=DestroyImage(blur_image);
250 return((Image *) NULL);
251 }
cristyf89cb1d2011-07-07 01:24:37 +0000252 (void) AdaptiveLevelImage(edge_image,"20%,95%");
cristy3ed852e2009-09-05 21:47:34 +0000253 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,exception);
254 if (gaussian_image != (Image *) NULL)
255 {
256 edge_image=DestroyImage(edge_image);
257 edge_image=gaussian_image;
258 }
cristyf89cb1d2011-07-07 01:24:37 +0000259 (void) AdaptiveLevelImage(edge_image,"10%,95%");
cristy3ed852e2009-09-05 21:47:34 +0000260 /*
261 Create a set of kernels from maximum (radius,sigma) to minimum.
262 */
263 width=GetOptimalKernelWidth2D(radius,sigma);
264 kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
265 if (kernel == (double **) NULL)
266 {
267 edge_image=DestroyImage(edge_image);
268 blur_image=DestroyImage(blur_image);
269 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
270 }
271 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
cristybb503372010-05-27 20:51:26 +0000272 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000273 {
274 kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)*
275 sizeof(**kernel));
276 if (kernel[i] == (double *) NULL)
277 break;
cristy47e00502009-12-17 19:19:57 +0000278 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000279 j=(ssize_t) (width-i)/2;
cristy47e00502009-12-17 19:19:57 +0000280 k=0;
281 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000282 {
cristy47e00502009-12-17 19:19:57 +0000283 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000284 {
cristy4205a3c2010-09-12 20:19:59 +0000285 kernel[i][k]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
286 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +0000287 normalize+=kernel[i][k];
288 k++;
cristy3ed852e2009-09-05 21:47:34 +0000289 }
290 }
cristy3ed852e2009-09-05 21:47:34 +0000291 if (fabs(normalize) <= MagickEpsilon)
292 normalize=1.0;
293 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000294 for (k=0; k < (j*j); k++)
295 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000296 }
cristybb503372010-05-27 20:51:26 +0000297 if (i < (ssize_t) width)
cristy3ed852e2009-09-05 21:47:34 +0000298 {
299 for (i-=2; i >= 0; i-=2)
300 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
301 kernel=(double **) RelinquishMagickMemory(kernel);
302 edge_image=DestroyImage(edge_image);
303 blur_image=DestroyImage(blur_image);
304 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
305 }
306 /*
307 Adaptively blur image.
308 */
309 status=MagickTrue;
310 progress=0;
cristy4c08aed2011-07-01 19:47:50 +0000311 GetPixelInfo(image,&bias);
312 SetPixelInfoBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000313 image_view=AcquireCacheView(image);
314 edge_view=AcquireCacheView(edge_image);
315 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000316#if defined(MAGICKCORE_OPENMP_SUPPORT)
317 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000318#endif
cristybb503372010-05-27 20:51:26 +0000319 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000320 {
cristy4c08aed2011-07-01 19:47:50 +0000321 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000322 *restrict p,
323 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000324
cristy4c08aed2011-07-01 19:47:50 +0000325 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000326 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000327
cristy117ff172010-08-15 21:35:32 +0000328 register ssize_t
329 x;
330
cristy3ed852e2009-09-05 21:47:34 +0000331 if (status == MagickFalse)
332 continue;
333 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
334 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
335 exception);
cristy4c08aed2011-07-01 19:47:50 +0000336 if ((r == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000337 {
338 status=MagickFalse;
339 continue;
340 }
cristybb503372010-05-27 20:51:26 +0000341 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000342 {
cristy4c08aed2011-07-01 19:47:50 +0000343 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000344 pixel;
345
346 MagickRealType
347 alpha,
348 gamma;
349
350 register const double
cristyc47d1f82009-11-26 01:44:43 +0000351 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000352
cristybb503372010-05-27 20:51:26 +0000353 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000354 i,
355 u,
356 v;
357
358 gamma=0.0;
cristy4c08aed2011-07-01 19:47:50 +0000359 i=(ssize_t) ceil((double) width*QuantumScale*
360 GetPixelIntensity(edge_image,r)-0.5);
cristy3ed852e2009-09-05 21:47:34 +0000361 if (i < 0)
362 i=0;
363 else
cristybb503372010-05-27 20:51:26 +0000364 if (i > (ssize_t) width)
365 i=(ssize_t) width;
cristy3ed852e2009-09-05 21:47:34 +0000366 if ((i & 0x01) != 0)
367 i--;
cristya21afde2010-07-02 00:45:40 +0000368 p=GetCacheViewVirtualPixels(image_view,x-((ssize_t) (width-i)/2L),y-
369 (ssize_t) ((width-i)/2L),width-i,width-i,exception);
cristy4c08aed2011-07-01 19:47:50 +0000370 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000371 break;
cristyddd82202009-11-03 20:14:50 +0000372 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +0000373 k=kernel[i];
cristybb503372010-05-27 20:51:26 +0000374 for (v=0; v < (ssize_t) (width-i); v++)
cristy3ed852e2009-09-05 21:47:34 +0000375 {
cristybb503372010-05-27 20:51:26 +0000376 for (u=0; u < (ssize_t) (width-i); u++)
cristy3ed852e2009-09-05 21:47:34 +0000377 {
378 alpha=1.0;
cristy2b9582a2011-07-04 17:38:56 +0000379 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000380 (image->matte != MagickFalse))
cristy4c08aed2011-07-01 19:47:50 +0000381 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,p));
cristy2b9582a2011-07-04 17:38:56 +0000382 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000383 pixel.red+=(*k)*alpha*GetPixelRed(image,p);
cristy2b9582a2011-07-04 17:38:56 +0000384 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000385 pixel.green+=(*k)*alpha*GetPixelGreen(image,p);
cristy2b9582a2011-07-04 17:38:56 +0000386 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000387 pixel.blue+=(*k)*alpha*GetPixelBlue(image,p);
cristy2b9582a2011-07-04 17:38:56 +0000388 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000389 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +0000390 pixel.black+=(*k)*alpha*GetPixelBlack(image,p);
cristy2b9582a2011-07-04 17:38:56 +0000391 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000392 pixel.alpha+=(*k)*GetPixelAlpha(image,p);
cristy3ed852e2009-09-05 21:47:34 +0000393 gamma+=(*k)*alpha;
394 k++;
cristy4c08aed2011-07-01 19:47:50 +0000395 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000396 }
397 }
398 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy2b9582a2011-07-04 17:38:56 +0000399 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000400 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +0000401 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000402 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +0000403 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000404 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +0000405 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000406 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +0000407 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +0000408 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000409 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
410 q+=GetPixelChannels(blur_image);
411 r+=GetPixelChannels(edge_image);
cristy3ed852e2009-09-05 21:47:34 +0000412 }
413 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
414 status=MagickFalse;
415 if (image->progress_monitor != (MagickProgressMonitor) NULL)
416 {
417 MagickBooleanType
418 proceed;
419
cristyb5d5f722009-11-04 03:03:49 +0000420#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000421 #pragma omp critical (MagickCore_AdaptiveBlurImageChannel)
422#endif
423 proceed=SetImageProgress(image,AdaptiveBlurImageTag,progress++,
424 image->rows);
425 if (proceed == MagickFalse)
426 status=MagickFalse;
427 }
428 }
429 blur_image->type=image->type;
430 blur_view=DestroyCacheView(blur_view);
431 edge_view=DestroyCacheView(edge_view);
432 image_view=DestroyCacheView(image_view);
433 edge_image=DestroyImage(edge_image);
cristybb503372010-05-27 20:51:26 +0000434 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000435 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
436 kernel=(double **) RelinquishMagickMemory(kernel);
437 if (status == MagickFalse)
438 blur_image=DestroyImage(blur_image);
439 return(blur_image);
440}
441
442/*
443%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
444% %
445% %
446% %
447% A d a p t i v e S h a r p e n I m a g e %
448% %
449% %
450% %
451%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
452%
453% AdaptiveSharpenImage() adaptively sharpens the image by sharpening more
454% intensely near image edges and less intensely far from edges. We sharpen the
455% image with a Gaussian operator of the given radius and standard deviation
456% (sigma). For reasonable results, radius should be larger than sigma. Use a
457% radius of 0 and AdaptiveSharpenImage() selects a suitable radius for you.
458%
459% The format of the AdaptiveSharpenImage method is:
460%
461% Image *AdaptiveSharpenImage(const Image *image,const double radius,
462% const double sigma,ExceptionInfo *exception)
463% Image *AdaptiveSharpenImageChannel(const Image *image,
464% const ChannelType channel,double radius,const double sigma,
465% ExceptionInfo *exception)
466%
467% A description of each parameter follows:
468%
469% o image: the image.
470%
471% o channel: the channel type.
472%
473% o radius: the radius of the Gaussian, in pixels, not counting the center
474% pixel.
475%
476% o sigma: the standard deviation of the Laplacian, in pixels.
477%
478% o exception: return any errors or warnings in this structure.
479%
480*/
481
482MagickExport Image *AdaptiveSharpenImage(const Image *image,const double radius,
483 const double sigma,ExceptionInfo *exception)
484{
485 Image
486 *sharp_image;
487
488 sharp_image=AdaptiveSharpenImageChannel(image,DefaultChannels,radius,sigma,
489 exception);
490 return(sharp_image);
491}
492
493MagickExport Image *AdaptiveSharpenImageChannel(const Image *image,
494 const ChannelType channel,const double radius,const double sigma,
495 ExceptionInfo *exception)
496{
497#define AdaptiveSharpenImageTag "Convolve/Image"
498#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
499
cristyc4c8d132010-01-07 01:58:38 +0000500 CacheView
501 *sharp_view,
502 *edge_view,
503 *image_view;
504
cristy3ed852e2009-09-05 21:47:34 +0000505 double
cristy47e00502009-12-17 19:19:57 +0000506 **kernel,
507 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000508
509 Image
510 *sharp_image,
511 *edge_image,
512 *gaussian_image;
513
cristy3ed852e2009-09-05 21:47:34 +0000514 MagickBooleanType
515 status;
516
cristybb503372010-05-27 20:51:26 +0000517 MagickOffsetType
518 progress;
519
cristy4c08aed2011-07-01 19:47:50 +0000520 PixelInfo
cristyddd82202009-11-03 20:14:50 +0000521 bias;
cristy3ed852e2009-09-05 21:47:34 +0000522
cristybb503372010-05-27 20:51:26 +0000523 register ssize_t
cristy47e00502009-12-17 19:19:57 +0000524 i;
cristy3ed852e2009-09-05 21:47:34 +0000525
cristybb503372010-05-27 20:51:26 +0000526 size_t
cristy3ed852e2009-09-05 21:47:34 +0000527 width;
528
cristybb503372010-05-27 20:51:26 +0000529 ssize_t
530 j,
531 k,
532 u,
533 v,
534 y;
535
cristy3ed852e2009-09-05 21:47:34 +0000536 assert(image != (const Image *) NULL);
537 assert(image->signature == MagickSignature);
538 if (image->debug != MagickFalse)
539 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
540 assert(exception != (ExceptionInfo *) NULL);
541 assert(exception->signature == MagickSignature);
542 sharp_image=CloneImage(image,0,0,MagickTrue,exception);
543 if (sharp_image == (Image *) NULL)
544 return((Image *) NULL);
545 if (fabs(sigma) <= MagickEpsilon)
546 return(sharp_image);
547 if (SetImageStorageClass(sharp_image,DirectClass) == MagickFalse)
548 {
549 InheritException(exception,&sharp_image->exception);
550 sharp_image=DestroyImage(sharp_image);
551 return((Image *) NULL);
552 }
553 /*
554 Edge detect the image brighness channel, level, sharp, and level again.
555 */
556 edge_image=EdgeImage(image,radius,exception);
557 if (edge_image == (Image *) NULL)
558 {
559 sharp_image=DestroyImage(sharp_image);
560 return((Image *) NULL);
561 }
cristyf89cb1d2011-07-07 01:24:37 +0000562 (void) AdaptiveLevelImage(edge_image,"20%,95%");
cristy3ed852e2009-09-05 21:47:34 +0000563 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,exception);
564 if (gaussian_image != (Image *) NULL)
565 {
566 edge_image=DestroyImage(edge_image);
567 edge_image=gaussian_image;
568 }
cristyf89cb1d2011-07-07 01:24:37 +0000569 (void) AdaptiveLevelImage(edge_image,"10%,95%");
cristy3ed852e2009-09-05 21:47:34 +0000570 /*
571 Create a set of kernels from maximum (radius,sigma) to minimum.
572 */
573 width=GetOptimalKernelWidth2D(radius,sigma);
574 kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
575 if (kernel == (double **) NULL)
576 {
577 edge_image=DestroyImage(edge_image);
578 sharp_image=DestroyImage(sharp_image);
579 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
580 }
581 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
cristybb503372010-05-27 20:51:26 +0000582 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000583 {
584 kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)*
585 sizeof(**kernel));
586 if (kernel[i] == (double *) NULL)
587 break;
cristy47e00502009-12-17 19:19:57 +0000588 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000589 j=(ssize_t) (width-i)/2;
cristy47e00502009-12-17 19:19:57 +0000590 k=0;
591 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000592 {
cristy47e00502009-12-17 19:19:57 +0000593 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000594 {
cristy4205a3c2010-09-12 20:19:59 +0000595 kernel[i][k]=(double) (-exp(-((double) u*u+v*v)/(2.0*MagickSigma*
596 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +0000597 normalize+=kernel[i][k];
598 k++;
cristy3ed852e2009-09-05 21:47:34 +0000599 }
600 }
cristy3ed852e2009-09-05 21:47:34 +0000601 if (fabs(normalize) <= MagickEpsilon)
602 normalize=1.0;
603 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000604 for (k=0; k < (j*j); k++)
605 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000606 }
cristybb503372010-05-27 20:51:26 +0000607 if (i < (ssize_t) width)
cristy3ed852e2009-09-05 21:47:34 +0000608 {
609 for (i-=2; i >= 0; i-=2)
610 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
611 kernel=(double **) RelinquishMagickMemory(kernel);
612 edge_image=DestroyImage(edge_image);
613 sharp_image=DestroyImage(sharp_image);
614 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
615 }
616 /*
617 Adaptively sharpen image.
618 */
619 status=MagickTrue;
620 progress=0;
cristy4c08aed2011-07-01 19:47:50 +0000621 GetPixelInfo(image,&bias);
622 SetPixelInfoBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000623 image_view=AcquireCacheView(image);
624 edge_view=AcquireCacheView(edge_image);
625 sharp_view=AcquireCacheView(sharp_image);
cristyb5d5f722009-11-04 03:03:49 +0000626#if defined(MAGICKCORE_OPENMP_SUPPORT)
627 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000628#endif
cristybb503372010-05-27 20:51:26 +0000629 for (y=0; y < (ssize_t) sharp_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000630 {
cristy4c08aed2011-07-01 19:47:50 +0000631 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000632 *restrict p,
633 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000634
cristy4c08aed2011-07-01 19:47:50 +0000635 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000636 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000637
cristy117ff172010-08-15 21:35:32 +0000638 register ssize_t
639 x;
640
cristy3ed852e2009-09-05 21:47:34 +0000641 if (status == MagickFalse)
642 continue;
643 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
644 q=QueueCacheViewAuthenticPixels(sharp_view,0,y,sharp_image->columns,1,
645 exception);
cristy4c08aed2011-07-01 19:47:50 +0000646 if ((r == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000647 {
648 status=MagickFalse;
649 continue;
650 }
cristybb503372010-05-27 20:51:26 +0000651 for (x=0; x < (ssize_t) sharp_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000652 {
cristy4c08aed2011-07-01 19:47:50 +0000653 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000654 pixel;
655
656 MagickRealType
657 alpha,
658 gamma;
659
660 register const double
cristyc47d1f82009-11-26 01:44:43 +0000661 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000662
cristybb503372010-05-27 20:51:26 +0000663 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000664 i,
665 u,
666 v;
667
668 gamma=0.0;
cristy4c08aed2011-07-01 19:47:50 +0000669 i=(ssize_t) ceil((double) width*QuantumScale*
670 GetPixelIntensity(edge_image,r)-0.5);
cristy3ed852e2009-09-05 21:47:34 +0000671 if (i < 0)
672 i=0;
673 else
cristybb503372010-05-27 20:51:26 +0000674 if (i > (ssize_t) width)
675 i=(ssize_t) width;
cristy3ed852e2009-09-05 21:47:34 +0000676 if ((i & 0x01) != 0)
677 i--;
cristy117ff172010-08-15 21:35:32 +0000678 p=GetCacheViewVirtualPixels(image_view,x-((ssize_t) (width-i)/2L),y-
679 (ssize_t) ((width-i)/2L),width-i,width-i,exception);
cristy4c08aed2011-07-01 19:47:50 +0000680 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000681 break;
cristy3ed852e2009-09-05 21:47:34 +0000682 k=kernel[i];
cristyddd82202009-11-03 20:14:50 +0000683 pixel=bias;
cristybb503372010-05-27 20:51:26 +0000684 for (v=0; v < (ssize_t) (width-i); v++)
cristy3ed852e2009-09-05 21:47:34 +0000685 {
cristybb503372010-05-27 20:51:26 +0000686 for (u=0; u < (ssize_t) (width-i); u++)
cristy3ed852e2009-09-05 21:47:34 +0000687 {
688 alpha=1.0;
cristy2b9582a2011-07-04 17:38:56 +0000689 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000690 (image->matte != MagickFalse))
cristy4c08aed2011-07-01 19:47:50 +0000691 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,p));
cristy2b9582a2011-07-04 17:38:56 +0000692 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000693 pixel.red+=(*k)*alpha*GetPixelRed(image,p);
cristy2b9582a2011-07-04 17:38:56 +0000694 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000695 pixel.green+=(*k)*alpha*GetPixelGreen(image,p);
cristy2b9582a2011-07-04 17:38:56 +0000696 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000697 pixel.blue+=(*k)*alpha*GetPixelBlue(image,p);
cristy2b9582a2011-07-04 17:38:56 +0000698 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000699 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +0000700 pixel.black+=(*k)*alpha*GetPixelBlack(image,p);
cristy2b9582a2011-07-04 17:38:56 +0000701 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000702 pixel.alpha+=(*k)*GetPixelAlpha(image,p);
cristy3ed852e2009-09-05 21:47:34 +0000703 gamma+=(*k)*alpha;
704 k++;
cristy4c08aed2011-07-01 19:47:50 +0000705 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000706 }
707 }
708 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy2b9582a2011-07-04 17:38:56 +0000709 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000710 SetPixelRed(sharp_image,ClampToQuantum(gamma*pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +0000711 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000712 SetPixelGreen(sharp_image,ClampToQuantum(gamma*pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +0000713 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000714 SetPixelBlue(sharp_image,ClampToQuantum(gamma*pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +0000715 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000716 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +0000717 SetPixelBlack(sharp_image,ClampToQuantum(gamma*pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +0000718 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000719 SetPixelAlpha(sharp_image,ClampToQuantum(pixel.alpha),q);
720 q+=GetPixelChannels(sharp_image);
721 r+=GetPixelChannels(edge_image);
cristy3ed852e2009-09-05 21:47:34 +0000722 }
723 if (SyncCacheViewAuthenticPixels(sharp_view,exception) == MagickFalse)
724 status=MagickFalse;
725 if (image->progress_monitor != (MagickProgressMonitor) NULL)
726 {
727 MagickBooleanType
728 proceed;
729
cristyb5d5f722009-11-04 03:03:49 +0000730#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000731 #pragma omp critical (MagickCore_AdaptiveSharpenImageChannel)
732#endif
733 proceed=SetImageProgress(image,AdaptiveSharpenImageTag,progress++,
734 image->rows);
735 if (proceed == MagickFalse)
736 status=MagickFalse;
737 }
738 }
739 sharp_image->type=image->type;
740 sharp_view=DestroyCacheView(sharp_view);
741 edge_view=DestroyCacheView(edge_view);
742 image_view=DestroyCacheView(image_view);
743 edge_image=DestroyImage(edge_image);
cristybb503372010-05-27 20:51:26 +0000744 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000745 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
746 kernel=(double **) RelinquishMagickMemory(kernel);
747 if (status == MagickFalse)
748 sharp_image=DestroyImage(sharp_image);
749 return(sharp_image);
750}
751
752/*
753%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
754% %
755% %
756% %
757% B l u r I m a g e %
758% %
759% %
760% %
761%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
762%
763% BlurImage() blurs an image. We convolve the image with a Gaussian operator
764% of the given radius and standard deviation (sigma). For reasonable results,
765% the radius should be larger than sigma. Use a radius of 0 and BlurImage()
766% selects a suitable radius for you.
767%
768% BlurImage() differs from GaussianBlurImage() in that it uses a separable
769% kernel which is faster but mathematically equivalent to the non-separable
770% kernel.
771%
772% The format of the BlurImage method is:
773%
774% Image *BlurImage(const Image *image,const double radius,
775% const double sigma,ExceptionInfo *exception)
776% Image *BlurImageChannel(const Image *image,const ChannelType channel,
777% const double radius,const double sigma,ExceptionInfo *exception)
778%
779% A description of each parameter follows:
780%
781% o image: the image.
782%
783% o channel: the channel type.
784%
785% o radius: the radius of the Gaussian, in pixels, not counting the center
786% pixel.
787%
788% o sigma: the standard deviation of the Gaussian, in pixels.
789%
790% o exception: return any errors or warnings in this structure.
791%
792*/
793
794MagickExport Image *BlurImage(const Image *image,const double radius,
795 const double sigma,ExceptionInfo *exception)
796{
797 Image
798 *blur_image;
799
800 blur_image=BlurImageChannel(image,DefaultChannels,radius,sigma,exception);
801 return(blur_image);
802}
803
cristybb503372010-05-27 20:51:26 +0000804static double *GetBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +0000805{
cristy3ed852e2009-09-05 21:47:34 +0000806 double
cristy47e00502009-12-17 19:19:57 +0000807 *kernel,
808 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000809
cristy117ff172010-08-15 21:35:32 +0000810 register ssize_t
811 i;
812
cristybb503372010-05-27 20:51:26 +0000813 ssize_t
cristy47e00502009-12-17 19:19:57 +0000814 j,
815 k;
cristy3ed852e2009-09-05 21:47:34 +0000816
cristy3ed852e2009-09-05 21:47:34 +0000817 /*
818 Generate a 1-D convolution kernel.
819 */
820 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
821 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
822 if (kernel == (double *) NULL)
823 return(0);
cristy3ed852e2009-09-05 21:47:34 +0000824 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000825 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +0000826 i=0;
827 for (k=(-j); k <= j; k++)
828 {
cristy4205a3c2010-09-12 20:19:59 +0000829 kernel[i]=(double) (exp(-((double) k*k)/(2.0*MagickSigma*MagickSigma))/
830 (MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +0000831 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +0000832 i++;
833 }
cristybb503372010-05-27 20:51:26 +0000834 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000835 kernel[i]/=normalize;
836 return(kernel);
837}
838
839MagickExport Image *BlurImageChannel(const Image *image,
840 const ChannelType channel,const double radius,const double sigma,
841 ExceptionInfo *exception)
842{
843#define BlurImageTag "Blur/Image"
844
cristyc4c8d132010-01-07 01:58:38 +0000845 CacheView
846 *blur_view,
847 *image_view;
848
cristy3ed852e2009-09-05 21:47:34 +0000849 double
850 *kernel;
851
852 Image
853 *blur_image;
854
cristy3ed852e2009-09-05 21:47:34 +0000855 MagickBooleanType
856 status;
857
cristybb503372010-05-27 20:51:26 +0000858 MagickOffsetType
859 progress;
860
cristy4c08aed2011-07-01 19:47:50 +0000861 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000862 bias;
863
cristybb503372010-05-27 20:51:26 +0000864 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000865 i;
866
cristybb503372010-05-27 20:51:26 +0000867 size_t
cristy3ed852e2009-09-05 21:47:34 +0000868 width;
869
cristybb503372010-05-27 20:51:26 +0000870 ssize_t
871 x,
872 y;
873
cristy3ed852e2009-09-05 21:47:34 +0000874 /*
875 Initialize blur image attributes.
876 */
877 assert(image != (Image *) NULL);
878 assert(image->signature == MagickSignature);
879 if (image->debug != MagickFalse)
880 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
881 assert(exception != (ExceptionInfo *) NULL);
882 assert(exception->signature == MagickSignature);
883 blur_image=CloneImage(image,0,0,MagickTrue,exception);
884 if (blur_image == (Image *) NULL)
885 return((Image *) NULL);
886 if (fabs(sigma) <= MagickEpsilon)
887 return(blur_image);
888 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
889 {
890 InheritException(exception,&blur_image->exception);
891 blur_image=DestroyImage(blur_image);
892 return((Image *) NULL);
893 }
894 width=GetOptimalKernelWidth1D(radius,sigma);
895 kernel=GetBlurKernel(width,sigma);
896 if (kernel == (double *) NULL)
897 {
898 blur_image=DestroyImage(blur_image);
899 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
900 }
901 if (image->debug != MagickFalse)
902 {
903 char
904 format[MaxTextExtent],
905 *message;
906
907 register const double
908 *k;
909
910 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +0000911 " BlurImage with %.20g kernel:",(double) width);
cristy3ed852e2009-09-05 21:47:34 +0000912 message=AcquireString("");
913 k=kernel;
cristybb503372010-05-27 20:51:26 +0000914 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000915 {
916 *message='\0';
cristyb51dff52011-05-19 16:55:47 +0000917 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) i);
cristy3ed852e2009-09-05 21:47:34 +0000918 (void) ConcatenateString(&message,format);
cristyb51dff52011-05-19 16:55:47 +0000919 (void) FormatLocaleString(format,MaxTextExtent,"%g ",*k++);
cristy3ed852e2009-09-05 21:47:34 +0000920 (void) ConcatenateString(&message,format);
921 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
922 }
923 message=DestroyString(message);
924 }
925 /*
926 Blur rows.
927 */
928 status=MagickTrue;
929 progress=0;
cristy4c08aed2011-07-01 19:47:50 +0000930 GetPixelInfo(image,&bias);
931 SetPixelInfoBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000932 image_view=AcquireCacheView(image);
933 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000934#if defined(MAGICKCORE_OPENMP_SUPPORT)
935 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000936#endif
cristybb503372010-05-27 20:51:26 +0000937 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000938 {
cristy4c08aed2011-07-01 19:47:50 +0000939 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000940 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000941
cristy4c08aed2011-07-01 19:47:50 +0000942 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000943 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000944
cristy117ff172010-08-15 21:35:32 +0000945 register ssize_t
946 x;
947
cristy3ed852e2009-09-05 21:47:34 +0000948 if (status == MagickFalse)
949 continue;
cristy117ff172010-08-15 21:35:32 +0000950 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y,
951 image->columns+width,1,exception);
cristy3ed852e2009-09-05 21:47:34 +0000952 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
953 exception);
cristy4c08aed2011-07-01 19:47:50 +0000954 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000955 {
956 status=MagickFalse;
957 continue;
958 }
cristybb503372010-05-27 20:51:26 +0000959 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000960 {
cristy4c08aed2011-07-01 19:47:50 +0000961 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000962 pixel;
963
964 register const double
cristyc47d1f82009-11-26 01:44:43 +0000965 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000966
cristy4c08aed2011-07-01 19:47:50 +0000967 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000968 *restrict kernel_pixels;
cristy3ed852e2009-09-05 21:47:34 +0000969
cristybb503372010-05-27 20:51:26 +0000970 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000971 i;
972
cristyddd82202009-11-03 20:14:50 +0000973 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +0000974 k=kernel;
975 kernel_pixels=p;
cristy2b9582a2011-07-04 17:38:56 +0000976 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +0000977 {
cristybb503372010-05-27 20:51:26 +0000978 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000979 {
cristy4c08aed2011-07-01 19:47:50 +0000980 pixel.red+=(*k)*GetPixelRed(image,kernel_pixels);
981 pixel.green+=(*k)*GetPixelGreen(image,kernel_pixels);
982 pixel.blue+=(*k)*GetPixelBlue(image,kernel_pixels);
983 if (image->colorspace == CMYKColorspace)
984 pixel.black+=(*k)*GetPixelBlack(image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +0000985 k++;
cristy4c08aed2011-07-01 19:47:50 +0000986 kernel_pixels+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000987 }
cristy2b9582a2011-07-04 17:38:56 +0000988 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000989 SetPixelRed(blur_image,ClampToQuantum(pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +0000990 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000991 SetPixelGreen(blur_image,ClampToQuantum(pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +0000992 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000993 SetPixelBlue(blur_image,ClampToQuantum(pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +0000994 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000995 (blur_image->colorspace == CMYKColorspace))
996 SetPixelBlack(blur_image,ClampToQuantum(pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +0000997 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +0000998 {
999 k=kernel;
1000 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001001 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001002 {
cristy4c08aed2011-07-01 19:47:50 +00001003 pixel.alpha+=(*k)*GetPixelAlpha(image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001004 k++;
cristy4c08aed2011-07-01 19:47:50 +00001005 kernel_pixels+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001006 }
cristy4c08aed2011-07-01 19:47:50 +00001007 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00001008 }
1009 }
1010 else
1011 {
1012 MagickRealType
1013 alpha,
1014 gamma;
1015
1016 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00001017 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001018 {
cristy4c08aed2011-07-01 19:47:50 +00001019 alpha=(MagickRealType) (QuantumScale*
1020 GetPixelAlpha(image,kernel_pixels));
1021 pixel.red+=(*k)*alpha*GetPixelRed(image,kernel_pixels);
1022 pixel.green+=(*k)*alpha*GetPixelGreen(image,kernel_pixels);
1023 pixel.blue+=(*k)*alpha*GetPixelBlue(image,kernel_pixels);
1024 if (image->colorspace == CMYKColorspace)
1025 pixel.black+=(*k)*alpha*GetPixelBlack(image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001026 gamma+=(*k)*alpha;
1027 k++;
cristy4c08aed2011-07-01 19:47:50 +00001028 kernel_pixels+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001029 }
1030 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy2b9582a2011-07-04 17:38:56 +00001031 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001032 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00001033 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001034 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00001035 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001036 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00001037 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001038 (blur_image->colorspace == CMYKColorspace))
1039 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00001040 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001041 {
1042 k=kernel;
1043 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001044 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001045 {
cristy4c08aed2011-07-01 19:47:50 +00001046 pixel.alpha+=(*k)*GetPixelAlpha(image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001047 k++;
cristy4c08aed2011-07-01 19:47:50 +00001048 kernel_pixels+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001049 }
cristy4c08aed2011-07-01 19:47:50 +00001050 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00001051 }
1052 }
cristy4c08aed2011-07-01 19:47:50 +00001053 p+=GetPixelChannels(image);
1054 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001055 }
1056 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1057 status=MagickFalse;
1058 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1059 {
1060 MagickBooleanType
1061 proceed;
1062
cristyb5d5f722009-11-04 03:03:49 +00001063#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001064 #pragma omp critical (MagickCore_BlurImageChannel)
1065#endif
1066 proceed=SetImageProgress(image,BlurImageTag,progress++,blur_image->rows+
1067 blur_image->columns);
1068 if (proceed == MagickFalse)
1069 status=MagickFalse;
1070 }
1071 }
1072 blur_view=DestroyCacheView(blur_view);
1073 image_view=DestroyCacheView(image_view);
1074 /*
1075 Blur columns.
1076 */
1077 image_view=AcquireCacheView(blur_image);
1078 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00001079#if defined(MAGICKCORE_OPENMP_SUPPORT)
1080 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001081#endif
cristybb503372010-05-27 20:51:26 +00001082 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001083 {
cristy4c08aed2011-07-01 19:47:50 +00001084 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001085 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001086
cristy4c08aed2011-07-01 19:47:50 +00001087 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001088 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001089
cristy117ff172010-08-15 21:35:32 +00001090 register ssize_t
1091 y;
1092
cristy3ed852e2009-09-05 21:47:34 +00001093 if (status == MagickFalse)
1094 continue;
cristy117ff172010-08-15 21:35:32 +00001095 p=GetCacheViewVirtualPixels(image_view,x,-((ssize_t) width/2L),1,
1096 image->rows+width,exception);
cristy3ed852e2009-09-05 21:47:34 +00001097 q=GetCacheViewAuthenticPixels(blur_view,x,0,1,blur_image->rows,exception);
cristy4c08aed2011-07-01 19:47:50 +00001098 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001099 {
1100 status=MagickFalse;
1101 continue;
1102 }
cristybb503372010-05-27 20:51:26 +00001103 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001104 {
cristy4c08aed2011-07-01 19:47:50 +00001105 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001106 pixel;
1107
1108 register const double
cristyc47d1f82009-11-26 01:44:43 +00001109 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00001110
cristy4c08aed2011-07-01 19:47:50 +00001111 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001112 *restrict kernel_pixels;
cristy3ed852e2009-09-05 21:47:34 +00001113
cristybb503372010-05-27 20:51:26 +00001114 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001115 i;
1116
cristyddd82202009-11-03 20:14:50 +00001117 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00001118 k=kernel;
1119 kernel_pixels=p;
cristy2b9582a2011-07-04 17:38:56 +00001120 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) == 0) || (blur_image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00001121 {
cristybb503372010-05-27 20:51:26 +00001122 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001123 {
cristy4c08aed2011-07-01 19:47:50 +00001124 pixel.red+=(*k)*GetPixelRed(blur_image,kernel_pixels);
1125 pixel.green+=(*k)*GetPixelGreen(blur_image,kernel_pixels);
1126 pixel.blue+=(*k)*GetPixelBlue(blur_image,kernel_pixels);
1127 if (blur_image->colorspace == CMYKColorspace)
1128 pixel.black+=(*k)*GetPixelBlack(blur_image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001129 k++;
cristy4c08aed2011-07-01 19:47:50 +00001130 kernel_pixels+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001131 }
cristy2b9582a2011-07-04 17:38:56 +00001132 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001133 SetPixelRed(blur_image,ClampToQuantum(pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00001134 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001135 SetPixelGreen(blur_image,ClampToQuantum(pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00001136 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001137 SetPixelBlue(blur_image,ClampToQuantum(pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00001138 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001139 (blur_image->colorspace == CMYKColorspace))
1140 SetPixelBlack(blur_image,ClampToQuantum(pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00001141 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001142 {
1143 k=kernel;
1144 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001145 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001146 {
cristy4c08aed2011-07-01 19:47:50 +00001147 pixel.alpha+=(*k)*GetPixelAlpha(blur_image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001148 k++;
cristy4c08aed2011-07-01 19:47:50 +00001149 kernel_pixels+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001150 }
cristy4c08aed2011-07-01 19:47:50 +00001151 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00001152 }
1153 }
1154 else
1155 {
1156 MagickRealType
1157 alpha,
1158 gamma;
1159
1160 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00001161 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001162 {
cristy46f08202010-01-10 04:04:21 +00001163 alpha=(MagickRealType) (QuantumScale*
cristy4c08aed2011-07-01 19:47:50 +00001164 GetPixelAlpha(blur_image,kernel_pixels));
1165 pixel.red+=(*k)*alpha*GetPixelRed(blur_image,kernel_pixels);
1166 pixel.green+=(*k)*alpha*GetPixelGreen(blur_image,kernel_pixels);
1167 pixel.blue+=(*k)*alpha*GetPixelBlue(blur_image,kernel_pixels);
1168 if (blur_image->colorspace == CMYKColorspace)
1169 pixel.black+=(*k)*alpha*GetPixelBlack(blur_image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001170 gamma+=(*k)*alpha;
1171 k++;
cristy4c08aed2011-07-01 19:47:50 +00001172 kernel_pixels+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001173 }
1174 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy2b9582a2011-07-04 17:38:56 +00001175 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001176 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00001177 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001178 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00001179 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001180 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00001181 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001182 (blur_image->colorspace == CMYKColorspace))
1183 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00001184 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001185 {
1186 k=kernel;
1187 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001188 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001189 {
cristy4c08aed2011-07-01 19:47:50 +00001190 pixel.alpha+=(*k)*GetPixelAlpha(blur_image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001191 k++;
cristy4c08aed2011-07-01 19:47:50 +00001192 kernel_pixels+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001193 }
cristy4c08aed2011-07-01 19:47:50 +00001194 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00001195 }
1196 }
cristy4c08aed2011-07-01 19:47:50 +00001197 p+=GetPixelChannels(blur_image);
1198 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001199 }
1200 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1201 status=MagickFalse;
cristy4c08aed2011-07-01 19:47:50 +00001202 if (blur_image->progress_monitor != (MagickProgressMonitor) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001203 {
1204 MagickBooleanType
1205 proceed;
1206
cristyb5d5f722009-11-04 03:03:49 +00001207#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001208 #pragma omp critical (MagickCore_BlurImageChannel)
1209#endif
cristy4c08aed2011-07-01 19:47:50 +00001210 proceed=SetImageProgress(blur_image,BlurImageTag,progress++,
1211 blur_image->rows+blur_image->columns);
cristy3ed852e2009-09-05 21:47:34 +00001212 if (proceed == MagickFalse)
1213 status=MagickFalse;
1214 }
1215 }
1216 blur_view=DestroyCacheView(blur_view);
1217 image_view=DestroyCacheView(image_view);
1218 kernel=(double *) RelinquishMagickMemory(kernel);
1219 if (status == MagickFalse)
1220 blur_image=DestroyImage(blur_image);
1221 blur_image->type=image->type;
1222 return(blur_image);
1223}
1224
1225/*
1226%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1227% %
1228% %
1229% %
cristyfccdab92009-11-30 16:43:57 +00001230% C o n v o l v e I m a g e %
1231% %
1232% %
1233% %
1234%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1235%
1236% ConvolveImage() applies a custom convolution kernel to the image.
1237%
1238% The format of the ConvolveImage method is:
1239%
cristybb503372010-05-27 20:51:26 +00001240% Image *ConvolveImage(const Image *image,const size_t order,
cristyfccdab92009-11-30 16:43:57 +00001241% const double *kernel,ExceptionInfo *exception)
1242% Image *ConvolveImageChannel(const Image *image,const ChannelType channel,
cristy117ff172010-08-15 21:35:32 +00001243% const size_t order,const double *kernel,ExceptionInfo *exception)
cristyfccdab92009-11-30 16:43:57 +00001244%
1245% A description of each parameter follows:
1246%
1247% o image: the image.
1248%
1249% o channel: the channel type.
1250%
1251% o order: the number of columns and rows in the filter kernel.
1252%
1253% o kernel: An array of double representing the convolution kernel.
1254%
1255% o exception: return any errors or warnings in this structure.
1256%
1257*/
1258
cristybb503372010-05-27 20:51:26 +00001259MagickExport Image *ConvolveImage(const Image *image,const size_t order,
cristyfccdab92009-11-30 16:43:57 +00001260 const double *kernel,ExceptionInfo *exception)
1261{
1262 Image
1263 *convolve_image;
1264
1265 convolve_image=ConvolveImageChannel(image,DefaultChannels,order,kernel,
1266 exception);
1267 return(convolve_image);
1268}
1269
1270MagickExport Image *ConvolveImageChannel(const Image *image,
cristybb503372010-05-27 20:51:26 +00001271 const ChannelType channel,const size_t order,const double *kernel,
cristyfccdab92009-11-30 16:43:57 +00001272 ExceptionInfo *exception)
1273{
1274#define ConvolveImageTag "Convolve/Image"
1275
cristyc4c8d132010-01-07 01:58:38 +00001276 CacheView
1277 *convolve_view,
1278 *image_view;
1279
cristyfccdab92009-11-30 16:43:57 +00001280 double
1281 *normal_kernel;
1282
1283 Image
1284 *convolve_image;
1285
cristyfccdab92009-11-30 16:43:57 +00001286 MagickBooleanType
1287 status;
1288
cristybb503372010-05-27 20:51:26 +00001289 MagickOffsetType
1290 progress;
1291
cristy4c08aed2011-07-01 19:47:50 +00001292 PixelInfo
cristyfccdab92009-11-30 16:43:57 +00001293 bias;
1294
1295 MagickRealType
1296 gamma;
1297
cristybb503372010-05-27 20:51:26 +00001298 register ssize_t
cristyfccdab92009-11-30 16:43:57 +00001299 i;
1300
cristybb503372010-05-27 20:51:26 +00001301 size_t
cristyfccdab92009-11-30 16:43:57 +00001302 width;
1303
cristybb503372010-05-27 20:51:26 +00001304 ssize_t
1305 y;
1306
cristyfccdab92009-11-30 16:43:57 +00001307 /*
1308 Initialize convolve image attributes.
1309 */
1310 assert(image != (Image *) NULL);
1311 assert(image->signature == MagickSignature);
1312 if (image->debug != MagickFalse)
1313 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1314 assert(exception != (ExceptionInfo *) NULL);
1315 assert(exception->signature == MagickSignature);
1316 width=order;
1317 if ((width % 2) == 0)
1318 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
1319 convolve_image=CloneImage(image,0,0,MagickTrue,exception);
1320 if (convolve_image == (Image *) NULL)
1321 return((Image *) NULL);
1322 if (SetImageStorageClass(convolve_image,DirectClass) == MagickFalse)
1323 {
1324 InheritException(exception,&convolve_image->exception);
1325 convolve_image=DestroyImage(convolve_image);
1326 return((Image *) NULL);
1327 }
1328 if (image->debug != MagickFalse)
1329 {
1330 char
1331 format[MaxTextExtent],
1332 *message;
1333
cristy117ff172010-08-15 21:35:32 +00001334 register const double
1335 *k;
1336
cristybb503372010-05-27 20:51:26 +00001337 ssize_t
cristyfccdab92009-11-30 16:43:57 +00001338 u,
1339 v;
1340
cristyfccdab92009-11-30 16:43:57 +00001341 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00001342 " ConvolveImage with %.20gx%.20g kernel:",(double) width,(double)
1343 width);
cristyfccdab92009-11-30 16:43:57 +00001344 message=AcquireString("");
1345 k=kernel;
cristybb503372010-05-27 20:51:26 +00001346 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001347 {
1348 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00001349 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristyfccdab92009-11-30 16:43:57 +00001350 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00001351 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001352 {
cristyb51dff52011-05-19 16:55:47 +00001353 (void) FormatLocaleString(format,MaxTextExtent,"%g ",*k++);
cristyfccdab92009-11-30 16:43:57 +00001354 (void) ConcatenateString(&message,format);
1355 }
1356 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
1357 }
1358 message=DestroyString(message);
1359 }
1360 /*
1361 Normalize kernel.
1362 */
1363 normal_kernel=(double *) AcquireQuantumMemory(width*width,
1364 sizeof(*normal_kernel));
1365 if (normal_kernel == (double *) NULL)
1366 {
1367 convolve_image=DestroyImage(convolve_image);
1368 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1369 }
1370 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00001371 for (i=0; i < (ssize_t) (width*width); i++)
cristyfccdab92009-11-30 16:43:57 +00001372 gamma+=kernel[i];
1373 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristybb503372010-05-27 20:51:26 +00001374 for (i=0; i < (ssize_t) (width*width); i++)
cristyfccdab92009-11-30 16:43:57 +00001375 normal_kernel[i]=gamma*kernel[i];
1376 /*
1377 Convolve image.
1378 */
1379 status=MagickTrue;
1380 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00001381 GetPixelInfo(image,&bias);
1382 SetPixelInfoBias(image,&bias);
cristyfccdab92009-11-30 16:43:57 +00001383 image_view=AcquireCacheView(image);
1384 convolve_view=AcquireCacheView(convolve_image);
1385#if defined(MAGICKCORE_OPENMP_SUPPORT)
1386 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1387#endif
cristybb503372010-05-27 20:51:26 +00001388 for (y=0; y < (ssize_t) image->rows; y++)
cristyfccdab92009-11-30 16:43:57 +00001389 {
1390 MagickBooleanType
1391 sync;
1392
cristy4c08aed2011-07-01 19:47:50 +00001393 register const Quantum
cristyfccdab92009-11-30 16:43:57 +00001394 *restrict p;
1395
cristy4c08aed2011-07-01 19:47:50 +00001396 register Quantum
cristyfccdab92009-11-30 16:43:57 +00001397 *restrict q;
1398
cristy117ff172010-08-15 21:35:32 +00001399 register ssize_t
1400 x;
1401
cristyfccdab92009-11-30 16:43:57 +00001402 if (status == MagickFalse)
1403 continue;
cristyce889302010-06-30 19:16:36 +00001404 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
1405 (width/2L),image->columns+width,width,exception);
cristyfccdab92009-11-30 16:43:57 +00001406 q=GetCacheViewAuthenticPixels(convolve_view,0,y,convolve_image->columns,1,
1407 exception);
cristy4c08aed2011-07-01 19:47:50 +00001408 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristyfccdab92009-11-30 16:43:57 +00001409 {
1410 status=MagickFalse;
1411 continue;
1412 }
cristybb503372010-05-27 20:51:26 +00001413 for (x=0; x < (ssize_t) image->columns; x++)
cristyfccdab92009-11-30 16:43:57 +00001414 {
cristy4c08aed2011-07-01 19:47:50 +00001415 PixelInfo
cristyfccdab92009-11-30 16:43:57 +00001416 pixel;
1417
1418 register const double
1419 *restrict k;
1420
cristy4c08aed2011-07-01 19:47:50 +00001421 register const Quantum
cristyfccdab92009-11-30 16:43:57 +00001422 *restrict kernel_pixels;
1423
cristybb503372010-05-27 20:51:26 +00001424 register ssize_t
cristyfccdab92009-11-30 16:43:57 +00001425 u;
1426
cristy117ff172010-08-15 21:35:32 +00001427 ssize_t
1428 v;
1429
cristyfccdab92009-11-30 16:43:57 +00001430 pixel=bias;
1431 k=normal_kernel;
1432 kernel_pixels=p;
cristy2b9582a2011-07-04 17:38:56 +00001433 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) == 0) || (image->matte == MagickFalse))
cristyfccdab92009-11-30 16:43:57 +00001434 {
cristybb503372010-05-27 20:51:26 +00001435 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001436 {
cristybb503372010-05-27 20:51:26 +00001437 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001438 {
cristy4c08aed2011-07-01 19:47:50 +00001439 pixel.red+=(*k)*GetPixelRed(image,kernel_pixels+u*
1440 GetPixelChannels(image));
1441 pixel.green+=(*k)*GetPixelGreen(image,kernel_pixels+u*
1442 GetPixelChannels(image));
1443 pixel.blue+=(*k)*GetPixelBlue(image,kernel_pixels+u*
1444 GetPixelChannels(image));
1445 if (image->colorspace == CMYKColorspace)
1446 pixel.black+=(*k)*GetPixelBlack(image,kernel_pixels+u*
1447 GetPixelChannels(image));
cristyfccdab92009-11-30 16:43:57 +00001448 k++;
1449 }
cristy4c08aed2011-07-01 19:47:50 +00001450 kernel_pixels+=(image->columns+width)*GetPixelChannels(image);
cristyfccdab92009-11-30 16:43:57 +00001451 }
cristy2b9582a2011-07-04 17:38:56 +00001452 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001453 SetPixelRed(convolve_image,ClampToQuantum(pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00001454 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001455 SetPixelGreen(convolve_image,ClampToQuantum(pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00001456 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001457 SetPixelBlue(convolve_image,ClampToQuantum(pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00001458 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001459 (image->colorspace == CMYKColorspace))
1460 SetPixelBlack(convolve_image,ClampToQuantum(pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00001461 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristyfccdab92009-11-30 16:43:57 +00001462 {
1463 k=normal_kernel;
1464 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001465 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001466 {
cristybb503372010-05-27 20:51:26 +00001467 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001468 {
cristy4c08aed2011-07-01 19:47:50 +00001469 pixel.alpha+=(*k)*GetPixelAlpha(image,kernel_pixels+u*
1470 GetPixelChannels(image));
cristyfccdab92009-11-30 16:43:57 +00001471 k++;
1472 }
cristy4c08aed2011-07-01 19:47:50 +00001473 kernel_pixels+=(image->columns+width)*
1474 GetPixelChannels(image);
cristyfccdab92009-11-30 16:43:57 +00001475 }
cristy4c08aed2011-07-01 19:47:50 +00001476 SetPixelAlpha(convolve_image,ClampToQuantum(pixel.alpha),q);
cristyfccdab92009-11-30 16:43:57 +00001477 }
1478 }
1479 else
1480 {
1481 MagickRealType
1482 alpha,
1483 gamma;
1484
1485 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00001486 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001487 {
cristybb503372010-05-27 20:51:26 +00001488 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001489 {
cristy4c08aed2011-07-01 19:47:50 +00001490 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,
1491 kernel_pixels+u*GetPixelChannels(image)));
1492 pixel.red+=(*k)*alpha*GetPixelRed(image,kernel_pixels+u*
1493 GetPixelChannels(image));
1494 pixel.green+=(*k)*alpha*GetPixelGreen(image,kernel_pixels+u*
1495 GetPixelChannels(image));
1496 pixel.blue+=(*k)*alpha*GetPixelBlue(image,kernel_pixels+u*
1497 GetPixelChannels(image));
1498 if (image->colorspace == CMYKColorspace)
1499 pixel.black+=(*k)*alpha*GetPixelBlack(image,kernel_pixels+u*
1500 GetPixelChannels(image));
cristyfccdab92009-11-30 16:43:57 +00001501 gamma+=(*k)*alpha;
1502 k++;
1503 }
cristy4c08aed2011-07-01 19:47:50 +00001504 kernel_pixels+=(image->columns+width)*GetPixelChannels(image);
cristyfccdab92009-11-30 16:43:57 +00001505 }
1506 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy2b9582a2011-07-04 17:38:56 +00001507 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001508 SetPixelRed(convolve_image,ClampToQuantum(gamma*pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00001509 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001510 SetPixelGreen(convolve_image,ClampToQuantum(gamma*pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00001511 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001512 SetPixelBlue(convolve_image,ClampToQuantum(gamma*pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00001513 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristyfccdab92009-11-30 16:43:57 +00001514 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00001515 SetPixelBlack(convolve_image,ClampToQuantum(gamma*pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00001516 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristyfccdab92009-11-30 16:43:57 +00001517 {
cristyfccdab92009-11-30 16:43:57 +00001518 k=normal_kernel;
1519 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001520 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001521 {
cristybb503372010-05-27 20:51:26 +00001522 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001523 {
cristy4c08aed2011-07-01 19:47:50 +00001524 pixel.alpha+=(*k)*GetPixelAlpha(image,kernel_pixels+u);
cristyfccdab92009-11-30 16:43:57 +00001525 k++;
1526 }
cristy4c08aed2011-07-01 19:47:50 +00001527 kernel_pixels+=(image->columns+width)*
1528 GetPixelChannels(image);
cristyfccdab92009-11-30 16:43:57 +00001529 }
cristy4c08aed2011-07-01 19:47:50 +00001530 SetPixelAlpha(convolve_image,ClampToQuantum(pixel.alpha),q);
cristyfccdab92009-11-30 16:43:57 +00001531 }
1532 }
cristy4c08aed2011-07-01 19:47:50 +00001533 p+=GetPixelChannels(image);
1534 q+=GetPixelChannels(convolve_image);
cristyfccdab92009-11-30 16:43:57 +00001535 }
1536 sync=SyncCacheViewAuthenticPixels(convolve_view,exception);
1537 if (sync == MagickFalse)
1538 status=MagickFalse;
1539 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1540 {
1541 MagickBooleanType
1542 proceed;
1543
1544#if defined(MAGICKCORE_OPENMP_SUPPORT)
1545 #pragma omp critical (MagickCore_ConvolveImageChannel)
1546#endif
1547 proceed=SetImageProgress(image,ConvolveImageTag,progress++,image->rows);
1548 if (proceed == MagickFalse)
1549 status=MagickFalse;
1550 }
1551 }
1552 convolve_image->type=image->type;
1553 convolve_view=DestroyCacheView(convolve_view);
1554 image_view=DestroyCacheView(image_view);
1555 normal_kernel=(double *) RelinquishMagickMemory(normal_kernel);
1556 if (status == MagickFalse)
1557 convolve_image=DestroyImage(convolve_image);
1558 return(convolve_image);
1559}
1560
1561/*
1562%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1563% %
1564% %
1565% %
cristy3ed852e2009-09-05 21:47:34 +00001566% D e s p e c k l e I m a g e %
1567% %
1568% %
1569% %
1570%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1571%
1572% DespeckleImage() reduces the speckle noise in an image while perserving the
1573% edges of the original image.
1574%
1575% The format of the DespeckleImage method is:
1576%
1577% Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1578%
1579% A description of each parameter follows:
1580%
1581% o image: the image.
1582%
1583% o exception: return any errors or warnings in this structure.
1584%
1585*/
1586
cristybb503372010-05-27 20:51:26 +00001587static void Hull(const ssize_t x_offset,const ssize_t y_offset,
1588 const size_t columns,const size_t rows,Quantum *f,Quantum *g,
cristy3ed852e2009-09-05 21:47:34 +00001589 const int polarity)
1590{
cristy3ed852e2009-09-05 21:47:34 +00001591 MagickRealType
1592 v;
1593
cristy3ed852e2009-09-05 21:47:34 +00001594 register Quantum
1595 *p,
1596 *q,
1597 *r,
1598 *s;
1599
cristy117ff172010-08-15 21:35:32 +00001600 register ssize_t
1601 x;
1602
1603 ssize_t
1604 y;
1605
cristy3ed852e2009-09-05 21:47:34 +00001606 assert(f != (Quantum *) NULL);
1607 assert(g != (Quantum *) NULL);
1608 p=f+(columns+2);
1609 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001610 r=p+(y_offset*((ssize_t) columns+2)+x_offset);
1611 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001612 {
1613 p++;
1614 q++;
1615 r++;
1616 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001617 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001618 {
1619 v=(MagickRealType) (*p);
1620 if ((MagickRealType) *r >= (v+(MagickRealType) ScaleCharToQuantum(2)))
1621 v+=ScaleCharToQuantum(1);
1622 *q=(Quantum) v;
1623 p++;
1624 q++;
1625 r++;
1626 }
1627 else
cristybb503372010-05-27 20:51:26 +00001628 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001629 {
1630 v=(MagickRealType) (*p);
1631 if ((MagickRealType) *r <= (v-(MagickRealType) ScaleCharToQuantum(2)))
cristybb503372010-05-27 20:51:26 +00001632 v-=(ssize_t) ScaleCharToQuantum(1);
cristy3ed852e2009-09-05 21:47:34 +00001633 *q=(Quantum) v;
1634 p++;
1635 q++;
1636 r++;
1637 }
1638 p++;
1639 q++;
1640 r++;
1641 }
1642 p=f+(columns+2);
1643 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001644 r=q+(y_offset*((ssize_t) columns+2)+x_offset);
1645 s=q-(y_offset*((ssize_t) columns+2)+x_offset);
1646 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001647 {
1648 p++;
1649 q++;
1650 r++;
1651 s++;
1652 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001653 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001654 {
1655 v=(MagickRealType) (*q);
1656 if (((MagickRealType) *s >=
1657 (v+(MagickRealType) ScaleCharToQuantum(2))) &&
1658 ((MagickRealType) *r > v))
1659 v+=ScaleCharToQuantum(1);
1660 *p=(Quantum) v;
1661 p++;
1662 q++;
1663 r++;
1664 s++;
1665 }
1666 else
cristybb503372010-05-27 20:51:26 +00001667 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001668 {
1669 v=(MagickRealType) (*q);
1670 if (((MagickRealType) *s <=
1671 (v-(MagickRealType) ScaleCharToQuantum(2))) &&
1672 ((MagickRealType) *r < v))
1673 v-=(MagickRealType) ScaleCharToQuantum(1);
1674 *p=(Quantum) v;
1675 p++;
1676 q++;
1677 r++;
1678 s++;
1679 }
1680 p++;
1681 q++;
1682 r++;
1683 s++;
1684 }
1685}
1686
1687MagickExport Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1688{
1689#define DespeckleImageTag "Despeckle/Image"
1690
cristy2407fc22009-09-11 00:55:25 +00001691 CacheView
1692 *despeckle_view,
1693 *image_view;
1694
cristy3ed852e2009-09-05 21:47:34 +00001695 Image
1696 *despeckle_image;
1697
cristy3ed852e2009-09-05 21:47:34 +00001698 MagickBooleanType
1699 status;
1700
cristya58c3172011-02-19 19:23:11 +00001701 register ssize_t
1702 i;
1703
cristy3ed852e2009-09-05 21:47:34 +00001704 Quantum
cristy65b9f392011-02-22 14:22:54 +00001705 *restrict buffers,
1706 *restrict pixels;
cristy3ed852e2009-09-05 21:47:34 +00001707
1708 size_t
cristya58c3172011-02-19 19:23:11 +00001709 length,
1710 number_channels;
cristy117ff172010-08-15 21:35:32 +00001711
cristybb503372010-05-27 20:51:26 +00001712 static const ssize_t
cristy691a29e2009-09-11 00:44:10 +00001713 X[4] = {0, 1, 1,-1},
1714 Y[4] = {1, 0, 1, 1};
cristy3ed852e2009-09-05 21:47:34 +00001715
cristy3ed852e2009-09-05 21:47:34 +00001716 /*
1717 Allocate despeckled image.
1718 */
1719 assert(image != (const Image *) NULL);
1720 assert(image->signature == MagickSignature);
1721 if (image->debug != MagickFalse)
1722 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1723 assert(exception != (ExceptionInfo *) NULL);
1724 assert(exception->signature == MagickSignature);
1725 despeckle_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1726 exception);
1727 if (despeckle_image == (Image *) NULL)
1728 return((Image *) NULL);
1729 if (SetImageStorageClass(despeckle_image,DirectClass) == MagickFalse)
1730 {
1731 InheritException(exception,&despeckle_image->exception);
1732 despeckle_image=DestroyImage(despeckle_image);
1733 return((Image *) NULL);
1734 }
1735 /*
1736 Allocate image buffers.
1737 */
1738 length=(size_t) ((image->columns+2)*(image->rows+2));
cristy65b9f392011-02-22 14:22:54 +00001739 pixels=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1740 buffers=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1741 if ((pixels == (Quantum *) NULL) || (buffers == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001742 {
cristy65b9f392011-02-22 14:22:54 +00001743 if (buffers != (Quantum *) NULL)
1744 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1745 if (pixels != (Quantum *) NULL)
1746 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001747 despeckle_image=DestroyImage(despeckle_image);
1748 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1749 }
1750 /*
1751 Reduce speckle in the image.
1752 */
1753 status=MagickTrue;
cristy109695a2011-02-19 19:38:14 +00001754 number_channels=(size_t) (image->colorspace == CMYKColorspace ? 5 : 4);
cristy3ed852e2009-09-05 21:47:34 +00001755 image_view=AcquireCacheView(image);
1756 despeckle_view=AcquireCacheView(despeckle_image);
cristy8df3d002011-02-19 19:40:59 +00001757 for (i=0; i < (ssize_t) number_channels; i++)
cristy3ed852e2009-09-05 21:47:34 +00001758 {
cristy3ed852e2009-09-05 21:47:34 +00001759 register Quantum
1760 *buffer,
1761 *pixel;
1762
cristyc1488b52011-02-19 18:54:15 +00001763 register ssize_t
cristya58c3172011-02-19 19:23:11 +00001764 k,
cristyc1488b52011-02-19 18:54:15 +00001765 x;
1766
cristy117ff172010-08-15 21:35:32 +00001767 ssize_t
1768 j,
1769 y;
1770
cristy3ed852e2009-09-05 21:47:34 +00001771 if (status == MagickFalse)
1772 continue;
cristy65b9f392011-02-22 14:22:54 +00001773 pixel=pixels;
cristy3ed852e2009-09-05 21:47:34 +00001774 (void) ResetMagickMemory(pixel,0,length*sizeof(*pixel));
cristy65b9f392011-02-22 14:22:54 +00001775 buffer=buffers;
cristybb503372010-05-27 20:51:26 +00001776 j=(ssize_t) image->columns+2;
1777 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001778 {
cristy4c08aed2011-07-01 19:47:50 +00001779 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001780 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001781
1782 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001783 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001784 break;
1785 j++;
cristybb503372010-05-27 20:51:26 +00001786 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001787 {
cristya58c3172011-02-19 19:23:11 +00001788 switch (i)
cristy3ed852e2009-09-05 21:47:34 +00001789 {
cristy4c08aed2011-07-01 19:47:50 +00001790 case 0: pixel[j]=GetPixelRed(image,p); break;
1791 case 1: pixel[j]=GetPixelGreen(image,p); break;
1792 case 2: pixel[j]=GetPixelBlue(image,p); break;
1793 case 3: pixel[j]=GetPixelAlpha(image,p); break;
1794 case 4: pixel[j]=GetPixelBlack(image,p); break;
cristy3ed852e2009-09-05 21:47:34 +00001795 default: break;
1796 }
cristy4c08aed2011-07-01 19:47:50 +00001797 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001798 j++;
1799 }
1800 j++;
1801 }
cristy3ed852e2009-09-05 21:47:34 +00001802 (void) ResetMagickMemory(buffer,0,length*sizeof(*buffer));
cristya58c3172011-02-19 19:23:11 +00001803 for (k=0; k < 4; k++)
cristy3ed852e2009-09-05 21:47:34 +00001804 {
cristya58c3172011-02-19 19:23:11 +00001805 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,1);
1806 Hull(-X[k],-Y[k],image->columns,image->rows,pixel,buffer,1);
1807 Hull(-X[k],-Y[k],image->columns,image->rows,pixel,buffer,-1);
1808 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,-1);
cristy3ed852e2009-09-05 21:47:34 +00001809 }
cristybb503372010-05-27 20:51:26 +00001810 j=(ssize_t) image->columns+2;
1811 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001812 {
1813 MagickBooleanType
1814 sync;
1815
cristy4c08aed2011-07-01 19:47:50 +00001816 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001817 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001818
1819 q=GetCacheViewAuthenticPixels(despeckle_view,0,y,despeckle_image->columns,
1820 1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001821 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001822 break;
1823 j++;
cristybb503372010-05-27 20:51:26 +00001824 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001825 {
cristya58c3172011-02-19 19:23:11 +00001826 switch (i)
cristy3ed852e2009-09-05 21:47:34 +00001827 {
cristy4c08aed2011-07-01 19:47:50 +00001828 case 0: SetPixelRed(despeckle_image,pixel[j],q); break;
1829 case 1: SetPixelGreen(despeckle_image,pixel[j],q); break;
1830 case 2: SetPixelBlue(despeckle_image,pixel[j],q); break;
1831 case 3: SetPixelAlpha(despeckle_image,pixel[j],q); break;
1832 case 4: SetPixelBlack(despeckle_image,pixel[j],q); break;
cristy3ed852e2009-09-05 21:47:34 +00001833 default: break;
1834 }
cristy4c08aed2011-07-01 19:47:50 +00001835 q+=GetPixelChannels(despeckle_image);
cristy3ed852e2009-09-05 21:47:34 +00001836 j++;
1837 }
1838 sync=SyncCacheViewAuthenticPixels(despeckle_view,exception);
1839 if (sync == MagickFalse)
1840 {
1841 status=MagickFalse;
1842 break;
1843 }
1844 j++;
1845 }
1846 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1847 {
1848 MagickBooleanType
1849 proceed;
1850
cristya58c3172011-02-19 19:23:11 +00001851 proceed=SetImageProgress(image,DespeckleImageTag,(MagickOffsetType) i,
1852 number_channels);
cristy3ed852e2009-09-05 21:47:34 +00001853 if (proceed == MagickFalse)
1854 status=MagickFalse;
1855 }
1856 }
1857 despeckle_view=DestroyCacheView(despeckle_view);
1858 image_view=DestroyCacheView(image_view);
cristy65b9f392011-02-22 14:22:54 +00001859 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1860 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001861 despeckle_image->type=image->type;
1862 if (status == MagickFalse)
1863 despeckle_image=DestroyImage(despeckle_image);
1864 return(despeckle_image);
1865}
1866
1867/*
1868%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1869% %
1870% %
1871% %
1872% E d g e I m a g e %
1873% %
1874% %
1875% %
1876%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1877%
1878% EdgeImage() finds edges in an image. Radius defines the radius of the
1879% convolution filter. Use a radius of 0 and EdgeImage() selects a suitable
1880% radius for you.
1881%
1882% The format of the EdgeImage method is:
1883%
1884% Image *EdgeImage(const Image *image,const double radius,
1885% ExceptionInfo *exception)
1886%
1887% A description of each parameter follows:
1888%
1889% o image: the image.
1890%
1891% o radius: the radius of the pixel neighborhood.
1892%
1893% o exception: return any errors or warnings in this structure.
1894%
1895*/
1896MagickExport Image *EdgeImage(const Image *image,const double radius,
1897 ExceptionInfo *exception)
1898{
1899 Image
1900 *edge_image;
1901
1902 double
1903 *kernel;
1904
cristybb503372010-05-27 20:51:26 +00001905 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001906 i;
1907
cristybb503372010-05-27 20:51:26 +00001908 size_t
cristy3ed852e2009-09-05 21:47:34 +00001909 width;
1910
1911 assert(image != (const Image *) NULL);
1912 assert(image->signature == MagickSignature);
1913 if (image->debug != MagickFalse)
1914 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1915 assert(exception != (ExceptionInfo *) NULL);
1916 assert(exception->signature == MagickSignature);
1917 width=GetOptimalKernelWidth1D(radius,0.5);
1918 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
1919 if (kernel == (double *) NULL)
1920 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00001921 for (i=0; i < (ssize_t) (width*width); i++)
cristy3ed852e2009-09-05 21:47:34 +00001922 kernel[i]=(-1.0);
1923 kernel[i/2]=(double) (width*width-1.0);
1924 edge_image=ConvolveImage(image,width,kernel,exception);
1925 kernel=(double *) RelinquishMagickMemory(kernel);
1926 return(edge_image);
1927}
1928
1929/*
1930%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1931% %
1932% %
1933% %
1934% E m b o s s I m a g e %
1935% %
1936% %
1937% %
1938%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1939%
1940% EmbossImage() returns a grayscale image with a three-dimensional effect.
1941% We convolve the image with a Gaussian operator of the given radius and
1942% standard deviation (sigma). For reasonable results, radius should be
1943% larger than sigma. Use a radius of 0 and Emboss() selects a suitable
1944% radius for you.
1945%
1946% The format of the EmbossImage method is:
1947%
1948% Image *EmbossImage(const Image *image,const double radius,
1949% const double sigma,ExceptionInfo *exception)
1950%
1951% A description of each parameter follows:
1952%
1953% o image: the image.
1954%
1955% o radius: the radius of the pixel neighborhood.
1956%
1957% o sigma: the standard deviation of the Gaussian, in pixels.
1958%
1959% o exception: return any errors or warnings in this structure.
1960%
1961*/
1962MagickExport Image *EmbossImage(const Image *image,const double radius,
1963 const double sigma,ExceptionInfo *exception)
1964{
1965 double
1966 *kernel;
1967
1968 Image
1969 *emboss_image;
1970
cristybb503372010-05-27 20:51:26 +00001971 register ssize_t
cristy47e00502009-12-17 19:19:57 +00001972 i;
1973
cristybb503372010-05-27 20:51:26 +00001974 size_t
cristy3ed852e2009-09-05 21:47:34 +00001975 width;
1976
cristy117ff172010-08-15 21:35:32 +00001977 ssize_t
1978 j,
1979 k,
1980 u,
1981 v;
1982
cristy3ed852e2009-09-05 21:47:34 +00001983 assert(image != (Image *) NULL);
1984 assert(image->signature == MagickSignature);
1985 if (image->debug != MagickFalse)
1986 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1987 assert(exception != (ExceptionInfo *) NULL);
1988 assert(exception->signature == MagickSignature);
1989 width=GetOptimalKernelWidth2D(radius,sigma);
1990 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
1991 if (kernel == (double *) NULL)
1992 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00001993 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +00001994 k=j;
1995 i=0;
1996 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00001997 {
cristy47e00502009-12-17 19:19:57 +00001998 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00001999 {
cristy4205a3c2010-09-12 20:19:59 +00002000 kernel[i]=(double) (((u < 0) || (v < 0) ? -8.0 : 8.0)*
cristy47e00502009-12-17 19:19:57 +00002001 exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
cristy4205a3c2010-09-12 20:19:59 +00002002 (2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +00002003 if (u != k)
cristy3ed852e2009-09-05 21:47:34 +00002004 kernel[i]=0.0;
2005 i++;
2006 }
cristy47e00502009-12-17 19:19:57 +00002007 k--;
cristy3ed852e2009-09-05 21:47:34 +00002008 }
2009 emboss_image=ConvolveImage(image,width,kernel,exception);
2010 if (emboss_image != (Image *) NULL)
2011 (void) EqualizeImage(emboss_image);
2012 kernel=(double *) RelinquishMagickMemory(kernel);
2013 return(emboss_image);
2014}
2015
2016/*
2017%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2018% %
2019% %
2020% %
cristy56a9e512010-01-06 18:18:55 +00002021% F i l t e r I m a g e %
2022% %
2023% %
2024% %
2025%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2026%
2027% FilterImage() applies a custom convolution kernel to the image.
2028%
2029% The format of the FilterImage method is:
2030%
cristy2be15382010-01-21 02:38:03 +00002031% Image *FilterImage(const Image *image,const KernelInfo *kernel,
cristy56a9e512010-01-06 18:18:55 +00002032% ExceptionInfo *exception)
2033% Image *FilterImageChannel(const Image *image,const ChannelType channel,
cristy2be15382010-01-21 02:38:03 +00002034% const KernelInfo *kernel,ExceptionInfo *exception)
cristy56a9e512010-01-06 18:18:55 +00002035%
2036% A description of each parameter follows:
2037%
2038% o image: the image.
2039%
2040% o channel: the channel type.
2041%
2042% o kernel: the filtering kernel.
2043%
2044% o exception: return any errors or warnings in this structure.
2045%
2046*/
2047
cristy2be15382010-01-21 02:38:03 +00002048MagickExport Image *FilterImage(const Image *image,const KernelInfo *kernel,
cristy56a9e512010-01-06 18:18:55 +00002049 ExceptionInfo *exception)
2050{
2051 Image
2052 *filter_image;
2053
2054 filter_image=FilterImageChannel(image,DefaultChannels,kernel,exception);
2055 return(filter_image);
2056}
2057
2058MagickExport Image *FilterImageChannel(const Image *image,
cristy2be15382010-01-21 02:38:03 +00002059 const ChannelType channel,const KernelInfo *kernel,ExceptionInfo *exception)
cristy56a9e512010-01-06 18:18:55 +00002060{
2061#define FilterImageTag "Filter/Image"
2062
2063 CacheView
2064 *filter_view,
2065 *image_view;
2066
cristy56a9e512010-01-06 18:18:55 +00002067 Image
2068 *filter_image;
2069
cristy56a9e512010-01-06 18:18:55 +00002070 MagickBooleanType
2071 status;
2072
cristybb503372010-05-27 20:51:26 +00002073 MagickOffsetType
2074 progress;
2075
cristy4c08aed2011-07-01 19:47:50 +00002076 PixelInfo
cristy56a9e512010-01-06 18:18:55 +00002077 bias;
2078
cristybb503372010-05-27 20:51:26 +00002079 ssize_t
2080 y;
2081
cristy56a9e512010-01-06 18:18:55 +00002082 /*
2083 Initialize filter image attributes.
2084 */
2085 assert(image != (Image *) NULL);
2086 assert(image->signature == MagickSignature);
2087 if (image->debug != MagickFalse)
2088 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2089 assert(exception != (ExceptionInfo *) NULL);
2090 assert(exception->signature == MagickSignature);
2091 if ((kernel->width % 2) == 0)
2092 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
2093 filter_image=CloneImage(image,0,0,MagickTrue,exception);
2094 if (filter_image == (Image *) NULL)
2095 return((Image *) NULL);
2096 if (SetImageStorageClass(filter_image,DirectClass) == MagickFalse)
2097 {
2098 InheritException(exception,&filter_image->exception);
2099 filter_image=DestroyImage(filter_image);
2100 return((Image *) NULL);
2101 }
2102 if (image->debug != MagickFalse)
2103 {
2104 char
2105 format[MaxTextExtent],
2106 *message;
2107
cristy117ff172010-08-15 21:35:32 +00002108 register const double
2109 *k;
2110
cristybb503372010-05-27 20:51:26 +00002111 ssize_t
cristy56a9e512010-01-06 18:18:55 +00002112 u,
2113 v;
2114
cristy56a9e512010-01-06 18:18:55 +00002115 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00002116 " FilterImage with %.20gx%.20g kernel:",(double) kernel->width,(double)
2117 kernel->height);
cristy56a9e512010-01-06 18:18:55 +00002118 message=AcquireString("");
2119 k=kernel->values;
cristybb503372010-05-27 20:51:26 +00002120 for (v=0; v < (ssize_t) kernel->height; v++)
cristy56a9e512010-01-06 18:18:55 +00002121 {
2122 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00002123 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy56a9e512010-01-06 18:18:55 +00002124 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00002125 for (u=0; u < (ssize_t) kernel->width; u++)
cristy56a9e512010-01-06 18:18:55 +00002126 {
cristyb51dff52011-05-19 16:55:47 +00002127 (void) FormatLocaleString(format,MaxTextExtent,"%g ",*k++);
cristy56a9e512010-01-06 18:18:55 +00002128 (void) ConcatenateString(&message,format);
2129 }
2130 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
2131 }
2132 message=DestroyString(message);
2133 }
cristy36826ab2010-03-06 01:29:30 +00002134 status=AccelerateConvolveImage(image,kernel,filter_image,exception);
cristyd43a46b2010-01-21 02:13:41 +00002135 if (status == MagickTrue)
2136 return(filter_image);
cristy56a9e512010-01-06 18:18:55 +00002137 /*
2138 Filter image.
2139 */
2140 status=MagickTrue;
2141 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00002142 GetPixelInfo(image,&bias);
2143 SetPixelInfoBias(image,&bias);
cristy56a9e512010-01-06 18:18:55 +00002144 image_view=AcquireCacheView(image);
2145 filter_view=AcquireCacheView(filter_image);
2146#if defined(MAGICKCORE_OPENMP_SUPPORT)
2147 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2148#endif
cristybb503372010-05-27 20:51:26 +00002149 for (y=0; y < (ssize_t) image->rows; y++)
cristy56a9e512010-01-06 18:18:55 +00002150 {
2151 MagickBooleanType
2152 sync;
2153
cristy4c08aed2011-07-01 19:47:50 +00002154 register const Quantum
cristy56a9e512010-01-06 18:18:55 +00002155 *restrict p;
2156
cristy4c08aed2011-07-01 19:47:50 +00002157 register Quantum
cristy56a9e512010-01-06 18:18:55 +00002158 *restrict q;
2159
cristy117ff172010-08-15 21:35:32 +00002160 register ssize_t
2161 x;
2162
cristy56a9e512010-01-06 18:18:55 +00002163 if (status == MagickFalse)
2164 continue;
cristybb503372010-05-27 20:51:26 +00002165 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) kernel->width/2L),
cristy117ff172010-08-15 21:35:32 +00002166 y-(ssize_t) (kernel->height/2L),image->columns+kernel->width,
2167 kernel->height,exception);
cristy56a9e512010-01-06 18:18:55 +00002168 q=GetCacheViewAuthenticPixels(filter_view,0,y,filter_image->columns,1,
2169 exception);
cristy4c08aed2011-07-01 19:47:50 +00002170 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy56a9e512010-01-06 18:18:55 +00002171 {
2172 status=MagickFalse;
2173 continue;
2174 }
cristybb503372010-05-27 20:51:26 +00002175 for (x=0; x < (ssize_t) image->columns; x++)
cristy56a9e512010-01-06 18:18:55 +00002176 {
cristy4c08aed2011-07-01 19:47:50 +00002177 PixelInfo
cristy56a9e512010-01-06 18:18:55 +00002178 pixel;
2179
2180 register const double
2181 *restrict k;
2182
cristy4c08aed2011-07-01 19:47:50 +00002183 register const Quantum
cristy56a9e512010-01-06 18:18:55 +00002184 *restrict kernel_pixels;
2185
cristybb503372010-05-27 20:51:26 +00002186 register ssize_t
cristy56a9e512010-01-06 18:18:55 +00002187 u;
2188
cristy117ff172010-08-15 21:35:32 +00002189 ssize_t
2190 v;
2191
cristy56a9e512010-01-06 18:18:55 +00002192 pixel=bias;
cristy36826ab2010-03-06 01:29:30 +00002193 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002194 kernel_pixels=p;
cristy5ce8df82011-07-07 14:52:23 +00002195 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) == 0) ||
2196 (image->matte == MagickFalse))
cristy56a9e512010-01-06 18:18:55 +00002197 {
cristybb503372010-05-27 20:51:26 +00002198 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002199 {
cristybb503372010-05-27 20:51:26 +00002200 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002201 {
cristy4c08aed2011-07-01 19:47:50 +00002202 pixel.red+=(*k)*GetPixelRed(image,kernel_pixels+u*
2203 GetPixelChannels(image));
2204 pixel.green+=(*k)*GetPixelGreen(image,kernel_pixels+u*
2205 GetPixelChannels(image));
2206 pixel.blue+=(*k)*GetPixelBlue(image,kernel_pixels+u*
2207 GetPixelChannels(image));
2208 if (image->colorspace == CMYKColorspace)
2209 pixel.black+=(*k)*GetPixelBlack(image,kernel_pixels+u*
2210 GetPixelChannels(image));
cristy56a9e512010-01-06 18:18:55 +00002211 k++;
2212 }
cristy4c08aed2011-07-01 19:47:50 +00002213 kernel_pixels+=(image->columns+kernel->width)*
2214 GetPixelChannels(image);
cristy56a9e512010-01-06 18:18:55 +00002215 }
cristy2b9582a2011-07-04 17:38:56 +00002216 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002217 SetPixelRed(filter_image,ClampToQuantum(pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00002218 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002219 SetPixelGreen(filter_image,ClampToQuantum(pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00002220 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002221 SetPixelBlue(filter_image,ClampToQuantum(pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00002222 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002223 (image->colorspace == CMYKColorspace))
2224 SetPixelBlack(filter_image,ClampToQuantum(pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00002225 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy56a9e512010-01-06 18:18:55 +00002226 {
cristy36826ab2010-03-06 01:29:30 +00002227 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002228 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00002229 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002230 {
cristybb503372010-05-27 20:51:26 +00002231 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002232 {
cristy4c08aed2011-07-01 19:47:50 +00002233 pixel.alpha+=(*k)*GetPixelRed(image,kernel_pixels+u*
2234 GetPixelChannels(image));
cristy56a9e512010-01-06 18:18:55 +00002235 k++;
2236 }
cristy4c08aed2011-07-01 19:47:50 +00002237 kernel_pixels+=(image->columns+kernel->width)*
2238 GetPixelChannels(image);
cristy56a9e512010-01-06 18:18:55 +00002239 }
cristy4c08aed2011-07-01 19:47:50 +00002240 SetPixelAlpha(filter_image,ClampToQuantum(pixel.alpha),q);
cristy56a9e512010-01-06 18:18:55 +00002241 }
2242 }
2243 else
2244 {
2245 MagickRealType
2246 alpha,
2247 gamma;
2248
2249 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00002250 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002251 {
cristybb503372010-05-27 20:51:26 +00002252 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002253 {
cristy4c08aed2011-07-01 19:47:50 +00002254 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,
2255 kernel_pixels+u*GetPixelChannels(image)));
2256 pixel.red+=(*k)*alpha*GetPixelRed(image,kernel_pixels+u*
2257 GetPixelChannels(image));
2258 pixel.green+=(*k)*alpha*GetPixelGreen(image,kernel_pixels+u*
2259 GetPixelChannels(image));
2260 pixel.blue+=(*k)*alpha*GetPixelBlue(image,kernel_pixels+u*
2261 GetPixelChannels(image));
2262 if (image->colorspace == CMYKColorspace)
2263 pixel.black+=(*k)*alpha*GetPixelBlack(image,kernel_pixels+u*
2264 GetPixelChannels(image));
cristy56a9e512010-01-06 18:18:55 +00002265 gamma+=(*k)*alpha;
2266 k++;
2267 }
cristy5ce8df82011-07-07 14:52:23 +00002268 kernel_pixels+=(image->columns+kernel->width)*
cristy4c08aed2011-07-01 19:47:50 +00002269 GetPixelChannels(image);
cristy56a9e512010-01-06 18:18:55 +00002270 }
2271 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy2b9582a2011-07-04 17:38:56 +00002272 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002273 SetPixelRed(filter_image,ClampToQuantum(gamma*pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00002274 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002275 SetPixelGreen(filter_image,ClampToQuantum(gamma*pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00002276 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002277 SetPixelBlue(filter_image,ClampToQuantum(gamma*pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00002278 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy56a9e512010-01-06 18:18:55 +00002279 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002280 SetPixelBlack(filter_image,ClampToQuantum(gamma*pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00002281 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy56a9e512010-01-06 18:18:55 +00002282 {
cristy36826ab2010-03-06 01:29:30 +00002283 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002284 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00002285 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002286 {
cristybb503372010-05-27 20:51:26 +00002287 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002288 {
cristy4c08aed2011-07-01 19:47:50 +00002289 pixel.alpha+=(*k)*GetPixelAlpha(image,kernel_pixels+u*
2290 GetPixelChannels(image));
cristy56a9e512010-01-06 18:18:55 +00002291 k++;
2292 }
cristy4c08aed2011-07-01 19:47:50 +00002293 kernel_pixels+=(image->columns+kernel->width)*
2294 GetPixelChannels(image);
cristy56a9e512010-01-06 18:18:55 +00002295 }
cristy4c08aed2011-07-01 19:47:50 +00002296 SetPixelAlpha(filter_image,ClampToQuantum(pixel.alpha),q);
cristy56a9e512010-01-06 18:18:55 +00002297 }
2298 }
cristy4c08aed2011-07-01 19:47:50 +00002299 p+=GetPixelChannels(image);
2300 q+=GetPixelChannels(filter_image);
cristy56a9e512010-01-06 18:18:55 +00002301 }
2302 sync=SyncCacheViewAuthenticPixels(filter_view,exception);
2303 if (sync == MagickFalse)
2304 status=MagickFalse;
2305 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2306 {
2307 MagickBooleanType
2308 proceed;
2309
2310#if defined(MAGICKCORE_OPENMP_SUPPORT)
2311 #pragma omp critical (MagickCore_FilterImageChannel)
2312#endif
2313 proceed=SetImageProgress(image,FilterImageTag,progress++,image->rows);
2314 if (proceed == MagickFalse)
2315 status=MagickFalse;
2316 }
2317 }
2318 filter_image->type=image->type;
2319 filter_view=DestroyCacheView(filter_view);
2320 image_view=DestroyCacheView(image_view);
cristy56a9e512010-01-06 18:18:55 +00002321 if (status == MagickFalse)
2322 filter_image=DestroyImage(filter_image);
2323 return(filter_image);
2324}
2325
2326/*
2327%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2328% %
2329% %
2330% %
cristy3ed852e2009-09-05 21:47:34 +00002331% G a u s s i a n B l u r I m a g e %
2332% %
2333% %
2334% %
2335%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2336%
2337% GaussianBlurImage() blurs an image. We convolve the image with a
2338% Gaussian operator of the given radius and standard deviation (sigma).
2339% For reasonable results, the radius should be larger than sigma. Use a
2340% radius of 0 and GaussianBlurImage() selects a suitable radius for you
2341%
2342% The format of the GaussianBlurImage method is:
2343%
2344% Image *GaussianBlurImage(const Image *image,onst double radius,
2345% const double sigma,ExceptionInfo *exception)
2346% Image *GaussianBlurImageChannel(const Image *image,
2347% const ChannelType channel,const double radius,const double sigma,
2348% ExceptionInfo *exception)
2349%
2350% A description of each parameter follows:
2351%
2352% o image: the image.
2353%
2354% o channel: the channel type.
2355%
2356% o radius: the radius of the Gaussian, in pixels, not counting the center
2357% pixel.
2358%
2359% o sigma: the standard deviation of the Gaussian, in pixels.
2360%
2361% o exception: return any errors or warnings in this structure.
2362%
2363*/
2364
2365MagickExport Image *GaussianBlurImage(const Image *image,const double radius,
2366 const double sigma,ExceptionInfo *exception)
2367{
2368 Image
2369 *blur_image;
2370
2371 blur_image=GaussianBlurImageChannel(image,DefaultChannels,radius,sigma,
2372 exception);
2373 return(blur_image);
2374}
2375
2376MagickExport Image *GaussianBlurImageChannel(const Image *image,
2377 const ChannelType channel,const double radius,const double sigma,
2378 ExceptionInfo *exception)
2379{
2380 double
2381 *kernel;
2382
2383 Image
2384 *blur_image;
2385
cristybb503372010-05-27 20:51:26 +00002386 register ssize_t
cristy47e00502009-12-17 19:19:57 +00002387 i;
2388
cristybb503372010-05-27 20:51:26 +00002389 size_t
cristy3ed852e2009-09-05 21:47:34 +00002390 width;
2391
cristy117ff172010-08-15 21:35:32 +00002392 ssize_t
2393 j,
2394 u,
2395 v;
2396
cristy3ed852e2009-09-05 21:47:34 +00002397 assert(image != (const Image *) NULL);
2398 assert(image->signature == MagickSignature);
2399 if (image->debug != MagickFalse)
2400 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2401 assert(exception != (ExceptionInfo *) NULL);
2402 assert(exception->signature == MagickSignature);
2403 width=GetOptimalKernelWidth2D(radius,sigma);
2404 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2405 if (kernel == (double *) NULL)
2406 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00002407 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00002408 i=0;
cristy47e00502009-12-17 19:19:57 +00002409 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002410 {
cristy47e00502009-12-17 19:19:57 +00002411 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00002412 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
2413 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00002414 }
2415 blur_image=ConvolveImageChannel(image,channel,width,kernel,exception);
2416 kernel=(double *) RelinquishMagickMemory(kernel);
2417 return(blur_image);
2418}
2419
2420/*
2421%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2422% %
2423% %
2424% %
cristy3ed852e2009-09-05 21:47:34 +00002425% M o t i o n B l u r I m a g e %
2426% %
2427% %
2428% %
2429%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2430%
2431% MotionBlurImage() simulates motion blur. We convolve the image with a
2432% Gaussian operator of the given radius and standard deviation (sigma).
2433% For reasonable results, radius should be larger than sigma. Use a
2434% radius of 0 and MotionBlurImage() selects a suitable radius for you.
2435% Angle gives the angle of the blurring motion.
2436%
2437% Andrew Protano contributed this effect.
2438%
2439% The format of the MotionBlurImage method is:
2440%
2441% Image *MotionBlurImage(const Image *image,const double radius,
2442% const double sigma,const double angle,ExceptionInfo *exception)
2443% Image *MotionBlurImageChannel(const Image *image,const ChannelType channel,
2444% const double radius,const double sigma,const double angle,
2445% ExceptionInfo *exception)
2446%
2447% A description of each parameter follows:
2448%
2449% o image: the image.
2450%
2451% o channel: the channel type.
2452%
2453% o radius: the radius of the Gaussian, in pixels, not counting the center
2454% o radius: the radius of the Gaussian, in pixels, not counting
2455% the center pixel.
2456%
2457% o sigma: the standard deviation of the Gaussian, in pixels.
2458%
cristycee97112010-05-28 00:44:52 +00002459% o angle: Apply the effect along this angle.
cristy3ed852e2009-09-05 21:47:34 +00002460%
2461% o exception: return any errors or warnings in this structure.
2462%
2463*/
2464
cristybb503372010-05-27 20:51:26 +00002465static double *GetMotionBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +00002466{
cristy3ed852e2009-09-05 21:47:34 +00002467 double
cristy47e00502009-12-17 19:19:57 +00002468 *kernel,
cristy3ed852e2009-09-05 21:47:34 +00002469 normalize;
2470
cristybb503372010-05-27 20:51:26 +00002471 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002472 i;
2473
2474 /*
cristy47e00502009-12-17 19:19:57 +00002475 Generate a 1-D convolution kernel.
cristy3ed852e2009-09-05 21:47:34 +00002476 */
2477 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
2478 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
2479 if (kernel == (double *) NULL)
2480 return(kernel);
cristy3ed852e2009-09-05 21:47:34 +00002481 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00002482 for (i=0; i < (ssize_t) width; i++)
cristy47e00502009-12-17 19:19:57 +00002483 {
cristy4205a3c2010-09-12 20:19:59 +00002484 kernel[i]=(double) (exp((-((double) i*i)/(double) (2.0*MagickSigma*
2485 MagickSigma)))/(MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00002486 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +00002487 }
cristybb503372010-05-27 20:51:26 +00002488 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002489 kernel[i]/=normalize;
2490 return(kernel);
2491}
2492
2493MagickExport Image *MotionBlurImage(const Image *image,const double radius,
2494 const double sigma,const double angle,ExceptionInfo *exception)
2495{
2496 Image
2497 *motion_blur;
2498
2499 motion_blur=MotionBlurImageChannel(image,DefaultChannels,radius,sigma,angle,
2500 exception);
2501 return(motion_blur);
2502}
2503
2504MagickExport Image *MotionBlurImageChannel(const Image *image,
2505 const ChannelType channel,const double radius,const double sigma,
2506 const double angle,ExceptionInfo *exception)
2507{
cristyc4c8d132010-01-07 01:58:38 +00002508 CacheView
2509 *blur_view,
2510 *image_view;
2511
cristy3ed852e2009-09-05 21:47:34 +00002512 double
2513 *kernel;
2514
2515 Image
2516 *blur_image;
2517
cristy3ed852e2009-09-05 21:47:34 +00002518 MagickBooleanType
2519 status;
2520
cristybb503372010-05-27 20:51:26 +00002521 MagickOffsetType
2522 progress;
2523
cristy4c08aed2011-07-01 19:47:50 +00002524 PixelInfo
cristyddd82202009-11-03 20:14:50 +00002525 bias;
cristy3ed852e2009-09-05 21:47:34 +00002526
2527 OffsetInfo
2528 *offset;
2529
2530 PointInfo
2531 point;
2532
cristybb503372010-05-27 20:51:26 +00002533 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002534 i;
2535
cristybb503372010-05-27 20:51:26 +00002536 size_t
cristy3ed852e2009-09-05 21:47:34 +00002537 width;
2538
cristybb503372010-05-27 20:51:26 +00002539 ssize_t
2540 y;
2541
cristy3ed852e2009-09-05 21:47:34 +00002542 assert(image != (Image *) NULL);
2543 assert(image->signature == MagickSignature);
2544 if (image->debug != MagickFalse)
2545 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2546 assert(exception != (ExceptionInfo *) NULL);
2547 width=GetOptimalKernelWidth1D(radius,sigma);
2548 kernel=GetMotionBlurKernel(width,sigma);
2549 if (kernel == (double *) NULL)
2550 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2551 offset=(OffsetInfo *) AcquireQuantumMemory(width,sizeof(*offset));
2552 if (offset == (OffsetInfo *) NULL)
2553 {
2554 kernel=(double *) RelinquishMagickMemory(kernel);
2555 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2556 }
2557 blur_image=CloneImage(image,0,0,MagickTrue,exception);
2558 if (blur_image == (Image *) NULL)
2559 {
2560 kernel=(double *) RelinquishMagickMemory(kernel);
2561 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2562 return((Image *) NULL);
2563 }
2564 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
2565 {
2566 kernel=(double *) RelinquishMagickMemory(kernel);
2567 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2568 InheritException(exception,&blur_image->exception);
2569 blur_image=DestroyImage(blur_image);
2570 return((Image *) NULL);
2571 }
2572 point.x=(double) width*sin(DegreesToRadians(angle));
2573 point.y=(double) width*cos(DegreesToRadians(angle));
cristybb503372010-05-27 20:51:26 +00002574 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002575 {
cristybb503372010-05-27 20:51:26 +00002576 offset[i].x=(ssize_t) ceil((double) (i*point.y)/hypot(point.x,point.y)-0.5);
2577 offset[i].y=(ssize_t) ceil((double) (i*point.x)/hypot(point.x,point.y)-0.5);
cristy3ed852e2009-09-05 21:47:34 +00002578 }
2579 /*
2580 Motion blur image.
2581 */
2582 status=MagickTrue;
2583 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00002584 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00002585 image_view=AcquireCacheView(image);
2586 blur_view=AcquireCacheView(blur_image);
cristyb557a152011-02-22 12:14:30 +00002587#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00002588 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00002589#endif
cristybb503372010-05-27 20:51:26 +00002590 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002591 {
cristy4c08aed2011-07-01 19:47:50 +00002592 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002593 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002594
cristy117ff172010-08-15 21:35:32 +00002595 register ssize_t
2596 x;
2597
cristy3ed852e2009-09-05 21:47:34 +00002598 if (status == MagickFalse)
2599 continue;
2600 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
2601 exception);
cristy4c08aed2011-07-01 19:47:50 +00002602 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002603 {
2604 status=MagickFalse;
2605 continue;
2606 }
cristybb503372010-05-27 20:51:26 +00002607 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002608 {
cristy4c08aed2011-07-01 19:47:50 +00002609 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002610 qixel;
2611
2612 PixelPacket
2613 pixel;
2614
2615 register double
cristyc47d1f82009-11-26 01:44:43 +00002616 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00002617
cristybb503372010-05-27 20:51:26 +00002618 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002619 i;
2620
cristy3ed852e2009-09-05 21:47:34 +00002621 k=kernel;
cristyddd82202009-11-03 20:14:50 +00002622 qixel=bias;
cristy2b9582a2011-07-04 17:38:56 +00002623 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00002624 {
cristybb503372010-05-27 20:51:26 +00002625 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002626 {
2627 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
2628 offset[i].y,&pixel,exception);
2629 qixel.red+=(*k)*pixel.red;
2630 qixel.green+=(*k)*pixel.green;
2631 qixel.blue+=(*k)*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00002632 qixel.alpha+=(*k)*pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00002633 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00002634 qixel.black+=(*k)*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00002635 k++;
2636 }
cristy2b9582a2011-07-04 17:38:56 +00002637 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002638 SetPixelRed(blur_image,
2639 ClampToQuantum(qixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00002640 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002641 SetPixelGreen(blur_image,
2642 ClampToQuantum(qixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00002643 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002644 SetPixelBlue(blur_image,
2645 ClampToQuantum(qixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00002646 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002647 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002648 SetPixelBlack(blur_image,
2649 ClampToQuantum(qixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00002650 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002651 SetPixelAlpha(blur_image,
2652 ClampToQuantum(qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00002653 }
2654 else
2655 {
2656 MagickRealType
2657 alpha,
2658 gamma;
2659
2660 alpha=0.0;
2661 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00002662 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002663 {
2664 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
2665 offset[i].y,&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00002666 alpha=(MagickRealType) (QuantumScale*pixel.alpha);
cristy3ed852e2009-09-05 21:47:34 +00002667 qixel.red+=(*k)*alpha*pixel.red;
2668 qixel.green+=(*k)*alpha*pixel.green;
2669 qixel.blue+=(*k)*alpha*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00002670 qixel.alpha+=(*k)*pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00002671 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00002672 qixel.black+=(*k)*alpha*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00002673 gamma+=(*k)*alpha;
2674 k++;
2675 }
2676 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy2b9582a2011-07-04 17:38:56 +00002677 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002678 SetPixelRed(blur_image,
2679 ClampToQuantum(gamma*qixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00002680 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002681 SetPixelGreen(blur_image,
2682 ClampToQuantum(gamma*qixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00002683 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002684 SetPixelBlue(blur_image,
2685 ClampToQuantum(gamma*qixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00002686 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002687 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002688 SetPixelBlack(blur_image,
2689 ClampToQuantum(gamma*qixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00002690 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002691 SetPixelAlpha(blur_image,
2692 ClampToQuantum(qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00002693 }
cristy4c08aed2011-07-01 19:47:50 +00002694 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00002695 }
2696 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
2697 status=MagickFalse;
2698 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2699 {
2700 MagickBooleanType
2701 proceed;
2702
cristyb557a152011-02-22 12:14:30 +00002703#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002704 #pragma omp critical (MagickCore_MotionBlurImageChannel)
2705#endif
2706 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
2707 if (proceed == MagickFalse)
2708 status=MagickFalse;
2709 }
2710 }
2711 blur_view=DestroyCacheView(blur_view);
2712 image_view=DestroyCacheView(image_view);
2713 kernel=(double *) RelinquishMagickMemory(kernel);
2714 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2715 if (status == MagickFalse)
2716 blur_image=DestroyImage(blur_image);
2717 return(blur_image);
2718}
2719
2720/*
2721%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2722% %
2723% %
2724% %
2725% P r e v i e w I m a g e %
2726% %
2727% %
2728% %
2729%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2730%
2731% PreviewImage() tiles 9 thumbnails of the specified image with an image
2732% processing operation applied with varying parameters. This may be helpful
2733% pin-pointing an appropriate parameter for a particular image processing
2734% operation.
2735%
2736% The format of the PreviewImages method is:
2737%
2738% Image *PreviewImages(const Image *image,const PreviewType preview,
2739% ExceptionInfo *exception)
2740%
2741% A description of each parameter follows:
2742%
2743% o image: the image.
2744%
2745% o preview: the image processing operation.
2746%
2747% o exception: return any errors or warnings in this structure.
2748%
2749*/
2750MagickExport Image *PreviewImage(const Image *image,const PreviewType preview,
2751 ExceptionInfo *exception)
2752{
2753#define NumberTiles 9
2754#define PreviewImageTag "Preview/Image"
2755#define DefaultPreviewGeometry "204x204+10+10"
2756
2757 char
2758 factor[MaxTextExtent],
2759 label[MaxTextExtent];
2760
2761 double
2762 degrees,
2763 gamma,
2764 percentage,
2765 radius,
2766 sigma,
2767 threshold;
2768
2769 Image
2770 *images,
2771 *montage_image,
2772 *preview_image,
2773 *thumbnail;
2774
2775 ImageInfo
2776 *preview_info;
2777
cristy3ed852e2009-09-05 21:47:34 +00002778 MagickBooleanType
2779 proceed;
2780
2781 MontageInfo
2782 *montage_info;
2783
2784 QuantizeInfo
2785 quantize_info;
2786
2787 RectangleInfo
2788 geometry;
2789
cristybb503372010-05-27 20:51:26 +00002790 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002791 i,
2792 x;
2793
cristybb503372010-05-27 20:51:26 +00002794 size_t
cristy3ed852e2009-09-05 21:47:34 +00002795 colors;
2796
cristy117ff172010-08-15 21:35:32 +00002797 ssize_t
2798 y;
2799
cristy3ed852e2009-09-05 21:47:34 +00002800 /*
2801 Open output image file.
2802 */
2803 assert(image != (Image *) NULL);
2804 assert(image->signature == MagickSignature);
2805 if (image->debug != MagickFalse)
2806 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2807 colors=2;
2808 degrees=0.0;
2809 gamma=(-0.2f);
2810 preview_info=AcquireImageInfo();
2811 SetGeometry(image,&geometry);
2812 (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y,
2813 &geometry.width,&geometry.height);
2814 images=NewImageList();
2815 percentage=12.5;
2816 GetQuantizeInfo(&quantize_info);
2817 radius=0.0;
2818 sigma=1.0;
2819 threshold=0.0;
2820 x=0;
2821 y=0;
2822 for (i=0; i < NumberTiles; i++)
2823 {
2824 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
2825 if (thumbnail == (Image *) NULL)
2826 break;
2827 (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL,
2828 (void *) NULL);
2829 (void) SetImageProperty(thumbnail,"label",DefaultTileLabel);
2830 if (i == (NumberTiles/2))
2831 {
2832 (void) QueryColorDatabase("#dfdfdf",&thumbnail->matte_color,exception);
2833 AppendImageToList(&images,thumbnail);
2834 continue;
2835 }
2836 switch (preview)
2837 {
2838 case RotatePreview:
2839 {
2840 degrees+=45.0;
2841 preview_image=RotateImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002842 (void) FormatLocaleString(label,MaxTextExtent,"rotate %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002843 break;
2844 }
2845 case ShearPreview:
2846 {
2847 degrees+=5.0;
2848 preview_image=ShearImage(thumbnail,degrees,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002849 (void) FormatLocaleString(label,MaxTextExtent,"shear %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002850 degrees,2.0*degrees);
2851 break;
2852 }
2853 case RollPreview:
2854 {
cristybb503372010-05-27 20:51:26 +00002855 x=(ssize_t) ((i+1)*thumbnail->columns)/NumberTiles;
2856 y=(ssize_t) ((i+1)*thumbnail->rows)/NumberTiles;
cristy3ed852e2009-09-05 21:47:34 +00002857 preview_image=RollImage(thumbnail,x,y,exception);
cristyb51dff52011-05-19 16:55:47 +00002858 (void) FormatLocaleString(label,MaxTextExtent,"roll %+.20gx%+.20g",
cristye8c25f92010-06-03 00:53:06 +00002859 (double) x,(double) y);
cristy3ed852e2009-09-05 21:47:34 +00002860 break;
2861 }
2862 case HuePreview:
2863 {
2864 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2865 if (preview_image == (Image *) NULL)
2866 break;
cristyb51dff52011-05-19 16:55:47 +00002867 (void) FormatLocaleString(factor,MaxTextExtent,"100,100,%g",
cristy3ed852e2009-09-05 21:47:34 +00002868 2.0*percentage);
2869 (void) ModulateImage(preview_image,factor);
cristyb51dff52011-05-19 16:55:47 +00002870 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002871 break;
2872 }
2873 case SaturationPreview:
2874 {
2875 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2876 if (preview_image == (Image *) NULL)
2877 break;
cristyb51dff52011-05-19 16:55:47 +00002878 (void) FormatLocaleString(factor,MaxTextExtent,"100,%g",
cristy8cd5b312010-01-07 01:10:24 +00002879 2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00002880 (void) ModulateImage(preview_image,factor);
cristyb51dff52011-05-19 16:55:47 +00002881 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002882 break;
2883 }
2884 case BrightnessPreview:
2885 {
2886 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2887 if (preview_image == (Image *) NULL)
2888 break;
cristyb51dff52011-05-19 16:55:47 +00002889 (void) FormatLocaleString(factor,MaxTextExtent,"%g",2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00002890 (void) ModulateImage(preview_image,factor);
cristyb51dff52011-05-19 16:55:47 +00002891 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002892 break;
2893 }
2894 case GammaPreview:
2895 default:
2896 {
2897 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2898 if (preview_image == (Image *) NULL)
2899 break;
2900 gamma+=0.4f;
cristy50fbc382011-07-07 02:19:17 +00002901 (void) GammaImage(preview_image,gamma);
cristyb51dff52011-05-19 16:55:47 +00002902 (void) FormatLocaleString(label,MaxTextExtent,"gamma %g",gamma);
cristy3ed852e2009-09-05 21:47:34 +00002903 break;
2904 }
2905 case SpiffPreview:
2906 {
2907 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2908 if (preview_image != (Image *) NULL)
2909 for (x=0; x < i; x++)
2910 (void) ContrastImage(preview_image,MagickTrue);
cristyb51dff52011-05-19 16:55:47 +00002911 (void) FormatLocaleString(label,MaxTextExtent,"contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002912 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002913 break;
2914 }
2915 case DullPreview:
2916 {
2917 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2918 if (preview_image == (Image *) NULL)
2919 break;
2920 for (x=0; x < i; x++)
2921 (void) ContrastImage(preview_image,MagickFalse);
cristyb51dff52011-05-19 16:55:47 +00002922 (void) FormatLocaleString(label,MaxTextExtent,"+contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002923 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002924 break;
2925 }
2926 case GrayscalePreview:
2927 {
2928 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2929 if (preview_image == (Image *) NULL)
2930 break;
2931 colors<<=1;
2932 quantize_info.number_colors=colors;
2933 quantize_info.colorspace=GRAYColorspace;
2934 (void) QuantizeImage(&quantize_info,preview_image);
cristyb51dff52011-05-19 16:55:47 +00002935 (void) FormatLocaleString(label,MaxTextExtent,
cristye8c25f92010-06-03 00:53:06 +00002936 "-colorspace gray -colors %.20g",(double) colors);
cristy3ed852e2009-09-05 21:47:34 +00002937 break;
2938 }
2939 case QuantizePreview:
2940 {
2941 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2942 if (preview_image == (Image *) NULL)
2943 break;
2944 colors<<=1;
2945 quantize_info.number_colors=colors;
2946 (void) QuantizeImage(&quantize_info,preview_image);
cristyb51dff52011-05-19 16:55:47 +00002947 (void) FormatLocaleString(label,MaxTextExtent,"colors %.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00002948 colors);
cristy3ed852e2009-09-05 21:47:34 +00002949 break;
2950 }
2951 case DespecklePreview:
2952 {
2953 for (x=0; x < (i-1); x++)
2954 {
2955 preview_image=DespeckleImage(thumbnail,exception);
2956 if (preview_image == (Image *) NULL)
2957 break;
2958 thumbnail=DestroyImage(thumbnail);
2959 thumbnail=preview_image;
2960 }
2961 preview_image=DespeckleImage(thumbnail,exception);
2962 if (preview_image == (Image *) NULL)
2963 break;
cristyb51dff52011-05-19 16:55:47 +00002964 (void) FormatLocaleString(label,MaxTextExtent,"despeckle (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002965 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002966 break;
2967 }
2968 case ReduceNoisePreview:
2969 {
cristy95c38342011-03-18 22:39:51 +00002970 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) radius,
2971 (size_t) radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002972 (void) FormatLocaleString(label,MaxTextExtent,"noise %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002973 break;
2974 }
2975 case AddNoisePreview:
2976 {
2977 switch ((int) i)
2978 {
2979 case 0:
2980 {
2981 (void) CopyMagickString(factor,"uniform",MaxTextExtent);
2982 break;
2983 }
2984 case 1:
2985 {
2986 (void) CopyMagickString(factor,"gaussian",MaxTextExtent);
2987 break;
2988 }
2989 case 2:
2990 {
2991 (void) CopyMagickString(factor,"multiplicative",MaxTextExtent);
2992 break;
2993 }
2994 case 3:
2995 {
2996 (void) CopyMagickString(factor,"impulse",MaxTextExtent);
2997 break;
2998 }
2999 case 4:
3000 {
3001 (void) CopyMagickString(factor,"laplacian",MaxTextExtent);
3002 break;
3003 }
3004 case 5:
3005 {
3006 (void) CopyMagickString(factor,"Poisson",MaxTextExtent);
3007 break;
3008 }
3009 default:
3010 {
3011 (void) CopyMagickString(thumbnail->magick,"NULL",MaxTextExtent);
3012 break;
3013 }
3014 }
cristyd76c51e2011-03-26 00:21:26 +00003015 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) i,
3016 (size_t) i,exception);
cristyb51dff52011-05-19 16:55:47 +00003017 (void) FormatLocaleString(label,MaxTextExtent,"+noise %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00003018 break;
3019 }
3020 case SharpenPreview:
3021 {
3022 preview_image=SharpenImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00003023 (void) FormatLocaleString(label,MaxTextExtent,"sharpen %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003024 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00003025 break;
3026 }
3027 case BlurPreview:
3028 {
3029 preview_image=BlurImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00003030 (void) FormatLocaleString(label,MaxTextExtent,"blur %gx%g",radius,
cristy3ed852e2009-09-05 21:47:34 +00003031 sigma);
3032 break;
3033 }
3034 case ThresholdPreview:
3035 {
3036 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3037 if (preview_image == (Image *) NULL)
3038 break;
3039 (void) BilevelImage(thumbnail,
3040 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
cristyb51dff52011-05-19 16:55:47 +00003041 (void) FormatLocaleString(label,MaxTextExtent,"threshold %g",
cristy3ed852e2009-09-05 21:47:34 +00003042 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
3043 break;
3044 }
3045 case EdgeDetectPreview:
3046 {
3047 preview_image=EdgeImage(thumbnail,radius,exception);
cristyb51dff52011-05-19 16:55:47 +00003048 (void) FormatLocaleString(label,MaxTextExtent,"edge %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003049 break;
3050 }
3051 case SpreadPreview:
3052 {
3053 preview_image=SpreadImage(thumbnail,radius,exception);
cristyb51dff52011-05-19 16:55:47 +00003054 (void) FormatLocaleString(label,MaxTextExtent,"spread %g",
cristy8cd5b312010-01-07 01:10:24 +00003055 radius+0.5);
cristy3ed852e2009-09-05 21:47:34 +00003056 break;
3057 }
3058 case SolarizePreview:
3059 {
3060 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3061 if (preview_image == (Image *) NULL)
3062 break;
3063 (void) SolarizeImage(preview_image,(double) QuantumRange*
3064 percentage/100.0);
cristyb51dff52011-05-19 16:55:47 +00003065 (void) FormatLocaleString(label,MaxTextExtent,"solarize %g",
cristy3ed852e2009-09-05 21:47:34 +00003066 (QuantumRange*percentage)/100.0);
3067 break;
3068 }
3069 case ShadePreview:
3070 {
3071 degrees+=10.0;
3072 preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees,
3073 exception);
cristyb51dff52011-05-19 16:55:47 +00003074 (void) FormatLocaleString(label,MaxTextExtent,"shade %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003075 degrees,degrees);
cristy3ed852e2009-09-05 21:47:34 +00003076 break;
3077 }
3078 case RaisePreview:
3079 {
3080 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3081 if (preview_image == (Image *) NULL)
3082 break;
cristybb503372010-05-27 20:51:26 +00003083 geometry.width=(size_t) (2*i+2);
3084 geometry.height=(size_t) (2*i+2);
cristy3ed852e2009-09-05 21:47:34 +00003085 geometry.x=i/2;
3086 geometry.y=i/2;
3087 (void) RaiseImage(preview_image,&geometry,MagickTrue);
cristyb51dff52011-05-19 16:55:47 +00003088 (void) FormatLocaleString(label,MaxTextExtent,
cristy6d8abba2010-06-03 01:10:47 +00003089 "raise %.20gx%.20g%+.20g%+.20g",(double) geometry.width,(double)
cristye8c25f92010-06-03 00:53:06 +00003090 geometry.height,(double) geometry.x,(double) geometry.y);
cristy3ed852e2009-09-05 21:47:34 +00003091 break;
3092 }
3093 case SegmentPreview:
3094 {
3095 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3096 if (preview_image == (Image *) NULL)
3097 break;
3098 threshold+=0.4f;
3099 (void) SegmentImage(preview_image,RGBColorspace,MagickFalse,threshold,
3100 threshold);
cristyb51dff52011-05-19 16:55:47 +00003101 (void) FormatLocaleString(label,MaxTextExtent,"segment %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00003102 threshold,threshold);
3103 break;
3104 }
3105 case SwirlPreview:
3106 {
3107 preview_image=SwirlImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00003108 (void) FormatLocaleString(label,MaxTextExtent,"swirl %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003109 degrees+=45.0;
3110 break;
3111 }
3112 case ImplodePreview:
3113 {
3114 degrees+=0.1f;
3115 preview_image=ImplodeImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00003116 (void) FormatLocaleString(label,MaxTextExtent,"implode %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003117 break;
3118 }
3119 case WavePreview:
3120 {
3121 degrees+=5.0f;
3122 preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00003123 (void) FormatLocaleString(label,MaxTextExtent,"wave %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003124 0.5*degrees,2.0*degrees);
cristy3ed852e2009-09-05 21:47:34 +00003125 break;
3126 }
3127 case OilPaintPreview:
3128 {
3129 preview_image=OilPaintImage(thumbnail,(double) radius,exception);
cristyb51dff52011-05-19 16:55:47 +00003130 (void) FormatLocaleString(label,MaxTextExtent,"paint %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003131 break;
3132 }
3133 case CharcoalDrawingPreview:
3134 {
3135 preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma,
3136 exception);
cristyb51dff52011-05-19 16:55:47 +00003137 (void) FormatLocaleString(label,MaxTextExtent,"charcoal %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003138 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00003139 break;
3140 }
3141 case JPEGPreview:
3142 {
3143 char
3144 filename[MaxTextExtent];
3145
3146 int
3147 file;
3148
3149 MagickBooleanType
3150 status;
3151
3152 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3153 if (preview_image == (Image *) NULL)
3154 break;
cristybb503372010-05-27 20:51:26 +00003155 preview_info->quality=(size_t) percentage;
cristyb51dff52011-05-19 16:55:47 +00003156 (void) FormatLocaleString(factor,MaxTextExtent,"%.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00003157 preview_info->quality);
cristy3ed852e2009-09-05 21:47:34 +00003158 file=AcquireUniqueFileResource(filename);
3159 if (file != -1)
3160 file=close(file)-1;
cristyb51dff52011-05-19 16:55:47 +00003161 (void) FormatLocaleString(preview_image->filename,MaxTextExtent,
cristy3ed852e2009-09-05 21:47:34 +00003162 "jpeg:%s",filename);
3163 status=WriteImage(preview_info,preview_image);
3164 if (status != MagickFalse)
3165 {
3166 Image
3167 *quality_image;
3168
3169 (void) CopyMagickString(preview_info->filename,
3170 preview_image->filename,MaxTextExtent);
3171 quality_image=ReadImage(preview_info,exception);
3172 if (quality_image != (Image *) NULL)
3173 {
3174 preview_image=DestroyImage(preview_image);
3175 preview_image=quality_image;
3176 }
3177 }
3178 (void) RelinquishUniqueFileResource(preview_image->filename);
3179 if ((GetBlobSize(preview_image)/1024) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00003180 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%gmb ",
cristy3ed852e2009-09-05 21:47:34 +00003181 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
3182 1024.0/1024.0);
3183 else
3184 if (GetBlobSize(preview_image) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00003185 (void) FormatLocaleString(label,MaxTextExtent,
cristye7f51092010-01-17 00:39:37 +00003186 "quality %s\n%gkb ",factor,(double) ((MagickOffsetType)
cristy8cd5b312010-01-07 01:10:24 +00003187 GetBlobSize(preview_image))/1024.0);
cristy3ed852e2009-09-05 21:47:34 +00003188 else
cristyb51dff52011-05-19 16:55:47 +00003189 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%.20gb ",
cristy54ea5732011-06-10 12:39:53 +00003190 factor,(double) ((MagickOffsetType) GetBlobSize(thumbnail)));
cristy3ed852e2009-09-05 21:47:34 +00003191 break;
3192 }
3193 }
3194 thumbnail=DestroyImage(thumbnail);
3195 percentage+=12.5;
3196 radius+=0.5;
3197 sigma+=0.25;
3198 if (preview_image == (Image *) NULL)
3199 break;
3200 (void) DeleteImageProperty(preview_image,"label");
3201 (void) SetImageProperty(preview_image,"label",label);
3202 AppendImageToList(&images,preview_image);
cristybb503372010-05-27 20:51:26 +00003203 proceed=SetImageProgress(image,PreviewImageTag,(MagickOffsetType) i,
3204 NumberTiles);
cristy3ed852e2009-09-05 21:47:34 +00003205 if (proceed == MagickFalse)
3206 break;
3207 }
3208 if (images == (Image *) NULL)
3209 {
3210 preview_info=DestroyImageInfo(preview_info);
3211 return((Image *) NULL);
3212 }
3213 /*
3214 Create the montage.
3215 */
3216 montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL);
3217 (void) CopyMagickString(montage_info->filename,image->filename,MaxTextExtent);
3218 montage_info->shadow=MagickTrue;
3219 (void) CloneString(&montage_info->tile,"3x3");
3220 (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry);
3221 (void) CloneString(&montage_info->frame,DefaultTileFrame);
3222 montage_image=MontageImages(images,montage_info,exception);
3223 montage_info=DestroyMontageInfo(montage_info);
3224 images=DestroyImageList(images);
3225 if (montage_image == (Image *) NULL)
3226 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3227 if (montage_image->montage != (char *) NULL)
3228 {
3229 /*
3230 Free image directory.
3231 */
3232 montage_image->montage=(char *) RelinquishMagickMemory(
3233 montage_image->montage);
3234 if (image->directory != (char *) NULL)
3235 montage_image->directory=(char *) RelinquishMagickMemory(
3236 montage_image->directory);
3237 }
3238 preview_info=DestroyImageInfo(preview_info);
3239 return(montage_image);
3240}
3241
3242/*
3243%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3244% %
3245% %
3246% %
3247% R a d i a l B l u r I m a g e %
3248% %
3249% %
3250% %
3251%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3252%
3253% RadialBlurImage() applies a radial blur to the image.
3254%
3255% Andrew Protano contributed this effect.
3256%
3257% The format of the RadialBlurImage method is:
3258%
3259% Image *RadialBlurImage(const Image *image,const double angle,
3260% ExceptionInfo *exception)
3261% Image *RadialBlurImageChannel(const Image *image,const ChannelType channel,
3262% const double angle,ExceptionInfo *exception)
3263%
3264% A description of each parameter follows:
3265%
3266% o image: the image.
3267%
3268% o channel: the channel type.
3269%
3270% o angle: the angle of the radial blur.
3271%
3272% o exception: return any errors or warnings in this structure.
3273%
3274*/
3275
3276MagickExport Image *RadialBlurImage(const Image *image,const double angle,
3277 ExceptionInfo *exception)
3278{
3279 Image
3280 *blur_image;
3281
3282 blur_image=RadialBlurImageChannel(image,DefaultChannels,angle,exception);
3283 return(blur_image);
3284}
3285
3286MagickExport Image *RadialBlurImageChannel(const Image *image,
3287 const ChannelType channel,const double angle,ExceptionInfo *exception)
3288{
cristyc4c8d132010-01-07 01:58:38 +00003289 CacheView
3290 *blur_view,
3291 *image_view;
3292
cristy3ed852e2009-09-05 21:47:34 +00003293 Image
3294 *blur_image;
3295
cristy3ed852e2009-09-05 21:47:34 +00003296 MagickBooleanType
3297 status;
3298
cristybb503372010-05-27 20:51:26 +00003299 MagickOffsetType
3300 progress;
3301
cristy4c08aed2011-07-01 19:47:50 +00003302 PixelInfo
cristyddd82202009-11-03 20:14:50 +00003303 bias;
cristy3ed852e2009-09-05 21:47:34 +00003304
3305 MagickRealType
3306 blur_radius,
3307 *cos_theta,
3308 offset,
3309 *sin_theta,
3310 theta;
3311
3312 PointInfo
3313 blur_center;
3314
cristybb503372010-05-27 20:51:26 +00003315 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003316 i;
3317
cristybb503372010-05-27 20:51:26 +00003318 size_t
cristy3ed852e2009-09-05 21:47:34 +00003319 n;
3320
cristybb503372010-05-27 20:51:26 +00003321 ssize_t
3322 y;
3323
cristy3ed852e2009-09-05 21:47:34 +00003324 /*
3325 Allocate blur image.
3326 */
3327 assert(image != (Image *) NULL);
3328 assert(image->signature == MagickSignature);
3329 if (image->debug != MagickFalse)
3330 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3331 assert(exception != (ExceptionInfo *) NULL);
3332 assert(exception->signature == MagickSignature);
3333 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3334 if (blur_image == (Image *) NULL)
3335 return((Image *) NULL);
3336 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3337 {
3338 InheritException(exception,&blur_image->exception);
3339 blur_image=DestroyImage(blur_image);
3340 return((Image *) NULL);
3341 }
3342 blur_center.x=(double) image->columns/2.0;
3343 blur_center.y=(double) image->rows/2.0;
3344 blur_radius=hypot(blur_center.x,blur_center.y);
cristy117ff172010-08-15 21:35:32 +00003345 n=(size_t) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+2UL);
cristy3ed852e2009-09-05 21:47:34 +00003346 theta=DegreesToRadians(angle)/(MagickRealType) (n-1);
3347 cos_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3348 sizeof(*cos_theta));
3349 sin_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3350 sizeof(*sin_theta));
3351 if ((cos_theta == (MagickRealType *) NULL) ||
3352 (sin_theta == (MagickRealType *) NULL))
3353 {
3354 blur_image=DestroyImage(blur_image);
3355 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3356 }
3357 offset=theta*(MagickRealType) (n-1)/2.0;
cristybb503372010-05-27 20:51:26 +00003358 for (i=0; i < (ssize_t) n; i++)
cristy3ed852e2009-09-05 21:47:34 +00003359 {
3360 cos_theta[i]=cos((double) (theta*i-offset));
3361 sin_theta[i]=sin((double) (theta*i-offset));
3362 }
3363 /*
3364 Radial blur image.
3365 */
3366 status=MagickTrue;
3367 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00003368 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003369 image_view=AcquireCacheView(image);
3370 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003371#if defined(MAGICKCORE_OPENMP_SUPPORT)
3372 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003373#endif
cristybb503372010-05-27 20:51:26 +00003374 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003375 {
cristy4c08aed2011-07-01 19:47:50 +00003376 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003377 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003378
cristy117ff172010-08-15 21:35:32 +00003379 register ssize_t
3380 x;
3381
cristy3ed852e2009-09-05 21:47:34 +00003382 if (status == MagickFalse)
3383 continue;
3384 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3385 exception);
cristy4c08aed2011-07-01 19:47:50 +00003386 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003387 {
3388 status=MagickFalse;
3389 continue;
3390 }
cristybb503372010-05-27 20:51:26 +00003391 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003392 {
cristy4c08aed2011-07-01 19:47:50 +00003393 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003394 qixel;
3395
3396 MagickRealType
3397 normalize,
3398 radius;
3399
3400 PixelPacket
3401 pixel;
3402
3403 PointInfo
3404 center;
3405
cristybb503372010-05-27 20:51:26 +00003406 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003407 i;
3408
cristybb503372010-05-27 20:51:26 +00003409 size_t
cristy3ed852e2009-09-05 21:47:34 +00003410 step;
3411
3412 center.x=(double) x-blur_center.x;
3413 center.y=(double) y-blur_center.y;
3414 radius=hypot((double) center.x,center.y);
3415 if (radius == 0)
3416 step=1;
3417 else
3418 {
cristybb503372010-05-27 20:51:26 +00003419 step=(size_t) (blur_radius/radius);
cristy3ed852e2009-09-05 21:47:34 +00003420 if (step == 0)
3421 step=1;
3422 else
3423 if (step >= n)
3424 step=n-1;
3425 }
3426 normalize=0.0;
cristyddd82202009-11-03 20:14:50 +00003427 qixel=bias;
cristy2b9582a2011-07-04 17:38:56 +00003428 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00003429 {
cristyeaedf062010-05-29 22:36:02 +00003430 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00003431 {
cristyeaedf062010-05-29 22:36:02 +00003432 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
3433 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
3434 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
3435 cos_theta[i]+0.5),&pixel,exception);
cristy3ed852e2009-09-05 21:47:34 +00003436 qixel.red+=pixel.red;
3437 qixel.green+=pixel.green;
3438 qixel.blue+=pixel.blue;
cristy3ed852e2009-09-05 21:47:34 +00003439 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00003440 qixel.black+=pixel.black;
3441 qixel.alpha+=pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00003442 normalize+=1.0;
3443 }
3444 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
3445 normalize);
cristy2b9582a2011-07-04 17:38:56 +00003446 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003447 SetPixelRed(blur_image,
3448 ClampToQuantum(normalize*qixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00003449 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003450 SetPixelGreen(blur_image,
3451 ClampToQuantum(normalize*qixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00003452 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003453 SetPixelBlue(blur_image,
3454 ClampToQuantum(normalize*qixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00003455 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003456 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003457 SetPixelBlack(blur_image,
3458 ClampToQuantum(normalize*qixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00003459 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003460 SetPixelAlpha(blur_image,
3461 ClampToQuantum(normalize*qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003462 }
3463 else
3464 {
3465 MagickRealType
3466 alpha,
3467 gamma;
3468
3469 alpha=1.0;
3470 gamma=0.0;
cristyeaedf062010-05-29 22:36:02 +00003471 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00003472 {
cristyeaedf062010-05-29 22:36:02 +00003473 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
3474 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
3475 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
3476 cos_theta[i]+0.5),&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00003477 alpha=(MagickRealType) (QuantumScale*pixel.alpha);
cristy3ed852e2009-09-05 21:47:34 +00003478 qixel.red+=alpha*pixel.red;
3479 qixel.green+=alpha*pixel.green;
3480 qixel.blue+=alpha*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00003481 qixel.alpha+=pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00003482 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00003483 qixel.black+=alpha*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00003484 gamma+=alpha;
3485 normalize+=1.0;
3486 }
3487 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3488 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
3489 normalize);
cristy2b9582a2011-07-04 17:38:56 +00003490 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003491 SetPixelRed(blur_image,
3492 ClampToQuantum(gamma*qixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00003493 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003494 SetPixelGreen(blur_image,
3495 ClampToQuantum(gamma*qixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00003496 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003497 SetPixelBlue(blur_image,
3498 ClampToQuantum(gamma*qixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00003499 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003500 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003501 SetPixelBlack(blur_image,
3502 ClampToQuantum(gamma*qixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00003503 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003504 SetPixelAlpha(blur_image,
3505 ClampToQuantum(normalize*qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003506 }
cristy4c08aed2011-07-01 19:47:50 +00003507 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003508 }
3509 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
3510 status=MagickFalse;
3511 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3512 {
3513 MagickBooleanType
3514 proceed;
3515
cristyb5d5f722009-11-04 03:03:49 +00003516#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003517 #pragma omp critical (MagickCore_RadialBlurImageChannel)
3518#endif
3519 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
3520 if (proceed == MagickFalse)
3521 status=MagickFalse;
3522 }
3523 }
3524 blur_view=DestroyCacheView(blur_view);
3525 image_view=DestroyCacheView(image_view);
3526 cos_theta=(MagickRealType *) RelinquishMagickMemory(cos_theta);
3527 sin_theta=(MagickRealType *) RelinquishMagickMemory(sin_theta);
3528 if (status == MagickFalse)
3529 blur_image=DestroyImage(blur_image);
3530 return(blur_image);
3531}
3532
3533/*
3534%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3535% %
3536% %
3537% %
cristy3ed852e2009-09-05 21:47:34 +00003538% S e l e c t i v e B l u r I m a g e %
3539% %
3540% %
3541% %
3542%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3543%
3544% SelectiveBlurImage() selectively blur pixels within a contrast threshold.
3545% It is similar to the unsharpen mask that sharpens everything with contrast
3546% above a certain threshold.
3547%
3548% The format of the SelectiveBlurImage method is:
3549%
3550% Image *SelectiveBlurImage(const Image *image,const double radius,
3551% const double sigma,const double threshold,ExceptionInfo *exception)
3552% Image *SelectiveBlurImageChannel(const Image *image,
3553% const ChannelType channel,const double radius,const double sigma,
3554% const double threshold,ExceptionInfo *exception)
3555%
3556% A description of each parameter follows:
3557%
3558% o image: the image.
3559%
3560% o channel: the channel type.
3561%
3562% o radius: the radius of the Gaussian, in pixels, not counting the center
3563% pixel.
3564%
3565% o sigma: the standard deviation of the Gaussian, in pixels.
3566%
3567% o threshold: only pixels within this contrast threshold are included
3568% in the blur operation.
3569%
3570% o exception: return any errors or warnings in this structure.
3571%
3572*/
3573
cristy3ed852e2009-09-05 21:47:34 +00003574MagickExport Image *SelectiveBlurImage(const Image *image,const double radius,
3575 const double sigma,const double threshold,ExceptionInfo *exception)
3576{
3577 Image
3578 *blur_image;
3579
3580 blur_image=SelectiveBlurImageChannel(image,DefaultChannels,radius,sigma,
3581 threshold,exception);
3582 return(blur_image);
3583}
3584
3585MagickExport Image *SelectiveBlurImageChannel(const Image *image,
3586 const ChannelType channel,const double radius,const double sigma,
3587 const double threshold,ExceptionInfo *exception)
3588{
3589#define SelectiveBlurImageTag "SelectiveBlur/Image"
3590
cristy47e00502009-12-17 19:19:57 +00003591 CacheView
3592 *blur_view,
3593 *image_view;
3594
cristy3ed852e2009-09-05 21:47:34 +00003595 double
cristy3ed852e2009-09-05 21:47:34 +00003596 *kernel;
3597
3598 Image
3599 *blur_image;
3600
cristy3ed852e2009-09-05 21:47:34 +00003601 MagickBooleanType
3602 status;
3603
cristybb503372010-05-27 20:51:26 +00003604 MagickOffsetType
3605 progress;
3606
cristy4c08aed2011-07-01 19:47:50 +00003607 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003608 bias;
3609
cristybb503372010-05-27 20:51:26 +00003610 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003611 i;
cristy3ed852e2009-09-05 21:47:34 +00003612
cristybb503372010-05-27 20:51:26 +00003613 size_t
cristy3ed852e2009-09-05 21:47:34 +00003614 width;
3615
cristybb503372010-05-27 20:51:26 +00003616 ssize_t
3617 j,
3618 u,
3619 v,
3620 y;
3621
cristy3ed852e2009-09-05 21:47:34 +00003622 /*
3623 Initialize blur image attributes.
3624 */
3625 assert(image != (Image *) NULL);
3626 assert(image->signature == MagickSignature);
3627 if (image->debug != MagickFalse)
3628 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3629 assert(exception != (ExceptionInfo *) NULL);
3630 assert(exception->signature == MagickSignature);
3631 width=GetOptimalKernelWidth1D(radius,sigma);
3632 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
3633 if (kernel == (double *) NULL)
3634 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00003635 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00003636 i=0;
cristy47e00502009-12-17 19:19:57 +00003637 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00003638 {
cristy47e00502009-12-17 19:19:57 +00003639 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00003640 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
3641 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00003642 }
3643 if (image->debug != MagickFalse)
3644 {
3645 char
3646 format[MaxTextExtent],
3647 *message;
3648
cristy117ff172010-08-15 21:35:32 +00003649 register const double
3650 *k;
3651
cristybb503372010-05-27 20:51:26 +00003652 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003653 u,
3654 v;
3655
cristy3ed852e2009-09-05 21:47:34 +00003656 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00003657 " SelectiveBlurImage with %.20gx%.20g kernel:",(double) width,(double)
3658 width);
cristy3ed852e2009-09-05 21:47:34 +00003659 message=AcquireString("");
3660 k=kernel;
cristybb503372010-05-27 20:51:26 +00003661 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003662 {
3663 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00003664 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy3ed852e2009-09-05 21:47:34 +00003665 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00003666 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003667 {
cristyb51dff52011-05-19 16:55:47 +00003668 (void) FormatLocaleString(format,MaxTextExtent,"%+f ",*k++);
cristy3ed852e2009-09-05 21:47:34 +00003669 (void) ConcatenateString(&message,format);
3670 }
3671 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
3672 }
3673 message=DestroyString(message);
3674 }
3675 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3676 if (blur_image == (Image *) NULL)
3677 return((Image *) NULL);
3678 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3679 {
3680 InheritException(exception,&blur_image->exception);
3681 blur_image=DestroyImage(blur_image);
3682 return((Image *) NULL);
3683 }
3684 /*
3685 Threshold blur image.
3686 */
3687 status=MagickTrue;
3688 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00003689 GetPixelInfo(image,&bias);
3690 SetPixelInfoBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003691 image_view=AcquireCacheView(image);
3692 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003693#if defined(MAGICKCORE_OPENMP_SUPPORT)
3694 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003695#endif
cristybb503372010-05-27 20:51:26 +00003696 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003697 {
cristy4c08aed2011-07-01 19:47:50 +00003698 double
3699 contrast;
3700
cristy3ed852e2009-09-05 21:47:34 +00003701 MagickBooleanType
3702 sync;
3703
3704 MagickRealType
3705 gamma;
3706
cristy4c08aed2011-07-01 19:47:50 +00003707 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003708 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003709
cristy4c08aed2011-07-01 19:47:50 +00003710 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003711 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003712
cristy117ff172010-08-15 21:35:32 +00003713 register ssize_t
3714 x;
3715
cristy3ed852e2009-09-05 21:47:34 +00003716 if (status == MagickFalse)
3717 continue;
cristy117ff172010-08-15 21:35:32 +00003718 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
3719 (width/2L),image->columns+width,width,exception);
cristy3ed852e2009-09-05 21:47:34 +00003720 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3721 exception);
cristy4c08aed2011-07-01 19:47:50 +00003722 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003723 {
3724 status=MagickFalse;
3725 continue;
3726 }
cristybb503372010-05-27 20:51:26 +00003727 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003728 {
cristy4c08aed2011-07-01 19:47:50 +00003729 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003730 pixel;
3731
3732 register const double
cristyc47d1f82009-11-26 01:44:43 +00003733 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00003734
cristybb503372010-05-27 20:51:26 +00003735 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003736 u;
3737
cristy117ff172010-08-15 21:35:32 +00003738 ssize_t
3739 j,
3740 v;
3741
cristyddd82202009-11-03 20:14:50 +00003742 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00003743 k=kernel;
3744 gamma=0.0;
3745 j=0;
cristy2b9582a2011-07-04 17:38:56 +00003746 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00003747 {
cristybb503372010-05-27 20:51:26 +00003748 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003749 {
cristybb503372010-05-27 20:51:26 +00003750 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003751 {
cristy4c08aed2011-07-01 19:47:50 +00003752 contrast=GetPixelIntensity(image,p+(u+j)*GetPixelChannels(image))-
3753 (double) GetPixelIntensity(blur_image,q);
3754 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003755 {
cristy4c08aed2011-07-01 19:47:50 +00003756 pixel.red+=(*k)*
3757 GetPixelRed(image,p+(u+j)*GetPixelChannels(image));
3758 pixel.green+=(*k)*
3759 GetPixelGreen(image,p+(u+j)*GetPixelChannels(image));
3760 pixel.blue+=(*k)*
3761 GetPixelBlue(image,p+(u+j)*GetPixelChannels(image));
3762 if (image->colorspace == CMYKColorspace)
3763 pixel.black+=(*k)*
3764 GetPixelBlack(image,p+(u+j)*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003765 gamma+=(*k);
3766 k++;
3767 }
3768 }
cristyd99b0962010-05-29 23:14:26 +00003769 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003770 }
3771 if (gamma != 0.0)
3772 {
3773 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy2b9582a2011-07-04 17:38:56 +00003774 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003775 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00003776 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003777 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00003778 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003779 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00003780 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00003781 (image->colorspace == CMYKColorspace))
3782 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00003783 }
cristy2b9582a2011-07-04 17:38:56 +00003784 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003785 {
3786 gamma=0.0;
3787 j=0;
cristybb503372010-05-27 20:51:26 +00003788 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003789 {
cristybb503372010-05-27 20:51:26 +00003790 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003791 {
cristy4c08aed2011-07-01 19:47:50 +00003792 contrast=GetPixelIntensity(image,p+(u+j)*
3793 GetPixelChannels(image))-(double)
3794 GetPixelIntensity(blur_image,q);
3795 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003796 {
cristy4c08aed2011-07-01 19:47:50 +00003797 pixel.alpha+=(*k)*
3798 GetPixelAlpha(image,p+(u+j)*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003799 gamma+=(*k);
3800 k++;
3801 }
3802 }
cristyeaedf062010-05-29 22:36:02 +00003803 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003804 }
3805 if (gamma != 0.0)
3806 {
3807 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
3808 gamma);
cristy4c08aed2011-07-01 19:47:50 +00003809 SetPixelAlpha(blur_image,ClampToQuantum(gamma*pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003810 }
3811 }
3812 }
3813 else
3814 {
3815 MagickRealType
3816 alpha;
3817
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 alpha=(MagickRealType) (QuantumScale*
3828 GetPixelAlpha(image,p+(u+j)*GetPixelChannels(image)));
3829 pixel.red+=(*k)*alpha*
3830 GetPixelRed(image,p+(u+j)*GetPixelChannels(image));
3831 pixel.green+=(*k)*alpha*GetPixelGreen(image,p+(u+j)*
3832 GetPixelChannels(image));
3833 pixel.blue+=(*k)*alpha*GetPixelBlue(image,p+(u+j)*
3834 GetPixelChannels(image));
3835 pixel.alpha+=(*k)*GetPixelAlpha(image,p+(u+j)*
3836 GetPixelChannels(image));
3837 if (image->colorspace == CMYKColorspace)
3838 pixel.black+=(*k)*GetPixelBlack(image,p+(u+j)*
3839 GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003840 gamma+=(*k)*alpha;
3841 k++;
3842 }
3843 }
cristyeaedf062010-05-29 22:36:02 +00003844 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003845 }
3846 if (gamma != 0.0)
3847 {
3848 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy2b9582a2011-07-04 17:38:56 +00003849 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003850 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00003851 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003852 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00003853 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003854 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00003855 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00003856 (image->colorspace == CMYKColorspace))
3857 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00003858 }
cristy2b9582a2011-07-04 17:38:56 +00003859 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003860 {
3861 gamma=0.0;
3862 j=0;
cristybb503372010-05-27 20:51:26 +00003863 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003864 {
cristybb503372010-05-27 20:51:26 +00003865 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003866 {
cristy4c08aed2011-07-01 19:47:50 +00003867 contrast=GetPixelIntensity(image,p+(u+j)*
3868 GetPixelChannels(image))-(double)
3869 GetPixelIntensity(blur_image,q);
3870 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003871 {
cristy4c08aed2011-07-01 19:47:50 +00003872 pixel.alpha+=(*k)*
3873 GetPixelAlpha(image,p+(u+j)*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003874 gamma+=(*k);
3875 k++;
3876 }
3877 }
cristyeaedf062010-05-29 22:36:02 +00003878 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003879 }
3880 if (gamma != 0.0)
3881 {
3882 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
3883 gamma);
cristy4c08aed2011-07-01 19:47:50 +00003884 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003885 }
3886 }
3887 }
cristy4c08aed2011-07-01 19:47:50 +00003888 p+=GetPixelChannels(image);
3889 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003890 }
3891 sync=SyncCacheViewAuthenticPixels(blur_view,exception);
3892 if (sync == MagickFalse)
3893 status=MagickFalse;
3894 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3895 {
3896 MagickBooleanType
3897 proceed;
3898
cristyb5d5f722009-11-04 03:03:49 +00003899#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003900 #pragma omp critical (MagickCore_SelectiveBlurImageChannel)
3901#endif
3902 proceed=SetImageProgress(image,SelectiveBlurImageTag,progress++,
3903 image->rows);
3904 if (proceed == MagickFalse)
3905 status=MagickFalse;
3906 }
3907 }
3908 blur_image->type=image->type;
3909 blur_view=DestroyCacheView(blur_view);
3910 image_view=DestroyCacheView(image_view);
3911 kernel=(double *) RelinquishMagickMemory(kernel);
3912 if (status == MagickFalse)
3913 blur_image=DestroyImage(blur_image);
3914 return(blur_image);
3915}
3916
3917/*
3918%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3919% %
3920% %
3921% %
3922% S h a d e I m a g e %
3923% %
3924% %
3925% %
3926%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3927%
3928% ShadeImage() shines a distant light on an image to create a
3929% three-dimensional effect. You control the positioning of the light with
3930% azimuth and elevation; azimuth is measured in degrees off the x axis
3931% and elevation is measured in pixels above the Z axis.
3932%
3933% The format of the ShadeImage method is:
3934%
3935% Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3936% const double azimuth,const double elevation,ExceptionInfo *exception)
3937%
3938% A description of each parameter follows:
3939%
3940% o image: the image.
3941%
3942% o gray: A value other than zero shades the intensity of each pixel.
3943%
3944% o azimuth, elevation: Define the light source direction.
3945%
3946% o exception: return any errors or warnings in this structure.
3947%
3948*/
3949MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3950 const double azimuth,const double elevation,ExceptionInfo *exception)
3951{
3952#define ShadeImageTag "Shade/Image"
3953
cristyc4c8d132010-01-07 01:58:38 +00003954 CacheView
3955 *image_view,
3956 *shade_view;
3957
cristy3ed852e2009-09-05 21:47:34 +00003958 Image
3959 *shade_image;
3960
cristy3ed852e2009-09-05 21:47:34 +00003961 MagickBooleanType
3962 status;
3963
cristybb503372010-05-27 20:51:26 +00003964 MagickOffsetType
3965 progress;
3966
cristy3ed852e2009-09-05 21:47:34 +00003967 PrimaryInfo
3968 light;
3969
cristybb503372010-05-27 20:51:26 +00003970 ssize_t
3971 y;
3972
cristy3ed852e2009-09-05 21:47:34 +00003973 /*
3974 Initialize shaded image attributes.
3975 */
3976 assert(image != (const Image *) NULL);
3977 assert(image->signature == MagickSignature);
3978 if (image->debug != MagickFalse)
3979 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3980 assert(exception != (ExceptionInfo *) NULL);
3981 assert(exception->signature == MagickSignature);
3982 shade_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
3983 if (shade_image == (Image *) NULL)
3984 return((Image *) NULL);
3985 if (SetImageStorageClass(shade_image,DirectClass) == MagickFalse)
3986 {
3987 InheritException(exception,&shade_image->exception);
3988 shade_image=DestroyImage(shade_image);
3989 return((Image *) NULL);
3990 }
3991 /*
3992 Compute the light vector.
3993 */
3994 light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))*
3995 cos(DegreesToRadians(elevation));
3996 light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))*
3997 cos(DegreesToRadians(elevation));
3998 light.z=(double) QuantumRange*sin(DegreesToRadians(elevation));
3999 /*
4000 Shade image.
4001 */
4002 status=MagickTrue;
4003 progress=0;
4004 image_view=AcquireCacheView(image);
4005 shade_view=AcquireCacheView(shade_image);
cristyb5d5f722009-11-04 03:03:49 +00004006#if defined(MAGICKCORE_OPENMP_SUPPORT)
4007 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004008#endif
cristybb503372010-05-27 20:51:26 +00004009 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00004010 {
4011 MagickRealType
4012 distance,
4013 normal_distance,
4014 shade;
4015
4016 PrimaryInfo
4017 normal;
4018
cristy4c08aed2011-07-01 19:47:50 +00004019 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00004020 *restrict p,
4021 *restrict s0,
4022 *restrict s1,
4023 *restrict s2;
cristy3ed852e2009-09-05 21:47:34 +00004024
cristy4c08aed2011-07-01 19:47:50 +00004025 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00004026 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004027
cristy117ff172010-08-15 21:35:32 +00004028 register ssize_t
4029 x;
4030
cristy3ed852e2009-09-05 21:47:34 +00004031 if (status == MagickFalse)
4032 continue;
4033 p=GetCacheViewVirtualPixels(image_view,-1,y-1,image->columns+2,3,exception);
4034 q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1,
4035 exception);
cristy4c08aed2011-07-01 19:47:50 +00004036 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00004037 {
4038 status=MagickFalse;
4039 continue;
4040 }
4041 /*
4042 Shade this row of pixels.
4043 */
4044 normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */
cristy4c08aed2011-07-01 19:47:50 +00004045 s0=p+GetPixelChannels(image);
4046 s1=s0+(image->columns+2)*GetPixelChannels(image);
4047 s2=s1+(image->columns+2)*GetPixelChannels(image);
cristybb503372010-05-27 20:51:26 +00004048 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004049 {
4050 /*
4051 Determine the surface normal and compute shading.
4052 */
cristy4c08aed2011-07-01 19:47:50 +00004053 normal.x=(double) (GetPixelIntensity(image,s0-GetPixelChannels(image))+
4054 GetPixelIntensity(image,s1-GetPixelChannels(image))+
4055 GetPixelIntensity(image,s2-GetPixelChannels(image))-
4056 GetPixelIntensity(image,s0+GetPixelChannels(image))-
4057 GetPixelIntensity(image,s1+GetPixelChannels(image))-
4058 GetPixelIntensity(image,s2+GetPixelChannels(image)));
4059 normal.y=(double) (GetPixelIntensity(image,s2-GetPixelChannels(image))+
4060 GetPixelIntensity(image,s2)+
4061 GetPixelIntensity(image,s2+GetPixelChannels(image))-
4062 GetPixelIntensity(image,s0-GetPixelChannels(image))-
4063 GetPixelIntensity(image,s0)-
4064 GetPixelIntensity(image,s0+GetPixelChannels(image)));
cristy3ed852e2009-09-05 21:47:34 +00004065 if ((normal.x == 0.0) && (normal.y == 0.0))
4066 shade=light.z;
4067 else
4068 {
4069 shade=0.0;
4070 distance=normal.x*light.x+normal.y*light.y+normal.z*light.z;
4071 if (distance > MagickEpsilon)
4072 {
4073 normal_distance=
4074 normal.x*normal.x+normal.y*normal.y+normal.z*normal.z;
4075 if (normal_distance > (MagickEpsilon*MagickEpsilon))
4076 shade=distance/sqrt((double) normal_distance);
4077 }
4078 }
4079 if (gray != MagickFalse)
4080 {
cristy4c08aed2011-07-01 19:47:50 +00004081 SetPixelRed(shade_image,ClampToQuantum(shade),q);
4082 SetPixelGreen(shade_image,ClampToQuantum(shade),q);
4083 SetPixelBlue(shade_image,ClampToQuantum(shade),q);
cristy3ed852e2009-09-05 21:47:34 +00004084 }
4085 else
4086 {
cristy4c08aed2011-07-01 19:47:50 +00004087 SetPixelRed(shade_image,ClampToQuantum(QuantumScale*shade*
4088 GetPixelRed(image,s1)),q);
4089 SetPixelGreen(shade_image,ClampToQuantum(QuantumScale*shade*
4090 GetPixelGreen(image,s1)),q);
4091 SetPixelBlue(shade_image,ClampToQuantum(QuantumScale*shade*
4092 GetPixelBlue(image,s1)),q);
cristy3ed852e2009-09-05 21:47:34 +00004093 }
cristy4c08aed2011-07-01 19:47:50 +00004094 SetPixelAlpha(shade_image,GetPixelAlpha(image,s1),q);
4095 s0+=GetPixelChannels(image);
4096 s1+=GetPixelChannels(image);
4097 s2+=GetPixelChannels(image);
4098 q+=GetPixelChannels(shade_image);
cristy3ed852e2009-09-05 21:47:34 +00004099 }
4100 if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
4101 status=MagickFalse;
4102 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4103 {
4104 MagickBooleanType
4105 proceed;
4106
cristyb5d5f722009-11-04 03:03:49 +00004107#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004108 #pragma omp critical (MagickCore_ShadeImage)
4109#endif
4110 proceed=SetImageProgress(image,ShadeImageTag,progress++,image->rows);
4111 if (proceed == MagickFalse)
4112 status=MagickFalse;
4113 }
4114 }
4115 shade_view=DestroyCacheView(shade_view);
4116 image_view=DestroyCacheView(image_view);
4117 if (status == MagickFalse)
4118 shade_image=DestroyImage(shade_image);
4119 return(shade_image);
4120}
4121
4122/*
4123%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4124% %
4125% %
4126% %
4127% S h a r p e n I m a g e %
4128% %
4129% %
4130% %
4131%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4132%
4133% SharpenImage() sharpens the image. We convolve the image with a Gaussian
4134% operator of the given radius and standard deviation (sigma). For
4135% reasonable results, radius should be larger than sigma. Use a radius of 0
4136% and SharpenImage() selects a suitable radius for you.
4137%
4138% Using a separable kernel would be faster, but the negative weights cancel
4139% out on the corners of the kernel producing often undesirable ringing in the
4140% filtered result; this can be avoided by using a 2D gaussian shaped image
4141% sharpening kernel instead.
4142%
4143% The format of the SharpenImage method is:
4144%
4145% Image *SharpenImage(const Image *image,const double radius,
4146% const double sigma,ExceptionInfo *exception)
4147% Image *SharpenImageChannel(const Image *image,const ChannelType channel,
4148% const double radius,const double sigma,ExceptionInfo *exception)
4149%
4150% A description of each parameter follows:
4151%
4152% o image: the image.
4153%
4154% o channel: the channel type.
4155%
4156% o radius: the radius of the Gaussian, in pixels, not counting the center
4157% pixel.
4158%
4159% o sigma: the standard deviation of the Laplacian, in pixels.
4160%
4161% o exception: return any errors or warnings in this structure.
4162%
4163*/
4164
4165MagickExport Image *SharpenImage(const Image *image,const double radius,
4166 const double sigma,ExceptionInfo *exception)
4167{
4168 Image
4169 *sharp_image;
4170
4171 sharp_image=SharpenImageChannel(image,DefaultChannels,radius,sigma,exception);
4172 return(sharp_image);
4173}
4174
4175MagickExport Image *SharpenImageChannel(const Image *image,
4176 const ChannelType channel,const double radius,const double sigma,
4177 ExceptionInfo *exception)
4178{
4179 double
cristy47e00502009-12-17 19:19:57 +00004180 *kernel,
4181 normalize;
cristy3ed852e2009-09-05 21:47:34 +00004182
4183 Image
4184 *sharp_image;
4185
cristybb503372010-05-27 20:51:26 +00004186 register ssize_t
cristy47e00502009-12-17 19:19:57 +00004187 i;
4188
cristybb503372010-05-27 20:51:26 +00004189 size_t
cristy3ed852e2009-09-05 21:47:34 +00004190 width;
4191
cristy117ff172010-08-15 21:35:32 +00004192 ssize_t
4193 j,
4194 u,
4195 v;
4196
cristy3ed852e2009-09-05 21:47:34 +00004197 assert(image != (const Image *) NULL);
4198 assert(image->signature == MagickSignature);
4199 if (image->debug != MagickFalse)
4200 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4201 assert(exception != (ExceptionInfo *) NULL);
4202 assert(exception->signature == MagickSignature);
4203 width=GetOptimalKernelWidth2D(radius,sigma);
4204 kernel=(double *) AcquireQuantumMemory((size_t) width*width,sizeof(*kernel));
4205 if (kernel == (double *) NULL)
4206 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy3ed852e2009-09-05 21:47:34 +00004207 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00004208 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +00004209 i=0;
4210 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00004211 {
cristy47e00502009-12-17 19:19:57 +00004212 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00004213 {
cristy4205a3c2010-09-12 20:19:59 +00004214 kernel[i]=(double) (-exp(-((double) u*u+v*v)/(2.0*MagickSigma*
4215 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00004216 normalize+=kernel[i];
4217 i++;
4218 }
4219 }
4220 kernel[i/2]=(double) ((-2.0)*normalize);
4221 sharp_image=ConvolveImageChannel(image,channel,width,kernel,exception);
4222 kernel=(double *) RelinquishMagickMemory(kernel);
4223 return(sharp_image);
4224}
4225
4226/*
4227%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4228% %
4229% %
4230% %
4231% S p r e a d I m a g e %
4232% %
4233% %
4234% %
4235%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4236%
4237% SpreadImage() is a special effects method that randomly displaces each
4238% pixel in a block defined by the radius parameter.
4239%
4240% The format of the SpreadImage method is:
4241%
4242% Image *SpreadImage(const Image *image,const double radius,
4243% ExceptionInfo *exception)
4244%
4245% A description of each parameter follows:
4246%
4247% o image: the image.
4248%
4249% o radius: Choose a random pixel in a neighborhood of this extent.
4250%
4251% o exception: return any errors or warnings in this structure.
4252%
4253*/
4254MagickExport Image *SpreadImage(const Image *image,const double radius,
4255 ExceptionInfo *exception)
4256{
4257#define SpreadImageTag "Spread/Image"
4258
cristyfa112112010-01-04 17:48:07 +00004259 CacheView
cristy9f7e7cb2011-03-26 00:49:57 +00004260 *image_view,
4261 *spread_view;
cristyfa112112010-01-04 17:48:07 +00004262
cristy3ed852e2009-09-05 21:47:34 +00004263 Image
4264 *spread_image;
4265
cristy3ed852e2009-09-05 21:47:34 +00004266 MagickBooleanType
4267 status;
4268
cristybb503372010-05-27 20:51:26 +00004269 MagickOffsetType
4270 progress;
4271
cristy4c08aed2011-07-01 19:47:50 +00004272 PixelInfo
cristyddd82202009-11-03 20:14:50 +00004273 bias;
cristy3ed852e2009-09-05 21:47:34 +00004274
4275 RandomInfo
cristyfa112112010-01-04 17:48:07 +00004276 **restrict random_info;
cristy3ed852e2009-09-05 21:47:34 +00004277
cristybb503372010-05-27 20:51:26 +00004278 size_t
cristy3ed852e2009-09-05 21:47:34 +00004279 width;
4280
cristybb503372010-05-27 20:51:26 +00004281 ssize_t
4282 y;
4283
cristy3ed852e2009-09-05 21:47:34 +00004284 /*
4285 Initialize spread image attributes.
4286 */
4287 assert(image != (Image *) NULL);
4288 assert(image->signature == MagickSignature);
4289 if (image->debug != MagickFalse)
4290 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4291 assert(exception != (ExceptionInfo *) NULL);
4292 assert(exception->signature == MagickSignature);
4293 spread_image=CloneImage(image,image->columns,image->rows,MagickTrue,
4294 exception);
4295 if (spread_image == (Image *) NULL)
4296 return((Image *) NULL);
4297 if (SetImageStorageClass(spread_image,DirectClass) == MagickFalse)
4298 {
4299 InheritException(exception,&spread_image->exception);
4300 spread_image=DestroyImage(spread_image);
4301 return((Image *) NULL);
4302 }
4303 /*
4304 Spread image.
4305 */
4306 status=MagickTrue;
4307 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00004308 GetPixelInfo(spread_image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00004309 width=GetOptimalKernelWidth1D(radius,0.5);
cristy3ed852e2009-09-05 21:47:34 +00004310 random_info=AcquireRandomInfoThreadSet();
cristy9f7e7cb2011-03-26 00:49:57 +00004311 image_view=AcquireCacheView(image);
4312 spread_view=AcquireCacheView(spread_image);
cristyb557a152011-02-22 12:14:30 +00004313#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00004314 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00004315#endif
cristybb503372010-05-27 20:51:26 +00004316 for (y=0; y < (ssize_t) spread_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00004317 {
cristy5c9e6f22010-09-17 17:31:01 +00004318 const int
4319 id = GetOpenMPThreadId();
cristy6ebe97c2010-07-03 01:17:28 +00004320
cristy4c08aed2011-07-01 19:47:50 +00004321 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00004322 pixel;
4323
cristy4c08aed2011-07-01 19:47:50 +00004324 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00004325 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004326
cristy117ff172010-08-15 21:35:32 +00004327 register ssize_t
4328 x;
4329
cristy3ed852e2009-09-05 21:47:34 +00004330 if (status == MagickFalse)
4331 continue;
cristy9f7e7cb2011-03-26 00:49:57 +00004332 q=QueueCacheViewAuthenticPixels(spread_view,0,y,spread_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +00004333 exception);
cristy4c08aed2011-07-01 19:47:50 +00004334 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00004335 {
4336 status=MagickFalse;
4337 continue;
4338 }
cristyddd82202009-11-03 20:14:50 +00004339 pixel=bias;
cristybb503372010-05-27 20:51:26 +00004340 for (x=0; x < (ssize_t) spread_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004341 {
cristy4c08aed2011-07-01 19:47:50 +00004342 (void) InterpolatePixelInfo(image,image_view,
cristy8a7c3e82011-03-26 02:10:53 +00004343 UndefinedInterpolatePixel,(double) x+width*(GetPseudoRandomValue(
4344 random_info[id])-0.5),(double) y+width*(GetPseudoRandomValue(
4345 random_info[id])-0.5),&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00004346 SetPixelPixelInfo(spread_image,&pixel,q);
4347 q+=GetPixelChannels(spread_image);
cristy3ed852e2009-09-05 21:47:34 +00004348 }
cristy9f7e7cb2011-03-26 00:49:57 +00004349 if (SyncCacheViewAuthenticPixels(spread_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00004350 status=MagickFalse;
4351 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4352 {
4353 MagickBooleanType
4354 proceed;
4355
cristyb557a152011-02-22 12:14:30 +00004356#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004357 #pragma omp critical (MagickCore_SpreadImage)
4358#endif
4359 proceed=SetImageProgress(image,SpreadImageTag,progress++,image->rows);
4360 if (proceed == MagickFalse)
4361 status=MagickFalse;
4362 }
4363 }
cristy9f7e7cb2011-03-26 00:49:57 +00004364 spread_view=DestroyCacheView(spread_view);
cristy3ed852e2009-09-05 21:47:34 +00004365 image_view=DestroyCacheView(image_view);
4366 random_info=DestroyRandomInfoThreadSet(random_info);
cristy3ed852e2009-09-05 21:47:34 +00004367 return(spread_image);
4368}
4369
4370/*
4371%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4372% %
4373% %
4374% %
cristy0834d642011-03-18 18:26:08 +00004375% S t a t i s t i c I m a g e %
4376% %
4377% %
4378% %
4379%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4380%
4381% StatisticImage() makes each pixel the min / max / median / mode / etc. of
cristy8d752042011-03-19 01:00:36 +00004382% the neighborhood of the specified width and height.
cristy0834d642011-03-18 18:26:08 +00004383%
4384% The format of the StatisticImage method is:
4385%
4386% Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00004387% const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00004388% Image *StatisticImageChannel(const Image *image,
4389% const ChannelType channel,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00004390% const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00004391%
4392% A description of each parameter follows:
4393%
4394% o image: the image.
4395%
4396% o channel: the image channel.
4397%
4398% o type: the statistic type (median, mode, etc.).
4399%
cristy95c38342011-03-18 22:39:51 +00004400% o width: the width of the pixel neighborhood.
4401%
4402% o height: the height of the pixel neighborhood.
cristy0834d642011-03-18 18:26:08 +00004403%
4404% o exception: return any errors or warnings in this structure.
4405%
4406*/
4407
cristy733678d2011-03-18 21:29:28 +00004408#define ListChannels 5
4409
4410typedef struct _ListNode
4411{
4412 size_t
4413 next[9],
4414 count,
4415 signature;
4416} ListNode;
4417
4418typedef struct _SkipList
4419{
4420 ssize_t
4421 level;
4422
4423 ListNode
4424 *nodes;
4425} SkipList;
4426
4427typedef struct _PixelList
4428{
4429 size_t
cristy6fc86bb2011-03-18 23:45:16 +00004430 length,
cristy733678d2011-03-18 21:29:28 +00004431 seed,
4432 signature;
4433
4434 SkipList
4435 lists[ListChannels];
4436} PixelList;
4437
4438static PixelList *DestroyPixelList(PixelList *pixel_list)
4439{
4440 register ssize_t
4441 i;
4442
4443 if (pixel_list == (PixelList *) NULL)
4444 return((PixelList *) NULL);
4445 for (i=0; i < ListChannels; i++)
4446 if (pixel_list->lists[i].nodes != (ListNode *) NULL)
4447 pixel_list->lists[i].nodes=(ListNode *) RelinquishMagickMemory(
4448 pixel_list->lists[i].nodes);
4449 pixel_list=(PixelList *) RelinquishMagickMemory(pixel_list);
4450 return(pixel_list);
4451}
4452
4453static PixelList **DestroyPixelListThreadSet(PixelList **pixel_list)
4454{
4455 register ssize_t
4456 i;
4457
4458 assert(pixel_list != (PixelList **) NULL);
4459 for (i=0; i < (ssize_t) GetOpenMPMaximumThreads(); i++)
4460 if (pixel_list[i] != (PixelList *) NULL)
4461 pixel_list[i]=DestroyPixelList(pixel_list[i]);
4462 pixel_list=(PixelList **) RelinquishMagickMemory(pixel_list);
4463 return(pixel_list);
4464}
4465
cristy6fc86bb2011-03-18 23:45:16 +00004466static PixelList *AcquirePixelList(const size_t width,const size_t height)
cristy733678d2011-03-18 21:29:28 +00004467{
4468 PixelList
4469 *pixel_list;
4470
4471 register ssize_t
4472 i;
4473
4474 pixel_list=(PixelList *) AcquireMagickMemory(sizeof(*pixel_list));
4475 if (pixel_list == (PixelList *) NULL)
4476 return(pixel_list);
4477 (void) ResetMagickMemory((void *) pixel_list,0,sizeof(*pixel_list));
cristy6fc86bb2011-03-18 23:45:16 +00004478 pixel_list->length=width*height;
cristy733678d2011-03-18 21:29:28 +00004479 for (i=0; i < ListChannels; i++)
4480 {
4481 pixel_list->lists[i].nodes=(ListNode *) AcquireQuantumMemory(65537UL,
4482 sizeof(*pixel_list->lists[i].nodes));
4483 if (pixel_list->lists[i].nodes == (ListNode *) NULL)
4484 return(DestroyPixelList(pixel_list));
4485 (void) ResetMagickMemory(pixel_list->lists[i].nodes,0,65537UL*
4486 sizeof(*pixel_list->lists[i].nodes));
4487 }
4488 pixel_list->signature=MagickSignature;
4489 return(pixel_list);
4490}
4491
cristy6fc86bb2011-03-18 23:45:16 +00004492static PixelList **AcquirePixelListThreadSet(const size_t width,
4493 const size_t height)
cristy733678d2011-03-18 21:29:28 +00004494{
4495 PixelList
4496 **pixel_list;
4497
4498 register ssize_t
4499 i;
4500
4501 size_t
4502 number_threads;
4503
4504 number_threads=GetOpenMPMaximumThreads();
4505 pixel_list=(PixelList **) AcquireQuantumMemory(number_threads,
4506 sizeof(*pixel_list));
4507 if (pixel_list == (PixelList **) NULL)
4508 return((PixelList **) NULL);
4509 (void) ResetMagickMemory(pixel_list,0,number_threads*sizeof(*pixel_list));
4510 for (i=0; i < (ssize_t) number_threads; i++)
4511 {
cristy6fc86bb2011-03-18 23:45:16 +00004512 pixel_list[i]=AcquirePixelList(width,height);
cristy733678d2011-03-18 21:29:28 +00004513 if (pixel_list[i] == (PixelList *) NULL)
4514 return(DestroyPixelListThreadSet(pixel_list));
4515 }
4516 return(pixel_list);
4517}
4518
4519static void AddNodePixelList(PixelList *pixel_list,const ssize_t channel,
4520 const size_t color)
4521{
4522 register SkipList
4523 *list;
4524
4525 register ssize_t
4526 level;
4527
4528 size_t
4529 search,
4530 update[9];
4531
4532 /*
4533 Initialize the node.
4534 */
4535 list=pixel_list->lists+channel;
4536 list->nodes[color].signature=pixel_list->signature;
4537 list->nodes[color].count=1;
4538 /*
4539 Determine where it belongs in the list.
4540 */
4541 search=65536UL;
4542 for (level=list->level; level >= 0; level--)
4543 {
4544 while (list->nodes[search].next[level] < color)
4545 search=list->nodes[search].next[level];
4546 update[level]=search;
4547 }
4548 /*
4549 Generate a pseudo-random level for this node.
4550 */
4551 for (level=0; ; level++)
4552 {
4553 pixel_list->seed=(pixel_list->seed*42893621L)+1L;
4554 if ((pixel_list->seed & 0x300) != 0x300)
4555 break;
4556 }
4557 if (level > 8)
4558 level=8;
4559 if (level > (list->level+2))
4560 level=list->level+2;
4561 /*
4562 If we're raising the list's level, link back to the root node.
4563 */
4564 while (level > list->level)
4565 {
4566 list->level++;
4567 update[list->level]=65536UL;
4568 }
4569 /*
4570 Link the node into the skip-list.
4571 */
4572 do
4573 {
4574 list->nodes[color].next[level]=list->nodes[update[level]].next[level];
4575 list->nodes[update[level]].next[level]=color;
cristy3cba8ca2011-03-19 01:29:12 +00004576 } while (level-- > 0);
cristy733678d2011-03-18 21:29:28 +00004577}
4578
cristy4c08aed2011-07-01 19:47:50 +00004579static PixelInfo GetMaximumPixelList(PixelList *pixel_list)
cristy6fc86bb2011-03-18 23:45:16 +00004580{
cristy4c08aed2011-07-01 19:47:50 +00004581 PixelInfo
cristy6fc86bb2011-03-18 23:45:16 +00004582 pixel;
4583
4584 register SkipList
4585 *list;
4586
4587 register ssize_t
4588 channel;
4589
4590 size_t
cristyd76c51e2011-03-26 00:21:26 +00004591 color,
4592 maximum;
cristy49f37242011-03-22 18:18:23 +00004593
4594 ssize_t
cristy6fc86bb2011-03-18 23:45:16 +00004595 count;
4596
4597 unsigned short
cristyd76c51e2011-03-26 00:21:26 +00004598 channels[ListChannels];
cristy6fc86bb2011-03-18 23:45:16 +00004599
4600 /*
4601 Find the maximum value for each of the color.
4602 */
4603 for (channel=0; channel < 5; channel++)
4604 {
4605 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004606 color=65536L;
cristy6fc86bb2011-03-18 23:45:16 +00004607 count=0;
cristy49f37242011-03-22 18:18:23 +00004608 maximum=list->nodes[color].next[0];
cristy6fc86bb2011-03-18 23:45:16 +00004609 do
4610 {
4611 color=list->nodes[color].next[0];
cristy49f37242011-03-22 18:18:23 +00004612 if (color > maximum)
4613 maximum=color;
cristy6fc86bb2011-03-18 23:45:16 +00004614 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004615 } while (count < (ssize_t) pixel_list->length);
cristy49f37242011-03-22 18:18:23 +00004616 channels[channel]=(unsigned short) maximum;
4617 }
cristy4c08aed2011-07-01 19:47:50 +00004618 GetPixelInfo((const Image *) NULL,&pixel);
cristy49f37242011-03-22 18:18:23 +00004619 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4620 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4621 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004622 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4623 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy49f37242011-03-22 18:18:23 +00004624 return(pixel);
4625}
4626
cristy4c08aed2011-07-01 19:47:50 +00004627static PixelInfo GetMeanPixelList(PixelList *pixel_list)
cristy49f37242011-03-22 18:18:23 +00004628{
cristy4c08aed2011-07-01 19:47:50 +00004629 PixelInfo
cristy49f37242011-03-22 18:18:23 +00004630 pixel;
4631
cristy80a99a32011-03-30 01:30:23 +00004632 MagickRealType
4633 sum;
4634
cristy49f37242011-03-22 18:18:23 +00004635 register SkipList
4636 *list;
4637
4638 register ssize_t
4639 channel;
4640
4641 size_t
cristy80a99a32011-03-30 01:30:23 +00004642 color;
cristy49f37242011-03-22 18:18:23 +00004643
4644 ssize_t
4645 count;
4646
4647 unsigned short
4648 channels[ListChannels];
4649
4650 /*
4651 Find the mean value for each of the color.
4652 */
4653 for (channel=0; channel < 5; channel++)
4654 {
4655 list=pixel_list->lists+channel;
4656 color=65536L;
4657 count=0;
cristy80a99a32011-03-30 01:30:23 +00004658 sum=0.0;
cristy49f37242011-03-22 18:18:23 +00004659 do
4660 {
4661 color=list->nodes[color].next[0];
cristy80a99a32011-03-30 01:30:23 +00004662 sum+=(MagickRealType) list->nodes[color].count*color;
cristy49f37242011-03-22 18:18:23 +00004663 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004664 } while (count < (ssize_t) pixel_list->length);
cristy80a99a32011-03-30 01:30:23 +00004665 sum/=pixel_list->length;
4666 channels[channel]=(unsigned short) sum;
cristy6fc86bb2011-03-18 23:45:16 +00004667 }
cristy4c08aed2011-07-01 19:47:50 +00004668 GetPixelInfo((const Image *) NULL,&pixel);
cristy6fc86bb2011-03-18 23:45:16 +00004669 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4670 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4671 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004672 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4673 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy6fc86bb2011-03-18 23:45:16 +00004674 return(pixel);
4675}
4676
cristy4c08aed2011-07-01 19:47:50 +00004677static PixelInfo GetMedianPixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004678{
cristy4c08aed2011-07-01 19:47:50 +00004679 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004680 pixel;
4681
4682 register SkipList
4683 *list;
4684
4685 register ssize_t
4686 channel;
4687
4688 size_t
cristy49f37242011-03-22 18:18:23 +00004689 color;
4690
4691 ssize_t
cristy733678d2011-03-18 21:29:28 +00004692 count;
4693
4694 unsigned short
4695 channels[ListChannels];
4696
4697 /*
4698 Find the median value for each of the color.
4699 */
cristy733678d2011-03-18 21:29:28 +00004700 for (channel=0; channel < 5; channel++)
4701 {
4702 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004703 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004704 count=0;
4705 do
4706 {
4707 color=list->nodes[color].next[0];
4708 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004709 } while (count <= (ssize_t) (pixel_list->length >> 1));
cristy6fc86bb2011-03-18 23:45:16 +00004710 channels[channel]=(unsigned short) color;
4711 }
cristy4c08aed2011-07-01 19:47:50 +00004712 GetPixelInfo((const Image *) NULL,&pixel);
cristy6fc86bb2011-03-18 23:45:16 +00004713 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4714 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4715 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004716 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4717 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy6fc86bb2011-03-18 23:45:16 +00004718 return(pixel);
4719}
4720
cristy4c08aed2011-07-01 19:47:50 +00004721static PixelInfo GetMinimumPixelList(PixelList *pixel_list)
cristy6fc86bb2011-03-18 23:45:16 +00004722{
cristy4c08aed2011-07-01 19:47:50 +00004723 PixelInfo
cristy6fc86bb2011-03-18 23:45:16 +00004724 pixel;
4725
4726 register SkipList
4727 *list;
4728
4729 register ssize_t
4730 channel;
4731
4732 size_t
cristyd76c51e2011-03-26 00:21:26 +00004733 color,
4734 minimum;
cristy6fc86bb2011-03-18 23:45:16 +00004735
cristy49f37242011-03-22 18:18:23 +00004736 ssize_t
4737 count;
4738
cristy6fc86bb2011-03-18 23:45:16 +00004739 unsigned short
cristyd76c51e2011-03-26 00:21:26 +00004740 channels[ListChannels];
cristy6fc86bb2011-03-18 23:45:16 +00004741
4742 /*
4743 Find the minimum value for each of the color.
4744 */
4745 for (channel=0; channel < 5; channel++)
4746 {
4747 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004748 count=0;
cristy6fc86bb2011-03-18 23:45:16 +00004749 color=65536UL;
cristy49f37242011-03-22 18:18:23 +00004750 minimum=list->nodes[color].next[0];
4751 do
4752 {
4753 color=list->nodes[color].next[0];
4754 if (color < minimum)
4755 minimum=color;
4756 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004757 } while (count < (ssize_t) pixel_list->length);
cristy49f37242011-03-22 18:18:23 +00004758 channels[channel]=(unsigned short) minimum;
cristy733678d2011-03-18 21:29:28 +00004759 }
cristy4c08aed2011-07-01 19:47:50 +00004760 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004761 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4762 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4763 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004764 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4765 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy733678d2011-03-18 21:29:28 +00004766 return(pixel);
4767}
4768
cristy4c08aed2011-07-01 19:47:50 +00004769static PixelInfo GetModePixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004770{
cristy4c08aed2011-07-01 19:47:50 +00004771 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004772 pixel;
4773
4774 register SkipList
4775 *list;
4776
4777 register ssize_t
4778 channel;
4779
4780 size_t
4781 color,
cristy733678d2011-03-18 21:29:28 +00004782 max_count,
cristy6fc86bb2011-03-18 23:45:16 +00004783 mode;
cristy733678d2011-03-18 21:29:28 +00004784
cristy49f37242011-03-22 18:18:23 +00004785 ssize_t
4786 count;
4787
cristy733678d2011-03-18 21:29:28 +00004788 unsigned short
4789 channels[5];
4790
4791 /*
glennrp30d2dc62011-06-25 03:17:16 +00004792 Make each pixel the 'predominant color' of the specified neighborhood.
cristy733678d2011-03-18 21:29:28 +00004793 */
cristy733678d2011-03-18 21:29:28 +00004794 for (channel=0; channel < 5; channel++)
4795 {
4796 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004797 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004798 mode=color;
4799 max_count=list->nodes[mode].count;
4800 count=0;
4801 do
4802 {
4803 color=list->nodes[color].next[0];
4804 if (list->nodes[color].count > max_count)
4805 {
4806 mode=color;
4807 max_count=list->nodes[mode].count;
4808 }
4809 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004810 } while (count < (ssize_t) pixel_list->length);
cristy733678d2011-03-18 21:29:28 +00004811 channels[channel]=(unsigned short) mode;
4812 }
cristy4c08aed2011-07-01 19:47:50 +00004813 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004814 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4815 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4816 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004817 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4818 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy733678d2011-03-18 21:29:28 +00004819 return(pixel);
4820}
4821
cristy4c08aed2011-07-01 19:47:50 +00004822static PixelInfo GetNonpeakPixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004823{
cristy4c08aed2011-07-01 19:47:50 +00004824 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004825 pixel;
4826
4827 register SkipList
4828 *list;
4829
4830 register ssize_t
4831 channel;
4832
4833 size_t
cristy733678d2011-03-18 21:29:28 +00004834 color,
cristy733678d2011-03-18 21:29:28 +00004835 next,
4836 previous;
4837
cristy49f37242011-03-22 18:18:23 +00004838 ssize_t
4839 count;
4840
cristy733678d2011-03-18 21:29:28 +00004841 unsigned short
4842 channels[5];
4843
4844 /*
cristy49f37242011-03-22 18:18:23 +00004845 Finds the non peak value for each of the colors.
cristy733678d2011-03-18 21:29:28 +00004846 */
cristy733678d2011-03-18 21:29:28 +00004847 for (channel=0; channel < 5; channel++)
4848 {
4849 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004850 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004851 next=list->nodes[color].next[0];
4852 count=0;
4853 do
4854 {
4855 previous=color;
4856 color=next;
4857 next=list->nodes[color].next[0];
4858 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004859 } while (count <= (ssize_t) (pixel_list->length >> 1));
cristy733678d2011-03-18 21:29:28 +00004860 if ((previous == 65536UL) && (next != 65536UL))
4861 color=next;
4862 else
4863 if ((previous != 65536UL) && (next == 65536UL))
4864 color=previous;
4865 channels[channel]=(unsigned short) color;
4866 }
cristy4c08aed2011-07-01 19:47:50 +00004867 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004868 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4869 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4870 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004871 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4872 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy733678d2011-03-18 21:29:28 +00004873 return(pixel);
4874}
4875
cristy4c08aed2011-07-01 19:47:50 +00004876static PixelInfo GetStandardDeviationPixelList(PixelList *pixel_list)
cristy9a68cbb2011-03-29 00:51:23 +00004877{
cristy4c08aed2011-07-01 19:47:50 +00004878 PixelInfo
cristy9a68cbb2011-03-29 00:51:23 +00004879 pixel;
4880
cristy80a99a32011-03-30 01:30:23 +00004881 MagickRealType
4882 sum,
4883 sum_squared;
4884
cristy9a68cbb2011-03-29 00:51:23 +00004885 register SkipList
4886 *list;
4887
4888 register ssize_t
4889 channel;
4890
4891 size_t
cristy80a99a32011-03-30 01:30:23 +00004892 color;
cristy9a68cbb2011-03-29 00:51:23 +00004893
4894 ssize_t
4895 count;
4896
4897 unsigned short
4898 channels[ListChannels];
4899
4900 /*
cristy80a99a32011-03-30 01:30:23 +00004901 Find the standard-deviation value for each of the color.
cristy9a68cbb2011-03-29 00:51:23 +00004902 */
4903 for (channel=0; channel < 5; channel++)
4904 {
4905 list=pixel_list->lists+channel;
4906 color=65536L;
4907 count=0;
cristy80a99a32011-03-30 01:30:23 +00004908 sum=0.0;
4909 sum_squared=0.0;
cristy9a68cbb2011-03-29 00:51:23 +00004910 do
4911 {
cristy80a99a32011-03-30 01:30:23 +00004912 register ssize_t
4913 i;
4914
cristy9a68cbb2011-03-29 00:51:23 +00004915 color=list->nodes[color].next[0];
cristy80a99a32011-03-30 01:30:23 +00004916 sum+=(MagickRealType) list->nodes[color].count*color;
4917 for (i=0; i < (ssize_t) list->nodes[color].count; i++)
4918 sum_squared+=((MagickRealType) color)*((MagickRealType) color);
cristy9a68cbb2011-03-29 00:51:23 +00004919 count+=list->nodes[color].count;
4920 } while (count < (ssize_t) pixel_list->length);
cristy80a99a32011-03-30 01:30:23 +00004921 sum/=pixel_list->length;
4922 sum_squared/=pixel_list->length;
4923 channels[channel]=(unsigned short) sqrt(sum_squared-(sum*sum));
cristy9a68cbb2011-03-29 00:51:23 +00004924 }
cristy4c08aed2011-07-01 19:47:50 +00004925 GetPixelInfo((const Image *) NULL,&pixel);
cristy9a68cbb2011-03-29 00:51:23 +00004926 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4927 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4928 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004929 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4930 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy9a68cbb2011-03-29 00:51:23 +00004931 return(pixel);
4932}
4933
cristy4c08aed2011-07-01 19:47:50 +00004934static inline void InsertPixelList(const Image *image,const Quantum *pixel,
4935 PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004936{
4937 size_t
4938 signature;
4939
4940 unsigned short
4941 index;
4942
cristy4c08aed2011-07-01 19:47:50 +00004943 index=ScaleQuantumToShort(GetPixelRed(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004944 signature=pixel_list->lists[0].nodes[index].signature;
4945 if (signature == pixel_list->signature)
4946 pixel_list->lists[0].nodes[index].count++;
4947 else
4948 AddNodePixelList(pixel_list,0,index);
cristy4c08aed2011-07-01 19:47:50 +00004949 index=ScaleQuantumToShort(GetPixelGreen(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004950 signature=pixel_list->lists[1].nodes[index].signature;
4951 if (signature == pixel_list->signature)
4952 pixel_list->lists[1].nodes[index].count++;
4953 else
4954 AddNodePixelList(pixel_list,1,index);
cristy4c08aed2011-07-01 19:47:50 +00004955 index=ScaleQuantumToShort(GetPixelBlue(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004956 signature=pixel_list->lists[2].nodes[index].signature;
4957 if (signature == pixel_list->signature)
4958 pixel_list->lists[2].nodes[index].count++;
4959 else
4960 AddNodePixelList(pixel_list,2,index);
cristy4c08aed2011-07-01 19:47:50 +00004961 index=ScaleQuantumToShort(GetPixelAlpha(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004962 signature=pixel_list->lists[3].nodes[index].signature;
4963 if (signature == pixel_list->signature)
4964 pixel_list->lists[3].nodes[index].count++;
4965 else
4966 AddNodePixelList(pixel_list,3,index);
4967 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00004968 index=ScaleQuantumToShort(GetPixelBlack(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004969 signature=pixel_list->lists[4].nodes[index].signature;
4970 if (signature == pixel_list->signature)
4971 pixel_list->lists[4].nodes[index].count++;
4972 else
4973 AddNodePixelList(pixel_list,4,index);
4974}
4975
cristy80c99742011-04-04 14:46:39 +00004976static inline MagickRealType MagickAbsoluteValue(const MagickRealType x)
4977{
4978 if (x < 0)
4979 return(-x);
4980 return(x);
4981}
4982
cristy733678d2011-03-18 21:29:28 +00004983static void ResetPixelList(PixelList *pixel_list)
4984{
4985 int
4986 level;
4987
4988 register ListNode
4989 *root;
4990
4991 register SkipList
4992 *list;
4993
4994 register ssize_t
4995 channel;
4996
4997 /*
4998 Reset the skip-list.
4999 */
5000 for (channel=0; channel < 5; channel++)
5001 {
5002 list=pixel_list->lists+channel;
5003 root=list->nodes+65536UL;
5004 list->level=0;
5005 for (level=0; level < 9; level++)
5006 root->next[level]=65536UL;
5007 }
5008 pixel_list->seed=pixel_list->signature++;
5009}
5010
cristy0834d642011-03-18 18:26:08 +00005011MagickExport Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00005012 const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00005013{
cristy95c38342011-03-18 22:39:51 +00005014 Image
5015 *statistic_image;
5016
5017 statistic_image=StatisticImageChannel(image,DefaultChannels,type,width,
5018 height,exception);
5019 return(statistic_image);
cristy0834d642011-03-18 18:26:08 +00005020}
5021
5022MagickExport Image *StatisticImageChannel(const Image *image,
cristy95c38342011-03-18 22:39:51 +00005023 const ChannelType channel,const StatisticType type,const size_t width,
5024 const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00005025{
cristy3cba8ca2011-03-19 01:29:12 +00005026#define StatisticWidth \
cristyd76c51e2011-03-26 00:21:26 +00005027 (width == 0 ? GetOptimalKernelWidth2D((double) width,0.5) : width)
cristy3cba8ca2011-03-19 01:29:12 +00005028#define StatisticHeight \
cristyd76c51e2011-03-26 00:21:26 +00005029 (height == 0 ? GetOptimalKernelWidth2D((double) height,0.5) : height)
cristy0834d642011-03-18 18:26:08 +00005030#define StatisticImageTag "Statistic/Image"
5031
5032 CacheView
5033 *image_view,
5034 *statistic_view;
5035
5036 Image
5037 *statistic_image;
5038
5039 MagickBooleanType
5040 status;
5041
5042 MagickOffsetType
5043 progress;
5044
5045 PixelList
5046 **restrict pixel_list;
5047
cristy0834d642011-03-18 18:26:08 +00005048 ssize_t
5049 y;
5050
5051 /*
5052 Initialize statistics image attributes.
5053 */
5054 assert(image != (Image *) NULL);
5055 assert(image->signature == MagickSignature);
5056 if (image->debug != MagickFalse)
5057 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5058 assert(exception != (ExceptionInfo *) NULL);
5059 assert(exception->signature == MagickSignature);
cristy0834d642011-03-18 18:26:08 +00005060 statistic_image=CloneImage(image,image->columns,image->rows,MagickTrue,
5061 exception);
5062 if (statistic_image == (Image *) NULL)
5063 return((Image *) NULL);
5064 if (SetImageStorageClass(statistic_image,DirectClass) == MagickFalse)
5065 {
5066 InheritException(exception,&statistic_image->exception);
5067 statistic_image=DestroyImage(statistic_image);
5068 return((Image *) NULL);
5069 }
cristy6fc86bb2011-03-18 23:45:16 +00005070 pixel_list=AcquirePixelListThreadSet(StatisticWidth,StatisticHeight);
cristy0834d642011-03-18 18:26:08 +00005071 if (pixel_list == (PixelList **) NULL)
5072 {
5073 statistic_image=DestroyImage(statistic_image);
5074 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
5075 }
5076 /*
cristy8d752042011-03-19 01:00:36 +00005077 Make each pixel the min / max / median / mode / etc. of the neighborhood.
cristy0834d642011-03-18 18:26:08 +00005078 */
5079 status=MagickTrue;
5080 progress=0;
5081 image_view=AcquireCacheView(image);
5082 statistic_view=AcquireCacheView(statistic_image);
5083#if defined(MAGICKCORE_OPENMP_SUPPORT)
5084 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
5085#endif
5086 for (y=0; y < (ssize_t) statistic_image->rows; y++)
5087 {
5088 const int
5089 id = GetOpenMPThreadId();
5090
cristy4c08aed2011-07-01 19:47:50 +00005091 register const Quantum
cristy0834d642011-03-18 18:26:08 +00005092 *restrict p;
5093
cristy4c08aed2011-07-01 19:47:50 +00005094 register Quantum
cristy0834d642011-03-18 18:26:08 +00005095 *restrict q;
5096
5097 register ssize_t
5098 x;
5099
5100 if (status == MagickFalse)
5101 continue;
cristy6fc86bb2011-03-18 23:45:16 +00005102 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) StatisticWidth/2L),y-
5103 (ssize_t) (StatisticHeight/2L),image->columns+StatisticWidth,
5104 StatisticHeight,exception);
cristy3cba8ca2011-03-19 01:29:12 +00005105 q=QueueCacheViewAuthenticPixels(statistic_view,0,y,statistic_image->columns, 1,exception);
cristy4c08aed2011-07-01 19:47:50 +00005106 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy0834d642011-03-18 18:26:08 +00005107 {
5108 status=MagickFalse;
5109 continue;
5110 }
cristy0834d642011-03-18 18:26:08 +00005111 for (x=0; x < (ssize_t) statistic_image->columns; x++)
5112 {
cristy4c08aed2011-07-01 19:47:50 +00005113 PixelInfo
cristy0834d642011-03-18 18:26:08 +00005114 pixel;
5115
cristy4c08aed2011-07-01 19:47:50 +00005116 register const Quantum
cristy6e3026a2011-03-19 00:54:38 +00005117 *restrict r;
5118
cristy0834d642011-03-18 18:26:08 +00005119 register ssize_t
5120 u,
5121 v;
5122
5123 r=p;
cristy0834d642011-03-18 18:26:08 +00005124 ResetPixelList(pixel_list[id]);
cristy6e4c3292011-03-19 00:53:55 +00005125 for (v=0; v < (ssize_t) StatisticHeight; v++)
cristy0834d642011-03-18 18:26:08 +00005126 {
cristy6e4c3292011-03-19 00:53:55 +00005127 for (u=0; u < (ssize_t) StatisticWidth; u++)
cristy4c08aed2011-07-01 19:47:50 +00005128 InsertPixelList(image,r+u*GetPixelChannels(image),pixel_list[id]);
5129 r+=(image->columns+StatisticWidth)*GetPixelChannels(image);
cristy0834d642011-03-18 18:26:08 +00005130 }
cristy4c08aed2011-07-01 19:47:50 +00005131 GetPixelInfo(image,&pixel);
cristy490408a2011-07-07 14:42:05 +00005132 SetPixelInfo(image,p+(StatisticWidth*StatisticHeight/2)*
5133 GetPixelChannels(image),&pixel);
cristy0834d642011-03-18 18:26:08 +00005134 switch (type)
5135 {
cristy80c99742011-04-04 14:46:39 +00005136 case GradientStatistic:
5137 {
cristy4c08aed2011-07-01 19:47:50 +00005138 PixelInfo
cristy80c99742011-04-04 14:46:39 +00005139 maximum,
5140 minimum;
5141
5142 minimum=GetMinimumPixelList(pixel_list[id]);
5143 maximum=GetMaximumPixelList(pixel_list[id]);
5144 pixel.red=MagickAbsoluteValue(maximum.red-minimum.red);
5145 pixel.green=MagickAbsoluteValue(maximum.green-minimum.green);
5146 pixel.blue=MagickAbsoluteValue(maximum.blue-minimum.blue);
cristy4c08aed2011-07-01 19:47:50 +00005147 pixel.alpha=MagickAbsoluteValue(maximum.alpha-minimum.alpha);
cristy80c99742011-04-04 14:46:39 +00005148 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00005149 pixel.black=MagickAbsoluteValue(maximum.black-minimum.black);
cristy80c99742011-04-04 14:46:39 +00005150 break;
5151 }
cristy6fc86bb2011-03-18 23:45:16 +00005152 case MaximumStatistic:
5153 {
5154 pixel=GetMaximumPixelList(pixel_list[id]);
5155 break;
5156 }
cristy49f37242011-03-22 18:18:23 +00005157 case MeanStatistic:
5158 {
5159 pixel=GetMeanPixelList(pixel_list[id]);
5160 break;
5161 }
cristyf2ad14a2011-03-18 18:57:25 +00005162 case MedianStatistic:
cristy6fc86bb2011-03-18 23:45:16 +00005163 default:
cristyf2ad14a2011-03-18 18:57:25 +00005164 {
5165 pixel=GetMedianPixelList(pixel_list[id]);
5166 break;
5167 }
cristy6fc86bb2011-03-18 23:45:16 +00005168 case MinimumStatistic:
5169 {
5170 pixel=GetMinimumPixelList(pixel_list[id]);
5171 break;
5172 }
cristyf2ad14a2011-03-18 18:57:25 +00005173 case ModeStatistic:
5174 {
5175 pixel=GetModePixelList(pixel_list[id]);
5176 break;
5177 }
5178 case NonpeakStatistic:
5179 {
5180 pixel=GetNonpeakPixelList(pixel_list[id]);
5181 break;
5182 }
cristy9a68cbb2011-03-29 00:51:23 +00005183 case StandardDeviationStatistic:
5184 {
5185 pixel=GetStandardDeviationPixelList(pixel_list[id]);
5186 break;
5187 }
cristy0834d642011-03-18 18:26:08 +00005188 }
cristy2b9582a2011-07-04 17:38:56 +00005189 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00005190 SetPixelRed(statistic_image,ClampToQuantum(pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00005191 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00005192 SetPixelGreen(statistic_image,ClampToQuantum(pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00005193 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00005194 SetPixelBlue(statistic_image,ClampToQuantum(pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00005195 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy0834d642011-03-18 18:26:08 +00005196 (image->colorspace == CMYKColorspace))
cristy490408a2011-07-07 14:42:05 +00005197 SetPixelBlack(statistic_image,ClampToQuantum(pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00005198 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00005199 (image->matte != MagickFalse))
cristy490408a2011-07-07 14:42:05 +00005200 SetPixelAlpha(statistic_image,ClampToQuantum(pixel.alpha),q);
cristy4c08aed2011-07-01 19:47:50 +00005201 p+=GetPixelChannels(image);
5202 q+=GetPixelChannels(statistic_image);
cristy0834d642011-03-18 18:26:08 +00005203 }
5204 if (SyncCacheViewAuthenticPixels(statistic_view,exception) == MagickFalse)
5205 status=MagickFalse;
5206 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5207 {
5208 MagickBooleanType
5209 proceed;
5210
5211#if defined(MAGICKCORE_OPENMP_SUPPORT)
5212 #pragma omp critical (MagickCore_StatisticImage)
5213#endif
5214 proceed=SetImageProgress(image,StatisticImageTag,progress++,
5215 image->rows);
5216 if (proceed == MagickFalse)
5217 status=MagickFalse;
5218 }
5219 }
5220 statistic_view=DestroyCacheView(statistic_view);
5221 image_view=DestroyCacheView(image_view);
5222 pixel_list=DestroyPixelListThreadSet(pixel_list);
5223 return(statistic_image);
5224}
5225
5226/*
5227%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5228% %
5229% %
5230% %
cristy3ed852e2009-09-05 21:47:34 +00005231% U n s h a r p M a s k I m a g e %
5232% %
5233% %
5234% %
5235%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5236%
5237% UnsharpMaskImage() sharpens one or more image channels. We convolve the
5238% image with a Gaussian operator of the given radius and standard deviation
5239% (sigma). For reasonable results, radius should be larger than sigma. Use a
5240% radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
5241%
5242% The format of the UnsharpMaskImage method is:
5243%
5244% Image *UnsharpMaskImage(const Image *image,const double radius,
5245% const double sigma,const double amount,const double threshold,
5246% ExceptionInfo *exception)
5247% Image *UnsharpMaskImageChannel(const Image *image,
5248% const ChannelType channel,const double radius,const double sigma,
5249% const double amount,const double threshold,ExceptionInfo *exception)
5250%
5251% A description of each parameter follows:
5252%
5253% o image: the image.
5254%
5255% o channel: the channel type.
5256%
5257% o radius: the radius of the Gaussian, in pixels, not counting the center
5258% pixel.
5259%
5260% o sigma: the standard deviation of the Gaussian, in pixels.
5261%
5262% o amount: the percentage of the difference between the original and the
5263% blur image that is added back into the original.
5264%
5265% o threshold: the threshold in pixels needed to apply the diffence amount.
5266%
5267% o exception: return any errors or warnings in this structure.
5268%
5269*/
5270
5271MagickExport Image *UnsharpMaskImage(const Image *image,const double radius,
5272 const double sigma,const double amount,const double threshold,
5273 ExceptionInfo *exception)
5274{
5275 Image
5276 *sharp_image;
5277
5278 sharp_image=UnsharpMaskImageChannel(image,DefaultChannels,radius,sigma,amount,
5279 threshold,exception);
5280 return(sharp_image);
5281}
5282
5283MagickExport Image *UnsharpMaskImageChannel(const Image *image,
5284 const ChannelType channel,const double radius,const double sigma,
5285 const double amount,const double threshold,ExceptionInfo *exception)
5286{
5287#define SharpenImageTag "Sharpen/Image"
5288
cristyc4c8d132010-01-07 01:58:38 +00005289 CacheView
5290 *image_view,
5291 *unsharp_view;
5292
cristy3ed852e2009-09-05 21:47:34 +00005293 Image
5294 *unsharp_image;
5295
cristy3ed852e2009-09-05 21:47:34 +00005296 MagickBooleanType
5297 status;
5298
cristybb503372010-05-27 20:51:26 +00005299 MagickOffsetType
5300 progress;
5301
cristy4c08aed2011-07-01 19:47:50 +00005302 PixelInfo
cristyddd82202009-11-03 20:14:50 +00005303 bias;
cristy3ed852e2009-09-05 21:47:34 +00005304
5305 MagickRealType
5306 quantum_threshold;
5307
cristybb503372010-05-27 20:51:26 +00005308 ssize_t
5309 y;
5310
cristy3ed852e2009-09-05 21:47:34 +00005311 assert(image != (const Image *) NULL);
5312 assert(image->signature == MagickSignature);
5313 if (image->debug != MagickFalse)
5314 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5315 assert(exception != (ExceptionInfo *) NULL);
5316 unsharp_image=BlurImageChannel(image,channel,radius,sigma,exception);
5317 if (unsharp_image == (Image *) NULL)
5318 return((Image *) NULL);
5319 quantum_threshold=(MagickRealType) QuantumRange*threshold;
5320 /*
5321 Unsharp-mask image.
5322 */
5323 status=MagickTrue;
5324 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00005325 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00005326 image_view=AcquireCacheView(image);
5327 unsharp_view=AcquireCacheView(unsharp_image);
cristyb5d5f722009-11-04 03:03:49 +00005328#if defined(MAGICKCORE_OPENMP_SUPPORT)
5329 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00005330#endif
cristybb503372010-05-27 20:51:26 +00005331 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00005332 {
cristy4c08aed2011-07-01 19:47:50 +00005333 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00005334 pixel;
5335
cristy4c08aed2011-07-01 19:47:50 +00005336 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00005337 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00005338
cristy4c08aed2011-07-01 19:47:50 +00005339 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00005340 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00005341
cristy117ff172010-08-15 21:35:32 +00005342 register ssize_t
5343 x;
5344
cristy3ed852e2009-09-05 21:47:34 +00005345 if (status == MagickFalse)
5346 continue;
5347 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
5348 q=GetCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
5349 exception);
cristy4c08aed2011-07-01 19:47:50 +00005350 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00005351 {
5352 status=MagickFalse;
5353 continue;
5354 }
cristyddd82202009-11-03 20:14:50 +00005355 pixel=bias;
cristybb503372010-05-27 20:51:26 +00005356 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00005357 {
cristy2b9582a2011-07-04 17:38:56 +00005358 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00005359 {
cristy4c08aed2011-07-01 19:47:50 +00005360 pixel.red=GetPixelRed(image,p)-(MagickRealType) GetPixelRed(image,q);
cristy3ed852e2009-09-05 21:47:34 +00005361 if (fabs(2.0*pixel.red) < quantum_threshold)
cristy4c08aed2011-07-01 19:47:50 +00005362 pixel.red=(MagickRealType) GetPixelRed(image,p);
cristy3ed852e2009-09-05 21:47:34 +00005363 else
cristy4c08aed2011-07-01 19:47:50 +00005364 pixel.red=(MagickRealType) GetPixelRed(image,p)+(pixel.red*amount);
5365 SetPixelRed(unsharp_image,ClampToQuantum(pixel.red),q);
cristy3ed852e2009-09-05 21:47:34 +00005366 }
cristy2b9582a2011-07-04 17:38:56 +00005367 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00005368 {
cristy4c08aed2011-07-01 19:47:50 +00005369 pixel.green=GetPixelGreen(image,p)-
5370 (MagickRealType) GetPixelGreen(image,q);
cristy3ed852e2009-09-05 21:47:34 +00005371 if (fabs(2.0*pixel.green) < quantum_threshold)
cristy4c08aed2011-07-01 19:47:50 +00005372 pixel.green=(MagickRealType)
5373 GetPixelGreen(image,p);
cristy3ed852e2009-09-05 21:47:34 +00005374 else
cristy4c08aed2011-07-01 19:47:50 +00005375 pixel.green=(MagickRealType)
5376 GetPixelGreen(image,p)+
5377 (pixel.green*amount);
5378 SetPixelGreen(unsharp_image,
5379 ClampToQuantum(pixel.green),q);
cristy3ed852e2009-09-05 21:47:34 +00005380 }
cristy2b9582a2011-07-04 17:38:56 +00005381 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00005382 {
cristy4c08aed2011-07-01 19:47:50 +00005383 pixel.blue=GetPixelBlue(image,p)-
5384 (MagickRealType) GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00005385 if (fabs(2.0*pixel.blue) < quantum_threshold)
cristy4c08aed2011-07-01 19:47:50 +00005386 pixel.blue=(MagickRealType)
5387 GetPixelBlue(image,p);
cristy3ed852e2009-09-05 21:47:34 +00005388 else
cristy4c08aed2011-07-01 19:47:50 +00005389 pixel.blue=(MagickRealType)
5390 GetPixelBlue(image,p)+(pixel.blue*amount);
5391 SetPixelBlue(unsharp_image,
5392 ClampToQuantum(pixel.blue),q);
cristy3ed852e2009-09-05 21:47:34 +00005393 }
cristy2b9582a2011-07-04 17:38:56 +00005394 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00005395 (image->colorspace == CMYKColorspace))
5396 {
cristy4c08aed2011-07-01 19:47:50 +00005397 pixel.black=GetPixelBlack(image,p)-
5398 (MagickRealType) GetPixelBlack(image,q);
5399 if (fabs(2.0*pixel.black) < quantum_threshold)
5400 pixel.black=(MagickRealType)
5401 GetPixelBlack(image,p);
cristy3ed852e2009-09-05 21:47:34 +00005402 else
cristy4c08aed2011-07-01 19:47:50 +00005403 pixel.black=(MagickRealType)
5404 GetPixelBlack(image,p)+(pixel.black*
5405 amount);
5406 SetPixelBlack(unsharp_image,
5407 ClampToQuantum(pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00005408 }
cristy2b9582a2011-07-04 17:38:56 +00005409 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00005410 {
5411 pixel.alpha=GetPixelAlpha(image,p)-
5412 (MagickRealType) GetPixelAlpha(image,q);
5413 if (fabs(2.0*pixel.alpha) < quantum_threshold)
5414 pixel.alpha=(MagickRealType)
5415 GetPixelAlpha(image,p);
5416 else
5417 pixel.alpha=GetPixelAlpha(image,p)+
5418 (pixel.alpha*amount);
5419 SetPixelAlpha(unsharp_image,
5420 ClampToQuantum(pixel.alpha),q);
5421 }
5422 p+=GetPixelChannels(image);
5423 q+=GetPixelChannels(unsharp_image);
cristy3ed852e2009-09-05 21:47:34 +00005424 }
5425 if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
5426 status=MagickFalse;
5427 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5428 {
5429 MagickBooleanType
5430 proceed;
5431
cristyb5d5f722009-11-04 03:03:49 +00005432#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00005433 #pragma omp critical (MagickCore_UnsharpMaskImageChannel)
5434#endif
5435 proceed=SetImageProgress(image,SharpenImageTag,progress++,image->rows);
5436 if (proceed == MagickFalse)
5437 status=MagickFalse;
5438 }
5439 }
5440 unsharp_image->type=image->type;
5441 unsharp_view=DestroyCacheView(unsharp_view);
5442 image_view=DestroyCacheView(image_view);
5443 if (status == MagickFalse)
5444 unsharp_image=DestroyImage(unsharp_image);
5445 return(unsharp_image);
5446}