blob: 92239c584615ee78f9a6c2995c8117db6c89b42d [file] [log] [blame]
cristy3ed852e2009-09-05 21:47:34 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% EEEEE FFFFF FFFFF EEEEE CCCC TTTTT %
7% E F F E C T %
8% EEE FFF FFF EEE C T %
9% E F F E C T %
10% EEEEE F F EEEEE CCCC T %
11% %
12% %
13% MagickCore Image Effects Methods %
14% %
15% Software Design %
16% John Cristy %
17% October 1996 %
18% %
19% %
cristy7e41fe82010-12-04 23:12:08 +000020% Copyright 1999-2011 ImageMagick Studio LLC, a non-profit organization %
cristy3ed852e2009-09-05 21:47:34 +000021% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
26% http://www.imagemagick.org/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
36%
37%
38*/
39
40/*
41 Include declarations.
42*/
cristy4c08aed2011-07-01 19:47:50 +000043#include "MagickCore/studio.h"
44#include "MagickCore/accelerate.h"
45#include "MagickCore/blob.h"
46#include "MagickCore/cache-view.h"
47#include "MagickCore/color.h"
48#include "MagickCore/color-private.h"
49#include "MagickCore/colorspace.h"
50#include "MagickCore/constitute.h"
51#include "MagickCore/decorate.h"
52#include "MagickCore/draw.h"
53#include "MagickCore/enhance.h"
54#include "MagickCore/exception.h"
55#include "MagickCore/exception-private.h"
56#include "MagickCore/effect.h"
57#include "MagickCore/fx.h"
58#include "MagickCore/gem.h"
59#include "MagickCore/geometry.h"
60#include "MagickCore/image-private.h"
61#include "MagickCore/list.h"
62#include "MagickCore/log.h"
63#include "MagickCore/memory_.h"
64#include "MagickCore/monitor.h"
65#include "MagickCore/monitor-private.h"
66#include "MagickCore/montage.h"
67#include "MagickCore/morphology.h"
68#include "MagickCore/paint.h"
69#include "MagickCore/pixel-accessor.h"
70#include "MagickCore/property.h"
71#include "MagickCore/quantize.h"
72#include "MagickCore/quantum.h"
73#include "MagickCore/quantum-private.h"
74#include "MagickCore/random_.h"
75#include "MagickCore/random-private.h"
76#include "MagickCore/resample.h"
77#include "MagickCore/resample-private.h"
78#include "MagickCore/resize.h"
79#include "MagickCore/resource_.h"
80#include "MagickCore/segment.h"
81#include "MagickCore/shear.h"
82#include "MagickCore/signature-private.h"
83#include "MagickCore/string_.h"
84#include "MagickCore/thread-private.h"
85#include "MagickCore/transform.h"
86#include "MagickCore/threshold.h"
cristy3ed852e2009-09-05 21:47:34 +000087
88/*
89%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
90% %
91% %
92% %
93% A d a p t i v e B l u r I m a g e %
94% %
95% %
96% %
97%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
98%
99% AdaptiveBlurImage() adaptively blurs the image by blurring less
100% intensely near image edges and more intensely far from edges. We blur the
101% image with a Gaussian operator of the given radius and standard deviation
102% (sigma). For reasonable results, radius should be larger than sigma. Use a
103% radius of 0 and AdaptiveBlurImage() selects a suitable radius for you.
104%
105% The format of the AdaptiveBlurImage method is:
106%
107% Image *AdaptiveBlurImage(const Image *image,const double radius,
108% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000109%
110% A description of each parameter follows:
111%
112% o image: the image.
113%
cristy3ed852e2009-09-05 21:47:34 +0000114% o radius: the radius of the Gaussian, in pixels, not counting the center
115% pixel.
116%
117% o sigma: the standard deviation of the Laplacian, in pixels.
118%
119% o exception: return any errors or warnings in this structure.
120%
121*/
122
cristyf89cb1d2011-07-07 01:24:37 +0000123MagickExport MagickBooleanType AdaptiveLevelImage(Image *image,
124 const char *levels)
125{
126 double
127 black_point,
128 gamma,
129 white_point;
130
131 GeometryInfo
132 geometry_info;
133
134 MagickBooleanType
135 status;
136
137 MagickStatusType
138 flags;
139
140 /*
141 Parse levels.
142 */
143 if (levels == (char *) NULL)
144 return(MagickFalse);
145 flags=ParseGeometry(levels,&geometry_info);
146 black_point=geometry_info.rho;
147 white_point=(double) QuantumRange;
148 if ((flags & SigmaValue) != 0)
149 white_point=geometry_info.sigma;
150 gamma=1.0;
151 if ((flags & XiValue) != 0)
152 gamma=geometry_info.xi;
153 if ((flags & PercentValue) != 0)
154 {
155 black_point*=(double) image->columns*image->rows/100.0;
156 white_point*=(double) image->columns*image->rows/100.0;
157 }
158 if ((flags & SigmaValue) == 0)
159 white_point=(double) QuantumRange-black_point;
160 if ((flags & AspectValue ) == 0)
161 status=LevelImage(image,black_point,white_point,gamma);
162 else
163 status=LevelizeImage(image,black_point,white_point,gamma);
164 return(status);
165}
166
cristyf4ad9df2011-07-08 16:49:03 +0000167MagickExport Image *AdaptiveBlurImage(const Image *image,
168 const double radius,const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000169{
170#define AdaptiveBlurImageTag "Convolve/Image"
171#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
172
cristyc4c8d132010-01-07 01:58:38 +0000173 CacheView
174 *blur_view,
175 *edge_view,
176 *image_view;
177
cristy3ed852e2009-09-05 21:47:34 +0000178 double
cristy47e00502009-12-17 19:19:57 +0000179 **kernel,
180 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000181
182 Image
183 *blur_image,
184 *edge_image,
185 *gaussian_image;
186
cristy3ed852e2009-09-05 21:47:34 +0000187 MagickBooleanType
188 status;
189
cristybb503372010-05-27 20:51:26 +0000190 MagickOffsetType
191 progress;
192
cristy4c08aed2011-07-01 19:47:50 +0000193 PixelInfo
cristyddd82202009-11-03 20:14:50 +0000194 bias;
cristy3ed852e2009-09-05 21:47:34 +0000195
cristybb503372010-05-27 20:51:26 +0000196 register ssize_t
cristy47e00502009-12-17 19:19:57 +0000197 i;
cristy3ed852e2009-09-05 21:47:34 +0000198
cristybb503372010-05-27 20:51:26 +0000199 size_t
cristy3ed852e2009-09-05 21:47:34 +0000200 width;
201
cristybb503372010-05-27 20:51:26 +0000202 ssize_t
203 j,
204 k,
205 u,
206 v,
207 y;
208
cristy3ed852e2009-09-05 21:47:34 +0000209 assert(image != (const Image *) NULL);
210 assert(image->signature == MagickSignature);
211 if (image->debug != MagickFalse)
212 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
213 assert(exception != (ExceptionInfo *) NULL);
214 assert(exception->signature == MagickSignature);
215 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
216 if (blur_image == (Image *) NULL)
217 return((Image *) NULL);
218 if (fabs(sigma) <= MagickEpsilon)
219 return(blur_image);
220 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
221 {
222 InheritException(exception,&blur_image->exception);
223 blur_image=DestroyImage(blur_image);
224 return((Image *) NULL);
225 }
226 /*
227 Edge detect the image brighness channel, level, blur, and level again.
228 */
229 edge_image=EdgeImage(image,radius,exception);
230 if (edge_image == (Image *) NULL)
231 {
232 blur_image=DestroyImage(blur_image);
233 return((Image *) NULL);
234 }
cristyf89cb1d2011-07-07 01:24:37 +0000235 (void) AdaptiveLevelImage(edge_image,"20%,95%");
cristy3ed852e2009-09-05 21:47:34 +0000236 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,exception);
237 if (gaussian_image != (Image *) NULL)
238 {
239 edge_image=DestroyImage(edge_image);
240 edge_image=gaussian_image;
241 }
cristyf89cb1d2011-07-07 01:24:37 +0000242 (void) AdaptiveLevelImage(edge_image,"10%,95%");
cristy3ed852e2009-09-05 21:47:34 +0000243 /*
244 Create a set of kernels from maximum (radius,sigma) to minimum.
245 */
246 width=GetOptimalKernelWidth2D(radius,sigma);
247 kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
248 if (kernel == (double **) NULL)
249 {
250 edge_image=DestroyImage(edge_image);
251 blur_image=DestroyImage(blur_image);
252 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
253 }
254 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
cristybb503372010-05-27 20:51:26 +0000255 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000256 {
257 kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)*
258 sizeof(**kernel));
259 if (kernel[i] == (double *) NULL)
260 break;
cristy47e00502009-12-17 19:19:57 +0000261 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000262 j=(ssize_t) (width-i)/2;
cristy47e00502009-12-17 19:19:57 +0000263 k=0;
264 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000265 {
cristy47e00502009-12-17 19:19:57 +0000266 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000267 {
cristy4205a3c2010-09-12 20:19:59 +0000268 kernel[i][k]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
269 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +0000270 normalize+=kernel[i][k];
271 k++;
cristy3ed852e2009-09-05 21:47:34 +0000272 }
273 }
cristy3ed852e2009-09-05 21:47:34 +0000274 if (fabs(normalize) <= MagickEpsilon)
275 normalize=1.0;
276 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000277 for (k=0; k < (j*j); k++)
278 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000279 }
cristybb503372010-05-27 20:51:26 +0000280 if (i < (ssize_t) width)
cristy3ed852e2009-09-05 21:47:34 +0000281 {
282 for (i-=2; i >= 0; i-=2)
283 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
284 kernel=(double **) RelinquishMagickMemory(kernel);
285 edge_image=DestroyImage(edge_image);
286 blur_image=DestroyImage(blur_image);
287 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
288 }
289 /*
290 Adaptively blur image.
291 */
292 status=MagickTrue;
293 progress=0;
cristy4c08aed2011-07-01 19:47:50 +0000294 GetPixelInfo(image,&bias);
295 SetPixelInfoBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000296 image_view=AcquireCacheView(image);
297 edge_view=AcquireCacheView(edge_image);
298 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000299#if defined(MAGICKCORE_OPENMP_SUPPORT)
300 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000301#endif
cristybb503372010-05-27 20:51:26 +0000302 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000303 {
cristy4c08aed2011-07-01 19:47:50 +0000304 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000305 *restrict p,
306 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000307
cristy4c08aed2011-07-01 19:47:50 +0000308 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000309 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000310
cristy117ff172010-08-15 21:35:32 +0000311 register ssize_t
312 x;
313
cristy3ed852e2009-09-05 21:47:34 +0000314 if (status == MagickFalse)
315 continue;
316 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
317 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
318 exception);
cristy4c08aed2011-07-01 19:47:50 +0000319 if ((r == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000320 {
321 status=MagickFalse;
322 continue;
323 }
cristybb503372010-05-27 20:51:26 +0000324 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000325 {
cristy4c08aed2011-07-01 19:47:50 +0000326 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000327 pixel;
328
329 MagickRealType
330 alpha,
331 gamma;
332
333 register const double
cristyc47d1f82009-11-26 01:44:43 +0000334 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000335
cristybb503372010-05-27 20:51:26 +0000336 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000337 i,
338 u,
339 v;
340
341 gamma=0.0;
cristy4c08aed2011-07-01 19:47:50 +0000342 i=(ssize_t) ceil((double) width*QuantumScale*
343 GetPixelIntensity(edge_image,r)-0.5);
cristy3ed852e2009-09-05 21:47:34 +0000344 if (i < 0)
345 i=0;
346 else
cristybb503372010-05-27 20:51:26 +0000347 if (i > (ssize_t) width)
348 i=(ssize_t) width;
cristy3ed852e2009-09-05 21:47:34 +0000349 if ((i & 0x01) != 0)
350 i--;
cristya21afde2010-07-02 00:45:40 +0000351 p=GetCacheViewVirtualPixels(image_view,x-((ssize_t) (width-i)/2L),y-
352 (ssize_t) ((width-i)/2L),width-i,width-i,exception);
cristy4c08aed2011-07-01 19:47:50 +0000353 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000354 break;
cristyddd82202009-11-03 20:14:50 +0000355 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +0000356 k=kernel[i];
cristybb503372010-05-27 20:51:26 +0000357 for (v=0; v < (ssize_t) (width-i); v++)
cristy3ed852e2009-09-05 21:47:34 +0000358 {
cristybb503372010-05-27 20:51:26 +0000359 for (u=0; u < (ssize_t) (width-i); u++)
cristy3ed852e2009-09-05 21:47:34 +0000360 {
361 alpha=1.0;
cristy2b9582a2011-07-04 17:38:56 +0000362 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000363 (image->matte != MagickFalse))
cristy4c08aed2011-07-01 19:47:50 +0000364 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,p));
cristy2b9582a2011-07-04 17:38:56 +0000365 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000366 pixel.red+=(*k)*alpha*GetPixelRed(image,p);
cristy2b9582a2011-07-04 17:38:56 +0000367 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000368 pixel.green+=(*k)*alpha*GetPixelGreen(image,p);
cristy2b9582a2011-07-04 17:38:56 +0000369 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000370 pixel.blue+=(*k)*alpha*GetPixelBlue(image,p);
cristy2b9582a2011-07-04 17:38:56 +0000371 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000372 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +0000373 pixel.black+=(*k)*alpha*GetPixelBlack(image,p);
cristy2b9582a2011-07-04 17:38:56 +0000374 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000375 pixel.alpha+=(*k)*GetPixelAlpha(image,p);
cristy3ed852e2009-09-05 21:47:34 +0000376 gamma+=(*k)*alpha;
377 k++;
cristydcfc1ad2011-07-07 16:25:41 +0000378 p+=GetPixelComponents(image);
cristy3ed852e2009-09-05 21:47:34 +0000379 }
380 }
381 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy2b9582a2011-07-04 17:38:56 +0000382 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000383 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +0000384 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000385 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +0000386 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000387 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
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 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +0000391 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000392 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristydcfc1ad2011-07-07 16:25:41 +0000393 q+=GetPixelComponents(blur_image);
394 r+=GetPixelComponents(edge_image);
cristy3ed852e2009-09-05 21:47:34 +0000395 }
396 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
397 status=MagickFalse;
398 if (image->progress_monitor != (MagickProgressMonitor) NULL)
399 {
400 MagickBooleanType
401 proceed;
402
cristyb5d5f722009-11-04 03:03:49 +0000403#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +0000404 #pragma omp critical (MagickCore_AdaptiveBlurImage)
cristy3ed852e2009-09-05 21:47:34 +0000405#endif
406 proceed=SetImageProgress(image,AdaptiveBlurImageTag,progress++,
407 image->rows);
408 if (proceed == MagickFalse)
409 status=MagickFalse;
410 }
411 }
412 blur_image->type=image->type;
413 blur_view=DestroyCacheView(blur_view);
414 edge_view=DestroyCacheView(edge_view);
415 image_view=DestroyCacheView(image_view);
416 edge_image=DestroyImage(edge_image);
cristybb503372010-05-27 20:51:26 +0000417 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000418 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
419 kernel=(double **) RelinquishMagickMemory(kernel);
420 if (status == MagickFalse)
421 blur_image=DestroyImage(blur_image);
422 return(blur_image);
423}
424
425/*
426%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
427% %
428% %
429% %
430% A d a p t i v e S h a r p e n I m a g e %
431% %
432% %
433% %
434%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
435%
436% AdaptiveSharpenImage() adaptively sharpens the image by sharpening more
437% intensely near image edges and less intensely far from edges. We sharpen the
438% image with a Gaussian operator of the given radius and standard deviation
439% (sigma). For reasonable results, radius should be larger than sigma. Use a
440% radius of 0 and AdaptiveSharpenImage() selects a suitable radius for you.
441%
442% The format of the AdaptiveSharpenImage method is:
443%
444% Image *AdaptiveSharpenImage(const Image *image,const double radius,
445% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000446%
447% A description of each parameter follows:
448%
449% o image: the image.
450%
cristy3ed852e2009-09-05 21:47:34 +0000451% o radius: the radius of the Gaussian, in pixels, not counting the center
452% pixel.
453%
454% o sigma: the standard deviation of the Laplacian, in pixels.
455%
456% o exception: return any errors or warnings in this structure.
457%
458*/
cristy3ed852e2009-09-05 21:47:34 +0000459MagickExport Image *AdaptiveSharpenImage(const Image *image,const double radius,
460 const double sigma,ExceptionInfo *exception)
461{
cristy3ed852e2009-09-05 21:47:34 +0000462#define AdaptiveSharpenImageTag "Convolve/Image"
463#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
464
cristyc4c8d132010-01-07 01:58:38 +0000465 CacheView
466 *sharp_view,
467 *edge_view,
468 *image_view;
469
cristy3ed852e2009-09-05 21:47:34 +0000470 double
cristy47e00502009-12-17 19:19:57 +0000471 **kernel,
472 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000473
474 Image
475 *sharp_image,
476 *edge_image,
477 *gaussian_image;
478
cristy3ed852e2009-09-05 21:47:34 +0000479 MagickBooleanType
480 status;
481
cristybb503372010-05-27 20:51:26 +0000482 MagickOffsetType
483 progress;
484
cristy4c08aed2011-07-01 19:47:50 +0000485 PixelInfo
cristyddd82202009-11-03 20:14:50 +0000486 bias;
cristy3ed852e2009-09-05 21:47:34 +0000487
cristybb503372010-05-27 20:51:26 +0000488 register ssize_t
cristy47e00502009-12-17 19:19:57 +0000489 i;
cristy3ed852e2009-09-05 21:47:34 +0000490
cristybb503372010-05-27 20:51:26 +0000491 size_t
cristy3ed852e2009-09-05 21:47:34 +0000492 width;
493
cristybb503372010-05-27 20:51:26 +0000494 ssize_t
495 j,
496 k,
497 u,
498 v,
499 y;
500
cristy3ed852e2009-09-05 21:47:34 +0000501 assert(image != (const Image *) NULL);
502 assert(image->signature == MagickSignature);
503 if (image->debug != MagickFalse)
504 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
505 assert(exception != (ExceptionInfo *) NULL);
506 assert(exception->signature == MagickSignature);
507 sharp_image=CloneImage(image,0,0,MagickTrue,exception);
508 if (sharp_image == (Image *) NULL)
509 return((Image *) NULL);
510 if (fabs(sigma) <= MagickEpsilon)
511 return(sharp_image);
512 if (SetImageStorageClass(sharp_image,DirectClass) == MagickFalse)
513 {
514 InheritException(exception,&sharp_image->exception);
515 sharp_image=DestroyImage(sharp_image);
516 return((Image *) NULL);
517 }
518 /*
519 Edge detect the image brighness channel, level, sharp, and level again.
520 */
521 edge_image=EdgeImage(image,radius,exception);
522 if (edge_image == (Image *) NULL)
523 {
524 sharp_image=DestroyImage(sharp_image);
525 return((Image *) NULL);
526 }
cristyf89cb1d2011-07-07 01:24:37 +0000527 (void) AdaptiveLevelImage(edge_image,"20%,95%");
cristy3ed852e2009-09-05 21:47:34 +0000528 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,exception);
529 if (gaussian_image != (Image *) NULL)
530 {
531 edge_image=DestroyImage(edge_image);
532 edge_image=gaussian_image;
533 }
cristyf89cb1d2011-07-07 01:24:37 +0000534 (void) AdaptiveLevelImage(edge_image,"10%,95%");
cristy3ed852e2009-09-05 21:47:34 +0000535 /*
536 Create a set of kernels from maximum (radius,sigma) to minimum.
537 */
538 width=GetOptimalKernelWidth2D(radius,sigma);
539 kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
540 if (kernel == (double **) NULL)
541 {
542 edge_image=DestroyImage(edge_image);
543 sharp_image=DestroyImage(sharp_image);
544 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
545 }
546 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
cristybb503372010-05-27 20:51:26 +0000547 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000548 {
549 kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)*
550 sizeof(**kernel));
551 if (kernel[i] == (double *) NULL)
552 break;
cristy47e00502009-12-17 19:19:57 +0000553 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000554 j=(ssize_t) (width-i)/2;
cristy47e00502009-12-17 19:19:57 +0000555 k=0;
556 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000557 {
cristy47e00502009-12-17 19:19:57 +0000558 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000559 {
cristy4205a3c2010-09-12 20:19:59 +0000560 kernel[i][k]=(double) (-exp(-((double) u*u+v*v)/(2.0*MagickSigma*
561 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +0000562 normalize+=kernel[i][k];
563 k++;
cristy3ed852e2009-09-05 21:47:34 +0000564 }
565 }
cristy3ed852e2009-09-05 21:47:34 +0000566 if (fabs(normalize) <= MagickEpsilon)
567 normalize=1.0;
568 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000569 for (k=0; k < (j*j); k++)
570 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000571 }
cristybb503372010-05-27 20:51:26 +0000572 if (i < (ssize_t) width)
cristy3ed852e2009-09-05 21:47:34 +0000573 {
574 for (i-=2; i >= 0; i-=2)
575 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
576 kernel=(double **) RelinquishMagickMemory(kernel);
577 edge_image=DestroyImage(edge_image);
578 sharp_image=DestroyImage(sharp_image);
579 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
580 }
581 /*
582 Adaptively sharpen image.
583 */
584 status=MagickTrue;
585 progress=0;
cristy4c08aed2011-07-01 19:47:50 +0000586 GetPixelInfo(image,&bias);
587 SetPixelInfoBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000588 image_view=AcquireCacheView(image);
589 edge_view=AcquireCacheView(edge_image);
590 sharp_view=AcquireCacheView(sharp_image);
cristyb5d5f722009-11-04 03:03:49 +0000591#if defined(MAGICKCORE_OPENMP_SUPPORT)
592 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000593#endif
cristybb503372010-05-27 20:51:26 +0000594 for (y=0; y < (ssize_t) sharp_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000595 {
cristy4c08aed2011-07-01 19:47:50 +0000596 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000597 *restrict p,
598 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000599
cristy4c08aed2011-07-01 19:47:50 +0000600 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000601 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000602
cristy117ff172010-08-15 21:35:32 +0000603 register ssize_t
604 x;
605
cristy3ed852e2009-09-05 21:47:34 +0000606 if (status == MagickFalse)
607 continue;
608 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
609 q=QueueCacheViewAuthenticPixels(sharp_view,0,y,sharp_image->columns,1,
610 exception);
cristy4c08aed2011-07-01 19:47:50 +0000611 if ((r == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000612 {
613 status=MagickFalse;
614 continue;
615 }
cristybb503372010-05-27 20:51:26 +0000616 for (x=0; x < (ssize_t) sharp_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000617 {
cristy4c08aed2011-07-01 19:47:50 +0000618 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000619 pixel;
620
621 MagickRealType
622 alpha,
623 gamma;
624
625 register const double
cristyc47d1f82009-11-26 01:44:43 +0000626 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000627
cristybb503372010-05-27 20:51:26 +0000628 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000629 i,
630 u,
631 v;
632
633 gamma=0.0;
cristy4c08aed2011-07-01 19:47:50 +0000634 i=(ssize_t) ceil((double) width*QuantumScale*
635 GetPixelIntensity(edge_image,r)-0.5);
cristy3ed852e2009-09-05 21:47:34 +0000636 if (i < 0)
637 i=0;
638 else
cristybb503372010-05-27 20:51:26 +0000639 if (i > (ssize_t) width)
640 i=(ssize_t) width;
cristy3ed852e2009-09-05 21:47:34 +0000641 if ((i & 0x01) != 0)
642 i--;
cristy117ff172010-08-15 21:35:32 +0000643 p=GetCacheViewVirtualPixels(image_view,x-((ssize_t) (width-i)/2L),y-
644 (ssize_t) ((width-i)/2L),width-i,width-i,exception);
cristy4c08aed2011-07-01 19:47:50 +0000645 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000646 break;
cristy3ed852e2009-09-05 21:47:34 +0000647 k=kernel[i];
cristyddd82202009-11-03 20:14:50 +0000648 pixel=bias;
cristybb503372010-05-27 20:51:26 +0000649 for (v=0; v < (ssize_t) (width-i); v++)
cristy3ed852e2009-09-05 21:47:34 +0000650 {
cristybb503372010-05-27 20:51:26 +0000651 for (u=0; u < (ssize_t) (width-i); u++)
cristy3ed852e2009-09-05 21:47:34 +0000652 {
653 alpha=1.0;
cristy2b9582a2011-07-04 17:38:56 +0000654 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000655 (image->matte != MagickFalse))
cristy4c08aed2011-07-01 19:47:50 +0000656 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,p));
cristy2b9582a2011-07-04 17:38:56 +0000657 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000658 pixel.red+=(*k)*alpha*GetPixelRed(image,p);
cristy2b9582a2011-07-04 17:38:56 +0000659 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000660 pixel.green+=(*k)*alpha*GetPixelGreen(image,p);
cristy2b9582a2011-07-04 17:38:56 +0000661 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000662 pixel.blue+=(*k)*alpha*GetPixelBlue(image,p);
cristy2b9582a2011-07-04 17:38:56 +0000663 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000664 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +0000665 pixel.black+=(*k)*alpha*GetPixelBlack(image,p);
cristy2b9582a2011-07-04 17:38:56 +0000666 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000667 pixel.alpha+=(*k)*GetPixelAlpha(image,p);
cristy3ed852e2009-09-05 21:47:34 +0000668 gamma+=(*k)*alpha;
669 k++;
cristydcfc1ad2011-07-07 16:25:41 +0000670 p+=GetPixelComponents(image);
cristy3ed852e2009-09-05 21:47:34 +0000671 }
672 }
673 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy2b9582a2011-07-04 17:38:56 +0000674 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000675 SetPixelRed(sharp_image,ClampToQuantum(gamma*pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +0000676 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000677 SetPixelGreen(sharp_image,ClampToQuantum(gamma*pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +0000678 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000679 SetPixelBlue(sharp_image,ClampToQuantum(gamma*pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +0000680 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000681 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +0000682 SetPixelBlack(sharp_image,ClampToQuantum(gamma*pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +0000683 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000684 SetPixelAlpha(sharp_image,ClampToQuantum(pixel.alpha),q);
cristydcfc1ad2011-07-07 16:25:41 +0000685 q+=GetPixelComponents(sharp_image);
686 r+=GetPixelComponents(edge_image);
cristy3ed852e2009-09-05 21:47:34 +0000687 }
688 if (SyncCacheViewAuthenticPixels(sharp_view,exception) == MagickFalse)
689 status=MagickFalse;
690 if (image->progress_monitor != (MagickProgressMonitor) NULL)
691 {
692 MagickBooleanType
693 proceed;
694
cristyb5d5f722009-11-04 03:03:49 +0000695#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +0000696 #pragma omp critical (MagickCore_AdaptiveSharpenImage)
cristy3ed852e2009-09-05 21:47:34 +0000697#endif
698 proceed=SetImageProgress(image,AdaptiveSharpenImageTag,progress++,
699 image->rows);
700 if (proceed == MagickFalse)
701 status=MagickFalse;
702 }
703 }
704 sharp_image->type=image->type;
705 sharp_view=DestroyCacheView(sharp_view);
706 edge_view=DestroyCacheView(edge_view);
707 image_view=DestroyCacheView(image_view);
708 edge_image=DestroyImage(edge_image);
cristybb503372010-05-27 20:51:26 +0000709 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000710 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
711 kernel=(double **) RelinquishMagickMemory(kernel);
712 if (status == MagickFalse)
713 sharp_image=DestroyImage(sharp_image);
714 return(sharp_image);
715}
716
717/*
718%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
719% %
720% %
721% %
722% B l u r I m a g e %
723% %
724% %
725% %
726%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
727%
728% BlurImage() blurs an image. We convolve the image with a Gaussian operator
729% of the given radius and standard deviation (sigma). For reasonable results,
730% the radius should be larger than sigma. Use a radius of 0 and BlurImage()
731% selects a suitable radius for you.
732%
733% BlurImage() differs from GaussianBlurImage() in that it uses a separable
734% kernel which is faster but mathematically equivalent to the non-separable
735% kernel.
736%
737% The format of the BlurImage method is:
738%
739% Image *BlurImage(const Image *image,const double radius,
740% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000741%
742% A description of each parameter follows:
743%
744% o image: the image.
745%
cristy3ed852e2009-09-05 21:47:34 +0000746% o radius: the radius of the Gaussian, in pixels, not counting the center
747% pixel.
748%
749% o sigma: the standard deviation of the Gaussian, in pixels.
750%
751% o exception: return any errors or warnings in this structure.
752%
753*/
754
cristybb503372010-05-27 20:51:26 +0000755static double *GetBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +0000756{
cristy3ed852e2009-09-05 21:47:34 +0000757 double
cristy47e00502009-12-17 19:19:57 +0000758 *kernel,
759 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000760
cristy117ff172010-08-15 21:35:32 +0000761 register ssize_t
762 i;
763
cristybb503372010-05-27 20:51:26 +0000764 ssize_t
cristy47e00502009-12-17 19:19:57 +0000765 j,
766 k;
cristy3ed852e2009-09-05 21:47:34 +0000767
cristy3ed852e2009-09-05 21:47:34 +0000768 /*
769 Generate a 1-D convolution kernel.
770 */
771 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
772 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
773 if (kernel == (double *) NULL)
774 return(0);
cristy3ed852e2009-09-05 21:47:34 +0000775 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000776 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +0000777 i=0;
778 for (k=(-j); k <= j; k++)
779 {
cristy4205a3c2010-09-12 20:19:59 +0000780 kernel[i]=(double) (exp(-((double) k*k)/(2.0*MagickSigma*MagickSigma))/
781 (MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +0000782 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +0000783 i++;
784 }
cristybb503372010-05-27 20:51:26 +0000785 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000786 kernel[i]/=normalize;
787 return(kernel);
788}
789
cristyf4ad9df2011-07-08 16:49:03 +0000790MagickExport Image *BlurImage(const Image *image,const double radius,
791 const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000792{
793#define BlurImageTag "Blur/Image"
794
cristyc4c8d132010-01-07 01:58:38 +0000795 CacheView
796 *blur_view,
797 *image_view;
798
cristy3ed852e2009-09-05 21:47:34 +0000799 double
800 *kernel;
801
802 Image
803 *blur_image;
804
cristy3ed852e2009-09-05 21:47:34 +0000805 MagickBooleanType
806 status;
807
cristybb503372010-05-27 20:51:26 +0000808 MagickOffsetType
809 progress;
810
cristy4c08aed2011-07-01 19:47:50 +0000811 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000812 bias;
813
cristybb503372010-05-27 20:51:26 +0000814 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000815 i;
816
cristybb503372010-05-27 20:51:26 +0000817 size_t
cristy3ed852e2009-09-05 21:47:34 +0000818 width;
819
cristybb503372010-05-27 20:51:26 +0000820 ssize_t
821 x,
822 y;
823
cristy3ed852e2009-09-05 21:47:34 +0000824 /*
825 Initialize blur image attributes.
826 */
827 assert(image != (Image *) NULL);
828 assert(image->signature == MagickSignature);
829 if (image->debug != MagickFalse)
830 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
831 assert(exception != (ExceptionInfo *) NULL);
832 assert(exception->signature == MagickSignature);
833 blur_image=CloneImage(image,0,0,MagickTrue,exception);
834 if (blur_image == (Image *) NULL)
835 return((Image *) NULL);
836 if (fabs(sigma) <= MagickEpsilon)
837 return(blur_image);
838 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
839 {
840 InheritException(exception,&blur_image->exception);
841 blur_image=DestroyImage(blur_image);
842 return((Image *) NULL);
843 }
844 width=GetOptimalKernelWidth1D(radius,sigma);
845 kernel=GetBlurKernel(width,sigma);
846 if (kernel == (double *) NULL)
847 {
848 blur_image=DestroyImage(blur_image);
849 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
850 }
851 if (image->debug != MagickFalse)
852 {
853 char
854 format[MaxTextExtent],
855 *message;
856
857 register const double
858 *k;
859
860 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +0000861 " BlurImage with %.20g kernel:",(double) width);
cristy3ed852e2009-09-05 21:47:34 +0000862 message=AcquireString("");
863 k=kernel;
cristybb503372010-05-27 20:51:26 +0000864 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000865 {
866 *message='\0';
cristyb51dff52011-05-19 16:55:47 +0000867 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) i);
cristy3ed852e2009-09-05 21:47:34 +0000868 (void) ConcatenateString(&message,format);
cristyb51dff52011-05-19 16:55:47 +0000869 (void) FormatLocaleString(format,MaxTextExtent,"%g ",*k++);
cristy3ed852e2009-09-05 21:47:34 +0000870 (void) ConcatenateString(&message,format);
871 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
872 }
873 message=DestroyString(message);
874 }
875 /*
876 Blur rows.
877 */
878 status=MagickTrue;
879 progress=0;
cristy4c08aed2011-07-01 19:47:50 +0000880 GetPixelInfo(image,&bias);
881 SetPixelInfoBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000882 image_view=AcquireCacheView(image);
883 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000884#if defined(MAGICKCORE_OPENMP_SUPPORT)
885 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000886#endif
cristybb503372010-05-27 20:51:26 +0000887 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000888 {
cristy4c08aed2011-07-01 19:47:50 +0000889 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000890 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000891
cristy4c08aed2011-07-01 19:47:50 +0000892 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000893 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000894
cristy117ff172010-08-15 21:35:32 +0000895 register ssize_t
896 x;
897
cristy3ed852e2009-09-05 21:47:34 +0000898 if (status == MagickFalse)
899 continue;
cristy117ff172010-08-15 21:35:32 +0000900 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y,
901 image->columns+width,1,exception);
cristy3ed852e2009-09-05 21:47:34 +0000902 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
903 exception);
cristy4c08aed2011-07-01 19:47:50 +0000904 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000905 {
906 status=MagickFalse;
907 continue;
908 }
cristybb503372010-05-27 20:51:26 +0000909 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000910 {
cristy4c08aed2011-07-01 19:47:50 +0000911 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000912 pixel;
913
914 register const double
cristyc47d1f82009-11-26 01:44:43 +0000915 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000916
cristy4c08aed2011-07-01 19:47:50 +0000917 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000918 *restrict kernel_pixels;
cristy3ed852e2009-09-05 21:47:34 +0000919
cristybb503372010-05-27 20:51:26 +0000920 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000921 i;
922
cristyddd82202009-11-03 20:14:50 +0000923 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +0000924 k=kernel;
925 kernel_pixels=p;
cristyf4ad9df2011-07-08 16:49:03 +0000926 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) == 0) ||
927 (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +0000928 {
cristybb503372010-05-27 20:51:26 +0000929 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000930 {
cristy4c08aed2011-07-01 19:47:50 +0000931 pixel.red+=(*k)*GetPixelRed(image,kernel_pixels);
932 pixel.green+=(*k)*GetPixelGreen(image,kernel_pixels);
933 pixel.blue+=(*k)*GetPixelBlue(image,kernel_pixels);
934 if (image->colorspace == CMYKColorspace)
935 pixel.black+=(*k)*GetPixelBlack(image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +0000936 k++;
cristydcfc1ad2011-07-07 16:25:41 +0000937 kernel_pixels+=GetPixelComponents(image);
cristy3ed852e2009-09-05 21:47:34 +0000938 }
cristy2b9582a2011-07-04 17:38:56 +0000939 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000940 SetPixelRed(blur_image,ClampToQuantum(pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +0000941 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000942 SetPixelGreen(blur_image,ClampToQuantum(pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +0000943 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000944 SetPixelBlue(blur_image,ClampToQuantum(pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +0000945 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000946 (blur_image->colorspace == CMYKColorspace))
947 SetPixelBlack(blur_image,ClampToQuantum(pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +0000948 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +0000949 {
950 k=kernel;
951 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +0000952 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000953 {
cristy4c08aed2011-07-01 19:47:50 +0000954 pixel.alpha+=(*k)*GetPixelAlpha(image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +0000955 k++;
cristydcfc1ad2011-07-07 16:25:41 +0000956 kernel_pixels+=GetPixelComponents(image);
cristy3ed852e2009-09-05 21:47:34 +0000957 }
cristy4c08aed2011-07-01 19:47:50 +0000958 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +0000959 }
960 }
961 else
962 {
963 MagickRealType
964 alpha,
965 gamma;
966
967 gamma=0.0;
cristybb503372010-05-27 20:51:26 +0000968 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000969 {
cristy4c08aed2011-07-01 19:47:50 +0000970 alpha=(MagickRealType) (QuantumScale*
971 GetPixelAlpha(image,kernel_pixels));
972 pixel.red+=(*k)*alpha*GetPixelRed(image,kernel_pixels);
973 pixel.green+=(*k)*alpha*GetPixelGreen(image,kernel_pixels);
974 pixel.blue+=(*k)*alpha*GetPixelBlue(image,kernel_pixels);
975 if (image->colorspace == CMYKColorspace)
976 pixel.black+=(*k)*alpha*GetPixelBlack(image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +0000977 gamma+=(*k)*alpha;
978 k++;
cristydcfc1ad2011-07-07 16:25:41 +0000979 kernel_pixels+=GetPixelComponents(image);
cristy3ed852e2009-09-05 21:47:34 +0000980 }
981 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy2b9582a2011-07-04 17:38:56 +0000982 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000983 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +0000984 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000985 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +0000986 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000987 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +0000988 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000989 (blur_image->colorspace == CMYKColorspace))
990 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +0000991 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +0000992 {
993 k=kernel;
994 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +0000995 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000996 {
cristy4c08aed2011-07-01 19:47:50 +0000997 pixel.alpha+=(*k)*GetPixelAlpha(image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +0000998 k++;
cristydcfc1ad2011-07-07 16:25:41 +0000999 kernel_pixels+=GetPixelComponents(image);
cristy3ed852e2009-09-05 21:47:34 +00001000 }
cristy4c08aed2011-07-01 19:47:50 +00001001 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00001002 }
1003 }
cristydcfc1ad2011-07-07 16:25:41 +00001004 p+=GetPixelComponents(image);
1005 q+=GetPixelComponents(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001006 }
1007 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1008 status=MagickFalse;
1009 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1010 {
1011 MagickBooleanType
1012 proceed;
1013
cristyb5d5f722009-11-04 03:03:49 +00001014#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00001015 #pragma omp critical (MagickCore_BlurImage)
cristy3ed852e2009-09-05 21:47:34 +00001016#endif
1017 proceed=SetImageProgress(image,BlurImageTag,progress++,blur_image->rows+
1018 blur_image->columns);
1019 if (proceed == MagickFalse)
1020 status=MagickFalse;
1021 }
1022 }
1023 blur_view=DestroyCacheView(blur_view);
1024 image_view=DestroyCacheView(image_view);
1025 /*
1026 Blur columns.
1027 */
1028 image_view=AcquireCacheView(blur_image);
1029 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00001030#if defined(MAGICKCORE_OPENMP_SUPPORT)
1031 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001032#endif
cristybb503372010-05-27 20:51:26 +00001033 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001034 {
cristy4c08aed2011-07-01 19:47:50 +00001035 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001036 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001037
cristy4c08aed2011-07-01 19:47:50 +00001038 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001039 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001040
cristy117ff172010-08-15 21:35:32 +00001041 register ssize_t
1042 y;
1043
cristy3ed852e2009-09-05 21:47:34 +00001044 if (status == MagickFalse)
1045 continue;
cristy117ff172010-08-15 21:35:32 +00001046 p=GetCacheViewVirtualPixels(image_view,x,-((ssize_t) width/2L),1,
1047 image->rows+width,exception);
cristy3ed852e2009-09-05 21:47:34 +00001048 q=GetCacheViewAuthenticPixels(blur_view,x,0,1,blur_image->rows,exception);
cristy4c08aed2011-07-01 19:47:50 +00001049 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001050 {
1051 status=MagickFalse;
1052 continue;
1053 }
cristybb503372010-05-27 20:51:26 +00001054 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001055 {
cristy4c08aed2011-07-01 19:47:50 +00001056 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001057 pixel;
1058
1059 register const double
cristyc47d1f82009-11-26 01:44:43 +00001060 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00001061
cristy4c08aed2011-07-01 19:47:50 +00001062 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001063 *restrict kernel_pixels;
cristy3ed852e2009-09-05 21:47:34 +00001064
cristybb503372010-05-27 20:51:26 +00001065 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001066 i;
1067
cristyddd82202009-11-03 20:14:50 +00001068 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00001069 k=kernel;
1070 kernel_pixels=p;
cristyf4ad9df2011-07-08 16:49:03 +00001071 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) == 0) ||
1072 (blur_image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00001073 {
cristybb503372010-05-27 20:51:26 +00001074 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001075 {
cristy4c08aed2011-07-01 19:47:50 +00001076 pixel.red+=(*k)*GetPixelRed(blur_image,kernel_pixels);
1077 pixel.green+=(*k)*GetPixelGreen(blur_image,kernel_pixels);
1078 pixel.blue+=(*k)*GetPixelBlue(blur_image,kernel_pixels);
1079 if (blur_image->colorspace == CMYKColorspace)
1080 pixel.black+=(*k)*GetPixelBlack(blur_image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001081 k++;
cristydcfc1ad2011-07-07 16:25:41 +00001082 kernel_pixels+=GetPixelComponents(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001083 }
cristy2b9582a2011-07-04 17:38:56 +00001084 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001085 SetPixelRed(blur_image,ClampToQuantum(pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00001086 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001087 SetPixelGreen(blur_image,ClampToQuantum(pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00001088 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001089 SetPixelBlue(blur_image,ClampToQuantum(pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00001090 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001091 (blur_image->colorspace == CMYKColorspace))
1092 SetPixelBlack(blur_image,ClampToQuantum(pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00001093 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001094 {
1095 k=kernel;
1096 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001097 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001098 {
cristy4c08aed2011-07-01 19:47:50 +00001099 pixel.alpha+=(*k)*GetPixelAlpha(blur_image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001100 k++;
cristydcfc1ad2011-07-07 16:25:41 +00001101 kernel_pixels+=GetPixelComponents(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001102 }
cristy4c08aed2011-07-01 19:47:50 +00001103 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00001104 }
1105 }
1106 else
1107 {
1108 MagickRealType
1109 alpha,
1110 gamma;
1111
1112 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00001113 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001114 {
cristy46f08202010-01-10 04:04:21 +00001115 alpha=(MagickRealType) (QuantumScale*
cristy4c08aed2011-07-01 19:47:50 +00001116 GetPixelAlpha(blur_image,kernel_pixels));
1117 pixel.red+=(*k)*alpha*GetPixelRed(blur_image,kernel_pixels);
1118 pixel.green+=(*k)*alpha*GetPixelGreen(blur_image,kernel_pixels);
1119 pixel.blue+=(*k)*alpha*GetPixelBlue(blur_image,kernel_pixels);
1120 if (blur_image->colorspace == CMYKColorspace)
1121 pixel.black+=(*k)*alpha*GetPixelBlack(blur_image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001122 gamma+=(*k)*alpha;
1123 k++;
cristydcfc1ad2011-07-07 16:25:41 +00001124 kernel_pixels+=GetPixelComponents(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001125 }
1126 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy2b9582a2011-07-04 17:38:56 +00001127 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001128 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00001129 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001130 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00001131 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001132 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00001133 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001134 (blur_image->colorspace == CMYKColorspace))
1135 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00001136 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001137 {
1138 k=kernel;
1139 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001140 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001141 {
cristy4c08aed2011-07-01 19:47:50 +00001142 pixel.alpha+=(*k)*GetPixelAlpha(blur_image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001143 k++;
cristydcfc1ad2011-07-07 16:25:41 +00001144 kernel_pixels+=GetPixelComponents(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001145 }
cristy4c08aed2011-07-01 19:47:50 +00001146 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00001147 }
1148 }
cristydcfc1ad2011-07-07 16:25:41 +00001149 p+=GetPixelComponents(blur_image);
1150 q+=GetPixelComponents(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001151 }
1152 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1153 status=MagickFalse;
cristy4c08aed2011-07-01 19:47:50 +00001154 if (blur_image->progress_monitor != (MagickProgressMonitor) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001155 {
1156 MagickBooleanType
1157 proceed;
1158
cristyb5d5f722009-11-04 03:03:49 +00001159#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00001160 #pragma omp critical (MagickCore_BlurImage)
cristy3ed852e2009-09-05 21:47:34 +00001161#endif
cristy4c08aed2011-07-01 19:47:50 +00001162 proceed=SetImageProgress(blur_image,BlurImageTag,progress++,
1163 blur_image->rows+blur_image->columns);
cristy3ed852e2009-09-05 21:47:34 +00001164 if (proceed == MagickFalse)
1165 status=MagickFalse;
1166 }
1167 }
1168 blur_view=DestroyCacheView(blur_view);
1169 image_view=DestroyCacheView(image_view);
1170 kernel=(double *) RelinquishMagickMemory(kernel);
1171 if (status == MagickFalse)
1172 blur_image=DestroyImage(blur_image);
1173 blur_image->type=image->type;
1174 return(blur_image);
1175}
1176
1177/*
1178%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1179% %
1180% %
1181% %
cristyfccdab92009-11-30 16:43:57 +00001182% C o n v o l v e I m a g e %
1183% %
1184% %
1185% %
1186%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1187%
1188% ConvolveImage() applies a custom convolution kernel to the image.
1189%
1190% The format of the ConvolveImage method is:
1191%
cristybb503372010-05-27 20:51:26 +00001192% Image *ConvolveImage(const Image *image,const size_t order,
cristyfccdab92009-11-30 16:43:57 +00001193% const double *kernel,ExceptionInfo *exception)
cristy3f829a22011-07-10 21:40:04 +00001194%
cristyfccdab92009-11-30 16:43:57 +00001195% A description of each parameter follows:
1196%
1197% o image: the image.
1198%
cristyfccdab92009-11-30 16:43:57 +00001199% o order: the number of columns and rows in the filter kernel.
1200%
1201% o kernel: An array of double representing the convolution kernel.
1202%
1203% o exception: return any errors or warnings in this structure.
1204%
1205*/
cristybb503372010-05-27 20:51:26 +00001206MagickExport Image *ConvolveImage(const Image *image,const size_t order,
cristyfccdab92009-11-30 16:43:57 +00001207 const double *kernel,ExceptionInfo *exception)
1208{
cristyfccdab92009-11-30 16:43:57 +00001209#define ConvolveImageTag "Convolve/Image"
1210
cristyc4c8d132010-01-07 01:58:38 +00001211 CacheView
1212 *convolve_view,
1213 *image_view;
1214
cristyfccdab92009-11-30 16:43:57 +00001215 double
1216 *normal_kernel;
1217
1218 Image
1219 *convolve_image;
1220
cristyfccdab92009-11-30 16:43:57 +00001221 MagickBooleanType
1222 status;
1223
cristybb503372010-05-27 20:51:26 +00001224 MagickOffsetType
1225 progress;
1226
cristyfccdab92009-11-30 16:43:57 +00001227 MagickRealType
1228 gamma;
1229
cristybb503372010-05-27 20:51:26 +00001230 register ssize_t
cristyfccdab92009-11-30 16:43:57 +00001231 i;
1232
cristybb503372010-05-27 20:51:26 +00001233 size_t
cristy3f829a22011-07-10 21:40:04 +00001234 convolve_components,
1235 image_components,
cristyfccdab92009-11-30 16:43:57 +00001236 width;
1237
cristybb503372010-05-27 20:51:26 +00001238 ssize_t
1239 y;
1240
cristyfccdab92009-11-30 16:43:57 +00001241 /*
1242 Initialize convolve image attributes.
1243 */
1244 assert(image != (Image *) NULL);
1245 assert(image->signature == MagickSignature);
1246 if (image->debug != MagickFalse)
1247 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1248 assert(exception != (ExceptionInfo *) NULL);
1249 assert(exception->signature == MagickSignature);
1250 width=order;
1251 if ((width % 2) == 0)
1252 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
1253 convolve_image=CloneImage(image,0,0,MagickTrue,exception);
1254 if (convolve_image == (Image *) NULL)
1255 return((Image *) NULL);
1256 if (SetImageStorageClass(convolve_image,DirectClass) == MagickFalse)
1257 {
1258 InheritException(exception,&convolve_image->exception);
1259 convolve_image=DestroyImage(convolve_image);
1260 return((Image *) NULL);
1261 }
1262 if (image->debug != MagickFalse)
1263 {
1264 char
1265 format[MaxTextExtent],
1266 *message;
1267
cristy117ff172010-08-15 21:35:32 +00001268 register const double
1269 *k;
1270
cristybb503372010-05-27 20:51:26 +00001271 ssize_t
cristyfccdab92009-11-30 16:43:57 +00001272 u,
1273 v;
1274
cristyfccdab92009-11-30 16:43:57 +00001275 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00001276 " ConvolveImage with %.20gx%.20g kernel:",(double) width,(double)
1277 width);
cristyfccdab92009-11-30 16:43:57 +00001278 message=AcquireString("");
1279 k=kernel;
cristybb503372010-05-27 20:51:26 +00001280 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001281 {
1282 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00001283 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristyfccdab92009-11-30 16:43:57 +00001284 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00001285 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001286 {
cristyb51dff52011-05-19 16:55:47 +00001287 (void) FormatLocaleString(format,MaxTextExtent,"%g ",*k++);
cristyfccdab92009-11-30 16:43:57 +00001288 (void) ConcatenateString(&message,format);
1289 }
1290 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
1291 }
1292 message=DestroyString(message);
1293 }
1294 /*
1295 Normalize kernel.
1296 */
1297 normal_kernel=(double *) AcquireQuantumMemory(width*width,
1298 sizeof(*normal_kernel));
1299 if (normal_kernel == (double *) NULL)
1300 {
1301 convolve_image=DestroyImage(convolve_image);
1302 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1303 }
1304 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00001305 for (i=0; i < (ssize_t) (width*width); i++)
cristyfccdab92009-11-30 16:43:57 +00001306 gamma+=kernel[i];
1307 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristybb503372010-05-27 20:51:26 +00001308 for (i=0; i < (ssize_t) (width*width); i++)
cristyfccdab92009-11-30 16:43:57 +00001309 normal_kernel[i]=gamma*kernel[i];
1310 /*
1311 Convolve image.
1312 */
1313 status=MagickTrue;
1314 progress=0;
cristy3f829a22011-07-10 21:40:04 +00001315 image_components=GetPixelComponents(image);
1316 convolve_components=GetPixelComponents(convolve_image);
cristyfccdab92009-11-30 16:43:57 +00001317 image_view=AcquireCacheView(image);
1318 convolve_view=AcquireCacheView(convolve_image);
1319#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3f829a22011-07-10 21:40:04 +00001320 #pragma omp parallel for schedule(dynamic,8) shared(progress,status)
cristyfccdab92009-11-30 16:43:57 +00001321#endif
cristybb503372010-05-27 20:51:26 +00001322 for (y=0; y < (ssize_t) image->rows; y++)
cristyfccdab92009-11-30 16:43:57 +00001323 {
cristy4c08aed2011-07-01 19:47:50 +00001324 register const Quantum
cristyfccdab92009-11-30 16:43:57 +00001325 *restrict p;
1326
cristy4c08aed2011-07-01 19:47:50 +00001327 register Quantum
cristyfccdab92009-11-30 16:43:57 +00001328 *restrict q;
1329
cristy117ff172010-08-15 21:35:32 +00001330 register ssize_t
1331 x;
1332
cristyfccdab92009-11-30 16:43:57 +00001333 if (status == MagickFalse)
1334 continue;
cristyce889302010-06-30 19:16:36 +00001335 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
1336 (width/2L),image->columns+width,width,exception);
cristyfccdab92009-11-30 16:43:57 +00001337 q=GetCacheViewAuthenticPixels(convolve_view,0,y,convolve_image->columns,1,
1338 exception);
cristy4c08aed2011-07-01 19:47:50 +00001339 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristyfccdab92009-11-30 16:43:57 +00001340 {
1341 status=MagickFalse;
1342 continue;
1343 }
cristybb503372010-05-27 20:51:26 +00001344 for (x=0; x < (ssize_t) image->columns; x++)
cristyfccdab92009-11-30 16:43:57 +00001345 {
cristy3f829a22011-07-10 21:40:04 +00001346 const Quantum
1347 *restrict kernel_pixels;
1348
1349 MagickRealType
1350 pixel[MaxPixelComponents];
cristyfccdab92009-11-30 16:43:57 +00001351
1352 register const double
1353 *restrict k;
1354
cristybb503372010-05-27 20:51:26 +00001355 register ssize_t
cristyfccdab92009-11-30 16:43:57 +00001356 u;
1357
cristy117ff172010-08-15 21:35:32 +00001358 ssize_t
1359 v;
1360
cristy3f829a22011-07-10 21:40:04 +00001361 for (i=0; i < (ssize_t) image_components; i++)
1362 pixel[i]=image->bias;
cristyfccdab92009-11-30 16:43:57 +00001363 kernel_pixels=p;
cristy3f829a22011-07-10 21:40:04 +00001364 k=normal_kernel;
cristy72ab5002011-07-07 18:33:45 +00001365 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) == 0) ||
1366 (image->matte == MagickFalse))
cristyfccdab92009-11-30 16:43:57 +00001367 {
cristy3f829a22011-07-10 21:40:04 +00001368 /*
1369 Convolve without blending alpha (optimization).
1370 */
cristybb503372010-05-27 20:51:26 +00001371 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001372 {
cristybb503372010-05-27 20:51:26 +00001373 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001374 {
cristy3f829a22011-07-10 21:40:04 +00001375 for (i=0; i < (ssize_t) image_components; i++)
1376 if ((GetPixelTraits(image,i) & ActivePixelTrait) != 0)
1377 pixel[i]+=(*k)*kernel_pixels[u*image_components+i];
cristyfccdab92009-11-30 16:43:57 +00001378 k++;
1379 }
cristy3f829a22011-07-10 21:40:04 +00001380 kernel_pixels+=(image->columns+width)*image_components;
cristyfccdab92009-11-30 16:43:57 +00001381 }
cristy3f829a22011-07-10 21:40:04 +00001382 for (i=0; i < (ssize_t) image_components; i++)
1383 {
1384 if (((GetPixelTraits(image,i) & ActivePixelTrait) != 0) &&
1385 ((GetPixelTraits(convolve_image,i) & ActivePixelTrait) != 0))
1386 q[i]=ClampToQuantum(pixel[i]);
1387 }
cristyfccdab92009-11-30 16:43:57 +00001388 }
1389 else
1390 {
1391 MagickRealType
cristyfccdab92009-11-30 16:43:57 +00001392 gamma;
1393
cristy3f829a22011-07-10 21:40:04 +00001394 /*
1395 Convolve while blending alpha.
1396 */
cristyfccdab92009-11-30 16:43:57 +00001397 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00001398 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001399 {
cristybb503372010-05-27 20:51:26 +00001400 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001401 {
cristy3f829a22011-07-10 21:40:04 +00001402 MagickRealType
1403 alpha;
1404
cristy4c08aed2011-07-01 19:47:50 +00001405 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,
cristy3f829a22011-07-10 21:40:04 +00001406 kernel_pixels+u*image_components));
1407 for (i=0; i < (ssize_t) image_components; i++)
1408 if ((GetPixelTraits(image,i) & ActivePixelTrait) != 0)
1409 {
1410 if ((GetPixelTraits(image,i) & BlendPixelTrait) == 0)
1411 pixel[i]+=(*k)*kernel_pixels[u*image_components+i];
1412 else
1413 pixel[i]+=(*k)*alpha*kernel_pixels[u*image_components+i];
1414 }
cristyfccdab92009-11-30 16:43:57 +00001415 gamma+=(*k)*alpha;
1416 k++;
1417 }
cristy3f829a22011-07-10 21:40:04 +00001418 kernel_pixels+=(image->columns+width)*image_components;
cristyfccdab92009-11-30 16:43:57 +00001419 }
1420 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy3f829a22011-07-10 21:40:04 +00001421 for (i=0; i < (ssize_t) image_components; i++)
1422 {
1423 if (((GetPixelTraits(image,i) & ActivePixelTrait) != 0) &&
1424 ((GetPixelTraits(convolve_image,i) & ActivePixelTrait) != 0))
cristyfccdab92009-11-30 16:43:57 +00001425 {
cristy3f829a22011-07-10 21:40:04 +00001426 if ((GetPixelTraits(image,i) & BlendPixelTrait) != 0)
1427 q[i]=ClampToQuantum(gamma*pixel[i]);
1428 else
1429 q[i]=ClampToQuantum(pixel[i]);
cristyfccdab92009-11-30 16:43:57 +00001430 }
cristy3f829a22011-07-10 21:40:04 +00001431 }
cristyfccdab92009-11-30 16:43:57 +00001432 }
cristy3f829a22011-07-10 21:40:04 +00001433 p+=image_components;
1434 q+=convolve_components;
cristyfccdab92009-11-30 16:43:57 +00001435 }
cristy3f829a22011-07-10 21:40:04 +00001436 if (SyncCacheViewAuthenticPixels(convolve_view,exception) == MagickFalse)
cristyfccdab92009-11-30 16:43:57 +00001437 status=MagickFalse;
1438 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1439 {
1440 MagickBooleanType
1441 proceed;
1442
1443#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00001444 #pragma omp critical (MagickCore_ConvolveImage)
cristyfccdab92009-11-30 16:43:57 +00001445#endif
1446 proceed=SetImageProgress(image,ConvolveImageTag,progress++,image->rows);
1447 if (proceed == MagickFalse)
1448 status=MagickFalse;
1449 }
1450 }
1451 convolve_image->type=image->type;
1452 convolve_view=DestroyCacheView(convolve_view);
1453 image_view=DestroyCacheView(image_view);
1454 normal_kernel=(double *) RelinquishMagickMemory(normal_kernel);
1455 if (status == MagickFalse)
1456 convolve_image=DestroyImage(convolve_image);
1457 return(convolve_image);
1458}
1459
1460/*
1461%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1462% %
1463% %
1464% %
cristy3ed852e2009-09-05 21:47:34 +00001465% D e s p e c k l e I m a g e %
1466% %
1467% %
1468% %
1469%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1470%
1471% DespeckleImage() reduces the speckle noise in an image while perserving the
1472% edges of the original image.
1473%
1474% The format of the DespeckleImage method is:
1475%
1476% Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1477%
1478% A description of each parameter follows:
1479%
1480% o image: the image.
1481%
1482% o exception: return any errors or warnings in this structure.
1483%
1484*/
1485
cristybb503372010-05-27 20:51:26 +00001486static void Hull(const ssize_t x_offset,const ssize_t y_offset,
1487 const size_t columns,const size_t rows,Quantum *f,Quantum *g,
cristy3ed852e2009-09-05 21:47:34 +00001488 const int polarity)
1489{
cristy3ed852e2009-09-05 21:47:34 +00001490 MagickRealType
1491 v;
1492
cristy3ed852e2009-09-05 21:47:34 +00001493 register Quantum
1494 *p,
1495 *q,
1496 *r,
1497 *s;
1498
cristy117ff172010-08-15 21:35:32 +00001499 register ssize_t
1500 x;
1501
1502 ssize_t
1503 y;
1504
cristy3ed852e2009-09-05 21:47:34 +00001505 assert(f != (Quantum *) NULL);
1506 assert(g != (Quantum *) NULL);
1507 p=f+(columns+2);
1508 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001509 r=p+(y_offset*((ssize_t) columns+2)+x_offset);
1510 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001511 {
1512 p++;
1513 q++;
1514 r++;
1515 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001516 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001517 {
1518 v=(MagickRealType) (*p);
1519 if ((MagickRealType) *r >= (v+(MagickRealType) ScaleCharToQuantum(2)))
1520 v+=ScaleCharToQuantum(1);
1521 *q=(Quantum) v;
1522 p++;
1523 q++;
1524 r++;
1525 }
1526 else
cristybb503372010-05-27 20:51:26 +00001527 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001528 {
1529 v=(MagickRealType) (*p);
1530 if ((MagickRealType) *r <= (v-(MagickRealType) ScaleCharToQuantum(2)))
cristybb503372010-05-27 20:51:26 +00001531 v-=(ssize_t) ScaleCharToQuantum(1);
cristy3ed852e2009-09-05 21:47:34 +00001532 *q=(Quantum) v;
1533 p++;
1534 q++;
1535 r++;
1536 }
1537 p++;
1538 q++;
1539 r++;
1540 }
1541 p=f+(columns+2);
1542 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001543 r=q+(y_offset*((ssize_t) columns+2)+x_offset);
1544 s=q-(y_offset*((ssize_t) columns+2)+x_offset);
1545 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001546 {
1547 p++;
1548 q++;
1549 r++;
1550 s++;
1551 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001552 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001553 {
1554 v=(MagickRealType) (*q);
1555 if (((MagickRealType) *s >=
1556 (v+(MagickRealType) ScaleCharToQuantum(2))) &&
1557 ((MagickRealType) *r > v))
1558 v+=ScaleCharToQuantum(1);
1559 *p=(Quantum) v;
1560 p++;
1561 q++;
1562 r++;
1563 s++;
1564 }
1565 else
cristybb503372010-05-27 20:51:26 +00001566 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001567 {
1568 v=(MagickRealType) (*q);
1569 if (((MagickRealType) *s <=
1570 (v-(MagickRealType) ScaleCharToQuantum(2))) &&
1571 ((MagickRealType) *r < v))
1572 v-=(MagickRealType) ScaleCharToQuantum(1);
1573 *p=(Quantum) v;
1574 p++;
1575 q++;
1576 r++;
1577 s++;
1578 }
1579 p++;
1580 q++;
1581 r++;
1582 s++;
1583 }
1584}
1585
1586MagickExport Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1587{
1588#define DespeckleImageTag "Despeckle/Image"
1589
cristy2407fc22009-09-11 00:55:25 +00001590 CacheView
1591 *despeckle_view,
1592 *image_view;
1593
cristy3ed852e2009-09-05 21:47:34 +00001594 Image
1595 *despeckle_image;
1596
cristy3ed852e2009-09-05 21:47:34 +00001597 MagickBooleanType
1598 status;
1599
cristya58c3172011-02-19 19:23:11 +00001600 register ssize_t
1601 i;
1602
cristy3ed852e2009-09-05 21:47:34 +00001603 Quantum
cristy65b9f392011-02-22 14:22:54 +00001604 *restrict buffers,
1605 *restrict pixels;
cristy3ed852e2009-09-05 21:47:34 +00001606
1607 size_t
cristya58c3172011-02-19 19:23:11 +00001608 length,
1609 number_channels;
cristy117ff172010-08-15 21:35:32 +00001610
cristybb503372010-05-27 20:51:26 +00001611 static const ssize_t
cristy691a29e2009-09-11 00:44:10 +00001612 X[4] = {0, 1, 1,-1},
1613 Y[4] = {1, 0, 1, 1};
cristy3ed852e2009-09-05 21:47:34 +00001614
cristy3ed852e2009-09-05 21:47:34 +00001615 /*
1616 Allocate despeckled image.
1617 */
1618 assert(image != (const Image *) NULL);
1619 assert(image->signature == MagickSignature);
1620 if (image->debug != MagickFalse)
1621 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1622 assert(exception != (ExceptionInfo *) NULL);
1623 assert(exception->signature == MagickSignature);
1624 despeckle_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1625 exception);
1626 if (despeckle_image == (Image *) NULL)
1627 return((Image *) NULL);
1628 if (SetImageStorageClass(despeckle_image,DirectClass) == MagickFalse)
1629 {
1630 InheritException(exception,&despeckle_image->exception);
1631 despeckle_image=DestroyImage(despeckle_image);
1632 return((Image *) NULL);
1633 }
1634 /*
1635 Allocate image buffers.
1636 */
1637 length=(size_t) ((image->columns+2)*(image->rows+2));
cristy65b9f392011-02-22 14:22:54 +00001638 pixels=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1639 buffers=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1640 if ((pixels == (Quantum *) NULL) || (buffers == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001641 {
cristy65b9f392011-02-22 14:22:54 +00001642 if (buffers != (Quantum *) NULL)
1643 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1644 if (pixels != (Quantum *) NULL)
1645 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001646 despeckle_image=DestroyImage(despeckle_image);
1647 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1648 }
1649 /*
1650 Reduce speckle in the image.
1651 */
1652 status=MagickTrue;
cristy109695a2011-02-19 19:38:14 +00001653 number_channels=(size_t) (image->colorspace == CMYKColorspace ? 5 : 4);
cristy3ed852e2009-09-05 21:47:34 +00001654 image_view=AcquireCacheView(image);
1655 despeckle_view=AcquireCacheView(despeckle_image);
cristy8df3d002011-02-19 19:40:59 +00001656 for (i=0; i < (ssize_t) number_channels; i++)
cristy3ed852e2009-09-05 21:47:34 +00001657 {
cristy3ed852e2009-09-05 21:47:34 +00001658 register Quantum
1659 *buffer,
1660 *pixel;
1661
cristyc1488b52011-02-19 18:54:15 +00001662 register ssize_t
cristya58c3172011-02-19 19:23:11 +00001663 k,
cristyc1488b52011-02-19 18:54:15 +00001664 x;
1665
cristy117ff172010-08-15 21:35:32 +00001666 ssize_t
1667 j,
1668 y;
1669
cristy3ed852e2009-09-05 21:47:34 +00001670 if (status == MagickFalse)
1671 continue;
cristy65b9f392011-02-22 14:22:54 +00001672 pixel=pixels;
cristy3ed852e2009-09-05 21:47:34 +00001673 (void) ResetMagickMemory(pixel,0,length*sizeof(*pixel));
cristy65b9f392011-02-22 14:22:54 +00001674 buffer=buffers;
cristybb503372010-05-27 20:51:26 +00001675 j=(ssize_t) image->columns+2;
1676 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001677 {
cristy4c08aed2011-07-01 19:47:50 +00001678 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001679 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001680
1681 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001682 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001683 break;
1684 j++;
cristybb503372010-05-27 20:51:26 +00001685 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001686 {
cristya58c3172011-02-19 19:23:11 +00001687 switch (i)
cristy3ed852e2009-09-05 21:47:34 +00001688 {
cristy4c08aed2011-07-01 19:47:50 +00001689 case 0: pixel[j]=GetPixelRed(image,p); break;
1690 case 1: pixel[j]=GetPixelGreen(image,p); break;
1691 case 2: pixel[j]=GetPixelBlue(image,p); break;
1692 case 3: pixel[j]=GetPixelAlpha(image,p); break;
1693 case 4: pixel[j]=GetPixelBlack(image,p); break;
cristy3ed852e2009-09-05 21:47:34 +00001694 default: break;
1695 }
cristydcfc1ad2011-07-07 16:25:41 +00001696 p+=GetPixelComponents(image);
cristy3ed852e2009-09-05 21:47:34 +00001697 j++;
1698 }
1699 j++;
1700 }
cristy3ed852e2009-09-05 21:47:34 +00001701 (void) ResetMagickMemory(buffer,0,length*sizeof(*buffer));
cristya58c3172011-02-19 19:23:11 +00001702 for (k=0; k < 4; k++)
cristy3ed852e2009-09-05 21:47:34 +00001703 {
cristya58c3172011-02-19 19:23:11 +00001704 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,1);
1705 Hull(-X[k],-Y[k],image->columns,image->rows,pixel,buffer,1);
1706 Hull(-X[k],-Y[k],image->columns,image->rows,pixel,buffer,-1);
1707 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,-1);
cristy3ed852e2009-09-05 21:47:34 +00001708 }
cristybb503372010-05-27 20:51:26 +00001709 j=(ssize_t) image->columns+2;
1710 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001711 {
1712 MagickBooleanType
1713 sync;
1714
cristy4c08aed2011-07-01 19:47:50 +00001715 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001716 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001717
1718 q=GetCacheViewAuthenticPixels(despeckle_view,0,y,despeckle_image->columns,
1719 1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001720 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001721 break;
1722 j++;
cristybb503372010-05-27 20:51:26 +00001723 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001724 {
cristya58c3172011-02-19 19:23:11 +00001725 switch (i)
cristy3ed852e2009-09-05 21:47:34 +00001726 {
cristy4c08aed2011-07-01 19:47:50 +00001727 case 0: SetPixelRed(despeckle_image,pixel[j],q); break;
1728 case 1: SetPixelGreen(despeckle_image,pixel[j],q); break;
1729 case 2: SetPixelBlue(despeckle_image,pixel[j],q); break;
1730 case 3: SetPixelAlpha(despeckle_image,pixel[j],q); break;
1731 case 4: SetPixelBlack(despeckle_image,pixel[j],q); break;
cristy3ed852e2009-09-05 21:47:34 +00001732 default: break;
1733 }
cristydcfc1ad2011-07-07 16:25:41 +00001734 q+=GetPixelComponents(despeckle_image);
cristy3ed852e2009-09-05 21:47:34 +00001735 j++;
1736 }
1737 sync=SyncCacheViewAuthenticPixels(despeckle_view,exception);
1738 if (sync == MagickFalse)
1739 {
1740 status=MagickFalse;
1741 break;
1742 }
1743 j++;
1744 }
1745 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1746 {
1747 MagickBooleanType
1748 proceed;
1749
cristya58c3172011-02-19 19:23:11 +00001750 proceed=SetImageProgress(image,DespeckleImageTag,(MagickOffsetType) i,
1751 number_channels);
cristy3ed852e2009-09-05 21:47:34 +00001752 if (proceed == MagickFalse)
1753 status=MagickFalse;
1754 }
1755 }
1756 despeckle_view=DestroyCacheView(despeckle_view);
1757 image_view=DestroyCacheView(image_view);
cristy65b9f392011-02-22 14:22:54 +00001758 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1759 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001760 despeckle_image->type=image->type;
1761 if (status == MagickFalse)
1762 despeckle_image=DestroyImage(despeckle_image);
1763 return(despeckle_image);
1764}
1765
1766/*
1767%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1768% %
1769% %
1770% %
1771% E d g e I m a g e %
1772% %
1773% %
1774% %
1775%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1776%
1777% EdgeImage() finds edges in an image. Radius defines the radius of the
1778% convolution filter. Use a radius of 0 and EdgeImage() selects a suitable
1779% radius for you.
1780%
1781% The format of the EdgeImage method is:
1782%
1783% Image *EdgeImage(const Image *image,const double radius,
1784% ExceptionInfo *exception)
1785%
1786% A description of each parameter follows:
1787%
1788% o image: the image.
1789%
1790% o radius: the radius of the pixel neighborhood.
1791%
1792% o exception: return any errors or warnings in this structure.
1793%
1794*/
1795MagickExport Image *EdgeImage(const Image *image,const double radius,
1796 ExceptionInfo *exception)
1797{
1798 Image
1799 *edge_image;
1800
1801 double
1802 *kernel;
1803
cristybb503372010-05-27 20:51:26 +00001804 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001805 i;
1806
cristybb503372010-05-27 20:51:26 +00001807 size_t
cristy3ed852e2009-09-05 21:47:34 +00001808 width;
1809
1810 assert(image != (const Image *) NULL);
1811 assert(image->signature == MagickSignature);
1812 if (image->debug != MagickFalse)
1813 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1814 assert(exception != (ExceptionInfo *) NULL);
1815 assert(exception->signature == MagickSignature);
1816 width=GetOptimalKernelWidth1D(radius,0.5);
1817 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
1818 if (kernel == (double *) NULL)
1819 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00001820 for (i=0; i < (ssize_t) (width*width); i++)
cristy3ed852e2009-09-05 21:47:34 +00001821 kernel[i]=(-1.0);
1822 kernel[i/2]=(double) (width*width-1.0);
1823 edge_image=ConvolveImage(image,width,kernel,exception);
1824 kernel=(double *) RelinquishMagickMemory(kernel);
1825 return(edge_image);
1826}
1827
1828/*
1829%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1830% %
1831% %
1832% %
1833% E m b o s s I m a g e %
1834% %
1835% %
1836% %
1837%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1838%
1839% EmbossImage() returns a grayscale image with a three-dimensional effect.
1840% We convolve the image with a Gaussian operator of the given radius and
1841% standard deviation (sigma). For reasonable results, radius should be
1842% larger than sigma. Use a radius of 0 and Emboss() selects a suitable
1843% radius for you.
1844%
1845% The format of the EmbossImage method is:
1846%
1847% Image *EmbossImage(const Image *image,const double radius,
1848% const double sigma,ExceptionInfo *exception)
1849%
1850% A description of each parameter follows:
1851%
1852% o image: the image.
1853%
1854% o radius: the radius of the pixel neighborhood.
1855%
1856% o sigma: the standard deviation of the Gaussian, in pixels.
1857%
1858% o exception: return any errors or warnings in this structure.
1859%
1860*/
1861MagickExport Image *EmbossImage(const Image *image,const double radius,
1862 const double sigma,ExceptionInfo *exception)
1863{
1864 double
1865 *kernel;
1866
1867 Image
1868 *emboss_image;
1869
cristybb503372010-05-27 20:51:26 +00001870 register ssize_t
cristy47e00502009-12-17 19:19:57 +00001871 i;
1872
cristybb503372010-05-27 20:51:26 +00001873 size_t
cristy3ed852e2009-09-05 21:47:34 +00001874 width;
1875
cristy117ff172010-08-15 21:35:32 +00001876 ssize_t
1877 j,
1878 k,
1879 u,
1880 v;
1881
cristy3ed852e2009-09-05 21:47:34 +00001882 assert(image != (Image *) NULL);
1883 assert(image->signature == MagickSignature);
1884 if (image->debug != MagickFalse)
1885 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1886 assert(exception != (ExceptionInfo *) NULL);
1887 assert(exception->signature == MagickSignature);
1888 width=GetOptimalKernelWidth2D(radius,sigma);
1889 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
1890 if (kernel == (double *) NULL)
1891 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00001892 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +00001893 k=j;
1894 i=0;
1895 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00001896 {
cristy47e00502009-12-17 19:19:57 +00001897 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00001898 {
cristy4205a3c2010-09-12 20:19:59 +00001899 kernel[i]=(double) (((u < 0) || (v < 0) ? -8.0 : 8.0)*
cristy47e00502009-12-17 19:19:57 +00001900 exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
cristy4205a3c2010-09-12 20:19:59 +00001901 (2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +00001902 if (u != k)
cristy3ed852e2009-09-05 21:47:34 +00001903 kernel[i]=0.0;
1904 i++;
1905 }
cristy47e00502009-12-17 19:19:57 +00001906 k--;
cristy3ed852e2009-09-05 21:47:34 +00001907 }
1908 emboss_image=ConvolveImage(image,width,kernel,exception);
1909 if (emboss_image != (Image *) NULL)
1910 (void) EqualizeImage(emboss_image);
1911 kernel=(double *) RelinquishMagickMemory(kernel);
1912 return(emboss_image);
1913}
1914
1915/*
1916%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1917% %
1918% %
1919% %
cristy56a9e512010-01-06 18:18:55 +00001920% F i l t e r I m a g e %
1921% %
1922% %
1923% %
1924%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1925%
1926% FilterImage() applies a custom convolution kernel to the image.
1927%
1928% The format of the FilterImage method is:
1929%
cristy2be15382010-01-21 02:38:03 +00001930% Image *FilterImage(const Image *image,const KernelInfo *kernel,
cristy56a9e512010-01-06 18:18:55 +00001931% ExceptionInfo *exception)
cristy56a9e512010-01-06 18:18:55 +00001932%
1933% A description of each parameter follows:
1934%
1935% o image: the image.
1936%
cristy56a9e512010-01-06 18:18:55 +00001937% o kernel: the filtering kernel.
1938%
1939% o exception: return any errors or warnings in this structure.
1940%
1941*/
cristyf4ad9df2011-07-08 16:49:03 +00001942MagickExport Image *FilterImage(const Image *image,
1943 const KernelInfo *kernel,ExceptionInfo *exception)
cristy56a9e512010-01-06 18:18:55 +00001944{
1945#define FilterImageTag "Filter/Image"
1946
1947 CacheView
1948 *filter_view,
1949 *image_view;
1950
cristy56a9e512010-01-06 18:18:55 +00001951 Image
1952 *filter_image;
1953
cristy56a9e512010-01-06 18:18:55 +00001954 MagickBooleanType
1955 status;
1956
cristybb503372010-05-27 20:51:26 +00001957 MagickOffsetType
1958 progress;
1959
cristy4c08aed2011-07-01 19:47:50 +00001960 PixelInfo
cristy56a9e512010-01-06 18:18:55 +00001961 bias;
1962
cristybb503372010-05-27 20:51:26 +00001963 ssize_t
1964 y;
1965
cristy56a9e512010-01-06 18:18:55 +00001966 /*
1967 Initialize filter image attributes.
1968 */
1969 assert(image != (Image *) NULL);
1970 assert(image->signature == MagickSignature);
1971 if (image->debug != MagickFalse)
1972 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1973 assert(exception != (ExceptionInfo *) NULL);
1974 assert(exception->signature == MagickSignature);
1975 if ((kernel->width % 2) == 0)
1976 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
1977 filter_image=CloneImage(image,0,0,MagickTrue,exception);
1978 if (filter_image == (Image *) NULL)
1979 return((Image *) NULL);
1980 if (SetImageStorageClass(filter_image,DirectClass) == MagickFalse)
1981 {
1982 InheritException(exception,&filter_image->exception);
1983 filter_image=DestroyImage(filter_image);
1984 return((Image *) NULL);
1985 }
1986 if (image->debug != MagickFalse)
1987 {
1988 char
1989 format[MaxTextExtent],
1990 *message;
1991
cristy117ff172010-08-15 21:35:32 +00001992 register const double
1993 *k;
1994
cristybb503372010-05-27 20:51:26 +00001995 ssize_t
cristy56a9e512010-01-06 18:18:55 +00001996 u,
1997 v;
1998
cristy56a9e512010-01-06 18:18:55 +00001999 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00002000 " FilterImage with %.20gx%.20g kernel:",(double) kernel->width,(double)
2001 kernel->height);
cristy56a9e512010-01-06 18:18:55 +00002002 message=AcquireString("");
2003 k=kernel->values;
cristybb503372010-05-27 20:51:26 +00002004 for (v=0; v < (ssize_t) kernel->height; v++)
cristy56a9e512010-01-06 18:18:55 +00002005 {
2006 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00002007 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy56a9e512010-01-06 18:18:55 +00002008 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00002009 for (u=0; u < (ssize_t) kernel->width; u++)
cristy56a9e512010-01-06 18:18:55 +00002010 {
cristyb51dff52011-05-19 16:55:47 +00002011 (void) FormatLocaleString(format,MaxTextExtent,"%g ",*k++);
cristy56a9e512010-01-06 18:18:55 +00002012 (void) ConcatenateString(&message,format);
2013 }
2014 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
2015 }
2016 message=DestroyString(message);
2017 }
cristy36826ab2010-03-06 01:29:30 +00002018 status=AccelerateConvolveImage(image,kernel,filter_image,exception);
cristyd43a46b2010-01-21 02:13:41 +00002019 if (status == MagickTrue)
2020 return(filter_image);
cristy56a9e512010-01-06 18:18:55 +00002021 /*
2022 Filter image.
2023 */
2024 status=MagickTrue;
2025 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00002026 GetPixelInfo(image,&bias);
2027 SetPixelInfoBias(image,&bias);
cristy56a9e512010-01-06 18:18:55 +00002028 image_view=AcquireCacheView(image);
2029 filter_view=AcquireCacheView(filter_image);
2030#if defined(MAGICKCORE_OPENMP_SUPPORT)
2031 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2032#endif
cristybb503372010-05-27 20:51:26 +00002033 for (y=0; y < (ssize_t) image->rows; y++)
cristy56a9e512010-01-06 18:18:55 +00002034 {
2035 MagickBooleanType
2036 sync;
2037
cristy4c08aed2011-07-01 19:47:50 +00002038 register const Quantum
cristy56a9e512010-01-06 18:18:55 +00002039 *restrict p;
2040
cristy4c08aed2011-07-01 19:47:50 +00002041 register Quantum
cristy56a9e512010-01-06 18:18:55 +00002042 *restrict q;
2043
cristy117ff172010-08-15 21:35:32 +00002044 register ssize_t
2045 x;
2046
cristy56a9e512010-01-06 18:18:55 +00002047 if (status == MagickFalse)
2048 continue;
cristybb503372010-05-27 20:51:26 +00002049 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) kernel->width/2L),
cristy117ff172010-08-15 21:35:32 +00002050 y-(ssize_t) (kernel->height/2L),image->columns+kernel->width,
2051 kernel->height,exception);
cristy56a9e512010-01-06 18:18:55 +00002052 q=GetCacheViewAuthenticPixels(filter_view,0,y,filter_image->columns,1,
2053 exception);
cristy4c08aed2011-07-01 19:47:50 +00002054 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy56a9e512010-01-06 18:18:55 +00002055 {
2056 status=MagickFalse;
2057 continue;
2058 }
cristybb503372010-05-27 20:51:26 +00002059 for (x=0; x < (ssize_t) image->columns; x++)
cristy56a9e512010-01-06 18:18:55 +00002060 {
cristy4c08aed2011-07-01 19:47:50 +00002061 PixelInfo
cristy56a9e512010-01-06 18:18:55 +00002062 pixel;
2063
2064 register const double
2065 *restrict k;
2066
cristy4c08aed2011-07-01 19:47:50 +00002067 register const Quantum
cristy56a9e512010-01-06 18:18:55 +00002068 *restrict kernel_pixels;
2069
cristybb503372010-05-27 20:51:26 +00002070 register ssize_t
cristy56a9e512010-01-06 18:18:55 +00002071 u;
2072
cristy117ff172010-08-15 21:35:32 +00002073 ssize_t
2074 v;
2075
cristy56a9e512010-01-06 18:18:55 +00002076 pixel=bias;
cristy36826ab2010-03-06 01:29:30 +00002077 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002078 kernel_pixels=p;
cristy5ce8df82011-07-07 14:52:23 +00002079 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) == 0) ||
2080 (image->matte == MagickFalse))
cristy56a9e512010-01-06 18:18:55 +00002081 {
cristybb503372010-05-27 20:51:26 +00002082 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002083 {
cristybb503372010-05-27 20:51:26 +00002084 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002085 {
cristy4c08aed2011-07-01 19:47:50 +00002086 pixel.red+=(*k)*GetPixelRed(image,kernel_pixels+u*
cristydcfc1ad2011-07-07 16:25:41 +00002087 GetPixelComponents(image));
cristy4c08aed2011-07-01 19:47:50 +00002088 pixel.green+=(*k)*GetPixelGreen(image,kernel_pixels+u*
cristydcfc1ad2011-07-07 16:25:41 +00002089 GetPixelComponents(image));
cristy4c08aed2011-07-01 19:47:50 +00002090 pixel.blue+=(*k)*GetPixelBlue(image,kernel_pixels+u*
cristydcfc1ad2011-07-07 16:25:41 +00002091 GetPixelComponents(image));
cristy4c08aed2011-07-01 19:47:50 +00002092 if (image->colorspace == CMYKColorspace)
2093 pixel.black+=(*k)*GetPixelBlack(image,kernel_pixels+u*
cristydcfc1ad2011-07-07 16:25:41 +00002094 GetPixelComponents(image));
cristy56a9e512010-01-06 18:18:55 +00002095 k++;
2096 }
cristy4c08aed2011-07-01 19:47:50 +00002097 kernel_pixels+=(image->columns+kernel->width)*
cristydcfc1ad2011-07-07 16:25:41 +00002098 GetPixelComponents(image);
cristy56a9e512010-01-06 18:18:55 +00002099 }
cristy2b9582a2011-07-04 17:38:56 +00002100 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002101 SetPixelRed(filter_image,ClampToQuantum(pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00002102 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002103 SetPixelGreen(filter_image,ClampToQuantum(pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00002104 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002105 SetPixelBlue(filter_image,ClampToQuantum(pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00002106 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002107 (image->colorspace == CMYKColorspace))
2108 SetPixelBlack(filter_image,ClampToQuantum(pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00002109 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy56a9e512010-01-06 18:18:55 +00002110 {
cristy36826ab2010-03-06 01:29:30 +00002111 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002112 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00002113 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002114 {
cristybb503372010-05-27 20:51:26 +00002115 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002116 {
cristy4c08aed2011-07-01 19:47:50 +00002117 pixel.alpha+=(*k)*GetPixelRed(image,kernel_pixels+u*
cristydcfc1ad2011-07-07 16:25:41 +00002118 GetPixelComponents(image));
cristy56a9e512010-01-06 18:18:55 +00002119 k++;
2120 }
cristy4c08aed2011-07-01 19:47:50 +00002121 kernel_pixels+=(image->columns+kernel->width)*
cristydcfc1ad2011-07-07 16:25:41 +00002122 GetPixelComponents(image);
cristy56a9e512010-01-06 18:18:55 +00002123 }
cristy4c08aed2011-07-01 19:47:50 +00002124 SetPixelAlpha(filter_image,ClampToQuantum(pixel.alpha),q);
cristy56a9e512010-01-06 18:18:55 +00002125 }
2126 }
2127 else
2128 {
2129 MagickRealType
2130 alpha,
2131 gamma;
2132
2133 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00002134 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002135 {
cristybb503372010-05-27 20:51:26 +00002136 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002137 {
cristy4c08aed2011-07-01 19:47:50 +00002138 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,
cristydcfc1ad2011-07-07 16:25:41 +00002139 kernel_pixels+u*GetPixelComponents(image)));
cristy4c08aed2011-07-01 19:47:50 +00002140 pixel.red+=(*k)*alpha*GetPixelRed(image,kernel_pixels+u*
cristydcfc1ad2011-07-07 16:25:41 +00002141 GetPixelComponents(image));
cristy4c08aed2011-07-01 19:47:50 +00002142 pixel.green+=(*k)*alpha*GetPixelGreen(image,kernel_pixels+u*
cristydcfc1ad2011-07-07 16:25:41 +00002143 GetPixelComponents(image));
cristy4c08aed2011-07-01 19:47:50 +00002144 pixel.blue+=(*k)*alpha*GetPixelBlue(image,kernel_pixels+u*
cristydcfc1ad2011-07-07 16:25:41 +00002145 GetPixelComponents(image));
cristy4c08aed2011-07-01 19:47:50 +00002146 if (image->colorspace == CMYKColorspace)
2147 pixel.black+=(*k)*alpha*GetPixelBlack(image,kernel_pixels+u*
cristydcfc1ad2011-07-07 16:25:41 +00002148 GetPixelComponents(image));
cristy56a9e512010-01-06 18:18:55 +00002149 gamma+=(*k)*alpha;
2150 k++;
2151 }
cristy5ce8df82011-07-07 14:52:23 +00002152 kernel_pixels+=(image->columns+kernel->width)*
cristydcfc1ad2011-07-07 16:25:41 +00002153 GetPixelComponents(image);
cristy56a9e512010-01-06 18:18:55 +00002154 }
2155 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy2b9582a2011-07-04 17:38:56 +00002156 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002157 SetPixelRed(filter_image,ClampToQuantum(gamma*pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00002158 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002159 SetPixelGreen(filter_image,ClampToQuantum(gamma*pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00002160 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002161 SetPixelBlue(filter_image,ClampToQuantum(gamma*pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00002162 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy56a9e512010-01-06 18:18:55 +00002163 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002164 SetPixelBlack(filter_image,ClampToQuantum(gamma*pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00002165 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy56a9e512010-01-06 18:18:55 +00002166 {
cristy36826ab2010-03-06 01:29:30 +00002167 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002168 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00002169 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002170 {
cristybb503372010-05-27 20:51:26 +00002171 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002172 {
cristy4c08aed2011-07-01 19:47:50 +00002173 pixel.alpha+=(*k)*GetPixelAlpha(image,kernel_pixels+u*
cristydcfc1ad2011-07-07 16:25:41 +00002174 GetPixelComponents(image));
cristy56a9e512010-01-06 18:18:55 +00002175 k++;
2176 }
cristy4c08aed2011-07-01 19:47:50 +00002177 kernel_pixels+=(image->columns+kernel->width)*
cristydcfc1ad2011-07-07 16:25:41 +00002178 GetPixelComponents(image);
cristy56a9e512010-01-06 18:18:55 +00002179 }
cristy4c08aed2011-07-01 19:47:50 +00002180 SetPixelAlpha(filter_image,ClampToQuantum(pixel.alpha),q);
cristy56a9e512010-01-06 18:18:55 +00002181 }
2182 }
cristydcfc1ad2011-07-07 16:25:41 +00002183 p+=GetPixelComponents(image);
2184 q+=GetPixelComponents(filter_image);
cristy56a9e512010-01-06 18:18:55 +00002185 }
2186 sync=SyncCacheViewAuthenticPixels(filter_view,exception);
2187 if (sync == MagickFalse)
2188 status=MagickFalse;
2189 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2190 {
2191 MagickBooleanType
2192 proceed;
2193
2194#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00002195 #pragma omp critical (MagickCore_FilterImage)
cristy56a9e512010-01-06 18:18:55 +00002196#endif
2197 proceed=SetImageProgress(image,FilterImageTag,progress++,image->rows);
2198 if (proceed == MagickFalse)
2199 status=MagickFalse;
2200 }
2201 }
2202 filter_image->type=image->type;
2203 filter_view=DestroyCacheView(filter_view);
2204 image_view=DestroyCacheView(image_view);
cristy56a9e512010-01-06 18:18:55 +00002205 if (status == MagickFalse)
2206 filter_image=DestroyImage(filter_image);
2207 return(filter_image);
2208}
2209
2210/*
2211%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2212% %
2213% %
2214% %
cristy3ed852e2009-09-05 21:47:34 +00002215% G a u s s i a n B l u r I m a g e %
2216% %
2217% %
2218% %
2219%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2220%
2221% GaussianBlurImage() blurs an image. We convolve the image with a
2222% Gaussian operator of the given radius and standard deviation (sigma).
2223% For reasonable results, the radius should be larger than sigma. Use a
2224% radius of 0 and GaussianBlurImage() selects a suitable radius for you
2225%
2226% The format of the GaussianBlurImage method is:
2227%
2228% Image *GaussianBlurImage(const Image *image,onst double radius,
2229% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002230%
2231% A description of each parameter follows:
2232%
2233% o image: the image.
2234%
cristy3ed852e2009-09-05 21:47:34 +00002235% o radius: the radius of the Gaussian, in pixels, not counting the center
2236% pixel.
2237%
2238% o sigma: the standard deviation of the Gaussian, in pixels.
2239%
2240% o exception: return any errors or warnings in this structure.
2241%
2242*/
cristyf4ad9df2011-07-08 16:49:03 +00002243MagickExport Image *GaussianBlurImage(const Image *image,
2244 const double radius,const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002245{
2246 double
2247 *kernel;
2248
2249 Image
2250 *blur_image;
2251
cristybb503372010-05-27 20:51:26 +00002252 register ssize_t
cristy47e00502009-12-17 19:19:57 +00002253 i;
2254
cristybb503372010-05-27 20:51:26 +00002255 size_t
cristy3ed852e2009-09-05 21:47:34 +00002256 width;
2257
cristy117ff172010-08-15 21:35:32 +00002258 ssize_t
2259 j,
2260 u,
2261 v;
2262
cristy3ed852e2009-09-05 21:47:34 +00002263 assert(image != (const Image *) NULL);
2264 assert(image->signature == MagickSignature);
2265 if (image->debug != MagickFalse)
2266 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2267 assert(exception != (ExceptionInfo *) NULL);
2268 assert(exception->signature == MagickSignature);
2269 width=GetOptimalKernelWidth2D(radius,sigma);
2270 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2271 if (kernel == (double *) NULL)
2272 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00002273 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00002274 i=0;
cristy47e00502009-12-17 19:19:57 +00002275 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002276 {
cristy47e00502009-12-17 19:19:57 +00002277 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00002278 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
2279 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00002280 }
cristyf4ad9df2011-07-08 16:49:03 +00002281 blur_image=ConvolveImage(image,width,kernel,exception);
cristy3ed852e2009-09-05 21:47:34 +00002282 kernel=(double *) RelinquishMagickMemory(kernel);
2283 return(blur_image);
2284}
2285
2286/*
2287%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2288% %
2289% %
2290% %
cristy3ed852e2009-09-05 21:47:34 +00002291% M o t i o n B l u r I m a g e %
2292% %
2293% %
2294% %
2295%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2296%
2297% MotionBlurImage() simulates motion blur. We convolve the image with a
2298% Gaussian operator of the given radius and standard deviation (sigma).
2299% For reasonable results, radius should be larger than sigma. Use a
2300% radius of 0 and MotionBlurImage() selects a suitable radius for you.
2301% Angle gives the angle of the blurring motion.
2302%
2303% Andrew Protano contributed this effect.
2304%
2305% The format of the MotionBlurImage method is:
2306%
2307% Image *MotionBlurImage(const Image *image,const double radius,
2308% const double sigma,const double angle,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002309%
2310% A description of each parameter follows:
2311%
2312% o image: the image.
2313%
cristy3ed852e2009-09-05 21:47:34 +00002314% o radius: the radius of the Gaussian, in pixels, not counting
2315% the center pixel.
2316%
2317% o sigma: the standard deviation of the Gaussian, in pixels.
2318%
cristycee97112010-05-28 00:44:52 +00002319% o angle: Apply the effect along this angle.
cristy3ed852e2009-09-05 21:47:34 +00002320%
2321% o exception: return any errors or warnings in this structure.
2322%
2323*/
2324
cristybb503372010-05-27 20:51:26 +00002325static double *GetMotionBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +00002326{
cristy3ed852e2009-09-05 21:47:34 +00002327 double
cristy47e00502009-12-17 19:19:57 +00002328 *kernel,
cristy3ed852e2009-09-05 21:47:34 +00002329 normalize;
2330
cristybb503372010-05-27 20:51:26 +00002331 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002332 i;
2333
2334 /*
cristy47e00502009-12-17 19:19:57 +00002335 Generate a 1-D convolution kernel.
cristy3ed852e2009-09-05 21:47:34 +00002336 */
2337 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
2338 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
2339 if (kernel == (double *) NULL)
2340 return(kernel);
cristy3ed852e2009-09-05 21:47:34 +00002341 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00002342 for (i=0; i < (ssize_t) width; i++)
cristy47e00502009-12-17 19:19:57 +00002343 {
cristy4205a3c2010-09-12 20:19:59 +00002344 kernel[i]=(double) (exp((-((double) i*i)/(double) (2.0*MagickSigma*
2345 MagickSigma)))/(MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00002346 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +00002347 }
cristybb503372010-05-27 20:51:26 +00002348 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002349 kernel[i]/=normalize;
2350 return(kernel);
2351}
2352
cristyf4ad9df2011-07-08 16:49:03 +00002353MagickExport Image *MotionBlurImage(const Image *image,
2354 const double radius,const double sigma,const double angle,
2355 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002356{
cristyc4c8d132010-01-07 01:58:38 +00002357 CacheView
2358 *blur_view,
2359 *image_view;
2360
cristy3ed852e2009-09-05 21:47:34 +00002361 double
2362 *kernel;
2363
2364 Image
2365 *blur_image;
2366
cristy3ed852e2009-09-05 21:47:34 +00002367 MagickBooleanType
2368 status;
2369
cristybb503372010-05-27 20:51:26 +00002370 MagickOffsetType
2371 progress;
2372
cristy4c08aed2011-07-01 19:47:50 +00002373 PixelInfo
cristyddd82202009-11-03 20:14:50 +00002374 bias;
cristy3ed852e2009-09-05 21:47:34 +00002375
2376 OffsetInfo
2377 *offset;
2378
2379 PointInfo
2380 point;
2381
cristybb503372010-05-27 20:51:26 +00002382 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002383 i;
2384
cristybb503372010-05-27 20:51:26 +00002385 size_t
cristy3ed852e2009-09-05 21:47:34 +00002386 width;
2387
cristybb503372010-05-27 20:51:26 +00002388 ssize_t
2389 y;
2390
cristy3ed852e2009-09-05 21:47:34 +00002391 assert(image != (Image *) NULL);
2392 assert(image->signature == MagickSignature);
2393 if (image->debug != MagickFalse)
2394 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2395 assert(exception != (ExceptionInfo *) NULL);
2396 width=GetOptimalKernelWidth1D(radius,sigma);
2397 kernel=GetMotionBlurKernel(width,sigma);
2398 if (kernel == (double *) NULL)
2399 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2400 offset=(OffsetInfo *) AcquireQuantumMemory(width,sizeof(*offset));
2401 if (offset == (OffsetInfo *) NULL)
2402 {
2403 kernel=(double *) RelinquishMagickMemory(kernel);
2404 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2405 }
2406 blur_image=CloneImage(image,0,0,MagickTrue,exception);
2407 if (blur_image == (Image *) NULL)
2408 {
2409 kernel=(double *) RelinquishMagickMemory(kernel);
2410 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2411 return((Image *) NULL);
2412 }
2413 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
2414 {
2415 kernel=(double *) RelinquishMagickMemory(kernel);
2416 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2417 InheritException(exception,&blur_image->exception);
2418 blur_image=DestroyImage(blur_image);
2419 return((Image *) NULL);
2420 }
2421 point.x=(double) width*sin(DegreesToRadians(angle));
2422 point.y=(double) width*cos(DegreesToRadians(angle));
cristybb503372010-05-27 20:51:26 +00002423 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002424 {
cristybb503372010-05-27 20:51:26 +00002425 offset[i].x=(ssize_t) ceil((double) (i*point.y)/hypot(point.x,point.y)-0.5);
2426 offset[i].y=(ssize_t) ceil((double) (i*point.x)/hypot(point.x,point.y)-0.5);
cristy3ed852e2009-09-05 21:47:34 +00002427 }
2428 /*
2429 Motion blur image.
2430 */
2431 status=MagickTrue;
2432 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00002433 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00002434 image_view=AcquireCacheView(image);
2435 blur_view=AcquireCacheView(blur_image);
cristyb557a152011-02-22 12:14:30 +00002436#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00002437 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00002438#endif
cristybb503372010-05-27 20:51:26 +00002439 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002440 {
cristy4c08aed2011-07-01 19:47:50 +00002441 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002442 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002443
cristy117ff172010-08-15 21:35:32 +00002444 register ssize_t
2445 x;
2446
cristy3ed852e2009-09-05 21:47:34 +00002447 if (status == MagickFalse)
2448 continue;
2449 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
2450 exception);
cristy4c08aed2011-07-01 19:47:50 +00002451 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002452 {
2453 status=MagickFalse;
2454 continue;
2455 }
cristybb503372010-05-27 20:51:26 +00002456 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002457 {
cristy4c08aed2011-07-01 19:47:50 +00002458 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002459 qixel;
2460
2461 PixelPacket
2462 pixel;
2463
2464 register double
cristyc47d1f82009-11-26 01:44:43 +00002465 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00002466
cristybb503372010-05-27 20:51:26 +00002467 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002468 i;
2469
cristy3ed852e2009-09-05 21:47:34 +00002470 k=kernel;
cristyddd82202009-11-03 20:14:50 +00002471 qixel=bias;
cristy2b9582a2011-07-04 17:38:56 +00002472 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00002473 {
cristybb503372010-05-27 20:51:26 +00002474 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002475 {
2476 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
2477 offset[i].y,&pixel,exception);
2478 qixel.red+=(*k)*pixel.red;
2479 qixel.green+=(*k)*pixel.green;
2480 qixel.blue+=(*k)*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00002481 qixel.alpha+=(*k)*pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00002482 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00002483 qixel.black+=(*k)*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00002484 k++;
2485 }
cristy2b9582a2011-07-04 17:38:56 +00002486 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002487 SetPixelRed(blur_image,
2488 ClampToQuantum(qixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00002489 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002490 SetPixelGreen(blur_image,
2491 ClampToQuantum(qixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00002492 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002493 SetPixelBlue(blur_image,
2494 ClampToQuantum(qixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00002495 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002496 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002497 SetPixelBlack(blur_image,
2498 ClampToQuantum(qixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00002499 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002500 SetPixelAlpha(blur_image,
2501 ClampToQuantum(qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00002502 }
2503 else
2504 {
2505 MagickRealType
2506 alpha,
2507 gamma;
2508
2509 alpha=0.0;
2510 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00002511 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002512 {
2513 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
2514 offset[i].y,&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00002515 alpha=(MagickRealType) (QuantumScale*pixel.alpha);
cristy3ed852e2009-09-05 21:47:34 +00002516 qixel.red+=(*k)*alpha*pixel.red;
2517 qixel.green+=(*k)*alpha*pixel.green;
2518 qixel.blue+=(*k)*alpha*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00002519 qixel.alpha+=(*k)*pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00002520 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00002521 qixel.black+=(*k)*alpha*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00002522 gamma+=(*k)*alpha;
2523 k++;
2524 }
2525 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy2b9582a2011-07-04 17:38:56 +00002526 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002527 SetPixelRed(blur_image,
2528 ClampToQuantum(gamma*qixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00002529 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002530 SetPixelGreen(blur_image,
2531 ClampToQuantum(gamma*qixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00002532 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002533 SetPixelBlue(blur_image,
2534 ClampToQuantum(gamma*qixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00002535 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002536 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002537 SetPixelBlack(blur_image,
2538 ClampToQuantum(gamma*qixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00002539 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002540 SetPixelAlpha(blur_image,
2541 ClampToQuantum(qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00002542 }
cristydcfc1ad2011-07-07 16:25:41 +00002543 q+=GetPixelComponents(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00002544 }
2545 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
2546 status=MagickFalse;
2547 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2548 {
2549 MagickBooleanType
2550 proceed;
2551
cristyb557a152011-02-22 12:14:30 +00002552#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00002553 #pragma omp critical (MagickCore_MotionBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00002554#endif
2555 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
2556 if (proceed == MagickFalse)
2557 status=MagickFalse;
2558 }
2559 }
2560 blur_view=DestroyCacheView(blur_view);
2561 image_view=DestroyCacheView(image_view);
2562 kernel=(double *) RelinquishMagickMemory(kernel);
2563 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2564 if (status == MagickFalse)
2565 blur_image=DestroyImage(blur_image);
2566 return(blur_image);
2567}
2568
2569/*
2570%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2571% %
2572% %
2573% %
2574% P r e v i e w I m a g e %
2575% %
2576% %
2577% %
2578%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2579%
2580% PreviewImage() tiles 9 thumbnails of the specified image with an image
2581% processing operation applied with varying parameters. This may be helpful
2582% pin-pointing an appropriate parameter for a particular image processing
2583% operation.
2584%
2585% The format of the PreviewImages method is:
2586%
2587% Image *PreviewImages(const Image *image,const PreviewType preview,
2588% ExceptionInfo *exception)
2589%
2590% A description of each parameter follows:
2591%
2592% o image: the image.
2593%
2594% o preview: the image processing operation.
2595%
2596% o exception: return any errors or warnings in this structure.
2597%
2598*/
2599MagickExport Image *PreviewImage(const Image *image,const PreviewType preview,
2600 ExceptionInfo *exception)
2601{
2602#define NumberTiles 9
2603#define PreviewImageTag "Preview/Image"
2604#define DefaultPreviewGeometry "204x204+10+10"
2605
2606 char
2607 factor[MaxTextExtent],
2608 label[MaxTextExtent];
2609
2610 double
2611 degrees,
2612 gamma,
2613 percentage,
2614 radius,
2615 sigma,
2616 threshold;
2617
2618 Image
2619 *images,
2620 *montage_image,
2621 *preview_image,
2622 *thumbnail;
2623
2624 ImageInfo
2625 *preview_info;
2626
cristy3ed852e2009-09-05 21:47:34 +00002627 MagickBooleanType
2628 proceed;
2629
2630 MontageInfo
2631 *montage_info;
2632
2633 QuantizeInfo
2634 quantize_info;
2635
2636 RectangleInfo
2637 geometry;
2638
cristybb503372010-05-27 20:51:26 +00002639 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002640 i,
2641 x;
2642
cristybb503372010-05-27 20:51:26 +00002643 size_t
cristy3ed852e2009-09-05 21:47:34 +00002644 colors;
2645
cristy117ff172010-08-15 21:35:32 +00002646 ssize_t
2647 y;
2648
cristy3ed852e2009-09-05 21:47:34 +00002649 /*
2650 Open output image file.
2651 */
2652 assert(image != (Image *) NULL);
2653 assert(image->signature == MagickSignature);
2654 if (image->debug != MagickFalse)
2655 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2656 colors=2;
2657 degrees=0.0;
2658 gamma=(-0.2f);
2659 preview_info=AcquireImageInfo();
2660 SetGeometry(image,&geometry);
2661 (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y,
2662 &geometry.width,&geometry.height);
2663 images=NewImageList();
2664 percentage=12.5;
2665 GetQuantizeInfo(&quantize_info);
2666 radius=0.0;
2667 sigma=1.0;
2668 threshold=0.0;
2669 x=0;
2670 y=0;
2671 for (i=0; i < NumberTiles; i++)
2672 {
2673 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
2674 if (thumbnail == (Image *) NULL)
2675 break;
2676 (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL,
2677 (void *) NULL);
2678 (void) SetImageProperty(thumbnail,"label",DefaultTileLabel);
2679 if (i == (NumberTiles/2))
2680 {
2681 (void) QueryColorDatabase("#dfdfdf",&thumbnail->matte_color,exception);
2682 AppendImageToList(&images,thumbnail);
2683 continue;
2684 }
2685 switch (preview)
2686 {
2687 case RotatePreview:
2688 {
2689 degrees+=45.0;
2690 preview_image=RotateImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002691 (void) FormatLocaleString(label,MaxTextExtent,"rotate %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002692 break;
2693 }
2694 case ShearPreview:
2695 {
2696 degrees+=5.0;
2697 preview_image=ShearImage(thumbnail,degrees,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002698 (void) FormatLocaleString(label,MaxTextExtent,"shear %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002699 degrees,2.0*degrees);
2700 break;
2701 }
2702 case RollPreview:
2703 {
cristybb503372010-05-27 20:51:26 +00002704 x=(ssize_t) ((i+1)*thumbnail->columns)/NumberTiles;
2705 y=(ssize_t) ((i+1)*thumbnail->rows)/NumberTiles;
cristy3ed852e2009-09-05 21:47:34 +00002706 preview_image=RollImage(thumbnail,x,y,exception);
cristyb51dff52011-05-19 16:55:47 +00002707 (void) FormatLocaleString(label,MaxTextExtent,"roll %+.20gx%+.20g",
cristye8c25f92010-06-03 00:53:06 +00002708 (double) x,(double) y);
cristy3ed852e2009-09-05 21:47:34 +00002709 break;
2710 }
2711 case HuePreview:
2712 {
2713 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2714 if (preview_image == (Image *) NULL)
2715 break;
cristyb51dff52011-05-19 16:55:47 +00002716 (void) FormatLocaleString(factor,MaxTextExtent,"100,100,%g",
cristy3ed852e2009-09-05 21:47:34 +00002717 2.0*percentage);
2718 (void) ModulateImage(preview_image,factor);
cristyb51dff52011-05-19 16:55:47 +00002719 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002720 break;
2721 }
2722 case SaturationPreview:
2723 {
2724 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2725 if (preview_image == (Image *) NULL)
2726 break;
cristyb51dff52011-05-19 16:55:47 +00002727 (void) FormatLocaleString(factor,MaxTextExtent,"100,%g",
cristy8cd5b312010-01-07 01:10:24 +00002728 2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00002729 (void) ModulateImage(preview_image,factor);
cristyb51dff52011-05-19 16:55:47 +00002730 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002731 break;
2732 }
2733 case BrightnessPreview:
2734 {
2735 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2736 if (preview_image == (Image *) NULL)
2737 break;
cristyb51dff52011-05-19 16:55:47 +00002738 (void) FormatLocaleString(factor,MaxTextExtent,"%g",2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00002739 (void) ModulateImage(preview_image,factor);
cristyb51dff52011-05-19 16:55:47 +00002740 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002741 break;
2742 }
2743 case GammaPreview:
2744 default:
2745 {
2746 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2747 if (preview_image == (Image *) NULL)
2748 break;
2749 gamma+=0.4f;
cristy50fbc382011-07-07 02:19:17 +00002750 (void) GammaImage(preview_image,gamma);
cristyb51dff52011-05-19 16:55:47 +00002751 (void) FormatLocaleString(label,MaxTextExtent,"gamma %g",gamma);
cristy3ed852e2009-09-05 21:47:34 +00002752 break;
2753 }
2754 case SpiffPreview:
2755 {
2756 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2757 if (preview_image != (Image *) NULL)
2758 for (x=0; x < i; x++)
2759 (void) ContrastImage(preview_image,MagickTrue);
cristyb51dff52011-05-19 16:55:47 +00002760 (void) FormatLocaleString(label,MaxTextExtent,"contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002761 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002762 break;
2763 }
2764 case DullPreview:
2765 {
2766 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2767 if (preview_image == (Image *) NULL)
2768 break;
2769 for (x=0; x < i; x++)
2770 (void) ContrastImage(preview_image,MagickFalse);
cristyb51dff52011-05-19 16:55:47 +00002771 (void) FormatLocaleString(label,MaxTextExtent,"+contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002772 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002773 break;
2774 }
2775 case GrayscalePreview:
2776 {
2777 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2778 if (preview_image == (Image *) NULL)
2779 break;
2780 colors<<=1;
2781 quantize_info.number_colors=colors;
2782 quantize_info.colorspace=GRAYColorspace;
2783 (void) QuantizeImage(&quantize_info,preview_image);
cristyb51dff52011-05-19 16:55:47 +00002784 (void) FormatLocaleString(label,MaxTextExtent,
cristye8c25f92010-06-03 00:53:06 +00002785 "-colorspace gray -colors %.20g",(double) colors);
cristy3ed852e2009-09-05 21:47:34 +00002786 break;
2787 }
2788 case QuantizePreview:
2789 {
2790 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2791 if (preview_image == (Image *) NULL)
2792 break;
2793 colors<<=1;
2794 quantize_info.number_colors=colors;
2795 (void) QuantizeImage(&quantize_info,preview_image);
cristyb51dff52011-05-19 16:55:47 +00002796 (void) FormatLocaleString(label,MaxTextExtent,"colors %.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00002797 colors);
cristy3ed852e2009-09-05 21:47:34 +00002798 break;
2799 }
2800 case DespecklePreview:
2801 {
2802 for (x=0; x < (i-1); x++)
2803 {
2804 preview_image=DespeckleImage(thumbnail,exception);
2805 if (preview_image == (Image *) NULL)
2806 break;
2807 thumbnail=DestroyImage(thumbnail);
2808 thumbnail=preview_image;
2809 }
2810 preview_image=DespeckleImage(thumbnail,exception);
2811 if (preview_image == (Image *) NULL)
2812 break;
cristyb51dff52011-05-19 16:55:47 +00002813 (void) FormatLocaleString(label,MaxTextExtent,"despeckle (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002814 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002815 break;
2816 }
2817 case ReduceNoisePreview:
2818 {
cristy95c38342011-03-18 22:39:51 +00002819 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) radius,
2820 (size_t) radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002821 (void) FormatLocaleString(label,MaxTextExtent,"noise %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002822 break;
2823 }
2824 case AddNoisePreview:
2825 {
2826 switch ((int) i)
2827 {
2828 case 0:
2829 {
2830 (void) CopyMagickString(factor,"uniform",MaxTextExtent);
2831 break;
2832 }
2833 case 1:
2834 {
2835 (void) CopyMagickString(factor,"gaussian",MaxTextExtent);
2836 break;
2837 }
2838 case 2:
2839 {
2840 (void) CopyMagickString(factor,"multiplicative",MaxTextExtent);
2841 break;
2842 }
2843 case 3:
2844 {
2845 (void) CopyMagickString(factor,"impulse",MaxTextExtent);
2846 break;
2847 }
2848 case 4:
2849 {
2850 (void) CopyMagickString(factor,"laplacian",MaxTextExtent);
2851 break;
2852 }
2853 case 5:
2854 {
2855 (void) CopyMagickString(factor,"Poisson",MaxTextExtent);
2856 break;
2857 }
2858 default:
2859 {
2860 (void) CopyMagickString(thumbnail->magick,"NULL",MaxTextExtent);
2861 break;
2862 }
2863 }
cristyd76c51e2011-03-26 00:21:26 +00002864 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) i,
2865 (size_t) i,exception);
cristyb51dff52011-05-19 16:55:47 +00002866 (void) FormatLocaleString(label,MaxTextExtent,"+noise %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002867 break;
2868 }
2869 case SharpenPreview:
2870 {
2871 preview_image=SharpenImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00002872 (void) FormatLocaleString(label,MaxTextExtent,"sharpen %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002873 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002874 break;
2875 }
2876 case BlurPreview:
2877 {
2878 preview_image=BlurImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00002879 (void) FormatLocaleString(label,MaxTextExtent,"blur %gx%g",radius,
cristy3ed852e2009-09-05 21:47:34 +00002880 sigma);
2881 break;
2882 }
2883 case ThresholdPreview:
2884 {
2885 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2886 if (preview_image == (Image *) NULL)
2887 break;
2888 (void) BilevelImage(thumbnail,
2889 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
cristyb51dff52011-05-19 16:55:47 +00002890 (void) FormatLocaleString(label,MaxTextExtent,"threshold %g",
cristy3ed852e2009-09-05 21:47:34 +00002891 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
2892 break;
2893 }
2894 case EdgeDetectPreview:
2895 {
2896 preview_image=EdgeImage(thumbnail,radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002897 (void) FormatLocaleString(label,MaxTextExtent,"edge %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002898 break;
2899 }
2900 case SpreadPreview:
2901 {
2902 preview_image=SpreadImage(thumbnail,radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002903 (void) FormatLocaleString(label,MaxTextExtent,"spread %g",
cristy8cd5b312010-01-07 01:10:24 +00002904 radius+0.5);
cristy3ed852e2009-09-05 21:47:34 +00002905 break;
2906 }
2907 case SolarizePreview:
2908 {
2909 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2910 if (preview_image == (Image *) NULL)
2911 break;
2912 (void) SolarizeImage(preview_image,(double) QuantumRange*
2913 percentage/100.0);
cristyb51dff52011-05-19 16:55:47 +00002914 (void) FormatLocaleString(label,MaxTextExtent,"solarize %g",
cristy3ed852e2009-09-05 21:47:34 +00002915 (QuantumRange*percentage)/100.0);
2916 break;
2917 }
2918 case ShadePreview:
2919 {
2920 degrees+=10.0;
2921 preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees,
2922 exception);
cristyb51dff52011-05-19 16:55:47 +00002923 (void) FormatLocaleString(label,MaxTextExtent,"shade %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002924 degrees,degrees);
cristy3ed852e2009-09-05 21:47:34 +00002925 break;
2926 }
2927 case RaisePreview:
2928 {
2929 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2930 if (preview_image == (Image *) NULL)
2931 break;
cristybb503372010-05-27 20:51:26 +00002932 geometry.width=(size_t) (2*i+2);
2933 geometry.height=(size_t) (2*i+2);
cristy3ed852e2009-09-05 21:47:34 +00002934 geometry.x=i/2;
2935 geometry.y=i/2;
2936 (void) RaiseImage(preview_image,&geometry,MagickTrue);
cristyb51dff52011-05-19 16:55:47 +00002937 (void) FormatLocaleString(label,MaxTextExtent,
cristy6d8abba2010-06-03 01:10:47 +00002938 "raise %.20gx%.20g%+.20g%+.20g",(double) geometry.width,(double)
cristye8c25f92010-06-03 00:53:06 +00002939 geometry.height,(double) geometry.x,(double) geometry.y);
cristy3ed852e2009-09-05 21:47:34 +00002940 break;
2941 }
2942 case SegmentPreview:
2943 {
2944 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2945 if (preview_image == (Image *) NULL)
2946 break;
2947 threshold+=0.4f;
2948 (void) SegmentImage(preview_image,RGBColorspace,MagickFalse,threshold,
2949 threshold);
cristyb51dff52011-05-19 16:55:47 +00002950 (void) FormatLocaleString(label,MaxTextExtent,"segment %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002951 threshold,threshold);
2952 break;
2953 }
2954 case SwirlPreview:
2955 {
2956 preview_image=SwirlImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002957 (void) FormatLocaleString(label,MaxTextExtent,"swirl %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002958 degrees+=45.0;
2959 break;
2960 }
2961 case ImplodePreview:
2962 {
2963 degrees+=0.1f;
2964 preview_image=ImplodeImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002965 (void) FormatLocaleString(label,MaxTextExtent,"implode %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002966 break;
2967 }
2968 case WavePreview:
2969 {
2970 degrees+=5.0f;
2971 preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002972 (void) FormatLocaleString(label,MaxTextExtent,"wave %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002973 0.5*degrees,2.0*degrees);
cristy3ed852e2009-09-05 21:47:34 +00002974 break;
2975 }
2976 case OilPaintPreview:
2977 {
2978 preview_image=OilPaintImage(thumbnail,(double) radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002979 (void) FormatLocaleString(label,MaxTextExtent,"paint %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002980 break;
2981 }
2982 case CharcoalDrawingPreview:
2983 {
2984 preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma,
2985 exception);
cristyb51dff52011-05-19 16:55:47 +00002986 (void) FormatLocaleString(label,MaxTextExtent,"charcoal %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002987 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002988 break;
2989 }
2990 case JPEGPreview:
2991 {
2992 char
2993 filename[MaxTextExtent];
2994
2995 int
2996 file;
2997
2998 MagickBooleanType
2999 status;
3000
3001 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3002 if (preview_image == (Image *) NULL)
3003 break;
cristybb503372010-05-27 20:51:26 +00003004 preview_info->quality=(size_t) percentage;
cristyb51dff52011-05-19 16:55:47 +00003005 (void) FormatLocaleString(factor,MaxTextExtent,"%.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00003006 preview_info->quality);
cristy3ed852e2009-09-05 21:47:34 +00003007 file=AcquireUniqueFileResource(filename);
3008 if (file != -1)
3009 file=close(file)-1;
cristyb51dff52011-05-19 16:55:47 +00003010 (void) FormatLocaleString(preview_image->filename,MaxTextExtent,
cristy3ed852e2009-09-05 21:47:34 +00003011 "jpeg:%s",filename);
3012 status=WriteImage(preview_info,preview_image);
3013 if (status != MagickFalse)
3014 {
3015 Image
3016 *quality_image;
3017
3018 (void) CopyMagickString(preview_info->filename,
3019 preview_image->filename,MaxTextExtent);
3020 quality_image=ReadImage(preview_info,exception);
3021 if (quality_image != (Image *) NULL)
3022 {
3023 preview_image=DestroyImage(preview_image);
3024 preview_image=quality_image;
3025 }
3026 }
3027 (void) RelinquishUniqueFileResource(preview_image->filename);
3028 if ((GetBlobSize(preview_image)/1024) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00003029 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%gmb ",
cristy3ed852e2009-09-05 21:47:34 +00003030 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
3031 1024.0/1024.0);
3032 else
3033 if (GetBlobSize(preview_image) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00003034 (void) FormatLocaleString(label,MaxTextExtent,
cristye7f51092010-01-17 00:39:37 +00003035 "quality %s\n%gkb ",factor,(double) ((MagickOffsetType)
cristy8cd5b312010-01-07 01:10:24 +00003036 GetBlobSize(preview_image))/1024.0);
cristy3ed852e2009-09-05 21:47:34 +00003037 else
cristyb51dff52011-05-19 16:55:47 +00003038 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%.20gb ",
cristy54ea5732011-06-10 12:39:53 +00003039 factor,(double) ((MagickOffsetType) GetBlobSize(thumbnail)));
cristy3ed852e2009-09-05 21:47:34 +00003040 break;
3041 }
3042 }
3043 thumbnail=DestroyImage(thumbnail);
3044 percentage+=12.5;
3045 radius+=0.5;
3046 sigma+=0.25;
3047 if (preview_image == (Image *) NULL)
3048 break;
3049 (void) DeleteImageProperty(preview_image,"label");
3050 (void) SetImageProperty(preview_image,"label",label);
3051 AppendImageToList(&images,preview_image);
cristybb503372010-05-27 20:51:26 +00003052 proceed=SetImageProgress(image,PreviewImageTag,(MagickOffsetType) i,
3053 NumberTiles);
cristy3ed852e2009-09-05 21:47:34 +00003054 if (proceed == MagickFalse)
3055 break;
3056 }
3057 if (images == (Image *) NULL)
3058 {
3059 preview_info=DestroyImageInfo(preview_info);
3060 return((Image *) NULL);
3061 }
3062 /*
3063 Create the montage.
3064 */
3065 montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL);
3066 (void) CopyMagickString(montage_info->filename,image->filename,MaxTextExtent);
3067 montage_info->shadow=MagickTrue;
3068 (void) CloneString(&montage_info->tile,"3x3");
3069 (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry);
3070 (void) CloneString(&montage_info->frame,DefaultTileFrame);
3071 montage_image=MontageImages(images,montage_info,exception);
3072 montage_info=DestroyMontageInfo(montage_info);
3073 images=DestroyImageList(images);
3074 if (montage_image == (Image *) NULL)
3075 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3076 if (montage_image->montage != (char *) NULL)
3077 {
3078 /*
3079 Free image directory.
3080 */
3081 montage_image->montage=(char *) RelinquishMagickMemory(
3082 montage_image->montage);
3083 if (image->directory != (char *) NULL)
3084 montage_image->directory=(char *) RelinquishMagickMemory(
3085 montage_image->directory);
3086 }
3087 preview_info=DestroyImageInfo(preview_info);
3088 return(montage_image);
3089}
3090
3091/*
3092%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3093% %
3094% %
3095% %
3096% R a d i a l B l u r I m a g e %
3097% %
3098% %
3099% %
3100%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3101%
3102% RadialBlurImage() applies a radial blur to the image.
3103%
3104% Andrew Protano contributed this effect.
3105%
3106% The format of the RadialBlurImage method is:
3107%
3108% Image *RadialBlurImage(const Image *image,const double angle,
3109% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003110%
3111% A description of each parameter follows:
3112%
3113% o image: the image.
3114%
cristy3ed852e2009-09-05 21:47:34 +00003115% o angle: the angle of the radial blur.
3116%
3117% o exception: return any errors or warnings in this structure.
3118%
3119*/
cristyf4ad9df2011-07-08 16:49:03 +00003120MagickExport Image *RadialBlurImage(const Image *image,
3121 const double angle,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003122{
cristyc4c8d132010-01-07 01:58:38 +00003123 CacheView
3124 *blur_view,
3125 *image_view;
3126
cristy3ed852e2009-09-05 21:47:34 +00003127 Image
3128 *blur_image;
3129
cristy3ed852e2009-09-05 21:47:34 +00003130 MagickBooleanType
3131 status;
3132
cristybb503372010-05-27 20:51:26 +00003133 MagickOffsetType
3134 progress;
3135
cristy4c08aed2011-07-01 19:47:50 +00003136 PixelInfo
cristyddd82202009-11-03 20:14:50 +00003137 bias;
cristy3ed852e2009-09-05 21:47:34 +00003138
3139 MagickRealType
3140 blur_radius,
3141 *cos_theta,
3142 offset,
3143 *sin_theta,
3144 theta;
3145
3146 PointInfo
3147 blur_center;
3148
cristybb503372010-05-27 20:51:26 +00003149 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003150 i;
3151
cristybb503372010-05-27 20:51:26 +00003152 size_t
cristy3ed852e2009-09-05 21:47:34 +00003153 n;
3154
cristybb503372010-05-27 20:51:26 +00003155 ssize_t
3156 y;
3157
cristy3ed852e2009-09-05 21:47:34 +00003158 /*
3159 Allocate blur image.
3160 */
3161 assert(image != (Image *) NULL);
3162 assert(image->signature == MagickSignature);
3163 if (image->debug != MagickFalse)
3164 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3165 assert(exception != (ExceptionInfo *) NULL);
3166 assert(exception->signature == MagickSignature);
3167 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3168 if (blur_image == (Image *) NULL)
3169 return((Image *) NULL);
3170 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3171 {
3172 InheritException(exception,&blur_image->exception);
3173 blur_image=DestroyImage(blur_image);
3174 return((Image *) NULL);
3175 }
3176 blur_center.x=(double) image->columns/2.0;
3177 blur_center.y=(double) image->rows/2.0;
3178 blur_radius=hypot(blur_center.x,blur_center.y);
cristy117ff172010-08-15 21:35:32 +00003179 n=(size_t) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+2UL);
cristy3ed852e2009-09-05 21:47:34 +00003180 theta=DegreesToRadians(angle)/(MagickRealType) (n-1);
3181 cos_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3182 sizeof(*cos_theta));
3183 sin_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3184 sizeof(*sin_theta));
3185 if ((cos_theta == (MagickRealType *) NULL) ||
3186 (sin_theta == (MagickRealType *) NULL))
3187 {
3188 blur_image=DestroyImage(blur_image);
3189 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3190 }
3191 offset=theta*(MagickRealType) (n-1)/2.0;
cristybb503372010-05-27 20:51:26 +00003192 for (i=0; i < (ssize_t) n; i++)
cristy3ed852e2009-09-05 21:47:34 +00003193 {
3194 cos_theta[i]=cos((double) (theta*i-offset));
3195 sin_theta[i]=sin((double) (theta*i-offset));
3196 }
3197 /*
3198 Radial blur image.
3199 */
3200 status=MagickTrue;
3201 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00003202 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003203 image_view=AcquireCacheView(image);
3204 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003205#if defined(MAGICKCORE_OPENMP_SUPPORT)
3206 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003207#endif
cristybb503372010-05-27 20:51:26 +00003208 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003209 {
cristy4c08aed2011-07-01 19:47:50 +00003210 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003211 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003212
cristy117ff172010-08-15 21:35:32 +00003213 register ssize_t
3214 x;
3215
cristy3ed852e2009-09-05 21:47:34 +00003216 if (status == MagickFalse)
3217 continue;
3218 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3219 exception);
cristy4c08aed2011-07-01 19:47:50 +00003220 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003221 {
3222 status=MagickFalse;
3223 continue;
3224 }
cristybb503372010-05-27 20:51:26 +00003225 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003226 {
cristy4c08aed2011-07-01 19:47:50 +00003227 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003228 qixel;
3229
3230 MagickRealType
3231 normalize,
3232 radius;
3233
3234 PixelPacket
3235 pixel;
3236
3237 PointInfo
3238 center;
3239
cristybb503372010-05-27 20:51:26 +00003240 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003241 i;
3242
cristybb503372010-05-27 20:51:26 +00003243 size_t
cristy3ed852e2009-09-05 21:47:34 +00003244 step;
3245
3246 center.x=(double) x-blur_center.x;
3247 center.y=(double) y-blur_center.y;
3248 radius=hypot((double) center.x,center.y);
3249 if (radius == 0)
3250 step=1;
3251 else
3252 {
cristybb503372010-05-27 20:51:26 +00003253 step=(size_t) (blur_radius/radius);
cristy3ed852e2009-09-05 21:47:34 +00003254 if (step == 0)
3255 step=1;
3256 else
3257 if (step >= n)
3258 step=n-1;
3259 }
3260 normalize=0.0;
cristyddd82202009-11-03 20:14:50 +00003261 qixel=bias;
cristy2b9582a2011-07-04 17:38:56 +00003262 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00003263 {
cristyeaedf062010-05-29 22:36:02 +00003264 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00003265 {
cristyeaedf062010-05-29 22:36:02 +00003266 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
3267 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
3268 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
3269 cos_theta[i]+0.5),&pixel,exception);
cristy3ed852e2009-09-05 21:47:34 +00003270 qixel.red+=pixel.red;
3271 qixel.green+=pixel.green;
3272 qixel.blue+=pixel.blue;
cristy3ed852e2009-09-05 21:47:34 +00003273 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00003274 qixel.black+=pixel.black;
3275 qixel.alpha+=pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00003276 normalize+=1.0;
3277 }
3278 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
3279 normalize);
cristy2b9582a2011-07-04 17:38:56 +00003280 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003281 SetPixelRed(blur_image,
3282 ClampToQuantum(normalize*qixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00003283 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003284 SetPixelGreen(blur_image,
3285 ClampToQuantum(normalize*qixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00003286 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003287 SetPixelBlue(blur_image,
3288 ClampToQuantum(normalize*qixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00003289 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003290 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003291 SetPixelBlack(blur_image,
3292 ClampToQuantum(normalize*qixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00003293 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003294 SetPixelAlpha(blur_image,
3295 ClampToQuantum(normalize*qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003296 }
3297 else
3298 {
3299 MagickRealType
3300 alpha,
3301 gamma;
3302
3303 alpha=1.0;
3304 gamma=0.0;
cristyeaedf062010-05-29 22:36:02 +00003305 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00003306 {
cristyeaedf062010-05-29 22:36:02 +00003307 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
3308 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
3309 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
3310 cos_theta[i]+0.5),&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00003311 alpha=(MagickRealType) (QuantumScale*pixel.alpha);
cristy3ed852e2009-09-05 21:47:34 +00003312 qixel.red+=alpha*pixel.red;
3313 qixel.green+=alpha*pixel.green;
3314 qixel.blue+=alpha*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00003315 qixel.alpha+=pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00003316 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00003317 qixel.black+=alpha*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00003318 gamma+=alpha;
3319 normalize+=1.0;
3320 }
3321 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3322 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
3323 normalize);
cristy2b9582a2011-07-04 17:38:56 +00003324 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003325 SetPixelRed(blur_image,
3326 ClampToQuantum(gamma*qixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00003327 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003328 SetPixelGreen(blur_image,
3329 ClampToQuantum(gamma*qixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00003330 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003331 SetPixelBlue(blur_image,
3332 ClampToQuantum(gamma*qixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00003333 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003334 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003335 SetPixelBlack(blur_image,
3336 ClampToQuantum(gamma*qixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00003337 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003338 SetPixelAlpha(blur_image,
3339 ClampToQuantum(normalize*qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003340 }
cristydcfc1ad2011-07-07 16:25:41 +00003341 q+=GetPixelComponents(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003342 }
3343 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
3344 status=MagickFalse;
3345 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3346 {
3347 MagickBooleanType
3348 proceed;
3349
cristyb5d5f722009-11-04 03:03:49 +00003350#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00003351 #pragma omp critical (MagickCore_RadialBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00003352#endif
3353 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
3354 if (proceed == MagickFalse)
3355 status=MagickFalse;
3356 }
3357 }
3358 blur_view=DestroyCacheView(blur_view);
3359 image_view=DestroyCacheView(image_view);
3360 cos_theta=(MagickRealType *) RelinquishMagickMemory(cos_theta);
3361 sin_theta=(MagickRealType *) RelinquishMagickMemory(sin_theta);
3362 if (status == MagickFalse)
3363 blur_image=DestroyImage(blur_image);
3364 return(blur_image);
3365}
3366
3367/*
3368%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3369% %
3370% %
3371% %
cristy3ed852e2009-09-05 21:47:34 +00003372% S e l e c t i v e B l u r I m a g e %
3373% %
3374% %
3375% %
3376%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3377%
3378% SelectiveBlurImage() selectively blur pixels within a contrast threshold.
3379% It is similar to the unsharpen mask that sharpens everything with contrast
3380% above a certain threshold.
3381%
3382% The format of the SelectiveBlurImage method is:
3383%
3384% Image *SelectiveBlurImage(const Image *image,const double radius,
3385% const double sigma,const double threshold,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003386%
3387% A description of each parameter follows:
3388%
3389% o image: the image.
3390%
cristy3ed852e2009-09-05 21:47:34 +00003391% o radius: the radius of the Gaussian, in pixels, not counting the center
3392% pixel.
3393%
3394% o sigma: the standard deviation of the Gaussian, in pixels.
3395%
3396% o threshold: only pixels within this contrast threshold are included
3397% in the blur operation.
3398%
3399% o exception: return any errors or warnings in this structure.
3400%
3401*/
cristyf4ad9df2011-07-08 16:49:03 +00003402MagickExport Image *SelectiveBlurImage(const Image *image,
3403 const double radius,const double sigma,const double threshold,
3404 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003405{
3406#define SelectiveBlurImageTag "SelectiveBlur/Image"
3407
cristy47e00502009-12-17 19:19:57 +00003408 CacheView
3409 *blur_view,
3410 *image_view;
3411
cristy3ed852e2009-09-05 21:47:34 +00003412 double
cristy3ed852e2009-09-05 21:47:34 +00003413 *kernel;
3414
3415 Image
3416 *blur_image;
3417
cristy3ed852e2009-09-05 21:47:34 +00003418 MagickBooleanType
3419 status;
3420
cristybb503372010-05-27 20:51:26 +00003421 MagickOffsetType
3422 progress;
3423
cristy4c08aed2011-07-01 19:47:50 +00003424 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003425 bias;
3426
cristybb503372010-05-27 20:51:26 +00003427 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003428 i;
cristy3ed852e2009-09-05 21:47:34 +00003429
cristybb503372010-05-27 20:51:26 +00003430 size_t
cristy3ed852e2009-09-05 21:47:34 +00003431 width;
3432
cristybb503372010-05-27 20:51:26 +00003433 ssize_t
3434 j,
3435 u,
3436 v,
3437 y;
3438
cristy3ed852e2009-09-05 21:47:34 +00003439 /*
3440 Initialize blur image attributes.
3441 */
3442 assert(image != (Image *) NULL);
3443 assert(image->signature == MagickSignature);
3444 if (image->debug != MagickFalse)
3445 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3446 assert(exception != (ExceptionInfo *) NULL);
3447 assert(exception->signature == MagickSignature);
3448 width=GetOptimalKernelWidth1D(radius,sigma);
3449 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
3450 if (kernel == (double *) NULL)
3451 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00003452 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00003453 i=0;
cristy47e00502009-12-17 19:19:57 +00003454 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00003455 {
cristy47e00502009-12-17 19:19:57 +00003456 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00003457 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
3458 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00003459 }
3460 if (image->debug != MagickFalse)
3461 {
3462 char
3463 format[MaxTextExtent],
3464 *message;
3465
cristy117ff172010-08-15 21:35:32 +00003466 register const double
3467 *k;
3468
cristybb503372010-05-27 20:51:26 +00003469 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003470 u,
3471 v;
3472
cristy3ed852e2009-09-05 21:47:34 +00003473 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00003474 " SelectiveBlurImage with %.20gx%.20g kernel:",(double) width,(double)
3475 width);
cristy3ed852e2009-09-05 21:47:34 +00003476 message=AcquireString("");
3477 k=kernel;
cristybb503372010-05-27 20:51:26 +00003478 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003479 {
3480 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00003481 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy3ed852e2009-09-05 21:47:34 +00003482 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00003483 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003484 {
cristyb51dff52011-05-19 16:55:47 +00003485 (void) FormatLocaleString(format,MaxTextExtent,"%+f ",*k++);
cristy3ed852e2009-09-05 21:47:34 +00003486 (void) ConcatenateString(&message,format);
3487 }
3488 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
3489 }
3490 message=DestroyString(message);
3491 }
3492 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3493 if (blur_image == (Image *) NULL)
3494 return((Image *) NULL);
3495 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3496 {
3497 InheritException(exception,&blur_image->exception);
3498 blur_image=DestroyImage(blur_image);
3499 return((Image *) NULL);
3500 }
3501 /*
3502 Threshold blur image.
3503 */
3504 status=MagickTrue;
3505 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00003506 GetPixelInfo(image,&bias);
3507 SetPixelInfoBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003508 image_view=AcquireCacheView(image);
3509 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003510#if defined(MAGICKCORE_OPENMP_SUPPORT)
3511 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003512#endif
cristybb503372010-05-27 20:51:26 +00003513 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003514 {
cristy4c08aed2011-07-01 19:47:50 +00003515 double
3516 contrast;
3517
cristy3ed852e2009-09-05 21:47:34 +00003518 MagickBooleanType
3519 sync;
3520
3521 MagickRealType
3522 gamma;
3523
cristy4c08aed2011-07-01 19:47:50 +00003524 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003525 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003526
cristy4c08aed2011-07-01 19:47:50 +00003527 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003528 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003529
cristy117ff172010-08-15 21:35:32 +00003530 register ssize_t
3531 x;
3532
cristy3ed852e2009-09-05 21:47:34 +00003533 if (status == MagickFalse)
3534 continue;
cristy117ff172010-08-15 21:35:32 +00003535 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
3536 (width/2L),image->columns+width,width,exception);
cristy3ed852e2009-09-05 21:47:34 +00003537 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3538 exception);
cristy4c08aed2011-07-01 19:47:50 +00003539 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003540 {
3541 status=MagickFalse;
3542 continue;
3543 }
cristybb503372010-05-27 20:51:26 +00003544 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003545 {
cristy4c08aed2011-07-01 19:47:50 +00003546 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003547 pixel;
3548
3549 register const double
cristyc47d1f82009-11-26 01:44:43 +00003550 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00003551
cristybb503372010-05-27 20:51:26 +00003552 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003553 u;
3554
cristy117ff172010-08-15 21:35:32 +00003555 ssize_t
3556 j,
3557 v;
3558
cristyddd82202009-11-03 20:14:50 +00003559 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00003560 k=kernel;
3561 gamma=0.0;
3562 j=0;
cristy2b9582a2011-07-04 17:38:56 +00003563 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00003564 {
cristybb503372010-05-27 20:51:26 +00003565 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003566 {
cristybb503372010-05-27 20:51:26 +00003567 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003568 {
cristydcfc1ad2011-07-07 16:25:41 +00003569 contrast=GetPixelIntensity(image,p+(u+j)*GetPixelComponents(image))-
cristy4c08aed2011-07-01 19:47:50 +00003570 (double) GetPixelIntensity(blur_image,q);
3571 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003572 {
cristy4c08aed2011-07-01 19:47:50 +00003573 pixel.red+=(*k)*
cristydcfc1ad2011-07-07 16:25:41 +00003574 GetPixelRed(image,p+(u+j)*GetPixelComponents(image));
cristy4c08aed2011-07-01 19:47:50 +00003575 pixel.green+=(*k)*
cristydcfc1ad2011-07-07 16:25:41 +00003576 GetPixelGreen(image,p+(u+j)*GetPixelComponents(image));
cristy4c08aed2011-07-01 19:47:50 +00003577 pixel.blue+=(*k)*
cristydcfc1ad2011-07-07 16:25:41 +00003578 GetPixelBlue(image,p+(u+j)*GetPixelComponents(image));
cristy4c08aed2011-07-01 19:47:50 +00003579 if (image->colorspace == CMYKColorspace)
3580 pixel.black+=(*k)*
cristydcfc1ad2011-07-07 16:25:41 +00003581 GetPixelBlack(image,p+(u+j)*GetPixelComponents(image));
cristy3ed852e2009-09-05 21:47:34 +00003582 gamma+=(*k);
3583 k++;
3584 }
3585 }
cristyd99b0962010-05-29 23:14:26 +00003586 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003587 }
3588 if (gamma != 0.0)
3589 {
3590 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy2b9582a2011-07-04 17:38:56 +00003591 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003592 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00003593 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003594 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00003595 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003596 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00003597 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00003598 (image->colorspace == CMYKColorspace))
3599 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00003600 }
cristy2b9582a2011-07-04 17:38:56 +00003601 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003602 {
3603 gamma=0.0;
3604 j=0;
cristybb503372010-05-27 20:51:26 +00003605 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003606 {
cristybb503372010-05-27 20:51:26 +00003607 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003608 {
cristy4c08aed2011-07-01 19:47:50 +00003609 contrast=GetPixelIntensity(image,p+(u+j)*
cristydcfc1ad2011-07-07 16:25:41 +00003610 GetPixelComponents(image))-(double)
cristy4c08aed2011-07-01 19:47:50 +00003611 GetPixelIntensity(blur_image,q);
3612 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003613 {
cristy4c08aed2011-07-01 19:47:50 +00003614 pixel.alpha+=(*k)*
cristydcfc1ad2011-07-07 16:25:41 +00003615 GetPixelAlpha(image,p+(u+j)*GetPixelComponents(image));
cristy3ed852e2009-09-05 21:47:34 +00003616 gamma+=(*k);
3617 k++;
3618 }
3619 }
cristyeaedf062010-05-29 22:36:02 +00003620 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003621 }
3622 if (gamma != 0.0)
3623 {
3624 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
3625 gamma);
cristy4c08aed2011-07-01 19:47:50 +00003626 SetPixelAlpha(blur_image,ClampToQuantum(gamma*pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003627 }
3628 }
3629 }
3630 else
3631 {
3632 MagickRealType
3633 alpha;
3634
cristybb503372010-05-27 20:51:26 +00003635 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003636 {
cristybb503372010-05-27 20:51:26 +00003637 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003638 {
cristy4c08aed2011-07-01 19:47:50 +00003639 contrast=GetPixelIntensity(image,p+(u+j)*
cristydcfc1ad2011-07-07 16:25:41 +00003640 GetPixelComponents(image))-(double)
cristy4c08aed2011-07-01 19:47:50 +00003641 GetPixelIntensity(blur_image,q);
3642 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003643 {
cristy4c08aed2011-07-01 19:47:50 +00003644 alpha=(MagickRealType) (QuantumScale*
cristydcfc1ad2011-07-07 16:25:41 +00003645 GetPixelAlpha(image,p+(u+j)*GetPixelComponents(image)));
cristy4c08aed2011-07-01 19:47:50 +00003646 pixel.red+=(*k)*alpha*
cristydcfc1ad2011-07-07 16:25:41 +00003647 GetPixelRed(image,p+(u+j)*GetPixelComponents(image));
cristy4c08aed2011-07-01 19:47:50 +00003648 pixel.green+=(*k)*alpha*GetPixelGreen(image,p+(u+j)*
cristydcfc1ad2011-07-07 16:25:41 +00003649 GetPixelComponents(image));
cristy4c08aed2011-07-01 19:47:50 +00003650 pixel.blue+=(*k)*alpha*GetPixelBlue(image,p+(u+j)*
cristydcfc1ad2011-07-07 16:25:41 +00003651 GetPixelComponents(image));
cristy4c08aed2011-07-01 19:47:50 +00003652 pixel.alpha+=(*k)*GetPixelAlpha(image,p+(u+j)*
cristydcfc1ad2011-07-07 16:25:41 +00003653 GetPixelComponents(image));
cristy4c08aed2011-07-01 19:47:50 +00003654 if (image->colorspace == CMYKColorspace)
3655 pixel.black+=(*k)*GetPixelBlack(image,p+(u+j)*
cristydcfc1ad2011-07-07 16:25:41 +00003656 GetPixelComponents(image));
cristy3ed852e2009-09-05 21:47:34 +00003657 gamma+=(*k)*alpha;
3658 k++;
3659 }
3660 }
cristyeaedf062010-05-29 22:36:02 +00003661 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003662 }
3663 if (gamma != 0.0)
3664 {
3665 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy2b9582a2011-07-04 17:38:56 +00003666 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003667 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00003668 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003669 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00003670 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003671 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00003672 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00003673 (image->colorspace == CMYKColorspace))
3674 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00003675 }
cristy2b9582a2011-07-04 17:38:56 +00003676 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003677 {
3678 gamma=0.0;
3679 j=0;
cristybb503372010-05-27 20:51:26 +00003680 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003681 {
cristybb503372010-05-27 20:51:26 +00003682 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003683 {
cristy4c08aed2011-07-01 19:47:50 +00003684 contrast=GetPixelIntensity(image,p+(u+j)*
cristydcfc1ad2011-07-07 16:25:41 +00003685 GetPixelComponents(image))-(double)
cristy4c08aed2011-07-01 19:47:50 +00003686 GetPixelIntensity(blur_image,q);
3687 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003688 {
cristy4c08aed2011-07-01 19:47:50 +00003689 pixel.alpha+=(*k)*
cristydcfc1ad2011-07-07 16:25:41 +00003690 GetPixelAlpha(image,p+(u+j)*GetPixelComponents(image));
cristy3ed852e2009-09-05 21:47:34 +00003691 gamma+=(*k);
3692 k++;
3693 }
3694 }
cristyeaedf062010-05-29 22:36:02 +00003695 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003696 }
3697 if (gamma != 0.0)
3698 {
3699 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
3700 gamma);
cristy4c08aed2011-07-01 19:47:50 +00003701 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003702 }
3703 }
3704 }
cristydcfc1ad2011-07-07 16:25:41 +00003705 p+=GetPixelComponents(image);
3706 q+=GetPixelComponents(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003707 }
3708 sync=SyncCacheViewAuthenticPixels(blur_view,exception);
3709 if (sync == MagickFalse)
3710 status=MagickFalse;
3711 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3712 {
3713 MagickBooleanType
3714 proceed;
3715
cristyb5d5f722009-11-04 03:03:49 +00003716#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00003717 #pragma omp critical (MagickCore_SelectiveBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00003718#endif
3719 proceed=SetImageProgress(image,SelectiveBlurImageTag,progress++,
3720 image->rows);
3721 if (proceed == MagickFalse)
3722 status=MagickFalse;
3723 }
3724 }
3725 blur_image->type=image->type;
3726 blur_view=DestroyCacheView(blur_view);
3727 image_view=DestroyCacheView(image_view);
3728 kernel=(double *) RelinquishMagickMemory(kernel);
3729 if (status == MagickFalse)
3730 blur_image=DestroyImage(blur_image);
3731 return(blur_image);
3732}
3733
3734/*
3735%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3736% %
3737% %
3738% %
3739% S h a d e I m a g e %
3740% %
3741% %
3742% %
3743%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3744%
3745% ShadeImage() shines a distant light on an image to create a
3746% three-dimensional effect. You control the positioning of the light with
3747% azimuth and elevation; azimuth is measured in degrees off the x axis
3748% and elevation is measured in pixels above the Z axis.
3749%
3750% The format of the ShadeImage method is:
3751%
3752% Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3753% const double azimuth,const double elevation,ExceptionInfo *exception)
3754%
3755% A description of each parameter follows:
3756%
3757% o image: the image.
3758%
3759% o gray: A value other than zero shades the intensity of each pixel.
3760%
3761% o azimuth, elevation: Define the light source direction.
3762%
3763% o exception: return any errors or warnings in this structure.
3764%
3765*/
3766MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3767 const double azimuth,const double elevation,ExceptionInfo *exception)
3768{
3769#define ShadeImageTag "Shade/Image"
3770
cristyc4c8d132010-01-07 01:58:38 +00003771 CacheView
3772 *image_view,
3773 *shade_view;
3774
cristy3ed852e2009-09-05 21:47:34 +00003775 Image
3776 *shade_image;
3777
cristy3ed852e2009-09-05 21:47:34 +00003778 MagickBooleanType
3779 status;
3780
cristybb503372010-05-27 20:51:26 +00003781 MagickOffsetType
3782 progress;
3783
cristy3ed852e2009-09-05 21:47:34 +00003784 PrimaryInfo
3785 light;
3786
cristybb503372010-05-27 20:51:26 +00003787 ssize_t
3788 y;
3789
cristy3ed852e2009-09-05 21:47:34 +00003790 /*
3791 Initialize shaded image attributes.
3792 */
3793 assert(image != (const Image *) NULL);
3794 assert(image->signature == MagickSignature);
3795 if (image->debug != MagickFalse)
3796 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3797 assert(exception != (ExceptionInfo *) NULL);
3798 assert(exception->signature == MagickSignature);
3799 shade_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
3800 if (shade_image == (Image *) NULL)
3801 return((Image *) NULL);
3802 if (SetImageStorageClass(shade_image,DirectClass) == MagickFalse)
3803 {
3804 InheritException(exception,&shade_image->exception);
3805 shade_image=DestroyImage(shade_image);
3806 return((Image *) NULL);
3807 }
3808 /*
3809 Compute the light vector.
3810 */
3811 light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))*
3812 cos(DegreesToRadians(elevation));
3813 light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))*
3814 cos(DegreesToRadians(elevation));
3815 light.z=(double) QuantumRange*sin(DegreesToRadians(elevation));
3816 /*
3817 Shade image.
3818 */
3819 status=MagickTrue;
3820 progress=0;
3821 image_view=AcquireCacheView(image);
3822 shade_view=AcquireCacheView(shade_image);
cristyb5d5f722009-11-04 03:03:49 +00003823#if defined(MAGICKCORE_OPENMP_SUPPORT)
3824 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003825#endif
cristybb503372010-05-27 20:51:26 +00003826 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003827 {
3828 MagickRealType
3829 distance,
3830 normal_distance,
3831 shade;
3832
3833 PrimaryInfo
3834 normal;
3835
cristy4c08aed2011-07-01 19:47:50 +00003836 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003837 *restrict p,
3838 *restrict s0,
3839 *restrict s1,
3840 *restrict s2;
cristy3ed852e2009-09-05 21:47:34 +00003841
cristy4c08aed2011-07-01 19:47:50 +00003842 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003843 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003844
cristy117ff172010-08-15 21:35:32 +00003845 register ssize_t
3846 x;
3847
cristy3ed852e2009-09-05 21:47:34 +00003848 if (status == MagickFalse)
3849 continue;
3850 p=GetCacheViewVirtualPixels(image_view,-1,y-1,image->columns+2,3,exception);
3851 q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1,
3852 exception);
cristy4c08aed2011-07-01 19:47:50 +00003853 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003854 {
3855 status=MagickFalse;
3856 continue;
3857 }
3858 /*
3859 Shade this row of pixels.
3860 */
3861 normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */
cristydcfc1ad2011-07-07 16:25:41 +00003862 s0=p+GetPixelComponents(image);
3863 s1=s0+(image->columns+2)*GetPixelComponents(image);
3864 s2=s1+(image->columns+2)*GetPixelComponents(image);
cristybb503372010-05-27 20:51:26 +00003865 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003866 {
3867 /*
3868 Determine the surface normal and compute shading.
3869 */
cristydcfc1ad2011-07-07 16:25:41 +00003870 normal.x=(double) (GetPixelIntensity(image,s0-GetPixelComponents(image))+
3871 GetPixelIntensity(image,s1-GetPixelComponents(image))+
3872 GetPixelIntensity(image,s2-GetPixelComponents(image))-
3873 GetPixelIntensity(image,s0+GetPixelComponents(image))-
3874 GetPixelIntensity(image,s1+GetPixelComponents(image))-
3875 GetPixelIntensity(image,s2+GetPixelComponents(image)));
3876 normal.y=(double) (GetPixelIntensity(image,s2-GetPixelComponents(image))+
cristy4c08aed2011-07-01 19:47:50 +00003877 GetPixelIntensity(image,s2)+
cristydcfc1ad2011-07-07 16:25:41 +00003878 GetPixelIntensity(image,s2+GetPixelComponents(image))-
3879 GetPixelIntensity(image,s0-GetPixelComponents(image))-
cristy4c08aed2011-07-01 19:47:50 +00003880 GetPixelIntensity(image,s0)-
cristydcfc1ad2011-07-07 16:25:41 +00003881 GetPixelIntensity(image,s0+GetPixelComponents(image)));
cristy3ed852e2009-09-05 21:47:34 +00003882 if ((normal.x == 0.0) && (normal.y == 0.0))
3883 shade=light.z;
3884 else
3885 {
3886 shade=0.0;
3887 distance=normal.x*light.x+normal.y*light.y+normal.z*light.z;
3888 if (distance > MagickEpsilon)
3889 {
3890 normal_distance=
3891 normal.x*normal.x+normal.y*normal.y+normal.z*normal.z;
3892 if (normal_distance > (MagickEpsilon*MagickEpsilon))
3893 shade=distance/sqrt((double) normal_distance);
3894 }
3895 }
3896 if (gray != MagickFalse)
3897 {
cristy4c08aed2011-07-01 19:47:50 +00003898 SetPixelRed(shade_image,ClampToQuantum(shade),q);
3899 SetPixelGreen(shade_image,ClampToQuantum(shade),q);
3900 SetPixelBlue(shade_image,ClampToQuantum(shade),q);
cristy3ed852e2009-09-05 21:47:34 +00003901 }
3902 else
3903 {
cristy4c08aed2011-07-01 19:47:50 +00003904 SetPixelRed(shade_image,ClampToQuantum(QuantumScale*shade*
3905 GetPixelRed(image,s1)),q);
3906 SetPixelGreen(shade_image,ClampToQuantum(QuantumScale*shade*
3907 GetPixelGreen(image,s1)),q);
3908 SetPixelBlue(shade_image,ClampToQuantum(QuantumScale*shade*
3909 GetPixelBlue(image,s1)),q);
cristy3ed852e2009-09-05 21:47:34 +00003910 }
cristy4c08aed2011-07-01 19:47:50 +00003911 SetPixelAlpha(shade_image,GetPixelAlpha(image,s1),q);
cristydcfc1ad2011-07-07 16:25:41 +00003912 s0+=GetPixelComponents(image);
3913 s1+=GetPixelComponents(image);
3914 s2+=GetPixelComponents(image);
3915 q+=GetPixelComponents(shade_image);
cristy3ed852e2009-09-05 21:47:34 +00003916 }
3917 if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
3918 status=MagickFalse;
3919 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3920 {
3921 MagickBooleanType
3922 proceed;
3923
cristyb5d5f722009-11-04 03:03:49 +00003924#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003925 #pragma omp critical (MagickCore_ShadeImage)
3926#endif
3927 proceed=SetImageProgress(image,ShadeImageTag,progress++,image->rows);
3928 if (proceed == MagickFalse)
3929 status=MagickFalse;
3930 }
3931 }
3932 shade_view=DestroyCacheView(shade_view);
3933 image_view=DestroyCacheView(image_view);
3934 if (status == MagickFalse)
3935 shade_image=DestroyImage(shade_image);
3936 return(shade_image);
3937}
3938
3939/*
3940%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3941% %
3942% %
3943% %
3944% S h a r p e n I m a g e %
3945% %
3946% %
3947% %
3948%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3949%
3950% SharpenImage() sharpens the image. We convolve the image with a Gaussian
3951% operator of the given radius and standard deviation (sigma). For
3952% reasonable results, radius should be larger than sigma. Use a radius of 0
3953% and SharpenImage() selects a suitable radius for you.
3954%
3955% Using a separable kernel would be faster, but the negative weights cancel
3956% out on the corners of the kernel producing often undesirable ringing in the
3957% filtered result; this can be avoided by using a 2D gaussian shaped image
3958% sharpening kernel instead.
3959%
3960% The format of the SharpenImage method is:
3961%
3962% Image *SharpenImage(const Image *image,const double radius,
3963% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003964%
3965% A description of each parameter follows:
3966%
3967% o image: the image.
3968%
cristy3ed852e2009-09-05 21:47:34 +00003969% o radius: the radius of the Gaussian, in pixels, not counting the center
3970% pixel.
3971%
3972% o sigma: the standard deviation of the Laplacian, in pixels.
3973%
3974% o exception: return any errors or warnings in this structure.
3975%
3976*/
cristy3ed852e2009-09-05 21:47:34 +00003977MagickExport Image *SharpenImage(const Image *image,const double radius,
3978 const double sigma,ExceptionInfo *exception)
3979{
cristy3ed852e2009-09-05 21:47:34 +00003980 double
cristy47e00502009-12-17 19:19:57 +00003981 *kernel,
3982 normalize;
cristy3ed852e2009-09-05 21:47:34 +00003983
3984 Image
3985 *sharp_image;
3986
cristybb503372010-05-27 20:51:26 +00003987 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003988 i;
3989
cristybb503372010-05-27 20:51:26 +00003990 size_t
cristy3ed852e2009-09-05 21:47:34 +00003991 width;
3992
cristy117ff172010-08-15 21:35:32 +00003993 ssize_t
3994 j,
3995 u,
3996 v;
3997
cristy3ed852e2009-09-05 21:47:34 +00003998 assert(image != (const Image *) NULL);
3999 assert(image->signature == MagickSignature);
4000 if (image->debug != MagickFalse)
4001 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4002 assert(exception != (ExceptionInfo *) NULL);
4003 assert(exception->signature == MagickSignature);
4004 width=GetOptimalKernelWidth2D(radius,sigma);
4005 kernel=(double *) AcquireQuantumMemory((size_t) width*width,sizeof(*kernel));
4006 if (kernel == (double *) NULL)
4007 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy3ed852e2009-09-05 21:47:34 +00004008 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00004009 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +00004010 i=0;
4011 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00004012 {
cristy47e00502009-12-17 19:19:57 +00004013 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00004014 {
cristy4205a3c2010-09-12 20:19:59 +00004015 kernel[i]=(double) (-exp(-((double) u*u+v*v)/(2.0*MagickSigma*
4016 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00004017 normalize+=kernel[i];
4018 i++;
4019 }
4020 }
4021 kernel[i/2]=(double) ((-2.0)*normalize);
cristyf4ad9df2011-07-08 16:49:03 +00004022 sharp_image=ConvolveImage(image,width,kernel,exception);
cristy3ed852e2009-09-05 21:47:34 +00004023 kernel=(double *) RelinquishMagickMemory(kernel);
4024 return(sharp_image);
4025}
4026
4027/*
4028%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4029% %
4030% %
4031% %
4032% S p r e a d I m a g e %
4033% %
4034% %
4035% %
4036%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4037%
4038% SpreadImage() is a special effects method that randomly displaces each
4039% pixel in a block defined by the radius parameter.
4040%
4041% The format of the SpreadImage method is:
4042%
4043% Image *SpreadImage(const Image *image,const double radius,
4044% ExceptionInfo *exception)
4045%
4046% A description of each parameter follows:
4047%
4048% o image: the image.
4049%
4050% o radius: Choose a random pixel in a neighborhood of this extent.
4051%
4052% o exception: return any errors or warnings in this structure.
4053%
4054*/
4055MagickExport Image *SpreadImage(const Image *image,const double radius,
4056 ExceptionInfo *exception)
4057{
4058#define SpreadImageTag "Spread/Image"
4059
cristyfa112112010-01-04 17:48:07 +00004060 CacheView
cristy9f7e7cb2011-03-26 00:49:57 +00004061 *image_view,
4062 *spread_view;
cristyfa112112010-01-04 17:48:07 +00004063
cristy3ed852e2009-09-05 21:47:34 +00004064 Image
4065 *spread_image;
4066
cristy3ed852e2009-09-05 21:47:34 +00004067 MagickBooleanType
4068 status;
4069
cristybb503372010-05-27 20:51:26 +00004070 MagickOffsetType
4071 progress;
4072
cristy4c08aed2011-07-01 19:47:50 +00004073 PixelInfo
cristyddd82202009-11-03 20:14:50 +00004074 bias;
cristy3ed852e2009-09-05 21:47:34 +00004075
4076 RandomInfo
cristyfa112112010-01-04 17:48:07 +00004077 **restrict random_info;
cristy3ed852e2009-09-05 21:47:34 +00004078
cristybb503372010-05-27 20:51:26 +00004079 size_t
cristy3ed852e2009-09-05 21:47:34 +00004080 width;
4081
cristybb503372010-05-27 20:51:26 +00004082 ssize_t
4083 y;
4084
cristy3ed852e2009-09-05 21:47:34 +00004085 /*
4086 Initialize spread image attributes.
4087 */
4088 assert(image != (Image *) NULL);
4089 assert(image->signature == MagickSignature);
4090 if (image->debug != MagickFalse)
4091 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4092 assert(exception != (ExceptionInfo *) NULL);
4093 assert(exception->signature == MagickSignature);
4094 spread_image=CloneImage(image,image->columns,image->rows,MagickTrue,
4095 exception);
4096 if (spread_image == (Image *) NULL)
4097 return((Image *) NULL);
4098 if (SetImageStorageClass(spread_image,DirectClass) == MagickFalse)
4099 {
4100 InheritException(exception,&spread_image->exception);
4101 spread_image=DestroyImage(spread_image);
4102 return((Image *) NULL);
4103 }
4104 /*
4105 Spread image.
4106 */
4107 status=MagickTrue;
4108 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00004109 GetPixelInfo(spread_image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00004110 width=GetOptimalKernelWidth1D(radius,0.5);
cristy3ed852e2009-09-05 21:47:34 +00004111 random_info=AcquireRandomInfoThreadSet();
cristy9f7e7cb2011-03-26 00:49:57 +00004112 image_view=AcquireCacheView(image);
4113 spread_view=AcquireCacheView(spread_image);
cristyb557a152011-02-22 12:14:30 +00004114#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00004115 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00004116#endif
cristybb503372010-05-27 20:51:26 +00004117 for (y=0; y < (ssize_t) spread_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00004118 {
cristy5c9e6f22010-09-17 17:31:01 +00004119 const int
4120 id = GetOpenMPThreadId();
cristy6ebe97c2010-07-03 01:17:28 +00004121
cristy4c08aed2011-07-01 19:47:50 +00004122 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00004123 pixel;
4124
cristy4c08aed2011-07-01 19:47:50 +00004125 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00004126 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004127
cristy117ff172010-08-15 21:35:32 +00004128 register ssize_t
4129 x;
4130
cristy3ed852e2009-09-05 21:47:34 +00004131 if (status == MagickFalse)
4132 continue;
cristy9f7e7cb2011-03-26 00:49:57 +00004133 q=QueueCacheViewAuthenticPixels(spread_view,0,y,spread_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +00004134 exception);
cristy4c08aed2011-07-01 19:47:50 +00004135 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00004136 {
4137 status=MagickFalse;
4138 continue;
4139 }
cristyddd82202009-11-03 20:14:50 +00004140 pixel=bias;
cristybb503372010-05-27 20:51:26 +00004141 for (x=0; x < (ssize_t) spread_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004142 {
cristy4c08aed2011-07-01 19:47:50 +00004143 (void) InterpolatePixelInfo(image,image_view,
cristy8a7c3e82011-03-26 02:10:53 +00004144 UndefinedInterpolatePixel,(double) x+width*(GetPseudoRandomValue(
4145 random_info[id])-0.5),(double) y+width*(GetPseudoRandomValue(
4146 random_info[id])-0.5),&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00004147 SetPixelPixelInfo(spread_image,&pixel,q);
cristydcfc1ad2011-07-07 16:25:41 +00004148 q+=GetPixelComponents(spread_image);
cristy3ed852e2009-09-05 21:47:34 +00004149 }
cristy9f7e7cb2011-03-26 00:49:57 +00004150 if (SyncCacheViewAuthenticPixels(spread_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00004151 status=MagickFalse;
4152 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4153 {
4154 MagickBooleanType
4155 proceed;
4156
cristyb557a152011-02-22 12:14:30 +00004157#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004158 #pragma omp critical (MagickCore_SpreadImage)
4159#endif
4160 proceed=SetImageProgress(image,SpreadImageTag,progress++,image->rows);
4161 if (proceed == MagickFalse)
4162 status=MagickFalse;
4163 }
4164 }
cristy9f7e7cb2011-03-26 00:49:57 +00004165 spread_view=DestroyCacheView(spread_view);
cristy3ed852e2009-09-05 21:47:34 +00004166 image_view=DestroyCacheView(image_view);
4167 random_info=DestroyRandomInfoThreadSet(random_info);
cristy3ed852e2009-09-05 21:47:34 +00004168 return(spread_image);
4169}
4170
4171/*
4172%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4173% %
4174% %
4175% %
cristy0834d642011-03-18 18:26:08 +00004176% S t a t i s t i c I m a g e %
4177% %
4178% %
4179% %
4180%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4181%
4182% StatisticImage() makes each pixel the min / max / median / mode / etc. of
cristy8d752042011-03-19 01:00:36 +00004183% the neighborhood of the specified width and height.
cristy0834d642011-03-18 18:26:08 +00004184%
4185% The format of the StatisticImage method is:
4186%
4187% Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00004188% const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00004189%
4190% A description of each parameter follows:
4191%
4192% o image: the image.
4193%
cristy0834d642011-03-18 18:26:08 +00004194% o type: the statistic type (median, mode, etc.).
4195%
cristy95c38342011-03-18 22:39:51 +00004196% o width: the width of the pixel neighborhood.
4197%
4198% o height: the height of the pixel neighborhood.
cristy0834d642011-03-18 18:26:08 +00004199%
4200% o exception: return any errors or warnings in this structure.
4201%
4202*/
4203
cristy733678d2011-03-18 21:29:28 +00004204#define ListChannels 5
4205
4206typedef struct _ListNode
4207{
4208 size_t
4209 next[9],
4210 count,
4211 signature;
4212} ListNode;
4213
4214typedef struct _SkipList
4215{
4216 ssize_t
4217 level;
4218
4219 ListNode
4220 *nodes;
4221} SkipList;
4222
4223typedef struct _PixelList
4224{
4225 size_t
cristy6fc86bb2011-03-18 23:45:16 +00004226 length,
cristy733678d2011-03-18 21:29:28 +00004227 seed,
4228 signature;
4229
4230 SkipList
4231 lists[ListChannels];
4232} PixelList;
4233
4234static PixelList *DestroyPixelList(PixelList *pixel_list)
4235{
4236 register ssize_t
4237 i;
4238
4239 if (pixel_list == (PixelList *) NULL)
4240 return((PixelList *) NULL);
4241 for (i=0; i < ListChannels; i++)
4242 if (pixel_list->lists[i].nodes != (ListNode *) NULL)
4243 pixel_list->lists[i].nodes=(ListNode *) RelinquishMagickMemory(
4244 pixel_list->lists[i].nodes);
4245 pixel_list=(PixelList *) RelinquishMagickMemory(pixel_list);
4246 return(pixel_list);
4247}
4248
4249static PixelList **DestroyPixelListThreadSet(PixelList **pixel_list)
4250{
4251 register ssize_t
4252 i;
4253
4254 assert(pixel_list != (PixelList **) NULL);
4255 for (i=0; i < (ssize_t) GetOpenMPMaximumThreads(); i++)
4256 if (pixel_list[i] != (PixelList *) NULL)
4257 pixel_list[i]=DestroyPixelList(pixel_list[i]);
4258 pixel_list=(PixelList **) RelinquishMagickMemory(pixel_list);
4259 return(pixel_list);
4260}
4261
cristy6fc86bb2011-03-18 23:45:16 +00004262static PixelList *AcquirePixelList(const size_t width,const size_t height)
cristy733678d2011-03-18 21:29:28 +00004263{
4264 PixelList
4265 *pixel_list;
4266
4267 register ssize_t
4268 i;
4269
4270 pixel_list=(PixelList *) AcquireMagickMemory(sizeof(*pixel_list));
4271 if (pixel_list == (PixelList *) NULL)
4272 return(pixel_list);
4273 (void) ResetMagickMemory((void *) pixel_list,0,sizeof(*pixel_list));
cristy6fc86bb2011-03-18 23:45:16 +00004274 pixel_list->length=width*height;
cristy733678d2011-03-18 21:29:28 +00004275 for (i=0; i < ListChannels; i++)
4276 {
4277 pixel_list->lists[i].nodes=(ListNode *) AcquireQuantumMemory(65537UL,
4278 sizeof(*pixel_list->lists[i].nodes));
4279 if (pixel_list->lists[i].nodes == (ListNode *) NULL)
4280 return(DestroyPixelList(pixel_list));
4281 (void) ResetMagickMemory(pixel_list->lists[i].nodes,0,65537UL*
4282 sizeof(*pixel_list->lists[i].nodes));
4283 }
4284 pixel_list->signature=MagickSignature;
4285 return(pixel_list);
4286}
4287
cristy6fc86bb2011-03-18 23:45:16 +00004288static PixelList **AcquirePixelListThreadSet(const size_t width,
4289 const size_t height)
cristy733678d2011-03-18 21:29:28 +00004290{
4291 PixelList
4292 **pixel_list;
4293
4294 register ssize_t
4295 i;
4296
4297 size_t
4298 number_threads;
4299
4300 number_threads=GetOpenMPMaximumThreads();
4301 pixel_list=(PixelList **) AcquireQuantumMemory(number_threads,
4302 sizeof(*pixel_list));
4303 if (pixel_list == (PixelList **) NULL)
4304 return((PixelList **) NULL);
4305 (void) ResetMagickMemory(pixel_list,0,number_threads*sizeof(*pixel_list));
4306 for (i=0; i < (ssize_t) number_threads; i++)
4307 {
cristy6fc86bb2011-03-18 23:45:16 +00004308 pixel_list[i]=AcquirePixelList(width,height);
cristy733678d2011-03-18 21:29:28 +00004309 if (pixel_list[i] == (PixelList *) NULL)
4310 return(DestroyPixelListThreadSet(pixel_list));
4311 }
4312 return(pixel_list);
4313}
4314
4315static void AddNodePixelList(PixelList *pixel_list,const ssize_t channel,
4316 const size_t color)
4317{
4318 register SkipList
4319 *list;
4320
4321 register ssize_t
4322 level;
4323
4324 size_t
4325 search,
4326 update[9];
4327
4328 /*
4329 Initialize the node.
4330 */
4331 list=pixel_list->lists+channel;
4332 list->nodes[color].signature=pixel_list->signature;
4333 list->nodes[color].count=1;
4334 /*
4335 Determine where it belongs in the list.
4336 */
4337 search=65536UL;
4338 for (level=list->level; level >= 0; level--)
4339 {
4340 while (list->nodes[search].next[level] < color)
4341 search=list->nodes[search].next[level];
4342 update[level]=search;
4343 }
4344 /*
4345 Generate a pseudo-random level for this node.
4346 */
4347 for (level=0; ; level++)
4348 {
4349 pixel_list->seed=(pixel_list->seed*42893621L)+1L;
4350 if ((pixel_list->seed & 0x300) != 0x300)
4351 break;
4352 }
4353 if (level > 8)
4354 level=8;
4355 if (level > (list->level+2))
4356 level=list->level+2;
4357 /*
4358 If we're raising the list's level, link back to the root node.
4359 */
4360 while (level > list->level)
4361 {
4362 list->level++;
4363 update[list->level]=65536UL;
4364 }
4365 /*
4366 Link the node into the skip-list.
4367 */
4368 do
4369 {
4370 list->nodes[color].next[level]=list->nodes[update[level]].next[level];
4371 list->nodes[update[level]].next[level]=color;
cristy3cba8ca2011-03-19 01:29:12 +00004372 } while (level-- > 0);
cristy733678d2011-03-18 21:29:28 +00004373}
4374
cristy4c08aed2011-07-01 19:47:50 +00004375static PixelInfo GetMaximumPixelList(PixelList *pixel_list)
cristy6fc86bb2011-03-18 23:45:16 +00004376{
cristy4c08aed2011-07-01 19:47:50 +00004377 PixelInfo
cristy6fc86bb2011-03-18 23:45:16 +00004378 pixel;
4379
4380 register SkipList
4381 *list;
4382
4383 register ssize_t
4384 channel;
4385
4386 size_t
cristyd76c51e2011-03-26 00:21:26 +00004387 color,
4388 maximum;
cristy49f37242011-03-22 18:18:23 +00004389
4390 ssize_t
cristy6fc86bb2011-03-18 23:45:16 +00004391 count;
4392
4393 unsigned short
cristyd76c51e2011-03-26 00:21:26 +00004394 channels[ListChannels];
cristy6fc86bb2011-03-18 23:45:16 +00004395
4396 /*
4397 Find the maximum value for each of the color.
4398 */
4399 for (channel=0; channel < 5; channel++)
4400 {
4401 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004402 color=65536L;
cristy6fc86bb2011-03-18 23:45:16 +00004403 count=0;
cristy49f37242011-03-22 18:18:23 +00004404 maximum=list->nodes[color].next[0];
cristy6fc86bb2011-03-18 23:45:16 +00004405 do
4406 {
4407 color=list->nodes[color].next[0];
cristy49f37242011-03-22 18:18:23 +00004408 if (color > maximum)
4409 maximum=color;
cristy6fc86bb2011-03-18 23:45:16 +00004410 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004411 } while (count < (ssize_t) pixel_list->length);
cristy49f37242011-03-22 18:18:23 +00004412 channels[channel]=(unsigned short) maximum;
4413 }
cristy4c08aed2011-07-01 19:47:50 +00004414 GetPixelInfo((const Image *) NULL,&pixel);
cristy49f37242011-03-22 18:18:23 +00004415 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4416 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4417 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004418 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4419 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy49f37242011-03-22 18:18:23 +00004420 return(pixel);
4421}
4422
cristy4c08aed2011-07-01 19:47:50 +00004423static PixelInfo GetMeanPixelList(PixelList *pixel_list)
cristy49f37242011-03-22 18:18:23 +00004424{
cristy4c08aed2011-07-01 19:47:50 +00004425 PixelInfo
cristy49f37242011-03-22 18:18:23 +00004426 pixel;
4427
cristy80a99a32011-03-30 01:30:23 +00004428 MagickRealType
4429 sum;
4430
cristy49f37242011-03-22 18:18:23 +00004431 register SkipList
4432 *list;
4433
4434 register ssize_t
4435 channel;
4436
4437 size_t
cristy80a99a32011-03-30 01:30:23 +00004438 color;
cristy49f37242011-03-22 18:18:23 +00004439
4440 ssize_t
4441 count;
4442
4443 unsigned short
4444 channels[ListChannels];
4445
4446 /*
4447 Find the mean value for each of the color.
4448 */
4449 for (channel=0; channel < 5; channel++)
4450 {
4451 list=pixel_list->lists+channel;
4452 color=65536L;
4453 count=0;
cristy80a99a32011-03-30 01:30:23 +00004454 sum=0.0;
cristy49f37242011-03-22 18:18:23 +00004455 do
4456 {
4457 color=list->nodes[color].next[0];
cristy80a99a32011-03-30 01:30:23 +00004458 sum+=(MagickRealType) list->nodes[color].count*color;
cristy49f37242011-03-22 18:18:23 +00004459 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004460 } while (count < (ssize_t) pixel_list->length);
cristy80a99a32011-03-30 01:30:23 +00004461 sum/=pixel_list->length;
4462 channels[channel]=(unsigned short) sum;
cristy6fc86bb2011-03-18 23:45:16 +00004463 }
cristy4c08aed2011-07-01 19:47:50 +00004464 GetPixelInfo((const Image *) NULL,&pixel);
cristy6fc86bb2011-03-18 23:45:16 +00004465 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4466 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4467 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004468 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4469 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy6fc86bb2011-03-18 23:45:16 +00004470 return(pixel);
4471}
4472
cristy4c08aed2011-07-01 19:47:50 +00004473static PixelInfo GetMedianPixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004474{
cristy4c08aed2011-07-01 19:47:50 +00004475 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004476 pixel;
4477
4478 register SkipList
4479 *list;
4480
4481 register ssize_t
4482 channel;
4483
4484 size_t
cristy49f37242011-03-22 18:18:23 +00004485 color;
4486
4487 ssize_t
cristy733678d2011-03-18 21:29:28 +00004488 count;
4489
4490 unsigned short
4491 channels[ListChannels];
4492
4493 /*
4494 Find the median value for each of the color.
4495 */
cristy733678d2011-03-18 21:29:28 +00004496 for (channel=0; channel < 5; channel++)
4497 {
4498 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004499 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004500 count=0;
4501 do
4502 {
4503 color=list->nodes[color].next[0];
4504 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004505 } while (count <= (ssize_t) (pixel_list->length >> 1));
cristy6fc86bb2011-03-18 23:45:16 +00004506 channels[channel]=(unsigned short) color;
4507 }
cristy4c08aed2011-07-01 19:47:50 +00004508 GetPixelInfo((const Image *) NULL,&pixel);
cristy6fc86bb2011-03-18 23:45:16 +00004509 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4510 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4511 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004512 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4513 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy6fc86bb2011-03-18 23:45:16 +00004514 return(pixel);
4515}
4516
cristy4c08aed2011-07-01 19:47:50 +00004517static PixelInfo GetMinimumPixelList(PixelList *pixel_list)
cristy6fc86bb2011-03-18 23:45:16 +00004518{
cristy4c08aed2011-07-01 19:47:50 +00004519 PixelInfo
cristy6fc86bb2011-03-18 23:45:16 +00004520 pixel;
4521
4522 register SkipList
4523 *list;
4524
4525 register ssize_t
4526 channel;
4527
4528 size_t
cristyd76c51e2011-03-26 00:21:26 +00004529 color,
4530 minimum;
cristy6fc86bb2011-03-18 23:45:16 +00004531
cristy49f37242011-03-22 18:18:23 +00004532 ssize_t
4533 count;
4534
cristy6fc86bb2011-03-18 23:45:16 +00004535 unsigned short
cristyd76c51e2011-03-26 00:21:26 +00004536 channels[ListChannels];
cristy6fc86bb2011-03-18 23:45:16 +00004537
4538 /*
4539 Find the minimum value for each of the color.
4540 */
4541 for (channel=0; channel < 5; channel++)
4542 {
4543 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004544 count=0;
cristy6fc86bb2011-03-18 23:45:16 +00004545 color=65536UL;
cristy49f37242011-03-22 18:18:23 +00004546 minimum=list->nodes[color].next[0];
4547 do
4548 {
4549 color=list->nodes[color].next[0];
4550 if (color < minimum)
4551 minimum=color;
4552 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004553 } while (count < (ssize_t) pixel_list->length);
cristy49f37242011-03-22 18:18:23 +00004554 channels[channel]=(unsigned short) minimum;
cristy733678d2011-03-18 21:29:28 +00004555 }
cristy4c08aed2011-07-01 19:47:50 +00004556 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004557 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4558 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4559 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004560 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4561 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy733678d2011-03-18 21:29:28 +00004562 return(pixel);
4563}
4564
cristy4c08aed2011-07-01 19:47:50 +00004565static PixelInfo GetModePixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004566{
cristy4c08aed2011-07-01 19:47:50 +00004567 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004568 pixel;
4569
4570 register SkipList
4571 *list;
4572
4573 register ssize_t
4574 channel;
4575
4576 size_t
4577 color,
cristy733678d2011-03-18 21:29:28 +00004578 max_count,
cristy6fc86bb2011-03-18 23:45:16 +00004579 mode;
cristy733678d2011-03-18 21:29:28 +00004580
cristy49f37242011-03-22 18:18:23 +00004581 ssize_t
4582 count;
4583
cristy733678d2011-03-18 21:29:28 +00004584 unsigned short
4585 channels[5];
4586
4587 /*
glennrp30d2dc62011-06-25 03:17:16 +00004588 Make each pixel the 'predominant color' of the specified neighborhood.
cristy733678d2011-03-18 21:29:28 +00004589 */
cristy733678d2011-03-18 21:29:28 +00004590 for (channel=0; channel < 5; channel++)
4591 {
4592 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004593 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004594 mode=color;
4595 max_count=list->nodes[mode].count;
4596 count=0;
4597 do
4598 {
4599 color=list->nodes[color].next[0];
4600 if (list->nodes[color].count > max_count)
4601 {
4602 mode=color;
4603 max_count=list->nodes[mode].count;
4604 }
4605 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004606 } while (count < (ssize_t) pixel_list->length);
cristy733678d2011-03-18 21:29:28 +00004607 channels[channel]=(unsigned short) mode;
4608 }
cristy4c08aed2011-07-01 19:47:50 +00004609 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004610 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4611 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4612 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004613 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4614 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy733678d2011-03-18 21:29:28 +00004615 return(pixel);
4616}
4617
cristy4c08aed2011-07-01 19:47:50 +00004618static PixelInfo GetNonpeakPixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004619{
cristy4c08aed2011-07-01 19:47:50 +00004620 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004621 pixel;
4622
4623 register SkipList
4624 *list;
4625
4626 register ssize_t
4627 channel;
4628
4629 size_t
cristy733678d2011-03-18 21:29:28 +00004630 color,
cristy733678d2011-03-18 21:29:28 +00004631 next,
4632 previous;
4633
cristy49f37242011-03-22 18:18:23 +00004634 ssize_t
4635 count;
4636
cristy733678d2011-03-18 21:29:28 +00004637 unsigned short
4638 channels[5];
4639
4640 /*
cristy49f37242011-03-22 18:18:23 +00004641 Finds the non peak value for each of the colors.
cristy733678d2011-03-18 21:29:28 +00004642 */
cristy733678d2011-03-18 21:29:28 +00004643 for (channel=0; channel < 5; channel++)
4644 {
4645 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004646 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004647 next=list->nodes[color].next[0];
4648 count=0;
4649 do
4650 {
4651 previous=color;
4652 color=next;
4653 next=list->nodes[color].next[0];
4654 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004655 } while (count <= (ssize_t) (pixel_list->length >> 1));
cristy733678d2011-03-18 21:29:28 +00004656 if ((previous == 65536UL) && (next != 65536UL))
4657 color=next;
4658 else
4659 if ((previous != 65536UL) && (next == 65536UL))
4660 color=previous;
4661 channels[channel]=(unsigned short) color;
4662 }
cristy4c08aed2011-07-01 19:47:50 +00004663 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004664 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4665 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4666 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004667 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4668 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy733678d2011-03-18 21:29:28 +00004669 return(pixel);
4670}
4671
cristy4c08aed2011-07-01 19:47:50 +00004672static PixelInfo GetStandardDeviationPixelList(PixelList *pixel_list)
cristy9a68cbb2011-03-29 00:51:23 +00004673{
cristy4c08aed2011-07-01 19:47:50 +00004674 PixelInfo
cristy9a68cbb2011-03-29 00:51:23 +00004675 pixel;
4676
cristy80a99a32011-03-30 01:30:23 +00004677 MagickRealType
4678 sum,
4679 sum_squared;
4680
cristy9a68cbb2011-03-29 00:51:23 +00004681 register SkipList
4682 *list;
4683
4684 register ssize_t
4685 channel;
4686
4687 size_t
cristy80a99a32011-03-30 01:30:23 +00004688 color;
cristy9a68cbb2011-03-29 00:51:23 +00004689
4690 ssize_t
4691 count;
4692
4693 unsigned short
4694 channels[ListChannels];
4695
4696 /*
cristy80a99a32011-03-30 01:30:23 +00004697 Find the standard-deviation value for each of the color.
cristy9a68cbb2011-03-29 00:51:23 +00004698 */
4699 for (channel=0; channel < 5; channel++)
4700 {
4701 list=pixel_list->lists+channel;
4702 color=65536L;
4703 count=0;
cristy80a99a32011-03-30 01:30:23 +00004704 sum=0.0;
4705 sum_squared=0.0;
cristy9a68cbb2011-03-29 00:51:23 +00004706 do
4707 {
cristy80a99a32011-03-30 01:30:23 +00004708 register ssize_t
4709 i;
4710
cristy9a68cbb2011-03-29 00:51:23 +00004711 color=list->nodes[color].next[0];
cristy80a99a32011-03-30 01:30:23 +00004712 sum+=(MagickRealType) list->nodes[color].count*color;
4713 for (i=0; i < (ssize_t) list->nodes[color].count; i++)
4714 sum_squared+=((MagickRealType) color)*((MagickRealType) color);
cristy9a68cbb2011-03-29 00:51:23 +00004715 count+=list->nodes[color].count;
4716 } while (count < (ssize_t) pixel_list->length);
cristy80a99a32011-03-30 01:30:23 +00004717 sum/=pixel_list->length;
4718 sum_squared/=pixel_list->length;
4719 channels[channel]=(unsigned short) sqrt(sum_squared-(sum*sum));
cristy9a68cbb2011-03-29 00:51:23 +00004720 }
cristy4c08aed2011-07-01 19:47:50 +00004721 GetPixelInfo((const Image *) NULL,&pixel);
cristy9a68cbb2011-03-29 00:51:23 +00004722 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4723 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4724 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004725 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4726 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy9a68cbb2011-03-29 00:51:23 +00004727 return(pixel);
4728}
4729
cristy4c08aed2011-07-01 19:47:50 +00004730static inline void InsertPixelList(const Image *image,const Quantum *pixel,
4731 PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004732{
4733 size_t
4734 signature;
4735
4736 unsigned short
4737 index;
4738
cristy4c08aed2011-07-01 19:47:50 +00004739 index=ScaleQuantumToShort(GetPixelRed(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004740 signature=pixel_list->lists[0].nodes[index].signature;
4741 if (signature == pixel_list->signature)
4742 pixel_list->lists[0].nodes[index].count++;
4743 else
4744 AddNodePixelList(pixel_list,0,index);
cristy4c08aed2011-07-01 19:47:50 +00004745 index=ScaleQuantumToShort(GetPixelGreen(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004746 signature=pixel_list->lists[1].nodes[index].signature;
4747 if (signature == pixel_list->signature)
4748 pixel_list->lists[1].nodes[index].count++;
4749 else
4750 AddNodePixelList(pixel_list,1,index);
cristy4c08aed2011-07-01 19:47:50 +00004751 index=ScaleQuantumToShort(GetPixelBlue(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004752 signature=pixel_list->lists[2].nodes[index].signature;
4753 if (signature == pixel_list->signature)
4754 pixel_list->lists[2].nodes[index].count++;
4755 else
4756 AddNodePixelList(pixel_list,2,index);
cristy4c08aed2011-07-01 19:47:50 +00004757 index=ScaleQuantumToShort(GetPixelAlpha(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004758 signature=pixel_list->lists[3].nodes[index].signature;
4759 if (signature == pixel_list->signature)
4760 pixel_list->lists[3].nodes[index].count++;
4761 else
4762 AddNodePixelList(pixel_list,3,index);
4763 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00004764 index=ScaleQuantumToShort(GetPixelBlack(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004765 signature=pixel_list->lists[4].nodes[index].signature;
4766 if (signature == pixel_list->signature)
4767 pixel_list->lists[4].nodes[index].count++;
4768 else
4769 AddNodePixelList(pixel_list,4,index);
4770}
4771
cristy80c99742011-04-04 14:46:39 +00004772static inline MagickRealType MagickAbsoluteValue(const MagickRealType x)
4773{
4774 if (x < 0)
4775 return(-x);
4776 return(x);
4777}
4778
cristy733678d2011-03-18 21:29:28 +00004779static void ResetPixelList(PixelList *pixel_list)
4780{
4781 int
4782 level;
4783
4784 register ListNode
4785 *root;
4786
4787 register SkipList
4788 *list;
4789
4790 register ssize_t
4791 channel;
4792
4793 /*
4794 Reset the skip-list.
4795 */
4796 for (channel=0; channel < 5; channel++)
4797 {
4798 list=pixel_list->lists+channel;
4799 root=list->nodes+65536UL;
4800 list->level=0;
4801 for (level=0; level < 9; level++)
4802 root->next[level]=65536UL;
4803 }
4804 pixel_list->seed=pixel_list->signature++;
4805}
4806
cristy0834d642011-03-18 18:26:08 +00004807MagickExport Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00004808 const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00004809{
cristy3cba8ca2011-03-19 01:29:12 +00004810#define StatisticWidth \
cristyd76c51e2011-03-26 00:21:26 +00004811 (width == 0 ? GetOptimalKernelWidth2D((double) width,0.5) : width)
cristy3cba8ca2011-03-19 01:29:12 +00004812#define StatisticHeight \
cristyd76c51e2011-03-26 00:21:26 +00004813 (height == 0 ? GetOptimalKernelWidth2D((double) height,0.5) : height)
cristy0834d642011-03-18 18:26:08 +00004814#define StatisticImageTag "Statistic/Image"
4815
4816 CacheView
4817 *image_view,
4818 *statistic_view;
4819
4820 Image
4821 *statistic_image;
4822
4823 MagickBooleanType
4824 status;
4825
4826 MagickOffsetType
4827 progress;
4828
4829 PixelList
4830 **restrict pixel_list;
4831
cristy0834d642011-03-18 18:26:08 +00004832 ssize_t
4833 y;
4834
4835 /*
4836 Initialize statistics image attributes.
4837 */
4838 assert(image != (Image *) NULL);
4839 assert(image->signature == MagickSignature);
4840 if (image->debug != MagickFalse)
4841 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4842 assert(exception != (ExceptionInfo *) NULL);
4843 assert(exception->signature == MagickSignature);
cristy0834d642011-03-18 18:26:08 +00004844 statistic_image=CloneImage(image,image->columns,image->rows,MagickTrue,
4845 exception);
4846 if (statistic_image == (Image *) NULL)
4847 return((Image *) NULL);
4848 if (SetImageStorageClass(statistic_image,DirectClass) == MagickFalse)
4849 {
4850 InheritException(exception,&statistic_image->exception);
4851 statistic_image=DestroyImage(statistic_image);
4852 return((Image *) NULL);
4853 }
cristy6fc86bb2011-03-18 23:45:16 +00004854 pixel_list=AcquirePixelListThreadSet(StatisticWidth,StatisticHeight);
cristy0834d642011-03-18 18:26:08 +00004855 if (pixel_list == (PixelList **) NULL)
4856 {
4857 statistic_image=DestroyImage(statistic_image);
4858 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
4859 }
4860 /*
cristy8d752042011-03-19 01:00:36 +00004861 Make each pixel the min / max / median / mode / etc. of the neighborhood.
cristy0834d642011-03-18 18:26:08 +00004862 */
4863 status=MagickTrue;
4864 progress=0;
4865 image_view=AcquireCacheView(image);
4866 statistic_view=AcquireCacheView(statistic_image);
4867#if defined(MAGICKCORE_OPENMP_SUPPORT)
4868 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
4869#endif
4870 for (y=0; y < (ssize_t) statistic_image->rows; y++)
4871 {
4872 const int
4873 id = GetOpenMPThreadId();
4874
cristy4c08aed2011-07-01 19:47:50 +00004875 register const Quantum
cristy0834d642011-03-18 18:26:08 +00004876 *restrict p;
4877
cristy4c08aed2011-07-01 19:47:50 +00004878 register Quantum
cristy0834d642011-03-18 18:26:08 +00004879 *restrict q;
4880
4881 register ssize_t
4882 x;
4883
4884 if (status == MagickFalse)
4885 continue;
cristy6fc86bb2011-03-18 23:45:16 +00004886 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) StatisticWidth/2L),y-
4887 (ssize_t) (StatisticHeight/2L),image->columns+StatisticWidth,
4888 StatisticHeight,exception);
cristy3cba8ca2011-03-19 01:29:12 +00004889 q=QueueCacheViewAuthenticPixels(statistic_view,0,y,statistic_image->columns, 1,exception);
cristy4c08aed2011-07-01 19:47:50 +00004890 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy0834d642011-03-18 18:26:08 +00004891 {
4892 status=MagickFalse;
4893 continue;
4894 }
cristy0834d642011-03-18 18:26:08 +00004895 for (x=0; x < (ssize_t) statistic_image->columns; x++)
4896 {
cristy4c08aed2011-07-01 19:47:50 +00004897 PixelInfo
cristy0834d642011-03-18 18:26:08 +00004898 pixel;
4899
cristy4c08aed2011-07-01 19:47:50 +00004900 register const Quantum
cristy6e3026a2011-03-19 00:54:38 +00004901 *restrict r;
4902
cristy0834d642011-03-18 18:26:08 +00004903 register ssize_t
4904 u,
4905 v;
4906
4907 r=p;
cristy0834d642011-03-18 18:26:08 +00004908 ResetPixelList(pixel_list[id]);
cristy6e4c3292011-03-19 00:53:55 +00004909 for (v=0; v < (ssize_t) StatisticHeight; v++)
cristy0834d642011-03-18 18:26:08 +00004910 {
cristy6e4c3292011-03-19 00:53:55 +00004911 for (u=0; u < (ssize_t) StatisticWidth; u++)
cristydcfc1ad2011-07-07 16:25:41 +00004912 InsertPixelList(image,r+u*GetPixelComponents(image),pixel_list[id]);
4913 r+=(image->columns+StatisticWidth)*GetPixelComponents(image);
cristy0834d642011-03-18 18:26:08 +00004914 }
cristy4c08aed2011-07-01 19:47:50 +00004915 GetPixelInfo(image,&pixel);
cristy490408a2011-07-07 14:42:05 +00004916 SetPixelInfo(image,p+(StatisticWidth*StatisticHeight/2)*
cristydcfc1ad2011-07-07 16:25:41 +00004917 GetPixelComponents(image),&pixel);
cristy0834d642011-03-18 18:26:08 +00004918 switch (type)
4919 {
cristy80c99742011-04-04 14:46:39 +00004920 case GradientStatistic:
4921 {
cristy4c08aed2011-07-01 19:47:50 +00004922 PixelInfo
cristy80c99742011-04-04 14:46:39 +00004923 maximum,
4924 minimum;
4925
4926 minimum=GetMinimumPixelList(pixel_list[id]);
4927 maximum=GetMaximumPixelList(pixel_list[id]);
4928 pixel.red=MagickAbsoluteValue(maximum.red-minimum.red);
4929 pixel.green=MagickAbsoluteValue(maximum.green-minimum.green);
4930 pixel.blue=MagickAbsoluteValue(maximum.blue-minimum.blue);
cristy4c08aed2011-07-01 19:47:50 +00004931 pixel.alpha=MagickAbsoluteValue(maximum.alpha-minimum.alpha);
cristy80c99742011-04-04 14:46:39 +00004932 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00004933 pixel.black=MagickAbsoluteValue(maximum.black-minimum.black);
cristy80c99742011-04-04 14:46:39 +00004934 break;
4935 }
cristy6fc86bb2011-03-18 23:45:16 +00004936 case MaximumStatistic:
4937 {
4938 pixel=GetMaximumPixelList(pixel_list[id]);
4939 break;
4940 }
cristy49f37242011-03-22 18:18:23 +00004941 case MeanStatistic:
4942 {
4943 pixel=GetMeanPixelList(pixel_list[id]);
4944 break;
4945 }
cristyf2ad14a2011-03-18 18:57:25 +00004946 case MedianStatistic:
cristy6fc86bb2011-03-18 23:45:16 +00004947 default:
cristyf2ad14a2011-03-18 18:57:25 +00004948 {
4949 pixel=GetMedianPixelList(pixel_list[id]);
4950 break;
4951 }
cristy6fc86bb2011-03-18 23:45:16 +00004952 case MinimumStatistic:
4953 {
4954 pixel=GetMinimumPixelList(pixel_list[id]);
4955 break;
4956 }
cristyf2ad14a2011-03-18 18:57:25 +00004957 case ModeStatistic:
4958 {
4959 pixel=GetModePixelList(pixel_list[id]);
4960 break;
4961 }
4962 case NonpeakStatistic:
4963 {
4964 pixel=GetNonpeakPixelList(pixel_list[id]);
4965 break;
4966 }
cristy9a68cbb2011-03-29 00:51:23 +00004967 case StandardDeviationStatistic:
4968 {
4969 pixel=GetStandardDeviationPixelList(pixel_list[id]);
4970 break;
4971 }
cristy0834d642011-03-18 18:26:08 +00004972 }
cristy2b9582a2011-07-04 17:38:56 +00004973 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00004974 SetPixelRed(statistic_image,ClampToQuantum(pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00004975 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00004976 SetPixelGreen(statistic_image,ClampToQuantum(pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00004977 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00004978 SetPixelBlue(statistic_image,ClampToQuantum(pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00004979 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy0834d642011-03-18 18:26:08 +00004980 (image->colorspace == CMYKColorspace))
cristy490408a2011-07-07 14:42:05 +00004981 SetPixelBlack(statistic_image,ClampToQuantum(pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00004982 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00004983 (image->matte != MagickFalse))
cristy490408a2011-07-07 14:42:05 +00004984 SetPixelAlpha(statistic_image,ClampToQuantum(pixel.alpha),q);
cristydcfc1ad2011-07-07 16:25:41 +00004985 p+=GetPixelComponents(image);
4986 q+=GetPixelComponents(statistic_image);
cristy0834d642011-03-18 18:26:08 +00004987 }
4988 if (SyncCacheViewAuthenticPixels(statistic_view,exception) == MagickFalse)
4989 status=MagickFalse;
4990 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4991 {
4992 MagickBooleanType
4993 proceed;
4994
4995#if defined(MAGICKCORE_OPENMP_SUPPORT)
4996 #pragma omp critical (MagickCore_StatisticImage)
4997#endif
4998 proceed=SetImageProgress(image,StatisticImageTag,progress++,
4999 image->rows);
5000 if (proceed == MagickFalse)
5001 status=MagickFalse;
5002 }
5003 }
5004 statistic_view=DestroyCacheView(statistic_view);
5005 image_view=DestroyCacheView(image_view);
5006 pixel_list=DestroyPixelListThreadSet(pixel_list);
5007 return(statistic_image);
5008}
5009
5010/*
5011%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5012% %
5013% %
5014% %
cristy3ed852e2009-09-05 21:47:34 +00005015% U n s h a r p M a s k I m a g e %
5016% %
5017% %
5018% %
5019%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5020%
5021% UnsharpMaskImage() sharpens one or more image channels. We convolve the
5022% image with a Gaussian operator of the given radius and standard deviation
5023% (sigma). For reasonable results, radius should be larger than sigma. Use a
5024% radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
5025%
5026% The format of the UnsharpMaskImage method is:
5027%
5028% Image *UnsharpMaskImage(const Image *image,const double radius,
5029% const double sigma,const double amount,const double threshold,
5030% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00005031%
5032% A description of each parameter follows:
5033%
5034% o image: the image.
5035%
cristy3ed852e2009-09-05 21:47:34 +00005036% o radius: the radius of the Gaussian, in pixels, not counting the center
5037% pixel.
5038%
5039% o sigma: the standard deviation of the Gaussian, in pixels.
5040%
5041% o amount: the percentage of the difference between the original and the
5042% blur image that is added back into the original.
5043%
5044% o threshold: the threshold in pixels needed to apply the diffence amount.
5045%
5046% o exception: return any errors or warnings in this structure.
5047%
5048*/
cristyf4ad9df2011-07-08 16:49:03 +00005049MagickExport Image *UnsharpMaskImage(const Image *image,
5050 const double radius,const double sigma,const double amount,
5051 const double threshold,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00005052{
5053#define SharpenImageTag "Sharpen/Image"
5054
cristyc4c8d132010-01-07 01:58:38 +00005055 CacheView
5056 *image_view,
5057 *unsharp_view;
5058
cristy3ed852e2009-09-05 21:47:34 +00005059 Image
5060 *unsharp_image;
5061
cristy3ed852e2009-09-05 21:47:34 +00005062 MagickBooleanType
5063 status;
5064
cristybb503372010-05-27 20:51:26 +00005065 MagickOffsetType
5066 progress;
5067
cristy4c08aed2011-07-01 19:47:50 +00005068 PixelInfo
cristyddd82202009-11-03 20:14:50 +00005069 bias;
cristy3ed852e2009-09-05 21:47:34 +00005070
5071 MagickRealType
5072 quantum_threshold;
5073
cristybb503372010-05-27 20:51:26 +00005074 ssize_t
5075 y;
5076
cristy3ed852e2009-09-05 21:47:34 +00005077 assert(image != (const Image *) NULL);
5078 assert(image->signature == MagickSignature);
5079 if (image->debug != MagickFalse)
5080 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5081 assert(exception != (ExceptionInfo *) NULL);
cristyf4ad9df2011-07-08 16:49:03 +00005082 unsharp_image=BlurImage(image,radius,sigma,exception);
cristy3ed852e2009-09-05 21:47:34 +00005083 if (unsharp_image == (Image *) NULL)
5084 return((Image *) NULL);
5085 quantum_threshold=(MagickRealType) QuantumRange*threshold;
5086 /*
5087 Unsharp-mask image.
5088 */
5089 status=MagickTrue;
5090 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00005091 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00005092 image_view=AcquireCacheView(image);
5093 unsharp_view=AcquireCacheView(unsharp_image);
cristyb5d5f722009-11-04 03:03:49 +00005094#if defined(MAGICKCORE_OPENMP_SUPPORT)
5095 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00005096#endif
cristybb503372010-05-27 20:51:26 +00005097 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00005098 {
cristy4c08aed2011-07-01 19:47:50 +00005099 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00005100 pixel;
5101
cristy4c08aed2011-07-01 19:47:50 +00005102 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00005103 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00005104
cristy4c08aed2011-07-01 19:47:50 +00005105 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00005106 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00005107
cristy117ff172010-08-15 21:35:32 +00005108 register ssize_t
5109 x;
5110
cristy3ed852e2009-09-05 21:47:34 +00005111 if (status == MagickFalse)
5112 continue;
5113 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
5114 q=GetCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
5115 exception);
cristy4c08aed2011-07-01 19:47:50 +00005116 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00005117 {
5118 status=MagickFalse;
5119 continue;
5120 }
cristyddd82202009-11-03 20:14:50 +00005121 pixel=bias;
cristybb503372010-05-27 20:51:26 +00005122 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00005123 {
cristy2b9582a2011-07-04 17:38:56 +00005124 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00005125 {
cristy4c08aed2011-07-01 19:47:50 +00005126 pixel.red=GetPixelRed(image,p)-(MagickRealType) GetPixelRed(image,q);
cristy3ed852e2009-09-05 21:47:34 +00005127 if (fabs(2.0*pixel.red) < quantum_threshold)
cristy4c08aed2011-07-01 19:47:50 +00005128 pixel.red=(MagickRealType) GetPixelRed(image,p);
cristy3ed852e2009-09-05 21:47:34 +00005129 else
cristy4c08aed2011-07-01 19:47:50 +00005130 pixel.red=(MagickRealType) GetPixelRed(image,p)+(pixel.red*amount);
5131 SetPixelRed(unsharp_image,ClampToQuantum(pixel.red),q);
cristy3ed852e2009-09-05 21:47:34 +00005132 }
cristy2b9582a2011-07-04 17:38:56 +00005133 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00005134 {
cristy4c08aed2011-07-01 19:47:50 +00005135 pixel.green=GetPixelGreen(image,p)-
5136 (MagickRealType) GetPixelGreen(image,q);
cristy3ed852e2009-09-05 21:47:34 +00005137 if (fabs(2.0*pixel.green) < quantum_threshold)
cristy4c08aed2011-07-01 19:47:50 +00005138 pixel.green=(MagickRealType)
5139 GetPixelGreen(image,p);
cristy3ed852e2009-09-05 21:47:34 +00005140 else
cristy4c08aed2011-07-01 19:47:50 +00005141 pixel.green=(MagickRealType)
5142 GetPixelGreen(image,p)+
5143 (pixel.green*amount);
5144 SetPixelGreen(unsharp_image,
5145 ClampToQuantum(pixel.green),q);
cristy3ed852e2009-09-05 21:47:34 +00005146 }
cristy2b9582a2011-07-04 17:38:56 +00005147 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00005148 {
cristy4c08aed2011-07-01 19:47:50 +00005149 pixel.blue=GetPixelBlue(image,p)-
5150 (MagickRealType) GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00005151 if (fabs(2.0*pixel.blue) < quantum_threshold)
cristy4c08aed2011-07-01 19:47:50 +00005152 pixel.blue=(MagickRealType)
5153 GetPixelBlue(image,p);
cristy3ed852e2009-09-05 21:47:34 +00005154 else
cristy4c08aed2011-07-01 19:47:50 +00005155 pixel.blue=(MagickRealType)
5156 GetPixelBlue(image,p)+(pixel.blue*amount);
5157 SetPixelBlue(unsharp_image,
5158 ClampToQuantum(pixel.blue),q);
cristy3ed852e2009-09-05 21:47:34 +00005159 }
cristy2b9582a2011-07-04 17:38:56 +00005160 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00005161 (image->colorspace == CMYKColorspace))
5162 {
cristy4c08aed2011-07-01 19:47:50 +00005163 pixel.black=GetPixelBlack(image,p)-
5164 (MagickRealType) GetPixelBlack(image,q);
5165 if (fabs(2.0*pixel.black) < quantum_threshold)
5166 pixel.black=(MagickRealType)
5167 GetPixelBlack(image,p);
cristy3ed852e2009-09-05 21:47:34 +00005168 else
cristy4c08aed2011-07-01 19:47:50 +00005169 pixel.black=(MagickRealType)
5170 GetPixelBlack(image,p)+(pixel.black*
5171 amount);
5172 SetPixelBlack(unsharp_image,
5173 ClampToQuantum(pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00005174 }
cristy2b9582a2011-07-04 17:38:56 +00005175 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00005176 {
5177 pixel.alpha=GetPixelAlpha(image,p)-
5178 (MagickRealType) GetPixelAlpha(image,q);
5179 if (fabs(2.0*pixel.alpha) < quantum_threshold)
5180 pixel.alpha=(MagickRealType)
5181 GetPixelAlpha(image,p);
5182 else
5183 pixel.alpha=GetPixelAlpha(image,p)+
5184 (pixel.alpha*amount);
5185 SetPixelAlpha(unsharp_image,
5186 ClampToQuantum(pixel.alpha),q);
5187 }
cristydcfc1ad2011-07-07 16:25:41 +00005188 p+=GetPixelComponents(image);
5189 q+=GetPixelComponents(unsharp_image);
cristy3ed852e2009-09-05 21:47:34 +00005190 }
5191 if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
5192 status=MagickFalse;
5193 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5194 {
5195 MagickBooleanType
5196 proceed;
5197
cristyb5d5f722009-11-04 03:03:49 +00005198#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00005199 #pragma omp critical (MagickCore_UnsharpMaskImage)
cristy3ed852e2009-09-05 21:47:34 +00005200#endif
5201 proceed=SetImageProgress(image,SharpenImageTag,progress++,image->rows);
5202 if (proceed == MagickFalse)
5203 status=MagickFalse;
5204 }
5205 }
5206 unsharp_image->type=image->type;
5207 unsharp_view=DestroyCacheView(unsharp_view);
5208 image_view=DestroyCacheView(image_view);
5209 if (status == MagickFalse)
5210 unsharp_image=DestroyImage(unsharp_image);
5211 return(unsharp_image);
5212}