blob: 36598f1806fb4729b5fe440770bb79896733875d [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"
cristy8ea81222011-09-04 10:33:32 +000059#include "MagickCore/gem-private.h"
cristy4c08aed2011-07-01 19:47:50 +000060#include "MagickCore/geometry.h"
61#include "MagickCore/image-private.h"
62#include "MagickCore/list.h"
63#include "MagickCore/log.h"
64#include "MagickCore/memory_.h"
65#include "MagickCore/monitor.h"
66#include "MagickCore/monitor-private.h"
67#include "MagickCore/montage.h"
68#include "MagickCore/morphology.h"
69#include "MagickCore/paint.h"
70#include "MagickCore/pixel-accessor.h"
71#include "MagickCore/property.h"
72#include "MagickCore/quantize.h"
73#include "MagickCore/quantum.h"
74#include "MagickCore/quantum-private.h"
75#include "MagickCore/random_.h"
76#include "MagickCore/random-private.h"
77#include "MagickCore/resample.h"
78#include "MagickCore/resample-private.h"
79#include "MagickCore/resize.h"
80#include "MagickCore/resource_.h"
81#include "MagickCore/segment.h"
82#include "MagickCore/shear.h"
83#include "MagickCore/signature-private.h"
84#include "MagickCore/string_.h"
85#include "MagickCore/thread-private.h"
86#include "MagickCore/transform.h"
87#include "MagickCore/threshold.h"
cristy3ed852e2009-09-05 21:47:34 +000088
89/*
90%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
91% %
92% %
93% %
94% A d a p t i v e B l u r I m a g e %
95% %
96% %
97% %
98%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
99%
100% AdaptiveBlurImage() adaptively blurs the image by blurring less
101% intensely near image edges and more intensely far from edges. We blur the
102% image with a Gaussian operator of the given radius and standard deviation
103% (sigma). For reasonable results, radius should be larger than sigma. Use a
104% radius of 0 and AdaptiveBlurImage() selects a suitable radius for you.
105%
106% The format of the AdaptiveBlurImage method is:
107%
108% Image *AdaptiveBlurImage(const Image *image,const double radius,
cristy4c11c2b2011-09-05 20:17:07 +0000109% const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000110%
111% A description of each parameter follows:
112%
113% o image: the image.
114%
cristy3ed852e2009-09-05 21:47:34 +0000115% o radius: the radius of the Gaussian, in pixels, not counting the center
116% pixel.
117%
118% o sigma: the standard deviation of the Laplacian, in pixels.
119%
cristy4c11c2b2011-09-05 20:17:07 +0000120% o bias: the bias.
121%
cristy3ed852e2009-09-05 21:47:34 +0000122% o exception: return any errors or warnings in this structure.
123%
124*/
125
cristyf89cb1d2011-07-07 01:24:37 +0000126MagickExport MagickBooleanType AdaptiveLevelImage(Image *image,
cristy051718b2011-08-28 22:49:25 +0000127 const char *levels,ExceptionInfo *exception)
cristyf89cb1d2011-07-07 01:24:37 +0000128{
129 double
130 black_point,
131 gamma,
132 white_point;
133
134 GeometryInfo
135 geometry_info;
136
137 MagickBooleanType
138 status;
139
140 MagickStatusType
141 flags;
142
143 /*
144 Parse levels.
145 */
146 if (levels == (char *) NULL)
147 return(MagickFalse);
148 flags=ParseGeometry(levels,&geometry_info);
149 black_point=geometry_info.rho;
150 white_point=(double) QuantumRange;
151 if ((flags & SigmaValue) != 0)
152 white_point=geometry_info.sigma;
153 gamma=1.0;
154 if ((flags & XiValue) != 0)
155 gamma=geometry_info.xi;
156 if ((flags & PercentValue) != 0)
157 {
158 black_point*=(double) image->columns*image->rows/100.0;
159 white_point*=(double) image->columns*image->rows/100.0;
160 }
161 if ((flags & SigmaValue) == 0)
162 white_point=(double) QuantumRange-black_point;
163 if ((flags & AspectValue ) == 0)
cristy7c0a0a42011-08-23 17:57:25 +0000164 status=LevelImage(image,black_point,white_point,gamma,exception);
cristyf89cb1d2011-07-07 01:24:37 +0000165 else
cristy7c0a0a42011-08-23 17:57:25 +0000166 status=LevelizeImage(image,black_point,white_point,gamma,exception);
cristyf89cb1d2011-07-07 01:24:37 +0000167 return(status);
168}
169
cristyf4ad9df2011-07-08 16:49:03 +0000170MagickExport Image *AdaptiveBlurImage(const Image *image,
cristy4c11c2b2011-09-05 20:17:07 +0000171 const double radius,const double sigma,const double bias,
172 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000173{
174#define AdaptiveBlurImageTag "Convolve/Image"
175#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
176
cristyc4c8d132010-01-07 01:58:38 +0000177 CacheView
178 *blur_view,
179 *edge_view,
180 *image_view;
181
cristy3ed852e2009-09-05 21:47:34 +0000182 double
cristy47e00502009-12-17 19:19:57 +0000183 **kernel,
184 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000185
186 Image
187 *blur_image,
188 *edge_image,
189 *gaussian_image;
190
cristy3ed852e2009-09-05 21:47:34 +0000191 MagickBooleanType
192 status;
193
cristybb503372010-05-27 20:51:26 +0000194 MagickOffsetType
195 progress;
196
cristybb503372010-05-27 20:51:26 +0000197 register ssize_t
cristy47e00502009-12-17 19:19:57 +0000198 i;
cristy3ed852e2009-09-05 21:47:34 +0000199
cristybb503372010-05-27 20:51:26 +0000200 size_t
cristy3ed852e2009-09-05 21:47:34 +0000201 width;
202
cristybb503372010-05-27 20:51:26 +0000203 ssize_t
204 j,
205 k,
206 u,
207 v,
208 y;
209
cristy3ed852e2009-09-05 21:47:34 +0000210 assert(image != (const Image *) NULL);
211 assert(image->signature == MagickSignature);
212 if (image->debug != MagickFalse)
213 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
214 assert(exception != (ExceptionInfo *) NULL);
215 assert(exception->signature == MagickSignature);
216 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
217 if (blur_image == (Image *) NULL)
218 return((Image *) NULL);
219 if (fabs(sigma) <= MagickEpsilon)
220 return(blur_image);
cristy574cc262011-08-05 01:23:58 +0000221 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000222 {
cristy3ed852e2009-09-05 21:47:34 +0000223 blur_image=DestroyImage(blur_image);
224 return((Image *) NULL);
225 }
226 /*
227 Edge detect the image brighness channel, level, blur, and level again.
228 */
cristy8ae632d2011-09-05 17:29:53 +0000229 edge_image=EdgeImage(image,radius,sigma,exception);
cristy3ed852e2009-09-05 21:47:34 +0000230 if (edge_image == (Image *) NULL)
231 {
232 blur_image=DestroyImage(blur_image);
233 return((Image *) NULL);
234 }
cristy051718b2011-08-28 22:49:25 +0000235 (void) AdaptiveLevelImage(edge_image,"20%,95%",exception);
cristy05c0c9a2011-09-05 23:16:13 +0000236 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,bias,exception);
cristy3ed852e2009-09-05 21:47:34 +0000237 if (gaussian_image != (Image *) NULL)
238 {
239 edge_image=DestroyImage(edge_image);
240 edge_image=gaussian_image;
241 }
cristy051718b2011-08-28 22:49:25 +0000242 (void) AdaptiveLevelImage(edge_image,"10%,95%",exception);
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;
cristy3ed852e2009-09-05 21:47:34 +0000294 image_view=AcquireCacheView(image);
295 edge_view=AcquireCacheView(edge_image);
296 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000297#if defined(MAGICKCORE_OPENMP_SUPPORT)
298 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000299#endif
cristybb503372010-05-27 20:51:26 +0000300 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000301 {
cristy4c08aed2011-07-01 19:47:50 +0000302 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000303 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000304
cristy4c08aed2011-07-01 19:47:50 +0000305 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000306 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000307
cristy117ff172010-08-15 21:35:32 +0000308 register ssize_t
309 x;
310
cristy3ed852e2009-09-05 21:47:34 +0000311 if (status == MagickFalse)
312 continue;
313 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
314 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
315 exception);
cristyacd2ed22011-08-30 01:44:23 +0000316 if ((r == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000317 {
318 status=MagickFalse;
319 continue;
320 }
cristybb503372010-05-27 20:51:26 +0000321 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000322 {
cristy4c11c2b2011-09-05 20:17:07 +0000323 register const Quantum
324 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000325
cristybb503372010-05-27 20:51:26 +0000326 register ssize_t
cristy4c11c2b2011-09-05 20:17:07 +0000327 i;
cristy3ed852e2009-09-05 21:47:34 +0000328
cristy4c11c2b2011-09-05 20:17:07 +0000329 ssize_t
330 center,
331 j;
332
333 j=(ssize_t) ceil((double) width*QuantumScale*
cristy4c08aed2011-07-01 19:47:50 +0000334 GetPixelIntensity(edge_image,r)-0.5);
cristy4c11c2b2011-09-05 20:17:07 +0000335 if (j < 0)
336 j=0;
cristy3ed852e2009-09-05 21:47:34 +0000337 else
cristy4c11c2b2011-09-05 20:17:07 +0000338 if (j > (ssize_t) width)
339 j=(ssize_t) width;
340 if ((j & 0x01) != 0)
341 j--;
342 p=GetCacheViewVirtualPixels(image_view,x-((ssize_t) (width-j)/2L),y-
343 (ssize_t) ((width-j)/2L),width-j,width-j,exception);
cristy4c08aed2011-07-01 19:47:50 +0000344 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000345 break;
cristy4c11c2b2011-09-05 20:17:07 +0000346 center=(ssize_t) GetPixelChannels(image)*(width-j)*
cristy075ff302011-09-07 01:51:24 +0000347 ((width-j)/2L)+GetPixelChannels(image)*((width-j)/2L);
cristy4c11c2b2011-09-05 20:17:07 +0000348 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy3ed852e2009-09-05 21:47:34 +0000349 {
cristy4c11c2b2011-09-05 20:17:07 +0000350 MagickRealType
351 alpha,
352 gamma,
353 pixel;
354
355 PixelChannel
356 channel;
357
358 PixelTrait
359 blur_traits,
360 traits;
361
362 register const double
363 *restrict k;
364
365 register const Quantum
366 *restrict pixels;
367
368 register ssize_t
369 u;
370
371 ssize_t
372 v;
373
374 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
375 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
376 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
377 if ((traits == UndefinedPixelTrait) ||
378 (blur_traits == UndefinedPixelTrait))
379 continue;
380 if ((blur_traits & CopyPixelTrait) != 0)
381 {
382 q[channel]=p[center+i];
383 continue;
384 }
385 k=kernel[j];
386 pixels=p;
387 pixel=bias;
388 gamma=0.0;
389 if ((blur_traits & BlendPixelTrait) == 0)
390 {
391 /*
392 No alpha blending.
393 */
394 for (v=0; v < (ssize_t) (width-j); v++)
395 {
396 for (u=0; u < (ssize_t) (width-j); u++)
397 {
398 pixel+=(*k)*pixels[i];
399 gamma+=(*k);
400 k++;
401 pixels+=GetPixelChannels(image);
402 }
403 }
404 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
405 q[channel]=ClampToQuantum(gamma*pixel);
406 continue;
407 }
408 /*
409 Alpha blending.
410 */
411 for (v=0; v < (ssize_t) (width-j); v++)
cristy3ed852e2009-09-05 21:47:34 +0000412 {
cristy4c11c2b2011-09-05 20:17:07 +0000413 for (u=0; u < (ssize_t) (width-j); u++)
414 {
415 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,pixels));
416 pixel+=(*k)*alpha*pixels[i];
417 gamma+=(*k)*alpha;
418 k++;
419 pixels+=GetPixelChannels(image);
420 }
cristy3ed852e2009-09-05 21:47:34 +0000421 }
cristy4c11c2b2011-09-05 20:17:07 +0000422 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
423 q[channel]=ClampToQuantum(gamma*pixel);
cristy3ed852e2009-09-05 21:47:34 +0000424 }
cristyed231572011-07-14 02:18:59 +0000425 q+=GetPixelChannels(blur_image);
426 r+=GetPixelChannels(edge_image);
cristy3ed852e2009-09-05 21:47:34 +0000427 }
428 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
429 status=MagickFalse;
430 if (image->progress_monitor != (MagickProgressMonitor) NULL)
431 {
432 MagickBooleanType
433 proceed;
434
cristyb5d5f722009-11-04 03:03:49 +0000435#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy8ae632d2011-09-05 17:29:53 +0000436 #pragma omp critical (MagickCore_AdaptiveBlurImage)
cristy3ed852e2009-09-05 21:47:34 +0000437#endif
438 proceed=SetImageProgress(image,AdaptiveBlurImageTag,progress++,
439 image->rows);
440 if (proceed == MagickFalse)
441 status=MagickFalse;
442 }
443 }
444 blur_image->type=image->type;
445 blur_view=DestroyCacheView(blur_view);
446 edge_view=DestroyCacheView(edge_view);
447 image_view=DestroyCacheView(image_view);
448 edge_image=DestroyImage(edge_image);
cristybb503372010-05-27 20:51:26 +0000449 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000450 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
451 kernel=(double **) RelinquishMagickMemory(kernel);
452 if (status == MagickFalse)
453 blur_image=DestroyImage(blur_image);
454 return(blur_image);
455}
456
457/*
458%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
459% %
460% %
461% %
462% A d a p t i v e S h a r p e n I m a g e %
463% %
464% %
465% %
466%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
467%
468% AdaptiveSharpenImage() adaptively sharpens the image by sharpening more
469% intensely near image edges and less intensely far from edges. We sharpen the
470% image with a Gaussian operator of the given radius and standard deviation
471% (sigma). For reasonable results, radius should be larger than sigma. Use a
472% radius of 0 and AdaptiveSharpenImage() selects a suitable radius for you.
473%
474% The format of the AdaptiveSharpenImage method is:
475%
476% Image *AdaptiveSharpenImage(const Image *image,const double radius,
cristy4c11c2b2011-09-05 20:17:07 +0000477% const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000478%
479% A description of each parameter follows:
480%
481% o image: the image.
482%
cristy3ed852e2009-09-05 21:47:34 +0000483% o radius: the radius of the Gaussian, in pixels, not counting the center
484% pixel.
485%
486% o sigma: the standard deviation of the Laplacian, in pixels.
487%
cristy4c11c2b2011-09-05 20:17:07 +0000488% o bias: the bias.
489%
cristy3ed852e2009-09-05 21:47:34 +0000490% o exception: return any errors or warnings in this structure.
491%
492*/
cristy3ed852e2009-09-05 21:47:34 +0000493MagickExport Image *AdaptiveSharpenImage(const Image *image,const double radius,
cristy4c11c2b2011-09-05 20:17:07 +0000494 const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000495{
cristy3ed852e2009-09-05 21:47:34 +0000496#define AdaptiveSharpenImageTag "Convolve/Image"
497#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
498
cristyc4c8d132010-01-07 01:58:38 +0000499 CacheView
500 *sharp_view,
501 *edge_view,
502 *image_view;
503
cristy3ed852e2009-09-05 21:47:34 +0000504 double
cristy47e00502009-12-17 19:19:57 +0000505 **kernel,
506 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000507
508 Image
509 *sharp_image,
510 *edge_image,
511 *gaussian_image;
512
cristy3ed852e2009-09-05 21:47:34 +0000513 MagickBooleanType
514 status;
515
cristybb503372010-05-27 20:51:26 +0000516 MagickOffsetType
517 progress;
518
cristybb503372010-05-27 20:51:26 +0000519 register ssize_t
cristy47e00502009-12-17 19:19:57 +0000520 i;
cristy3ed852e2009-09-05 21:47:34 +0000521
cristybb503372010-05-27 20:51:26 +0000522 size_t
cristy3ed852e2009-09-05 21:47:34 +0000523 width;
524
cristybb503372010-05-27 20:51:26 +0000525 ssize_t
526 j,
527 k,
528 u,
529 v,
530 y;
531
cristy3ed852e2009-09-05 21:47:34 +0000532 assert(image != (const Image *) NULL);
533 assert(image->signature == MagickSignature);
534 if (image->debug != MagickFalse)
535 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
536 assert(exception != (ExceptionInfo *) NULL);
537 assert(exception->signature == MagickSignature);
538 sharp_image=CloneImage(image,0,0,MagickTrue,exception);
539 if (sharp_image == (Image *) NULL)
540 return((Image *) NULL);
541 if (fabs(sigma) <= MagickEpsilon)
542 return(sharp_image);
cristy574cc262011-08-05 01:23:58 +0000543 if (SetImageStorageClass(sharp_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000544 {
cristy3ed852e2009-09-05 21:47:34 +0000545 sharp_image=DestroyImage(sharp_image);
546 return((Image *) NULL);
547 }
548 /*
549 Edge detect the image brighness channel, level, sharp, and level again.
550 */
cristy8ae632d2011-09-05 17:29:53 +0000551 edge_image=EdgeImage(image,radius,sigma,exception);
cristy3ed852e2009-09-05 21:47:34 +0000552 if (edge_image == (Image *) NULL)
553 {
554 sharp_image=DestroyImage(sharp_image);
555 return((Image *) NULL);
556 }
cristy051718b2011-08-28 22:49:25 +0000557 (void) AdaptiveLevelImage(edge_image,"20%,95%",exception);
cristy05c0c9a2011-09-05 23:16:13 +0000558 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,bias,exception);
cristy3ed852e2009-09-05 21:47:34 +0000559 if (gaussian_image != (Image *) NULL)
560 {
561 edge_image=DestroyImage(edge_image);
562 edge_image=gaussian_image;
563 }
cristy051718b2011-08-28 22:49:25 +0000564 (void) AdaptiveLevelImage(edge_image,"10%,95%",exception);
cristy3ed852e2009-09-05 21:47:34 +0000565 /*
566 Create a set of kernels from maximum (radius,sigma) to minimum.
567 */
568 width=GetOptimalKernelWidth2D(radius,sigma);
569 kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
570 if (kernel == (double **) NULL)
571 {
572 edge_image=DestroyImage(edge_image);
573 sharp_image=DestroyImage(sharp_image);
574 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
575 }
576 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
cristybb503372010-05-27 20:51:26 +0000577 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000578 {
579 kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)*
580 sizeof(**kernel));
581 if (kernel[i] == (double *) NULL)
582 break;
cristy47e00502009-12-17 19:19:57 +0000583 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000584 j=(ssize_t) (width-i)/2;
cristy47e00502009-12-17 19:19:57 +0000585 k=0;
586 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000587 {
cristy47e00502009-12-17 19:19:57 +0000588 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000589 {
cristy4205a3c2010-09-12 20:19:59 +0000590 kernel[i][k]=(double) (-exp(-((double) u*u+v*v)/(2.0*MagickSigma*
591 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +0000592 normalize+=kernel[i][k];
593 k++;
cristy3ed852e2009-09-05 21:47:34 +0000594 }
595 }
cristy3ed852e2009-09-05 21:47:34 +0000596 if (fabs(normalize) <= MagickEpsilon)
597 normalize=1.0;
598 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000599 for (k=0; k < (j*j); k++)
600 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000601 }
cristybb503372010-05-27 20:51:26 +0000602 if (i < (ssize_t) width)
cristy3ed852e2009-09-05 21:47:34 +0000603 {
604 for (i-=2; i >= 0; i-=2)
605 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
606 kernel=(double **) RelinquishMagickMemory(kernel);
607 edge_image=DestroyImage(edge_image);
608 sharp_image=DestroyImage(sharp_image);
609 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
610 }
611 /*
612 Adaptively sharpen image.
613 */
614 status=MagickTrue;
615 progress=0;
cristy3ed852e2009-09-05 21:47:34 +0000616 image_view=AcquireCacheView(image);
617 edge_view=AcquireCacheView(edge_image);
618 sharp_view=AcquireCacheView(sharp_image);
cristyb5d5f722009-11-04 03:03:49 +0000619#if defined(MAGICKCORE_OPENMP_SUPPORT)
620 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000621#endif
cristybb503372010-05-27 20:51:26 +0000622 for (y=0; y < (ssize_t) sharp_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000623 {
cristy4c08aed2011-07-01 19:47:50 +0000624 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000625 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000626
cristy4c08aed2011-07-01 19:47:50 +0000627 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000628 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000629
cristy117ff172010-08-15 21:35:32 +0000630 register ssize_t
631 x;
632
cristy3ed852e2009-09-05 21:47:34 +0000633 if (status == MagickFalse)
634 continue;
635 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
636 q=QueueCacheViewAuthenticPixels(sharp_view,0,y,sharp_image->columns,1,
637 exception);
cristy4c08aed2011-07-01 19:47:50 +0000638 if ((r == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000639 {
640 status=MagickFalse;
641 continue;
642 }
cristybb503372010-05-27 20:51:26 +0000643 for (x=0; x < (ssize_t) sharp_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000644 {
cristy4c11c2b2011-09-05 20:17:07 +0000645 register const Quantum
646 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000647
cristybb503372010-05-27 20:51:26 +0000648 register ssize_t
cristy4c11c2b2011-09-05 20:17:07 +0000649 i;
cristy3ed852e2009-09-05 21:47:34 +0000650
cristy4c11c2b2011-09-05 20:17:07 +0000651 ssize_t
652 center,
653 j;
654
655 j=(ssize_t) ceil((double) width*QuantumScale*
cristy4c08aed2011-07-01 19:47:50 +0000656 GetPixelIntensity(edge_image,r)-0.5);
cristy4c11c2b2011-09-05 20:17:07 +0000657 if (j < 0)
658 j=0;
cristy3ed852e2009-09-05 21:47:34 +0000659 else
cristy4c11c2b2011-09-05 20:17:07 +0000660 if (j > (ssize_t) width)
661 j=(ssize_t) width;
662 if ((j & 0x01) != 0)
663 j--;
664 p=GetCacheViewVirtualPixels(image_view,x-((ssize_t) (width-j)/2L),y-
665 (ssize_t) ((width-j)/2L),width-j,width-j,exception);
cristy4c08aed2011-07-01 19:47:50 +0000666 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000667 break;
cristy4c11c2b2011-09-05 20:17:07 +0000668 center=(ssize_t) GetPixelChannels(image)*(width-j)*
669 ((width-j)/2L)+GetPixelChannels(image)*((width-j)/2);
670 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy3ed852e2009-09-05 21:47:34 +0000671 {
cristy4c11c2b2011-09-05 20:17:07 +0000672 MagickRealType
673 alpha,
674 gamma,
675 pixel;
676
677 PixelChannel
678 channel;
679
680 PixelTrait
681 sharp_traits,
682 traits;
683
684 register const double
685 *restrict k;
686
687 register const Quantum
688 *restrict pixels;
689
690 register ssize_t
691 u;
692
693 ssize_t
694 v;
695
696 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
697 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
698 sharp_traits=GetPixelChannelMapTraits(sharp_image,channel);
699 if ((traits == UndefinedPixelTrait) ||
700 (sharp_traits == UndefinedPixelTrait))
701 continue;
702 if ((sharp_traits & CopyPixelTrait) != 0)
703 {
704 q[channel]=p[center+i];
705 continue;
706 }
707 k=kernel[j];
708 pixels=p;
709 pixel=bias;
710 gamma=0.0;
711 if ((sharp_traits & BlendPixelTrait) == 0)
712 {
713 /*
714 No alpha blending.
715 */
716 for (v=0; v < (ssize_t) (width-j); v++)
717 {
718 for (u=0; u < (ssize_t) (width-j); u++)
719 {
720 pixel+=(*k)*pixels[i];
721 gamma+=(*k);
722 k++;
723 pixels+=GetPixelChannels(image);
724 }
725 }
726 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
727 q[channel]=ClampToQuantum(gamma*pixel);
728 continue;
729 }
730 /*
731 Alpha blending.
732 */
733 for (v=0; v < (ssize_t) (width-j); v++)
cristy3ed852e2009-09-05 21:47:34 +0000734 {
cristy4c11c2b2011-09-05 20:17:07 +0000735 for (u=0; u < (ssize_t) (width-j); u++)
736 {
737 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,pixels));
738 pixel+=(*k)*alpha*pixels[i];
739 gamma+=(*k)*alpha;
740 k++;
741 pixels+=GetPixelChannels(image);
742 }
cristy3ed852e2009-09-05 21:47:34 +0000743 }
cristy4c11c2b2011-09-05 20:17:07 +0000744 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
745 q[channel]=ClampToQuantum(gamma*pixel);
cristy3ed852e2009-09-05 21:47:34 +0000746 }
cristyed231572011-07-14 02:18:59 +0000747 q+=GetPixelChannels(sharp_image);
748 r+=GetPixelChannels(edge_image);
cristy3ed852e2009-09-05 21:47:34 +0000749 }
750 if (SyncCacheViewAuthenticPixels(sharp_view,exception) == MagickFalse)
751 status=MagickFalse;
752 if (image->progress_monitor != (MagickProgressMonitor) NULL)
753 {
754 MagickBooleanType
755 proceed;
756
cristyb5d5f722009-11-04 03:03:49 +0000757#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +0000758 #pragma omp critical (MagickCore_AdaptiveSharpenImage)
cristy3ed852e2009-09-05 21:47:34 +0000759#endif
760 proceed=SetImageProgress(image,AdaptiveSharpenImageTag,progress++,
761 image->rows);
762 if (proceed == MagickFalse)
763 status=MagickFalse;
764 }
765 }
766 sharp_image->type=image->type;
767 sharp_view=DestroyCacheView(sharp_view);
768 edge_view=DestroyCacheView(edge_view);
769 image_view=DestroyCacheView(image_view);
770 edge_image=DestroyImage(edge_image);
cristybb503372010-05-27 20:51:26 +0000771 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000772 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
773 kernel=(double **) RelinquishMagickMemory(kernel);
774 if (status == MagickFalse)
775 sharp_image=DestroyImage(sharp_image);
776 return(sharp_image);
777}
778
779/*
780%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
781% %
782% %
783% %
784% B l u r I m a g e %
785% %
786% %
787% %
788%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
789%
790% BlurImage() blurs an image. We convolve the image with a Gaussian operator
791% of the given radius and standard deviation (sigma). For reasonable results,
792% the radius should be larger than sigma. Use a radius of 0 and BlurImage()
793% selects a suitable radius for you.
794%
795% BlurImage() differs from GaussianBlurImage() in that it uses a separable
796% kernel which is faster but mathematically equivalent to the non-separable
797% kernel.
798%
799% The format of the BlurImage method is:
800%
801% Image *BlurImage(const Image *image,const double radius,
cristy05c0c9a2011-09-05 23:16:13 +0000802% const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000803%
804% A description of each parameter follows:
805%
806% o image: the image.
807%
cristy3ed852e2009-09-05 21:47:34 +0000808% o radius: the radius of the Gaussian, in pixels, not counting the center
809% pixel.
810%
811% o sigma: the standard deviation of the Gaussian, in pixels.
812%
cristy05c0c9a2011-09-05 23:16:13 +0000813% o bias: the bias.
814%
cristy3ed852e2009-09-05 21:47:34 +0000815% o exception: return any errors or warnings in this structure.
816%
817*/
818
cristybb503372010-05-27 20:51:26 +0000819static double *GetBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +0000820{
cristy3ed852e2009-09-05 21:47:34 +0000821 double
cristy47e00502009-12-17 19:19:57 +0000822 *kernel,
823 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000824
cristy117ff172010-08-15 21:35:32 +0000825 register ssize_t
826 i;
827
cristybb503372010-05-27 20:51:26 +0000828 ssize_t
cristy47e00502009-12-17 19:19:57 +0000829 j,
830 k;
cristy3ed852e2009-09-05 21:47:34 +0000831
cristy3ed852e2009-09-05 21:47:34 +0000832 /*
833 Generate a 1-D convolution kernel.
834 */
835 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
836 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
837 if (kernel == (double *) NULL)
838 return(0);
cristy3ed852e2009-09-05 21:47:34 +0000839 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000840 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +0000841 i=0;
842 for (k=(-j); k <= j; k++)
843 {
cristy4205a3c2010-09-12 20:19:59 +0000844 kernel[i]=(double) (exp(-((double) k*k)/(2.0*MagickSigma*MagickSigma))/
845 (MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +0000846 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +0000847 i++;
848 }
cristybb503372010-05-27 20:51:26 +0000849 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000850 kernel[i]/=normalize;
851 return(kernel);
852}
853
cristyf4ad9df2011-07-08 16:49:03 +0000854MagickExport Image *BlurImage(const Image *image,const double radius,
cristy05c0c9a2011-09-05 23:16:13 +0000855 const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000856{
857#define BlurImageTag "Blur/Image"
858
cristyc4c8d132010-01-07 01:58:38 +0000859 CacheView
860 *blur_view,
861 *image_view;
862
cristy3ed852e2009-09-05 21:47:34 +0000863 double
864 *kernel;
865
866 Image
867 *blur_image;
868
cristy3ed852e2009-09-05 21:47:34 +0000869 MagickBooleanType
870 status;
871
cristybb503372010-05-27 20:51:26 +0000872 MagickOffsetType
873 progress;
874
cristybb503372010-05-27 20:51:26 +0000875 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000876 i;
877
cristybb503372010-05-27 20:51:26 +0000878 size_t
cristy3ed852e2009-09-05 21:47:34 +0000879 width;
880
cristybb503372010-05-27 20:51:26 +0000881 ssize_t
cristyb41a1172011-09-06 00:55:14 +0000882 center,
cristybb503372010-05-27 20:51:26 +0000883 x,
884 y;
885
cristy3ed852e2009-09-05 21:47:34 +0000886 /*
887 Initialize blur image attributes.
888 */
889 assert(image != (Image *) NULL);
890 assert(image->signature == MagickSignature);
891 if (image->debug != MagickFalse)
892 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
893 assert(exception != (ExceptionInfo *) NULL);
894 assert(exception->signature == MagickSignature);
cristyd25c77e2011-09-06 00:10:24 +0000895 blur_image=CloneImage(image,0,0,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +0000896 if (blur_image == (Image *) NULL)
897 return((Image *) NULL);
898 if (fabs(sigma) <= MagickEpsilon)
899 return(blur_image);
cristy574cc262011-08-05 01:23:58 +0000900 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000901 {
cristy3ed852e2009-09-05 21:47:34 +0000902 blur_image=DestroyImage(blur_image);
903 return((Image *) NULL);
904 }
905 width=GetOptimalKernelWidth1D(radius,sigma);
906 kernel=GetBlurKernel(width,sigma);
907 if (kernel == (double *) NULL)
908 {
909 blur_image=DestroyImage(blur_image);
910 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
911 }
912 if (image->debug != MagickFalse)
913 {
914 char
915 format[MaxTextExtent],
916 *message;
917
918 register const double
919 *k;
920
921 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +0000922 " BlurImage with %.20g kernel:",(double) width);
cristy3ed852e2009-09-05 21:47:34 +0000923 message=AcquireString("");
924 k=kernel;
cristybb503372010-05-27 20:51:26 +0000925 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000926 {
927 *message='\0';
cristyb51dff52011-05-19 16:55:47 +0000928 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) i);
cristy3ed852e2009-09-05 21:47:34 +0000929 (void) ConcatenateString(&message,format);
cristyb51dff52011-05-19 16:55:47 +0000930 (void) FormatLocaleString(format,MaxTextExtent,"%g ",*k++);
cristy3ed852e2009-09-05 21:47:34 +0000931 (void) ConcatenateString(&message,format);
932 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
933 }
934 message=DestroyString(message);
935 }
936 /*
937 Blur rows.
938 */
939 status=MagickTrue;
940 progress=0;
cristyb41a1172011-09-06 00:55:14 +0000941 center=(ssize_t) GetPixelChannels(image)*(width/2L);
cristy3ed852e2009-09-05 21:47:34 +0000942 image_view=AcquireCacheView(image);
943 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000944#if defined(MAGICKCORE_OPENMP_SUPPORT)
945 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000946#endif
cristyb41a1172011-09-06 00:55:14 +0000947 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000948 {
cristy4c08aed2011-07-01 19:47:50 +0000949 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000950 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000951
cristy4c08aed2011-07-01 19:47:50 +0000952 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000953 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000954
cristy117ff172010-08-15 21:35:32 +0000955 register ssize_t
956 x;
957
cristy3ed852e2009-09-05 21:47:34 +0000958 if (status == MagickFalse)
959 continue;
cristy117ff172010-08-15 21:35:32 +0000960 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y,
961 image->columns+width,1,exception);
cristy3ed852e2009-09-05 21:47:34 +0000962 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
963 exception);
cristy4c08aed2011-07-01 19:47:50 +0000964 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000965 {
966 status=MagickFalse;
967 continue;
968 }
cristyb41a1172011-09-06 00:55:14 +0000969 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000970 {
cristybb503372010-05-27 20:51:26 +0000971 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000972 i;
973
cristyb41a1172011-09-06 00:55:14 +0000974 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
975 {
976 MagickRealType
977 alpha,
978 gamma,
979 pixel;
cristyd25c77e2011-09-06 00:10:24 +0000980
cristyb41a1172011-09-06 00:55:14 +0000981 PixelChannel
982 channel;
983
984 PixelTrait
985 blur_traits,
986 traits;
987
988 register const double
989 *restrict k;
990
991 register const Quantum
992 *restrict pixels;
993
994 register ssize_t
995 u;
996
997 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
998 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
999 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
1000 if ((traits == UndefinedPixelTrait) ||
1001 (blur_traits == UndefinedPixelTrait))
1002 continue;
1003 if ((blur_traits & CopyPixelTrait) != 0)
cristyd25c77e2011-09-06 00:10:24 +00001004 {
cristyb41a1172011-09-06 00:55:14 +00001005 q[channel]=p[center+i];
1006 continue;
cristyd25c77e2011-09-06 00:10:24 +00001007 }
cristyb41a1172011-09-06 00:55:14 +00001008 k=kernel;
1009 pixels=p;
1010 pixel=0.0;
1011 if ((blur_traits & BlendPixelTrait) == 0)
1012 {
1013 /*
1014 No alpha blending.
1015 */
1016 for (u=0; u < (ssize_t) width; u++)
cristyd25c77e2011-09-06 00:10:24 +00001017 {
cristyb41a1172011-09-06 00:55:14 +00001018 pixel+=(*k)*pixels[i];
1019 k++;
1020 pixels+=GetPixelChannels(image);
cristyd25c77e2011-09-06 00:10:24 +00001021 }
cristyb41a1172011-09-06 00:55:14 +00001022 q[channel]=ClampToQuantum(pixel);
1023 continue;
1024 }
1025 /*
1026 Alpha blending.
1027 */
1028 gamma=0.0;
1029 for (u=0; u < (ssize_t) width; u++)
1030 {
1031 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,pixels));
1032 pixel+=(*k)*alpha*pixels[i];
1033 gamma+=(*k)*alpha;
1034 k++;
1035 pixels+=GetPixelChannels(image);
cristyd25c77e2011-09-06 00:10:24 +00001036 }
cristyb41a1172011-09-06 00:55:14 +00001037 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1038 q[channel]=ClampToQuantum(gamma*pixel);
1039 }
cristyed231572011-07-14 02:18:59 +00001040 p+=GetPixelChannels(image);
1041 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001042 }
1043 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1044 status=MagickFalse;
1045 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1046 {
1047 MagickBooleanType
1048 proceed;
1049
cristyb5d5f722009-11-04 03:03:49 +00001050#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00001051 #pragma omp critical (MagickCore_BlurImage)
cristy3ed852e2009-09-05 21:47:34 +00001052#endif
1053 proceed=SetImageProgress(image,BlurImageTag,progress++,blur_image->rows+
1054 blur_image->columns);
1055 if (proceed == MagickFalse)
1056 status=MagickFalse;
1057 }
1058 }
1059 blur_view=DestroyCacheView(blur_view);
1060 image_view=DestroyCacheView(image_view);
1061 /*
1062 Blur columns.
1063 */
1064 image_view=AcquireCacheView(blur_image);
1065 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00001066#if defined(MAGICKCORE_OPENMP_SUPPORT)
1067 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001068#endif
cristyb41a1172011-09-06 00:55:14 +00001069 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001070 {
cristy4c08aed2011-07-01 19:47:50 +00001071 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001072 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001073
cristy4c08aed2011-07-01 19:47:50 +00001074 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001075 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001076
cristy117ff172010-08-15 21:35:32 +00001077 register ssize_t
1078 y;
1079
cristy3ed852e2009-09-05 21:47:34 +00001080 if (status == MagickFalse)
1081 continue;
cristy117ff172010-08-15 21:35:32 +00001082 p=GetCacheViewVirtualPixels(image_view,x,-((ssize_t) width/2L),1,
1083 image->rows+width,exception);
cristy3ed852e2009-09-05 21:47:34 +00001084 q=GetCacheViewAuthenticPixels(blur_view,x,0,1,blur_image->rows,exception);
cristy4c08aed2011-07-01 19:47:50 +00001085 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001086 {
1087 status=MagickFalse;
1088 continue;
1089 }
cristyb41a1172011-09-06 00:55:14 +00001090 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001091 {
cristybb503372010-05-27 20:51:26 +00001092 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001093 i;
1094
cristyb41a1172011-09-06 00:55:14 +00001095 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1096 {
1097 MagickRealType
1098 alpha,
1099 gamma,
1100 pixel;
cristyd25c77e2011-09-06 00:10:24 +00001101
cristyb41a1172011-09-06 00:55:14 +00001102 PixelChannel
1103 channel;
1104
1105 PixelTrait
1106 blur_traits,
1107 traits;
1108
1109 register const double
1110 *restrict k;
1111
1112 register const Quantum
1113 *restrict pixels;
1114
1115 register ssize_t
1116 u;
1117
1118 traits=GetPixelChannelMapTraits(blur_image,(PixelChannel) i);
1119 channel=GetPixelChannelMapChannel(blur_image,(PixelChannel) i);
1120 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
1121 if ((traits == UndefinedPixelTrait) ||
1122 (blur_traits == UndefinedPixelTrait))
1123 continue;
1124 if ((blur_traits & CopyPixelTrait) != 0)
cristyd25c77e2011-09-06 00:10:24 +00001125 {
cristyb41a1172011-09-06 00:55:14 +00001126 q[channel]=p[center+i];
1127 continue;
cristyd25c77e2011-09-06 00:10:24 +00001128 }
cristyb41a1172011-09-06 00:55:14 +00001129 k=kernel;
1130 pixels=p;
1131 pixel=0.0;
1132 if ((blur_traits & BlendPixelTrait) == 0)
1133 {
1134 /*
1135 No alpha blending.
1136 */
1137 for (u=0; u < (ssize_t) width; u++)
cristyd25c77e2011-09-06 00:10:24 +00001138 {
cristyb41a1172011-09-06 00:55:14 +00001139 pixel+=(*k)*pixels[i];
1140 k++;
1141 pixels+=GetPixelChannels(blur_image);
cristyd25c77e2011-09-06 00:10:24 +00001142 }
cristyb41a1172011-09-06 00:55:14 +00001143 q[channel]=ClampToQuantum(pixel);
1144 continue;
1145 }
1146 /*
1147 Alpha blending.
1148 */
1149 gamma=0.0;
1150 for (u=0; u < (ssize_t) width; u++)
1151 {
1152 alpha=(MagickRealType) (QuantumScale*
1153 GetPixelAlpha(blur_image,pixels));
1154 pixel+=(*k)*alpha*pixels[i];
1155 gamma+=(*k)*alpha;
1156 k++;
1157 pixels+=GetPixelChannels(blur_image);
cristyd25c77e2011-09-06 00:10:24 +00001158 }
cristyb41a1172011-09-06 00:55:14 +00001159 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1160 q[channel]=ClampToQuantum(gamma*pixel);
1161 }
cristyd25c77e2011-09-06 00:10:24 +00001162 p+=GetPixelChannels(blur_image);
cristyed231572011-07-14 02:18:59 +00001163 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001164 }
1165 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1166 status=MagickFalse;
cristy4c08aed2011-07-01 19:47:50 +00001167 if (blur_image->progress_monitor != (MagickProgressMonitor) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001168 {
1169 MagickBooleanType
1170 proceed;
1171
cristyb5d5f722009-11-04 03:03:49 +00001172#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00001173 #pragma omp critical (MagickCore_BlurImage)
cristy3ed852e2009-09-05 21:47:34 +00001174#endif
cristy4c08aed2011-07-01 19:47:50 +00001175 proceed=SetImageProgress(blur_image,BlurImageTag,progress++,
1176 blur_image->rows+blur_image->columns);
cristy3ed852e2009-09-05 21:47:34 +00001177 if (proceed == MagickFalse)
1178 status=MagickFalse;
1179 }
1180 }
1181 blur_view=DestroyCacheView(blur_view);
1182 image_view=DestroyCacheView(image_view);
1183 kernel=(double *) RelinquishMagickMemory(kernel);
1184 if (status == MagickFalse)
1185 blur_image=DestroyImage(blur_image);
1186 blur_image->type=image->type;
1187 return(blur_image);
1188}
1189
1190/*
1191%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1192% %
1193% %
1194% %
cristyfccdab92009-11-30 16:43:57 +00001195% C o n v o l v e I m a g e %
1196% %
1197% %
1198% %
1199%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1200%
1201% ConvolveImage() applies a custom convolution kernel to the image.
1202%
1203% The format of the ConvolveImage method is:
1204%
cristy5e6be1e2011-07-16 01:23:39 +00001205% Image *ConvolveImage(const Image *image,const KernelInfo *kernel,
1206% ExceptionInfo *exception)
1207%
cristyfccdab92009-11-30 16:43:57 +00001208% A description of each parameter follows:
1209%
1210% o image: the image.
1211%
cristy5e6be1e2011-07-16 01:23:39 +00001212% o kernel: the filtering kernel.
cristyfccdab92009-11-30 16:43:57 +00001213%
1214% o exception: return any errors or warnings in this structure.
1215%
1216*/
cristy5e6be1e2011-07-16 01:23:39 +00001217MagickExport Image *ConvolveImage(const Image *image,
1218 const KernelInfo *kernel_info,ExceptionInfo *exception)
cristyfccdab92009-11-30 16:43:57 +00001219{
cristyfccdab92009-11-30 16:43:57 +00001220#define ConvolveImageTag "Convolve/Image"
1221
cristyc4c8d132010-01-07 01:58:38 +00001222 CacheView
1223 *convolve_view,
cristy105ba3c2011-07-18 02:28:38 +00001224 *image_view;
cristyc4c8d132010-01-07 01:58:38 +00001225
cristyfccdab92009-11-30 16:43:57 +00001226 Image
1227 *convolve_image;
1228
cristyfccdab92009-11-30 16:43:57 +00001229 MagickBooleanType
1230 status;
1231
cristybb503372010-05-27 20:51:26 +00001232 MagickOffsetType
1233 progress;
1234
cristybb503372010-05-27 20:51:26 +00001235 ssize_t
cristy574cc262011-08-05 01:23:58 +00001236 center,
cristybb503372010-05-27 20:51:26 +00001237 y;
1238
cristyfccdab92009-11-30 16:43:57 +00001239 /*
1240 Initialize convolve image attributes.
1241 */
1242 assert(image != (Image *) NULL);
1243 assert(image->signature == MagickSignature);
1244 if (image->debug != MagickFalse)
1245 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1246 assert(exception != (ExceptionInfo *) NULL);
1247 assert(exception->signature == MagickSignature);
cristy5e6be1e2011-07-16 01:23:39 +00001248 if ((kernel_info->width % 2) == 0)
cristyfccdab92009-11-30 16:43:57 +00001249 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);
cristy574cc262011-08-05 01:23:58 +00001254 if (SetImageStorageClass(convolve_image,DirectClass,exception) == MagickFalse)
cristyfccdab92009-11-30 16:43:57 +00001255 {
cristyfccdab92009-11-30 16:43:57 +00001256 convolve_image=DestroyImage(convolve_image);
1257 return((Image *) NULL);
1258 }
1259 if (image->debug != MagickFalse)
1260 {
1261 char
1262 format[MaxTextExtent],
1263 *message;
1264
cristy117ff172010-08-15 21:35:32 +00001265 register const double
1266 *k;
1267
cristy4e154852011-07-14 13:28:53 +00001268 register ssize_t
1269 u;
1270
cristybb503372010-05-27 20:51:26 +00001271 ssize_t
cristyfccdab92009-11-30 16:43:57 +00001272 v;
1273
cristyfccdab92009-11-30 16:43:57 +00001274 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristy5e6be1e2011-07-16 01:23:39 +00001275 " ConvolveImage with %.20gx%.20g kernel:",(double) kernel_info->width,
1276 (double) kernel_info->height);
cristyfccdab92009-11-30 16:43:57 +00001277 message=AcquireString("");
cristy5e6be1e2011-07-16 01:23:39 +00001278 k=kernel_info->values;
1279 for (v=0; v < (ssize_t) kernel_info->width; v++)
cristyfccdab92009-11-30 16:43:57 +00001280 {
1281 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00001282 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristyfccdab92009-11-30 16:43:57 +00001283 (void) ConcatenateString(&message,format);
cristy5e6be1e2011-07-16 01:23:39 +00001284 for (u=0; u < (ssize_t) kernel_info->height; u++)
cristyfccdab92009-11-30 16:43:57 +00001285 {
cristyb51dff52011-05-19 16:55:47 +00001286 (void) FormatLocaleString(format,MaxTextExtent,"%g ",*k++);
cristyfccdab92009-11-30 16:43:57 +00001287 (void) ConcatenateString(&message,format);
1288 }
1289 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
1290 }
1291 message=DestroyString(message);
1292 }
1293 /*
cristyfccdab92009-11-30 16:43:57 +00001294 Convolve image.
1295 */
cristy574cc262011-08-05 01:23:58 +00001296 center=(ssize_t) GetPixelChannels(image)*(image->columns+kernel_info->width)*
cristy075ff302011-09-07 01:51:24 +00001297 (kernel_info->height/2L)+GetPixelChannels(image)*(kernel_info->width/2L);
cristyfccdab92009-11-30 16:43:57 +00001298 status=MagickTrue;
1299 progress=0;
cristyfccdab92009-11-30 16:43:57 +00001300 image_view=AcquireCacheView(image);
1301 convolve_view=AcquireCacheView(convolve_image);
1302#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy175653e2011-07-10 23:13:34 +00001303 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristyfccdab92009-11-30 16:43:57 +00001304#endif
cristybb503372010-05-27 20:51:26 +00001305 for (y=0; y < (ssize_t) image->rows; y++)
cristyfccdab92009-11-30 16:43:57 +00001306 {
cristy4c08aed2011-07-01 19:47:50 +00001307 register const Quantum
cristy105ba3c2011-07-18 02:28:38 +00001308 *restrict p;
cristyfccdab92009-11-30 16:43:57 +00001309
cristy4c08aed2011-07-01 19:47:50 +00001310 register Quantum
cristyfccdab92009-11-30 16:43:57 +00001311 *restrict q;
1312
cristy117ff172010-08-15 21:35:32 +00001313 register ssize_t
1314 x;
1315
cristyfccdab92009-11-30 16:43:57 +00001316 if (status == MagickFalse)
1317 continue;
cristy105ba3c2011-07-18 02:28:38 +00001318 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) kernel_info->width/2L),y-
1319 (ssize_t) (kernel_info->height/2L),image->columns+kernel_info->width,
1320 kernel_info->height,exception);
cristy08429172011-07-14 17:18:16 +00001321 q=QueueCacheViewAuthenticPixels(convolve_view,0,y,convolve_image->columns,1,
cristyfccdab92009-11-30 16:43:57 +00001322 exception);
cristy105ba3c2011-07-18 02:28:38 +00001323 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristyfccdab92009-11-30 16:43:57 +00001324 {
1325 status=MagickFalse;
1326 continue;
1327 }
cristybb503372010-05-27 20:51:26 +00001328 for (x=0; x < (ssize_t) image->columns; x++)
cristyfccdab92009-11-30 16:43:57 +00001329 {
cristybb503372010-05-27 20:51:26 +00001330 register ssize_t
cristyed231572011-07-14 02:18:59 +00001331 i;
cristyfccdab92009-11-30 16:43:57 +00001332
cristya30d9ba2011-07-23 21:00:48 +00001333 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristyed231572011-07-14 02:18:59 +00001334 {
cristyed231572011-07-14 02:18:59 +00001335 MagickRealType
cristy4e154852011-07-14 13:28:53 +00001336 alpha,
1337 gamma,
cristyed231572011-07-14 02:18:59 +00001338 pixel;
1339
1340 PixelChannel
1341 channel;
1342
1343 PixelTrait
1344 convolve_traits,
1345 traits;
1346
1347 register const double
1348 *restrict k;
1349
1350 register const Quantum
cristyeb52cde2011-07-17 01:52:52 +00001351 *restrict pixels;
cristyed231572011-07-14 02:18:59 +00001352
1353 register ssize_t
1354 u;
1355
1356 ssize_t
1357 v;
1358
cristy30301712011-07-18 15:06:51 +00001359 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
cristy30301712011-07-18 15:06:51 +00001360 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
cristy4e154852011-07-14 13:28:53 +00001361 convolve_traits=GetPixelChannelMapTraits(convolve_image,channel);
cristy010d7d12011-08-31 01:02:48 +00001362 if ((traits == UndefinedPixelTrait) ||
1363 (convolve_traits == UndefinedPixelTrait))
cristy4e154852011-07-14 13:28:53 +00001364 continue;
1365 if ((convolve_traits & CopyPixelTrait) != 0)
1366 {
cristyf7dc44c2011-07-20 14:41:15 +00001367 q[channel]=p[center+i];
cristy4e154852011-07-14 13:28:53 +00001368 continue;
1369 }
cristy5e6be1e2011-07-16 01:23:39 +00001370 k=kernel_info->values;
cristy105ba3c2011-07-18 02:28:38 +00001371 pixels=p;
cristy0a922382011-07-16 15:30:34 +00001372 pixel=kernel_info->bias;
cristy222b19c2011-08-04 01:35:11 +00001373 if ((convolve_traits & BlendPixelTrait) == 0)
cristyfccdab92009-11-30 16:43:57 +00001374 {
cristyed231572011-07-14 02:18:59 +00001375 /*
cristy4e154852011-07-14 13:28:53 +00001376 No alpha blending.
cristyed231572011-07-14 02:18:59 +00001377 */
cristyeb52cde2011-07-17 01:52:52 +00001378 for (v=0; v < (ssize_t) kernel_info->height; v++)
cristyfccdab92009-11-30 16:43:57 +00001379 {
cristyeb52cde2011-07-17 01:52:52 +00001380 for (u=0; u < (ssize_t) kernel_info->width; u++)
cristy175653e2011-07-10 23:13:34 +00001381 {
cristyeb52cde2011-07-17 01:52:52 +00001382 pixel+=(*k)*pixels[i];
cristyed231572011-07-14 02:18:59 +00001383 k++;
cristya30d9ba2011-07-23 21:00:48 +00001384 pixels+=GetPixelChannels(image);
cristy175653e2011-07-10 23:13:34 +00001385 }
cristya30d9ba2011-07-23 21:00:48 +00001386 pixels+=image->columns*GetPixelChannels(image);
cristy175653e2011-07-10 23:13:34 +00001387 }
cristyf7dc44c2011-07-20 14:41:15 +00001388 q[channel]=ClampToQuantum(pixel);
cristy4e154852011-07-14 13:28:53 +00001389 continue;
cristyed231572011-07-14 02:18:59 +00001390 }
cristy4e154852011-07-14 13:28:53 +00001391 /*
1392 Alpha blending.
1393 */
1394 gamma=0.0;
cristyeb52cde2011-07-17 01:52:52 +00001395 for (v=0; v < (ssize_t) kernel_info->height; v++)
cristy4e154852011-07-14 13:28:53 +00001396 {
cristyeb52cde2011-07-17 01:52:52 +00001397 for (u=0; u < (ssize_t) kernel_info->width; u++)
cristy4e154852011-07-14 13:28:53 +00001398 {
cristyeb52cde2011-07-17 01:52:52 +00001399 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,pixels));
1400 pixel+=(*k)*alpha*pixels[i];
cristy4e154852011-07-14 13:28:53 +00001401 gamma+=(*k)*alpha;
1402 k++;
cristya30d9ba2011-07-23 21:00:48 +00001403 pixels+=GetPixelChannels(image);
cristy4e154852011-07-14 13:28:53 +00001404 }
cristya30d9ba2011-07-23 21:00:48 +00001405 pixels+=image->columns*GetPixelChannels(image);
cristy4e154852011-07-14 13:28:53 +00001406 }
cristy1ce96d02011-07-14 17:57:24 +00001407 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristye7a41c92011-07-20 21:31:01 +00001408 q[channel]=ClampToQuantum(gamma*pixel);
cristyed231572011-07-14 02:18:59 +00001409 }
cristya30d9ba2011-07-23 21:00:48 +00001410 p+=GetPixelChannels(image);
1411 q+=GetPixelChannels(convolve_image);
cristyfccdab92009-11-30 16:43:57 +00001412 }
cristyed231572011-07-14 02:18:59 +00001413 if (SyncCacheViewAuthenticPixels(convolve_view,exception) == MagickFalse)
cristyfccdab92009-11-30 16:43:57 +00001414 status=MagickFalse;
1415 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1416 {
1417 MagickBooleanType
1418 proceed;
1419
1420#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00001421 #pragma omp critical (MagickCore_ConvolveImage)
cristyfccdab92009-11-30 16:43:57 +00001422#endif
1423 proceed=SetImageProgress(image,ConvolveImageTag,progress++,image->rows);
1424 if (proceed == MagickFalse)
1425 status=MagickFalse;
1426 }
1427 }
1428 convolve_image->type=image->type;
1429 convolve_view=DestroyCacheView(convolve_view);
1430 image_view=DestroyCacheView(image_view);
cristyfccdab92009-11-30 16:43:57 +00001431 if (status == MagickFalse)
1432 convolve_image=DestroyImage(convolve_image);
1433 return(convolve_image);
1434}
1435
1436/*
1437%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1438% %
1439% %
1440% %
cristy3ed852e2009-09-05 21:47:34 +00001441% D e s p e c k l e I m a g e %
1442% %
1443% %
1444% %
1445%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1446%
1447% DespeckleImage() reduces the speckle noise in an image while perserving the
1448% edges of the original image.
1449%
1450% The format of the DespeckleImage method is:
1451%
1452% Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1453%
1454% A description of each parameter follows:
1455%
1456% o image: the image.
1457%
1458% o exception: return any errors or warnings in this structure.
1459%
1460*/
1461
cristybb503372010-05-27 20:51:26 +00001462static void Hull(const ssize_t x_offset,const ssize_t y_offset,
1463 const size_t columns,const size_t rows,Quantum *f,Quantum *g,
cristy3ed852e2009-09-05 21:47:34 +00001464 const int polarity)
1465{
cristy3ed852e2009-09-05 21:47:34 +00001466 MagickRealType
1467 v;
1468
cristy3ed852e2009-09-05 21:47:34 +00001469 register Quantum
1470 *p,
1471 *q,
1472 *r,
1473 *s;
1474
cristy117ff172010-08-15 21:35:32 +00001475 register ssize_t
1476 x;
1477
1478 ssize_t
1479 y;
1480
cristy3ed852e2009-09-05 21:47:34 +00001481 assert(f != (Quantum *) NULL);
1482 assert(g != (Quantum *) NULL);
1483 p=f+(columns+2);
1484 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001485 r=p+(y_offset*((ssize_t) columns+2)+x_offset);
1486 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001487 {
1488 p++;
1489 q++;
1490 r++;
1491 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001492 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001493 {
1494 v=(MagickRealType) (*p);
1495 if ((MagickRealType) *r >= (v+(MagickRealType) ScaleCharToQuantum(2)))
1496 v+=ScaleCharToQuantum(1);
1497 *q=(Quantum) v;
1498 p++;
1499 q++;
1500 r++;
1501 }
1502 else
cristybb503372010-05-27 20:51:26 +00001503 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001504 {
1505 v=(MagickRealType) (*p);
1506 if ((MagickRealType) *r <= (v-(MagickRealType) ScaleCharToQuantum(2)))
cristybb503372010-05-27 20:51:26 +00001507 v-=(ssize_t) ScaleCharToQuantum(1);
cristy3ed852e2009-09-05 21:47:34 +00001508 *q=(Quantum) v;
1509 p++;
1510 q++;
1511 r++;
1512 }
1513 p++;
1514 q++;
1515 r++;
1516 }
1517 p=f+(columns+2);
1518 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001519 r=q+(y_offset*((ssize_t) columns+2)+x_offset);
1520 s=q-(y_offset*((ssize_t) columns+2)+x_offset);
1521 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001522 {
1523 p++;
1524 q++;
1525 r++;
1526 s++;
1527 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001528 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001529 {
1530 v=(MagickRealType) (*q);
1531 if (((MagickRealType) *s >=
1532 (v+(MagickRealType) ScaleCharToQuantum(2))) &&
1533 ((MagickRealType) *r > v))
1534 v+=ScaleCharToQuantum(1);
1535 *p=(Quantum) v;
1536 p++;
1537 q++;
1538 r++;
1539 s++;
1540 }
1541 else
cristybb503372010-05-27 20:51:26 +00001542 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001543 {
1544 v=(MagickRealType) (*q);
1545 if (((MagickRealType) *s <=
1546 (v-(MagickRealType) ScaleCharToQuantum(2))) &&
1547 ((MagickRealType) *r < v))
1548 v-=(MagickRealType) ScaleCharToQuantum(1);
1549 *p=(Quantum) v;
1550 p++;
1551 q++;
1552 r++;
1553 s++;
1554 }
1555 p++;
1556 q++;
1557 r++;
1558 s++;
1559 }
1560}
1561
1562MagickExport Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1563{
1564#define DespeckleImageTag "Despeckle/Image"
1565
cristy2407fc22009-09-11 00:55:25 +00001566 CacheView
1567 *despeckle_view,
1568 *image_view;
1569
cristy3ed852e2009-09-05 21:47:34 +00001570 Image
1571 *despeckle_image;
1572
cristy3ed852e2009-09-05 21:47:34 +00001573 MagickBooleanType
1574 status;
1575
cristya58c3172011-02-19 19:23:11 +00001576 register ssize_t
1577 i;
1578
cristy3ed852e2009-09-05 21:47:34 +00001579 Quantum
cristy65b9f392011-02-22 14:22:54 +00001580 *restrict buffers,
1581 *restrict pixels;
cristy3ed852e2009-09-05 21:47:34 +00001582
1583 size_t
cristya58c3172011-02-19 19:23:11 +00001584 length,
1585 number_channels;
cristy117ff172010-08-15 21:35:32 +00001586
cristybb503372010-05-27 20:51:26 +00001587 static const ssize_t
cristy691a29e2009-09-11 00:44:10 +00001588 X[4] = {0, 1, 1,-1},
1589 Y[4] = {1, 0, 1, 1};
cristy3ed852e2009-09-05 21:47:34 +00001590
cristy3ed852e2009-09-05 21:47:34 +00001591 /*
1592 Allocate despeckled image.
1593 */
1594 assert(image != (const Image *) NULL);
1595 assert(image->signature == MagickSignature);
1596 if (image->debug != MagickFalse)
1597 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1598 assert(exception != (ExceptionInfo *) NULL);
1599 assert(exception->signature == MagickSignature);
1600 despeckle_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1601 exception);
1602 if (despeckle_image == (Image *) NULL)
1603 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00001604 if (SetImageStorageClass(despeckle_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001605 {
cristy3ed852e2009-09-05 21:47:34 +00001606 despeckle_image=DestroyImage(despeckle_image);
1607 return((Image *) NULL);
1608 }
1609 /*
1610 Allocate image buffers.
1611 */
1612 length=(size_t) ((image->columns+2)*(image->rows+2));
cristy65b9f392011-02-22 14:22:54 +00001613 pixels=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1614 buffers=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1615 if ((pixels == (Quantum *) NULL) || (buffers == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001616 {
cristy65b9f392011-02-22 14:22:54 +00001617 if (buffers != (Quantum *) NULL)
1618 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1619 if (pixels != (Quantum *) NULL)
1620 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001621 despeckle_image=DestroyImage(despeckle_image);
1622 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1623 }
1624 /*
1625 Reduce speckle in the image.
1626 */
1627 status=MagickTrue;
cristy109695a2011-02-19 19:38:14 +00001628 number_channels=(size_t) (image->colorspace == CMYKColorspace ? 5 : 4);
cristy3ed852e2009-09-05 21:47:34 +00001629 image_view=AcquireCacheView(image);
1630 despeckle_view=AcquireCacheView(despeckle_image);
cristy8df3d002011-02-19 19:40:59 +00001631 for (i=0; i < (ssize_t) number_channels; i++)
cristy3ed852e2009-09-05 21:47:34 +00001632 {
cristy3ed852e2009-09-05 21:47:34 +00001633 register Quantum
1634 *buffer,
1635 *pixel;
1636
cristyc1488b52011-02-19 18:54:15 +00001637 register ssize_t
cristya58c3172011-02-19 19:23:11 +00001638 k,
cristyc1488b52011-02-19 18:54:15 +00001639 x;
1640
cristy117ff172010-08-15 21:35:32 +00001641 ssize_t
1642 j,
1643 y;
1644
cristy3ed852e2009-09-05 21:47:34 +00001645 if (status == MagickFalse)
1646 continue;
cristy65b9f392011-02-22 14:22:54 +00001647 pixel=pixels;
cristy3ed852e2009-09-05 21:47:34 +00001648 (void) ResetMagickMemory(pixel,0,length*sizeof(*pixel));
cristy65b9f392011-02-22 14:22:54 +00001649 buffer=buffers;
cristybb503372010-05-27 20:51:26 +00001650 j=(ssize_t) image->columns+2;
1651 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001652 {
cristy4c08aed2011-07-01 19:47:50 +00001653 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001654 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001655
1656 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001657 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001658 break;
1659 j++;
cristybb503372010-05-27 20:51:26 +00001660 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001661 {
cristya58c3172011-02-19 19:23:11 +00001662 switch (i)
cristy3ed852e2009-09-05 21:47:34 +00001663 {
cristy4c08aed2011-07-01 19:47:50 +00001664 case 0: pixel[j]=GetPixelRed(image,p); break;
1665 case 1: pixel[j]=GetPixelGreen(image,p); break;
1666 case 2: pixel[j]=GetPixelBlue(image,p); break;
1667 case 3: pixel[j]=GetPixelAlpha(image,p); break;
1668 case 4: pixel[j]=GetPixelBlack(image,p); break;
cristy3ed852e2009-09-05 21:47:34 +00001669 default: break;
1670 }
cristyed231572011-07-14 02:18:59 +00001671 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001672 j++;
1673 }
1674 j++;
1675 }
cristy3ed852e2009-09-05 21:47:34 +00001676 (void) ResetMagickMemory(buffer,0,length*sizeof(*buffer));
cristya58c3172011-02-19 19:23:11 +00001677 for (k=0; k < 4; k++)
cristy3ed852e2009-09-05 21:47:34 +00001678 {
cristya58c3172011-02-19 19:23:11 +00001679 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,1);
1680 Hull(-X[k],-Y[k],image->columns,image->rows,pixel,buffer,1);
1681 Hull(-X[k],-Y[k],image->columns,image->rows,pixel,buffer,-1);
1682 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,-1);
cristy3ed852e2009-09-05 21:47:34 +00001683 }
cristybb503372010-05-27 20:51:26 +00001684 j=(ssize_t) image->columns+2;
1685 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001686 {
1687 MagickBooleanType
1688 sync;
1689
cristy4c08aed2011-07-01 19:47:50 +00001690 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001691 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001692
1693 q=GetCacheViewAuthenticPixels(despeckle_view,0,y,despeckle_image->columns,
1694 1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001695 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001696 break;
1697 j++;
cristybb503372010-05-27 20:51:26 +00001698 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001699 {
cristya58c3172011-02-19 19:23:11 +00001700 switch (i)
cristy3ed852e2009-09-05 21:47:34 +00001701 {
cristy4c08aed2011-07-01 19:47:50 +00001702 case 0: SetPixelRed(despeckle_image,pixel[j],q); break;
1703 case 1: SetPixelGreen(despeckle_image,pixel[j],q); break;
1704 case 2: SetPixelBlue(despeckle_image,pixel[j],q); break;
1705 case 3: SetPixelAlpha(despeckle_image,pixel[j],q); break;
1706 case 4: SetPixelBlack(despeckle_image,pixel[j],q); break;
cristy3ed852e2009-09-05 21:47:34 +00001707 default: break;
1708 }
cristyed231572011-07-14 02:18:59 +00001709 q+=GetPixelChannels(despeckle_image);
cristy3ed852e2009-09-05 21:47:34 +00001710 j++;
1711 }
1712 sync=SyncCacheViewAuthenticPixels(despeckle_view,exception);
1713 if (sync == MagickFalse)
1714 {
1715 status=MagickFalse;
1716 break;
1717 }
1718 j++;
1719 }
1720 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1721 {
1722 MagickBooleanType
1723 proceed;
1724
cristya58c3172011-02-19 19:23:11 +00001725 proceed=SetImageProgress(image,DespeckleImageTag,(MagickOffsetType) i,
1726 number_channels);
cristy3ed852e2009-09-05 21:47:34 +00001727 if (proceed == MagickFalse)
1728 status=MagickFalse;
1729 }
1730 }
1731 despeckle_view=DestroyCacheView(despeckle_view);
1732 image_view=DestroyCacheView(image_view);
cristy65b9f392011-02-22 14:22:54 +00001733 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1734 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001735 despeckle_image->type=image->type;
1736 if (status == MagickFalse)
1737 despeckle_image=DestroyImage(despeckle_image);
1738 return(despeckle_image);
1739}
1740
1741/*
1742%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1743% %
1744% %
1745% %
1746% E d g e I m a g e %
1747% %
1748% %
1749% %
1750%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1751%
1752% EdgeImage() finds edges in an image. Radius defines the radius of the
1753% convolution filter. Use a radius of 0 and EdgeImage() selects a suitable
1754% radius for you.
1755%
1756% The format of the EdgeImage method is:
1757%
1758% Image *EdgeImage(const Image *image,const double radius,
cristy8ae632d2011-09-05 17:29:53 +00001759% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001760%
1761% A description of each parameter follows:
1762%
1763% o image: the image.
1764%
1765% o radius: the radius of the pixel neighborhood.
1766%
cristy8ae632d2011-09-05 17:29:53 +00001767% o sigma: the standard deviation of the Gaussian, in pixels.
1768%
cristy3ed852e2009-09-05 21:47:34 +00001769% o exception: return any errors or warnings in this structure.
1770%
1771*/
1772MagickExport Image *EdgeImage(const Image *image,const double radius,
cristy8ae632d2011-09-05 17:29:53 +00001773 const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001774{
1775 Image
1776 *edge_image;
1777
cristy41cbe682011-07-15 19:12:37 +00001778 KernelInfo
1779 *kernel_info;
cristy3ed852e2009-09-05 21:47:34 +00001780
cristybb503372010-05-27 20:51:26 +00001781 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001782 i;
1783
cristybb503372010-05-27 20:51:26 +00001784 size_t
cristy3ed852e2009-09-05 21:47:34 +00001785 width;
1786
cristy41cbe682011-07-15 19:12:37 +00001787 ssize_t
1788 j,
1789 u,
1790 v;
1791
cristy3ed852e2009-09-05 21:47:34 +00001792 assert(image != (const Image *) NULL);
1793 assert(image->signature == MagickSignature);
1794 if (image->debug != MagickFalse)
1795 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1796 assert(exception != (ExceptionInfo *) NULL);
1797 assert(exception->signature == MagickSignature);
cristy8ae632d2011-09-05 17:29:53 +00001798 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00001799 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00001800 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001801 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00001802 kernel_info->width=width;
1803 kernel_info->height=width;
cristy41cbe682011-07-15 19:12:37 +00001804 kernel_info->values=(double *) AcquireAlignedMemory(kernel_info->width,
1805 kernel_info->width*sizeof(*kernel_info->values));
1806 if (kernel_info->values == (double *) NULL)
1807 {
1808 kernel_info=DestroyKernelInfo(kernel_info);
1809 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1810 }
1811 j=(ssize_t) kernel_info->width/2;
1812 i=0;
1813 for (v=(-j); v <= j; v++)
1814 {
1815 for (u=(-j); u <= j; u++)
1816 {
1817 kernel_info->values[i]=(-1.0);
1818 i++;
1819 }
1820 }
1821 kernel_info->values[i/2]=(double) (width*width-1.0);
cristy0a922382011-07-16 15:30:34 +00001822 kernel_info->bias=image->bias;
cristy5e6be1e2011-07-16 01:23:39 +00001823 edge_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00001824 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00001825 return(edge_image);
1826}
1827
1828/*
1829%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1830% %
1831% %
1832% %
1833% E m b o s s I m a g e %
1834% %
1835% %
1836% %
1837%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1838%
1839% EmbossImage() returns a grayscale image with a three-dimensional effect.
1840% We convolve the image with a Gaussian operator of the given radius and
1841% standard deviation (sigma). For reasonable results, radius should be
1842% larger than sigma. Use a radius of 0 and Emboss() selects a suitable
1843% radius for you.
1844%
1845% The format of the EmbossImage method is:
1846%
1847% Image *EmbossImage(const Image *image,const double radius,
1848% const double sigma,ExceptionInfo *exception)
1849%
1850% A description of each parameter follows:
1851%
1852% o image: the image.
1853%
1854% o radius: the radius of the pixel neighborhood.
1855%
1856% o sigma: the standard deviation of the Gaussian, in pixels.
1857%
1858% o exception: return any errors or warnings in this structure.
1859%
1860*/
1861MagickExport Image *EmbossImage(const Image *image,const double radius,
1862 const double sigma,ExceptionInfo *exception)
1863{
cristy3ed852e2009-09-05 21:47:34 +00001864 Image
1865 *emboss_image;
1866
cristy41cbe682011-07-15 19:12:37 +00001867 KernelInfo
1868 *kernel_info;
1869
cristybb503372010-05-27 20:51:26 +00001870 register ssize_t
cristy47e00502009-12-17 19:19:57 +00001871 i;
1872
cristybb503372010-05-27 20:51:26 +00001873 size_t
cristy3ed852e2009-09-05 21:47:34 +00001874 width;
1875
cristy117ff172010-08-15 21:35:32 +00001876 ssize_t
1877 j,
1878 k,
1879 u,
1880 v;
1881
cristy41cbe682011-07-15 19:12:37 +00001882 assert(image != (const Image *) NULL);
cristy3ed852e2009-09-05 21:47:34 +00001883 assert(image->signature == MagickSignature);
1884 if (image->debug != MagickFalse)
1885 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1886 assert(exception != (ExceptionInfo *) NULL);
1887 assert(exception->signature == MagickSignature);
1888 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00001889 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00001890 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001891 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00001892 kernel_info->width=width;
1893 kernel_info->height=width;
cristy41cbe682011-07-15 19:12:37 +00001894 kernel_info->values=(double *) AcquireAlignedMemory(kernel_info->width,
1895 kernel_info->width*sizeof(*kernel_info->values));
1896 if (kernel_info->values == (double *) NULL)
1897 {
1898 kernel_info=DestroyKernelInfo(kernel_info);
1899 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1900 }
1901 j=(ssize_t) kernel_info->width/2;
cristy47e00502009-12-17 19:19:57 +00001902 k=j;
1903 i=0;
1904 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00001905 {
cristy47e00502009-12-17 19:19:57 +00001906 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00001907 {
cristy41cbe682011-07-15 19:12:37 +00001908 kernel_info->values[i]=(double) (((u < 0) || (v < 0) ? -8.0 : 8.0)*
cristy47e00502009-12-17 19:19:57 +00001909 exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
cristy4205a3c2010-09-12 20:19:59 +00001910 (2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +00001911 if (u != k)
cristy41cbe682011-07-15 19:12:37 +00001912 kernel_info->values[i]=0.0;
cristy3ed852e2009-09-05 21:47:34 +00001913 i++;
1914 }
cristy47e00502009-12-17 19:19:57 +00001915 k--;
cristy3ed852e2009-09-05 21:47:34 +00001916 }
cristy0a922382011-07-16 15:30:34 +00001917 kernel_info->bias=image->bias;
cristy5e6be1e2011-07-16 01:23:39 +00001918 emboss_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00001919 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00001920 if (emboss_image != (Image *) NULL)
cristy6d8c3d72011-08-22 01:20:01 +00001921 (void) EqualizeImage(emboss_image,exception);
cristy3ed852e2009-09-05 21:47:34 +00001922 return(emboss_image);
1923}
1924
1925/*
1926%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1927% %
1928% %
1929% %
1930% G a u s s i a n B l u r I m a g e %
1931% %
1932% %
1933% %
1934%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1935%
1936% GaussianBlurImage() blurs an image. We convolve the image with a
1937% Gaussian operator of the given radius and standard deviation (sigma).
1938% For reasonable results, the radius should be larger than sigma. Use a
1939% radius of 0 and GaussianBlurImage() selects a suitable radius for you
1940%
1941% The format of the GaussianBlurImage method is:
1942%
1943% Image *GaussianBlurImage(const Image *image,onst double radius,
cristy05c0c9a2011-09-05 23:16:13 +00001944% const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001945%
1946% A description of each parameter follows:
1947%
1948% o image: the image.
1949%
cristy3ed852e2009-09-05 21:47:34 +00001950% o radius: the radius of the Gaussian, in pixels, not counting the center
1951% pixel.
1952%
1953% o sigma: the standard deviation of the Gaussian, in pixels.
1954%
cristy05c0c9a2011-09-05 23:16:13 +00001955% o bias: the bias.
1956%
cristy3ed852e2009-09-05 21:47:34 +00001957% o exception: return any errors or warnings in this structure.
1958%
1959*/
cristy41cbe682011-07-15 19:12:37 +00001960MagickExport Image *GaussianBlurImage(const Image *image,const double radius,
cristy05c0c9a2011-09-05 23:16:13 +00001961 const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001962{
cristy3ed852e2009-09-05 21:47:34 +00001963 Image
1964 *blur_image;
1965
cristy41cbe682011-07-15 19:12:37 +00001966 KernelInfo
1967 *kernel_info;
1968
cristybb503372010-05-27 20:51:26 +00001969 register ssize_t
cristy47e00502009-12-17 19:19:57 +00001970 i;
1971
cristybb503372010-05-27 20:51:26 +00001972 size_t
cristy3ed852e2009-09-05 21:47:34 +00001973 width;
1974
cristy117ff172010-08-15 21:35:32 +00001975 ssize_t
1976 j,
1977 u,
1978 v;
1979
cristy3ed852e2009-09-05 21:47:34 +00001980 assert(image != (const Image *) NULL);
1981 assert(image->signature == MagickSignature);
1982 if (image->debug != MagickFalse)
1983 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1984 assert(exception != (ExceptionInfo *) NULL);
1985 assert(exception->signature == MagickSignature);
1986 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00001987 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00001988 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001989 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00001990 (void) ResetMagickMemory(kernel_info,0,sizeof(*kernel_info));
1991 kernel_info->width=width;
1992 kernel_info->height=width;
cristy05c0c9a2011-09-05 23:16:13 +00001993 kernel_info->bias=bias;
cristy41cbe682011-07-15 19:12:37 +00001994 kernel_info->signature=MagickSignature;
1995 kernel_info->values=(double *) AcquireAlignedMemory(kernel_info->width,
1996 kernel_info->width*sizeof(*kernel_info->values));
1997 if (kernel_info->values == (double *) NULL)
1998 {
1999 kernel_info=DestroyKernelInfo(kernel_info);
2000 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2001 }
2002 j=(ssize_t) kernel_info->width/2;
cristy3ed852e2009-09-05 21:47:34 +00002003 i=0;
cristy47e00502009-12-17 19:19:57 +00002004 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002005 {
cristy47e00502009-12-17 19:19:57 +00002006 for (u=(-j); u <= j; u++)
cristy41cbe682011-07-15 19:12:37 +00002007 {
2008 kernel_info->values[i]=(double) (exp(-((double) u*u+v*v)/(2.0*
2009 MagickSigma*MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
2010 i++;
2011 }
cristy3ed852e2009-09-05 21:47:34 +00002012 }
cristy5e6be1e2011-07-16 01:23:39 +00002013 blur_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00002014 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00002015 return(blur_image);
2016}
2017
2018/*
2019%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2020% %
2021% %
2022% %
cristy3ed852e2009-09-05 21:47:34 +00002023% M o t i o n B l u r I m a g e %
2024% %
2025% %
2026% %
2027%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2028%
2029% MotionBlurImage() simulates motion blur. We convolve the image with a
2030% Gaussian operator of the given radius and standard deviation (sigma).
2031% For reasonable results, radius should be larger than sigma. Use a
2032% radius of 0 and MotionBlurImage() selects a suitable radius for you.
2033% Angle gives the angle of the blurring motion.
2034%
2035% Andrew Protano contributed this effect.
2036%
2037% The format of the MotionBlurImage method is:
2038%
2039% Image *MotionBlurImage(const Image *image,const double radius,
2040% const double sigma,const double angle,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002041%
2042% A description of each parameter follows:
2043%
2044% o image: the image.
2045%
cristy3ed852e2009-09-05 21:47:34 +00002046% o radius: the radius of the Gaussian, in pixels, not counting
2047% the center pixel.
2048%
2049% o sigma: the standard deviation of the Gaussian, in pixels.
2050%
cristycee97112010-05-28 00:44:52 +00002051% o angle: Apply the effect along this angle.
cristy3ed852e2009-09-05 21:47:34 +00002052%
2053% o exception: return any errors or warnings in this structure.
2054%
2055*/
2056
cristybb503372010-05-27 20:51:26 +00002057static double *GetMotionBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +00002058{
cristy3ed852e2009-09-05 21:47:34 +00002059 double
cristy47e00502009-12-17 19:19:57 +00002060 *kernel,
cristy3ed852e2009-09-05 21:47:34 +00002061 normalize;
2062
cristybb503372010-05-27 20:51:26 +00002063 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002064 i;
2065
2066 /*
cristy47e00502009-12-17 19:19:57 +00002067 Generate a 1-D convolution kernel.
cristy3ed852e2009-09-05 21:47:34 +00002068 */
2069 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
2070 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
2071 if (kernel == (double *) NULL)
2072 return(kernel);
cristy3ed852e2009-09-05 21:47:34 +00002073 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00002074 for (i=0; i < (ssize_t) width; i++)
cristy47e00502009-12-17 19:19:57 +00002075 {
cristy4205a3c2010-09-12 20:19:59 +00002076 kernel[i]=(double) (exp((-((double) i*i)/(double) (2.0*MagickSigma*
2077 MagickSigma)))/(MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00002078 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +00002079 }
cristybb503372010-05-27 20:51:26 +00002080 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002081 kernel[i]/=normalize;
2082 return(kernel);
2083}
2084
cristyf4ad9df2011-07-08 16:49:03 +00002085MagickExport Image *MotionBlurImage(const Image *image,
2086 const double radius,const double sigma,const double angle,
2087 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002088{
cristyc4c8d132010-01-07 01:58:38 +00002089 CacheView
2090 *blur_view,
2091 *image_view;
2092
cristy3ed852e2009-09-05 21:47:34 +00002093 double
2094 *kernel;
2095
2096 Image
2097 *blur_image;
2098
cristy3ed852e2009-09-05 21:47:34 +00002099 MagickBooleanType
2100 status;
2101
cristybb503372010-05-27 20:51:26 +00002102 MagickOffsetType
2103 progress;
2104
cristy4c08aed2011-07-01 19:47:50 +00002105 PixelInfo
cristyddd82202009-11-03 20:14:50 +00002106 bias;
cristy3ed852e2009-09-05 21:47:34 +00002107
2108 OffsetInfo
2109 *offset;
2110
2111 PointInfo
2112 point;
2113
cristybb503372010-05-27 20:51:26 +00002114 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002115 i;
2116
cristybb503372010-05-27 20:51:26 +00002117 size_t
cristy3ed852e2009-09-05 21:47:34 +00002118 width;
2119
cristybb503372010-05-27 20:51:26 +00002120 ssize_t
2121 y;
2122
cristy3ed852e2009-09-05 21:47:34 +00002123 assert(image != (Image *) NULL);
2124 assert(image->signature == MagickSignature);
2125 if (image->debug != MagickFalse)
2126 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2127 assert(exception != (ExceptionInfo *) NULL);
2128 width=GetOptimalKernelWidth1D(radius,sigma);
2129 kernel=GetMotionBlurKernel(width,sigma);
2130 if (kernel == (double *) NULL)
2131 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2132 offset=(OffsetInfo *) AcquireQuantumMemory(width,sizeof(*offset));
2133 if (offset == (OffsetInfo *) NULL)
2134 {
2135 kernel=(double *) RelinquishMagickMemory(kernel);
2136 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2137 }
2138 blur_image=CloneImage(image,0,0,MagickTrue,exception);
2139 if (blur_image == (Image *) NULL)
2140 {
2141 kernel=(double *) RelinquishMagickMemory(kernel);
2142 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2143 return((Image *) NULL);
2144 }
cristy574cc262011-08-05 01:23:58 +00002145 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00002146 {
2147 kernel=(double *) RelinquishMagickMemory(kernel);
2148 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
cristy3ed852e2009-09-05 21:47:34 +00002149 blur_image=DestroyImage(blur_image);
2150 return((Image *) NULL);
2151 }
2152 point.x=(double) width*sin(DegreesToRadians(angle));
2153 point.y=(double) width*cos(DegreesToRadians(angle));
cristybb503372010-05-27 20:51:26 +00002154 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002155 {
cristybb503372010-05-27 20:51:26 +00002156 offset[i].x=(ssize_t) ceil((double) (i*point.y)/hypot(point.x,point.y)-0.5);
2157 offset[i].y=(ssize_t) ceil((double) (i*point.x)/hypot(point.x,point.y)-0.5);
cristy3ed852e2009-09-05 21:47:34 +00002158 }
2159 /*
2160 Motion blur image.
2161 */
2162 status=MagickTrue;
2163 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00002164 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00002165 image_view=AcquireCacheView(image);
2166 blur_view=AcquireCacheView(blur_image);
cristyb557a152011-02-22 12:14:30 +00002167#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00002168 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00002169#endif
cristybb503372010-05-27 20:51:26 +00002170 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002171 {
cristy4c08aed2011-07-01 19:47:50 +00002172 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002173 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002174
cristy117ff172010-08-15 21:35:32 +00002175 register ssize_t
2176 x;
2177
cristy3ed852e2009-09-05 21:47:34 +00002178 if (status == MagickFalse)
2179 continue;
2180 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
2181 exception);
cristyacd2ed22011-08-30 01:44:23 +00002182 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002183 {
2184 status=MagickFalse;
2185 continue;
2186 }
cristybb503372010-05-27 20:51:26 +00002187 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002188 {
cristy4c08aed2011-07-01 19:47:50 +00002189 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002190 qixel;
2191
2192 PixelPacket
2193 pixel;
2194
2195 register double
cristyc47d1f82009-11-26 01:44:43 +00002196 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00002197
cristybb503372010-05-27 20:51:26 +00002198 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002199 i;
2200
cristy3ed852e2009-09-05 21:47:34 +00002201 k=kernel;
cristyddd82202009-11-03 20:14:50 +00002202 qixel=bias;
cristyed231572011-07-14 02:18:59 +00002203 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00002204 {
cristybb503372010-05-27 20:51:26 +00002205 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002206 {
2207 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
2208 offset[i].y,&pixel,exception);
2209 qixel.red+=(*k)*pixel.red;
2210 qixel.green+=(*k)*pixel.green;
2211 qixel.blue+=(*k)*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00002212 qixel.alpha+=(*k)*pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00002213 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00002214 qixel.black+=(*k)*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00002215 k++;
2216 }
cristyed231572011-07-14 02:18:59 +00002217 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002218 SetPixelRed(blur_image,
2219 ClampToQuantum(qixel.red),q);
cristyed231572011-07-14 02:18:59 +00002220 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002221 SetPixelGreen(blur_image,
2222 ClampToQuantum(qixel.green),q);
cristyed231572011-07-14 02:18:59 +00002223 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002224 SetPixelBlue(blur_image,
2225 ClampToQuantum(qixel.blue),q);
cristyed231572011-07-14 02:18:59 +00002226 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002227 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002228 SetPixelBlack(blur_image,
2229 ClampToQuantum(qixel.black),q);
cristyed231572011-07-14 02:18:59 +00002230 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002231 SetPixelAlpha(blur_image,
2232 ClampToQuantum(qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00002233 }
2234 else
2235 {
2236 MagickRealType
2237 alpha,
2238 gamma;
2239
2240 alpha=0.0;
2241 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00002242 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002243 {
2244 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
2245 offset[i].y,&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00002246 alpha=(MagickRealType) (QuantumScale*pixel.alpha);
cristy3ed852e2009-09-05 21:47:34 +00002247 qixel.red+=(*k)*alpha*pixel.red;
2248 qixel.green+=(*k)*alpha*pixel.green;
2249 qixel.blue+=(*k)*alpha*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00002250 qixel.alpha+=(*k)*pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00002251 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00002252 qixel.black+=(*k)*alpha*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00002253 gamma+=(*k)*alpha;
2254 k++;
2255 }
2256 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +00002257 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002258 SetPixelRed(blur_image,
2259 ClampToQuantum(gamma*qixel.red),q);
cristyed231572011-07-14 02:18:59 +00002260 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002261 SetPixelGreen(blur_image,
2262 ClampToQuantum(gamma*qixel.green),q);
cristyed231572011-07-14 02:18:59 +00002263 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002264 SetPixelBlue(blur_image,
2265 ClampToQuantum(gamma*qixel.blue),q);
cristyed231572011-07-14 02:18:59 +00002266 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002267 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002268 SetPixelBlack(blur_image,
2269 ClampToQuantum(gamma*qixel.black),q);
cristyed231572011-07-14 02:18:59 +00002270 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002271 SetPixelAlpha(blur_image,
2272 ClampToQuantum(qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00002273 }
cristyed231572011-07-14 02:18:59 +00002274 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00002275 }
2276 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
2277 status=MagickFalse;
2278 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2279 {
2280 MagickBooleanType
2281 proceed;
2282
cristyb557a152011-02-22 12:14:30 +00002283#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00002284 #pragma omp critical (MagickCore_MotionBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00002285#endif
2286 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
2287 if (proceed == MagickFalse)
2288 status=MagickFalse;
2289 }
2290 }
2291 blur_view=DestroyCacheView(blur_view);
2292 image_view=DestroyCacheView(image_view);
2293 kernel=(double *) RelinquishMagickMemory(kernel);
2294 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2295 if (status == MagickFalse)
2296 blur_image=DestroyImage(blur_image);
2297 return(blur_image);
2298}
2299
2300/*
2301%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2302% %
2303% %
2304% %
2305% P r e v i e w I m a g e %
2306% %
2307% %
2308% %
2309%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2310%
2311% PreviewImage() tiles 9 thumbnails of the specified image with an image
2312% processing operation applied with varying parameters. This may be helpful
2313% pin-pointing an appropriate parameter for a particular image processing
2314% operation.
2315%
2316% The format of the PreviewImages method is:
2317%
2318% Image *PreviewImages(const Image *image,const PreviewType preview,
2319% ExceptionInfo *exception)
2320%
2321% A description of each parameter follows:
2322%
2323% o image: the image.
2324%
2325% o preview: the image processing operation.
2326%
2327% o exception: return any errors or warnings in this structure.
2328%
2329*/
2330MagickExport Image *PreviewImage(const Image *image,const PreviewType preview,
2331 ExceptionInfo *exception)
2332{
2333#define NumberTiles 9
2334#define PreviewImageTag "Preview/Image"
2335#define DefaultPreviewGeometry "204x204+10+10"
2336
2337 char
2338 factor[MaxTextExtent],
2339 label[MaxTextExtent];
2340
2341 double
2342 degrees,
2343 gamma,
2344 percentage,
2345 radius,
2346 sigma,
2347 threshold;
2348
2349 Image
2350 *images,
2351 *montage_image,
2352 *preview_image,
2353 *thumbnail;
2354
2355 ImageInfo
2356 *preview_info;
2357
cristy3ed852e2009-09-05 21:47:34 +00002358 MagickBooleanType
2359 proceed;
2360
2361 MontageInfo
2362 *montage_info;
2363
2364 QuantizeInfo
2365 quantize_info;
2366
2367 RectangleInfo
2368 geometry;
2369
cristybb503372010-05-27 20:51:26 +00002370 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002371 i,
2372 x;
2373
cristybb503372010-05-27 20:51:26 +00002374 size_t
cristy3ed852e2009-09-05 21:47:34 +00002375 colors;
2376
cristy117ff172010-08-15 21:35:32 +00002377 ssize_t
2378 y;
2379
cristy3ed852e2009-09-05 21:47:34 +00002380 /*
2381 Open output image file.
2382 */
2383 assert(image != (Image *) NULL);
2384 assert(image->signature == MagickSignature);
2385 if (image->debug != MagickFalse)
2386 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2387 colors=2;
2388 degrees=0.0;
2389 gamma=(-0.2f);
2390 preview_info=AcquireImageInfo();
2391 SetGeometry(image,&geometry);
2392 (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y,
2393 &geometry.width,&geometry.height);
2394 images=NewImageList();
2395 percentage=12.5;
2396 GetQuantizeInfo(&quantize_info);
2397 radius=0.0;
2398 sigma=1.0;
2399 threshold=0.0;
2400 x=0;
2401 y=0;
2402 for (i=0; i < NumberTiles; i++)
2403 {
2404 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
2405 if (thumbnail == (Image *) NULL)
2406 break;
2407 (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL,
2408 (void *) NULL);
2409 (void) SetImageProperty(thumbnail,"label",DefaultTileLabel);
2410 if (i == (NumberTiles/2))
2411 {
2412 (void) QueryColorDatabase("#dfdfdf",&thumbnail->matte_color,exception);
2413 AppendImageToList(&images,thumbnail);
2414 continue;
2415 }
2416 switch (preview)
2417 {
2418 case RotatePreview:
2419 {
2420 degrees+=45.0;
2421 preview_image=RotateImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002422 (void) FormatLocaleString(label,MaxTextExtent,"rotate %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002423 break;
2424 }
2425 case ShearPreview:
2426 {
2427 degrees+=5.0;
2428 preview_image=ShearImage(thumbnail,degrees,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002429 (void) FormatLocaleString(label,MaxTextExtent,"shear %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002430 degrees,2.0*degrees);
2431 break;
2432 }
2433 case RollPreview:
2434 {
cristybb503372010-05-27 20:51:26 +00002435 x=(ssize_t) ((i+1)*thumbnail->columns)/NumberTiles;
2436 y=(ssize_t) ((i+1)*thumbnail->rows)/NumberTiles;
cristy3ed852e2009-09-05 21:47:34 +00002437 preview_image=RollImage(thumbnail,x,y,exception);
cristyb51dff52011-05-19 16:55:47 +00002438 (void) FormatLocaleString(label,MaxTextExtent,"roll %+.20gx%+.20g",
cristye8c25f92010-06-03 00:53:06 +00002439 (double) x,(double) y);
cristy3ed852e2009-09-05 21:47:34 +00002440 break;
2441 }
2442 case HuePreview:
2443 {
2444 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2445 if (preview_image == (Image *) NULL)
2446 break;
cristyb51dff52011-05-19 16:55:47 +00002447 (void) FormatLocaleString(factor,MaxTextExtent,"100,100,%g",
cristy3ed852e2009-09-05 21:47:34 +00002448 2.0*percentage);
cristy33bd5152011-08-24 01:42:24 +00002449 (void) ModulateImage(preview_image,factor,exception);
cristyb51dff52011-05-19 16:55:47 +00002450 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002451 break;
2452 }
2453 case SaturationPreview:
2454 {
2455 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2456 if (preview_image == (Image *) NULL)
2457 break;
cristyb51dff52011-05-19 16:55:47 +00002458 (void) FormatLocaleString(factor,MaxTextExtent,"100,%g",
cristy8cd5b312010-01-07 01:10:24 +00002459 2.0*percentage);
cristy33bd5152011-08-24 01:42:24 +00002460 (void) ModulateImage(preview_image,factor,exception);
cristyb51dff52011-05-19 16:55:47 +00002461 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002462 break;
2463 }
2464 case BrightnessPreview:
2465 {
2466 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2467 if (preview_image == (Image *) NULL)
2468 break;
cristyb51dff52011-05-19 16:55:47 +00002469 (void) FormatLocaleString(factor,MaxTextExtent,"%g",2.0*percentage);
cristy33bd5152011-08-24 01:42:24 +00002470 (void) ModulateImage(preview_image,factor,exception);
cristyb51dff52011-05-19 16:55:47 +00002471 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002472 break;
2473 }
2474 case GammaPreview:
2475 default:
2476 {
2477 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2478 if (preview_image == (Image *) NULL)
2479 break;
2480 gamma+=0.4f;
cristyb3e7c6c2011-07-24 01:43:55 +00002481 (void) GammaImage(preview_image,gamma,exception);
cristyb51dff52011-05-19 16:55:47 +00002482 (void) FormatLocaleString(label,MaxTextExtent,"gamma %g",gamma);
cristy3ed852e2009-09-05 21:47:34 +00002483 break;
2484 }
2485 case SpiffPreview:
2486 {
2487 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2488 if (preview_image != (Image *) NULL)
2489 for (x=0; x < i; x++)
cristye23ec9d2011-08-16 18:15:40 +00002490 (void) ContrastImage(preview_image,MagickTrue,exception);
cristyb51dff52011-05-19 16:55:47 +00002491 (void) FormatLocaleString(label,MaxTextExtent,"contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002492 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002493 break;
2494 }
2495 case DullPreview:
2496 {
2497 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2498 if (preview_image == (Image *) NULL)
2499 break;
2500 for (x=0; x < i; x++)
cristye23ec9d2011-08-16 18:15:40 +00002501 (void) ContrastImage(preview_image,MagickFalse,exception);
cristyb51dff52011-05-19 16:55:47 +00002502 (void) FormatLocaleString(label,MaxTextExtent,"+contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002503 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002504 break;
2505 }
2506 case GrayscalePreview:
2507 {
2508 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2509 if (preview_image == (Image *) NULL)
2510 break;
2511 colors<<=1;
2512 quantize_info.number_colors=colors;
2513 quantize_info.colorspace=GRAYColorspace;
cristy018f07f2011-09-04 21:15:19 +00002514 (void) QuantizeImage(&quantize_info,preview_image,exception);
cristyb51dff52011-05-19 16:55:47 +00002515 (void) FormatLocaleString(label,MaxTextExtent,
cristye8c25f92010-06-03 00:53:06 +00002516 "-colorspace gray -colors %.20g",(double) colors);
cristy3ed852e2009-09-05 21:47:34 +00002517 break;
2518 }
2519 case QuantizePreview:
2520 {
2521 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2522 if (preview_image == (Image *) NULL)
2523 break;
2524 colors<<=1;
2525 quantize_info.number_colors=colors;
cristy018f07f2011-09-04 21:15:19 +00002526 (void) QuantizeImage(&quantize_info,preview_image,exception);
cristyb51dff52011-05-19 16:55:47 +00002527 (void) FormatLocaleString(label,MaxTextExtent,"colors %.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00002528 colors);
cristy3ed852e2009-09-05 21:47:34 +00002529 break;
2530 }
2531 case DespecklePreview:
2532 {
2533 for (x=0; x < (i-1); x++)
2534 {
2535 preview_image=DespeckleImage(thumbnail,exception);
2536 if (preview_image == (Image *) NULL)
2537 break;
2538 thumbnail=DestroyImage(thumbnail);
2539 thumbnail=preview_image;
2540 }
2541 preview_image=DespeckleImage(thumbnail,exception);
2542 if (preview_image == (Image *) NULL)
2543 break;
cristyb51dff52011-05-19 16:55:47 +00002544 (void) FormatLocaleString(label,MaxTextExtent,"despeckle (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002545 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002546 break;
2547 }
2548 case ReduceNoisePreview:
2549 {
cristy95c38342011-03-18 22:39:51 +00002550 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) radius,
2551 (size_t) radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002552 (void) FormatLocaleString(label,MaxTextExtent,"noise %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002553 break;
2554 }
2555 case AddNoisePreview:
2556 {
2557 switch ((int) i)
2558 {
2559 case 0:
2560 {
2561 (void) CopyMagickString(factor,"uniform",MaxTextExtent);
2562 break;
2563 }
2564 case 1:
2565 {
2566 (void) CopyMagickString(factor,"gaussian",MaxTextExtent);
2567 break;
2568 }
2569 case 2:
2570 {
2571 (void) CopyMagickString(factor,"multiplicative",MaxTextExtent);
2572 break;
2573 }
2574 case 3:
2575 {
2576 (void) CopyMagickString(factor,"impulse",MaxTextExtent);
2577 break;
2578 }
2579 case 4:
2580 {
2581 (void) CopyMagickString(factor,"laplacian",MaxTextExtent);
2582 break;
2583 }
2584 case 5:
2585 {
2586 (void) CopyMagickString(factor,"Poisson",MaxTextExtent);
2587 break;
2588 }
2589 default:
2590 {
2591 (void) CopyMagickString(thumbnail->magick,"NULL",MaxTextExtent);
2592 break;
2593 }
2594 }
cristyd76c51e2011-03-26 00:21:26 +00002595 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) i,
2596 (size_t) i,exception);
cristyb51dff52011-05-19 16:55:47 +00002597 (void) FormatLocaleString(label,MaxTextExtent,"+noise %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002598 break;
2599 }
2600 case SharpenPreview:
2601 {
cristy05c0c9a2011-09-05 23:16:13 +00002602 preview_image=SharpenImage(thumbnail,radius,sigma,image->bias,
2603 exception);
cristyb51dff52011-05-19 16:55:47 +00002604 (void) FormatLocaleString(label,MaxTextExtent,"sharpen %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002605 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002606 break;
2607 }
2608 case BlurPreview:
2609 {
cristy05c0c9a2011-09-05 23:16:13 +00002610 preview_image=BlurImage(thumbnail,radius,sigma,image->bias,exception);
cristyb51dff52011-05-19 16:55:47 +00002611 (void) FormatLocaleString(label,MaxTextExtent,"blur %gx%g",radius,
cristy3ed852e2009-09-05 21:47:34 +00002612 sigma);
2613 break;
2614 }
2615 case ThresholdPreview:
2616 {
2617 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2618 if (preview_image == (Image *) NULL)
2619 break;
2620 (void) BilevelImage(thumbnail,
2621 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
cristyb51dff52011-05-19 16:55:47 +00002622 (void) FormatLocaleString(label,MaxTextExtent,"threshold %g",
cristy3ed852e2009-09-05 21:47:34 +00002623 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
2624 break;
2625 }
2626 case EdgeDetectPreview:
2627 {
cristy8ae632d2011-09-05 17:29:53 +00002628 preview_image=EdgeImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00002629 (void) FormatLocaleString(label,MaxTextExtent,"edge %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002630 break;
2631 }
2632 case SpreadPreview:
2633 {
2634 preview_image=SpreadImage(thumbnail,radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002635 (void) FormatLocaleString(label,MaxTextExtent,"spread %g",
cristy8cd5b312010-01-07 01:10:24 +00002636 radius+0.5);
cristy3ed852e2009-09-05 21:47:34 +00002637 break;
2638 }
2639 case SolarizePreview:
2640 {
2641 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2642 if (preview_image == (Image *) NULL)
2643 break;
2644 (void) SolarizeImage(preview_image,(double) QuantumRange*
cristy5cbc0162011-08-29 00:36:28 +00002645 percentage/100.0,exception);
cristyb51dff52011-05-19 16:55:47 +00002646 (void) FormatLocaleString(label,MaxTextExtent,"solarize %g",
cristy3ed852e2009-09-05 21:47:34 +00002647 (QuantumRange*percentage)/100.0);
2648 break;
2649 }
2650 case ShadePreview:
2651 {
2652 degrees+=10.0;
2653 preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees,
2654 exception);
cristyb51dff52011-05-19 16:55:47 +00002655 (void) FormatLocaleString(label,MaxTextExtent,"shade %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002656 degrees,degrees);
cristy3ed852e2009-09-05 21:47:34 +00002657 break;
2658 }
2659 case RaisePreview:
2660 {
2661 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2662 if (preview_image == (Image *) NULL)
2663 break;
cristybb503372010-05-27 20:51:26 +00002664 geometry.width=(size_t) (2*i+2);
2665 geometry.height=(size_t) (2*i+2);
cristy3ed852e2009-09-05 21:47:34 +00002666 geometry.x=i/2;
2667 geometry.y=i/2;
cristy6170ac32011-08-28 14:15:37 +00002668 (void) RaiseImage(preview_image,&geometry,MagickTrue,exception);
cristyb51dff52011-05-19 16:55:47 +00002669 (void) FormatLocaleString(label,MaxTextExtent,
cristy6d8abba2010-06-03 01:10:47 +00002670 "raise %.20gx%.20g%+.20g%+.20g",(double) geometry.width,(double)
cristye8c25f92010-06-03 00:53:06 +00002671 geometry.height,(double) geometry.x,(double) geometry.y);
cristy3ed852e2009-09-05 21:47:34 +00002672 break;
2673 }
2674 case SegmentPreview:
2675 {
2676 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2677 if (preview_image == (Image *) NULL)
2678 break;
2679 threshold+=0.4f;
2680 (void) SegmentImage(preview_image,RGBColorspace,MagickFalse,threshold,
cristy018f07f2011-09-04 21:15:19 +00002681 threshold,exception);
cristyb51dff52011-05-19 16:55:47 +00002682 (void) FormatLocaleString(label,MaxTextExtent,"segment %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002683 threshold,threshold);
2684 break;
2685 }
2686 case SwirlPreview:
2687 {
2688 preview_image=SwirlImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002689 (void) FormatLocaleString(label,MaxTextExtent,"swirl %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002690 degrees+=45.0;
2691 break;
2692 }
2693 case ImplodePreview:
2694 {
2695 degrees+=0.1f;
2696 preview_image=ImplodeImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002697 (void) FormatLocaleString(label,MaxTextExtent,"implode %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002698 break;
2699 }
2700 case WavePreview:
2701 {
2702 degrees+=5.0f;
2703 preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002704 (void) FormatLocaleString(label,MaxTextExtent,"wave %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002705 0.5*degrees,2.0*degrees);
cristy3ed852e2009-09-05 21:47:34 +00002706 break;
2707 }
2708 case OilPaintPreview:
2709 {
cristy14973ba2011-08-27 23:48:07 +00002710 preview_image=OilPaintImage(thumbnail,(double) radius,(double) sigma,
2711 exception);
2712 (void) FormatLocaleString(label,MaxTextExtent,"charcoal %gx%g",
2713 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002714 break;
2715 }
2716 case CharcoalDrawingPreview:
2717 {
2718 preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma,
cristy05c0c9a2011-09-05 23:16:13 +00002719 image->bias,exception);
cristyb51dff52011-05-19 16:55:47 +00002720 (void) FormatLocaleString(label,MaxTextExtent,"charcoal %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002721 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002722 break;
2723 }
2724 case JPEGPreview:
2725 {
2726 char
2727 filename[MaxTextExtent];
2728
2729 int
2730 file;
2731
2732 MagickBooleanType
2733 status;
2734
2735 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2736 if (preview_image == (Image *) NULL)
2737 break;
cristybb503372010-05-27 20:51:26 +00002738 preview_info->quality=(size_t) percentage;
cristyb51dff52011-05-19 16:55:47 +00002739 (void) FormatLocaleString(factor,MaxTextExtent,"%.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00002740 preview_info->quality);
cristy3ed852e2009-09-05 21:47:34 +00002741 file=AcquireUniqueFileResource(filename);
2742 if (file != -1)
2743 file=close(file)-1;
cristyb51dff52011-05-19 16:55:47 +00002744 (void) FormatLocaleString(preview_image->filename,MaxTextExtent,
cristy3ed852e2009-09-05 21:47:34 +00002745 "jpeg:%s",filename);
cristy6f9e0d32011-08-28 16:32:09 +00002746 status=WriteImage(preview_info,preview_image,exception);
cristy3ed852e2009-09-05 21:47:34 +00002747 if (status != MagickFalse)
2748 {
2749 Image
2750 *quality_image;
2751
2752 (void) CopyMagickString(preview_info->filename,
2753 preview_image->filename,MaxTextExtent);
2754 quality_image=ReadImage(preview_info,exception);
2755 if (quality_image != (Image *) NULL)
2756 {
2757 preview_image=DestroyImage(preview_image);
2758 preview_image=quality_image;
2759 }
2760 }
2761 (void) RelinquishUniqueFileResource(preview_image->filename);
2762 if ((GetBlobSize(preview_image)/1024) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00002763 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%gmb ",
cristy3ed852e2009-09-05 21:47:34 +00002764 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
2765 1024.0/1024.0);
2766 else
2767 if (GetBlobSize(preview_image) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00002768 (void) FormatLocaleString(label,MaxTextExtent,
cristye7f51092010-01-17 00:39:37 +00002769 "quality %s\n%gkb ",factor,(double) ((MagickOffsetType)
cristy8cd5b312010-01-07 01:10:24 +00002770 GetBlobSize(preview_image))/1024.0);
cristy3ed852e2009-09-05 21:47:34 +00002771 else
cristyb51dff52011-05-19 16:55:47 +00002772 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%.20gb ",
cristy54ea5732011-06-10 12:39:53 +00002773 factor,(double) ((MagickOffsetType) GetBlobSize(thumbnail)));
cristy3ed852e2009-09-05 21:47:34 +00002774 break;
2775 }
2776 }
2777 thumbnail=DestroyImage(thumbnail);
2778 percentage+=12.5;
2779 radius+=0.5;
2780 sigma+=0.25;
2781 if (preview_image == (Image *) NULL)
2782 break;
2783 (void) DeleteImageProperty(preview_image,"label");
2784 (void) SetImageProperty(preview_image,"label",label);
2785 AppendImageToList(&images,preview_image);
cristybb503372010-05-27 20:51:26 +00002786 proceed=SetImageProgress(image,PreviewImageTag,(MagickOffsetType) i,
2787 NumberTiles);
cristy3ed852e2009-09-05 21:47:34 +00002788 if (proceed == MagickFalse)
2789 break;
2790 }
2791 if (images == (Image *) NULL)
2792 {
2793 preview_info=DestroyImageInfo(preview_info);
2794 return((Image *) NULL);
2795 }
2796 /*
2797 Create the montage.
2798 */
2799 montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL);
2800 (void) CopyMagickString(montage_info->filename,image->filename,MaxTextExtent);
2801 montage_info->shadow=MagickTrue;
2802 (void) CloneString(&montage_info->tile,"3x3");
2803 (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry);
2804 (void) CloneString(&montage_info->frame,DefaultTileFrame);
2805 montage_image=MontageImages(images,montage_info,exception);
2806 montage_info=DestroyMontageInfo(montage_info);
2807 images=DestroyImageList(images);
2808 if (montage_image == (Image *) NULL)
2809 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2810 if (montage_image->montage != (char *) NULL)
2811 {
2812 /*
2813 Free image directory.
2814 */
2815 montage_image->montage=(char *) RelinquishMagickMemory(
2816 montage_image->montage);
2817 if (image->directory != (char *) NULL)
2818 montage_image->directory=(char *) RelinquishMagickMemory(
2819 montage_image->directory);
2820 }
2821 preview_info=DestroyImageInfo(preview_info);
2822 return(montage_image);
2823}
2824
2825/*
2826%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2827% %
2828% %
2829% %
2830% R a d i a l B l u r I m a g e %
2831% %
2832% %
2833% %
2834%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2835%
2836% RadialBlurImage() applies a radial blur to the image.
2837%
2838% Andrew Protano contributed this effect.
2839%
2840% The format of the RadialBlurImage method is:
2841%
2842% Image *RadialBlurImage(const Image *image,const double angle,
2843% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002844%
2845% A description of each parameter follows:
2846%
2847% o image: the image.
2848%
cristy3ed852e2009-09-05 21:47:34 +00002849% o angle: the angle of the radial blur.
2850%
2851% o exception: return any errors or warnings in this structure.
2852%
2853*/
cristyf4ad9df2011-07-08 16:49:03 +00002854MagickExport Image *RadialBlurImage(const Image *image,
2855 const double angle,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002856{
cristyc4c8d132010-01-07 01:58:38 +00002857 CacheView
2858 *blur_view,
2859 *image_view;
2860
cristy3ed852e2009-09-05 21:47:34 +00002861 Image
2862 *blur_image;
2863
cristy3ed852e2009-09-05 21:47:34 +00002864 MagickBooleanType
2865 status;
2866
cristybb503372010-05-27 20:51:26 +00002867 MagickOffsetType
2868 progress;
2869
cristy4c08aed2011-07-01 19:47:50 +00002870 PixelInfo
cristyddd82202009-11-03 20:14:50 +00002871 bias;
cristy3ed852e2009-09-05 21:47:34 +00002872
2873 MagickRealType
2874 blur_radius,
2875 *cos_theta,
2876 offset,
2877 *sin_theta,
2878 theta;
2879
2880 PointInfo
2881 blur_center;
2882
cristybb503372010-05-27 20:51:26 +00002883 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002884 i;
2885
cristybb503372010-05-27 20:51:26 +00002886 size_t
cristy3ed852e2009-09-05 21:47:34 +00002887 n;
2888
cristybb503372010-05-27 20:51:26 +00002889 ssize_t
2890 y;
2891
cristy3ed852e2009-09-05 21:47:34 +00002892 /*
2893 Allocate blur image.
2894 */
2895 assert(image != (Image *) NULL);
2896 assert(image->signature == MagickSignature);
2897 if (image->debug != MagickFalse)
2898 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2899 assert(exception != (ExceptionInfo *) NULL);
2900 assert(exception->signature == MagickSignature);
2901 blur_image=CloneImage(image,0,0,MagickTrue,exception);
2902 if (blur_image == (Image *) NULL)
2903 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00002904 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00002905 {
cristy3ed852e2009-09-05 21:47:34 +00002906 blur_image=DestroyImage(blur_image);
2907 return((Image *) NULL);
2908 }
2909 blur_center.x=(double) image->columns/2.0;
2910 blur_center.y=(double) image->rows/2.0;
2911 blur_radius=hypot(blur_center.x,blur_center.y);
cristy117ff172010-08-15 21:35:32 +00002912 n=(size_t) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+2UL);
cristy3ed852e2009-09-05 21:47:34 +00002913 theta=DegreesToRadians(angle)/(MagickRealType) (n-1);
2914 cos_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
2915 sizeof(*cos_theta));
2916 sin_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
2917 sizeof(*sin_theta));
2918 if ((cos_theta == (MagickRealType *) NULL) ||
2919 (sin_theta == (MagickRealType *) NULL))
2920 {
2921 blur_image=DestroyImage(blur_image);
2922 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2923 }
2924 offset=theta*(MagickRealType) (n-1)/2.0;
cristybb503372010-05-27 20:51:26 +00002925 for (i=0; i < (ssize_t) n; i++)
cristy3ed852e2009-09-05 21:47:34 +00002926 {
2927 cos_theta[i]=cos((double) (theta*i-offset));
2928 sin_theta[i]=sin((double) (theta*i-offset));
2929 }
2930 /*
2931 Radial blur image.
2932 */
2933 status=MagickTrue;
2934 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00002935 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00002936 image_view=AcquireCacheView(image);
2937 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00002938#if defined(MAGICKCORE_OPENMP_SUPPORT)
2939 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002940#endif
cristybb503372010-05-27 20:51:26 +00002941 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002942 {
cristy4c08aed2011-07-01 19:47:50 +00002943 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002944 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002945
cristy117ff172010-08-15 21:35:32 +00002946 register ssize_t
2947 x;
2948
cristy3ed852e2009-09-05 21:47:34 +00002949 if (status == MagickFalse)
2950 continue;
2951 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
2952 exception);
cristyacd2ed22011-08-30 01:44:23 +00002953 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002954 {
2955 status=MagickFalse;
2956 continue;
2957 }
cristybb503372010-05-27 20:51:26 +00002958 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002959 {
cristy4c08aed2011-07-01 19:47:50 +00002960 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002961 qixel;
2962
2963 MagickRealType
2964 normalize,
2965 radius;
2966
2967 PixelPacket
2968 pixel;
2969
2970 PointInfo
2971 center;
2972
cristybb503372010-05-27 20:51:26 +00002973 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002974 i;
2975
cristybb503372010-05-27 20:51:26 +00002976 size_t
cristy3ed852e2009-09-05 21:47:34 +00002977 step;
2978
2979 center.x=(double) x-blur_center.x;
2980 center.y=(double) y-blur_center.y;
2981 radius=hypot((double) center.x,center.y);
2982 if (radius == 0)
2983 step=1;
2984 else
2985 {
cristybb503372010-05-27 20:51:26 +00002986 step=(size_t) (blur_radius/radius);
cristy3ed852e2009-09-05 21:47:34 +00002987 if (step == 0)
2988 step=1;
2989 else
2990 if (step >= n)
2991 step=n-1;
2992 }
2993 normalize=0.0;
cristyddd82202009-11-03 20:14:50 +00002994 qixel=bias;
cristyed231572011-07-14 02:18:59 +00002995 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00002996 {
cristyeaedf062010-05-29 22:36:02 +00002997 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00002998 {
cristyeaedf062010-05-29 22:36:02 +00002999 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
3000 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
3001 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
3002 cos_theta[i]+0.5),&pixel,exception);
cristy3ed852e2009-09-05 21:47:34 +00003003 qixel.red+=pixel.red;
3004 qixel.green+=pixel.green;
3005 qixel.blue+=pixel.blue;
cristy3ed852e2009-09-05 21:47:34 +00003006 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00003007 qixel.black+=pixel.black;
3008 qixel.alpha+=pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00003009 normalize+=1.0;
3010 }
3011 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
3012 normalize);
cristyed231572011-07-14 02:18:59 +00003013 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003014 SetPixelRed(blur_image,
3015 ClampToQuantum(normalize*qixel.red),q);
cristyed231572011-07-14 02:18:59 +00003016 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003017 SetPixelGreen(blur_image,
3018 ClampToQuantum(normalize*qixel.green),q);
cristyed231572011-07-14 02:18:59 +00003019 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003020 SetPixelBlue(blur_image,
3021 ClampToQuantum(normalize*qixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003022 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003023 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003024 SetPixelBlack(blur_image,
3025 ClampToQuantum(normalize*qixel.black),q);
cristyed231572011-07-14 02:18:59 +00003026 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003027 SetPixelAlpha(blur_image,
3028 ClampToQuantum(normalize*qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003029 }
3030 else
3031 {
3032 MagickRealType
3033 alpha,
3034 gamma;
3035
3036 alpha=1.0;
3037 gamma=0.0;
cristyeaedf062010-05-29 22:36:02 +00003038 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00003039 {
cristyeaedf062010-05-29 22:36:02 +00003040 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
3041 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
3042 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
3043 cos_theta[i]+0.5),&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00003044 alpha=(MagickRealType) (QuantumScale*pixel.alpha);
cristy3ed852e2009-09-05 21:47:34 +00003045 qixel.red+=alpha*pixel.red;
3046 qixel.green+=alpha*pixel.green;
3047 qixel.blue+=alpha*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00003048 qixel.alpha+=pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00003049 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00003050 qixel.black+=alpha*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00003051 gamma+=alpha;
3052 normalize+=1.0;
3053 }
3054 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3055 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
3056 normalize);
cristyed231572011-07-14 02:18:59 +00003057 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003058 SetPixelRed(blur_image,
3059 ClampToQuantum(gamma*qixel.red),q);
cristyed231572011-07-14 02:18:59 +00003060 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003061 SetPixelGreen(blur_image,
3062 ClampToQuantum(gamma*qixel.green),q);
cristyed231572011-07-14 02:18:59 +00003063 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003064 SetPixelBlue(blur_image,
3065 ClampToQuantum(gamma*qixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003066 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003067 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003068 SetPixelBlack(blur_image,
3069 ClampToQuantum(gamma*qixel.black),q);
cristyed231572011-07-14 02:18:59 +00003070 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003071 SetPixelAlpha(blur_image,
3072 ClampToQuantum(normalize*qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003073 }
cristyed231572011-07-14 02:18:59 +00003074 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003075 }
3076 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
3077 status=MagickFalse;
3078 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3079 {
3080 MagickBooleanType
3081 proceed;
3082
cristyb5d5f722009-11-04 03:03:49 +00003083#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00003084 #pragma omp critical (MagickCore_RadialBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00003085#endif
3086 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
3087 if (proceed == MagickFalse)
3088 status=MagickFalse;
3089 }
3090 }
3091 blur_view=DestroyCacheView(blur_view);
3092 image_view=DestroyCacheView(image_view);
3093 cos_theta=(MagickRealType *) RelinquishMagickMemory(cos_theta);
3094 sin_theta=(MagickRealType *) RelinquishMagickMemory(sin_theta);
3095 if (status == MagickFalse)
3096 blur_image=DestroyImage(blur_image);
3097 return(blur_image);
3098}
3099
3100/*
3101%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3102% %
3103% %
3104% %
cristy3ed852e2009-09-05 21:47:34 +00003105% S e l e c t i v e B l u r I m a g e %
3106% %
3107% %
3108% %
3109%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3110%
3111% SelectiveBlurImage() selectively blur pixels within a contrast threshold.
3112% It is similar to the unsharpen mask that sharpens everything with contrast
3113% above a certain threshold.
3114%
3115% The format of the SelectiveBlurImage method is:
3116%
3117% Image *SelectiveBlurImage(const Image *image,const double radius,
3118% const double sigma,const double threshold,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003119%
3120% A description of each parameter follows:
3121%
3122% o image: the image.
3123%
cristy3ed852e2009-09-05 21:47:34 +00003124% o radius: the radius of the Gaussian, in pixels, not counting the center
3125% pixel.
3126%
3127% o sigma: the standard deviation of the Gaussian, in pixels.
3128%
3129% o threshold: only pixels within this contrast threshold are included
3130% in the blur operation.
3131%
3132% o exception: return any errors or warnings in this structure.
3133%
3134*/
cristyf4ad9df2011-07-08 16:49:03 +00003135MagickExport Image *SelectiveBlurImage(const Image *image,
3136 const double radius,const double sigma,const double threshold,
3137 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003138{
3139#define SelectiveBlurImageTag "SelectiveBlur/Image"
3140
cristy47e00502009-12-17 19:19:57 +00003141 CacheView
3142 *blur_view,
3143 *image_view;
3144
cristy3ed852e2009-09-05 21:47:34 +00003145 double
cristy3ed852e2009-09-05 21:47:34 +00003146 *kernel;
3147
3148 Image
3149 *blur_image;
3150
cristy3ed852e2009-09-05 21:47:34 +00003151 MagickBooleanType
3152 status;
3153
cristybb503372010-05-27 20:51:26 +00003154 MagickOffsetType
3155 progress;
3156
cristy4c08aed2011-07-01 19:47:50 +00003157 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003158 bias;
3159
cristybb503372010-05-27 20:51:26 +00003160 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003161 i;
cristy3ed852e2009-09-05 21:47:34 +00003162
cristybb503372010-05-27 20:51:26 +00003163 size_t
cristy3ed852e2009-09-05 21:47:34 +00003164 width;
3165
cristybb503372010-05-27 20:51:26 +00003166 ssize_t
3167 j,
3168 u,
3169 v,
3170 y;
3171
cristy3ed852e2009-09-05 21:47:34 +00003172 /*
3173 Initialize blur image attributes.
3174 */
3175 assert(image != (Image *) NULL);
3176 assert(image->signature == MagickSignature);
3177 if (image->debug != MagickFalse)
3178 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3179 assert(exception != (ExceptionInfo *) NULL);
3180 assert(exception->signature == MagickSignature);
3181 width=GetOptimalKernelWidth1D(radius,sigma);
3182 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
3183 if (kernel == (double *) NULL)
3184 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00003185 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00003186 i=0;
cristy47e00502009-12-17 19:19:57 +00003187 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00003188 {
cristy47e00502009-12-17 19:19:57 +00003189 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00003190 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
3191 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00003192 }
3193 if (image->debug != MagickFalse)
3194 {
3195 char
3196 format[MaxTextExtent],
3197 *message;
3198
cristy117ff172010-08-15 21:35:32 +00003199 register const double
3200 *k;
3201
cristybb503372010-05-27 20:51:26 +00003202 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003203 u,
3204 v;
3205
cristy3ed852e2009-09-05 21:47:34 +00003206 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00003207 " SelectiveBlurImage with %.20gx%.20g kernel:",(double) width,(double)
3208 width);
cristy3ed852e2009-09-05 21:47:34 +00003209 message=AcquireString("");
3210 k=kernel;
cristybb503372010-05-27 20:51:26 +00003211 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003212 {
3213 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00003214 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy3ed852e2009-09-05 21:47:34 +00003215 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00003216 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003217 {
cristyb51dff52011-05-19 16:55:47 +00003218 (void) FormatLocaleString(format,MaxTextExtent,"%+f ",*k++);
cristy3ed852e2009-09-05 21:47:34 +00003219 (void) ConcatenateString(&message,format);
3220 }
3221 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
3222 }
3223 message=DestroyString(message);
3224 }
3225 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3226 if (blur_image == (Image *) NULL)
3227 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00003228 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003229 {
cristy3ed852e2009-09-05 21:47:34 +00003230 blur_image=DestroyImage(blur_image);
3231 return((Image *) NULL);
3232 }
3233 /*
3234 Threshold blur image.
3235 */
3236 status=MagickTrue;
3237 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00003238 GetPixelInfo(image,&bias);
3239 SetPixelInfoBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003240 image_view=AcquireCacheView(image);
3241 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003242#if defined(MAGICKCORE_OPENMP_SUPPORT)
3243 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003244#endif
cristybb503372010-05-27 20:51:26 +00003245 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003246 {
cristy4c08aed2011-07-01 19:47:50 +00003247 double
3248 contrast;
3249
cristy3ed852e2009-09-05 21:47:34 +00003250 MagickBooleanType
3251 sync;
3252
3253 MagickRealType
3254 gamma;
3255
cristy4c08aed2011-07-01 19:47:50 +00003256 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003257 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003258
cristy4c08aed2011-07-01 19:47:50 +00003259 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003260 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003261
cristy117ff172010-08-15 21:35:32 +00003262 register ssize_t
3263 x;
3264
cristy3ed852e2009-09-05 21:47:34 +00003265 if (status == MagickFalse)
3266 continue;
cristy117ff172010-08-15 21:35:32 +00003267 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
3268 (width/2L),image->columns+width,width,exception);
cristy3ed852e2009-09-05 21:47:34 +00003269 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3270 exception);
cristy4c08aed2011-07-01 19:47:50 +00003271 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003272 {
3273 status=MagickFalse;
3274 continue;
3275 }
cristybb503372010-05-27 20:51:26 +00003276 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003277 {
cristy4c08aed2011-07-01 19:47:50 +00003278 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003279 pixel;
3280
3281 register const double
cristyc47d1f82009-11-26 01:44:43 +00003282 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00003283
cristybb503372010-05-27 20:51:26 +00003284 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003285 u;
3286
cristy117ff172010-08-15 21:35:32 +00003287 ssize_t
3288 j,
3289 v;
3290
cristyddd82202009-11-03 20:14:50 +00003291 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00003292 k=kernel;
3293 gamma=0.0;
3294 j=0;
cristyed231572011-07-14 02:18:59 +00003295 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00003296 {
cristybb503372010-05-27 20:51:26 +00003297 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003298 {
cristybb503372010-05-27 20:51:26 +00003299 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003300 {
cristyed231572011-07-14 02:18:59 +00003301 contrast=GetPixelIntensity(image,p+(u+j)*GetPixelChannels(image))-
cristy4c08aed2011-07-01 19:47:50 +00003302 (double) GetPixelIntensity(blur_image,q);
3303 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003304 {
cristy4c08aed2011-07-01 19:47:50 +00003305 pixel.red+=(*k)*
cristyed231572011-07-14 02:18:59 +00003306 GetPixelRed(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003307 pixel.green+=(*k)*
cristyed231572011-07-14 02:18:59 +00003308 GetPixelGreen(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003309 pixel.blue+=(*k)*
cristyed231572011-07-14 02:18:59 +00003310 GetPixelBlue(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003311 if (image->colorspace == CMYKColorspace)
3312 pixel.black+=(*k)*
cristyed231572011-07-14 02:18:59 +00003313 GetPixelBlack(image,p+(u+j)*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003314 gamma+=(*k);
3315 k++;
3316 }
3317 }
cristyd99b0962010-05-29 23:14:26 +00003318 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003319 }
3320 if (gamma != 0.0)
3321 {
3322 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +00003323 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003324 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +00003325 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003326 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +00003327 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003328 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003329 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00003330 (image->colorspace == CMYKColorspace))
3331 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00003332 }
cristyed231572011-07-14 02:18:59 +00003333 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003334 {
3335 gamma=0.0;
3336 j=0;
cristybb503372010-05-27 20:51:26 +00003337 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003338 {
cristybb503372010-05-27 20:51:26 +00003339 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003340 {
cristy4c08aed2011-07-01 19:47:50 +00003341 contrast=GetPixelIntensity(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003342 GetPixelChannels(image))-(double)
cristy4c08aed2011-07-01 19:47:50 +00003343 GetPixelIntensity(blur_image,q);
3344 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003345 {
cristy4c08aed2011-07-01 19:47:50 +00003346 pixel.alpha+=(*k)*
cristyed231572011-07-14 02:18:59 +00003347 GetPixelAlpha(image,p+(u+j)*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003348 gamma+=(*k);
3349 k++;
3350 }
3351 }
cristyeaedf062010-05-29 22:36:02 +00003352 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003353 }
3354 if (gamma != 0.0)
3355 {
3356 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
3357 gamma);
cristy4c08aed2011-07-01 19:47:50 +00003358 SetPixelAlpha(blur_image,ClampToQuantum(gamma*pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003359 }
3360 }
3361 }
3362 else
3363 {
3364 MagickRealType
3365 alpha;
3366
cristybb503372010-05-27 20:51:26 +00003367 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003368 {
cristybb503372010-05-27 20:51:26 +00003369 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003370 {
cristy4c08aed2011-07-01 19:47:50 +00003371 contrast=GetPixelIntensity(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003372 GetPixelChannels(image))-(double)
cristy4c08aed2011-07-01 19:47:50 +00003373 GetPixelIntensity(blur_image,q);
3374 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003375 {
cristy4c08aed2011-07-01 19:47:50 +00003376 alpha=(MagickRealType) (QuantumScale*
cristyed231572011-07-14 02:18:59 +00003377 GetPixelAlpha(image,p+(u+j)*GetPixelChannels(image)));
cristy4c08aed2011-07-01 19:47:50 +00003378 pixel.red+=(*k)*alpha*
cristyed231572011-07-14 02:18:59 +00003379 GetPixelRed(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003380 pixel.green+=(*k)*alpha*GetPixelGreen(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003381 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003382 pixel.blue+=(*k)*alpha*GetPixelBlue(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003383 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003384 pixel.alpha+=(*k)*GetPixelAlpha(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003385 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003386 if (image->colorspace == CMYKColorspace)
3387 pixel.black+=(*k)*GetPixelBlack(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003388 GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003389 gamma+=(*k)*alpha;
3390 k++;
3391 }
3392 }
cristyeaedf062010-05-29 22:36:02 +00003393 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003394 }
3395 if (gamma != 0.0)
3396 {
3397 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +00003398 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003399 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +00003400 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003401 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +00003402 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003403 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003404 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00003405 (image->colorspace == CMYKColorspace))
3406 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00003407 }
cristyed231572011-07-14 02:18:59 +00003408 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003409 {
3410 gamma=0.0;
3411 j=0;
cristybb503372010-05-27 20:51:26 +00003412 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003413 {
cristybb503372010-05-27 20:51:26 +00003414 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003415 {
cristy4c08aed2011-07-01 19:47:50 +00003416 contrast=GetPixelIntensity(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003417 GetPixelChannels(image))-(double)
cristy4c08aed2011-07-01 19:47:50 +00003418 GetPixelIntensity(blur_image,q);
3419 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003420 {
cristy4c08aed2011-07-01 19:47:50 +00003421 pixel.alpha+=(*k)*
cristyed231572011-07-14 02:18:59 +00003422 GetPixelAlpha(image,p+(u+j)*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003423 gamma+=(*k);
3424 k++;
3425 }
3426 }
cristyeaedf062010-05-29 22:36:02 +00003427 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003428 }
3429 if (gamma != 0.0)
3430 {
3431 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
3432 gamma);
cristy4c08aed2011-07-01 19:47:50 +00003433 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003434 }
3435 }
3436 }
cristyed231572011-07-14 02:18:59 +00003437 p+=GetPixelChannels(image);
3438 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003439 }
3440 sync=SyncCacheViewAuthenticPixels(blur_view,exception);
3441 if (sync == MagickFalse)
3442 status=MagickFalse;
3443 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3444 {
3445 MagickBooleanType
3446 proceed;
3447
cristyb5d5f722009-11-04 03:03:49 +00003448#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00003449 #pragma omp critical (MagickCore_SelectiveBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00003450#endif
3451 proceed=SetImageProgress(image,SelectiveBlurImageTag,progress++,
3452 image->rows);
3453 if (proceed == MagickFalse)
3454 status=MagickFalse;
3455 }
3456 }
3457 blur_image->type=image->type;
3458 blur_view=DestroyCacheView(blur_view);
3459 image_view=DestroyCacheView(image_view);
3460 kernel=(double *) RelinquishMagickMemory(kernel);
3461 if (status == MagickFalse)
3462 blur_image=DestroyImage(blur_image);
3463 return(blur_image);
3464}
3465
3466/*
3467%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3468% %
3469% %
3470% %
3471% S h a d e I m a g e %
3472% %
3473% %
3474% %
3475%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3476%
3477% ShadeImage() shines a distant light on an image to create a
3478% three-dimensional effect. You control the positioning of the light with
3479% azimuth and elevation; azimuth is measured in degrees off the x axis
3480% and elevation is measured in pixels above the Z axis.
3481%
3482% The format of the ShadeImage method is:
3483%
3484% Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3485% const double azimuth,const double elevation,ExceptionInfo *exception)
3486%
3487% A description of each parameter follows:
3488%
3489% o image: the image.
3490%
3491% o gray: A value other than zero shades the intensity of each pixel.
3492%
3493% o azimuth, elevation: Define the light source direction.
3494%
3495% o exception: return any errors or warnings in this structure.
3496%
3497*/
3498MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3499 const double azimuth,const double elevation,ExceptionInfo *exception)
3500{
3501#define ShadeImageTag "Shade/Image"
3502
cristyc4c8d132010-01-07 01:58:38 +00003503 CacheView
3504 *image_view,
3505 *shade_view;
3506
cristy3ed852e2009-09-05 21:47:34 +00003507 Image
3508 *shade_image;
3509
cristy3ed852e2009-09-05 21:47:34 +00003510 MagickBooleanType
3511 status;
3512
cristybb503372010-05-27 20:51:26 +00003513 MagickOffsetType
3514 progress;
3515
cristy3ed852e2009-09-05 21:47:34 +00003516 PrimaryInfo
3517 light;
3518
cristybb503372010-05-27 20:51:26 +00003519 ssize_t
3520 y;
3521
cristy3ed852e2009-09-05 21:47:34 +00003522 /*
3523 Initialize shaded image attributes.
3524 */
3525 assert(image != (const Image *) NULL);
3526 assert(image->signature == MagickSignature);
3527 if (image->debug != MagickFalse)
3528 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3529 assert(exception != (ExceptionInfo *) NULL);
3530 assert(exception->signature == MagickSignature);
3531 shade_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
3532 if (shade_image == (Image *) NULL)
3533 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00003534 if (SetImageStorageClass(shade_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003535 {
cristy3ed852e2009-09-05 21:47:34 +00003536 shade_image=DestroyImage(shade_image);
3537 return((Image *) NULL);
3538 }
3539 /*
3540 Compute the light vector.
3541 */
3542 light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))*
3543 cos(DegreesToRadians(elevation));
3544 light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))*
3545 cos(DegreesToRadians(elevation));
3546 light.z=(double) QuantumRange*sin(DegreesToRadians(elevation));
3547 /*
3548 Shade image.
3549 */
3550 status=MagickTrue;
3551 progress=0;
3552 image_view=AcquireCacheView(image);
3553 shade_view=AcquireCacheView(shade_image);
cristyb5d5f722009-11-04 03:03:49 +00003554#if defined(MAGICKCORE_OPENMP_SUPPORT)
3555 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003556#endif
cristybb503372010-05-27 20:51:26 +00003557 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003558 {
3559 MagickRealType
3560 distance,
3561 normal_distance,
3562 shade;
3563
3564 PrimaryInfo
3565 normal;
3566
cristy4c08aed2011-07-01 19:47:50 +00003567 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003568 *restrict p,
3569 *restrict s0,
3570 *restrict s1,
3571 *restrict s2;
cristy3ed852e2009-09-05 21:47:34 +00003572
cristy4c08aed2011-07-01 19:47:50 +00003573 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003574 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003575
cristy117ff172010-08-15 21:35:32 +00003576 register ssize_t
3577 x;
3578
cristy3ed852e2009-09-05 21:47:34 +00003579 if (status == MagickFalse)
3580 continue;
3581 p=GetCacheViewVirtualPixels(image_view,-1,y-1,image->columns+2,3,exception);
3582 q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1,
3583 exception);
cristy4c08aed2011-07-01 19:47:50 +00003584 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003585 {
3586 status=MagickFalse;
3587 continue;
3588 }
3589 /*
3590 Shade this row of pixels.
3591 */
3592 normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */
cristyed231572011-07-14 02:18:59 +00003593 s0=p+GetPixelChannels(image);
3594 s1=s0+(image->columns+2)*GetPixelChannels(image);
3595 s2=s1+(image->columns+2)*GetPixelChannels(image);
cristybb503372010-05-27 20:51:26 +00003596 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003597 {
3598 /*
3599 Determine the surface normal and compute shading.
3600 */
cristyed231572011-07-14 02:18:59 +00003601 normal.x=(double) (GetPixelIntensity(image,s0-GetPixelChannels(image))+
3602 GetPixelIntensity(image,s1-GetPixelChannels(image))+
3603 GetPixelIntensity(image,s2-GetPixelChannels(image))-
3604 GetPixelIntensity(image,s0+GetPixelChannels(image))-
3605 GetPixelIntensity(image,s1+GetPixelChannels(image))-
3606 GetPixelIntensity(image,s2+GetPixelChannels(image)));
3607 normal.y=(double) (GetPixelIntensity(image,s2-GetPixelChannels(image))+
cristy4c08aed2011-07-01 19:47:50 +00003608 GetPixelIntensity(image,s2)+
cristyed231572011-07-14 02:18:59 +00003609 GetPixelIntensity(image,s2+GetPixelChannels(image))-
3610 GetPixelIntensity(image,s0-GetPixelChannels(image))-
cristy4c08aed2011-07-01 19:47:50 +00003611 GetPixelIntensity(image,s0)-
cristyed231572011-07-14 02:18:59 +00003612 GetPixelIntensity(image,s0+GetPixelChannels(image)));
cristy3ed852e2009-09-05 21:47:34 +00003613 if ((normal.x == 0.0) && (normal.y == 0.0))
3614 shade=light.z;
3615 else
3616 {
3617 shade=0.0;
3618 distance=normal.x*light.x+normal.y*light.y+normal.z*light.z;
3619 if (distance > MagickEpsilon)
3620 {
3621 normal_distance=
3622 normal.x*normal.x+normal.y*normal.y+normal.z*normal.z;
3623 if (normal_distance > (MagickEpsilon*MagickEpsilon))
3624 shade=distance/sqrt((double) normal_distance);
3625 }
3626 }
3627 if (gray != MagickFalse)
3628 {
cristy4c08aed2011-07-01 19:47:50 +00003629 SetPixelRed(shade_image,ClampToQuantum(shade),q);
3630 SetPixelGreen(shade_image,ClampToQuantum(shade),q);
3631 SetPixelBlue(shade_image,ClampToQuantum(shade),q);
cristy3ed852e2009-09-05 21:47:34 +00003632 }
3633 else
3634 {
cristy4c08aed2011-07-01 19:47:50 +00003635 SetPixelRed(shade_image,ClampToQuantum(QuantumScale*shade*
3636 GetPixelRed(image,s1)),q);
3637 SetPixelGreen(shade_image,ClampToQuantum(QuantumScale*shade*
3638 GetPixelGreen(image,s1)),q);
3639 SetPixelBlue(shade_image,ClampToQuantum(QuantumScale*shade*
3640 GetPixelBlue(image,s1)),q);
cristy3ed852e2009-09-05 21:47:34 +00003641 }
cristy4c08aed2011-07-01 19:47:50 +00003642 SetPixelAlpha(shade_image,GetPixelAlpha(image,s1),q);
cristyed231572011-07-14 02:18:59 +00003643 s0+=GetPixelChannels(image);
3644 s1+=GetPixelChannels(image);
3645 s2+=GetPixelChannels(image);
3646 q+=GetPixelChannels(shade_image);
cristy3ed852e2009-09-05 21:47:34 +00003647 }
3648 if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
3649 status=MagickFalse;
3650 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3651 {
3652 MagickBooleanType
3653 proceed;
3654
cristyb5d5f722009-11-04 03:03:49 +00003655#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003656 #pragma omp critical (MagickCore_ShadeImage)
3657#endif
3658 proceed=SetImageProgress(image,ShadeImageTag,progress++,image->rows);
3659 if (proceed == MagickFalse)
3660 status=MagickFalse;
3661 }
3662 }
3663 shade_view=DestroyCacheView(shade_view);
3664 image_view=DestroyCacheView(image_view);
3665 if (status == MagickFalse)
3666 shade_image=DestroyImage(shade_image);
3667 return(shade_image);
3668}
3669
3670/*
3671%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3672% %
3673% %
3674% %
3675% S h a r p e n I m a g e %
3676% %
3677% %
3678% %
3679%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3680%
3681% SharpenImage() sharpens the image. We convolve the image with a Gaussian
3682% operator of the given radius and standard deviation (sigma). For
3683% reasonable results, radius should be larger than sigma. Use a radius of 0
3684% and SharpenImage() selects a suitable radius for you.
3685%
3686% Using a separable kernel would be faster, but the negative weights cancel
3687% out on the corners of the kernel producing often undesirable ringing in the
3688% filtered result; this can be avoided by using a 2D gaussian shaped image
3689% sharpening kernel instead.
3690%
3691% The format of the SharpenImage method is:
3692%
3693% Image *SharpenImage(const Image *image,const double radius,
cristy05c0c9a2011-09-05 23:16:13 +00003694% const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003695%
3696% A description of each parameter follows:
3697%
3698% o image: the image.
3699%
cristy3ed852e2009-09-05 21:47:34 +00003700% o radius: the radius of the Gaussian, in pixels, not counting the center
3701% pixel.
3702%
3703% o sigma: the standard deviation of the Laplacian, in pixels.
3704%
cristy05c0c9a2011-09-05 23:16:13 +00003705% o bias: bias.
3706%
cristy3ed852e2009-09-05 21:47:34 +00003707% o exception: return any errors or warnings in this structure.
3708%
3709*/
cristy3ed852e2009-09-05 21:47:34 +00003710MagickExport Image *SharpenImage(const Image *image,const double radius,
cristy05c0c9a2011-09-05 23:16:13 +00003711 const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003712{
cristy3ed852e2009-09-05 21:47:34 +00003713 double
cristy47e00502009-12-17 19:19:57 +00003714 normalize;
cristy3ed852e2009-09-05 21:47:34 +00003715
3716 Image
3717 *sharp_image;
3718
cristy41cbe682011-07-15 19:12:37 +00003719 KernelInfo
3720 *kernel_info;
3721
cristybb503372010-05-27 20:51:26 +00003722 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003723 i;
3724
cristybb503372010-05-27 20:51:26 +00003725 size_t
cristy3ed852e2009-09-05 21:47:34 +00003726 width;
3727
cristy117ff172010-08-15 21:35:32 +00003728 ssize_t
3729 j,
3730 u,
3731 v;
3732
cristy3ed852e2009-09-05 21:47:34 +00003733 assert(image != (const Image *) NULL);
3734 assert(image->signature == MagickSignature);
3735 if (image->debug != MagickFalse)
3736 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3737 assert(exception != (ExceptionInfo *) NULL);
3738 assert(exception->signature == MagickSignature);
3739 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00003740 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00003741 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003742 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00003743 (void) ResetMagickMemory(kernel_info,0,sizeof(*kernel_info));
3744 kernel_info->width=width;
3745 kernel_info->height=width;
cristy05c0c9a2011-09-05 23:16:13 +00003746 kernel_info->bias=bias;
cristy41cbe682011-07-15 19:12:37 +00003747 kernel_info->signature=MagickSignature;
3748 kernel_info->values=(double *) AcquireAlignedMemory(kernel_info->width,
3749 kernel_info->width*sizeof(*kernel_info->values));
3750 if (kernel_info->values == (double *) NULL)
3751 {
3752 kernel_info=DestroyKernelInfo(kernel_info);
3753 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3754 }
cristy3ed852e2009-09-05 21:47:34 +00003755 normalize=0.0;
cristy41cbe682011-07-15 19:12:37 +00003756 j=(ssize_t) kernel_info->width/2;
cristy47e00502009-12-17 19:19:57 +00003757 i=0;
3758 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00003759 {
cristy47e00502009-12-17 19:19:57 +00003760 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00003761 {
cristy41cbe682011-07-15 19:12:37 +00003762 kernel_info->values[i]=(double) (-exp(-((double) u*u+v*v)/(2.0*
3763 MagickSigma*MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
3764 normalize+=kernel_info->values[i];
cristy3ed852e2009-09-05 21:47:34 +00003765 i++;
3766 }
3767 }
cristy41cbe682011-07-15 19:12:37 +00003768 kernel_info->values[i/2]=(double) ((-2.0)*normalize);
cristy5e6be1e2011-07-16 01:23:39 +00003769 sharp_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00003770 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00003771 return(sharp_image);
3772}
3773
3774/*
3775%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3776% %
3777% %
3778% %
3779% S p r e a d I m a g e %
3780% %
3781% %
3782% %
3783%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3784%
3785% SpreadImage() is a special effects method that randomly displaces each
3786% pixel in a block defined by the radius parameter.
3787%
3788% The format of the SpreadImage method is:
3789%
3790% Image *SpreadImage(const Image *image,const double radius,
3791% ExceptionInfo *exception)
3792%
3793% A description of each parameter follows:
3794%
3795% o image: the image.
3796%
3797% o radius: Choose a random pixel in a neighborhood of this extent.
3798%
3799% o exception: return any errors or warnings in this structure.
3800%
3801*/
3802MagickExport Image *SpreadImage(const Image *image,const double radius,
3803 ExceptionInfo *exception)
3804{
3805#define SpreadImageTag "Spread/Image"
3806
cristyfa112112010-01-04 17:48:07 +00003807 CacheView
cristy9f7e7cb2011-03-26 00:49:57 +00003808 *image_view,
3809 *spread_view;
cristyfa112112010-01-04 17:48:07 +00003810
cristy3ed852e2009-09-05 21:47:34 +00003811 Image
3812 *spread_image;
3813
cristy3ed852e2009-09-05 21:47:34 +00003814 MagickBooleanType
3815 status;
3816
cristybb503372010-05-27 20:51:26 +00003817 MagickOffsetType
3818 progress;
3819
cristy3ed852e2009-09-05 21:47:34 +00003820 RandomInfo
cristyfa112112010-01-04 17:48:07 +00003821 **restrict random_info;
cristy3ed852e2009-09-05 21:47:34 +00003822
cristybb503372010-05-27 20:51:26 +00003823 size_t
cristy3ed852e2009-09-05 21:47:34 +00003824 width;
3825
cristybb503372010-05-27 20:51:26 +00003826 ssize_t
3827 y;
3828
cristy3ed852e2009-09-05 21:47:34 +00003829 /*
3830 Initialize spread image attributes.
3831 */
3832 assert(image != (Image *) NULL);
3833 assert(image->signature == MagickSignature);
3834 if (image->debug != MagickFalse)
3835 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3836 assert(exception != (ExceptionInfo *) NULL);
3837 assert(exception->signature == MagickSignature);
3838 spread_image=CloneImage(image,image->columns,image->rows,MagickTrue,
3839 exception);
3840 if (spread_image == (Image *) NULL)
3841 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00003842 if (SetImageStorageClass(spread_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003843 {
cristy3ed852e2009-09-05 21:47:34 +00003844 spread_image=DestroyImage(spread_image);
3845 return((Image *) NULL);
3846 }
3847 /*
3848 Spread image.
3849 */
3850 status=MagickTrue;
3851 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00003852 width=GetOptimalKernelWidth1D(radius,0.5);
cristy3ed852e2009-09-05 21:47:34 +00003853 random_info=AcquireRandomInfoThreadSet();
cristy9f7e7cb2011-03-26 00:49:57 +00003854 image_view=AcquireCacheView(image);
3855 spread_view=AcquireCacheView(spread_image);
cristyb557a152011-02-22 12:14:30 +00003856#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00003857 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00003858#endif
cristybe82ad52011-09-06 01:17:20 +00003859 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003860 {
cristy5c9e6f22010-09-17 17:31:01 +00003861 const int
3862 id = GetOpenMPThreadId();
cristy6ebe97c2010-07-03 01:17:28 +00003863
cristybe82ad52011-09-06 01:17:20 +00003864 register const Quantum
3865 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003866
cristy4c08aed2011-07-01 19:47:50 +00003867 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003868 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003869
cristy117ff172010-08-15 21:35:32 +00003870 register ssize_t
3871 x;
3872
cristy3ed852e2009-09-05 21:47:34 +00003873 if (status == MagickFalse)
3874 continue;
cristybe82ad52011-09-06 01:17:20 +00003875 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy9f7e7cb2011-03-26 00:49:57 +00003876 q=QueueCacheViewAuthenticPixels(spread_view,0,y,spread_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +00003877 exception);
cristybe82ad52011-09-06 01:17:20 +00003878 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003879 {
3880 status=MagickFalse;
3881 continue;
3882 }
cristybe82ad52011-09-06 01:17:20 +00003883 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003884 {
cristybe82ad52011-09-06 01:17:20 +00003885 PointInfo
3886 point;
3887
3888 register ssize_t
3889 i;
3890
3891 point.x=GetPseudoRandomValue(random_info[id]);
3892 point.y=GetPseudoRandomValue(random_info[id]);
3893 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3894 {
3895 MagickRealType
3896 pixel;
3897
3898 PixelChannel
3899 channel;
3900
3901 PixelTrait
3902 spread_traits,
3903 traits;
3904
3905 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
3906 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
3907 spread_traits=GetPixelChannelMapTraits(spread_image,channel);
3908 if ((traits == UndefinedPixelTrait) ||
3909 (spread_traits == UndefinedPixelTrait))
3910 continue;
3911 if ((spread_traits & CopyPixelTrait) != 0)
3912 {
3913 q[channel]=p[i];
3914 continue;
3915 }
3916 status=InterpolatePixelChannel(image,image_view,(PixelChannel) i,
3917 MeshInterpolatePixel,(double) x+width*point.x-0.5,(double) y+width*
3918 point.y-0.5,&pixel,exception);
3919 q[channel]=ClampToQuantum(pixel);
3920 }
cristyed231572011-07-14 02:18:59 +00003921 q+=GetPixelChannels(spread_image);
cristy3ed852e2009-09-05 21:47:34 +00003922 }
cristy9f7e7cb2011-03-26 00:49:57 +00003923 if (SyncCacheViewAuthenticPixels(spread_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003924 status=MagickFalse;
3925 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3926 {
3927 MagickBooleanType
3928 proceed;
3929
cristyb557a152011-02-22 12:14:30 +00003930#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003931 #pragma omp critical (MagickCore_SpreadImage)
3932#endif
3933 proceed=SetImageProgress(image,SpreadImageTag,progress++,image->rows);
3934 if (proceed == MagickFalse)
3935 status=MagickFalse;
3936 }
3937 }
cristy9f7e7cb2011-03-26 00:49:57 +00003938 spread_view=DestroyCacheView(spread_view);
cristy3ed852e2009-09-05 21:47:34 +00003939 image_view=DestroyCacheView(image_view);
3940 random_info=DestroyRandomInfoThreadSet(random_info);
cristy3ed852e2009-09-05 21:47:34 +00003941 return(spread_image);
3942}
3943
3944/*
3945%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3946% %
3947% %
3948% %
cristy0834d642011-03-18 18:26:08 +00003949% S t a t i s t i c I m a g e %
3950% %
3951% %
3952% %
3953%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3954%
3955% StatisticImage() makes each pixel the min / max / median / mode / etc. of
cristy8d752042011-03-19 01:00:36 +00003956% the neighborhood of the specified width and height.
cristy0834d642011-03-18 18:26:08 +00003957%
3958% The format of the StatisticImage method is:
3959%
3960% Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00003961% const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00003962%
3963% A description of each parameter follows:
3964%
3965% o image: the image.
3966%
cristy0834d642011-03-18 18:26:08 +00003967% o type: the statistic type (median, mode, etc.).
3968%
cristy95c38342011-03-18 22:39:51 +00003969% o width: the width of the pixel neighborhood.
3970%
3971% o height: the height of the pixel neighborhood.
cristy0834d642011-03-18 18:26:08 +00003972%
3973% o exception: return any errors or warnings in this structure.
3974%
3975*/
3976
cristy075ff302011-09-07 01:51:24 +00003977#define ListChannels 1
cristy733678d2011-03-18 21:29:28 +00003978
3979typedef struct _ListNode
3980{
3981 size_t
3982 next[9],
3983 count,
3984 signature;
3985} ListNode;
3986
3987typedef struct _SkipList
3988{
3989 ssize_t
3990 level;
3991
3992 ListNode
3993 *nodes;
3994} SkipList;
3995
3996typedef struct _PixelList
3997{
3998 size_t
cristy6fc86bb2011-03-18 23:45:16 +00003999 length,
cristy733678d2011-03-18 21:29:28 +00004000 seed,
4001 signature;
4002
4003 SkipList
4004 lists[ListChannels];
4005} PixelList;
4006
4007static PixelList *DestroyPixelList(PixelList *pixel_list)
4008{
4009 register ssize_t
4010 i;
4011
4012 if (pixel_list == (PixelList *) NULL)
4013 return((PixelList *) NULL);
4014 for (i=0; i < ListChannels; i++)
4015 if (pixel_list->lists[i].nodes != (ListNode *) NULL)
4016 pixel_list->lists[i].nodes=(ListNode *) RelinquishMagickMemory(
4017 pixel_list->lists[i].nodes);
4018 pixel_list=(PixelList *) RelinquishMagickMemory(pixel_list);
4019 return(pixel_list);
4020}
4021
4022static PixelList **DestroyPixelListThreadSet(PixelList **pixel_list)
4023{
4024 register ssize_t
4025 i;
4026
4027 assert(pixel_list != (PixelList **) NULL);
4028 for (i=0; i < (ssize_t) GetOpenMPMaximumThreads(); i++)
4029 if (pixel_list[i] != (PixelList *) NULL)
4030 pixel_list[i]=DestroyPixelList(pixel_list[i]);
4031 pixel_list=(PixelList **) RelinquishMagickMemory(pixel_list);
4032 return(pixel_list);
4033}
4034
cristy6fc86bb2011-03-18 23:45:16 +00004035static PixelList *AcquirePixelList(const size_t width,const size_t height)
cristy733678d2011-03-18 21:29:28 +00004036{
4037 PixelList
4038 *pixel_list;
4039
4040 register ssize_t
4041 i;
4042
4043 pixel_list=(PixelList *) AcquireMagickMemory(sizeof(*pixel_list));
4044 if (pixel_list == (PixelList *) NULL)
4045 return(pixel_list);
4046 (void) ResetMagickMemory((void *) pixel_list,0,sizeof(*pixel_list));
cristy6fc86bb2011-03-18 23:45:16 +00004047 pixel_list->length=width*height;
cristy733678d2011-03-18 21:29:28 +00004048 for (i=0; i < ListChannels; i++)
4049 {
4050 pixel_list->lists[i].nodes=(ListNode *) AcquireQuantumMemory(65537UL,
4051 sizeof(*pixel_list->lists[i].nodes));
4052 if (pixel_list->lists[i].nodes == (ListNode *) NULL)
4053 return(DestroyPixelList(pixel_list));
4054 (void) ResetMagickMemory(pixel_list->lists[i].nodes,0,65537UL*
4055 sizeof(*pixel_list->lists[i].nodes));
4056 }
4057 pixel_list->signature=MagickSignature;
4058 return(pixel_list);
4059}
4060
cristy6fc86bb2011-03-18 23:45:16 +00004061static PixelList **AcquirePixelListThreadSet(const size_t width,
4062 const size_t height)
cristy733678d2011-03-18 21:29:28 +00004063{
4064 PixelList
4065 **pixel_list;
4066
4067 register ssize_t
4068 i;
4069
4070 size_t
4071 number_threads;
4072
4073 number_threads=GetOpenMPMaximumThreads();
4074 pixel_list=(PixelList **) AcquireQuantumMemory(number_threads,
4075 sizeof(*pixel_list));
4076 if (pixel_list == (PixelList **) NULL)
4077 return((PixelList **) NULL);
4078 (void) ResetMagickMemory(pixel_list,0,number_threads*sizeof(*pixel_list));
4079 for (i=0; i < (ssize_t) number_threads; i++)
4080 {
cristy6fc86bb2011-03-18 23:45:16 +00004081 pixel_list[i]=AcquirePixelList(width,height);
cristy733678d2011-03-18 21:29:28 +00004082 if (pixel_list[i] == (PixelList *) NULL)
4083 return(DestroyPixelListThreadSet(pixel_list));
4084 }
4085 return(pixel_list);
4086}
4087
4088static void AddNodePixelList(PixelList *pixel_list,const ssize_t channel,
4089 const size_t color)
4090{
4091 register SkipList
4092 *list;
4093
4094 register ssize_t
4095 level;
4096
4097 size_t
4098 search,
4099 update[9];
4100
4101 /*
4102 Initialize the node.
4103 */
4104 list=pixel_list->lists+channel;
4105 list->nodes[color].signature=pixel_list->signature;
4106 list->nodes[color].count=1;
4107 /*
4108 Determine where it belongs in the list.
4109 */
4110 search=65536UL;
4111 for (level=list->level; level >= 0; level--)
4112 {
4113 while (list->nodes[search].next[level] < color)
4114 search=list->nodes[search].next[level];
4115 update[level]=search;
4116 }
4117 /*
4118 Generate a pseudo-random level for this node.
4119 */
4120 for (level=0; ; level++)
4121 {
4122 pixel_list->seed=(pixel_list->seed*42893621L)+1L;
4123 if ((pixel_list->seed & 0x300) != 0x300)
4124 break;
4125 }
4126 if (level > 8)
4127 level=8;
4128 if (level > (list->level+2))
4129 level=list->level+2;
4130 /*
4131 If we're raising the list's level, link back to the root node.
4132 */
4133 while (level > list->level)
4134 {
4135 list->level++;
4136 update[list->level]=65536UL;
4137 }
4138 /*
4139 Link the node into the skip-list.
4140 */
4141 do
4142 {
4143 list->nodes[color].next[level]=list->nodes[update[level]].next[level];
4144 list->nodes[update[level]].next[level]=color;
cristy3cba8ca2011-03-19 01:29:12 +00004145 } while (level-- > 0);
cristy733678d2011-03-18 21:29:28 +00004146}
4147
cristy075ff302011-09-07 01:51:24 +00004148static Quantum GetMaximumPixelList(PixelList *pixel_list)
cristy6fc86bb2011-03-18 23:45:16 +00004149{
cristy6fc86bb2011-03-18 23:45:16 +00004150 register SkipList
4151 *list;
4152
4153 register ssize_t
4154 channel;
4155
4156 size_t
cristyd76c51e2011-03-26 00:21:26 +00004157 color,
4158 maximum;
cristy49f37242011-03-22 18:18:23 +00004159
4160 ssize_t
cristy6fc86bb2011-03-18 23:45:16 +00004161 count;
4162
4163 unsigned short
cristyd76c51e2011-03-26 00:21:26 +00004164 channels[ListChannels];
cristy6fc86bb2011-03-18 23:45:16 +00004165
4166 /*
4167 Find the maximum value for each of the color.
4168 */
cristy448e3a32011-09-07 01:09:15 +00004169 for (channel=0; channel < ListChannels; channel++)
cristy6fc86bb2011-03-18 23:45:16 +00004170 {
4171 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004172 color=65536L;
cristy6fc86bb2011-03-18 23:45:16 +00004173 count=0;
cristy49f37242011-03-22 18:18:23 +00004174 maximum=list->nodes[color].next[0];
cristy6fc86bb2011-03-18 23:45:16 +00004175 do
4176 {
4177 color=list->nodes[color].next[0];
cristy49f37242011-03-22 18:18:23 +00004178 if (color > maximum)
4179 maximum=color;
cristy6fc86bb2011-03-18 23:45:16 +00004180 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004181 } while (count < (ssize_t) pixel_list->length);
cristy49f37242011-03-22 18:18:23 +00004182 channels[channel]=(unsigned short) maximum;
4183 }
cristy075ff302011-09-07 01:51:24 +00004184 return((MagickRealType) ScaleShortToQuantum(channels[0]));
cristy49f37242011-03-22 18:18:23 +00004185}
4186
cristy075ff302011-09-07 01:51:24 +00004187static Quantum GetMeanPixelList(PixelList *pixel_list)
cristy49f37242011-03-22 18:18:23 +00004188{
cristy80a99a32011-03-30 01:30:23 +00004189 MagickRealType
4190 sum;
4191
cristy49f37242011-03-22 18:18:23 +00004192 register SkipList
4193 *list;
4194
4195 register ssize_t
4196 channel;
4197
4198 size_t
cristy80a99a32011-03-30 01:30:23 +00004199 color;
cristy49f37242011-03-22 18:18:23 +00004200
4201 ssize_t
4202 count;
4203
4204 unsigned short
4205 channels[ListChannels];
4206
4207 /*
4208 Find the mean value for each of the color.
4209 */
cristy448e3a32011-09-07 01:09:15 +00004210 for (channel=0; channel < ListChannels; channel++)
cristy49f37242011-03-22 18:18:23 +00004211 {
4212 list=pixel_list->lists+channel;
4213 color=65536L;
4214 count=0;
cristy80a99a32011-03-30 01:30:23 +00004215 sum=0.0;
cristy49f37242011-03-22 18:18:23 +00004216 do
4217 {
4218 color=list->nodes[color].next[0];
cristy80a99a32011-03-30 01:30:23 +00004219 sum+=(MagickRealType) list->nodes[color].count*color;
cristy49f37242011-03-22 18:18:23 +00004220 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004221 } while (count < (ssize_t) pixel_list->length);
cristy80a99a32011-03-30 01:30:23 +00004222 sum/=pixel_list->length;
4223 channels[channel]=(unsigned short) sum;
cristy6fc86bb2011-03-18 23:45:16 +00004224 }
cristy075ff302011-09-07 01:51:24 +00004225 return((MagickRealType) ScaleShortToQuantum(channels[0]));
cristy6fc86bb2011-03-18 23:45:16 +00004226}
4227
cristy075ff302011-09-07 01:51:24 +00004228static Quantum GetMedianPixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004229{
cristy733678d2011-03-18 21:29:28 +00004230 register SkipList
4231 *list;
4232
4233 register ssize_t
4234 channel;
4235
4236 size_t
cristy49f37242011-03-22 18:18:23 +00004237 color;
4238
4239 ssize_t
cristy733678d2011-03-18 21:29:28 +00004240 count;
4241
4242 unsigned short
4243 channels[ListChannels];
4244
4245 /*
4246 Find the median value for each of the color.
4247 */
cristy448e3a32011-09-07 01:09:15 +00004248 for (channel=0; channel < ListChannels; channel++)
cristy733678d2011-03-18 21:29:28 +00004249 {
4250 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004251 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004252 count=0;
4253 do
4254 {
4255 color=list->nodes[color].next[0];
4256 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004257 } while (count <= (ssize_t) (pixel_list->length >> 1));
cristy6fc86bb2011-03-18 23:45:16 +00004258 channels[channel]=(unsigned short) color;
4259 }
cristy075ff302011-09-07 01:51:24 +00004260 return((MagickRealType) ScaleShortToQuantum(channels[0]));
cristy6fc86bb2011-03-18 23:45:16 +00004261}
4262
cristy075ff302011-09-07 01:51:24 +00004263static Quantum GetMinimumPixelList(PixelList *pixel_list)
cristy6fc86bb2011-03-18 23:45:16 +00004264{
cristy6fc86bb2011-03-18 23:45:16 +00004265 register SkipList
4266 *list;
4267
4268 register ssize_t
4269 channel;
4270
4271 size_t
cristyd76c51e2011-03-26 00:21:26 +00004272 color,
4273 minimum;
cristy6fc86bb2011-03-18 23:45:16 +00004274
cristy49f37242011-03-22 18:18:23 +00004275 ssize_t
4276 count;
4277
cristy6fc86bb2011-03-18 23:45:16 +00004278 unsigned short
cristyd76c51e2011-03-26 00:21:26 +00004279 channels[ListChannels];
cristy6fc86bb2011-03-18 23:45:16 +00004280
4281 /*
4282 Find the minimum value for each of the color.
4283 */
cristy448e3a32011-09-07 01:09:15 +00004284 for (channel=0; channel < ListChannels; channel++)
cristy6fc86bb2011-03-18 23:45:16 +00004285 {
4286 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004287 count=0;
cristy6fc86bb2011-03-18 23:45:16 +00004288 color=65536UL;
cristy49f37242011-03-22 18:18:23 +00004289 minimum=list->nodes[color].next[0];
4290 do
4291 {
4292 color=list->nodes[color].next[0];
4293 if (color < minimum)
4294 minimum=color;
4295 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004296 } while (count < (ssize_t) pixel_list->length);
cristy49f37242011-03-22 18:18:23 +00004297 channels[channel]=(unsigned short) minimum;
cristy733678d2011-03-18 21:29:28 +00004298 }
cristy075ff302011-09-07 01:51:24 +00004299 return((MagickRealType) ScaleShortToQuantum(channels[0]));
cristy733678d2011-03-18 21:29:28 +00004300}
4301
cristy075ff302011-09-07 01:51:24 +00004302static Quantum GetModePixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004303{
cristy733678d2011-03-18 21:29:28 +00004304 register SkipList
4305 *list;
4306
4307 register ssize_t
4308 channel;
4309
4310 size_t
4311 color,
cristy733678d2011-03-18 21:29:28 +00004312 max_count,
cristy6fc86bb2011-03-18 23:45:16 +00004313 mode;
cristy733678d2011-03-18 21:29:28 +00004314
cristy49f37242011-03-22 18:18:23 +00004315 ssize_t
4316 count;
4317
cristy733678d2011-03-18 21:29:28 +00004318 unsigned short
cristy075ff302011-09-07 01:51:24 +00004319 channels[ListChannels];
cristy733678d2011-03-18 21:29:28 +00004320
4321 /*
glennrp30d2dc62011-06-25 03:17:16 +00004322 Make each pixel the 'predominant color' of the specified neighborhood.
cristy733678d2011-03-18 21:29:28 +00004323 */
cristy448e3a32011-09-07 01:09:15 +00004324 for (channel=0; channel < ListChannels; channel++)
cristy733678d2011-03-18 21:29:28 +00004325 {
4326 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004327 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004328 mode=color;
4329 max_count=list->nodes[mode].count;
4330 count=0;
4331 do
4332 {
4333 color=list->nodes[color].next[0];
4334 if (list->nodes[color].count > max_count)
4335 {
4336 mode=color;
4337 max_count=list->nodes[mode].count;
4338 }
4339 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004340 } while (count < (ssize_t) pixel_list->length);
cristy733678d2011-03-18 21:29:28 +00004341 channels[channel]=(unsigned short) mode;
4342 }
cristy075ff302011-09-07 01:51:24 +00004343 return((MagickRealType) ScaleShortToQuantum(channels[0]));
cristy733678d2011-03-18 21:29:28 +00004344}
4345
cristy075ff302011-09-07 01:51:24 +00004346static Quantum GetNonpeakPixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004347{
cristy733678d2011-03-18 21:29:28 +00004348 register SkipList
4349 *list;
4350
4351 register ssize_t
4352 channel;
4353
4354 size_t
cristy733678d2011-03-18 21:29:28 +00004355 color,
cristy733678d2011-03-18 21:29:28 +00004356 next,
4357 previous;
4358
cristy49f37242011-03-22 18:18:23 +00004359 ssize_t
4360 count;
4361
cristy733678d2011-03-18 21:29:28 +00004362 unsigned short
cristy075ff302011-09-07 01:51:24 +00004363 channels[ListChannels];
cristy733678d2011-03-18 21:29:28 +00004364
4365 /*
cristy49f37242011-03-22 18:18:23 +00004366 Finds the non peak value for each of the colors.
cristy733678d2011-03-18 21:29:28 +00004367 */
cristy448e3a32011-09-07 01:09:15 +00004368 for (channel=0; channel < ListChannels; channel++)
cristy733678d2011-03-18 21:29:28 +00004369 {
4370 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004371 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004372 next=list->nodes[color].next[0];
4373 count=0;
4374 do
4375 {
4376 previous=color;
4377 color=next;
4378 next=list->nodes[color].next[0];
4379 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004380 } while (count <= (ssize_t) (pixel_list->length >> 1));
cristy733678d2011-03-18 21:29:28 +00004381 if ((previous == 65536UL) && (next != 65536UL))
4382 color=next;
4383 else
4384 if ((previous != 65536UL) && (next == 65536UL))
4385 color=previous;
4386 channels[channel]=(unsigned short) color;
4387 }
cristy075ff302011-09-07 01:51:24 +00004388 return((MagickRealType) ScaleShortToQuantum(channels[0]));
cristy733678d2011-03-18 21:29:28 +00004389}
4390
cristy075ff302011-09-07 01:51:24 +00004391static Quantum GetStandardDeviationPixelList(PixelList *pixel_list)
cristy9a68cbb2011-03-29 00:51:23 +00004392{
cristy80a99a32011-03-30 01:30:23 +00004393 MagickRealType
4394 sum,
4395 sum_squared;
4396
cristy9a68cbb2011-03-29 00:51:23 +00004397 register SkipList
4398 *list;
4399
4400 register ssize_t
4401 channel;
4402
4403 size_t
cristy80a99a32011-03-30 01:30:23 +00004404 color;
cristy9a68cbb2011-03-29 00:51:23 +00004405
4406 ssize_t
4407 count;
4408
4409 unsigned short
4410 channels[ListChannels];
4411
4412 /*
cristy80a99a32011-03-30 01:30:23 +00004413 Find the standard-deviation value for each of the color.
cristy9a68cbb2011-03-29 00:51:23 +00004414 */
cristy448e3a32011-09-07 01:09:15 +00004415 for (channel=0; channel < ListChannels; channel++)
cristy9a68cbb2011-03-29 00:51:23 +00004416 {
4417 list=pixel_list->lists+channel;
4418 color=65536L;
4419 count=0;
cristy80a99a32011-03-30 01:30:23 +00004420 sum=0.0;
4421 sum_squared=0.0;
cristy9a68cbb2011-03-29 00:51:23 +00004422 do
4423 {
cristy80a99a32011-03-30 01:30:23 +00004424 register ssize_t
4425 i;
4426
cristy9a68cbb2011-03-29 00:51:23 +00004427 color=list->nodes[color].next[0];
cristy80a99a32011-03-30 01:30:23 +00004428 sum+=(MagickRealType) list->nodes[color].count*color;
4429 for (i=0; i < (ssize_t) list->nodes[color].count; i++)
4430 sum_squared+=((MagickRealType) color)*((MagickRealType) color);
cristy9a68cbb2011-03-29 00:51:23 +00004431 count+=list->nodes[color].count;
4432 } while (count < (ssize_t) pixel_list->length);
cristy80a99a32011-03-30 01:30:23 +00004433 sum/=pixel_list->length;
4434 sum_squared/=pixel_list->length;
4435 channels[channel]=(unsigned short) sqrt(sum_squared-(sum*sum));
cristy9a68cbb2011-03-29 00:51:23 +00004436 }
cristy075ff302011-09-07 01:51:24 +00004437 return((MagickRealType) ScaleShortToQuantum(channels[0]));
cristy9a68cbb2011-03-29 00:51:23 +00004438}
4439
cristy4c08aed2011-07-01 19:47:50 +00004440static inline void InsertPixelList(const Image *image,const Quantum *pixel,
4441 PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004442{
4443 size_t
4444 signature;
4445
4446 unsigned short
4447 index;
4448
cristy075ff302011-09-07 01:51:24 +00004449 index=ScaleQuantumToShort(*pixel);
cristy733678d2011-03-18 21:29:28 +00004450 signature=pixel_list->lists[0].nodes[index].signature;
4451 if (signature == pixel_list->signature)
4452 pixel_list->lists[0].nodes[index].count++;
4453 else
4454 AddNodePixelList(pixel_list,0,index);
cristy733678d2011-03-18 21:29:28 +00004455}
4456
cristy80c99742011-04-04 14:46:39 +00004457static inline MagickRealType MagickAbsoluteValue(const MagickRealType x)
4458{
4459 if (x < 0)
4460 return(-x);
4461 return(x);
4462}
4463
cristy733678d2011-03-18 21:29:28 +00004464static void ResetPixelList(PixelList *pixel_list)
4465{
4466 int
4467 level;
4468
4469 register ListNode
4470 *root;
4471
4472 register SkipList
4473 *list;
4474
4475 register ssize_t
4476 channel;
4477
4478 /*
4479 Reset the skip-list.
4480 */
cristy448e3a32011-09-07 01:09:15 +00004481 for (channel=0; channel < ListChannels; channel++)
cristy733678d2011-03-18 21:29:28 +00004482 {
4483 list=pixel_list->lists+channel;
4484 root=list->nodes+65536UL;
4485 list->level=0;
4486 for (level=0; level < 9; level++)
4487 root->next[level]=65536UL;
4488 }
4489 pixel_list->seed=pixel_list->signature++;
4490}
4491
cristy0834d642011-03-18 18:26:08 +00004492MagickExport Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00004493 const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00004494{
cristy3cba8ca2011-03-19 01:29:12 +00004495#define StatisticWidth \
cristyd76c51e2011-03-26 00:21:26 +00004496 (width == 0 ? GetOptimalKernelWidth2D((double) width,0.5) : width)
cristy3cba8ca2011-03-19 01:29:12 +00004497#define StatisticHeight \
cristyd76c51e2011-03-26 00:21:26 +00004498 (height == 0 ? GetOptimalKernelWidth2D((double) height,0.5) : height)
cristy0834d642011-03-18 18:26:08 +00004499#define StatisticImageTag "Statistic/Image"
4500
4501 CacheView
4502 *image_view,
4503 *statistic_view;
4504
4505 Image
4506 *statistic_image;
4507
4508 MagickBooleanType
4509 status;
4510
4511 MagickOffsetType
4512 progress;
4513
4514 PixelList
4515 **restrict pixel_list;
4516
cristy0834d642011-03-18 18:26:08 +00004517 ssize_t
cristy075ff302011-09-07 01:51:24 +00004518 center,
cristy0834d642011-03-18 18:26:08 +00004519 y;
4520
4521 /*
4522 Initialize statistics image attributes.
4523 */
4524 assert(image != (Image *) NULL);
4525 assert(image->signature == MagickSignature);
4526 if (image->debug != MagickFalse)
4527 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4528 assert(exception != (ExceptionInfo *) NULL);
4529 assert(exception->signature == MagickSignature);
cristy0834d642011-03-18 18:26:08 +00004530 statistic_image=CloneImage(image,image->columns,image->rows,MagickTrue,
4531 exception);
4532 if (statistic_image == (Image *) NULL)
4533 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00004534 if (SetImageStorageClass(statistic_image,DirectClass,exception) == MagickFalse)
cristy0834d642011-03-18 18:26:08 +00004535 {
cristy0834d642011-03-18 18:26:08 +00004536 statistic_image=DestroyImage(statistic_image);
4537 return((Image *) NULL);
4538 }
cristy6fc86bb2011-03-18 23:45:16 +00004539 pixel_list=AcquirePixelListThreadSet(StatisticWidth,StatisticHeight);
cristy0834d642011-03-18 18:26:08 +00004540 if (pixel_list == (PixelList **) NULL)
4541 {
4542 statistic_image=DestroyImage(statistic_image);
4543 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
4544 }
4545 /*
cristy8d752042011-03-19 01:00:36 +00004546 Make each pixel the min / max / median / mode / etc. of the neighborhood.
cristy0834d642011-03-18 18:26:08 +00004547 */
cristy075ff302011-09-07 01:51:24 +00004548 center=(ssize_t) GetPixelChannels(image)*(image->columns+StatisticWidth)*
4549 (StatisticHeight/2L)+GetPixelChannels(image)*(StatisticWidth/2L);
cristy0834d642011-03-18 18:26:08 +00004550 status=MagickTrue;
4551 progress=0;
4552 image_view=AcquireCacheView(image);
4553 statistic_view=AcquireCacheView(statistic_image);
4554#if defined(MAGICKCORE_OPENMP_SUPPORT)
4555 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
4556#endif
4557 for (y=0; y < (ssize_t) statistic_image->rows; y++)
4558 {
4559 const int
4560 id = GetOpenMPThreadId();
4561
cristy4c08aed2011-07-01 19:47:50 +00004562 register const Quantum
cristy0834d642011-03-18 18:26:08 +00004563 *restrict p;
4564
cristy4c08aed2011-07-01 19:47:50 +00004565 register Quantum
cristy0834d642011-03-18 18:26:08 +00004566 *restrict q;
4567
4568 register ssize_t
4569 x;
4570
4571 if (status == MagickFalse)
4572 continue;
cristy6fc86bb2011-03-18 23:45:16 +00004573 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) StatisticWidth/2L),y-
4574 (ssize_t) (StatisticHeight/2L),image->columns+StatisticWidth,
4575 StatisticHeight,exception);
cristy3cba8ca2011-03-19 01:29:12 +00004576 q=QueueCacheViewAuthenticPixels(statistic_view,0,y,statistic_image->columns, 1,exception);
cristy4c08aed2011-07-01 19:47:50 +00004577 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy0834d642011-03-18 18:26:08 +00004578 {
4579 status=MagickFalse;
4580 continue;
4581 }
cristy0834d642011-03-18 18:26:08 +00004582 for (x=0; x < (ssize_t) statistic_image->columns; x++)
4583 {
cristy0834d642011-03-18 18:26:08 +00004584 register ssize_t
cristy075ff302011-09-07 01:51:24 +00004585 i;
cristy0834d642011-03-18 18:26:08 +00004586
cristy075ff302011-09-07 01:51:24 +00004587 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy0834d642011-03-18 18:26:08 +00004588 {
cristy075ff302011-09-07 01:51:24 +00004589 MagickRealType
4590 pixel;
cristy80c99742011-04-04 14:46:39 +00004591
cristy075ff302011-09-07 01:51:24 +00004592 PixelChannel
4593 channel;
4594
4595 PixelTrait
4596 statistic_traits,
4597 traits;
4598
4599 register const Quantum
4600 *restrict pixels;
4601
4602 register ssize_t
4603 u;
4604
4605 ssize_t
4606 v;
4607
4608 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
4609 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
4610 statistic_traits=GetPixelChannelMapTraits(statistic_image,channel);
4611 if ((traits == UndefinedPixelTrait) ||
4612 (statistic_traits == UndefinedPixelTrait))
4613 continue;
4614 if ((statistic_traits & CopyPixelTrait) != 0)
4615 {
4616 q[channel]=p[center+i];
4617 continue;
4618 }
4619 pixels=p;
4620 ResetPixelList(pixel_list[id]);
4621 for (v=0; v < (ssize_t) StatisticHeight; v++)
cristy6fc86bb2011-03-18 23:45:16 +00004622 {
cristy075ff302011-09-07 01:51:24 +00004623 for (u=0; u < (ssize_t) StatisticWidth; u++)
4624 {
4625 InsertPixelList(image,pixels+i,pixel_list[id]);
4626 pixels+=GetPixelChannels(image);
4627 }
4628 pixels+=image->columns*GetPixelChannels(image);
cristy6fc86bb2011-03-18 23:45:16 +00004629 }
cristy075ff302011-09-07 01:51:24 +00004630 switch (type)
cristy49f37242011-03-22 18:18:23 +00004631 {
cristy075ff302011-09-07 01:51:24 +00004632 case GradientStatistic:
4633 {
4634 MagickRealType
4635 maximum,
4636 minimum;
4637
4638 minimum=GetMinimumPixelList(pixel_list[id]);
4639 maximum=GetMaximumPixelList(pixel_list[id]);
4640 pixel=MagickAbsoluteValue(maximum-minimum);
4641 break;
4642 }
4643 case MaximumStatistic:
4644 {
4645 pixel=GetMaximumPixelList(pixel_list[id]);
4646 break;
4647 }
4648 case MeanStatistic:
4649 {
4650 pixel=GetMeanPixelList(pixel_list[id]);
4651 break;
4652 }
4653 case MedianStatistic:
4654 default:
4655 {
4656 pixel=GetMedianPixelList(pixel_list[id]);
4657 break;
4658 }
4659 case MinimumStatistic:
4660 {
4661 pixel=GetMinimumPixelList(pixel_list[id]);
4662 break;
4663 }
4664 case ModeStatistic:
4665 {
4666 pixel=GetModePixelList(pixel_list[id]);
4667 break;
4668 }
4669 case NonpeakStatistic:
4670 {
4671 pixel=GetNonpeakPixelList(pixel_list[id]);
4672 break;
4673 }
4674 case StandardDeviationStatistic:
4675 {
4676 pixel=GetStandardDeviationPixelList(pixel_list[id]);
4677 break;
4678 }
cristy49f37242011-03-22 18:18:23 +00004679 }
cristy075ff302011-09-07 01:51:24 +00004680 q[channel]=ClampToQuantum(pixel);
cristy0834d642011-03-18 18:26:08 +00004681 }
cristyed231572011-07-14 02:18:59 +00004682 p+=GetPixelChannels(image);
4683 q+=GetPixelChannels(statistic_image);
cristy0834d642011-03-18 18:26:08 +00004684 }
4685 if (SyncCacheViewAuthenticPixels(statistic_view,exception) == MagickFalse)
4686 status=MagickFalse;
4687 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4688 {
4689 MagickBooleanType
4690 proceed;
4691
4692#if defined(MAGICKCORE_OPENMP_SUPPORT)
4693 #pragma omp critical (MagickCore_StatisticImage)
4694#endif
4695 proceed=SetImageProgress(image,StatisticImageTag,progress++,
4696 image->rows);
4697 if (proceed == MagickFalse)
4698 status=MagickFalse;
4699 }
4700 }
4701 statistic_view=DestroyCacheView(statistic_view);
4702 image_view=DestroyCacheView(image_view);
4703 pixel_list=DestroyPixelListThreadSet(pixel_list);
4704 return(statistic_image);
4705}
4706
4707/*
4708%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4709% %
4710% %
4711% %
cristy3ed852e2009-09-05 21:47:34 +00004712% U n s h a r p M a s k I m a g e %
4713% %
4714% %
4715% %
4716%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4717%
4718% UnsharpMaskImage() sharpens one or more image channels. We convolve the
4719% image with a Gaussian operator of the given radius and standard deviation
4720% (sigma). For reasonable results, radius should be larger than sigma. Use a
4721% radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
4722%
4723% The format of the UnsharpMaskImage method is:
4724%
4725% Image *UnsharpMaskImage(const Image *image,const double radius,
4726% const double sigma,const double amount,const double threshold,
4727% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00004728%
4729% A description of each parameter follows:
4730%
4731% o image: the image.
4732%
cristy3ed852e2009-09-05 21:47:34 +00004733% o radius: the radius of the Gaussian, in pixels, not counting the center
4734% pixel.
4735%
4736% o sigma: the standard deviation of the Gaussian, in pixels.
4737%
4738% o amount: the percentage of the difference between the original and the
4739% blur image that is added back into the original.
4740%
4741% o threshold: the threshold in pixels needed to apply the diffence amount.
4742%
4743% o exception: return any errors or warnings in this structure.
4744%
4745*/
cristyf4ad9df2011-07-08 16:49:03 +00004746MagickExport Image *UnsharpMaskImage(const Image *image,
4747 const double radius,const double sigma,const double amount,
4748 const double threshold,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00004749{
4750#define SharpenImageTag "Sharpen/Image"
4751
cristyc4c8d132010-01-07 01:58:38 +00004752 CacheView
4753 *image_view,
4754 *unsharp_view;
4755
cristy3ed852e2009-09-05 21:47:34 +00004756 Image
4757 *unsharp_image;
4758
cristy3ed852e2009-09-05 21:47:34 +00004759 MagickBooleanType
4760 status;
4761
cristybb503372010-05-27 20:51:26 +00004762 MagickOffsetType
4763 progress;
4764
cristy3ed852e2009-09-05 21:47:34 +00004765 MagickRealType
4766 quantum_threshold;
4767
cristybb503372010-05-27 20:51:26 +00004768 ssize_t
4769 y;
4770
cristy3ed852e2009-09-05 21:47:34 +00004771 assert(image != (const Image *) NULL);
4772 assert(image->signature == MagickSignature);
4773 if (image->debug != MagickFalse)
4774 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4775 assert(exception != (ExceptionInfo *) NULL);
cristy05c0c9a2011-09-05 23:16:13 +00004776 unsharp_image=BlurImage(image,radius,sigma,image->bias,exception);
cristy3ed852e2009-09-05 21:47:34 +00004777 if (unsharp_image == (Image *) NULL)
4778 return((Image *) NULL);
4779 quantum_threshold=(MagickRealType) QuantumRange*threshold;
4780 /*
4781 Unsharp-mask image.
4782 */
4783 status=MagickTrue;
4784 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00004785 image_view=AcquireCacheView(image);
4786 unsharp_view=AcquireCacheView(unsharp_image);
cristyb5d5f722009-11-04 03:03:49 +00004787#if defined(MAGICKCORE_OPENMP_SUPPORT)
4788 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004789#endif
cristybb503372010-05-27 20:51:26 +00004790 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00004791 {
cristy4c08aed2011-07-01 19:47:50 +00004792 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00004793 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00004794
cristy4c08aed2011-07-01 19:47:50 +00004795 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00004796 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004797
cristy117ff172010-08-15 21:35:32 +00004798 register ssize_t
4799 x;
4800
cristy3ed852e2009-09-05 21:47:34 +00004801 if (status == MagickFalse)
4802 continue;
4803 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
4804 q=GetCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
4805 exception);
cristy4c08aed2011-07-01 19:47:50 +00004806 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00004807 {
4808 status=MagickFalse;
4809 continue;
4810 }
cristybb503372010-05-27 20:51:26 +00004811 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004812 {
cristy7f3a0d12011-09-05 23:27:59 +00004813 register ssize_t
4814 i;
4815
4816 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4817 {
4818 MagickRealType
4819 pixel;
4820
4821 PixelChannel
4822 channel;
4823
4824 PixelTrait
4825 traits,
4826 unsharp_traits;
4827
4828 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
4829 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
4830 unsharp_traits=GetPixelChannelMapTraits(unsharp_image,channel);
4831 if ((traits == UndefinedPixelTrait) ||
4832 (unsharp_traits == UndefinedPixelTrait))
4833 continue;
4834 if ((unsharp_traits & CopyPixelTrait) != 0)
4835 {
4836 q[channel]=p[i];
4837 continue;
4838 }
4839 pixel=p[i]-(MagickRealType) q[channel];
4840 if (fabs(2.0*pixel) < quantum_threshold)
4841 pixel=(MagickRealType) p[i];
4842 else
4843 pixel=(MagickRealType) p[i]+amount*pixel;
4844 q[channel]=ClampToQuantum(pixel);
4845 }
cristyed231572011-07-14 02:18:59 +00004846 p+=GetPixelChannels(image);
4847 q+=GetPixelChannels(unsharp_image);
cristy3ed852e2009-09-05 21:47:34 +00004848 }
4849 if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
4850 status=MagickFalse;
4851 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4852 {
4853 MagickBooleanType
4854 proceed;
4855
cristyb5d5f722009-11-04 03:03:49 +00004856#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00004857 #pragma omp critical (MagickCore_UnsharpMaskImage)
cristy3ed852e2009-09-05 21:47:34 +00004858#endif
4859 proceed=SetImageProgress(image,SharpenImageTag,progress++,image->rows);
4860 if (proceed == MagickFalse)
4861 status=MagickFalse;
4862 }
4863 }
4864 unsharp_image->type=image->type;
4865 unsharp_view=DestroyCacheView(unsharp_view);
4866 image_view=DestroyCacheView(image_view);
4867 if (status == MagickFalse)
4868 unsharp_image=DestroyImage(unsharp_image);
4869 return(unsharp_image);
4870}