blob: 20f65f1769175b34636acf065825f3e3575bb6b5 [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;
cristyed231572011-07-14 02:18:59 +0000362 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000363 (image->matte != MagickFalse))
cristy4c08aed2011-07-01 19:47:50 +0000364 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,p));
cristyed231572011-07-14 02:18:59 +0000365 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000366 pixel.red+=(*k)*alpha*GetPixelRed(image,p);
cristyed231572011-07-14 02:18:59 +0000367 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000368 pixel.green+=(*k)*alpha*GetPixelGreen(image,p);
cristyed231572011-07-14 02:18:59 +0000369 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000370 pixel.blue+=(*k)*alpha*GetPixelBlue(image,p);
cristyed231572011-07-14 02:18:59 +0000371 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 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);
cristyed231572011-07-14 02:18:59 +0000374 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 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++;
cristyed231572011-07-14 02:18:59 +0000378 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000379 }
380 }
381 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +0000382 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000383 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +0000384 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000385 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +0000386 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000387 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +0000388 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 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);
cristyed231572011-07-14 02:18:59 +0000391 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000392 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristyed231572011-07-14 02:18:59 +0000393 q+=GetPixelChannels(blur_image);
394 r+=GetPixelChannels(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;
cristyed231572011-07-14 02:18:59 +0000654 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000655 (image->matte != MagickFalse))
cristy4c08aed2011-07-01 19:47:50 +0000656 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,p));
cristyed231572011-07-14 02:18:59 +0000657 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000658 pixel.red+=(*k)*alpha*GetPixelRed(image,p);
cristyed231572011-07-14 02:18:59 +0000659 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000660 pixel.green+=(*k)*alpha*GetPixelGreen(image,p);
cristyed231572011-07-14 02:18:59 +0000661 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000662 pixel.blue+=(*k)*alpha*GetPixelBlue(image,p);
cristyed231572011-07-14 02:18:59 +0000663 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 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);
cristyed231572011-07-14 02:18:59 +0000666 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 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++;
cristyed231572011-07-14 02:18:59 +0000670 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000671 }
672 }
673 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +0000674 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000675 SetPixelRed(sharp_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +0000676 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000677 SetPixelGreen(sharp_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +0000678 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000679 SetPixelBlue(sharp_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +0000680 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 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);
cristyed231572011-07-14 02:18:59 +0000683 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000684 SetPixelAlpha(sharp_image,ClampToQuantum(pixel.alpha),q);
cristyed231572011-07-14 02:18:59 +0000685 q+=GetPixelChannels(sharp_image);
686 r+=GetPixelChannels(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;
cristyed231572011-07-14 02:18:59 +0000926 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) ||
cristyf4ad9df2011-07-08 16:49:03 +0000927 (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++;
cristyed231572011-07-14 02:18:59 +0000937 kernel_pixels+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000938 }
cristyed231572011-07-14 02:18:59 +0000939 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000940 SetPixelRed(blur_image,ClampToQuantum(pixel.red),q);
cristyed231572011-07-14 02:18:59 +0000941 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000942 SetPixelGreen(blur_image,ClampToQuantum(pixel.green),q);
cristyed231572011-07-14 02:18:59 +0000943 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000944 SetPixelBlue(blur_image,ClampToQuantum(pixel.blue),q);
cristyed231572011-07-14 02:18:59 +0000945 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000946 (blur_image->colorspace == CMYKColorspace))
947 SetPixelBlack(blur_image,ClampToQuantum(pixel.black),q);
cristyed231572011-07-14 02:18:59 +0000948 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 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++;
cristyed231572011-07-14 02:18:59 +0000956 kernel_pixels+=GetPixelChannels(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++;
cristyed231572011-07-14 02:18:59 +0000979 kernel_pixels+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000980 }
981 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +0000982 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000983 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +0000984 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000985 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +0000986 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000987 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +0000988 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000989 (blur_image->colorspace == CMYKColorspace))
990 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristyed231572011-07-14 02:18:59 +0000991 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 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++;
cristyed231572011-07-14 02:18:59 +0000999 kernel_pixels+=GetPixelChannels(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 }
cristyed231572011-07-14 02:18:59 +00001004 p+=GetPixelChannels(image);
1005 q+=GetPixelChannels(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;
cristyed231572011-07-14 02:18:59 +00001071 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) ||
cristyf4ad9df2011-07-08 16:49:03 +00001072 (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++;
cristyed231572011-07-14 02:18:59 +00001082 kernel_pixels+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001083 }
cristyed231572011-07-14 02:18:59 +00001084 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001085 SetPixelRed(blur_image,ClampToQuantum(pixel.red),q);
cristyed231572011-07-14 02:18:59 +00001086 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001087 SetPixelGreen(blur_image,ClampToQuantum(pixel.green),q);
cristyed231572011-07-14 02:18:59 +00001088 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001089 SetPixelBlue(blur_image,ClampToQuantum(pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00001090 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001091 (blur_image->colorspace == CMYKColorspace))
1092 SetPixelBlack(blur_image,ClampToQuantum(pixel.black),q);
cristyed231572011-07-14 02:18:59 +00001093 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 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++;
cristyed231572011-07-14 02:18:59 +00001101 kernel_pixels+=GetPixelChannels(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++;
cristyed231572011-07-14 02:18:59 +00001124 kernel_pixels+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001125 }
1126 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +00001127 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001128 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +00001129 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001130 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +00001131 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001132 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00001133 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001134 (blur_image->colorspace == CMYKColorspace))
1135 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristyed231572011-07-14 02:18:59 +00001136 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 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++;
cristyed231572011-07-14 02:18:59 +00001144 kernel_pixels+=GetPixelChannels(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 }
cristyed231572011-07-14 02:18:59 +00001149 p+=GetPixelChannels(blur_image);
1150 q+=GetPixelChannels(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
cristyfccdab92009-11-30 16:43:57 +00001226 MagickRealType
1227 gamma;
1228
cristybb503372010-05-27 20:51:26 +00001229 register ssize_t
cristyfccdab92009-11-30 16:43:57 +00001230 i;
1231
cristybb503372010-05-27 20:51:26 +00001232 size_t
cristyfccdab92009-11-30 16:43:57 +00001233 width;
1234
cristybb503372010-05-27 20:51:26 +00001235 ssize_t
1236 y;
1237
cristyfccdab92009-11-30 16:43:57 +00001238 /*
1239 Initialize convolve image attributes.
1240 */
1241 assert(image != (Image *) NULL);
1242 assert(image->signature == MagickSignature);
1243 if (image->debug != MagickFalse)
1244 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1245 assert(exception != (ExceptionInfo *) NULL);
1246 assert(exception->signature == MagickSignature);
1247 width=order;
1248 if ((width % 2) == 0)
1249 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
cristy08429172011-07-14 17:18:16 +00001250 convolve_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1251 exception);
cristyfccdab92009-11-30 16:43:57 +00001252 if (convolve_image == (Image *) NULL)
1253 return((Image *) NULL);
1254 if (SetImageStorageClass(convolve_image,DirectClass) == MagickFalse)
1255 {
1256 InheritException(exception,&convolve_image->exception);
1257 convolve_image=DestroyImage(convolve_image);
1258 return((Image *) NULL);
1259 }
1260 if (image->debug != MagickFalse)
1261 {
1262 char
1263 format[MaxTextExtent],
1264 *message;
1265
cristy117ff172010-08-15 21:35:32 +00001266 register const double
1267 *k;
1268
cristy4e154852011-07-14 13:28:53 +00001269 register ssize_t
1270 u;
1271
cristybb503372010-05-27 20:51:26 +00001272 ssize_t
cristyfccdab92009-11-30 16:43:57 +00001273 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;
cristyfccdab92009-11-30 16:43:57 +00001315 image_view=AcquireCacheView(image);
1316 convolve_view=AcquireCacheView(convolve_image);
1317#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy175653e2011-07-10 23:13:34 +00001318 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristyfccdab92009-11-30 16:43:57 +00001319#endif
cristybb503372010-05-27 20:51:26 +00001320 for (y=0; y < (ssize_t) image->rows; y++)
cristyfccdab92009-11-30 16:43:57 +00001321 {
cristy4c08aed2011-07-01 19:47:50 +00001322 register const Quantum
cristyfccdab92009-11-30 16:43:57 +00001323 *restrict p;
1324
cristy4c08aed2011-07-01 19:47:50 +00001325 register Quantum
cristyfccdab92009-11-30 16:43:57 +00001326 *restrict q;
1327
cristy117ff172010-08-15 21:35:32 +00001328 register ssize_t
1329 x;
1330
cristy4e154852011-07-14 13:28:53 +00001331 ssize_t
cristyed231572011-07-14 02:18:59 +00001332 channels,
1333 convolve_channels;
1334
cristyfccdab92009-11-30 16:43:57 +00001335 if (status == MagickFalse)
1336 continue;
cristyce889302010-06-30 19:16:36 +00001337 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
1338 (width/2L),image->columns+width,width,exception);
cristy08429172011-07-14 17:18:16 +00001339 q=QueueCacheViewAuthenticPixels(convolve_view,0,y,convolve_image->columns,1,
cristyfccdab92009-11-30 16:43:57 +00001340 exception);
cristy4c08aed2011-07-01 19:47:50 +00001341 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristyfccdab92009-11-30 16:43:57 +00001342 {
1343 status=MagickFalse;
1344 continue;
1345 }
cristyed231572011-07-14 02:18:59 +00001346 channels=GetPixelChannels(image);
1347 convolve_channels=GetPixelChannels(convolve_image);
cristybb503372010-05-27 20:51:26 +00001348 for (x=0; x < (ssize_t) image->columns; x++)
cristyfccdab92009-11-30 16:43:57 +00001349 {
cristybb503372010-05-27 20:51:26 +00001350 register ssize_t
cristyed231572011-07-14 02:18:59 +00001351 i;
cristyfccdab92009-11-30 16:43:57 +00001352
cristyed231572011-07-14 02:18:59 +00001353 for (i=0; i < (ssize_t) channels; i++)
1354 {
cristyed231572011-07-14 02:18:59 +00001355 MagickRealType
cristy4e154852011-07-14 13:28:53 +00001356 alpha,
1357 gamma,
cristyed231572011-07-14 02:18:59 +00001358 pixel;
1359
1360 PixelChannel
1361 channel;
1362
1363 PixelTrait
1364 convolve_traits,
1365 traits;
1366
1367 register const double
1368 *restrict k;
1369
1370 register const Quantum
1371 *restrict kernel_pixels;
1372
1373 register ssize_t
1374 u;
1375
1376 ssize_t
1377 v;
1378
cristyed231572011-07-14 02:18:59 +00001379 traits=GetPixelChannelMapTraits(image,i);
cristy4e154852011-07-14 13:28:53 +00001380 if (traits == UndefinedPixelTrait)
cristyed231572011-07-14 02:18:59 +00001381 continue;
cristy4e154852011-07-14 13:28:53 +00001382 channel=GetPixelChannelMapChannel(image,i);
1383 convolve_traits=GetPixelChannelMapTraits(convolve_image,channel);
1384 if (convolve_traits == UndefinedPixelTrait)
1385 continue;
1386 if ((convolve_traits & CopyPixelTrait) != 0)
1387 {
cristy39177402011-07-14 16:45:04 +00001388 size_t
cristyf8500872011-07-14 14:02:41 +00001389 center;
cristy4e154852011-07-14 13:28:53 +00001390
cristyf8500872011-07-14 14:02:41 +00001391 center=((image->columns+width)*width/2)*channels+i;
1392 SetPixelChannel(convolve_image,channel,p[center],q);
cristy4e154852011-07-14 13:28:53 +00001393 continue;
1394 }
cristyed231572011-07-14 02:18:59 +00001395 k=normal_kernel;
1396 kernel_pixels=p;
cristy4e154852011-07-14 13:28:53 +00001397 pixel=image->bias;
cristyc8698832011-07-14 13:33:20 +00001398 if ((GetPixelAlphaTraits(image) == UndefinedPixelTrait) ||
cristyed231572011-07-14 02:18:59 +00001399 (image->matte == MagickFalse))
cristyfccdab92009-11-30 16:43:57 +00001400 {
cristyed231572011-07-14 02:18:59 +00001401 /*
cristy4e154852011-07-14 13:28:53 +00001402 No alpha blending.
cristyed231572011-07-14 02:18:59 +00001403 */
1404 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001405 {
cristyed231572011-07-14 02:18:59 +00001406 for (u=0; u < (ssize_t) width; u++)
cristy175653e2011-07-10 23:13:34 +00001407 {
cristyed231572011-07-14 02:18:59 +00001408 pixel+=(*k)*kernel_pixels[u*channels+i];
1409 k++;
cristy175653e2011-07-10 23:13:34 +00001410 }
cristyed231572011-07-14 02:18:59 +00001411 kernel_pixels+=(image->columns+width)*channels;
cristy175653e2011-07-10 23:13:34 +00001412 }
cristy4e154852011-07-14 13:28:53 +00001413 SetPixelChannel(convolve_image,channel,ClampToQuantum(pixel),q);
1414 continue;
cristyed231572011-07-14 02:18:59 +00001415 }
cristy4e154852011-07-14 13:28:53 +00001416 /*
1417 Alpha blending.
1418 */
1419 gamma=0.0;
1420 for (v=0; v < (ssize_t) width; v++)
1421 {
1422 for (u=0; u < (ssize_t) width; u++)
1423 {
1424 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,
1425 kernel_pixels+u*channels));
1426 if ((traits & BlendPixelTrait) == 0)
1427 pixel+=(*k)*kernel_pixels[u*channels+i];
1428 else
1429 pixel+=(*k)*alpha*kernel_pixels[u*channels+i];
1430 gamma+=(*k)*alpha;
1431 k++;
1432 }
1433 kernel_pixels+=(image->columns+width)*channels;
1434 }
1435 if ((convolve_traits & BlendPixelTrait) == 0)
1436 SetPixelChannel(convolve_image,channel,ClampToQuantum(pixel),q);
cristyed231572011-07-14 02:18:59 +00001437 else
cristyfccdab92009-11-30 16:43:57 +00001438 {
cristyed231572011-07-14 02:18:59 +00001439 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy4e154852011-07-14 13:28:53 +00001440 SetPixelChannel(convolve_image,channel,ClampToQuantum(gamma*pixel),
1441 q);
cristyed231572011-07-14 02:18:59 +00001442 }
1443 }
1444 p+=channels;
1445 q+=convolve_channels;
cristyfccdab92009-11-30 16:43:57 +00001446 }
cristyed231572011-07-14 02:18:59 +00001447 if (SyncCacheViewAuthenticPixels(convolve_view,exception) == MagickFalse)
cristyfccdab92009-11-30 16:43:57 +00001448 status=MagickFalse;
1449 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1450 {
1451 MagickBooleanType
1452 proceed;
1453
1454#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00001455 #pragma omp critical (MagickCore_ConvolveImage)
cristyfccdab92009-11-30 16:43:57 +00001456#endif
1457 proceed=SetImageProgress(image,ConvolveImageTag,progress++,image->rows);
1458 if (proceed == MagickFalse)
1459 status=MagickFalse;
1460 }
1461 }
1462 convolve_image->type=image->type;
1463 convolve_view=DestroyCacheView(convolve_view);
1464 image_view=DestroyCacheView(image_view);
1465 normal_kernel=(double *) RelinquishMagickMemory(normal_kernel);
1466 if (status == MagickFalse)
1467 convolve_image=DestroyImage(convolve_image);
1468 return(convolve_image);
1469}
1470
1471/*
1472%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1473% %
1474% %
1475% %
cristy3ed852e2009-09-05 21:47:34 +00001476% D e s p e c k l e I m a g e %
1477% %
1478% %
1479% %
1480%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1481%
1482% DespeckleImage() reduces the speckle noise in an image while perserving the
1483% edges of the original image.
1484%
1485% The format of the DespeckleImage method is:
1486%
1487% Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1488%
1489% A description of each parameter follows:
1490%
1491% o image: the image.
1492%
1493% o exception: return any errors or warnings in this structure.
1494%
1495*/
1496
cristybb503372010-05-27 20:51:26 +00001497static void Hull(const ssize_t x_offset,const ssize_t y_offset,
1498 const size_t columns,const size_t rows,Quantum *f,Quantum *g,
cristy3ed852e2009-09-05 21:47:34 +00001499 const int polarity)
1500{
cristy3ed852e2009-09-05 21:47:34 +00001501 MagickRealType
1502 v;
1503
cristy3ed852e2009-09-05 21:47:34 +00001504 register Quantum
1505 *p,
1506 *q,
1507 *r,
1508 *s;
1509
cristy117ff172010-08-15 21:35:32 +00001510 register ssize_t
1511 x;
1512
1513 ssize_t
1514 y;
1515
cristy3ed852e2009-09-05 21:47:34 +00001516 assert(f != (Quantum *) NULL);
1517 assert(g != (Quantum *) NULL);
1518 p=f+(columns+2);
1519 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001520 r=p+(y_offset*((ssize_t) columns+2)+x_offset);
1521 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001522 {
1523 p++;
1524 q++;
1525 r++;
1526 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001527 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001528 {
1529 v=(MagickRealType) (*p);
1530 if ((MagickRealType) *r >= (v+(MagickRealType) ScaleCharToQuantum(2)))
1531 v+=ScaleCharToQuantum(1);
1532 *q=(Quantum) v;
1533 p++;
1534 q++;
1535 r++;
1536 }
1537 else
cristybb503372010-05-27 20:51:26 +00001538 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001539 {
1540 v=(MagickRealType) (*p);
1541 if ((MagickRealType) *r <= (v-(MagickRealType) ScaleCharToQuantum(2)))
cristybb503372010-05-27 20:51:26 +00001542 v-=(ssize_t) ScaleCharToQuantum(1);
cristy3ed852e2009-09-05 21:47:34 +00001543 *q=(Quantum) v;
1544 p++;
1545 q++;
1546 r++;
1547 }
1548 p++;
1549 q++;
1550 r++;
1551 }
1552 p=f+(columns+2);
1553 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001554 r=q+(y_offset*((ssize_t) columns+2)+x_offset);
1555 s=q-(y_offset*((ssize_t) columns+2)+x_offset);
1556 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001557 {
1558 p++;
1559 q++;
1560 r++;
1561 s++;
1562 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001563 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001564 {
1565 v=(MagickRealType) (*q);
1566 if (((MagickRealType) *s >=
1567 (v+(MagickRealType) ScaleCharToQuantum(2))) &&
1568 ((MagickRealType) *r > v))
1569 v+=ScaleCharToQuantum(1);
1570 *p=(Quantum) v;
1571 p++;
1572 q++;
1573 r++;
1574 s++;
1575 }
1576 else
cristybb503372010-05-27 20:51:26 +00001577 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001578 {
1579 v=(MagickRealType) (*q);
1580 if (((MagickRealType) *s <=
1581 (v-(MagickRealType) ScaleCharToQuantum(2))) &&
1582 ((MagickRealType) *r < v))
1583 v-=(MagickRealType) ScaleCharToQuantum(1);
1584 *p=(Quantum) v;
1585 p++;
1586 q++;
1587 r++;
1588 s++;
1589 }
1590 p++;
1591 q++;
1592 r++;
1593 s++;
1594 }
1595}
1596
1597MagickExport Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1598{
1599#define DespeckleImageTag "Despeckle/Image"
1600
cristy2407fc22009-09-11 00:55:25 +00001601 CacheView
1602 *despeckle_view,
1603 *image_view;
1604
cristy3ed852e2009-09-05 21:47:34 +00001605 Image
1606 *despeckle_image;
1607
cristy3ed852e2009-09-05 21:47:34 +00001608 MagickBooleanType
1609 status;
1610
cristya58c3172011-02-19 19:23:11 +00001611 register ssize_t
1612 i;
1613
cristy3ed852e2009-09-05 21:47:34 +00001614 Quantum
cristy65b9f392011-02-22 14:22:54 +00001615 *restrict buffers,
1616 *restrict pixels;
cristy3ed852e2009-09-05 21:47:34 +00001617
1618 size_t
cristya58c3172011-02-19 19:23:11 +00001619 length,
1620 number_channels;
cristy117ff172010-08-15 21:35:32 +00001621
cristybb503372010-05-27 20:51:26 +00001622 static const ssize_t
cristy691a29e2009-09-11 00:44:10 +00001623 X[4] = {0, 1, 1,-1},
1624 Y[4] = {1, 0, 1, 1};
cristy3ed852e2009-09-05 21:47:34 +00001625
cristy3ed852e2009-09-05 21:47:34 +00001626 /*
1627 Allocate despeckled image.
1628 */
1629 assert(image != (const Image *) NULL);
1630 assert(image->signature == MagickSignature);
1631 if (image->debug != MagickFalse)
1632 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1633 assert(exception != (ExceptionInfo *) NULL);
1634 assert(exception->signature == MagickSignature);
1635 despeckle_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1636 exception);
1637 if (despeckle_image == (Image *) NULL)
1638 return((Image *) NULL);
1639 if (SetImageStorageClass(despeckle_image,DirectClass) == MagickFalse)
1640 {
1641 InheritException(exception,&despeckle_image->exception);
1642 despeckle_image=DestroyImage(despeckle_image);
1643 return((Image *) NULL);
1644 }
1645 /*
1646 Allocate image buffers.
1647 */
1648 length=(size_t) ((image->columns+2)*(image->rows+2));
cristy65b9f392011-02-22 14:22:54 +00001649 pixels=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1650 buffers=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1651 if ((pixels == (Quantum *) NULL) || (buffers == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001652 {
cristy65b9f392011-02-22 14:22:54 +00001653 if (buffers != (Quantum *) NULL)
1654 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1655 if (pixels != (Quantum *) NULL)
1656 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001657 despeckle_image=DestroyImage(despeckle_image);
1658 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1659 }
1660 /*
1661 Reduce speckle in the image.
1662 */
1663 status=MagickTrue;
cristy109695a2011-02-19 19:38:14 +00001664 number_channels=(size_t) (image->colorspace == CMYKColorspace ? 5 : 4);
cristy3ed852e2009-09-05 21:47:34 +00001665 image_view=AcquireCacheView(image);
1666 despeckle_view=AcquireCacheView(despeckle_image);
cristy8df3d002011-02-19 19:40:59 +00001667 for (i=0; i < (ssize_t) number_channels; i++)
cristy3ed852e2009-09-05 21:47:34 +00001668 {
cristy3ed852e2009-09-05 21:47:34 +00001669 register Quantum
1670 *buffer,
1671 *pixel;
1672
cristyc1488b52011-02-19 18:54:15 +00001673 register ssize_t
cristya58c3172011-02-19 19:23:11 +00001674 k,
cristyc1488b52011-02-19 18:54:15 +00001675 x;
1676
cristy117ff172010-08-15 21:35:32 +00001677 ssize_t
1678 j,
1679 y;
1680
cristy3ed852e2009-09-05 21:47:34 +00001681 if (status == MagickFalse)
1682 continue;
cristy65b9f392011-02-22 14:22:54 +00001683 pixel=pixels;
cristy3ed852e2009-09-05 21:47:34 +00001684 (void) ResetMagickMemory(pixel,0,length*sizeof(*pixel));
cristy65b9f392011-02-22 14:22:54 +00001685 buffer=buffers;
cristybb503372010-05-27 20:51:26 +00001686 j=(ssize_t) image->columns+2;
1687 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001688 {
cristy4c08aed2011-07-01 19:47:50 +00001689 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001690 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001691
1692 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001693 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001694 break;
1695 j++;
cristybb503372010-05-27 20:51:26 +00001696 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001697 {
cristya58c3172011-02-19 19:23:11 +00001698 switch (i)
cristy3ed852e2009-09-05 21:47:34 +00001699 {
cristy4c08aed2011-07-01 19:47:50 +00001700 case 0: pixel[j]=GetPixelRed(image,p); break;
1701 case 1: pixel[j]=GetPixelGreen(image,p); break;
1702 case 2: pixel[j]=GetPixelBlue(image,p); break;
1703 case 3: pixel[j]=GetPixelAlpha(image,p); break;
1704 case 4: pixel[j]=GetPixelBlack(image,p); break;
cristy3ed852e2009-09-05 21:47:34 +00001705 default: break;
1706 }
cristyed231572011-07-14 02:18:59 +00001707 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001708 j++;
1709 }
1710 j++;
1711 }
cristy3ed852e2009-09-05 21:47:34 +00001712 (void) ResetMagickMemory(buffer,0,length*sizeof(*buffer));
cristya58c3172011-02-19 19:23:11 +00001713 for (k=0; k < 4; k++)
cristy3ed852e2009-09-05 21:47:34 +00001714 {
cristya58c3172011-02-19 19:23:11 +00001715 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,1);
1716 Hull(-X[k],-Y[k],image->columns,image->rows,pixel,buffer,1);
1717 Hull(-X[k],-Y[k],image->columns,image->rows,pixel,buffer,-1);
1718 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,-1);
cristy3ed852e2009-09-05 21:47:34 +00001719 }
cristybb503372010-05-27 20:51:26 +00001720 j=(ssize_t) image->columns+2;
1721 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001722 {
1723 MagickBooleanType
1724 sync;
1725
cristy4c08aed2011-07-01 19:47:50 +00001726 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001727 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001728
1729 q=GetCacheViewAuthenticPixels(despeckle_view,0,y,despeckle_image->columns,
1730 1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001731 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001732 break;
1733 j++;
cristybb503372010-05-27 20:51:26 +00001734 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001735 {
cristya58c3172011-02-19 19:23:11 +00001736 switch (i)
cristy3ed852e2009-09-05 21:47:34 +00001737 {
cristy4c08aed2011-07-01 19:47:50 +00001738 case 0: SetPixelRed(despeckle_image,pixel[j],q); break;
1739 case 1: SetPixelGreen(despeckle_image,pixel[j],q); break;
1740 case 2: SetPixelBlue(despeckle_image,pixel[j],q); break;
1741 case 3: SetPixelAlpha(despeckle_image,pixel[j],q); break;
1742 case 4: SetPixelBlack(despeckle_image,pixel[j],q); break;
cristy3ed852e2009-09-05 21:47:34 +00001743 default: break;
1744 }
cristyed231572011-07-14 02:18:59 +00001745 q+=GetPixelChannels(despeckle_image);
cristy3ed852e2009-09-05 21:47:34 +00001746 j++;
1747 }
1748 sync=SyncCacheViewAuthenticPixels(despeckle_view,exception);
1749 if (sync == MagickFalse)
1750 {
1751 status=MagickFalse;
1752 break;
1753 }
1754 j++;
1755 }
1756 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1757 {
1758 MagickBooleanType
1759 proceed;
1760
cristya58c3172011-02-19 19:23:11 +00001761 proceed=SetImageProgress(image,DespeckleImageTag,(MagickOffsetType) i,
1762 number_channels);
cristy3ed852e2009-09-05 21:47:34 +00001763 if (proceed == MagickFalse)
1764 status=MagickFalse;
1765 }
1766 }
1767 despeckle_view=DestroyCacheView(despeckle_view);
1768 image_view=DestroyCacheView(image_view);
cristy65b9f392011-02-22 14:22:54 +00001769 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1770 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001771 despeckle_image->type=image->type;
1772 if (status == MagickFalse)
1773 despeckle_image=DestroyImage(despeckle_image);
1774 return(despeckle_image);
1775}
1776
1777/*
1778%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1779% %
1780% %
1781% %
1782% E d g e I m a g e %
1783% %
1784% %
1785% %
1786%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1787%
1788% EdgeImage() finds edges in an image. Radius defines the radius of the
1789% convolution filter. Use a radius of 0 and EdgeImage() selects a suitable
1790% radius for you.
1791%
1792% The format of the EdgeImage method is:
1793%
1794% Image *EdgeImage(const Image *image,const double radius,
1795% ExceptionInfo *exception)
1796%
1797% A description of each parameter follows:
1798%
1799% o image: the image.
1800%
1801% o radius: the radius of the pixel neighborhood.
1802%
1803% o exception: return any errors or warnings in this structure.
1804%
1805*/
1806MagickExport Image *EdgeImage(const Image *image,const double radius,
1807 ExceptionInfo *exception)
1808{
1809 Image
1810 *edge_image;
1811
1812 double
1813 *kernel;
1814
cristybb503372010-05-27 20:51:26 +00001815 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001816 i;
1817
cristybb503372010-05-27 20:51:26 +00001818 size_t
cristy3ed852e2009-09-05 21:47:34 +00001819 width;
1820
1821 assert(image != (const Image *) NULL);
1822 assert(image->signature == MagickSignature);
1823 if (image->debug != MagickFalse)
1824 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1825 assert(exception != (ExceptionInfo *) NULL);
1826 assert(exception->signature == MagickSignature);
1827 width=GetOptimalKernelWidth1D(radius,0.5);
1828 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
1829 if (kernel == (double *) NULL)
1830 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00001831 for (i=0; i < (ssize_t) (width*width); i++)
cristy3ed852e2009-09-05 21:47:34 +00001832 kernel[i]=(-1.0);
1833 kernel[i/2]=(double) (width*width-1.0);
1834 edge_image=ConvolveImage(image,width,kernel,exception);
1835 kernel=(double *) RelinquishMagickMemory(kernel);
1836 return(edge_image);
1837}
1838
1839/*
1840%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1841% %
1842% %
1843% %
1844% E m b o s s I m a g e %
1845% %
1846% %
1847% %
1848%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1849%
1850% EmbossImage() returns a grayscale image with a three-dimensional effect.
1851% We convolve the image with a Gaussian operator of the given radius and
1852% standard deviation (sigma). For reasonable results, radius should be
1853% larger than sigma. Use a radius of 0 and Emboss() selects a suitable
1854% radius for you.
1855%
1856% The format of the EmbossImage method is:
1857%
1858% Image *EmbossImage(const Image *image,const double radius,
1859% const double sigma,ExceptionInfo *exception)
1860%
1861% A description of each parameter follows:
1862%
1863% o image: the image.
1864%
1865% o radius: the radius of the pixel neighborhood.
1866%
1867% o sigma: the standard deviation of the Gaussian, in pixels.
1868%
1869% o exception: return any errors or warnings in this structure.
1870%
1871*/
1872MagickExport Image *EmbossImage(const Image *image,const double radius,
1873 const double sigma,ExceptionInfo *exception)
1874{
1875 double
1876 *kernel;
1877
1878 Image
1879 *emboss_image;
1880
cristybb503372010-05-27 20:51:26 +00001881 register ssize_t
cristy47e00502009-12-17 19:19:57 +00001882 i;
1883
cristybb503372010-05-27 20:51:26 +00001884 size_t
cristy3ed852e2009-09-05 21:47:34 +00001885 width;
1886
cristy117ff172010-08-15 21:35:32 +00001887 ssize_t
1888 j,
1889 k,
1890 u,
1891 v;
1892
cristy3ed852e2009-09-05 21:47:34 +00001893 assert(image != (Image *) NULL);
1894 assert(image->signature == MagickSignature);
1895 if (image->debug != MagickFalse)
1896 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1897 assert(exception != (ExceptionInfo *) NULL);
1898 assert(exception->signature == MagickSignature);
1899 width=GetOptimalKernelWidth2D(radius,sigma);
1900 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
1901 if (kernel == (double *) NULL)
1902 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00001903 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +00001904 k=j;
1905 i=0;
1906 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00001907 {
cristy47e00502009-12-17 19:19:57 +00001908 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00001909 {
cristy4205a3c2010-09-12 20:19:59 +00001910 kernel[i]=(double) (((u < 0) || (v < 0) ? -8.0 : 8.0)*
cristy47e00502009-12-17 19:19:57 +00001911 exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
cristy4205a3c2010-09-12 20:19:59 +00001912 (2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +00001913 if (u != k)
cristy3ed852e2009-09-05 21:47:34 +00001914 kernel[i]=0.0;
1915 i++;
1916 }
cristy47e00502009-12-17 19:19:57 +00001917 k--;
cristy3ed852e2009-09-05 21:47:34 +00001918 }
1919 emboss_image=ConvolveImage(image,width,kernel,exception);
1920 if (emboss_image != (Image *) NULL)
1921 (void) EqualizeImage(emboss_image);
1922 kernel=(double *) RelinquishMagickMemory(kernel);
1923 return(emboss_image);
1924}
1925
1926/*
1927%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1928% %
1929% %
1930% %
cristy56a9e512010-01-06 18:18:55 +00001931% F i l t e r I m a g e %
1932% %
1933% %
1934% %
1935%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1936%
1937% FilterImage() applies a custom convolution kernel to the image.
1938%
1939% The format of the FilterImage method is:
1940%
cristy2be15382010-01-21 02:38:03 +00001941% Image *FilterImage(const Image *image,const KernelInfo *kernel,
cristy56a9e512010-01-06 18:18:55 +00001942% ExceptionInfo *exception)
cristy56a9e512010-01-06 18:18:55 +00001943%
1944% A description of each parameter follows:
1945%
1946% o image: the image.
1947%
cristy56a9e512010-01-06 18:18:55 +00001948% o kernel: the filtering kernel.
1949%
1950% o exception: return any errors or warnings in this structure.
1951%
1952*/
cristyf4ad9df2011-07-08 16:49:03 +00001953MagickExport Image *FilterImage(const Image *image,
1954 const KernelInfo *kernel,ExceptionInfo *exception)
cristy56a9e512010-01-06 18:18:55 +00001955{
1956#define FilterImageTag "Filter/Image"
1957
1958 CacheView
1959 *filter_view,
1960 *image_view;
1961
cristy56a9e512010-01-06 18:18:55 +00001962 Image
1963 *filter_image;
1964
cristy56a9e512010-01-06 18:18:55 +00001965 MagickBooleanType
1966 status;
1967
cristybb503372010-05-27 20:51:26 +00001968 MagickOffsetType
1969 progress;
1970
cristy4c08aed2011-07-01 19:47:50 +00001971 PixelInfo
cristy56a9e512010-01-06 18:18:55 +00001972 bias;
1973
cristybb503372010-05-27 20:51:26 +00001974 ssize_t
1975 y;
1976
cristy56a9e512010-01-06 18:18:55 +00001977 /*
1978 Initialize filter image attributes.
1979 */
1980 assert(image != (Image *) NULL);
1981 assert(image->signature == MagickSignature);
1982 if (image->debug != MagickFalse)
1983 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1984 assert(exception != (ExceptionInfo *) NULL);
1985 assert(exception->signature == MagickSignature);
1986 if ((kernel->width % 2) == 0)
1987 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
1988 filter_image=CloneImage(image,0,0,MagickTrue,exception);
1989 if (filter_image == (Image *) NULL)
1990 return((Image *) NULL);
1991 if (SetImageStorageClass(filter_image,DirectClass) == MagickFalse)
1992 {
1993 InheritException(exception,&filter_image->exception);
1994 filter_image=DestroyImage(filter_image);
1995 return((Image *) NULL);
1996 }
1997 if (image->debug != MagickFalse)
1998 {
1999 char
2000 format[MaxTextExtent],
2001 *message;
2002
cristy117ff172010-08-15 21:35:32 +00002003 register const double
2004 *k;
2005
cristybb503372010-05-27 20:51:26 +00002006 ssize_t
cristy56a9e512010-01-06 18:18:55 +00002007 u,
2008 v;
2009
cristy56a9e512010-01-06 18:18:55 +00002010 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00002011 " FilterImage with %.20gx%.20g kernel:",(double) kernel->width,(double)
2012 kernel->height);
cristy56a9e512010-01-06 18:18:55 +00002013 message=AcquireString("");
2014 k=kernel->values;
cristybb503372010-05-27 20:51:26 +00002015 for (v=0; v < (ssize_t) kernel->height; v++)
cristy56a9e512010-01-06 18:18:55 +00002016 {
2017 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00002018 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy56a9e512010-01-06 18:18:55 +00002019 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00002020 for (u=0; u < (ssize_t) kernel->width; u++)
cristy56a9e512010-01-06 18:18:55 +00002021 {
cristyb51dff52011-05-19 16:55:47 +00002022 (void) FormatLocaleString(format,MaxTextExtent,"%g ",*k++);
cristy56a9e512010-01-06 18:18:55 +00002023 (void) ConcatenateString(&message,format);
2024 }
2025 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
2026 }
2027 message=DestroyString(message);
2028 }
cristy36826ab2010-03-06 01:29:30 +00002029 status=AccelerateConvolveImage(image,kernel,filter_image,exception);
cristyd43a46b2010-01-21 02:13:41 +00002030 if (status == MagickTrue)
2031 return(filter_image);
cristy56a9e512010-01-06 18:18:55 +00002032 /*
2033 Filter image.
2034 */
2035 status=MagickTrue;
2036 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00002037 GetPixelInfo(image,&bias);
2038 SetPixelInfoBias(image,&bias);
cristy56a9e512010-01-06 18:18:55 +00002039 image_view=AcquireCacheView(image);
2040 filter_view=AcquireCacheView(filter_image);
2041#if defined(MAGICKCORE_OPENMP_SUPPORT)
2042 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2043#endif
cristybb503372010-05-27 20:51:26 +00002044 for (y=0; y < (ssize_t) image->rows; y++)
cristy56a9e512010-01-06 18:18:55 +00002045 {
2046 MagickBooleanType
2047 sync;
2048
cristy4c08aed2011-07-01 19:47:50 +00002049 register const Quantum
cristy56a9e512010-01-06 18:18:55 +00002050 *restrict p;
2051
cristy4c08aed2011-07-01 19:47:50 +00002052 register Quantum
cristy56a9e512010-01-06 18:18:55 +00002053 *restrict q;
2054
cristy117ff172010-08-15 21:35:32 +00002055 register ssize_t
2056 x;
2057
cristy56a9e512010-01-06 18:18:55 +00002058 if (status == MagickFalse)
2059 continue;
cristybb503372010-05-27 20:51:26 +00002060 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) kernel->width/2L),
cristy117ff172010-08-15 21:35:32 +00002061 y-(ssize_t) (kernel->height/2L),image->columns+kernel->width,
2062 kernel->height,exception);
cristy56a9e512010-01-06 18:18:55 +00002063 q=GetCacheViewAuthenticPixels(filter_view,0,y,filter_image->columns,1,
2064 exception);
cristy4c08aed2011-07-01 19:47:50 +00002065 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy56a9e512010-01-06 18:18:55 +00002066 {
2067 status=MagickFalse;
2068 continue;
2069 }
cristybb503372010-05-27 20:51:26 +00002070 for (x=0; x < (ssize_t) image->columns; x++)
cristy56a9e512010-01-06 18:18:55 +00002071 {
cristy4c08aed2011-07-01 19:47:50 +00002072 PixelInfo
cristy56a9e512010-01-06 18:18:55 +00002073 pixel;
2074
2075 register const double
2076 *restrict k;
2077
cristy4c08aed2011-07-01 19:47:50 +00002078 register const Quantum
cristy56a9e512010-01-06 18:18:55 +00002079 *restrict kernel_pixels;
2080
cristybb503372010-05-27 20:51:26 +00002081 register ssize_t
cristy56a9e512010-01-06 18:18:55 +00002082 u;
2083
cristy117ff172010-08-15 21:35:32 +00002084 ssize_t
2085 v;
2086
cristy56a9e512010-01-06 18:18:55 +00002087 pixel=bias;
cristy36826ab2010-03-06 01:29:30 +00002088 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002089 kernel_pixels=p;
cristyed231572011-07-14 02:18:59 +00002090 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) ||
cristy5ce8df82011-07-07 14:52:23 +00002091 (image->matte == MagickFalse))
cristy56a9e512010-01-06 18:18:55 +00002092 {
cristybb503372010-05-27 20:51:26 +00002093 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002094 {
cristybb503372010-05-27 20:51:26 +00002095 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002096 {
cristy4c08aed2011-07-01 19:47:50 +00002097 pixel.red+=(*k)*GetPixelRed(image,kernel_pixels+u*
cristyed231572011-07-14 02:18:59 +00002098 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00002099 pixel.green+=(*k)*GetPixelGreen(image,kernel_pixels+u*
cristyed231572011-07-14 02:18:59 +00002100 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00002101 pixel.blue+=(*k)*GetPixelBlue(image,kernel_pixels+u*
cristyed231572011-07-14 02:18:59 +00002102 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00002103 if (image->colorspace == CMYKColorspace)
2104 pixel.black+=(*k)*GetPixelBlack(image,kernel_pixels+u*
cristyed231572011-07-14 02:18:59 +00002105 GetPixelChannels(image));
cristy56a9e512010-01-06 18:18:55 +00002106 k++;
2107 }
cristy4c08aed2011-07-01 19:47:50 +00002108 kernel_pixels+=(image->columns+kernel->width)*
cristyed231572011-07-14 02:18:59 +00002109 GetPixelChannels(image);
cristy56a9e512010-01-06 18:18:55 +00002110 }
cristyed231572011-07-14 02:18:59 +00002111 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002112 SetPixelRed(filter_image,ClampToQuantum(pixel.red),q);
cristyed231572011-07-14 02:18:59 +00002113 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002114 SetPixelGreen(filter_image,ClampToQuantum(pixel.green),q);
cristyed231572011-07-14 02:18:59 +00002115 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002116 SetPixelBlue(filter_image,ClampToQuantum(pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00002117 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002118 (image->colorspace == CMYKColorspace))
2119 SetPixelBlack(filter_image,ClampToQuantum(pixel.black),q);
cristyed231572011-07-14 02:18:59 +00002120 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy56a9e512010-01-06 18:18:55 +00002121 {
cristy36826ab2010-03-06 01:29:30 +00002122 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002123 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00002124 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002125 {
cristybb503372010-05-27 20:51:26 +00002126 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002127 {
cristy4c08aed2011-07-01 19:47:50 +00002128 pixel.alpha+=(*k)*GetPixelRed(image,kernel_pixels+u*
cristyed231572011-07-14 02:18:59 +00002129 GetPixelChannels(image));
cristy56a9e512010-01-06 18:18:55 +00002130 k++;
2131 }
cristy4c08aed2011-07-01 19:47:50 +00002132 kernel_pixels+=(image->columns+kernel->width)*
cristyed231572011-07-14 02:18:59 +00002133 GetPixelChannels(image);
cristy56a9e512010-01-06 18:18:55 +00002134 }
cristy4c08aed2011-07-01 19:47:50 +00002135 SetPixelAlpha(filter_image,ClampToQuantum(pixel.alpha),q);
cristy56a9e512010-01-06 18:18:55 +00002136 }
2137 }
2138 else
2139 {
2140 MagickRealType
2141 alpha,
2142 gamma;
2143
2144 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00002145 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002146 {
cristybb503372010-05-27 20:51:26 +00002147 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002148 {
cristy4c08aed2011-07-01 19:47:50 +00002149 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,
cristyed231572011-07-14 02:18:59 +00002150 kernel_pixels+u*GetPixelChannels(image)));
cristy4c08aed2011-07-01 19:47:50 +00002151 pixel.red+=(*k)*alpha*GetPixelRed(image,kernel_pixels+u*
cristyed231572011-07-14 02:18:59 +00002152 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00002153 pixel.green+=(*k)*alpha*GetPixelGreen(image,kernel_pixels+u*
cristyed231572011-07-14 02:18:59 +00002154 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00002155 pixel.blue+=(*k)*alpha*GetPixelBlue(image,kernel_pixels+u*
cristyed231572011-07-14 02:18:59 +00002156 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00002157 if (image->colorspace == CMYKColorspace)
2158 pixel.black+=(*k)*alpha*GetPixelBlack(image,kernel_pixels+u*
cristyed231572011-07-14 02:18:59 +00002159 GetPixelChannels(image));
cristy56a9e512010-01-06 18:18:55 +00002160 gamma+=(*k)*alpha;
2161 k++;
2162 }
cristy5ce8df82011-07-07 14:52:23 +00002163 kernel_pixels+=(image->columns+kernel->width)*
cristyed231572011-07-14 02:18:59 +00002164 GetPixelChannels(image);
cristy56a9e512010-01-06 18:18:55 +00002165 }
2166 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +00002167 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002168 SetPixelRed(filter_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +00002169 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002170 SetPixelGreen(filter_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +00002171 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002172 SetPixelBlue(filter_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00002173 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy56a9e512010-01-06 18:18:55 +00002174 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002175 SetPixelBlack(filter_image,ClampToQuantum(gamma*pixel.black),q);
cristyed231572011-07-14 02:18:59 +00002176 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy56a9e512010-01-06 18:18:55 +00002177 {
cristy36826ab2010-03-06 01:29:30 +00002178 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002179 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00002180 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002181 {
cristybb503372010-05-27 20:51:26 +00002182 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002183 {
cristy4c08aed2011-07-01 19:47:50 +00002184 pixel.alpha+=(*k)*GetPixelAlpha(image,kernel_pixels+u*
cristyed231572011-07-14 02:18:59 +00002185 GetPixelChannels(image));
cristy56a9e512010-01-06 18:18:55 +00002186 k++;
2187 }
cristy4c08aed2011-07-01 19:47:50 +00002188 kernel_pixels+=(image->columns+kernel->width)*
cristyed231572011-07-14 02:18:59 +00002189 GetPixelChannels(image);
cristy56a9e512010-01-06 18:18:55 +00002190 }
cristy4c08aed2011-07-01 19:47:50 +00002191 SetPixelAlpha(filter_image,ClampToQuantum(pixel.alpha),q);
cristy56a9e512010-01-06 18:18:55 +00002192 }
2193 }
cristyed231572011-07-14 02:18:59 +00002194 p+=GetPixelChannels(image);
2195 q+=GetPixelChannels(filter_image);
cristy56a9e512010-01-06 18:18:55 +00002196 }
2197 sync=SyncCacheViewAuthenticPixels(filter_view,exception);
2198 if (sync == MagickFalse)
2199 status=MagickFalse;
2200 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2201 {
2202 MagickBooleanType
2203 proceed;
2204
2205#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00002206 #pragma omp critical (MagickCore_FilterImage)
cristy56a9e512010-01-06 18:18:55 +00002207#endif
2208 proceed=SetImageProgress(image,FilterImageTag,progress++,image->rows);
2209 if (proceed == MagickFalse)
2210 status=MagickFalse;
2211 }
2212 }
2213 filter_image->type=image->type;
2214 filter_view=DestroyCacheView(filter_view);
2215 image_view=DestroyCacheView(image_view);
cristy56a9e512010-01-06 18:18:55 +00002216 if (status == MagickFalse)
2217 filter_image=DestroyImage(filter_image);
2218 return(filter_image);
2219}
2220
2221/*
2222%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2223% %
2224% %
2225% %
cristy3ed852e2009-09-05 21:47:34 +00002226% G a u s s i a n B l u r I m a g e %
2227% %
2228% %
2229% %
2230%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2231%
2232% GaussianBlurImage() blurs an image. We convolve the image with a
2233% Gaussian operator of the given radius and standard deviation (sigma).
2234% For reasonable results, the radius should be larger than sigma. Use a
2235% radius of 0 and GaussianBlurImage() selects a suitable radius for you
2236%
2237% The format of the GaussianBlurImage method is:
2238%
2239% Image *GaussianBlurImage(const Image *image,onst double radius,
2240% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002241%
2242% A description of each parameter follows:
2243%
2244% o image: the image.
2245%
cristy3ed852e2009-09-05 21:47:34 +00002246% o radius: the radius of the Gaussian, in pixels, not counting the center
2247% pixel.
2248%
2249% o sigma: the standard deviation of the Gaussian, in pixels.
2250%
2251% o exception: return any errors or warnings in this structure.
2252%
2253*/
cristyf4ad9df2011-07-08 16:49:03 +00002254MagickExport Image *GaussianBlurImage(const Image *image,
2255 const double radius,const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002256{
2257 double
2258 *kernel;
2259
2260 Image
2261 *blur_image;
2262
cristybb503372010-05-27 20:51:26 +00002263 register ssize_t
cristy47e00502009-12-17 19:19:57 +00002264 i;
2265
cristybb503372010-05-27 20:51:26 +00002266 size_t
cristy3ed852e2009-09-05 21:47:34 +00002267 width;
2268
cristy117ff172010-08-15 21:35:32 +00002269 ssize_t
2270 j,
2271 u,
2272 v;
2273
cristy3ed852e2009-09-05 21:47:34 +00002274 assert(image != (const Image *) NULL);
2275 assert(image->signature == MagickSignature);
2276 if (image->debug != MagickFalse)
2277 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2278 assert(exception != (ExceptionInfo *) NULL);
2279 assert(exception->signature == MagickSignature);
2280 width=GetOptimalKernelWidth2D(radius,sigma);
2281 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2282 if (kernel == (double *) NULL)
2283 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00002284 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00002285 i=0;
cristy47e00502009-12-17 19:19:57 +00002286 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002287 {
cristy47e00502009-12-17 19:19:57 +00002288 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00002289 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
2290 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00002291 }
cristyf4ad9df2011-07-08 16:49:03 +00002292 blur_image=ConvolveImage(image,width,kernel,exception);
cristy3ed852e2009-09-05 21:47:34 +00002293 kernel=(double *) RelinquishMagickMemory(kernel);
2294 return(blur_image);
2295}
2296
2297/*
2298%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2299% %
2300% %
2301% %
cristy3ed852e2009-09-05 21:47:34 +00002302% M o t i o n B l u r I m a g e %
2303% %
2304% %
2305% %
2306%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2307%
2308% MotionBlurImage() simulates motion blur. We convolve the image with a
2309% Gaussian operator of the given radius and standard deviation (sigma).
2310% For reasonable results, radius should be larger than sigma. Use a
2311% radius of 0 and MotionBlurImage() selects a suitable radius for you.
2312% Angle gives the angle of the blurring motion.
2313%
2314% Andrew Protano contributed this effect.
2315%
2316% The format of the MotionBlurImage method is:
2317%
2318% Image *MotionBlurImage(const Image *image,const double radius,
2319% const double sigma,const double angle,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002320%
2321% A description of each parameter follows:
2322%
2323% o image: the image.
2324%
cristy3ed852e2009-09-05 21:47:34 +00002325% o radius: the radius of the Gaussian, in pixels, not counting
2326% the center pixel.
2327%
2328% o sigma: the standard deviation of the Gaussian, in pixels.
2329%
cristycee97112010-05-28 00:44:52 +00002330% o angle: Apply the effect along this angle.
cristy3ed852e2009-09-05 21:47:34 +00002331%
2332% o exception: return any errors or warnings in this structure.
2333%
2334*/
2335
cristybb503372010-05-27 20:51:26 +00002336static double *GetMotionBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +00002337{
cristy3ed852e2009-09-05 21:47:34 +00002338 double
cristy47e00502009-12-17 19:19:57 +00002339 *kernel,
cristy3ed852e2009-09-05 21:47:34 +00002340 normalize;
2341
cristybb503372010-05-27 20:51:26 +00002342 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002343 i;
2344
2345 /*
cristy47e00502009-12-17 19:19:57 +00002346 Generate a 1-D convolution kernel.
cristy3ed852e2009-09-05 21:47:34 +00002347 */
2348 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
2349 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
2350 if (kernel == (double *) NULL)
2351 return(kernel);
cristy3ed852e2009-09-05 21:47:34 +00002352 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00002353 for (i=0; i < (ssize_t) width; i++)
cristy47e00502009-12-17 19:19:57 +00002354 {
cristy4205a3c2010-09-12 20:19:59 +00002355 kernel[i]=(double) (exp((-((double) i*i)/(double) (2.0*MagickSigma*
2356 MagickSigma)))/(MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00002357 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +00002358 }
cristybb503372010-05-27 20:51:26 +00002359 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002360 kernel[i]/=normalize;
2361 return(kernel);
2362}
2363
cristyf4ad9df2011-07-08 16:49:03 +00002364MagickExport Image *MotionBlurImage(const Image *image,
2365 const double radius,const double sigma,const double angle,
2366 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002367{
cristyc4c8d132010-01-07 01:58:38 +00002368 CacheView
2369 *blur_view,
2370 *image_view;
2371
cristy3ed852e2009-09-05 21:47:34 +00002372 double
2373 *kernel;
2374
2375 Image
2376 *blur_image;
2377
cristy3ed852e2009-09-05 21:47:34 +00002378 MagickBooleanType
2379 status;
2380
cristybb503372010-05-27 20:51:26 +00002381 MagickOffsetType
2382 progress;
2383
cristy4c08aed2011-07-01 19:47:50 +00002384 PixelInfo
cristyddd82202009-11-03 20:14:50 +00002385 bias;
cristy3ed852e2009-09-05 21:47:34 +00002386
2387 OffsetInfo
2388 *offset;
2389
2390 PointInfo
2391 point;
2392
cristybb503372010-05-27 20:51:26 +00002393 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002394 i;
2395
cristybb503372010-05-27 20:51:26 +00002396 size_t
cristy3ed852e2009-09-05 21:47:34 +00002397 width;
2398
cristybb503372010-05-27 20:51:26 +00002399 ssize_t
2400 y;
2401
cristy3ed852e2009-09-05 21:47:34 +00002402 assert(image != (Image *) NULL);
2403 assert(image->signature == MagickSignature);
2404 if (image->debug != MagickFalse)
2405 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2406 assert(exception != (ExceptionInfo *) NULL);
2407 width=GetOptimalKernelWidth1D(radius,sigma);
2408 kernel=GetMotionBlurKernel(width,sigma);
2409 if (kernel == (double *) NULL)
2410 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2411 offset=(OffsetInfo *) AcquireQuantumMemory(width,sizeof(*offset));
2412 if (offset == (OffsetInfo *) NULL)
2413 {
2414 kernel=(double *) RelinquishMagickMemory(kernel);
2415 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2416 }
2417 blur_image=CloneImage(image,0,0,MagickTrue,exception);
2418 if (blur_image == (Image *) NULL)
2419 {
2420 kernel=(double *) RelinquishMagickMemory(kernel);
2421 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2422 return((Image *) NULL);
2423 }
2424 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
2425 {
2426 kernel=(double *) RelinquishMagickMemory(kernel);
2427 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2428 InheritException(exception,&blur_image->exception);
2429 blur_image=DestroyImage(blur_image);
2430 return((Image *) NULL);
2431 }
2432 point.x=(double) width*sin(DegreesToRadians(angle));
2433 point.y=(double) width*cos(DegreesToRadians(angle));
cristybb503372010-05-27 20:51:26 +00002434 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002435 {
cristybb503372010-05-27 20:51:26 +00002436 offset[i].x=(ssize_t) ceil((double) (i*point.y)/hypot(point.x,point.y)-0.5);
2437 offset[i].y=(ssize_t) ceil((double) (i*point.x)/hypot(point.x,point.y)-0.5);
cristy3ed852e2009-09-05 21:47:34 +00002438 }
2439 /*
2440 Motion blur image.
2441 */
2442 status=MagickTrue;
2443 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00002444 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00002445 image_view=AcquireCacheView(image);
2446 blur_view=AcquireCacheView(blur_image);
cristyb557a152011-02-22 12:14:30 +00002447#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00002448 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00002449#endif
cristybb503372010-05-27 20:51:26 +00002450 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002451 {
cristy4c08aed2011-07-01 19:47:50 +00002452 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002453 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002454
cristy117ff172010-08-15 21:35:32 +00002455 register ssize_t
2456 x;
2457
cristy3ed852e2009-09-05 21:47:34 +00002458 if (status == MagickFalse)
2459 continue;
2460 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
2461 exception);
cristy4c08aed2011-07-01 19:47:50 +00002462 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002463 {
2464 status=MagickFalse;
2465 continue;
2466 }
cristybb503372010-05-27 20:51:26 +00002467 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002468 {
cristy4c08aed2011-07-01 19:47:50 +00002469 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002470 qixel;
2471
2472 PixelPacket
2473 pixel;
2474
2475 register double
cristyc47d1f82009-11-26 01:44:43 +00002476 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00002477
cristybb503372010-05-27 20:51:26 +00002478 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002479 i;
2480
cristy3ed852e2009-09-05 21:47:34 +00002481 k=kernel;
cristyddd82202009-11-03 20:14:50 +00002482 qixel=bias;
cristyed231572011-07-14 02:18:59 +00002483 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00002484 {
cristybb503372010-05-27 20:51:26 +00002485 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002486 {
2487 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
2488 offset[i].y,&pixel,exception);
2489 qixel.red+=(*k)*pixel.red;
2490 qixel.green+=(*k)*pixel.green;
2491 qixel.blue+=(*k)*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00002492 qixel.alpha+=(*k)*pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00002493 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00002494 qixel.black+=(*k)*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00002495 k++;
2496 }
cristyed231572011-07-14 02:18:59 +00002497 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002498 SetPixelRed(blur_image,
2499 ClampToQuantum(qixel.red),q);
cristyed231572011-07-14 02:18:59 +00002500 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002501 SetPixelGreen(blur_image,
2502 ClampToQuantum(qixel.green),q);
cristyed231572011-07-14 02:18:59 +00002503 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002504 SetPixelBlue(blur_image,
2505 ClampToQuantum(qixel.blue),q);
cristyed231572011-07-14 02:18:59 +00002506 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002507 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002508 SetPixelBlack(blur_image,
2509 ClampToQuantum(qixel.black),q);
cristyed231572011-07-14 02:18:59 +00002510 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002511 SetPixelAlpha(blur_image,
2512 ClampToQuantum(qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00002513 }
2514 else
2515 {
2516 MagickRealType
2517 alpha,
2518 gamma;
2519
2520 alpha=0.0;
2521 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00002522 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002523 {
2524 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
2525 offset[i].y,&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00002526 alpha=(MagickRealType) (QuantumScale*pixel.alpha);
cristy3ed852e2009-09-05 21:47:34 +00002527 qixel.red+=(*k)*alpha*pixel.red;
2528 qixel.green+=(*k)*alpha*pixel.green;
2529 qixel.blue+=(*k)*alpha*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00002530 qixel.alpha+=(*k)*pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00002531 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00002532 qixel.black+=(*k)*alpha*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00002533 gamma+=(*k)*alpha;
2534 k++;
2535 }
2536 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +00002537 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002538 SetPixelRed(blur_image,
2539 ClampToQuantum(gamma*qixel.red),q);
cristyed231572011-07-14 02:18:59 +00002540 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002541 SetPixelGreen(blur_image,
2542 ClampToQuantum(gamma*qixel.green),q);
cristyed231572011-07-14 02:18:59 +00002543 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002544 SetPixelBlue(blur_image,
2545 ClampToQuantum(gamma*qixel.blue),q);
cristyed231572011-07-14 02:18:59 +00002546 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002547 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002548 SetPixelBlack(blur_image,
2549 ClampToQuantum(gamma*qixel.black),q);
cristyed231572011-07-14 02:18:59 +00002550 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002551 SetPixelAlpha(blur_image,
2552 ClampToQuantum(qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00002553 }
cristyed231572011-07-14 02:18:59 +00002554 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00002555 }
2556 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
2557 status=MagickFalse;
2558 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2559 {
2560 MagickBooleanType
2561 proceed;
2562
cristyb557a152011-02-22 12:14:30 +00002563#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00002564 #pragma omp critical (MagickCore_MotionBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00002565#endif
2566 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
2567 if (proceed == MagickFalse)
2568 status=MagickFalse;
2569 }
2570 }
2571 blur_view=DestroyCacheView(blur_view);
2572 image_view=DestroyCacheView(image_view);
2573 kernel=(double *) RelinquishMagickMemory(kernel);
2574 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2575 if (status == MagickFalse)
2576 blur_image=DestroyImage(blur_image);
2577 return(blur_image);
2578}
2579
2580/*
2581%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2582% %
2583% %
2584% %
2585% P r e v i e w I m a g e %
2586% %
2587% %
2588% %
2589%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2590%
2591% PreviewImage() tiles 9 thumbnails of the specified image with an image
2592% processing operation applied with varying parameters. This may be helpful
2593% pin-pointing an appropriate parameter for a particular image processing
2594% operation.
2595%
2596% The format of the PreviewImages method is:
2597%
2598% Image *PreviewImages(const Image *image,const PreviewType preview,
2599% ExceptionInfo *exception)
2600%
2601% A description of each parameter follows:
2602%
2603% o image: the image.
2604%
2605% o preview: the image processing operation.
2606%
2607% o exception: return any errors or warnings in this structure.
2608%
2609*/
2610MagickExport Image *PreviewImage(const Image *image,const PreviewType preview,
2611 ExceptionInfo *exception)
2612{
2613#define NumberTiles 9
2614#define PreviewImageTag "Preview/Image"
2615#define DefaultPreviewGeometry "204x204+10+10"
2616
2617 char
2618 factor[MaxTextExtent],
2619 label[MaxTextExtent];
2620
2621 double
2622 degrees,
2623 gamma,
2624 percentage,
2625 radius,
2626 sigma,
2627 threshold;
2628
2629 Image
2630 *images,
2631 *montage_image,
2632 *preview_image,
2633 *thumbnail;
2634
2635 ImageInfo
2636 *preview_info;
2637
cristy3ed852e2009-09-05 21:47:34 +00002638 MagickBooleanType
2639 proceed;
2640
2641 MontageInfo
2642 *montage_info;
2643
2644 QuantizeInfo
2645 quantize_info;
2646
2647 RectangleInfo
2648 geometry;
2649
cristybb503372010-05-27 20:51:26 +00002650 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002651 i,
2652 x;
2653
cristybb503372010-05-27 20:51:26 +00002654 size_t
cristy3ed852e2009-09-05 21:47:34 +00002655 colors;
2656
cristy117ff172010-08-15 21:35:32 +00002657 ssize_t
2658 y;
2659
cristy3ed852e2009-09-05 21:47:34 +00002660 /*
2661 Open output image file.
2662 */
2663 assert(image != (Image *) NULL);
2664 assert(image->signature == MagickSignature);
2665 if (image->debug != MagickFalse)
2666 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2667 colors=2;
2668 degrees=0.0;
2669 gamma=(-0.2f);
2670 preview_info=AcquireImageInfo();
2671 SetGeometry(image,&geometry);
2672 (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y,
2673 &geometry.width,&geometry.height);
2674 images=NewImageList();
2675 percentage=12.5;
2676 GetQuantizeInfo(&quantize_info);
2677 radius=0.0;
2678 sigma=1.0;
2679 threshold=0.0;
2680 x=0;
2681 y=0;
2682 for (i=0; i < NumberTiles; i++)
2683 {
2684 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
2685 if (thumbnail == (Image *) NULL)
2686 break;
2687 (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL,
2688 (void *) NULL);
2689 (void) SetImageProperty(thumbnail,"label",DefaultTileLabel);
2690 if (i == (NumberTiles/2))
2691 {
2692 (void) QueryColorDatabase("#dfdfdf",&thumbnail->matte_color,exception);
2693 AppendImageToList(&images,thumbnail);
2694 continue;
2695 }
2696 switch (preview)
2697 {
2698 case RotatePreview:
2699 {
2700 degrees+=45.0;
2701 preview_image=RotateImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002702 (void) FormatLocaleString(label,MaxTextExtent,"rotate %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002703 break;
2704 }
2705 case ShearPreview:
2706 {
2707 degrees+=5.0;
2708 preview_image=ShearImage(thumbnail,degrees,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002709 (void) FormatLocaleString(label,MaxTextExtent,"shear %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002710 degrees,2.0*degrees);
2711 break;
2712 }
2713 case RollPreview:
2714 {
cristybb503372010-05-27 20:51:26 +00002715 x=(ssize_t) ((i+1)*thumbnail->columns)/NumberTiles;
2716 y=(ssize_t) ((i+1)*thumbnail->rows)/NumberTiles;
cristy3ed852e2009-09-05 21:47:34 +00002717 preview_image=RollImage(thumbnail,x,y,exception);
cristyb51dff52011-05-19 16:55:47 +00002718 (void) FormatLocaleString(label,MaxTextExtent,"roll %+.20gx%+.20g",
cristye8c25f92010-06-03 00:53:06 +00002719 (double) x,(double) y);
cristy3ed852e2009-09-05 21:47:34 +00002720 break;
2721 }
2722 case HuePreview:
2723 {
2724 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2725 if (preview_image == (Image *) NULL)
2726 break;
cristyb51dff52011-05-19 16:55:47 +00002727 (void) FormatLocaleString(factor,MaxTextExtent,"100,100,%g",
cristy3ed852e2009-09-05 21:47:34 +00002728 2.0*percentage);
2729 (void) ModulateImage(preview_image,factor);
cristyb51dff52011-05-19 16:55:47 +00002730 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002731 break;
2732 }
2733 case SaturationPreview:
2734 {
2735 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2736 if (preview_image == (Image *) NULL)
2737 break;
cristyb51dff52011-05-19 16:55:47 +00002738 (void) FormatLocaleString(factor,MaxTextExtent,"100,%g",
cristy8cd5b312010-01-07 01:10:24 +00002739 2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00002740 (void) ModulateImage(preview_image,factor);
cristyb51dff52011-05-19 16:55:47 +00002741 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002742 break;
2743 }
2744 case BrightnessPreview:
2745 {
2746 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2747 if (preview_image == (Image *) NULL)
2748 break;
cristyb51dff52011-05-19 16:55:47 +00002749 (void) FormatLocaleString(factor,MaxTextExtent,"%g",2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00002750 (void) ModulateImage(preview_image,factor);
cristyb51dff52011-05-19 16:55:47 +00002751 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002752 break;
2753 }
2754 case GammaPreview:
2755 default:
2756 {
2757 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2758 if (preview_image == (Image *) NULL)
2759 break;
2760 gamma+=0.4f;
cristy50fbc382011-07-07 02:19:17 +00002761 (void) GammaImage(preview_image,gamma);
cristyb51dff52011-05-19 16:55:47 +00002762 (void) FormatLocaleString(label,MaxTextExtent,"gamma %g",gamma);
cristy3ed852e2009-09-05 21:47:34 +00002763 break;
2764 }
2765 case SpiffPreview:
2766 {
2767 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2768 if (preview_image != (Image *) NULL)
2769 for (x=0; x < i; x++)
2770 (void) ContrastImage(preview_image,MagickTrue);
cristyb51dff52011-05-19 16:55:47 +00002771 (void) FormatLocaleString(label,MaxTextExtent,"contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002772 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002773 break;
2774 }
2775 case DullPreview:
2776 {
2777 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2778 if (preview_image == (Image *) NULL)
2779 break;
2780 for (x=0; x < i; x++)
2781 (void) ContrastImage(preview_image,MagickFalse);
cristyb51dff52011-05-19 16:55:47 +00002782 (void) FormatLocaleString(label,MaxTextExtent,"+contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002783 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002784 break;
2785 }
2786 case GrayscalePreview:
2787 {
2788 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2789 if (preview_image == (Image *) NULL)
2790 break;
2791 colors<<=1;
2792 quantize_info.number_colors=colors;
2793 quantize_info.colorspace=GRAYColorspace;
2794 (void) QuantizeImage(&quantize_info,preview_image);
cristyb51dff52011-05-19 16:55:47 +00002795 (void) FormatLocaleString(label,MaxTextExtent,
cristye8c25f92010-06-03 00:53:06 +00002796 "-colorspace gray -colors %.20g",(double) colors);
cristy3ed852e2009-09-05 21:47:34 +00002797 break;
2798 }
2799 case QuantizePreview:
2800 {
2801 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2802 if (preview_image == (Image *) NULL)
2803 break;
2804 colors<<=1;
2805 quantize_info.number_colors=colors;
2806 (void) QuantizeImage(&quantize_info,preview_image);
cristyb51dff52011-05-19 16:55:47 +00002807 (void) FormatLocaleString(label,MaxTextExtent,"colors %.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00002808 colors);
cristy3ed852e2009-09-05 21:47:34 +00002809 break;
2810 }
2811 case DespecklePreview:
2812 {
2813 for (x=0; x < (i-1); x++)
2814 {
2815 preview_image=DespeckleImage(thumbnail,exception);
2816 if (preview_image == (Image *) NULL)
2817 break;
2818 thumbnail=DestroyImage(thumbnail);
2819 thumbnail=preview_image;
2820 }
2821 preview_image=DespeckleImage(thumbnail,exception);
2822 if (preview_image == (Image *) NULL)
2823 break;
cristyb51dff52011-05-19 16:55:47 +00002824 (void) FormatLocaleString(label,MaxTextExtent,"despeckle (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002825 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002826 break;
2827 }
2828 case ReduceNoisePreview:
2829 {
cristy95c38342011-03-18 22:39:51 +00002830 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) radius,
2831 (size_t) radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002832 (void) FormatLocaleString(label,MaxTextExtent,"noise %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002833 break;
2834 }
2835 case AddNoisePreview:
2836 {
2837 switch ((int) i)
2838 {
2839 case 0:
2840 {
2841 (void) CopyMagickString(factor,"uniform",MaxTextExtent);
2842 break;
2843 }
2844 case 1:
2845 {
2846 (void) CopyMagickString(factor,"gaussian",MaxTextExtent);
2847 break;
2848 }
2849 case 2:
2850 {
2851 (void) CopyMagickString(factor,"multiplicative",MaxTextExtent);
2852 break;
2853 }
2854 case 3:
2855 {
2856 (void) CopyMagickString(factor,"impulse",MaxTextExtent);
2857 break;
2858 }
2859 case 4:
2860 {
2861 (void) CopyMagickString(factor,"laplacian",MaxTextExtent);
2862 break;
2863 }
2864 case 5:
2865 {
2866 (void) CopyMagickString(factor,"Poisson",MaxTextExtent);
2867 break;
2868 }
2869 default:
2870 {
2871 (void) CopyMagickString(thumbnail->magick,"NULL",MaxTextExtent);
2872 break;
2873 }
2874 }
cristyd76c51e2011-03-26 00:21:26 +00002875 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) i,
2876 (size_t) i,exception);
cristyb51dff52011-05-19 16:55:47 +00002877 (void) FormatLocaleString(label,MaxTextExtent,"+noise %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002878 break;
2879 }
2880 case SharpenPreview:
2881 {
2882 preview_image=SharpenImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00002883 (void) FormatLocaleString(label,MaxTextExtent,"sharpen %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002884 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002885 break;
2886 }
2887 case BlurPreview:
2888 {
2889 preview_image=BlurImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00002890 (void) FormatLocaleString(label,MaxTextExtent,"blur %gx%g",radius,
cristy3ed852e2009-09-05 21:47:34 +00002891 sigma);
2892 break;
2893 }
2894 case ThresholdPreview:
2895 {
2896 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2897 if (preview_image == (Image *) NULL)
2898 break;
2899 (void) BilevelImage(thumbnail,
2900 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
cristyb51dff52011-05-19 16:55:47 +00002901 (void) FormatLocaleString(label,MaxTextExtent,"threshold %g",
cristy3ed852e2009-09-05 21:47:34 +00002902 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
2903 break;
2904 }
2905 case EdgeDetectPreview:
2906 {
2907 preview_image=EdgeImage(thumbnail,radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002908 (void) FormatLocaleString(label,MaxTextExtent,"edge %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002909 break;
2910 }
2911 case SpreadPreview:
2912 {
2913 preview_image=SpreadImage(thumbnail,radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002914 (void) FormatLocaleString(label,MaxTextExtent,"spread %g",
cristy8cd5b312010-01-07 01:10:24 +00002915 radius+0.5);
cristy3ed852e2009-09-05 21:47:34 +00002916 break;
2917 }
2918 case SolarizePreview:
2919 {
2920 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2921 if (preview_image == (Image *) NULL)
2922 break;
2923 (void) SolarizeImage(preview_image,(double) QuantumRange*
2924 percentage/100.0);
cristyb51dff52011-05-19 16:55:47 +00002925 (void) FormatLocaleString(label,MaxTextExtent,"solarize %g",
cristy3ed852e2009-09-05 21:47:34 +00002926 (QuantumRange*percentage)/100.0);
2927 break;
2928 }
2929 case ShadePreview:
2930 {
2931 degrees+=10.0;
2932 preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees,
2933 exception);
cristyb51dff52011-05-19 16:55:47 +00002934 (void) FormatLocaleString(label,MaxTextExtent,"shade %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002935 degrees,degrees);
cristy3ed852e2009-09-05 21:47:34 +00002936 break;
2937 }
2938 case RaisePreview:
2939 {
2940 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2941 if (preview_image == (Image *) NULL)
2942 break;
cristybb503372010-05-27 20:51:26 +00002943 geometry.width=(size_t) (2*i+2);
2944 geometry.height=(size_t) (2*i+2);
cristy3ed852e2009-09-05 21:47:34 +00002945 geometry.x=i/2;
2946 geometry.y=i/2;
2947 (void) RaiseImage(preview_image,&geometry,MagickTrue);
cristyb51dff52011-05-19 16:55:47 +00002948 (void) FormatLocaleString(label,MaxTextExtent,
cristy6d8abba2010-06-03 01:10:47 +00002949 "raise %.20gx%.20g%+.20g%+.20g",(double) geometry.width,(double)
cristye8c25f92010-06-03 00:53:06 +00002950 geometry.height,(double) geometry.x,(double) geometry.y);
cristy3ed852e2009-09-05 21:47:34 +00002951 break;
2952 }
2953 case SegmentPreview:
2954 {
2955 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2956 if (preview_image == (Image *) NULL)
2957 break;
2958 threshold+=0.4f;
2959 (void) SegmentImage(preview_image,RGBColorspace,MagickFalse,threshold,
2960 threshold);
cristyb51dff52011-05-19 16:55:47 +00002961 (void) FormatLocaleString(label,MaxTextExtent,"segment %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002962 threshold,threshold);
2963 break;
2964 }
2965 case SwirlPreview:
2966 {
2967 preview_image=SwirlImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002968 (void) FormatLocaleString(label,MaxTextExtent,"swirl %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002969 degrees+=45.0;
2970 break;
2971 }
2972 case ImplodePreview:
2973 {
2974 degrees+=0.1f;
2975 preview_image=ImplodeImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002976 (void) FormatLocaleString(label,MaxTextExtent,"implode %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002977 break;
2978 }
2979 case WavePreview:
2980 {
2981 degrees+=5.0f;
2982 preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002983 (void) FormatLocaleString(label,MaxTextExtent,"wave %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002984 0.5*degrees,2.0*degrees);
cristy3ed852e2009-09-05 21:47:34 +00002985 break;
2986 }
2987 case OilPaintPreview:
2988 {
2989 preview_image=OilPaintImage(thumbnail,(double) radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002990 (void) FormatLocaleString(label,MaxTextExtent,"paint %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002991 break;
2992 }
2993 case CharcoalDrawingPreview:
2994 {
2995 preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma,
2996 exception);
cristyb51dff52011-05-19 16:55:47 +00002997 (void) FormatLocaleString(label,MaxTextExtent,"charcoal %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002998 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002999 break;
3000 }
3001 case JPEGPreview:
3002 {
3003 char
3004 filename[MaxTextExtent];
3005
3006 int
3007 file;
3008
3009 MagickBooleanType
3010 status;
3011
3012 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3013 if (preview_image == (Image *) NULL)
3014 break;
cristybb503372010-05-27 20:51:26 +00003015 preview_info->quality=(size_t) percentage;
cristyb51dff52011-05-19 16:55:47 +00003016 (void) FormatLocaleString(factor,MaxTextExtent,"%.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00003017 preview_info->quality);
cristy3ed852e2009-09-05 21:47:34 +00003018 file=AcquireUniqueFileResource(filename);
3019 if (file != -1)
3020 file=close(file)-1;
cristyb51dff52011-05-19 16:55:47 +00003021 (void) FormatLocaleString(preview_image->filename,MaxTextExtent,
cristy3ed852e2009-09-05 21:47:34 +00003022 "jpeg:%s",filename);
3023 status=WriteImage(preview_info,preview_image);
3024 if (status != MagickFalse)
3025 {
3026 Image
3027 *quality_image;
3028
3029 (void) CopyMagickString(preview_info->filename,
3030 preview_image->filename,MaxTextExtent);
3031 quality_image=ReadImage(preview_info,exception);
3032 if (quality_image != (Image *) NULL)
3033 {
3034 preview_image=DestroyImage(preview_image);
3035 preview_image=quality_image;
3036 }
3037 }
3038 (void) RelinquishUniqueFileResource(preview_image->filename);
3039 if ((GetBlobSize(preview_image)/1024) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00003040 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%gmb ",
cristy3ed852e2009-09-05 21:47:34 +00003041 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
3042 1024.0/1024.0);
3043 else
3044 if (GetBlobSize(preview_image) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00003045 (void) FormatLocaleString(label,MaxTextExtent,
cristye7f51092010-01-17 00:39:37 +00003046 "quality %s\n%gkb ",factor,(double) ((MagickOffsetType)
cristy8cd5b312010-01-07 01:10:24 +00003047 GetBlobSize(preview_image))/1024.0);
cristy3ed852e2009-09-05 21:47:34 +00003048 else
cristyb51dff52011-05-19 16:55:47 +00003049 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%.20gb ",
cristy54ea5732011-06-10 12:39:53 +00003050 factor,(double) ((MagickOffsetType) GetBlobSize(thumbnail)));
cristy3ed852e2009-09-05 21:47:34 +00003051 break;
3052 }
3053 }
3054 thumbnail=DestroyImage(thumbnail);
3055 percentage+=12.5;
3056 radius+=0.5;
3057 sigma+=0.25;
3058 if (preview_image == (Image *) NULL)
3059 break;
3060 (void) DeleteImageProperty(preview_image,"label");
3061 (void) SetImageProperty(preview_image,"label",label);
3062 AppendImageToList(&images,preview_image);
cristybb503372010-05-27 20:51:26 +00003063 proceed=SetImageProgress(image,PreviewImageTag,(MagickOffsetType) i,
3064 NumberTiles);
cristy3ed852e2009-09-05 21:47:34 +00003065 if (proceed == MagickFalse)
3066 break;
3067 }
3068 if (images == (Image *) NULL)
3069 {
3070 preview_info=DestroyImageInfo(preview_info);
3071 return((Image *) NULL);
3072 }
3073 /*
3074 Create the montage.
3075 */
3076 montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL);
3077 (void) CopyMagickString(montage_info->filename,image->filename,MaxTextExtent);
3078 montage_info->shadow=MagickTrue;
3079 (void) CloneString(&montage_info->tile,"3x3");
3080 (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry);
3081 (void) CloneString(&montage_info->frame,DefaultTileFrame);
3082 montage_image=MontageImages(images,montage_info,exception);
3083 montage_info=DestroyMontageInfo(montage_info);
3084 images=DestroyImageList(images);
3085 if (montage_image == (Image *) NULL)
3086 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3087 if (montage_image->montage != (char *) NULL)
3088 {
3089 /*
3090 Free image directory.
3091 */
3092 montage_image->montage=(char *) RelinquishMagickMemory(
3093 montage_image->montage);
3094 if (image->directory != (char *) NULL)
3095 montage_image->directory=(char *) RelinquishMagickMemory(
3096 montage_image->directory);
3097 }
3098 preview_info=DestroyImageInfo(preview_info);
3099 return(montage_image);
3100}
3101
3102/*
3103%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3104% %
3105% %
3106% %
3107% R a d i a l B l u r I m a g e %
3108% %
3109% %
3110% %
3111%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3112%
3113% RadialBlurImage() applies a radial blur to the image.
3114%
3115% Andrew Protano contributed this effect.
3116%
3117% The format of the RadialBlurImage method is:
3118%
3119% Image *RadialBlurImage(const Image *image,const double angle,
3120% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003121%
3122% A description of each parameter follows:
3123%
3124% o image: the image.
3125%
cristy3ed852e2009-09-05 21:47:34 +00003126% o angle: the angle of the radial blur.
3127%
3128% o exception: return any errors or warnings in this structure.
3129%
3130*/
cristyf4ad9df2011-07-08 16:49:03 +00003131MagickExport Image *RadialBlurImage(const Image *image,
3132 const double angle,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003133{
cristyc4c8d132010-01-07 01:58:38 +00003134 CacheView
3135 *blur_view,
3136 *image_view;
3137
cristy3ed852e2009-09-05 21:47:34 +00003138 Image
3139 *blur_image;
3140
cristy3ed852e2009-09-05 21:47:34 +00003141 MagickBooleanType
3142 status;
3143
cristybb503372010-05-27 20:51:26 +00003144 MagickOffsetType
3145 progress;
3146
cristy4c08aed2011-07-01 19:47:50 +00003147 PixelInfo
cristyddd82202009-11-03 20:14:50 +00003148 bias;
cristy3ed852e2009-09-05 21:47:34 +00003149
3150 MagickRealType
3151 blur_radius,
3152 *cos_theta,
3153 offset,
3154 *sin_theta,
3155 theta;
3156
3157 PointInfo
3158 blur_center;
3159
cristybb503372010-05-27 20:51:26 +00003160 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003161 i;
3162
cristybb503372010-05-27 20:51:26 +00003163 size_t
cristy3ed852e2009-09-05 21:47:34 +00003164 n;
3165
cristybb503372010-05-27 20:51:26 +00003166 ssize_t
3167 y;
3168
cristy3ed852e2009-09-05 21:47:34 +00003169 /*
3170 Allocate blur image.
3171 */
3172 assert(image != (Image *) NULL);
3173 assert(image->signature == MagickSignature);
3174 if (image->debug != MagickFalse)
3175 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3176 assert(exception != (ExceptionInfo *) NULL);
3177 assert(exception->signature == MagickSignature);
3178 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3179 if (blur_image == (Image *) NULL)
3180 return((Image *) NULL);
3181 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3182 {
3183 InheritException(exception,&blur_image->exception);
3184 blur_image=DestroyImage(blur_image);
3185 return((Image *) NULL);
3186 }
3187 blur_center.x=(double) image->columns/2.0;
3188 blur_center.y=(double) image->rows/2.0;
3189 blur_radius=hypot(blur_center.x,blur_center.y);
cristy117ff172010-08-15 21:35:32 +00003190 n=(size_t) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+2UL);
cristy3ed852e2009-09-05 21:47:34 +00003191 theta=DegreesToRadians(angle)/(MagickRealType) (n-1);
3192 cos_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3193 sizeof(*cos_theta));
3194 sin_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3195 sizeof(*sin_theta));
3196 if ((cos_theta == (MagickRealType *) NULL) ||
3197 (sin_theta == (MagickRealType *) NULL))
3198 {
3199 blur_image=DestroyImage(blur_image);
3200 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3201 }
3202 offset=theta*(MagickRealType) (n-1)/2.0;
cristybb503372010-05-27 20:51:26 +00003203 for (i=0; i < (ssize_t) n; i++)
cristy3ed852e2009-09-05 21:47:34 +00003204 {
3205 cos_theta[i]=cos((double) (theta*i-offset));
3206 sin_theta[i]=sin((double) (theta*i-offset));
3207 }
3208 /*
3209 Radial blur image.
3210 */
3211 status=MagickTrue;
3212 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00003213 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003214 image_view=AcquireCacheView(image);
3215 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003216#if defined(MAGICKCORE_OPENMP_SUPPORT)
3217 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003218#endif
cristybb503372010-05-27 20:51:26 +00003219 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003220 {
cristy4c08aed2011-07-01 19:47:50 +00003221 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003222 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003223
cristy117ff172010-08-15 21:35:32 +00003224 register ssize_t
3225 x;
3226
cristy3ed852e2009-09-05 21:47:34 +00003227 if (status == MagickFalse)
3228 continue;
3229 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3230 exception);
cristy4c08aed2011-07-01 19:47:50 +00003231 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003232 {
3233 status=MagickFalse;
3234 continue;
3235 }
cristybb503372010-05-27 20:51:26 +00003236 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003237 {
cristy4c08aed2011-07-01 19:47:50 +00003238 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003239 qixel;
3240
3241 MagickRealType
3242 normalize,
3243 radius;
3244
3245 PixelPacket
3246 pixel;
3247
3248 PointInfo
3249 center;
3250
cristybb503372010-05-27 20:51:26 +00003251 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003252 i;
3253
cristybb503372010-05-27 20:51:26 +00003254 size_t
cristy3ed852e2009-09-05 21:47:34 +00003255 step;
3256
3257 center.x=(double) x-blur_center.x;
3258 center.y=(double) y-blur_center.y;
3259 radius=hypot((double) center.x,center.y);
3260 if (radius == 0)
3261 step=1;
3262 else
3263 {
cristybb503372010-05-27 20:51:26 +00003264 step=(size_t) (blur_radius/radius);
cristy3ed852e2009-09-05 21:47:34 +00003265 if (step == 0)
3266 step=1;
3267 else
3268 if (step >= n)
3269 step=n-1;
3270 }
3271 normalize=0.0;
cristyddd82202009-11-03 20:14:50 +00003272 qixel=bias;
cristyed231572011-07-14 02:18:59 +00003273 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00003274 {
cristyeaedf062010-05-29 22:36:02 +00003275 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00003276 {
cristyeaedf062010-05-29 22:36:02 +00003277 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
3278 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
3279 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
3280 cos_theta[i]+0.5),&pixel,exception);
cristy3ed852e2009-09-05 21:47:34 +00003281 qixel.red+=pixel.red;
3282 qixel.green+=pixel.green;
3283 qixel.blue+=pixel.blue;
cristy3ed852e2009-09-05 21:47:34 +00003284 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00003285 qixel.black+=pixel.black;
3286 qixel.alpha+=pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00003287 normalize+=1.0;
3288 }
3289 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
3290 normalize);
cristyed231572011-07-14 02:18:59 +00003291 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003292 SetPixelRed(blur_image,
3293 ClampToQuantum(normalize*qixel.red),q);
cristyed231572011-07-14 02:18:59 +00003294 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003295 SetPixelGreen(blur_image,
3296 ClampToQuantum(normalize*qixel.green),q);
cristyed231572011-07-14 02:18:59 +00003297 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003298 SetPixelBlue(blur_image,
3299 ClampToQuantum(normalize*qixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003300 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003301 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003302 SetPixelBlack(blur_image,
3303 ClampToQuantum(normalize*qixel.black),q);
cristyed231572011-07-14 02:18:59 +00003304 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003305 SetPixelAlpha(blur_image,
3306 ClampToQuantum(normalize*qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003307 }
3308 else
3309 {
3310 MagickRealType
3311 alpha,
3312 gamma;
3313
3314 alpha=1.0;
3315 gamma=0.0;
cristyeaedf062010-05-29 22:36:02 +00003316 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00003317 {
cristyeaedf062010-05-29 22:36:02 +00003318 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
3319 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
3320 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
3321 cos_theta[i]+0.5),&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00003322 alpha=(MagickRealType) (QuantumScale*pixel.alpha);
cristy3ed852e2009-09-05 21:47:34 +00003323 qixel.red+=alpha*pixel.red;
3324 qixel.green+=alpha*pixel.green;
3325 qixel.blue+=alpha*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00003326 qixel.alpha+=pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00003327 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00003328 qixel.black+=alpha*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00003329 gamma+=alpha;
3330 normalize+=1.0;
3331 }
3332 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3333 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
3334 normalize);
cristyed231572011-07-14 02:18:59 +00003335 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003336 SetPixelRed(blur_image,
3337 ClampToQuantum(gamma*qixel.red),q);
cristyed231572011-07-14 02:18:59 +00003338 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003339 SetPixelGreen(blur_image,
3340 ClampToQuantum(gamma*qixel.green),q);
cristyed231572011-07-14 02:18:59 +00003341 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003342 SetPixelBlue(blur_image,
3343 ClampToQuantum(gamma*qixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003344 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003345 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003346 SetPixelBlack(blur_image,
3347 ClampToQuantum(gamma*qixel.black),q);
cristyed231572011-07-14 02:18:59 +00003348 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003349 SetPixelAlpha(blur_image,
3350 ClampToQuantum(normalize*qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003351 }
cristyed231572011-07-14 02:18:59 +00003352 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003353 }
3354 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
3355 status=MagickFalse;
3356 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3357 {
3358 MagickBooleanType
3359 proceed;
3360
cristyb5d5f722009-11-04 03:03:49 +00003361#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00003362 #pragma omp critical (MagickCore_RadialBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00003363#endif
3364 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
3365 if (proceed == MagickFalse)
3366 status=MagickFalse;
3367 }
3368 }
3369 blur_view=DestroyCacheView(blur_view);
3370 image_view=DestroyCacheView(image_view);
3371 cos_theta=(MagickRealType *) RelinquishMagickMemory(cos_theta);
3372 sin_theta=(MagickRealType *) RelinquishMagickMemory(sin_theta);
3373 if (status == MagickFalse)
3374 blur_image=DestroyImage(blur_image);
3375 return(blur_image);
3376}
3377
3378/*
3379%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3380% %
3381% %
3382% %
cristy3ed852e2009-09-05 21:47:34 +00003383% S e l e c t i v e B l u r I m a g e %
3384% %
3385% %
3386% %
3387%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3388%
3389% SelectiveBlurImage() selectively blur pixels within a contrast threshold.
3390% It is similar to the unsharpen mask that sharpens everything with contrast
3391% above a certain threshold.
3392%
3393% The format of the SelectiveBlurImage method is:
3394%
3395% Image *SelectiveBlurImage(const Image *image,const double radius,
3396% const double sigma,const double threshold,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003397%
3398% A description of each parameter follows:
3399%
3400% o image: the image.
3401%
cristy3ed852e2009-09-05 21:47:34 +00003402% o radius: the radius of the Gaussian, in pixels, not counting the center
3403% pixel.
3404%
3405% o sigma: the standard deviation of the Gaussian, in pixels.
3406%
3407% o threshold: only pixels within this contrast threshold are included
3408% in the blur operation.
3409%
3410% o exception: return any errors or warnings in this structure.
3411%
3412*/
cristyf4ad9df2011-07-08 16:49:03 +00003413MagickExport Image *SelectiveBlurImage(const Image *image,
3414 const double radius,const double sigma,const double threshold,
3415 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003416{
3417#define SelectiveBlurImageTag "SelectiveBlur/Image"
3418
cristy47e00502009-12-17 19:19:57 +00003419 CacheView
3420 *blur_view,
3421 *image_view;
3422
cristy3ed852e2009-09-05 21:47:34 +00003423 double
cristy3ed852e2009-09-05 21:47:34 +00003424 *kernel;
3425
3426 Image
3427 *blur_image;
3428
cristy3ed852e2009-09-05 21:47:34 +00003429 MagickBooleanType
3430 status;
3431
cristybb503372010-05-27 20:51:26 +00003432 MagickOffsetType
3433 progress;
3434
cristy4c08aed2011-07-01 19:47:50 +00003435 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003436 bias;
3437
cristybb503372010-05-27 20:51:26 +00003438 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003439 i;
cristy3ed852e2009-09-05 21:47:34 +00003440
cristybb503372010-05-27 20:51:26 +00003441 size_t
cristy3ed852e2009-09-05 21:47:34 +00003442 width;
3443
cristybb503372010-05-27 20:51:26 +00003444 ssize_t
3445 j,
3446 u,
3447 v,
3448 y;
3449
cristy3ed852e2009-09-05 21:47:34 +00003450 /*
3451 Initialize blur image attributes.
3452 */
3453 assert(image != (Image *) NULL);
3454 assert(image->signature == MagickSignature);
3455 if (image->debug != MagickFalse)
3456 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3457 assert(exception != (ExceptionInfo *) NULL);
3458 assert(exception->signature == MagickSignature);
3459 width=GetOptimalKernelWidth1D(radius,sigma);
3460 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
3461 if (kernel == (double *) NULL)
3462 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00003463 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00003464 i=0;
cristy47e00502009-12-17 19:19:57 +00003465 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00003466 {
cristy47e00502009-12-17 19:19:57 +00003467 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00003468 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
3469 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00003470 }
3471 if (image->debug != MagickFalse)
3472 {
3473 char
3474 format[MaxTextExtent],
3475 *message;
3476
cristy117ff172010-08-15 21:35:32 +00003477 register const double
3478 *k;
3479
cristybb503372010-05-27 20:51:26 +00003480 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003481 u,
3482 v;
3483
cristy3ed852e2009-09-05 21:47:34 +00003484 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00003485 " SelectiveBlurImage with %.20gx%.20g kernel:",(double) width,(double)
3486 width);
cristy3ed852e2009-09-05 21:47:34 +00003487 message=AcquireString("");
3488 k=kernel;
cristybb503372010-05-27 20:51:26 +00003489 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003490 {
3491 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00003492 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy3ed852e2009-09-05 21:47:34 +00003493 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00003494 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003495 {
cristyb51dff52011-05-19 16:55:47 +00003496 (void) FormatLocaleString(format,MaxTextExtent,"%+f ",*k++);
cristy3ed852e2009-09-05 21:47:34 +00003497 (void) ConcatenateString(&message,format);
3498 }
3499 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
3500 }
3501 message=DestroyString(message);
3502 }
3503 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3504 if (blur_image == (Image *) NULL)
3505 return((Image *) NULL);
3506 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3507 {
3508 InheritException(exception,&blur_image->exception);
3509 blur_image=DestroyImage(blur_image);
3510 return((Image *) NULL);
3511 }
3512 /*
3513 Threshold blur image.
3514 */
3515 status=MagickTrue;
3516 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00003517 GetPixelInfo(image,&bias);
3518 SetPixelInfoBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003519 image_view=AcquireCacheView(image);
3520 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003521#if defined(MAGICKCORE_OPENMP_SUPPORT)
3522 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003523#endif
cristybb503372010-05-27 20:51:26 +00003524 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003525 {
cristy4c08aed2011-07-01 19:47:50 +00003526 double
3527 contrast;
3528
cristy3ed852e2009-09-05 21:47:34 +00003529 MagickBooleanType
3530 sync;
3531
3532 MagickRealType
3533 gamma;
3534
cristy4c08aed2011-07-01 19:47:50 +00003535 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003536 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003537
cristy4c08aed2011-07-01 19:47:50 +00003538 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003539 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003540
cristy117ff172010-08-15 21:35:32 +00003541 register ssize_t
3542 x;
3543
cristy3ed852e2009-09-05 21:47:34 +00003544 if (status == MagickFalse)
3545 continue;
cristy117ff172010-08-15 21:35:32 +00003546 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
3547 (width/2L),image->columns+width,width,exception);
cristy3ed852e2009-09-05 21:47:34 +00003548 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3549 exception);
cristy4c08aed2011-07-01 19:47:50 +00003550 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003551 {
3552 status=MagickFalse;
3553 continue;
3554 }
cristybb503372010-05-27 20:51:26 +00003555 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003556 {
cristy4c08aed2011-07-01 19:47:50 +00003557 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003558 pixel;
3559
3560 register const double
cristyc47d1f82009-11-26 01:44:43 +00003561 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00003562
cristybb503372010-05-27 20:51:26 +00003563 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003564 u;
3565
cristy117ff172010-08-15 21:35:32 +00003566 ssize_t
3567 j,
3568 v;
3569
cristyddd82202009-11-03 20:14:50 +00003570 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00003571 k=kernel;
3572 gamma=0.0;
3573 j=0;
cristyed231572011-07-14 02:18:59 +00003574 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00003575 {
cristybb503372010-05-27 20:51:26 +00003576 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003577 {
cristybb503372010-05-27 20:51:26 +00003578 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003579 {
cristyed231572011-07-14 02:18:59 +00003580 contrast=GetPixelIntensity(image,p+(u+j)*GetPixelChannels(image))-
cristy4c08aed2011-07-01 19:47:50 +00003581 (double) GetPixelIntensity(blur_image,q);
3582 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003583 {
cristy4c08aed2011-07-01 19:47:50 +00003584 pixel.red+=(*k)*
cristyed231572011-07-14 02:18:59 +00003585 GetPixelRed(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003586 pixel.green+=(*k)*
cristyed231572011-07-14 02:18:59 +00003587 GetPixelGreen(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003588 pixel.blue+=(*k)*
cristyed231572011-07-14 02:18:59 +00003589 GetPixelBlue(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003590 if (image->colorspace == CMYKColorspace)
3591 pixel.black+=(*k)*
cristyed231572011-07-14 02:18:59 +00003592 GetPixelBlack(image,p+(u+j)*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003593 gamma+=(*k);
3594 k++;
3595 }
3596 }
cristyd99b0962010-05-29 23:14:26 +00003597 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003598 }
3599 if (gamma != 0.0)
3600 {
3601 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +00003602 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003603 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +00003604 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003605 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +00003606 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003607 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003608 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00003609 (image->colorspace == CMYKColorspace))
3610 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00003611 }
cristyed231572011-07-14 02:18:59 +00003612 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003613 {
3614 gamma=0.0;
3615 j=0;
cristybb503372010-05-27 20:51:26 +00003616 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003617 {
cristybb503372010-05-27 20:51:26 +00003618 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003619 {
cristy4c08aed2011-07-01 19:47:50 +00003620 contrast=GetPixelIntensity(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003621 GetPixelChannels(image))-(double)
cristy4c08aed2011-07-01 19:47:50 +00003622 GetPixelIntensity(blur_image,q);
3623 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003624 {
cristy4c08aed2011-07-01 19:47:50 +00003625 pixel.alpha+=(*k)*
cristyed231572011-07-14 02:18:59 +00003626 GetPixelAlpha(image,p+(u+j)*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003627 gamma+=(*k);
3628 k++;
3629 }
3630 }
cristyeaedf062010-05-29 22:36:02 +00003631 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003632 }
3633 if (gamma != 0.0)
3634 {
3635 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
3636 gamma);
cristy4c08aed2011-07-01 19:47:50 +00003637 SetPixelAlpha(blur_image,ClampToQuantum(gamma*pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003638 }
3639 }
3640 }
3641 else
3642 {
3643 MagickRealType
3644 alpha;
3645
cristybb503372010-05-27 20:51:26 +00003646 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003647 {
cristybb503372010-05-27 20:51:26 +00003648 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003649 {
cristy4c08aed2011-07-01 19:47:50 +00003650 contrast=GetPixelIntensity(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003651 GetPixelChannels(image))-(double)
cristy4c08aed2011-07-01 19:47:50 +00003652 GetPixelIntensity(blur_image,q);
3653 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003654 {
cristy4c08aed2011-07-01 19:47:50 +00003655 alpha=(MagickRealType) (QuantumScale*
cristyed231572011-07-14 02:18:59 +00003656 GetPixelAlpha(image,p+(u+j)*GetPixelChannels(image)));
cristy4c08aed2011-07-01 19:47:50 +00003657 pixel.red+=(*k)*alpha*
cristyed231572011-07-14 02:18:59 +00003658 GetPixelRed(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003659 pixel.green+=(*k)*alpha*GetPixelGreen(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003660 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003661 pixel.blue+=(*k)*alpha*GetPixelBlue(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003662 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003663 pixel.alpha+=(*k)*GetPixelAlpha(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003664 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003665 if (image->colorspace == CMYKColorspace)
3666 pixel.black+=(*k)*GetPixelBlack(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003667 GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003668 gamma+=(*k)*alpha;
3669 k++;
3670 }
3671 }
cristyeaedf062010-05-29 22:36:02 +00003672 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003673 }
3674 if (gamma != 0.0)
3675 {
3676 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +00003677 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003678 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +00003679 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003680 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +00003681 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003682 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003683 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00003684 (image->colorspace == CMYKColorspace))
3685 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00003686 }
cristyed231572011-07-14 02:18:59 +00003687 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003688 {
3689 gamma=0.0;
3690 j=0;
cristybb503372010-05-27 20:51:26 +00003691 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003692 {
cristybb503372010-05-27 20:51:26 +00003693 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003694 {
cristy4c08aed2011-07-01 19:47:50 +00003695 contrast=GetPixelIntensity(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003696 GetPixelChannels(image))-(double)
cristy4c08aed2011-07-01 19:47:50 +00003697 GetPixelIntensity(blur_image,q);
3698 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003699 {
cristy4c08aed2011-07-01 19:47:50 +00003700 pixel.alpha+=(*k)*
cristyed231572011-07-14 02:18:59 +00003701 GetPixelAlpha(image,p+(u+j)*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003702 gamma+=(*k);
3703 k++;
3704 }
3705 }
cristyeaedf062010-05-29 22:36:02 +00003706 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003707 }
3708 if (gamma != 0.0)
3709 {
3710 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
3711 gamma);
cristy4c08aed2011-07-01 19:47:50 +00003712 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003713 }
3714 }
3715 }
cristyed231572011-07-14 02:18:59 +00003716 p+=GetPixelChannels(image);
3717 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003718 }
3719 sync=SyncCacheViewAuthenticPixels(blur_view,exception);
3720 if (sync == MagickFalse)
3721 status=MagickFalse;
3722 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3723 {
3724 MagickBooleanType
3725 proceed;
3726
cristyb5d5f722009-11-04 03:03:49 +00003727#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00003728 #pragma omp critical (MagickCore_SelectiveBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00003729#endif
3730 proceed=SetImageProgress(image,SelectiveBlurImageTag,progress++,
3731 image->rows);
3732 if (proceed == MagickFalse)
3733 status=MagickFalse;
3734 }
3735 }
3736 blur_image->type=image->type;
3737 blur_view=DestroyCacheView(blur_view);
3738 image_view=DestroyCacheView(image_view);
3739 kernel=(double *) RelinquishMagickMemory(kernel);
3740 if (status == MagickFalse)
3741 blur_image=DestroyImage(blur_image);
3742 return(blur_image);
3743}
3744
3745/*
3746%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3747% %
3748% %
3749% %
3750% S h a d e I m a g e %
3751% %
3752% %
3753% %
3754%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3755%
3756% ShadeImage() shines a distant light on an image to create a
3757% three-dimensional effect. You control the positioning of the light with
3758% azimuth and elevation; azimuth is measured in degrees off the x axis
3759% and elevation is measured in pixels above the Z axis.
3760%
3761% The format of the ShadeImage method is:
3762%
3763% Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3764% const double azimuth,const double elevation,ExceptionInfo *exception)
3765%
3766% A description of each parameter follows:
3767%
3768% o image: the image.
3769%
3770% o gray: A value other than zero shades the intensity of each pixel.
3771%
3772% o azimuth, elevation: Define the light source direction.
3773%
3774% o exception: return any errors or warnings in this structure.
3775%
3776*/
3777MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3778 const double azimuth,const double elevation,ExceptionInfo *exception)
3779{
3780#define ShadeImageTag "Shade/Image"
3781
cristyc4c8d132010-01-07 01:58:38 +00003782 CacheView
3783 *image_view,
3784 *shade_view;
3785
cristy3ed852e2009-09-05 21:47:34 +00003786 Image
3787 *shade_image;
3788
cristy3ed852e2009-09-05 21:47:34 +00003789 MagickBooleanType
3790 status;
3791
cristybb503372010-05-27 20:51:26 +00003792 MagickOffsetType
3793 progress;
3794
cristy3ed852e2009-09-05 21:47:34 +00003795 PrimaryInfo
3796 light;
3797
cristybb503372010-05-27 20:51:26 +00003798 ssize_t
3799 y;
3800
cristy3ed852e2009-09-05 21:47:34 +00003801 /*
3802 Initialize shaded image attributes.
3803 */
3804 assert(image != (const Image *) NULL);
3805 assert(image->signature == MagickSignature);
3806 if (image->debug != MagickFalse)
3807 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3808 assert(exception != (ExceptionInfo *) NULL);
3809 assert(exception->signature == MagickSignature);
3810 shade_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
3811 if (shade_image == (Image *) NULL)
3812 return((Image *) NULL);
3813 if (SetImageStorageClass(shade_image,DirectClass) == MagickFalse)
3814 {
3815 InheritException(exception,&shade_image->exception);
3816 shade_image=DestroyImage(shade_image);
3817 return((Image *) NULL);
3818 }
3819 /*
3820 Compute the light vector.
3821 */
3822 light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))*
3823 cos(DegreesToRadians(elevation));
3824 light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))*
3825 cos(DegreesToRadians(elevation));
3826 light.z=(double) QuantumRange*sin(DegreesToRadians(elevation));
3827 /*
3828 Shade image.
3829 */
3830 status=MagickTrue;
3831 progress=0;
3832 image_view=AcquireCacheView(image);
3833 shade_view=AcquireCacheView(shade_image);
cristyb5d5f722009-11-04 03:03:49 +00003834#if defined(MAGICKCORE_OPENMP_SUPPORT)
3835 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003836#endif
cristybb503372010-05-27 20:51:26 +00003837 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003838 {
3839 MagickRealType
3840 distance,
3841 normal_distance,
3842 shade;
3843
3844 PrimaryInfo
3845 normal;
3846
cristy4c08aed2011-07-01 19:47:50 +00003847 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003848 *restrict p,
3849 *restrict s0,
3850 *restrict s1,
3851 *restrict s2;
cristy3ed852e2009-09-05 21:47:34 +00003852
cristy4c08aed2011-07-01 19:47:50 +00003853 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003854 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003855
cristy117ff172010-08-15 21:35:32 +00003856 register ssize_t
3857 x;
3858
cristy3ed852e2009-09-05 21:47:34 +00003859 if (status == MagickFalse)
3860 continue;
3861 p=GetCacheViewVirtualPixels(image_view,-1,y-1,image->columns+2,3,exception);
3862 q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1,
3863 exception);
cristy4c08aed2011-07-01 19:47:50 +00003864 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003865 {
3866 status=MagickFalse;
3867 continue;
3868 }
3869 /*
3870 Shade this row of pixels.
3871 */
3872 normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */
cristyed231572011-07-14 02:18:59 +00003873 s0=p+GetPixelChannels(image);
3874 s1=s0+(image->columns+2)*GetPixelChannels(image);
3875 s2=s1+(image->columns+2)*GetPixelChannels(image);
cristybb503372010-05-27 20:51:26 +00003876 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003877 {
3878 /*
3879 Determine the surface normal and compute shading.
3880 */
cristyed231572011-07-14 02:18:59 +00003881 normal.x=(double) (GetPixelIntensity(image,s0-GetPixelChannels(image))+
3882 GetPixelIntensity(image,s1-GetPixelChannels(image))+
3883 GetPixelIntensity(image,s2-GetPixelChannels(image))-
3884 GetPixelIntensity(image,s0+GetPixelChannels(image))-
3885 GetPixelIntensity(image,s1+GetPixelChannels(image))-
3886 GetPixelIntensity(image,s2+GetPixelChannels(image)));
3887 normal.y=(double) (GetPixelIntensity(image,s2-GetPixelChannels(image))+
cristy4c08aed2011-07-01 19:47:50 +00003888 GetPixelIntensity(image,s2)+
cristyed231572011-07-14 02:18:59 +00003889 GetPixelIntensity(image,s2+GetPixelChannels(image))-
3890 GetPixelIntensity(image,s0-GetPixelChannels(image))-
cristy4c08aed2011-07-01 19:47:50 +00003891 GetPixelIntensity(image,s0)-
cristyed231572011-07-14 02:18:59 +00003892 GetPixelIntensity(image,s0+GetPixelChannels(image)));
cristy3ed852e2009-09-05 21:47:34 +00003893 if ((normal.x == 0.0) && (normal.y == 0.0))
3894 shade=light.z;
3895 else
3896 {
3897 shade=0.0;
3898 distance=normal.x*light.x+normal.y*light.y+normal.z*light.z;
3899 if (distance > MagickEpsilon)
3900 {
3901 normal_distance=
3902 normal.x*normal.x+normal.y*normal.y+normal.z*normal.z;
3903 if (normal_distance > (MagickEpsilon*MagickEpsilon))
3904 shade=distance/sqrt((double) normal_distance);
3905 }
3906 }
3907 if (gray != MagickFalse)
3908 {
cristy4c08aed2011-07-01 19:47:50 +00003909 SetPixelRed(shade_image,ClampToQuantum(shade),q);
3910 SetPixelGreen(shade_image,ClampToQuantum(shade),q);
3911 SetPixelBlue(shade_image,ClampToQuantum(shade),q);
cristy3ed852e2009-09-05 21:47:34 +00003912 }
3913 else
3914 {
cristy4c08aed2011-07-01 19:47:50 +00003915 SetPixelRed(shade_image,ClampToQuantum(QuantumScale*shade*
3916 GetPixelRed(image,s1)),q);
3917 SetPixelGreen(shade_image,ClampToQuantum(QuantumScale*shade*
3918 GetPixelGreen(image,s1)),q);
3919 SetPixelBlue(shade_image,ClampToQuantum(QuantumScale*shade*
3920 GetPixelBlue(image,s1)),q);
cristy3ed852e2009-09-05 21:47:34 +00003921 }
cristy4c08aed2011-07-01 19:47:50 +00003922 SetPixelAlpha(shade_image,GetPixelAlpha(image,s1),q);
cristyed231572011-07-14 02:18:59 +00003923 s0+=GetPixelChannels(image);
3924 s1+=GetPixelChannels(image);
3925 s2+=GetPixelChannels(image);
3926 q+=GetPixelChannels(shade_image);
cristy3ed852e2009-09-05 21:47:34 +00003927 }
3928 if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
3929 status=MagickFalse;
3930 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3931 {
3932 MagickBooleanType
3933 proceed;
3934
cristyb5d5f722009-11-04 03:03:49 +00003935#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003936 #pragma omp critical (MagickCore_ShadeImage)
3937#endif
3938 proceed=SetImageProgress(image,ShadeImageTag,progress++,image->rows);
3939 if (proceed == MagickFalse)
3940 status=MagickFalse;
3941 }
3942 }
3943 shade_view=DestroyCacheView(shade_view);
3944 image_view=DestroyCacheView(image_view);
3945 if (status == MagickFalse)
3946 shade_image=DestroyImage(shade_image);
3947 return(shade_image);
3948}
3949
3950/*
3951%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3952% %
3953% %
3954% %
3955% S h a r p e n I m a g e %
3956% %
3957% %
3958% %
3959%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3960%
3961% SharpenImage() sharpens the image. We convolve the image with a Gaussian
3962% operator of the given radius and standard deviation (sigma). For
3963% reasonable results, radius should be larger than sigma. Use a radius of 0
3964% and SharpenImage() selects a suitable radius for you.
3965%
3966% Using a separable kernel would be faster, but the negative weights cancel
3967% out on the corners of the kernel producing often undesirable ringing in the
3968% filtered result; this can be avoided by using a 2D gaussian shaped image
3969% sharpening kernel instead.
3970%
3971% The format of the SharpenImage method is:
3972%
3973% Image *SharpenImage(const Image *image,const double radius,
3974% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003975%
3976% A description of each parameter follows:
3977%
3978% o image: the image.
3979%
cristy3ed852e2009-09-05 21:47:34 +00003980% o radius: the radius of the Gaussian, in pixels, not counting the center
3981% pixel.
3982%
3983% o sigma: the standard deviation of the Laplacian, in pixels.
3984%
3985% o exception: return any errors or warnings in this structure.
3986%
3987*/
cristy3ed852e2009-09-05 21:47:34 +00003988MagickExport Image *SharpenImage(const Image *image,const double radius,
3989 const double sigma,ExceptionInfo *exception)
3990{
cristy3ed852e2009-09-05 21:47:34 +00003991 double
cristy47e00502009-12-17 19:19:57 +00003992 *kernel,
3993 normalize;
cristy3ed852e2009-09-05 21:47:34 +00003994
3995 Image
3996 *sharp_image;
3997
cristybb503372010-05-27 20:51:26 +00003998 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003999 i;
4000
cristybb503372010-05-27 20:51:26 +00004001 size_t
cristy3ed852e2009-09-05 21:47:34 +00004002 width;
4003
cristy117ff172010-08-15 21:35:32 +00004004 ssize_t
4005 j,
4006 u,
4007 v;
4008
cristy3ed852e2009-09-05 21:47:34 +00004009 assert(image != (const Image *) NULL);
4010 assert(image->signature == MagickSignature);
4011 if (image->debug != MagickFalse)
4012 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4013 assert(exception != (ExceptionInfo *) NULL);
4014 assert(exception->signature == MagickSignature);
4015 width=GetOptimalKernelWidth2D(radius,sigma);
4016 kernel=(double *) AcquireQuantumMemory((size_t) width*width,sizeof(*kernel));
4017 if (kernel == (double *) NULL)
4018 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy3ed852e2009-09-05 21:47:34 +00004019 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00004020 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +00004021 i=0;
4022 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00004023 {
cristy47e00502009-12-17 19:19:57 +00004024 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00004025 {
cristy4205a3c2010-09-12 20:19:59 +00004026 kernel[i]=(double) (-exp(-((double) u*u+v*v)/(2.0*MagickSigma*
4027 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00004028 normalize+=kernel[i];
4029 i++;
4030 }
4031 }
4032 kernel[i/2]=(double) ((-2.0)*normalize);
cristyf4ad9df2011-07-08 16:49:03 +00004033 sharp_image=ConvolveImage(image,width,kernel,exception);
cristy3ed852e2009-09-05 21:47:34 +00004034 kernel=(double *) RelinquishMagickMemory(kernel);
4035 return(sharp_image);
4036}
4037
4038/*
4039%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4040% %
4041% %
4042% %
4043% S p r e a d I m a g e %
4044% %
4045% %
4046% %
4047%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4048%
4049% SpreadImage() is a special effects method that randomly displaces each
4050% pixel in a block defined by the radius parameter.
4051%
4052% The format of the SpreadImage method is:
4053%
4054% Image *SpreadImage(const Image *image,const double radius,
4055% ExceptionInfo *exception)
4056%
4057% A description of each parameter follows:
4058%
4059% o image: the image.
4060%
4061% o radius: Choose a random pixel in a neighborhood of this extent.
4062%
4063% o exception: return any errors or warnings in this structure.
4064%
4065*/
4066MagickExport Image *SpreadImage(const Image *image,const double radius,
4067 ExceptionInfo *exception)
4068{
4069#define SpreadImageTag "Spread/Image"
4070
cristyfa112112010-01-04 17:48:07 +00004071 CacheView
cristy9f7e7cb2011-03-26 00:49:57 +00004072 *image_view,
4073 *spread_view;
cristyfa112112010-01-04 17:48:07 +00004074
cristy3ed852e2009-09-05 21:47:34 +00004075 Image
4076 *spread_image;
4077
cristy3ed852e2009-09-05 21:47:34 +00004078 MagickBooleanType
4079 status;
4080
cristybb503372010-05-27 20:51:26 +00004081 MagickOffsetType
4082 progress;
4083
cristy4c08aed2011-07-01 19:47:50 +00004084 PixelInfo
cristyddd82202009-11-03 20:14:50 +00004085 bias;
cristy3ed852e2009-09-05 21:47:34 +00004086
4087 RandomInfo
cristyfa112112010-01-04 17:48:07 +00004088 **restrict random_info;
cristy3ed852e2009-09-05 21:47:34 +00004089
cristybb503372010-05-27 20:51:26 +00004090 size_t
cristy3ed852e2009-09-05 21:47:34 +00004091 width;
4092
cristybb503372010-05-27 20:51:26 +00004093 ssize_t
4094 y;
4095
cristy3ed852e2009-09-05 21:47:34 +00004096 /*
4097 Initialize spread image attributes.
4098 */
4099 assert(image != (Image *) NULL);
4100 assert(image->signature == MagickSignature);
4101 if (image->debug != MagickFalse)
4102 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4103 assert(exception != (ExceptionInfo *) NULL);
4104 assert(exception->signature == MagickSignature);
4105 spread_image=CloneImage(image,image->columns,image->rows,MagickTrue,
4106 exception);
4107 if (spread_image == (Image *) NULL)
4108 return((Image *) NULL);
4109 if (SetImageStorageClass(spread_image,DirectClass) == MagickFalse)
4110 {
4111 InheritException(exception,&spread_image->exception);
4112 spread_image=DestroyImage(spread_image);
4113 return((Image *) NULL);
4114 }
4115 /*
4116 Spread image.
4117 */
4118 status=MagickTrue;
4119 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00004120 GetPixelInfo(spread_image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00004121 width=GetOptimalKernelWidth1D(radius,0.5);
cristy3ed852e2009-09-05 21:47:34 +00004122 random_info=AcquireRandomInfoThreadSet();
cristy9f7e7cb2011-03-26 00:49:57 +00004123 image_view=AcquireCacheView(image);
4124 spread_view=AcquireCacheView(spread_image);
cristyb557a152011-02-22 12:14:30 +00004125#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00004126 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00004127#endif
cristybb503372010-05-27 20:51:26 +00004128 for (y=0; y < (ssize_t) spread_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00004129 {
cristy5c9e6f22010-09-17 17:31:01 +00004130 const int
4131 id = GetOpenMPThreadId();
cristy6ebe97c2010-07-03 01:17:28 +00004132
cristy4c08aed2011-07-01 19:47:50 +00004133 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00004134 pixel;
4135
cristy4c08aed2011-07-01 19:47:50 +00004136 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00004137 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004138
cristy117ff172010-08-15 21:35:32 +00004139 register ssize_t
4140 x;
4141
cristy3ed852e2009-09-05 21:47:34 +00004142 if (status == MagickFalse)
4143 continue;
cristy9f7e7cb2011-03-26 00:49:57 +00004144 q=QueueCacheViewAuthenticPixels(spread_view,0,y,spread_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +00004145 exception);
cristy4c08aed2011-07-01 19:47:50 +00004146 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00004147 {
4148 status=MagickFalse;
4149 continue;
4150 }
cristyddd82202009-11-03 20:14:50 +00004151 pixel=bias;
cristybb503372010-05-27 20:51:26 +00004152 for (x=0; x < (ssize_t) spread_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004153 {
cristy4c08aed2011-07-01 19:47:50 +00004154 (void) InterpolatePixelInfo(image,image_view,
cristy8a7c3e82011-03-26 02:10:53 +00004155 UndefinedInterpolatePixel,(double) x+width*(GetPseudoRandomValue(
4156 random_info[id])-0.5),(double) y+width*(GetPseudoRandomValue(
4157 random_info[id])-0.5),&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00004158 SetPixelPixelInfo(spread_image,&pixel,q);
cristyed231572011-07-14 02:18:59 +00004159 q+=GetPixelChannels(spread_image);
cristy3ed852e2009-09-05 21:47:34 +00004160 }
cristy9f7e7cb2011-03-26 00:49:57 +00004161 if (SyncCacheViewAuthenticPixels(spread_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00004162 status=MagickFalse;
4163 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4164 {
4165 MagickBooleanType
4166 proceed;
4167
cristyb557a152011-02-22 12:14:30 +00004168#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004169 #pragma omp critical (MagickCore_SpreadImage)
4170#endif
4171 proceed=SetImageProgress(image,SpreadImageTag,progress++,image->rows);
4172 if (proceed == MagickFalse)
4173 status=MagickFalse;
4174 }
4175 }
cristy9f7e7cb2011-03-26 00:49:57 +00004176 spread_view=DestroyCacheView(spread_view);
cristy3ed852e2009-09-05 21:47:34 +00004177 image_view=DestroyCacheView(image_view);
4178 random_info=DestroyRandomInfoThreadSet(random_info);
cristy3ed852e2009-09-05 21:47:34 +00004179 return(spread_image);
4180}
4181
4182/*
4183%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4184% %
4185% %
4186% %
cristy0834d642011-03-18 18:26:08 +00004187% S t a t i s t i c I m a g e %
4188% %
4189% %
4190% %
4191%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4192%
4193% StatisticImage() makes each pixel the min / max / median / mode / etc. of
cristy8d752042011-03-19 01:00:36 +00004194% the neighborhood of the specified width and height.
cristy0834d642011-03-18 18:26:08 +00004195%
4196% The format of the StatisticImage method is:
4197%
4198% Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00004199% const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00004200%
4201% A description of each parameter follows:
4202%
4203% o image: the image.
4204%
cristy0834d642011-03-18 18:26:08 +00004205% o type: the statistic type (median, mode, etc.).
4206%
cristy95c38342011-03-18 22:39:51 +00004207% o width: the width of the pixel neighborhood.
4208%
4209% o height: the height of the pixel neighborhood.
cristy0834d642011-03-18 18:26:08 +00004210%
4211% o exception: return any errors or warnings in this structure.
4212%
4213*/
4214
cristy733678d2011-03-18 21:29:28 +00004215#define ListChannels 5
4216
4217typedef struct _ListNode
4218{
4219 size_t
4220 next[9],
4221 count,
4222 signature;
4223} ListNode;
4224
4225typedef struct _SkipList
4226{
4227 ssize_t
4228 level;
4229
4230 ListNode
4231 *nodes;
4232} SkipList;
4233
4234typedef struct _PixelList
4235{
4236 size_t
cristy6fc86bb2011-03-18 23:45:16 +00004237 length,
cristy733678d2011-03-18 21:29:28 +00004238 seed,
4239 signature;
4240
4241 SkipList
4242 lists[ListChannels];
4243} PixelList;
4244
4245static PixelList *DestroyPixelList(PixelList *pixel_list)
4246{
4247 register ssize_t
4248 i;
4249
4250 if (pixel_list == (PixelList *) NULL)
4251 return((PixelList *) NULL);
4252 for (i=0; i < ListChannels; i++)
4253 if (pixel_list->lists[i].nodes != (ListNode *) NULL)
4254 pixel_list->lists[i].nodes=(ListNode *) RelinquishMagickMemory(
4255 pixel_list->lists[i].nodes);
4256 pixel_list=(PixelList *) RelinquishMagickMemory(pixel_list);
4257 return(pixel_list);
4258}
4259
4260static PixelList **DestroyPixelListThreadSet(PixelList **pixel_list)
4261{
4262 register ssize_t
4263 i;
4264
4265 assert(pixel_list != (PixelList **) NULL);
4266 for (i=0; i < (ssize_t) GetOpenMPMaximumThreads(); i++)
4267 if (pixel_list[i] != (PixelList *) NULL)
4268 pixel_list[i]=DestroyPixelList(pixel_list[i]);
4269 pixel_list=(PixelList **) RelinquishMagickMemory(pixel_list);
4270 return(pixel_list);
4271}
4272
cristy6fc86bb2011-03-18 23:45:16 +00004273static PixelList *AcquirePixelList(const size_t width,const size_t height)
cristy733678d2011-03-18 21:29:28 +00004274{
4275 PixelList
4276 *pixel_list;
4277
4278 register ssize_t
4279 i;
4280
4281 pixel_list=(PixelList *) AcquireMagickMemory(sizeof(*pixel_list));
4282 if (pixel_list == (PixelList *) NULL)
4283 return(pixel_list);
4284 (void) ResetMagickMemory((void *) pixel_list,0,sizeof(*pixel_list));
cristy6fc86bb2011-03-18 23:45:16 +00004285 pixel_list->length=width*height;
cristy733678d2011-03-18 21:29:28 +00004286 for (i=0; i < ListChannels; i++)
4287 {
4288 pixel_list->lists[i].nodes=(ListNode *) AcquireQuantumMemory(65537UL,
4289 sizeof(*pixel_list->lists[i].nodes));
4290 if (pixel_list->lists[i].nodes == (ListNode *) NULL)
4291 return(DestroyPixelList(pixel_list));
4292 (void) ResetMagickMemory(pixel_list->lists[i].nodes,0,65537UL*
4293 sizeof(*pixel_list->lists[i].nodes));
4294 }
4295 pixel_list->signature=MagickSignature;
4296 return(pixel_list);
4297}
4298
cristy6fc86bb2011-03-18 23:45:16 +00004299static PixelList **AcquirePixelListThreadSet(const size_t width,
4300 const size_t height)
cristy733678d2011-03-18 21:29:28 +00004301{
4302 PixelList
4303 **pixel_list;
4304
4305 register ssize_t
4306 i;
4307
4308 size_t
4309 number_threads;
4310
4311 number_threads=GetOpenMPMaximumThreads();
4312 pixel_list=(PixelList **) AcquireQuantumMemory(number_threads,
4313 sizeof(*pixel_list));
4314 if (pixel_list == (PixelList **) NULL)
4315 return((PixelList **) NULL);
4316 (void) ResetMagickMemory(pixel_list,0,number_threads*sizeof(*pixel_list));
4317 for (i=0; i < (ssize_t) number_threads; i++)
4318 {
cristy6fc86bb2011-03-18 23:45:16 +00004319 pixel_list[i]=AcquirePixelList(width,height);
cristy733678d2011-03-18 21:29:28 +00004320 if (pixel_list[i] == (PixelList *) NULL)
4321 return(DestroyPixelListThreadSet(pixel_list));
4322 }
4323 return(pixel_list);
4324}
4325
4326static void AddNodePixelList(PixelList *pixel_list,const ssize_t channel,
4327 const size_t color)
4328{
4329 register SkipList
4330 *list;
4331
4332 register ssize_t
4333 level;
4334
4335 size_t
4336 search,
4337 update[9];
4338
4339 /*
4340 Initialize the node.
4341 */
4342 list=pixel_list->lists+channel;
4343 list->nodes[color].signature=pixel_list->signature;
4344 list->nodes[color].count=1;
4345 /*
4346 Determine where it belongs in the list.
4347 */
4348 search=65536UL;
4349 for (level=list->level; level >= 0; level--)
4350 {
4351 while (list->nodes[search].next[level] < color)
4352 search=list->nodes[search].next[level];
4353 update[level]=search;
4354 }
4355 /*
4356 Generate a pseudo-random level for this node.
4357 */
4358 for (level=0; ; level++)
4359 {
4360 pixel_list->seed=(pixel_list->seed*42893621L)+1L;
4361 if ((pixel_list->seed & 0x300) != 0x300)
4362 break;
4363 }
4364 if (level > 8)
4365 level=8;
4366 if (level > (list->level+2))
4367 level=list->level+2;
4368 /*
4369 If we're raising the list's level, link back to the root node.
4370 */
4371 while (level > list->level)
4372 {
4373 list->level++;
4374 update[list->level]=65536UL;
4375 }
4376 /*
4377 Link the node into the skip-list.
4378 */
4379 do
4380 {
4381 list->nodes[color].next[level]=list->nodes[update[level]].next[level];
4382 list->nodes[update[level]].next[level]=color;
cristy3cba8ca2011-03-19 01:29:12 +00004383 } while (level-- > 0);
cristy733678d2011-03-18 21:29:28 +00004384}
4385
cristy4c08aed2011-07-01 19:47:50 +00004386static PixelInfo GetMaximumPixelList(PixelList *pixel_list)
cristy6fc86bb2011-03-18 23:45:16 +00004387{
cristy4c08aed2011-07-01 19:47:50 +00004388 PixelInfo
cristy6fc86bb2011-03-18 23:45:16 +00004389 pixel;
4390
4391 register SkipList
4392 *list;
4393
4394 register ssize_t
4395 channel;
4396
4397 size_t
cristyd76c51e2011-03-26 00:21:26 +00004398 color,
4399 maximum;
cristy49f37242011-03-22 18:18:23 +00004400
4401 ssize_t
cristy6fc86bb2011-03-18 23:45:16 +00004402 count;
4403
4404 unsigned short
cristyd76c51e2011-03-26 00:21:26 +00004405 channels[ListChannels];
cristy6fc86bb2011-03-18 23:45:16 +00004406
4407 /*
4408 Find the maximum value for each of the color.
4409 */
4410 for (channel=0; channel < 5; channel++)
4411 {
4412 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004413 color=65536L;
cristy6fc86bb2011-03-18 23:45:16 +00004414 count=0;
cristy49f37242011-03-22 18:18:23 +00004415 maximum=list->nodes[color].next[0];
cristy6fc86bb2011-03-18 23:45:16 +00004416 do
4417 {
4418 color=list->nodes[color].next[0];
cristy49f37242011-03-22 18:18:23 +00004419 if (color > maximum)
4420 maximum=color;
cristy6fc86bb2011-03-18 23:45:16 +00004421 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004422 } while (count < (ssize_t) pixel_list->length);
cristy49f37242011-03-22 18:18:23 +00004423 channels[channel]=(unsigned short) maximum;
4424 }
cristy4c08aed2011-07-01 19:47:50 +00004425 GetPixelInfo((const Image *) NULL,&pixel);
cristy49f37242011-03-22 18:18:23 +00004426 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4427 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4428 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004429 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4430 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy49f37242011-03-22 18:18:23 +00004431 return(pixel);
4432}
4433
cristy4c08aed2011-07-01 19:47:50 +00004434static PixelInfo GetMeanPixelList(PixelList *pixel_list)
cristy49f37242011-03-22 18:18:23 +00004435{
cristy4c08aed2011-07-01 19:47:50 +00004436 PixelInfo
cristy49f37242011-03-22 18:18:23 +00004437 pixel;
4438
cristy80a99a32011-03-30 01:30:23 +00004439 MagickRealType
4440 sum;
4441
cristy49f37242011-03-22 18:18:23 +00004442 register SkipList
4443 *list;
4444
4445 register ssize_t
4446 channel;
4447
4448 size_t
cristy80a99a32011-03-30 01:30:23 +00004449 color;
cristy49f37242011-03-22 18:18:23 +00004450
4451 ssize_t
4452 count;
4453
4454 unsigned short
4455 channels[ListChannels];
4456
4457 /*
4458 Find the mean value for each of the color.
4459 */
4460 for (channel=0; channel < 5; channel++)
4461 {
4462 list=pixel_list->lists+channel;
4463 color=65536L;
4464 count=0;
cristy80a99a32011-03-30 01:30:23 +00004465 sum=0.0;
cristy49f37242011-03-22 18:18:23 +00004466 do
4467 {
4468 color=list->nodes[color].next[0];
cristy80a99a32011-03-30 01:30:23 +00004469 sum+=(MagickRealType) list->nodes[color].count*color;
cristy49f37242011-03-22 18:18:23 +00004470 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004471 } while (count < (ssize_t) pixel_list->length);
cristy80a99a32011-03-30 01:30:23 +00004472 sum/=pixel_list->length;
4473 channels[channel]=(unsigned short) sum;
cristy6fc86bb2011-03-18 23:45:16 +00004474 }
cristy4c08aed2011-07-01 19:47:50 +00004475 GetPixelInfo((const Image *) NULL,&pixel);
cristy6fc86bb2011-03-18 23:45:16 +00004476 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4477 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4478 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004479 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4480 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy6fc86bb2011-03-18 23:45:16 +00004481 return(pixel);
4482}
4483
cristy4c08aed2011-07-01 19:47:50 +00004484static PixelInfo GetMedianPixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004485{
cristy4c08aed2011-07-01 19:47:50 +00004486 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004487 pixel;
4488
4489 register SkipList
4490 *list;
4491
4492 register ssize_t
4493 channel;
4494
4495 size_t
cristy49f37242011-03-22 18:18:23 +00004496 color;
4497
4498 ssize_t
cristy733678d2011-03-18 21:29:28 +00004499 count;
4500
4501 unsigned short
4502 channels[ListChannels];
4503
4504 /*
4505 Find the median value for each of the color.
4506 */
cristy733678d2011-03-18 21:29:28 +00004507 for (channel=0; channel < 5; channel++)
4508 {
4509 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004510 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004511 count=0;
4512 do
4513 {
4514 color=list->nodes[color].next[0];
4515 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004516 } while (count <= (ssize_t) (pixel_list->length >> 1));
cristy6fc86bb2011-03-18 23:45:16 +00004517 channels[channel]=(unsigned short) color;
4518 }
cristy4c08aed2011-07-01 19:47:50 +00004519 GetPixelInfo((const Image *) NULL,&pixel);
cristy6fc86bb2011-03-18 23:45:16 +00004520 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4521 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4522 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004523 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4524 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy6fc86bb2011-03-18 23:45:16 +00004525 return(pixel);
4526}
4527
cristy4c08aed2011-07-01 19:47:50 +00004528static PixelInfo GetMinimumPixelList(PixelList *pixel_list)
cristy6fc86bb2011-03-18 23:45:16 +00004529{
cristy4c08aed2011-07-01 19:47:50 +00004530 PixelInfo
cristy6fc86bb2011-03-18 23:45:16 +00004531 pixel;
4532
4533 register SkipList
4534 *list;
4535
4536 register ssize_t
4537 channel;
4538
4539 size_t
cristyd76c51e2011-03-26 00:21:26 +00004540 color,
4541 minimum;
cristy6fc86bb2011-03-18 23:45:16 +00004542
cristy49f37242011-03-22 18:18:23 +00004543 ssize_t
4544 count;
4545
cristy6fc86bb2011-03-18 23:45:16 +00004546 unsigned short
cristyd76c51e2011-03-26 00:21:26 +00004547 channels[ListChannels];
cristy6fc86bb2011-03-18 23:45:16 +00004548
4549 /*
4550 Find the minimum value for each of the color.
4551 */
4552 for (channel=0; channel < 5; channel++)
4553 {
4554 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004555 count=0;
cristy6fc86bb2011-03-18 23:45:16 +00004556 color=65536UL;
cristy49f37242011-03-22 18:18:23 +00004557 minimum=list->nodes[color].next[0];
4558 do
4559 {
4560 color=list->nodes[color].next[0];
4561 if (color < minimum)
4562 minimum=color;
4563 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004564 } while (count < (ssize_t) pixel_list->length);
cristy49f37242011-03-22 18:18:23 +00004565 channels[channel]=(unsigned short) minimum;
cristy733678d2011-03-18 21:29:28 +00004566 }
cristy4c08aed2011-07-01 19:47:50 +00004567 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004568 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4569 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4570 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004571 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4572 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy733678d2011-03-18 21:29:28 +00004573 return(pixel);
4574}
4575
cristy4c08aed2011-07-01 19:47:50 +00004576static PixelInfo GetModePixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004577{
cristy4c08aed2011-07-01 19:47:50 +00004578 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004579 pixel;
4580
4581 register SkipList
4582 *list;
4583
4584 register ssize_t
4585 channel;
4586
4587 size_t
4588 color,
cristy733678d2011-03-18 21:29:28 +00004589 max_count,
cristy6fc86bb2011-03-18 23:45:16 +00004590 mode;
cristy733678d2011-03-18 21:29:28 +00004591
cristy49f37242011-03-22 18:18:23 +00004592 ssize_t
4593 count;
4594
cristy733678d2011-03-18 21:29:28 +00004595 unsigned short
4596 channels[5];
4597
4598 /*
glennrp30d2dc62011-06-25 03:17:16 +00004599 Make each pixel the 'predominant color' of the specified neighborhood.
cristy733678d2011-03-18 21:29:28 +00004600 */
cristy733678d2011-03-18 21:29:28 +00004601 for (channel=0; channel < 5; channel++)
4602 {
4603 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004604 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004605 mode=color;
4606 max_count=list->nodes[mode].count;
4607 count=0;
4608 do
4609 {
4610 color=list->nodes[color].next[0];
4611 if (list->nodes[color].count > max_count)
4612 {
4613 mode=color;
4614 max_count=list->nodes[mode].count;
4615 }
4616 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004617 } while (count < (ssize_t) pixel_list->length);
cristy733678d2011-03-18 21:29:28 +00004618 channels[channel]=(unsigned short) mode;
4619 }
cristy4c08aed2011-07-01 19:47:50 +00004620 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004621 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4622 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4623 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004624 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4625 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy733678d2011-03-18 21:29:28 +00004626 return(pixel);
4627}
4628
cristy4c08aed2011-07-01 19:47:50 +00004629static PixelInfo GetNonpeakPixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004630{
cristy4c08aed2011-07-01 19:47:50 +00004631 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004632 pixel;
4633
4634 register SkipList
4635 *list;
4636
4637 register ssize_t
4638 channel;
4639
4640 size_t
cristy733678d2011-03-18 21:29:28 +00004641 color,
cristy733678d2011-03-18 21:29:28 +00004642 next,
4643 previous;
4644
cristy49f37242011-03-22 18:18:23 +00004645 ssize_t
4646 count;
4647
cristy733678d2011-03-18 21:29:28 +00004648 unsigned short
4649 channels[5];
4650
4651 /*
cristy49f37242011-03-22 18:18:23 +00004652 Finds the non peak value for each of the colors.
cristy733678d2011-03-18 21:29:28 +00004653 */
cristy733678d2011-03-18 21:29:28 +00004654 for (channel=0; channel < 5; channel++)
4655 {
4656 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004657 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004658 next=list->nodes[color].next[0];
4659 count=0;
4660 do
4661 {
4662 previous=color;
4663 color=next;
4664 next=list->nodes[color].next[0];
4665 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004666 } while (count <= (ssize_t) (pixel_list->length >> 1));
cristy733678d2011-03-18 21:29:28 +00004667 if ((previous == 65536UL) && (next != 65536UL))
4668 color=next;
4669 else
4670 if ((previous != 65536UL) && (next == 65536UL))
4671 color=previous;
4672 channels[channel]=(unsigned short) color;
4673 }
cristy4c08aed2011-07-01 19:47:50 +00004674 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004675 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4676 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4677 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004678 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4679 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy733678d2011-03-18 21:29:28 +00004680 return(pixel);
4681}
4682
cristy4c08aed2011-07-01 19:47:50 +00004683static PixelInfo GetStandardDeviationPixelList(PixelList *pixel_list)
cristy9a68cbb2011-03-29 00:51:23 +00004684{
cristy4c08aed2011-07-01 19:47:50 +00004685 PixelInfo
cristy9a68cbb2011-03-29 00:51:23 +00004686 pixel;
4687
cristy80a99a32011-03-30 01:30:23 +00004688 MagickRealType
4689 sum,
4690 sum_squared;
4691
cristy9a68cbb2011-03-29 00:51:23 +00004692 register SkipList
4693 *list;
4694
4695 register ssize_t
4696 channel;
4697
4698 size_t
cristy80a99a32011-03-30 01:30:23 +00004699 color;
cristy9a68cbb2011-03-29 00:51:23 +00004700
4701 ssize_t
4702 count;
4703
4704 unsigned short
4705 channels[ListChannels];
4706
4707 /*
cristy80a99a32011-03-30 01:30:23 +00004708 Find the standard-deviation value for each of the color.
cristy9a68cbb2011-03-29 00:51:23 +00004709 */
4710 for (channel=0; channel < 5; channel++)
4711 {
4712 list=pixel_list->lists+channel;
4713 color=65536L;
4714 count=0;
cristy80a99a32011-03-30 01:30:23 +00004715 sum=0.0;
4716 sum_squared=0.0;
cristy9a68cbb2011-03-29 00:51:23 +00004717 do
4718 {
cristy80a99a32011-03-30 01:30:23 +00004719 register ssize_t
4720 i;
4721
cristy9a68cbb2011-03-29 00:51:23 +00004722 color=list->nodes[color].next[0];
cristy80a99a32011-03-30 01:30:23 +00004723 sum+=(MagickRealType) list->nodes[color].count*color;
4724 for (i=0; i < (ssize_t) list->nodes[color].count; i++)
4725 sum_squared+=((MagickRealType) color)*((MagickRealType) color);
cristy9a68cbb2011-03-29 00:51:23 +00004726 count+=list->nodes[color].count;
4727 } while (count < (ssize_t) pixel_list->length);
cristy80a99a32011-03-30 01:30:23 +00004728 sum/=pixel_list->length;
4729 sum_squared/=pixel_list->length;
4730 channels[channel]=(unsigned short) sqrt(sum_squared-(sum*sum));
cristy9a68cbb2011-03-29 00:51:23 +00004731 }
cristy4c08aed2011-07-01 19:47:50 +00004732 GetPixelInfo((const Image *) NULL,&pixel);
cristy9a68cbb2011-03-29 00:51:23 +00004733 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4734 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4735 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004736 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4737 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy9a68cbb2011-03-29 00:51:23 +00004738 return(pixel);
4739}
4740
cristy4c08aed2011-07-01 19:47:50 +00004741static inline void InsertPixelList(const Image *image,const Quantum *pixel,
4742 PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004743{
4744 size_t
4745 signature;
4746
4747 unsigned short
4748 index;
4749
cristy4c08aed2011-07-01 19:47:50 +00004750 index=ScaleQuantumToShort(GetPixelRed(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004751 signature=pixel_list->lists[0].nodes[index].signature;
4752 if (signature == pixel_list->signature)
4753 pixel_list->lists[0].nodes[index].count++;
4754 else
4755 AddNodePixelList(pixel_list,0,index);
cristy4c08aed2011-07-01 19:47:50 +00004756 index=ScaleQuantumToShort(GetPixelGreen(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004757 signature=pixel_list->lists[1].nodes[index].signature;
4758 if (signature == pixel_list->signature)
4759 pixel_list->lists[1].nodes[index].count++;
4760 else
4761 AddNodePixelList(pixel_list,1,index);
cristy4c08aed2011-07-01 19:47:50 +00004762 index=ScaleQuantumToShort(GetPixelBlue(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004763 signature=pixel_list->lists[2].nodes[index].signature;
4764 if (signature == pixel_list->signature)
4765 pixel_list->lists[2].nodes[index].count++;
4766 else
4767 AddNodePixelList(pixel_list,2,index);
cristy4c08aed2011-07-01 19:47:50 +00004768 index=ScaleQuantumToShort(GetPixelAlpha(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004769 signature=pixel_list->lists[3].nodes[index].signature;
4770 if (signature == pixel_list->signature)
4771 pixel_list->lists[3].nodes[index].count++;
4772 else
4773 AddNodePixelList(pixel_list,3,index);
4774 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00004775 index=ScaleQuantumToShort(GetPixelBlack(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004776 signature=pixel_list->lists[4].nodes[index].signature;
4777 if (signature == pixel_list->signature)
4778 pixel_list->lists[4].nodes[index].count++;
4779 else
4780 AddNodePixelList(pixel_list,4,index);
4781}
4782
cristy80c99742011-04-04 14:46:39 +00004783static inline MagickRealType MagickAbsoluteValue(const MagickRealType x)
4784{
4785 if (x < 0)
4786 return(-x);
4787 return(x);
4788}
4789
cristy733678d2011-03-18 21:29:28 +00004790static void ResetPixelList(PixelList *pixel_list)
4791{
4792 int
4793 level;
4794
4795 register ListNode
4796 *root;
4797
4798 register SkipList
4799 *list;
4800
4801 register ssize_t
4802 channel;
4803
4804 /*
4805 Reset the skip-list.
4806 */
4807 for (channel=0; channel < 5; channel++)
4808 {
4809 list=pixel_list->lists+channel;
4810 root=list->nodes+65536UL;
4811 list->level=0;
4812 for (level=0; level < 9; level++)
4813 root->next[level]=65536UL;
4814 }
4815 pixel_list->seed=pixel_list->signature++;
4816}
4817
cristy0834d642011-03-18 18:26:08 +00004818MagickExport Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00004819 const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00004820{
cristy3cba8ca2011-03-19 01:29:12 +00004821#define StatisticWidth \
cristyd76c51e2011-03-26 00:21:26 +00004822 (width == 0 ? GetOptimalKernelWidth2D((double) width,0.5) : width)
cristy3cba8ca2011-03-19 01:29:12 +00004823#define StatisticHeight \
cristyd76c51e2011-03-26 00:21:26 +00004824 (height == 0 ? GetOptimalKernelWidth2D((double) height,0.5) : height)
cristy0834d642011-03-18 18:26:08 +00004825#define StatisticImageTag "Statistic/Image"
4826
4827 CacheView
4828 *image_view,
4829 *statistic_view;
4830
4831 Image
4832 *statistic_image;
4833
4834 MagickBooleanType
4835 status;
4836
4837 MagickOffsetType
4838 progress;
4839
4840 PixelList
4841 **restrict pixel_list;
4842
cristy0834d642011-03-18 18:26:08 +00004843 ssize_t
4844 y;
4845
4846 /*
4847 Initialize statistics image attributes.
4848 */
4849 assert(image != (Image *) NULL);
4850 assert(image->signature == MagickSignature);
4851 if (image->debug != MagickFalse)
4852 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4853 assert(exception != (ExceptionInfo *) NULL);
4854 assert(exception->signature == MagickSignature);
cristy0834d642011-03-18 18:26:08 +00004855 statistic_image=CloneImage(image,image->columns,image->rows,MagickTrue,
4856 exception);
4857 if (statistic_image == (Image *) NULL)
4858 return((Image *) NULL);
4859 if (SetImageStorageClass(statistic_image,DirectClass) == MagickFalse)
4860 {
4861 InheritException(exception,&statistic_image->exception);
4862 statistic_image=DestroyImage(statistic_image);
4863 return((Image *) NULL);
4864 }
cristy6fc86bb2011-03-18 23:45:16 +00004865 pixel_list=AcquirePixelListThreadSet(StatisticWidth,StatisticHeight);
cristy0834d642011-03-18 18:26:08 +00004866 if (pixel_list == (PixelList **) NULL)
4867 {
4868 statistic_image=DestroyImage(statistic_image);
4869 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
4870 }
4871 /*
cristy8d752042011-03-19 01:00:36 +00004872 Make each pixel the min / max / median / mode / etc. of the neighborhood.
cristy0834d642011-03-18 18:26:08 +00004873 */
4874 status=MagickTrue;
4875 progress=0;
4876 image_view=AcquireCacheView(image);
4877 statistic_view=AcquireCacheView(statistic_image);
4878#if defined(MAGICKCORE_OPENMP_SUPPORT)
4879 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
4880#endif
4881 for (y=0; y < (ssize_t) statistic_image->rows; y++)
4882 {
4883 const int
4884 id = GetOpenMPThreadId();
4885
cristy4c08aed2011-07-01 19:47:50 +00004886 register const Quantum
cristy0834d642011-03-18 18:26:08 +00004887 *restrict p;
4888
cristy4c08aed2011-07-01 19:47:50 +00004889 register Quantum
cristy0834d642011-03-18 18:26:08 +00004890 *restrict q;
4891
4892 register ssize_t
4893 x;
4894
4895 if (status == MagickFalse)
4896 continue;
cristy6fc86bb2011-03-18 23:45:16 +00004897 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) StatisticWidth/2L),y-
4898 (ssize_t) (StatisticHeight/2L),image->columns+StatisticWidth,
4899 StatisticHeight,exception);
cristy3cba8ca2011-03-19 01:29:12 +00004900 q=QueueCacheViewAuthenticPixels(statistic_view,0,y,statistic_image->columns, 1,exception);
cristy4c08aed2011-07-01 19:47:50 +00004901 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy0834d642011-03-18 18:26:08 +00004902 {
4903 status=MagickFalse;
4904 continue;
4905 }
cristy0834d642011-03-18 18:26:08 +00004906 for (x=0; x < (ssize_t) statistic_image->columns; x++)
4907 {
cristy4c08aed2011-07-01 19:47:50 +00004908 PixelInfo
cristy0834d642011-03-18 18:26:08 +00004909 pixel;
4910
cristy4c08aed2011-07-01 19:47:50 +00004911 register const Quantum
cristy6e3026a2011-03-19 00:54:38 +00004912 *restrict r;
4913
cristy0834d642011-03-18 18:26:08 +00004914 register ssize_t
4915 u,
4916 v;
4917
4918 r=p;
cristy0834d642011-03-18 18:26:08 +00004919 ResetPixelList(pixel_list[id]);
cristy6e4c3292011-03-19 00:53:55 +00004920 for (v=0; v < (ssize_t) StatisticHeight; v++)
cristy0834d642011-03-18 18:26:08 +00004921 {
cristy6e4c3292011-03-19 00:53:55 +00004922 for (u=0; u < (ssize_t) StatisticWidth; u++)
cristyed231572011-07-14 02:18:59 +00004923 InsertPixelList(image,r+u*GetPixelChannels(image),pixel_list[id]);
4924 r+=(image->columns+StatisticWidth)*GetPixelChannels(image);
cristy0834d642011-03-18 18:26:08 +00004925 }
cristy4c08aed2011-07-01 19:47:50 +00004926 GetPixelInfo(image,&pixel);
cristy490408a2011-07-07 14:42:05 +00004927 SetPixelInfo(image,p+(StatisticWidth*StatisticHeight/2)*
cristyed231572011-07-14 02:18:59 +00004928 GetPixelChannels(image),&pixel);
cristy0834d642011-03-18 18:26:08 +00004929 switch (type)
4930 {
cristy80c99742011-04-04 14:46:39 +00004931 case GradientStatistic:
4932 {
cristy4c08aed2011-07-01 19:47:50 +00004933 PixelInfo
cristy80c99742011-04-04 14:46:39 +00004934 maximum,
4935 minimum;
4936
4937 minimum=GetMinimumPixelList(pixel_list[id]);
4938 maximum=GetMaximumPixelList(pixel_list[id]);
4939 pixel.red=MagickAbsoluteValue(maximum.red-minimum.red);
4940 pixel.green=MagickAbsoluteValue(maximum.green-minimum.green);
4941 pixel.blue=MagickAbsoluteValue(maximum.blue-minimum.blue);
cristy4c08aed2011-07-01 19:47:50 +00004942 pixel.alpha=MagickAbsoluteValue(maximum.alpha-minimum.alpha);
cristy80c99742011-04-04 14:46:39 +00004943 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00004944 pixel.black=MagickAbsoluteValue(maximum.black-minimum.black);
cristy80c99742011-04-04 14:46:39 +00004945 break;
4946 }
cristy6fc86bb2011-03-18 23:45:16 +00004947 case MaximumStatistic:
4948 {
4949 pixel=GetMaximumPixelList(pixel_list[id]);
4950 break;
4951 }
cristy49f37242011-03-22 18:18:23 +00004952 case MeanStatistic:
4953 {
4954 pixel=GetMeanPixelList(pixel_list[id]);
4955 break;
4956 }
cristyf2ad14a2011-03-18 18:57:25 +00004957 case MedianStatistic:
cristy6fc86bb2011-03-18 23:45:16 +00004958 default:
cristyf2ad14a2011-03-18 18:57:25 +00004959 {
4960 pixel=GetMedianPixelList(pixel_list[id]);
4961 break;
4962 }
cristy6fc86bb2011-03-18 23:45:16 +00004963 case MinimumStatistic:
4964 {
4965 pixel=GetMinimumPixelList(pixel_list[id]);
4966 break;
4967 }
cristyf2ad14a2011-03-18 18:57:25 +00004968 case ModeStatistic:
4969 {
4970 pixel=GetModePixelList(pixel_list[id]);
4971 break;
4972 }
4973 case NonpeakStatistic:
4974 {
4975 pixel=GetNonpeakPixelList(pixel_list[id]);
4976 break;
4977 }
cristy9a68cbb2011-03-29 00:51:23 +00004978 case StandardDeviationStatistic:
4979 {
4980 pixel=GetStandardDeviationPixelList(pixel_list[id]);
4981 break;
4982 }
cristy0834d642011-03-18 18:26:08 +00004983 }
cristyed231572011-07-14 02:18:59 +00004984 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00004985 SetPixelRed(statistic_image,ClampToQuantum(pixel.red),q);
cristyed231572011-07-14 02:18:59 +00004986 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00004987 SetPixelGreen(statistic_image,ClampToQuantum(pixel.green),q);
cristyed231572011-07-14 02:18:59 +00004988 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00004989 SetPixelBlue(statistic_image,ClampToQuantum(pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00004990 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy0834d642011-03-18 18:26:08 +00004991 (image->colorspace == CMYKColorspace))
cristy490408a2011-07-07 14:42:05 +00004992 SetPixelBlack(statistic_image,ClampToQuantum(pixel.black),q);
cristyed231572011-07-14 02:18:59 +00004993 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00004994 (image->matte != MagickFalse))
cristy490408a2011-07-07 14:42:05 +00004995 SetPixelAlpha(statistic_image,ClampToQuantum(pixel.alpha),q);
cristyed231572011-07-14 02:18:59 +00004996 p+=GetPixelChannels(image);
4997 q+=GetPixelChannels(statistic_image);
cristy0834d642011-03-18 18:26:08 +00004998 }
4999 if (SyncCacheViewAuthenticPixels(statistic_view,exception) == MagickFalse)
5000 status=MagickFalse;
5001 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5002 {
5003 MagickBooleanType
5004 proceed;
5005
5006#if defined(MAGICKCORE_OPENMP_SUPPORT)
5007 #pragma omp critical (MagickCore_StatisticImage)
5008#endif
5009 proceed=SetImageProgress(image,StatisticImageTag,progress++,
5010 image->rows);
5011 if (proceed == MagickFalse)
5012 status=MagickFalse;
5013 }
5014 }
5015 statistic_view=DestroyCacheView(statistic_view);
5016 image_view=DestroyCacheView(image_view);
5017 pixel_list=DestroyPixelListThreadSet(pixel_list);
5018 return(statistic_image);
5019}
5020
5021/*
5022%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5023% %
5024% %
5025% %
cristy3ed852e2009-09-05 21:47:34 +00005026% U n s h a r p M a s k I m a g e %
5027% %
5028% %
5029% %
5030%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5031%
5032% UnsharpMaskImage() sharpens one or more image channels. We convolve the
5033% image with a Gaussian operator of the given radius and standard deviation
5034% (sigma). For reasonable results, radius should be larger than sigma. Use a
5035% radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
5036%
5037% The format of the UnsharpMaskImage method is:
5038%
5039% Image *UnsharpMaskImage(const Image *image,const double radius,
5040% const double sigma,const double amount,const double threshold,
5041% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00005042%
5043% A description of each parameter follows:
5044%
5045% o image: the image.
5046%
cristy3ed852e2009-09-05 21:47:34 +00005047% o radius: the radius of the Gaussian, in pixels, not counting the center
5048% pixel.
5049%
5050% o sigma: the standard deviation of the Gaussian, in pixels.
5051%
5052% o amount: the percentage of the difference between the original and the
5053% blur image that is added back into the original.
5054%
5055% o threshold: the threshold in pixels needed to apply the diffence amount.
5056%
5057% o exception: return any errors or warnings in this structure.
5058%
5059*/
cristyf4ad9df2011-07-08 16:49:03 +00005060MagickExport Image *UnsharpMaskImage(const Image *image,
5061 const double radius,const double sigma,const double amount,
5062 const double threshold,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00005063{
5064#define SharpenImageTag "Sharpen/Image"
5065
cristyc4c8d132010-01-07 01:58:38 +00005066 CacheView
5067 *image_view,
5068 *unsharp_view;
5069
cristy3ed852e2009-09-05 21:47:34 +00005070 Image
5071 *unsharp_image;
5072
cristy3ed852e2009-09-05 21:47:34 +00005073 MagickBooleanType
5074 status;
5075
cristybb503372010-05-27 20:51:26 +00005076 MagickOffsetType
5077 progress;
5078
cristy4c08aed2011-07-01 19:47:50 +00005079 PixelInfo
cristyddd82202009-11-03 20:14:50 +00005080 bias;
cristy3ed852e2009-09-05 21:47:34 +00005081
5082 MagickRealType
5083 quantum_threshold;
5084
cristybb503372010-05-27 20:51:26 +00005085 ssize_t
5086 y;
5087
cristy3ed852e2009-09-05 21:47:34 +00005088 assert(image != (const Image *) NULL);
5089 assert(image->signature == MagickSignature);
5090 if (image->debug != MagickFalse)
5091 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5092 assert(exception != (ExceptionInfo *) NULL);
cristyf4ad9df2011-07-08 16:49:03 +00005093 unsharp_image=BlurImage(image,radius,sigma,exception);
cristy3ed852e2009-09-05 21:47:34 +00005094 if (unsharp_image == (Image *) NULL)
5095 return((Image *) NULL);
5096 quantum_threshold=(MagickRealType) QuantumRange*threshold;
5097 /*
5098 Unsharp-mask image.
5099 */
5100 status=MagickTrue;
5101 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00005102 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00005103 image_view=AcquireCacheView(image);
5104 unsharp_view=AcquireCacheView(unsharp_image);
cristyb5d5f722009-11-04 03:03:49 +00005105#if defined(MAGICKCORE_OPENMP_SUPPORT)
5106 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00005107#endif
cristybb503372010-05-27 20:51:26 +00005108 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00005109 {
cristy4c08aed2011-07-01 19:47:50 +00005110 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00005111 pixel;
5112
cristy4c08aed2011-07-01 19:47:50 +00005113 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00005114 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00005115
cristy4c08aed2011-07-01 19:47:50 +00005116 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00005117 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00005118
cristy117ff172010-08-15 21:35:32 +00005119 register ssize_t
5120 x;
5121
cristy3ed852e2009-09-05 21:47:34 +00005122 if (status == MagickFalse)
5123 continue;
5124 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
5125 q=GetCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
5126 exception);
cristy4c08aed2011-07-01 19:47:50 +00005127 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00005128 {
5129 status=MagickFalse;
5130 continue;
5131 }
cristyddd82202009-11-03 20:14:50 +00005132 pixel=bias;
cristybb503372010-05-27 20:51:26 +00005133 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00005134 {
cristyed231572011-07-14 02:18:59 +00005135 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00005136 {
cristy4c08aed2011-07-01 19:47:50 +00005137 pixel.red=GetPixelRed(image,p)-(MagickRealType) GetPixelRed(image,q);
cristy3ed852e2009-09-05 21:47:34 +00005138 if (fabs(2.0*pixel.red) < quantum_threshold)
cristy4c08aed2011-07-01 19:47:50 +00005139 pixel.red=(MagickRealType) GetPixelRed(image,p);
cristy3ed852e2009-09-05 21:47:34 +00005140 else
cristy4c08aed2011-07-01 19:47:50 +00005141 pixel.red=(MagickRealType) GetPixelRed(image,p)+(pixel.red*amount);
5142 SetPixelRed(unsharp_image,ClampToQuantum(pixel.red),q);
cristy3ed852e2009-09-05 21:47:34 +00005143 }
cristyed231572011-07-14 02:18:59 +00005144 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00005145 {
cristy4c08aed2011-07-01 19:47:50 +00005146 pixel.green=GetPixelGreen(image,p)-
5147 (MagickRealType) GetPixelGreen(image,q);
cristy3ed852e2009-09-05 21:47:34 +00005148 if (fabs(2.0*pixel.green) < quantum_threshold)
cristy4c08aed2011-07-01 19:47:50 +00005149 pixel.green=(MagickRealType)
5150 GetPixelGreen(image,p);
cristy3ed852e2009-09-05 21:47:34 +00005151 else
cristy4c08aed2011-07-01 19:47:50 +00005152 pixel.green=(MagickRealType)
5153 GetPixelGreen(image,p)+
5154 (pixel.green*amount);
5155 SetPixelGreen(unsharp_image,
5156 ClampToQuantum(pixel.green),q);
cristy3ed852e2009-09-05 21:47:34 +00005157 }
cristyed231572011-07-14 02:18:59 +00005158 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00005159 {
cristy4c08aed2011-07-01 19:47:50 +00005160 pixel.blue=GetPixelBlue(image,p)-
5161 (MagickRealType) GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00005162 if (fabs(2.0*pixel.blue) < quantum_threshold)
cristy4c08aed2011-07-01 19:47:50 +00005163 pixel.blue=(MagickRealType)
5164 GetPixelBlue(image,p);
cristy3ed852e2009-09-05 21:47:34 +00005165 else
cristy4c08aed2011-07-01 19:47:50 +00005166 pixel.blue=(MagickRealType)
5167 GetPixelBlue(image,p)+(pixel.blue*amount);
5168 SetPixelBlue(unsharp_image,
5169 ClampToQuantum(pixel.blue),q);
cristy3ed852e2009-09-05 21:47:34 +00005170 }
cristyed231572011-07-14 02:18:59 +00005171 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00005172 (image->colorspace == CMYKColorspace))
5173 {
cristy4c08aed2011-07-01 19:47:50 +00005174 pixel.black=GetPixelBlack(image,p)-
5175 (MagickRealType) GetPixelBlack(image,q);
5176 if (fabs(2.0*pixel.black) < quantum_threshold)
5177 pixel.black=(MagickRealType)
5178 GetPixelBlack(image,p);
cristy3ed852e2009-09-05 21:47:34 +00005179 else
cristy4c08aed2011-07-01 19:47:50 +00005180 pixel.black=(MagickRealType)
5181 GetPixelBlack(image,p)+(pixel.black*
5182 amount);
5183 SetPixelBlack(unsharp_image,
5184 ClampToQuantum(pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00005185 }
cristyed231572011-07-14 02:18:59 +00005186 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00005187 {
5188 pixel.alpha=GetPixelAlpha(image,p)-
5189 (MagickRealType) GetPixelAlpha(image,q);
5190 if (fabs(2.0*pixel.alpha) < quantum_threshold)
5191 pixel.alpha=(MagickRealType)
5192 GetPixelAlpha(image,p);
5193 else
5194 pixel.alpha=GetPixelAlpha(image,p)+
5195 (pixel.alpha*amount);
5196 SetPixelAlpha(unsharp_image,
5197 ClampToQuantum(pixel.alpha),q);
5198 }
cristyed231572011-07-14 02:18:59 +00005199 p+=GetPixelChannels(image);
5200 q+=GetPixelChannels(unsharp_image);
cristy3ed852e2009-09-05 21:47:34 +00005201 }
5202 if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
5203 status=MagickFalse;
5204 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5205 {
5206 MagickBooleanType
5207 proceed;
5208
cristyb5d5f722009-11-04 03:03:49 +00005209#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00005210 #pragma omp critical (MagickCore_UnsharpMaskImage)
cristy3ed852e2009-09-05 21:47:34 +00005211#endif
5212 proceed=SetImageProgress(image,SharpenImageTag,progress++,image->rows);
5213 if (proceed == MagickFalse)
5214 status=MagickFalse;
5215 }
5216 }
5217 unsharp_image->type=image->type;
5218 unsharp_view=DestroyCacheView(unsharp_view);
5219 image_view=DestroyCacheView(image_view);
5220 if (status == MagickFalse)
5221 unsharp_image=DestroyImage(unsharp_image);
5222 return(unsharp_image);
5223}