blob: 59f7deee703f7d0e454a8d361c3260d671457df0 [file] [log] [blame]
cristy3ed852e2009-09-05 21:47:34 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% EEEEE FFFFF FFFFF EEEEE CCCC TTTTT %
7% E F F E C T %
8% EEE FFF FFF EEE C T %
9% E F F E C T %
10% EEEEE F F EEEEE CCCC T %
11% %
12% %
13% MagickCore Image Effects Methods %
14% %
15% Software Design %
16% John Cristy %
17% October 1996 %
18% %
19% %
cristy7e41fe82010-12-04 23:12:08 +000020% Copyright 1999-2011 ImageMagick Studio LLC, a non-profit organization %
cristy3ed852e2009-09-05 21:47:34 +000021% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
26% http://www.imagemagick.org/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
36%
37%
38*/
39
40/*
41 Include declarations.
42*/
cristy4c08aed2011-07-01 19:47:50 +000043#include "MagickCore/studio.h"
44#include "MagickCore/accelerate.h"
45#include "MagickCore/blob.h"
46#include "MagickCore/cache-view.h"
47#include "MagickCore/color.h"
48#include "MagickCore/color-private.h"
49#include "MagickCore/colorspace.h"
50#include "MagickCore/constitute.h"
51#include "MagickCore/decorate.h"
52#include "MagickCore/draw.h"
53#include "MagickCore/enhance.h"
54#include "MagickCore/exception.h"
55#include "MagickCore/exception-private.h"
56#include "MagickCore/effect.h"
57#include "MagickCore/fx.h"
58#include "MagickCore/gem.h"
59#include "MagickCore/geometry.h"
60#include "MagickCore/image-private.h"
61#include "MagickCore/list.h"
62#include "MagickCore/log.h"
63#include "MagickCore/memory_.h"
64#include "MagickCore/monitor.h"
65#include "MagickCore/monitor-private.h"
66#include "MagickCore/montage.h"
67#include "MagickCore/morphology.h"
68#include "MagickCore/paint.h"
69#include "MagickCore/pixel-accessor.h"
70#include "MagickCore/property.h"
71#include "MagickCore/quantize.h"
72#include "MagickCore/quantum.h"
73#include "MagickCore/quantum-private.h"
74#include "MagickCore/random_.h"
75#include "MagickCore/random-private.h"
76#include "MagickCore/resample.h"
77#include "MagickCore/resample-private.h"
78#include "MagickCore/resize.h"
79#include "MagickCore/resource_.h"
80#include "MagickCore/segment.h"
81#include "MagickCore/shear.h"
82#include "MagickCore/signature-private.h"
83#include "MagickCore/string_.h"
84#include "MagickCore/thread-private.h"
85#include "MagickCore/transform.h"
86#include "MagickCore/threshold.h"
cristy3ed852e2009-09-05 21:47:34 +000087
88/*
89%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
90% %
91% %
92% %
93% A d a p t i v e B l u r I m a g e %
94% %
95% %
96% %
97%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
98%
99% AdaptiveBlurImage() adaptively blurs the image by blurring less
100% intensely near image edges and more intensely far from edges. We blur the
101% image with a Gaussian operator of the given radius and standard deviation
102% (sigma). For reasonable results, radius should be larger than sigma. Use a
103% radius of 0 and AdaptiveBlurImage() selects a suitable radius for you.
104%
105% The format of the AdaptiveBlurImage method is:
106%
107% Image *AdaptiveBlurImage(const Image *image,const double radius,
108% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000109%
110% A description of each parameter follows:
111%
112% o image: the image.
113%
cristy3ed852e2009-09-05 21:47:34 +0000114% o radius: the radius of the Gaussian, in pixels, not counting the center
115% pixel.
116%
117% o sigma: the standard deviation of the Laplacian, in pixels.
118%
119% o exception: return any errors or warnings in this structure.
120%
121*/
122
cristyf89cb1d2011-07-07 01:24:37 +0000123MagickExport MagickBooleanType AdaptiveLevelImage(Image *image,
124 const char *levels)
125{
126 double
127 black_point,
128 gamma,
129 white_point;
130
131 GeometryInfo
132 geometry_info;
133
134 MagickBooleanType
135 status;
136
137 MagickStatusType
138 flags;
139
140 /*
141 Parse levels.
142 */
143 if (levels == (char *) NULL)
144 return(MagickFalse);
145 flags=ParseGeometry(levels,&geometry_info);
146 black_point=geometry_info.rho;
147 white_point=(double) QuantumRange;
148 if ((flags & SigmaValue) != 0)
149 white_point=geometry_info.sigma;
150 gamma=1.0;
151 if ((flags & XiValue) != 0)
152 gamma=geometry_info.xi;
153 if ((flags & PercentValue) != 0)
154 {
155 black_point*=(double) image->columns*image->rows/100.0;
156 white_point*=(double) image->columns*image->rows/100.0;
157 }
158 if ((flags & SigmaValue) == 0)
159 white_point=(double) QuantumRange-black_point;
160 if ((flags & AspectValue ) == 0)
161 status=LevelImage(image,black_point,white_point,gamma);
162 else
163 status=LevelizeImage(image,black_point,white_point,gamma);
164 return(status);
165}
166
cristyf4ad9df2011-07-08 16:49:03 +0000167MagickExport Image *AdaptiveBlurImage(const Image *image,
168 const double radius,const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000169{
170#define AdaptiveBlurImageTag "Convolve/Image"
171#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
172
cristyc4c8d132010-01-07 01:58:38 +0000173 CacheView
174 *blur_view,
175 *edge_view,
176 *image_view;
177
cristy3ed852e2009-09-05 21:47:34 +0000178 double
cristy47e00502009-12-17 19:19:57 +0000179 **kernel,
180 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000181
182 Image
183 *blur_image,
184 *edge_image,
185 *gaussian_image;
186
cristy3ed852e2009-09-05 21:47:34 +0000187 MagickBooleanType
188 status;
189
cristybb503372010-05-27 20:51:26 +0000190 MagickOffsetType
191 progress;
192
cristy4c08aed2011-07-01 19:47:50 +0000193 PixelInfo
cristyddd82202009-11-03 20:14:50 +0000194 bias;
cristy3ed852e2009-09-05 21:47:34 +0000195
cristybb503372010-05-27 20:51:26 +0000196 register ssize_t
cristy47e00502009-12-17 19:19:57 +0000197 i;
cristy3ed852e2009-09-05 21:47:34 +0000198
cristybb503372010-05-27 20:51:26 +0000199 size_t
cristy3ed852e2009-09-05 21:47:34 +0000200 width;
201
cristybb503372010-05-27 20:51:26 +0000202 ssize_t
203 j,
204 k,
205 u,
206 v,
207 y;
208
cristy3ed852e2009-09-05 21:47:34 +0000209 assert(image != (const Image *) NULL);
210 assert(image->signature == MagickSignature);
211 if (image->debug != MagickFalse)
212 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
213 assert(exception != (ExceptionInfo *) NULL);
214 assert(exception->signature == MagickSignature);
215 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
216 if (blur_image == (Image *) NULL)
217 return((Image *) NULL);
218 if (fabs(sigma) <= MagickEpsilon)
219 return(blur_image);
220 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
221 {
222 InheritException(exception,&blur_image->exception);
223 blur_image=DestroyImage(blur_image);
224 return((Image *) NULL);
225 }
226 /*
227 Edge detect the image brighness channel, level, blur, and level again.
228 */
229 edge_image=EdgeImage(image,radius,exception);
230 if (edge_image == (Image *) NULL)
231 {
232 blur_image=DestroyImage(blur_image);
233 return((Image *) NULL);
234 }
cristyf89cb1d2011-07-07 01:24:37 +0000235 (void) AdaptiveLevelImage(edge_image,"20%,95%");
cristy3ed852e2009-09-05 21:47:34 +0000236 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,exception);
237 if (gaussian_image != (Image *) NULL)
238 {
239 edge_image=DestroyImage(edge_image);
240 edge_image=gaussian_image;
241 }
cristyf89cb1d2011-07-07 01:24:37 +0000242 (void) AdaptiveLevelImage(edge_image,"10%,95%");
cristy3ed852e2009-09-05 21:47:34 +0000243 /*
244 Create a set of kernels from maximum (radius,sigma) to minimum.
245 */
246 width=GetOptimalKernelWidth2D(radius,sigma);
247 kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
248 if (kernel == (double **) NULL)
249 {
250 edge_image=DestroyImage(edge_image);
251 blur_image=DestroyImage(blur_image);
252 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
253 }
254 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
cristybb503372010-05-27 20:51:26 +0000255 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000256 {
257 kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)*
258 sizeof(**kernel));
259 if (kernel[i] == (double *) NULL)
260 break;
cristy47e00502009-12-17 19:19:57 +0000261 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000262 j=(ssize_t) (width-i)/2;
cristy47e00502009-12-17 19:19:57 +0000263 k=0;
264 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000265 {
cristy47e00502009-12-17 19:19:57 +0000266 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000267 {
cristy4205a3c2010-09-12 20:19:59 +0000268 kernel[i][k]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
269 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +0000270 normalize+=kernel[i][k];
271 k++;
cristy3ed852e2009-09-05 21:47:34 +0000272 }
273 }
cristy3ed852e2009-09-05 21:47:34 +0000274 if (fabs(normalize) <= MagickEpsilon)
275 normalize=1.0;
276 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000277 for (k=0; k < (j*j); k++)
278 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000279 }
cristybb503372010-05-27 20:51:26 +0000280 if (i < (ssize_t) width)
cristy3ed852e2009-09-05 21:47:34 +0000281 {
282 for (i-=2; i >= 0; i-=2)
283 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
284 kernel=(double **) RelinquishMagickMemory(kernel);
285 edge_image=DestroyImage(edge_image);
286 blur_image=DestroyImage(blur_image);
287 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
288 }
289 /*
290 Adaptively blur image.
291 */
292 status=MagickTrue;
293 progress=0;
cristy4c08aed2011-07-01 19:47:50 +0000294 GetPixelInfo(image,&bias);
295 SetPixelInfoBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000296 image_view=AcquireCacheView(image);
297 edge_view=AcquireCacheView(edge_image);
298 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000299#if defined(MAGICKCORE_OPENMP_SUPPORT)
300 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000301#endif
cristybb503372010-05-27 20:51:26 +0000302 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000303 {
cristy4c08aed2011-07-01 19:47:50 +0000304 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000305 *restrict p,
306 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000307
cristy4c08aed2011-07-01 19:47:50 +0000308 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000309 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000310
cristy117ff172010-08-15 21:35:32 +0000311 register ssize_t
312 x;
313
cristy3ed852e2009-09-05 21:47:34 +0000314 if (status == MagickFalse)
315 continue;
316 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
317 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
318 exception);
cristy4c08aed2011-07-01 19:47:50 +0000319 if ((r == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000320 {
321 status=MagickFalse;
322 continue;
323 }
cristybb503372010-05-27 20:51:26 +0000324 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000325 {
cristy4c08aed2011-07-01 19:47:50 +0000326 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000327 pixel;
328
329 MagickRealType
330 alpha,
331 gamma;
332
333 register const double
cristyc47d1f82009-11-26 01:44:43 +0000334 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000335
cristybb503372010-05-27 20:51:26 +0000336 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000337 i,
338 u,
339 v;
340
341 gamma=0.0;
cristy4c08aed2011-07-01 19:47:50 +0000342 i=(ssize_t) ceil((double) width*QuantumScale*
343 GetPixelIntensity(edge_image,r)-0.5);
cristy3ed852e2009-09-05 21:47:34 +0000344 if (i < 0)
345 i=0;
346 else
cristybb503372010-05-27 20:51:26 +0000347 if (i > (ssize_t) width)
348 i=(ssize_t) width;
cristy3ed852e2009-09-05 21:47:34 +0000349 if ((i & 0x01) != 0)
350 i--;
cristya21afde2010-07-02 00:45:40 +0000351 p=GetCacheViewVirtualPixels(image_view,x-((ssize_t) (width-i)/2L),y-
352 (ssize_t) ((width-i)/2L),width-i,width-i,exception);
cristy4c08aed2011-07-01 19:47:50 +0000353 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000354 break;
cristyddd82202009-11-03 20:14:50 +0000355 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +0000356 k=kernel[i];
cristybb503372010-05-27 20:51:26 +0000357 for (v=0; v < (ssize_t) (width-i); v++)
cristy3ed852e2009-09-05 21:47:34 +0000358 {
cristybb503372010-05-27 20:51:26 +0000359 for (u=0; u < (ssize_t) (width-i); u++)
cristy3ed852e2009-09-05 21:47:34 +0000360 {
361 alpha=1.0;
cristyed231572011-07-14 02:18:59 +0000362 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000363 (image->matte != MagickFalse))
cristy4c08aed2011-07-01 19:47:50 +0000364 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,p));
cristyed231572011-07-14 02:18:59 +0000365 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000366 pixel.red+=(*k)*alpha*GetPixelRed(image,p);
cristyed231572011-07-14 02:18:59 +0000367 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000368 pixel.green+=(*k)*alpha*GetPixelGreen(image,p);
cristyed231572011-07-14 02:18:59 +0000369 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000370 pixel.blue+=(*k)*alpha*GetPixelBlue(image,p);
cristyed231572011-07-14 02:18:59 +0000371 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000372 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +0000373 pixel.black+=(*k)*alpha*GetPixelBlack(image,p);
cristyed231572011-07-14 02:18:59 +0000374 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000375 pixel.alpha+=(*k)*GetPixelAlpha(image,p);
cristy3ed852e2009-09-05 21:47:34 +0000376 gamma+=(*k)*alpha;
377 k++;
cristyed231572011-07-14 02:18:59 +0000378 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000379 }
380 }
381 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +0000382 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000383 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +0000384 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000385 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +0000386 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000387 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +0000388 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000389 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +0000390 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristyed231572011-07-14 02:18:59 +0000391 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000392 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristyed231572011-07-14 02:18:59 +0000393 q+=GetPixelChannels(blur_image);
394 r+=GetPixelChannels(edge_image);
cristy3ed852e2009-09-05 21:47:34 +0000395 }
396 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
397 status=MagickFalse;
398 if (image->progress_monitor != (MagickProgressMonitor) NULL)
399 {
400 MagickBooleanType
401 proceed;
402
cristyb5d5f722009-11-04 03:03:49 +0000403#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +0000404 #pragma omp critical (MagickCore_AdaptiveBlurImage)
cristy3ed852e2009-09-05 21:47:34 +0000405#endif
406 proceed=SetImageProgress(image,AdaptiveBlurImageTag,progress++,
407 image->rows);
408 if (proceed == MagickFalse)
409 status=MagickFalse;
410 }
411 }
412 blur_image->type=image->type;
413 blur_view=DestroyCacheView(blur_view);
414 edge_view=DestroyCacheView(edge_view);
415 image_view=DestroyCacheView(image_view);
416 edge_image=DestroyImage(edge_image);
cristybb503372010-05-27 20:51:26 +0000417 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000418 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
419 kernel=(double **) RelinquishMagickMemory(kernel);
420 if (status == MagickFalse)
421 blur_image=DestroyImage(blur_image);
422 return(blur_image);
423}
424
425/*
426%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
427% %
428% %
429% %
430% A d a p t i v e S h a r p e n I m a g e %
431% %
432% %
433% %
434%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
435%
436% AdaptiveSharpenImage() adaptively sharpens the image by sharpening more
437% intensely near image edges and less intensely far from edges. We sharpen the
438% image with a Gaussian operator of the given radius and standard deviation
439% (sigma). For reasonable results, radius should be larger than sigma. Use a
440% radius of 0 and AdaptiveSharpenImage() selects a suitable radius for you.
441%
442% The format of the AdaptiveSharpenImage method is:
443%
444% Image *AdaptiveSharpenImage(const Image *image,const double radius,
445% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000446%
447% A description of each parameter follows:
448%
449% o image: the image.
450%
cristy3ed852e2009-09-05 21:47:34 +0000451% o radius: the radius of the Gaussian, in pixels, not counting the center
452% pixel.
453%
454% o sigma: the standard deviation of the Laplacian, in pixels.
455%
456% o exception: return any errors or warnings in this structure.
457%
458*/
cristy3ed852e2009-09-05 21:47:34 +0000459MagickExport Image *AdaptiveSharpenImage(const Image *image,const double radius,
460 const double sigma,ExceptionInfo *exception)
461{
cristy3ed852e2009-09-05 21:47:34 +0000462#define AdaptiveSharpenImageTag "Convolve/Image"
463#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
464
cristyc4c8d132010-01-07 01:58:38 +0000465 CacheView
466 *sharp_view,
467 *edge_view,
468 *image_view;
469
cristy3ed852e2009-09-05 21:47:34 +0000470 double
cristy47e00502009-12-17 19:19:57 +0000471 **kernel,
472 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000473
474 Image
475 *sharp_image,
476 *edge_image,
477 *gaussian_image;
478
cristy3ed852e2009-09-05 21:47:34 +0000479 MagickBooleanType
480 status;
481
cristybb503372010-05-27 20:51:26 +0000482 MagickOffsetType
483 progress;
484
cristy4c08aed2011-07-01 19:47:50 +0000485 PixelInfo
cristyddd82202009-11-03 20:14:50 +0000486 bias;
cristy3ed852e2009-09-05 21:47:34 +0000487
cristybb503372010-05-27 20:51:26 +0000488 register ssize_t
cristy47e00502009-12-17 19:19:57 +0000489 i;
cristy3ed852e2009-09-05 21:47:34 +0000490
cristybb503372010-05-27 20:51:26 +0000491 size_t
cristy3ed852e2009-09-05 21:47:34 +0000492 width;
493
cristybb503372010-05-27 20:51:26 +0000494 ssize_t
495 j,
496 k,
497 u,
498 v,
499 y;
500
cristy3ed852e2009-09-05 21:47:34 +0000501 assert(image != (const Image *) NULL);
502 assert(image->signature == MagickSignature);
503 if (image->debug != MagickFalse)
504 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
505 assert(exception != (ExceptionInfo *) NULL);
506 assert(exception->signature == MagickSignature);
507 sharp_image=CloneImage(image,0,0,MagickTrue,exception);
508 if (sharp_image == (Image *) NULL)
509 return((Image *) NULL);
510 if (fabs(sigma) <= MagickEpsilon)
511 return(sharp_image);
512 if (SetImageStorageClass(sharp_image,DirectClass) == MagickFalse)
513 {
514 InheritException(exception,&sharp_image->exception);
515 sharp_image=DestroyImage(sharp_image);
516 return((Image *) NULL);
517 }
518 /*
519 Edge detect the image brighness channel, level, sharp, and level again.
520 */
521 edge_image=EdgeImage(image,radius,exception);
522 if (edge_image == (Image *) NULL)
523 {
524 sharp_image=DestroyImage(sharp_image);
525 return((Image *) NULL);
526 }
cristyf89cb1d2011-07-07 01:24:37 +0000527 (void) AdaptiveLevelImage(edge_image,"20%,95%");
cristy3ed852e2009-09-05 21:47:34 +0000528 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,exception);
529 if (gaussian_image != (Image *) NULL)
530 {
531 edge_image=DestroyImage(edge_image);
532 edge_image=gaussian_image;
533 }
cristyf89cb1d2011-07-07 01:24:37 +0000534 (void) AdaptiveLevelImage(edge_image,"10%,95%");
cristy3ed852e2009-09-05 21:47:34 +0000535 /*
536 Create a set of kernels from maximum (radius,sigma) to minimum.
537 */
538 width=GetOptimalKernelWidth2D(radius,sigma);
539 kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
540 if (kernel == (double **) NULL)
541 {
542 edge_image=DestroyImage(edge_image);
543 sharp_image=DestroyImage(sharp_image);
544 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
545 }
546 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
cristybb503372010-05-27 20:51:26 +0000547 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000548 {
549 kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)*
550 sizeof(**kernel));
551 if (kernel[i] == (double *) NULL)
552 break;
cristy47e00502009-12-17 19:19:57 +0000553 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000554 j=(ssize_t) (width-i)/2;
cristy47e00502009-12-17 19:19:57 +0000555 k=0;
556 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000557 {
cristy47e00502009-12-17 19:19:57 +0000558 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000559 {
cristy4205a3c2010-09-12 20:19:59 +0000560 kernel[i][k]=(double) (-exp(-((double) u*u+v*v)/(2.0*MagickSigma*
561 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +0000562 normalize+=kernel[i][k];
563 k++;
cristy3ed852e2009-09-05 21:47:34 +0000564 }
565 }
cristy3ed852e2009-09-05 21:47:34 +0000566 if (fabs(normalize) <= MagickEpsilon)
567 normalize=1.0;
568 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000569 for (k=0; k < (j*j); k++)
570 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000571 }
cristybb503372010-05-27 20:51:26 +0000572 if (i < (ssize_t) width)
cristy3ed852e2009-09-05 21:47:34 +0000573 {
574 for (i-=2; i >= 0; i-=2)
575 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
576 kernel=(double **) RelinquishMagickMemory(kernel);
577 edge_image=DestroyImage(edge_image);
578 sharp_image=DestroyImage(sharp_image);
579 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
580 }
581 /*
582 Adaptively sharpen image.
583 */
584 status=MagickTrue;
585 progress=0;
cristy4c08aed2011-07-01 19:47:50 +0000586 GetPixelInfo(image,&bias);
587 SetPixelInfoBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000588 image_view=AcquireCacheView(image);
589 edge_view=AcquireCacheView(edge_image);
590 sharp_view=AcquireCacheView(sharp_image);
cristyb5d5f722009-11-04 03:03:49 +0000591#if defined(MAGICKCORE_OPENMP_SUPPORT)
592 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000593#endif
cristybb503372010-05-27 20:51:26 +0000594 for (y=0; y < (ssize_t) sharp_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000595 {
cristy4c08aed2011-07-01 19:47:50 +0000596 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000597 *restrict p,
598 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000599
cristy4c08aed2011-07-01 19:47:50 +0000600 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000601 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000602
cristy117ff172010-08-15 21:35:32 +0000603 register ssize_t
604 x;
605
cristy3ed852e2009-09-05 21:47:34 +0000606 if (status == MagickFalse)
607 continue;
608 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
609 q=QueueCacheViewAuthenticPixels(sharp_view,0,y,sharp_image->columns,1,
610 exception);
cristy4c08aed2011-07-01 19:47:50 +0000611 if ((r == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000612 {
613 status=MagickFalse;
614 continue;
615 }
cristybb503372010-05-27 20:51:26 +0000616 for (x=0; x < (ssize_t) sharp_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000617 {
cristy4c08aed2011-07-01 19:47:50 +0000618 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000619 pixel;
620
621 MagickRealType
622 alpha,
623 gamma;
624
625 register const double
cristyc47d1f82009-11-26 01:44:43 +0000626 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000627
cristybb503372010-05-27 20:51:26 +0000628 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000629 i,
630 u,
631 v;
632
633 gamma=0.0;
cristy4c08aed2011-07-01 19:47:50 +0000634 i=(ssize_t) ceil((double) width*QuantumScale*
635 GetPixelIntensity(edge_image,r)-0.5);
cristy3ed852e2009-09-05 21:47:34 +0000636 if (i < 0)
637 i=0;
638 else
cristybb503372010-05-27 20:51:26 +0000639 if (i > (ssize_t) width)
640 i=(ssize_t) width;
cristy3ed852e2009-09-05 21:47:34 +0000641 if ((i & 0x01) != 0)
642 i--;
cristy117ff172010-08-15 21:35:32 +0000643 p=GetCacheViewVirtualPixels(image_view,x-((ssize_t) (width-i)/2L),y-
644 (ssize_t) ((width-i)/2L),width-i,width-i,exception);
cristy4c08aed2011-07-01 19:47:50 +0000645 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000646 break;
cristy3ed852e2009-09-05 21:47:34 +0000647 k=kernel[i];
cristyddd82202009-11-03 20:14:50 +0000648 pixel=bias;
cristybb503372010-05-27 20:51:26 +0000649 for (v=0; v < (ssize_t) (width-i); v++)
cristy3ed852e2009-09-05 21:47:34 +0000650 {
cristybb503372010-05-27 20:51:26 +0000651 for (u=0; u < (ssize_t) (width-i); u++)
cristy3ed852e2009-09-05 21:47:34 +0000652 {
653 alpha=1.0;
cristyed231572011-07-14 02:18:59 +0000654 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000655 (image->matte != MagickFalse))
cristy4c08aed2011-07-01 19:47:50 +0000656 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,p));
cristyed231572011-07-14 02:18:59 +0000657 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000658 pixel.red+=(*k)*alpha*GetPixelRed(image,p);
cristyed231572011-07-14 02:18:59 +0000659 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000660 pixel.green+=(*k)*alpha*GetPixelGreen(image,p);
cristyed231572011-07-14 02:18:59 +0000661 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000662 pixel.blue+=(*k)*alpha*GetPixelBlue(image,p);
cristyed231572011-07-14 02:18:59 +0000663 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000664 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +0000665 pixel.black+=(*k)*alpha*GetPixelBlack(image,p);
cristyed231572011-07-14 02:18:59 +0000666 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000667 pixel.alpha+=(*k)*GetPixelAlpha(image,p);
cristy3ed852e2009-09-05 21:47:34 +0000668 gamma+=(*k)*alpha;
669 k++;
cristyed231572011-07-14 02:18:59 +0000670 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000671 }
672 }
673 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +0000674 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000675 SetPixelRed(sharp_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +0000676 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000677 SetPixelGreen(sharp_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +0000678 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000679 SetPixelBlue(sharp_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +0000680 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000681 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +0000682 SetPixelBlack(sharp_image,ClampToQuantum(gamma*pixel.black),q);
cristyed231572011-07-14 02:18:59 +0000683 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000684 SetPixelAlpha(sharp_image,ClampToQuantum(pixel.alpha),q);
cristyed231572011-07-14 02:18:59 +0000685 q+=GetPixelChannels(sharp_image);
686 r+=GetPixelChannels(edge_image);
cristy3ed852e2009-09-05 21:47:34 +0000687 }
688 if (SyncCacheViewAuthenticPixels(sharp_view,exception) == MagickFalse)
689 status=MagickFalse;
690 if (image->progress_monitor != (MagickProgressMonitor) NULL)
691 {
692 MagickBooleanType
693 proceed;
694
cristyb5d5f722009-11-04 03:03:49 +0000695#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +0000696 #pragma omp critical (MagickCore_AdaptiveSharpenImage)
cristy3ed852e2009-09-05 21:47:34 +0000697#endif
698 proceed=SetImageProgress(image,AdaptiveSharpenImageTag,progress++,
699 image->rows);
700 if (proceed == MagickFalse)
701 status=MagickFalse;
702 }
703 }
704 sharp_image->type=image->type;
705 sharp_view=DestroyCacheView(sharp_view);
706 edge_view=DestroyCacheView(edge_view);
707 image_view=DestroyCacheView(image_view);
708 edge_image=DestroyImage(edge_image);
cristybb503372010-05-27 20:51:26 +0000709 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000710 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
711 kernel=(double **) RelinquishMagickMemory(kernel);
712 if (status == MagickFalse)
713 sharp_image=DestroyImage(sharp_image);
714 return(sharp_image);
715}
716
717/*
718%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
719% %
720% %
721% %
722% B l u r I m a g e %
723% %
724% %
725% %
726%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
727%
728% BlurImage() blurs an image. We convolve the image with a Gaussian operator
729% of the given radius and standard deviation (sigma). For reasonable results,
730% the radius should be larger than sigma. Use a radius of 0 and BlurImage()
731% selects a suitable radius for you.
732%
733% BlurImage() differs from GaussianBlurImage() in that it uses a separable
734% kernel which is faster but mathematically equivalent to the non-separable
735% kernel.
736%
737% The format of the BlurImage method is:
738%
739% Image *BlurImage(const Image *image,const double radius,
740% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000741%
742% A description of each parameter follows:
743%
744% o image: the image.
745%
cristy3ed852e2009-09-05 21:47:34 +0000746% o radius: the radius of the Gaussian, in pixels, not counting the center
747% pixel.
748%
749% o sigma: the standard deviation of the Gaussian, in pixels.
750%
751% o exception: return any errors or warnings in this structure.
752%
753*/
754
cristybb503372010-05-27 20:51:26 +0000755static double *GetBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +0000756{
cristy3ed852e2009-09-05 21:47:34 +0000757 double
cristy47e00502009-12-17 19:19:57 +0000758 *kernel,
759 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000760
cristy117ff172010-08-15 21:35:32 +0000761 register ssize_t
762 i;
763
cristybb503372010-05-27 20:51:26 +0000764 ssize_t
cristy47e00502009-12-17 19:19:57 +0000765 j,
766 k;
cristy3ed852e2009-09-05 21:47:34 +0000767
cristy3ed852e2009-09-05 21:47:34 +0000768 /*
769 Generate a 1-D convolution kernel.
770 */
771 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
772 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
773 if (kernel == (double *) NULL)
774 return(0);
cristy3ed852e2009-09-05 21:47:34 +0000775 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000776 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +0000777 i=0;
778 for (k=(-j); k <= j; k++)
779 {
cristy4205a3c2010-09-12 20:19:59 +0000780 kernel[i]=(double) (exp(-((double) k*k)/(2.0*MagickSigma*MagickSigma))/
781 (MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +0000782 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +0000783 i++;
784 }
cristybb503372010-05-27 20:51:26 +0000785 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000786 kernel[i]/=normalize;
787 return(kernel);
788}
789
cristyf4ad9df2011-07-08 16:49:03 +0000790MagickExport Image *BlurImage(const Image *image,const double radius,
791 const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000792{
793#define BlurImageTag "Blur/Image"
794
cristyc4c8d132010-01-07 01:58:38 +0000795 CacheView
796 *blur_view,
797 *image_view;
798
cristy3ed852e2009-09-05 21:47:34 +0000799 double
800 *kernel;
801
802 Image
803 *blur_image;
804
cristy3ed852e2009-09-05 21:47:34 +0000805 MagickBooleanType
806 status;
807
cristybb503372010-05-27 20:51:26 +0000808 MagickOffsetType
809 progress;
810
cristy4c08aed2011-07-01 19:47:50 +0000811 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000812 bias;
813
cristybb503372010-05-27 20:51:26 +0000814 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000815 i;
816
cristybb503372010-05-27 20:51:26 +0000817 size_t
cristy3ed852e2009-09-05 21:47:34 +0000818 width;
819
cristybb503372010-05-27 20:51:26 +0000820 ssize_t
821 x,
822 y;
823
cristy3ed852e2009-09-05 21:47:34 +0000824 /*
825 Initialize blur image attributes.
826 */
827 assert(image != (Image *) NULL);
828 assert(image->signature == MagickSignature);
829 if (image->debug != MagickFalse)
830 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
831 assert(exception != (ExceptionInfo *) NULL);
832 assert(exception->signature == MagickSignature);
833 blur_image=CloneImage(image,0,0,MagickTrue,exception);
834 if (blur_image == (Image *) NULL)
835 return((Image *) NULL);
836 if (fabs(sigma) <= MagickEpsilon)
837 return(blur_image);
838 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
839 {
840 InheritException(exception,&blur_image->exception);
841 blur_image=DestroyImage(blur_image);
842 return((Image *) NULL);
843 }
844 width=GetOptimalKernelWidth1D(radius,sigma);
845 kernel=GetBlurKernel(width,sigma);
846 if (kernel == (double *) NULL)
847 {
848 blur_image=DestroyImage(blur_image);
849 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
850 }
851 if (image->debug != MagickFalse)
852 {
853 char
854 format[MaxTextExtent],
855 *message;
856
857 register const double
858 *k;
859
860 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +0000861 " BlurImage with %.20g kernel:",(double) width);
cristy3ed852e2009-09-05 21:47:34 +0000862 message=AcquireString("");
863 k=kernel;
cristybb503372010-05-27 20:51:26 +0000864 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000865 {
866 *message='\0';
cristyb51dff52011-05-19 16:55:47 +0000867 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) i);
cristy3ed852e2009-09-05 21:47:34 +0000868 (void) ConcatenateString(&message,format);
cristyb51dff52011-05-19 16:55:47 +0000869 (void) FormatLocaleString(format,MaxTextExtent,"%g ",*k++);
cristy3ed852e2009-09-05 21:47:34 +0000870 (void) ConcatenateString(&message,format);
871 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
872 }
873 message=DestroyString(message);
874 }
875 /*
876 Blur rows.
877 */
878 status=MagickTrue;
879 progress=0;
cristy4c08aed2011-07-01 19:47:50 +0000880 GetPixelInfo(image,&bias);
881 SetPixelInfoBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000882 image_view=AcquireCacheView(image);
883 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000884#if defined(MAGICKCORE_OPENMP_SUPPORT)
885 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000886#endif
cristybb503372010-05-27 20:51:26 +0000887 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000888 {
cristy4c08aed2011-07-01 19:47:50 +0000889 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000890 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000891
cristy4c08aed2011-07-01 19:47:50 +0000892 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000893 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000894
cristy117ff172010-08-15 21:35:32 +0000895 register ssize_t
896 x;
897
cristy3ed852e2009-09-05 21:47:34 +0000898 if (status == MagickFalse)
899 continue;
cristy117ff172010-08-15 21:35:32 +0000900 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y,
901 image->columns+width,1,exception);
cristy3ed852e2009-09-05 21:47:34 +0000902 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
903 exception);
cristy4c08aed2011-07-01 19:47:50 +0000904 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000905 {
906 status=MagickFalse;
907 continue;
908 }
cristybb503372010-05-27 20:51:26 +0000909 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000910 {
cristy4c08aed2011-07-01 19:47:50 +0000911 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000912 pixel;
913
914 register const double
cristyc47d1f82009-11-26 01:44:43 +0000915 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000916
cristy4c08aed2011-07-01 19:47:50 +0000917 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000918 *restrict kernel_pixels;
cristy3ed852e2009-09-05 21:47:34 +0000919
cristybb503372010-05-27 20:51:26 +0000920 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000921 i;
922
cristyddd82202009-11-03 20:14:50 +0000923 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +0000924 k=kernel;
925 kernel_pixels=p;
cristyed231572011-07-14 02:18:59 +0000926 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) ||
cristyf4ad9df2011-07-08 16:49:03 +0000927 (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +0000928 {
cristybb503372010-05-27 20:51:26 +0000929 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000930 {
cristy4c08aed2011-07-01 19:47:50 +0000931 pixel.red+=(*k)*GetPixelRed(image,kernel_pixels);
932 pixel.green+=(*k)*GetPixelGreen(image,kernel_pixels);
933 pixel.blue+=(*k)*GetPixelBlue(image,kernel_pixels);
934 if (image->colorspace == CMYKColorspace)
935 pixel.black+=(*k)*GetPixelBlack(image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +0000936 k++;
cristyed231572011-07-14 02:18:59 +0000937 kernel_pixels+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000938 }
cristyed231572011-07-14 02:18:59 +0000939 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000940 SetPixelRed(blur_image,ClampToQuantum(pixel.red),q);
cristyed231572011-07-14 02:18:59 +0000941 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000942 SetPixelGreen(blur_image,ClampToQuantum(pixel.green),q);
cristyed231572011-07-14 02:18:59 +0000943 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000944 SetPixelBlue(blur_image,ClampToQuantum(pixel.blue),q);
cristyed231572011-07-14 02:18:59 +0000945 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000946 (blur_image->colorspace == CMYKColorspace))
947 SetPixelBlack(blur_image,ClampToQuantum(pixel.black),q);
cristyed231572011-07-14 02:18:59 +0000948 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +0000949 {
950 k=kernel;
951 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +0000952 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000953 {
cristy4c08aed2011-07-01 19:47:50 +0000954 pixel.alpha+=(*k)*GetPixelAlpha(image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +0000955 k++;
cristyed231572011-07-14 02:18:59 +0000956 kernel_pixels+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000957 }
cristy4c08aed2011-07-01 19:47:50 +0000958 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +0000959 }
960 }
961 else
962 {
963 MagickRealType
964 alpha,
965 gamma;
966
967 gamma=0.0;
cristybb503372010-05-27 20:51:26 +0000968 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000969 {
cristy4c08aed2011-07-01 19:47:50 +0000970 alpha=(MagickRealType) (QuantumScale*
971 GetPixelAlpha(image,kernel_pixels));
972 pixel.red+=(*k)*alpha*GetPixelRed(image,kernel_pixels);
973 pixel.green+=(*k)*alpha*GetPixelGreen(image,kernel_pixels);
974 pixel.blue+=(*k)*alpha*GetPixelBlue(image,kernel_pixels);
975 if (image->colorspace == CMYKColorspace)
976 pixel.black+=(*k)*alpha*GetPixelBlack(image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +0000977 gamma+=(*k)*alpha;
978 k++;
cristyed231572011-07-14 02:18:59 +0000979 kernel_pixels+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000980 }
981 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +0000982 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000983 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +0000984 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000985 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +0000986 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000987 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +0000988 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000989 (blur_image->colorspace == CMYKColorspace))
990 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristyed231572011-07-14 02:18:59 +0000991 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +0000992 {
993 k=kernel;
994 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +0000995 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000996 {
cristy4c08aed2011-07-01 19:47:50 +0000997 pixel.alpha+=(*k)*GetPixelAlpha(image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +0000998 k++;
cristyed231572011-07-14 02:18:59 +0000999 kernel_pixels+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001000 }
cristy4c08aed2011-07-01 19:47:50 +00001001 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00001002 }
1003 }
cristyed231572011-07-14 02:18:59 +00001004 p+=GetPixelChannels(image);
1005 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001006 }
1007 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1008 status=MagickFalse;
1009 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1010 {
1011 MagickBooleanType
1012 proceed;
1013
cristyb5d5f722009-11-04 03:03:49 +00001014#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00001015 #pragma omp critical (MagickCore_BlurImage)
cristy3ed852e2009-09-05 21:47:34 +00001016#endif
1017 proceed=SetImageProgress(image,BlurImageTag,progress++,blur_image->rows+
1018 blur_image->columns);
1019 if (proceed == MagickFalse)
1020 status=MagickFalse;
1021 }
1022 }
1023 blur_view=DestroyCacheView(blur_view);
1024 image_view=DestroyCacheView(image_view);
1025 /*
1026 Blur columns.
1027 */
1028 image_view=AcquireCacheView(blur_image);
1029 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00001030#if defined(MAGICKCORE_OPENMP_SUPPORT)
1031 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001032#endif
cristybb503372010-05-27 20:51:26 +00001033 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001034 {
cristy4c08aed2011-07-01 19:47:50 +00001035 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001036 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001037
cristy4c08aed2011-07-01 19:47:50 +00001038 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001039 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001040
cristy117ff172010-08-15 21:35:32 +00001041 register ssize_t
1042 y;
1043
cristy3ed852e2009-09-05 21:47:34 +00001044 if (status == MagickFalse)
1045 continue;
cristy117ff172010-08-15 21:35:32 +00001046 p=GetCacheViewVirtualPixels(image_view,x,-((ssize_t) width/2L),1,
1047 image->rows+width,exception);
cristy3ed852e2009-09-05 21:47:34 +00001048 q=GetCacheViewAuthenticPixels(blur_view,x,0,1,blur_image->rows,exception);
cristy4c08aed2011-07-01 19:47:50 +00001049 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001050 {
1051 status=MagickFalse;
1052 continue;
1053 }
cristybb503372010-05-27 20:51:26 +00001054 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001055 {
cristy4c08aed2011-07-01 19:47:50 +00001056 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001057 pixel;
1058
1059 register const double
cristyc47d1f82009-11-26 01:44:43 +00001060 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00001061
cristy4c08aed2011-07-01 19:47:50 +00001062 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001063 *restrict kernel_pixels;
cristy3ed852e2009-09-05 21:47:34 +00001064
cristybb503372010-05-27 20:51:26 +00001065 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001066 i;
1067
cristyddd82202009-11-03 20:14:50 +00001068 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00001069 k=kernel;
1070 kernel_pixels=p;
cristyed231572011-07-14 02:18:59 +00001071 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) ||
cristyf4ad9df2011-07-08 16:49:03 +00001072 (blur_image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00001073 {
cristybb503372010-05-27 20:51:26 +00001074 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001075 {
cristy4c08aed2011-07-01 19:47:50 +00001076 pixel.red+=(*k)*GetPixelRed(blur_image,kernel_pixels);
1077 pixel.green+=(*k)*GetPixelGreen(blur_image,kernel_pixels);
1078 pixel.blue+=(*k)*GetPixelBlue(blur_image,kernel_pixels);
1079 if (blur_image->colorspace == CMYKColorspace)
1080 pixel.black+=(*k)*GetPixelBlack(blur_image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001081 k++;
cristyed231572011-07-14 02:18:59 +00001082 kernel_pixels+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001083 }
cristyed231572011-07-14 02:18:59 +00001084 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001085 SetPixelRed(blur_image,ClampToQuantum(pixel.red),q);
cristyed231572011-07-14 02:18:59 +00001086 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001087 SetPixelGreen(blur_image,ClampToQuantum(pixel.green),q);
cristyed231572011-07-14 02:18:59 +00001088 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001089 SetPixelBlue(blur_image,ClampToQuantum(pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00001090 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001091 (blur_image->colorspace == CMYKColorspace))
1092 SetPixelBlack(blur_image,ClampToQuantum(pixel.black),q);
cristyed231572011-07-14 02:18:59 +00001093 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001094 {
1095 k=kernel;
1096 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001097 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001098 {
cristy4c08aed2011-07-01 19:47:50 +00001099 pixel.alpha+=(*k)*GetPixelAlpha(blur_image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001100 k++;
cristyed231572011-07-14 02:18:59 +00001101 kernel_pixels+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001102 }
cristy4c08aed2011-07-01 19:47:50 +00001103 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00001104 }
1105 }
1106 else
1107 {
1108 MagickRealType
1109 alpha,
1110 gamma;
1111
1112 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00001113 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001114 {
cristy46f08202010-01-10 04:04:21 +00001115 alpha=(MagickRealType) (QuantumScale*
cristy4c08aed2011-07-01 19:47:50 +00001116 GetPixelAlpha(blur_image,kernel_pixels));
1117 pixel.red+=(*k)*alpha*GetPixelRed(blur_image,kernel_pixels);
1118 pixel.green+=(*k)*alpha*GetPixelGreen(blur_image,kernel_pixels);
1119 pixel.blue+=(*k)*alpha*GetPixelBlue(blur_image,kernel_pixels);
1120 if (blur_image->colorspace == CMYKColorspace)
1121 pixel.black+=(*k)*alpha*GetPixelBlack(blur_image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001122 gamma+=(*k)*alpha;
1123 k++;
cristyed231572011-07-14 02:18:59 +00001124 kernel_pixels+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001125 }
1126 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +00001127 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001128 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +00001129 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001130 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +00001131 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001132 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00001133 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001134 (blur_image->colorspace == CMYKColorspace))
1135 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristyed231572011-07-14 02:18:59 +00001136 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001137 {
1138 k=kernel;
1139 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001140 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001141 {
cristy4c08aed2011-07-01 19:47:50 +00001142 pixel.alpha+=(*k)*GetPixelAlpha(blur_image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001143 k++;
cristyed231572011-07-14 02:18:59 +00001144 kernel_pixels+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001145 }
cristy4c08aed2011-07-01 19:47:50 +00001146 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00001147 }
1148 }
cristyed231572011-07-14 02:18:59 +00001149 p+=GetPixelChannels(blur_image);
1150 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001151 }
1152 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1153 status=MagickFalse;
cristy4c08aed2011-07-01 19:47:50 +00001154 if (blur_image->progress_monitor != (MagickProgressMonitor) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001155 {
1156 MagickBooleanType
1157 proceed;
1158
cristyb5d5f722009-11-04 03:03:49 +00001159#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00001160 #pragma omp critical (MagickCore_BlurImage)
cristy3ed852e2009-09-05 21:47:34 +00001161#endif
cristy4c08aed2011-07-01 19:47:50 +00001162 proceed=SetImageProgress(blur_image,BlurImageTag,progress++,
1163 blur_image->rows+blur_image->columns);
cristy3ed852e2009-09-05 21:47:34 +00001164 if (proceed == MagickFalse)
1165 status=MagickFalse;
1166 }
1167 }
1168 blur_view=DestroyCacheView(blur_view);
1169 image_view=DestroyCacheView(image_view);
1170 kernel=(double *) RelinquishMagickMemory(kernel);
1171 if (status == MagickFalse)
1172 blur_image=DestroyImage(blur_image);
1173 blur_image->type=image->type;
1174 return(blur_image);
1175}
1176
1177/*
1178%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1179% %
1180% %
1181% %
cristyfccdab92009-11-30 16:43:57 +00001182% C o n v o l v e I m a g e %
1183% %
1184% %
1185% %
1186%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1187%
1188% ConvolveImage() applies a custom convolution kernel to the image.
1189%
1190% The format of the ConvolveImage method is:
1191%
cristybb503372010-05-27 20:51:26 +00001192% Image *ConvolveImage(const Image *image,const size_t order,
cristyfccdab92009-11-30 16:43:57 +00001193% const double *kernel,ExceptionInfo *exception)
cristyfccdab92009-11-30 16:43:57 +00001194% A description of each parameter follows:
1195%
1196% o image: the image.
1197%
cristyfccdab92009-11-30 16:43:57 +00001198% o order: the number of columns and rows in the filter kernel.
1199%
1200% o kernel: An array of double representing the convolution kernel.
1201%
1202% o exception: return any errors or warnings in this structure.
1203%
1204*/
cristybb503372010-05-27 20:51:26 +00001205MagickExport Image *ConvolveImage(const Image *image,const size_t order,
cristyfccdab92009-11-30 16:43:57 +00001206 const double *kernel,ExceptionInfo *exception)
1207{
cristyfccdab92009-11-30 16:43:57 +00001208#define ConvolveImageTag "Convolve/Image"
1209
cristyc4c8d132010-01-07 01:58:38 +00001210 CacheView
1211 *convolve_view,
1212 *image_view;
1213
cristyfccdab92009-11-30 16:43:57 +00001214 double
1215 *normal_kernel;
1216
1217 Image
1218 *convolve_image;
1219
cristyfccdab92009-11-30 16:43:57 +00001220 MagickBooleanType
1221 status;
1222
cristybb503372010-05-27 20:51:26 +00001223 MagickOffsetType
1224 progress;
1225
cristyfccdab92009-11-30 16:43:57 +00001226 MagickRealType
1227 gamma;
1228
cristybb503372010-05-27 20:51:26 +00001229 register ssize_t
cristyfccdab92009-11-30 16:43:57 +00001230 i;
1231
cristybb503372010-05-27 20:51:26 +00001232 size_t
cristyfccdab92009-11-30 16:43:57 +00001233 width;
1234
cristybb503372010-05-27 20:51:26 +00001235 ssize_t
1236 y;
1237
cristyfccdab92009-11-30 16:43:57 +00001238 /*
1239 Initialize convolve image attributes.
1240 */
1241 assert(image != (Image *) NULL);
1242 assert(image->signature == MagickSignature);
1243 if (image->debug != MagickFalse)
1244 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1245 assert(exception != (ExceptionInfo *) NULL);
1246 assert(exception->signature == MagickSignature);
1247 width=order;
1248 if ((width % 2) == 0)
1249 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
cristy08429172011-07-14 17:18:16 +00001250 convolve_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1251 exception);
cristyfccdab92009-11-30 16:43:57 +00001252 if (convolve_image == (Image *) NULL)
1253 return((Image *) NULL);
1254 if (SetImageStorageClass(convolve_image,DirectClass) == MagickFalse)
1255 {
1256 InheritException(exception,&convolve_image->exception);
1257 convolve_image=DestroyImage(convolve_image);
1258 return((Image *) NULL);
1259 }
1260 if (image->debug != MagickFalse)
1261 {
1262 char
1263 format[MaxTextExtent],
1264 *message;
1265
cristy117ff172010-08-15 21:35:32 +00001266 register const double
1267 *k;
1268
cristy4e154852011-07-14 13:28:53 +00001269 register ssize_t
1270 u;
1271
cristybb503372010-05-27 20:51:26 +00001272 ssize_t
cristyfccdab92009-11-30 16:43:57 +00001273 v;
1274
cristyfccdab92009-11-30 16:43:57 +00001275 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00001276 " ConvolveImage with %.20gx%.20g kernel:",(double) width,(double)
1277 width);
cristyfccdab92009-11-30 16:43:57 +00001278 message=AcquireString("");
1279 k=kernel;
cristybb503372010-05-27 20:51:26 +00001280 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001281 {
1282 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00001283 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristyfccdab92009-11-30 16:43:57 +00001284 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00001285 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001286 {
cristyb51dff52011-05-19 16:55:47 +00001287 (void) FormatLocaleString(format,MaxTextExtent,"%g ",*k++);
cristyfccdab92009-11-30 16:43:57 +00001288 (void) ConcatenateString(&message,format);
1289 }
1290 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
1291 }
1292 message=DestroyString(message);
1293 }
1294 /*
1295 Normalize kernel.
1296 */
1297 normal_kernel=(double *) AcquireQuantumMemory(width*width,
1298 sizeof(*normal_kernel));
1299 if (normal_kernel == (double *) NULL)
1300 {
1301 convolve_image=DestroyImage(convolve_image);
1302 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1303 }
1304 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00001305 for (i=0; i < (ssize_t) (width*width); i++)
cristyfccdab92009-11-30 16:43:57 +00001306 gamma+=kernel[i];
1307 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristybb503372010-05-27 20:51:26 +00001308 for (i=0; i < (ssize_t) (width*width); i++)
cristyfccdab92009-11-30 16:43:57 +00001309 normal_kernel[i]=gamma*kernel[i];
1310 /*
1311 Convolve image.
1312 */
1313 status=MagickTrue;
1314 progress=0;
cristyfccdab92009-11-30 16:43:57 +00001315 image_view=AcquireCacheView(image);
1316 convolve_view=AcquireCacheView(convolve_image);
1317#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy175653e2011-07-10 23:13:34 +00001318 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristyfccdab92009-11-30 16:43:57 +00001319#endif
cristybb503372010-05-27 20:51:26 +00001320 for (y=0; y < (ssize_t) image->rows; y++)
cristyfccdab92009-11-30 16:43:57 +00001321 {
cristy4c08aed2011-07-01 19:47:50 +00001322 register const Quantum
cristyfccdab92009-11-30 16:43:57 +00001323 *restrict p;
1324
cristy4c08aed2011-07-01 19:47:50 +00001325 register Quantum
cristyfccdab92009-11-30 16:43:57 +00001326 *restrict q;
1327
cristy117ff172010-08-15 21:35:32 +00001328 register ssize_t
1329 x;
1330
cristy4e154852011-07-14 13:28:53 +00001331 ssize_t
cristyed231572011-07-14 02:18:59 +00001332 channels,
1333 convolve_channels;
1334
cristyfccdab92009-11-30 16:43:57 +00001335 if (status == MagickFalse)
1336 continue;
cristyce889302010-06-30 19:16:36 +00001337 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
1338 (width/2L),image->columns+width,width,exception);
cristy08429172011-07-14 17:18:16 +00001339 q=QueueCacheViewAuthenticPixels(convolve_view,0,y,convolve_image->columns,1,
cristyfccdab92009-11-30 16:43:57 +00001340 exception);
cristy4c08aed2011-07-01 19:47:50 +00001341 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristyfccdab92009-11-30 16:43:57 +00001342 {
1343 status=MagickFalse;
1344 continue;
1345 }
cristyed231572011-07-14 02:18:59 +00001346 channels=GetPixelChannels(image);
1347 convolve_channels=GetPixelChannels(convolve_image);
cristybb503372010-05-27 20:51:26 +00001348 for (x=0; x < (ssize_t) image->columns; x++)
cristyfccdab92009-11-30 16:43:57 +00001349 {
cristybb503372010-05-27 20:51:26 +00001350 register ssize_t
cristyed231572011-07-14 02:18:59 +00001351 i;
cristyfccdab92009-11-30 16:43:57 +00001352
cristyed231572011-07-14 02:18:59 +00001353 for (i=0; i < (ssize_t) channels; i++)
1354 {
cristyed231572011-07-14 02:18:59 +00001355 MagickRealType
cristy4e154852011-07-14 13:28:53 +00001356 alpha,
1357 gamma,
cristyed231572011-07-14 02:18:59 +00001358 pixel;
1359
1360 PixelChannel
1361 channel;
1362
1363 PixelTrait
1364 convolve_traits,
1365 traits;
1366
1367 register const double
1368 *restrict k;
1369
1370 register const Quantum
1371 *restrict kernel_pixels;
1372
1373 register ssize_t
1374 u;
1375
1376 ssize_t
1377 v;
1378
cristyed231572011-07-14 02:18:59 +00001379 traits=GetPixelChannelMapTraits(image,i);
cristy4e154852011-07-14 13:28:53 +00001380 if (traits == UndefinedPixelTrait)
cristyed231572011-07-14 02:18:59 +00001381 continue;
cristy4e154852011-07-14 13:28:53 +00001382 channel=GetPixelChannelMapChannel(image,i);
1383 convolve_traits=GetPixelChannelMapTraits(convolve_image,channel);
1384 if (convolve_traits == UndefinedPixelTrait)
1385 continue;
1386 if ((convolve_traits & CopyPixelTrait) != 0)
1387 {
cristy39177402011-07-14 16:45:04 +00001388 size_t
cristyf8500872011-07-14 14:02:41 +00001389 center;
cristy4e154852011-07-14 13:28:53 +00001390
cristyf8500872011-07-14 14:02:41 +00001391 center=((image->columns+width)*width/2)*channels+i;
1392 SetPixelChannel(convolve_image,channel,p[center],q);
cristy4e154852011-07-14 13:28:53 +00001393 continue;
1394 }
cristyed231572011-07-14 02:18:59 +00001395 k=normal_kernel;
1396 kernel_pixels=p;
cristy4e154852011-07-14 13:28:53 +00001397 pixel=image->bias;
cristy1ce96d02011-07-14 17:57:24 +00001398 if (((convolve_traits & BlendPixelTrait) == 0) ||
1399 (GetPixelAlphaTraits(image) == UndefinedPixelTrait) ||
cristyed231572011-07-14 02:18:59 +00001400 (image->matte == MagickFalse))
cristyfccdab92009-11-30 16:43:57 +00001401 {
cristyed231572011-07-14 02:18:59 +00001402 /*
cristy4e154852011-07-14 13:28:53 +00001403 No alpha blending.
cristyed231572011-07-14 02:18:59 +00001404 */
1405 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001406 {
cristyed231572011-07-14 02:18:59 +00001407 for (u=0; u < (ssize_t) width; u++)
cristy175653e2011-07-10 23:13:34 +00001408 {
cristyed231572011-07-14 02:18:59 +00001409 pixel+=(*k)*kernel_pixels[u*channels+i];
1410 k++;
cristy175653e2011-07-10 23:13:34 +00001411 }
cristyed231572011-07-14 02:18:59 +00001412 kernel_pixels+=(image->columns+width)*channels;
cristy175653e2011-07-10 23:13:34 +00001413 }
cristy4e154852011-07-14 13:28:53 +00001414 SetPixelChannel(convolve_image,channel,ClampToQuantum(pixel),q);
1415 continue;
cristyed231572011-07-14 02:18:59 +00001416 }
cristy4e154852011-07-14 13:28:53 +00001417 /*
1418 Alpha blending.
1419 */
1420 gamma=0.0;
1421 for (v=0; v < (ssize_t) width; v++)
1422 {
1423 for (u=0; u < (ssize_t) width; u++)
1424 {
1425 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,
1426 kernel_pixels+u*channels));
cristy1ce96d02011-07-14 17:57:24 +00001427 pixel+=(*k)*alpha*kernel_pixels[u*channels+i];
cristy4e154852011-07-14 13:28:53 +00001428 gamma+=(*k)*alpha;
1429 k++;
1430 }
1431 kernel_pixels+=(image->columns+width)*channels;
1432 }
cristy1ce96d02011-07-14 17:57:24 +00001433 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1434 SetPixelChannel(convolve_image,channel,ClampToQuantum(gamma*pixel),q);
cristyed231572011-07-14 02:18:59 +00001435 }
1436 p+=channels;
1437 q+=convolve_channels;
cristyfccdab92009-11-30 16:43:57 +00001438 }
cristyed231572011-07-14 02:18:59 +00001439 if (SyncCacheViewAuthenticPixels(convolve_view,exception) == MagickFalse)
cristyfccdab92009-11-30 16:43:57 +00001440 status=MagickFalse;
1441 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1442 {
1443 MagickBooleanType
1444 proceed;
1445
1446#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00001447 #pragma omp critical (MagickCore_ConvolveImage)
cristyfccdab92009-11-30 16:43:57 +00001448#endif
1449 proceed=SetImageProgress(image,ConvolveImageTag,progress++,image->rows);
1450 if (proceed == MagickFalse)
1451 status=MagickFalse;
1452 }
1453 }
1454 convolve_image->type=image->type;
1455 convolve_view=DestroyCacheView(convolve_view);
1456 image_view=DestroyCacheView(image_view);
1457 normal_kernel=(double *) RelinquishMagickMemory(normal_kernel);
1458 if (status == MagickFalse)
1459 convolve_image=DestroyImage(convolve_image);
1460 return(convolve_image);
1461}
1462
1463/*
1464%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1465% %
1466% %
1467% %
cristy3ed852e2009-09-05 21:47:34 +00001468% D e s p e c k l e I m a g e %
1469% %
1470% %
1471% %
1472%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1473%
1474% DespeckleImage() reduces the speckle noise in an image while perserving the
1475% edges of the original image.
1476%
1477% The format of the DespeckleImage method is:
1478%
1479% Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1480%
1481% A description of each parameter follows:
1482%
1483% o image: the image.
1484%
1485% o exception: return any errors or warnings in this structure.
1486%
1487*/
1488
cristybb503372010-05-27 20:51:26 +00001489static void Hull(const ssize_t x_offset,const ssize_t y_offset,
1490 const size_t columns,const size_t rows,Quantum *f,Quantum *g,
cristy3ed852e2009-09-05 21:47:34 +00001491 const int polarity)
1492{
cristy3ed852e2009-09-05 21:47:34 +00001493 MagickRealType
1494 v;
1495
cristy3ed852e2009-09-05 21:47:34 +00001496 register Quantum
1497 *p,
1498 *q,
1499 *r,
1500 *s;
1501
cristy117ff172010-08-15 21:35:32 +00001502 register ssize_t
1503 x;
1504
1505 ssize_t
1506 y;
1507
cristy3ed852e2009-09-05 21:47:34 +00001508 assert(f != (Quantum *) NULL);
1509 assert(g != (Quantum *) NULL);
1510 p=f+(columns+2);
1511 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001512 r=p+(y_offset*((ssize_t) columns+2)+x_offset);
1513 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001514 {
1515 p++;
1516 q++;
1517 r++;
1518 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001519 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001520 {
1521 v=(MagickRealType) (*p);
1522 if ((MagickRealType) *r >= (v+(MagickRealType) ScaleCharToQuantum(2)))
1523 v+=ScaleCharToQuantum(1);
1524 *q=(Quantum) v;
1525 p++;
1526 q++;
1527 r++;
1528 }
1529 else
cristybb503372010-05-27 20:51:26 +00001530 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001531 {
1532 v=(MagickRealType) (*p);
1533 if ((MagickRealType) *r <= (v-(MagickRealType) ScaleCharToQuantum(2)))
cristybb503372010-05-27 20:51:26 +00001534 v-=(ssize_t) ScaleCharToQuantum(1);
cristy3ed852e2009-09-05 21:47:34 +00001535 *q=(Quantum) v;
1536 p++;
1537 q++;
1538 r++;
1539 }
1540 p++;
1541 q++;
1542 r++;
1543 }
1544 p=f+(columns+2);
1545 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001546 r=q+(y_offset*((ssize_t) columns+2)+x_offset);
1547 s=q-(y_offset*((ssize_t) columns+2)+x_offset);
1548 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001549 {
1550 p++;
1551 q++;
1552 r++;
1553 s++;
1554 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001555 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001556 {
1557 v=(MagickRealType) (*q);
1558 if (((MagickRealType) *s >=
1559 (v+(MagickRealType) ScaleCharToQuantum(2))) &&
1560 ((MagickRealType) *r > v))
1561 v+=ScaleCharToQuantum(1);
1562 *p=(Quantum) v;
1563 p++;
1564 q++;
1565 r++;
1566 s++;
1567 }
1568 else
cristybb503372010-05-27 20:51:26 +00001569 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001570 {
1571 v=(MagickRealType) (*q);
1572 if (((MagickRealType) *s <=
1573 (v-(MagickRealType) ScaleCharToQuantum(2))) &&
1574 ((MagickRealType) *r < v))
1575 v-=(MagickRealType) ScaleCharToQuantum(1);
1576 *p=(Quantum) v;
1577 p++;
1578 q++;
1579 r++;
1580 s++;
1581 }
1582 p++;
1583 q++;
1584 r++;
1585 s++;
1586 }
1587}
1588
1589MagickExport Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1590{
1591#define DespeckleImageTag "Despeckle/Image"
1592
cristy2407fc22009-09-11 00:55:25 +00001593 CacheView
1594 *despeckle_view,
1595 *image_view;
1596
cristy3ed852e2009-09-05 21:47:34 +00001597 Image
1598 *despeckle_image;
1599
cristy3ed852e2009-09-05 21:47:34 +00001600 MagickBooleanType
1601 status;
1602
cristya58c3172011-02-19 19:23:11 +00001603 register ssize_t
1604 i;
1605
cristy3ed852e2009-09-05 21:47:34 +00001606 Quantum
cristy65b9f392011-02-22 14:22:54 +00001607 *restrict buffers,
1608 *restrict pixels;
cristy3ed852e2009-09-05 21:47:34 +00001609
1610 size_t
cristya58c3172011-02-19 19:23:11 +00001611 length,
1612 number_channels;
cristy117ff172010-08-15 21:35:32 +00001613
cristybb503372010-05-27 20:51:26 +00001614 static const ssize_t
cristy691a29e2009-09-11 00:44:10 +00001615 X[4] = {0, 1, 1,-1},
1616 Y[4] = {1, 0, 1, 1};
cristy3ed852e2009-09-05 21:47:34 +00001617
cristy3ed852e2009-09-05 21:47:34 +00001618 /*
1619 Allocate despeckled image.
1620 */
1621 assert(image != (const Image *) NULL);
1622 assert(image->signature == MagickSignature);
1623 if (image->debug != MagickFalse)
1624 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1625 assert(exception != (ExceptionInfo *) NULL);
1626 assert(exception->signature == MagickSignature);
1627 despeckle_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1628 exception);
1629 if (despeckle_image == (Image *) NULL)
1630 return((Image *) NULL);
1631 if (SetImageStorageClass(despeckle_image,DirectClass) == MagickFalse)
1632 {
1633 InheritException(exception,&despeckle_image->exception);
1634 despeckle_image=DestroyImage(despeckle_image);
1635 return((Image *) NULL);
1636 }
1637 /*
1638 Allocate image buffers.
1639 */
1640 length=(size_t) ((image->columns+2)*(image->rows+2));
cristy65b9f392011-02-22 14:22:54 +00001641 pixels=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1642 buffers=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1643 if ((pixels == (Quantum *) NULL) || (buffers == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001644 {
cristy65b9f392011-02-22 14:22:54 +00001645 if (buffers != (Quantum *) NULL)
1646 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1647 if (pixels != (Quantum *) NULL)
1648 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001649 despeckle_image=DestroyImage(despeckle_image);
1650 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1651 }
1652 /*
1653 Reduce speckle in the image.
1654 */
1655 status=MagickTrue;
cristy109695a2011-02-19 19:38:14 +00001656 number_channels=(size_t) (image->colorspace == CMYKColorspace ? 5 : 4);
cristy3ed852e2009-09-05 21:47:34 +00001657 image_view=AcquireCacheView(image);
1658 despeckle_view=AcquireCacheView(despeckle_image);
cristy8df3d002011-02-19 19:40:59 +00001659 for (i=0; i < (ssize_t) number_channels; i++)
cristy3ed852e2009-09-05 21:47:34 +00001660 {
cristy3ed852e2009-09-05 21:47:34 +00001661 register Quantum
1662 *buffer,
1663 *pixel;
1664
cristyc1488b52011-02-19 18:54:15 +00001665 register ssize_t
cristya58c3172011-02-19 19:23:11 +00001666 k,
cristyc1488b52011-02-19 18:54:15 +00001667 x;
1668
cristy117ff172010-08-15 21:35:32 +00001669 ssize_t
1670 j,
1671 y;
1672
cristy3ed852e2009-09-05 21:47:34 +00001673 if (status == MagickFalse)
1674 continue;
cristy65b9f392011-02-22 14:22:54 +00001675 pixel=pixels;
cristy3ed852e2009-09-05 21:47:34 +00001676 (void) ResetMagickMemory(pixel,0,length*sizeof(*pixel));
cristy65b9f392011-02-22 14:22:54 +00001677 buffer=buffers;
cristybb503372010-05-27 20:51:26 +00001678 j=(ssize_t) image->columns+2;
1679 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001680 {
cristy4c08aed2011-07-01 19:47:50 +00001681 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001682 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001683
1684 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001685 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001686 break;
1687 j++;
cristybb503372010-05-27 20:51:26 +00001688 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001689 {
cristya58c3172011-02-19 19:23:11 +00001690 switch (i)
cristy3ed852e2009-09-05 21:47:34 +00001691 {
cristy4c08aed2011-07-01 19:47:50 +00001692 case 0: pixel[j]=GetPixelRed(image,p); break;
1693 case 1: pixel[j]=GetPixelGreen(image,p); break;
1694 case 2: pixel[j]=GetPixelBlue(image,p); break;
1695 case 3: pixel[j]=GetPixelAlpha(image,p); break;
1696 case 4: pixel[j]=GetPixelBlack(image,p); break;
cristy3ed852e2009-09-05 21:47:34 +00001697 default: break;
1698 }
cristyed231572011-07-14 02:18:59 +00001699 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001700 j++;
1701 }
1702 j++;
1703 }
cristy3ed852e2009-09-05 21:47:34 +00001704 (void) ResetMagickMemory(buffer,0,length*sizeof(*buffer));
cristya58c3172011-02-19 19:23:11 +00001705 for (k=0; k < 4; k++)
cristy3ed852e2009-09-05 21:47:34 +00001706 {
cristya58c3172011-02-19 19:23:11 +00001707 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,1);
1708 Hull(-X[k],-Y[k],image->columns,image->rows,pixel,buffer,1);
1709 Hull(-X[k],-Y[k],image->columns,image->rows,pixel,buffer,-1);
1710 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,-1);
cristy3ed852e2009-09-05 21:47:34 +00001711 }
cristybb503372010-05-27 20:51:26 +00001712 j=(ssize_t) image->columns+2;
1713 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001714 {
1715 MagickBooleanType
1716 sync;
1717
cristy4c08aed2011-07-01 19:47:50 +00001718 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001719 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001720
1721 q=GetCacheViewAuthenticPixels(despeckle_view,0,y,despeckle_image->columns,
1722 1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001723 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001724 break;
1725 j++;
cristybb503372010-05-27 20:51:26 +00001726 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001727 {
cristya58c3172011-02-19 19:23:11 +00001728 switch (i)
cristy3ed852e2009-09-05 21:47:34 +00001729 {
cristy4c08aed2011-07-01 19:47:50 +00001730 case 0: SetPixelRed(despeckle_image,pixel[j],q); break;
1731 case 1: SetPixelGreen(despeckle_image,pixel[j],q); break;
1732 case 2: SetPixelBlue(despeckle_image,pixel[j],q); break;
1733 case 3: SetPixelAlpha(despeckle_image,pixel[j],q); break;
1734 case 4: SetPixelBlack(despeckle_image,pixel[j],q); break;
cristy3ed852e2009-09-05 21:47:34 +00001735 default: break;
1736 }
cristyed231572011-07-14 02:18:59 +00001737 q+=GetPixelChannels(despeckle_image);
cristy3ed852e2009-09-05 21:47:34 +00001738 j++;
1739 }
1740 sync=SyncCacheViewAuthenticPixels(despeckle_view,exception);
1741 if (sync == MagickFalse)
1742 {
1743 status=MagickFalse;
1744 break;
1745 }
1746 j++;
1747 }
1748 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1749 {
1750 MagickBooleanType
1751 proceed;
1752
cristya58c3172011-02-19 19:23:11 +00001753 proceed=SetImageProgress(image,DespeckleImageTag,(MagickOffsetType) i,
1754 number_channels);
cristy3ed852e2009-09-05 21:47:34 +00001755 if (proceed == MagickFalse)
1756 status=MagickFalse;
1757 }
1758 }
1759 despeckle_view=DestroyCacheView(despeckle_view);
1760 image_view=DestroyCacheView(image_view);
cristy65b9f392011-02-22 14:22:54 +00001761 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1762 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001763 despeckle_image->type=image->type;
1764 if (status == MagickFalse)
1765 despeckle_image=DestroyImage(despeckle_image);
1766 return(despeckle_image);
1767}
1768
1769/*
1770%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1771% %
1772% %
1773% %
1774% E d g e I m a g e %
1775% %
1776% %
1777% %
1778%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1779%
1780% EdgeImage() finds edges in an image. Radius defines the radius of the
1781% convolution filter. Use a radius of 0 and EdgeImage() selects a suitable
1782% radius for you.
1783%
1784% The format of the EdgeImage method is:
1785%
1786% Image *EdgeImage(const Image *image,const double radius,
1787% ExceptionInfo *exception)
1788%
1789% A description of each parameter follows:
1790%
1791% o image: the image.
1792%
1793% o radius: the radius of the pixel neighborhood.
1794%
1795% o exception: return any errors or warnings in this structure.
1796%
1797*/
1798MagickExport Image *EdgeImage(const Image *image,const double radius,
1799 ExceptionInfo *exception)
1800{
1801 Image
1802 *edge_image;
1803
1804 double
1805 *kernel;
1806
cristybb503372010-05-27 20:51:26 +00001807 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001808 i;
1809
cristybb503372010-05-27 20:51:26 +00001810 size_t
cristy3ed852e2009-09-05 21:47:34 +00001811 width;
1812
1813 assert(image != (const Image *) NULL);
1814 assert(image->signature == MagickSignature);
1815 if (image->debug != MagickFalse)
1816 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1817 assert(exception != (ExceptionInfo *) NULL);
1818 assert(exception->signature == MagickSignature);
1819 width=GetOptimalKernelWidth1D(radius,0.5);
1820 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
1821 if (kernel == (double *) NULL)
1822 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00001823 for (i=0; i < (ssize_t) (width*width); i++)
cristy3ed852e2009-09-05 21:47:34 +00001824 kernel[i]=(-1.0);
1825 kernel[i/2]=(double) (width*width-1.0);
1826 edge_image=ConvolveImage(image,width,kernel,exception);
1827 kernel=(double *) RelinquishMagickMemory(kernel);
1828 return(edge_image);
1829}
1830
1831/*
1832%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1833% %
1834% %
1835% %
1836% E m b o s s I m a g e %
1837% %
1838% %
1839% %
1840%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1841%
1842% EmbossImage() returns a grayscale image with a three-dimensional effect.
1843% We convolve the image with a Gaussian operator of the given radius and
1844% standard deviation (sigma). For reasonable results, radius should be
1845% larger than sigma. Use a radius of 0 and Emboss() selects a suitable
1846% radius for you.
1847%
1848% The format of the EmbossImage method is:
1849%
1850% Image *EmbossImage(const Image *image,const double radius,
1851% const double sigma,ExceptionInfo *exception)
1852%
1853% A description of each parameter follows:
1854%
1855% o image: the image.
1856%
1857% o radius: the radius of the pixel neighborhood.
1858%
1859% o sigma: the standard deviation of the Gaussian, in pixels.
1860%
1861% o exception: return any errors or warnings in this structure.
1862%
1863*/
1864MagickExport Image *EmbossImage(const Image *image,const double radius,
1865 const double sigma,ExceptionInfo *exception)
1866{
1867 double
1868 *kernel;
1869
1870 Image
1871 *emboss_image;
1872
cristybb503372010-05-27 20:51:26 +00001873 register ssize_t
cristy47e00502009-12-17 19:19:57 +00001874 i;
1875
cristybb503372010-05-27 20:51:26 +00001876 size_t
cristy3ed852e2009-09-05 21:47:34 +00001877 width;
1878
cristy117ff172010-08-15 21:35:32 +00001879 ssize_t
1880 j,
1881 k,
1882 u,
1883 v;
1884
cristy3ed852e2009-09-05 21:47:34 +00001885 assert(image != (Image *) NULL);
1886 assert(image->signature == MagickSignature);
1887 if (image->debug != MagickFalse)
1888 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1889 assert(exception != (ExceptionInfo *) NULL);
1890 assert(exception->signature == MagickSignature);
1891 width=GetOptimalKernelWidth2D(radius,sigma);
1892 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
1893 if (kernel == (double *) NULL)
1894 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00001895 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +00001896 k=j;
1897 i=0;
1898 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00001899 {
cristy47e00502009-12-17 19:19:57 +00001900 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00001901 {
cristy4205a3c2010-09-12 20:19:59 +00001902 kernel[i]=(double) (((u < 0) || (v < 0) ? -8.0 : 8.0)*
cristy47e00502009-12-17 19:19:57 +00001903 exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
cristy4205a3c2010-09-12 20:19:59 +00001904 (2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +00001905 if (u != k)
cristy3ed852e2009-09-05 21:47:34 +00001906 kernel[i]=0.0;
1907 i++;
1908 }
cristy47e00502009-12-17 19:19:57 +00001909 k--;
cristy3ed852e2009-09-05 21:47:34 +00001910 }
1911 emboss_image=ConvolveImage(image,width,kernel,exception);
1912 if (emboss_image != (Image *) NULL)
1913 (void) EqualizeImage(emboss_image);
1914 kernel=(double *) RelinquishMagickMemory(kernel);
1915 return(emboss_image);
1916}
1917
1918/*
1919%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1920% %
1921% %
1922% %
cristy56a9e512010-01-06 18:18:55 +00001923% F i l t e r I m a g e %
1924% %
1925% %
1926% %
1927%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1928%
1929% FilterImage() applies a custom convolution kernel to the image.
1930%
1931% The format of the FilterImage method is:
1932%
cristy2be15382010-01-21 02:38:03 +00001933% Image *FilterImage(const Image *image,const KernelInfo *kernel,
cristy56a9e512010-01-06 18:18:55 +00001934% ExceptionInfo *exception)
cristy56a9e512010-01-06 18:18:55 +00001935%
1936% A description of each parameter follows:
1937%
1938% o image: the image.
1939%
cristy56a9e512010-01-06 18:18:55 +00001940% o kernel: the filtering kernel.
1941%
1942% o exception: return any errors or warnings in this structure.
1943%
1944*/
cristyf4ad9df2011-07-08 16:49:03 +00001945MagickExport Image *FilterImage(const Image *image,
1946 const KernelInfo *kernel,ExceptionInfo *exception)
cristy56a9e512010-01-06 18:18:55 +00001947{
1948#define FilterImageTag "Filter/Image"
1949
1950 CacheView
1951 *filter_view,
1952 *image_view;
1953
cristy56a9e512010-01-06 18:18:55 +00001954 Image
1955 *filter_image;
1956
cristy56a9e512010-01-06 18:18:55 +00001957 MagickBooleanType
1958 status;
1959
cristybb503372010-05-27 20:51:26 +00001960 MagickOffsetType
1961 progress;
1962
cristy4c08aed2011-07-01 19:47:50 +00001963 PixelInfo
cristy56a9e512010-01-06 18:18:55 +00001964 bias;
1965
cristybb503372010-05-27 20:51:26 +00001966 ssize_t
1967 y;
1968
cristy56a9e512010-01-06 18:18:55 +00001969 /*
1970 Initialize filter image attributes.
1971 */
1972 assert(image != (Image *) NULL);
1973 assert(image->signature == MagickSignature);
1974 if (image->debug != MagickFalse)
1975 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1976 assert(exception != (ExceptionInfo *) NULL);
1977 assert(exception->signature == MagickSignature);
1978 if ((kernel->width % 2) == 0)
1979 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
1980 filter_image=CloneImage(image,0,0,MagickTrue,exception);
1981 if (filter_image == (Image *) NULL)
1982 return((Image *) NULL);
1983 if (SetImageStorageClass(filter_image,DirectClass) == MagickFalse)
1984 {
1985 InheritException(exception,&filter_image->exception);
1986 filter_image=DestroyImage(filter_image);
1987 return((Image *) NULL);
1988 }
1989 if (image->debug != MagickFalse)
1990 {
1991 char
1992 format[MaxTextExtent],
1993 *message;
1994
cristy117ff172010-08-15 21:35:32 +00001995 register const double
1996 *k;
1997
cristybb503372010-05-27 20:51:26 +00001998 ssize_t
cristy56a9e512010-01-06 18:18:55 +00001999 u,
2000 v;
2001
cristy56a9e512010-01-06 18:18:55 +00002002 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00002003 " FilterImage with %.20gx%.20g kernel:",(double) kernel->width,(double)
2004 kernel->height);
cristy56a9e512010-01-06 18:18:55 +00002005 message=AcquireString("");
2006 k=kernel->values;
cristybb503372010-05-27 20:51:26 +00002007 for (v=0; v < (ssize_t) kernel->height; v++)
cristy56a9e512010-01-06 18:18:55 +00002008 {
2009 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00002010 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy56a9e512010-01-06 18:18:55 +00002011 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00002012 for (u=0; u < (ssize_t) kernel->width; u++)
cristy56a9e512010-01-06 18:18:55 +00002013 {
cristyb51dff52011-05-19 16:55:47 +00002014 (void) FormatLocaleString(format,MaxTextExtent,"%g ",*k++);
cristy56a9e512010-01-06 18:18:55 +00002015 (void) ConcatenateString(&message,format);
2016 }
2017 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
2018 }
2019 message=DestroyString(message);
2020 }
cristy36826ab2010-03-06 01:29:30 +00002021 status=AccelerateConvolveImage(image,kernel,filter_image,exception);
cristyd43a46b2010-01-21 02:13:41 +00002022 if (status == MagickTrue)
2023 return(filter_image);
cristy56a9e512010-01-06 18:18:55 +00002024 /*
2025 Filter image.
2026 */
2027 status=MagickTrue;
2028 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00002029 GetPixelInfo(image,&bias);
2030 SetPixelInfoBias(image,&bias);
cristy56a9e512010-01-06 18:18:55 +00002031 image_view=AcquireCacheView(image);
2032 filter_view=AcquireCacheView(filter_image);
2033#if defined(MAGICKCORE_OPENMP_SUPPORT)
2034 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2035#endif
cristybb503372010-05-27 20:51:26 +00002036 for (y=0; y < (ssize_t) image->rows; y++)
cristy56a9e512010-01-06 18:18:55 +00002037 {
2038 MagickBooleanType
2039 sync;
2040
cristy4c08aed2011-07-01 19:47:50 +00002041 register const Quantum
cristy56a9e512010-01-06 18:18:55 +00002042 *restrict p;
2043
cristy4c08aed2011-07-01 19:47:50 +00002044 register Quantum
cristy56a9e512010-01-06 18:18:55 +00002045 *restrict q;
2046
cristy117ff172010-08-15 21:35:32 +00002047 register ssize_t
2048 x;
2049
cristy56a9e512010-01-06 18:18:55 +00002050 if (status == MagickFalse)
2051 continue;
cristybb503372010-05-27 20:51:26 +00002052 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) kernel->width/2L),
cristy117ff172010-08-15 21:35:32 +00002053 y-(ssize_t) (kernel->height/2L),image->columns+kernel->width,
2054 kernel->height,exception);
cristy56a9e512010-01-06 18:18:55 +00002055 q=GetCacheViewAuthenticPixels(filter_view,0,y,filter_image->columns,1,
2056 exception);
cristy4c08aed2011-07-01 19:47:50 +00002057 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy56a9e512010-01-06 18:18:55 +00002058 {
2059 status=MagickFalse;
2060 continue;
2061 }
cristybb503372010-05-27 20:51:26 +00002062 for (x=0; x < (ssize_t) image->columns; x++)
cristy56a9e512010-01-06 18:18:55 +00002063 {
cristy4c08aed2011-07-01 19:47:50 +00002064 PixelInfo
cristy56a9e512010-01-06 18:18:55 +00002065 pixel;
2066
2067 register const double
2068 *restrict k;
2069
cristy4c08aed2011-07-01 19:47:50 +00002070 register const Quantum
cristy56a9e512010-01-06 18:18:55 +00002071 *restrict kernel_pixels;
2072
cristybb503372010-05-27 20:51:26 +00002073 register ssize_t
cristy56a9e512010-01-06 18:18:55 +00002074 u;
2075
cristy117ff172010-08-15 21:35:32 +00002076 ssize_t
2077 v;
2078
cristy56a9e512010-01-06 18:18:55 +00002079 pixel=bias;
cristy36826ab2010-03-06 01:29:30 +00002080 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002081 kernel_pixels=p;
cristyed231572011-07-14 02:18:59 +00002082 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) ||
cristy5ce8df82011-07-07 14:52:23 +00002083 (image->matte == MagickFalse))
cristy56a9e512010-01-06 18:18:55 +00002084 {
cristybb503372010-05-27 20:51:26 +00002085 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002086 {
cristybb503372010-05-27 20:51:26 +00002087 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002088 {
cristy4c08aed2011-07-01 19:47:50 +00002089 pixel.red+=(*k)*GetPixelRed(image,kernel_pixels+u*
cristyed231572011-07-14 02:18:59 +00002090 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00002091 pixel.green+=(*k)*GetPixelGreen(image,kernel_pixels+u*
cristyed231572011-07-14 02:18:59 +00002092 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00002093 pixel.blue+=(*k)*GetPixelBlue(image,kernel_pixels+u*
cristyed231572011-07-14 02:18:59 +00002094 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00002095 if (image->colorspace == CMYKColorspace)
2096 pixel.black+=(*k)*GetPixelBlack(image,kernel_pixels+u*
cristyed231572011-07-14 02:18:59 +00002097 GetPixelChannels(image));
cristy56a9e512010-01-06 18:18:55 +00002098 k++;
2099 }
cristy4c08aed2011-07-01 19:47:50 +00002100 kernel_pixels+=(image->columns+kernel->width)*
cristyed231572011-07-14 02:18:59 +00002101 GetPixelChannels(image);
cristy56a9e512010-01-06 18:18:55 +00002102 }
cristyed231572011-07-14 02:18:59 +00002103 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002104 SetPixelRed(filter_image,ClampToQuantum(pixel.red),q);
cristyed231572011-07-14 02:18:59 +00002105 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002106 SetPixelGreen(filter_image,ClampToQuantum(pixel.green),q);
cristyed231572011-07-14 02:18:59 +00002107 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002108 SetPixelBlue(filter_image,ClampToQuantum(pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00002109 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002110 (image->colorspace == CMYKColorspace))
2111 SetPixelBlack(filter_image,ClampToQuantum(pixel.black),q);
cristyed231572011-07-14 02:18:59 +00002112 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy56a9e512010-01-06 18:18:55 +00002113 {
cristy36826ab2010-03-06 01:29:30 +00002114 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002115 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00002116 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002117 {
cristybb503372010-05-27 20:51:26 +00002118 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002119 {
cristy4c08aed2011-07-01 19:47:50 +00002120 pixel.alpha+=(*k)*GetPixelRed(image,kernel_pixels+u*
cristyed231572011-07-14 02:18:59 +00002121 GetPixelChannels(image));
cristy56a9e512010-01-06 18:18:55 +00002122 k++;
2123 }
cristy4c08aed2011-07-01 19:47:50 +00002124 kernel_pixels+=(image->columns+kernel->width)*
cristyed231572011-07-14 02:18:59 +00002125 GetPixelChannels(image);
cristy56a9e512010-01-06 18:18:55 +00002126 }
cristy4c08aed2011-07-01 19:47:50 +00002127 SetPixelAlpha(filter_image,ClampToQuantum(pixel.alpha),q);
cristy56a9e512010-01-06 18:18:55 +00002128 }
2129 }
2130 else
2131 {
2132 MagickRealType
2133 alpha,
2134 gamma;
2135
2136 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00002137 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002138 {
cristybb503372010-05-27 20:51:26 +00002139 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002140 {
cristy4c08aed2011-07-01 19:47:50 +00002141 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,
cristyed231572011-07-14 02:18:59 +00002142 kernel_pixels+u*GetPixelChannels(image)));
cristy4c08aed2011-07-01 19:47:50 +00002143 pixel.red+=(*k)*alpha*GetPixelRed(image,kernel_pixels+u*
cristyed231572011-07-14 02:18:59 +00002144 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00002145 pixel.green+=(*k)*alpha*GetPixelGreen(image,kernel_pixels+u*
cristyed231572011-07-14 02:18:59 +00002146 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00002147 pixel.blue+=(*k)*alpha*GetPixelBlue(image,kernel_pixels+u*
cristyed231572011-07-14 02:18:59 +00002148 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00002149 if (image->colorspace == CMYKColorspace)
2150 pixel.black+=(*k)*alpha*GetPixelBlack(image,kernel_pixels+u*
cristyed231572011-07-14 02:18:59 +00002151 GetPixelChannels(image));
cristy56a9e512010-01-06 18:18:55 +00002152 gamma+=(*k)*alpha;
2153 k++;
2154 }
cristy5ce8df82011-07-07 14:52:23 +00002155 kernel_pixels+=(image->columns+kernel->width)*
cristyed231572011-07-14 02:18:59 +00002156 GetPixelChannels(image);
cristy56a9e512010-01-06 18:18:55 +00002157 }
2158 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +00002159 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002160 SetPixelRed(filter_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +00002161 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002162 SetPixelGreen(filter_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +00002163 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002164 SetPixelBlue(filter_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00002165 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy56a9e512010-01-06 18:18:55 +00002166 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002167 SetPixelBlack(filter_image,ClampToQuantum(gamma*pixel.black),q);
cristyed231572011-07-14 02:18:59 +00002168 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy56a9e512010-01-06 18:18:55 +00002169 {
cristy36826ab2010-03-06 01:29:30 +00002170 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002171 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00002172 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002173 {
cristybb503372010-05-27 20:51:26 +00002174 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002175 {
cristy4c08aed2011-07-01 19:47:50 +00002176 pixel.alpha+=(*k)*GetPixelAlpha(image,kernel_pixels+u*
cristyed231572011-07-14 02:18:59 +00002177 GetPixelChannels(image));
cristy56a9e512010-01-06 18:18:55 +00002178 k++;
2179 }
cristy4c08aed2011-07-01 19:47:50 +00002180 kernel_pixels+=(image->columns+kernel->width)*
cristyed231572011-07-14 02:18:59 +00002181 GetPixelChannels(image);
cristy56a9e512010-01-06 18:18:55 +00002182 }
cristy4c08aed2011-07-01 19:47:50 +00002183 SetPixelAlpha(filter_image,ClampToQuantum(pixel.alpha),q);
cristy56a9e512010-01-06 18:18:55 +00002184 }
2185 }
cristyed231572011-07-14 02:18:59 +00002186 p+=GetPixelChannels(image);
2187 q+=GetPixelChannels(filter_image);
cristy56a9e512010-01-06 18:18:55 +00002188 }
2189 sync=SyncCacheViewAuthenticPixels(filter_view,exception);
2190 if (sync == MagickFalse)
2191 status=MagickFalse;
2192 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2193 {
2194 MagickBooleanType
2195 proceed;
2196
2197#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00002198 #pragma omp critical (MagickCore_FilterImage)
cristy56a9e512010-01-06 18:18:55 +00002199#endif
2200 proceed=SetImageProgress(image,FilterImageTag,progress++,image->rows);
2201 if (proceed == MagickFalse)
2202 status=MagickFalse;
2203 }
2204 }
2205 filter_image->type=image->type;
2206 filter_view=DestroyCacheView(filter_view);
2207 image_view=DestroyCacheView(image_view);
cristy56a9e512010-01-06 18:18:55 +00002208 if (status == MagickFalse)
2209 filter_image=DestroyImage(filter_image);
2210 return(filter_image);
2211}
2212
2213/*
2214%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2215% %
2216% %
2217% %
cristy3ed852e2009-09-05 21:47:34 +00002218% G a u s s i a n B l u r I m a g e %
2219% %
2220% %
2221% %
2222%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2223%
2224% GaussianBlurImage() blurs an image. We convolve the image with a
2225% Gaussian operator of the given radius and standard deviation (sigma).
2226% For reasonable results, the radius should be larger than sigma. Use a
2227% radius of 0 and GaussianBlurImage() selects a suitable radius for you
2228%
2229% The format of the GaussianBlurImage method is:
2230%
2231% Image *GaussianBlurImage(const Image *image,onst double radius,
2232% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002233%
2234% A description of each parameter follows:
2235%
2236% o image: the image.
2237%
cristy3ed852e2009-09-05 21:47:34 +00002238% o radius: the radius of the Gaussian, in pixels, not counting the center
2239% pixel.
2240%
2241% o sigma: the standard deviation of the Gaussian, in pixels.
2242%
2243% o exception: return any errors or warnings in this structure.
2244%
2245*/
cristyf4ad9df2011-07-08 16:49:03 +00002246MagickExport Image *GaussianBlurImage(const Image *image,
2247 const double radius,const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002248{
2249 double
2250 *kernel;
2251
2252 Image
2253 *blur_image;
2254
cristybb503372010-05-27 20:51:26 +00002255 register ssize_t
cristy47e00502009-12-17 19:19:57 +00002256 i;
2257
cristybb503372010-05-27 20:51:26 +00002258 size_t
cristy3ed852e2009-09-05 21:47:34 +00002259 width;
2260
cristy117ff172010-08-15 21:35:32 +00002261 ssize_t
2262 j,
2263 u,
2264 v;
2265
cristy3ed852e2009-09-05 21:47:34 +00002266 assert(image != (const Image *) NULL);
2267 assert(image->signature == MagickSignature);
2268 if (image->debug != MagickFalse)
2269 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2270 assert(exception != (ExceptionInfo *) NULL);
2271 assert(exception->signature == MagickSignature);
2272 width=GetOptimalKernelWidth2D(radius,sigma);
2273 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2274 if (kernel == (double *) NULL)
2275 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00002276 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00002277 i=0;
cristy47e00502009-12-17 19:19:57 +00002278 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002279 {
cristy47e00502009-12-17 19:19:57 +00002280 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00002281 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
2282 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00002283 }
cristyf4ad9df2011-07-08 16:49:03 +00002284 blur_image=ConvolveImage(image,width,kernel,exception);
cristy3ed852e2009-09-05 21:47:34 +00002285 kernel=(double *) RelinquishMagickMemory(kernel);
2286 return(blur_image);
2287}
2288
2289/*
2290%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2291% %
2292% %
2293% %
cristy3ed852e2009-09-05 21:47:34 +00002294% M o t i o n B l u r I m a g e %
2295% %
2296% %
2297% %
2298%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2299%
2300% MotionBlurImage() simulates motion blur. We convolve the image with a
2301% Gaussian operator of the given radius and standard deviation (sigma).
2302% For reasonable results, radius should be larger than sigma. Use a
2303% radius of 0 and MotionBlurImage() selects a suitable radius for you.
2304% Angle gives the angle of the blurring motion.
2305%
2306% Andrew Protano contributed this effect.
2307%
2308% The format of the MotionBlurImage method is:
2309%
2310% Image *MotionBlurImage(const Image *image,const double radius,
2311% const double sigma,const double angle,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002312%
2313% A description of each parameter follows:
2314%
2315% o image: the image.
2316%
cristy3ed852e2009-09-05 21:47:34 +00002317% o radius: the radius of the Gaussian, in pixels, not counting
2318% the center pixel.
2319%
2320% o sigma: the standard deviation of the Gaussian, in pixels.
2321%
cristycee97112010-05-28 00:44:52 +00002322% o angle: Apply the effect along this angle.
cristy3ed852e2009-09-05 21:47:34 +00002323%
2324% o exception: return any errors or warnings in this structure.
2325%
2326*/
2327
cristybb503372010-05-27 20:51:26 +00002328static double *GetMotionBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +00002329{
cristy3ed852e2009-09-05 21:47:34 +00002330 double
cristy47e00502009-12-17 19:19:57 +00002331 *kernel,
cristy3ed852e2009-09-05 21:47:34 +00002332 normalize;
2333
cristybb503372010-05-27 20:51:26 +00002334 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002335 i;
2336
2337 /*
cristy47e00502009-12-17 19:19:57 +00002338 Generate a 1-D convolution kernel.
cristy3ed852e2009-09-05 21:47:34 +00002339 */
2340 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
2341 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
2342 if (kernel == (double *) NULL)
2343 return(kernel);
cristy3ed852e2009-09-05 21:47:34 +00002344 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00002345 for (i=0; i < (ssize_t) width; i++)
cristy47e00502009-12-17 19:19:57 +00002346 {
cristy4205a3c2010-09-12 20:19:59 +00002347 kernel[i]=(double) (exp((-((double) i*i)/(double) (2.0*MagickSigma*
2348 MagickSigma)))/(MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00002349 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +00002350 }
cristybb503372010-05-27 20:51:26 +00002351 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002352 kernel[i]/=normalize;
2353 return(kernel);
2354}
2355
cristyf4ad9df2011-07-08 16:49:03 +00002356MagickExport Image *MotionBlurImage(const Image *image,
2357 const double radius,const double sigma,const double angle,
2358 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002359{
cristyc4c8d132010-01-07 01:58:38 +00002360 CacheView
2361 *blur_view,
2362 *image_view;
2363
cristy3ed852e2009-09-05 21:47:34 +00002364 double
2365 *kernel;
2366
2367 Image
2368 *blur_image;
2369
cristy3ed852e2009-09-05 21:47:34 +00002370 MagickBooleanType
2371 status;
2372
cristybb503372010-05-27 20:51:26 +00002373 MagickOffsetType
2374 progress;
2375
cristy4c08aed2011-07-01 19:47:50 +00002376 PixelInfo
cristyddd82202009-11-03 20:14:50 +00002377 bias;
cristy3ed852e2009-09-05 21:47:34 +00002378
2379 OffsetInfo
2380 *offset;
2381
2382 PointInfo
2383 point;
2384
cristybb503372010-05-27 20:51:26 +00002385 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002386 i;
2387
cristybb503372010-05-27 20:51:26 +00002388 size_t
cristy3ed852e2009-09-05 21:47:34 +00002389 width;
2390
cristybb503372010-05-27 20:51:26 +00002391 ssize_t
2392 y;
2393
cristy3ed852e2009-09-05 21:47:34 +00002394 assert(image != (Image *) NULL);
2395 assert(image->signature == MagickSignature);
2396 if (image->debug != MagickFalse)
2397 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2398 assert(exception != (ExceptionInfo *) NULL);
2399 width=GetOptimalKernelWidth1D(radius,sigma);
2400 kernel=GetMotionBlurKernel(width,sigma);
2401 if (kernel == (double *) NULL)
2402 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2403 offset=(OffsetInfo *) AcquireQuantumMemory(width,sizeof(*offset));
2404 if (offset == (OffsetInfo *) NULL)
2405 {
2406 kernel=(double *) RelinquishMagickMemory(kernel);
2407 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2408 }
2409 blur_image=CloneImage(image,0,0,MagickTrue,exception);
2410 if (blur_image == (Image *) NULL)
2411 {
2412 kernel=(double *) RelinquishMagickMemory(kernel);
2413 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2414 return((Image *) NULL);
2415 }
2416 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
2417 {
2418 kernel=(double *) RelinquishMagickMemory(kernel);
2419 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2420 InheritException(exception,&blur_image->exception);
2421 blur_image=DestroyImage(blur_image);
2422 return((Image *) NULL);
2423 }
2424 point.x=(double) width*sin(DegreesToRadians(angle));
2425 point.y=(double) width*cos(DegreesToRadians(angle));
cristybb503372010-05-27 20:51:26 +00002426 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002427 {
cristybb503372010-05-27 20:51:26 +00002428 offset[i].x=(ssize_t) ceil((double) (i*point.y)/hypot(point.x,point.y)-0.5);
2429 offset[i].y=(ssize_t) ceil((double) (i*point.x)/hypot(point.x,point.y)-0.5);
cristy3ed852e2009-09-05 21:47:34 +00002430 }
2431 /*
2432 Motion blur image.
2433 */
2434 status=MagickTrue;
2435 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00002436 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00002437 image_view=AcquireCacheView(image);
2438 blur_view=AcquireCacheView(blur_image);
cristyb557a152011-02-22 12:14:30 +00002439#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00002440 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00002441#endif
cristybb503372010-05-27 20:51:26 +00002442 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002443 {
cristy4c08aed2011-07-01 19:47:50 +00002444 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002445 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002446
cristy117ff172010-08-15 21:35:32 +00002447 register ssize_t
2448 x;
2449
cristy3ed852e2009-09-05 21:47:34 +00002450 if (status == MagickFalse)
2451 continue;
2452 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
2453 exception);
cristy4c08aed2011-07-01 19:47:50 +00002454 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002455 {
2456 status=MagickFalse;
2457 continue;
2458 }
cristybb503372010-05-27 20:51:26 +00002459 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002460 {
cristy4c08aed2011-07-01 19:47:50 +00002461 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002462 qixel;
2463
2464 PixelPacket
2465 pixel;
2466
2467 register double
cristyc47d1f82009-11-26 01:44:43 +00002468 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00002469
cristybb503372010-05-27 20:51:26 +00002470 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002471 i;
2472
cristy3ed852e2009-09-05 21:47:34 +00002473 k=kernel;
cristyddd82202009-11-03 20:14:50 +00002474 qixel=bias;
cristyed231572011-07-14 02:18:59 +00002475 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00002476 {
cristybb503372010-05-27 20:51:26 +00002477 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002478 {
2479 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
2480 offset[i].y,&pixel,exception);
2481 qixel.red+=(*k)*pixel.red;
2482 qixel.green+=(*k)*pixel.green;
2483 qixel.blue+=(*k)*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00002484 qixel.alpha+=(*k)*pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00002485 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00002486 qixel.black+=(*k)*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00002487 k++;
2488 }
cristyed231572011-07-14 02:18:59 +00002489 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002490 SetPixelRed(blur_image,
2491 ClampToQuantum(qixel.red),q);
cristyed231572011-07-14 02:18:59 +00002492 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002493 SetPixelGreen(blur_image,
2494 ClampToQuantum(qixel.green),q);
cristyed231572011-07-14 02:18:59 +00002495 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002496 SetPixelBlue(blur_image,
2497 ClampToQuantum(qixel.blue),q);
cristyed231572011-07-14 02:18:59 +00002498 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002499 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002500 SetPixelBlack(blur_image,
2501 ClampToQuantum(qixel.black),q);
cristyed231572011-07-14 02:18:59 +00002502 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002503 SetPixelAlpha(blur_image,
2504 ClampToQuantum(qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00002505 }
2506 else
2507 {
2508 MagickRealType
2509 alpha,
2510 gamma;
2511
2512 alpha=0.0;
2513 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00002514 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002515 {
2516 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
2517 offset[i].y,&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00002518 alpha=(MagickRealType) (QuantumScale*pixel.alpha);
cristy3ed852e2009-09-05 21:47:34 +00002519 qixel.red+=(*k)*alpha*pixel.red;
2520 qixel.green+=(*k)*alpha*pixel.green;
2521 qixel.blue+=(*k)*alpha*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00002522 qixel.alpha+=(*k)*pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00002523 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00002524 qixel.black+=(*k)*alpha*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00002525 gamma+=(*k)*alpha;
2526 k++;
2527 }
2528 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +00002529 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002530 SetPixelRed(blur_image,
2531 ClampToQuantum(gamma*qixel.red),q);
cristyed231572011-07-14 02:18:59 +00002532 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002533 SetPixelGreen(blur_image,
2534 ClampToQuantum(gamma*qixel.green),q);
cristyed231572011-07-14 02:18:59 +00002535 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002536 SetPixelBlue(blur_image,
2537 ClampToQuantum(gamma*qixel.blue),q);
cristyed231572011-07-14 02:18:59 +00002538 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002539 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002540 SetPixelBlack(blur_image,
2541 ClampToQuantum(gamma*qixel.black),q);
cristyed231572011-07-14 02:18:59 +00002542 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002543 SetPixelAlpha(blur_image,
2544 ClampToQuantum(qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00002545 }
cristyed231572011-07-14 02:18:59 +00002546 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00002547 }
2548 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
2549 status=MagickFalse;
2550 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2551 {
2552 MagickBooleanType
2553 proceed;
2554
cristyb557a152011-02-22 12:14:30 +00002555#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00002556 #pragma omp critical (MagickCore_MotionBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00002557#endif
2558 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
2559 if (proceed == MagickFalse)
2560 status=MagickFalse;
2561 }
2562 }
2563 blur_view=DestroyCacheView(blur_view);
2564 image_view=DestroyCacheView(image_view);
2565 kernel=(double *) RelinquishMagickMemory(kernel);
2566 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2567 if (status == MagickFalse)
2568 blur_image=DestroyImage(blur_image);
2569 return(blur_image);
2570}
2571
2572/*
2573%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2574% %
2575% %
2576% %
2577% P r e v i e w I m a g e %
2578% %
2579% %
2580% %
2581%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2582%
2583% PreviewImage() tiles 9 thumbnails of the specified image with an image
2584% processing operation applied with varying parameters. This may be helpful
2585% pin-pointing an appropriate parameter for a particular image processing
2586% operation.
2587%
2588% The format of the PreviewImages method is:
2589%
2590% Image *PreviewImages(const Image *image,const PreviewType preview,
2591% ExceptionInfo *exception)
2592%
2593% A description of each parameter follows:
2594%
2595% o image: the image.
2596%
2597% o preview: the image processing operation.
2598%
2599% o exception: return any errors or warnings in this structure.
2600%
2601*/
2602MagickExport Image *PreviewImage(const Image *image,const PreviewType preview,
2603 ExceptionInfo *exception)
2604{
2605#define NumberTiles 9
2606#define PreviewImageTag "Preview/Image"
2607#define DefaultPreviewGeometry "204x204+10+10"
2608
2609 char
2610 factor[MaxTextExtent],
2611 label[MaxTextExtent];
2612
2613 double
2614 degrees,
2615 gamma,
2616 percentage,
2617 radius,
2618 sigma,
2619 threshold;
2620
2621 Image
2622 *images,
2623 *montage_image,
2624 *preview_image,
2625 *thumbnail;
2626
2627 ImageInfo
2628 *preview_info;
2629
cristy3ed852e2009-09-05 21:47:34 +00002630 MagickBooleanType
2631 proceed;
2632
2633 MontageInfo
2634 *montage_info;
2635
2636 QuantizeInfo
2637 quantize_info;
2638
2639 RectangleInfo
2640 geometry;
2641
cristybb503372010-05-27 20:51:26 +00002642 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002643 i,
2644 x;
2645
cristybb503372010-05-27 20:51:26 +00002646 size_t
cristy3ed852e2009-09-05 21:47:34 +00002647 colors;
2648
cristy117ff172010-08-15 21:35:32 +00002649 ssize_t
2650 y;
2651
cristy3ed852e2009-09-05 21:47:34 +00002652 /*
2653 Open output image file.
2654 */
2655 assert(image != (Image *) NULL);
2656 assert(image->signature == MagickSignature);
2657 if (image->debug != MagickFalse)
2658 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2659 colors=2;
2660 degrees=0.0;
2661 gamma=(-0.2f);
2662 preview_info=AcquireImageInfo();
2663 SetGeometry(image,&geometry);
2664 (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y,
2665 &geometry.width,&geometry.height);
2666 images=NewImageList();
2667 percentage=12.5;
2668 GetQuantizeInfo(&quantize_info);
2669 radius=0.0;
2670 sigma=1.0;
2671 threshold=0.0;
2672 x=0;
2673 y=0;
2674 for (i=0; i < NumberTiles; i++)
2675 {
2676 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
2677 if (thumbnail == (Image *) NULL)
2678 break;
2679 (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL,
2680 (void *) NULL);
2681 (void) SetImageProperty(thumbnail,"label",DefaultTileLabel);
2682 if (i == (NumberTiles/2))
2683 {
2684 (void) QueryColorDatabase("#dfdfdf",&thumbnail->matte_color,exception);
2685 AppendImageToList(&images,thumbnail);
2686 continue;
2687 }
2688 switch (preview)
2689 {
2690 case RotatePreview:
2691 {
2692 degrees+=45.0;
2693 preview_image=RotateImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002694 (void) FormatLocaleString(label,MaxTextExtent,"rotate %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002695 break;
2696 }
2697 case ShearPreview:
2698 {
2699 degrees+=5.0;
2700 preview_image=ShearImage(thumbnail,degrees,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002701 (void) FormatLocaleString(label,MaxTextExtent,"shear %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002702 degrees,2.0*degrees);
2703 break;
2704 }
2705 case RollPreview:
2706 {
cristybb503372010-05-27 20:51:26 +00002707 x=(ssize_t) ((i+1)*thumbnail->columns)/NumberTiles;
2708 y=(ssize_t) ((i+1)*thumbnail->rows)/NumberTiles;
cristy3ed852e2009-09-05 21:47:34 +00002709 preview_image=RollImage(thumbnail,x,y,exception);
cristyb51dff52011-05-19 16:55:47 +00002710 (void) FormatLocaleString(label,MaxTextExtent,"roll %+.20gx%+.20g",
cristye8c25f92010-06-03 00:53:06 +00002711 (double) x,(double) y);
cristy3ed852e2009-09-05 21:47:34 +00002712 break;
2713 }
2714 case HuePreview:
2715 {
2716 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2717 if (preview_image == (Image *) NULL)
2718 break;
cristyb51dff52011-05-19 16:55:47 +00002719 (void) FormatLocaleString(factor,MaxTextExtent,"100,100,%g",
cristy3ed852e2009-09-05 21:47:34 +00002720 2.0*percentage);
2721 (void) ModulateImage(preview_image,factor);
cristyb51dff52011-05-19 16:55:47 +00002722 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002723 break;
2724 }
2725 case SaturationPreview:
2726 {
2727 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2728 if (preview_image == (Image *) NULL)
2729 break;
cristyb51dff52011-05-19 16:55:47 +00002730 (void) FormatLocaleString(factor,MaxTextExtent,"100,%g",
cristy8cd5b312010-01-07 01:10:24 +00002731 2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00002732 (void) ModulateImage(preview_image,factor);
cristyb51dff52011-05-19 16:55:47 +00002733 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002734 break;
2735 }
2736 case BrightnessPreview:
2737 {
2738 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2739 if (preview_image == (Image *) NULL)
2740 break;
cristyb51dff52011-05-19 16:55:47 +00002741 (void) FormatLocaleString(factor,MaxTextExtent,"%g",2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00002742 (void) ModulateImage(preview_image,factor);
cristyb51dff52011-05-19 16:55:47 +00002743 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002744 break;
2745 }
2746 case GammaPreview:
2747 default:
2748 {
2749 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2750 if (preview_image == (Image *) NULL)
2751 break;
2752 gamma+=0.4f;
cristy50fbc382011-07-07 02:19:17 +00002753 (void) GammaImage(preview_image,gamma);
cristyb51dff52011-05-19 16:55:47 +00002754 (void) FormatLocaleString(label,MaxTextExtent,"gamma %g",gamma);
cristy3ed852e2009-09-05 21:47:34 +00002755 break;
2756 }
2757 case SpiffPreview:
2758 {
2759 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2760 if (preview_image != (Image *) NULL)
2761 for (x=0; x < i; x++)
2762 (void) ContrastImage(preview_image,MagickTrue);
cristyb51dff52011-05-19 16:55:47 +00002763 (void) FormatLocaleString(label,MaxTextExtent,"contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002764 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002765 break;
2766 }
2767 case DullPreview:
2768 {
2769 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2770 if (preview_image == (Image *) NULL)
2771 break;
2772 for (x=0; x < i; x++)
2773 (void) ContrastImage(preview_image,MagickFalse);
cristyb51dff52011-05-19 16:55:47 +00002774 (void) FormatLocaleString(label,MaxTextExtent,"+contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002775 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002776 break;
2777 }
2778 case GrayscalePreview:
2779 {
2780 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2781 if (preview_image == (Image *) NULL)
2782 break;
2783 colors<<=1;
2784 quantize_info.number_colors=colors;
2785 quantize_info.colorspace=GRAYColorspace;
2786 (void) QuantizeImage(&quantize_info,preview_image);
cristyb51dff52011-05-19 16:55:47 +00002787 (void) FormatLocaleString(label,MaxTextExtent,
cristye8c25f92010-06-03 00:53:06 +00002788 "-colorspace gray -colors %.20g",(double) colors);
cristy3ed852e2009-09-05 21:47:34 +00002789 break;
2790 }
2791 case QuantizePreview:
2792 {
2793 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2794 if (preview_image == (Image *) NULL)
2795 break;
2796 colors<<=1;
2797 quantize_info.number_colors=colors;
2798 (void) QuantizeImage(&quantize_info,preview_image);
cristyb51dff52011-05-19 16:55:47 +00002799 (void) FormatLocaleString(label,MaxTextExtent,"colors %.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00002800 colors);
cristy3ed852e2009-09-05 21:47:34 +00002801 break;
2802 }
2803 case DespecklePreview:
2804 {
2805 for (x=0; x < (i-1); x++)
2806 {
2807 preview_image=DespeckleImage(thumbnail,exception);
2808 if (preview_image == (Image *) NULL)
2809 break;
2810 thumbnail=DestroyImage(thumbnail);
2811 thumbnail=preview_image;
2812 }
2813 preview_image=DespeckleImage(thumbnail,exception);
2814 if (preview_image == (Image *) NULL)
2815 break;
cristyb51dff52011-05-19 16:55:47 +00002816 (void) FormatLocaleString(label,MaxTextExtent,"despeckle (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002817 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002818 break;
2819 }
2820 case ReduceNoisePreview:
2821 {
cristy95c38342011-03-18 22:39:51 +00002822 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) radius,
2823 (size_t) radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002824 (void) FormatLocaleString(label,MaxTextExtent,"noise %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002825 break;
2826 }
2827 case AddNoisePreview:
2828 {
2829 switch ((int) i)
2830 {
2831 case 0:
2832 {
2833 (void) CopyMagickString(factor,"uniform",MaxTextExtent);
2834 break;
2835 }
2836 case 1:
2837 {
2838 (void) CopyMagickString(factor,"gaussian",MaxTextExtent);
2839 break;
2840 }
2841 case 2:
2842 {
2843 (void) CopyMagickString(factor,"multiplicative",MaxTextExtent);
2844 break;
2845 }
2846 case 3:
2847 {
2848 (void) CopyMagickString(factor,"impulse",MaxTextExtent);
2849 break;
2850 }
2851 case 4:
2852 {
2853 (void) CopyMagickString(factor,"laplacian",MaxTextExtent);
2854 break;
2855 }
2856 case 5:
2857 {
2858 (void) CopyMagickString(factor,"Poisson",MaxTextExtent);
2859 break;
2860 }
2861 default:
2862 {
2863 (void) CopyMagickString(thumbnail->magick,"NULL",MaxTextExtent);
2864 break;
2865 }
2866 }
cristyd76c51e2011-03-26 00:21:26 +00002867 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) i,
2868 (size_t) i,exception);
cristyb51dff52011-05-19 16:55:47 +00002869 (void) FormatLocaleString(label,MaxTextExtent,"+noise %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002870 break;
2871 }
2872 case SharpenPreview:
2873 {
2874 preview_image=SharpenImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00002875 (void) FormatLocaleString(label,MaxTextExtent,"sharpen %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002876 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002877 break;
2878 }
2879 case BlurPreview:
2880 {
2881 preview_image=BlurImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00002882 (void) FormatLocaleString(label,MaxTextExtent,"blur %gx%g",radius,
cristy3ed852e2009-09-05 21:47:34 +00002883 sigma);
2884 break;
2885 }
2886 case ThresholdPreview:
2887 {
2888 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2889 if (preview_image == (Image *) NULL)
2890 break;
2891 (void) BilevelImage(thumbnail,
2892 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
cristyb51dff52011-05-19 16:55:47 +00002893 (void) FormatLocaleString(label,MaxTextExtent,"threshold %g",
cristy3ed852e2009-09-05 21:47:34 +00002894 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
2895 break;
2896 }
2897 case EdgeDetectPreview:
2898 {
2899 preview_image=EdgeImage(thumbnail,radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002900 (void) FormatLocaleString(label,MaxTextExtent,"edge %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002901 break;
2902 }
2903 case SpreadPreview:
2904 {
2905 preview_image=SpreadImage(thumbnail,radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002906 (void) FormatLocaleString(label,MaxTextExtent,"spread %g",
cristy8cd5b312010-01-07 01:10:24 +00002907 radius+0.5);
cristy3ed852e2009-09-05 21:47:34 +00002908 break;
2909 }
2910 case SolarizePreview:
2911 {
2912 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2913 if (preview_image == (Image *) NULL)
2914 break;
2915 (void) SolarizeImage(preview_image,(double) QuantumRange*
2916 percentage/100.0);
cristyb51dff52011-05-19 16:55:47 +00002917 (void) FormatLocaleString(label,MaxTextExtent,"solarize %g",
cristy3ed852e2009-09-05 21:47:34 +00002918 (QuantumRange*percentage)/100.0);
2919 break;
2920 }
2921 case ShadePreview:
2922 {
2923 degrees+=10.0;
2924 preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees,
2925 exception);
cristyb51dff52011-05-19 16:55:47 +00002926 (void) FormatLocaleString(label,MaxTextExtent,"shade %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002927 degrees,degrees);
cristy3ed852e2009-09-05 21:47:34 +00002928 break;
2929 }
2930 case RaisePreview:
2931 {
2932 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2933 if (preview_image == (Image *) NULL)
2934 break;
cristybb503372010-05-27 20:51:26 +00002935 geometry.width=(size_t) (2*i+2);
2936 geometry.height=(size_t) (2*i+2);
cristy3ed852e2009-09-05 21:47:34 +00002937 geometry.x=i/2;
2938 geometry.y=i/2;
2939 (void) RaiseImage(preview_image,&geometry,MagickTrue);
cristyb51dff52011-05-19 16:55:47 +00002940 (void) FormatLocaleString(label,MaxTextExtent,
cristy6d8abba2010-06-03 01:10:47 +00002941 "raise %.20gx%.20g%+.20g%+.20g",(double) geometry.width,(double)
cristye8c25f92010-06-03 00:53:06 +00002942 geometry.height,(double) geometry.x,(double) geometry.y);
cristy3ed852e2009-09-05 21:47:34 +00002943 break;
2944 }
2945 case SegmentPreview:
2946 {
2947 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2948 if (preview_image == (Image *) NULL)
2949 break;
2950 threshold+=0.4f;
2951 (void) SegmentImage(preview_image,RGBColorspace,MagickFalse,threshold,
2952 threshold);
cristyb51dff52011-05-19 16:55:47 +00002953 (void) FormatLocaleString(label,MaxTextExtent,"segment %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002954 threshold,threshold);
2955 break;
2956 }
2957 case SwirlPreview:
2958 {
2959 preview_image=SwirlImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002960 (void) FormatLocaleString(label,MaxTextExtent,"swirl %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002961 degrees+=45.0;
2962 break;
2963 }
2964 case ImplodePreview:
2965 {
2966 degrees+=0.1f;
2967 preview_image=ImplodeImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002968 (void) FormatLocaleString(label,MaxTextExtent,"implode %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002969 break;
2970 }
2971 case WavePreview:
2972 {
2973 degrees+=5.0f;
2974 preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002975 (void) FormatLocaleString(label,MaxTextExtent,"wave %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002976 0.5*degrees,2.0*degrees);
cristy3ed852e2009-09-05 21:47:34 +00002977 break;
2978 }
2979 case OilPaintPreview:
2980 {
2981 preview_image=OilPaintImage(thumbnail,(double) radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002982 (void) FormatLocaleString(label,MaxTextExtent,"paint %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002983 break;
2984 }
2985 case CharcoalDrawingPreview:
2986 {
2987 preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma,
2988 exception);
cristyb51dff52011-05-19 16:55:47 +00002989 (void) FormatLocaleString(label,MaxTextExtent,"charcoal %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002990 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002991 break;
2992 }
2993 case JPEGPreview:
2994 {
2995 char
2996 filename[MaxTextExtent];
2997
2998 int
2999 file;
3000
3001 MagickBooleanType
3002 status;
3003
3004 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3005 if (preview_image == (Image *) NULL)
3006 break;
cristybb503372010-05-27 20:51:26 +00003007 preview_info->quality=(size_t) percentage;
cristyb51dff52011-05-19 16:55:47 +00003008 (void) FormatLocaleString(factor,MaxTextExtent,"%.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00003009 preview_info->quality);
cristy3ed852e2009-09-05 21:47:34 +00003010 file=AcquireUniqueFileResource(filename);
3011 if (file != -1)
3012 file=close(file)-1;
cristyb51dff52011-05-19 16:55:47 +00003013 (void) FormatLocaleString(preview_image->filename,MaxTextExtent,
cristy3ed852e2009-09-05 21:47:34 +00003014 "jpeg:%s",filename);
3015 status=WriteImage(preview_info,preview_image);
3016 if (status != MagickFalse)
3017 {
3018 Image
3019 *quality_image;
3020
3021 (void) CopyMagickString(preview_info->filename,
3022 preview_image->filename,MaxTextExtent);
3023 quality_image=ReadImage(preview_info,exception);
3024 if (quality_image != (Image *) NULL)
3025 {
3026 preview_image=DestroyImage(preview_image);
3027 preview_image=quality_image;
3028 }
3029 }
3030 (void) RelinquishUniqueFileResource(preview_image->filename);
3031 if ((GetBlobSize(preview_image)/1024) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00003032 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%gmb ",
cristy3ed852e2009-09-05 21:47:34 +00003033 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
3034 1024.0/1024.0);
3035 else
3036 if (GetBlobSize(preview_image) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00003037 (void) FormatLocaleString(label,MaxTextExtent,
cristye7f51092010-01-17 00:39:37 +00003038 "quality %s\n%gkb ",factor,(double) ((MagickOffsetType)
cristy8cd5b312010-01-07 01:10:24 +00003039 GetBlobSize(preview_image))/1024.0);
cristy3ed852e2009-09-05 21:47:34 +00003040 else
cristyb51dff52011-05-19 16:55:47 +00003041 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%.20gb ",
cristy54ea5732011-06-10 12:39:53 +00003042 factor,(double) ((MagickOffsetType) GetBlobSize(thumbnail)));
cristy3ed852e2009-09-05 21:47:34 +00003043 break;
3044 }
3045 }
3046 thumbnail=DestroyImage(thumbnail);
3047 percentage+=12.5;
3048 radius+=0.5;
3049 sigma+=0.25;
3050 if (preview_image == (Image *) NULL)
3051 break;
3052 (void) DeleteImageProperty(preview_image,"label");
3053 (void) SetImageProperty(preview_image,"label",label);
3054 AppendImageToList(&images,preview_image);
cristybb503372010-05-27 20:51:26 +00003055 proceed=SetImageProgress(image,PreviewImageTag,(MagickOffsetType) i,
3056 NumberTiles);
cristy3ed852e2009-09-05 21:47:34 +00003057 if (proceed == MagickFalse)
3058 break;
3059 }
3060 if (images == (Image *) NULL)
3061 {
3062 preview_info=DestroyImageInfo(preview_info);
3063 return((Image *) NULL);
3064 }
3065 /*
3066 Create the montage.
3067 */
3068 montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL);
3069 (void) CopyMagickString(montage_info->filename,image->filename,MaxTextExtent);
3070 montage_info->shadow=MagickTrue;
3071 (void) CloneString(&montage_info->tile,"3x3");
3072 (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry);
3073 (void) CloneString(&montage_info->frame,DefaultTileFrame);
3074 montage_image=MontageImages(images,montage_info,exception);
3075 montage_info=DestroyMontageInfo(montage_info);
3076 images=DestroyImageList(images);
3077 if (montage_image == (Image *) NULL)
3078 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3079 if (montage_image->montage != (char *) NULL)
3080 {
3081 /*
3082 Free image directory.
3083 */
3084 montage_image->montage=(char *) RelinquishMagickMemory(
3085 montage_image->montage);
3086 if (image->directory != (char *) NULL)
3087 montage_image->directory=(char *) RelinquishMagickMemory(
3088 montage_image->directory);
3089 }
3090 preview_info=DestroyImageInfo(preview_info);
3091 return(montage_image);
3092}
3093
3094/*
3095%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3096% %
3097% %
3098% %
3099% R a d i a l B l u r I m a g e %
3100% %
3101% %
3102% %
3103%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3104%
3105% RadialBlurImage() applies a radial blur to the image.
3106%
3107% Andrew Protano contributed this effect.
3108%
3109% The format of the RadialBlurImage method is:
3110%
3111% Image *RadialBlurImage(const Image *image,const double angle,
3112% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003113%
3114% A description of each parameter follows:
3115%
3116% o image: the image.
3117%
cristy3ed852e2009-09-05 21:47:34 +00003118% o angle: the angle of the radial blur.
3119%
3120% o exception: return any errors or warnings in this structure.
3121%
3122*/
cristyf4ad9df2011-07-08 16:49:03 +00003123MagickExport Image *RadialBlurImage(const Image *image,
3124 const double angle,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003125{
cristyc4c8d132010-01-07 01:58:38 +00003126 CacheView
3127 *blur_view,
3128 *image_view;
3129
cristy3ed852e2009-09-05 21:47:34 +00003130 Image
3131 *blur_image;
3132
cristy3ed852e2009-09-05 21:47:34 +00003133 MagickBooleanType
3134 status;
3135
cristybb503372010-05-27 20:51:26 +00003136 MagickOffsetType
3137 progress;
3138
cristy4c08aed2011-07-01 19:47:50 +00003139 PixelInfo
cristyddd82202009-11-03 20:14:50 +00003140 bias;
cristy3ed852e2009-09-05 21:47:34 +00003141
3142 MagickRealType
3143 blur_radius,
3144 *cos_theta,
3145 offset,
3146 *sin_theta,
3147 theta;
3148
3149 PointInfo
3150 blur_center;
3151
cristybb503372010-05-27 20:51:26 +00003152 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003153 i;
3154
cristybb503372010-05-27 20:51:26 +00003155 size_t
cristy3ed852e2009-09-05 21:47:34 +00003156 n;
3157
cristybb503372010-05-27 20:51:26 +00003158 ssize_t
3159 y;
3160
cristy3ed852e2009-09-05 21:47:34 +00003161 /*
3162 Allocate blur image.
3163 */
3164 assert(image != (Image *) NULL);
3165 assert(image->signature == MagickSignature);
3166 if (image->debug != MagickFalse)
3167 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3168 assert(exception != (ExceptionInfo *) NULL);
3169 assert(exception->signature == MagickSignature);
3170 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3171 if (blur_image == (Image *) NULL)
3172 return((Image *) NULL);
3173 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3174 {
3175 InheritException(exception,&blur_image->exception);
3176 blur_image=DestroyImage(blur_image);
3177 return((Image *) NULL);
3178 }
3179 blur_center.x=(double) image->columns/2.0;
3180 blur_center.y=(double) image->rows/2.0;
3181 blur_radius=hypot(blur_center.x,blur_center.y);
cristy117ff172010-08-15 21:35:32 +00003182 n=(size_t) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+2UL);
cristy3ed852e2009-09-05 21:47:34 +00003183 theta=DegreesToRadians(angle)/(MagickRealType) (n-1);
3184 cos_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3185 sizeof(*cos_theta));
3186 sin_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3187 sizeof(*sin_theta));
3188 if ((cos_theta == (MagickRealType *) NULL) ||
3189 (sin_theta == (MagickRealType *) NULL))
3190 {
3191 blur_image=DestroyImage(blur_image);
3192 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3193 }
3194 offset=theta*(MagickRealType) (n-1)/2.0;
cristybb503372010-05-27 20:51:26 +00003195 for (i=0; i < (ssize_t) n; i++)
cristy3ed852e2009-09-05 21:47:34 +00003196 {
3197 cos_theta[i]=cos((double) (theta*i-offset));
3198 sin_theta[i]=sin((double) (theta*i-offset));
3199 }
3200 /*
3201 Radial blur image.
3202 */
3203 status=MagickTrue;
3204 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00003205 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003206 image_view=AcquireCacheView(image);
3207 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003208#if defined(MAGICKCORE_OPENMP_SUPPORT)
3209 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003210#endif
cristybb503372010-05-27 20:51:26 +00003211 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003212 {
cristy4c08aed2011-07-01 19:47:50 +00003213 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003214 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003215
cristy117ff172010-08-15 21:35:32 +00003216 register ssize_t
3217 x;
3218
cristy3ed852e2009-09-05 21:47:34 +00003219 if (status == MagickFalse)
3220 continue;
3221 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3222 exception);
cristy4c08aed2011-07-01 19:47:50 +00003223 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003224 {
3225 status=MagickFalse;
3226 continue;
3227 }
cristybb503372010-05-27 20:51:26 +00003228 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003229 {
cristy4c08aed2011-07-01 19:47:50 +00003230 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003231 qixel;
3232
3233 MagickRealType
3234 normalize,
3235 radius;
3236
3237 PixelPacket
3238 pixel;
3239
3240 PointInfo
3241 center;
3242
cristybb503372010-05-27 20:51:26 +00003243 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003244 i;
3245
cristybb503372010-05-27 20:51:26 +00003246 size_t
cristy3ed852e2009-09-05 21:47:34 +00003247 step;
3248
3249 center.x=(double) x-blur_center.x;
3250 center.y=(double) y-blur_center.y;
3251 radius=hypot((double) center.x,center.y);
3252 if (radius == 0)
3253 step=1;
3254 else
3255 {
cristybb503372010-05-27 20:51:26 +00003256 step=(size_t) (blur_radius/radius);
cristy3ed852e2009-09-05 21:47:34 +00003257 if (step == 0)
3258 step=1;
3259 else
3260 if (step >= n)
3261 step=n-1;
3262 }
3263 normalize=0.0;
cristyddd82202009-11-03 20:14:50 +00003264 qixel=bias;
cristyed231572011-07-14 02:18:59 +00003265 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00003266 {
cristyeaedf062010-05-29 22:36:02 +00003267 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00003268 {
cristyeaedf062010-05-29 22:36:02 +00003269 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
3270 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
3271 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
3272 cos_theta[i]+0.5),&pixel,exception);
cristy3ed852e2009-09-05 21:47:34 +00003273 qixel.red+=pixel.red;
3274 qixel.green+=pixel.green;
3275 qixel.blue+=pixel.blue;
cristy3ed852e2009-09-05 21:47:34 +00003276 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00003277 qixel.black+=pixel.black;
3278 qixel.alpha+=pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00003279 normalize+=1.0;
3280 }
3281 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
3282 normalize);
cristyed231572011-07-14 02:18:59 +00003283 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003284 SetPixelRed(blur_image,
3285 ClampToQuantum(normalize*qixel.red),q);
cristyed231572011-07-14 02:18:59 +00003286 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003287 SetPixelGreen(blur_image,
3288 ClampToQuantum(normalize*qixel.green),q);
cristyed231572011-07-14 02:18:59 +00003289 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003290 SetPixelBlue(blur_image,
3291 ClampToQuantum(normalize*qixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003292 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003293 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003294 SetPixelBlack(blur_image,
3295 ClampToQuantum(normalize*qixel.black),q);
cristyed231572011-07-14 02:18:59 +00003296 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003297 SetPixelAlpha(blur_image,
3298 ClampToQuantum(normalize*qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003299 }
3300 else
3301 {
3302 MagickRealType
3303 alpha,
3304 gamma;
3305
3306 alpha=1.0;
3307 gamma=0.0;
cristyeaedf062010-05-29 22:36:02 +00003308 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00003309 {
cristyeaedf062010-05-29 22:36:02 +00003310 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
3311 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
3312 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
3313 cos_theta[i]+0.5),&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00003314 alpha=(MagickRealType) (QuantumScale*pixel.alpha);
cristy3ed852e2009-09-05 21:47:34 +00003315 qixel.red+=alpha*pixel.red;
3316 qixel.green+=alpha*pixel.green;
3317 qixel.blue+=alpha*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00003318 qixel.alpha+=pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00003319 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00003320 qixel.black+=alpha*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00003321 gamma+=alpha;
3322 normalize+=1.0;
3323 }
3324 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3325 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
3326 normalize);
cristyed231572011-07-14 02:18:59 +00003327 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003328 SetPixelRed(blur_image,
3329 ClampToQuantum(gamma*qixel.red),q);
cristyed231572011-07-14 02:18:59 +00003330 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003331 SetPixelGreen(blur_image,
3332 ClampToQuantum(gamma*qixel.green),q);
cristyed231572011-07-14 02:18:59 +00003333 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003334 SetPixelBlue(blur_image,
3335 ClampToQuantum(gamma*qixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003336 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003337 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003338 SetPixelBlack(blur_image,
3339 ClampToQuantum(gamma*qixel.black),q);
cristyed231572011-07-14 02:18:59 +00003340 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003341 SetPixelAlpha(blur_image,
3342 ClampToQuantum(normalize*qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003343 }
cristyed231572011-07-14 02:18:59 +00003344 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003345 }
3346 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
3347 status=MagickFalse;
3348 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3349 {
3350 MagickBooleanType
3351 proceed;
3352
cristyb5d5f722009-11-04 03:03:49 +00003353#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00003354 #pragma omp critical (MagickCore_RadialBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00003355#endif
3356 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
3357 if (proceed == MagickFalse)
3358 status=MagickFalse;
3359 }
3360 }
3361 blur_view=DestroyCacheView(blur_view);
3362 image_view=DestroyCacheView(image_view);
3363 cos_theta=(MagickRealType *) RelinquishMagickMemory(cos_theta);
3364 sin_theta=(MagickRealType *) RelinquishMagickMemory(sin_theta);
3365 if (status == MagickFalse)
3366 blur_image=DestroyImage(blur_image);
3367 return(blur_image);
3368}
3369
3370/*
3371%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3372% %
3373% %
3374% %
cristy3ed852e2009-09-05 21:47:34 +00003375% S e l e c t i v e B l u r I m a g e %
3376% %
3377% %
3378% %
3379%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3380%
3381% SelectiveBlurImage() selectively blur pixels within a contrast threshold.
3382% It is similar to the unsharpen mask that sharpens everything with contrast
3383% above a certain threshold.
3384%
3385% The format of the SelectiveBlurImage method is:
3386%
3387% Image *SelectiveBlurImage(const Image *image,const double radius,
3388% const double sigma,const double threshold,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003389%
3390% A description of each parameter follows:
3391%
3392% o image: the image.
3393%
cristy3ed852e2009-09-05 21:47:34 +00003394% o radius: the radius of the Gaussian, in pixels, not counting the center
3395% pixel.
3396%
3397% o sigma: the standard deviation of the Gaussian, in pixels.
3398%
3399% o threshold: only pixels within this contrast threshold are included
3400% in the blur operation.
3401%
3402% o exception: return any errors or warnings in this structure.
3403%
3404*/
cristyf4ad9df2011-07-08 16:49:03 +00003405MagickExport Image *SelectiveBlurImage(const Image *image,
3406 const double radius,const double sigma,const double threshold,
3407 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003408{
3409#define SelectiveBlurImageTag "SelectiveBlur/Image"
3410
cristy47e00502009-12-17 19:19:57 +00003411 CacheView
3412 *blur_view,
3413 *image_view;
3414
cristy3ed852e2009-09-05 21:47:34 +00003415 double
cristy3ed852e2009-09-05 21:47:34 +00003416 *kernel;
3417
3418 Image
3419 *blur_image;
3420
cristy3ed852e2009-09-05 21:47:34 +00003421 MagickBooleanType
3422 status;
3423
cristybb503372010-05-27 20:51:26 +00003424 MagickOffsetType
3425 progress;
3426
cristy4c08aed2011-07-01 19:47:50 +00003427 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003428 bias;
3429
cristybb503372010-05-27 20:51:26 +00003430 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003431 i;
cristy3ed852e2009-09-05 21:47:34 +00003432
cristybb503372010-05-27 20:51:26 +00003433 size_t
cristy3ed852e2009-09-05 21:47:34 +00003434 width;
3435
cristybb503372010-05-27 20:51:26 +00003436 ssize_t
3437 j,
3438 u,
3439 v,
3440 y;
3441
cristy3ed852e2009-09-05 21:47:34 +00003442 /*
3443 Initialize blur image attributes.
3444 */
3445 assert(image != (Image *) NULL);
3446 assert(image->signature == MagickSignature);
3447 if (image->debug != MagickFalse)
3448 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3449 assert(exception != (ExceptionInfo *) NULL);
3450 assert(exception->signature == MagickSignature);
3451 width=GetOptimalKernelWidth1D(radius,sigma);
3452 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
3453 if (kernel == (double *) NULL)
3454 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00003455 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00003456 i=0;
cristy47e00502009-12-17 19:19:57 +00003457 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00003458 {
cristy47e00502009-12-17 19:19:57 +00003459 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00003460 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
3461 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00003462 }
3463 if (image->debug != MagickFalse)
3464 {
3465 char
3466 format[MaxTextExtent],
3467 *message;
3468
cristy117ff172010-08-15 21:35:32 +00003469 register const double
3470 *k;
3471
cristybb503372010-05-27 20:51:26 +00003472 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003473 u,
3474 v;
3475
cristy3ed852e2009-09-05 21:47:34 +00003476 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00003477 " SelectiveBlurImage with %.20gx%.20g kernel:",(double) width,(double)
3478 width);
cristy3ed852e2009-09-05 21:47:34 +00003479 message=AcquireString("");
3480 k=kernel;
cristybb503372010-05-27 20:51:26 +00003481 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003482 {
3483 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00003484 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy3ed852e2009-09-05 21:47:34 +00003485 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00003486 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003487 {
cristyb51dff52011-05-19 16:55:47 +00003488 (void) FormatLocaleString(format,MaxTextExtent,"%+f ",*k++);
cristy3ed852e2009-09-05 21:47:34 +00003489 (void) ConcatenateString(&message,format);
3490 }
3491 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
3492 }
3493 message=DestroyString(message);
3494 }
3495 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3496 if (blur_image == (Image *) NULL)
3497 return((Image *) NULL);
3498 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3499 {
3500 InheritException(exception,&blur_image->exception);
3501 blur_image=DestroyImage(blur_image);
3502 return((Image *) NULL);
3503 }
3504 /*
3505 Threshold blur image.
3506 */
3507 status=MagickTrue;
3508 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00003509 GetPixelInfo(image,&bias);
3510 SetPixelInfoBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003511 image_view=AcquireCacheView(image);
3512 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003513#if defined(MAGICKCORE_OPENMP_SUPPORT)
3514 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003515#endif
cristybb503372010-05-27 20:51:26 +00003516 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003517 {
cristy4c08aed2011-07-01 19:47:50 +00003518 double
3519 contrast;
3520
cristy3ed852e2009-09-05 21:47:34 +00003521 MagickBooleanType
3522 sync;
3523
3524 MagickRealType
3525 gamma;
3526
cristy4c08aed2011-07-01 19:47:50 +00003527 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003528 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003529
cristy4c08aed2011-07-01 19:47:50 +00003530 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003531 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003532
cristy117ff172010-08-15 21:35:32 +00003533 register ssize_t
3534 x;
3535
cristy3ed852e2009-09-05 21:47:34 +00003536 if (status == MagickFalse)
3537 continue;
cristy117ff172010-08-15 21:35:32 +00003538 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
3539 (width/2L),image->columns+width,width,exception);
cristy3ed852e2009-09-05 21:47:34 +00003540 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3541 exception);
cristy4c08aed2011-07-01 19:47:50 +00003542 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003543 {
3544 status=MagickFalse;
3545 continue;
3546 }
cristybb503372010-05-27 20:51:26 +00003547 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003548 {
cristy4c08aed2011-07-01 19:47:50 +00003549 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003550 pixel;
3551
3552 register const double
cristyc47d1f82009-11-26 01:44:43 +00003553 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00003554
cristybb503372010-05-27 20:51:26 +00003555 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003556 u;
3557
cristy117ff172010-08-15 21:35:32 +00003558 ssize_t
3559 j,
3560 v;
3561
cristyddd82202009-11-03 20:14:50 +00003562 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00003563 k=kernel;
3564 gamma=0.0;
3565 j=0;
cristyed231572011-07-14 02:18:59 +00003566 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00003567 {
cristybb503372010-05-27 20:51:26 +00003568 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003569 {
cristybb503372010-05-27 20:51:26 +00003570 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003571 {
cristyed231572011-07-14 02:18:59 +00003572 contrast=GetPixelIntensity(image,p+(u+j)*GetPixelChannels(image))-
cristy4c08aed2011-07-01 19:47:50 +00003573 (double) GetPixelIntensity(blur_image,q);
3574 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003575 {
cristy4c08aed2011-07-01 19:47:50 +00003576 pixel.red+=(*k)*
cristyed231572011-07-14 02:18:59 +00003577 GetPixelRed(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003578 pixel.green+=(*k)*
cristyed231572011-07-14 02:18:59 +00003579 GetPixelGreen(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003580 pixel.blue+=(*k)*
cristyed231572011-07-14 02:18:59 +00003581 GetPixelBlue(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003582 if (image->colorspace == CMYKColorspace)
3583 pixel.black+=(*k)*
cristyed231572011-07-14 02:18:59 +00003584 GetPixelBlack(image,p+(u+j)*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003585 gamma+=(*k);
3586 k++;
3587 }
3588 }
cristyd99b0962010-05-29 23:14:26 +00003589 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003590 }
3591 if (gamma != 0.0)
3592 {
3593 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +00003594 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003595 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +00003596 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003597 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +00003598 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003599 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003600 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00003601 (image->colorspace == CMYKColorspace))
3602 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00003603 }
cristyed231572011-07-14 02:18:59 +00003604 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003605 {
3606 gamma=0.0;
3607 j=0;
cristybb503372010-05-27 20:51:26 +00003608 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003609 {
cristybb503372010-05-27 20:51:26 +00003610 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003611 {
cristy4c08aed2011-07-01 19:47:50 +00003612 contrast=GetPixelIntensity(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003613 GetPixelChannels(image))-(double)
cristy4c08aed2011-07-01 19:47:50 +00003614 GetPixelIntensity(blur_image,q);
3615 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003616 {
cristy4c08aed2011-07-01 19:47:50 +00003617 pixel.alpha+=(*k)*
cristyed231572011-07-14 02:18:59 +00003618 GetPixelAlpha(image,p+(u+j)*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003619 gamma+=(*k);
3620 k++;
3621 }
3622 }
cristyeaedf062010-05-29 22:36:02 +00003623 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003624 }
3625 if (gamma != 0.0)
3626 {
3627 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
3628 gamma);
cristy4c08aed2011-07-01 19:47:50 +00003629 SetPixelAlpha(blur_image,ClampToQuantum(gamma*pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003630 }
3631 }
3632 }
3633 else
3634 {
3635 MagickRealType
3636 alpha;
3637
cristybb503372010-05-27 20:51:26 +00003638 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003639 {
cristybb503372010-05-27 20:51:26 +00003640 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003641 {
cristy4c08aed2011-07-01 19:47:50 +00003642 contrast=GetPixelIntensity(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003643 GetPixelChannels(image))-(double)
cristy4c08aed2011-07-01 19:47:50 +00003644 GetPixelIntensity(blur_image,q);
3645 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003646 {
cristy4c08aed2011-07-01 19:47:50 +00003647 alpha=(MagickRealType) (QuantumScale*
cristyed231572011-07-14 02:18:59 +00003648 GetPixelAlpha(image,p+(u+j)*GetPixelChannels(image)));
cristy4c08aed2011-07-01 19:47:50 +00003649 pixel.red+=(*k)*alpha*
cristyed231572011-07-14 02:18:59 +00003650 GetPixelRed(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003651 pixel.green+=(*k)*alpha*GetPixelGreen(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003652 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003653 pixel.blue+=(*k)*alpha*GetPixelBlue(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003654 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003655 pixel.alpha+=(*k)*GetPixelAlpha(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003656 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003657 if (image->colorspace == CMYKColorspace)
3658 pixel.black+=(*k)*GetPixelBlack(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003659 GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003660 gamma+=(*k)*alpha;
3661 k++;
3662 }
3663 }
cristyeaedf062010-05-29 22:36:02 +00003664 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003665 }
3666 if (gamma != 0.0)
3667 {
3668 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +00003669 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003670 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +00003671 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003672 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +00003673 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003674 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003675 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00003676 (image->colorspace == CMYKColorspace))
3677 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00003678 }
cristyed231572011-07-14 02:18:59 +00003679 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003680 {
3681 gamma=0.0;
3682 j=0;
cristybb503372010-05-27 20:51:26 +00003683 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003684 {
cristybb503372010-05-27 20:51:26 +00003685 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003686 {
cristy4c08aed2011-07-01 19:47:50 +00003687 contrast=GetPixelIntensity(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003688 GetPixelChannels(image))-(double)
cristy4c08aed2011-07-01 19:47:50 +00003689 GetPixelIntensity(blur_image,q);
3690 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003691 {
cristy4c08aed2011-07-01 19:47:50 +00003692 pixel.alpha+=(*k)*
cristyed231572011-07-14 02:18:59 +00003693 GetPixelAlpha(image,p+(u+j)*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003694 gamma+=(*k);
3695 k++;
3696 }
3697 }
cristyeaedf062010-05-29 22:36:02 +00003698 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003699 }
3700 if (gamma != 0.0)
3701 {
3702 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
3703 gamma);
cristy4c08aed2011-07-01 19:47:50 +00003704 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003705 }
3706 }
3707 }
cristyed231572011-07-14 02:18:59 +00003708 p+=GetPixelChannels(image);
3709 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003710 }
3711 sync=SyncCacheViewAuthenticPixels(blur_view,exception);
3712 if (sync == MagickFalse)
3713 status=MagickFalse;
3714 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3715 {
3716 MagickBooleanType
3717 proceed;
3718
cristyb5d5f722009-11-04 03:03:49 +00003719#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00003720 #pragma omp critical (MagickCore_SelectiveBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00003721#endif
3722 proceed=SetImageProgress(image,SelectiveBlurImageTag,progress++,
3723 image->rows);
3724 if (proceed == MagickFalse)
3725 status=MagickFalse;
3726 }
3727 }
3728 blur_image->type=image->type;
3729 blur_view=DestroyCacheView(blur_view);
3730 image_view=DestroyCacheView(image_view);
3731 kernel=(double *) RelinquishMagickMemory(kernel);
3732 if (status == MagickFalse)
3733 blur_image=DestroyImage(blur_image);
3734 return(blur_image);
3735}
3736
3737/*
3738%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3739% %
3740% %
3741% %
3742% S h a d e I m a g e %
3743% %
3744% %
3745% %
3746%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3747%
3748% ShadeImage() shines a distant light on an image to create a
3749% three-dimensional effect. You control the positioning of the light with
3750% azimuth and elevation; azimuth is measured in degrees off the x axis
3751% and elevation is measured in pixels above the Z axis.
3752%
3753% The format of the ShadeImage method is:
3754%
3755% Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3756% const double azimuth,const double elevation,ExceptionInfo *exception)
3757%
3758% A description of each parameter follows:
3759%
3760% o image: the image.
3761%
3762% o gray: A value other than zero shades the intensity of each pixel.
3763%
3764% o azimuth, elevation: Define the light source direction.
3765%
3766% o exception: return any errors or warnings in this structure.
3767%
3768*/
3769MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3770 const double azimuth,const double elevation,ExceptionInfo *exception)
3771{
3772#define ShadeImageTag "Shade/Image"
3773
cristyc4c8d132010-01-07 01:58:38 +00003774 CacheView
3775 *image_view,
3776 *shade_view;
3777
cristy3ed852e2009-09-05 21:47:34 +00003778 Image
3779 *shade_image;
3780
cristy3ed852e2009-09-05 21:47:34 +00003781 MagickBooleanType
3782 status;
3783
cristybb503372010-05-27 20:51:26 +00003784 MagickOffsetType
3785 progress;
3786
cristy3ed852e2009-09-05 21:47:34 +00003787 PrimaryInfo
3788 light;
3789
cristybb503372010-05-27 20:51:26 +00003790 ssize_t
3791 y;
3792
cristy3ed852e2009-09-05 21:47:34 +00003793 /*
3794 Initialize shaded image attributes.
3795 */
3796 assert(image != (const Image *) NULL);
3797 assert(image->signature == MagickSignature);
3798 if (image->debug != MagickFalse)
3799 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3800 assert(exception != (ExceptionInfo *) NULL);
3801 assert(exception->signature == MagickSignature);
3802 shade_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
3803 if (shade_image == (Image *) NULL)
3804 return((Image *) NULL);
3805 if (SetImageStorageClass(shade_image,DirectClass) == MagickFalse)
3806 {
3807 InheritException(exception,&shade_image->exception);
3808 shade_image=DestroyImage(shade_image);
3809 return((Image *) NULL);
3810 }
3811 /*
3812 Compute the light vector.
3813 */
3814 light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))*
3815 cos(DegreesToRadians(elevation));
3816 light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))*
3817 cos(DegreesToRadians(elevation));
3818 light.z=(double) QuantumRange*sin(DegreesToRadians(elevation));
3819 /*
3820 Shade image.
3821 */
3822 status=MagickTrue;
3823 progress=0;
3824 image_view=AcquireCacheView(image);
3825 shade_view=AcquireCacheView(shade_image);
cristyb5d5f722009-11-04 03:03:49 +00003826#if defined(MAGICKCORE_OPENMP_SUPPORT)
3827 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003828#endif
cristybb503372010-05-27 20:51:26 +00003829 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003830 {
3831 MagickRealType
3832 distance,
3833 normal_distance,
3834 shade;
3835
3836 PrimaryInfo
3837 normal;
3838
cristy4c08aed2011-07-01 19:47:50 +00003839 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003840 *restrict p,
3841 *restrict s0,
3842 *restrict s1,
3843 *restrict s2;
cristy3ed852e2009-09-05 21:47:34 +00003844
cristy4c08aed2011-07-01 19:47:50 +00003845 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003846 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003847
cristy117ff172010-08-15 21:35:32 +00003848 register ssize_t
3849 x;
3850
cristy3ed852e2009-09-05 21:47:34 +00003851 if (status == MagickFalse)
3852 continue;
3853 p=GetCacheViewVirtualPixels(image_view,-1,y-1,image->columns+2,3,exception);
3854 q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1,
3855 exception);
cristy4c08aed2011-07-01 19:47:50 +00003856 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003857 {
3858 status=MagickFalse;
3859 continue;
3860 }
3861 /*
3862 Shade this row of pixels.
3863 */
3864 normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */
cristyed231572011-07-14 02:18:59 +00003865 s0=p+GetPixelChannels(image);
3866 s1=s0+(image->columns+2)*GetPixelChannels(image);
3867 s2=s1+(image->columns+2)*GetPixelChannels(image);
cristybb503372010-05-27 20:51:26 +00003868 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003869 {
3870 /*
3871 Determine the surface normal and compute shading.
3872 */
cristyed231572011-07-14 02:18:59 +00003873 normal.x=(double) (GetPixelIntensity(image,s0-GetPixelChannels(image))+
3874 GetPixelIntensity(image,s1-GetPixelChannels(image))+
3875 GetPixelIntensity(image,s2-GetPixelChannels(image))-
3876 GetPixelIntensity(image,s0+GetPixelChannels(image))-
3877 GetPixelIntensity(image,s1+GetPixelChannels(image))-
3878 GetPixelIntensity(image,s2+GetPixelChannels(image)));
3879 normal.y=(double) (GetPixelIntensity(image,s2-GetPixelChannels(image))+
cristy4c08aed2011-07-01 19:47:50 +00003880 GetPixelIntensity(image,s2)+
cristyed231572011-07-14 02:18:59 +00003881 GetPixelIntensity(image,s2+GetPixelChannels(image))-
3882 GetPixelIntensity(image,s0-GetPixelChannels(image))-
cristy4c08aed2011-07-01 19:47:50 +00003883 GetPixelIntensity(image,s0)-
cristyed231572011-07-14 02:18:59 +00003884 GetPixelIntensity(image,s0+GetPixelChannels(image)));
cristy3ed852e2009-09-05 21:47:34 +00003885 if ((normal.x == 0.0) && (normal.y == 0.0))
3886 shade=light.z;
3887 else
3888 {
3889 shade=0.0;
3890 distance=normal.x*light.x+normal.y*light.y+normal.z*light.z;
3891 if (distance > MagickEpsilon)
3892 {
3893 normal_distance=
3894 normal.x*normal.x+normal.y*normal.y+normal.z*normal.z;
3895 if (normal_distance > (MagickEpsilon*MagickEpsilon))
3896 shade=distance/sqrt((double) normal_distance);
3897 }
3898 }
3899 if (gray != MagickFalse)
3900 {
cristy4c08aed2011-07-01 19:47:50 +00003901 SetPixelRed(shade_image,ClampToQuantum(shade),q);
3902 SetPixelGreen(shade_image,ClampToQuantum(shade),q);
3903 SetPixelBlue(shade_image,ClampToQuantum(shade),q);
cristy3ed852e2009-09-05 21:47:34 +00003904 }
3905 else
3906 {
cristy4c08aed2011-07-01 19:47:50 +00003907 SetPixelRed(shade_image,ClampToQuantum(QuantumScale*shade*
3908 GetPixelRed(image,s1)),q);
3909 SetPixelGreen(shade_image,ClampToQuantum(QuantumScale*shade*
3910 GetPixelGreen(image,s1)),q);
3911 SetPixelBlue(shade_image,ClampToQuantum(QuantumScale*shade*
3912 GetPixelBlue(image,s1)),q);
cristy3ed852e2009-09-05 21:47:34 +00003913 }
cristy4c08aed2011-07-01 19:47:50 +00003914 SetPixelAlpha(shade_image,GetPixelAlpha(image,s1),q);
cristyed231572011-07-14 02:18:59 +00003915 s0+=GetPixelChannels(image);
3916 s1+=GetPixelChannels(image);
3917 s2+=GetPixelChannels(image);
3918 q+=GetPixelChannels(shade_image);
cristy3ed852e2009-09-05 21:47:34 +00003919 }
3920 if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
3921 status=MagickFalse;
3922 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3923 {
3924 MagickBooleanType
3925 proceed;
3926
cristyb5d5f722009-11-04 03:03:49 +00003927#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003928 #pragma omp critical (MagickCore_ShadeImage)
3929#endif
3930 proceed=SetImageProgress(image,ShadeImageTag,progress++,image->rows);
3931 if (proceed == MagickFalse)
3932 status=MagickFalse;
3933 }
3934 }
3935 shade_view=DestroyCacheView(shade_view);
3936 image_view=DestroyCacheView(image_view);
3937 if (status == MagickFalse)
3938 shade_image=DestroyImage(shade_image);
3939 return(shade_image);
3940}
3941
3942/*
3943%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3944% %
3945% %
3946% %
3947% S h a r p e n I m a g e %
3948% %
3949% %
3950% %
3951%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3952%
3953% SharpenImage() sharpens the image. We convolve the image with a Gaussian
3954% operator of the given radius and standard deviation (sigma). For
3955% reasonable results, radius should be larger than sigma. Use a radius of 0
3956% and SharpenImage() selects a suitable radius for you.
3957%
3958% Using a separable kernel would be faster, but the negative weights cancel
3959% out on the corners of the kernel producing often undesirable ringing in the
3960% filtered result; this can be avoided by using a 2D gaussian shaped image
3961% sharpening kernel instead.
3962%
3963% The format of the SharpenImage method is:
3964%
3965% Image *SharpenImage(const Image *image,const double radius,
3966% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003967%
3968% A description of each parameter follows:
3969%
3970% o image: the image.
3971%
cristy3ed852e2009-09-05 21:47:34 +00003972% o radius: the radius of the Gaussian, in pixels, not counting the center
3973% pixel.
3974%
3975% o sigma: the standard deviation of the Laplacian, in pixels.
3976%
3977% o exception: return any errors or warnings in this structure.
3978%
3979*/
cristy3ed852e2009-09-05 21:47:34 +00003980MagickExport Image *SharpenImage(const Image *image,const double radius,
3981 const double sigma,ExceptionInfo *exception)
3982{
cristy3ed852e2009-09-05 21:47:34 +00003983 double
cristy47e00502009-12-17 19:19:57 +00003984 *kernel,
3985 normalize;
cristy3ed852e2009-09-05 21:47:34 +00003986
3987 Image
3988 *sharp_image;
3989
cristybb503372010-05-27 20:51:26 +00003990 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003991 i;
3992
cristybb503372010-05-27 20:51:26 +00003993 size_t
cristy3ed852e2009-09-05 21:47:34 +00003994 width;
3995
cristy117ff172010-08-15 21:35:32 +00003996 ssize_t
3997 j,
3998 u,
3999 v;
4000
cristy3ed852e2009-09-05 21:47:34 +00004001 assert(image != (const Image *) NULL);
4002 assert(image->signature == MagickSignature);
4003 if (image->debug != MagickFalse)
4004 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4005 assert(exception != (ExceptionInfo *) NULL);
4006 assert(exception->signature == MagickSignature);
4007 width=GetOptimalKernelWidth2D(radius,sigma);
4008 kernel=(double *) AcquireQuantumMemory((size_t) width*width,sizeof(*kernel));
4009 if (kernel == (double *) NULL)
4010 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy3ed852e2009-09-05 21:47:34 +00004011 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00004012 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +00004013 i=0;
4014 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00004015 {
cristy47e00502009-12-17 19:19:57 +00004016 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00004017 {
cristy4205a3c2010-09-12 20:19:59 +00004018 kernel[i]=(double) (-exp(-((double) u*u+v*v)/(2.0*MagickSigma*
4019 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00004020 normalize+=kernel[i];
4021 i++;
4022 }
4023 }
4024 kernel[i/2]=(double) ((-2.0)*normalize);
cristyf4ad9df2011-07-08 16:49:03 +00004025 sharp_image=ConvolveImage(image,width,kernel,exception);
cristy3ed852e2009-09-05 21:47:34 +00004026 kernel=(double *) RelinquishMagickMemory(kernel);
4027 return(sharp_image);
4028}
4029
4030/*
4031%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4032% %
4033% %
4034% %
4035% S p r e a d I m a g e %
4036% %
4037% %
4038% %
4039%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4040%
4041% SpreadImage() is a special effects method that randomly displaces each
4042% pixel in a block defined by the radius parameter.
4043%
4044% The format of the SpreadImage method is:
4045%
4046% Image *SpreadImage(const Image *image,const double radius,
4047% ExceptionInfo *exception)
4048%
4049% A description of each parameter follows:
4050%
4051% o image: the image.
4052%
4053% o radius: Choose a random pixel in a neighborhood of this extent.
4054%
4055% o exception: return any errors or warnings in this structure.
4056%
4057*/
4058MagickExport Image *SpreadImage(const Image *image,const double radius,
4059 ExceptionInfo *exception)
4060{
4061#define SpreadImageTag "Spread/Image"
4062
cristyfa112112010-01-04 17:48:07 +00004063 CacheView
cristy9f7e7cb2011-03-26 00:49:57 +00004064 *image_view,
4065 *spread_view;
cristyfa112112010-01-04 17:48:07 +00004066
cristy3ed852e2009-09-05 21:47:34 +00004067 Image
4068 *spread_image;
4069
cristy3ed852e2009-09-05 21:47:34 +00004070 MagickBooleanType
4071 status;
4072
cristybb503372010-05-27 20:51:26 +00004073 MagickOffsetType
4074 progress;
4075
cristy4c08aed2011-07-01 19:47:50 +00004076 PixelInfo
cristyddd82202009-11-03 20:14:50 +00004077 bias;
cristy3ed852e2009-09-05 21:47:34 +00004078
4079 RandomInfo
cristyfa112112010-01-04 17:48:07 +00004080 **restrict random_info;
cristy3ed852e2009-09-05 21:47:34 +00004081
cristybb503372010-05-27 20:51:26 +00004082 size_t
cristy3ed852e2009-09-05 21:47:34 +00004083 width;
4084
cristybb503372010-05-27 20:51:26 +00004085 ssize_t
4086 y;
4087
cristy3ed852e2009-09-05 21:47:34 +00004088 /*
4089 Initialize spread image attributes.
4090 */
4091 assert(image != (Image *) NULL);
4092 assert(image->signature == MagickSignature);
4093 if (image->debug != MagickFalse)
4094 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4095 assert(exception != (ExceptionInfo *) NULL);
4096 assert(exception->signature == MagickSignature);
4097 spread_image=CloneImage(image,image->columns,image->rows,MagickTrue,
4098 exception);
4099 if (spread_image == (Image *) NULL)
4100 return((Image *) NULL);
4101 if (SetImageStorageClass(spread_image,DirectClass) == MagickFalse)
4102 {
4103 InheritException(exception,&spread_image->exception);
4104 spread_image=DestroyImage(spread_image);
4105 return((Image *) NULL);
4106 }
4107 /*
4108 Spread image.
4109 */
4110 status=MagickTrue;
4111 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00004112 GetPixelInfo(spread_image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00004113 width=GetOptimalKernelWidth1D(radius,0.5);
cristy3ed852e2009-09-05 21:47:34 +00004114 random_info=AcquireRandomInfoThreadSet();
cristy9f7e7cb2011-03-26 00:49:57 +00004115 image_view=AcquireCacheView(image);
4116 spread_view=AcquireCacheView(spread_image);
cristyb557a152011-02-22 12:14:30 +00004117#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00004118 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00004119#endif
cristybb503372010-05-27 20:51:26 +00004120 for (y=0; y < (ssize_t) spread_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00004121 {
cristy5c9e6f22010-09-17 17:31:01 +00004122 const int
4123 id = GetOpenMPThreadId();
cristy6ebe97c2010-07-03 01:17:28 +00004124
cristy4c08aed2011-07-01 19:47:50 +00004125 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00004126 pixel;
4127
cristy4c08aed2011-07-01 19:47:50 +00004128 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00004129 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004130
cristy117ff172010-08-15 21:35:32 +00004131 register ssize_t
4132 x;
4133
cristy3ed852e2009-09-05 21:47:34 +00004134 if (status == MagickFalse)
4135 continue;
cristy9f7e7cb2011-03-26 00:49:57 +00004136 q=QueueCacheViewAuthenticPixels(spread_view,0,y,spread_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +00004137 exception);
cristy4c08aed2011-07-01 19:47:50 +00004138 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00004139 {
4140 status=MagickFalse;
4141 continue;
4142 }
cristyddd82202009-11-03 20:14:50 +00004143 pixel=bias;
cristybb503372010-05-27 20:51:26 +00004144 for (x=0; x < (ssize_t) spread_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004145 {
cristy4c08aed2011-07-01 19:47:50 +00004146 (void) InterpolatePixelInfo(image,image_view,
cristy8a7c3e82011-03-26 02:10:53 +00004147 UndefinedInterpolatePixel,(double) x+width*(GetPseudoRandomValue(
4148 random_info[id])-0.5),(double) y+width*(GetPseudoRandomValue(
4149 random_info[id])-0.5),&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00004150 SetPixelPixelInfo(spread_image,&pixel,q);
cristyed231572011-07-14 02:18:59 +00004151 q+=GetPixelChannels(spread_image);
cristy3ed852e2009-09-05 21:47:34 +00004152 }
cristy9f7e7cb2011-03-26 00:49:57 +00004153 if (SyncCacheViewAuthenticPixels(spread_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00004154 status=MagickFalse;
4155 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4156 {
4157 MagickBooleanType
4158 proceed;
4159
cristyb557a152011-02-22 12:14:30 +00004160#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004161 #pragma omp critical (MagickCore_SpreadImage)
4162#endif
4163 proceed=SetImageProgress(image,SpreadImageTag,progress++,image->rows);
4164 if (proceed == MagickFalse)
4165 status=MagickFalse;
4166 }
4167 }
cristy9f7e7cb2011-03-26 00:49:57 +00004168 spread_view=DestroyCacheView(spread_view);
cristy3ed852e2009-09-05 21:47:34 +00004169 image_view=DestroyCacheView(image_view);
4170 random_info=DestroyRandomInfoThreadSet(random_info);
cristy3ed852e2009-09-05 21:47:34 +00004171 return(spread_image);
4172}
4173
4174/*
4175%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4176% %
4177% %
4178% %
cristy0834d642011-03-18 18:26:08 +00004179% S t a t i s t i c I m a g e %
4180% %
4181% %
4182% %
4183%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4184%
4185% StatisticImage() makes each pixel the min / max / median / mode / etc. of
cristy8d752042011-03-19 01:00:36 +00004186% the neighborhood of the specified width and height.
cristy0834d642011-03-18 18:26:08 +00004187%
4188% The format of the StatisticImage method is:
4189%
4190% Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00004191% const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00004192%
4193% A description of each parameter follows:
4194%
4195% o image: the image.
4196%
cristy0834d642011-03-18 18:26:08 +00004197% o type: the statistic type (median, mode, etc.).
4198%
cristy95c38342011-03-18 22:39:51 +00004199% o width: the width of the pixel neighborhood.
4200%
4201% o height: the height of the pixel neighborhood.
cristy0834d642011-03-18 18:26:08 +00004202%
4203% o exception: return any errors or warnings in this structure.
4204%
4205*/
4206
cristy733678d2011-03-18 21:29:28 +00004207#define ListChannels 5
4208
4209typedef struct _ListNode
4210{
4211 size_t
4212 next[9],
4213 count,
4214 signature;
4215} ListNode;
4216
4217typedef struct _SkipList
4218{
4219 ssize_t
4220 level;
4221
4222 ListNode
4223 *nodes;
4224} SkipList;
4225
4226typedef struct _PixelList
4227{
4228 size_t
cristy6fc86bb2011-03-18 23:45:16 +00004229 length,
cristy733678d2011-03-18 21:29:28 +00004230 seed,
4231 signature;
4232
4233 SkipList
4234 lists[ListChannels];
4235} PixelList;
4236
4237static PixelList *DestroyPixelList(PixelList *pixel_list)
4238{
4239 register ssize_t
4240 i;
4241
4242 if (pixel_list == (PixelList *) NULL)
4243 return((PixelList *) NULL);
4244 for (i=0; i < ListChannels; i++)
4245 if (pixel_list->lists[i].nodes != (ListNode *) NULL)
4246 pixel_list->lists[i].nodes=(ListNode *) RelinquishMagickMemory(
4247 pixel_list->lists[i].nodes);
4248 pixel_list=(PixelList *) RelinquishMagickMemory(pixel_list);
4249 return(pixel_list);
4250}
4251
4252static PixelList **DestroyPixelListThreadSet(PixelList **pixel_list)
4253{
4254 register ssize_t
4255 i;
4256
4257 assert(pixel_list != (PixelList **) NULL);
4258 for (i=0; i < (ssize_t) GetOpenMPMaximumThreads(); i++)
4259 if (pixel_list[i] != (PixelList *) NULL)
4260 pixel_list[i]=DestroyPixelList(pixel_list[i]);
4261 pixel_list=(PixelList **) RelinquishMagickMemory(pixel_list);
4262 return(pixel_list);
4263}
4264
cristy6fc86bb2011-03-18 23:45:16 +00004265static PixelList *AcquirePixelList(const size_t width,const size_t height)
cristy733678d2011-03-18 21:29:28 +00004266{
4267 PixelList
4268 *pixel_list;
4269
4270 register ssize_t
4271 i;
4272
4273 pixel_list=(PixelList *) AcquireMagickMemory(sizeof(*pixel_list));
4274 if (pixel_list == (PixelList *) NULL)
4275 return(pixel_list);
4276 (void) ResetMagickMemory((void *) pixel_list,0,sizeof(*pixel_list));
cristy6fc86bb2011-03-18 23:45:16 +00004277 pixel_list->length=width*height;
cristy733678d2011-03-18 21:29:28 +00004278 for (i=0; i < ListChannels; i++)
4279 {
4280 pixel_list->lists[i].nodes=(ListNode *) AcquireQuantumMemory(65537UL,
4281 sizeof(*pixel_list->lists[i].nodes));
4282 if (pixel_list->lists[i].nodes == (ListNode *) NULL)
4283 return(DestroyPixelList(pixel_list));
4284 (void) ResetMagickMemory(pixel_list->lists[i].nodes,0,65537UL*
4285 sizeof(*pixel_list->lists[i].nodes));
4286 }
4287 pixel_list->signature=MagickSignature;
4288 return(pixel_list);
4289}
4290
cristy6fc86bb2011-03-18 23:45:16 +00004291static PixelList **AcquirePixelListThreadSet(const size_t width,
4292 const size_t height)
cristy733678d2011-03-18 21:29:28 +00004293{
4294 PixelList
4295 **pixel_list;
4296
4297 register ssize_t
4298 i;
4299
4300 size_t
4301 number_threads;
4302
4303 number_threads=GetOpenMPMaximumThreads();
4304 pixel_list=(PixelList **) AcquireQuantumMemory(number_threads,
4305 sizeof(*pixel_list));
4306 if (pixel_list == (PixelList **) NULL)
4307 return((PixelList **) NULL);
4308 (void) ResetMagickMemory(pixel_list,0,number_threads*sizeof(*pixel_list));
4309 for (i=0; i < (ssize_t) number_threads; i++)
4310 {
cristy6fc86bb2011-03-18 23:45:16 +00004311 pixel_list[i]=AcquirePixelList(width,height);
cristy733678d2011-03-18 21:29:28 +00004312 if (pixel_list[i] == (PixelList *) NULL)
4313 return(DestroyPixelListThreadSet(pixel_list));
4314 }
4315 return(pixel_list);
4316}
4317
4318static void AddNodePixelList(PixelList *pixel_list,const ssize_t channel,
4319 const size_t color)
4320{
4321 register SkipList
4322 *list;
4323
4324 register ssize_t
4325 level;
4326
4327 size_t
4328 search,
4329 update[9];
4330
4331 /*
4332 Initialize the node.
4333 */
4334 list=pixel_list->lists+channel;
4335 list->nodes[color].signature=pixel_list->signature;
4336 list->nodes[color].count=1;
4337 /*
4338 Determine where it belongs in the list.
4339 */
4340 search=65536UL;
4341 for (level=list->level; level >= 0; level--)
4342 {
4343 while (list->nodes[search].next[level] < color)
4344 search=list->nodes[search].next[level];
4345 update[level]=search;
4346 }
4347 /*
4348 Generate a pseudo-random level for this node.
4349 */
4350 for (level=0; ; level++)
4351 {
4352 pixel_list->seed=(pixel_list->seed*42893621L)+1L;
4353 if ((pixel_list->seed & 0x300) != 0x300)
4354 break;
4355 }
4356 if (level > 8)
4357 level=8;
4358 if (level > (list->level+2))
4359 level=list->level+2;
4360 /*
4361 If we're raising the list's level, link back to the root node.
4362 */
4363 while (level > list->level)
4364 {
4365 list->level++;
4366 update[list->level]=65536UL;
4367 }
4368 /*
4369 Link the node into the skip-list.
4370 */
4371 do
4372 {
4373 list->nodes[color].next[level]=list->nodes[update[level]].next[level];
4374 list->nodes[update[level]].next[level]=color;
cristy3cba8ca2011-03-19 01:29:12 +00004375 } while (level-- > 0);
cristy733678d2011-03-18 21:29:28 +00004376}
4377
cristy4c08aed2011-07-01 19:47:50 +00004378static PixelInfo GetMaximumPixelList(PixelList *pixel_list)
cristy6fc86bb2011-03-18 23:45:16 +00004379{
cristy4c08aed2011-07-01 19:47:50 +00004380 PixelInfo
cristy6fc86bb2011-03-18 23:45:16 +00004381 pixel;
4382
4383 register SkipList
4384 *list;
4385
4386 register ssize_t
4387 channel;
4388
4389 size_t
cristyd76c51e2011-03-26 00:21:26 +00004390 color,
4391 maximum;
cristy49f37242011-03-22 18:18:23 +00004392
4393 ssize_t
cristy6fc86bb2011-03-18 23:45:16 +00004394 count;
4395
4396 unsigned short
cristyd76c51e2011-03-26 00:21:26 +00004397 channels[ListChannels];
cristy6fc86bb2011-03-18 23:45:16 +00004398
4399 /*
4400 Find the maximum value for each of the color.
4401 */
4402 for (channel=0; channel < 5; channel++)
4403 {
4404 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004405 color=65536L;
cristy6fc86bb2011-03-18 23:45:16 +00004406 count=0;
cristy49f37242011-03-22 18:18:23 +00004407 maximum=list->nodes[color].next[0];
cristy6fc86bb2011-03-18 23:45:16 +00004408 do
4409 {
4410 color=list->nodes[color].next[0];
cristy49f37242011-03-22 18:18:23 +00004411 if (color > maximum)
4412 maximum=color;
cristy6fc86bb2011-03-18 23:45:16 +00004413 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004414 } while (count < (ssize_t) pixel_list->length);
cristy49f37242011-03-22 18:18:23 +00004415 channels[channel]=(unsigned short) maximum;
4416 }
cristy4c08aed2011-07-01 19:47:50 +00004417 GetPixelInfo((const Image *) NULL,&pixel);
cristy49f37242011-03-22 18:18:23 +00004418 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4419 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4420 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004421 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4422 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy49f37242011-03-22 18:18:23 +00004423 return(pixel);
4424}
4425
cristy4c08aed2011-07-01 19:47:50 +00004426static PixelInfo GetMeanPixelList(PixelList *pixel_list)
cristy49f37242011-03-22 18:18:23 +00004427{
cristy4c08aed2011-07-01 19:47:50 +00004428 PixelInfo
cristy49f37242011-03-22 18:18:23 +00004429 pixel;
4430
cristy80a99a32011-03-30 01:30:23 +00004431 MagickRealType
4432 sum;
4433
cristy49f37242011-03-22 18:18:23 +00004434 register SkipList
4435 *list;
4436
4437 register ssize_t
4438 channel;
4439
4440 size_t
cristy80a99a32011-03-30 01:30:23 +00004441 color;
cristy49f37242011-03-22 18:18:23 +00004442
4443 ssize_t
4444 count;
4445
4446 unsigned short
4447 channels[ListChannels];
4448
4449 /*
4450 Find the mean value for each of the color.
4451 */
4452 for (channel=0; channel < 5; channel++)
4453 {
4454 list=pixel_list->lists+channel;
4455 color=65536L;
4456 count=0;
cristy80a99a32011-03-30 01:30:23 +00004457 sum=0.0;
cristy49f37242011-03-22 18:18:23 +00004458 do
4459 {
4460 color=list->nodes[color].next[0];
cristy80a99a32011-03-30 01:30:23 +00004461 sum+=(MagickRealType) list->nodes[color].count*color;
cristy49f37242011-03-22 18:18:23 +00004462 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004463 } while (count < (ssize_t) pixel_list->length);
cristy80a99a32011-03-30 01:30:23 +00004464 sum/=pixel_list->length;
4465 channels[channel]=(unsigned short) sum;
cristy6fc86bb2011-03-18 23:45:16 +00004466 }
cristy4c08aed2011-07-01 19:47:50 +00004467 GetPixelInfo((const Image *) NULL,&pixel);
cristy6fc86bb2011-03-18 23:45:16 +00004468 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4469 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4470 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004471 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4472 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy6fc86bb2011-03-18 23:45:16 +00004473 return(pixel);
4474}
4475
cristy4c08aed2011-07-01 19:47:50 +00004476static PixelInfo GetMedianPixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004477{
cristy4c08aed2011-07-01 19:47:50 +00004478 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004479 pixel;
4480
4481 register SkipList
4482 *list;
4483
4484 register ssize_t
4485 channel;
4486
4487 size_t
cristy49f37242011-03-22 18:18:23 +00004488 color;
4489
4490 ssize_t
cristy733678d2011-03-18 21:29:28 +00004491 count;
4492
4493 unsigned short
4494 channels[ListChannels];
4495
4496 /*
4497 Find the median value for each of the color.
4498 */
cristy733678d2011-03-18 21:29:28 +00004499 for (channel=0; channel < 5; channel++)
4500 {
4501 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004502 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004503 count=0;
4504 do
4505 {
4506 color=list->nodes[color].next[0];
4507 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004508 } while (count <= (ssize_t) (pixel_list->length >> 1));
cristy6fc86bb2011-03-18 23:45:16 +00004509 channels[channel]=(unsigned short) color;
4510 }
cristy4c08aed2011-07-01 19:47:50 +00004511 GetPixelInfo((const Image *) NULL,&pixel);
cristy6fc86bb2011-03-18 23:45:16 +00004512 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4513 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4514 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004515 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4516 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy6fc86bb2011-03-18 23:45:16 +00004517 return(pixel);
4518}
4519
cristy4c08aed2011-07-01 19:47:50 +00004520static PixelInfo GetMinimumPixelList(PixelList *pixel_list)
cristy6fc86bb2011-03-18 23:45:16 +00004521{
cristy4c08aed2011-07-01 19:47:50 +00004522 PixelInfo
cristy6fc86bb2011-03-18 23:45:16 +00004523 pixel;
4524
4525 register SkipList
4526 *list;
4527
4528 register ssize_t
4529 channel;
4530
4531 size_t
cristyd76c51e2011-03-26 00:21:26 +00004532 color,
4533 minimum;
cristy6fc86bb2011-03-18 23:45:16 +00004534
cristy49f37242011-03-22 18:18:23 +00004535 ssize_t
4536 count;
4537
cristy6fc86bb2011-03-18 23:45:16 +00004538 unsigned short
cristyd76c51e2011-03-26 00:21:26 +00004539 channels[ListChannels];
cristy6fc86bb2011-03-18 23:45:16 +00004540
4541 /*
4542 Find the minimum value for each of the color.
4543 */
4544 for (channel=0; channel < 5; channel++)
4545 {
4546 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004547 count=0;
cristy6fc86bb2011-03-18 23:45:16 +00004548 color=65536UL;
cristy49f37242011-03-22 18:18:23 +00004549 minimum=list->nodes[color].next[0];
4550 do
4551 {
4552 color=list->nodes[color].next[0];
4553 if (color < minimum)
4554 minimum=color;
4555 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004556 } while (count < (ssize_t) pixel_list->length);
cristy49f37242011-03-22 18:18:23 +00004557 channels[channel]=(unsigned short) minimum;
cristy733678d2011-03-18 21:29:28 +00004558 }
cristy4c08aed2011-07-01 19:47:50 +00004559 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004560 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4561 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4562 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004563 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4564 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy733678d2011-03-18 21:29:28 +00004565 return(pixel);
4566}
4567
cristy4c08aed2011-07-01 19:47:50 +00004568static PixelInfo GetModePixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004569{
cristy4c08aed2011-07-01 19:47:50 +00004570 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004571 pixel;
4572
4573 register SkipList
4574 *list;
4575
4576 register ssize_t
4577 channel;
4578
4579 size_t
4580 color,
cristy733678d2011-03-18 21:29:28 +00004581 max_count,
cristy6fc86bb2011-03-18 23:45:16 +00004582 mode;
cristy733678d2011-03-18 21:29:28 +00004583
cristy49f37242011-03-22 18:18:23 +00004584 ssize_t
4585 count;
4586
cristy733678d2011-03-18 21:29:28 +00004587 unsigned short
4588 channels[5];
4589
4590 /*
glennrp30d2dc62011-06-25 03:17:16 +00004591 Make each pixel the 'predominant color' of the specified neighborhood.
cristy733678d2011-03-18 21:29:28 +00004592 */
cristy733678d2011-03-18 21:29:28 +00004593 for (channel=0; channel < 5; channel++)
4594 {
4595 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004596 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004597 mode=color;
4598 max_count=list->nodes[mode].count;
4599 count=0;
4600 do
4601 {
4602 color=list->nodes[color].next[0];
4603 if (list->nodes[color].count > max_count)
4604 {
4605 mode=color;
4606 max_count=list->nodes[mode].count;
4607 }
4608 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004609 } while (count < (ssize_t) pixel_list->length);
cristy733678d2011-03-18 21:29:28 +00004610 channels[channel]=(unsigned short) mode;
4611 }
cristy4c08aed2011-07-01 19:47:50 +00004612 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004613 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4614 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4615 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004616 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4617 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy733678d2011-03-18 21:29:28 +00004618 return(pixel);
4619}
4620
cristy4c08aed2011-07-01 19:47:50 +00004621static PixelInfo GetNonpeakPixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004622{
cristy4c08aed2011-07-01 19:47:50 +00004623 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004624 pixel;
4625
4626 register SkipList
4627 *list;
4628
4629 register ssize_t
4630 channel;
4631
4632 size_t
cristy733678d2011-03-18 21:29:28 +00004633 color,
cristy733678d2011-03-18 21:29:28 +00004634 next,
4635 previous;
4636
cristy49f37242011-03-22 18:18:23 +00004637 ssize_t
4638 count;
4639
cristy733678d2011-03-18 21:29:28 +00004640 unsigned short
4641 channels[5];
4642
4643 /*
cristy49f37242011-03-22 18:18:23 +00004644 Finds the non peak value for each of the colors.
cristy733678d2011-03-18 21:29:28 +00004645 */
cristy733678d2011-03-18 21:29:28 +00004646 for (channel=0; channel < 5; channel++)
4647 {
4648 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004649 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004650 next=list->nodes[color].next[0];
4651 count=0;
4652 do
4653 {
4654 previous=color;
4655 color=next;
4656 next=list->nodes[color].next[0];
4657 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004658 } while (count <= (ssize_t) (pixel_list->length >> 1));
cristy733678d2011-03-18 21:29:28 +00004659 if ((previous == 65536UL) && (next != 65536UL))
4660 color=next;
4661 else
4662 if ((previous != 65536UL) && (next == 65536UL))
4663 color=previous;
4664 channels[channel]=(unsigned short) color;
4665 }
cristy4c08aed2011-07-01 19:47:50 +00004666 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004667 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4668 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4669 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004670 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4671 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy733678d2011-03-18 21:29:28 +00004672 return(pixel);
4673}
4674
cristy4c08aed2011-07-01 19:47:50 +00004675static PixelInfo GetStandardDeviationPixelList(PixelList *pixel_list)
cristy9a68cbb2011-03-29 00:51:23 +00004676{
cristy4c08aed2011-07-01 19:47:50 +00004677 PixelInfo
cristy9a68cbb2011-03-29 00:51:23 +00004678 pixel;
4679
cristy80a99a32011-03-30 01:30:23 +00004680 MagickRealType
4681 sum,
4682 sum_squared;
4683
cristy9a68cbb2011-03-29 00:51:23 +00004684 register SkipList
4685 *list;
4686
4687 register ssize_t
4688 channel;
4689
4690 size_t
cristy80a99a32011-03-30 01:30:23 +00004691 color;
cristy9a68cbb2011-03-29 00:51:23 +00004692
4693 ssize_t
4694 count;
4695
4696 unsigned short
4697 channels[ListChannels];
4698
4699 /*
cristy80a99a32011-03-30 01:30:23 +00004700 Find the standard-deviation value for each of the color.
cristy9a68cbb2011-03-29 00:51:23 +00004701 */
4702 for (channel=0; channel < 5; channel++)
4703 {
4704 list=pixel_list->lists+channel;
4705 color=65536L;
4706 count=0;
cristy80a99a32011-03-30 01:30:23 +00004707 sum=0.0;
4708 sum_squared=0.0;
cristy9a68cbb2011-03-29 00:51:23 +00004709 do
4710 {
cristy80a99a32011-03-30 01:30:23 +00004711 register ssize_t
4712 i;
4713
cristy9a68cbb2011-03-29 00:51:23 +00004714 color=list->nodes[color].next[0];
cristy80a99a32011-03-30 01:30:23 +00004715 sum+=(MagickRealType) list->nodes[color].count*color;
4716 for (i=0; i < (ssize_t) list->nodes[color].count; i++)
4717 sum_squared+=((MagickRealType) color)*((MagickRealType) color);
cristy9a68cbb2011-03-29 00:51:23 +00004718 count+=list->nodes[color].count;
4719 } while (count < (ssize_t) pixel_list->length);
cristy80a99a32011-03-30 01:30:23 +00004720 sum/=pixel_list->length;
4721 sum_squared/=pixel_list->length;
4722 channels[channel]=(unsigned short) sqrt(sum_squared-(sum*sum));
cristy9a68cbb2011-03-29 00:51:23 +00004723 }
cristy4c08aed2011-07-01 19:47:50 +00004724 GetPixelInfo((const Image *) NULL,&pixel);
cristy9a68cbb2011-03-29 00:51:23 +00004725 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4726 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4727 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004728 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4729 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy9a68cbb2011-03-29 00:51:23 +00004730 return(pixel);
4731}
4732
cristy4c08aed2011-07-01 19:47:50 +00004733static inline void InsertPixelList(const Image *image,const Quantum *pixel,
4734 PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004735{
4736 size_t
4737 signature;
4738
4739 unsigned short
4740 index;
4741
cristy4c08aed2011-07-01 19:47:50 +00004742 index=ScaleQuantumToShort(GetPixelRed(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004743 signature=pixel_list->lists[0].nodes[index].signature;
4744 if (signature == pixel_list->signature)
4745 pixel_list->lists[0].nodes[index].count++;
4746 else
4747 AddNodePixelList(pixel_list,0,index);
cristy4c08aed2011-07-01 19:47:50 +00004748 index=ScaleQuantumToShort(GetPixelGreen(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004749 signature=pixel_list->lists[1].nodes[index].signature;
4750 if (signature == pixel_list->signature)
4751 pixel_list->lists[1].nodes[index].count++;
4752 else
4753 AddNodePixelList(pixel_list,1,index);
cristy4c08aed2011-07-01 19:47:50 +00004754 index=ScaleQuantumToShort(GetPixelBlue(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004755 signature=pixel_list->lists[2].nodes[index].signature;
4756 if (signature == pixel_list->signature)
4757 pixel_list->lists[2].nodes[index].count++;
4758 else
4759 AddNodePixelList(pixel_list,2,index);
cristy4c08aed2011-07-01 19:47:50 +00004760 index=ScaleQuantumToShort(GetPixelAlpha(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004761 signature=pixel_list->lists[3].nodes[index].signature;
4762 if (signature == pixel_list->signature)
4763 pixel_list->lists[3].nodes[index].count++;
4764 else
4765 AddNodePixelList(pixel_list,3,index);
4766 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00004767 index=ScaleQuantumToShort(GetPixelBlack(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004768 signature=pixel_list->lists[4].nodes[index].signature;
4769 if (signature == pixel_list->signature)
4770 pixel_list->lists[4].nodes[index].count++;
4771 else
4772 AddNodePixelList(pixel_list,4,index);
4773}
4774
cristy80c99742011-04-04 14:46:39 +00004775static inline MagickRealType MagickAbsoluteValue(const MagickRealType x)
4776{
4777 if (x < 0)
4778 return(-x);
4779 return(x);
4780}
4781
cristy733678d2011-03-18 21:29:28 +00004782static void ResetPixelList(PixelList *pixel_list)
4783{
4784 int
4785 level;
4786
4787 register ListNode
4788 *root;
4789
4790 register SkipList
4791 *list;
4792
4793 register ssize_t
4794 channel;
4795
4796 /*
4797 Reset the skip-list.
4798 */
4799 for (channel=0; channel < 5; channel++)
4800 {
4801 list=pixel_list->lists+channel;
4802 root=list->nodes+65536UL;
4803 list->level=0;
4804 for (level=0; level < 9; level++)
4805 root->next[level]=65536UL;
4806 }
4807 pixel_list->seed=pixel_list->signature++;
4808}
4809
cristy0834d642011-03-18 18:26:08 +00004810MagickExport Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00004811 const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00004812{
cristy3cba8ca2011-03-19 01:29:12 +00004813#define StatisticWidth \
cristyd76c51e2011-03-26 00:21:26 +00004814 (width == 0 ? GetOptimalKernelWidth2D((double) width,0.5) : width)
cristy3cba8ca2011-03-19 01:29:12 +00004815#define StatisticHeight \
cristyd76c51e2011-03-26 00:21:26 +00004816 (height == 0 ? GetOptimalKernelWidth2D((double) height,0.5) : height)
cristy0834d642011-03-18 18:26:08 +00004817#define StatisticImageTag "Statistic/Image"
4818
4819 CacheView
4820 *image_view,
4821 *statistic_view;
4822
4823 Image
4824 *statistic_image;
4825
4826 MagickBooleanType
4827 status;
4828
4829 MagickOffsetType
4830 progress;
4831
4832 PixelList
4833 **restrict pixel_list;
4834
cristy0834d642011-03-18 18:26:08 +00004835 ssize_t
4836 y;
4837
4838 /*
4839 Initialize statistics image attributes.
4840 */
4841 assert(image != (Image *) NULL);
4842 assert(image->signature == MagickSignature);
4843 if (image->debug != MagickFalse)
4844 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4845 assert(exception != (ExceptionInfo *) NULL);
4846 assert(exception->signature == MagickSignature);
cristy0834d642011-03-18 18:26:08 +00004847 statistic_image=CloneImage(image,image->columns,image->rows,MagickTrue,
4848 exception);
4849 if (statistic_image == (Image *) NULL)
4850 return((Image *) NULL);
4851 if (SetImageStorageClass(statistic_image,DirectClass) == MagickFalse)
4852 {
4853 InheritException(exception,&statistic_image->exception);
4854 statistic_image=DestroyImage(statistic_image);
4855 return((Image *) NULL);
4856 }
cristy6fc86bb2011-03-18 23:45:16 +00004857 pixel_list=AcquirePixelListThreadSet(StatisticWidth,StatisticHeight);
cristy0834d642011-03-18 18:26:08 +00004858 if (pixel_list == (PixelList **) NULL)
4859 {
4860 statistic_image=DestroyImage(statistic_image);
4861 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
4862 }
4863 /*
cristy8d752042011-03-19 01:00:36 +00004864 Make each pixel the min / max / median / mode / etc. of the neighborhood.
cristy0834d642011-03-18 18:26:08 +00004865 */
4866 status=MagickTrue;
4867 progress=0;
4868 image_view=AcquireCacheView(image);
4869 statistic_view=AcquireCacheView(statistic_image);
4870#if defined(MAGICKCORE_OPENMP_SUPPORT)
4871 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
4872#endif
4873 for (y=0; y < (ssize_t) statistic_image->rows; y++)
4874 {
4875 const int
4876 id = GetOpenMPThreadId();
4877
cristy4c08aed2011-07-01 19:47:50 +00004878 register const Quantum
cristy0834d642011-03-18 18:26:08 +00004879 *restrict p;
4880
cristy4c08aed2011-07-01 19:47:50 +00004881 register Quantum
cristy0834d642011-03-18 18:26:08 +00004882 *restrict q;
4883
4884 register ssize_t
4885 x;
4886
4887 if (status == MagickFalse)
4888 continue;
cristy6fc86bb2011-03-18 23:45:16 +00004889 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) StatisticWidth/2L),y-
4890 (ssize_t) (StatisticHeight/2L),image->columns+StatisticWidth,
4891 StatisticHeight,exception);
cristy3cba8ca2011-03-19 01:29:12 +00004892 q=QueueCacheViewAuthenticPixels(statistic_view,0,y,statistic_image->columns, 1,exception);
cristy4c08aed2011-07-01 19:47:50 +00004893 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy0834d642011-03-18 18:26:08 +00004894 {
4895 status=MagickFalse;
4896 continue;
4897 }
cristy0834d642011-03-18 18:26:08 +00004898 for (x=0; x < (ssize_t) statistic_image->columns; x++)
4899 {
cristy4c08aed2011-07-01 19:47:50 +00004900 PixelInfo
cristy0834d642011-03-18 18:26:08 +00004901 pixel;
4902
cristy4c08aed2011-07-01 19:47:50 +00004903 register const Quantum
cristy6e3026a2011-03-19 00:54:38 +00004904 *restrict r;
4905
cristy0834d642011-03-18 18:26:08 +00004906 register ssize_t
4907 u,
4908 v;
4909
4910 r=p;
cristy0834d642011-03-18 18:26:08 +00004911 ResetPixelList(pixel_list[id]);
cristy6e4c3292011-03-19 00:53:55 +00004912 for (v=0; v < (ssize_t) StatisticHeight; v++)
cristy0834d642011-03-18 18:26:08 +00004913 {
cristy6e4c3292011-03-19 00:53:55 +00004914 for (u=0; u < (ssize_t) StatisticWidth; u++)
cristyed231572011-07-14 02:18:59 +00004915 InsertPixelList(image,r+u*GetPixelChannels(image),pixel_list[id]);
4916 r+=(image->columns+StatisticWidth)*GetPixelChannels(image);
cristy0834d642011-03-18 18:26:08 +00004917 }
cristy4c08aed2011-07-01 19:47:50 +00004918 GetPixelInfo(image,&pixel);
cristy490408a2011-07-07 14:42:05 +00004919 SetPixelInfo(image,p+(StatisticWidth*StatisticHeight/2)*
cristyed231572011-07-14 02:18:59 +00004920 GetPixelChannels(image),&pixel);
cristy0834d642011-03-18 18:26:08 +00004921 switch (type)
4922 {
cristy80c99742011-04-04 14:46:39 +00004923 case GradientStatistic:
4924 {
cristy4c08aed2011-07-01 19:47:50 +00004925 PixelInfo
cristy80c99742011-04-04 14:46:39 +00004926 maximum,
4927 minimum;
4928
4929 minimum=GetMinimumPixelList(pixel_list[id]);
4930 maximum=GetMaximumPixelList(pixel_list[id]);
4931 pixel.red=MagickAbsoluteValue(maximum.red-minimum.red);
4932 pixel.green=MagickAbsoluteValue(maximum.green-minimum.green);
4933 pixel.blue=MagickAbsoluteValue(maximum.blue-minimum.blue);
cristy4c08aed2011-07-01 19:47:50 +00004934 pixel.alpha=MagickAbsoluteValue(maximum.alpha-minimum.alpha);
cristy80c99742011-04-04 14:46:39 +00004935 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00004936 pixel.black=MagickAbsoluteValue(maximum.black-minimum.black);
cristy80c99742011-04-04 14:46:39 +00004937 break;
4938 }
cristy6fc86bb2011-03-18 23:45:16 +00004939 case MaximumStatistic:
4940 {
4941 pixel=GetMaximumPixelList(pixel_list[id]);
4942 break;
4943 }
cristy49f37242011-03-22 18:18:23 +00004944 case MeanStatistic:
4945 {
4946 pixel=GetMeanPixelList(pixel_list[id]);
4947 break;
4948 }
cristyf2ad14a2011-03-18 18:57:25 +00004949 case MedianStatistic:
cristy6fc86bb2011-03-18 23:45:16 +00004950 default:
cristyf2ad14a2011-03-18 18:57:25 +00004951 {
4952 pixel=GetMedianPixelList(pixel_list[id]);
4953 break;
4954 }
cristy6fc86bb2011-03-18 23:45:16 +00004955 case MinimumStatistic:
4956 {
4957 pixel=GetMinimumPixelList(pixel_list[id]);
4958 break;
4959 }
cristyf2ad14a2011-03-18 18:57:25 +00004960 case ModeStatistic:
4961 {
4962 pixel=GetModePixelList(pixel_list[id]);
4963 break;
4964 }
4965 case NonpeakStatistic:
4966 {
4967 pixel=GetNonpeakPixelList(pixel_list[id]);
4968 break;
4969 }
cristy9a68cbb2011-03-29 00:51:23 +00004970 case StandardDeviationStatistic:
4971 {
4972 pixel=GetStandardDeviationPixelList(pixel_list[id]);
4973 break;
4974 }
cristy0834d642011-03-18 18:26:08 +00004975 }
cristyed231572011-07-14 02:18:59 +00004976 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00004977 SetPixelRed(statistic_image,ClampToQuantum(pixel.red),q);
cristyed231572011-07-14 02:18:59 +00004978 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00004979 SetPixelGreen(statistic_image,ClampToQuantum(pixel.green),q);
cristyed231572011-07-14 02:18:59 +00004980 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00004981 SetPixelBlue(statistic_image,ClampToQuantum(pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00004982 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy0834d642011-03-18 18:26:08 +00004983 (image->colorspace == CMYKColorspace))
cristy490408a2011-07-07 14:42:05 +00004984 SetPixelBlack(statistic_image,ClampToQuantum(pixel.black),q);
cristyed231572011-07-14 02:18:59 +00004985 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00004986 (image->matte != MagickFalse))
cristy490408a2011-07-07 14:42:05 +00004987 SetPixelAlpha(statistic_image,ClampToQuantum(pixel.alpha),q);
cristyed231572011-07-14 02:18:59 +00004988 p+=GetPixelChannels(image);
4989 q+=GetPixelChannels(statistic_image);
cristy0834d642011-03-18 18:26:08 +00004990 }
4991 if (SyncCacheViewAuthenticPixels(statistic_view,exception) == MagickFalse)
4992 status=MagickFalse;
4993 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4994 {
4995 MagickBooleanType
4996 proceed;
4997
4998#if defined(MAGICKCORE_OPENMP_SUPPORT)
4999 #pragma omp critical (MagickCore_StatisticImage)
5000#endif
5001 proceed=SetImageProgress(image,StatisticImageTag,progress++,
5002 image->rows);
5003 if (proceed == MagickFalse)
5004 status=MagickFalse;
5005 }
5006 }
5007 statistic_view=DestroyCacheView(statistic_view);
5008 image_view=DestroyCacheView(image_view);
5009 pixel_list=DestroyPixelListThreadSet(pixel_list);
5010 return(statistic_image);
5011}
5012
5013/*
5014%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5015% %
5016% %
5017% %
cristy3ed852e2009-09-05 21:47:34 +00005018% U n s h a r p M a s k I m a g e %
5019% %
5020% %
5021% %
5022%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5023%
5024% UnsharpMaskImage() sharpens one or more image channels. We convolve the
5025% image with a Gaussian operator of the given radius and standard deviation
5026% (sigma). For reasonable results, radius should be larger than sigma. Use a
5027% radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
5028%
5029% The format of the UnsharpMaskImage method is:
5030%
5031% Image *UnsharpMaskImage(const Image *image,const double radius,
5032% const double sigma,const double amount,const double threshold,
5033% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00005034%
5035% A description of each parameter follows:
5036%
5037% o image: the image.
5038%
cristy3ed852e2009-09-05 21:47:34 +00005039% o radius: the radius of the Gaussian, in pixels, not counting the center
5040% pixel.
5041%
5042% o sigma: the standard deviation of the Gaussian, in pixels.
5043%
5044% o amount: the percentage of the difference between the original and the
5045% blur image that is added back into the original.
5046%
5047% o threshold: the threshold in pixels needed to apply the diffence amount.
5048%
5049% o exception: return any errors or warnings in this structure.
5050%
5051*/
cristyf4ad9df2011-07-08 16:49:03 +00005052MagickExport Image *UnsharpMaskImage(const Image *image,
5053 const double radius,const double sigma,const double amount,
5054 const double threshold,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00005055{
5056#define SharpenImageTag "Sharpen/Image"
5057
cristyc4c8d132010-01-07 01:58:38 +00005058 CacheView
5059 *image_view,
5060 *unsharp_view;
5061
cristy3ed852e2009-09-05 21:47:34 +00005062 Image
5063 *unsharp_image;
5064
cristy3ed852e2009-09-05 21:47:34 +00005065 MagickBooleanType
5066 status;
5067
cristybb503372010-05-27 20:51:26 +00005068 MagickOffsetType
5069 progress;
5070
cristy4c08aed2011-07-01 19:47:50 +00005071 PixelInfo
cristyddd82202009-11-03 20:14:50 +00005072 bias;
cristy3ed852e2009-09-05 21:47:34 +00005073
5074 MagickRealType
5075 quantum_threshold;
5076
cristybb503372010-05-27 20:51:26 +00005077 ssize_t
5078 y;
5079
cristy3ed852e2009-09-05 21:47:34 +00005080 assert(image != (const Image *) NULL);
5081 assert(image->signature == MagickSignature);
5082 if (image->debug != MagickFalse)
5083 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5084 assert(exception != (ExceptionInfo *) NULL);
cristyf4ad9df2011-07-08 16:49:03 +00005085 unsharp_image=BlurImage(image,radius,sigma,exception);
cristy3ed852e2009-09-05 21:47:34 +00005086 if (unsharp_image == (Image *) NULL)
5087 return((Image *) NULL);
5088 quantum_threshold=(MagickRealType) QuantumRange*threshold;
5089 /*
5090 Unsharp-mask image.
5091 */
5092 status=MagickTrue;
5093 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00005094 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00005095 image_view=AcquireCacheView(image);
5096 unsharp_view=AcquireCacheView(unsharp_image);
cristyb5d5f722009-11-04 03:03:49 +00005097#if defined(MAGICKCORE_OPENMP_SUPPORT)
5098 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00005099#endif
cristybb503372010-05-27 20:51:26 +00005100 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00005101 {
cristy4c08aed2011-07-01 19:47:50 +00005102 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00005103 pixel;
5104
cristy4c08aed2011-07-01 19:47:50 +00005105 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00005106 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00005107
cristy4c08aed2011-07-01 19:47:50 +00005108 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00005109 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00005110
cristy117ff172010-08-15 21:35:32 +00005111 register ssize_t
5112 x;
5113
cristy3ed852e2009-09-05 21:47:34 +00005114 if (status == MagickFalse)
5115 continue;
5116 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
5117 q=GetCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
5118 exception);
cristy4c08aed2011-07-01 19:47:50 +00005119 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00005120 {
5121 status=MagickFalse;
5122 continue;
5123 }
cristyddd82202009-11-03 20:14:50 +00005124 pixel=bias;
cristybb503372010-05-27 20:51:26 +00005125 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00005126 {
cristyed231572011-07-14 02:18:59 +00005127 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00005128 {
cristy4c08aed2011-07-01 19:47:50 +00005129 pixel.red=GetPixelRed(image,p)-(MagickRealType) GetPixelRed(image,q);
cristy3ed852e2009-09-05 21:47:34 +00005130 if (fabs(2.0*pixel.red) < quantum_threshold)
cristy4c08aed2011-07-01 19:47:50 +00005131 pixel.red=(MagickRealType) GetPixelRed(image,p);
cristy3ed852e2009-09-05 21:47:34 +00005132 else
cristy4c08aed2011-07-01 19:47:50 +00005133 pixel.red=(MagickRealType) GetPixelRed(image,p)+(pixel.red*amount);
5134 SetPixelRed(unsharp_image,ClampToQuantum(pixel.red),q);
cristy3ed852e2009-09-05 21:47:34 +00005135 }
cristyed231572011-07-14 02:18:59 +00005136 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00005137 {
cristy4c08aed2011-07-01 19:47:50 +00005138 pixel.green=GetPixelGreen(image,p)-
5139 (MagickRealType) GetPixelGreen(image,q);
cristy3ed852e2009-09-05 21:47:34 +00005140 if (fabs(2.0*pixel.green) < quantum_threshold)
cristy4c08aed2011-07-01 19:47:50 +00005141 pixel.green=(MagickRealType)
5142 GetPixelGreen(image,p);
cristy3ed852e2009-09-05 21:47:34 +00005143 else
cristy4c08aed2011-07-01 19:47:50 +00005144 pixel.green=(MagickRealType)
5145 GetPixelGreen(image,p)+
5146 (pixel.green*amount);
5147 SetPixelGreen(unsharp_image,
5148 ClampToQuantum(pixel.green),q);
cristy3ed852e2009-09-05 21:47:34 +00005149 }
cristyed231572011-07-14 02:18:59 +00005150 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00005151 {
cristy4c08aed2011-07-01 19:47:50 +00005152 pixel.blue=GetPixelBlue(image,p)-
5153 (MagickRealType) GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00005154 if (fabs(2.0*pixel.blue) < quantum_threshold)
cristy4c08aed2011-07-01 19:47:50 +00005155 pixel.blue=(MagickRealType)
5156 GetPixelBlue(image,p);
cristy3ed852e2009-09-05 21:47:34 +00005157 else
cristy4c08aed2011-07-01 19:47:50 +00005158 pixel.blue=(MagickRealType)
5159 GetPixelBlue(image,p)+(pixel.blue*amount);
5160 SetPixelBlue(unsharp_image,
5161 ClampToQuantum(pixel.blue),q);
cristy3ed852e2009-09-05 21:47:34 +00005162 }
cristyed231572011-07-14 02:18:59 +00005163 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00005164 (image->colorspace == CMYKColorspace))
5165 {
cristy4c08aed2011-07-01 19:47:50 +00005166 pixel.black=GetPixelBlack(image,p)-
5167 (MagickRealType) GetPixelBlack(image,q);
5168 if (fabs(2.0*pixel.black) < quantum_threshold)
5169 pixel.black=(MagickRealType)
5170 GetPixelBlack(image,p);
cristy3ed852e2009-09-05 21:47:34 +00005171 else
cristy4c08aed2011-07-01 19:47:50 +00005172 pixel.black=(MagickRealType)
5173 GetPixelBlack(image,p)+(pixel.black*
5174 amount);
5175 SetPixelBlack(unsharp_image,
5176 ClampToQuantum(pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00005177 }
cristyed231572011-07-14 02:18:59 +00005178 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00005179 {
5180 pixel.alpha=GetPixelAlpha(image,p)-
5181 (MagickRealType) GetPixelAlpha(image,q);
5182 if (fabs(2.0*pixel.alpha) < quantum_threshold)
5183 pixel.alpha=(MagickRealType)
5184 GetPixelAlpha(image,p);
5185 else
5186 pixel.alpha=GetPixelAlpha(image,p)+
5187 (pixel.alpha*amount);
5188 SetPixelAlpha(unsharp_image,
5189 ClampToQuantum(pixel.alpha),q);
5190 }
cristyed231572011-07-14 02:18:59 +00005191 p+=GetPixelChannels(image);
5192 q+=GetPixelChannels(unsharp_image);
cristy3ed852e2009-09-05 21:47:34 +00005193 }
5194 if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
5195 status=MagickFalse;
5196 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5197 {
5198 MagickBooleanType
5199 proceed;
5200
cristyb5d5f722009-11-04 03:03:49 +00005201#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00005202 #pragma omp critical (MagickCore_UnsharpMaskImage)
cristy3ed852e2009-09-05 21:47:34 +00005203#endif
5204 proceed=SetImageProgress(image,SharpenImageTag,progress++,image->rows);
5205 if (proceed == MagickFalse)
5206 status=MagickFalse;
5207 }
5208 }
5209 unsharp_image->type=image->type;
5210 unsharp_view=DestroyCacheView(unsharp_view);
5211 image_view=DestroyCacheView(image_view);
5212 if (status == MagickFalse)
5213 unsharp_image=DestroyImage(unsharp_image);
5214 return(unsharp_image);
5215}