blob: 5f6c8325a1da4c2f5087155c5bbcf3feeca5bf3d [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 {
cristyb7e8bb52011-07-13 00:44:37 +00001458 pixel.alpha+=(*k)*GetPixelAlpha(image,kernel_pixels+u*
1459 GetPixelComponents(image));
cristy175653e2011-07-10 23:13:34 +00001460 k++;
1461 }
1462 kernel_pixels+=(image->columns+width)*GetPixelComponents(image);
cristyfccdab92009-11-30 16:43:57 +00001463 }
cristy175653e2011-07-10 23:13:34 +00001464 SetPixelAlpha(convolve_image,ClampToQuantum(pixel.alpha),q);
1465 }
cristyfccdab92009-11-30 16:43:57 +00001466 }
cristy175653e2011-07-10 23:13:34 +00001467 p+=GetPixelComponents(image);
1468 q+=GetPixelComponents(convolve_image);
cristyfccdab92009-11-30 16:43:57 +00001469 }
cristy175653e2011-07-10 23:13:34 +00001470 sync=SyncCacheViewAuthenticPixels(convolve_view,exception);
1471 if (sync == MagickFalse)
cristyfccdab92009-11-30 16:43:57 +00001472 status=MagickFalse;
1473 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1474 {
1475 MagickBooleanType
1476 proceed;
1477
1478#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00001479 #pragma omp critical (MagickCore_ConvolveImage)
cristyfccdab92009-11-30 16:43:57 +00001480#endif
1481 proceed=SetImageProgress(image,ConvolveImageTag,progress++,image->rows);
1482 if (proceed == MagickFalse)
1483 status=MagickFalse;
1484 }
1485 }
1486 convolve_image->type=image->type;
1487 convolve_view=DestroyCacheView(convolve_view);
1488 image_view=DestroyCacheView(image_view);
1489 normal_kernel=(double *) RelinquishMagickMemory(normal_kernel);
1490 if (status == MagickFalse)
1491 convolve_image=DestroyImage(convolve_image);
1492 return(convolve_image);
1493}
1494
1495/*
1496%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1497% %
1498% %
1499% %
cristy3ed852e2009-09-05 21:47:34 +00001500% D e s p e c k l e I m a g e %
1501% %
1502% %
1503% %
1504%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1505%
1506% DespeckleImage() reduces the speckle noise in an image while perserving the
1507% edges of the original image.
1508%
1509% The format of the DespeckleImage method is:
1510%
1511% Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1512%
1513% A description of each parameter follows:
1514%
1515% o image: the image.
1516%
1517% o exception: return any errors or warnings in this structure.
1518%
1519*/
1520
cristybb503372010-05-27 20:51:26 +00001521static void Hull(const ssize_t x_offset,const ssize_t y_offset,
1522 const size_t columns,const size_t rows,Quantum *f,Quantum *g,
cristy3ed852e2009-09-05 21:47:34 +00001523 const int polarity)
1524{
cristy3ed852e2009-09-05 21:47:34 +00001525 MagickRealType
1526 v;
1527
cristy3ed852e2009-09-05 21:47:34 +00001528 register Quantum
1529 *p,
1530 *q,
1531 *r,
1532 *s;
1533
cristy117ff172010-08-15 21:35:32 +00001534 register ssize_t
1535 x;
1536
1537 ssize_t
1538 y;
1539
cristy3ed852e2009-09-05 21:47:34 +00001540 assert(f != (Quantum *) NULL);
1541 assert(g != (Quantum *) NULL);
1542 p=f+(columns+2);
1543 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001544 r=p+(y_offset*((ssize_t) columns+2)+x_offset);
1545 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001546 {
1547 p++;
1548 q++;
1549 r++;
1550 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001551 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001552 {
1553 v=(MagickRealType) (*p);
1554 if ((MagickRealType) *r >= (v+(MagickRealType) ScaleCharToQuantum(2)))
1555 v+=ScaleCharToQuantum(1);
1556 *q=(Quantum) v;
1557 p++;
1558 q++;
1559 r++;
1560 }
1561 else
cristybb503372010-05-27 20:51:26 +00001562 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001563 {
1564 v=(MagickRealType) (*p);
1565 if ((MagickRealType) *r <= (v-(MagickRealType) ScaleCharToQuantum(2)))
cristybb503372010-05-27 20:51:26 +00001566 v-=(ssize_t) ScaleCharToQuantum(1);
cristy3ed852e2009-09-05 21:47:34 +00001567 *q=(Quantum) v;
1568 p++;
1569 q++;
1570 r++;
1571 }
1572 p++;
1573 q++;
1574 r++;
1575 }
1576 p=f+(columns+2);
1577 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001578 r=q+(y_offset*((ssize_t) columns+2)+x_offset);
1579 s=q-(y_offset*((ssize_t) columns+2)+x_offset);
1580 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001581 {
1582 p++;
1583 q++;
1584 r++;
1585 s++;
1586 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001587 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001588 {
1589 v=(MagickRealType) (*q);
1590 if (((MagickRealType) *s >=
1591 (v+(MagickRealType) ScaleCharToQuantum(2))) &&
1592 ((MagickRealType) *r > v))
1593 v+=ScaleCharToQuantum(1);
1594 *p=(Quantum) v;
1595 p++;
1596 q++;
1597 r++;
1598 s++;
1599 }
1600 else
cristybb503372010-05-27 20:51:26 +00001601 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001602 {
1603 v=(MagickRealType) (*q);
1604 if (((MagickRealType) *s <=
1605 (v-(MagickRealType) ScaleCharToQuantum(2))) &&
1606 ((MagickRealType) *r < v))
1607 v-=(MagickRealType) ScaleCharToQuantum(1);
1608 *p=(Quantum) v;
1609 p++;
1610 q++;
1611 r++;
1612 s++;
1613 }
1614 p++;
1615 q++;
1616 r++;
1617 s++;
1618 }
1619}
1620
1621MagickExport Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1622{
1623#define DespeckleImageTag "Despeckle/Image"
1624
cristy2407fc22009-09-11 00:55:25 +00001625 CacheView
1626 *despeckle_view,
1627 *image_view;
1628
cristy3ed852e2009-09-05 21:47:34 +00001629 Image
1630 *despeckle_image;
1631
cristy3ed852e2009-09-05 21:47:34 +00001632 MagickBooleanType
1633 status;
1634
cristya58c3172011-02-19 19:23:11 +00001635 register ssize_t
1636 i;
1637
cristy3ed852e2009-09-05 21:47:34 +00001638 Quantum
cristy65b9f392011-02-22 14:22:54 +00001639 *restrict buffers,
1640 *restrict pixels;
cristy3ed852e2009-09-05 21:47:34 +00001641
1642 size_t
cristya58c3172011-02-19 19:23:11 +00001643 length,
1644 number_channels;
cristy117ff172010-08-15 21:35:32 +00001645
cristybb503372010-05-27 20:51:26 +00001646 static const ssize_t
cristy691a29e2009-09-11 00:44:10 +00001647 X[4] = {0, 1, 1,-1},
1648 Y[4] = {1, 0, 1, 1};
cristy3ed852e2009-09-05 21:47:34 +00001649
cristy3ed852e2009-09-05 21:47:34 +00001650 /*
1651 Allocate despeckled image.
1652 */
1653 assert(image != (const Image *) NULL);
1654 assert(image->signature == MagickSignature);
1655 if (image->debug != MagickFalse)
1656 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1657 assert(exception != (ExceptionInfo *) NULL);
1658 assert(exception->signature == MagickSignature);
1659 despeckle_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1660 exception);
1661 if (despeckle_image == (Image *) NULL)
1662 return((Image *) NULL);
1663 if (SetImageStorageClass(despeckle_image,DirectClass) == MagickFalse)
1664 {
1665 InheritException(exception,&despeckle_image->exception);
1666 despeckle_image=DestroyImage(despeckle_image);
1667 return((Image *) NULL);
1668 }
1669 /*
1670 Allocate image buffers.
1671 */
1672 length=(size_t) ((image->columns+2)*(image->rows+2));
cristy65b9f392011-02-22 14:22:54 +00001673 pixels=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1674 buffers=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1675 if ((pixels == (Quantum *) NULL) || (buffers == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001676 {
cristy65b9f392011-02-22 14:22:54 +00001677 if (buffers != (Quantum *) NULL)
1678 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1679 if (pixels != (Quantum *) NULL)
1680 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001681 despeckle_image=DestroyImage(despeckle_image);
1682 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1683 }
1684 /*
1685 Reduce speckle in the image.
1686 */
1687 status=MagickTrue;
cristy109695a2011-02-19 19:38:14 +00001688 number_channels=(size_t) (image->colorspace == CMYKColorspace ? 5 : 4);
cristy3ed852e2009-09-05 21:47:34 +00001689 image_view=AcquireCacheView(image);
1690 despeckle_view=AcquireCacheView(despeckle_image);
cristy8df3d002011-02-19 19:40:59 +00001691 for (i=0; i < (ssize_t) number_channels; i++)
cristy3ed852e2009-09-05 21:47:34 +00001692 {
cristy3ed852e2009-09-05 21:47:34 +00001693 register Quantum
1694 *buffer,
1695 *pixel;
1696
cristyc1488b52011-02-19 18:54:15 +00001697 register ssize_t
cristya58c3172011-02-19 19:23:11 +00001698 k,
cristyc1488b52011-02-19 18:54:15 +00001699 x;
1700
cristy117ff172010-08-15 21:35:32 +00001701 ssize_t
1702 j,
1703 y;
1704
cristy3ed852e2009-09-05 21:47:34 +00001705 if (status == MagickFalse)
1706 continue;
cristy65b9f392011-02-22 14:22:54 +00001707 pixel=pixels;
cristy3ed852e2009-09-05 21:47:34 +00001708 (void) ResetMagickMemory(pixel,0,length*sizeof(*pixel));
cristy65b9f392011-02-22 14:22:54 +00001709 buffer=buffers;
cristybb503372010-05-27 20:51:26 +00001710 j=(ssize_t) image->columns+2;
1711 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001712 {
cristy4c08aed2011-07-01 19:47:50 +00001713 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001714 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001715
1716 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001717 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001718 break;
1719 j++;
cristybb503372010-05-27 20:51:26 +00001720 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001721 {
cristya58c3172011-02-19 19:23:11 +00001722 switch (i)
cristy3ed852e2009-09-05 21:47:34 +00001723 {
cristy4c08aed2011-07-01 19:47:50 +00001724 case 0: pixel[j]=GetPixelRed(image,p); break;
1725 case 1: pixel[j]=GetPixelGreen(image,p); break;
1726 case 2: pixel[j]=GetPixelBlue(image,p); break;
1727 case 3: pixel[j]=GetPixelAlpha(image,p); break;
1728 case 4: pixel[j]=GetPixelBlack(image,p); break;
cristy3ed852e2009-09-05 21:47:34 +00001729 default: break;
1730 }
cristydcfc1ad2011-07-07 16:25:41 +00001731 p+=GetPixelComponents(image);
cristy3ed852e2009-09-05 21:47:34 +00001732 j++;
1733 }
1734 j++;
1735 }
cristy3ed852e2009-09-05 21:47:34 +00001736 (void) ResetMagickMemory(buffer,0,length*sizeof(*buffer));
cristya58c3172011-02-19 19:23:11 +00001737 for (k=0; k < 4; k++)
cristy3ed852e2009-09-05 21:47:34 +00001738 {
cristya58c3172011-02-19 19:23:11 +00001739 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);
1742 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,-1);
cristy3ed852e2009-09-05 21:47:34 +00001743 }
cristybb503372010-05-27 20:51:26 +00001744 j=(ssize_t) image->columns+2;
1745 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001746 {
1747 MagickBooleanType
1748 sync;
1749
cristy4c08aed2011-07-01 19:47:50 +00001750 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001751 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001752
1753 q=GetCacheViewAuthenticPixels(despeckle_view,0,y,despeckle_image->columns,
1754 1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001755 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001756 break;
1757 j++;
cristybb503372010-05-27 20:51:26 +00001758 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001759 {
cristya58c3172011-02-19 19:23:11 +00001760 switch (i)
cristy3ed852e2009-09-05 21:47:34 +00001761 {
cristy4c08aed2011-07-01 19:47:50 +00001762 case 0: SetPixelRed(despeckle_image,pixel[j],q); break;
1763 case 1: SetPixelGreen(despeckle_image,pixel[j],q); break;
1764 case 2: SetPixelBlue(despeckle_image,pixel[j],q); break;
1765 case 3: SetPixelAlpha(despeckle_image,pixel[j],q); break;
1766 case 4: SetPixelBlack(despeckle_image,pixel[j],q); break;
cristy3ed852e2009-09-05 21:47:34 +00001767 default: break;
1768 }
cristydcfc1ad2011-07-07 16:25:41 +00001769 q+=GetPixelComponents(despeckle_image);
cristy3ed852e2009-09-05 21:47:34 +00001770 j++;
1771 }
1772 sync=SyncCacheViewAuthenticPixels(despeckle_view,exception);
1773 if (sync == MagickFalse)
1774 {
1775 status=MagickFalse;
1776 break;
1777 }
1778 j++;
1779 }
1780 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1781 {
1782 MagickBooleanType
1783 proceed;
1784
cristya58c3172011-02-19 19:23:11 +00001785 proceed=SetImageProgress(image,DespeckleImageTag,(MagickOffsetType) i,
1786 number_channels);
cristy3ed852e2009-09-05 21:47:34 +00001787 if (proceed == MagickFalse)
1788 status=MagickFalse;
1789 }
1790 }
1791 despeckle_view=DestroyCacheView(despeckle_view);
1792 image_view=DestroyCacheView(image_view);
cristy65b9f392011-02-22 14:22:54 +00001793 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1794 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001795 despeckle_image->type=image->type;
1796 if (status == MagickFalse)
1797 despeckle_image=DestroyImage(despeckle_image);
1798 return(despeckle_image);
1799}
1800
1801/*
1802%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1803% %
1804% %
1805% %
1806% E d g e I m a g e %
1807% %
1808% %
1809% %
1810%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1811%
1812% EdgeImage() finds edges in an image. Radius defines the radius of the
1813% convolution filter. Use a radius of 0 and EdgeImage() selects a suitable
1814% radius for you.
1815%
1816% The format of the EdgeImage method is:
1817%
1818% Image *EdgeImage(const Image *image,const double radius,
1819% ExceptionInfo *exception)
1820%
1821% A description of each parameter follows:
1822%
1823% o image: the image.
1824%
1825% o radius: the radius of the pixel neighborhood.
1826%
1827% o exception: return any errors or warnings in this structure.
1828%
1829*/
1830MagickExport Image *EdgeImage(const Image *image,const double radius,
1831 ExceptionInfo *exception)
1832{
1833 Image
1834 *edge_image;
1835
1836 double
1837 *kernel;
1838
cristybb503372010-05-27 20:51:26 +00001839 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001840 i;
1841
cristybb503372010-05-27 20:51:26 +00001842 size_t
cristy3ed852e2009-09-05 21:47:34 +00001843 width;
1844
1845 assert(image != (const Image *) NULL);
1846 assert(image->signature == MagickSignature);
1847 if (image->debug != MagickFalse)
1848 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1849 assert(exception != (ExceptionInfo *) NULL);
1850 assert(exception->signature == MagickSignature);
1851 width=GetOptimalKernelWidth1D(radius,0.5);
1852 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
1853 if (kernel == (double *) NULL)
1854 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00001855 for (i=0; i < (ssize_t) (width*width); i++)
cristy3ed852e2009-09-05 21:47:34 +00001856 kernel[i]=(-1.0);
1857 kernel[i/2]=(double) (width*width-1.0);
1858 edge_image=ConvolveImage(image,width,kernel,exception);
1859 kernel=(double *) RelinquishMagickMemory(kernel);
1860 return(edge_image);
1861}
1862
1863/*
1864%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1865% %
1866% %
1867% %
1868% E m b o s s I m a g e %
1869% %
1870% %
1871% %
1872%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1873%
1874% EmbossImage() returns a grayscale image with a three-dimensional effect.
1875% We convolve the image with a Gaussian operator of the given radius and
1876% standard deviation (sigma). For reasonable results, radius should be
1877% larger than sigma. Use a radius of 0 and Emboss() selects a suitable
1878% radius for you.
1879%
1880% The format of the EmbossImage method is:
1881%
1882% Image *EmbossImage(const Image *image,const double radius,
1883% const double sigma,ExceptionInfo *exception)
1884%
1885% A description of each parameter follows:
1886%
1887% o image: the image.
1888%
1889% o radius: the radius of the pixel neighborhood.
1890%
1891% o sigma: the standard deviation of the Gaussian, in pixels.
1892%
1893% o exception: return any errors or warnings in this structure.
1894%
1895*/
1896MagickExport Image *EmbossImage(const Image *image,const double radius,
1897 const double sigma,ExceptionInfo *exception)
1898{
1899 double
1900 *kernel;
1901
1902 Image
1903 *emboss_image;
1904
cristybb503372010-05-27 20:51:26 +00001905 register ssize_t
cristy47e00502009-12-17 19:19:57 +00001906 i;
1907
cristybb503372010-05-27 20:51:26 +00001908 size_t
cristy3ed852e2009-09-05 21:47:34 +00001909 width;
1910
cristy117ff172010-08-15 21:35:32 +00001911 ssize_t
1912 j,
1913 k,
1914 u,
1915 v;
1916
cristy3ed852e2009-09-05 21:47:34 +00001917 assert(image != (Image *) NULL);
1918 assert(image->signature == MagickSignature);
1919 if (image->debug != MagickFalse)
1920 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1921 assert(exception != (ExceptionInfo *) NULL);
1922 assert(exception->signature == MagickSignature);
1923 width=GetOptimalKernelWidth2D(radius,sigma);
1924 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
1925 if (kernel == (double *) NULL)
1926 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00001927 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +00001928 k=j;
1929 i=0;
1930 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00001931 {
cristy47e00502009-12-17 19:19:57 +00001932 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00001933 {
cristy4205a3c2010-09-12 20:19:59 +00001934 kernel[i]=(double) (((u < 0) || (v < 0) ? -8.0 : 8.0)*
cristy47e00502009-12-17 19:19:57 +00001935 exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
cristy4205a3c2010-09-12 20:19:59 +00001936 (2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +00001937 if (u != k)
cristy3ed852e2009-09-05 21:47:34 +00001938 kernel[i]=0.0;
1939 i++;
1940 }
cristy47e00502009-12-17 19:19:57 +00001941 k--;
cristy3ed852e2009-09-05 21:47:34 +00001942 }
1943 emboss_image=ConvolveImage(image,width,kernel,exception);
1944 if (emboss_image != (Image *) NULL)
1945 (void) EqualizeImage(emboss_image);
1946 kernel=(double *) RelinquishMagickMemory(kernel);
1947 return(emboss_image);
1948}
1949
1950/*
1951%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1952% %
1953% %
1954% %
cristy56a9e512010-01-06 18:18:55 +00001955% F i l t e r I m a g e %
1956% %
1957% %
1958% %
1959%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1960%
1961% FilterImage() applies a custom convolution kernel to the image.
1962%
1963% The format of the FilterImage method is:
1964%
cristy2be15382010-01-21 02:38:03 +00001965% Image *FilterImage(const Image *image,const KernelInfo *kernel,
cristy56a9e512010-01-06 18:18:55 +00001966% ExceptionInfo *exception)
cristy56a9e512010-01-06 18:18:55 +00001967%
1968% A description of each parameter follows:
1969%
1970% o image: the image.
1971%
cristy56a9e512010-01-06 18:18:55 +00001972% o kernel: the filtering kernel.
1973%
1974% o exception: return any errors or warnings in this structure.
1975%
1976*/
cristyf4ad9df2011-07-08 16:49:03 +00001977MagickExport Image *FilterImage(const Image *image,
1978 const KernelInfo *kernel,ExceptionInfo *exception)
cristy56a9e512010-01-06 18:18:55 +00001979{
1980#define FilterImageTag "Filter/Image"
1981
1982 CacheView
1983 *filter_view,
1984 *image_view;
1985
cristy56a9e512010-01-06 18:18:55 +00001986 Image
1987 *filter_image;
1988
cristy56a9e512010-01-06 18:18:55 +00001989 MagickBooleanType
1990 status;
1991
cristybb503372010-05-27 20:51:26 +00001992 MagickOffsetType
1993 progress;
1994
cristy4c08aed2011-07-01 19:47:50 +00001995 PixelInfo
cristy56a9e512010-01-06 18:18:55 +00001996 bias;
1997
cristybb503372010-05-27 20:51:26 +00001998 ssize_t
1999 y;
2000
cristy56a9e512010-01-06 18:18:55 +00002001 /*
2002 Initialize filter image attributes.
2003 */
2004 assert(image != (Image *) NULL);
2005 assert(image->signature == MagickSignature);
2006 if (image->debug != MagickFalse)
2007 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2008 assert(exception != (ExceptionInfo *) NULL);
2009 assert(exception->signature == MagickSignature);
2010 if ((kernel->width % 2) == 0)
2011 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
2012 filter_image=CloneImage(image,0,0,MagickTrue,exception);
2013 if (filter_image == (Image *) NULL)
2014 return((Image *) NULL);
2015 if (SetImageStorageClass(filter_image,DirectClass) == MagickFalse)
2016 {
2017 InheritException(exception,&filter_image->exception);
2018 filter_image=DestroyImage(filter_image);
2019 return((Image *) NULL);
2020 }
2021 if (image->debug != MagickFalse)
2022 {
2023 char
2024 format[MaxTextExtent],
2025 *message;
2026
cristy117ff172010-08-15 21:35:32 +00002027 register const double
2028 *k;
2029
cristybb503372010-05-27 20:51:26 +00002030 ssize_t
cristy56a9e512010-01-06 18:18:55 +00002031 u,
2032 v;
2033
cristy56a9e512010-01-06 18:18:55 +00002034 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00002035 " FilterImage with %.20gx%.20g kernel:",(double) kernel->width,(double)
2036 kernel->height);
cristy56a9e512010-01-06 18:18:55 +00002037 message=AcquireString("");
2038 k=kernel->values;
cristybb503372010-05-27 20:51:26 +00002039 for (v=0; v < (ssize_t) kernel->height; v++)
cristy56a9e512010-01-06 18:18:55 +00002040 {
2041 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00002042 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy56a9e512010-01-06 18:18:55 +00002043 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00002044 for (u=0; u < (ssize_t) kernel->width; u++)
cristy56a9e512010-01-06 18:18:55 +00002045 {
cristyb51dff52011-05-19 16:55:47 +00002046 (void) FormatLocaleString(format,MaxTextExtent,"%g ",*k++);
cristy56a9e512010-01-06 18:18:55 +00002047 (void) ConcatenateString(&message,format);
2048 }
2049 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
2050 }
2051 message=DestroyString(message);
2052 }
cristy36826ab2010-03-06 01:29:30 +00002053 status=AccelerateConvolveImage(image,kernel,filter_image,exception);
cristyd43a46b2010-01-21 02:13:41 +00002054 if (status == MagickTrue)
2055 return(filter_image);
cristy56a9e512010-01-06 18:18:55 +00002056 /*
2057 Filter image.
2058 */
2059 status=MagickTrue;
2060 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00002061 GetPixelInfo(image,&bias);
2062 SetPixelInfoBias(image,&bias);
cristy56a9e512010-01-06 18:18:55 +00002063 image_view=AcquireCacheView(image);
2064 filter_view=AcquireCacheView(filter_image);
2065#if defined(MAGICKCORE_OPENMP_SUPPORT)
2066 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2067#endif
cristybb503372010-05-27 20:51:26 +00002068 for (y=0; y < (ssize_t) image->rows; y++)
cristy56a9e512010-01-06 18:18:55 +00002069 {
2070 MagickBooleanType
2071 sync;
2072
cristy4c08aed2011-07-01 19:47:50 +00002073 register const Quantum
cristy56a9e512010-01-06 18:18:55 +00002074 *restrict p;
2075
cristy4c08aed2011-07-01 19:47:50 +00002076 register Quantum
cristy56a9e512010-01-06 18:18:55 +00002077 *restrict q;
2078
cristy117ff172010-08-15 21:35:32 +00002079 register ssize_t
2080 x;
2081
cristy56a9e512010-01-06 18:18:55 +00002082 if (status == MagickFalse)
2083 continue;
cristybb503372010-05-27 20:51:26 +00002084 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) kernel->width/2L),
cristy117ff172010-08-15 21:35:32 +00002085 y-(ssize_t) (kernel->height/2L),image->columns+kernel->width,
2086 kernel->height,exception);
cristy56a9e512010-01-06 18:18:55 +00002087 q=GetCacheViewAuthenticPixels(filter_view,0,y,filter_image->columns,1,
2088 exception);
cristy4c08aed2011-07-01 19:47:50 +00002089 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy56a9e512010-01-06 18:18:55 +00002090 {
2091 status=MagickFalse;
2092 continue;
2093 }
cristybb503372010-05-27 20:51:26 +00002094 for (x=0; x < (ssize_t) image->columns; x++)
cristy56a9e512010-01-06 18:18:55 +00002095 {
cristy4c08aed2011-07-01 19:47:50 +00002096 PixelInfo
cristy56a9e512010-01-06 18:18:55 +00002097 pixel;
2098
2099 register const double
2100 *restrict k;
2101
cristy4c08aed2011-07-01 19:47:50 +00002102 register const Quantum
cristy56a9e512010-01-06 18:18:55 +00002103 *restrict kernel_pixels;
2104
cristybb503372010-05-27 20:51:26 +00002105 register ssize_t
cristy56a9e512010-01-06 18:18:55 +00002106 u;
2107
cristy117ff172010-08-15 21:35:32 +00002108 ssize_t
2109 v;
2110
cristy56a9e512010-01-06 18:18:55 +00002111 pixel=bias;
cristy36826ab2010-03-06 01:29:30 +00002112 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002113 kernel_pixels=p;
cristy5ce8df82011-07-07 14:52:23 +00002114 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) == 0) ||
2115 (image->matte == MagickFalse))
cristy56a9e512010-01-06 18:18:55 +00002116 {
cristybb503372010-05-27 20:51:26 +00002117 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002118 {
cristybb503372010-05-27 20:51:26 +00002119 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002120 {
cristy4c08aed2011-07-01 19:47:50 +00002121 pixel.red+=(*k)*GetPixelRed(image,kernel_pixels+u*
cristydcfc1ad2011-07-07 16:25:41 +00002122 GetPixelComponents(image));
cristy4c08aed2011-07-01 19:47:50 +00002123 pixel.green+=(*k)*GetPixelGreen(image,kernel_pixels+u*
cristydcfc1ad2011-07-07 16:25:41 +00002124 GetPixelComponents(image));
cristy4c08aed2011-07-01 19:47:50 +00002125 pixel.blue+=(*k)*GetPixelBlue(image,kernel_pixels+u*
cristydcfc1ad2011-07-07 16:25:41 +00002126 GetPixelComponents(image));
cristy4c08aed2011-07-01 19:47:50 +00002127 if (image->colorspace == CMYKColorspace)
2128 pixel.black+=(*k)*GetPixelBlack(image,kernel_pixels+u*
cristydcfc1ad2011-07-07 16:25:41 +00002129 GetPixelComponents(image));
cristy56a9e512010-01-06 18:18:55 +00002130 k++;
2131 }
cristy4c08aed2011-07-01 19:47:50 +00002132 kernel_pixels+=(image->columns+kernel->width)*
cristydcfc1ad2011-07-07 16:25:41 +00002133 GetPixelComponents(image);
cristy56a9e512010-01-06 18:18:55 +00002134 }
cristy2b9582a2011-07-04 17:38:56 +00002135 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002136 SetPixelRed(filter_image,ClampToQuantum(pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00002137 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002138 SetPixelGreen(filter_image,ClampToQuantum(pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00002139 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002140 SetPixelBlue(filter_image,ClampToQuantum(pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00002141 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002142 (image->colorspace == CMYKColorspace))
2143 SetPixelBlack(filter_image,ClampToQuantum(pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00002144 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy56a9e512010-01-06 18:18:55 +00002145 {
cristy36826ab2010-03-06 01:29:30 +00002146 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002147 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00002148 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002149 {
cristybb503372010-05-27 20:51:26 +00002150 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002151 {
cristy4c08aed2011-07-01 19:47:50 +00002152 pixel.alpha+=(*k)*GetPixelRed(image,kernel_pixels+u*
cristydcfc1ad2011-07-07 16:25:41 +00002153 GetPixelComponents(image));
cristy56a9e512010-01-06 18:18:55 +00002154 k++;
2155 }
cristy4c08aed2011-07-01 19:47:50 +00002156 kernel_pixels+=(image->columns+kernel->width)*
cristydcfc1ad2011-07-07 16:25:41 +00002157 GetPixelComponents(image);
cristy56a9e512010-01-06 18:18:55 +00002158 }
cristy4c08aed2011-07-01 19:47:50 +00002159 SetPixelAlpha(filter_image,ClampToQuantum(pixel.alpha),q);
cristy56a9e512010-01-06 18:18:55 +00002160 }
2161 }
2162 else
2163 {
2164 MagickRealType
2165 alpha,
2166 gamma;
2167
2168 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00002169 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002170 {
cristybb503372010-05-27 20:51:26 +00002171 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002172 {
cristy4c08aed2011-07-01 19:47:50 +00002173 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,
cristydcfc1ad2011-07-07 16:25:41 +00002174 kernel_pixels+u*GetPixelComponents(image)));
cristy4c08aed2011-07-01 19:47:50 +00002175 pixel.red+=(*k)*alpha*GetPixelRed(image,kernel_pixels+u*
cristydcfc1ad2011-07-07 16:25:41 +00002176 GetPixelComponents(image));
cristy4c08aed2011-07-01 19:47:50 +00002177 pixel.green+=(*k)*alpha*GetPixelGreen(image,kernel_pixels+u*
cristydcfc1ad2011-07-07 16:25:41 +00002178 GetPixelComponents(image));
cristy4c08aed2011-07-01 19:47:50 +00002179 pixel.blue+=(*k)*alpha*GetPixelBlue(image,kernel_pixels+u*
cristydcfc1ad2011-07-07 16:25:41 +00002180 GetPixelComponents(image));
cristy4c08aed2011-07-01 19:47:50 +00002181 if (image->colorspace == CMYKColorspace)
2182 pixel.black+=(*k)*alpha*GetPixelBlack(image,kernel_pixels+u*
cristydcfc1ad2011-07-07 16:25:41 +00002183 GetPixelComponents(image));
cristy56a9e512010-01-06 18:18:55 +00002184 gamma+=(*k)*alpha;
2185 k++;
2186 }
cristy5ce8df82011-07-07 14:52:23 +00002187 kernel_pixels+=(image->columns+kernel->width)*
cristydcfc1ad2011-07-07 16:25:41 +00002188 GetPixelComponents(image);
cristy56a9e512010-01-06 18:18:55 +00002189 }
2190 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy2b9582a2011-07-04 17:38:56 +00002191 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002192 SetPixelRed(filter_image,ClampToQuantum(gamma*pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00002193 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002194 SetPixelGreen(filter_image,ClampToQuantum(gamma*pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00002195 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002196 SetPixelBlue(filter_image,ClampToQuantum(gamma*pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00002197 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy56a9e512010-01-06 18:18:55 +00002198 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002199 SetPixelBlack(filter_image,ClampToQuantum(gamma*pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00002200 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy56a9e512010-01-06 18:18:55 +00002201 {
cristy36826ab2010-03-06 01:29:30 +00002202 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002203 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00002204 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002205 {
cristybb503372010-05-27 20:51:26 +00002206 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002207 {
cristy4c08aed2011-07-01 19:47:50 +00002208 pixel.alpha+=(*k)*GetPixelAlpha(image,kernel_pixels+u*
cristydcfc1ad2011-07-07 16:25:41 +00002209 GetPixelComponents(image));
cristy56a9e512010-01-06 18:18:55 +00002210 k++;
2211 }
cristy4c08aed2011-07-01 19:47:50 +00002212 kernel_pixels+=(image->columns+kernel->width)*
cristydcfc1ad2011-07-07 16:25:41 +00002213 GetPixelComponents(image);
cristy56a9e512010-01-06 18:18:55 +00002214 }
cristy4c08aed2011-07-01 19:47:50 +00002215 SetPixelAlpha(filter_image,ClampToQuantum(pixel.alpha),q);
cristy56a9e512010-01-06 18:18:55 +00002216 }
2217 }
cristydcfc1ad2011-07-07 16:25:41 +00002218 p+=GetPixelComponents(image);
2219 q+=GetPixelComponents(filter_image);
cristy56a9e512010-01-06 18:18:55 +00002220 }
2221 sync=SyncCacheViewAuthenticPixels(filter_view,exception);
2222 if (sync == MagickFalse)
2223 status=MagickFalse;
2224 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2225 {
2226 MagickBooleanType
2227 proceed;
2228
2229#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00002230 #pragma omp critical (MagickCore_FilterImage)
cristy56a9e512010-01-06 18:18:55 +00002231#endif
2232 proceed=SetImageProgress(image,FilterImageTag,progress++,image->rows);
2233 if (proceed == MagickFalse)
2234 status=MagickFalse;
2235 }
2236 }
2237 filter_image->type=image->type;
2238 filter_view=DestroyCacheView(filter_view);
2239 image_view=DestroyCacheView(image_view);
cristy56a9e512010-01-06 18:18:55 +00002240 if (status == MagickFalse)
2241 filter_image=DestroyImage(filter_image);
2242 return(filter_image);
2243}
2244
2245/*
2246%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2247% %
2248% %
2249% %
cristy3ed852e2009-09-05 21:47:34 +00002250% G a u s s i a n B l u r I m a g e %
2251% %
2252% %
2253% %
2254%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2255%
2256% GaussianBlurImage() blurs an image. We convolve the image with a
2257% Gaussian operator of the given radius and standard deviation (sigma).
2258% For reasonable results, the radius should be larger than sigma. Use a
2259% radius of 0 and GaussianBlurImage() selects a suitable radius for you
2260%
2261% The format of the GaussianBlurImage method is:
2262%
2263% Image *GaussianBlurImage(const Image *image,onst double radius,
2264% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002265%
2266% A description of each parameter follows:
2267%
2268% o image: the image.
2269%
cristy3ed852e2009-09-05 21:47:34 +00002270% o radius: the radius of the Gaussian, in pixels, not counting the center
2271% pixel.
2272%
2273% o sigma: the standard deviation of the Gaussian, in pixels.
2274%
2275% o exception: return any errors or warnings in this structure.
2276%
2277*/
cristyf4ad9df2011-07-08 16:49:03 +00002278MagickExport Image *GaussianBlurImage(const Image *image,
2279 const double radius,const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002280{
2281 double
2282 *kernel;
2283
2284 Image
2285 *blur_image;
2286
cristybb503372010-05-27 20:51:26 +00002287 register ssize_t
cristy47e00502009-12-17 19:19:57 +00002288 i;
2289
cristybb503372010-05-27 20:51:26 +00002290 size_t
cristy3ed852e2009-09-05 21:47:34 +00002291 width;
2292
cristy117ff172010-08-15 21:35:32 +00002293 ssize_t
2294 j,
2295 u,
2296 v;
2297
cristy3ed852e2009-09-05 21:47:34 +00002298 assert(image != (const Image *) NULL);
2299 assert(image->signature == MagickSignature);
2300 if (image->debug != MagickFalse)
2301 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2302 assert(exception != (ExceptionInfo *) NULL);
2303 assert(exception->signature == MagickSignature);
2304 width=GetOptimalKernelWidth2D(radius,sigma);
2305 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2306 if (kernel == (double *) NULL)
2307 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00002308 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00002309 i=0;
cristy47e00502009-12-17 19:19:57 +00002310 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002311 {
cristy47e00502009-12-17 19:19:57 +00002312 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00002313 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
2314 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00002315 }
cristyf4ad9df2011-07-08 16:49:03 +00002316 blur_image=ConvolveImage(image,width,kernel,exception);
cristy3ed852e2009-09-05 21:47:34 +00002317 kernel=(double *) RelinquishMagickMemory(kernel);
2318 return(blur_image);
2319}
2320
2321/*
2322%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2323% %
2324% %
2325% %
cristy3ed852e2009-09-05 21:47:34 +00002326% M o t i o n B l u r I m a g e %
2327% %
2328% %
2329% %
2330%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2331%
2332% MotionBlurImage() simulates motion blur. We convolve the image with a
2333% Gaussian operator of the given radius and standard deviation (sigma).
2334% For reasonable results, radius should be larger than sigma. Use a
2335% radius of 0 and MotionBlurImage() selects a suitable radius for you.
2336% Angle gives the angle of the blurring motion.
2337%
2338% Andrew Protano contributed this effect.
2339%
2340% The format of the MotionBlurImage method is:
2341%
2342% Image *MotionBlurImage(const Image *image,const double radius,
2343% const double sigma,const double angle,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002344%
2345% A description of each parameter follows:
2346%
2347% o image: the image.
2348%
cristy3ed852e2009-09-05 21:47:34 +00002349% o radius: the radius of the Gaussian, in pixels, not counting
2350% the center pixel.
2351%
2352% o sigma: the standard deviation of the Gaussian, in pixels.
2353%
cristycee97112010-05-28 00:44:52 +00002354% o angle: Apply the effect along this angle.
cristy3ed852e2009-09-05 21:47:34 +00002355%
2356% o exception: return any errors or warnings in this structure.
2357%
2358*/
2359
cristybb503372010-05-27 20:51:26 +00002360static double *GetMotionBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +00002361{
cristy3ed852e2009-09-05 21:47:34 +00002362 double
cristy47e00502009-12-17 19:19:57 +00002363 *kernel,
cristy3ed852e2009-09-05 21:47:34 +00002364 normalize;
2365
cristybb503372010-05-27 20:51:26 +00002366 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002367 i;
2368
2369 /*
cristy47e00502009-12-17 19:19:57 +00002370 Generate a 1-D convolution kernel.
cristy3ed852e2009-09-05 21:47:34 +00002371 */
2372 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
2373 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
2374 if (kernel == (double *) NULL)
2375 return(kernel);
cristy3ed852e2009-09-05 21:47:34 +00002376 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00002377 for (i=0; i < (ssize_t) width; i++)
cristy47e00502009-12-17 19:19:57 +00002378 {
cristy4205a3c2010-09-12 20:19:59 +00002379 kernel[i]=(double) (exp((-((double) i*i)/(double) (2.0*MagickSigma*
2380 MagickSigma)))/(MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00002381 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +00002382 }
cristybb503372010-05-27 20:51:26 +00002383 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002384 kernel[i]/=normalize;
2385 return(kernel);
2386}
2387
cristyf4ad9df2011-07-08 16:49:03 +00002388MagickExport Image *MotionBlurImage(const Image *image,
2389 const double radius,const double sigma,const double angle,
2390 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002391{
cristyc4c8d132010-01-07 01:58:38 +00002392 CacheView
2393 *blur_view,
2394 *image_view;
2395
cristy3ed852e2009-09-05 21:47:34 +00002396 double
2397 *kernel;
2398
2399 Image
2400 *blur_image;
2401
cristy3ed852e2009-09-05 21:47:34 +00002402 MagickBooleanType
2403 status;
2404
cristybb503372010-05-27 20:51:26 +00002405 MagickOffsetType
2406 progress;
2407
cristy4c08aed2011-07-01 19:47:50 +00002408 PixelInfo
cristyddd82202009-11-03 20:14:50 +00002409 bias;
cristy3ed852e2009-09-05 21:47:34 +00002410
2411 OffsetInfo
2412 *offset;
2413
2414 PointInfo
2415 point;
2416
cristybb503372010-05-27 20:51:26 +00002417 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002418 i;
2419
cristybb503372010-05-27 20:51:26 +00002420 size_t
cristy3ed852e2009-09-05 21:47:34 +00002421 width;
2422
cristybb503372010-05-27 20:51:26 +00002423 ssize_t
2424 y;
2425
cristy3ed852e2009-09-05 21:47:34 +00002426 assert(image != (Image *) NULL);
2427 assert(image->signature == MagickSignature);
2428 if (image->debug != MagickFalse)
2429 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2430 assert(exception != (ExceptionInfo *) NULL);
2431 width=GetOptimalKernelWidth1D(radius,sigma);
2432 kernel=GetMotionBlurKernel(width,sigma);
2433 if (kernel == (double *) NULL)
2434 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2435 offset=(OffsetInfo *) AcquireQuantumMemory(width,sizeof(*offset));
2436 if (offset == (OffsetInfo *) NULL)
2437 {
2438 kernel=(double *) RelinquishMagickMemory(kernel);
2439 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2440 }
2441 blur_image=CloneImage(image,0,0,MagickTrue,exception);
2442 if (blur_image == (Image *) NULL)
2443 {
2444 kernel=(double *) RelinquishMagickMemory(kernel);
2445 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2446 return((Image *) NULL);
2447 }
2448 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
2449 {
2450 kernel=(double *) RelinquishMagickMemory(kernel);
2451 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2452 InheritException(exception,&blur_image->exception);
2453 blur_image=DestroyImage(blur_image);
2454 return((Image *) NULL);
2455 }
2456 point.x=(double) width*sin(DegreesToRadians(angle));
2457 point.y=(double) width*cos(DegreesToRadians(angle));
cristybb503372010-05-27 20:51:26 +00002458 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002459 {
cristybb503372010-05-27 20:51:26 +00002460 offset[i].x=(ssize_t) ceil((double) (i*point.y)/hypot(point.x,point.y)-0.5);
2461 offset[i].y=(ssize_t) ceil((double) (i*point.x)/hypot(point.x,point.y)-0.5);
cristy3ed852e2009-09-05 21:47:34 +00002462 }
2463 /*
2464 Motion blur image.
2465 */
2466 status=MagickTrue;
2467 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00002468 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00002469 image_view=AcquireCacheView(image);
2470 blur_view=AcquireCacheView(blur_image);
cristyb557a152011-02-22 12:14:30 +00002471#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00002472 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00002473#endif
cristybb503372010-05-27 20:51:26 +00002474 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002475 {
cristy4c08aed2011-07-01 19:47:50 +00002476 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002477 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002478
cristy117ff172010-08-15 21:35:32 +00002479 register ssize_t
2480 x;
2481
cristy3ed852e2009-09-05 21:47:34 +00002482 if (status == MagickFalse)
2483 continue;
2484 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
2485 exception);
cristy4c08aed2011-07-01 19:47:50 +00002486 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002487 {
2488 status=MagickFalse;
2489 continue;
2490 }
cristybb503372010-05-27 20:51:26 +00002491 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002492 {
cristy4c08aed2011-07-01 19:47:50 +00002493 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002494 qixel;
2495
2496 PixelPacket
2497 pixel;
2498
2499 register double
cristyc47d1f82009-11-26 01:44:43 +00002500 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00002501
cristybb503372010-05-27 20:51:26 +00002502 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002503 i;
2504
cristy3ed852e2009-09-05 21:47:34 +00002505 k=kernel;
cristyddd82202009-11-03 20:14:50 +00002506 qixel=bias;
cristy2b9582a2011-07-04 17:38:56 +00002507 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00002508 {
cristybb503372010-05-27 20:51:26 +00002509 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002510 {
2511 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
2512 offset[i].y,&pixel,exception);
2513 qixel.red+=(*k)*pixel.red;
2514 qixel.green+=(*k)*pixel.green;
2515 qixel.blue+=(*k)*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00002516 qixel.alpha+=(*k)*pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00002517 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00002518 qixel.black+=(*k)*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00002519 k++;
2520 }
cristy2b9582a2011-07-04 17:38:56 +00002521 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002522 SetPixelRed(blur_image,
2523 ClampToQuantum(qixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00002524 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002525 SetPixelGreen(blur_image,
2526 ClampToQuantum(qixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00002527 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002528 SetPixelBlue(blur_image,
2529 ClampToQuantum(qixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00002530 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002531 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002532 SetPixelBlack(blur_image,
2533 ClampToQuantum(qixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00002534 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002535 SetPixelAlpha(blur_image,
2536 ClampToQuantum(qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00002537 }
2538 else
2539 {
2540 MagickRealType
2541 alpha,
2542 gamma;
2543
2544 alpha=0.0;
2545 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00002546 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002547 {
2548 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
2549 offset[i].y,&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00002550 alpha=(MagickRealType) (QuantumScale*pixel.alpha);
cristy3ed852e2009-09-05 21:47:34 +00002551 qixel.red+=(*k)*alpha*pixel.red;
2552 qixel.green+=(*k)*alpha*pixel.green;
2553 qixel.blue+=(*k)*alpha*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00002554 qixel.alpha+=(*k)*pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00002555 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00002556 qixel.black+=(*k)*alpha*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00002557 gamma+=(*k)*alpha;
2558 k++;
2559 }
2560 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy2b9582a2011-07-04 17:38:56 +00002561 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002562 SetPixelRed(blur_image,
2563 ClampToQuantum(gamma*qixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00002564 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002565 SetPixelGreen(blur_image,
2566 ClampToQuantum(gamma*qixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00002567 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002568 SetPixelBlue(blur_image,
2569 ClampToQuantum(gamma*qixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00002570 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002571 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002572 SetPixelBlack(blur_image,
2573 ClampToQuantum(gamma*qixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00002574 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002575 SetPixelAlpha(blur_image,
2576 ClampToQuantum(qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00002577 }
cristydcfc1ad2011-07-07 16:25:41 +00002578 q+=GetPixelComponents(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00002579 }
2580 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
2581 status=MagickFalse;
2582 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2583 {
2584 MagickBooleanType
2585 proceed;
2586
cristyb557a152011-02-22 12:14:30 +00002587#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00002588 #pragma omp critical (MagickCore_MotionBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00002589#endif
2590 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
2591 if (proceed == MagickFalse)
2592 status=MagickFalse;
2593 }
2594 }
2595 blur_view=DestroyCacheView(blur_view);
2596 image_view=DestroyCacheView(image_view);
2597 kernel=(double *) RelinquishMagickMemory(kernel);
2598 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2599 if (status == MagickFalse)
2600 blur_image=DestroyImage(blur_image);
2601 return(blur_image);
2602}
2603
2604/*
2605%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2606% %
2607% %
2608% %
2609% P r e v i e w I m a g e %
2610% %
2611% %
2612% %
2613%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2614%
2615% PreviewImage() tiles 9 thumbnails of the specified image with an image
2616% processing operation applied with varying parameters. This may be helpful
2617% pin-pointing an appropriate parameter for a particular image processing
2618% operation.
2619%
2620% The format of the PreviewImages method is:
2621%
2622% Image *PreviewImages(const Image *image,const PreviewType preview,
2623% ExceptionInfo *exception)
2624%
2625% A description of each parameter follows:
2626%
2627% o image: the image.
2628%
2629% o preview: the image processing operation.
2630%
2631% o exception: return any errors or warnings in this structure.
2632%
2633*/
2634MagickExport Image *PreviewImage(const Image *image,const PreviewType preview,
2635 ExceptionInfo *exception)
2636{
2637#define NumberTiles 9
2638#define PreviewImageTag "Preview/Image"
2639#define DefaultPreviewGeometry "204x204+10+10"
2640
2641 char
2642 factor[MaxTextExtent],
2643 label[MaxTextExtent];
2644
2645 double
2646 degrees,
2647 gamma,
2648 percentage,
2649 radius,
2650 sigma,
2651 threshold;
2652
2653 Image
2654 *images,
2655 *montage_image,
2656 *preview_image,
2657 *thumbnail;
2658
2659 ImageInfo
2660 *preview_info;
2661
cristy3ed852e2009-09-05 21:47:34 +00002662 MagickBooleanType
2663 proceed;
2664
2665 MontageInfo
2666 *montage_info;
2667
2668 QuantizeInfo
2669 quantize_info;
2670
2671 RectangleInfo
2672 geometry;
2673
cristybb503372010-05-27 20:51:26 +00002674 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002675 i,
2676 x;
2677
cristybb503372010-05-27 20:51:26 +00002678 size_t
cristy3ed852e2009-09-05 21:47:34 +00002679 colors;
2680
cristy117ff172010-08-15 21:35:32 +00002681 ssize_t
2682 y;
2683
cristy3ed852e2009-09-05 21:47:34 +00002684 /*
2685 Open output image file.
2686 */
2687 assert(image != (Image *) NULL);
2688 assert(image->signature == MagickSignature);
2689 if (image->debug != MagickFalse)
2690 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2691 colors=2;
2692 degrees=0.0;
2693 gamma=(-0.2f);
2694 preview_info=AcquireImageInfo();
2695 SetGeometry(image,&geometry);
2696 (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y,
2697 &geometry.width,&geometry.height);
2698 images=NewImageList();
2699 percentage=12.5;
2700 GetQuantizeInfo(&quantize_info);
2701 radius=0.0;
2702 sigma=1.0;
2703 threshold=0.0;
2704 x=0;
2705 y=0;
2706 for (i=0; i < NumberTiles; i++)
2707 {
2708 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
2709 if (thumbnail == (Image *) NULL)
2710 break;
2711 (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL,
2712 (void *) NULL);
2713 (void) SetImageProperty(thumbnail,"label",DefaultTileLabel);
2714 if (i == (NumberTiles/2))
2715 {
2716 (void) QueryColorDatabase("#dfdfdf",&thumbnail->matte_color,exception);
2717 AppendImageToList(&images,thumbnail);
2718 continue;
2719 }
2720 switch (preview)
2721 {
2722 case RotatePreview:
2723 {
2724 degrees+=45.0;
2725 preview_image=RotateImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002726 (void) FormatLocaleString(label,MaxTextExtent,"rotate %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002727 break;
2728 }
2729 case ShearPreview:
2730 {
2731 degrees+=5.0;
2732 preview_image=ShearImage(thumbnail,degrees,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002733 (void) FormatLocaleString(label,MaxTextExtent,"shear %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002734 degrees,2.0*degrees);
2735 break;
2736 }
2737 case RollPreview:
2738 {
cristybb503372010-05-27 20:51:26 +00002739 x=(ssize_t) ((i+1)*thumbnail->columns)/NumberTiles;
2740 y=(ssize_t) ((i+1)*thumbnail->rows)/NumberTiles;
cristy3ed852e2009-09-05 21:47:34 +00002741 preview_image=RollImage(thumbnail,x,y,exception);
cristyb51dff52011-05-19 16:55:47 +00002742 (void) FormatLocaleString(label,MaxTextExtent,"roll %+.20gx%+.20g",
cristye8c25f92010-06-03 00:53:06 +00002743 (double) x,(double) y);
cristy3ed852e2009-09-05 21:47:34 +00002744 break;
2745 }
2746 case HuePreview:
2747 {
2748 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2749 if (preview_image == (Image *) NULL)
2750 break;
cristyb51dff52011-05-19 16:55:47 +00002751 (void) FormatLocaleString(factor,MaxTextExtent,"100,100,%g",
cristy3ed852e2009-09-05 21:47:34 +00002752 2.0*percentage);
2753 (void) ModulateImage(preview_image,factor);
cristyb51dff52011-05-19 16:55:47 +00002754 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002755 break;
2756 }
2757 case SaturationPreview:
2758 {
2759 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2760 if (preview_image == (Image *) NULL)
2761 break;
cristyb51dff52011-05-19 16:55:47 +00002762 (void) FormatLocaleString(factor,MaxTextExtent,"100,%g",
cristy8cd5b312010-01-07 01:10:24 +00002763 2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00002764 (void) ModulateImage(preview_image,factor);
cristyb51dff52011-05-19 16:55:47 +00002765 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002766 break;
2767 }
2768 case BrightnessPreview:
2769 {
2770 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2771 if (preview_image == (Image *) NULL)
2772 break;
cristyb51dff52011-05-19 16:55:47 +00002773 (void) FormatLocaleString(factor,MaxTextExtent,"%g",2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00002774 (void) ModulateImage(preview_image,factor);
cristyb51dff52011-05-19 16:55:47 +00002775 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002776 break;
2777 }
2778 case GammaPreview:
2779 default:
2780 {
2781 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2782 if (preview_image == (Image *) NULL)
2783 break;
2784 gamma+=0.4f;
cristy50fbc382011-07-07 02:19:17 +00002785 (void) GammaImage(preview_image,gamma);
cristyb51dff52011-05-19 16:55:47 +00002786 (void) FormatLocaleString(label,MaxTextExtent,"gamma %g",gamma);
cristy3ed852e2009-09-05 21:47:34 +00002787 break;
2788 }
2789 case SpiffPreview:
2790 {
2791 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2792 if (preview_image != (Image *) NULL)
2793 for (x=0; x < i; x++)
2794 (void) ContrastImage(preview_image,MagickTrue);
cristyb51dff52011-05-19 16:55:47 +00002795 (void) FormatLocaleString(label,MaxTextExtent,"contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002796 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002797 break;
2798 }
2799 case DullPreview:
2800 {
2801 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2802 if (preview_image == (Image *) NULL)
2803 break;
2804 for (x=0; x < i; x++)
2805 (void) ContrastImage(preview_image,MagickFalse);
cristyb51dff52011-05-19 16:55:47 +00002806 (void) FormatLocaleString(label,MaxTextExtent,"+contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002807 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002808 break;
2809 }
2810 case GrayscalePreview:
2811 {
2812 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2813 if (preview_image == (Image *) NULL)
2814 break;
2815 colors<<=1;
2816 quantize_info.number_colors=colors;
2817 quantize_info.colorspace=GRAYColorspace;
2818 (void) QuantizeImage(&quantize_info,preview_image);
cristyb51dff52011-05-19 16:55:47 +00002819 (void) FormatLocaleString(label,MaxTextExtent,
cristye8c25f92010-06-03 00:53:06 +00002820 "-colorspace gray -colors %.20g",(double) colors);
cristy3ed852e2009-09-05 21:47:34 +00002821 break;
2822 }
2823 case QuantizePreview:
2824 {
2825 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2826 if (preview_image == (Image *) NULL)
2827 break;
2828 colors<<=1;
2829 quantize_info.number_colors=colors;
2830 (void) QuantizeImage(&quantize_info,preview_image);
cristyb51dff52011-05-19 16:55:47 +00002831 (void) FormatLocaleString(label,MaxTextExtent,"colors %.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00002832 colors);
cristy3ed852e2009-09-05 21:47:34 +00002833 break;
2834 }
2835 case DespecklePreview:
2836 {
2837 for (x=0; x < (i-1); x++)
2838 {
2839 preview_image=DespeckleImage(thumbnail,exception);
2840 if (preview_image == (Image *) NULL)
2841 break;
2842 thumbnail=DestroyImage(thumbnail);
2843 thumbnail=preview_image;
2844 }
2845 preview_image=DespeckleImage(thumbnail,exception);
2846 if (preview_image == (Image *) NULL)
2847 break;
cristyb51dff52011-05-19 16:55:47 +00002848 (void) FormatLocaleString(label,MaxTextExtent,"despeckle (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002849 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002850 break;
2851 }
2852 case ReduceNoisePreview:
2853 {
cristy95c38342011-03-18 22:39:51 +00002854 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) radius,
2855 (size_t) radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002856 (void) FormatLocaleString(label,MaxTextExtent,"noise %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002857 break;
2858 }
2859 case AddNoisePreview:
2860 {
2861 switch ((int) i)
2862 {
2863 case 0:
2864 {
2865 (void) CopyMagickString(factor,"uniform",MaxTextExtent);
2866 break;
2867 }
2868 case 1:
2869 {
2870 (void) CopyMagickString(factor,"gaussian",MaxTextExtent);
2871 break;
2872 }
2873 case 2:
2874 {
2875 (void) CopyMagickString(factor,"multiplicative",MaxTextExtent);
2876 break;
2877 }
2878 case 3:
2879 {
2880 (void) CopyMagickString(factor,"impulse",MaxTextExtent);
2881 break;
2882 }
2883 case 4:
2884 {
2885 (void) CopyMagickString(factor,"laplacian",MaxTextExtent);
2886 break;
2887 }
2888 case 5:
2889 {
2890 (void) CopyMagickString(factor,"Poisson",MaxTextExtent);
2891 break;
2892 }
2893 default:
2894 {
2895 (void) CopyMagickString(thumbnail->magick,"NULL",MaxTextExtent);
2896 break;
2897 }
2898 }
cristyd76c51e2011-03-26 00:21:26 +00002899 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) i,
2900 (size_t) i,exception);
cristyb51dff52011-05-19 16:55:47 +00002901 (void) FormatLocaleString(label,MaxTextExtent,"+noise %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002902 break;
2903 }
2904 case SharpenPreview:
2905 {
2906 preview_image=SharpenImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00002907 (void) FormatLocaleString(label,MaxTextExtent,"sharpen %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002908 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002909 break;
2910 }
2911 case BlurPreview:
2912 {
2913 preview_image=BlurImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00002914 (void) FormatLocaleString(label,MaxTextExtent,"blur %gx%g",radius,
cristy3ed852e2009-09-05 21:47:34 +00002915 sigma);
2916 break;
2917 }
2918 case ThresholdPreview:
2919 {
2920 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2921 if (preview_image == (Image *) NULL)
2922 break;
2923 (void) BilevelImage(thumbnail,
2924 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
cristyb51dff52011-05-19 16:55:47 +00002925 (void) FormatLocaleString(label,MaxTextExtent,"threshold %g",
cristy3ed852e2009-09-05 21:47:34 +00002926 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
2927 break;
2928 }
2929 case EdgeDetectPreview:
2930 {
2931 preview_image=EdgeImage(thumbnail,radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002932 (void) FormatLocaleString(label,MaxTextExtent,"edge %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002933 break;
2934 }
2935 case SpreadPreview:
2936 {
2937 preview_image=SpreadImage(thumbnail,radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002938 (void) FormatLocaleString(label,MaxTextExtent,"spread %g",
cristy8cd5b312010-01-07 01:10:24 +00002939 radius+0.5);
cristy3ed852e2009-09-05 21:47:34 +00002940 break;
2941 }
2942 case SolarizePreview:
2943 {
2944 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2945 if (preview_image == (Image *) NULL)
2946 break;
2947 (void) SolarizeImage(preview_image,(double) QuantumRange*
2948 percentage/100.0);
cristyb51dff52011-05-19 16:55:47 +00002949 (void) FormatLocaleString(label,MaxTextExtent,"solarize %g",
cristy3ed852e2009-09-05 21:47:34 +00002950 (QuantumRange*percentage)/100.0);
2951 break;
2952 }
2953 case ShadePreview:
2954 {
2955 degrees+=10.0;
2956 preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees,
2957 exception);
cristyb51dff52011-05-19 16:55:47 +00002958 (void) FormatLocaleString(label,MaxTextExtent,"shade %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002959 degrees,degrees);
cristy3ed852e2009-09-05 21:47:34 +00002960 break;
2961 }
2962 case RaisePreview:
2963 {
2964 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2965 if (preview_image == (Image *) NULL)
2966 break;
cristybb503372010-05-27 20:51:26 +00002967 geometry.width=(size_t) (2*i+2);
2968 geometry.height=(size_t) (2*i+2);
cristy3ed852e2009-09-05 21:47:34 +00002969 geometry.x=i/2;
2970 geometry.y=i/2;
2971 (void) RaiseImage(preview_image,&geometry,MagickTrue);
cristyb51dff52011-05-19 16:55:47 +00002972 (void) FormatLocaleString(label,MaxTextExtent,
cristy6d8abba2010-06-03 01:10:47 +00002973 "raise %.20gx%.20g%+.20g%+.20g",(double) geometry.width,(double)
cristye8c25f92010-06-03 00:53:06 +00002974 geometry.height,(double) geometry.x,(double) geometry.y);
cristy3ed852e2009-09-05 21:47:34 +00002975 break;
2976 }
2977 case SegmentPreview:
2978 {
2979 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2980 if (preview_image == (Image *) NULL)
2981 break;
2982 threshold+=0.4f;
2983 (void) SegmentImage(preview_image,RGBColorspace,MagickFalse,threshold,
2984 threshold);
cristyb51dff52011-05-19 16:55:47 +00002985 (void) FormatLocaleString(label,MaxTextExtent,"segment %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002986 threshold,threshold);
2987 break;
2988 }
2989 case SwirlPreview:
2990 {
2991 preview_image=SwirlImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002992 (void) FormatLocaleString(label,MaxTextExtent,"swirl %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002993 degrees+=45.0;
2994 break;
2995 }
2996 case ImplodePreview:
2997 {
2998 degrees+=0.1f;
2999 preview_image=ImplodeImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00003000 (void) FormatLocaleString(label,MaxTextExtent,"implode %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003001 break;
3002 }
3003 case WavePreview:
3004 {
3005 degrees+=5.0f;
3006 preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00003007 (void) FormatLocaleString(label,MaxTextExtent,"wave %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003008 0.5*degrees,2.0*degrees);
cristy3ed852e2009-09-05 21:47:34 +00003009 break;
3010 }
3011 case OilPaintPreview:
3012 {
3013 preview_image=OilPaintImage(thumbnail,(double) radius,exception);
cristyb51dff52011-05-19 16:55:47 +00003014 (void) FormatLocaleString(label,MaxTextExtent,"paint %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003015 break;
3016 }
3017 case CharcoalDrawingPreview:
3018 {
3019 preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma,
3020 exception);
cristyb51dff52011-05-19 16:55:47 +00003021 (void) FormatLocaleString(label,MaxTextExtent,"charcoal %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003022 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00003023 break;
3024 }
3025 case JPEGPreview:
3026 {
3027 char
3028 filename[MaxTextExtent];
3029
3030 int
3031 file;
3032
3033 MagickBooleanType
3034 status;
3035
3036 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3037 if (preview_image == (Image *) NULL)
3038 break;
cristybb503372010-05-27 20:51:26 +00003039 preview_info->quality=(size_t) percentage;
cristyb51dff52011-05-19 16:55:47 +00003040 (void) FormatLocaleString(factor,MaxTextExtent,"%.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00003041 preview_info->quality);
cristy3ed852e2009-09-05 21:47:34 +00003042 file=AcquireUniqueFileResource(filename);
3043 if (file != -1)
3044 file=close(file)-1;
cristyb51dff52011-05-19 16:55:47 +00003045 (void) FormatLocaleString(preview_image->filename,MaxTextExtent,
cristy3ed852e2009-09-05 21:47:34 +00003046 "jpeg:%s",filename);
3047 status=WriteImage(preview_info,preview_image);
3048 if (status != MagickFalse)
3049 {
3050 Image
3051 *quality_image;
3052
3053 (void) CopyMagickString(preview_info->filename,
3054 preview_image->filename,MaxTextExtent);
3055 quality_image=ReadImage(preview_info,exception);
3056 if (quality_image != (Image *) NULL)
3057 {
3058 preview_image=DestroyImage(preview_image);
3059 preview_image=quality_image;
3060 }
3061 }
3062 (void) RelinquishUniqueFileResource(preview_image->filename);
3063 if ((GetBlobSize(preview_image)/1024) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00003064 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%gmb ",
cristy3ed852e2009-09-05 21:47:34 +00003065 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
3066 1024.0/1024.0);
3067 else
3068 if (GetBlobSize(preview_image) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00003069 (void) FormatLocaleString(label,MaxTextExtent,
cristye7f51092010-01-17 00:39:37 +00003070 "quality %s\n%gkb ",factor,(double) ((MagickOffsetType)
cristy8cd5b312010-01-07 01:10:24 +00003071 GetBlobSize(preview_image))/1024.0);
cristy3ed852e2009-09-05 21:47:34 +00003072 else
cristyb51dff52011-05-19 16:55:47 +00003073 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%.20gb ",
cristy54ea5732011-06-10 12:39:53 +00003074 factor,(double) ((MagickOffsetType) GetBlobSize(thumbnail)));
cristy3ed852e2009-09-05 21:47:34 +00003075 break;
3076 }
3077 }
3078 thumbnail=DestroyImage(thumbnail);
3079 percentage+=12.5;
3080 radius+=0.5;
3081 sigma+=0.25;
3082 if (preview_image == (Image *) NULL)
3083 break;
3084 (void) DeleteImageProperty(preview_image,"label");
3085 (void) SetImageProperty(preview_image,"label",label);
3086 AppendImageToList(&images,preview_image);
cristybb503372010-05-27 20:51:26 +00003087 proceed=SetImageProgress(image,PreviewImageTag,(MagickOffsetType) i,
3088 NumberTiles);
cristy3ed852e2009-09-05 21:47:34 +00003089 if (proceed == MagickFalse)
3090 break;
3091 }
3092 if (images == (Image *) NULL)
3093 {
3094 preview_info=DestroyImageInfo(preview_info);
3095 return((Image *) NULL);
3096 }
3097 /*
3098 Create the montage.
3099 */
3100 montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL);
3101 (void) CopyMagickString(montage_info->filename,image->filename,MaxTextExtent);
3102 montage_info->shadow=MagickTrue;
3103 (void) CloneString(&montage_info->tile,"3x3");
3104 (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry);
3105 (void) CloneString(&montage_info->frame,DefaultTileFrame);
3106 montage_image=MontageImages(images,montage_info,exception);
3107 montage_info=DestroyMontageInfo(montage_info);
3108 images=DestroyImageList(images);
3109 if (montage_image == (Image *) NULL)
3110 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3111 if (montage_image->montage != (char *) NULL)
3112 {
3113 /*
3114 Free image directory.
3115 */
3116 montage_image->montage=(char *) RelinquishMagickMemory(
3117 montage_image->montage);
3118 if (image->directory != (char *) NULL)
3119 montage_image->directory=(char *) RelinquishMagickMemory(
3120 montage_image->directory);
3121 }
3122 preview_info=DestroyImageInfo(preview_info);
3123 return(montage_image);
3124}
3125
3126/*
3127%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3128% %
3129% %
3130% %
3131% R a d i a l B l u r I m a g e %
3132% %
3133% %
3134% %
3135%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3136%
3137% RadialBlurImage() applies a radial blur to the image.
3138%
3139% Andrew Protano contributed this effect.
3140%
3141% The format of the RadialBlurImage method is:
3142%
3143% Image *RadialBlurImage(const Image *image,const double angle,
3144% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003145%
3146% A description of each parameter follows:
3147%
3148% o image: the image.
3149%
cristy3ed852e2009-09-05 21:47:34 +00003150% o angle: the angle of the radial blur.
3151%
3152% o exception: return any errors or warnings in this structure.
3153%
3154*/
cristyf4ad9df2011-07-08 16:49:03 +00003155MagickExport Image *RadialBlurImage(const Image *image,
3156 const double angle,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003157{
cristyc4c8d132010-01-07 01:58:38 +00003158 CacheView
3159 *blur_view,
3160 *image_view;
3161
cristy3ed852e2009-09-05 21:47:34 +00003162 Image
3163 *blur_image;
3164
cristy3ed852e2009-09-05 21:47:34 +00003165 MagickBooleanType
3166 status;
3167
cristybb503372010-05-27 20:51:26 +00003168 MagickOffsetType
3169 progress;
3170
cristy4c08aed2011-07-01 19:47:50 +00003171 PixelInfo
cristyddd82202009-11-03 20:14:50 +00003172 bias;
cristy3ed852e2009-09-05 21:47:34 +00003173
3174 MagickRealType
3175 blur_radius,
3176 *cos_theta,
3177 offset,
3178 *sin_theta,
3179 theta;
3180
3181 PointInfo
3182 blur_center;
3183
cristybb503372010-05-27 20:51:26 +00003184 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003185 i;
3186
cristybb503372010-05-27 20:51:26 +00003187 size_t
cristy3ed852e2009-09-05 21:47:34 +00003188 n;
3189
cristybb503372010-05-27 20:51:26 +00003190 ssize_t
3191 y;
3192
cristy3ed852e2009-09-05 21:47:34 +00003193 /*
3194 Allocate blur image.
3195 */
3196 assert(image != (Image *) NULL);
3197 assert(image->signature == MagickSignature);
3198 if (image->debug != MagickFalse)
3199 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3200 assert(exception != (ExceptionInfo *) NULL);
3201 assert(exception->signature == MagickSignature);
3202 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3203 if (blur_image == (Image *) NULL)
3204 return((Image *) NULL);
3205 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3206 {
3207 InheritException(exception,&blur_image->exception);
3208 blur_image=DestroyImage(blur_image);
3209 return((Image *) NULL);
3210 }
3211 blur_center.x=(double) image->columns/2.0;
3212 blur_center.y=(double) image->rows/2.0;
3213 blur_radius=hypot(blur_center.x,blur_center.y);
cristy117ff172010-08-15 21:35:32 +00003214 n=(size_t) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+2UL);
cristy3ed852e2009-09-05 21:47:34 +00003215 theta=DegreesToRadians(angle)/(MagickRealType) (n-1);
3216 cos_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3217 sizeof(*cos_theta));
3218 sin_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3219 sizeof(*sin_theta));
3220 if ((cos_theta == (MagickRealType *) NULL) ||
3221 (sin_theta == (MagickRealType *) NULL))
3222 {
3223 blur_image=DestroyImage(blur_image);
3224 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3225 }
3226 offset=theta*(MagickRealType) (n-1)/2.0;
cristybb503372010-05-27 20:51:26 +00003227 for (i=0; i < (ssize_t) n; i++)
cristy3ed852e2009-09-05 21:47:34 +00003228 {
3229 cos_theta[i]=cos((double) (theta*i-offset));
3230 sin_theta[i]=sin((double) (theta*i-offset));
3231 }
3232 /*
3233 Radial blur image.
3234 */
3235 status=MagickTrue;
3236 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00003237 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003238 image_view=AcquireCacheView(image);
3239 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003240#if defined(MAGICKCORE_OPENMP_SUPPORT)
3241 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003242#endif
cristybb503372010-05-27 20:51:26 +00003243 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003244 {
cristy4c08aed2011-07-01 19:47:50 +00003245 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003246 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003247
cristy117ff172010-08-15 21:35:32 +00003248 register ssize_t
3249 x;
3250
cristy3ed852e2009-09-05 21:47:34 +00003251 if (status == MagickFalse)
3252 continue;
3253 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3254 exception);
cristy4c08aed2011-07-01 19:47:50 +00003255 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003256 {
3257 status=MagickFalse;
3258 continue;
3259 }
cristybb503372010-05-27 20:51:26 +00003260 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003261 {
cristy4c08aed2011-07-01 19:47:50 +00003262 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003263 qixel;
3264
3265 MagickRealType
3266 normalize,
3267 radius;
3268
3269 PixelPacket
3270 pixel;
3271
3272 PointInfo
3273 center;
3274
cristybb503372010-05-27 20:51:26 +00003275 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003276 i;
3277
cristybb503372010-05-27 20:51:26 +00003278 size_t
cristy3ed852e2009-09-05 21:47:34 +00003279 step;
3280
3281 center.x=(double) x-blur_center.x;
3282 center.y=(double) y-blur_center.y;
3283 radius=hypot((double) center.x,center.y);
3284 if (radius == 0)
3285 step=1;
3286 else
3287 {
cristybb503372010-05-27 20:51:26 +00003288 step=(size_t) (blur_radius/radius);
cristy3ed852e2009-09-05 21:47:34 +00003289 if (step == 0)
3290 step=1;
3291 else
3292 if (step >= n)
3293 step=n-1;
3294 }
3295 normalize=0.0;
cristyddd82202009-11-03 20:14:50 +00003296 qixel=bias;
cristy2b9582a2011-07-04 17:38:56 +00003297 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00003298 {
cristyeaedf062010-05-29 22:36:02 +00003299 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00003300 {
cristyeaedf062010-05-29 22:36:02 +00003301 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
3302 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
3303 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
3304 cos_theta[i]+0.5),&pixel,exception);
cristy3ed852e2009-09-05 21:47:34 +00003305 qixel.red+=pixel.red;
3306 qixel.green+=pixel.green;
3307 qixel.blue+=pixel.blue;
cristy3ed852e2009-09-05 21:47:34 +00003308 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00003309 qixel.black+=pixel.black;
3310 qixel.alpha+=pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00003311 normalize+=1.0;
3312 }
3313 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
3314 normalize);
cristy2b9582a2011-07-04 17:38:56 +00003315 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003316 SetPixelRed(blur_image,
3317 ClampToQuantum(normalize*qixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00003318 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003319 SetPixelGreen(blur_image,
3320 ClampToQuantum(normalize*qixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00003321 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003322 SetPixelBlue(blur_image,
3323 ClampToQuantum(normalize*qixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00003324 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003325 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003326 SetPixelBlack(blur_image,
3327 ClampToQuantum(normalize*qixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00003328 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003329 SetPixelAlpha(blur_image,
3330 ClampToQuantum(normalize*qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003331 }
3332 else
3333 {
3334 MagickRealType
3335 alpha,
3336 gamma;
3337
3338 alpha=1.0;
3339 gamma=0.0;
cristyeaedf062010-05-29 22:36:02 +00003340 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00003341 {
cristyeaedf062010-05-29 22:36:02 +00003342 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
3343 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
3344 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
3345 cos_theta[i]+0.5),&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00003346 alpha=(MagickRealType) (QuantumScale*pixel.alpha);
cristy3ed852e2009-09-05 21:47:34 +00003347 qixel.red+=alpha*pixel.red;
3348 qixel.green+=alpha*pixel.green;
3349 qixel.blue+=alpha*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00003350 qixel.alpha+=pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00003351 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00003352 qixel.black+=alpha*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00003353 gamma+=alpha;
3354 normalize+=1.0;
3355 }
3356 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3357 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
3358 normalize);
cristy2b9582a2011-07-04 17:38:56 +00003359 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003360 SetPixelRed(blur_image,
3361 ClampToQuantum(gamma*qixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00003362 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003363 SetPixelGreen(blur_image,
3364 ClampToQuantum(gamma*qixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00003365 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003366 SetPixelBlue(blur_image,
3367 ClampToQuantum(gamma*qixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00003368 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003369 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003370 SetPixelBlack(blur_image,
3371 ClampToQuantum(gamma*qixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00003372 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003373 SetPixelAlpha(blur_image,
3374 ClampToQuantum(normalize*qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003375 }
cristydcfc1ad2011-07-07 16:25:41 +00003376 q+=GetPixelComponents(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003377 }
3378 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
3379 status=MagickFalse;
3380 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3381 {
3382 MagickBooleanType
3383 proceed;
3384
cristyb5d5f722009-11-04 03:03:49 +00003385#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00003386 #pragma omp critical (MagickCore_RadialBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00003387#endif
3388 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
3389 if (proceed == MagickFalse)
3390 status=MagickFalse;
3391 }
3392 }
3393 blur_view=DestroyCacheView(blur_view);
3394 image_view=DestroyCacheView(image_view);
3395 cos_theta=(MagickRealType *) RelinquishMagickMemory(cos_theta);
3396 sin_theta=(MagickRealType *) RelinquishMagickMemory(sin_theta);
3397 if (status == MagickFalse)
3398 blur_image=DestroyImage(blur_image);
3399 return(blur_image);
3400}
3401
3402/*
3403%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3404% %
3405% %
3406% %
cristy3ed852e2009-09-05 21:47:34 +00003407% S e l e c t i v e B l u r I m a g e %
3408% %
3409% %
3410% %
3411%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3412%
3413% SelectiveBlurImage() selectively blur pixels within a contrast threshold.
3414% It is similar to the unsharpen mask that sharpens everything with contrast
3415% above a certain threshold.
3416%
3417% The format of the SelectiveBlurImage method is:
3418%
3419% Image *SelectiveBlurImage(const Image *image,const double radius,
3420% const double sigma,const double threshold,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003421%
3422% A description of each parameter follows:
3423%
3424% o image: the image.
3425%
cristy3ed852e2009-09-05 21:47:34 +00003426% o radius: the radius of the Gaussian, in pixels, not counting the center
3427% pixel.
3428%
3429% o sigma: the standard deviation of the Gaussian, in pixels.
3430%
3431% o threshold: only pixels within this contrast threshold are included
3432% in the blur operation.
3433%
3434% o exception: return any errors or warnings in this structure.
3435%
3436*/
cristyf4ad9df2011-07-08 16:49:03 +00003437MagickExport Image *SelectiveBlurImage(const Image *image,
3438 const double radius,const double sigma,const double threshold,
3439 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003440{
3441#define SelectiveBlurImageTag "SelectiveBlur/Image"
3442
cristy47e00502009-12-17 19:19:57 +00003443 CacheView
3444 *blur_view,
3445 *image_view;
3446
cristy3ed852e2009-09-05 21:47:34 +00003447 double
cristy3ed852e2009-09-05 21:47:34 +00003448 *kernel;
3449
3450 Image
3451 *blur_image;
3452
cristy3ed852e2009-09-05 21:47:34 +00003453 MagickBooleanType
3454 status;
3455
cristybb503372010-05-27 20:51:26 +00003456 MagickOffsetType
3457 progress;
3458
cristy4c08aed2011-07-01 19:47:50 +00003459 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003460 bias;
3461
cristybb503372010-05-27 20:51:26 +00003462 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003463 i;
cristy3ed852e2009-09-05 21:47:34 +00003464
cristybb503372010-05-27 20:51:26 +00003465 size_t
cristy3ed852e2009-09-05 21:47:34 +00003466 width;
3467
cristybb503372010-05-27 20:51:26 +00003468 ssize_t
3469 j,
3470 u,
3471 v,
3472 y;
3473
cristy3ed852e2009-09-05 21:47:34 +00003474 /*
3475 Initialize blur image attributes.
3476 */
3477 assert(image != (Image *) NULL);
3478 assert(image->signature == MagickSignature);
3479 if (image->debug != MagickFalse)
3480 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3481 assert(exception != (ExceptionInfo *) NULL);
3482 assert(exception->signature == MagickSignature);
3483 width=GetOptimalKernelWidth1D(radius,sigma);
3484 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
3485 if (kernel == (double *) NULL)
3486 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00003487 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00003488 i=0;
cristy47e00502009-12-17 19:19:57 +00003489 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00003490 {
cristy47e00502009-12-17 19:19:57 +00003491 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00003492 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
3493 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00003494 }
3495 if (image->debug != MagickFalse)
3496 {
3497 char
3498 format[MaxTextExtent],
3499 *message;
3500
cristy117ff172010-08-15 21:35:32 +00003501 register const double
3502 *k;
3503
cristybb503372010-05-27 20:51:26 +00003504 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003505 u,
3506 v;
3507
cristy3ed852e2009-09-05 21:47:34 +00003508 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00003509 " SelectiveBlurImage with %.20gx%.20g kernel:",(double) width,(double)
3510 width);
cristy3ed852e2009-09-05 21:47:34 +00003511 message=AcquireString("");
3512 k=kernel;
cristybb503372010-05-27 20:51:26 +00003513 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003514 {
3515 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00003516 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy3ed852e2009-09-05 21:47:34 +00003517 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00003518 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003519 {
cristyb51dff52011-05-19 16:55:47 +00003520 (void) FormatLocaleString(format,MaxTextExtent,"%+f ",*k++);
cristy3ed852e2009-09-05 21:47:34 +00003521 (void) ConcatenateString(&message,format);
3522 }
3523 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
3524 }
3525 message=DestroyString(message);
3526 }
3527 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3528 if (blur_image == (Image *) NULL)
3529 return((Image *) NULL);
3530 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3531 {
3532 InheritException(exception,&blur_image->exception);
3533 blur_image=DestroyImage(blur_image);
3534 return((Image *) NULL);
3535 }
3536 /*
3537 Threshold blur image.
3538 */
3539 status=MagickTrue;
3540 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00003541 GetPixelInfo(image,&bias);
3542 SetPixelInfoBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003543 image_view=AcquireCacheView(image);
3544 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003545#if defined(MAGICKCORE_OPENMP_SUPPORT)
3546 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003547#endif
cristybb503372010-05-27 20:51:26 +00003548 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003549 {
cristy4c08aed2011-07-01 19:47:50 +00003550 double
3551 contrast;
3552
cristy3ed852e2009-09-05 21:47:34 +00003553 MagickBooleanType
3554 sync;
3555
3556 MagickRealType
3557 gamma;
3558
cristy4c08aed2011-07-01 19:47:50 +00003559 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003560 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003561
cristy4c08aed2011-07-01 19:47:50 +00003562 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003563 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003564
cristy117ff172010-08-15 21:35:32 +00003565 register ssize_t
3566 x;
3567
cristy3ed852e2009-09-05 21:47:34 +00003568 if (status == MagickFalse)
3569 continue;
cristy117ff172010-08-15 21:35:32 +00003570 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
3571 (width/2L),image->columns+width,width,exception);
cristy3ed852e2009-09-05 21:47:34 +00003572 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3573 exception);
cristy4c08aed2011-07-01 19:47:50 +00003574 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003575 {
3576 status=MagickFalse;
3577 continue;
3578 }
cristybb503372010-05-27 20:51:26 +00003579 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003580 {
cristy4c08aed2011-07-01 19:47:50 +00003581 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003582 pixel;
3583
3584 register const double
cristyc47d1f82009-11-26 01:44:43 +00003585 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00003586
cristybb503372010-05-27 20:51:26 +00003587 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003588 u;
3589
cristy117ff172010-08-15 21:35:32 +00003590 ssize_t
3591 j,
3592 v;
3593
cristyddd82202009-11-03 20:14:50 +00003594 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00003595 k=kernel;
3596 gamma=0.0;
3597 j=0;
cristy2b9582a2011-07-04 17:38:56 +00003598 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00003599 {
cristybb503372010-05-27 20:51:26 +00003600 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003601 {
cristybb503372010-05-27 20:51:26 +00003602 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003603 {
cristydcfc1ad2011-07-07 16:25:41 +00003604 contrast=GetPixelIntensity(image,p+(u+j)*GetPixelComponents(image))-
cristy4c08aed2011-07-01 19:47:50 +00003605 (double) GetPixelIntensity(blur_image,q);
3606 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003607 {
cristy4c08aed2011-07-01 19:47:50 +00003608 pixel.red+=(*k)*
cristydcfc1ad2011-07-07 16:25:41 +00003609 GetPixelRed(image,p+(u+j)*GetPixelComponents(image));
cristy4c08aed2011-07-01 19:47:50 +00003610 pixel.green+=(*k)*
cristydcfc1ad2011-07-07 16:25:41 +00003611 GetPixelGreen(image,p+(u+j)*GetPixelComponents(image));
cristy4c08aed2011-07-01 19:47:50 +00003612 pixel.blue+=(*k)*
cristydcfc1ad2011-07-07 16:25:41 +00003613 GetPixelBlue(image,p+(u+j)*GetPixelComponents(image));
cristy4c08aed2011-07-01 19:47:50 +00003614 if (image->colorspace == CMYKColorspace)
3615 pixel.black+=(*k)*
cristydcfc1ad2011-07-07 16:25:41 +00003616 GetPixelBlack(image,p+(u+j)*GetPixelComponents(image));
cristy3ed852e2009-09-05 21:47:34 +00003617 gamma+=(*k);
3618 k++;
3619 }
3620 }
cristyd99b0962010-05-29 23:14:26 +00003621 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003622 }
3623 if (gamma != 0.0)
3624 {
3625 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy2b9582a2011-07-04 17:38:56 +00003626 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003627 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00003628 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003629 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00003630 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003631 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00003632 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00003633 (image->colorspace == CMYKColorspace))
3634 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00003635 }
cristy2b9582a2011-07-04 17:38:56 +00003636 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003637 {
3638 gamma=0.0;
3639 j=0;
cristybb503372010-05-27 20:51:26 +00003640 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003641 {
cristybb503372010-05-27 20:51:26 +00003642 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003643 {
cristy4c08aed2011-07-01 19:47:50 +00003644 contrast=GetPixelIntensity(image,p+(u+j)*
cristydcfc1ad2011-07-07 16:25:41 +00003645 GetPixelComponents(image))-(double)
cristy4c08aed2011-07-01 19:47:50 +00003646 GetPixelIntensity(blur_image,q);
3647 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003648 {
cristy4c08aed2011-07-01 19:47:50 +00003649 pixel.alpha+=(*k)*
cristydcfc1ad2011-07-07 16:25:41 +00003650 GetPixelAlpha(image,p+(u+j)*GetPixelComponents(image));
cristy3ed852e2009-09-05 21:47:34 +00003651 gamma+=(*k);
3652 k++;
3653 }
3654 }
cristyeaedf062010-05-29 22:36:02 +00003655 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003656 }
3657 if (gamma != 0.0)
3658 {
3659 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
3660 gamma);
cristy4c08aed2011-07-01 19:47:50 +00003661 SetPixelAlpha(blur_image,ClampToQuantum(gamma*pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003662 }
3663 }
3664 }
3665 else
3666 {
3667 MagickRealType
3668 alpha;
3669
cristybb503372010-05-27 20:51:26 +00003670 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003671 {
cristybb503372010-05-27 20:51:26 +00003672 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003673 {
cristy4c08aed2011-07-01 19:47:50 +00003674 contrast=GetPixelIntensity(image,p+(u+j)*
cristydcfc1ad2011-07-07 16:25:41 +00003675 GetPixelComponents(image))-(double)
cristy4c08aed2011-07-01 19:47:50 +00003676 GetPixelIntensity(blur_image,q);
3677 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003678 {
cristy4c08aed2011-07-01 19:47:50 +00003679 alpha=(MagickRealType) (QuantumScale*
cristydcfc1ad2011-07-07 16:25:41 +00003680 GetPixelAlpha(image,p+(u+j)*GetPixelComponents(image)));
cristy4c08aed2011-07-01 19:47:50 +00003681 pixel.red+=(*k)*alpha*
cristydcfc1ad2011-07-07 16:25:41 +00003682 GetPixelRed(image,p+(u+j)*GetPixelComponents(image));
cristy4c08aed2011-07-01 19:47:50 +00003683 pixel.green+=(*k)*alpha*GetPixelGreen(image,p+(u+j)*
cristydcfc1ad2011-07-07 16:25:41 +00003684 GetPixelComponents(image));
cristy4c08aed2011-07-01 19:47:50 +00003685 pixel.blue+=(*k)*alpha*GetPixelBlue(image,p+(u+j)*
cristydcfc1ad2011-07-07 16:25:41 +00003686 GetPixelComponents(image));
cristy4c08aed2011-07-01 19:47:50 +00003687 pixel.alpha+=(*k)*GetPixelAlpha(image,p+(u+j)*
cristydcfc1ad2011-07-07 16:25:41 +00003688 GetPixelComponents(image));
cristy4c08aed2011-07-01 19:47:50 +00003689 if (image->colorspace == CMYKColorspace)
3690 pixel.black+=(*k)*GetPixelBlack(image,p+(u+j)*
cristydcfc1ad2011-07-07 16:25:41 +00003691 GetPixelComponents(image));
cristy3ed852e2009-09-05 21:47:34 +00003692 gamma+=(*k)*alpha;
3693 k++;
3694 }
3695 }
cristyeaedf062010-05-29 22:36:02 +00003696 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003697 }
3698 if (gamma != 0.0)
3699 {
3700 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy2b9582a2011-07-04 17:38:56 +00003701 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003702 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00003703 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003704 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00003705 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003706 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00003707 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00003708 (image->colorspace == CMYKColorspace))
3709 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00003710 }
cristy2b9582a2011-07-04 17:38:56 +00003711 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003712 {
3713 gamma=0.0;
3714 j=0;
cristybb503372010-05-27 20:51:26 +00003715 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003716 {
cristybb503372010-05-27 20:51:26 +00003717 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003718 {
cristy4c08aed2011-07-01 19:47:50 +00003719 contrast=GetPixelIntensity(image,p+(u+j)*
cristydcfc1ad2011-07-07 16:25:41 +00003720 GetPixelComponents(image))-(double)
cristy4c08aed2011-07-01 19:47:50 +00003721 GetPixelIntensity(blur_image,q);
3722 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003723 {
cristy4c08aed2011-07-01 19:47:50 +00003724 pixel.alpha+=(*k)*
cristydcfc1ad2011-07-07 16:25:41 +00003725 GetPixelAlpha(image,p+(u+j)*GetPixelComponents(image));
cristy3ed852e2009-09-05 21:47:34 +00003726 gamma+=(*k);
3727 k++;
3728 }
3729 }
cristyeaedf062010-05-29 22:36:02 +00003730 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003731 }
3732 if (gamma != 0.0)
3733 {
3734 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
3735 gamma);
cristy4c08aed2011-07-01 19:47:50 +00003736 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003737 }
3738 }
3739 }
cristydcfc1ad2011-07-07 16:25:41 +00003740 p+=GetPixelComponents(image);
3741 q+=GetPixelComponents(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003742 }
3743 sync=SyncCacheViewAuthenticPixels(blur_view,exception);
3744 if (sync == MagickFalse)
3745 status=MagickFalse;
3746 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3747 {
3748 MagickBooleanType
3749 proceed;
3750
cristyb5d5f722009-11-04 03:03:49 +00003751#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00003752 #pragma omp critical (MagickCore_SelectiveBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00003753#endif
3754 proceed=SetImageProgress(image,SelectiveBlurImageTag,progress++,
3755 image->rows);
3756 if (proceed == MagickFalse)
3757 status=MagickFalse;
3758 }
3759 }
3760 blur_image->type=image->type;
3761 blur_view=DestroyCacheView(blur_view);
3762 image_view=DestroyCacheView(image_view);
3763 kernel=(double *) RelinquishMagickMemory(kernel);
3764 if (status == MagickFalse)
3765 blur_image=DestroyImage(blur_image);
3766 return(blur_image);
3767}
3768
3769/*
3770%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3771% %
3772% %
3773% %
3774% S h a d e I m a g e %
3775% %
3776% %
3777% %
3778%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3779%
3780% ShadeImage() shines a distant light on an image to create a
3781% three-dimensional effect. You control the positioning of the light with
3782% azimuth and elevation; azimuth is measured in degrees off the x axis
3783% and elevation is measured in pixels above the Z axis.
3784%
3785% The format of the ShadeImage method is:
3786%
3787% Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3788% const double azimuth,const double elevation,ExceptionInfo *exception)
3789%
3790% A description of each parameter follows:
3791%
3792% o image: the image.
3793%
3794% o gray: A value other than zero shades the intensity of each pixel.
3795%
3796% o azimuth, elevation: Define the light source direction.
3797%
3798% o exception: return any errors or warnings in this structure.
3799%
3800*/
3801MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3802 const double azimuth,const double elevation,ExceptionInfo *exception)
3803{
3804#define ShadeImageTag "Shade/Image"
3805
cristyc4c8d132010-01-07 01:58:38 +00003806 CacheView
3807 *image_view,
3808 *shade_view;
3809
cristy3ed852e2009-09-05 21:47:34 +00003810 Image
3811 *shade_image;
3812
cristy3ed852e2009-09-05 21:47:34 +00003813 MagickBooleanType
3814 status;
3815
cristybb503372010-05-27 20:51:26 +00003816 MagickOffsetType
3817 progress;
3818
cristy3ed852e2009-09-05 21:47:34 +00003819 PrimaryInfo
3820 light;
3821
cristybb503372010-05-27 20:51:26 +00003822 ssize_t
3823 y;
3824
cristy3ed852e2009-09-05 21:47:34 +00003825 /*
3826 Initialize shaded image attributes.
3827 */
3828 assert(image != (const Image *) NULL);
3829 assert(image->signature == MagickSignature);
3830 if (image->debug != MagickFalse)
3831 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3832 assert(exception != (ExceptionInfo *) NULL);
3833 assert(exception->signature == MagickSignature);
3834 shade_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
3835 if (shade_image == (Image *) NULL)
3836 return((Image *) NULL);
3837 if (SetImageStorageClass(shade_image,DirectClass) == MagickFalse)
3838 {
3839 InheritException(exception,&shade_image->exception);
3840 shade_image=DestroyImage(shade_image);
3841 return((Image *) NULL);
3842 }
3843 /*
3844 Compute the light vector.
3845 */
3846 light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))*
3847 cos(DegreesToRadians(elevation));
3848 light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))*
3849 cos(DegreesToRadians(elevation));
3850 light.z=(double) QuantumRange*sin(DegreesToRadians(elevation));
3851 /*
3852 Shade image.
3853 */
3854 status=MagickTrue;
3855 progress=0;
3856 image_view=AcquireCacheView(image);
3857 shade_view=AcquireCacheView(shade_image);
cristyb5d5f722009-11-04 03:03:49 +00003858#if defined(MAGICKCORE_OPENMP_SUPPORT)
3859 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003860#endif
cristybb503372010-05-27 20:51:26 +00003861 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003862 {
3863 MagickRealType
3864 distance,
3865 normal_distance,
3866 shade;
3867
3868 PrimaryInfo
3869 normal;
3870
cristy4c08aed2011-07-01 19:47:50 +00003871 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003872 *restrict p,
3873 *restrict s0,
3874 *restrict s1,
3875 *restrict s2;
cristy3ed852e2009-09-05 21:47:34 +00003876
cristy4c08aed2011-07-01 19:47:50 +00003877 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003878 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003879
cristy117ff172010-08-15 21:35:32 +00003880 register ssize_t
3881 x;
3882
cristy3ed852e2009-09-05 21:47:34 +00003883 if (status == MagickFalse)
3884 continue;
3885 p=GetCacheViewVirtualPixels(image_view,-1,y-1,image->columns+2,3,exception);
3886 q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1,
3887 exception);
cristy4c08aed2011-07-01 19:47:50 +00003888 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003889 {
3890 status=MagickFalse;
3891 continue;
3892 }
3893 /*
3894 Shade this row of pixels.
3895 */
3896 normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */
cristydcfc1ad2011-07-07 16:25:41 +00003897 s0=p+GetPixelComponents(image);
3898 s1=s0+(image->columns+2)*GetPixelComponents(image);
3899 s2=s1+(image->columns+2)*GetPixelComponents(image);
cristybb503372010-05-27 20:51:26 +00003900 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003901 {
3902 /*
3903 Determine the surface normal and compute shading.
3904 */
cristydcfc1ad2011-07-07 16:25:41 +00003905 normal.x=(double) (GetPixelIntensity(image,s0-GetPixelComponents(image))+
3906 GetPixelIntensity(image,s1-GetPixelComponents(image))+
3907 GetPixelIntensity(image,s2-GetPixelComponents(image))-
3908 GetPixelIntensity(image,s0+GetPixelComponents(image))-
3909 GetPixelIntensity(image,s1+GetPixelComponents(image))-
3910 GetPixelIntensity(image,s2+GetPixelComponents(image)));
3911 normal.y=(double) (GetPixelIntensity(image,s2-GetPixelComponents(image))+
cristy4c08aed2011-07-01 19:47:50 +00003912 GetPixelIntensity(image,s2)+
cristydcfc1ad2011-07-07 16:25:41 +00003913 GetPixelIntensity(image,s2+GetPixelComponents(image))-
3914 GetPixelIntensity(image,s0-GetPixelComponents(image))-
cristy4c08aed2011-07-01 19:47:50 +00003915 GetPixelIntensity(image,s0)-
cristydcfc1ad2011-07-07 16:25:41 +00003916 GetPixelIntensity(image,s0+GetPixelComponents(image)));
cristy3ed852e2009-09-05 21:47:34 +00003917 if ((normal.x == 0.0) && (normal.y == 0.0))
3918 shade=light.z;
3919 else
3920 {
3921 shade=0.0;
3922 distance=normal.x*light.x+normal.y*light.y+normal.z*light.z;
3923 if (distance > MagickEpsilon)
3924 {
3925 normal_distance=
3926 normal.x*normal.x+normal.y*normal.y+normal.z*normal.z;
3927 if (normal_distance > (MagickEpsilon*MagickEpsilon))
3928 shade=distance/sqrt((double) normal_distance);
3929 }
3930 }
3931 if (gray != MagickFalse)
3932 {
cristy4c08aed2011-07-01 19:47:50 +00003933 SetPixelRed(shade_image,ClampToQuantum(shade),q);
3934 SetPixelGreen(shade_image,ClampToQuantum(shade),q);
3935 SetPixelBlue(shade_image,ClampToQuantum(shade),q);
cristy3ed852e2009-09-05 21:47:34 +00003936 }
3937 else
3938 {
cristy4c08aed2011-07-01 19:47:50 +00003939 SetPixelRed(shade_image,ClampToQuantum(QuantumScale*shade*
3940 GetPixelRed(image,s1)),q);
3941 SetPixelGreen(shade_image,ClampToQuantum(QuantumScale*shade*
3942 GetPixelGreen(image,s1)),q);
3943 SetPixelBlue(shade_image,ClampToQuantum(QuantumScale*shade*
3944 GetPixelBlue(image,s1)),q);
cristy3ed852e2009-09-05 21:47:34 +00003945 }
cristy4c08aed2011-07-01 19:47:50 +00003946 SetPixelAlpha(shade_image,GetPixelAlpha(image,s1),q);
cristydcfc1ad2011-07-07 16:25:41 +00003947 s0+=GetPixelComponents(image);
3948 s1+=GetPixelComponents(image);
3949 s2+=GetPixelComponents(image);
3950 q+=GetPixelComponents(shade_image);
cristy3ed852e2009-09-05 21:47:34 +00003951 }
3952 if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
3953 status=MagickFalse;
3954 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3955 {
3956 MagickBooleanType
3957 proceed;
3958
cristyb5d5f722009-11-04 03:03:49 +00003959#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003960 #pragma omp critical (MagickCore_ShadeImage)
3961#endif
3962 proceed=SetImageProgress(image,ShadeImageTag,progress++,image->rows);
3963 if (proceed == MagickFalse)
3964 status=MagickFalse;
3965 }
3966 }
3967 shade_view=DestroyCacheView(shade_view);
3968 image_view=DestroyCacheView(image_view);
3969 if (status == MagickFalse)
3970 shade_image=DestroyImage(shade_image);
3971 return(shade_image);
3972}
3973
3974/*
3975%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3976% %
3977% %
3978% %
3979% S h a r p e n I m a g e %
3980% %
3981% %
3982% %
3983%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3984%
3985% SharpenImage() sharpens the image. We convolve the image with a Gaussian
3986% operator of the given radius and standard deviation (sigma). For
3987% reasonable results, radius should be larger than sigma. Use a radius of 0
3988% and SharpenImage() selects a suitable radius for you.
3989%
3990% Using a separable kernel would be faster, but the negative weights cancel
3991% out on the corners of the kernel producing often undesirable ringing in the
3992% filtered result; this can be avoided by using a 2D gaussian shaped image
3993% sharpening kernel instead.
3994%
3995% The format of the SharpenImage method is:
3996%
3997% Image *SharpenImage(const Image *image,const double radius,
3998% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003999%
4000% A description of each parameter follows:
4001%
4002% o image: the image.
4003%
cristy3ed852e2009-09-05 21:47:34 +00004004% o radius: the radius of the Gaussian, in pixels, not counting the center
4005% pixel.
4006%
4007% o sigma: the standard deviation of the Laplacian, in pixels.
4008%
4009% o exception: return any errors or warnings in this structure.
4010%
4011*/
cristy3ed852e2009-09-05 21:47:34 +00004012MagickExport Image *SharpenImage(const Image *image,const double radius,
4013 const double sigma,ExceptionInfo *exception)
4014{
cristy3ed852e2009-09-05 21:47:34 +00004015 double
cristy47e00502009-12-17 19:19:57 +00004016 *kernel,
4017 normalize;
cristy3ed852e2009-09-05 21:47:34 +00004018
4019 Image
4020 *sharp_image;
4021
cristybb503372010-05-27 20:51:26 +00004022 register ssize_t
cristy47e00502009-12-17 19:19:57 +00004023 i;
4024
cristybb503372010-05-27 20:51:26 +00004025 size_t
cristy3ed852e2009-09-05 21:47:34 +00004026 width;
4027
cristy117ff172010-08-15 21:35:32 +00004028 ssize_t
4029 j,
4030 u,
4031 v;
4032
cristy3ed852e2009-09-05 21:47:34 +00004033 assert(image != (const Image *) NULL);
4034 assert(image->signature == MagickSignature);
4035 if (image->debug != MagickFalse)
4036 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4037 assert(exception != (ExceptionInfo *) NULL);
4038 assert(exception->signature == MagickSignature);
4039 width=GetOptimalKernelWidth2D(radius,sigma);
4040 kernel=(double *) AcquireQuantumMemory((size_t) width*width,sizeof(*kernel));
4041 if (kernel == (double *) NULL)
4042 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy3ed852e2009-09-05 21:47:34 +00004043 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00004044 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +00004045 i=0;
4046 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00004047 {
cristy47e00502009-12-17 19:19:57 +00004048 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00004049 {
cristy4205a3c2010-09-12 20:19:59 +00004050 kernel[i]=(double) (-exp(-((double) u*u+v*v)/(2.0*MagickSigma*
4051 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00004052 normalize+=kernel[i];
4053 i++;
4054 }
4055 }
4056 kernel[i/2]=(double) ((-2.0)*normalize);
cristyf4ad9df2011-07-08 16:49:03 +00004057 sharp_image=ConvolveImage(image,width,kernel,exception);
cristy3ed852e2009-09-05 21:47:34 +00004058 kernel=(double *) RelinquishMagickMemory(kernel);
4059 return(sharp_image);
4060}
4061
4062/*
4063%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4064% %
4065% %
4066% %
4067% S p r e a d I m a g e %
4068% %
4069% %
4070% %
4071%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4072%
4073% SpreadImage() is a special effects method that randomly displaces each
4074% pixel in a block defined by the radius parameter.
4075%
4076% The format of the SpreadImage method is:
4077%
4078% Image *SpreadImage(const Image *image,const double radius,
4079% ExceptionInfo *exception)
4080%
4081% A description of each parameter follows:
4082%
4083% o image: the image.
4084%
4085% o radius: Choose a random pixel in a neighborhood of this extent.
4086%
4087% o exception: return any errors or warnings in this structure.
4088%
4089*/
4090MagickExport Image *SpreadImage(const Image *image,const double radius,
4091 ExceptionInfo *exception)
4092{
4093#define SpreadImageTag "Spread/Image"
4094
cristyfa112112010-01-04 17:48:07 +00004095 CacheView
cristy9f7e7cb2011-03-26 00:49:57 +00004096 *image_view,
4097 *spread_view;
cristyfa112112010-01-04 17:48:07 +00004098
cristy3ed852e2009-09-05 21:47:34 +00004099 Image
4100 *spread_image;
4101
cristy3ed852e2009-09-05 21:47:34 +00004102 MagickBooleanType
4103 status;
4104
cristybb503372010-05-27 20:51:26 +00004105 MagickOffsetType
4106 progress;
4107
cristy4c08aed2011-07-01 19:47:50 +00004108 PixelInfo
cristyddd82202009-11-03 20:14:50 +00004109 bias;
cristy3ed852e2009-09-05 21:47:34 +00004110
4111 RandomInfo
cristyfa112112010-01-04 17:48:07 +00004112 **restrict random_info;
cristy3ed852e2009-09-05 21:47:34 +00004113
cristybb503372010-05-27 20:51:26 +00004114 size_t
cristy3ed852e2009-09-05 21:47:34 +00004115 width;
4116
cristybb503372010-05-27 20:51:26 +00004117 ssize_t
4118 y;
4119
cristy3ed852e2009-09-05 21:47:34 +00004120 /*
4121 Initialize spread image attributes.
4122 */
4123 assert(image != (Image *) NULL);
4124 assert(image->signature == MagickSignature);
4125 if (image->debug != MagickFalse)
4126 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4127 assert(exception != (ExceptionInfo *) NULL);
4128 assert(exception->signature == MagickSignature);
4129 spread_image=CloneImage(image,image->columns,image->rows,MagickTrue,
4130 exception);
4131 if (spread_image == (Image *) NULL)
4132 return((Image *) NULL);
4133 if (SetImageStorageClass(spread_image,DirectClass) == MagickFalse)
4134 {
4135 InheritException(exception,&spread_image->exception);
4136 spread_image=DestroyImage(spread_image);
4137 return((Image *) NULL);
4138 }
4139 /*
4140 Spread image.
4141 */
4142 status=MagickTrue;
4143 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00004144 GetPixelInfo(spread_image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00004145 width=GetOptimalKernelWidth1D(radius,0.5);
cristy3ed852e2009-09-05 21:47:34 +00004146 random_info=AcquireRandomInfoThreadSet();
cristy9f7e7cb2011-03-26 00:49:57 +00004147 image_view=AcquireCacheView(image);
4148 spread_view=AcquireCacheView(spread_image);
cristyb557a152011-02-22 12:14:30 +00004149#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00004150 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00004151#endif
cristybb503372010-05-27 20:51:26 +00004152 for (y=0; y < (ssize_t) spread_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00004153 {
cristy5c9e6f22010-09-17 17:31:01 +00004154 const int
4155 id = GetOpenMPThreadId();
cristy6ebe97c2010-07-03 01:17:28 +00004156
cristy4c08aed2011-07-01 19:47:50 +00004157 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00004158 pixel;
4159
cristy4c08aed2011-07-01 19:47:50 +00004160 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00004161 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004162
cristy117ff172010-08-15 21:35:32 +00004163 register ssize_t
4164 x;
4165
cristy3ed852e2009-09-05 21:47:34 +00004166 if (status == MagickFalse)
4167 continue;
cristy9f7e7cb2011-03-26 00:49:57 +00004168 q=QueueCacheViewAuthenticPixels(spread_view,0,y,spread_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +00004169 exception);
cristy4c08aed2011-07-01 19:47:50 +00004170 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00004171 {
4172 status=MagickFalse;
4173 continue;
4174 }
cristyddd82202009-11-03 20:14:50 +00004175 pixel=bias;
cristybb503372010-05-27 20:51:26 +00004176 for (x=0; x < (ssize_t) spread_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004177 {
cristy4c08aed2011-07-01 19:47:50 +00004178 (void) InterpolatePixelInfo(image,image_view,
cristy8a7c3e82011-03-26 02:10:53 +00004179 UndefinedInterpolatePixel,(double) x+width*(GetPseudoRandomValue(
4180 random_info[id])-0.5),(double) y+width*(GetPseudoRandomValue(
4181 random_info[id])-0.5),&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00004182 SetPixelPixelInfo(spread_image,&pixel,q);
cristydcfc1ad2011-07-07 16:25:41 +00004183 q+=GetPixelComponents(spread_image);
cristy3ed852e2009-09-05 21:47:34 +00004184 }
cristy9f7e7cb2011-03-26 00:49:57 +00004185 if (SyncCacheViewAuthenticPixels(spread_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00004186 status=MagickFalse;
4187 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4188 {
4189 MagickBooleanType
4190 proceed;
4191
cristyb557a152011-02-22 12:14:30 +00004192#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004193 #pragma omp critical (MagickCore_SpreadImage)
4194#endif
4195 proceed=SetImageProgress(image,SpreadImageTag,progress++,image->rows);
4196 if (proceed == MagickFalse)
4197 status=MagickFalse;
4198 }
4199 }
cristy9f7e7cb2011-03-26 00:49:57 +00004200 spread_view=DestroyCacheView(spread_view);
cristy3ed852e2009-09-05 21:47:34 +00004201 image_view=DestroyCacheView(image_view);
4202 random_info=DestroyRandomInfoThreadSet(random_info);
cristy3ed852e2009-09-05 21:47:34 +00004203 return(spread_image);
4204}
4205
4206/*
4207%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4208% %
4209% %
4210% %
cristy0834d642011-03-18 18:26:08 +00004211% S t a t i s t i c I m a g e %
4212% %
4213% %
4214% %
4215%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4216%
4217% StatisticImage() makes each pixel the min / max / median / mode / etc. of
cristy8d752042011-03-19 01:00:36 +00004218% the neighborhood of the specified width and height.
cristy0834d642011-03-18 18:26:08 +00004219%
4220% The format of the StatisticImage method is:
4221%
4222% Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00004223% const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00004224%
4225% A description of each parameter follows:
4226%
4227% o image: the image.
4228%
cristy0834d642011-03-18 18:26:08 +00004229% o type: the statistic type (median, mode, etc.).
4230%
cristy95c38342011-03-18 22:39:51 +00004231% o width: the width of the pixel neighborhood.
4232%
4233% o height: the height of the pixel neighborhood.
cristy0834d642011-03-18 18:26:08 +00004234%
4235% o exception: return any errors or warnings in this structure.
4236%
4237*/
4238
cristy733678d2011-03-18 21:29:28 +00004239#define ListChannels 5
4240
4241typedef struct _ListNode
4242{
4243 size_t
4244 next[9],
4245 count,
4246 signature;
4247} ListNode;
4248
4249typedef struct _SkipList
4250{
4251 ssize_t
4252 level;
4253
4254 ListNode
4255 *nodes;
4256} SkipList;
4257
4258typedef struct _PixelList
4259{
4260 size_t
cristy6fc86bb2011-03-18 23:45:16 +00004261 length,
cristy733678d2011-03-18 21:29:28 +00004262 seed,
4263 signature;
4264
4265 SkipList
4266 lists[ListChannels];
4267} PixelList;
4268
4269static PixelList *DestroyPixelList(PixelList *pixel_list)
4270{
4271 register ssize_t
4272 i;
4273
4274 if (pixel_list == (PixelList *) NULL)
4275 return((PixelList *) NULL);
4276 for (i=0; i < ListChannels; i++)
4277 if (pixel_list->lists[i].nodes != (ListNode *) NULL)
4278 pixel_list->lists[i].nodes=(ListNode *) RelinquishMagickMemory(
4279 pixel_list->lists[i].nodes);
4280 pixel_list=(PixelList *) RelinquishMagickMemory(pixel_list);
4281 return(pixel_list);
4282}
4283
4284static PixelList **DestroyPixelListThreadSet(PixelList **pixel_list)
4285{
4286 register ssize_t
4287 i;
4288
4289 assert(pixel_list != (PixelList **) NULL);
4290 for (i=0; i < (ssize_t) GetOpenMPMaximumThreads(); i++)
4291 if (pixel_list[i] != (PixelList *) NULL)
4292 pixel_list[i]=DestroyPixelList(pixel_list[i]);
4293 pixel_list=(PixelList **) RelinquishMagickMemory(pixel_list);
4294 return(pixel_list);
4295}
4296
cristy6fc86bb2011-03-18 23:45:16 +00004297static PixelList *AcquirePixelList(const size_t width,const size_t height)
cristy733678d2011-03-18 21:29:28 +00004298{
4299 PixelList
4300 *pixel_list;
4301
4302 register ssize_t
4303 i;
4304
4305 pixel_list=(PixelList *) AcquireMagickMemory(sizeof(*pixel_list));
4306 if (pixel_list == (PixelList *) NULL)
4307 return(pixel_list);
4308 (void) ResetMagickMemory((void *) pixel_list,0,sizeof(*pixel_list));
cristy6fc86bb2011-03-18 23:45:16 +00004309 pixel_list->length=width*height;
cristy733678d2011-03-18 21:29:28 +00004310 for (i=0; i < ListChannels; i++)
4311 {
4312 pixel_list->lists[i].nodes=(ListNode *) AcquireQuantumMemory(65537UL,
4313 sizeof(*pixel_list->lists[i].nodes));
4314 if (pixel_list->lists[i].nodes == (ListNode *) NULL)
4315 return(DestroyPixelList(pixel_list));
4316 (void) ResetMagickMemory(pixel_list->lists[i].nodes,0,65537UL*
4317 sizeof(*pixel_list->lists[i].nodes));
4318 }
4319 pixel_list->signature=MagickSignature;
4320 return(pixel_list);
4321}
4322
cristy6fc86bb2011-03-18 23:45:16 +00004323static PixelList **AcquirePixelListThreadSet(const size_t width,
4324 const size_t height)
cristy733678d2011-03-18 21:29:28 +00004325{
4326 PixelList
4327 **pixel_list;
4328
4329 register ssize_t
4330 i;
4331
4332 size_t
4333 number_threads;
4334
4335 number_threads=GetOpenMPMaximumThreads();
4336 pixel_list=(PixelList **) AcquireQuantumMemory(number_threads,
4337 sizeof(*pixel_list));
4338 if (pixel_list == (PixelList **) NULL)
4339 return((PixelList **) NULL);
4340 (void) ResetMagickMemory(pixel_list,0,number_threads*sizeof(*pixel_list));
4341 for (i=0; i < (ssize_t) number_threads; i++)
4342 {
cristy6fc86bb2011-03-18 23:45:16 +00004343 pixel_list[i]=AcquirePixelList(width,height);
cristy733678d2011-03-18 21:29:28 +00004344 if (pixel_list[i] == (PixelList *) NULL)
4345 return(DestroyPixelListThreadSet(pixel_list));
4346 }
4347 return(pixel_list);
4348}
4349
4350static void AddNodePixelList(PixelList *pixel_list,const ssize_t channel,
4351 const size_t color)
4352{
4353 register SkipList
4354 *list;
4355
4356 register ssize_t
4357 level;
4358
4359 size_t
4360 search,
4361 update[9];
4362
4363 /*
4364 Initialize the node.
4365 */
4366 list=pixel_list->lists+channel;
4367 list->nodes[color].signature=pixel_list->signature;
4368 list->nodes[color].count=1;
4369 /*
4370 Determine where it belongs in the list.
4371 */
4372 search=65536UL;
4373 for (level=list->level; level >= 0; level--)
4374 {
4375 while (list->nodes[search].next[level] < color)
4376 search=list->nodes[search].next[level];
4377 update[level]=search;
4378 }
4379 /*
4380 Generate a pseudo-random level for this node.
4381 */
4382 for (level=0; ; level++)
4383 {
4384 pixel_list->seed=(pixel_list->seed*42893621L)+1L;
4385 if ((pixel_list->seed & 0x300) != 0x300)
4386 break;
4387 }
4388 if (level > 8)
4389 level=8;
4390 if (level > (list->level+2))
4391 level=list->level+2;
4392 /*
4393 If we're raising the list's level, link back to the root node.
4394 */
4395 while (level > list->level)
4396 {
4397 list->level++;
4398 update[list->level]=65536UL;
4399 }
4400 /*
4401 Link the node into the skip-list.
4402 */
4403 do
4404 {
4405 list->nodes[color].next[level]=list->nodes[update[level]].next[level];
4406 list->nodes[update[level]].next[level]=color;
cristy3cba8ca2011-03-19 01:29:12 +00004407 } while (level-- > 0);
cristy733678d2011-03-18 21:29:28 +00004408}
4409
cristy4c08aed2011-07-01 19:47:50 +00004410static PixelInfo GetMaximumPixelList(PixelList *pixel_list)
cristy6fc86bb2011-03-18 23:45:16 +00004411{
cristy4c08aed2011-07-01 19:47:50 +00004412 PixelInfo
cristy6fc86bb2011-03-18 23:45:16 +00004413 pixel;
4414
4415 register SkipList
4416 *list;
4417
4418 register ssize_t
4419 channel;
4420
4421 size_t
cristyd76c51e2011-03-26 00:21:26 +00004422 color,
4423 maximum;
cristy49f37242011-03-22 18:18:23 +00004424
4425 ssize_t
cristy6fc86bb2011-03-18 23:45:16 +00004426 count;
4427
4428 unsigned short
cristyd76c51e2011-03-26 00:21:26 +00004429 channels[ListChannels];
cristy6fc86bb2011-03-18 23:45:16 +00004430
4431 /*
4432 Find the maximum value for each of the color.
4433 */
4434 for (channel=0; channel < 5; channel++)
4435 {
4436 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004437 color=65536L;
cristy6fc86bb2011-03-18 23:45:16 +00004438 count=0;
cristy49f37242011-03-22 18:18:23 +00004439 maximum=list->nodes[color].next[0];
cristy6fc86bb2011-03-18 23:45:16 +00004440 do
4441 {
4442 color=list->nodes[color].next[0];
cristy49f37242011-03-22 18:18:23 +00004443 if (color > maximum)
4444 maximum=color;
cristy6fc86bb2011-03-18 23:45:16 +00004445 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004446 } while (count < (ssize_t) pixel_list->length);
cristy49f37242011-03-22 18:18:23 +00004447 channels[channel]=(unsigned short) maximum;
4448 }
cristy4c08aed2011-07-01 19:47:50 +00004449 GetPixelInfo((const Image *) NULL,&pixel);
cristy49f37242011-03-22 18:18:23 +00004450 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4451 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4452 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004453 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4454 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy49f37242011-03-22 18:18:23 +00004455 return(pixel);
4456}
4457
cristy4c08aed2011-07-01 19:47:50 +00004458static PixelInfo GetMeanPixelList(PixelList *pixel_list)
cristy49f37242011-03-22 18:18:23 +00004459{
cristy4c08aed2011-07-01 19:47:50 +00004460 PixelInfo
cristy49f37242011-03-22 18:18:23 +00004461 pixel;
4462
cristy80a99a32011-03-30 01:30:23 +00004463 MagickRealType
4464 sum;
4465
cristy49f37242011-03-22 18:18:23 +00004466 register SkipList
4467 *list;
4468
4469 register ssize_t
4470 channel;
4471
4472 size_t
cristy80a99a32011-03-30 01:30:23 +00004473 color;
cristy49f37242011-03-22 18:18:23 +00004474
4475 ssize_t
4476 count;
4477
4478 unsigned short
4479 channels[ListChannels];
4480
4481 /*
4482 Find the mean value for each of the color.
4483 */
4484 for (channel=0; channel < 5; channel++)
4485 {
4486 list=pixel_list->lists+channel;
4487 color=65536L;
4488 count=0;
cristy80a99a32011-03-30 01:30:23 +00004489 sum=0.0;
cristy49f37242011-03-22 18:18:23 +00004490 do
4491 {
4492 color=list->nodes[color].next[0];
cristy80a99a32011-03-30 01:30:23 +00004493 sum+=(MagickRealType) list->nodes[color].count*color;
cristy49f37242011-03-22 18:18:23 +00004494 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004495 } while (count < (ssize_t) pixel_list->length);
cristy80a99a32011-03-30 01:30:23 +00004496 sum/=pixel_list->length;
4497 channels[channel]=(unsigned short) sum;
cristy6fc86bb2011-03-18 23:45:16 +00004498 }
cristy4c08aed2011-07-01 19:47:50 +00004499 GetPixelInfo((const Image *) NULL,&pixel);
cristy6fc86bb2011-03-18 23:45:16 +00004500 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4501 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4502 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004503 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4504 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy6fc86bb2011-03-18 23:45:16 +00004505 return(pixel);
4506}
4507
cristy4c08aed2011-07-01 19:47:50 +00004508static PixelInfo GetMedianPixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004509{
cristy4c08aed2011-07-01 19:47:50 +00004510 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004511 pixel;
4512
4513 register SkipList
4514 *list;
4515
4516 register ssize_t
4517 channel;
4518
4519 size_t
cristy49f37242011-03-22 18:18:23 +00004520 color;
4521
4522 ssize_t
cristy733678d2011-03-18 21:29:28 +00004523 count;
4524
4525 unsigned short
4526 channels[ListChannels];
4527
4528 /*
4529 Find the median value for each of the color.
4530 */
cristy733678d2011-03-18 21:29:28 +00004531 for (channel=0; channel < 5; channel++)
4532 {
4533 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004534 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004535 count=0;
4536 do
4537 {
4538 color=list->nodes[color].next[0];
4539 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004540 } while (count <= (ssize_t) (pixel_list->length >> 1));
cristy6fc86bb2011-03-18 23:45:16 +00004541 channels[channel]=(unsigned short) color;
4542 }
cristy4c08aed2011-07-01 19:47:50 +00004543 GetPixelInfo((const Image *) NULL,&pixel);
cristy6fc86bb2011-03-18 23:45:16 +00004544 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4545 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4546 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004547 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4548 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy6fc86bb2011-03-18 23:45:16 +00004549 return(pixel);
4550}
4551
cristy4c08aed2011-07-01 19:47:50 +00004552static PixelInfo GetMinimumPixelList(PixelList *pixel_list)
cristy6fc86bb2011-03-18 23:45:16 +00004553{
cristy4c08aed2011-07-01 19:47:50 +00004554 PixelInfo
cristy6fc86bb2011-03-18 23:45:16 +00004555 pixel;
4556
4557 register SkipList
4558 *list;
4559
4560 register ssize_t
4561 channel;
4562
4563 size_t
cristyd76c51e2011-03-26 00:21:26 +00004564 color,
4565 minimum;
cristy6fc86bb2011-03-18 23:45:16 +00004566
cristy49f37242011-03-22 18:18:23 +00004567 ssize_t
4568 count;
4569
cristy6fc86bb2011-03-18 23:45:16 +00004570 unsigned short
cristyd76c51e2011-03-26 00:21:26 +00004571 channels[ListChannels];
cristy6fc86bb2011-03-18 23:45:16 +00004572
4573 /*
4574 Find the minimum value for each of the color.
4575 */
4576 for (channel=0; channel < 5; channel++)
4577 {
4578 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004579 count=0;
cristy6fc86bb2011-03-18 23:45:16 +00004580 color=65536UL;
cristy49f37242011-03-22 18:18:23 +00004581 minimum=list->nodes[color].next[0];
4582 do
4583 {
4584 color=list->nodes[color].next[0];
4585 if (color < minimum)
4586 minimum=color;
4587 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004588 } while (count < (ssize_t) pixel_list->length);
cristy49f37242011-03-22 18:18:23 +00004589 channels[channel]=(unsigned short) minimum;
cristy733678d2011-03-18 21:29:28 +00004590 }
cristy4c08aed2011-07-01 19:47:50 +00004591 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004592 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4593 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4594 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004595 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4596 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy733678d2011-03-18 21:29:28 +00004597 return(pixel);
4598}
4599
cristy4c08aed2011-07-01 19:47:50 +00004600static PixelInfo GetModePixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004601{
cristy4c08aed2011-07-01 19:47:50 +00004602 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004603 pixel;
4604
4605 register SkipList
4606 *list;
4607
4608 register ssize_t
4609 channel;
4610
4611 size_t
4612 color,
cristy733678d2011-03-18 21:29:28 +00004613 max_count,
cristy6fc86bb2011-03-18 23:45:16 +00004614 mode;
cristy733678d2011-03-18 21:29:28 +00004615
cristy49f37242011-03-22 18:18:23 +00004616 ssize_t
4617 count;
4618
cristy733678d2011-03-18 21:29:28 +00004619 unsigned short
4620 channels[5];
4621
4622 /*
glennrp30d2dc62011-06-25 03:17:16 +00004623 Make each pixel the 'predominant color' of the specified neighborhood.
cristy733678d2011-03-18 21:29:28 +00004624 */
cristy733678d2011-03-18 21:29:28 +00004625 for (channel=0; channel < 5; channel++)
4626 {
4627 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004628 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004629 mode=color;
4630 max_count=list->nodes[mode].count;
4631 count=0;
4632 do
4633 {
4634 color=list->nodes[color].next[0];
4635 if (list->nodes[color].count > max_count)
4636 {
4637 mode=color;
4638 max_count=list->nodes[mode].count;
4639 }
4640 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004641 } while (count < (ssize_t) pixel_list->length);
cristy733678d2011-03-18 21:29:28 +00004642 channels[channel]=(unsigned short) mode;
4643 }
cristy4c08aed2011-07-01 19:47:50 +00004644 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004645 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4646 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4647 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004648 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4649 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy733678d2011-03-18 21:29:28 +00004650 return(pixel);
4651}
4652
cristy4c08aed2011-07-01 19:47:50 +00004653static PixelInfo GetNonpeakPixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004654{
cristy4c08aed2011-07-01 19:47:50 +00004655 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004656 pixel;
4657
4658 register SkipList
4659 *list;
4660
4661 register ssize_t
4662 channel;
4663
4664 size_t
cristy733678d2011-03-18 21:29:28 +00004665 color,
cristy733678d2011-03-18 21:29:28 +00004666 next,
4667 previous;
4668
cristy49f37242011-03-22 18:18:23 +00004669 ssize_t
4670 count;
4671
cristy733678d2011-03-18 21:29:28 +00004672 unsigned short
4673 channels[5];
4674
4675 /*
cristy49f37242011-03-22 18:18:23 +00004676 Finds the non peak value for each of the colors.
cristy733678d2011-03-18 21:29:28 +00004677 */
cristy733678d2011-03-18 21:29:28 +00004678 for (channel=0; channel < 5; channel++)
4679 {
4680 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004681 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004682 next=list->nodes[color].next[0];
4683 count=0;
4684 do
4685 {
4686 previous=color;
4687 color=next;
4688 next=list->nodes[color].next[0];
4689 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004690 } while (count <= (ssize_t) (pixel_list->length >> 1));
cristy733678d2011-03-18 21:29:28 +00004691 if ((previous == 65536UL) && (next != 65536UL))
4692 color=next;
4693 else
4694 if ((previous != 65536UL) && (next == 65536UL))
4695 color=previous;
4696 channels[channel]=(unsigned short) color;
4697 }
cristy4c08aed2011-07-01 19:47:50 +00004698 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004699 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4700 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4701 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004702 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4703 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy733678d2011-03-18 21:29:28 +00004704 return(pixel);
4705}
4706
cristy4c08aed2011-07-01 19:47:50 +00004707static PixelInfo GetStandardDeviationPixelList(PixelList *pixel_list)
cristy9a68cbb2011-03-29 00:51:23 +00004708{
cristy4c08aed2011-07-01 19:47:50 +00004709 PixelInfo
cristy9a68cbb2011-03-29 00:51:23 +00004710 pixel;
4711
cristy80a99a32011-03-30 01:30:23 +00004712 MagickRealType
4713 sum,
4714 sum_squared;
4715
cristy9a68cbb2011-03-29 00:51:23 +00004716 register SkipList
4717 *list;
4718
4719 register ssize_t
4720 channel;
4721
4722 size_t
cristy80a99a32011-03-30 01:30:23 +00004723 color;
cristy9a68cbb2011-03-29 00:51:23 +00004724
4725 ssize_t
4726 count;
4727
4728 unsigned short
4729 channels[ListChannels];
4730
4731 /*
cristy80a99a32011-03-30 01:30:23 +00004732 Find the standard-deviation value for each of the color.
cristy9a68cbb2011-03-29 00:51:23 +00004733 */
4734 for (channel=0; channel < 5; channel++)
4735 {
4736 list=pixel_list->lists+channel;
4737 color=65536L;
4738 count=0;
cristy80a99a32011-03-30 01:30:23 +00004739 sum=0.0;
4740 sum_squared=0.0;
cristy9a68cbb2011-03-29 00:51:23 +00004741 do
4742 {
cristy80a99a32011-03-30 01:30:23 +00004743 register ssize_t
4744 i;
4745
cristy9a68cbb2011-03-29 00:51:23 +00004746 color=list->nodes[color].next[0];
cristy80a99a32011-03-30 01:30:23 +00004747 sum+=(MagickRealType) list->nodes[color].count*color;
4748 for (i=0; i < (ssize_t) list->nodes[color].count; i++)
4749 sum_squared+=((MagickRealType) color)*((MagickRealType) color);
cristy9a68cbb2011-03-29 00:51:23 +00004750 count+=list->nodes[color].count;
4751 } while (count < (ssize_t) pixel_list->length);
cristy80a99a32011-03-30 01:30:23 +00004752 sum/=pixel_list->length;
4753 sum_squared/=pixel_list->length;
4754 channels[channel]=(unsigned short) sqrt(sum_squared-(sum*sum));
cristy9a68cbb2011-03-29 00:51:23 +00004755 }
cristy4c08aed2011-07-01 19:47:50 +00004756 GetPixelInfo((const Image *) NULL,&pixel);
cristy9a68cbb2011-03-29 00:51:23 +00004757 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4758 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4759 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004760 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4761 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy9a68cbb2011-03-29 00:51:23 +00004762 return(pixel);
4763}
4764
cristy4c08aed2011-07-01 19:47:50 +00004765static inline void InsertPixelList(const Image *image,const Quantum *pixel,
4766 PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004767{
4768 size_t
4769 signature;
4770
4771 unsigned short
4772 index;
4773
cristy4c08aed2011-07-01 19:47:50 +00004774 index=ScaleQuantumToShort(GetPixelRed(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004775 signature=pixel_list->lists[0].nodes[index].signature;
4776 if (signature == pixel_list->signature)
4777 pixel_list->lists[0].nodes[index].count++;
4778 else
4779 AddNodePixelList(pixel_list,0,index);
cristy4c08aed2011-07-01 19:47:50 +00004780 index=ScaleQuantumToShort(GetPixelGreen(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004781 signature=pixel_list->lists[1].nodes[index].signature;
4782 if (signature == pixel_list->signature)
4783 pixel_list->lists[1].nodes[index].count++;
4784 else
4785 AddNodePixelList(pixel_list,1,index);
cristy4c08aed2011-07-01 19:47:50 +00004786 index=ScaleQuantumToShort(GetPixelBlue(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004787 signature=pixel_list->lists[2].nodes[index].signature;
4788 if (signature == pixel_list->signature)
4789 pixel_list->lists[2].nodes[index].count++;
4790 else
4791 AddNodePixelList(pixel_list,2,index);
cristy4c08aed2011-07-01 19:47:50 +00004792 index=ScaleQuantumToShort(GetPixelAlpha(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004793 signature=pixel_list->lists[3].nodes[index].signature;
4794 if (signature == pixel_list->signature)
4795 pixel_list->lists[3].nodes[index].count++;
4796 else
4797 AddNodePixelList(pixel_list,3,index);
4798 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00004799 index=ScaleQuantumToShort(GetPixelBlack(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004800 signature=pixel_list->lists[4].nodes[index].signature;
4801 if (signature == pixel_list->signature)
4802 pixel_list->lists[4].nodes[index].count++;
4803 else
4804 AddNodePixelList(pixel_list,4,index);
4805}
4806
cristy80c99742011-04-04 14:46:39 +00004807static inline MagickRealType MagickAbsoluteValue(const MagickRealType x)
4808{
4809 if (x < 0)
4810 return(-x);
4811 return(x);
4812}
4813
cristy733678d2011-03-18 21:29:28 +00004814static void ResetPixelList(PixelList *pixel_list)
4815{
4816 int
4817 level;
4818
4819 register ListNode
4820 *root;
4821
4822 register SkipList
4823 *list;
4824
4825 register ssize_t
4826 channel;
4827
4828 /*
4829 Reset the skip-list.
4830 */
4831 for (channel=0; channel < 5; channel++)
4832 {
4833 list=pixel_list->lists+channel;
4834 root=list->nodes+65536UL;
4835 list->level=0;
4836 for (level=0; level < 9; level++)
4837 root->next[level]=65536UL;
4838 }
4839 pixel_list->seed=pixel_list->signature++;
4840}
4841
cristy0834d642011-03-18 18:26:08 +00004842MagickExport Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00004843 const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00004844{
cristy3cba8ca2011-03-19 01:29:12 +00004845#define StatisticWidth \
cristyd76c51e2011-03-26 00:21:26 +00004846 (width == 0 ? GetOptimalKernelWidth2D((double) width,0.5) : width)
cristy3cba8ca2011-03-19 01:29:12 +00004847#define StatisticHeight \
cristyd76c51e2011-03-26 00:21:26 +00004848 (height == 0 ? GetOptimalKernelWidth2D((double) height,0.5) : height)
cristy0834d642011-03-18 18:26:08 +00004849#define StatisticImageTag "Statistic/Image"
4850
4851 CacheView
4852 *image_view,
4853 *statistic_view;
4854
4855 Image
4856 *statistic_image;
4857
4858 MagickBooleanType
4859 status;
4860
4861 MagickOffsetType
4862 progress;
4863
4864 PixelList
4865 **restrict pixel_list;
4866
cristy0834d642011-03-18 18:26:08 +00004867 ssize_t
4868 y;
4869
4870 /*
4871 Initialize statistics image attributes.
4872 */
4873 assert(image != (Image *) NULL);
4874 assert(image->signature == MagickSignature);
4875 if (image->debug != MagickFalse)
4876 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4877 assert(exception != (ExceptionInfo *) NULL);
4878 assert(exception->signature == MagickSignature);
cristy0834d642011-03-18 18:26:08 +00004879 statistic_image=CloneImage(image,image->columns,image->rows,MagickTrue,
4880 exception);
4881 if (statistic_image == (Image *) NULL)
4882 return((Image *) NULL);
4883 if (SetImageStorageClass(statistic_image,DirectClass) == MagickFalse)
4884 {
4885 InheritException(exception,&statistic_image->exception);
4886 statistic_image=DestroyImage(statistic_image);
4887 return((Image *) NULL);
4888 }
cristy6fc86bb2011-03-18 23:45:16 +00004889 pixel_list=AcquirePixelListThreadSet(StatisticWidth,StatisticHeight);
cristy0834d642011-03-18 18:26:08 +00004890 if (pixel_list == (PixelList **) NULL)
4891 {
4892 statistic_image=DestroyImage(statistic_image);
4893 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
4894 }
4895 /*
cristy8d752042011-03-19 01:00:36 +00004896 Make each pixel the min / max / median / mode / etc. of the neighborhood.
cristy0834d642011-03-18 18:26:08 +00004897 */
4898 status=MagickTrue;
4899 progress=0;
4900 image_view=AcquireCacheView(image);
4901 statistic_view=AcquireCacheView(statistic_image);
4902#if defined(MAGICKCORE_OPENMP_SUPPORT)
4903 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
4904#endif
4905 for (y=0; y < (ssize_t) statistic_image->rows; y++)
4906 {
4907 const int
4908 id = GetOpenMPThreadId();
4909
cristy4c08aed2011-07-01 19:47:50 +00004910 register const Quantum
cristy0834d642011-03-18 18:26:08 +00004911 *restrict p;
4912
cristy4c08aed2011-07-01 19:47:50 +00004913 register Quantum
cristy0834d642011-03-18 18:26:08 +00004914 *restrict q;
4915
4916 register ssize_t
4917 x;
4918
4919 if (status == MagickFalse)
4920 continue;
cristy6fc86bb2011-03-18 23:45:16 +00004921 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) StatisticWidth/2L),y-
4922 (ssize_t) (StatisticHeight/2L),image->columns+StatisticWidth,
4923 StatisticHeight,exception);
cristy3cba8ca2011-03-19 01:29:12 +00004924 q=QueueCacheViewAuthenticPixels(statistic_view,0,y,statistic_image->columns, 1,exception);
cristy4c08aed2011-07-01 19:47:50 +00004925 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy0834d642011-03-18 18:26:08 +00004926 {
4927 status=MagickFalse;
4928 continue;
4929 }
cristy0834d642011-03-18 18:26:08 +00004930 for (x=0; x < (ssize_t) statistic_image->columns; x++)
4931 {
cristy4c08aed2011-07-01 19:47:50 +00004932 PixelInfo
cristy0834d642011-03-18 18:26:08 +00004933 pixel;
4934
cristy4c08aed2011-07-01 19:47:50 +00004935 register const Quantum
cristy6e3026a2011-03-19 00:54:38 +00004936 *restrict r;
4937
cristy0834d642011-03-18 18:26:08 +00004938 register ssize_t
4939 u,
4940 v;
4941
4942 r=p;
cristy0834d642011-03-18 18:26:08 +00004943 ResetPixelList(pixel_list[id]);
cristy6e4c3292011-03-19 00:53:55 +00004944 for (v=0; v < (ssize_t) StatisticHeight; v++)
cristy0834d642011-03-18 18:26:08 +00004945 {
cristy6e4c3292011-03-19 00:53:55 +00004946 for (u=0; u < (ssize_t) StatisticWidth; u++)
cristydcfc1ad2011-07-07 16:25:41 +00004947 InsertPixelList(image,r+u*GetPixelComponents(image),pixel_list[id]);
4948 r+=(image->columns+StatisticWidth)*GetPixelComponents(image);
cristy0834d642011-03-18 18:26:08 +00004949 }
cristy4c08aed2011-07-01 19:47:50 +00004950 GetPixelInfo(image,&pixel);
cristy490408a2011-07-07 14:42:05 +00004951 SetPixelInfo(image,p+(StatisticWidth*StatisticHeight/2)*
cristydcfc1ad2011-07-07 16:25:41 +00004952 GetPixelComponents(image),&pixel);
cristy0834d642011-03-18 18:26:08 +00004953 switch (type)
4954 {
cristy80c99742011-04-04 14:46:39 +00004955 case GradientStatistic:
4956 {
cristy4c08aed2011-07-01 19:47:50 +00004957 PixelInfo
cristy80c99742011-04-04 14:46:39 +00004958 maximum,
4959 minimum;
4960
4961 minimum=GetMinimumPixelList(pixel_list[id]);
4962 maximum=GetMaximumPixelList(pixel_list[id]);
4963 pixel.red=MagickAbsoluteValue(maximum.red-minimum.red);
4964 pixel.green=MagickAbsoluteValue(maximum.green-minimum.green);
4965 pixel.blue=MagickAbsoluteValue(maximum.blue-minimum.blue);
cristy4c08aed2011-07-01 19:47:50 +00004966 pixel.alpha=MagickAbsoluteValue(maximum.alpha-minimum.alpha);
cristy80c99742011-04-04 14:46:39 +00004967 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00004968 pixel.black=MagickAbsoluteValue(maximum.black-minimum.black);
cristy80c99742011-04-04 14:46:39 +00004969 break;
4970 }
cristy6fc86bb2011-03-18 23:45:16 +00004971 case MaximumStatistic:
4972 {
4973 pixel=GetMaximumPixelList(pixel_list[id]);
4974 break;
4975 }
cristy49f37242011-03-22 18:18:23 +00004976 case MeanStatistic:
4977 {
4978 pixel=GetMeanPixelList(pixel_list[id]);
4979 break;
4980 }
cristyf2ad14a2011-03-18 18:57:25 +00004981 case MedianStatistic:
cristy6fc86bb2011-03-18 23:45:16 +00004982 default:
cristyf2ad14a2011-03-18 18:57:25 +00004983 {
4984 pixel=GetMedianPixelList(pixel_list[id]);
4985 break;
4986 }
cristy6fc86bb2011-03-18 23:45:16 +00004987 case MinimumStatistic:
4988 {
4989 pixel=GetMinimumPixelList(pixel_list[id]);
4990 break;
4991 }
cristyf2ad14a2011-03-18 18:57:25 +00004992 case ModeStatistic:
4993 {
4994 pixel=GetModePixelList(pixel_list[id]);
4995 break;
4996 }
4997 case NonpeakStatistic:
4998 {
4999 pixel=GetNonpeakPixelList(pixel_list[id]);
5000 break;
5001 }
cristy9a68cbb2011-03-29 00:51:23 +00005002 case StandardDeviationStatistic:
5003 {
5004 pixel=GetStandardDeviationPixelList(pixel_list[id]);
5005 break;
5006 }
cristy0834d642011-03-18 18:26:08 +00005007 }
cristy2b9582a2011-07-04 17:38:56 +00005008 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00005009 SetPixelRed(statistic_image,ClampToQuantum(pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00005010 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00005011 SetPixelGreen(statistic_image,ClampToQuantum(pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00005012 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00005013 SetPixelBlue(statistic_image,ClampToQuantum(pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00005014 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy0834d642011-03-18 18:26:08 +00005015 (image->colorspace == CMYKColorspace))
cristy490408a2011-07-07 14:42:05 +00005016 SetPixelBlack(statistic_image,ClampToQuantum(pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00005017 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00005018 (image->matte != MagickFalse))
cristy490408a2011-07-07 14:42:05 +00005019 SetPixelAlpha(statistic_image,ClampToQuantum(pixel.alpha),q);
cristydcfc1ad2011-07-07 16:25:41 +00005020 p+=GetPixelComponents(image);
5021 q+=GetPixelComponents(statistic_image);
cristy0834d642011-03-18 18:26:08 +00005022 }
5023 if (SyncCacheViewAuthenticPixels(statistic_view,exception) == MagickFalse)
5024 status=MagickFalse;
5025 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5026 {
5027 MagickBooleanType
5028 proceed;
5029
5030#if defined(MAGICKCORE_OPENMP_SUPPORT)
5031 #pragma omp critical (MagickCore_StatisticImage)
5032#endif
5033 proceed=SetImageProgress(image,StatisticImageTag,progress++,
5034 image->rows);
5035 if (proceed == MagickFalse)
5036 status=MagickFalse;
5037 }
5038 }
5039 statistic_view=DestroyCacheView(statistic_view);
5040 image_view=DestroyCacheView(image_view);
5041 pixel_list=DestroyPixelListThreadSet(pixel_list);
5042 return(statistic_image);
5043}
5044
5045/*
5046%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5047% %
5048% %
5049% %
cristy3ed852e2009-09-05 21:47:34 +00005050% U n s h a r p M a s k I m a g e %
5051% %
5052% %
5053% %
5054%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5055%
5056% UnsharpMaskImage() sharpens one or more image channels. We convolve the
5057% image with a Gaussian operator of the given radius and standard deviation
5058% (sigma). For reasonable results, radius should be larger than sigma. Use a
5059% radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
5060%
5061% The format of the UnsharpMaskImage method is:
5062%
5063% Image *UnsharpMaskImage(const Image *image,const double radius,
5064% const double sigma,const double amount,const double threshold,
5065% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00005066%
5067% A description of each parameter follows:
5068%
5069% o image: the image.
5070%
cristy3ed852e2009-09-05 21:47:34 +00005071% o radius: the radius of the Gaussian, in pixels, not counting the center
5072% pixel.
5073%
5074% o sigma: the standard deviation of the Gaussian, in pixels.
5075%
5076% o amount: the percentage of the difference between the original and the
5077% blur image that is added back into the original.
5078%
5079% o threshold: the threshold in pixels needed to apply the diffence amount.
5080%
5081% o exception: return any errors or warnings in this structure.
5082%
5083*/
cristyf4ad9df2011-07-08 16:49:03 +00005084MagickExport Image *UnsharpMaskImage(const Image *image,
5085 const double radius,const double sigma,const double amount,
5086 const double threshold,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00005087{
5088#define SharpenImageTag "Sharpen/Image"
5089
cristyc4c8d132010-01-07 01:58:38 +00005090 CacheView
5091 *image_view,
5092 *unsharp_view;
5093
cristy3ed852e2009-09-05 21:47:34 +00005094 Image
5095 *unsharp_image;
5096
cristy3ed852e2009-09-05 21:47:34 +00005097 MagickBooleanType
5098 status;
5099
cristybb503372010-05-27 20:51:26 +00005100 MagickOffsetType
5101 progress;
5102
cristy4c08aed2011-07-01 19:47:50 +00005103 PixelInfo
cristyddd82202009-11-03 20:14:50 +00005104 bias;
cristy3ed852e2009-09-05 21:47:34 +00005105
5106 MagickRealType
5107 quantum_threshold;
5108
cristybb503372010-05-27 20:51:26 +00005109 ssize_t
5110 y;
5111
cristy3ed852e2009-09-05 21:47:34 +00005112 assert(image != (const Image *) NULL);
5113 assert(image->signature == MagickSignature);
5114 if (image->debug != MagickFalse)
5115 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5116 assert(exception != (ExceptionInfo *) NULL);
cristyf4ad9df2011-07-08 16:49:03 +00005117 unsharp_image=BlurImage(image,radius,sigma,exception);
cristy3ed852e2009-09-05 21:47:34 +00005118 if (unsharp_image == (Image *) NULL)
5119 return((Image *) NULL);
5120 quantum_threshold=(MagickRealType) QuantumRange*threshold;
5121 /*
5122 Unsharp-mask image.
5123 */
5124 status=MagickTrue;
5125 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00005126 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00005127 image_view=AcquireCacheView(image);
5128 unsharp_view=AcquireCacheView(unsharp_image);
cristyb5d5f722009-11-04 03:03:49 +00005129#if defined(MAGICKCORE_OPENMP_SUPPORT)
5130 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00005131#endif
cristybb503372010-05-27 20:51:26 +00005132 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00005133 {
cristy4c08aed2011-07-01 19:47:50 +00005134 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00005135 pixel;
5136
cristy4c08aed2011-07-01 19:47:50 +00005137 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00005138 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00005139
cristy4c08aed2011-07-01 19:47:50 +00005140 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00005141 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00005142
cristy117ff172010-08-15 21:35:32 +00005143 register ssize_t
5144 x;
5145
cristy3ed852e2009-09-05 21:47:34 +00005146 if (status == MagickFalse)
5147 continue;
5148 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
5149 q=GetCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
5150 exception);
cristy4c08aed2011-07-01 19:47:50 +00005151 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00005152 {
5153 status=MagickFalse;
5154 continue;
5155 }
cristyddd82202009-11-03 20:14:50 +00005156 pixel=bias;
cristybb503372010-05-27 20:51:26 +00005157 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00005158 {
cristy2b9582a2011-07-04 17:38:56 +00005159 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00005160 {
cristy4c08aed2011-07-01 19:47:50 +00005161 pixel.red=GetPixelRed(image,p)-(MagickRealType) GetPixelRed(image,q);
cristy3ed852e2009-09-05 21:47:34 +00005162 if (fabs(2.0*pixel.red) < quantum_threshold)
cristy4c08aed2011-07-01 19:47:50 +00005163 pixel.red=(MagickRealType) GetPixelRed(image,p);
cristy3ed852e2009-09-05 21:47:34 +00005164 else
cristy4c08aed2011-07-01 19:47:50 +00005165 pixel.red=(MagickRealType) GetPixelRed(image,p)+(pixel.red*amount);
5166 SetPixelRed(unsharp_image,ClampToQuantum(pixel.red),q);
cristy3ed852e2009-09-05 21:47:34 +00005167 }
cristy2b9582a2011-07-04 17:38:56 +00005168 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00005169 {
cristy4c08aed2011-07-01 19:47:50 +00005170 pixel.green=GetPixelGreen(image,p)-
5171 (MagickRealType) GetPixelGreen(image,q);
cristy3ed852e2009-09-05 21:47:34 +00005172 if (fabs(2.0*pixel.green) < quantum_threshold)
cristy4c08aed2011-07-01 19:47:50 +00005173 pixel.green=(MagickRealType)
5174 GetPixelGreen(image,p);
cristy3ed852e2009-09-05 21:47:34 +00005175 else
cristy4c08aed2011-07-01 19:47:50 +00005176 pixel.green=(MagickRealType)
5177 GetPixelGreen(image,p)+
5178 (pixel.green*amount);
5179 SetPixelGreen(unsharp_image,
5180 ClampToQuantum(pixel.green),q);
cristy3ed852e2009-09-05 21:47:34 +00005181 }
cristy2b9582a2011-07-04 17:38:56 +00005182 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00005183 {
cristy4c08aed2011-07-01 19:47:50 +00005184 pixel.blue=GetPixelBlue(image,p)-
5185 (MagickRealType) GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00005186 if (fabs(2.0*pixel.blue) < quantum_threshold)
cristy4c08aed2011-07-01 19:47:50 +00005187 pixel.blue=(MagickRealType)
5188 GetPixelBlue(image,p);
cristy3ed852e2009-09-05 21:47:34 +00005189 else
cristy4c08aed2011-07-01 19:47:50 +00005190 pixel.blue=(MagickRealType)
5191 GetPixelBlue(image,p)+(pixel.blue*amount);
5192 SetPixelBlue(unsharp_image,
5193 ClampToQuantum(pixel.blue),q);
cristy3ed852e2009-09-05 21:47:34 +00005194 }
cristy2b9582a2011-07-04 17:38:56 +00005195 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00005196 (image->colorspace == CMYKColorspace))
5197 {
cristy4c08aed2011-07-01 19:47:50 +00005198 pixel.black=GetPixelBlack(image,p)-
5199 (MagickRealType) GetPixelBlack(image,q);
5200 if (fabs(2.0*pixel.black) < quantum_threshold)
5201 pixel.black=(MagickRealType)
5202 GetPixelBlack(image,p);
cristy3ed852e2009-09-05 21:47:34 +00005203 else
cristy4c08aed2011-07-01 19:47:50 +00005204 pixel.black=(MagickRealType)
5205 GetPixelBlack(image,p)+(pixel.black*
5206 amount);
5207 SetPixelBlack(unsharp_image,
5208 ClampToQuantum(pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00005209 }
cristy2b9582a2011-07-04 17:38:56 +00005210 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00005211 {
5212 pixel.alpha=GetPixelAlpha(image,p)-
5213 (MagickRealType) GetPixelAlpha(image,q);
5214 if (fabs(2.0*pixel.alpha) < quantum_threshold)
5215 pixel.alpha=(MagickRealType)
5216 GetPixelAlpha(image,p);
5217 else
5218 pixel.alpha=GetPixelAlpha(image,p)+
5219 (pixel.alpha*amount);
5220 SetPixelAlpha(unsharp_image,
5221 ClampToQuantum(pixel.alpha),q);
5222 }
cristydcfc1ad2011-07-07 16:25:41 +00005223 p+=GetPixelComponents(image);
5224 q+=GetPixelComponents(unsharp_image);
cristy3ed852e2009-09-05 21:47:34 +00005225 }
5226 if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
5227 status=MagickFalse;
5228 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5229 {
5230 MagickBooleanType
5231 proceed;
5232
cristyb5d5f722009-11-04 03:03:49 +00005233#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00005234 #pragma omp critical (MagickCore_UnsharpMaskImage)
cristy3ed852e2009-09-05 21:47:34 +00005235#endif
5236 proceed=SetImageProgress(image,SharpenImageTag,progress++,image->rows);
5237 if (proceed == MagickFalse)
5238 status=MagickFalse;
5239 }
5240 }
5241 unsharp_image->type=image->type;
5242 unsharp_view=DestroyCacheView(unsharp_view);
5243 image_view=DestroyCacheView(image_view);
5244 if (status == MagickFalse)
5245 unsharp_image=DestroyImage(unsharp_image);
5246 return(unsharp_image);
5247}