blob: 57f74815ec1a977a622955ec9cbea75c862bc79e [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");
1250 convolve_image=CloneImage(image,0,0,MagickTrue,exception);
1251 if (convolve_image == (Image *) NULL)
1252 return((Image *) NULL);
1253 if (SetImageStorageClass(convolve_image,DirectClass) == MagickFalse)
1254 {
1255 InheritException(exception,&convolve_image->exception);
1256 convolve_image=DestroyImage(convolve_image);
1257 return((Image *) NULL);
1258 }
1259 if (image->debug != MagickFalse)
1260 {
1261 char
1262 format[MaxTextExtent],
1263 *message;
1264
cristy117ff172010-08-15 21:35:32 +00001265 register const double
1266 *k;
1267
cristy4e154852011-07-14 13:28:53 +00001268 register ssize_t
1269 u;
1270
cristybb503372010-05-27 20:51:26 +00001271 ssize_t
cristyfccdab92009-11-30 16:43:57 +00001272 v;
1273
cristyfccdab92009-11-30 16:43:57 +00001274 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00001275 " ConvolveImage with %.20gx%.20g kernel:",(double) width,(double)
1276 width);
cristyfccdab92009-11-30 16:43:57 +00001277 message=AcquireString("");
1278 k=kernel;
cristybb503372010-05-27 20:51:26 +00001279 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001280 {
1281 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00001282 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristyfccdab92009-11-30 16:43:57 +00001283 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00001284 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001285 {
cristyb51dff52011-05-19 16:55:47 +00001286 (void) FormatLocaleString(format,MaxTextExtent,"%g ",*k++);
cristyfccdab92009-11-30 16:43:57 +00001287 (void) ConcatenateString(&message,format);
1288 }
1289 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
1290 }
1291 message=DestroyString(message);
1292 }
1293 /*
1294 Normalize kernel.
1295 */
1296 normal_kernel=(double *) AcquireQuantumMemory(width*width,
1297 sizeof(*normal_kernel));
1298 if (normal_kernel == (double *) NULL)
1299 {
1300 convolve_image=DestroyImage(convolve_image);
1301 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1302 }
1303 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00001304 for (i=0; i < (ssize_t) (width*width); i++)
cristyfccdab92009-11-30 16:43:57 +00001305 gamma+=kernel[i];
1306 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristybb503372010-05-27 20:51:26 +00001307 for (i=0; i < (ssize_t) (width*width); i++)
cristyfccdab92009-11-30 16:43:57 +00001308 normal_kernel[i]=gamma*kernel[i];
1309 /*
1310 Convolve image.
1311 */
1312 status=MagickTrue;
1313 progress=0;
cristyfccdab92009-11-30 16:43:57 +00001314 image_view=AcquireCacheView(image);
1315 convolve_view=AcquireCacheView(convolve_image);
1316#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy175653e2011-07-10 23:13:34 +00001317 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristyfccdab92009-11-30 16:43:57 +00001318#endif
cristybb503372010-05-27 20:51:26 +00001319 for (y=0; y < (ssize_t) image->rows; y++)
cristyfccdab92009-11-30 16:43:57 +00001320 {
cristy4c08aed2011-07-01 19:47:50 +00001321 register const Quantum
cristyfccdab92009-11-30 16:43:57 +00001322 *restrict p;
1323
cristy4c08aed2011-07-01 19:47:50 +00001324 register Quantum
cristyfccdab92009-11-30 16:43:57 +00001325 *restrict q;
1326
cristy117ff172010-08-15 21:35:32 +00001327 register ssize_t
1328 x;
1329
cristy4e154852011-07-14 13:28:53 +00001330 ssize_t
cristyed231572011-07-14 02:18:59 +00001331 channels,
1332 convolve_channels;
1333
cristyfccdab92009-11-30 16:43:57 +00001334 if (status == MagickFalse)
1335 continue;
cristyce889302010-06-30 19:16:36 +00001336 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
1337 (width/2L),image->columns+width,width,exception);
cristyfccdab92009-11-30 16:43:57 +00001338 q=GetCacheViewAuthenticPixels(convolve_view,0,y,convolve_image->columns,1,
1339 exception);
cristy4c08aed2011-07-01 19:47:50 +00001340 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristyfccdab92009-11-30 16:43:57 +00001341 {
1342 status=MagickFalse;
1343 continue;
1344 }
cristyed231572011-07-14 02:18:59 +00001345 channels=GetPixelChannels(image);
1346 convolve_channels=GetPixelChannels(convolve_image);
cristybb503372010-05-27 20:51:26 +00001347 for (x=0; x < (ssize_t) image->columns; x++)
cristyfccdab92009-11-30 16:43:57 +00001348 {
cristybb503372010-05-27 20:51:26 +00001349 register ssize_t
cristyed231572011-07-14 02:18:59 +00001350 i;
cristyfccdab92009-11-30 16:43:57 +00001351
cristyed231572011-07-14 02:18:59 +00001352 for (i=0; i < (ssize_t) channels; i++)
1353 {
cristyed231572011-07-14 02:18:59 +00001354 MagickRealType
cristy4e154852011-07-14 13:28:53 +00001355 alpha,
1356 gamma,
cristyed231572011-07-14 02:18:59 +00001357 pixel;
1358
1359 PixelChannel
1360 channel;
1361
1362 PixelTrait
1363 convolve_traits,
1364 traits;
1365
1366 register const double
1367 *restrict k;
1368
1369 register const Quantum
1370 *restrict kernel_pixels;
1371
1372 register ssize_t
1373 u;
1374
1375 ssize_t
1376 v;
1377
cristyed231572011-07-14 02:18:59 +00001378 traits=GetPixelChannelMapTraits(image,i);
cristy4e154852011-07-14 13:28:53 +00001379 if (traits == UndefinedPixelTrait)
cristyed231572011-07-14 02:18:59 +00001380 continue;
cristy4e154852011-07-14 13:28:53 +00001381 channel=GetPixelChannelMapChannel(image,i);
1382 convolve_traits=GetPixelChannelMapTraits(convolve_image,channel);
1383 if (convolve_traits == UndefinedPixelTrait)
1384 continue;
1385 if ((convolve_traits & CopyPixelTrait) != 0)
1386 {
cristyf8500872011-07-14 14:02:41 +00001387 const size_t
1388 center;
cristy4e154852011-07-14 13:28:53 +00001389
cristyf8500872011-07-14 14:02:41 +00001390 center=((image->columns+width)*width/2)*channels+i;
1391 SetPixelChannel(convolve_image,channel,p[center],q);
cristy4e154852011-07-14 13:28:53 +00001392 continue;
1393 }
cristyed231572011-07-14 02:18:59 +00001394 k=normal_kernel;
1395 kernel_pixels=p;
cristy4e154852011-07-14 13:28:53 +00001396 pixel=image->bias;
cristyc8698832011-07-14 13:33:20 +00001397 if ((GetPixelAlphaTraits(image) == UndefinedPixelTrait) ||
cristyed231572011-07-14 02:18:59 +00001398 (image->matte == MagickFalse))
cristyfccdab92009-11-30 16:43:57 +00001399 {
cristyed231572011-07-14 02:18:59 +00001400 /*
cristy4e154852011-07-14 13:28:53 +00001401 No alpha blending.
cristyed231572011-07-14 02:18:59 +00001402 */
1403 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001404 {
cristyed231572011-07-14 02:18:59 +00001405 for (u=0; u < (ssize_t) width; u++)
cristy175653e2011-07-10 23:13:34 +00001406 {
cristyed231572011-07-14 02:18:59 +00001407 pixel+=(*k)*kernel_pixels[u*channels+i];
1408 k++;
cristy175653e2011-07-10 23:13:34 +00001409 }
cristyed231572011-07-14 02:18:59 +00001410 kernel_pixels+=(image->columns+width)*channels;
cristy175653e2011-07-10 23:13:34 +00001411 }
cristy4e154852011-07-14 13:28:53 +00001412 SetPixelChannel(convolve_image,channel,ClampToQuantum(pixel),q);
1413 continue;
cristyed231572011-07-14 02:18:59 +00001414 }
cristy4e154852011-07-14 13:28:53 +00001415 /*
1416 Alpha blending.
1417 */
1418 gamma=0.0;
1419 for (v=0; v < (ssize_t) width; v++)
1420 {
1421 for (u=0; u < (ssize_t) width; u++)
1422 {
1423 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,
1424 kernel_pixels+u*channels));
1425 if ((traits & BlendPixelTrait) == 0)
1426 pixel+=(*k)*kernel_pixels[u*channels+i];
1427 else
1428 pixel+=(*k)*alpha*kernel_pixels[u*channels+i];
1429 gamma+=(*k)*alpha;
1430 k++;
1431 }
1432 kernel_pixels+=(image->columns+width)*channels;
1433 }
1434 if ((convolve_traits & BlendPixelTrait) == 0)
1435 SetPixelChannel(convolve_image,channel,ClampToQuantum(pixel),q);
cristyed231572011-07-14 02:18:59 +00001436 else
cristyfccdab92009-11-30 16:43:57 +00001437 {
cristyed231572011-07-14 02:18:59 +00001438 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy4e154852011-07-14 13:28:53 +00001439 SetPixelChannel(convolve_image,channel,ClampToQuantum(gamma*pixel),
1440 q);
cristyed231572011-07-14 02:18:59 +00001441 }
1442 }
1443 p+=channels;
1444 q+=convolve_channels;
cristyfccdab92009-11-30 16:43:57 +00001445 }
cristyed231572011-07-14 02:18:59 +00001446 if (SyncCacheViewAuthenticPixels(convolve_view,exception) == MagickFalse)
cristyfccdab92009-11-30 16:43:57 +00001447 status=MagickFalse;
1448 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1449 {
1450 MagickBooleanType
1451 proceed;
1452
1453#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00001454 #pragma omp critical (MagickCore_ConvolveImage)
cristyfccdab92009-11-30 16:43:57 +00001455#endif
1456 proceed=SetImageProgress(image,ConvolveImageTag,progress++,image->rows);
1457 if (proceed == MagickFalse)
1458 status=MagickFalse;
1459 }
1460 }
1461 convolve_image->type=image->type;
1462 convolve_view=DestroyCacheView(convolve_view);
1463 image_view=DestroyCacheView(image_view);
1464 normal_kernel=(double *) RelinquishMagickMemory(normal_kernel);
1465 if (status == MagickFalse)
1466 convolve_image=DestroyImage(convolve_image);
1467 return(convolve_image);
1468}
1469
1470/*
1471%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1472% %
1473% %
1474% %
cristy3ed852e2009-09-05 21:47:34 +00001475% D e s p e c k l e I m a g e %
1476% %
1477% %
1478% %
1479%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1480%
1481% DespeckleImage() reduces the speckle noise in an image while perserving the
1482% edges of the original image.
1483%
1484% The format of the DespeckleImage method is:
1485%
1486% Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1487%
1488% A description of each parameter follows:
1489%
1490% o image: the image.
1491%
1492% o exception: return any errors or warnings in this structure.
1493%
1494*/
1495
cristybb503372010-05-27 20:51:26 +00001496static void Hull(const ssize_t x_offset,const ssize_t y_offset,
1497 const size_t columns,const size_t rows,Quantum *f,Quantum *g,
cristy3ed852e2009-09-05 21:47:34 +00001498 const int polarity)
1499{
cristy3ed852e2009-09-05 21:47:34 +00001500 MagickRealType
1501 v;
1502
cristy3ed852e2009-09-05 21:47:34 +00001503 register Quantum
1504 *p,
1505 *q,
1506 *r,
1507 *s;
1508
cristy117ff172010-08-15 21:35:32 +00001509 register ssize_t
1510 x;
1511
1512 ssize_t
1513 y;
1514
cristy3ed852e2009-09-05 21:47:34 +00001515 assert(f != (Quantum *) NULL);
1516 assert(g != (Quantum *) NULL);
1517 p=f+(columns+2);
1518 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001519 r=p+(y_offset*((ssize_t) columns+2)+x_offset);
1520 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001521 {
1522 p++;
1523 q++;
1524 r++;
1525 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001526 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001527 {
1528 v=(MagickRealType) (*p);
1529 if ((MagickRealType) *r >= (v+(MagickRealType) ScaleCharToQuantum(2)))
1530 v+=ScaleCharToQuantum(1);
1531 *q=(Quantum) v;
1532 p++;
1533 q++;
1534 r++;
1535 }
1536 else
cristybb503372010-05-27 20:51:26 +00001537 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001538 {
1539 v=(MagickRealType) (*p);
1540 if ((MagickRealType) *r <= (v-(MagickRealType) ScaleCharToQuantum(2)))
cristybb503372010-05-27 20:51:26 +00001541 v-=(ssize_t) ScaleCharToQuantum(1);
cristy3ed852e2009-09-05 21:47:34 +00001542 *q=(Quantum) v;
1543 p++;
1544 q++;
1545 r++;
1546 }
1547 p++;
1548 q++;
1549 r++;
1550 }
1551 p=f+(columns+2);
1552 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001553 r=q+(y_offset*((ssize_t) columns+2)+x_offset);
1554 s=q-(y_offset*((ssize_t) columns+2)+x_offset);
1555 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001556 {
1557 p++;
1558 q++;
1559 r++;
1560 s++;
1561 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001562 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001563 {
1564 v=(MagickRealType) (*q);
1565 if (((MagickRealType) *s >=
1566 (v+(MagickRealType) ScaleCharToQuantum(2))) &&
1567 ((MagickRealType) *r > v))
1568 v+=ScaleCharToQuantum(1);
1569 *p=(Quantum) v;
1570 p++;
1571 q++;
1572 r++;
1573 s++;
1574 }
1575 else
cristybb503372010-05-27 20:51:26 +00001576 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001577 {
1578 v=(MagickRealType) (*q);
1579 if (((MagickRealType) *s <=
1580 (v-(MagickRealType) ScaleCharToQuantum(2))) &&
1581 ((MagickRealType) *r < v))
1582 v-=(MagickRealType) ScaleCharToQuantum(1);
1583 *p=(Quantum) v;
1584 p++;
1585 q++;
1586 r++;
1587 s++;
1588 }
1589 p++;
1590 q++;
1591 r++;
1592 s++;
1593 }
1594}
1595
1596MagickExport Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1597{
1598#define DespeckleImageTag "Despeckle/Image"
1599
cristy2407fc22009-09-11 00:55:25 +00001600 CacheView
1601 *despeckle_view,
1602 *image_view;
1603
cristy3ed852e2009-09-05 21:47:34 +00001604 Image
1605 *despeckle_image;
1606
cristy3ed852e2009-09-05 21:47:34 +00001607 MagickBooleanType
1608 status;
1609
cristya58c3172011-02-19 19:23:11 +00001610 register ssize_t
1611 i;
1612
cristy3ed852e2009-09-05 21:47:34 +00001613 Quantum
cristy65b9f392011-02-22 14:22:54 +00001614 *restrict buffers,
1615 *restrict pixels;
cristy3ed852e2009-09-05 21:47:34 +00001616
1617 size_t
cristya58c3172011-02-19 19:23:11 +00001618 length,
1619 number_channels;
cristy117ff172010-08-15 21:35:32 +00001620
cristybb503372010-05-27 20:51:26 +00001621 static const ssize_t
cristy691a29e2009-09-11 00:44:10 +00001622 X[4] = {0, 1, 1,-1},
1623 Y[4] = {1, 0, 1, 1};
cristy3ed852e2009-09-05 21:47:34 +00001624
cristy3ed852e2009-09-05 21:47:34 +00001625 /*
1626 Allocate despeckled image.
1627 */
1628 assert(image != (const Image *) NULL);
1629 assert(image->signature == MagickSignature);
1630 if (image->debug != MagickFalse)
1631 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1632 assert(exception != (ExceptionInfo *) NULL);
1633 assert(exception->signature == MagickSignature);
1634 despeckle_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1635 exception);
1636 if (despeckle_image == (Image *) NULL)
1637 return((Image *) NULL);
1638 if (SetImageStorageClass(despeckle_image,DirectClass) == MagickFalse)
1639 {
1640 InheritException(exception,&despeckle_image->exception);
1641 despeckle_image=DestroyImage(despeckle_image);
1642 return((Image *) NULL);
1643 }
1644 /*
1645 Allocate image buffers.
1646 */
1647 length=(size_t) ((image->columns+2)*(image->rows+2));
cristy65b9f392011-02-22 14:22:54 +00001648 pixels=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1649 buffers=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1650 if ((pixels == (Quantum *) NULL) || (buffers == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001651 {
cristy65b9f392011-02-22 14:22:54 +00001652 if (buffers != (Quantum *) NULL)
1653 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1654 if (pixels != (Quantum *) NULL)
1655 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001656 despeckle_image=DestroyImage(despeckle_image);
1657 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1658 }
1659 /*
1660 Reduce speckle in the image.
1661 */
1662 status=MagickTrue;
cristy109695a2011-02-19 19:38:14 +00001663 number_channels=(size_t) (image->colorspace == CMYKColorspace ? 5 : 4);
cristy3ed852e2009-09-05 21:47:34 +00001664 image_view=AcquireCacheView(image);
1665 despeckle_view=AcquireCacheView(despeckle_image);
cristy8df3d002011-02-19 19:40:59 +00001666 for (i=0; i < (ssize_t) number_channels; i++)
cristy3ed852e2009-09-05 21:47:34 +00001667 {
cristy3ed852e2009-09-05 21:47:34 +00001668 register Quantum
1669 *buffer,
1670 *pixel;
1671
cristyc1488b52011-02-19 18:54:15 +00001672 register ssize_t
cristya58c3172011-02-19 19:23:11 +00001673 k,
cristyc1488b52011-02-19 18:54:15 +00001674 x;
1675
cristy117ff172010-08-15 21:35:32 +00001676 ssize_t
1677 j,
1678 y;
1679
cristy3ed852e2009-09-05 21:47:34 +00001680 if (status == MagickFalse)
1681 continue;
cristy65b9f392011-02-22 14:22:54 +00001682 pixel=pixels;
cristy3ed852e2009-09-05 21:47:34 +00001683 (void) ResetMagickMemory(pixel,0,length*sizeof(*pixel));
cristy65b9f392011-02-22 14:22:54 +00001684 buffer=buffers;
cristybb503372010-05-27 20:51:26 +00001685 j=(ssize_t) image->columns+2;
1686 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001687 {
cristy4c08aed2011-07-01 19:47:50 +00001688 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001689 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001690
1691 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001692 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001693 break;
1694 j++;
cristybb503372010-05-27 20:51:26 +00001695 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001696 {
cristya58c3172011-02-19 19:23:11 +00001697 switch (i)
cristy3ed852e2009-09-05 21:47:34 +00001698 {
cristy4c08aed2011-07-01 19:47:50 +00001699 case 0: pixel[j]=GetPixelRed(image,p); break;
1700 case 1: pixel[j]=GetPixelGreen(image,p); break;
1701 case 2: pixel[j]=GetPixelBlue(image,p); break;
1702 case 3: pixel[j]=GetPixelAlpha(image,p); break;
1703 case 4: pixel[j]=GetPixelBlack(image,p); break;
cristy3ed852e2009-09-05 21:47:34 +00001704 default: break;
1705 }
cristyed231572011-07-14 02:18:59 +00001706 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001707 j++;
1708 }
1709 j++;
1710 }
cristy3ed852e2009-09-05 21:47:34 +00001711 (void) ResetMagickMemory(buffer,0,length*sizeof(*buffer));
cristya58c3172011-02-19 19:23:11 +00001712 for (k=0; k < 4; k++)
cristy3ed852e2009-09-05 21:47:34 +00001713 {
cristya58c3172011-02-19 19:23:11 +00001714 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,1);
1715 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);
cristy3ed852e2009-09-05 21:47:34 +00001718 }
cristybb503372010-05-27 20:51:26 +00001719 j=(ssize_t) image->columns+2;
1720 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001721 {
1722 MagickBooleanType
1723 sync;
1724
cristy4c08aed2011-07-01 19:47:50 +00001725 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001726 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001727
1728 q=GetCacheViewAuthenticPixels(despeckle_view,0,y,despeckle_image->columns,
1729 1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001730 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001731 break;
1732 j++;
cristybb503372010-05-27 20:51:26 +00001733 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001734 {
cristya58c3172011-02-19 19:23:11 +00001735 switch (i)
cristy3ed852e2009-09-05 21:47:34 +00001736 {
cristy4c08aed2011-07-01 19:47:50 +00001737 case 0: SetPixelRed(despeckle_image,pixel[j],q); break;
1738 case 1: SetPixelGreen(despeckle_image,pixel[j],q); break;
1739 case 2: SetPixelBlue(despeckle_image,pixel[j],q); break;
1740 case 3: SetPixelAlpha(despeckle_image,pixel[j],q); break;
1741 case 4: SetPixelBlack(despeckle_image,pixel[j],q); break;
cristy3ed852e2009-09-05 21:47:34 +00001742 default: break;
1743 }
cristyed231572011-07-14 02:18:59 +00001744 q+=GetPixelChannels(despeckle_image);
cristy3ed852e2009-09-05 21:47:34 +00001745 j++;
1746 }
1747 sync=SyncCacheViewAuthenticPixels(despeckle_view,exception);
1748 if (sync == MagickFalse)
1749 {
1750 status=MagickFalse;
1751 break;
1752 }
1753 j++;
1754 }
1755 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1756 {
1757 MagickBooleanType
1758 proceed;
1759
cristya58c3172011-02-19 19:23:11 +00001760 proceed=SetImageProgress(image,DespeckleImageTag,(MagickOffsetType) i,
1761 number_channels);
cristy3ed852e2009-09-05 21:47:34 +00001762 if (proceed == MagickFalse)
1763 status=MagickFalse;
1764 }
1765 }
1766 despeckle_view=DestroyCacheView(despeckle_view);
1767 image_view=DestroyCacheView(image_view);
cristy65b9f392011-02-22 14:22:54 +00001768 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1769 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001770 despeckle_image->type=image->type;
1771 if (status == MagickFalse)
1772 despeckle_image=DestroyImage(despeckle_image);
1773 return(despeckle_image);
1774}
1775
1776/*
1777%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1778% %
1779% %
1780% %
1781% E d g e I m a g e %
1782% %
1783% %
1784% %
1785%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1786%
1787% EdgeImage() finds edges in an image. Radius defines the radius of the
1788% convolution filter. Use a radius of 0 and EdgeImage() selects a suitable
1789% radius for you.
1790%
1791% The format of the EdgeImage method is:
1792%
1793% Image *EdgeImage(const Image *image,const double radius,
1794% ExceptionInfo *exception)
1795%
1796% A description of each parameter follows:
1797%
1798% o image: the image.
1799%
1800% o radius: the radius of the pixel neighborhood.
1801%
1802% o exception: return any errors or warnings in this structure.
1803%
1804*/
1805MagickExport Image *EdgeImage(const Image *image,const double radius,
1806 ExceptionInfo *exception)
1807{
1808 Image
1809 *edge_image;
1810
1811 double
1812 *kernel;
1813
cristybb503372010-05-27 20:51:26 +00001814 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001815 i;
1816
cristybb503372010-05-27 20:51:26 +00001817 size_t
cristy3ed852e2009-09-05 21:47:34 +00001818 width;
1819
1820 assert(image != (const Image *) NULL);
1821 assert(image->signature == MagickSignature);
1822 if (image->debug != MagickFalse)
1823 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1824 assert(exception != (ExceptionInfo *) NULL);
1825 assert(exception->signature == MagickSignature);
1826 width=GetOptimalKernelWidth1D(radius,0.5);
1827 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
1828 if (kernel == (double *) NULL)
1829 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00001830 for (i=0; i < (ssize_t) (width*width); i++)
cristy3ed852e2009-09-05 21:47:34 +00001831 kernel[i]=(-1.0);
1832 kernel[i/2]=(double) (width*width-1.0);
1833 edge_image=ConvolveImage(image,width,kernel,exception);
1834 kernel=(double *) RelinquishMagickMemory(kernel);
1835 return(edge_image);
1836}
1837
1838/*
1839%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1840% %
1841% %
1842% %
1843% E m b o s s I m a g e %
1844% %
1845% %
1846% %
1847%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1848%
1849% EmbossImage() returns a grayscale image with a three-dimensional effect.
1850% We convolve the image with a Gaussian operator of the given radius and
1851% standard deviation (sigma). For reasonable results, radius should be
1852% larger than sigma. Use a radius of 0 and Emboss() selects a suitable
1853% radius for you.
1854%
1855% The format of the EmbossImage method is:
1856%
1857% Image *EmbossImage(const Image *image,const double radius,
1858% const double sigma,ExceptionInfo *exception)
1859%
1860% A description of each parameter follows:
1861%
1862% o image: the image.
1863%
1864% o radius: the radius of the pixel neighborhood.
1865%
1866% o sigma: the standard deviation of the Gaussian, in pixels.
1867%
1868% o exception: return any errors or warnings in this structure.
1869%
1870*/
1871MagickExport Image *EmbossImage(const Image *image,const double radius,
1872 const double sigma,ExceptionInfo *exception)
1873{
1874 double
1875 *kernel;
1876
1877 Image
1878 *emboss_image;
1879
cristybb503372010-05-27 20:51:26 +00001880 register ssize_t
cristy47e00502009-12-17 19:19:57 +00001881 i;
1882
cristybb503372010-05-27 20:51:26 +00001883 size_t
cristy3ed852e2009-09-05 21:47:34 +00001884 width;
1885
cristy117ff172010-08-15 21:35:32 +00001886 ssize_t
1887 j,
1888 k,
1889 u,
1890 v;
1891
cristy3ed852e2009-09-05 21:47:34 +00001892 assert(image != (Image *) NULL);
1893 assert(image->signature == MagickSignature);
1894 if (image->debug != MagickFalse)
1895 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1896 assert(exception != (ExceptionInfo *) NULL);
1897 assert(exception->signature == MagickSignature);
1898 width=GetOptimalKernelWidth2D(radius,sigma);
1899 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
1900 if (kernel == (double *) NULL)
1901 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00001902 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +00001903 k=j;
1904 i=0;
1905 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00001906 {
cristy47e00502009-12-17 19:19:57 +00001907 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00001908 {
cristy4205a3c2010-09-12 20:19:59 +00001909 kernel[i]=(double) (((u < 0) || (v < 0) ? -8.0 : 8.0)*
cristy47e00502009-12-17 19:19:57 +00001910 exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
cristy4205a3c2010-09-12 20:19:59 +00001911 (2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +00001912 if (u != k)
cristy3ed852e2009-09-05 21:47:34 +00001913 kernel[i]=0.0;
1914 i++;
1915 }
cristy47e00502009-12-17 19:19:57 +00001916 k--;
cristy3ed852e2009-09-05 21:47:34 +00001917 }
1918 emboss_image=ConvolveImage(image,width,kernel,exception);
1919 if (emboss_image != (Image *) NULL)
1920 (void) EqualizeImage(emboss_image);
1921 kernel=(double *) RelinquishMagickMemory(kernel);
1922 return(emboss_image);
1923}
1924
1925/*
1926%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1927% %
1928% %
1929% %
cristy56a9e512010-01-06 18:18:55 +00001930% F i l t e r I m a g e %
1931% %
1932% %
1933% %
1934%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1935%
1936% FilterImage() applies a custom convolution kernel to the image.
1937%
1938% The format of the FilterImage method is:
1939%
cristy2be15382010-01-21 02:38:03 +00001940% Image *FilterImage(const Image *image,const KernelInfo *kernel,
cristy56a9e512010-01-06 18:18:55 +00001941% ExceptionInfo *exception)
cristy56a9e512010-01-06 18:18:55 +00001942%
1943% A description of each parameter follows:
1944%
1945% o image: the image.
1946%
cristy56a9e512010-01-06 18:18:55 +00001947% o kernel: the filtering kernel.
1948%
1949% o exception: return any errors or warnings in this structure.
1950%
1951*/
cristyf4ad9df2011-07-08 16:49:03 +00001952MagickExport Image *FilterImage(const Image *image,
1953 const KernelInfo *kernel,ExceptionInfo *exception)
cristy56a9e512010-01-06 18:18:55 +00001954{
1955#define FilterImageTag "Filter/Image"
1956
1957 CacheView
1958 *filter_view,
1959 *image_view;
1960
cristy56a9e512010-01-06 18:18:55 +00001961 Image
1962 *filter_image;
1963
cristy56a9e512010-01-06 18:18:55 +00001964 MagickBooleanType
1965 status;
1966
cristybb503372010-05-27 20:51:26 +00001967 MagickOffsetType
1968 progress;
1969
cristy4c08aed2011-07-01 19:47:50 +00001970 PixelInfo
cristy56a9e512010-01-06 18:18:55 +00001971 bias;
1972
cristybb503372010-05-27 20:51:26 +00001973 ssize_t
1974 y;
1975
cristy56a9e512010-01-06 18:18:55 +00001976 /*
1977 Initialize filter image attributes.
1978 */
1979 assert(image != (Image *) NULL);
1980 assert(image->signature == MagickSignature);
1981 if (image->debug != MagickFalse)
1982 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1983 assert(exception != (ExceptionInfo *) NULL);
1984 assert(exception->signature == MagickSignature);
1985 if ((kernel->width % 2) == 0)
1986 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
1987 filter_image=CloneImage(image,0,0,MagickTrue,exception);
1988 if (filter_image == (Image *) NULL)
1989 return((Image *) NULL);
1990 if (SetImageStorageClass(filter_image,DirectClass) == MagickFalse)
1991 {
1992 InheritException(exception,&filter_image->exception);
1993 filter_image=DestroyImage(filter_image);
1994 return((Image *) NULL);
1995 }
1996 if (image->debug != MagickFalse)
1997 {
1998 char
1999 format[MaxTextExtent],
2000 *message;
2001
cristy117ff172010-08-15 21:35:32 +00002002 register const double
2003 *k;
2004
cristybb503372010-05-27 20:51:26 +00002005 ssize_t
cristy56a9e512010-01-06 18:18:55 +00002006 u,
2007 v;
2008
cristy56a9e512010-01-06 18:18:55 +00002009 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00002010 " FilterImage with %.20gx%.20g kernel:",(double) kernel->width,(double)
2011 kernel->height);
cristy56a9e512010-01-06 18:18:55 +00002012 message=AcquireString("");
2013 k=kernel->values;
cristybb503372010-05-27 20:51:26 +00002014 for (v=0; v < (ssize_t) kernel->height; v++)
cristy56a9e512010-01-06 18:18:55 +00002015 {
2016 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00002017 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy56a9e512010-01-06 18:18:55 +00002018 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00002019 for (u=0; u < (ssize_t) kernel->width; u++)
cristy56a9e512010-01-06 18:18:55 +00002020 {
cristyb51dff52011-05-19 16:55:47 +00002021 (void) FormatLocaleString(format,MaxTextExtent,"%g ",*k++);
cristy56a9e512010-01-06 18:18:55 +00002022 (void) ConcatenateString(&message,format);
2023 }
2024 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
2025 }
2026 message=DestroyString(message);
2027 }
cristy36826ab2010-03-06 01:29:30 +00002028 status=AccelerateConvolveImage(image,kernel,filter_image,exception);
cristyd43a46b2010-01-21 02:13:41 +00002029 if (status == MagickTrue)
2030 return(filter_image);
cristy56a9e512010-01-06 18:18:55 +00002031 /*
2032 Filter image.
2033 */
2034 status=MagickTrue;
2035 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00002036 GetPixelInfo(image,&bias);
2037 SetPixelInfoBias(image,&bias);
cristy56a9e512010-01-06 18:18:55 +00002038 image_view=AcquireCacheView(image);
2039 filter_view=AcquireCacheView(filter_image);
2040#if defined(MAGICKCORE_OPENMP_SUPPORT)
2041 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2042#endif
cristybb503372010-05-27 20:51:26 +00002043 for (y=0; y < (ssize_t) image->rows; y++)
cristy56a9e512010-01-06 18:18:55 +00002044 {
2045 MagickBooleanType
2046 sync;
2047
cristy4c08aed2011-07-01 19:47:50 +00002048 register const Quantum
cristy56a9e512010-01-06 18:18:55 +00002049 *restrict p;
2050
cristy4c08aed2011-07-01 19:47:50 +00002051 register Quantum
cristy56a9e512010-01-06 18:18:55 +00002052 *restrict q;
2053
cristy117ff172010-08-15 21:35:32 +00002054 register ssize_t
2055 x;
2056
cristy56a9e512010-01-06 18:18:55 +00002057 if (status == MagickFalse)
2058 continue;
cristybb503372010-05-27 20:51:26 +00002059 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) kernel->width/2L),
cristy117ff172010-08-15 21:35:32 +00002060 y-(ssize_t) (kernel->height/2L),image->columns+kernel->width,
2061 kernel->height,exception);
cristy56a9e512010-01-06 18:18:55 +00002062 q=GetCacheViewAuthenticPixels(filter_view,0,y,filter_image->columns,1,
2063 exception);
cristy4c08aed2011-07-01 19:47:50 +00002064 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy56a9e512010-01-06 18:18:55 +00002065 {
2066 status=MagickFalse;
2067 continue;
2068 }
cristybb503372010-05-27 20:51:26 +00002069 for (x=0; x < (ssize_t) image->columns; x++)
cristy56a9e512010-01-06 18:18:55 +00002070 {
cristy4c08aed2011-07-01 19:47:50 +00002071 PixelInfo
cristy56a9e512010-01-06 18:18:55 +00002072 pixel;
2073
2074 register const double
2075 *restrict k;
2076
cristy4c08aed2011-07-01 19:47:50 +00002077 register const Quantum
cristy56a9e512010-01-06 18:18:55 +00002078 *restrict kernel_pixels;
2079
cristybb503372010-05-27 20:51:26 +00002080 register ssize_t
cristy56a9e512010-01-06 18:18:55 +00002081 u;
2082
cristy117ff172010-08-15 21:35:32 +00002083 ssize_t
2084 v;
2085
cristy56a9e512010-01-06 18:18:55 +00002086 pixel=bias;
cristy36826ab2010-03-06 01:29:30 +00002087 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002088 kernel_pixels=p;
cristyed231572011-07-14 02:18:59 +00002089 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) ||
cristy5ce8df82011-07-07 14:52:23 +00002090 (image->matte == MagickFalse))
cristy56a9e512010-01-06 18:18:55 +00002091 {
cristybb503372010-05-27 20:51:26 +00002092 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002093 {
cristybb503372010-05-27 20:51:26 +00002094 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002095 {
cristy4c08aed2011-07-01 19:47:50 +00002096 pixel.red+=(*k)*GetPixelRed(image,kernel_pixels+u*
cristyed231572011-07-14 02:18:59 +00002097 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00002098 pixel.green+=(*k)*GetPixelGreen(image,kernel_pixels+u*
cristyed231572011-07-14 02:18:59 +00002099 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00002100 pixel.blue+=(*k)*GetPixelBlue(image,kernel_pixels+u*
cristyed231572011-07-14 02:18:59 +00002101 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00002102 if (image->colorspace == CMYKColorspace)
2103 pixel.black+=(*k)*GetPixelBlack(image,kernel_pixels+u*
cristyed231572011-07-14 02:18:59 +00002104 GetPixelChannels(image));
cristy56a9e512010-01-06 18:18:55 +00002105 k++;
2106 }
cristy4c08aed2011-07-01 19:47:50 +00002107 kernel_pixels+=(image->columns+kernel->width)*
cristyed231572011-07-14 02:18:59 +00002108 GetPixelChannels(image);
cristy56a9e512010-01-06 18:18:55 +00002109 }
cristyed231572011-07-14 02:18:59 +00002110 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002111 SetPixelRed(filter_image,ClampToQuantum(pixel.red),q);
cristyed231572011-07-14 02:18:59 +00002112 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002113 SetPixelGreen(filter_image,ClampToQuantum(pixel.green),q);
cristyed231572011-07-14 02:18:59 +00002114 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002115 SetPixelBlue(filter_image,ClampToQuantum(pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00002116 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002117 (image->colorspace == CMYKColorspace))
2118 SetPixelBlack(filter_image,ClampToQuantum(pixel.black),q);
cristyed231572011-07-14 02:18:59 +00002119 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy56a9e512010-01-06 18:18:55 +00002120 {
cristy36826ab2010-03-06 01:29:30 +00002121 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002122 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00002123 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002124 {
cristybb503372010-05-27 20:51:26 +00002125 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002126 {
cristy4c08aed2011-07-01 19:47:50 +00002127 pixel.alpha+=(*k)*GetPixelRed(image,kernel_pixels+u*
cristyed231572011-07-14 02:18:59 +00002128 GetPixelChannels(image));
cristy56a9e512010-01-06 18:18:55 +00002129 k++;
2130 }
cristy4c08aed2011-07-01 19:47:50 +00002131 kernel_pixels+=(image->columns+kernel->width)*
cristyed231572011-07-14 02:18:59 +00002132 GetPixelChannels(image);
cristy56a9e512010-01-06 18:18:55 +00002133 }
cristy4c08aed2011-07-01 19:47:50 +00002134 SetPixelAlpha(filter_image,ClampToQuantum(pixel.alpha),q);
cristy56a9e512010-01-06 18:18:55 +00002135 }
2136 }
2137 else
2138 {
2139 MagickRealType
2140 alpha,
2141 gamma;
2142
2143 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00002144 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002145 {
cristybb503372010-05-27 20:51:26 +00002146 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002147 {
cristy4c08aed2011-07-01 19:47:50 +00002148 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,
cristyed231572011-07-14 02:18:59 +00002149 kernel_pixels+u*GetPixelChannels(image)));
cristy4c08aed2011-07-01 19:47:50 +00002150 pixel.red+=(*k)*alpha*GetPixelRed(image,kernel_pixels+u*
cristyed231572011-07-14 02:18:59 +00002151 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00002152 pixel.green+=(*k)*alpha*GetPixelGreen(image,kernel_pixels+u*
cristyed231572011-07-14 02:18:59 +00002153 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00002154 pixel.blue+=(*k)*alpha*GetPixelBlue(image,kernel_pixels+u*
cristyed231572011-07-14 02:18:59 +00002155 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00002156 if (image->colorspace == CMYKColorspace)
2157 pixel.black+=(*k)*alpha*GetPixelBlack(image,kernel_pixels+u*
cristyed231572011-07-14 02:18:59 +00002158 GetPixelChannels(image));
cristy56a9e512010-01-06 18:18:55 +00002159 gamma+=(*k)*alpha;
2160 k++;
2161 }
cristy5ce8df82011-07-07 14:52:23 +00002162 kernel_pixels+=(image->columns+kernel->width)*
cristyed231572011-07-14 02:18:59 +00002163 GetPixelChannels(image);
cristy56a9e512010-01-06 18:18:55 +00002164 }
2165 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +00002166 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002167 SetPixelRed(filter_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +00002168 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002169 SetPixelGreen(filter_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +00002170 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002171 SetPixelBlue(filter_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00002172 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy56a9e512010-01-06 18:18:55 +00002173 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002174 SetPixelBlack(filter_image,ClampToQuantum(gamma*pixel.black),q);
cristyed231572011-07-14 02:18:59 +00002175 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy56a9e512010-01-06 18:18:55 +00002176 {
cristy36826ab2010-03-06 01:29:30 +00002177 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002178 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00002179 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002180 {
cristybb503372010-05-27 20:51:26 +00002181 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002182 {
cristy4c08aed2011-07-01 19:47:50 +00002183 pixel.alpha+=(*k)*GetPixelAlpha(image,kernel_pixels+u*
cristyed231572011-07-14 02:18:59 +00002184 GetPixelChannels(image));
cristy56a9e512010-01-06 18:18:55 +00002185 k++;
2186 }
cristy4c08aed2011-07-01 19:47:50 +00002187 kernel_pixels+=(image->columns+kernel->width)*
cristyed231572011-07-14 02:18:59 +00002188 GetPixelChannels(image);
cristy56a9e512010-01-06 18:18:55 +00002189 }
cristy4c08aed2011-07-01 19:47:50 +00002190 SetPixelAlpha(filter_image,ClampToQuantum(pixel.alpha),q);
cristy56a9e512010-01-06 18:18:55 +00002191 }
2192 }
cristyed231572011-07-14 02:18:59 +00002193 p+=GetPixelChannels(image);
2194 q+=GetPixelChannels(filter_image);
cristy56a9e512010-01-06 18:18:55 +00002195 }
2196 sync=SyncCacheViewAuthenticPixels(filter_view,exception);
2197 if (sync == MagickFalse)
2198 status=MagickFalse;
2199 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2200 {
2201 MagickBooleanType
2202 proceed;
2203
2204#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00002205 #pragma omp critical (MagickCore_FilterImage)
cristy56a9e512010-01-06 18:18:55 +00002206#endif
2207 proceed=SetImageProgress(image,FilterImageTag,progress++,image->rows);
2208 if (proceed == MagickFalse)
2209 status=MagickFalse;
2210 }
2211 }
2212 filter_image->type=image->type;
2213 filter_view=DestroyCacheView(filter_view);
2214 image_view=DestroyCacheView(image_view);
cristy56a9e512010-01-06 18:18:55 +00002215 if (status == MagickFalse)
2216 filter_image=DestroyImage(filter_image);
2217 return(filter_image);
2218}
2219
2220/*
2221%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2222% %
2223% %
2224% %
cristy3ed852e2009-09-05 21:47:34 +00002225% G a u s s i a n B l u r I m a g e %
2226% %
2227% %
2228% %
2229%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2230%
2231% GaussianBlurImage() blurs an image. We convolve the image with a
2232% Gaussian operator of the given radius and standard deviation (sigma).
2233% For reasonable results, the radius should be larger than sigma. Use a
2234% radius of 0 and GaussianBlurImage() selects a suitable radius for you
2235%
2236% The format of the GaussianBlurImage method is:
2237%
2238% Image *GaussianBlurImage(const Image *image,onst double radius,
2239% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002240%
2241% A description of each parameter follows:
2242%
2243% o image: the image.
2244%
cristy3ed852e2009-09-05 21:47:34 +00002245% o radius: the radius of the Gaussian, in pixels, not counting the center
2246% pixel.
2247%
2248% o sigma: the standard deviation of the Gaussian, in pixels.
2249%
2250% o exception: return any errors or warnings in this structure.
2251%
2252*/
cristyf4ad9df2011-07-08 16:49:03 +00002253MagickExport Image *GaussianBlurImage(const Image *image,
2254 const double radius,const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002255{
2256 double
2257 *kernel;
2258
2259 Image
2260 *blur_image;
2261
cristybb503372010-05-27 20:51:26 +00002262 register ssize_t
cristy47e00502009-12-17 19:19:57 +00002263 i;
2264
cristybb503372010-05-27 20:51:26 +00002265 size_t
cristy3ed852e2009-09-05 21:47:34 +00002266 width;
2267
cristy117ff172010-08-15 21:35:32 +00002268 ssize_t
2269 j,
2270 u,
2271 v;
2272
cristy3ed852e2009-09-05 21:47:34 +00002273 assert(image != (const Image *) NULL);
2274 assert(image->signature == MagickSignature);
2275 if (image->debug != MagickFalse)
2276 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2277 assert(exception != (ExceptionInfo *) NULL);
2278 assert(exception->signature == MagickSignature);
2279 width=GetOptimalKernelWidth2D(radius,sigma);
2280 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2281 if (kernel == (double *) NULL)
2282 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00002283 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00002284 i=0;
cristy47e00502009-12-17 19:19:57 +00002285 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002286 {
cristy47e00502009-12-17 19:19:57 +00002287 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00002288 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
2289 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00002290 }
cristyf4ad9df2011-07-08 16:49:03 +00002291 blur_image=ConvolveImage(image,width,kernel,exception);
cristy3ed852e2009-09-05 21:47:34 +00002292 kernel=(double *) RelinquishMagickMemory(kernel);
2293 return(blur_image);
2294}
2295
2296/*
2297%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2298% %
2299% %
2300% %
cristy3ed852e2009-09-05 21:47:34 +00002301% M o t i o n B l u r I m a g e %
2302% %
2303% %
2304% %
2305%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2306%
2307% MotionBlurImage() simulates motion blur. We convolve the image with a
2308% Gaussian operator of the given radius and standard deviation (sigma).
2309% For reasonable results, radius should be larger than sigma. Use a
2310% radius of 0 and MotionBlurImage() selects a suitable radius for you.
2311% Angle gives the angle of the blurring motion.
2312%
2313% Andrew Protano contributed this effect.
2314%
2315% The format of the MotionBlurImage method is:
2316%
2317% Image *MotionBlurImage(const Image *image,const double radius,
2318% const double sigma,const double angle,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002319%
2320% A description of each parameter follows:
2321%
2322% o image: the image.
2323%
cristy3ed852e2009-09-05 21:47:34 +00002324% o radius: the radius of the Gaussian, in pixels, not counting
2325% the center pixel.
2326%
2327% o sigma: the standard deviation of the Gaussian, in pixels.
2328%
cristycee97112010-05-28 00:44:52 +00002329% o angle: Apply the effect along this angle.
cristy3ed852e2009-09-05 21:47:34 +00002330%
2331% o exception: return any errors or warnings in this structure.
2332%
2333*/
2334
cristybb503372010-05-27 20:51:26 +00002335static double *GetMotionBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +00002336{
cristy3ed852e2009-09-05 21:47:34 +00002337 double
cristy47e00502009-12-17 19:19:57 +00002338 *kernel,
cristy3ed852e2009-09-05 21:47:34 +00002339 normalize;
2340
cristybb503372010-05-27 20:51:26 +00002341 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002342 i;
2343
2344 /*
cristy47e00502009-12-17 19:19:57 +00002345 Generate a 1-D convolution kernel.
cristy3ed852e2009-09-05 21:47:34 +00002346 */
2347 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
2348 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
2349 if (kernel == (double *) NULL)
2350 return(kernel);
cristy3ed852e2009-09-05 21:47:34 +00002351 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00002352 for (i=0; i < (ssize_t) width; i++)
cristy47e00502009-12-17 19:19:57 +00002353 {
cristy4205a3c2010-09-12 20:19:59 +00002354 kernel[i]=(double) (exp((-((double) i*i)/(double) (2.0*MagickSigma*
2355 MagickSigma)))/(MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00002356 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +00002357 }
cristybb503372010-05-27 20:51:26 +00002358 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002359 kernel[i]/=normalize;
2360 return(kernel);
2361}
2362
cristyf4ad9df2011-07-08 16:49:03 +00002363MagickExport Image *MotionBlurImage(const Image *image,
2364 const double radius,const double sigma,const double angle,
2365 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002366{
cristyc4c8d132010-01-07 01:58:38 +00002367 CacheView
2368 *blur_view,
2369 *image_view;
2370
cristy3ed852e2009-09-05 21:47:34 +00002371 double
2372 *kernel;
2373
2374 Image
2375 *blur_image;
2376
cristy3ed852e2009-09-05 21:47:34 +00002377 MagickBooleanType
2378 status;
2379
cristybb503372010-05-27 20:51:26 +00002380 MagickOffsetType
2381 progress;
2382
cristy4c08aed2011-07-01 19:47:50 +00002383 PixelInfo
cristyddd82202009-11-03 20:14:50 +00002384 bias;
cristy3ed852e2009-09-05 21:47:34 +00002385
2386 OffsetInfo
2387 *offset;
2388
2389 PointInfo
2390 point;
2391
cristybb503372010-05-27 20:51:26 +00002392 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002393 i;
2394
cristybb503372010-05-27 20:51:26 +00002395 size_t
cristy3ed852e2009-09-05 21:47:34 +00002396 width;
2397
cristybb503372010-05-27 20:51:26 +00002398 ssize_t
2399 y;
2400
cristy3ed852e2009-09-05 21:47:34 +00002401 assert(image != (Image *) NULL);
2402 assert(image->signature == MagickSignature);
2403 if (image->debug != MagickFalse)
2404 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2405 assert(exception != (ExceptionInfo *) NULL);
2406 width=GetOptimalKernelWidth1D(radius,sigma);
2407 kernel=GetMotionBlurKernel(width,sigma);
2408 if (kernel == (double *) NULL)
2409 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2410 offset=(OffsetInfo *) AcquireQuantumMemory(width,sizeof(*offset));
2411 if (offset == (OffsetInfo *) NULL)
2412 {
2413 kernel=(double *) RelinquishMagickMemory(kernel);
2414 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2415 }
2416 blur_image=CloneImage(image,0,0,MagickTrue,exception);
2417 if (blur_image == (Image *) NULL)
2418 {
2419 kernel=(double *) RelinquishMagickMemory(kernel);
2420 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2421 return((Image *) NULL);
2422 }
2423 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
2424 {
2425 kernel=(double *) RelinquishMagickMemory(kernel);
2426 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2427 InheritException(exception,&blur_image->exception);
2428 blur_image=DestroyImage(blur_image);
2429 return((Image *) NULL);
2430 }
2431 point.x=(double) width*sin(DegreesToRadians(angle));
2432 point.y=(double) width*cos(DegreesToRadians(angle));
cristybb503372010-05-27 20:51:26 +00002433 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002434 {
cristybb503372010-05-27 20:51:26 +00002435 offset[i].x=(ssize_t) ceil((double) (i*point.y)/hypot(point.x,point.y)-0.5);
2436 offset[i].y=(ssize_t) ceil((double) (i*point.x)/hypot(point.x,point.y)-0.5);
cristy3ed852e2009-09-05 21:47:34 +00002437 }
2438 /*
2439 Motion blur image.
2440 */
2441 status=MagickTrue;
2442 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00002443 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00002444 image_view=AcquireCacheView(image);
2445 blur_view=AcquireCacheView(blur_image);
cristyb557a152011-02-22 12:14:30 +00002446#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00002447 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00002448#endif
cristybb503372010-05-27 20:51:26 +00002449 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002450 {
cristy4c08aed2011-07-01 19:47:50 +00002451 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002452 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002453
cristy117ff172010-08-15 21:35:32 +00002454 register ssize_t
2455 x;
2456
cristy3ed852e2009-09-05 21:47:34 +00002457 if (status == MagickFalse)
2458 continue;
2459 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
2460 exception);
cristy4c08aed2011-07-01 19:47:50 +00002461 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002462 {
2463 status=MagickFalse;
2464 continue;
2465 }
cristybb503372010-05-27 20:51:26 +00002466 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002467 {
cristy4c08aed2011-07-01 19:47:50 +00002468 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002469 qixel;
2470
2471 PixelPacket
2472 pixel;
2473
2474 register double
cristyc47d1f82009-11-26 01:44:43 +00002475 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00002476
cristybb503372010-05-27 20:51:26 +00002477 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002478 i;
2479
cristy3ed852e2009-09-05 21:47:34 +00002480 k=kernel;
cristyddd82202009-11-03 20:14:50 +00002481 qixel=bias;
cristyed231572011-07-14 02:18:59 +00002482 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00002483 {
cristybb503372010-05-27 20:51:26 +00002484 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002485 {
2486 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
2487 offset[i].y,&pixel,exception);
2488 qixel.red+=(*k)*pixel.red;
2489 qixel.green+=(*k)*pixel.green;
2490 qixel.blue+=(*k)*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00002491 qixel.alpha+=(*k)*pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00002492 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00002493 qixel.black+=(*k)*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00002494 k++;
2495 }
cristyed231572011-07-14 02:18:59 +00002496 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002497 SetPixelRed(blur_image,
2498 ClampToQuantum(qixel.red),q);
cristyed231572011-07-14 02:18:59 +00002499 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002500 SetPixelGreen(blur_image,
2501 ClampToQuantum(qixel.green),q);
cristyed231572011-07-14 02:18:59 +00002502 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002503 SetPixelBlue(blur_image,
2504 ClampToQuantum(qixel.blue),q);
cristyed231572011-07-14 02:18:59 +00002505 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002506 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002507 SetPixelBlack(blur_image,
2508 ClampToQuantum(qixel.black),q);
cristyed231572011-07-14 02:18:59 +00002509 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002510 SetPixelAlpha(blur_image,
2511 ClampToQuantum(qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00002512 }
2513 else
2514 {
2515 MagickRealType
2516 alpha,
2517 gamma;
2518
2519 alpha=0.0;
2520 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00002521 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002522 {
2523 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
2524 offset[i].y,&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00002525 alpha=(MagickRealType) (QuantumScale*pixel.alpha);
cristy3ed852e2009-09-05 21:47:34 +00002526 qixel.red+=(*k)*alpha*pixel.red;
2527 qixel.green+=(*k)*alpha*pixel.green;
2528 qixel.blue+=(*k)*alpha*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00002529 qixel.alpha+=(*k)*pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00002530 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00002531 qixel.black+=(*k)*alpha*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00002532 gamma+=(*k)*alpha;
2533 k++;
2534 }
2535 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +00002536 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002537 SetPixelRed(blur_image,
2538 ClampToQuantum(gamma*qixel.red),q);
cristyed231572011-07-14 02:18:59 +00002539 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002540 SetPixelGreen(blur_image,
2541 ClampToQuantum(gamma*qixel.green),q);
cristyed231572011-07-14 02:18:59 +00002542 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002543 SetPixelBlue(blur_image,
2544 ClampToQuantum(gamma*qixel.blue),q);
cristyed231572011-07-14 02:18:59 +00002545 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002546 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002547 SetPixelBlack(blur_image,
2548 ClampToQuantum(gamma*qixel.black),q);
cristyed231572011-07-14 02:18:59 +00002549 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002550 SetPixelAlpha(blur_image,
2551 ClampToQuantum(qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00002552 }
cristyed231572011-07-14 02:18:59 +00002553 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00002554 }
2555 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
2556 status=MagickFalse;
2557 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2558 {
2559 MagickBooleanType
2560 proceed;
2561
cristyb557a152011-02-22 12:14:30 +00002562#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00002563 #pragma omp critical (MagickCore_MotionBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00002564#endif
2565 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
2566 if (proceed == MagickFalse)
2567 status=MagickFalse;
2568 }
2569 }
2570 blur_view=DestroyCacheView(blur_view);
2571 image_view=DestroyCacheView(image_view);
2572 kernel=(double *) RelinquishMagickMemory(kernel);
2573 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2574 if (status == MagickFalse)
2575 blur_image=DestroyImage(blur_image);
2576 return(blur_image);
2577}
2578
2579/*
2580%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2581% %
2582% %
2583% %
2584% P r e v i e w I m a g e %
2585% %
2586% %
2587% %
2588%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2589%
2590% PreviewImage() tiles 9 thumbnails of the specified image with an image
2591% processing operation applied with varying parameters. This may be helpful
2592% pin-pointing an appropriate parameter for a particular image processing
2593% operation.
2594%
2595% The format of the PreviewImages method is:
2596%
2597% Image *PreviewImages(const Image *image,const PreviewType preview,
2598% ExceptionInfo *exception)
2599%
2600% A description of each parameter follows:
2601%
2602% o image: the image.
2603%
2604% o preview: the image processing operation.
2605%
2606% o exception: return any errors or warnings in this structure.
2607%
2608*/
2609MagickExport Image *PreviewImage(const Image *image,const PreviewType preview,
2610 ExceptionInfo *exception)
2611{
2612#define NumberTiles 9
2613#define PreviewImageTag "Preview/Image"
2614#define DefaultPreviewGeometry "204x204+10+10"
2615
2616 char
2617 factor[MaxTextExtent],
2618 label[MaxTextExtent];
2619
2620 double
2621 degrees,
2622 gamma,
2623 percentage,
2624 radius,
2625 sigma,
2626 threshold;
2627
2628 Image
2629 *images,
2630 *montage_image,
2631 *preview_image,
2632 *thumbnail;
2633
2634 ImageInfo
2635 *preview_info;
2636
cristy3ed852e2009-09-05 21:47:34 +00002637 MagickBooleanType
2638 proceed;
2639
2640 MontageInfo
2641 *montage_info;
2642
2643 QuantizeInfo
2644 quantize_info;
2645
2646 RectangleInfo
2647 geometry;
2648
cristybb503372010-05-27 20:51:26 +00002649 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002650 i,
2651 x;
2652
cristybb503372010-05-27 20:51:26 +00002653 size_t
cristy3ed852e2009-09-05 21:47:34 +00002654 colors;
2655
cristy117ff172010-08-15 21:35:32 +00002656 ssize_t
2657 y;
2658
cristy3ed852e2009-09-05 21:47:34 +00002659 /*
2660 Open output image file.
2661 */
2662 assert(image != (Image *) NULL);
2663 assert(image->signature == MagickSignature);
2664 if (image->debug != MagickFalse)
2665 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2666 colors=2;
2667 degrees=0.0;
2668 gamma=(-0.2f);
2669 preview_info=AcquireImageInfo();
2670 SetGeometry(image,&geometry);
2671 (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y,
2672 &geometry.width,&geometry.height);
2673 images=NewImageList();
2674 percentage=12.5;
2675 GetQuantizeInfo(&quantize_info);
2676 radius=0.0;
2677 sigma=1.0;
2678 threshold=0.0;
2679 x=0;
2680 y=0;
2681 for (i=0; i < NumberTiles; i++)
2682 {
2683 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
2684 if (thumbnail == (Image *) NULL)
2685 break;
2686 (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL,
2687 (void *) NULL);
2688 (void) SetImageProperty(thumbnail,"label",DefaultTileLabel);
2689 if (i == (NumberTiles/2))
2690 {
2691 (void) QueryColorDatabase("#dfdfdf",&thumbnail->matte_color,exception);
2692 AppendImageToList(&images,thumbnail);
2693 continue;
2694 }
2695 switch (preview)
2696 {
2697 case RotatePreview:
2698 {
2699 degrees+=45.0;
2700 preview_image=RotateImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002701 (void) FormatLocaleString(label,MaxTextExtent,"rotate %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002702 break;
2703 }
2704 case ShearPreview:
2705 {
2706 degrees+=5.0;
2707 preview_image=ShearImage(thumbnail,degrees,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002708 (void) FormatLocaleString(label,MaxTextExtent,"shear %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002709 degrees,2.0*degrees);
2710 break;
2711 }
2712 case RollPreview:
2713 {
cristybb503372010-05-27 20:51:26 +00002714 x=(ssize_t) ((i+1)*thumbnail->columns)/NumberTiles;
2715 y=(ssize_t) ((i+1)*thumbnail->rows)/NumberTiles;
cristy3ed852e2009-09-05 21:47:34 +00002716 preview_image=RollImage(thumbnail,x,y,exception);
cristyb51dff52011-05-19 16:55:47 +00002717 (void) FormatLocaleString(label,MaxTextExtent,"roll %+.20gx%+.20g",
cristye8c25f92010-06-03 00:53:06 +00002718 (double) x,(double) y);
cristy3ed852e2009-09-05 21:47:34 +00002719 break;
2720 }
2721 case HuePreview:
2722 {
2723 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2724 if (preview_image == (Image *) NULL)
2725 break;
cristyb51dff52011-05-19 16:55:47 +00002726 (void) FormatLocaleString(factor,MaxTextExtent,"100,100,%g",
cristy3ed852e2009-09-05 21:47:34 +00002727 2.0*percentage);
2728 (void) ModulateImage(preview_image,factor);
cristyb51dff52011-05-19 16:55:47 +00002729 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002730 break;
2731 }
2732 case SaturationPreview:
2733 {
2734 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2735 if (preview_image == (Image *) NULL)
2736 break;
cristyb51dff52011-05-19 16:55:47 +00002737 (void) FormatLocaleString(factor,MaxTextExtent,"100,%g",
cristy8cd5b312010-01-07 01:10:24 +00002738 2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00002739 (void) ModulateImage(preview_image,factor);
cristyb51dff52011-05-19 16:55:47 +00002740 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002741 break;
2742 }
2743 case BrightnessPreview:
2744 {
2745 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2746 if (preview_image == (Image *) NULL)
2747 break;
cristyb51dff52011-05-19 16:55:47 +00002748 (void) FormatLocaleString(factor,MaxTextExtent,"%g",2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00002749 (void) ModulateImage(preview_image,factor);
cristyb51dff52011-05-19 16:55:47 +00002750 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002751 break;
2752 }
2753 case GammaPreview:
2754 default:
2755 {
2756 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2757 if (preview_image == (Image *) NULL)
2758 break;
2759 gamma+=0.4f;
cristy50fbc382011-07-07 02:19:17 +00002760 (void) GammaImage(preview_image,gamma);
cristyb51dff52011-05-19 16:55:47 +00002761 (void) FormatLocaleString(label,MaxTextExtent,"gamma %g",gamma);
cristy3ed852e2009-09-05 21:47:34 +00002762 break;
2763 }
2764 case SpiffPreview:
2765 {
2766 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2767 if (preview_image != (Image *) NULL)
2768 for (x=0; x < i; x++)
2769 (void) ContrastImage(preview_image,MagickTrue);
cristyb51dff52011-05-19 16:55:47 +00002770 (void) FormatLocaleString(label,MaxTextExtent,"contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002771 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002772 break;
2773 }
2774 case DullPreview:
2775 {
2776 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2777 if (preview_image == (Image *) NULL)
2778 break;
2779 for (x=0; x < i; x++)
2780 (void) ContrastImage(preview_image,MagickFalse);
cristyb51dff52011-05-19 16:55:47 +00002781 (void) FormatLocaleString(label,MaxTextExtent,"+contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002782 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002783 break;
2784 }
2785 case GrayscalePreview:
2786 {
2787 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2788 if (preview_image == (Image *) NULL)
2789 break;
2790 colors<<=1;
2791 quantize_info.number_colors=colors;
2792 quantize_info.colorspace=GRAYColorspace;
2793 (void) QuantizeImage(&quantize_info,preview_image);
cristyb51dff52011-05-19 16:55:47 +00002794 (void) FormatLocaleString(label,MaxTextExtent,
cristye8c25f92010-06-03 00:53:06 +00002795 "-colorspace gray -colors %.20g",(double) colors);
cristy3ed852e2009-09-05 21:47:34 +00002796 break;
2797 }
2798 case QuantizePreview:
2799 {
2800 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2801 if (preview_image == (Image *) NULL)
2802 break;
2803 colors<<=1;
2804 quantize_info.number_colors=colors;
2805 (void) QuantizeImage(&quantize_info,preview_image);
cristyb51dff52011-05-19 16:55:47 +00002806 (void) FormatLocaleString(label,MaxTextExtent,"colors %.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00002807 colors);
cristy3ed852e2009-09-05 21:47:34 +00002808 break;
2809 }
2810 case DespecklePreview:
2811 {
2812 for (x=0; x < (i-1); x++)
2813 {
2814 preview_image=DespeckleImage(thumbnail,exception);
2815 if (preview_image == (Image *) NULL)
2816 break;
2817 thumbnail=DestroyImage(thumbnail);
2818 thumbnail=preview_image;
2819 }
2820 preview_image=DespeckleImage(thumbnail,exception);
2821 if (preview_image == (Image *) NULL)
2822 break;
cristyb51dff52011-05-19 16:55:47 +00002823 (void) FormatLocaleString(label,MaxTextExtent,"despeckle (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002824 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002825 break;
2826 }
2827 case ReduceNoisePreview:
2828 {
cristy95c38342011-03-18 22:39:51 +00002829 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) radius,
2830 (size_t) radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002831 (void) FormatLocaleString(label,MaxTextExtent,"noise %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002832 break;
2833 }
2834 case AddNoisePreview:
2835 {
2836 switch ((int) i)
2837 {
2838 case 0:
2839 {
2840 (void) CopyMagickString(factor,"uniform",MaxTextExtent);
2841 break;
2842 }
2843 case 1:
2844 {
2845 (void) CopyMagickString(factor,"gaussian",MaxTextExtent);
2846 break;
2847 }
2848 case 2:
2849 {
2850 (void) CopyMagickString(factor,"multiplicative",MaxTextExtent);
2851 break;
2852 }
2853 case 3:
2854 {
2855 (void) CopyMagickString(factor,"impulse",MaxTextExtent);
2856 break;
2857 }
2858 case 4:
2859 {
2860 (void) CopyMagickString(factor,"laplacian",MaxTextExtent);
2861 break;
2862 }
2863 case 5:
2864 {
2865 (void) CopyMagickString(factor,"Poisson",MaxTextExtent);
2866 break;
2867 }
2868 default:
2869 {
2870 (void) CopyMagickString(thumbnail->magick,"NULL",MaxTextExtent);
2871 break;
2872 }
2873 }
cristyd76c51e2011-03-26 00:21:26 +00002874 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) i,
2875 (size_t) i,exception);
cristyb51dff52011-05-19 16:55:47 +00002876 (void) FormatLocaleString(label,MaxTextExtent,"+noise %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002877 break;
2878 }
2879 case SharpenPreview:
2880 {
2881 preview_image=SharpenImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00002882 (void) FormatLocaleString(label,MaxTextExtent,"sharpen %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002883 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002884 break;
2885 }
2886 case BlurPreview:
2887 {
2888 preview_image=BlurImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00002889 (void) FormatLocaleString(label,MaxTextExtent,"blur %gx%g",radius,
cristy3ed852e2009-09-05 21:47:34 +00002890 sigma);
2891 break;
2892 }
2893 case ThresholdPreview:
2894 {
2895 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2896 if (preview_image == (Image *) NULL)
2897 break;
2898 (void) BilevelImage(thumbnail,
2899 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
cristyb51dff52011-05-19 16:55:47 +00002900 (void) FormatLocaleString(label,MaxTextExtent,"threshold %g",
cristy3ed852e2009-09-05 21:47:34 +00002901 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
2902 break;
2903 }
2904 case EdgeDetectPreview:
2905 {
2906 preview_image=EdgeImage(thumbnail,radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002907 (void) FormatLocaleString(label,MaxTextExtent,"edge %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002908 break;
2909 }
2910 case SpreadPreview:
2911 {
2912 preview_image=SpreadImage(thumbnail,radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002913 (void) FormatLocaleString(label,MaxTextExtent,"spread %g",
cristy8cd5b312010-01-07 01:10:24 +00002914 radius+0.5);
cristy3ed852e2009-09-05 21:47:34 +00002915 break;
2916 }
2917 case SolarizePreview:
2918 {
2919 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2920 if (preview_image == (Image *) NULL)
2921 break;
2922 (void) SolarizeImage(preview_image,(double) QuantumRange*
2923 percentage/100.0);
cristyb51dff52011-05-19 16:55:47 +00002924 (void) FormatLocaleString(label,MaxTextExtent,"solarize %g",
cristy3ed852e2009-09-05 21:47:34 +00002925 (QuantumRange*percentage)/100.0);
2926 break;
2927 }
2928 case ShadePreview:
2929 {
2930 degrees+=10.0;
2931 preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees,
2932 exception);
cristyb51dff52011-05-19 16:55:47 +00002933 (void) FormatLocaleString(label,MaxTextExtent,"shade %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002934 degrees,degrees);
cristy3ed852e2009-09-05 21:47:34 +00002935 break;
2936 }
2937 case RaisePreview:
2938 {
2939 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2940 if (preview_image == (Image *) NULL)
2941 break;
cristybb503372010-05-27 20:51:26 +00002942 geometry.width=(size_t) (2*i+2);
2943 geometry.height=(size_t) (2*i+2);
cristy3ed852e2009-09-05 21:47:34 +00002944 geometry.x=i/2;
2945 geometry.y=i/2;
2946 (void) RaiseImage(preview_image,&geometry,MagickTrue);
cristyb51dff52011-05-19 16:55:47 +00002947 (void) FormatLocaleString(label,MaxTextExtent,
cristy6d8abba2010-06-03 01:10:47 +00002948 "raise %.20gx%.20g%+.20g%+.20g",(double) geometry.width,(double)
cristye8c25f92010-06-03 00:53:06 +00002949 geometry.height,(double) geometry.x,(double) geometry.y);
cristy3ed852e2009-09-05 21:47:34 +00002950 break;
2951 }
2952 case SegmentPreview:
2953 {
2954 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2955 if (preview_image == (Image *) NULL)
2956 break;
2957 threshold+=0.4f;
2958 (void) SegmentImage(preview_image,RGBColorspace,MagickFalse,threshold,
2959 threshold);
cristyb51dff52011-05-19 16:55:47 +00002960 (void) FormatLocaleString(label,MaxTextExtent,"segment %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002961 threshold,threshold);
2962 break;
2963 }
2964 case SwirlPreview:
2965 {
2966 preview_image=SwirlImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002967 (void) FormatLocaleString(label,MaxTextExtent,"swirl %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002968 degrees+=45.0;
2969 break;
2970 }
2971 case ImplodePreview:
2972 {
2973 degrees+=0.1f;
2974 preview_image=ImplodeImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002975 (void) FormatLocaleString(label,MaxTextExtent,"implode %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002976 break;
2977 }
2978 case WavePreview:
2979 {
2980 degrees+=5.0f;
2981 preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002982 (void) FormatLocaleString(label,MaxTextExtent,"wave %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002983 0.5*degrees,2.0*degrees);
cristy3ed852e2009-09-05 21:47:34 +00002984 break;
2985 }
2986 case OilPaintPreview:
2987 {
2988 preview_image=OilPaintImage(thumbnail,(double) radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002989 (void) FormatLocaleString(label,MaxTextExtent,"paint %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002990 break;
2991 }
2992 case CharcoalDrawingPreview:
2993 {
2994 preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma,
2995 exception);
cristyb51dff52011-05-19 16:55:47 +00002996 (void) FormatLocaleString(label,MaxTextExtent,"charcoal %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002997 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002998 break;
2999 }
3000 case JPEGPreview:
3001 {
3002 char
3003 filename[MaxTextExtent];
3004
3005 int
3006 file;
3007
3008 MagickBooleanType
3009 status;
3010
3011 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3012 if (preview_image == (Image *) NULL)
3013 break;
cristybb503372010-05-27 20:51:26 +00003014 preview_info->quality=(size_t) percentage;
cristyb51dff52011-05-19 16:55:47 +00003015 (void) FormatLocaleString(factor,MaxTextExtent,"%.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00003016 preview_info->quality);
cristy3ed852e2009-09-05 21:47:34 +00003017 file=AcquireUniqueFileResource(filename);
3018 if (file != -1)
3019 file=close(file)-1;
cristyb51dff52011-05-19 16:55:47 +00003020 (void) FormatLocaleString(preview_image->filename,MaxTextExtent,
cristy3ed852e2009-09-05 21:47:34 +00003021 "jpeg:%s",filename);
3022 status=WriteImage(preview_info,preview_image);
3023 if (status != MagickFalse)
3024 {
3025 Image
3026 *quality_image;
3027
3028 (void) CopyMagickString(preview_info->filename,
3029 preview_image->filename,MaxTextExtent);
3030 quality_image=ReadImage(preview_info,exception);
3031 if (quality_image != (Image *) NULL)
3032 {
3033 preview_image=DestroyImage(preview_image);
3034 preview_image=quality_image;
3035 }
3036 }
3037 (void) RelinquishUniqueFileResource(preview_image->filename);
3038 if ((GetBlobSize(preview_image)/1024) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00003039 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%gmb ",
cristy3ed852e2009-09-05 21:47:34 +00003040 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
3041 1024.0/1024.0);
3042 else
3043 if (GetBlobSize(preview_image) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00003044 (void) FormatLocaleString(label,MaxTextExtent,
cristye7f51092010-01-17 00:39:37 +00003045 "quality %s\n%gkb ",factor,(double) ((MagickOffsetType)
cristy8cd5b312010-01-07 01:10:24 +00003046 GetBlobSize(preview_image))/1024.0);
cristy3ed852e2009-09-05 21:47:34 +00003047 else
cristyb51dff52011-05-19 16:55:47 +00003048 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%.20gb ",
cristy54ea5732011-06-10 12:39:53 +00003049 factor,(double) ((MagickOffsetType) GetBlobSize(thumbnail)));
cristy3ed852e2009-09-05 21:47:34 +00003050 break;
3051 }
3052 }
3053 thumbnail=DestroyImage(thumbnail);
3054 percentage+=12.5;
3055 radius+=0.5;
3056 sigma+=0.25;
3057 if (preview_image == (Image *) NULL)
3058 break;
3059 (void) DeleteImageProperty(preview_image,"label");
3060 (void) SetImageProperty(preview_image,"label",label);
3061 AppendImageToList(&images,preview_image);
cristybb503372010-05-27 20:51:26 +00003062 proceed=SetImageProgress(image,PreviewImageTag,(MagickOffsetType) i,
3063 NumberTiles);
cristy3ed852e2009-09-05 21:47:34 +00003064 if (proceed == MagickFalse)
3065 break;
3066 }
3067 if (images == (Image *) NULL)
3068 {
3069 preview_info=DestroyImageInfo(preview_info);
3070 return((Image *) NULL);
3071 }
3072 /*
3073 Create the montage.
3074 */
3075 montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL);
3076 (void) CopyMagickString(montage_info->filename,image->filename,MaxTextExtent);
3077 montage_info->shadow=MagickTrue;
3078 (void) CloneString(&montage_info->tile,"3x3");
3079 (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry);
3080 (void) CloneString(&montage_info->frame,DefaultTileFrame);
3081 montage_image=MontageImages(images,montage_info,exception);
3082 montage_info=DestroyMontageInfo(montage_info);
3083 images=DestroyImageList(images);
3084 if (montage_image == (Image *) NULL)
3085 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3086 if (montage_image->montage != (char *) NULL)
3087 {
3088 /*
3089 Free image directory.
3090 */
3091 montage_image->montage=(char *) RelinquishMagickMemory(
3092 montage_image->montage);
3093 if (image->directory != (char *) NULL)
3094 montage_image->directory=(char *) RelinquishMagickMemory(
3095 montage_image->directory);
3096 }
3097 preview_info=DestroyImageInfo(preview_info);
3098 return(montage_image);
3099}
3100
3101/*
3102%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3103% %
3104% %
3105% %
3106% R a d i a l B l u r I m a g e %
3107% %
3108% %
3109% %
3110%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3111%
3112% RadialBlurImage() applies a radial blur to the image.
3113%
3114% Andrew Protano contributed this effect.
3115%
3116% The format of the RadialBlurImage method is:
3117%
3118% Image *RadialBlurImage(const Image *image,const double angle,
3119% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003120%
3121% A description of each parameter follows:
3122%
3123% o image: the image.
3124%
cristy3ed852e2009-09-05 21:47:34 +00003125% o angle: the angle of the radial blur.
3126%
3127% o exception: return any errors or warnings in this structure.
3128%
3129*/
cristyf4ad9df2011-07-08 16:49:03 +00003130MagickExport Image *RadialBlurImage(const Image *image,
3131 const double angle,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003132{
cristyc4c8d132010-01-07 01:58:38 +00003133 CacheView
3134 *blur_view,
3135 *image_view;
3136
cristy3ed852e2009-09-05 21:47:34 +00003137 Image
3138 *blur_image;
3139
cristy3ed852e2009-09-05 21:47:34 +00003140 MagickBooleanType
3141 status;
3142
cristybb503372010-05-27 20:51:26 +00003143 MagickOffsetType
3144 progress;
3145
cristy4c08aed2011-07-01 19:47:50 +00003146 PixelInfo
cristyddd82202009-11-03 20:14:50 +00003147 bias;
cristy3ed852e2009-09-05 21:47:34 +00003148
3149 MagickRealType
3150 blur_radius,
3151 *cos_theta,
3152 offset,
3153 *sin_theta,
3154 theta;
3155
3156 PointInfo
3157 blur_center;
3158
cristybb503372010-05-27 20:51:26 +00003159 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003160 i;
3161
cristybb503372010-05-27 20:51:26 +00003162 size_t
cristy3ed852e2009-09-05 21:47:34 +00003163 n;
3164
cristybb503372010-05-27 20:51:26 +00003165 ssize_t
3166 y;
3167
cristy3ed852e2009-09-05 21:47:34 +00003168 /*
3169 Allocate blur image.
3170 */
3171 assert(image != (Image *) NULL);
3172 assert(image->signature == MagickSignature);
3173 if (image->debug != MagickFalse)
3174 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3175 assert(exception != (ExceptionInfo *) NULL);
3176 assert(exception->signature == MagickSignature);
3177 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3178 if (blur_image == (Image *) NULL)
3179 return((Image *) NULL);
3180 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3181 {
3182 InheritException(exception,&blur_image->exception);
3183 blur_image=DestroyImage(blur_image);
3184 return((Image *) NULL);
3185 }
3186 blur_center.x=(double) image->columns/2.0;
3187 blur_center.y=(double) image->rows/2.0;
3188 blur_radius=hypot(blur_center.x,blur_center.y);
cristy117ff172010-08-15 21:35:32 +00003189 n=(size_t) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+2UL);
cristy3ed852e2009-09-05 21:47:34 +00003190 theta=DegreesToRadians(angle)/(MagickRealType) (n-1);
3191 cos_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3192 sizeof(*cos_theta));
3193 sin_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3194 sizeof(*sin_theta));
3195 if ((cos_theta == (MagickRealType *) NULL) ||
3196 (sin_theta == (MagickRealType *) NULL))
3197 {
3198 blur_image=DestroyImage(blur_image);
3199 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3200 }
3201 offset=theta*(MagickRealType) (n-1)/2.0;
cristybb503372010-05-27 20:51:26 +00003202 for (i=0; i < (ssize_t) n; i++)
cristy3ed852e2009-09-05 21:47:34 +00003203 {
3204 cos_theta[i]=cos((double) (theta*i-offset));
3205 sin_theta[i]=sin((double) (theta*i-offset));
3206 }
3207 /*
3208 Radial blur image.
3209 */
3210 status=MagickTrue;
3211 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00003212 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003213 image_view=AcquireCacheView(image);
3214 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003215#if defined(MAGICKCORE_OPENMP_SUPPORT)
3216 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003217#endif
cristybb503372010-05-27 20:51:26 +00003218 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003219 {
cristy4c08aed2011-07-01 19:47:50 +00003220 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003221 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003222
cristy117ff172010-08-15 21:35:32 +00003223 register ssize_t
3224 x;
3225
cristy3ed852e2009-09-05 21:47:34 +00003226 if (status == MagickFalse)
3227 continue;
3228 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3229 exception);
cristy4c08aed2011-07-01 19:47:50 +00003230 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003231 {
3232 status=MagickFalse;
3233 continue;
3234 }
cristybb503372010-05-27 20:51:26 +00003235 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003236 {
cristy4c08aed2011-07-01 19:47:50 +00003237 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003238 qixel;
3239
3240 MagickRealType
3241 normalize,
3242 radius;
3243
3244 PixelPacket
3245 pixel;
3246
3247 PointInfo
3248 center;
3249
cristybb503372010-05-27 20:51:26 +00003250 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003251 i;
3252
cristybb503372010-05-27 20:51:26 +00003253 size_t
cristy3ed852e2009-09-05 21:47:34 +00003254 step;
3255
3256 center.x=(double) x-blur_center.x;
3257 center.y=(double) y-blur_center.y;
3258 radius=hypot((double) center.x,center.y);
3259 if (radius == 0)
3260 step=1;
3261 else
3262 {
cristybb503372010-05-27 20:51:26 +00003263 step=(size_t) (blur_radius/radius);
cristy3ed852e2009-09-05 21:47:34 +00003264 if (step == 0)
3265 step=1;
3266 else
3267 if (step >= n)
3268 step=n-1;
3269 }
3270 normalize=0.0;
cristyddd82202009-11-03 20:14:50 +00003271 qixel=bias;
cristyed231572011-07-14 02:18:59 +00003272 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00003273 {
cristyeaedf062010-05-29 22:36:02 +00003274 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00003275 {
cristyeaedf062010-05-29 22:36:02 +00003276 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
3277 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
3278 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
3279 cos_theta[i]+0.5),&pixel,exception);
cristy3ed852e2009-09-05 21:47:34 +00003280 qixel.red+=pixel.red;
3281 qixel.green+=pixel.green;
3282 qixel.blue+=pixel.blue;
cristy3ed852e2009-09-05 21:47:34 +00003283 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00003284 qixel.black+=pixel.black;
3285 qixel.alpha+=pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00003286 normalize+=1.0;
3287 }
3288 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
3289 normalize);
cristyed231572011-07-14 02:18:59 +00003290 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003291 SetPixelRed(blur_image,
3292 ClampToQuantum(normalize*qixel.red),q);
cristyed231572011-07-14 02:18:59 +00003293 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003294 SetPixelGreen(blur_image,
3295 ClampToQuantum(normalize*qixel.green),q);
cristyed231572011-07-14 02:18:59 +00003296 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003297 SetPixelBlue(blur_image,
3298 ClampToQuantum(normalize*qixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003299 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003300 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003301 SetPixelBlack(blur_image,
3302 ClampToQuantum(normalize*qixel.black),q);
cristyed231572011-07-14 02:18:59 +00003303 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003304 SetPixelAlpha(blur_image,
3305 ClampToQuantum(normalize*qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003306 }
3307 else
3308 {
3309 MagickRealType
3310 alpha,
3311 gamma;
3312
3313 alpha=1.0;
3314 gamma=0.0;
cristyeaedf062010-05-29 22:36:02 +00003315 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00003316 {
cristyeaedf062010-05-29 22:36:02 +00003317 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
3318 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
3319 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
3320 cos_theta[i]+0.5),&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00003321 alpha=(MagickRealType) (QuantumScale*pixel.alpha);
cristy3ed852e2009-09-05 21:47:34 +00003322 qixel.red+=alpha*pixel.red;
3323 qixel.green+=alpha*pixel.green;
3324 qixel.blue+=alpha*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00003325 qixel.alpha+=pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00003326 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00003327 qixel.black+=alpha*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00003328 gamma+=alpha;
3329 normalize+=1.0;
3330 }
3331 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3332 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
3333 normalize);
cristyed231572011-07-14 02:18:59 +00003334 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003335 SetPixelRed(blur_image,
3336 ClampToQuantum(gamma*qixel.red),q);
cristyed231572011-07-14 02:18:59 +00003337 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003338 SetPixelGreen(blur_image,
3339 ClampToQuantum(gamma*qixel.green),q);
cristyed231572011-07-14 02:18:59 +00003340 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003341 SetPixelBlue(blur_image,
3342 ClampToQuantum(gamma*qixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003343 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003344 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003345 SetPixelBlack(blur_image,
3346 ClampToQuantum(gamma*qixel.black),q);
cristyed231572011-07-14 02:18:59 +00003347 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003348 SetPixelAlpha(blur_image,
3349 ClampToQuantum(normalize*qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003350 }
cristyed231572011-07-14 02:18:59 +00003351 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003352 }
3353 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
3354 status=MagickFalse;
3355 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3356 {
3357 MagickBooleanType
3358 proceed;
3359
cristyb5d5f722009-11-04 03:03:49 +00003360#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00003361 #pragma omp critical (MagickCore_RadialBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00003362#endif
3363 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
3364 if (proceed == MagickFalse)
3365 status=MagickFalse;
3366 }
3367 }
3368 blur_view=DestroyCacheView(blur_view);
3369 image_view=DestroyCacheView(image_view);
3370 cos_theta=(MagickRealType *) RelinquishMagickMemory(cos_theta);
3371 sin_theta=(MagickRealType *) RelinquishMagickMemory(sin_theta);
3372 if (status == MagickFalse)
3373 blur_image=DestroyImage(blur_image);
3374 return(blur_image);
3375}
3376
3377/*
3378%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3379% %
3380% %
3381% %
cristy3ed852e2009-09-05 21:47:34 +00003382% S e l e c t i v e B l u r I m a g e %
3383% %
3384% %
3385% %
3386%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3387%
3388% SelectiveBlurImage() selectively blur pixels within a contrast threshold.
3389% It is similar to the unsharpen mask that sharpens everything with contrast
3390% above a certain threshold.
3391%
3392% The format of the SelectiveBlurImage method is:
3393%
3394% Image *SelectiveBlurImage(const Image *image,const double radius,
3395% const double sigma,const double threshold,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003396%
3397% A description of each parameter follows:
3398%
3399% o image: the image.
3400%
cristy3ed852e2009-09-05 21:47:34 +00003401% o radius: the radius of the Gaussian, in pixels, not counting the center
3402% pixel.
3403%
3404% o sigma: the standard deviation of the Gaussian, in pixels.
3405%
3406% o threshold: only pixels within this contrast threshold are included
3407% in the blur operation.
3408%
3409% o exception: return any errors or warnings in this structure.
3410%
3411*/
cristyf4ad9df2011-07-08 16:49:03 +00003412MagickExport Image *SelectiveBlurImage(const Image *image,
3413 const double radius,const double sigma,const double threshold,
3414 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003415{
3416#define SelectiveBlurImageTag "SelectiveBlur/Image"
3417
cristy47e00502009-12-17 19:19:57 +00003418 CacheView
3419 *blur_view,
3420 *image_view;
3421
cristy3ed852e2009-09-05 21:47:34 +00003422 double
cristy3ed852e2009-09-05 21:47:34 +00003423 *kernel;
3424
3425 Image
3426 *blur_image;
3427
cristy3ed852e2009-09-05 21:47:34 +00003428 MagickBooleanType
3429 status;
3430
cristybb503372010-05-27 20:51:26 +00003431 MagickOffsetType
3432 progress;
3433
cristy4c08aed2011-07-01 19:47:50 +00003434 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003435 bias;
3436
cristybb503372010-05-27 20:51:26 +00003437 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003438 i;
cristy3ed852e2009-09-05 21:47:34 +00003439
cristybb503372010-05-27 20:51:26 +00003440 size_t
cristy3ed852e2009-09-05 21:47:34 +00003441 width;
3442
cristybb503372010-05-27 20:51:26 +00003443 ssize_t
3444 j,
3445 u,
3446 v,
3447 y;
3448
cristy3ed852e2009-09-05 21:47:34 +00003449 /*
3450 Initialize blur image attributes.
3451 */
3452 assert(image != (Image *) NULL);
3453 assert(image->signature == MagickSignature);
3454 if (image->debug != MagickFalse)
3455 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3456 assert(exception != (ExceptionInfo *) NULL);
3457 assert(exception->signature == MagickSignature);
3458 width=GetOptimalKernelWidth1D(radius,sigma);
3459 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
3460 if (kernel == (double *) NULL)
3461 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00003462 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00003463 i=0;
cristy47e00502009-12-17 19:19:57 +00003464 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00003465 {
cristy47e00502009-12-17 19:19:57 +00003466 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00003467 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
3468 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00003469 }
3470 if (image->debug != MagickFalse)
3471 {
3472 char
3473 format[MaxTextExtent],
3474 *message;
3475
cristy117ff172010-08-15 21:35:32 +00003476 register const double
3477 *k;
3478
cristybb503372010-05-27 20:51:26 +00003479 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003480 u,
3481 v;
3482
cristy3ed852e2009-09-05 21:47:34 +00003483 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00003484 " SelectiveBlurImage with %.20gx%.20g kernel:",(double) width,(double)
3485 width);
cristy3ed852e2009-09-05 21:47:34 +00003486 message=AcquireString("");
3487 k=kernel;
cristybb503372010-05-27 20:51:26 +00003488 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003489 {
3490 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00003491 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy3ed852e2009-09-05 21:47:34 +00003492 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00003493 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003494 {
cristyb51dff52011-05-19 16:55:47 +00003495 (void) FormatLocaleString(format,MaxTextExtent,"%+f ",*k++);
cristy3ed852e2009-09-05 21:47:34 +00003496 (void) ConcatenateString(&message,format);
3497 }
3498 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
3499 }
3500 message=DestroyString(message);
3501 }
3502 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3503 if (blur_image == (Image *) NULL)
3504 return((Image *) NULL);
3505 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3506 {
3507 InheritException(exception,&blur_image->exception);
3508 blur_image=DestroyImage(blur_image);
3509 return((Image *) NULL);
3510 }
3511 /*
3512 Threshold blur image.
3513 */
3514 status=MagickTrue;
3515 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00003516 GetPixelInfo(image,&bias);
3517 SetPixelInfoBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003518 image_view=AcquireCacheView(image);
3519 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003520#if defined(MAGICKCORE_OPENMP_SUPPORT)
3521 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003522#endif
cristybb503372010-05-27 20:51:26 +00003523 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003524 {
cristy4c08aed2011-07-01 19:47:50 +00003525 double
3526 contrast;
3527
cristy3ed852e2009-09-05 21:47:34 +00003528 MagickBooleanType
3529 sync;
3530
3531 MagickRealType
3532 gamma;
3533
cristy4c08aed2011-07-01 19:47:50 +00003534 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003535 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003536
cristy4c08aed2011-07-01 19:47:50 +00003537 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003538 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003539
cristy117ff172010-08-15 21:35:32 +00003540 register ssize_t
3541 x;
3542
cristy3ed852e2009-09-05 21:47:34 +00003543 if (status == MagickFalse)
3544 continue;
cristy117ff172010-08-15 21:35:32 +00003545 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
3546 (width/2L),image->columns+width,width,exception);
cristy3ed852e2009-09-05 21:47:34 +00003547 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3548 exception);
cristy4c08aed2011-07-01 19:47:50 +00003549 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003550 {
3551 status=MagickFalse;
3552 continue;
3553 }
cristybb503372010-05-27 20:51:26 +00003554 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003555 {
cristy4c08aed2011-07-01 19:47:50 +00003556 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003557 pixel;
3558
3559 register const double
cristyc47d1f82009-11-26 01:44:43 +00003560 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00003561
cristybb503372010-05-27 20:51:26 +00003562 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003563 u;
3564
cristy117ff172010-08-15 21:35:32 +00003565 ssize_t
3566 j,
3567 v;
3568
cristyddd82202009-11-03 20:14:50 +00003569 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00003570 k=kernel;
3571 gamma=0.0;
3572 j=0;
cristyed231572011-07-14 02:18:59 +00003573 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00003574 {
cristybb503372010-05-27 20:51:26 +00003575 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003576 {
cristybb503372010-05-27 20:51:26 +00003577 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003578 {
cristyed231572011-07-14 02:18:59 +00003579 contrast=GetPixelIntensity(image,p+(u+j)*GetPixelChannels(image))-
cristy4c08aed2011-07-01 19:47:50 +00003580 (double) GetPixelIntensity(blur_image,q);
3581 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003582 {
cristy4c08aed2011-07-01 19:47:50 +00003583 pixel.red+=(*k)*
cristyed231572011-07-14 02:18:59 +00003584 GetPixelRed(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003585 pixel.green+=(*k)*
cristyed231572011-07-14 02:18:59 +00003586 GetPixelGreen(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003587 pixel.blue+=(*k)*
cristyed231572011-07-14 02:18:59 +00003588 GetPixelBlue(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003589 if (image->colorspace == CMYKColorspace)
3590 pixel.black+=(*k)*
cristyed231572011-07-14 02:18:59 +00003591 GetPixelBlack(image,p+(u+j)*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003592 gamma+=(*k);
3593 k++;
3594 }
3595 }
cristyd99b0962010-05-29 23:14:26 +00003596 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003597 }
3598 if (gamma != 0.0)
3599 {
3600 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +00003601 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003602 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +00003603 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003604 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +00003605 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003606 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003607 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00003608 (image->colorspace == CMYKColorspace))
3609 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00003610 }
cristyed231572011-07-14 02:18:59 +00003611 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003612 {
3613 gamma=0.0;
3614 j=0;
cristybb503372010-05-27 20:51:26 +00003615 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003616 {
cristybb503372010-05-27 20:51:26 +00003617 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003618 {
cristy4c08aed2011-07-01 19:47:50 +00003619 contrast=GetPixelIntensity(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003620 GetPixelChannels(image))-(double)
cristy4c08aed2011-07-01 19:47:50 +00003621 GetPixelIntensity(blur_image,q);
3622 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003623 {
cristy4c08aed2011-07-01 19:47:50 +00003624 pixel.alpha+=(*k)*
cristyed231572011-07-14 02:18:59 +00003625 GetPixelAlpha(image,p+(u+j)*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003626 gamma+=(*k);
3627 k++;
3628 }
3629 }
cristyeaedf062010-05-29 22:36:02 +00003630 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003631 }
3632 if (gamma != 0.0)
3633 {
3634 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
3635 gamma);
cristy4c08aed2011-07-01 19:47:50 +00003636 SetPixelAlpha(blur_image,ClampToQuantum(gamma*pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003637 }
3638 }
3639 }
3640 else
3641 {
3642 MagickRealType
3643 alpha;
3644
cristybb503372010-05-27 20:51:26 +00003645 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003646 {
cristybb503372010-05-27 20:51:26 +00003647 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003648 {
cristy4c08aed2011-07-01 19:47:50 +00003649 contrast=GetPixelIntensity(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003650 GetPixelChannels(image))-(double)
cristy4c08aed2011-07-01 19:47:50 +00003651 GetPixelIntensity(blur_image,q);
3652 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003653 {
cristy4c08aed2011-07-01 19:47:50 +00003654 alpha=(MagickRealType) (QuantumScale*
cristyed231572011-07-14 02:18:59 +00003655 GetPixelAlpha(image,p+(u+j)*GetPixelChannels(image)));
cristy4c08aed2011-07-01 19:47:50 +00003656 pixel.red+=(*k)*alpha*
cristyed231572011-07-14 02:18:59 +00003657 GetPixelRed(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003658 pixel.green+=(*k)*alpha*GetPixelGreen(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003659 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003660 pixel.blue+=(*k)*alpha*GetPixelBlue(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003661 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003662 pixel.alpha+=(*k)*GetPixelAlpha(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003663 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003664 if (image->colorspace == CMYKColorspace)
3665 pixel.black+=(*k)*GetPixelBlack(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003666 GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003667 gamma+=(*k)*alpha;
3668 k++;
3669 }
3670 }
cristyeaedf062010-05-29 22:36:02 +00003671 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003672 }
3673 if (gamma != 0.0)
3674 {
3675 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +00003676 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003677 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +00003678 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003679 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +00003680 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003681 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003682 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00003683 (image->colorspace == CMYKColorspace))
3684 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00003685 }
cristyed231572011-07-14 02:18:59 +00003686 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003687 {
3688 gamma=0.0;
3689 j=0;
cristybb503372010-05-27 20:51:26 +00003690 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003691 {
cristybb503372010-05-27 20:51:26 +00003692 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003693 {
cristy4c08aed2011-07-01 19:47:50 +00003694 contrast=GetPixelIntensity(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003695 GetPixelChannels(image))-(double)
cristy4c08aed2011-07-01 19:47:50 +00003696 GetPixelIntensity(blur_image,q);
3697 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003698 {
cristy4c08aed2011-07-01 19:47:50 +00003699 pixel.alpha+=(*k)*
cristyed231572011-07-14 02:18:59 +00003700 GetPixelAlpha(image,p+(u+j)*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003701 gamma+=(*k);
3702 k++;
3703 }
3704 }
cristyeaedf062010-05-29 22:36:02 +00003705 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003706 }
3707 if (gamma != 0.0)
3708 {
3709 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
3710 gamma);
cristy4c08aed2011-07-01 19:47:50 +00003711 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003712 }
3713 }
3714 }
cristyed231572011-07-14 02:18:59 +00003715 p+=GetPixelChannels(image);
3716 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003717 }
3718 sync=SyncCacheViewAuthenticPixels(blur_view,exception);
3719 if (sync == MagickFalse)
3720 status=MagickFalse;
3721 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3722 {
3723 MagickBooleanType
3724 proceed;
3725
cristyb5d5f722009-11-04 03:03:49 +00003726#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00003727 #pragma omp critical (MagickCore_SelectiveBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00003728#endif
3729 proceed=SetImageProgress(image,SelectiveBlurImageTag,progress++,
3730 image->rows);
3731 if (proceed == MagickFalse)
3732 status=MagickFalse;
3733 }
3734 }
3735 blur_image->type=image->type;
3736 blur_view=DestroyCacheView(blur_view);
3737 image_view=DestroyCacheView(image_view);
3738 kernel=(double *) RelinquishMagickMemory(kernel);
3739 if (status == MagickFalse)
3740 blur_image=DestroyImage(blur_image);
3741 return(blur_image);
3742}
3743
3744/*
3745%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3746% %
3747% %
3748% %
3749% S h a d e I m a g e %
3750% %
3751% %
3752% %
3753%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3754%
3755% ShadeImage() shines a distant light on an image to create a
3756% three-dimensional effect. You control the positioning of the light with
3757% azimuth and elevation; azimuth is measured in degrees off the x axis
3758% and elevation is measured in pixels above the Z axis.
3759%
3760% The format of the ShadeImage method is:
3761%
3762% Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3763% const double azimuth,const double elevation,ExceptionInfo *exception)
3764%
3765% A description of each parameter follows:
3766%
3767% o image: the image.
3768%
3769% o gray: A value other than zero shades the intensity of each pixel.
3770%
3771% o azimuth, elevation: Define the light source direction.
3772%
3773% o exception: return any errors or warnings in this structure.
3774%
3775*/
3776MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3777 const double azimuth,const double elevation,ExceptionInfo *exception)
3778{
3779#define ShadeImageTag "Shade/Image"
3780
cristyc4c8d132010-01-07 01:58:38 +00003781 CacheView
3782 *image_view,
3783 *shade_view;
3784
cristy3ed852e2009-09-05 21:47:34 +00003785 Image
3786 *shade_image;
3787
cristy3ed852e2009-09-05 21:47:34 +00003788 MagickBooleanType
3789 status;
3790
cristybb503372010-05-27 20:51:26 +00003791 MagickOffsetType
3792 progress;
3793
cristy3ed852e2009-09-05 21:47:34 +00003794 PrimaryInfo
3795 light;
3796
cristybb503372010-05-27 20:51:26 +00003797 ssize_t
3798 y;
3799
cristy3ed852e2009-09-05 21:47:34 +00003800 /*
3801 Initialize shaded image attributes.
3802 */
3803 assert(image != (const Image *) NULL);
3804 assert(image->signature == MagickSignature);
3805 if (image->debug != MagickFalse)
3806 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3807 assert(exception != (ExceptionInfo *) NULL);
3808 assert(exception->signature == MagickSignature);
3809 shade_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
3810 if (shade_image == (Image *) NULL)
3811 return((Image *) NULL);
3812 if (SetImageStorageClass(shade_image,DirectClass) == MagickFalse)
3813 {
3814 InheritException(exception,&shade_image->exception);
3815 shade_image=DestroyImage(shade_image);
3816 return((Image *) NULL);
3817 }
3818 /*
3819 Compute the light vector.
3820 */
3821 light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))*
3822 cos(DegreesToRadians(elevation));
3823 light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))*
3824 cos(DegreesToRadians(elevation));
3825 light.z=(double) QuantumRange*sin(DegreesToRadians(elevation));
3826 /*
3827 Shade image.
3828 */
3829 status=MagickTrue;
3830 progress=0;
3831 image_view=AcquireCacheView(image);
3832 shade_view=AcquireCacheView(shade_image);
cristyb5d5f722009-11-04 03:03:49 +00003833#if defined(MAGICKCORE_OPENMP_SUPPORT)
3834 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003835#endif
cristybb503372010-05-27 20:51:26 +00003836 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003837 {
3838 MagickRealType
3839 distance,
3840 normal_distance,
3841 shade;
3842
3843 PrimaryInfo
3844 normal;
3845
cristy4c08aed2011-07-01 19:47:50 +00003846 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003847 *restrict p,
3848 *restrict s0,
3849 *restrict s1,
3850 *restrict s2;
cristy3ed852e2009-09-05 21:47:34 +00003851
cristy4c08aed2011-07-01 19:47:50 +00003852 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003853 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003854
cristy117ff172010-08-15 21:35:32 +00003855 register ssize_t
3856 x;
3857
cristy3ed852e2009-09-05 21:47:34 +00003858 if (status == MagickFalse)
3859 continue;
3860 p=GetCacheViewVirtualPixels(image_view,-1,y-1,image->columns+2,3,exception);
3861 q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1,
3862 exception);
cristy4c08aed2011-07-01 19:47:50 +00003863 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003864 {
3865 status=MagickFalse;
3866 continue;
3867 }
3868 /*
3869 Shade this row of pixels.
3870 */
3871 normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */
cristyed231572011-07-14 02:18:59 +00003872 s0=p+GetPixelChannels(image);
3873 s1=s0+(image->columns+2)*GetPixelChannels(image);
3874 s2=s1+(image->columns+2)*GetPixelChannels(image);
cristybb503372010-05-27 20:51:26 +00003875 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003876 {
3877 /*
3878 Determine the surface normal and compute shading.
3879 */
cristyed231572011-07-14 02:18:59 +00003880 normal.x=(double) (GetPixelIntensity(image,s0-GetPixelChannels(image))+
3881 GetPixelIntensity(image,s1-GetPixelChannels(image))+
3882 GetPixelIntensity(image,s2-GetPixelChannels(image))-
3883 GetPixelIntensity(image,s0+GetPixelChannels(image))-
3884 GetPixelIntensity(image,s1+GetPixelChannels(image))-
3885 GetPixelIntensity(image,s2+GetPixelChannels(image)));
3886 normal.y=(double) (GetPixelIntensity(image,s2-GetPixelChannels(image))+
cristy4c08aed2011-07-01 19:47:50 +00003887 GetPixelIntensity(image,s2)+
cristyed231572011-07-14 02:18:59 +00003888 GetPixelIntensity(image,s2+GetPixelChannels(image))-
3889 GetPixelIntensity(image,s0-GetPixelChannels(image))-
cristy4c08aed2011-07-01 19:47:50 +00003890 GetPixelIntensity(image,s0)-
cristyed231572011-07-14 02:18:59 +00003891 GetPixelIntensity(image,s0+GetPixelChannels(image)));
cristy3ed852e2009-09-05 21:47:34 +00003892 if ((normal.x == 0.0) && (normal.y == 0.0))
3893 shade=light.z;
3894 else
3895 {
3896 shade=0.0;
3897 distance=normal.x*light.x+normal.y*light.y+normal.z*light.z;
3898 if (distance > MagickEpsilon)
3899 {
3900 normal_distance=
3901 normal.x*normal.x+normal.y*normal.y+normal.z*normal.z;
3902 if (normal_distance > (MagickEpsilon*MagickEpsilon))
3903 shade=distance/sqrt((double) normal_distance);
3904 }
3905 }
3906 if (gray != MagickFalse)
3907 {
cristy4c08aed2011-07-01 19:47:50 +00003908 SetPixelRed(shade_image,ClampToQuantum(shade),q);
3909 SetPixelGreen(shade_image,ClampToQuantum(shade),q);
3910 SetPixelBlue(shade_image,ClampToQuantum(shade),q);
cristy3ed852e2009-09-05 21:47:34 +00003911 }
3912 else
3913 {
cristy4c08aed2011-07-01 19:47:50 +00003914 SetPixelRed(shade_image,ClampToQuantum(QuantumScale*shade*
3915 GetPixelRed(image,s1)),q);
3916 SetPixelGreen(shade_image,ClampToQuantum(QuantumScale*shade*
3917 GetPixelGreen(image,s1)),q);
3918 SetPixelBlue(shade_image,ClampToQuantum(QuantumScale*shade*
3919 GetPixelBlue(image,s1)),q);
cristy3ed852e2009-09-05 21:47:34 +00003920 }
cristy4c08aed2011-07-01 19:47:50 +00003921 SetPixelAlpha(shade_image,GetPixelAlpha(image,s1),q);
cristyed231572011-07-14 02:18:59 +00003922 s0+=GetPixelChannels(image);
3923 s1+=GetPixelChannels(image);
3924 s2+=GetPixelChannels(image);
3925 q+=GetPixelChannels(shade_image);
cristy3ed852e2009-09-05 21:47:34 +00003926 }
3927 if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
3928 status=MagickFalse;
3929 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3930 {
3931 MagickBooleanType
3932 proceed;
3933
cristyb5d5f722009-11-04 03:03:49 +00003934#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003935 #pragma omp critical (MagickCore_ShadeImage)
3936#endif
3937 proceed=SetImageProgress(image,ShadeImageTag,progress++,image->rows);
3938 if (proceed == MagickFalse)
3939 status=MagickFalse;
3940 }
3941 }
3942 shade_view=DestroyCacheView(shade_view);
3943 image_view=DestroyCacheView(image_view);
3944 if (status == MagickFalse)
3945 shade_image=DestroyImage(shade_image);
3946 return(shade_image);
3947}
3948
3949/*
3950%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3951% %
3952% %
3953% %
3954% S h a r p e n I m a g e %
3955% %
3956% %
3957% %
3958%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3959%
3960% SharpenImage() sharpens the image. We convolve the image with a Gaussian
3961% operator of the given radius and standard deviation (sigma). For
3962% reasonable results, radius should be larger than sigma. Use a radius of 0
3963% and SharpenImage() selects a suitable radius for you.
3964%
3965% Using a separable kernel would be faster, but the negative weights cancel
3966% out on the corners of the kernel producing often undesirable ringing in the
3967% filtered result; this can be avoided by using a 2D gaussian shaped image
3968% sharpening kernel instead.
3969%
3970% The format of the SharpenImage method is:
3971%
3972% Image *SharpenImage(const Image *image,const double radius,
3973% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003974%
3975% A description of each parameter follows:
3976%
3977% o image: the image.
3978%
cristy3ed852e2009-09-05 21:47:34 +00003979% o radius: the radius of the Gaussian, in pixels, not counting the center
3980% pixel.
3981%
3982% o sigma: the standard deviation of the Laplacian, in pixels.
3983%
3984% o exception: return any errors or warnings in this structure.
3985%
3986*/
cristy3ed852e2009-09-05 21:47:34 +00003987MagickExport Image *SharpenImage(const Image *image,const double radius,
3988 const double sigma,ExceptionInfo *exception)
3989{
cristy3ed852e2009-09-05 21:47:34 +00003990 double
cristy47e00502009-12-17 19:19:57 +00003991 *kernel,
3992 normalize;
cristy3ed852e2009-09-05 21:47:34 +00003993
3994 Image
3995 *sharp_image;
3996
cristybb503372010-05-27 20:51:26 +00003997 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003998 i;
3999
cristybb503372010-05-27 20:51:26 +00004000 size_t
cristy3ed852e2009-09-05 21:47:34 +00004001 width;
4002
cristy117ff172010-08-15 21:35:32 +00004003 ssize_t
4004 j,
4005 u,
4006 v;
4007
cristy3ed852e2009-09-05 21:47:34 +00004008 assert(image != (const Image *) NULL);
4009 assert(image->signature == MagickSignature);
4010 if (image->debug != MagickFalse)
4011 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4012 assert(exception != (ExceptionInfo *) NULL);
4013 assert(exception->signature == MagickSignature);
4014 width=GetOptimalKernelWidth2D(radius,sigma);
4015 kernel=(double *) AcquireQuantumMemory((size_t) width*width,sizeof(*kernel));
4016 if (kernel == (double *) NULL)
4017 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy3ed852e2009-09-05 21:47:34 +00004018 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00004019 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +00004020 i=0;
4021 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00004022 {
cristy47e00502009-12-17 19:19:57 +00004023 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00004024 {
cristy4205a3c2010-09-12 20:19:59 +00004025 kernel[i]=(double) (-exp(-((double) u*u+v*v)/(2.0*MagickSigma*
4026 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00004027 normalize+=kernel[i];
4028 i++;
4029 }
4030 }
4031 kernel[i/2]=(double) ((-2.0)*normalize);
cristyf4ad9df2011-07-08 16:49:03 +00004032 sharp_image=ConvolveImage(image,width,kernel,exception);
cristy3ed852e2009-09-05 21:47:34 +00004033 kernel=(double *) RelinquishMagickMemory(kernel);
4034 return(sharp_image);
4035}
4036
4037/*
4038%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4039% %
4040% %
4041% %
4042% S p r e a d I m a g e %
4043% %
4044% %
4045% %
4046%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4047%
4048% SpreadImage() is a special effects method that randomly displaces each
4049% pixel in a block defined by the radius parameter.
4050%
4051% The format of the SpreadImage method is:
4052%
4053% Image *SpreadImage(const Image *image,const double radius,
4054% ExceptionInfo *exception)
4055%
4056% A description of each parameter follows:
4057%
4058% o image: the image.
4059%
4060% o radius: Choose a random pixel in a neighborhood of this extent.
4061%
4062% o exception: return any errors or warnings in this structure.
4063%
4064*/
4065MagickExport Image *SpreadImage(const Image *image,const double radius,
4066 ExceptionInfo *exception)
4067{
4068#define SpreadImageTag "Spread/Image"
4069
cristyfa112112010-01-04 17:48:07 +00004070 CacheView
cristy9f7e7cb2011-03-26 00:49:57 +00004071 *image_view,
4072 *spread_view;
cristyfa112112010-01-04 17:48:07 +00004073
cristy3ed852e2009-09-05 21:47:34 +00004074 Image
4075 *spread_image;
4076
cristy3ed852e2009-09-05 21:47:34 +00004077 MagickBooleanType
4078 status;
4079
cristybb503372010-05-27 20:51:26 +00004080 MagickOffsetType
4081 progress;
4082
cristy4c08aed2011-07-01 19:47:50 +00004083 PixelInfo
cristyddd82202009-11-03 20:14:50 +00004084 bias;
cristy3ed852e2009-09-05 21:47:34 +00004085
4086 RandomInfo
cristyfa112112010-01-04 17:48:07 +00004087 **restrict random_info;
cristy3ed852e2009-09-05 21:47:34 +00004088
cristybb503372010-05-27 20:51:26 +00004089 size_t
cristy3ed852e2009-09-05 21:47:34 +00004090 width;
4091
cristybb503372010-05-27 20:51:26 +00004092 ssize_t
4093 y;
4094
cristy3ed852e2009-09-05 21:47:34 +00004095 /*
4096 Initialize spread image attributes.
4097 */
4098 assert(image != (Image *) NULL);
4099 assert(image->signature == MagickSignature);
4100 if (image->debug != MagickFalse)
4101 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4102 assert(exception != (ExceptionInfo *) NULL);
4103 assert(exception->signature == MagickSignature);
4104 spread_image=CloneImage(image,image->columns,image->rows,MagickTrue,
4105 exception);
4106 if (spread_image == (Image *) NULL)
4107 return((Image *) NULL);
4108 if (SetImageStorageClass(spread_image,DirectClass) == MagickFalse)
4109 {
4110 InheritException(exception,&spread_image->exception);
4111 spread_image=DestroyImage(spread_image);
4112 return((Image *) NULL);
4113 }
4114 /*
4115 Spread image.
4116 */
4117 status=MagickTrue;
4118 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00004119 GetPixelInfo(spread_image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00004120 width=GetOptimalKernelWidth1D(radius,0.5);
cristy3ed852e2009-09-05 21:47:34 +00004121 random_info=AcquireRandomInfoThreadSet();
cristy9f7e7cb2011-03-26 00:49:57 +00004122 image_view=AcquireCacheView(image);
4123 spread_view=AcquireCacheView(spread_image);
cristyb557a152011-02-22 12:14:30 +00004124#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00004125 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00004126#endif
cristybb503372010-05-27 20:51:26 +00004127 for (y=0; y < (ssize_t) spread_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00004128 {
cristy5c9e6f22010-09-17 17:31:01 +00004129 const int
4130 id = GetOpenMPThreadId();
cristy6ebe97c2010-07-03 01:17:28 +00004131
cristy4c08aed2011-07-01 19:47:50 +00004132 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00004133 pixel;
4134
cristy4c08aed2011-07-01 19:47:50 +00004135 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00004136 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004137
cristy117ff172010-08-15 21:35:32 +00004138 register ssize_t
4139 x;
4140
cristy3ed852e2009-09-05 21:47:34 +00004141 if (status == MagickFalse)
4142 continue;
cristy9f7e7cb2011-03-26 00:49:57 +00004143 q=QueueCacheViewAuthenticPixels(spread_view,0,y,spread_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +00004144 exception);
cristy4c08aed2011-07-01 19:47:50 +00004145 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00004146 {
4147 status=MagickFalse;
4148 continue;
4149 }
cristyddd82202009-11-03 20:14:50 +00004150 pixel=bias;
cristybb503372010-05-27 20:51:26 +00004151 for (x=0; x < (ssize_t) spread_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004152 {
cristy4c08aed2011-07-01 19:47:50 +00004153 (void) InterpolatePixelInfo(image,image_view,
cristy8a7c3e82011-03-26 02:10:53 +00004154 UndefinedInterpolatePixel,(double) x+width*(GetPseudoRandomValue(
4155 random_info[id])-0.5),(double) y+width*(GetPseudoRandomValue(
4156 random_info[id])-0.5),&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00004157 SetPixelPixelInfo(spread_image,&pixel,q);
cristyed231572011-07-14 02:18:59 +00004158 q+=GetPixelChannels(spread_image);
cristy3ed852e2009-09-05 21:47:34 +00004159 }
cristy9f7e7cb2011-03-26 00:49:57 +00004160 if (SyncCacheViewAuthenticPixels(spread_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00004161 status=MagickFalse;
4162 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4163 {
4164 MagickBooleanType
4165 proceed;
4166
cristyb557a152011-02-22 12:14:30 +00004167#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004168 #pragma omp critical (MagickCore_SpreadImage)
4169#endif
4170 proceed=SetImageProgress(image,SpreadImageTag,progress++,image->rows);
4171 if (proceed == MagickFalse)
4172 status=MagickFalse;
4173 }
4174 }
cristy9f7e7cb2011-03-26 00:49:57 +00004175 spread_view=DestroyCacheView(spread_view);
cristy3ed852e2009-09-05 21:47:34 +00004176 image_view=DestroyCacheView(image_view);
4177 random_info=DestroyRandomInfoThreadSet(random_info);
cristy3ed852e2009-09-05 21:47:34 +00004178 return(spread_image);
4179}
4180
4181/*
4182%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4183% %
4184% %
4185% %
cristy0834d642011-03-18 18:26:08 +00004186% S t a t i s t i c I m a g e %
4187% %
4188% %
4189% %
4190%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4191%
4192% StatisticImage() makes each pixel the min / max / median / mode / etc. of
cristy8d752042011-03-19 01:00:36 +00004193% the neighborhood of the specified width and height.
cristy0834d642011-03-18 18:26:08 +00004194%
4195% The format of the StatisticImage method is:
4196%
4197% Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00004198% const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00004199%
4200% A description of each parameter follows:
4201%
4202% o image: the image.
4203%
cristy0834d642011-03-18 18:26:08 +00004204% o type: the statistic type (median, mode, etc.).
4205%
cristy95c38342011-03-18 22:39:51 +00004206% o width: the width of the pixel neighborhood.
4207%
4208% o height: the height of the pixel neighborhood.
cristy0834d642011-03-18 18:26:08 +00004209%
4210% o exception: return any errors or warnings in this structure.
4211%
4212*/
4213
cristy733678d2011-03-18 21:29:28 +00004214#define ListChannels 5
4215
4216typedef struct _ListNode
4217{
4218 size_t
4219 next[9],
4220 count,
4221 signature;
4222} ListNode;
4223
4224typedef struct _SkipList
4225{
4226 ssize_t
4227 level;
4228
4229 ListNode
4230 *nodes;
4231} SkipList;
4232
4233typedef struct _PixelList
4234{
4235 size_t
cristy6fc86bb2011-03-18 23:45:16 +00004236 length,
cristy733678d2011-03-18 21:29:28 +00004237 seed,
4238 signature;
4239
4240 SkipList
4241 lists[ListChannels];
4242} PixelList;
4243
4244static PixelList *DestroyPixelList(PixelList *pixel_list)
4245{
4246 register ssize_t
4247 i;
4248
4249 if (pixel_list == (PixelList *) NULL)
4250 return((PixelList *) NULL);
4251 for (i=0; i < ListChannels; i++)
4252 if (pixel_list->lists[i].nodes != (ListNode *) NULL)
4253 pixel_list->lists[i].nodes=(ListNode *) RelinquishMagickMemory(
4254 pixel_list->lists[i].nodes);
4255 pixel_list=(PixelList *) RelinquishMagickMemory(pixel_list);
4256 return(pixel_list);
4257}
4258
4259static PixelList **DestroyPixelListThreadSet(PixelList **pixel_list)
4260{
4261 register ssize_t
4262 i;
4263
4264 assert(pixel_list != (PixelList **) NULL);
4265 for (i=0; i < (ssize_t) GetOpenMPMaximumThreads(); i++)
4266 if (pixel_list[i] != (PixelList *) NULL)
4267 pixel_list[i]=DestroyPixelList(pixel_list[i]);
4268 pixel_list=(PixelList **) RelinquishMagickMemory(pixel_list);
4269 return(pixel_list);
4270}
4271
cristy6fc86bb2011-03-18 23:45:16 +00004272static PixelList *AcquirePixelList(const size_t width,const size_t height)
cristy733678d2011-03-18 21:29:28 +00004273{
4274 PixelList
4275 *pixel_list;
4276
4277 register ssize_t
4278 i;
4279
4280 pixel_list=(PixelList *) AcquireMagickMemory(sizeof(*pixel_list));
4281 if (pixel_list == (PixelList *) NULL)
4282 return(pixel_list);
4283 (void) ResetMagickMemory((void *) pixel_list,0,sizeof(*pixel_list));
cristy6fc86bb2011-03-18 23:45:16 +00004284 pixel_list->length=width*height;
cristy733678d2011-03-18 21:29:28 +00004285 for (i=0; i < ListChannels; i++)
4286 {
4287 pixel_list->lists[i].nodes=(ListNode *) AcquireQuantumMemory(65537UL,
4288 sizeof(*pixel_list->lists[i].nodes));
4289 if (pixel_list->lists[i].nodes == (ListNode *) NULL)
4290 return(DestroyPixelList(pixel_list));
4291 (void) ResetMagickMemory(pixel_list->lists[i].nodes,0,65537UL*
4292 sizeof(*pixel_list->lists[i].nodes));
4293 }
4294 pixel_list->signature=MagickSignature;
4295 return(pixel_list);
4296}
4297
cristy6fc86bb2011-03-18 23:45:16 +00004298static PixelList **AcquirePixelListThreadSet(const size_t width,
4299 const size_t height)
cristy733678d2011-03-18 21:29:28 +00004300{
4301 PixelList
4302 **pixel_list;
4303
4304 register ssize_t
4305 i;
4306
4307 size_t
4308 number_threads;
4309
4310 number_threads=GetOpenMPMaximumThreads();
4311 pixel_list=(PixelList **) AcquireQuantumMemory(number_threads,
4312 sizeof(*pixel_list));
4313 if (pixel_list == (PixelList **) NULL)
4314 return((PixelList **) NULL);
4315 (void) ResetMagickMemory(pixel_list,0,number_threads*sizeof(*pixel_list));
4316 for (i=0; i < (ssize_t) number_threads; i++)
4317 {
cristy6fc86bb2011-03-18 23:45:16 +00004318 pixel_list[i]=AcquirePixelList(width,height);
cristy733678d2011-03-18 21:29:28 +00004319 if (pixel_list[i] == (PixelList *) NULL)
4320 return(DestroyPixelListThreadSet(pixel_list));
4321 }
4322 return(pixel_list);
4323}
4324
4325static void AddNodePixelList(PixelList *pixel_list,const ssize_t channel,
4326 const size_t color)
4327{
4328 register SkipList
4329 *list;
4330
4331 register ssize_t
4332 level;
4333
4334 size_t
4335 search,
4336 update[9];
4337
4338 /*
4339 Initialize the node.
4340 */
4341 list=pixel_list->lists+channel;
4342 list->nodes[color].signature=pixel_list->signature;
4343 list->nodes[color].count=1;
4344 /*
4345 Determine where it belongs in the list.
4346 */
4347 search=65536UL;
4348 for (level=list->level; level >= 0; level--)
4349 {
4350 while (list->nodes[search].next[level] < color)
4351 search=list->nodes[search].next[level];
4352 update[level]=search;
4353 }
4354 /*
4355 Generate a pseudo-random level for this node.
4356 */
4357 for (level=0; ; level++)
4358 {
4359 pixel_list->seed=(pixel_list->seed*42893621L)+1L;
4360 if ((pixel_list->seed & 0x300) != 0x300)
4361 break;
4362 }
4363 if (level > 8)
4364 level=8;
4365 if (level > (list->level+2))
4366 level=list->level+2;
4367 /*
4368 If we're raising the list's level, link back to the root node.
4369 */
4370 while (level > list->level)
4371 {
4372 list->level++;
4373 update[list->level]=65536UL;
4374 }
4375 /*
4376 Link the node into the skip-list.
4377 */
4378 do
4379 {
4380 list->nodes[color].next[level]=list->nodes[update[level]].next[level];
4381 list->nodes[update[level]].next[level]=color;
cristy3cba8ca2011-03-19 01:29:12 +00004382 } while (level-- > 0);
cristy733678d2011-03-18 21:29:28 +00004383}
4384
cristy4c08aed2011-07-01 19:47:50 +00004385static PixelInfo GetMaximumPixelList(PixelList *pixel_list)
cristy6fc86bb2011-03-18 23:45:16 +00004386{
cristy4c08aed2011-07-01 19:47:50 +00004387 PixelInfo
cristy6fc86bb2011-03-18 23:45:16 +00004388 pixel;
4389
4390 register SkipList
4391 *list;
4392
4393 register ssize_t
4394 channel;
4395
4396 size_t
cristyd76c51e2011-03-26 00:21:26 +00004397 color,
4398 maximum;
cristy49f37242011-03-22 18:18:23 +00004399
4400 ssize_t
cristy6fc86bb2011-03-18 23:45:16 +00004401 count;
4402
4403 unsigned short
cristyd76c51e2011-03-26 00:21:26 +00004404 channels[ListChannels];
cristy6fc86bb2011-03-18 23:45:16 +00004405
4406 /*
4407 Find the maximum value for each of the color.
4408 */
4409 for (channel=0; channel < 5; channel++)
4410 {
4411 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004412 color=65536L;
cristy6fc86bb2011-03-18 23:45:16 +00004413 count=0;
cristy49f37242011-03-22 18:18:23 +00004414 maximum=list->nodes[color].next[0];
cristy6fc86bb2011-03-18 23:45:16 +00004415 do
4416 {
4417 color=list->nodes[color].next[0];
cristy49f37242011-03-22 18:18:23 +00004418 if (color > maximum)
4419 maximum=color;
cristy6fc86bb2011-03-18 23:45:16 +00004420 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004421 } while (count < (ssize_t) pixel_list->length);
cristy49f37242011-03-22 18:18:23 +00004422 channels[channel]=(unsigned short) maximum;
4423 }
cristy4c08aed2011-07-01 19:47:50 +00004424 GetPixelInfo((const Image *) NULL,&pixel);
cristy49f37242011-03-22 18:18:23 +00004425 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4426 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4427 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004428 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4429 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy49f37242011-03-22 18:18:23 +00004430 return(pixel);
4431}
4432
cristy4c08aed2011-07-01 19:47:50 +00004433static PixelInfo GetMeanPixelList(PixelList *pixel_list)
cristy49f37242011-03-22 18:18:23 +00004434{
cristy4c08aed2011-07-01 19:47:50 +00004435 PixelInfo
cristy49f37242011-03-22 18:18:23 +00004436 pixel;
4437
cristy80a99a32011-03-30 01:30:23 +00004438 MagickRealType
4439 sum;
4440
cristy49f37242011-03-22 18:18:23 +00004441 register SkipList
4442 *list;
4443
4444 register ssize_t
4445 channel;
4446
4447 size_t
cristy80a99a32011-03-30 01:30:23 +00004448 color;
cristy49f37242011-03-22 18:18:23 +00004449
4450 ssize_t
4451 count;
4452
4453 unsigned short
4454 channels[ListChannels];
4455
4456 /*
4457 Find the mean value for each of the color.
4458 */
4459 for (channel=0; channel < 5; channel++)
4460 {
4461 list=pixel_list->lists+channel;
4462 color=65536L;
4463 count=0;
cristy80a99a32011-03-30 01:30:23 +00004464 sum=0.0;
cristy49f37242011-03-22 18:18:23 +00004465 do
4466 {
4467 color=list->nodes[color].next[0];
cristy80a99a32011-03-30 01:30:23 +00004468 sum+=(MagickRealType) list->nodes[color].count*color;
cristy49f37242011-03-22 18:18:23 +00004469 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004470 } while (count < (ssize_t) pixel_list->length);
cristy80a99a32011-03-30 01:30:23 +00004471 sum/=pixel_list->length;
4472 channels[channel]=(unsigned short) sum;
cristy6fc86bb2011-03-18 23:45:16 +00004473 }
cristy4c08aed2011-07-01 19:47:50 +00004474 GetPixelInfo((const Image *) NULL,&pixel);
cristy6fc86bb2011-03-18 23:45:16 +00004475 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4476 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4477 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004478 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4479 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy6fc86bb2011-03-18 23:45:16 +00004480 return(pixel);
4481}
4482
cristy4c08aed2011-07-01 19:47:50 +00004483static PixelInfo GetMedianPixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004484{
cristy4c08aed2011-07-01 19:47:50 +00004485 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004486 pixel;
4487
4488 register SkipList
4489 *list;
4490
4491 register ssize_t
4492 channel;
4493
4494 size_t
cristy49f37242011-03-22 18:18:23 +00004495 color;
4496
4497 ssize_t
cristy733678d2011-03-18 21:29:28 +00004498 count;
4499
4500 unsigned short
4501 channels[ListChannels];
4502
4503 /*
4504 Find the median value for each of the color.
4505 */
cristy733678d2011-03-18 21:29:28 +00004506 for (channel=0; channel < 5; channel++)
4507 {
4508 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004509 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004510 count=0;
4511 do
4512 {
4513 color=list->nodes[color].next[0];
4514 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004515 } while (count <= (ssize_t) (pixel_list->length >> 1));
cristy6fc86bb2011-03-18 23:45:16 +00004516 channels[channel]=(unsigned short) color;
4517 }
cristy4c08aed2011-07-01 19:47:50 +00004518 GetPixelInfo((const Image *) NULL,&pixel);
cristy6fc86bb2011-03-18 23:45:16 +00004519 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4520 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4521 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004522 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4523 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy6fc86bb2011-03-18 23:45:16 +00004524 return(pixel);
4525}
4526
cristy4c08aed2011-07-01 19:47:50 +00004527static PixelInfo GetMinimumPixelList(PixelList *pixel_list)
cristy6fc86bb2011-03-18 23:45:16 +00004528{
cristy4c08aed2011-07-01 19:47:50 +00004529 PixelInfo
cristy6fc86bb2011-03-18 23:45:16 +00004530 pixel;
4531
4532 register SkipList
4533 *list;
4534
4535 register ssize_t
4536 channel;
4537
4538 size_t
cristyd76c51e2011-03-26 00:21:26 +00004539 color,
4540 minimum;
cristy6fc86bb2011-03-18 23:45:16 +00004541
cristy49f37242011-03-22 18:18:23 +00004542 ssize_t
4543 count;
4544
cristy6fc86bb2011-03-18 23:45:16 +00004545 unsigned short
cristyd76c51e2011-03-26 00:21:26 +00004546 channels[ListChannels];
cristy6fc86bb2011-03-18 23:45:16 +00004547
4548 /*
4549 Find the minimum value for each of the color.
4550 */
4551 for (channel=0; channel < 5; channel++)
4552 {
4553 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004554 count=0;
cristy6fc86bb2011-03-18 23:45:16 +00004555 color=65536UL;
cristy49f37242011-03-22 18:18:23 +00004556 minimum=list->nodes[color].next[0];
4557 do
4558 {
4559 color=list->nodes[color].next[0];
4560 if (color < minimum)
4561 minimum=color;
4562 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004563 } while (count < (ssize_t) pixel_list->length);
cristy49f37242011-03-22 18:18:23 +00004564 channels[channel]=(unsigned short) minimum;
cristy733678d2011-03-18 21:29:28 +00004565 }
cristy4c08aed2011-07-01 19:47:50 +00004566 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004567 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4568 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4569 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004570 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4571 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy733678d2011-03-18 21:29:28 +00004572 return(pixel);
4573}
4574
cristy4c08aed2011-07-01 19:47:50 +00004575static PixelInfo GetModePixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004576{
cristy4c08aed2011-07-01 19:47:50 +00004577 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004578 pixel;
4579
4580 register SkipList
4581 *list;
4582
4583 register ssize_t
4584 channel;
4585
4586 size_t
4587 color,
cristy733678d2011-03-18 21:29:28 +00004588 max_count,
cristy6fc86bb2011-03-18 23:45:16 +00004589 mode;
cristy733678d2011-03-18 21:29:28 +00004590
cristy49f37242011-03-22 18:18:23 +00004591 ssize_t
4592 count;
4593
cristy733678d2011-03-18 21:29:28 +00004594 unsigned short
4595 channels[5];
4596
4597 /*
glennrp30d2dc62011-06-25 03:17:16 +00004598 Make each pixel the 'predominant color' of the specified neighborhood.
cristy733678d2011-03-18 21:29:28 +00004599 */
cristy733678d2011-03-18 21:29:28 +00004600 for (channel=0; channel < 5; channel++)
4601 {
4602 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004603 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004604 mode=color;
4605 max_count=list->nodes[mode].count;
4606 count=0;
4607 do
4608 {
4609 color=list->nodes[color].next[0];
4610 if (list->nodes[color].count > max_count)
4611 {
4612 mode=color;
4613 max_count=list->nodes[mode].count;
4614 }
4615 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004616 } while (count < (ssize_t) pixel_list->length);
cristy733678d2011-03-18 21:29:28 +00004617 channels[channel]=(unsigned short) mode;
4618 }
cristy4c08aed2011-07-01 19:47:50 +00004619 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004620 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4621 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4622 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004623 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4624 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy733678d2011-03-18 21:29:28 +00004625 return(pixel);
4626}
4627
cristy4c08aed2011-07-01 19:47:50 +00004628static PixelInfo GetNonpeakPixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004629{
cristy4c08aed2011-07-01 19:47:50 +00004630 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004631 pixel;
4632
4633 register SkipList
4634 *list;
4635
4636 register ssize_t
4637 channel;
4638
4639 size_t
cristy733678d2011-03-18 21:29:28 +00004640 color,
cristy733678d2011-03-18 21:29:28 +00004641 next,
4642 previous;
4643
cristy49f37242011-03-22 18:18:23 +00004644 ssize_t
4645 count;
4646
cristy733678d2011-03-18 21:29:28 +00004647 unsigned short
4648 channels[5];
4649
4650 /*
cristy49f37242011-03-22 18:18:23 +00004651 Finds the non peak value for each of the colors.
cristy733678d2011-03-18 21:29:28 +00004652 */
cristy733678d2011-03-18 21:29:28 +00004653 for (channel=0; channel < 5; channel++)
4654 {
4655 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004656 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004657 next=list->nodes[color].next[0];
4658 count=0;
4659 do
4660 {
4661 previous=color;
4662 color=next;
4663 next=list->nodes[color].next[0];
4664 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004665 } while (count <= (ssize_t) (pixel_list->length >> 1));
cristy733678d2011-03-18 21:29:28 +00004666 if ((previous == 65536UL) && (next != 65536UL))
4667 color=next;
4668 else
4669 if ((previous != 65536UL) && (next == 65536UL))
4670 color=previous;
4671 channels[channel]=(unsigned short) color;
4672 }
cristy4c08aed2011-07-01 19:47:50 +00004673 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004674 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4675 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4676 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004677 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4678 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy733678d2011-03-18 21:29:28 +00004679 return(pixel);
4680}
4681
cristy4c08aed2011-07-01 19:47:50 +00004682static PixelInfo GetStandardDeviationPixelList(PixelList *pixel_list)
cristy9a68cbb2011-03-29 00:51:23 +00004683{
cristy4c08aed2011-07-01 19:47:50 +00004684 PixelInfo
cristy9a68cbb2011-03-29 00:51:23 +00004685 pixel;
4686
cristy80a99a32011-03-30 01:30:23 +00004687 MagickRealType
4688 sum,
4689 sum_squared;
4690
cristy9a68cbb2011-03-29 00:51:23 +00004691 register SkipList
4692 *list;
4693
4694 register ssize_t
4695 channel;
4696
4697 size_t
cristy80a99a32011-03-30 01:30:23 +00004698 color;
cristy9a68cbb2011-03-29 00:51:23 +00004699
4700 ssize_t
4701 count;
4702
4703 unsigned short
4704 channels[ListChannels];
4705
4706 /*
cristy80a99a32011-03-30 01:30:23 +00004707 Find the standard-deviation value for each of the color.
cristy9a68cbb2011-03-29 00:51:23 +00004708 */
4709 for (channel=0; channel < 5; channel++)
4710 {
4711 list=pixel_list->lists+channel;
4712 color=65536L;
4713 count=0;
cristy80a99a32011-03-30 01:30:23 +00004714 sum=0.0;
4715 sum_squared=0.0;
cristy9a68cbb2011-03-29 00:51:23 +00004716 do
4717 {
cristy80a99a32011-03-30 01:30:23 +00004718 register ssize_t
4719 i;
4720
cristy9a68cbb2011-03-29 00:51:23 +00004721 color=list->nodes[color].next[0];
cristy80a99a32011-03-30 01:30:23 +00004722 sum+=(MagickRealType) list->nodes[color].count*color;
4723 for (i=0; i < (ssize_t) list->nodes[color].count; i++)
4724 sum_squared+=((MagickRealType) color)*((MagickRealType) color);
cristy9a68cbb2011-03-29 00:51:23 +00004725 count+=list->nodes[color].count;
4726 } while (count < (ssize_t) pixel_list->length);
cristy80a99a32011-03-30 01:30:23 +00004727 sum/=pixel_list->length;
4728 sum_squared/=pixel_list->length;
4729 channels[channel]=(unsigned short) sqrt(sum_squared-(sum*sum));
cristy9a68cbb2011-03-29 00:51:23 +00004730 }
cristy4c08aed2011-07-01 19:47:50 +00004731 GetPixelInfo((const Image *) NULL,&pixel);
cristy9a68cbb2011-03-29 00:51:23 +00004732 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4733 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4734 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004735 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4736 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy9a68cbb2011-03-29 00:51:23 +00004737 return(pixel);
4738}
4739
cristy4c08aed2011-07-01 19:47:50 +00004740static inline void InsertPixelList(const Image *image,const Quantum *pixel,
4741 PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004742{
4743 size_t
4744 signature;
4745
4746 unsigned short
4747 index;
4748
cristy4c08aed2011-07-01 19:47:50 +00004749 index=ScaleQuantumToShort(GetPixelRed(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004750 signature=pixel_list->lists[0].nodes[index].signature;
4751 if (signature == pixel_list->signature)
4752 pixel_list->lists[0].nodes[index].count++;
4753 else
4754 AddNodePixelList(pixel_list,0,index);
cristy4c08aed2011-07-01 19:47:50 +00004755 index=ScaleQuantumToShort(GetPixelGreen(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004756 signature=pixel_list->lists[1].nodes[index].signature;
4757 if (signature == pixel_list->signature)
4758 pixel_list->lists[1].nodes[index].count++;
4759 else
4760 AddNodePixelList(pixel_list,1,index);
cristy4c08aed2011-07-01 19:47:50 +00004761 index=ScaleQuantumToShort(GetPixelBlue(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004762 signature=pixel_list->lists[2].nodes[index].signature;
4763 if (signature == pixel_list->signature)
4764 pixel_list->lists[2].nodes[index].count++;
4765 else
4766 AddNodePixelList(pixel_list,2,index);
cristy4c08aed2011-07-01 19:47:50 +00004767 index=ScaleQuantumToShort(GetPixelAlpha(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004768 signature=pixel_list->lists[3].nodes[index].signature;
4769 if (signature == pixel_list->signature)
4770 pixel_list->lists[3].nodes[index].count++;
4771 else
4772 AddNodePixelList(pixel_list,3,index);
4773 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00004774 index=ScaleQuantumToShort(GetPixelBlack(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004775 signature=pixel_list->lists[4].nodes[index].signature;
4776 if (signature == pixel_list->signature)
4777 pixel_list->lists[4].nodes[index].count++;
4778 else
4779 AddNodePixelList(pixel_list,4,index);
4780}
4781
cristy80c99742011-04-04 14:46:39 +00004782static inline MagickRealType MagickAbsoluteValue(const MagickRealType x)
4783{
4784 if (x < 0)
4785 return(-x);
4786 return(x);
4787}
4788
cristy733678d2011-03-18 21:29:28 +00004789static void ResetPixelList(PixelList *pixel_list)
4790{
4791 int
4792 level;
4793
4794 register ListNode
4795 *root;
4796
4797 register SkipList
4798 *list;
4799
4800 register ssize_t
4801 channel;
4802
4803 /*
4804 Reset the skip-list.
4805 */
4806 for (channel=0; channel < 5; channel++)
4807 {
4808 list=pixel_list->lists+channel;
4809 root=list->nodes+65536UL;
4810 list->level=0;
4811 for (level=0; level < 9; level++)
4812 root->next[level]=65536UL;
4813 }
4814 pixel_list->seed=pixel_list->signature++;
4815}
4816
cristy0834d642011-03-18 18:26:08 +00004817MagickExport Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00004818 const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00004819{
cristy3cba8ca2011-03-19 01:29:12 +00004820#define StatisticWidth \
cristyd76c51e2011-03-26 00:21:26 +00004821 (width == 0 ? GetOptimalKernelWidth2D((double) width,0.5) : width)
cristy3cba8ca2011-03-19 01:29:12 +00004822#define StatisticHeight \
cristyd76c51e2011-03-26 00:21:26 +00004823 (height == 0 ? GetOptimalKernelWidth2D((double) height,0.5) : height)
cristy0834d642011-03-18 18:26:08 +00004824#define StatisticImageTag "Statistic/Image"
4825
4826 CacheView
4827 *image_view,
4828 *statistic_view;
4829
4830 Image
4831 *statistic_image;
4832
4833 MagickBooleanType
4834 status;
4835
4836 MagickOffsetType
4837 progress;
4838
4839 PixelList
4840 **restrict pixel_list;
4841
cristy0834d642011-03-18 18:26:08 +00004842 ssize_t
4843 y;
4844
4845 /*
4846 Initialize statistics image attributes.
4847 */
4848 assert(image != (Image *) NULL);
4849 assert(image->signature == MagickSignature);
4850 if (image->debug != MagickFalse)
4851 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4852 assert(exception != (ExceptionInfo *) NULL);
4853 assert(exception->signature == MagickSignature);
cristy0834d642011-03-18 18:26:08 +00004854 statistic_image=CloneImage(image,image->columns,image->rows,MagickTrue,
4855 exception);
4856 if (statistic_image == (Image *) NULL)
4857 return((Image *) NULL);
4858 if (SetImageStorageClass(statistic_image,DirectClass) == MagickFalse)
4859 {
4860 InheritException(exception,&statistic_image->exception);
4861 statistic_image=DestroyImage(statistic_image);
4862 return((Image *) NULL);
4863 }
cristy6fc86bb2011-03-18 23:45:16 +00004864 pixel_list=AcquirePixelListThreadSet(StatisticWidth,StatisticHeight);
cristy0834d642011-03-18 18:26:08 +00004865 if (pixel_list == (PixelList **) NULL)
4866 {
4867 statistic_image=DestroyImage(statistic_image);
4868 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
4869 }
4870 /*
cristy8d752042011-03-19 01:00:36 +00004871 Make each pixel the min / max / median / mode / etc. of the neighborhood.
cristy0834d642011-03-18 18:26:08 +00004872 */
4873 status=MagickTrue;
4874 progress=0;
4875 image_view=AcquireCacheView(image);
4876 statistic_view=AcquireCacheView(statistic_image);
4877#if defined(MAGICKCORE_OPENMP_SUPPORT)
4878 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
4879#endif
4880 for (y=0; y < (ssize_t) statistic_image->rows; y++)
4881 {
4882 const int
4883 id = GetOpenMPThreadId();
4884
cristy4c08aed2011-07-01 19:47:50 +00004885 register const Quantum
cristy0834d642011-03-18 18:26:08 +00004886 *restrict p;
4887
cristy4c08aed2011-07-01 19:47:50 +00004888 register Quantum
cristy0834d642011-03-18 18:26:08 +00004889 *restrict q;
4890
4891 register ssize_t
4892 x;
4893
4894 if (status == MagickFalse)
4895 continue;
cristy6fc86bb2011-03-18 23:45:16 +00004896 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) StatisticWidth/2L),y-
4897 (ssize_t) (StatisticHeight/2L),image->columns+StatisticWidth,
4898 StatisticHeight,exception);
cristy3cba8ca2011-03-19 01:29:12 +00004899 q=QueueCacheViewAuthenticPixels(statistic_view,0,y,statistic_image->columns, 1,exception);
cristy4c08aed2011-07-01 19:47:50 +00004900 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy0834d642011-03-18 18:26:08 +00004901 {
4902 status=MagickFalse;
4903 continue;
4904 }
cristy0834d642011-03-18 18:26:08 +00004905 for (x=0; x < (ssize_t) statistic_image->columns; x++)
4906 {
cristy4c08aed2011-07-01 19:47:50 +00004907 PixelInfo
cristy0834d642011-03-18 18:26:08 +00004908 pixel;
4909
cristy4c08aed2011-07-01 19:47:50 +00004910 register const Quantum
cristy6e3026a2011-03-19 00:54:38 +00004911 *restrict r;
4912
cristy0834d642011-03-18 18:26:08 +00004913 register ssize_t
4914 u,
4915 v;
4916
4917 r=p;
cristy0834d642011-03-18 18:26:08 +00004918 ResetPixelList(pixel_list[id]);
cristy6e4c3292011-03-19 00:53:55 +00004919 for (v=0; v < (ssize_t) StatisticHeight; v++)
cristy0834d642011-03-18 18:26:08 +00004920 {
cristy6e4c3292011-03-19 00:53:55 +00004921 for (u=0; u < (ssize_t) StatisticWidth; u++)
cristyed231572011-07-14 02:18:59 +00004922 InsertPixelList(image,r+u*GetPixelChannels(image),pixel_list[id]);
4923 r+=(image->columns+StatisticWidth)*GetPixelChannels(image);
cristy0834d642011-03-18 18:26:08 +00004924 }
cristy4c08aed2011-07-01 19:47:50 +00004925 GetPixelInfo(image,&pixel);
cristy490408a2011-07-07 14:42:05 +00004926 SetPixelInfo(image,p+(StatisticWidth*StatisticHeight/2)*
cristyed231572011-07-14 02:18:59 +00004927 GetPixelChannels(image),&pixel);
cristy0834d642011-03-18 18:26:08 +00004928 switch (type)
4929 {
cristy80c99742011-04-04 14:46:39 +00004930 case GradientStatistic:
4931 {
cristy4c08aed2011-07-01 19:47:50 +00004932 PixelInfo
cristy80c99742011-04-04 14:46:39 +00004933 maximum,
4934 minimum;
4935
4936 minimum=GetMinimumPixelList(pixel_list[id]);
4937 maximum=GetMaximumPixelList(pixel_list[id]);
4938 pixel.red=MagickAbsoluteValue(maximum.red-minimum.red);
4939 pixel.green=MagickAbsoluteValue(maximum.green-minimum.green);
4940 pixel.blue=MagickAbsoluteValue(maximum.blue-minimum.blue);
cristy4c08aed2011-07-01 19:47:50 +00004941 pixel.alpha=MagickAbsoluteValue(maximum.alpha-minimum.alpha);
cristy80c99742011-04-04 14:46:39 +00004942 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00004943 pixel.black=MagickAbsoluteValue(maximum.black-minimum.black);
cristy80c99742011-04-04 14:46:39 +00004944 break;
4945 }
cristy6fc86bb2011-03-18 23:45:16 +00004946 case MaximumStatistic:
4947 {
4948 pixel=GetMaximumPixelList(pixel_list[id]);
4949 break;
4950 }
cristy49f37242011-03-22 18:18:23 +00004951 case MeanStatistic:
4952 {
4953 pixel=GetMeanPixelList(pixel_list[id]);
4954 break;
4955 }
cristyf2ad14a2011-03-18 18:57:25 +00004956 case MedianStatistic:
cristy6fc86bb2011-03-18 23:45:16 +00004957 default:
cristyf2ad14a2011-03-18 18:57:25 +00004958 {
4959 pixel=GetMedianPixelList(pixel_list[id]);
4960 break;
4961 }
cristy6fc86bb2011-03-18 23:45:16 +00004962 case MinimumStatistic:
4963 {
4964 pixel=GetMinimumPixelList(pixel_list[id]);
4965 break;
4966 }
cristyf2ad14a2011-03-18 18:57:25 +00004967 case ModeStatistic:
4968 {
4969 pixel=GetModePixelList(pixel_list[id]);
4970 break;
4971 }
4972 case NonpeakStatistic:
4973 {
4974 pixel=GetNonpeakPixelList(pixel_list[id]);
4975 break;
4976 }
cristy9a68cbb2011-03-29 00:51:23 +00004977 case StandardDeviationStatistic:
4978 {
4979 pixel=GetStandardDeviationPixelList(pixel_list[id]);
4980 break;
4981 }
cristy0834d642011-03-18 18:26:08 +00004982 }
cristyed231572011-07-14 02:18:59 +00004983 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00004984 SetPixelRed(statistic_image,ClampToQuantum(pixel.red),q);
cristyed231572011-07-14 02:18:59 +00004985 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00004986 SetPixelGreen(statistic_image,ClampToQuantum(pixel.green),q);
cristyed231572011-07-14 02:18:59 +00004987 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00004988 SetPixelBlue(statistic_image,ClampToQuantum(pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00004989 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy0834d642011-03-18 18:26:08 +00004990 (image->colorspace == CMYKColorspace))
cristy490408a2011-07-07 14:42:05 +00004991 SetPixelBlack(statistic_image,ClampToQuantum(pixel.black),q);
cristyed231572011-07-14 02:18:59 +00004992 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00004993 (image->matte != MagickFalse))
cristy490408a2011-07-07 14:42:05 +00004994 SetPixelAlpha(statistic_image,ClampToQuantum(pixel.alpha),q);
cristyed231572011-07-14 02:18:59 +00004995 p+=GetPixelChannels(image);
4996 q+=GetPixelChannels(statistic_image);
cristy0834d642011-03-18 18:26:08 +00004997 }
4998 if (SyncCacheViewAuthenticPixels(statistic_view,exception) == MagickFalse)
4999 status=MagickFalse;
5000 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5001 {
5002 MagickBooleanType
5003 proceed;
5004
5005#if defined(MAGICKCORE_OPENMP_SUPPORT)
5006 #pragma omp critical (MagickCore_StatisticImage)
5007#endif
5008 proceed=SetImageProgress(image,StatisticImageTag,progress++,
5009 image->rows);
5010 if (proceed == MagickFalse)
5011 status=MagickFalse;
5012 }
5013 }
5014 statistic_view=DestroyCacheView(statistic_view);
5015 image_view=DestroyCacheView(image_view);
5016 pixel_list=DestroyPixelListThreadSet(pixel_list);
5017 return(statistic_image);
5018}
5019
5020/*
5021%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5022% %
5023% %
5024% %
cristy3ed852e2009-09-05 21:47:34 +00005025% U n s h a r p M a s k I m a g e %
5026% %
5027% %
5028% %
5029%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5030%
5031% UnsharpMaskImage() sharpens one or more image channels. We convolve the
5032% image with a Gaussian operator of the given radius and standard deviation
5033% (sigma). For reasonable results, radius should be larger than sigma. Use a
5034% radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
5035%
5036% The format of the UnsharpMaskImage method is:
5037%
5038% Image *UnsharpMaskImage(const Image *image,const double radius,
5039% const double sigma,const double amount,const double threshold,
5040% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00005041%
5042% A description of each parameter follows:
5043%
5044% o image: the image.
5045%
cristy3ed852e2009-09-05 21:47:34 +00005046% o radius: the radius of the Gaussian, in pixels, not counting the center
5047% pixel.
5048%
5049% o sigma: the standard deviation of the Gaussian, in pixels.
5050%
5051% o amount: the percentage of the difference between the original and the
5052% blur image that is added back into the original.
5053%
5054% o threshold: the threshold in pixels needed to apply the diffence amount.
5055%
5056% o exception: return any errors or warnings in this structure.
5057%
5058*/
cristyf4ad9df2011-07-08 16:49:03 +00005059MagickExport Image *UnsharpMaskImage(const Image *image,
5060 const double radius,const double sigma,const double amount,
5061 const double threshold,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00005062{
5063#define SharpenImageTag "Sharpen/Image"
5064
cristyc4c8d132010-01-07 01:58:38 +00005065 CacheView
5066 *image_view,
5067 *unsharp_view;
5068
cristy3ed852e2009-09-05 21:47:34 +00005069 Image
5070 *unsharp_image;
5071
cristy3ed852e2009-09-05 21:47:34 +00005072 MagickBooleanType
5073 status;
5074
cristybb503372010-05-27 20:51:26 +00005075 MagickOffsetType
5076 progress;
5077
cristy4c08aed2011-07-01 19:47:50 +00005078 PixelInfo
cristyddd82202009-11-03 20:14:50 +00005079 bias;
cristy3ed852e2009-09-05 21:47:34 +00005080
5081 MagickRealType
5082 quantum_threshold;
5083
cristybb503372010-05-27 20:51:26 +00005084 ssize_t
5085 y;
5086
cristy3ed852e2009-09-05 21:47:34 +00005087 assert(image != (const Image *) NULL);
5088 assert(image->signature == MagickSignature);
5089 if (image->debug != MagickFalse)
5090 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5091 assert(exception != (ExceptionInfo *) NULL);
cristyf4ad9df2011-07-08 16:49:03 +00005092 unsharp_image=BlurImage(image,radius,sigma,exception);
cristy3ed852e2009-09-05 21:47:34 +00005093 if (unsharp_image == (Image *) NULL)
5094 return((Image *) NULL);
5095 quantum_threshold=(MagickRealType) QuantumRange*threshold;
5096 /*
5097 Unsharp-mask image.
5098 */
5099 status=MagickTrue;
5100 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00005101 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00005102 image_view=AcquireCacheView(image);
5103 unsharp_view=AcquireCacheView(unsharp_image);
cristyb5d5f722009-11-04 03:03:49 +00005104#if defined(MAGICKCORE_OPENMP_SUPPORT)
5105 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00005106#endif
cristybb503372010-05-27 20:51:26 +00005107 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00005108 {
cristy4c08aed2011-07-01 19:47:50 +00005109 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00005110 pixel;
5111
cristy4c08aed2011-07-01 19:47:50 +00005112 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00005113 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00005114
cristy4c08aed2011-07-01 19:47:50 +00005115 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00005116 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00005117
cristy117ff172010-08-15 21:35:32 +00005118 register ssize_t
5119 x;
5120
cristy3ed852e2009-09-05 21:47:34 +00005121 if (status == MagickFalse)
5122 continue;
5123 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
5124 q=GetCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
5125 exception);
cristy4c08aed2011-07-01 19:47:50 +00005126 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00005127 {
5128 status=MagickFalse;
5129 continue;
5130 }
cristyddd82202009-11-03 20:14:50 +00005131 pixel=bias;
cristybb503372010-05-27 20:51:26 +00005132 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00005133 {
cristyed231572011-07-14 02:18:59 +00005134 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00005135 {
cristy4c08aed2011-07-01 19:47:50 +00005136 pixel.red=GetPixelRed(image,p)-(MagickRealType) GetPixelRed(image,q);
cristy3ed852e2009-09-05 21:47:34 +00005137 if (fabs(2.0*pixel.red) < quantum_threshold)
cristy4c08aed2011-07-01 19:47:50 +00005138 pixel.red=(MagickRealType) GetPixelRed(image,p);
cristy3ed852e2009-09-05 21:47:34 +00005139 else
cristy4c08aed2011-07-01 19:47:50 +00005140 pixel.red=(MagickRealType) GetPixelRed(image,p)+(pixel.red*amount);
5141 SetPixelRed(unsharp_image,ClampToQuantum(pixel.red),q);
cristy3ed852e2009-09-05 21:47:34 +00005142 }
cristyed231572011-07-14 02:18:59 +00005143 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00005144 {
cristy4c08aed2011-07-01 19:47:50 +00005145 pixel.green=GetPixelGreen(image,p)-
5146 (MagickRealType) GetPixelGreen(image,q);
cristy3ed852e2009-09-05 21:47:34 +00005147 if (fabs(2.0*pixel.green) < quantum_threshold)
cristy4c08aed2011-07-01 19:47:50 +00005148 pixel.green=(MagickRealType)
5149 GetPixelGreen(image,p);
cristy3ed852e2009-09-05 21:47:34 +00005150 else
cristy4c08aed2011-07-01 19:47:50 +00005151 pixel.green=(MagickRealType)
5152 GetPixelGreen(image,p)+
5153 (pixel.green*amount);
5154 SetPixelGreen(unsharp_image,
5155 ClampToQuantum(pixel.green),q);
cristy3ed852e2009-09-05 21:47:34 +00005156 }
cristyed231572011-07-14 02:18:59 +00005157 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00005158 {
cristy4c08aed2011-07-01 19:47:50 +00005159 pixel.blue=GetPixelBlue(image,p)-
5160 (MagickRealType) GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00005161 if (fabs(2.0*pixel.blue) < quantum_threshold)
cristy4c08aed2011-07-01 19:47:50 +00005162 pixel.blue=(MagickRealType)
5163 GetPixelBlue(image,p);
cristy3ed852e2009-09-05 21:47:34 +00005164 else
cristy4c08aed2011-07-01 19:47:50 +00005165 pixel.blue=(MagickRealType)
5166 GetPixelBlue(image,p)+(pixel.blue*amount);
5167 SetPixelBlue(unsharp_image,
5168 ClampToQuantum(pixel.blue),q);
cristy3ed852e2009-09-05 21:47:34 +00005169 }
cristyed231572011-07-14 02:18:59 +00005170 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00005171 (image->colorspace == CMYKColorspace))
5172 {
cristy4c08aed2011-07-01 19:47:50 +00005173 pixel.black=GetPixelBlack(image,p)-
5174 (MagickRealType) GetPixelBlack(image,q);
5175 if (fabs(2.0*pixel.black) < quantum_threshold)
5176 pixel.black=(MagickRealType)
5177 GetPixelBlack(image,p);
cristy3ed852e2009-09-05 21:47:34 +00005178 else
cristy4c08aed2011-07-01 19:47:50 +00005179 pixel.black=(MagickRealType)
5180 GetPixelBlack(image,p)+(pixel.black*
5181 amount);
5182 SetPixelBlack(unsharp_image,
5183 ClampToQuantum(pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00005184 }
cristyed231572011-07-14 02:18:59 +00005185 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00005186 {
5187 pixel.alpha=GetPixelAlpha(image,p)-
5188 (MagickRealType) GetPixelAlpha(image,q);
5189 if (fabs(2.0*pixel.alpha) < quantum_threshold)
5190 pixel.alpha=(MagickRealType)
5191 GetPixelAlpha(image,p);
5192 else
5193 pixel.alpha=GetPixelAlpha(image,p)+
5194 (pixel.alpha*amount);
5195 SetPixelAlpha(unsharp_image,
5196 ClampToQuantum(pixel.alpha),q);
5197 }
cristyed231572011-07-14 02:18:59 +00005198 p+=GetPixelChannels(image);
5199 q+=GetPixelChannels(unsharp_image);
cristy3ed852e2009-09-05 21:47:34 +00005200 }
5201 if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
5202 status=MagickFalse;
5203 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5204 {
5205 MagickBooleanType
5206 proceed;
5207
cristyb5d5f722009-11-04 03:03:49 +00005208#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00005209 #pragma omp critical (MagickCore_UnsharpMaskImage)
cristy3ed852e2009-09-05 21:47:34 +00005210#endif
5211 proceed=SetImageProgress(image,SharpenImageTag,progress++,image->rows);
5212 if (proceed == MagickFalse)
5213 status=MagickFalse;
5214 }
5215 }
5216 unsharp_image->type=image->type;
5217 unsharp_view=DestroyCacheView(unsharp_view);
5218 image_view=DestroyCacheView(image_view);
5219 if (status == MagickFalse)
5220 unsharp_image=DestroyImage(unsharp_image);
5221 return(unsharp_image);
5222}