blob: 826d9877768a9b2430b90d879a6a0b171eb01e13 [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)
cristyfccdab92009-11-30 16:43:57 +00001194% A description of each parameter follows:
1195%
1196% o image: the image.
1197%
cristyfccdab92009-11-30 16:43:57 +00001198% o order: the number of columns and rows in the filter kernel.
1199%
1200% o kernel: An array of double representing the convolution kernel.
1201%
1202% o exception: return any errors or warnings in this structure.
1203%
1204*/
cristybb503372010-05-27 20:51:26 +00001205MagickExport Image *ConvolveImage(const Image *image,const size_t order,
cristyfccdab92009-11-30 16:43:57 +00001206 const double *kernel,ExceptionInfo *exception)
1207{
cristyfccdab92009-11-30 16:43:57 +00001208#define ConvolveImageTag "Convolve/Image"
1209
cristyc4c8d132010-01-07 01:58:38 +00001210 CacheView
1211 *convolve_view,
1212 *image_view;
1213
cristyfccdab92009-11-30 16:43:57 +00001214 double
1215 *normal_kernel;
1216
1217 Image
1218 *convolve_image;
1219
cristyfccdab92009-11-30 16:43:57 +00001220 MagickBooleanType
1221 status;
1222
cristybb503372010-05-27 20:51:26 +00001223 MagickOffsetType
1224 progress;
1225
cristy175653e2011-07-10 23:13:34 +00001226 PixelInfo
1227 bias;
1228
cristyfccdab92009-11-30 16:43:57 +00001229 MagickRealType
1230 gamma;
1231
cristybb503372010-05-27 20:51:26 +00001232 register ssize_t
cristyfccdab92009-11-30 16:43:57 +00001233 i;
1234
cristybb503372010-05-27 20:51:26 +00001235 size_t
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;
cristy175653e2011-07-10 23:13:34 +00001315 GetPixelInfo(image,&bias);
1316 SetPixelInfoBias(image,&bias);
cristyfccdab92009-11-30 16:43:57 +00001317 image_view=AcquireCacheView(image);
1318 convolve_view=AcquireCacheView(convolve_image);
1319#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy175653e2011-07-10 23:13:34 +00001320 #pragma omp parallel for schedule(dynamic,4) 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 {
cristy175653e2011-07-10 23:13:34 +00001324 MagickBooleanType
1325 sync;
1326
cristy4c08aed2011-07-01 19:47:50 +00001327 register const Quantum
cristyfccdab92009-11-30 16:43:57 +00001328 *restrict p;
1329
cristy4c08aed2011-07-01 19:47:50 +00001330 register Quantum
cristyfccdab92009-11-30 16:43:57 +00001331 *restrict q;
1332
cristy117ff172010-08-15 21:35:32 +00001333 register ssize_t
1334 x;
1335
cristyfccdab92009-11-30 16:43:57 +00001336 if (status == MagickFalse)
1337 continue;
cristyce889302010-06-30 19:16:36 +00001338 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
1339 (width/2L),image->columns+width,width,exception);
cristyfccdab92009-11-30 16:43:57 +00001340 q=GetCacheViewAuthenticPixels(convolve_view,0,y,convolve_image->columns,1,
1341 exception);
cristy4c08aed2011-07-01 19:47:50 +00001342 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristyfccdab92009-11-30 16:43:57 +00001343 {
1344 status=MagickFalse;
1345 continue;
1346 }
cristybb503372010-05-27 20:51:26 +00001347 for (x=0; x < (ssize_t) image->columns; x++)
cristyfccdab92009-11-30 16:43:57 +00001348 {
cristy175653e2011-07-10 23:13:34 +00001349 PixelInfo
1350 pixel;
cristyfccdab92009-11-30 16:43:57 +00001351
1352 register const double
1353 *restrict k;
1354
cristy175653e2011-07-10 23:13:34 +00001355 register const Quantum
1356 *restrict kernel_pixels;
1357
cristybb503372010-05-27 20:51:26 +00001358 register ssize_t
cristyfccdab92009-11-30 16:43:57 +00001359 u;
1360
cristy117ff172010-08-15 21:35:32 +00001361 ssize_t
1362 v;
1363
cristy175653e2011-07-10 23:13:34 +00001364 pixel=bias;
cristy3f829a22011-07-10 21:40:04 +00001365 k=normal_kernel;
cristy175653e2011-07-10 23:13:34 +00001366 kernel_pixels=p;
cristy72ab5002011-07-07 18:33:45 +00001367 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) == 0) ||
1368 (image->matte == MagickFalse))
cristyfccdab92009-11-30 16:43:57 +00001369 {
cristybb503372010-05-27 20:51:26 +00001370 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001371 {
cristybb503372010-05-27 20:51:26 +00001372 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001373 {
cristy175653e2011-07-10 23:13:34 +00001374 pixel.red+=(*k)*GetPixelRed(image,kernel_pixels+u*
1375 GetPixelComponents(image));
1376 pixel.green+=(*k)*GetPixelGreen(image,kernel_pixels+u*
1377 GetPixelComponents(image));
1378 pixel.blue+=(*k)*GetPixelBlue(image,kernel_pixels+u*
1379 GetPixelComponents(image));
1380 if (image->colorspace == CMYKColorspace)
1381 pixel.black+=(*k)*GetPixelBlack(image,kernel_pixels+u*
1382 GetPixelComponents(image));
cristyfccdab92009-11-30 16:43:57 +00001383 k++;
1384 }
cristy175653e2011-07-10 23:13:34 +00001385 kernel_pixels+=(image->columns+width)*GetPixelComponents(image);
cristyfccdab92009-11-30 16:43:57 +00001386 }
cristy175653e2011-07-10 23:13:34 +00001387 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
1388 SetPixelRed(convolve_image,ClampToQuantum(pixel.red),q);
1389 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
1390 SetPixelGreen(convolve_image,ClampToQuantum(pixel.green),q);
1391 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
1392 SetPixelBlue(convolve_image,ClampToQuantum(pixel.blue),q);
1393 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
1394 (image->colorspace == CMYKColorspace))
1395 SetPixelBlack(convolve_image,ClampToQuantum(pixel.black),q);
1396 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
1397 {
1398 k=normal_kernel;
1399 kernel_pixels=p;
1400 for (v=0; v < (ssize_t) width; v++)
1401 {
1402 for (u=0; u < (ssize_t) width; u++)
1403 {
1404 pixel.alpha+=(*k)*GetPixelAlpha(image,kernel_pixels+u*
1405 GetPixelComponents(image));
1406 k++;
1407 }
1408 kernel_pixels+=(image->columns+width)*GetPixelComponents(image);
1409 }
1410 SetPixelAlpha(convolve_image,ClampToQuantum(pixel.alpha),q);
1411 }
cristyfccdab92009-11-30 16:43:57 +00001412 }
1413 else
1414 {
1415 MagickRealType
cristy175653e2011-07-10 23:13:34 +00001416 alpha,
cristyfccdab92009-11-30 16:43:57 +00001417 gamma;
1418
1419 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00001420 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001421 {
cristybb503372010-05-27 20:51:26 +00001422 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001423 {
cristy4c08aed2011-07-01 19:47:50 +00001424 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,
cristy175653e2011-07-10 23:13:34 +00001425 kernel_pixels+u*GetPixelComponents(image)));
1426 pixel.red+=(*k)*alpha*GetPixelRed(image,kernel_pixels+u*
1427 GetPixelComponents(image));
1428 pixel.green+=(*k)*alpha*GetPixelGreen(image,kernel_pixels+u*
1429 GetPixelComponents(image));
1430 pixel.blue+=(*k)*alpha*GetPixelBlue(image,kernel_pixels+u*
1431 GetPixelComponents(image));
1432 if (image->colorspace == CMYKColorspace)
1433 pixel.black+=(*k)*alpha*GetPixelBlack(image,kernel_pixels+u*
1434 GetPixelComponents(image));
cristyfccdab92009-11-30 16:43:57 +00001435 gamma+=(*k)*alpha;
1436 k++;
1437 }
cristy175653e2011-07-10 23:13:34 +00001438 kernel_pixels+=(image->columns+width)*GetPixelComponents(image);
cristyfccdab92009-11-30 16:43:57 +00001439 }
1440 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy175653e2011-07-10 23:13:34 +00001441 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
1442 SetPixelRed(convolve_image,ClampToQuantum(gamma*pixel.red),q);
1443 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
1444 SetPixelGreen(convolve_image,ClampToQuantum(gamma*pixel.green),q);
1445 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
1446 SetPixelBlue(convolve_image,ClampToQuantum(gamma*pixel.blue),q);
1447 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
1448 (image->colorspace == CMYKColorspace))
1449 SetPixelBlack(convolve_image,ClampToQuantum(gamma*pixel.black),q);
1450 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
1451 {
1452 k=normal_kernel;
1453 kernel_pixels=p;
1454 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001455 {
cristy175653e2011-07-10 23:13:34 +00001456 for (u=0; u < (ssize_t) width; u++)
1457 {
1458 pixel.alpha+=(*k)*GetPixelAlpha(image,kernel_pixels+u);
1459 k++;
1460 }
1461 kernel_pixels+=(image->columns+width)*GetPixelComponents(image);
cristyfccdab92009-11-30 16:43:57 +00001462 }
cristy175653e2011-07-10 23:13:34 +00001463 SetPixelAlpha(convolve_image,ClampToQuantum(pixel.alpha),q);
1464 }
cristyfccdab92009-11-30 16:43:57 +00001465 }
cristy175653e2011-07-10 23:13:34 +00001466 p+=GetPixelComponents(image);
1467 q+=GetPixelComponents(convolve_image);
cristyfccdab92009-11-30 16:43:57 +00001468 }
cristy175653e2011-07-10 23:13:34 +00001469 sync=SyncCacheViewAuthenticPixels(convolve_view,exception);
1470 if (sync == MagickFalse)
cristyfccdab92009-11-30 16:43:57 +00001471 status=MagickFalse;
1472 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1473 {
1474 MagickBooleanType
1475 proceed;
1476
1477#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00001478 #pragma omp critical (MagickCore_ConvolveImage)
cristyfccdab92009-11-30 16:43:57 +00001479#endif
1480 proceed=SetImageProgress(image,ConvolveImageTag,progress++,image->rows);
1481 if (proceed == MagickFalse)
1482 status=MagickFalse;
1483 }
1484 }
1485 convolve_image->type=image->type;
1486 convolve_view=DestroyCacheView(convolve_view);
1487 image_view=DestroyCacheView(image_view);
1488 normal_kernel=(double *) RelinquishMagickMemory(normal_kernel);
1489 if (status == MagickFalse)
1490 convolve_image=DestroyImage(convolve_image);
1491 return(convolve_image);
1492}
1493
1494/*
1495%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1496% %
1497% %
1498% %
cristy3ed852e2009-09-05 21:47:34 +00001499% D e s p e c k l e I m a g e %
1500% %
1501% %
1502% %
1503%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1504%
1505% DespeckleImage() reduces the speckle noise in an image while perserving the
1506% edges of the original image.
1507%
1508% The format of the DespeckleImage method is:
1509%
1510% Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1511%
1512% A description of each parameter follows:
1513%
1514% o image: the image.
1515%
1516% o exception: return any errors or warnings in this structure.
1517%
1518*/
1519
cristybb503372010-05-27 20:51:26 +00001520static void Hull(const ssize_t x_offset,const ssize_t y_offset,
1521 const size_t columns,const size_t rows,Quantum *f,Quantum *g,
cristy3ed852e2009-09-05 21:47:34 +00001522 const int polarity)
1523{
cristy3ed852e2009-09-05 21:47:34 +00001524 MagickRealType
1525 v;
1526
cristy3ed852e2009-09-05 21:47:34 +00001527 register Quantum
1528 *p,
1529 *q,
1530 *r,
1531 *s;
1532
cristy117ff172010-08-15 21:35:32 +00001533 register ssize_t
1534 x;
1535
1536 ssize_t
1537 y;
1538
cristy3ed852e2009-09-05 21:47:34 +00001539 assert(f != (Quantum *) NULL);
1540 assert(g != (Quantum *) NULL);
1541 p=f+(columns+2);
1542 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001543 r=p+(y_offset*((ssize_t) columns+2)+x_offset);
1544 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001545 {
1546 p++;
1547 q++;
1548 r++;
1549 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001550 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001551 {
1552 v=(MagickRealType) (*p);
1553 if ((MagickRealType) *r >= (v+(MagickRealType) ScaleCharToQuantum(2)))
1554 v+=ScaleCharToQuantum(1);
1555 *q=(Quantum) v;
1556 p++;
1557 q++;
1558 r++;
1559 }
1560 else
cristybb503372010-05-27 20:51:26 +00001561 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001562 {
1563 v=(MagickRealType) (*p);
1564 if ((MagickRealType) *r <= (v-(MagickRealType) ScaleCharToQuantum(2)))
cristybb503372010-05-27 20:51:26 +00001565 v-=(ssize_t) ScaleCharToQuantum(1);
cristy3ed852e2009-09-05 21:47:34 +00001566 *q=(Quantum) v;
1567 p++;
1568 q++;
1569 r++;
1570 }
1571 p++;
1572 q++;
1573 r++;
1574 }
1575 p=f+(columns+2);
1576 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001577 r=q+(y_offset*((ssize_t) columns+2)+x_offset);
1578 s=q-(y_offset*((ssize_t) columns+2)+x_offset);
1579 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001580 {
1581 p++;
1582 q++;
1583 r++;
1584 s++;
1585 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001586 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001587 {
1588 v=(MagickRealType) (*q);
1589 if (((MagickRealType) *s >=
1590 (v+(MagickRealType) ScaleCharToQuantum(2))) &&
1591 ((MagickRealType) *r > v))
1592 v+=ScaleCharToQuantum(1);
1593 *p=(Quantum) v;
1594 p++;
1595 q++;
1596 r++;
1597 s++;
1598 }
1599 else
cristybb503372010-05-27 20:51:26 +00001600 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001601 {
1602 v=(MagickRealType) (*q);
1603 if (((MagickRealType) *s <=
1604 (v-(MagickRealType) ScaleCharToQuantum(2))) &&
1605 ((MagickRealType) *r < v))
1606 v-=(MagickRealType) ScaleCharToQuantum(1);
1607 *p=(Quantum) v;
1608 p++;
1609 q++;
1610 r++;
1611 s++;
1612 }
1613 p++;
1614 q++;
1615 r++;
1616 s++;
1617 }
1618}
1619
1620MagickExport Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1621{
1622#define DespeckleImageTag "Despeckle/Image"
1623
cristy2407fc22009-09-11 00:55:25 +00001624 CacheView
1625 *despeckle_view,
1626 *image_view;
1627
cristy3ed852e2009-09-05 21:47:34 +00001628 Image
1629 *despeckle_image;
1630
cristy3ed852e2009-09-05 21:47:34 +00001631 MagickBooleanType
1632 status;
1633
cristya58c3172011-02-19 19:23:11 +00001634 register ssize_t
1635 i;
1636
cristy3ed852e2009-09-05 21:47:34 +00001637 Quantum
cristy65b9f392011-02-22 14:22:54 +00001638 *restrict buffers,
1639 *restrict pixels;
cristy3ed852e2009-09-05 21:47:34 +00001640
1641 size_t
cristya58c3172011-02-19 19:23:11 +00001642 length,
1643 number_channels;
cristy117ff172010-08-15 21:35:32 +00001644
cristybb503372010-05-27 20:51:26 +00001645 static const ssize_t
cristy691a29e2009-09-11 00:44:10 +00001646 X[4] = {0, 1, 1,-1},
1647 Y[4] = {1, 0, 1, 1};
cristy3ed852e2009-09-05 21:47:34 +00001648
cristy3ed852e2009-09-05 21:47:34 +00001649 /*
1650 Allocate despeckled image.
1651 */
1652 assert(image != (const Image *) NULL);
1653 assert(image->signature == MagickSignature);
1654 if (image->debug != MagickFalse)
1655 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1656 assert(exception != (ExceptionInfo *) NULL);
1657 assert(exception->signature == MagickSignature);
1658 despeckle_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1659 exception);
1660 if (despeckle_image == (Image *) NULL)
1661 return((Image *) NULL);
1662 if (SetImageStorageClass(despeckle_image,DirectClass) == MagickFalse)
1663 {
1664 InheritException(exception,&despeckle_image->exception);
1665 despeckle_image=DestroyImage(despeckle_image);
1666 return((Image *) NULL);
1667 }
1668 /*
1669 Allocate image buffers.
1670 */
1671 length=(size_t) ((image->columns+2)*(image->rows+2));
cristy65b9f392011-02-22 14:22:54 +00001672 pixels=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1673 buffers=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1674 if ((pixels == (Quantum *) NULL) || (buffers == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001675 {
cristy65b9f392011-02-22 14:22:54 +00001676 if (buffers != (Quantum *) NULL)
1677 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1678 if (pixels != (Quantum *) NULL)
1679 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001680 despeckle_image=DestroyImage(despeckle_image);
1681 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1682 }
1683 /*
1684 Reduce speckle in the image.
1685 */
1686 status=MagickTrue;
cristy109695a2011-02-19 19:38:14 +00001687 number_channels=(size_t) (image->colorspace == CMYKColorspace ? 5 : 4);
cristy3ed852e2009-09-05 21:47:34 +00001688 image_view=AcquireCacheView(image);
1689 despeckle_view=AcquireCacheView(despeckle_image);
cristy8df3d002011-02-19 19:40:59 +00001690 for (i=0; i < (ssize_t) number_channels; i++)
cristy3ed852e2009-09-05 21:47:34 +00001691 {
cristy3ed852e2009-09-05 21:47:34 +00001692 register Quantum
1693 *buffer,
1694 *pixel;
1695
cristyc1488b52011-02-19 18:54:15 +00001696 register ssize_t
cristya58c3172011-02-19 19:23:11 +00001697 k,
cristyc1488b52011-02-19 18:54:15 +00001698 x;
1699
cristy117ff172010-08-15 21:35:32 +00001700 ssize_t
1701 j,
1702 y;
1703
cristy3ed852e2009-09-05 21:47:34 +00001704 if (status == MagickFalse)
1705 continue;
cristy65b9f392011-02-22 14:22:54 +00001706 pixel=pixels;
cristy3ed852e2009-09-05 21:47:34 +00001707 (void) ResetMagickMemory(pixel,0,length*sizeof(*pixel));
cristy65b9f392011-02-22 14:22:54 +00001708 buffer=buffers;
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 {
cristy4c08aed2011-07-01 19:47:50 +00001712 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001713 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001714
1715 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001716 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001717 break;
1718 j++;
cristybb503372010-05-27 20:51:26 +00001719 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001720 {
cristya58c3172011-02-19 19:23:11 +00001721 switch (i)
cristy3ed852e2009-09-05 21:47:34 +00001722 {
cristy4c08aed2011-07-01 19:47:50 +00001723 case 0: pixel[j]=GetPixelRed(image,p); break;
1724 case 1: pixel[j]=GetPixelGreen(image,p); break;
1725 case 2: pixel[j]=GetPixelBlue(image,p); break;
1726 case 3: pixel[j]=GetPixelAlpha(image,p); break;
1727 case 4: pixel[j]=GetPixelBlack(image,p); break;
cristy3ed852e2009-09-05 21:47:34 +00001728 default: break;
1729 }
cristydcfc1ad2011-07-07 16:25:41 +00001730 p+=GetPixelComponents(image);
cristy3ed852e2009-09-05 21:47:34 +00001731 j++;
1732 }
1733 j++;
1734 }
cristy3ed852e2009-09-05 21:47:34 +00001735 (void) ResetMagickMemory(buffer,0,length*sizeof(*buffer));
cristya58c3172011-02-19 19:23:11 +00001736 for (k=0; k < 4; k++)
cristy3ed852e2009-09-05 21:47:34 +00001737 {
cristya58c3172011-02-19 19:23:11 +00001738 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,1);
1739 Hull(-X[k],-Y[k],image->columns,image->rows,pixel,buffer,1);
1740 Hull(-X[k],-Y[k],image->columns,image->rows,pixel,buffer,-1);
1741 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,-1);
cristy3ed852e2009-09-05 21:47:34 +00001742 }
cristybb503372010-05-27 20:51:26 +00001743 j=(ssize_t) image->columns+2;
1744 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001745 {
1746 MagickBooleanType
1747 sync;
1748
cristy4c08aed2011-07-01 19:47:50 +00001749 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001750 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001751
1752 q=GetCacheViewAuthenticPixels(despeckle_view,0,y,despeckle_image->columns,
1753 1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001754 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001755 break;
1756 j++;
cristybb503372010-05-27 20:51:26 +00001757 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001758 {
cristya58c3172011-02-19 19:23:11 +00001759 switch (i)
cristy3ed852e2009-09-05 21:47:34 +00001760 {
cristy4c08aed2011-07-01 19:47:50 +00001761 case 0: SetPixelRed(despeckle_image,pixel[j],q); break;
1762 case 1: SetPixelGreen(despeckle_image,pixel[j],q); break;
1763 case 2: SetPixelBlue(despeckle_image,pixel[j],q); break;
1764 case 3: SetPixelAlpha(despeckle_image,pixel[j],q); break;
1765 case 4: SetPixelBlack(despeckle_image,pixel[j],q); break;
cristy3ed852e2009-09-05 21:47:34 +00001766 default: break;
1767 }
cristydcfc1ad2011-07-07 16:25:41 +00001768 q+=GetPixelComponents(despeckle_image);
cristy3ed852e2009-09-05 21:47:34 +00001769 j++;
1770 }
1771 sync=SyncCacheViewAuthenticPixels(despeckle_view,exception);
1772 if (sync == MagickFalse)
1773 {
1774 status=MagickFalse;
1775 break;
1776 }
1777 j++;
1778 }
1779 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1780 {
1781 MagickBooleanType
1782 proceed;
1783
cristya58c3172011-02-19 19:23:11 +00001784 proceed=SetImageProgress(image,DespeckleImageTag,(MagickOffsetType) i,
1785 number_channels);
cristy3ed852e2009-09-05 21:47:34 +00001786 if (proceed == MagickFalse)
1787 status=MagickFalse;
1788 }
1789 }
1790 despeckle_view=DestroyCacheView(despeckle_view);
1791 image_view=DestroyCacheView(image_view);
cristy65b9f392011-02-22 14:22:54 +00001792 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1793 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001794 despeckle_image->type=image->type;
1795 if (status == MagickFalse)
1796 despeckle_image=DestroyImage(despeckle_image);
1797 return(despeckle_image);
1798}
1799
1800/*
1801%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1802% %
1803% %
1804% %
1805% E d g e I m a g e %
1806% %
1807% %
1808% %
1809%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1810%
1811% EdgeImage() finds edges in an image. Radius defines the radius of the
1812% convolution filter. Use a radius of 0 and EdgeImage() selects a suitable
1813% radius for you.
1814%
1815% The format of the EdgeImage method is:
1816%
1817% Image *EdgeImage(const Image *image,const double radius,
1818% ExceptionInfo *exception)
1819%
1820% A description of each parameter follows:
1821%
1822% o image: the image.
1823%
1824% o radius: the radius of the pixel neighborhood.
1825%
1826% o exception: return any errors or warnings in this structure.
1827%
1828*/
1829MagickExport Image *EdgeImage(const Image *image,const double radius,
1830 ExceptionInfo *exception)
1831{
1832 Image
1833 *edge_image;
1834
1835 double
1836 *kernel;
1837
cristybb503372010-05-27 20:51:26 +00001838 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001839 i;
1840
cristybb503372010-05-27 20:51:26 +00001841 size_t
cristy3ed852e2009-09-05 21:47:34 +00001842 width;
1843
1844 assert(image != (const Image *) NULL);
1845 assert(image->signature == MagickSignature);
1846 if (image->debug != MagickFalse)
1847 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1848 assert(exception != (ExceptionInfo *) NULL);
1849 assert(exception->signature == MagickSignature);
1850 width=GetOptimalKernelWidth1D(radius,0.5);
1851 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
1852 if (kernel == (double *) NULL)
1853 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00001854 for (i=0; i < (ssize_t) (width*width); i++)
cristy3ed852e2009-09-05 21:47:34 +00001855 kernel[i]=(-1.0);
1856 kernel[i/2]=(double) (width*width-1.0);
1857 edge_image=ConvolveImage(image,width,kernel,exception);
1858 kernel=(double *) RelinquishMagickMemory(kernel);
1859 return(edge_image);
1860}
1861
1862/*
1863%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1864% %
1865% %
1866% %
1867% E m b o s s I m a g e %
1868% %
1869% %
1870% %
1871%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1872%
1873% EmbossImage() returns a grayscale image with a three-dimensional effect.
1874% We convolve the image with a Gaussian operator of the given radius and
1875% standard deviation (sigma). For reasonable results, radius should be
1876% larger than sigma. Use a radius of 0 and Emboss() selects a suitable
1877% radius for you.
1878%
1879% The format of the EmbossImage method is:
1880%
1881% Image *EmbossImage(const Image *image,const double radius,
1882% const double sigma,ExceptionInfo *exception)
1883%
1884% A description of each parameter follows:
1885%
1886% o image: the image.
1887%
1888% o radius: the radius of the pixel neighborhood.
1889%
1890% o sigma: the standard deviation of the Gaussian, in pixels.
1891%
1892% o exception: return any errors or warnings in this structure.
1893%
1894*/
1895MagickExport Image *EmbossImage(const Image *image,const double radius,
1896 const double sigma,ExceptionInfo *exception)
1897{
1898 double
1899 *kernel;
1900
1901 Image
1902 *emboss_image;
1903
cristybb503372010-05-27 20:51:26 +00001904 register ssize_t
cristy47e00502009-12-17 19:19:57 +00001905 i;
1906
cristybb503372010-05-27 20:51:26 +00001907 size_t
cristy3ed852e2009-09-05 21:47:34 +00001908 width;
1909
cristy117ff172010-08-15 21:35:32 +00001910 ssize_t
1911 j,
1912 k,
1913 u,
1914 v;
1915
cristy3ed852e2009-09-05 21:47:34 +00001916 assert(image != (Image *) NULL);
1917 assert(image->signature == MagickSignature);
1918 if (image->debug != MagickFalse)
1919 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1920 assert(exception != (ExceptionInfo *) NULL);
1921 assert(exception->signature == MagickSignature);
1922 width=GetOptimalKernelWidth2D(radius,sigma);
1923 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
1924 if (kernel == (double *) NULL)
1925 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00001926 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +00001927 k=j;
1928 i=0;
1929 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00001930 {
cristy47e00502009-12-17 19:19:57 +00001931 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00001932 {
cristy4205a3c2010-09-12 20:19:59 +00001933 kernel[i]=(double) (((u < 0) || (v < 0) ? -8.0 : 8.0)*
cristy47e00502009-12-17 19:19:57 +00001934 exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
cristy4205a3c2010-09-12 20:19:59 +00001935 (2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +00001936 if (u != k)
cristy3ed852e2009-09-05 21:47:34 +00001937 kernel[i]=0.0;
1938 i++;
1939 }
cristy47e00502009-12-17 19:19:57 +00001940 k--;
cristy3ed852e2009-09-05 21:47:34 +00001941 }
1942 emboss_image=ConvolveImage(image,width,kernel,exception);
1943 if (emboss_image != (Image *) NULL)
1944 (void) EqualizeImage(emboss_image);
1945 kernel=(double *) RelinquishMagickMemory(kernel);
1946 return(emboss_image);
1947}
1948
1949/*
1950%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1951% %
1952% %
1953% %
cristy56a9e512010-01-06 18:18:55 +00001954% F i l t e r I m a g e %
1955% %
1956% %
1957% %
1958%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1959%
1960% FilterImage() applies a custom convolution kernel to the image.
1961%
1962% The format of the FilterImage method is:
1963%
cristy2be15382010-01-21 02:38:03 +00001964% Image *FilterImage(const Image *image,const KernelInfo *kernel,
cristy56a9e512010-01-06 18:18:55 +00001965% ExceptionInfo *exception)
cristy56a9e512010-01-06 18:18:55 +00001966%
1967% A description of each parameter follows:
1968%
1969% o image: the image.
1970%
cristy56a9e512010-01-06 18:18:55 +00001971% o kernel: the filtering kernel.
1972%
1973% o exception: return any errors or warnings in this structure.
1974%
1975*/
cristyf4ad9df2011-07-08 16:49:03 +00001976MagickExport Image *FilterImage(const Image *image,
1977 const KernelInfo *kernel,ExceptionInfo *exception)
cristy56a9e512010-01-06 18:18:55 +00001978{
1979#define FilterImageTag "Filter/Image"
1980
1981 CacheView
1982 *filter_view,
1983 *image_view;
1984
cristy56a9e512010-01-06 18:18:55 +00001985 Image
1986 *filter_image;
1987
cristy56a9e512010-01-06 18:18:55 +00001988 MagickBooleanType
1989 status;
1990
cristybb503372010-05-27 20:51:26 +00001991 MagickOffsetType
1992 progress;
1993
cristy4c08aed2011-07-01 19:47:50 +00001994 PixelInfo
cristy56a9e512010-01-06 18:18:55 +00001995 bias;
1996
cristybb503372010-05-27 20:51:26 +00001997 ssize_t
1998 y;
1999
cristy56a9e512010-01-06 18:18:55 +00002000 /*
2001 Initialize filter image attributes.
2002 */
2003 assert(image != (Image *) NULL);
2004 assert(image->signature == MagickSignature);
2005 if (image->debug != MagickFalse)
2006 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2007 assert(exception != (ExceptionInfo *) NULL);
2008 assert(exception->signature == MagickSignature);
2009 if ((kernel->width % 2) == 0)
2010 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
2011 filter_image=CloneImage(image,0,0,MagickTrue,exception);
2012 if (filter_image == (Image *) NULL)
2013 return((Image *) NULL);
2014 if (SetImageStorageClass(filter_image,DirectClass) == MagickFalse)
2015 {
2016 InheritException(exception,&filter_image->exception);
2017 filter_image=DestroyImage(filter_image);
2018 return((Image *) NULL);
2019 }
2020 if (image->debug != MagickFalse)
2021 {
2022 char
2023 format[MaxTextExtent],
2024 *message;
2025
cristy117ff172010-08-15 21:35:32 +00002026 register const double
2027 *k;
2028
cristybb503372010-05-27 20:51:26 +00002029 ssize_t
cristy56a9e512010-01-06 18:18:55 +00002030 u,
2031 v;
2032
cristy56a9e512010-01-06 18:18:55 +00002033 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00002034 " FilterImage with %.20gx%.20g kernel:",(double) kernel->width,(double)
2035 kernel->height);
cristy56a9e512010-01-06 18:18:55 +00002036 message=AcquireString("");
2037 k=kernel->values;
cristybb503372010-05-27 20:51:26 +00002038 for (v=0; v < (ssize_t) kernel->height; v++)
cristy56a9e512010-01-06 18:18:55 +00002039 {
2040 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00002041 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy56a9e512010-01-06 18:18:55 +00002042 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00002043 for (u=0; u < (ssize_t) kernel->width; u++)
cristy56a9e512010-01-06 18:18:55 +00002044 {
cristyb51dff52011-05-19 16:55:47 +00002045 (void) FormatLocaleString(format,MaxTextExtent,"%g ",*k++);
cristy56a9e512010-01-06 18:18:55 +00002046 (void) ConcatenateString(&message,format);
2047 }
2048 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
2049 }
2050 message=DestroyString(message);
2051 }
cristy36826ab2010-03-06 01:29:30 +00002052 status=AccelerateConvolveImage(image,kernel,filter_image,exception);
cristyd43a46b2010-01-21 02:13:41 +00002053 if (status == MagickTrue)
2054 return(filter_image);
cristy56a9e512010-01-06 18:18:55 +00002055 /*
2056 Filter image.
2057 */
2058 status=MagickTrue;
2059 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00002060 GetPixelInfo(image,&bias);
2061 SetPixelInfoBias(image,&bias);
cristy56a9e512010-01-06 18:18:55 +00002062 image_view=AcquireCacheView(image);
2063 filter_view=AcquireCacheView(filter_image);
2064#if defined(MAGICKCORE_OPENMP_SUPPORT)
2065 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2066#endif
cristybb503372010-05-27 20:51:26 +00002067 for (y=0; y < (ssize_t) image->rows; y++)
cristy56a9e512010-01-06 18:18:55 +00002068 {
2069 MagickBooleanType
2070 sync;
2071
cristy4c08aed2011-07-01 19:47:50 +00002072 register const Quantum
cristy56a9e512010-01-06 18:18:55 +00002073 *restrict p;
2074
cristy4c08aed2011-07-01 19:47:50 +00002075 register Quantum
cristy56a9e512010-01-06 18:18:55 +00002076 *restrict q;
2077
cristy117ff172010-08-15 21:35:32 +00002078 register ssize_t
2079 x;
2080
cristy56a9e512010-01-06 18:18:55 +00002081 if (status == MagickFalse)
2082 continue;
cristybb503372010-05-27 20:51:26 +00002083 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) kernel->width/2L),
cristy117ff172010-08-15 21:35:32 +00002084 y-(ssize_t) (kernel->height/2L),image->columns+kernel->width,
2085 kernel->height,exception);
cristy56a9e512010-01-06 18:18:55 +00002086 q=GetCacheViewAuthenticPixels(filter_view,0,y,filter_image->columns,1,
2087 exception);
cristy4c08aed2011-07-01 19:47:50 +00002088 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy56a9e512010-01-06 18:18:55 +00002089 {
2090 status=MagickFalse;
2091 continue;
2092 }
cristybb503372010-05-27 20:51:26 +00002093 for (x=0; x < (ssize_t) image->columns; x++)
cristy56a9e512010-01-06 18:18:55 +00002094 {
cristy4c08aed2011-07-01 19:47:50 +00002095 PixelInfo
cristy56a9e512010-01-06 18:18:55 +00002096 pixel;
2097
2098 register const double
2099 *restrict k;
2100
cristy4c08aed2011-07-01 19:47:50 +00002101 register const Quantum
cristy56a9e512010-01-06 18:18:55 +00002102 *restrict kernel_pixels;
2103
cristybb503372010-05-27 20:51:26 +00002104 register ssize_t
cristy56a9e512010-01-06 18:18:55 +00002105 u;
2106
cristy117ff172010-08-15 21:35:32 +00002107 ssize_t
2108 v;
2109
cristy56a9e512010-01-06 18:18:55 +00002110 pixel=bias;
cristy36826ab2010-03-06 01:29:30 +00002111 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002112 kernel_pixels=p;
cristy5ce8df82011-07-07 14:52:23 +00002113 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) == 0) ||
2114 (image->matte == MagickFalse))
cristy56a9e512010-01-06 18:18:55 +00002115 {
cristybb503372010-05-27 20:51:26 +00002116 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002117 {
cristybb503372010-05-27 20:51:26 +00002118 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002119 {
cristy4c08aed2011-07-01 19:47:50 +00002120 pixel.red+=(*k)*GetPixelRed(image,kernel_pixels+u*
cristydcfc1ad2011-07-07 16:25:41 +00002121 GetPixelComponents(image));
cristy4c08aed2011-07-01 19:47:50 +00002122 pixel.green+=(*k)*GetPixelGreen(image,kernel_pixels+u*
cristydcfc1ad2011-07-07 16:25:41 +00002123 GetPixelComponents(image));
cristy4c08aed2011-07-01 19:47:50 +00002124 pixel.blue+=(*k)*GetPixelBlue(image,kernel_pixels+u*
cristydcfc1ad2011-07-07 16:25:41 +00002125 GetPixelComponents(image));
cristy4c08aed2011-07-01 19:47:50 +00002126 if (image->colorspace == CMYKColorspace)
2127 pixel.black+=(*k)*GetPixelBlack(image,kernel_pixels+u*
cristydcfc1ad2011-07-07 16:25:41 +00002128 GetPixelComponents(image));
cristy56a9e512010-01-06 18:18:55 +00002129 k++;
2130 }
cristy4c08aed2011-07-01 19:47:50 +00002131 kernel_pixels+=(image->columns+kernel->width)*
cristydcfc1ad2011-07-07 16:25:41 +00002132 GetPixelComponents(image);
cristy56a9e512010-01-06 18:18:55 +00002133 }
cristy2b9582a2011-07-04 17:38:56 +00002134 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002135 SetPixelRed(filter_image,ClampToQuantum(pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00002136 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002137 SetPixelGreen(filter_image,ClampToQuantum(pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00002138 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002139 SetPixelBlue(filter_image,ClampToQuantum(pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00002140 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002141 (image->colorspace == CMYKColorspace))
2142 SetPixelBlack(filter_image,ClampToQuantum(pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00002143 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy56a9e512010-01-06 18:18:55 +00002144 {
cristy36826ab2010-03-06 01:29:30 +00002145 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002146 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00002147 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002148 {
cristybb503372010-05-27 20:51:26 +00002149 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002150 {
cristy4c08aed2011-07-01 19:47:50 +00002151 pixel.alpha+=(*k)*GetPixelRed(image,kernel_pixels+u*
cristydcfc1ad2011-07-07 16:25:41 +00002152 GetPixelComponents(image));
cristy56a9e512010-01-06 18:18:55 +00002153 k++;
2154 }
cristy4c08aed2011-07-01 19:47:50 +00002155 kernel_pixels+=(image->columns+kernel->width)*
cristydcfc1ad2011-07-07 16:25:41 +00002156 GetPixelComponents(image);
cristy56a9e512010-01-06 18:18:55 +00002157 }
cristy4c08aed2011-07-01 19:47:50 +00002158 SetPixelAlpha(filter_image,ClampToQuantum(pixel.alpha),q);
cristy56a9e512010-01-06 18:18:55 +00002159 }
2160 }
2161 else
2162 {
2163 MagickRealType
2164 alpha,
2165 gamma;
2166
2167 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00002168 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002169 {
cristybb503372010-05-27 20:51:26 +00002170 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002171 {
cristy4c08aed2011-07-01 19:47:50 +00002172 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,
cristydcfc1ad2011-07-07 16:25:41 +00002173 kernel_pixels+u*GetPixelComponents(image)));
cristy4c08aed2011-07-01 19:47:50 +00002174 pixel.red+=(*k)*alpha*GetPixelRed(image,kernel_pixels+u*
cristydcfc1ad2011-07-07 16:25:41 +00002175 GetPixelComponents(image));
cristy4c08aed2011-07-01 19:47:50 +00002176 pixel.green+=(*k)*alpha*GetPixelGreen(image,kernel_pixels+u*
cristydcfc1ad2011-07-07 16:25:41 +00002177 GetPixelComponents(image));
cristy4c08aed2011-07-01 19:47:50 +00002178 pixel.blue+=(*k)*alpha*GetPixelBlue(image,kernel_pixels+u*
cristydcfc1ad2011-07-07 16:25:41 +00002179 GetPixelComponents(image));
cristy4c08aed2011-07-01 19:47:50 +00002180 if (image->colorspace == CMYKColorspace)
2181 pixel.black+=(*k)*alpha*GetPixelBlack(image,kernel_pixels+u*
cristydcfc1ad2011-07-07 16:25:41 +00002182 GetPixelComponents(image));
cristy56a9e512010-01-06 18:18:55 +00002183 gamma+=(*k)*alpha;
2184 k++;
2185 }
cristy5ce8df82011-07-07 14:52:23 +00002186 kernel_pixels+=(image->columns+kernel->width)*
cristydcfc1ad2011-07-07 16:25:41 +00002187 GetPixelComponents(image);
cristy56a9e512010-01-06 18:18:55 +00002188 }
2189 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy2b9582a2011-07-04 17:38:56 +00002190 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002191 SetPixelRed(filter_image,ClampToQuantum(gamma*pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00002192 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002193 SetPixelGreen(filter_image,ClampToQuantum(gamma*pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00002194 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002195 SetPixelBlue(filter_image,ClampToQuantum(gamma*pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00002196 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy56a9e512010-01-06 18:18:55 +00002197 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002198 SetPixelBlack(filter_image,ClampToQuantum(gamma*pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00002199 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy56a9e512010-01-06 18:18:55 +00002200 {
cristy36826ab2010-03-06 01:29:30 +00002201 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002202 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00002203 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002204 {
cristybb503372010-05-27 20:51:26 +00002205 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002206 {
cristy4c08aed2011-07-01 19:47:50 +00002207 pixel.alpha+=(*k)*GetPixelAlpha(image,kernel_pixels+u*
cristydcfc1ad2011-07-07 16:25:41 +00002208 GetPixelComponents(image));
cristy56a9e512010-01-06 18:18:55 +00002209 k++;
2210 }
cristy4c08aed2011-07-01 19:47:50 +00002211 kernel_pixels+=(image->columns+kernel->width)*
cristydcfc1ad2011-07-07 16:25:41 +00002212 GetPixelComponents(image);
cristy56a9e512010-01-06 18:18:55 +00002213 }
cristy4c08aed2011-07-01 19:47:50 +00002214 SetPixelAlpha(filter_image,ClampToQuantum(pixel.alpha),q);
cristy56a9e512010-01-06 18:18:55 +00002215 }
2216 }
cristydcfc1ad2011-07-07 16:25:41 +00002217 p+=GetPixelComponents(image);
2218 q+=GetPixelComponents(filter_image);
cristy56a9e512010-01-06 18:18:55 +00002219 }
2220 sync=SyncCacheViewAuthenticPixels(filter_view,exception);
2221 if (sync == MagickFalse)
2222 status=MagickFalse;
2223 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2224 {
2225 MagickBooleanType
2226 proceed;
2227
2228#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00002229 #pragma omp critical (MagickCore_FilterImage)
cristy56a9e512010-01-06 18:18:55 +00002230#endif
2231 proceed=SetImageProgress(image,FilterImageTag,progress++,image->rows);
2232 if (proceed == MagickFalse)
2233 status=MagickFalse;
2234 }
2235 }
2236 filter_image->type=image->type;
2237 filter_view=DestroyCacheView(filter_view);
2238 image_view=DestroyCacheView(image_view);
cristy56a9e512010-01-06 18:18:55 +00002239 if (status == MagickFalse)
2240 filter_image=DestroyImage(filter_image);
2241 return(filter_image);
2242}
2243
2244/*
2245%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2246% %
2247% %
2248% %
cristy3ed852e2009-09-05 21:47:34 +00002249% G a u s s i a n B l u r I m a g e %
2250% %
2251% %
2252% %
2253%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2254%
2255% GaussianBlurImage() blurs an image. We convolve the image with a
2256% Gaussian operator of the given radius and standard deviation (sigma).
2257% For reasonable results, the radius should be larger than sigma. Use a
2258% radius of 0 and GaussianBlurImage() selects a suitable radius for you
2259%
2260% The format of the GaussianBlurImage method is:
2261%
2262% Image *GaussianBlurImage(const Image *image,onst double radius,
2263% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002264%
2265% A description of each parameter follows:
2266%
2267% o image: the image.
2268%
cristy3ed852e2009-09-05 21:47:34 +00002269% o radius: the radius of the Gaussian, in pixels, not counting the center
2270% pixel.
2271%
2272% o sigma: the standard deviation of the Gaussian, in pixels.
2273%
2274% o exception: return any errors or warnings in this structure.
2275%
2276*/
cristyf4ad9df2011-07-08 16:49:03 +00002277MagickExport Image *GaussianBlurImage(const Image *image,
2278 const double radius,const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002279{
2280 double
2281 *kernel;
2282
2283 Image
2284 *blur_image;
2285
cristybb503372010-05-27 20:51:26 +00002286 register ssize_t
cristy47e00502009-12-17 19:19:57 +00002287 i;
2288
cristybb503372010-05-27 20:51:26 +00002289 size_t
cristy3ed852e2009-09-05 21:47:34 +00002290 width;
2291
cristy117ff172010-08-15 21:35:32 +00002292 ssize_t
2293 j,
2294 u,
2295 v;
2296
cristy3ed852e2009-09-05 21:47:34 +00002297 assert(image != (const Image *) NULL);
2298 assert(image->signature == MagickSignature);
2299 if (image->debug != MagickFalse)
2300 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2301 assert(exception != (ExceptionInfo *) NULL);
2302 assert(exception->signature == MagickSignature);
2303 width=GetOptimalKernelWidth2D(radius,sigma);
2304 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2305 if (kernel == (double *) NULL)
2306 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00002307 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00002308 i=0;
cristy47e00502009-12-17 19:19:57 +00002309 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002310 {
cristy47e00502009-12-17 19:19:57 +00002311 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00002312 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
2313 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00002314 }
cristyf4ad9df2011-07-08 16:49:03 +00002315 blur_image=ConvolveImage(image,width,kernel,exception);
cristy3ed852e2009-09-05 21:47:34 +00002316 kernel=(double *) RelinquishMagickMemory(kernel);
2317 return(blur_image);
2318}
2319
2320/*
2321%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2322% %
2323% %
2324% %
cristy3ed852e2009-09-05 21:47:34 +00002325% M o t i o n B l u r I m a g e %
2326% %
2327% %
2328% %
2329%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2330%
2331% MotionBlurImage() simulates motion blur. We convolve the image with a
2332% Gaussian operator of the given radius and standard deviation (sigma).
2333% For reasonable results, radius should be larger than sigma. Use a
2334% radius of 0 and MotionBlurImage() selects a suitable radius for you.
2335% Angle gives the angle of the blurring motion.
2336%
2337% Andrew Protano contributed this effect.
2338%
2339% The format of the MotionBlurImage method is:
2340%
2341% Image *MotionBlurImage(const Image *image,const double radius,
2342% const double sigma,const double angle,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002343%
2344% A description of each parameter follows:
2345%
2346% o image: the image.
2347%
cristy3ed852e2009-09-05 21:47:34 +00002348% o radius: the radius of the Gaussian, in pixels, not counting
2349% the center pixel.
2350%
2351% o sigma: the standard deviation of the Gaussian, in pixels.
2352%
cristycee97112010-05-28 00:44:52 +00002353% o angle: Apply the effect along this angle.
cristy3ed852e2009-09-05 21:47:34 +00002354%
2355% o exception: return any errors or warnings in this structure.
2356%
2357*/
2358
cristybb503372010-05-27 20:51:26 +00002359static double *GetMotionBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +00002360{
cristy3ed852e2009-09-05 21:47:34 +00002361 double
cristy47e00502009-12-17 19:19:57 +00002362 *kernel,
cristy3ed852e2009-09-05 21:47:34 +00002363 normalize;
2364
cristybb503372010-05-27 20:51:26 +00002365 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002366 i;
2367
2368 /*
cristy47e00502009-12-17 19:19:57 +00002369 Generate a 1-D convolution kernel.
cristy3ed852e2009-09-05 21:47:34 +00002370 */
2371 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
2372 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
2373 if (kernel == (double *) NULL)
2374 return(kernel);
cristy3ed852e2009-09-05 21:47:34 +00002375 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00002376 for (i=0; i < (ssize_t) width; i++)
cristy47e00502009-12-17 19:19:57 +00002377 {
cristy4205a3c2010-09-12 20:19:59 +00002378 kernel[i]=(double) (exp((-((double) i*i)/(double) (2.0*MagickSigma*
2379 MagickSigma)))/(MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00002380 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +00002381 }
cristybb503372010-05-27 20:51:26 +00002382 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002383 kernel[i]/=normalize;
2384 return(kernel);
2385}
2386
cristyf4ad9df2011-07-08 16:49:03 +00002387MagickExport Image *MotionBlurImage(const Image *image,
2388 const double radius,const double sigma,const double angle,
2389 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002390{
cristyc4c8d132010-01-07 01:58:38 +00002391 CacheView
2392 *blur_view,
2393 *image_view;
2394
cristy3ed852e2009-09-05 21:47:34 +00002395 double
2396 *kernel;
2397
2398 Image
2399 *blur_image;
2400
cristy3ed852e2009-09-05 21:47:34 +00002401 MagickBooleanType
2402 status;
2403
cristybb503372010-05-27 20:51:26 +00002404 MagickOffsetType
2405 progress;
2406
cristy4c08aed2011-07-01 19:47:50 +00002407 PixelInfo
cristyddd82202009-11-03 20:14:50 +00002408 bias;
cristy3ed852e2009-09-05 21:47:34 +00002409
2410 OffsetInfo
2411 *offset;
2412
2413 PointInfo
2414 point;
2415
cristybb503372010-05-27 20:51:26 +00002416 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002417 i;
2418
cristybb503372010-05-27 20:51:26 +00002419 size_t
cristy3ed852e2009-09-05 21:47:34 +00002420 width;
2421
cristybb503372010-05-27 20:51:26 +00002422 ssize_t
2423 y;
2424
cristy3ed852e2009-09-05 21:47:34 +00002425 assert(image != (Image *) NULL);
2426 assert(image->signature == MagickSignature);
2427 if (image->debug != MagickFalse)
2428 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2429 assert(exception != (ExceptionInfo *) NULL);
2430 width=GetOptimalKernelWidth1D(radius,sigma);
2431 kernel=GetMotionBlurKernel(width,sigma);
2432 if (kernel == (double *) NULL)
2433 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2434 offset=(OffsetInfo *) AcquireQuantumMemory(width,sizeof(*offset));
2435 if (offset == (OffsetInfo *) NULL)
2436 {
2437 kernel=(double *) RelinquishMagickMemory(kernel);
2438 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2439 }
2440 blur_image=CloneImage(image,0,0,MagickTrue,exception);
2441 if (blur_image == (Image *) NULL)
2442 {
2443 kernel=(double *) RelinquishMagickMemory(kernel);
2444 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2445 return((Image *) NULL);
2446 }
2447 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
2448 {
2449 kernel=(double *) RelinquishMagickMemory(kernel);
2450 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2451 InheritException(exception,&blur_image->exception);
2452 blur_image=DestroyImage(blur_image);
2453 return((Image *) NULL);
2454 }
2455 point.x=(double) width*sin(DegreesToRadians(angle));
2456 point.y=(double) width*cos(DegreesToRadians(angle));
cristybb503372010-05-27 20:51:26 +00002457 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002458 {
cristybb503372010-05-27 20:51:26 +00002459 offset[i].x=(ssize_t) ceil((double) (i*point.y)/hypot(point.x,point.y)-0.5);
2460 offset[i].y=(ssize_t) ceil((double) (i*point.x)/hypot(point.x,point.y)-0.5);
cristy3ed852e2009-09-05 21:47:34 +00002461 }
2462 /*
2463 Motion blur image.
2464 */
2465 status=MagickTrue;
2466 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00002467 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00002468 image_view=AcquireCacheView(image);
2469 blur_view=AcquireCacheView(blur_image);
cristyb557a152011-02-22 12:14:30 +00002470#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00002471 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00002472#endif
cristybb503372010-05-27 20:51:26 +00002473 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002474 {
cristy4c08aed2011-07-01 19:47:50 +00002475 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002476 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002477
cristy117ff172010-08-15 21:35:32 +00002478 register ssize_t
2479 x;
2480
cristy3ed852e2009-09-05 21:47:34 +00002481 if (status == MagickFalse)
2482 continue;
2483 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
2484 exception);
cristy4c08aed2011-07-01 19:47:50 +00002485 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002486 {
2487 status=MagickFalse;
2488 continue;
2489 }
cristybb503372010-05-27 20:51:26 +00002490 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002491 {
cristy4c08aed2011-07-01 19:47:50 +00002492 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002493 qixel;
2494
2495 PixelPacket
2496 pixel;
2497
2498 register double
cristyc47d1f82009-11-26 01:44:43 +00002499 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00002500
cristybb503372010-05-27 20:51:26 +00002501 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002502 i;
2503
cristy3ed852e2009-09-05 21:47:34 +00002504 k=kernel;
cristyddd82202009-11-03 20:14:50 +00002505 qixel=bias;
cristy2b9582a2011-07-04 17:38:56 +00002506 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00002507 {
cristybb503372010-05-27 20:51:26 +00002508 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002509 {
2510 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
2511 offset[i].y,&pixel,exception);
2512 qixel.red+=(*k)*pixel.red;
2513 qixel.green+=(*k)*pixel.green;
2514 qixel.blue+=(*k)*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00002515 qixel.alpha+=(*k)*pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00002516 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00002517 qixel.black+=(*k)*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00002518 k++;
2519 }
cristy2b9582a2011-07-04 17:38:56 +00002520 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002521 SetPixelRed(blur_image,
2522 ClampToQuantum(qixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00002523 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002524 SetPixelGreen(blur_image,
2525 ClampToQuantum(qixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00002526 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002527 SetPixelBlue(blur_image,
2528 ClampToQuantum(qixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00002529 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002530 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002531 SetPixelBlack(blur_image,
2532 ClampToQuantum(qixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00002533 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002534 SetPixelAlpha(blur_image,
2535 ClampToQuantum(qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00002536 }
2537 else
2538 {
2539 MagickRealType
2540 alpha,
2541 gamma;
2542
2543 alpha=0.0;
2544 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00002545 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002546 {
2547 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
2548 offset[i].y,&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00002549 alpha=(MagickRealType) (QuantumScale*pixel.alpha);
cristy3ed852e2009-09-05 21:47:34 +00002550 qixel.red+=(*k)*alpha*pixel.red;
2551 qixel.green+=(*k)*alpha*pixel.green;
2552 qixel.blue+=(*k)*alpha*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00002553 qixel.alpha+=(*k)*pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00002554 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00002555 qixel.black+=(*k)*alpha*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00002556 gamma+=(*k)*alpha;
2557 k++;
2558 }
2559 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy2b9582a2011-07-04 17:38:56 +00002560 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002561 SetPixelRed(blur_image,
2562 ClampToQuantum(gamma*qixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00002563 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002564 SetPixelGreen(blur_image,
2565 ClampToQuantum(gamma*qixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00002566 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002567 SetPixelBlue(blur_image,
2568 ClampToQuantum(gamma*qixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00002569 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002570 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002571 SetPixelBlack(blur_image,
2572 ClampToQuantum(gamma*qixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00002573 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002574 SetPixelAlpha(blur_image,
2575 ClampToQuantum(qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00002576 }
cristydcfc1ad2011-07-07 16:25:41 +00002577 q+=GetPixelComponents(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00002578 }
2579 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
2580 status=MagickFalse;
2581 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2582 {
2583 MagickBooleanType
2584 proceed;
2585
cristyb557a152011-02-22 12:14:30 +00002586#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00002587 #pragma omp critical (MagickCore_MotionBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00002588#endif
2589 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
2590 if (proceed == MagickFalse)
2591 status=MagickFalse;
2592 }
2593 }
2594 blur_view=DestroyCacheView(blur_view);
2595 image_view=DestroyCacheView(image_view);
2596 kernel=(double *) RelinquishMagickMemory(kernel);
2597 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2598 if (status == MagickFalse)
2599 blur_image=DestroyImage(blur_image);
2600 return(blur_image);
2601}
2602
2603/*
2604%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2605% %
2606% %
2607% %
2608% P r e v i e w I m a g e %
2609% %
2610% %
2611% %
2612%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2613%
2614% PreviewImage() tiles 9 thumbnails of the specified image with an image
2615% processing operation applied with varying parameters. This may be helpful
2616% pin-pointing an appropriate parameter for a particular image processing
2617% operation.
2618%
2619% The format of the PreviewImages method is:
2620%
2621% Image *PreviewImages(const Image *image,const PreviewType preview,
2622% ExceptionInfo *exception)
2623%
2624% A description of each parameter follows:
2625%
2626% o image: the image.
2627%
2628% o preview: the image processing operation.
2629%
2630% o exception: return any errors or warnings in this structure.
2631%
2632*/
2633MagickExport Image *PreviewImage(const Image *image,const PreviewType preview,
2634 ExceptionInfo *exception)
2635{
2636#define NumberTiles 9
2637#define PreviewImageTag "Preview/Image"
2638#define DefaultPreviewGeometry "204x204+10+10"
2639
2640 char
2641 factor[MaxTextExtent],
2642 label[MaxTextExtent];
2643
2644 double
2645 degrees,
2646 gamma,
2647 percentage,
2648 radius,
2649 sigma,
2650 threshold;
2651
2652 Image
2653 *images,
2654 *montage_image,
2655 *preview_image,
2656 *thumbnail;
2657
2658 ImageInfo
2659 *preview_info;
2660
cristy3ed852e2009-09-05 21:47:34 +00002661 MagickBooleanType
2662 proceed;
2663
2664 MontageInfo
2665 *montage_info;
2666
2667 QuantizeInfo
2668 quantize_info;
2669
2670 RectangleInfo
2671 geometry;
2672
cristybb503372010-05-27 20:51:26 +00002673 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002674 i,
2675 x;
2676
cristybb503372010-05-27 20:51:26 +00002677 size_t
cristy3ed852e2009-09-05 21:47:34 +00002678 colors;
2679
cristy117ff172010-08-15 21:35:32 +00002680 ssize_t
2681 y;
2682
cristy3ed852e2009-09-05 21:47:34 +00002683 /*
2684 Open output image file.
2685 */
2686 assert(image != (Image *) NULL);
2687 assert(image->signature == MagickSignature);
2688 if (image->debug != MagickFalse)
2689 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2690 colors=2;
2691 degrees=0.0;
2692 gamma=(-0.2f);
2693 preview_info=AcquireImageInfo();
2694 SetGeometry(image,&geometry);
2695 (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y,
2696 &geometry.width,&geometry.height);
2697 images=NewImageList();
2698 percentage=12.5;
2699 GetQuantizeInfo(&quantize_info);
2700 radius=0.0;
2701 sigma=1.0;
2702 threshold=0.0;
2703 x=0;
2704 y=0;
2705 for (i=0; i < NumberTiles; i++)
2706 {
2707 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
2708 if (thumbnail == (Image *) NULL)
2709 break;
2710 (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL,
2711 (void *) NULL);
2712 (void) SetImageProperty(thumbnail,"label",DefaultTileLabel);
2713 if (i == (NumberTiles/2))
2714 {
2715 (void) QueryColorDatabase("#dfdfdf",&thumbnail->matte_color,exception);
2716 AppendImageToList(&images,thumbnail);
2717 continue;
2718 }
2719 switch (preview)
2720 {
2721 case RotatePreview:
2722 {
2723 degrees+=45.0;
2724 preview_image=RotateImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002725 (void) FormatLocaleString(label,MaxTextExtent,"rotate %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002726 break;
2727 }
2728 case ShearPreview:
2729 {
2730 degrees+=5.0;
2731 preview_image=ShearImage(thumbnail,degrees,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002732 (void) FormatLocaleString(label,MaxTextExtent,"shear %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002733 degrees,2.0*degrees);
2734 break;
2735 }
2736 case RollPreview:
2737 {
cristybb503372010-05-27 20:51:26 +00002738 x=(ssize_t) ((i+1)*thumbnail->columns)/NumberTiles;
2739 y=(ssize_t) ((i+1)*thumbnail->rows)/NumberTiles;
cristy3ed852e2009-09-05 21:47:34 +00002740 preview_image=RollImage(thumbnail,x,y,exception);
cristyb51dff52011-05-19 16:55:47 +00002741 (void) FormatLocaleString(label,MaxTextExtent,"roll %+.20gx%+.20g",
cristye8c25f92010-06-03 00:53:06 +00002742 (double) x,(double) y);
cristy3ed852e2009-09-05 21:47:34 +00002743 break;
2744 }
2745 case HuePreview:
2746 {
2747 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2748 if (preview_image == (Image *) NULL)
2749 break;
cristyb51dff52011-05-19 16:55:47 +00002750 (void) FormatLocaleString(factor,MaxTextExtent,"100,100,%g",
cristy3ed852e2009-09-05 21:47:34 +00002751 2.0*percentage);
2752 (void) ModulateImage(preview_image,factor);
cristyb51dff52011-05-19 16:55:47 +00002753 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002754 break;
2755 }
2756 case SaturationPreview:
2757 {
2758 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2759 if (preview_image == (Image *) NULL)
2760 break;
cristyb51dff52011-05-19 16:55:47 +00002761 (void) FormatLocaleString(factor,MaxTextExtent,"100,%g",
cristy8cd5b312010-01-07 01:10:24 +00002762 2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00002763 (void) ModulateImage(preview_image,factor);
cristyb51dff52011-05-19 16:55:47 +00002764 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002765 break;
2766 }
2767 case BrightnessPreview:
2768 {
2769 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2770 if (preview_image == (Image *) NULL)
2771 break;
cristyb51dff52011-05-19 16:55:47 +00002772 (void) FormatLocaleString(factor,MaxTextExtent,"%g",2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00002773 (void) ModulateImage(preview_image,factor);
cristyb51dff52011-05-19 16:55:47 +00002774 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002775 break;
2776 }
2777 case GammaPreview:
2778 default:
2779 {
2780 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2781 if (preview_image == (Image *) NULL)
2782 break;
2783 gamma+=0.4f;
cristy50fbc382011-07-07 02:19:17 +00002784 (void) GammaImage(preview_image,gamma);
cristyb51dff52011-05-19 16:55:47 +00002785 (void) FormatLocaleString(label,MaxTextExtent,"gamma %g",gamma);
cristy3ed852e2009-09-05 21:47:34 +00002786 break;
2787 }
2788 case SpiffPreview:
2789 {
2790 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2791 if (preview_image != (Image *) NULL)
2792 for (x=0; x < i; x++)
2793 (void) ContrastImage(preview_image,MagickTrue);
cristyb51dff52011-05-19 16:55:47 +00002794 (void) FormatLocaleString(label,MaxTextExtent,"contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002795 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002796 break;
2797 }
2798 case DullPreview:
2799 {
2800 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2801 if (preview_image == (Image *) NULL)
2802 break;
2803 for (x=0; x < i; x++)
2804 (void) ContrastImage(preview_image,MagickFalse);
cristyb51dff52011-05-19 16:55:47 +00002805 (void) FormatLocaleString(label,MaxTextExtent,"+contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002806 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002807 break;
2808 }
2809 case GrayscalePreview:
2810 {
2811 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2812 if (preview_image == (Image *) NULL)
2813 break;
2814 colors<<=1;
2815 quantize_info.number_colors=colors;
2816 quantize_info.colorspace=GRAYColorspace;
2817 (void) QuantizeImage(&quantize_info,preview_image);
cristyb51dff52011-05-19 16:55:47 +00002818 (void) FormatLocaleString(label,MaxTextExtent,
cristye8c25f92010-06-03 00:53:06 +00002819 "-colorspace gray -colors %.20g",(double) colors);
cristy3ed852e2009-09-05 21:47:34 +00002820 break;
2821 }
2822 case QuantizePreview:
2823 {
2824 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2825 if (preview_image == (Image *) NULL)
2826 break;
2827 colors<<=1;
2828 quantize_info.number_colors=colors;
2829 (void) QuantizeImage(&quantize_info,preview_image);
cristyb51dff52011-05-19 16:55:47 +00002830 (void) FormatLocaleString(label,MaxTextExtent,"colors %.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00002831 colors);
cristy3ed852e2009-09-05 21:47:34 +00002832 break;
2833 }
2834 case DespecklePreview:
2835 {
2836 for (x=0; x < (i-1); x++)
2837 {
2838 preview_image=DespeckleImage(thumbnail,exception);
2839 if (preview_image == (Image *) NULL)
2840 break;
2841 thumbnail=DestroyImage(thumbnail);
2842 thumbnail=preview_image;
2843 }
2844 preview_image=DespeckleImage(thumbnail,exception);
2845 if (preview_image == (Image *) NULL)
2846 break;
cristyb51dff52011-05-19 16:55:47 +00002847 (void) FormatLocaleString(label,MaxTextExtent,"despeckle (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002848 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002849 break;
2850 }
2851 case ReduceNoisePreview:
2852 {
cristy95c38342011-03-18 22:39:51 +00002853 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) radius,
2854 (size_t) radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002855 (void) FormatLocaleString(label,MaxTextExtent,"noise %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002856 break;
2857 }
2858 case AddNoisePreview:
2859 {
2860 switch ((int) i)
2861 {
2862 case 0:
2863 {
2864 (void) CopyMagickString(factor,"uniform",MaxTextExtent);
2865 break;
2866 }
2867 case 1:
2868 {
2869 (void) CopyMagickString(factor,"gaussian",MaxTextExtent);
2870 break;
2871 }
2872 case 2:
2873 {
2874 (void) CopyMagickString(factor,"multiplicative",MaxTextExtent);
2875 break;
2876 }
2877 case 3:
2878 {
2879 (void) CopyMagickString(factor,"impulse",MaxTextExtent);
2880 break;
2881 }
2882 case 4:
2883 {
2884 (void) CopyMagickString(factor,"laplacian",MaxTextExtent);
2885 break;
2886 }
2887 case 5:
2888 {
2889 (void) CopyMagickString(factor,"Poisson",MaxTextExtent);
2890 break;
2891 }
2892 default:
2893 {
2894 (void) CopyMagickString(thumbnail->magick,"NULL",MaxTextExtent);
2895 break;
2896 }
2897 }
cristyd76c51e2011-03-26 00:21:26 +00002898 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) i,
2899 (size_t) i,exception);
cristyb51dff52011-05-19 16:55:47 +00002900 (void) FormatLocaleString(label,MaxTextExtent,"+noise %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002901 break;
2902 }
2903 case SharpenPreview:
2904 {
2905 preview_image=SharpenImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00002906 (void) FormatLocaleString(label,MaxTextExtent,"sharpen %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002907 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002908 break;
2909 }
2910 case BlurPreview:
2911 {
2912 preview_image=BlurImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00002913 (void) FormatLocaleString(label,MaxTextExtent,"blur %gx%g",radius,
cristy3ed852e2009-09-05 21:47:34 +00002914 sigma);
2915 break;
2916 }
2917 case ThresholdPreview:
2918 {
2919 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2920 if (preview_image == (Image *) NULL)
2921 break;
2922 (void) BilevelImage(thumbnail,
2923 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
cristyb51dff52011-05-19 16:55:47 +00002924 (void) FormatLocaleString(label,MaxTextExtent,"threshold %g",
cristy3ed852e2009-09-05 21:47:34 +00002925 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
2926 break;
2927 }
2928 case EdgeDetectPreview:
2929 {
2930 preview_image=EdgeImage(thumbnail,radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002931 (void) FormatLocaleString(label,MaxTextExtent,"edge %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002932 break;
2933 }
2934 case SpreadPreview:
2935 {
2936 preview_image=SpreadImage(thumbnail,radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002937 (void) FormatLocaleString(label,MaxTextExtent,"spread %g",
cristy8cd5b312010-01-07 01:10:24 +00002938 radius+0.5);
cristy3ed852e2009-09-05 21:47:34 +00002939 break;
2940 }
2941 case SolarizePreview:
2942 {
2943 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2944 if (preview_image == (Image *) NULL)
2945 break;
2946 (void) SolarizeImage(preview_image,(double) QuantumRange*
2947 percentage/100.0);
cristyb51dff52011-05-19 16:55:47 +00002948 (void) FormatLocaleString(label,MaxTextExtent,"solarize %g",
cristy3ed852e2009-09-05 21:47:34 +00002949 (QuantumRange*percentage)/100.0);
2950 break;
2951 }
2952 case ShadePreview:
2953 {
2954 degrees+=10.0;
2955 preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees,
2956 exception);
cristyb51dff52011-05-19 16:55:47 +00002957 (void) FormatLocaleString(label,MaxTextExtent,"shade %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002958 degrees,degrees);
cristy3ed852e2009-09-05 21:47:34 +00002959 break;
2960 }
2961 case RaisePreview:
2962 {
2963 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2964 if (preview_image == (Image *) NULL)
2965 break;
cristybb503372010-05-27 20:51:26 +00002966 geometry.width=(size_t) (2*i+2);
2967 geometry.height=(size_t) (2*i+2);
cristy3ed852e2009-09-05 21:47:34 +00002968 geometry.x=i/2;
2969 geometry.y=i/2;
2970 (void) RaiseImage(preview_image,&geometry,MagickTrue);
cristyb51dff52011-05-19 16:55:47 +00002971 (void) FormatLocaleString(label,MaxTextExtent,
cristy6d8abba2010-06-03 01:10:47 +00002972 "raise %.20gx%.20g%+.20g%+.20g",(double) geometry.width,(double)
cristye8c25f92010-06-03 00:53:06 +00002973 geometry.height,(double) geometry.x,(double) geometry.y);
cristy3ed852e2009-09-05 21:47:34 +00002974 break;
2975 }
2976 case SegmentPreview:
2977 {
2978 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2979 if (preview_image == (Image *) NULL)
2980 break;
2981 threshold+=0.4f;
2982 (void) SegmentImage(preview_image,RGBColorspace,MagickFalse,threshold,
2983 threshold);
cristyb51dff52011-05-19 16:55:47 +00002984 (void) FormatLocaleString(label,MaxTextExtent,"segment %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002985 threshold,threshold);
2986 break;
2987 }
2988 case SwirlPreview:
2989 {
2990 preview_image=SwirlImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002991 (void) FormatLocaleString(label,MaxTextExtent,"swirl %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002992 degrees+=45.0;
2993 break;
2994 }
2995 case ImplodePreview:
2996 {
2997 degrees+=0.1f;
2998 preview_image=ImplodeImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002999 (void) FormatLocaleString(label,MaxTextExtent,"implode %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003000 break;
3001 }
3002 case WavePreview:
3003 {
3004 degrees+=5.0f;
3005 preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00003006 (void) FormatLocaleString(label,MaxTextExtent,"wave %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003007 0.5*degrees,2.0*degrees);
cristy3ed852e2009-09-05 21:47:34 +00003008 break;
3009 }
3010 case OilPaintPreview:
3011 {
3012 preview_image=OilPaintImage(thumbnail,(double) radius,exception);
cristyb51dff52011-05-19 16:55:47 +00003013 (void) FormatLocaleString(label,MaxTextExtent,"paint %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003014 break;
3015 }
3016 case CharcoalDrawingPreview:
3017 {
3018 preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma,
3019 exception);
cristyb51dff52011-05-19 16:55:47 +00003020 (void) FormatLocaleString(label,MaxTextExtent,"charcoal %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003021 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00003022 break;
3023 }
3024 case JPEGPreview:
3025 {
3026 char
3027 filename[MaxTextExtent];
3028
3029 int
3030 file;
3031
3032 MagickBooleanType
3033 status;
3034
3035 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3036 if (preview_image == (Image *) NULL)
3037 break;
cristybb503372010-05-27 20:51:26 +00003038 preview_info->quality=(size_t) percentage;
cristyb51dff52011-05-19 16:55:47 +00003039 (void) FormatLocaleString(factor,MaxTextExtent,"%.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00003040 preview_info->quality);
cristy3ed852e2009-09-05 21:47:34 +00003041 file=AcquireUniqueFileResource(filename);
3042 if (file != -1)
3043 file=close(file)-1;
cristyb51dff52011-05-19 16:55:47 +00003044 (void) FormatLocaleString(preview_image->filename,MaxTextExtent,
cristy3ed852e2009-09-05 21:47:34 +00003045 "jpeg:%s",filename);
3046 status=WriteImage(preview_info,preview_image);
3047 if (status != MagickFalse)
3048 {
3049 Image
3050 *quality_image;
3051
3052 (void) CopyMagickString(preview_info->filename,
3053 preview_image->filename,MaxTextExtent);
3054 quality_image=ReadImage(preview_info,exception);
3055 if (quality_image != (Image *) NULL)
3056 {
3057 preview_image=DestroyImage(preview_image);
3058 preview_image=quality_image;
3059 }
3060 }
3061 (void) RelinquishUniqueFileResource(preview_image->filename);
3062 if ((GetBlobSize(preview_image)/1024) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00003063 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%gmb ",
cristy3ed852e2009-09-05 21:47:34 +00003064 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
3065 1024.0/1024.0);
3066 else
3067 if (GetBlobSize(preview_image) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00003068 (void) FormatLocaleString(label,MaxTextExtent,
cristye7f51092010-01-17 00:39:37 +00003069 "quality %s\n%gkb ",factor,(double) ((MagickOffsetType)
cristy8cd5b312010-01-07 01:10:24 +00003070 GetBlobSize(preview_image))/1024.0);
cristy3ed852e2009-09-05 21:47:34 +00003071 else
cristyb51dff52011-05-19 16:55:47 +00003072 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%.20gb ",
cristy54ea5732011-06-10 12:39:53 +00003073 factor,(double) ((MagickOffsetType) GetBlobSize(thumbnail)));
cristy3ed852e2009-09-05 21:47:34 +00003074 break;
3075 }
3076 }
3077 thumbnail=DestroyImage(thumbnail);
3078 percentage+=12.5;
3079 radius+=0.5;
3080 sigma+=0.25;
3081 if (preview_image == (Image *) NULL)
3082 break;
3083 (void) DeleteImageProperty(preview_image,"label");
3084 (void) SetImageProperty(preview_image,"label",label);
3085 AppendImageToList(&images,preview_image);
cristybb503372010-05-27 20:51:26 +00003086 proceed=SetImageProgress(image,PreviewImageTag,(MagickOffsetType) i,
3087 NumberTiles);
cristy3ed852e2009-09-05 21:47:34 +00003088 if (proceed == MagickFalse)
3089 break;
3090 }
3091 if (images == (Image *) NULL)
3092 {
3093 preview_info=DestroyImageInfo(preview_info);
3094 return((Image *) NULL);
3095 }
3096 /*
3097 Create the montage.
3098 */
3099 montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL);
3100 (void) CopyMagickString(montage_info->filename,image->filename,MaxTextExtent);
3101 montage_info->shadow=MagickTrue;
3102 (void) CloneString(&montage_info->tile,"3x3");
3103 (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry);
3104 (void) CloneString(&montage_info->frame,DefaultTileFrame);
3105 montage_image=MontageImages(images,montage_info,exception);
3106 montage_info=DestroyMontageInfo(montage_info);
3107 images=DestroyImageList(images);
3108 if (montage_image == (Image *) NULL)
3109 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3110 if (montage_image->montage != (char *) NULL)
3111 {
3112 /*
3113 Free image directory.
3114 */
3115 montage_image->montage=(char *) RelinquishMagickMemory(
3116 montage_image->montage);
3117 if (image->directory != (char *) NULL)
3118 montage_image->directory=(char *) RelinquishMagickMemory(
3119 montage_image->directory);
3120 }
3121 preview_info=DestroyImageInfo(preview_info);
3122 return(montage_image);
3123}
3124
3125/*
3126%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3127% %
3128% %
3129% %
3130% R a d i a l B l u r I m a g e %
3131% %
3132% %
3133% %
3134%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3135%
3136% RadialBlurImage() applies a radial blur to the image.
3137%
3138% Andrew Protano contributed this effect.
3139%
3140% The format of the RadialBlurImage method is:
3141%
3142% Image *RadialBlurImage(const Image *image,const double angle,
3143% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003144%
3145% A description of each parameter follows:
3146%
3147% o image: the image.
3148%
cristy3ed852e2009-09-05 21:47:34 +00003149% o angle: the angle of the radial blur.
3150%
3151% o exception: return any errors or warnings in this structure.
3152%
3153*/
cristyf4ad9df2011-07-08 16:49:03 +00003154MagickExport Image *RadialBlurImage(const Image *image,
3155 const double angle,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003156{
cristyc4c8d132010-01-07 01:58:38 +00003157 CacheView
3158 *blur_view,
3159 *image_view;
3160
cristy3ed852e2009-09-05 21:47:34 +00003161 Image
3162 *blur_image;
3163
cristy3ed852e2009-09-05 21:47:34 +00003164 MagickBooleanType
3165 status;
3166
cristybb503372010-05-27 20:51:26 +00003167 MagickOffsetType
3168 progress;
3169
cristy4c08aed2011-07-01 19:47:50 +00003170 PixelInfo
cristyddd82202009-11-03 20:14:50 +00003171 bias;
cristy3ed852e2009-09-05 21:47:34 +00003172
3173 MagickRealType
3174 blur_radius,
3175 *cos_theta,
3176 offset,
3177 *sin_theta,
3178 theta;
3179
3180 PointInfo
3181 blur_center;
3182
cristybb503372010-05-27 20:51:26 +00003183 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003184 i;
3185
cristybb503372010-05-27 20:51:26 +00003186 size_t
cristy3ed852e2009-09-05 21:47:34 +00003187 n;
3188
cristybb503372010-05-27 20:51:26 +00003189 ssize_t
3190 y;
3191
cristy3ed852e2009-09-05 21:47:34 +00003192 /*
3193 Allocate blur image.
3194 */
3195 assert(image != (Image *) NULL);
3196 assert(image->signature == MagickSignature);
3197 if (image->debug != MagickFalse)
3198 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3199 assert(exception != (ExceptionInfo *) NULL);
3200 assert(exception->signature == MagickSignature);
3201 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3202 if (blur_image == (Image *) NULL)
3203 return((Image *) NULL);
3204 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3205 {
3206 InheritException(exception,&blur_image->exception);
3207 blur_image=DestroyImage(blur_image);
3208 return((Image *) NULL);
3209 }
3210 blur_center.x=(double) image->columns/2.0;
3211 blur_center.y=(double) image->rows/2.0;
3212 blur_radius=hypot(blur_center.x,blur_center.y);
cristy117ff172010-08-15 21:35:32 +00003213 n=(size_t) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+2UL);
cristy3ed852e2009-09-05 21:47:34 +00003214 theta=DegreesToRadians(angle)/(MagickRealType) (n-1);
3215 cos_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3216 sizeof(*cos_theta));
3217 sin_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3218 sizeof(*sin_theta));
3219 if ((cos_theta == (MagickRealType *) NULL) ||
3220 (sin_theta == (MagickRealType *) NULL))
3221 {
3222 blur_image=DestroyImage(blur_image);
3223 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3224 }
3225 offset=theta*(MagickRealType) (n-1)/2.0;
cristybb503372010-05-27 20:51:26 +00003226 for (i=0; i < (ssize_t) n; i++)
cristy3ed852e2009-09-05 21:47:34 +00003227 {
3228 cos_theta[i]=cos((double) (theta*i-offset));
3229 sin_theta[i]=sin((double) (theta*i-offset));
3230 }
3231 /*
3232 Radial blur image.
3233 */
3234 status=MagickTrue;
3235 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00003236 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003237 image_view=AcquireCacheView(image);
3238 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003239#if defined(MAGICKCORE_OPENMP_SUPPORT)
3240 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003241#endif
cristybb503372010-05-27 20:51:26 +00003242 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003243 {
cristy4c08aed2011-07-01 19:47:50 +00003244 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003245 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003246
cristy117ff172010-08-15 21:35:32 +00003247 register ssize_t
3248 x;
3249
cristy3ed852e2009-09-05 21:47:34 +00003250 if (status == MagickFalse)
3251 continue;
3252 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3253 exception);
cristy4c08aed2011-07-01 19:47:50 +00003254 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003255 {
3256 status=MagickFalse;
3257 continue;
3258 }
cristybb503372010-05-27 20:51:26 +00003259 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003260 {
cristy4c08aed2011-07-01 19:47:50 +00003261 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003262 qixel;
3263
3264 MagickRealType
3265 normalize,
3266 radius;
3267
3268 PixelPacket
3269 pixel;
3270
3271 PointInfo
3272 center;
3273
cristybb503372010-05-27 20:51:26 +00003274 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003275 i;
3276
cristybb503372010-05-27 20:51:26 +00003277 size_t
cristy3ed852e2009-09-05 21:47:34 +00003278 step;
3279
3280 center.x=(double) x-blur_center.x;
3281 center.y=(double) y-blur_center.y;
3282 radius=hypot((double) center.x,center.y);
3283 if (radius == 0)
3284 step=1;
3285 else
3286 {
cristybb503372010-05-27 20:51:26 +00003287 step=(size_t) (blur_radius/radius);
cristy3ed852e2009-09-05 21:47:34 +00003288 if (step == 0)
3289 step=1;
3290 else
3291 if (step >= n)
3292 step=n-1;
3293 }
3294 normalize=0.0;
cristyddd82202009-11-03 20:14:50 +00003295 qixel=bias;
cristy2b9582a2011-07-04 17:38:56 +00003296 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00003297 {
cristyeaedf062010-05-29 22:36:02 +00003298 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00003299 {
cristyeaedf062010-05-29 22:36:02 +00003300 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
3301 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
3302 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
3303 cos_theta[i]+0.5),&pixel,exception);
cristy3ed852e2009-09-05 21:47:34 +00003304 qixel.red+=pixel.red;
3305 qixel.green+=pixel.green;
3306 qixel.blue+=pixel.blue;
cristy3ed852e2009-09-05 21:47:34 +00003307 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00003308 qixel.black+=pixel.black;
3309 qixel.alpha+=pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00003310 normalize+=1.0;
3311 }
3312 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
3313 normalize);
cristy2b9582a2011-07-04 17:38:56 +00003314 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003315 SetPixelRed(blur_image,
3316 ClampToQuantum(normalize*qixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00003317 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003318 SetPixelGreen(blur_image,
3319 ClampToQuantum(normalize*qixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00003320 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003321 SetPixelBlue(blur_image,
3322 ClampToQuantum(normalize*qixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00003323 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003324 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003325 SetPixelBlack(blur_image,
3326 ClampToQuantum(normalize*qixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00003327 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003328 SetPixelAlpha(blur_image,
3329 ClampToQuantum(normalize*qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003330 }
3331 else
3332 {
3333 MagickRealType
3334 alpha,
3335 gamma;
3336
3337 alpha=1.0;
3338 gamma=0.0;
cristyeaedf062010-05-29 22:36:02 +00003339 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00003340 {
cristyeaedf062010-05-29 22:36:02 +00003341 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
3342 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
3343 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
3344 cos_theta[i]+0.5),&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00003345 alpha=(MagickRealType) (QuantumScale*pixel.alpha);
cristy3ed852e2009-09-05 21:47:34 +00003346 qixel.red+=alpha*pixel.red;
3347 qixel.green+=alpha*pixel.green;
3348 qixel.blue+=alpha*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00003349 qixel.alpha+=pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00003350 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00003351 qixel.black+=alpha*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00003352 gamma+=alpha;
3353 normalize+=1.0;
3354 }
3355 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3356 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
3357 normalize);
cristy2b9582a2011-07-04 17:38:56 +00003358 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003359 SetPixelRed(blur_image,
3360 ClampToQuantum(gamma*qixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00003361 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003362 SetPixelGreen(blur_image,
3363 ClampToQuantum(gamma*qixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00003364 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003365 SetPixelBlue(blur_image,
3366 ClampToQuantum(gamma*qixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00003367 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003368 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003369 SetPixelBlack(blur_image,
3370 ClampToQuantum(gamma*qixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00003371 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003372 SetPixelAlpha(blur_image,
3373 ClampToQuantum(normalize*qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003374 }
cristydcfc1ad2011-07-07 16:25:41 +00003375 q+=GetPixelComponents(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003376 }
3377 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
3378 status=MagickFalse;
3379 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3380 {
3381 MagickBooleanType
3382 proceed;
3383
cristyb5d5f722009-11-04 03:03:49 +00003384#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00003385 #pragma omp critical (MagickCore_RadialBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00003386#endif
3387 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
3388 if (proceed == MagickFalse)
3389 status=MagickFalse;
3390 }
3391 }
3392 blur_view=DestroyCacheView(blur_view);
3393 image_view=DestroyCacheView(image_view);
3394 cos_theta=(MagickRealType *) RelinquishMagickMemory(cos_theta);
3395 sin_theta=(MagickRealType *) RelinquishMagickMemory(sin_theta);
3396 if (status == MagickFalse)
3397 blur_image=DestroyImage(blur_image);
3398 return(blur_image);
3399}
3400
3401/*
3402%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3403% %
3404% %
3405% %
cristy3ed852e2009-09-05 21:47:34 +00003406% S e l e c t i v e B l u r I m a g e %
3407% %
3408% %
3409% %
3410%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3411%
3412% SelectiveBlurImage() selectively blur pixels within a contrast threshold.
3413% It is similar to the unsharpen mask that sharpens everything with contrast
3414% above a certain threshold.
3415%
3416% The format of the SelectiveBlurImage method is:
3417%
3418% Image *SelectiveBlurImage(const Image *image,const double radius,
3419% const double sigma,const double threshold,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003420%
3421% A description of each parameter follows:
3422%
3423% o image: the image.
3424%
cristy3ed852e2009-09-05 21:47:34 +00003425% o radius: the radius of the Gaussian, in pixels, not counting the center
3426% pixel.
3427%
3428% o sigma: the standard deviation of the Gaussian, in pixels.
3429%
3430% o threshold: only pixels within this contrast threshold are included
3431% in the blur operation.
3432%
3433% o exception: return any errors or warnings in this structure.
3434%
3435*/
cristyf4ad9df2011-07-08 16:49:03 +00003436MagickExport Image *SelectiveBlurImage(const Image *image,
3437 const double radius,const double sigma,const double threshold,
3438 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003439{
3440#define SelectiveBlurImageTag "SelectiveBlur/Image"
3441
cristy47e00502009-12-17 19:19:57 +00003442 CacheView
3443 *blur_view,
3444 *image_view;
3445
cristy3ed852e2009-09-05 21:47:34 +00003446 double
cristy3ed852e2009-09-05 21:47:34 +00003447 *kernel;
3448
3449 Image
3450 *blur_image;
3451
cristy3ed852e2009-09-05 21:47:34 +00003452 MagickBooleanType
3453 status;
3454
cristybb503372010-05-27 20:51:26 +00003455 MagickOffsetType
3456 progress;
3457
cristy4c08aed2011-07-01 19:47:50 +00003458 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003459 bias;
3460
cristybb503372010-05-27 20:51:26 +00003461 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003462 i;
cristy3ed852e2009-09-05 21:47:34 +00003463
cristybb503372010-05-27 20:51:26 +00003464 size_t
cristy3ed852e2009-09-05 21:47:34 +00003465 width;
3466
cristybb503372010-05-27 20:51:26 +00003467 ssize_t
3468 j,
3469 u,
3470 v,
3471 y;
3472
cristy3ed852e2009-09-05 21:47:34 +00003473 /*
3474 Initialize blur image attributes.
3475 */
3476 assert(image != (Image *) NULL);
3477 assert(image->signature == MagickSignature);
3478 if (image->debug != MagickFalse)
3479 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3480 assert(exception != (ExceptionInfo *) NULL);
3481 assert(exception->signature == MagickSignature);
3482 width=GetOptimalKernelWidth1D(radius,sigma);
3483 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
3484 if (kernel == (double *) NULL)
3485 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00003486 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00003487 i=0;
cristy47e00502009-12-17 19:19:57 +00003488 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00003489 {
cristy47e00502009-12-17 19:19:57 +00003490 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00003491 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
3492 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00003493 }
3494 if (image->debug != MagickFalse)
3495 {
3496 char
3497 format[MaxTextExtent],
3498 *message;
3499
cristy117ff172010-08-15 21:35:32 +00003500 register const double
3501 *k;
3502
cristybb503372010-05-27 20:51:26 +00003503 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003504 u,
3505 v;
3506
cristy3ed852e2009-09-05 21:47:34 +00003507 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00003508 " SelectiveBlurImage with %.20gx%.20g kernel:",(double) width,(double)
3509 width);
cristy3ed852e2009-09-05 21:47:34 +00003510 message=AcquireString("");
3511 k=kernel;
cristybb503372010-05-27 20:51:26 +00003512 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003513 {
3514 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00003515 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy3ed852e2009-09-05 21:47:34 +00003516 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00003517 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003518 {
cristyb51dff52011-05-19 16:55:47 +00003519 (void) FormatLocaleString(format,MaxTextExtent,"%+f ",*k++);
cristy3ed852e2009-09-05 21:47:34 +00003520 (void) ConcatenateString(&message,format);
3521 }
3522 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
3523 }
3524 message=DestroyString(message);
3525 }
3526 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3527 if (blur_image == (Image *) NULL)
3528 return((Image *) NULL);
3529 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3530 {
3531 InheritException(exception,&blur_image->exception);
3532 blur_image=DestroyImage(blur_image);
3533 return((Image *) NULL);
3534 }
3535 /*
3536 Threshold blur image.
3537 */
3538 status=MagickTrue;
3539 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00003540 GetPixelInfo(image,&bias);
3541 SetPixelInfoBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003542 image_view=AcquireCacheView(image);
3543 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003544#if defined(MAGICKCORE_OPENMP_SUPPORT)
3545 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003546#endif
cristybb503372010-05-27 20:51:26 +00003547 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003548 {
cristy4c08aed2011-07-01 19:47:50 +00003549 double
3550 contrast;
3551
cristy3ed852e2009-09-05 21:47:34 +00003552 MagickBooleanType
3553 sync;
3554
3555 MagickRealType
3556 gamma;
3557
cristy4c08aed2011-07-01 19:47:50 +00003558 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003559 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003560
cristy4c08aed2011-07-01 19:47:50 +00003561 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003562 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003563
cristy117ff172010-08-15 21:35:32 +00003564 register ssize_t
3565 x;
3566
cristy3ed852e2009-09-05 21:47:34 +00003567 if (status == MagickFalse)
3568 continue;
cristy117ff172010-08-15 21:35:32 +00003569 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
3570 (width/2L),image->columns+width,width,exception);
cristy3ed852e2009-09-05 21:47:34 +00003571 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3572 exception);
cristy4c08aed2011-07-01 19:47:50 +00003573 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003574 {
3575 status=MagickFalse;
3576 continue;
3577 }
cristybb503372010-05-27 20:51:26 +00003578 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003579 {
cristy4c08aed2011-07-01 19:47:50 +00003580 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003581 pixel;
3582
3583 register const double
cristyc47d1f82009-11-26 01:44:43 +00003584 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00003585
cristybb503372010-05-27 20:51:26 +00003586 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003587 u;
3588
cristy117ff172010-08-15 21:35:32 +00003589 ssize_t
3590 j,
3591 v;
3592
cristyddd82202009-11-03 20:14:50 +00003593 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00003594 k=kernel;
3595 gamma=0.0;
3596 j=0;
cristy2b9582a2011-07-04 17:38:56 +00003597 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00003598 {
cristybb503372010-05-27 20:51:26 +00003599 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003600 {
cristybb503372010-05-27 20:51:26 +00003601 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003602 {
cristydcfc1ad2011-07-07 16:25:41 +00003603 contrast=GetPixelIntensity(image,p+(u+j)*GetPixelComponents(image))-
cristy4c08aed2011-07-01 19:47:50 +00003604 (double) GetPixelIntensity(blur_image,q);
3605 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003606 {
cristy4c08aed2011-07-01 19:47:50 +00003607 pixel.red+=(*k)*
cristydcfc1ad2011-07-07 16:25:41 +00003608 GetPixelRed(image,p+(u+j)*GetPixelComponents(image));
cristy4c08aed2011-07-01 19:47:50 +00003609 pixel.green+=(*k)*
cristydcfc1ad2011-07-07 16:25:41 +00003610 GetPixelGreen(image,p+(u+j)*GetPixelComponents(image));
cristy4c08aed2011-07-01 19:47:50 +00003611 pixel.blue+=(*k)*
cristydcfc1ad2011-07-07 16:25:41 +00003612 GetPixelBlue(image,p+(u+j)*GetPixelComponents(image));
cristy4c08aed2011-07-01 19:47:50 +00003613 if (image->colorspace == CMYKColorspace)
3614 pixel.black+=(*k)*
cristydcfc1ad2011-07-07 16:25:41 +00003615 GetPixelBlack(image,p+(u+j)*GetPixelComponents(image));
cristy3ed852e2009-09-05 21:47:34 +00003616 gamma+=(*k);
3617 k++;
3618 }
3619 }
cristyd99b0962010-05-29 23:14:26 +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 : gamma);
cristy2b9582a2011-07-04 17:38:56 +00003625 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003626 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00003627 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003628 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00003629 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003630 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00003631 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00003632 (image->colorspace == CMYKColorspace))
3633 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00003634 }
cristy2b9582a2011-07-04 17:38:56 +00003635 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003636 {
3637 gamma=0.0;
3638 j=0;
cristybb503372010-05-27 20:51:26 +00003639 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003640 {
cristybb503372010-05-27 20:51:26 +00003641 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003642 {
cristy4c08aed2011-07-01 19:47:50 +00003643 contrast=GetPixelIntensity(image,p+(u+j)*
cristydcfc1ad2011-07-07 16:25:41 +00003644 GetPixelComponents(image))-(double)
cristy4c08aed2011-07-01 19:47:50 +00003645 GetPixelIntensity(blur_image,q);
3646 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003647 {
cristy4c08aed2011-07-01 19:47:50 +00003648 pixel.alpha+=(*k)*
cristydcfc1ad2011-07-07 16:25:41 +00003649 GetPixelAlpha(image,p+(u+j)*GetPixelComponents(image));
cristy3ed852e2009-09-05 21:47:34 +00003650 gamma+=(*k);
3651 k++;
3652 }
3653 }
cristyeaedf062010-05-29 22:36:02 +00003654 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003655 }
3656 if (gamma != 0.0)
3657 {
3658 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
3659 gamma);
cristy4c08aed2011-07-01 19:47:50 +00003660 SetPixelAlpha(blur_image,ClampToQuantum(gamma*pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003661 }
3662 }
3663 }
3664 else
3665 {
3666 MagickRealType
3667 alpha;
3668
cristybb503372010-05-27 20:51:26 +00003669 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003670 {
cristybb503372010-05-27 20:51:26 +00003671 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003672 {
cristy4c08aed2011-07-01 19:47:50 +00003673 contrast=GetPixelIntensity(image,p+(u+j)*
cristydcfc1ad2011-07-07 16:25:41 +00003674 GetPixelComponents(image))-(double)
cristy4c08aed2011-07-01 19:47:50 +00003675 GetPixelIntensity(blur_image,q);
3676 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003677 {
cristy4c08aed2011-07-01 19:47:50 +00003678 alpha=(MagickRealType) (QuantumScale*
cristydcfc1ad2011-07-07 16:25:41 +00003679 GetPixelAlpha(image,p+(u+j)*GetPixelComponents(image)));
cristy4c08aed2011-07-01 19:47:50 +00003680 pixel.red+=(*k)*alpha*
cristydcfc1ad2011-07-07 16:25:41 +00003681 GetPixelRed(image,p+(u+j)*GetPixelComponents(image));
cristy4c08aed2011-07-01 19:47:50 +00003682 pixel.green+=(*k)*alpha*GetPixelGreen(image,p+(u+j)*
cristydcfc1ad2011-07-07 16:25:41 +00003683 GetPixelComponents(image));
cristy4c08aed2011-07-01 19:47:50 +00003684 pixel.blue+=(*k)*alpha*GetPixelBlue(image,p+(u+j)*
cristydcfc1ad2011-07-07 16:25:41 +00003685 GetPixelComponents(image));
cristy4c08aed2011-07-01 19:47:50 +00003686 pixel.alpha+=(*k)*GetPixelAlpha(image,p+(u+j)*
cristydcfc1ad2011-07-07 16:25:41 +00003687 GetPixelComponents(image));
cristy4c08aed2011-07-01 19:47:50 +00003688 if (image->colorspace == CMYKColorspace)
3689 pixel.black+=(*k)*GetPixelBlack(image,p+(u+j)*
cristydcfc1ad2011-07-07 16:25:41 +00003690 GetPixelComponents(image));
cristy3ed852e2009-09-05 21:47:34 +00003691 gamma+=(*k)*alpha;
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 : gamma);
cristy2b9582a2011-07-04 17:38:56 +00003700 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003701 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00003702 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003703 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00003704 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003705 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00003706 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00003707 (image->colorspace == CMYKColorspace))
3708 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00003709 }
cristy2b9582a2011-07-04 17:38:56 +00003710 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003711 {
3712 gamma=0.0;
3713 j=0;
cristybb503372010-05-27 20:51:26 +00003714 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003715 {
cristybb503372010-05-27 20:51:26 +00003716 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003717 {
cristy4c08aed2011-07-01 19:47:50 +00003718 contrast=GetPixelIntensity(image,p+(u+j)*
cristydcfc1ad2011-07-07 16:25:41 +00003719 GetPixelComponents(image))-(double)
cristy4c08aed2011-07-01 19:47:50 +00003720 GetPixelIntensity(blur_image,q);
3721 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003722 {
cristy4c08aed2011-07-01 19:47:50 +00003723 pixel.alpha+=(*k)*
cristydcfc1ad2011-07-07 16:25:41 +00003724 GetPixelAlpha(image,p+(u+j)*GetPixelComponents(image));
cristy3ed852e2009-09-05 21:47:34 +00003725 gamma+=(*k);
3726 k++;
3727 }
3728 }
cristyeaedf062010-05-29 22:36:02 +00003729 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003730 }
3731 if (gamma != 0.0)
3732 {
3733 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
3734 gamma);
cristy4c08aed2011-07-01 19:47:50 +00003735 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003736 }
3737 }
3738 }
cristydcfc1ad2011-07-07 16:25:41 +00003739 p+=GetPixelComponents(image);
3740 q+=GetPixelComponents(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003741 }
3742 sync=SyncCacheViewAuthenticPixels(blur_view,exception);
3743 if (sync == MagickFalse)
3744 status=MagickFalse;
3745 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3746 {
3747 MagickBooleanType
3748 proceed;
3749
cristyb5d5f722009-11-04 03:03:49 +00003750#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00003751 #pragma omp critical (MagickCore_SelectiveBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00003752#endif
3753 proceed=SetImageProgress(image,SelectiveBlurImageTag,progress++,
3754 image->rows);
3755 if (proceed == MagickFalse)
3756 status=MagickFalse;
3757 }
3758 }
3759 blur_image->type=image->type;
3760 blur_view=DestroyCacheView(blur_view);
3761 image_view=DestroyCacheView(image_view);
3762 kernel=(double *) RelinquishMagickMemory(kernel);
3763 if (status == MagickFalse)
3764 blur_image=DestroyImage(blur_image);
3765 return(blur_image);
3766}
3767
3768/*
3769%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3770% %
3771% %
3772% %
3773% S h a d e I m a g e %
3774% %
3775% %
3776% %
3777%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3778%
3779% ShadeImage() shines a distant light on an image to create a
3780% three-dimensional effect. You control the positioning of the light with
3781% azimuth and elevation; azimuth is measured in degrees off the x axis
3782% and elevation is measured in pixels above the Z axis.
3783%
3784% The format of the ShadeImage method is:
3785%
3786% Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3787% const double azimuth,const double elevation,ExceptionInfo *exception)
3788%
3789% A description of each parameter follows:
3790%
3791% o image: the image.
3792%
3793% o gray: A value other than zero shades the intensity of each pixel.
3794%
3795% o azimuth, elevation: Define the light source direction.
3796%
3797% o exception: return any errors or warnings in this structure.
3798%
3799*/
3800MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3801 const double azimuth,const double elevation,ExceptionInfo *exception)
3802{
3803#define ShadeImageTag "Shade/Image"
3804
cristyc4c8d132010-01-07 01:58:38 +00003805 CacheView
3806 *image_view,
3807 *shade_view;
3808
cristy3ed852e2009-09-05 21:47:34 +00003809 Image
3810 *shade_image;
3811
cristy3ed852e2009-09-05 21:47:34 +00003812 MagickBooleanType
3813 status;
3814
cristybb503372010-05-27 20:51:26 +00003815 MagickOffsetType
3816 progress;
3817
cristy3ed852e2009-09-05 21:47:34 +00003818 PrimaryInfo
3819 light;
3820
cristybb503372010-05-27 20:51:26 +00003821 ssize_t
3822 y;
3823
cristy3ed852e2009-09-05 21:47:34 +00003824 /*
3825 Initialize shaded image attributes.
3826 */
3827 assert(image != (const Image *) NULL);
3828 assert(image->signature == MagickSignature);
3829 if (image->debug != MagickFalse)
3830 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3831 assert(exception != (ExceptionInfo *) NULL);
3832 assert(exception->signature == MagickSignature);
3833 shade_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
3834 if (shade_image == (Image *) NULL)
3835 return((Image *) NULL);
3836 if (SetImageStorageClass(shade_image,DirectClass) == MagickFalse)
3837 {
3838 InheritException(exception,&shade_image->exception);
3839 shade_image=DestroyImage(shade_image);
3840 return((Image *) NULL);
3841 }
3842 /*
3843 Compute the light vector.
3844 */
3845 light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))*
3846 cos(DegreesToRadians(elevation));
3847 light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))*
3848 cos(DegreesToRadians(elevation));
3849 light.z=(double) QuantumRange*sin(DegreesToRadians(elevation));
3850 /*
3851 Shade image.
3852 */
3853 status=MagickTrue;
3854 progress=0;
3855 image_view=AcquireCacheView(image);
3856 shade_view=AcquireCacheView(shade_image);
cristyb5d5f722009-11-04 03:03:49 +00003857#if defined(MAGICKCORE_OPENMP_SUPPORT)
3858 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003859#endif
cristybb503372010-05-27 20:51:26 +00003860 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003861 {
3862 MagickRealType
3863 distance,
3864 normal_distance,
3865 shade;
3866
3867 PrimaryInfo
3868 normal;
3869
cristy4c08aed2011-07-01 19:47:50 +00003870 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003871 *restrict p,
3872 *restrict s0,
3873 *restrict s1,
3874 *restrict s2;
cristy3ed852e2009-09-05 21:47:34 +00003875
cristy4c08aed2011-07-01 19:47:50 +00003876 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003877 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003878
cristy117ff172010-08-15 21:35:32 +00003879 register ssize_t
3880 x;
3881
cristy3ed852e2009-09-05 21:47:34 +00003882 if (status == MagickFalse)
3883 continue;
3884 p=GetCacheViewVirtualPixels(image_view,-1,y-1,image->columns+2,3,exception);
3885 q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1,
3886 exception);
cristy4c08aed2011-07-01 19:47:50 +00003887 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003888 {
3889 status=MagickFalse;
3890 continue;
3891 }
3892 /*
3893 Shade this row of pixels.
3894 */
3895 normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */
cristydcfc1ad2011-07-07 16:25:41 +00003896 s0=p+GetPixelComponents(image);
3897 s1=s0+(image->columns+2)*GetPixelComponents(image);
3898 s2=s1+(image->columns+2)*GetPixelComponents(image);
cristybb503372010-05-27 20:51:26 +00003899 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003900 {
3901 /*
3902 Determine the surface normal and compute shading.
3903 */
cristydcfc1ad2011-07-07 16:25:41 +00003904 normal.x=(double) (GetPixelIntensity(image,s0-GetPixelComponents(image))+
3905 GetPixelIntensity(image,s1-GetPixelComponents(image))+
3906 GetPixelIntensity(image,s2-GetPixelComponents(image))-
3907 GetPixelIntensity(image,s0+GetPixelComponents(image))-
3908 GetPixelIntensity(image,s1+GetPixelComponents(image))-
3909 GetPixelIntensity(image,s2+GetPixelComponents(image)));
3910 normal.y=(double) (GetPixelIntensity(image,s2-GetPixelComponents(image))+
cristy4c08aed2011-07-01 19:47:50 +00003911 GetPixelIntensity(image,s2)+
cristydcfc1ad2011-07-07 16:25:41 +00003912 GetPixelIntensity(image,s2+GetPixelComponents(image))-
3913 GetPixelIntensity(image,s0-GetPixelComponents(image))-
cristy4c08aed2011-07-01 19:47:50 +00003914 GetPixelIntensity(image,s0)-
cristydcfc1ad2011-07-07 16:25:41 +00003915 GetPixelIntensity(image,s0+GetPixelComponents(image)));
cristy3ed852e2009-09-05 21:47:34 +00003916 if ((normal.x == 0.0) && (normal.y == 0.0))
3917 shade=light.z;
3918 else
3919 {
3920 shade=0.0;
3921 distance=normal.x*light.x+normal.y*light.y+normal.z*light.z;
3922 if (distance > MagickEpsilon)
3923 {
3924 normal_distance=
3925 normal.x*normal.x+normal.y*normal.y+normal.z*normal.z;
3926 if (normal_distance > (MagickEpsilon*MagickEpsilon))
3927 shade=distance/sqrt((double) normal_distance);
3928 }
3929 }
3930 if (gray != MagickFalse)
3931 {
cristy4c08aed2011-07-01 19:47:50 +00003932 SetPixelRed(shade_image,ClampToQuantum(shade),q);
3933 SetPixelGreen(shade_image,ClampToQuantum(shade),q);
3934 SetPixelBlue(shade_image,ClampToQuantum(shade),q);
cristy3ed852e2009-09-05 21:47:34 +00003935 }
3936 else
3937 {
cristy4c08aed2011-07-01 19:47:50 +00003938 SetPixelRed(shade_image,ClampToQuantum(QuantumScale*shade*
3939 GetPixelRed(image,s1)),q);
3940 SetPixelGreen(shade_image,ClampToQuantum(QuantumScale*shade*
3941 GetPixelGreen(image,s1)),q);
3942 SetPixelBlue(shade_image,ClampToQuantum(QuantumScale*shade*
3943 GetPixelBlue(image,s1)),q);
cristy3ed852e2009-09-05 21:47:34 +00003944 }
cristy4c08aed2011-07-01 19:47:50 +00003945 SetPixelAlpha(shade_image,GetPixelAlpha(image,s1),q);
cristydcfc1ad2011-07-07 16:25:41 +00003946 s0+=GetPixelComponents(image);
3947 s1+=GetPixelComponents(image);
3948 s2+=GetPixelComponents(image);
3949 q+=GetPixelComponents(shade_image);
cristy3ed852e2009-09-05 21:47:34 +00003950 }
3951 if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
3952 status=MagickFalse;
3953 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3954 {
3955 MagickBooleanType
3956 proceed;
3957
cristyb5d5f722009-11-04 03:03:49 +00003958#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003959 #pragma omp critical (MagickCore_ShadeImage)
3960#endif
3961 proceed=SetImageProgress(image,ShadeImageTag,progress++,image->rows);
3962 if (proceed == MagickFalse)
3963 status=MagickFalse;
3964 }
3965 }
3966 shade_view=DestroyCacheView(shade_view);
3967 image_view=DestroyCacheView(image_view);
3968 if (status == MagickFalse)
3969 shade_image=DestroyImage(shade_image);
3970 return(shade_image);
3971}
3972
3973/*
3974%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3975% %
3976% %
3977% %
3978% S h a r p e n I m a g e %
3979% %
3980% %
3981% %
3982%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3983%
3984% SharpenImage() sharpens the image. We convolve the image with a Gaussian
3985% operator of the given radius and standard deviation (sigma). For
3986% reasonable results, radius should be larger than sigma. Use a radius of 0
3987% and SharpenImage() selects a suitable radius for you.
3988%
3989% Using a separable kernel would be faster, but the negative weights cancel
3990% out on the corners of the kernel producing often undesirable ringing in the
3991% filtered result; this can be avoided by using a 2D gaussian shaped image
3992% sharpening kernel instead.
3993%
3994% The format of the SharpenImage method is:
3995%
3996% Image *SharpenImage(const Image *image,const double radius,
3997% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003998%
3999% A description of each parameter follows:
4000%
4001% o image: the image.
4002%
cristy3ed852e2009-09-05 21:47:34 +00004003% o radius: the radius of the Gaussian, in pixels, not counting the center
4004% pixel.
4005%
4006% o sigma: the standard deviation of the Laplacian, in pixels.
4007%
4008% o exception: return any errors or warnings in this structure.
4009%
4010*/
cristy3ed852e2009-09-05 21:47:34 +00004011MagickExport Image *SharpenImage(const Image *image,const double radius,
4012 const double sigma,ExceptionInfo *exception)
4013{
cristy3ed852e2009-09-05 21:47:34 +00004014 double
cristy47e00502009-12-17 19:19:57 +00004015 *kernel,
4016 normalize;
cristy3ed852e2009-09-05 21:47:34 +00004017
4018 Image
4019 *sharp_image;
4020
cristybb503372010-05-27 20:51:26 +00004021 register ssize_t
cristy47e00502009-12-17 19:19:57 +00004022 i;
4023
cristybb503372010-05-27 20:51:26 +00004024 size_t
cristy3ed852e2009-09-05 21:47:34 +00004025 width;
4026
cristy117ff172010-08-15 21:35:32 +00004027 ssize_t
4028 j,
4029 u,
4030 v;
4031
cristy3ed852e2009-09-05 21:47:34 +00004032 assert(image != (const Image *) NULL);
4033 assert(image->signature == MagickSignature);
4034 if (image->debug != MagickFalse)
4035 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4036 assert(exception != (ExceptionInfo *) NULL);
4037 assert(exception->signature == MagickSignature);
4038 width=GetOptimalKernelWidth2D(radius,sigma);
4039 kernel=(double *) AcquireQuantumMemory((size_t) width*width,sizeof(*kernel));
4040 if (kernel == (double *) NULL)
4041 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy3ed852e2009-09-05 21:47:34 +00004042 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00004043 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +00004044 i=0;
4045 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00004046 {
cristy47e00502009-12-17 19:19:57 +00004047 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00004048 {
cristy4205a3c2010-09-12 20:19:59 +00004049 kernel[i]=(double) (-exp(-((double) u*u+v*v)/(2.0*MagickSigma*
4050 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00004051 normalize+=kernel[i];
4052 i++;
4053 }
4054 }
4055 kernel[i/2]=(double) ((-2.0)*normalize);
cristyf4ad9df2011-07-08 16:49:03 +00004056 sharp_image=ConvolveImage(image,width,kernel,exception);
cristy3ed852e2009-09-05 21:47:34 +00004057 kernel=(double *) RelinquishMagickMemory(kernel);
4058 return(sharp_image);
4059}
4060
4061/*
4062%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4063% %
4064% %
4065% %
4066% S p r e a d I m a g e %
4067% %
4068% %
4069% %
4070%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4071%
4072% SpreadImage() is a special effects method that randomly displaces each
4073% pixel in a block defined by the radius parameter.
4074%
4075% The format of the SpreadImage method is:
4076%
4077% Image *SpreadImage(const Image *image,const double radius,
4078% ExceptionInfo *exception)
4079%
4080% A description of each parameter follows:
4081%
4082% o image: the image.
4083%
4084% o radius: Choose a random pixel in a neighborhood of this extent.
4085%
4086% o exception: return any errors or warnings in this structure.
4087%
4088*/
4089MagickExport Image *SpreadImage(const Image *image,const double radius,
4090 ExceptionInfo *exception)
4091{
4092#define SpreadImageTag "Spread/Image"
4093
cristyfa112112010-01-04 17:48:07 +00004094 CacheView
cristy9f7e7cb2011-03-26 00:49:57 +00004095 *image_view,
4096 *spread_view;
cristyfa112112010-01-04 17:48:07 +00004097
cristy3ed852e2009-09-05 21:47:34 +00004098 Image
4099 *spread_image;
4100
cristy3ed852e2009-09-05 21:47:34 +00004101 MagickBooleanType
4102 status;
4103
cristybb503372010-05-27 20:51:26 +00004104 MagickOffsetType
4105 progress;
4106
cristy4c08aed2011-07-01 19:47:50 +00004107 PixelInfo
cristyddd82202009-11-03 20:14:50 +00004108 bias;
cristy3ed852e2009-09-05 21:47:34 +00004109
4110 RandomInfo
cristyfa112112010-01-04 17:48:07 +00004111 **restrict random_info;
cristy3ed852e2009-09-05 21:47:34 +00004112
cristybb503372010-05-27 20:51:26 +00004113 size_t
cristy3ed852e2009-09-05 21:47:34 +00004114 width;
4115
cristybb503372010-05-27 20:51:26 +00004116 ssize_t
4117 y;
4118
cristy3ed852e2009-09-05 21:47:34 +00004119 /*
4120 Initialize spread image attributes.
4121 */
4122 assert(image != (Image *) NULL);
4123 assert(image->signature == MagickSignature);
4124 if (image->debug != MagickFalse)
4125 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4126 assert(exception != (ExceptionInfo *) NULL);
4127 assert(exception->signature == MagickSignature);
4128 spread_image=CloneImage(image,image->columns,image->rows,MagickTrue,
4129 exception);
4130 if (spread_image == (Image *) NULL)
4131 return((Image *) NULL);
4132 if (SetImageStorageClass(spread_image,DirectClass) == MagickFalse)
4133 {
4134 InheritException(exception,&spread_image->exception);
4135 spread_image=DestroyImage(spread_image);
4136 return((Image *) NULL);
4137 }
4138 /*
4139 Spread image.
4140 */
4141 status=MagickTrue;
4142 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00004143 GetPixelInfo(spread_image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00004144 width=GetOptimalKernelWidth1D(radius,0.5);
cristy3ed852e2009-09-05 21:47:34 +00004145 random_info=AcquireRandomInfoThreadSet();
cristy9f7e7cb2011-03-26 00:49:57 +00004146 image_view=AcquireCacheView(image);
4147 spread_view=AcquireCacheView(spread_image);
cristyb557a152011-02-22 12:14:30 +00004148#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00004149 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00004150#endif
cristybb503372010-05-27 20:51:26 +00004151 for (y=0; y < (ssize_t) spread_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00004152 {
cristy5c9e6f22010-09-17 17:31:01 +00004153 const int
4154 id = GetOpenMPThreadId();
cristy6ebe97c2010-07-03 01:17:28 +00004155
cristy4c08aed2011-07-01 19:47:50 +00004156 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00004157 pixel;
4158
cristy4c08aed2011-07-01 19:47:50 +00004159 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00004160 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004161
cristy117ff172010-08-15 21:35:32 +00004162 register ssize_t
4163 x;
4164
cristy3ed852e2009-09-05 21:47:34 +00004165 if (status == MagickFalse)
4166 continue;
cristy9f7e7cb2011-03-26 00:49:57 +00004167 q=QueueCacheViewAuthenticPixels(spread_view,0,y,spread_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +00004168 exception);
cristy4c08aed2011-07-01 19:47:50 +00004169 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00004170 {
4171 status=MagickFalse;
4172 continue;
4173 }
cristyddd82202009-11-03 20:14:50 +00004174 pixel=bias;
cristybb503372010-05-27 20:51:26 +00004175 for (x=0; x < (ssize_t) spread_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004176 {
cristy4c08aed2011-07-01 19:47:50 +00004177 (void) InterpolatePixelInfo(image,image_view,
cristy8a7c3e82011-03-26 02:10:53 +00004178 UndefinedInterpolatePixel,(double) x+width*(GetPseudoRandomValue(
4179 random_info[id])-0.5),(double) y+width*(GetPseudoRandomValue(
4180 random_info[id])-0.5),&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00004181 SetPixelPixelInfo(spread_image,&pixel,q);
cristydcfc1ad2011-07-07 16:25:41 +00004182 q+=GetPixelComponents(spread_image);
cristy3ed852e2009-09-05 21:47:34 +00004183 }
cristy9f7e7cb2011-03-26 00:49:57 +00004184 if (SyncCacheViewAuthenticPixels(spread_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00004185 status=MagickFalse;
4186 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4187 {
4188 MagickBooleanType
4189 proceed;
4190
cristyb557a152011-02-22 12:14:30 +00004191#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004192 #pragma omp critical (MagickCore_SpreadImage)
4193#endif
4194 proceed=SetImageProgress(image,SpreadImageTag,progress++,image->rows);
4195 if (proceed == MagickFalse)
4196 status=MagickFalse;
4197 }
4198 }
cristy9f7e7cb2011-03-26 00:49:57 +00004199 spread_view=DestroyCacheView(spread_view);
cristy3ed852e2009-09-05 21:47:34 +00004200 image_view=DestroyCacheView(image_view);
4201 random_info=DestroyRandomInfoThreadSet(random_info);
cristy3ed852e2009-09-05 21:47:34 +00004202 return(spread_image);
4203}
4204
4205/*
4206%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4207% %
4208% %
4209% %
cristy0834d642011-03-18 18:26:08 +00004210% S t a t i s t i c I m a g e %
4211% %
4212% %
4213% %
4214%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4215%
4216% StatisticImage() makes each pixel the min / max / median / mode / etc. of
cristy8d752042011-03-19 01:00:36 +00004217% the neighborhood of the specified width and height.
cristy0834d642011-03-18 18:26:08 +00004218%
4219% The format of the StatisticImage method is:
4220%
4221% Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00004222% const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00004223%
4224% A description of each parameter follows:
4225%
4226% o image: the image.
4227%
cristy0834d642011-03-18 18:26:08 +00004228% o type: the statistic type (median, mode, etc.).
4229%
cristy95c38342011-03-18 22:39:51 +00004230% o width: the width of the pixel neighborhood.
4231%
4232% o height: the height of the pixel neighborhood.
cristy0834d642011-03-18 18:26:08 +00004233%
4234% o exception: return any errors or warnings in this structure.
4235%
4236*/
4237
cristy733678d2011-03-18 21:29:28 +00004238#define ListChannels 5
4239
4240typedef struct _ListNode
4241{
4242 size_t
4243 next[9],
4244 count,
4245 signature;
4246} ListNode;
4247
4248typedef struct _SkipList
4249{
4250 ssize_t
4251 level;
4252
4253 ListNode
4254 *nodes;
4255} SkipList;
4256
4257typedef struct _PixelList
4258{
4259 size_t
cristy6fc86bb2011-03-18 23:45:16 +00004260 length,
cristy733678d2011-03-18 21:29:28 +00004261 seed,
4262 signature;
4263
4264 SkipList
4265 lists[ListChannels];
4266} PixelList;
4267
4268static PixelList *DestroyPixelList(PixelList *pixel_list)
4269{
4270 register ssize_t
4271 i;
4272
4273 if (pixel_list == (PixelList *) NULL)
4274 return((PixelList *) NULL);
4275 for (i=0; i < ListChannels; i++)
4276 if (pixel_list->lists[i].nodes != (ListNode *) NULL)
4277 pixel_list->lists[i].nodes=(ListNode *) RelinquishMagickMemory(
4278 pixel_list->lists[i].nodes);
4279 pixel_list=(PixelList *) RelinquishMagickMemory(pixel_list);
4280 return(pixel_list);
4281}
4282
4283static PixelList **DestroyPixelListThreadSet(PixelList **pixel_list)
4284{
4285 register ssize_t
4286 i;
4287
4288 assert(pixel_list != (PixelList **) NULL);
4289 for (i=0; i < (ssize_t) GetOpenMPMaximumThreads(); i++)
4290 if (pixel_list[i] != (PixelList *) NULL)
4291 pixel_list[i]=DestroyPixelList(pixel_list[i]);
4292 pixel_list=(PixelList **) RelinquishMagickMemory(pixel_list);
4293 return(pixel_list);
4294}
4295
cristy6fc86bb2011-03-18 23:45:16 +00004296static PixelList *AcquirePixelList(const size_t width,const size_t height)
cristy733678d2011-03-18 21:29:28 +00004297{
4298 PixelList
4299 *pixel_list;
4300
4301 register ssize_t
4302 i;
4303
4304 pixel_list=(PixelList *) AcquireMagickMemory(sizeof(*pixel_list));
4305 if (pixel_list == (PixelList *) NULL)
4306 return(pixel_list);
4307 (void) ResetMagickMemory((void *) pixel_list,0,sizeof(*pixel_list));
cristy6fc86bb2011-03-18 23:45:16 +00004308 pixel_list->length=width*height;
cristy733678d2011-03-18 21:29:28 +00004309 for (i=0; i < ListChannels; i++)
4310 {
4311 pixel_list->lists[i].nodes=(ListNode *) AcquireQuantumMemory(65537UL,
4312 sizeof(*pixel_list->lists[i].nodes));
4313 if (pixel_list->lists[i].nodes == (ListNode *) NULL)
4314 return(DestroyPixelList(pixel_list));
4315 (void) ResetMagickMemory(pixel_list->lists[i].nodes,0,65537UL*
4316 sizeof(*pixel_list->lists[i].nodes));
4317 }
4318 pixel_list->signature=MagickSignature;
4319 return(pixel_list);
4320}
4321
cristy6fc86bb2011-03-18 23:45:16 +00004322static PixelList **AcquirePixelListThreadSet(const size_t width,
4323 const size_t height)
cristy733678d2011-03-18 21:29:28 +00004324{
4325 PixelList
4326 **pixel_list;
4327
4328 register ssize_t
4329 i;
4330
4331 size_t
4332 number_threads;
4333
4334 number_threads=GetOpenMPMaximumThreads();
4335 pixel_list=(PixelList **) AcquireQuantumMemory(number_threads,
4336 sizeof(*pixel_list));
4337 if (pixel_list == (PixelList **) NULL)
4338 return((PixelList **) NULL);
4339 (void) ResetMagickMemory(pixel_list,0,number_threads*sizeof(*pixel_list));
4340 for (i=0; i < (ssize_t) number_threads; i++)
4341 {
cristy6fc86bb2011-03-18 23:45:16 +00004342 pixel_list[i]=AcquirePixelList(width,height);
cristy733678d2011-03-18 21:29:28 +00004343 if (pixel_list[i] == (PixelList *) NULL)
4344 return(DestroyPixelListThreadSet(pixel_list));
4345 }
4346 return(pixel_list);
4347}
4348
4349static void AddNodePixelList(PixelList *pixel_list,const ssize_t channel,
4350 const size_t color)
4351{
4352 register SkipList
4353 *list;
4354
4355 register ssize_t
4356 level;
4357
4358 size_t
4359 search,
4360 update[9];
4361
4362 /*
4363 Initialize the node.
4364 */
4365 list=pixel_list->lists+channel;
4366 list->nodes[color].signature=pixel_list->signature;
4367 list->nodes[color].count=1;
4368 /*
4369 Determine where it belongs in the list.
4370 */
4371 search=65536UL;
4372 for (level=list->level; level >= 0; level--)
4373 {
4374 while (list->nodes[search].next[level] < color)
4375 search=list->nodes[search].next[level];
4376 update[level]=search;
4377 }
4378 /*
4379 Generate a pseudo-random level for this node.
4380 */
4381 for (level=0; ; level++)
4382 {
4383 pixel_list->seed=(pixel_list->seed*42893621L)+1L;
4384 if ((pixel_list->seed & 0x300) != 0x300)
4385 break;
4386 }
4387 if (level > 8)
4388 level=8;
4389 if (level > (list->level+2))
4390 level=list->level+2;
4391 /*
4392 If we're raising the list's level, link back to the root node.
4393 */
4394 while (level > list->level)
4395 {
4396 list->level++;
4397 update[list->level]=65536UL;
4398 }
4399 /*
4400 Link the node into the skip-list.
4401 */
4402 do
4403 {
4404 list->nodes[color].next[level]=list->nodes[update[level]].next[level];
4405 list->nodes[update[level]].next[level]=color;
cristy3cba8ca2011-03-19 01:29:12 +00004406 } while (level-- > 0);
cristy733678d2011-03-18 21:29:28 +00004407}
4408
cristy4c08aed2011-07-01 19:47:50 +00004409static PixelInfo GetMaximumPixelList(PixelList *pixel_list)
cristy6fc86bb2011-03-18 23:45:16 +00004410{
cristy4c08aed2011-07-01 19:47:50 +00004411 PixelInfo
cristy6fc86bb2011-03-18 23:45:16 +00004412 pixel;
4413
4414 register SkipList
4415 *list;
4416
4417 register ssize_t
4418 channel;
4419
4420 size_t
cristyd76c51e2011-03-26 00:21:26 +00004421 color,
4422 maximum;
cristy49f37242011-03-22 18:18:23 +00004423
4424 ssize_t
cristy6fc86bb2011-03-18 23:45:16 +00004425 count;
4426
4427 unsigned short
cristyd76c51e2011-03-26 00:21:26 +00004428 channels[ListChannels];
cristy6fc86bb2011-03-18 23:45:16 +00004429
4430 /*
4431 Find the maximum value for each of the color.
4432 */
4433 for (channel=0; channel < 5; channel++)
4434 {
4435 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004436 color=65536L;
cristy6fc86bb2011-03-18 23:45:16 +00004437 count=0;
cristy49f37242011-03-22 18:18:23 +00004438 maximum=list->nodes[color].next[0];
cristy6fc86bb2011-03-18 23:45:16 +00004439 do
4440 {
4441 color=list->nodes[color].next[0];
cristy49f37242011-03-22 18:18:23 +00004442 if (color > maximum)
4443 maximum=color;
cristy6fc86bb2011-03-18 23:45:16 +00004444 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004445 } while (count < (ssize_t) pixel_list->length);
cristy49f37242011-03-22 18:18:23 +00004446 channels[channel]=(unsigned short) maximum;
4447 }
cristy4c08aed2011-07-01 19:47:50 +00004448 GetPixelInfo((const Image *) NULL,&pixel);
cristy49f37242011-03-22 18:18:23 +00004449 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4450 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4451 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004452 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4453 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy49f37242011-03-22 18:18:23 +00004454 return(pixel);
4455}
4456
cristy4c08aed2011-07-01 19:47:50 +00004457static PixelInfo GetMeanPixelList(PixelList *pixel_list)
cristy49f37242011-03-22 18:18:23 +00004458{
cristy4c08aed2011-07-01 19:47:50 +00004459 PixelInfo
cristy49f37242011-03-22 18:18:23 +00004460 pixel;
4461
cristy80a99a32011-03-30 01:30:23 +00004462 MagickRealType
4463 sum;
4464
cristy49f37242011-03-22 18:18:23 +00004465 register SkipList
4466 *list;
4467
4468 register ssize_t
4469 channel;
4470
4471 size_t
cristy80a99a32011-03-30 01:30:23 +00004472 color;
cristy49f37242011-03-22 18:18:23 +00004473
4474 ssize_t
4475 count;
4476
4477 unsigned short
4478 channels[ListChannels];
4479
4480 /*
4481 Find the mean value for each of the color.
4482 */
4483 for (channel=0; channel < 5; channel++)
4484 {
4485 list=pixel_list->lists+channel;
4486 color=65536L;
4487 count=0;
cristy80a99a32011-03-30 01:30:23 +00004488 sum=0.0;
cristy49f37242011-03-22 18:18:23 +00004489 do
4490 {
4491 color=list->nodes[color].next[0];
cristy80a99a32011-03-30 01:30:23 +00004492 sum+=(MagickRealType) list->nodes[color].count*color;
cristy49f37242011-03-22 18:18:23 +00004493 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004494 } while (count < (ssize_t) pixel_list->length);
cristy80a99a32011-03-30 01:30:23 +00004495 sum/=pixel_list->length;
4496 channels[channel]=(unsigned short) sum;
cristy6fc86bb2011-03-18 23:45:16 +00004497 }
cristy4c08aed2011-07-01 19:47:50 +00004498 GetPixelInfo((const Image *) NULL,&pixel);
cristy6fc86bb2011-03-18 23:45:16 +00004499 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4500 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4501 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004502 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4503 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy6fc86bb2011-03-18 23:45:16 +00004504 return(pixel);
4505}
4506
cristy4c08aed2011-07-01 19:47:50 +00004507static PixelInfo GetMedianPixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004508{
cristy4c08aed2011-07-01 19:47:50 +00004509 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004510 pixel;
4511
4512 register SkipList
4513 *list;
4514
4515 register ssize_t
4516 channel;
4517
4518 size_t
cristy49f37242011-03-22 18:18:23 +00004519 color;
4520
4521 ssize_t
cristy733678d2011-03-18 21:29:28 +00004522 count;
4523
4524 unsigned short
4525 channels[ListChannels];
4526
4527 /*
4528 Find the median value for each of the color.
4529 */
cristy733678d2011-03-18 21:29:28 +00004530 for (channel=0; channel < 5; channel++)
4531 {
4532 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004533 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004534 count=0;
4535 do
4536 {
4537 color=list->nodes[color].next[0];
4538 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004539 } while (count <= (ssize_t) (pixel_list->length >> 1));
cristy6fc86bb2011-03-18 23:45:16 +00004540 channels[channel]=(unsigned short) color;
4541 }
cristy4c08aed2011-07-01 19:47:50 +00004542 GetPixelInfo((const Image *) NULL,&pixel);
cristy6fc86bb2011-03-18 23:45:16 +00004543 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4544 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4545 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004546 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4547 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy6fc86bb2011-03-18 23:45:16 +00004548 return(pixel);
4549}
4550
cristy4c08aed2011-07-01 19:47:50 +00004551static PixelInfo GetMinimumPixelList(PixelList *pixel_list)
cristy6fc86bb2011-03-18 23:45:16 +00004552{
cristy4c08aed2011-07-01 19:47:50 +00004553 PixelInfo
cristy6fc86bb2011-03-18 23:45:16 +00004554 pixel;
4555
4556 register SkipList
4557 *list;
4558
4559 register ssize_t
4560 channel;
4561
4562 size_t
cristyd76c51e2011-03-26 00:21:26 +00004563 color,
4564 minimum;
cristy6fc86bb2011-03-18 23:45:16 +00004565
cristy49f37242011-03-22 18:18:23 +00004566 ssize_t
4567 count;
4568
cristy6fc86bb2011-03-18 23:45:16 +00004569 unsigned short
cristyd76c51e2011-03-26 00:21:26 +00004570 channels[ListChannels];
cristy6fc86bb2011-03-18 23:45:16 +00004571
4572 /*
4573 Find the minimum value for each of the color.
4574 */
4575 for (channel=0; channel < 5; channel++)
4576 {
4577 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004578 count=0;
cristy6fc86bb2011-03-18 23:45:16 +00004579 color=65536UL;
cristy49f37242011-03-22 18:18:23 +00004580 minimum=list->nodes[color].next[0];
4581 do
4582 {
4583 color=list->nodes[color].next[0];
4584 if (color < minimum)
4585 minimum=color;
4586 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004587 } while (count < (ssize_t) pixel_list->length);
cristy49f37242011-03-22 18:18:23 +00004588 channels[channel]=(unsigned short) minimum;
cristy733678d2011-03-18 21:29:28 +00004589 }
cristy4c08aed2011-07-01 19:47:50 +00004590 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004591 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4592 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4593 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004594 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4595 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy733678d2011-03-18 21:29:28 +00004596 return(pixel);
4597}
4598
cristy4c08aed2011-07-01 19:47:50 +00004599static PixelInfo GetModePixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004600{
cristy4c08aed2011-07-01 19:47:50 +00004601 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004602 pixel;
4603
4604 register SkipList
4605 *list;
4606
4607 register ssize_t
4608 channel;
4609
4610 size_t
4611 color,
cristy733678d2011-03-18 21:29:28 +00004612 max_count,
cristy6fc86bb2011-03-18 23:45:16 +00004613 mode;
cristy733678d2011-03-18 21:29:28 +00004614
cristy49f37242011-03-22 18:18:23 +00004615 ssize_t
4616 count;
4617
cristy733678d2011-03-18 21:29:28 +00004618 unsigned short
4619 channels[5];
4620
4621 /*
glennrp30d2dc62011-06-25 03:17:16 +00004622 Make each pixel the 'predominant color' of the specified neighborhood.
cristy733678d2011-03-18 21:29:28 +00004623 */
cristy733678d2011-03-18 21:29:28 +00004624 for (channel=0; channel < 5; channel++)
4625 {
4626 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004627 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004628 mode=color;
4629 max_count=list->nodes[mode].count;
4630 count=0;
4631 do
4632 {
4633 color=list->nodes[color].next[0];
4634 if (list->nodes[color].count > max_count)
4635 {
4636 mode=color;
4637 max_count=list->nodes[mode].count;
4638 }
4639 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004640 } while (count < (ssize_t) pixel_list->length);
cristy733678d2011-03-18 21:29:28 +00004641 channels[channel]=(unsigned short) mode;
4642 }
cristy4c08aed2011-07-01 19:47:50 +00004643 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004644 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4645 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4646 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004647 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4648 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy733678d2011-03-18 21:29:28 +00004649 return(pixel);
4650}
4651
cristy4c08aed2011-07-01 19:47:50 +00004652static PixelInfo GetNonpeakPixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004653{
cristy4c08aed2011-07-01 19:47:50 +00004654 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004655 pixel;
4656
4657 register SkipList
4658 *list;
4659
4660 register ssize_t
4661 channel;
4662
4663 size_t
cristy733678d2011-03-18 21:29:28 +00004664 color,
cristy733678d2011-03-18 21:29:28 +00004665 next,
4666 previous;
4667
cristy49f37242011-03-22 18:18:23 +00004668 ssize_t
4669 count;
4670
cristy733678d2011-03-18 21:29:28 +00004671 unsigned short
4672 channels[5];
4673
4674 /*
cristy49f37242011-03-22 18:18:23 +00004675 Finds the non peak value for each of the colors.
cristy733678d2011-03-18 21:29:28 +00004676 */
cristy733678d2011-03-18 21:29:28 +00004677 for (channel=0; channel < 5; channel++)
4678 {
4679 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004680 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004681 next=list->nodes[color].next[0];
4682 count=0;
4683 do
4684 {
4685 previous=color;
4686 color=next;
4687 next=list->nodes[color].next[0];
4688 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004689 } while (count <= (ssize_t) (pixel_list->length >> 1));
cristy733678d2011-03-18 21:29:28 +00004690 if ((previous == 65536UL) && (next != 65536UL))
4691 color=next;
4692 else
4693 if ((previous != 65536UL) && (next == 65536UL))
4694 color=previous;
4695 channels[channel]=(unsigned short) color;
4696 }
cristy4c08aed2011-07-01 19:47:50 +00004697 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004698 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4699 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4700 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004701 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4702 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy733678d2011-03-18 21:29:28 +00004703 return(pixel);
4704}
4705
cristy4c08aed2011-07-01 19:47:50 +00004706static PixelInfo GetStandardDeviationPixelList(PixelList *pixel_list)
cristy9a68cbb2011-03-29 00:51:23 +00004707{
cristy4c08aed2011-07-01 19:47:50 +00004708 PixelInfo
cristy9a68cbb2011-03-29 00:51:23 +00004709 pixel;
4710
cristy80a99a32011-03-30 01:30:23 +00004711 MagickRealType
4712 sum,
4713 sum_squared;
4714
cristy9a68cbb2011-03-29 00:51:23 +00004715 register SkipList
4716 *list;
4717
4718 register ssize_t
4719 channel;
4720
4721 size_t
cristy80a99a32011-03-30 01:30:23 +00004722 color;
cristy9a68cbb2011-03-29 00:51:23 +00004723
4724 ssize_t
4725 count;
4726
4727 unsigned short
4728 channels[ListChannels];
4729
4730 /*
cristy80a99a32011-03-30 01:30:23 +00004731 Find the standard-deviation value for each of the color.
cristy9a68cbb2011-03-29 00:51:23 +00004732 */
4733 for (channel=0; channel < 5; channel++)
4734 {
4735 list=pixel_list->lists+channel;
4736 color=65536L;
4737 count=0;
cristy80a99a32011-03-30 01:30:23 +00004738 sum=0.0;
4739 sum_squared=0.0;
cristy9a68cbb2011-03-29 00:51:23 +00004740 do
4741 {
cristy80a99a32011-03-30 01:30:23 +00004742 register ssize_t
4743 i;
4744
cristy9a68cbb2011-03-29 00:51:23 +00004745 color=list->nodes[color].next[0];
cristy80a99a32011-03-30 01:30:23 +00004746 sum+=(MagickRealType) list->nodes[color].count*color;
4747 for (i=0; i < (ssize_t) list->nodes[color].count; i++)
4748 sum_squared+=((MagickRealType) color)*((MagickRealType) color);
cristy9a68cbb2011-03-29 00:51:23 +00004749 count+=list->nodes[color].count;
4750 } while (count < (ssize_t) pixel_list->length);
cristy80a99a32011-03-30 01:30:23 +00004751 sum/=pixel_list->length;
4752 sum_squared/=pixel_list->length;
4753 channels[channel]=(unsigned short) sqrt(sum_squared-(sum*sum));
cristy9a68cbb2011-03-29 00:51:23 +00004754 }
cristy4c08aed2011-07-01 19:47:50 +00004755 GetPixelInfo((const Image *) NULL,&pixel);
cristy9a68cbb2011-03-29 00:51:23 +00004756 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4757 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4758 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004759 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4760 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy9a68cbb2011-03-29 00:51:23 +00004761 return(pixel);
4762}
4763
cristy4c08aed2011-07-01 19:47:50 +00004764static inline void InsertPixelList(const Image *image,const Quantum *pixel,
4765 PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004766{
4767 size_t
4768 signature;
4769
4770 unsigned short
4771 index;
4772
cristy4c08aed2011-07-01 19:47:50 +00004773 index=ScaleQuantumToShort(GetPixelRed(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004774 signature=pixel_list->lists[0].nodes[index].signature;
4775 if (signature == pixel_list->signature)
4776 pixel_list->lists[0].nodes[index].count++;
4777 else
4778 AddNodePixelList(pixel_list,0,index);
cristy4c08aed2011-07-01 19:47:50 +00004779 index=ScaleQuantumToShort(GetPixelGreen(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004780 signature=pixel_list->lists[1].nodes[index].signature;
4781 if (signature == pixel_list->signature)
4782 pixel_list->lists[1].nodes[index].count++;
4783 else
4784 AddNodePixelList(pixel_list,1,index);
cristy4c08aed2011-07-01 19:47:50 +00004785 index=ScaleQuantumToShort(GetPixelBlue(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004786 signature=pixel_list->lists[2].nodes[index].signature;
4787 if (signature == pixel_list->signature)
4788 pixel_list->lists[2].nodes[index].count++;
4789 else
4790 AddNodePixelList(pixel_list,2,index);
cristy4c08aed2011-07-01 19:47:50 +00004791 index=ScaleQuantumToShort(GetPixelAlpha(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004792 signature=pixel_list->lists[3].nodes[index].signature;
4793 if (signature == pixel_list->signature)
4794 pixel_list->lists[3].nodes[index].count++;
4795 else
4796 AddNodePixelList(pixel_list,3,index);
4797 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00004798 index=ScaleQuantumToShort(GetPixelBlack(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004799 signature=pixel_list->lists[4].nodes[index].signature;
4800 if (signature == pixel_list->signature)
4801 pixel_list->lists[4].nodes[index].count++;
4802 else
4803 AddNodePixelList(pixel_list,4,index);
4804}
4805
cristy80c99742011-04-04 14:46:39 +00004806static inline MagickRealType MagickAbsoluteValue(const MagickRealType x)
4807{
4808 if (x < 0)
4809 return(-x);
4810 return(x);
4811}
4812
cristy733678d2011-03-18 21:29:28 +00004813static void ResetPixelList(PixelList *pixel_list)
4814{
4815 int
4816 level;
4817
4818 register ListNode
4819 *root;
4820
4821 register SkipList
4822 *list;
4823
4824 register ssize_t
4825 channel;
4826
4827 /*
4828 Reset the skip-list.
4829 */
4830 for (channel=0; channel < 5; channel++)
4831 {
4832 list=pixel_list->lists+channel;
4833 root=list->nodes+65536UL;
4834 list->level=0;
4835 for (level=0; level < 9; level++)
4836 root->next[level]=65536UL;
4837 }
4838 pixel_list->seed=pixel_list->signature++;
4839}
4840
cristy0834d642011-03-18 18:26:08 +00004841MagickExport Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00004842 const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00004843{
cristy3cba8ca2011-03-19 01:29:12 +00004844#define StatisticWidth \
cristyd76c51e2011-03-26 00:21:26 +00004845 (width == 0 ? GetOptimalKernelWidth2D((double) width,0.5) : width)
cristy3cba8ca2011-03-19 01:29:12 +00004846#define StatisticHeight \
cristyd76c51e2011-03-26 00:21:26 +00004847 (height == 0 ? GetOptimalKernelWidth2D((double) height,0.5) : height)
cristy0834d642011-03-18 18:26:08 +00004848#define StatisticImageTag "Statistic/Image"
4849
4850 CacheView
4851 *image_view,
4852 *statistic_view;
4853
4854 Image
4855 *statistic_image;
4856
4857 MagickBooleanType
4858 status;
4859
4860 MagickOffsetType
4861 progress;
4862
4863 PixelList
4864 **restrict pixel_list;
4865
cristy0834d642011-03-18 18:26:08 +00004866 ssize_t
4867 y;
4868
4869 /*
4870 Initialize statistics image attributes.
4871 */
4872 assert(image != (Image *) NULL);
4873 assert(image->signature == MagickSignature);
4874 if (image->debug != MagickFalse)
4875 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4876 assert(exception != (ExceptionInfo *) NULL);
4877 assert(exception->signature == MagickSignature);
cristy0834d642011-03-18 18:26:08 +00004878 statistic_image=CloneImage(image,image->columns,image->rows,MagickTrue,
4879 exception);
4880 if (statistic_image == (Image *) NULL)
4881 return((Image *) NULL);
4882 if (SetImageStorageClass(statistic_image,DirectClass) == MagickFalse)
4883 {
4884 InheritException(exception,&statistic_image->exception);
4885 statistic_image=DestroyImage(statistic_image);
4886 return((Image *) NULL);
4887 }
cristy6fc86bb2011-03-18 23:45:16 +00004888 pixel_list=AcquirePixelListThreadSet(StatisticWidth,StatisticHeight);
cristy0834d642011-03-18 18:26:08 +00004889 if (pixel_list == (PixelList **) NULL)
4890 {
4891 statistic_image=DestroyImage(statistic_image);
4892 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
4893 }
4894 /*
cristy8d752042011-03-19 01:00:36 +00004895 Make each pixel the min / max / median / mode / etc. of the neighborhood.
cristy0834d642011-03-18 18:26:08 +00004896 */
4897 status=MagickTrue;
4898 progress=0;
4899 image_view=AcquireCacheView(image);
4900 statistic_view=AcquireCacheView(statistic_image);
4901#if defined(MAGICKCORE_OPENMP_SUPPORT)
4902 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
4903#endif
4904 for (y=0; y < (ssize_t) statistic_image->rows; y++)
4905 {
4906 const int
4907 id = GetOpenMPThreadId();
4908
cristy4c08aed2011-07-01 19:47:50 +00004909 register const Quantum
cristy0834d642011-03-18 18:26:08 +00004910 *restrict p;
4911
cristy4c08aed2011-07-01 19:47:50 +00004912 register Quantum
cristy0834d642011-03-18 18:26:08 +00004913 *restrict q;
4914
4915 register ssize_t
4916 x;
4917
4918 if (status == MagickFalse)
4919 continue;
cristy6fc86bb2011-03-18 23:45:16 +00004920 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) StatisticWidth/2L),y-
4921 (ssize_t) (StatisticHeight/2L),image->columns+StatisticWidth,
4922 StatisticHeight,exception);
cristy3cba8ca2011-03-19 01:29:12 +00004923 q=QueueCacheViewAuthenticPixels(statistic_view,0,y,statistic_image->columns, 1,exception);
cristy4c08aed2011-07-01 19:47:50 +00004924 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy0834d642011-03-18 18:26:08 +00004925 {
4926 status=MagickFalse;
4927 continue;
4928 }
cristy0834d642011-03-18 18:26:08 +00004929 for (x=0; x < (ssize_t) statistic_image->columns; x++)
4930 {
cristy4c08aed2011-07-01 19:47:50 +00004931 PixelInfo
cristy0834d642011-03-18 18:26:08 +00004932 pixel;
4933
cristy4c08aed2011-07-01 19:47:50 +00004934 register const Quantum
cristy6e3026a2011-03-19 00:54:38 +00004935 *restrict r;
4936
cristy0834d642011-03-18 18:26:08 +00004937 register ssize_t
4938 u,
4939 v;
4940
4941 r=p;
cristy0834d642011-03-18 18:26:08 +00004942 ResetPixelList(pixel_list[id]);
cristy6e4c3292011-03-19 00:53:55 +00004943 for (v=0; v < (ssize_t) StatisticHeight; v++)
cristy0834d642011-03-18 18:26:08 +00004944 {
cristy6e4c3292011-03-19 00:53:55 +00004945 for (u=0; u < (ssize_t) StatisticWidth; u++)
cristydcfc1ad2011-07-07 16:25:41 +00004946 InsertPixelList(image,r+u*GetPixelComponents(image),pixel_list[id]);
4947 r+=(image->columns+StatisticWidth)*GetPixelComponents(image);
cristy0834d642011-03-18 18:26:08 +00004948 }
cristy4c08aed2011-07-01 19:47:50 +00004949 GetPixelInfo(image,&pixel);
cristy490408a2011-07-07 14:42:05 +00004950 SetPixelInfo(image,p+(StatisticWidth*StatisticHeight/2)*
cristydcfc1ad2011-07-07 16:25:41 +00004951 GetPixelComponents(image),&pixel);
cristy0834d642011-03-18 18:26:08 +00004952 switch (type)
4953 {
cristy80c99742011-04-04 14:46:39 +00004954 case GradientStatistic:
4955 {
cristy4c08aed2011-07-01 19:47:50 +00004956 PixelInfo
cristy80c99742011-04-04 14:46:39 +00004957 maximum,
4958 minimum;
4959
4960 minimum=GetMinimumPixelList(pixel_list[id]);
4961 maximum=GetMaximumPixelList(pixel_list[id]);
4962 pixel.red=MagickAbsoluteValue(maximum.red-minimum.red);
4963 pixel.green=MagickAbsoluteValue(maximum.green-minimum.green);
4964 pixel.blue=MagickAbsoluteValue(maximum.blue-minimum.blue);
cristy4c08aed2011-07-01 19:47:50 +00004965 pixel.alpha=MagickAbsoluteValue(maximum.alpha-minimum.alpha);
cristy80c99742011-04-04 14:46:39 +00004966 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00004967 pixel.black=MagickAbsoluteValue(maximum.black-minimum.black);
cristy80c99742011-04-04 14:46:39 +00004968 break;
4969 }
cristy6fc86bb2011-03-18 23:45:16 +00004970 case MaximumStatistic:
4971 {
4972 pixel=GetMaximumPixelList(pixel_list[id]);
4973 break;
4974 }
cristy49f37242011-03-22 18:18:23 +00004975 case MeanStatistic:
4976 {
4977 pixel=GetMeanPixelList(pixel_list[id]);
4978 break;
4979 }
cristyf2ad14a2011-03-18 18:57:25 +00004980 case MedianStatistic:
cristy6fc86bb2011-03-18 23:45:16 +00004981 default:
cristyf2ad14a2011-03-18 18:57:25 +00004982 {
4983 pixel=GetMedianPixelList(pixel_list[id]);
4984 break;
4985 }
cristy6fc86bb2011-03-18 23:45:16 +00004986 case MinimumStatistic:
4987 {
4988 pixel=GetMinimumPixelList(pixel_list[id]);
4989 break;
4990 }
cristyf2ad14a2011-03-18 18:57:25 +00004991 case ModeStatistic:
4992 {
4993 pixel=GetModePixelList(pixel_list[id]);
4994 break;
4995 }
4996 case NonpeakStatistic:
4997 {
4998 pixel=GetNonpeakPixelList(pixel_list[id]);
4999 break;
5000 }
cristy9a68cbb2011-03-29 00:51:23 +00005001 case StandardDeviationStatistic:
5002 {
5003 pixel=GetStandardDeviationPixelList(pixel_list[id]);
5004 break;
5005 }
cristy0834d642011-03-18 18:26:08 +00005006 }
cristy2b9582a2011-07-04 17:38:56 +00005007 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00005008 SetPixelRed(statistic_image,ClampToQuantum(pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00005009 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00005010 SetPixelGreen(statistic_image,ClampToQuantum(pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00005011 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00005012 SetPixelBlue(statistic_image,ClampToQuantum(pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00005013 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy0834d642011-03-18 18:26:08 +00005014 (image->colorspace == CMYKColorspace))
cristy490408a2011-07-07 14:42:05 +00005015 SetPixelBlack(statistic_image,ClampToQuantum(pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00005016 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00005017 (image->matte != MagickFalse))
cristy490408a2011-07-07 14:42:05 +00005018 SetPixelAlpha(statistic_image,ClampToQuantum(pixel.alpha),q);
cristydcfc1ad2011-07-07 16:25:41 +00005019 p+=GetPixelComponents(image);
5020 q+=GetPixelComponents(statistic_image);
cristy0834d642011-03-18 18:26:08 +00005021 }
5022 if (SyncCacheViewAuthenticPixels(statistic_view,exception) == MagickFalse)
5023 status=MagickFalse;
5024 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5025 {
5026 MagickBooleanType
5027 proceed;
5028
5029#if defined(MAGICKCORE_OPENMP_SUPPORT)
5030 #pragma omp critical (MagickCore_StatisticImage)
5031#endif
5032 proceed=SetImageProgress(image,StatisticImageTag,progress++,
5033 image->rows);
5034 if (proceed == MagickFalse)
5035 status=MagickFalse;
5036 }
5037 }
5038 statistic_view=DestroyCacheView(statistic_view);
5039 image_view=DestroyCacheView(image_view);
5040 pixel_list=DestroyPixelListThreadSet(pixel_list);
5041 return(statistic_image);
5042}
5043
5044/*
5045%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5046% %
5047% %
5048% %
cristy3ed852e2009-09-05 21:47:34 +00005049% U n s h a r p M a s k I m a g e %
5050% %
5051% %
5052% %
5053%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5054%
5055% UnsharpMaskImage() sharpens one or more image channels. We convolve the
5056% image with a Gaussian operator of the given radius and standard deviation
5057% (sigma). For reasonable results, radius should be larger than sigma. Use a
5058% radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
5059%
5060% The format of the UnsharpMaskImage method is:
5061%
5062% Image *UnsharpMaskImage(const Image *image,const double radius,
5063% const double sigma,const double amount,const double threshold,
5064% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00005065%
5066% A description of each parameter follows:
5067%
5068% o image: the image.
5069%
cristy3ed852e2009-09-05 21:47:34 +00005070% o radius: the radius of the Gaussian, in pixels, not counting the center
5071% pixel.
5072%
5073% o sigma: the standard deviation of the Gaussian, in pixels.
5074%
5075% o amount: the percentage of the difference between the original and the
5076% blur image that is added back into the original.
5077%
5078% o threshold: the threshold in pixels needed to apply the diffence amount.
5079%
5080% o exception: return any errors or warnings in this structure.
5081%
5082*/
cristyf4ad9df2011-07-08 16:49:03 +00005083MagickExport Image *UnsharpMaskImage(const Image *image,
5084 const double radius,const double sigma,const double amount,
5085 const double threshold,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00005086{
5087#define SharpenImageTag "Sharpen/Image"
5088
cristyc4c8d132010-01-07 01:58:38 +00005089 CacheView
5090 *image_view,
5091 *unsharp_view;
5092
cristy3ed852e2009-09-05 21:47:34 +00005093 Image
5094 *unsharp_image;
5095
cristy3ed852e2009-09-05 21:47:34 +00005096 MagickBooleanType
5097 status;
5098
cristybb503372010-05-27 20:51:26 +00005099 MagickOffsetType
5100 progress;
5101
cristy4c08aed2011-07-01 19:47:50 +00005102 PixelInfo
cristyddd82202009-11-03 20:14:50 +00005103 bias;
cristy3ed852e2009-09-05 21:47:34 +00005104
5105 MagickRealType
5106 quantum_threshold;
5107
cristybb503372010-05-27 20:51:26 +00005108 ssize_t
5109 y;
5110
cristy3ed852e2009-09-05 21:47:34 +00005111 assert(image != (const Image *) NULL);
5112 assert(image->signature == MagickSignature);
5113 if (image->debug != MagickFalse)
5114 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5115 assert(exception != (ExceptionInfo *) NULL);
cristyf4ad9df2011-07-08 16:49:03 +00005116 unsharp_image=BlurImage(image,radius,sigma,exception);
cristy3ed852e2009-09-05 21:47:34 +00005117 if (unsharp_image == (Image *) NULL)
5118 return((Image *) NULL);
5119 quantum_threshold=(MagickRealType) QuantumRange*threshold;
5120 /*
5121 Unsharp-mask image.
5122 */
5123 status=MagickTrue;
5124 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00005125 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00005126 image_view=AcquireCacheView(image);
5127 unsharp_view=AcquireCacheView(unsharp_image);
cristyb5d5f722009-11-04 03:03:49 +00005128#if defined(MAGICKCORE_OPENMP_SUPPORT)
5129 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00005130#endif
cristybb503372010-05-27 20:51:26 +00005131 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00005132 {
cristy4c08aed2011-07-01 19:47:50 +00005133 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00005134 pixel;
5135
cristy4c08aed2011-07-01 19:47:50 +00005136 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00005137 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00005138
cristy4c08aed2011-07-01 19:47:50 +00005139 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00005140 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00005141
cristy117ff172010-08-15 21:35:32 +00005142 register ssize_t
5143 x;
5144
cristy3ed852e2009-09-05 21:47:34 +00005145 if (status == MagickFalse)
5146 continue;
5147 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
5148 q=GetCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
5149 exception);
cristy4c08aed2011-07-01 19:47:50 +00005150 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00005151 {
5152 status=MagickFalse;
5153 continue;
5154 }
cristyddd82202009-11-03 20:14:50 +00005155 pixel=bias;
cristybb503372010-05-27 20:51:26 +00005156 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00005157 {
cristy2b9582a2011-07-04 17:38:56 +00005158 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00005159 {
cristy4c08aed2011-07-01 19:47:50 +00005160 pixel.red=GetPixelRed(image,p)-(MagickRealType) GetPixelRed(image,q);
cristy3ed852e2009-09-05 21:47:34 +00005161 if (fabs(2.0*pixel.red) < quantum_threshold)
cristy4c08aed2011-07-01 19:47:50 +00005162 pixel.red=(MagickRealType) GetPixelRed(image,p);
cristy3ed852e2009-09-05 21:47:34 +00005163 else
cristy4c08aed2011-07-01 19:47:50 +00005164 pixel.red=(MagickRealType) GetPixelRed(image,p)+(pixel.red*amount);
5165 SetPixelRed(unsharp_image,ClampToQuantum(pixel.red),q);
cristy3ed852e2009-09-05 21:47:34 +00005166 }
cristy2b9582a2011-07-04 17:38:56 +00005167 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00005168 {
cristy4c08aed2011-07-01 19:47:50 +00005169 pixel.green=GetPixelGreen(image,p)-
5170 (MagickRealType) GetPixelGreen(image,q);
cristy3ed852e2009-09-05 21:47:34 +00005171 if (fabs(2.0*pixel.green) < quantum_threshold)
cristy4c08aed2011-07-01 19:47:50 +00005172 pixel.green=(MagickRealType)
5173 GetPixelGreen(image,p);
cristy3ed852e2009-09-05 21:47:34 +00005174 else
cristy4c08aed2011-07-01 19:47:50 +00005175 pixel.green=(MagickRealType)
5176 GetPixelGreen(image,p)+
5177 (pixel.green*amount);
5178 SetPixelGreen(unsharp_image,
5179 ClampToQuantum(pixel.green),q);
cristy3ed852e2009-09-05 21:47:34 +00005180 }
cristy2b9582a2011-07-04 17:38:56 +00005181 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00005182 {
cristy4c08aed2011-07-01 19:47:50 +00005183 pixel.blue=GetPixelBlue(image,p)-
5184 (MagickRealType) GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00005185 if (fabs(2.0*pixel.blue) < quantum_threshold)
cristy4c08aed2011-07-01 19:47:50 +00005186 pixel.blue=(MagickRealType)
5187 GetPixelBlue(image,p);
cristy3ed852e2009-09-05 21:47:34 +00005188 else
cristy4c08aed2011-07-01 19:47:50 +00005189 pixel.blue=(MagickRealType)
5190 GetPixelBlue(image,p)+(pixel.blue*amount);
5191 SetPixelBlue(unsharp_image,
5192 ClampToQuantum(pixel.blue),q);
cristy3ed852e2009-09-05 21:47:34 +00005193 }
cristy2b9582a2011-07-04 17:38:56 +00005194 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00005195 (image->colorspace == CMYKColorspace))
5196 {
cristy4c08aed2011-07-01 19:47:50 +00005197 pixel.black=GetPixelBlack(image,p)-
5198 (MagickRealType) GetPixelBlack(image,q);
5199 if (fabs(2.0*pixel.black) < quantum_threshold)
5200 pixel.black=(MagickRealType)
5201 GetPixelBlack(image,p);
cristy3ed852e2009-09-05 21:47:34 +00005202 else
cristy4c08aed2011-07-01 19:47:50 +00005203 pixel.black=(MagickRealType)
5204 GetPixelBlack(image,p)+(pixel.black*
5205 amount);
5206 SetPixelBlack(unsharp_image,
5207 ClampToQuantum(pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00005208 }
cristy2b9582a2011-07-04 17:38:56 +00005209 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00005210 {
5211 pixel.alpha=GetPixelAlpha(image,p)-
5212 (MagickRealType) GetPixelAlpha(image,q);
5213 if (fabs(2.0*pixel.alpha) < quantum_threshold)
5214 pixel.alpha=(MagickRealType)
5215 GetPixelAlpha(image,p);
5216 else
5217 pixel.alpha=GetPixelAlpha(image,p)+
5218 (pixel.alpha*amount);
5219 SetPixelAlpha(unsharp_image,
5220 ClampToQuantum(pixel.alpha),q);
5221 }
cristydcfc1ad2011-07-07 16:25:41 +00005222 p+=GetPixelComponents(image);
5223 q+=GetPixelComponents(unsharp_image);
cristy3ed852e2009-09-05 21:47:34 +00005224 }
5225 if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
5226 status=MagickFalse;
5227 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5228 {
5229 MagickBooleanType
5230 proceed;
5231
cristyb5d5f722009-11-04 03:03:49 +00005232#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00005233 #pragma omp critical (MagickCore_UnsharpMaskImage)
cristy3ed852e2009-09-05 21:47:34 +00005234#endif
5235 proceed=SetImageProgress(image,SharpenImageTag,progress++,image->rows);
5236 if (proceed == MagickFalse)
5237 status=MagickFalse;
5238 }
5239 }
5240 unsharp_image->type=image->type;
5241 unsharp_view=DestroyCacheView(unsharp_view);
5242 image_view=DestroyCacheView(image_view);
5243 if (status == MagickFalse)
5244 unsharp_image=DestroyImage(unsharp_image);
5245 return(unsharp_image);
5246}