blob: ff3b3c7aa87e55b9a3f028c062ffc098326b2be7 [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
1576 Quantum
cristy65b9f392011-02-22 14:22:54 +00001577 *restrict buffers,
1578 *restrict pixels;
cristy3ed852e2009-09-05 21:47:34 +00001579
cristya63e4a92011-09-09 00:47:59 +00001580 register ssize_t
1581 i;
1582
cristy3ed852e2009-09-05 21:47:34 +00001583 size_t
cristya63e4a92011-09-09 00:47:59 +00001584 length;
cristy117ff172010-08-15 21:35:32 +00001585
cristybb503372010-05-27 20:51:26 +00001586 static const ssize_t
cristy691a29e2009-09-11 00:44:10 +00001587 X[4] = {0, 1, 1,-1},
1588 Y[4] = {1, 0, 1, 1};
cristy3ed852e2009-09-05 21:47:34 +00001589
cristy3ed852e2009-09-05 21:47:34 +00001590 /*
1591 Allocate despeckled image.
1592 */
1593 assert(image != (const Image *) NULL);
1594 assert(image->signature == MagickSignature);
1595 if (image->debug != MagickFalse)
1596 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1597 assert(exception != (ExceptionInfo *) NULL);
1598 assert(exception->signature == MagickSignature);
cristya63e4a92011-09-09 00:47:59 +00001599 despeckle_image=CloneImage(image,0,0,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00001600 if (despeckle_image == (Image *) NULL)
1601 return((Image *) NULL);
cristya63e4a92011-09-09 00:47:59 +00001602 status=SetImageStorageClass(despeckle_image,DirectClass,exception);
1603 if (status == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001604 {
cristy3ed852e2009-09-05 21:47:34 +00001605 despeckle_image=DestroyImage(despeckle_image);
1606 return((Image *) NULL);
1607 }
1608 /*
1609 Allocate image buffers.
1610 */
1611 length=(size_t) ((image->columns+2)*(image->rows+2));
cristy65b9f392011-02-22 14:22:54 +00001612 pixels=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1613 buffers=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1614 if ((pixels == (Quantum *) NULL) || (buffers == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001615 {
cristy65b9f392011-02-22 14:22:54 +00001616 if (buffers != (Quantum *) NULL)
1617 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1618 if (pixels != (Quantum *) NULL)
1619 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001620 despeckle_image=DestroyImage(despeckle_image);
1621 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1622 }
1623 /*
1624 Reduce speckle in the image.
1625 */
1626 status=MagickTrue;
1627 image_view=AcquireCacheView(image);
1628 despeckle_view=AcquireCacheView(despeckle_image);
cristya63e4a92011-09-09 00:47:59 +00001629 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy3ed852e2009-09-05 21:47:34 +00001630 {
cristya63e4a92011-09-09 00:47:59 +00001631 PixelChannel
1632 channel;
1633
1634 PixelTrait
1635 despeckle_traits,
1636 traits;
1637
cristy3ed852e2009-09-05 21:47:34 +00001638 register Quantum
1639 *buffer,
1640 *pixel;
1641
cristyc1488b52011-02-19 18:54:15 +00001642 register ssize_t
cristya58c3172011-02-19 19:23:11 +00001643 k,
cristyc1488b52011-02-19 18:54:15 +00001644 x;
1645
cristy117ff172010-08-15 21:35:32 +00001646 ssize_t
1647 j,
1648 y;
1649
cristy3ed852e2009-09-05 21:47:34 +00001650 if (status == MagickFalse)
1651 continue;
cristya63e4a92011-09-09 00:47:59 +00001652 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
1653 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
1654 despeckle_traits=GetPixelChannelMapTraits(despeckle_image,channel);
1655 if ((traits == UndefinedPixelTrait) ||
1656 (despeckle_traits == UndefinedPixelTrait))
1657 continue;
1658 if ((despeckle_traits & CopyPixelTrait) != 0)
1659 continue;
cristy65b9f392011-02-22 14:22:54 +00001660 pixel=pixels;
cristy3ed852e2009-09-05 21:47:34 +00001661 (void) ResetMagickMemory(pixel,0,length*sizeof(*pixel));
cristy65b9f392011-02-22 14:22:54 +00001662 buffer=buffers;
cristybb503372010-05-27 20:51:26 +00001663 j=(ssize_t) image->columns+2;
1664 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001665 {
cristy4c08aed2011-07-01 19:47:50 +00001666 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001667 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001668
1669 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001670 if (p == (const Quantum *) NULL)
cristya63e4a92011-09-09 00:47:59 +00001671 {
1672 status=MagickFalse;
1673 continue;
1674 }
cristy3ed852e2009-09-05 21:47:34 +00001675 j++;
cristybb503372010-05-27 20:51:26 +00001676 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001677 {
cristya63e4a92011-09-09 00:47:59 +00001678 pixel[j++]=p[i];
cristyed231572011-07-14 02:18:59 +00001679 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001680 }
1681 j++;
1682 }
cristy3ed852e2009-09-05 21:47:34 +00001683 (void) ResetMagickMemory(buffer,0,length*sizeof(*buffer));
cristya58c3172011-02-19 19:23:11 +00001684 for (k=0; k < 4; k++)
cristy3ed852e2009-09-05 21:47:34 +00001685 {
cristya58c3172011-02-19 19:23:11 +00001686 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,1);
1687 Hull(-X[k],-Y[k],image->columns,image->rows,pixel,buffer,1);
1688 Hull(-X[k],-Y[k],image->columns,image->rows,pixel,buffer,-1);
1689 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,-1);
cristy3ed852e2009-09-05 21:47:34 +00001690 }
cristybb503372010-05-27 20:51:26 +00001691 j=(ssize_t) image->columns+2;
1692 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001693 {
1694 MagickBooleanType
1695 sync;
1696
cristy4c08aed2011-07-01 19:47:50 +00001697 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001698 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001699
1700 q=GetCacheViewAuthenticPixels(despeckle_view,0,y,despeckle_image->columns,
1701 1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001702 if (q == (Quantum *) NULL)
cristya63e4a92011-09-09 00:47:59 +00001703 {
1704 status=MagickFalse;
1705 continue;
1706 }
cristy3ed852e2009-09-05 21:47:34 +00001707 j++;
cristybb503372010-05-27 20:51:26 +00001708 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001709 {
cristya63e4a92011-09-09 00:47:59 +00001710 q[channel]=pixel[j++];
cristyed231572011-07-14 02:18:59 +00001711 q+=GetPixelChannels(despeckle_image);
cristy3ed852e2009-09-05 21:47:34 +00001712 }
1713 sync=SyncCacheViewAuthenticPixels(despeckle_view,exception);
1714 if (sync == MagickFalse)
cristya63e4a92011-09-09 00:47:59 +00001715 status=MagickFalse;
cristy3ed852e2009-09-05 21:47:34 +00001716 j++;
1717 }
1718 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1719 {
1720 MagickBooleanType
1721 proceed;
1722
cristya58c3172011-02-19 19:23:11 +00001723 proceed=SetImageProgress(image,DespeckleImageTag,(MagickOffsetType) i,
cristya63e4a92011-09-09 00:47:59 +00001724 GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00001725 if (proceed == MagickFalse)
1726 status=MagickFalse;
1727 }
1728 }
1729 despeckle_view=DestroyCacheView(despeckle_view);
1730 image_view=DestroyCacheView(image_view);
cristy65b9f392011-02-22 14:22:54 +00001731 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1732 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001733 despeckle_image->type=image->type;
1734 if (status == MagickFalse)
1735 despeckle_image=DestroyImage(despeckle_image);
1736 return(despeckle_image);
1737}
1738
1739/*
1740%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1741% %
1742% %
1743% %
1744% E d g e I m a g e %
1745% %
1746% %
1747% %
1748%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1749%
1750% EdgeImage() finds edges in an image. Radius defines the radius of the
1751% convolution filter. Use a radius of 0 and EdgeImage() selects a suitable
1752% radius for you.
1753%
1754% The format of the EdgeImage method is:
1755%
1756% Image *EdgeImage(const Image *image,const double radius,
cristy8ae632d2011-09-05 17:29:53 +00001757% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001758%
1759% A description of each parameter follows:
1760%
1761% o image: the image.
1762%
1763% o radius: the radius of the pixel neighborhood.
1764%
cristy8ae632d2011-09-05 17:29:53 +00001765% o sigma: the standard deviation of the Gaussian, in pixels.
1766%
cristy3ed852e2009-09-05 21:47:34 +00001767% o exception: return any errors or warnings in this structure.
1768%
1769*/
1770MagickExport Image *EdgeImage(const Image *image,const double radius,
cristy8ae632d2011-09-05 17:29:53 +00001771 const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001772{
1773 Image
1774 *edge_image;
1775
cristy41cbe682011-07-15 19:12:37 +00001776 KernelInfo
1777 *kernel_info;
cristy3ed852e2009-09-05 21:47:34 +00001778
cristybb503372010-05-27 20:51:26 +00001779 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001780 i;
1781
cristybb503372010-05-27 20:51:26 +00001782 size_t
cristy3ed852e2009-09-05 21:47:34 +00001783 width;
1784
cristy41cbe682011-07-15 19:12:37 +00001785 ssize_t
1786 j,
1787 u,
1788 v;
1789
cristy3ed852e2009-09-05 21:47:34 +00001790 assert(image != (const Image *) NULL);
1791 assert(image->signature == MagickSignature);
1792 if (image->debug != MagickFalse)
1793 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1794 assert(exception != (ExceptionInfo *) NULL);
1795 assert(exception->signature == MagickSignature);
cristy8ae632d2011-09-05 17:29:53 +00001796 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00001797 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00001798 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001799 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00001800 kernel_info->width=width;
1801 kernel_info->height=width;
cristy41cbe682011-07-15 19:12:37 +00001802 kernel_info->values=(double *) AcquireAlignedMemory(kernel_info->width,
1803 kernel_info->width*sizeof(*kernel_info->values));
1804 if (kernel_info->values == (double *) NULL)
1805 {
1806 kernel_info=DestroyKernelInfo(kernel_info);
1807 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1808 }
1809 j=(ssize_t) kernel_info->width/2;
1810 i=0;
1811 for (v=(-j); v <= j; v++)
1812 {
1813 for (u=(-j); u <= j; u++)
1814 {
1815 kernel_info->values[i]=(-1.0);
1816 i++;
1817 }
1818 }
1819 kernel_info->values[i/2]=(double) (width*width-1.0);
cristy0a922382011-07-16 15:30:34 +00001820 kernel_info->bias=image->bias;
cristy5e6be1e2011-07-16 01:23:39 +00001821 edge_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00001822 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00001823 return(edge_image);
1824}
1825
1826/*
1827%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1828% %
1829% %
1830% %
1831% E m b o s s I m a g e %
1832% %
1833% %
1834% %
1835%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1836%
1837% EmbossImage() returns a grayscale image with a three-dimensional effect.
1838% We convolve the image with a Gaussian operator of the given radius and
1839% standard deviation (sigma). For reasonable results, radius should be
1840% larger than sigma. Use a radius of 0 and Emboss() selects a suitable
1841% radius for you.
1842%
1843% The format of the EmbossImage method is:
1844%
1845% Image *EmbossImage(const Image *image,const double radius,
1846% const double sigma,ExceptionInfo *exception)
1847%
1848% A description of each parameter follows:
1849%
1850% o image: the image.
1851%
1852% o radius: the radius of the pixel neighborhood.
1853%
1854% o sigma: the standard deviation of the Gaussian, in pixels.
1855%
1856% o exception: return any errors or warnings in this structure.
1857%
1858*/
1859MagickExport Image *EmbossImage(const Image *image,const double radius,
1860 const double sigma,ExceptionInfo *exception)
1861{
cristy3ed852e2009-09-05 21:47:34 +00001862 Image
1863 *emboss_image;
1864
cristy41cbe682011-07-15 19:12:37 +00001865 KernelInfo
1866 *kernel_info;
1867
cristybb503372010-05-27 20:51:26 +00001868 register ssize_t
cristy47e00502009-12-17 19:19:57 +00001869 i;
1870
cristybb503372010-05-27 20:51:26 +00001871 size_t
cristy3ed852e2009-09-05 21:47:34 +00001872 width;
1873
cristy117ff172010-08-15 21:35:32 +00001874 ssize_t
1875 j,
1876 k,
1877 u,
1878 v;
1879
cristy41cbe682011-07-15 19:12:37 +00001880 assert(image != (const Image *) NULL);
cristy3ed852e2009-09-05 21:47:34 +00001881 assert(image->signature == MagickSignature);
1882 if (image->debug != MagickFalse)
1883 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1884 assert(exception != (ExceptionInfo *) NULL);
1885 assert(exception->signature == MagickSignature);
1886 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00001887 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00001888 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001889 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00001890 kernel_info->width=width;
1891 kernel_info->height=width;
cristy41cbe682011-07-15 19:12:37 +00001892 kernel_info->values=(double *) AcquireAlignedMemory(kernel_info->width,
1893 kernel_info->width*sizeof(*kernel_info->values));
1894 if (kernel_info->values == (double *) NULL)
1895 {
1896 kernel_info=DestroyKernelInfo(kernel_info);
1897 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1898 }
1899 j=(ssize_t) kernel_info->width/2;
cristy47e00502009-12-17 19:19:57 +00001900 k=j;
1901 i=0;
1902 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00001903 {
cristy47e00502009-12-17 19:19:57 +00001904 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00001905 {
cristy41cbe682011-07-15 19:12:37 +00001906 kernel_info->values[i]=(double) (((u < 0) || (v < 0) ? -8.0 : 8.0)*
cristy47e00502009-12-17 19:19:57 +00001907 exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
cristy4205a3c2010-09-12 20:19:59 +00001908 (2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +00001909 if (u != k)
cristy41cbe682011-07-15 19:12:37 +00001910 kernel_info->values[i]=0.0;
cristy3ed852e2009-09-05 21:47:34 +00001911 i++;
1912 }
cristy47e00502009-12-17 19:19:57 +00001913 k--;
cristy3ed852e2009-09-05 21:47:34 +00001914 }
cristy0a922382011-07-16 15:30:34 +00001915 kernel_info->bias=image->bias;
cristy5e6be1e2011-07-16 01:23:39 +00001916 emboss_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00001917 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00001918 if (emboss_image != (Image *) NULL)
cristy6d8c3d72011-08-22 01:20:01 +00001919 (void) EqualizeImage(emboss_image,exception);
cristy3ed852e2009-09-05 21:47:34 +00001920 return(emboss_image);
1921}
1922
1923/*
1924%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1925% %
1926% %
1927% %
1928% G a u s s i a n B l u r I m a g e %
1929% %
1930% %
1931% %
1932%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1933%
1934% GaussianBlurImage() blurs an image. We convolve the image with a
1935% Gaussian operator of the given radius and standard deviation (sigma).
1936% For reasonable results, the radius should be larger than sigma. Use a
1937% radius of 0 and GaussianBlurImage() selects a suitable radius for you
1938%
1939% The format of the GaussianBlurImage method is:
1940%
1941% Image *GaussianBlurImage(const Image *image,onst double radius,
cristy05c0c9a2011-09-05 23:16:13 +00001942% const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001943%
1944% A description of each parameter follows:
1945%
1946% o image: the image.
1947%
cristy3ed852e2009-09-05 21:47:34 +00001948% o radius: the radius of the Gaussian, in pixels, not counting the center
1949% pixel.
1950%
1951% o sigma: the standard deviation of the Gaussian, in pixels.
1952%
cristy05c0c9a2011-09-05 23:16:13 +00001953% o bias: the bias.
1954%
cristy3ed852e2009-09-05 21:47:34 +00001955% o exception: return any errors or warnings in this structure.
1956%
1957*/
cristy41cbe682011-07-15 19:12:37 +00001958MagickExport Image *GaussianBlurImage(const Image *image,const double radius,
cristy05c0c9a2011-09-05 23:16:13 +00001959 const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001960{
cristy3ed852e2009-09-05 21:47:34 +00001961 Image
1962 *blur_image;
1963
cristy41cbe682011-07-15 19:12:37 +00001964 KernelInfo
1965 *kernel_info;
1966
cristybb503372010-05-27 20:51:26 +00001967 register ssize_t
cristy47e00502009-12-17 19:19:57 +00001968 i;
1969
cristybb503372010-05-27 20:51:26 +00001970 size_t
cristy3ed852e2009-09-05 21:47:34 +00001971 width;
1972
cristy117ff172010-08-15 21:35:32 +00001973 ssize_t
1974 j,
1975 u,
1976 v;
1977
cristy3ed852e2009-09-05 21:47:34 +00001978 assert(image != (const Image *) NULL);
1979 assert(image->signature == MagickSignature);
1980 if (image->debug != MagickFalse)
1981 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1982 assert(exception != (ExceptionInfo *) NULL);
1983 assert(exception->signature == MagickSignature);
1984 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00001985 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00001986 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001987 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00001988 (void) ResetMagickMemory(kernel_info,0,sizeof(*kernel_info));
1989 kernel_info->width=width;
1990 kernel_info->height=width;
cristy05c0c9a2011-09-05 23:16:13 +00001991 kernel_info->bias=bias;
cristy41cbe682011-07-15 19:12:37 +00001992 kernel_info->signature=MagickSignature;
1993 kernel_info->values=(double *) AcquireAlignedMemory(kernel_info->width,
1994 kernel_info->width*sizeof(*kernel_info->values));
1995 if (kernel_info->values == (double *) NULL)
1996 {
1997 kernel_info=DestroyKernelInfo(kernel_info);
1998 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1999 }
2000 j=(ssize_t) kernel_info->width/2;
cristy3ed852e2009-09-05 21:47:34 +00002001 i=0;
cristy47e00502009-12-17 19:19:57 +00002002 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002003 {
cristy47e00502009-12-17 19:19:57 +00002004 for (u=(-j); u <= j; u++)
cristy41cbe682011-07-15 19:12:37 +00002005 {
2006 kernel_info->values[i]=(double) (exp(-((double) u*u+v*v)/(2.0*
2007 MagickSigma*MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
2008 i++;
2009 }
cristy3ed852e2009-09-05 21:47:34 +00002010 }
cristy5e6be1e2011-07-16 01:23:39 +00002011 blur_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00002012 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00002013 return(blur_image);
2014}
2015
2016/*
2017%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2018% %
2019% %
2020% %
cristy3ed852e2009-09-05 21:47:34 +00002021% M o t i o n B l u r I m a g e %
2022% %
2023% %
2024% %
2025%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2026%
2027% MotionBlurImage() simulates motion blur. We convolve the image with a
2028% Gaussian operator of the given radius and standard deviation (sigma).
2029% For reasonable results, radius should be larger than sigma. Use a
2030% radius of 0 and MotionBlurImage() selects a suitable radius for you.
2031% Angle gives the angle of the blurring motion.
2032%
2033% Andrew Protano contributed this effect.
2034%
2035% The format of the MotionBlurImage method is:
2036%
2037% Image *MotionBlurImage(const Image *image,const double radius,
cristyf7ef0252011-09-09 14:50:06 +00002038% const double sigma,const double angle,const double bias,
2039% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002040%
2041% A description of each parameter follows:
2042%
2043% o image: the image.
2044%
cristy3ed852e2009-09-05 21:47:34 +00002045% o radius: the radius of the Gaussian, in pixels, not counting
2046% the center pixel.
2047%
2048% o sigma: the standard deviation of the Gaussian, in pixels.
2049%
cristycee97112010-05-28 00:44:52 +00002050% o angle: Apply the effect along this angle.
cristy3ed852e2009-09-05 21:47:34 +00002051%
cristyf7ef0252011-09-09 14:50:06 +00002052% o bias: the bias.
2053%
cristy3ed852e2009-09-05 21:47:34 +00002054% o exception: return any errors or warnings in this structure.
2055%
2056*/
2057
cristybb503372010-05-27 20:51:26 +00002058static double *GetMotionBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +00002059{
cristy3ed852e2009-09-05 21:47:34 +00002060 double
cristy47e00502009-12-17 19:19:57 +00002061 *kernel,
cristy3ed852e2009-09-05 21:47:34 +00002062 normalize;
2063
cristybb503372010-05-27 20:51:26 +00002064 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002065 i;
2066
2067 /*
cristy47e00502009-12-17 19:19:57 +00002068 Generate a 1-D convolution kernel.
cristy3ed852e2009-09-05 21:47:34 +00002069 */
2070 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
2071 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
2072 if (kernel == (double *) NULL)
2073 return(kernel);
cristy3ed852e2009-09-05 21:47:34 +00002074 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00002075 for (i=0; i < (ssize_t) width; i++)
cristy47e00502009-12-17 19:19:57 +00002076 {
cristy4205a3c2010-09-12 20:19:59 +00002077 kernel[i]=(double) (exp((-((double) i*i)/(double) (2.0*MagickSigma*
2078 MagickSigma)))/(MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00002079 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +00002080 }
cristybb503372010-05-27 20:51:26 +00002081 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002082 kernel[i]/=normalize;
2083 return(kernel);
2084}
2085
cristya63e4a92011-09-09 00:47:59 +00002086MagickExport Image *MotionBlurImage(const Image *image,const double radius,
cristyf7ef0252011-09-09 14:50:06 +00002087 const double sigma,const double angle,const double bias,
2088 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002089{
cristyc4c8d132010-01-07 01:58:38 +00002090 CacheView
2091 *blur_view,
2092 *image_view;
2093
cristy3ed852e2009-09-05 21:47:34 +00002094 double
2095 *kernel;
2096
2097 Image
2098 *blur_image;
2099
cristy3ed852e2009-09-05 21:47:34 +00002100 MagickBooleanType
2101 status;
2102
cristybb503372010-05-27 20:51:26 +00002103 MagickOffsetType
2104 progress;
2105
cristy3ed852e2009-09-05 21:47:34 +00002106 OffsetInfo
2107 *offset;
2108
2109 PointInfo
2110 point;
2111
cristybb503372010-05-27 20:51:26 +00002112 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002113 i;
2114
cristybb503372010-05-27 20:51:26 +00002115 size_t
cristy3ed852e2009-09-05 21:47:34 +00002116 width;
2117
cristybb503372010-05-27 20:51:26 +00002118 ssize_t
2119 y;
2120
cristy3ed852e2009-09-05 21:47:34 +00002121 assert(image != (Image *) NULL);
2122 assert(image->signature == MagickSignature);
2123 if (image->debug != MagickFalse)
2124 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2125 assert(exception != (ExceptionInfo *) NULL);
2126 width=GetOptimalKernelWidth1D(radius,sigma);
2127 kernel=GetMotionBlurKernel(width,sigma);
2128 if (kernel == (double *) NULL)
2129 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2130 offset=(OffsetInfo *) AcquireQuantumMemory(width,sizeof(*offset));
2131 if (offset == (OffsetInfo *) NULL)
2132 {
2133 kernel=(double *) RelinquishMagickMemory(kernel);
2134 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2135 }
cristy1e7aa312011-09-10 20:01:36 +00002136 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00002137 if (blur_image == (Image *) NULL)
2138 {
2139 kernel=(double *) RelinquishMagickMemory(kernel);
2140 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2141 return((Image *) NULL);
2142 }
cristy574cc262011-08-05 01:23:58 +00002143 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00002144 {
2145 kernel=(double *) RelinquishMagickMemory(kernel);
2146 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
cristy3ed852e2009-09-05 21:47:34 +00002147 blur_image=DestroyImage(blur_image);
2148 return((Image *) NULL);
2149 }
2150 point.x=(double) width*sin(DegreesToRadians(angle));
2151 point.y=(double) width*cos(DegreesToRadians(angle));
cristybb503372010-05-27 20:51:26 +00002152 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002153 {
cristybb503372010-05-27 20:51:26 +00002154 offset[i].x=(ssize_t) ceil((double) (i*point.y)/hypot(point.x,point.y)-0.5);
2155 offset[i].y=(ssize_t) ceil((double) (i*point.x)/hypot(point.x,point.y)-0.5);
cristy3ed852e2009-09-05 21:47:34 +00002156 }
2157 /*
2158 Motion blur image.
2159 */
2160 status=MagickTrue;
2161 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00002162 image_view=AcquireCacheView(image);
2163 blur_view=AcquireCacheView(blur_image);
cristyb557a152011-02-22 12:14:30 +00002164#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00002165 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00002166#endif
cristybb503372010-05-27 20:51:26 +00002167 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002168 {
cristyf7ef0252011-09-09 14:50:06 +00002169 register const Quantum
2170 *restrict p;
2171
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;
cristyf7ef0252011-09-09 14:50:06 +00002180 p=GetCacheViewVirtualPixels(blur_view,0,y,image->columns,1,exception);
cristy3ed852e2009-09-05 21:47:34 +00002181 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
2182 exception);
cristyf7ef0252011-09-09 14:50:06 +00002183 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00002184 {
2185 status=MagickFalse;
2186 continue;
2187 }
cristybb503372010-05-27 20:51:26 +00002188 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002189 {
cristybb503372010-05-27 20:51:26 +00002190 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002191 i;
2192
cristyf7ef0252011-09-09 14:50:06 +00002193 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2194 {
2195 MagickRealType
2196 alpha,
2197 gamma,
2198 pixel;
cristy3ed852e2009-09-05 21:47:34 +00002199
cristyf7ef0252011-09-09 14:50:06 +00002200 PixelChannel
2201 channel;
2202
2203 PixelTrait
2204 blur_traits,
2205 traits;
2206
2207 register const Quantum
2208 *restrict r;
2209
2210 register double
2211 *restrict k;
2212
2213 register ssize_t
2214 j;
2215
2216 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
2217 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
2218 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
2219 if ((traits == UndefinedPixelTrait) ||
2220 (blur_traits == UndefinedPixelTrait))
2221 continue;
2222 if ((blur_traits & CopyPixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002223 {
cristyf7ef0252011-09-09 14:50:06 +00002224 q[channel]=p[i];
2225 continue;
cristy3ed852e2009-09-05 21:47:34 +00002226 }
cristyf7ef0252011-09-09 14:50:06 +00002227 k=kernel;
2228 pixel=bias;
2229 if ((blur_traits & BlendPixelTrait) == 0)
2230 {
2231 for (j=0; j < (ssize_t) width; j++)
2232 {
2233 r=GetCacheViewVirtualPixels(image_view,x+offset[j].x,y+
2234 offset[j].y,1,1,exception);
2235 if (r == (const Quantum *) NULL)
2236 {
2237 status=MagickFalse;
2238 continue;
2239 }
2240 pixel+=(*k)*r[i];
2241 k++;
2242 }
2243 q[channel]=ClampToQuantum(pixel);
2244 continue;
2245 }
2246 alpha=0.0;
2247 gamma=0.0;
2248 for (j=0; j < (ssize_t) width; j++)
2249 {
2250 r=GetCacheViewVirtualPixels(image_view,x+offset[j].x,y+offset[j].y,1,
2251 1,exception);
2252 if (r == (const Quantum *) NULL)
2253 {
2254 status=MagickFalse;
2255 continue;
2256 }
2257 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,r));
2258 pixel+=(*k)*alpha*r[i];
2259 gamma+=(*k)*alpha;
2260 k++;
cristy3ed852e2009-09-05 21:47:34 +00002261 }
cristyf7ef0252011-09-09 14:50:06 +00002262 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
2263 q[channel]=ClampToQuantum(gamma*pixel);
2264 }
2265 p+=GetPixelChannels(image);
cristyed231572011-07-14 02:18:59 +00002266 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00002267 }
2268 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
2269 status=MagickFalse;
2270 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2271 {
2272 MagickBooleanType
2273 proceed;
2274
cristyb557a152011-02-22 12:14:30 +00002275#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00002276 #pragma omp critical (MagickCore_MotionBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00002277#endif
2278 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
2279 if (proceed == MagickFalse)
2280 status=MagickFalse;
2281 }
2282 }
2283 blur_view=DestroyCacheView(blur_view);
2284 image_view=DestroyCacheView(image_view);
2285 kernel=(double *) RelinquishMagickMemory(kernel);
2286 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2287 if (status == MagickFalse)
2288 blur_image=DestroyImage(blur_image);
2289 return(blur_image);
2290}
2291
2292/*
2293%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2294% %
2295% %
2296% %
2297% P r e v i e w I m a g e %
2298% %
2299% %
2300% %
2301%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2302%
2303% PreviewImage() tiles 9 thumbnails of the specified image with an image
2304% processing operation applied with varying parameters. This may be helpful
2305% pin-pointing an appropriate parameter for a particular image processing
2306% operation.
2307%
2308% The format of the PreviewImages method is:
2309%
2310% Image *PreviewImages(const Image *image,const PreviewType preview,
2311% ExceptionInfo *exception)
2312%
2313% A description of each parameter follows:
2314%
2315% o image: the image.
2316%
2317% o preview: the image processing operation.
2318%
2319% o exception: return any errors or warnings in this structure.
2320%
2321*/
2322MagickExport Image *PreviewImage(const Image *image,const PreviewType preview,
2323 ExceptionInfo *exception)
2324{
2325#define NumberTiles 9
2326#define PreviewImageTag "Preview/Image"
2327#define DefaultPreviewGeometry "204x204+10+10"
2328
2329 char
2330 factor[MaxTextExtent],
2331 label[MaxTextExtent];
2332
2333 double
2334 degrees,
2335 gamma,
2336 percentage,
2337 radius,
2338 sigma,
2339 threshold;
2340
2341 Image
2342 *images,
2343 *montage_image,
2344 *preview_image,
2345 *thumbnail;
2346
2347 ImageInfo
2348 *preview_info;
2349
cristy3ed852e2009-09-05 21:47:34 +00002350 MagickBooleanType
2351 proceed;
2352
2353 MontageInfo
2354 *montage_info;
2355
2356 QuantizeInfo
2357 quantize_info;
2358
2359 RectangleInfo
2360 geometry;
2361
cristybb503372010-05-27 20:51:26 +00002362 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002363 i,
2364 x;
2365
cristybb503372010-05-27 20:51:26 +00002366 size_t
cristy3ed852e2009-09-05 21:47:34 +00002367 colors;
2368
cristy117ff172010-08-15 21:35:32 +00002369 ssize_t
2370 y;
2371
cristy3ed852e2009-09-05 21:47:34 +00002372 /*
2373 Open output image file.
2374 */
2375 assert(image != (Image *) NULL);
2376 assert(image->signature == MagickSignature);
2377 if (image->debug != MagickFalse)
2378 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2379 colors=2;
2380 degrees=0.0;
2381 gamma=(-0.2f);
2382 preview_info=AcquireImageInfo();
2383 SetGeometry(image,&geometry);
2384 (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y,
2385 &geometry.width,&geometry.height);
2386 images=NewImageList();
2387 percentage=12.5;
2388 GetQuantizeInfo(&quantize_info);
2389 radius=0.0;
2390 sigma=1.0;
2391 threshold=0.0;
2392 x=0;
2393 y=0;
2394 for (i=0; i < NumberTiles; i++)
2395 {
2396 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
2397 if (thumbnail == (Image *) NULL)
2398 break;
2399 (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL,
2400 (void *) NULL);
2401 (void) SetImageProperty(thumbnail,"label",DefaultTileLabel);
2402 if (i == (NumberTiles/2))
2403 {
2404 (void) QueryColorDatabase("#dfdfdf",&thumbnail->matte_color,exception);
2405 AppendImageToList(&images,thumbnail);
2406 continue;
2407 }
2408 switch (preview)
2409 {
2410 case RotatePreview:
2411 {
2412 degrees+=45.0;
2413 preview_image=RotateImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002414 (void) FormatLocaleString(label,MaxTextExtent,"rotate %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002415 break;
2416 }
2417 case ShearPreview:
2418 {
2419 degrees+=5.0;
2420 preview_image=ShearImage(thumbnail,degrees,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002421 (void) FormatLocaleString(label,MaxTextExtent,"shear %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002422 degrees,2.0*degrees);
2423 break;
2424 }
2425 case RollPreview:
2426 {
cristybb503372010-05-27 20:51:26 +00002427 x=(ssize_t) ((i+1)*thumbnail->columns)/NumberTiles;
2428 y=(ssize_t) ((i+1)*thumbnail->rows)/NumberTiles;
cristy3ed852e2009-09-05 21:47:34 +00002429 preview_image=RollImage(thumbnail,x,y,exception);
cristyb51dff52011-05-19 16:55:47 +00002430 (void) FormatLocaleString(label,MaxTextExtent,"roll %+.20gx%+.20g",
cristye8c25f92010-06-03 00:53:06 +00002431 (double) x,(double) y);
cristy3ed852e2009-09-05 21:47:34 +00002432 break;
2433 }
2434 case HuePreview:
2435 {
2436 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2437 if (preview_image == (Image *) NULL)
2438 break;
cristyb51dff52011-05-19 16:55:47 +00002439 (void) FormatLocaleString(factor,MaxTextExtent,"100,100,%g",
cristy3ed852e2009-09-05 21:47:34 +00002440 2.0*percentage);
cristy33bd5152011-08-24 01:42:24 +00002441 (void) ModulateImage(preview_image,factor,exception);
cristyb51dff52011-05-19 16:55:47 +00002442 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002443 break;
2444 }
2445 case SaturationPreview:
2446 {
2447 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2448 if (preview_image == (Image *) NULL)
2449 break;
cristyb51dff52011-05-19 16:55:47 +00002450 (void) FormatLocaleString(factor,MaxTextExtent,"100,%g",
cristy8cd5b312010-01-07 01:10:24 +00002451 2.0*percentage);
cristy33bd5152011-08-24 01:42:24 +00002452 (void) ModulateImage(preview_image,factor,exception);
cristyb51dff52011-05-19 16:55:47 +00002453 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002454 break;
2455 }
2456 case BrightnessPreview:
2457 {
2458 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2459 if (preview_image == (Image *) NULL)
2460 break;
cristyb51dff52011-05-19 16:55:47 +00002461 (void) FormatLocaleString(factor,MaxTextExtent,"%g",2.0*percentage);
cristy33bd5152011-08-24 01:42:24 +00002462 (void) ModulateImage(preview_image,factor,exception);
cristyb51dff52011-05-19 16:55:47 +00002463 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002464 break;
2465 }
2466 case GammaPreview:
2467 default:
2468 {
2469 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2470 if (preview_image == (Image *) NULL)
2471 break;
2472 gamma+=0.4f;
cristyb3e7c6c2011-07-24 01:43:55 +00002473 (void) GammaImage(preview_image,gamma,exception);
cristyb51dff52011-05-19 16:55:47 +00002474 (void) FormatLocaleString(label,MaxTextExtent,"gamma %g",gamma);
cristy3ed852e2009-09-05 21:47:34 +00002475 break;
2476 }
2477 case SpiffPreview:
2478 {
2479 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2480 if (preview_image != (Image *) NULL)
2481 for (x=0; x < i; x++)
cristye23ec9d2011-08-16 18:15:40 +00002482 (void) ContrastImage(preview_image,MagickTrue,exception);
cristyb51dff52011-05-19 16:55:47 +00002483 (void) FormatLocaleString(label,MaxTextExtent,"contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002484 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002485 break;
2486 }
2487 case DullPreview:
2488 {
2489 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2490 if (preview_image == (Image *) NULL)
2491 break;
2492 for (x=0; x < i; x++)
cristye23ec9d2011-08-16 18:15:40 +00002493 (void) ContrastImage(preview_image,MagickFalse,exception);
cristyb51dff52011-05-19 16:55:47 +00002494 (void) FormatLocaleString(label,MaxTextExtent,"+contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002495 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002496 break;
2497 }
2498 case GrayscalePreview:
2499 {
2500 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2501 if (preview_image == (Image *) NULL)
2502 break;
2503 colors<<=1;
2504 quantize_info.number_colors=colors;
2505 quantize_info.colorspace=GRAYColorspace;
cristy018f07f2011-09-04 21:15:19 +00002506 (void) QuantizeImage(&quantize_info,preview_image,exception);
cristyb51dff52011-05-19 16:55:47 +00002507 (void) FormatLocaleString(label,MaxTextExtent,
cristye8c25f92010-06-03 00:53:06 +00002508 "-colorspace gray -colors %.20g",(double) colors);
cristy3ed852e2009-09-05 21:47:34 +00002509 break;
2510 }
2511 case QuantizePreview:
2512 {
2513 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2514 if (preview_image == (Image *) NULL)
2515 break;
2516 colors<<=1;
2517 quantize_info.number_colors=colors;
cristy018f07f2011-09-04 21:15:19 +00002518 (void) QuantizeImage(&quantize_info,preview_image,exception);
cristyb51dff52011-05-19 16:55:47 +00002519 (void) FormatLocaleString(label,MaxTextExtent,"colors %.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00002520 colors);
cristy3ed852e2009-09-05 21:47:34 +00002521 break;
2522 }
2523 case DespecklePreview:
2524 {
2525 for (x=0; x < (i-1); x++)
2526 {
2527 preview_image=DespeckleImage(thumbnail,exception);
2528 if (preview_image == (Image *) NULL)
2529 break;
2530 thumbnail=DestroyImage(thumbnail);
2531 thumbnail=preview_image;
2532 }
2533 preview_image=DespeckleImage(thumbnail,exception);
2534 if (preview_image == (Image *) NULL)
2535 break;
cristyb51dff52011-05-19 16:55:47 +00002536 (void) FormatLocaleString(label,MaxTextExtent,"despeckle (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002537 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002538 break;
2539 }
2540 case ReduceNoisePreview:
2541 {
cristy95c38342011-03-18 22:39:51 +00002542 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) radius,
2543 (size_t) radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002544 (void) FormatLocaleString(label,MaxTextExtent,"noise %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002545 break;
2546 }
2547 case AddNoisePreview:
2548 {
2549 switch ((int) i)
2550 {
2551 case 0:
2552 {
2553 (void) CopyMagickString(factor,"uniform",MaxTextExtent);
2554 break;
2555 }
2556 case 1:
2557 {
2558 (void) CopyMagickString(factor,"gaussian",MaxTextExtent);
2559 break;
2560 }
2561 case 2:
2562 {
2563 (void) CopyMagickString(factor,"multiplicative",MaxTextExtent);
2564 break;
2565 }
2566 case 3:
2567 {
2568 (void) CopyMagickString(factor,"impulse",MaxTextExtent);
2569 break;
2570 }
2571 case 4:
2572 {
2573 (void) CopyMagickString(factor,"laplacian",MaxTextExtent);
2574 break;
2575 }
2576 case 5:
2577 {
2578 (void) CopyMagickString(factor,"Poisson",MaxTextExtent);
2579 break;
2580 }
2581 default:
2582 {
2583 (void) CopyMagickString(thumbnail->magick,"NULL",MaxTextExtent);
2584 break;
2585 }
2586 }
cristyd76c51e2011-03-26 00:21:26 +00002587 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) i,
2588 (size_t) i,exception);
cristyb51dff52011-05-19 16:55:47 +00002589 (void) FormatLocaleString(label,MaxTextExtent,"+noise %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002590 break;
2591 }
2592 case SharpenPreview:
2593 {
cristy05c0c9a2011-09-05 23:16:13 +00002594 preview_image=SharpenImage(thumbnail,radius,sigma,image->bias,
2595 exception);
cristyb51dff52011-05-19 16:55:47 +00002596 (void) FormatLocaleString(label,MaxTextExtent,"sharpen %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002597 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002598 break;
2599 }
2600 case BlurPreview:
2601 {
cristy05c0c9a2011-09-05 23:16:13 +00002602 preview_image=BlurImage(thumbnail,radius,sigma,image->bias,exception);
cristyb51dff52011-05-19 16:55:47 +00002603 (void) FormatLocaleString(label,MaxTextExtent,"blur %gx%g",radius,
cristy3ed852e2009-09-05 21:47:34 +00002604 sigma);
2605 break;
2606 }
2607 case ThresholdPreview:
2608 {
2609 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2610 if (preview_image == (Image *) NULL)
2611 break;
2612 (void) BilevelImage(thumbnail,
2613 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
cristyb51dff52011-05-19 16:55:47 +00002614 (void) FormatLocaleString(label,MaxTextExtent,"threshold %g",
cristy3ed852e2009-09-05 21:47:34 +00002615 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
2616 break;
2617 }
2618 case EdgeDetectPreview:
2619 {
cristy8ae632d2011-09-05 17:29:53 +00002620 preview_image=EdgeImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00002621 (void) FormatLocaleString(label,MaxTextExtent,"edge %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002622 break;
2623 }
2624 case SpreadPreview:
2625 {
cristy5c4e2582011-09-11 19:21:03 +00002626 preview_image=SpreadImage(thumbnail,radius,thumbnail->interpolate,
2627 exception);
cristyb51dff52011-05-19 16:55:47 +00002628 (void) FormatLocaleString(label,MaxTextExtent,"spread %g",
cristy8cd5b312010-01-07 01:10:24 +00002629 radius+0.5);
cristy3ed852e2009-09-05 21:47:34 +00002630 break;
2631 }
2632 case SolarizePreview:
2633 {
2634 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2635 if (preview_image == (Image *) NULL)
2636 break;
2637 (void) SolarizeImage(preview_image,(double) QuantumRange*
cristy5cbc0162011-08-29 00:36:28 +00002638 percentage/100.0,exception);
cristyb51dff52011-05-19 16:55:47 +00002639 (void) FormatLocaleString(label,MaxTextExtent,"solarize %g",
cristy3ed852e2009-09-05 21:47:34 +00002640 (QuantumRange*percentage)/100.0);
2641 break;
2642 }
2643 case ShadePreview:
2644 {
2645 degrees+=10.0;
2646 preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees,
2647 exception);
cristyb51dff52011-05-19 16:55:47 +00002648 (void) FormatLocaleString(label,MaxTextExtent,"shade %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002649 degrees,degrees);
cristy3ed852e2009-09-05 21:47:34 +00002650 break;
2651 }
2652 case RaisePreview:
2653 {
2654 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2655 if (preview_image == (Image *) NULL)
2656 break;
cristybb503372010-05-27 20:51:26 +00002657 geometry.width=(size_t) (2*i+2);
2658 geometry.height=(size_t) (2*i+2);
cristy3ed852e2009-09-05 21:47:34 +00002659 geometry.x=i/2;
2660 geometry.y=i/2;
cristy6170ac32011-08-28 14:15:37 +00002661 (void) RaiseImage(preview_image,&geometry,MagickTrue,exception);
cristyb51dff52011-05-19 16:55:47 +00002662 (void) FormatLocaleString(label,MaxTextExtent,
cristy6d8abba2010-06-03 01:10:47 +00002663 "raise %.20gx%.20g%+.20g%+.20g",(double) geometry.width,(double)
cristye8c25f92010-06-03 00:53:06 +00002664 geometry.height,(double) geometry.x,(double) geometry.y);
cristy3ed852e2009-09-05 21:47:34 +00002665 break;
2666 }
2667 case SegmentPreview:
2668 {
2669 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2670 if (preview_image == (Image *) NULL)
2671 break;
2672 threshold+=0.4f;
2673 (void) SegmentImage(preview_image,RGBColorspace,MagickFalse,threshold,
cristy018f07f2011-09-04 21:15:19 +00002674 threshold,exception);
cristyb51dff52011-05-19 16:55:47 +00002675 (void) FormatLocaleString(label,MaxTextExtent,"segment %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002676 threshold,threshold);
2677 break;
2678 }
2679 case SwirlPreview:
2680 {
cristy76f512e2011-09-12 01:26:56 +00002681 preview_image=SwirlImage(thumbnail,degrees,image->interpolate,
2682 exception);
cristyb51dff52011-05-19 16:55:47 +00002683 (void) FormatLocaleString(label,MaxTextExtent,"swirl %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002684 degrees+=45.0;
2685 break;
2686 }
2687 case ImplodePreview:
2688 {
2689 degrees+=0.1f;
cristy76f512e2011-09-12 01:26:56 +00002690 preview_image=ImplodeImage(thumbnail,degrees,image->interpolate,
2691 exception);
cristyb51dff52011-05-19 16:55:47 +00002692 (void) FormatLocaleString(label,MaxTextExtent,"implode %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002693 break;
2694 }
2695 case WavePreview:
2696 {
2697 degrees+=5.0f;
cristy5c4e2582011-09-11 19:21:03 +00002698 preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,
2699 image->interpolate,exception);
cristyb51dff52011-05-19 16:55:47 +00002700 (void) FormatLocaleString(label,MaxTextExtent,"wave %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002701 0.5*degrees,2.0*degrees);
cristy3ed852e2009-09-05 21:47:34 +00002702 break;
2703 }
2704 case OilPaintPreview:
2705 {
cristy14973ba2011-08-27 23:48:07 +00002706 preview_image=OilPaintImage(thumbnail,(double) radius,(double) sigma,
2707 exception);
2708 (void) FormatLocaleString(label,MaxTextExtent,"charcoal %gx%g",
2709 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002710 break;
2711 }
2712 case CharcoalDrawingPreview:
2713 {
2714 preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma,
cristy05c0c9a2011-09-05 23:16:13 +00002715 image->bias,exception);
cristyb51dff52011-05-19 16:55:47 +00002716 (void) FormatLocaleString(label,MaxTextExtent,"charcoal %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002717 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002718 break;
2719 }
2720 case JPEGPreview:
2721 {
2722 char
2723 filename[MaxTextExtent];
2724
2725 int
2726 file;
2727
2728 MagickBooleanType
2729 status;
2730
2731 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2732 if (preview_image == (Image *) NULL)
2733 break;
cristybb503372010-05-27 20:51:26 +00002734 preview_info->quality=(size_t) percentage;
cristyb51dff52011-05-19 16:55:47 +00002735 (void) FormatLocaleString(factor,MaxTextExtent,"%.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00002736 preview_info->quality);
cristy3ed852e2009-09-05 21:47:34 +00002737 file=AcquireUniqueFileResource(filename);
2738 if (file != -1)
2739 file=close(file)-1;
cristyb51dff52011-05-19 16:55:47 +00002740 (void) FormatLocaleString(preview_image->filename,MaxTextExtent,
cristy3ed852e2009-09-05 21:47:34 +00002741 "jpeg:%s",filename);
cristy6f9e0d32011-08-28 16:32:09 +00002742 status=WriteImage(preview_info,preview_image,exception);
cristy3ed852e2009-09-05 21:47:34 +00002743 if (status != MagickFalse)
2744 {
2745 Image
2746 *quality_image;
2747
2748 (void) CopyMagickString(preview_info->filename,
2749 preview_image->filename,MaxTextExtent);
2750 quality_image=ReadImage(preview_info,exception);
2751 if (quality_image != (Image *) NULL)
2752 {
2753 preview_image=DestroyImage(preview_image);
2754 preview_image=quality_image;
2755 }
2756 }
2757 (void) RelinquishUniqueFileResource(preview_image->filename);
2758 if ((GetBlobSize(preview_image)/1024) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00002759 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%gmb ",
cristy3ed852e2009-09-05 21:47:34 +00002760 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
2761 1024.0/1024.0);
2762 else
2763 if (GetBlobSize(preview_image) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00002764 (void) FormatLocaleString(label,MaxTextExtent,
cristye7f51092010-01-17 00:39:37 +00002765 "quality %s\n%gkb ",factor,(double) ((MagickOffsetType)
cristy8cd5b312010-01-07 01:10:24 +00002766 GetBlobSize(preview_image))/1024.0);
cristy3ed852e2009-09-05 21:47:34 +00002767 else
cristyb51dff52011-05-19 16:55:47 +00002768 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%.20gb ",
cristy54ea5732011-06-10 12:39:53 +00002769 factor,(double) ((MagickOffsetType) GetBlobSize(thumbnail)));
cristy3ed852e2009-09-05 21:47:34 +00002770 break;
2771 }
2772 }
2773 thumbnail=DestroyImage(thumbnail);
2774 percentage+=12.5;
2775 radius+=0.5;
2776 sigma+=0.25;
2777 if (preview_image == (Image *) NULL)
2778 break;
2779 (void) DeleteImageProperty(preview_image,"label");
2780 (void) SetImageProperty(preview_image,"label",label);
2781 AppendImageToList(&images,preview_image);
cristybb503372010-05-27 20:51:26 +00002782 proceed=SetImageProgress(image,PreviewImageTag,(MagickOffsetType) i,
2783 NumberTiles);
cristy3ed852e2009-09-05 21:47:34 +00002784 if (proceed == MagickFalse)
2785 break;
2786 }
2787 if (images == (Image *) NULL)
2788 {
2789 preview_info=DestroyImageInfo(preview_info);
2790 return((Image *) NULL);
2791 }
2792 /*
2793 Create the montage.
2794 */
2795 montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL);
2796 (void) CopyMagickString(montage_info->filename,image->filename,MaxTextExtent);
2797 montage_info->shadow=MagickTrue;
2798 (void) CloneString(&montage_info->tile,"3x3");
2799 (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry);
2800 (void) CloneString(&montage_info->frame,DefaultTileFrame);
2801 montage_image=MontageImages(images,montage_info,exception);
2802 montage_info=DestroyMontageInfo(montage_info);
2803 images=DestroyImageList(images);
2804 if (montage_image == (Image *) NULL)
2805 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2806 if (montage_image->montage != (char *) NULL)
2807 {
2808 /*
2809 Free image directory.
2810 */
2811 montage_image->montage=(char *) RelinquishMagickMemory(
2812 montage_image->montage);
2813 if (image->directory != (char *) NULL)
2814 montage_image->directory=(char *) RelinquishMagickMemory(
2815 montage_image->directory);
2816 }
2817 preview_info=DestroyImageInfo(preview_info);
2818 return(montage_image);
2819}
2820
2821/*
2822%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2823% %
2824% %
2825% %
2826% R a d i a l B l u r I m a g e %
2827% %
2828% %
2829% %
2830%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2831%
2832% RadialBlurImage() applies a radial blur to the image.
2833%
2834% Andrew Protano contributed this effect.
2835%
2836% The format of the RadialBlurImage method is:
2837%
2838% Image *RadialBlurImage(const Image *image,const double angle,
cristy6435bd92011-09-10 02:10:07 +00002839% const double blur,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002840%
2841% A description of each parameter follows:
2842%
2843% o image: the image.
2844%
cristy3ed852e2009-09-05 21:47:34 +00002845% o angle: the angle of the radial blur.
2846%
cristy6435bd92011-09-10 02:10:07 +00002847% o blur: the blur.
2848%
cristy3ed852e2009-09-05 21:47:34 +00002849% o exception: return any errors or warnings in this structure.
2850%
2851*/
cristyf4ad9df2011-07-08 16:49:03 +00002852MagickExport Image *RadialBlurImage(const Image *image,
cristy6435bd92011-09-10 02:10:07 +00002853 const double angle,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002854{
cristyc4c8d132010-01-07 01:58:38 +00002855 CacheView
2856 *blur_view,
2857 *image_view;
2858
cristy3ed852e2009-09-05 21:47:34 +00002859 Image
2860 *blur_image;
2861
cristy3ed852e2009-09-05 21:47:34 +00002862 MagickBooleanType
2863 status;
2864
cristybb503372010-05-27 20:51:26 +00002865 MagickOffsetType
2866 progress;
2867
cristy3ed852e2009-09-05 21:47:34 +00002868 MagickRealType
2869 blur_radius,
2870 *cos_theta,
2871 offset,
2872 *sin_theta,
2873 theta;
2874
2875 PointInfo
2876 blur_center;
2877
cristybb503372010-05-27 20:51:26 +00002878 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002879 i;
2880
cristybb503372010-05-27 20:51:26 +00002881 size_t
cristy3ed852e2009-09-05 21:47:34 +00002882 n;
2883
cristybb503372010-05-27 20:51:26 +00002884 ssize_t
2885 y;
2886
cristy3ed852e2009-09-05 21:47:34 +00002887 /*
2888 Allocate blur image.
2889 */
2890 assert(image != (Image *) NULL);
2891 assert(image->signature == MagickSignature);
2892 if (image->debug != MagickFalse)
2893 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2894 assert(exception != (ExceptionInfo *) NULL);
2895 assert(exception->signature == MagickSignature);
cristy1e7aa312011-09-10 20:01:36 +00002896 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00002897 if (blur_image == (Image *) NULL)
2898 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00002899 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00002900 {
cristy3ed852e2009-09-05 21:47:34 +00002901 blur_image=DestroyImage(blur_image);
2902 return((Image *) NULL);
2903 }
2904 blur_center.x=(double) image->columns/2.0;
2905 blur_center.y=(double) image->rows/2.0;
2906 blur_radius=hypot(blur_center.x,blur_center.y);
cristy117ff172010-08-15 21:35:32 +00002907 n=(size_t) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+2UL);
cristy3ed852e2009-09-05 21:47:34 +00002908 theta=DegreesToRadians(angle)/(MagickRealType) (n-1);
2909 cos_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
2910 sizeof(*cos_theta));
2911 sin_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
2912 sizeof(*sin_theta));
2913 if ((cos_theta == (MagickRealType *) NULL) ||
2914 (sin_theta == (MagickRealType *) NULL))
2915 {
2916 blur_image=DestroyImage(blur_image);
2917 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2918 }
2919 offset=theta*(MagickRealType) (n-1)/2.0;
cristybb503372010-05-27 20:51:26 +00002920 for (i=0; i < (ssize_t) n; i++)
cristy3ed852e2009-09-05 21:47:34 +00002921 {
2922 cos_theta[i]=cos((double) (theta*i-offset));
2923 sin_theta[i]=sin((double) (theta*i-offset));
2924 }
2925 /*
2926 Radial blur image.
2927 */
2928 status=MagickTrue;
2929 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00002930 image_view=AcquireCacheView(image);
2931 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00002932#if defined(MAGICKCORE_OPENMP_SUPPORT)
2933 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002934#endif
cristy1e7aa312011-09-10 20:01:36 +00002935 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002936 {
cristy1e7aa312011-09-10 20:01:36 +00002937 register const Quantum
2938 *restrict p;
2939
cristy4c08aed2011-07-01 19:47:50 +00002940 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002941 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002942
cristy117ff172010-08-15 21:35:32 +00002943 register ssize_t
2944 x;
2945
cristy3ed852e2009-09-05 21:47:34 +00002946 if (status == MagickFalse)
2947 continue;
cristy1e7aa312011-09-10 20:01:36 +00002948 p=GetCacheViewVirtualPixels(blur_view,0,y,image->columns,1,exception);
cristy3ed852e2009-09-05 21:47:34 +00002949 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
2950 exception);
cristy1e7aa312011-09-10 20:01:36 +00002951 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00002952 {
2953 status=MagickFalse;
2954 continue;
2955 }
cristy1e7aa312011-09-10 20:01:36 +00002956 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002957 {
cristy3ed852e2009-09-05 21:47:34 +00002958 MagickRealType
cristy3ed852e2009-09-05 21:47:34 +00002959 radius;
2960
cristy3ed852e2009-09-05 21:47:34 +00002961 PointInfo
2962 center;
2963
cristybb503372010-05-27 20:51:26 +00002964 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002965 i;
2966
cristybb503372010-05-27 20:51:26 +00002967 size_t
cristy3ed852e2009-09-05 21:47:34 +00002968 step;
2969
2970 center.x=(double) x-blur_center.x;
2971 center.y=(double) y-blur_center.y;
2972 radius=hypot((double) center.x,center.y);
2973 if (radius == 0)
2974 step=1;
2975 else
2976 {
cristybb503372010-05-27 20:51:26 +00002977 step=(size_t) (blur_radius/radius);
cristy3ed852e2009-09-05 21:47:34 +00002978 if (step == 0)
2979 step=1;
2980 else
2981 if (step >= n)
2982 step=n-1;
2983 }
cristy1e7aa312011-09-10 20:01:36 +00002984 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2985 {
2986 MagickRealType
2987 gamma,
2988 pixel;
cristy3ed852e2009-09-05 21:47:34 +00002989
cristy1e7aa312011-09-10 20:01:36 +00002990 PixelChannel
2991 channel;
2992
2993 PixelTrait
2994 blur_traits,
2995 traits;
2996
2997 register const Quantum
2998 *restrict r;
2999
3000 register ssize_t
3001 j;
3002
3003 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
3004 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
3005 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
3006 if ((traits == UndefinedPixelTrait) ||
3007 (blur_traits == UndefinedPixelTrait))
3008 continue;
3009 if ((blur_traits & CopyPixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003010 {
cristy1e7aa312011-09-10 20:01:36 +00003011 q[channel]=p[i];
3012 continue;
cristy3ed852e2009-09-05 21:47:34 +00003013 }
cristy1e7aa312011-09-10 20:01:36 +00003014 gamma=0.0;
3015 pixel=bias;
3016 if ((blur_traits & BlendPixelTrait) == 0)
3017 {
3018 for (j=0; j < (ssize_t) n; j+=(ssize_t) step)
3019 {
3020 r=GetCacheViewVirtualPixels(image_view, (ssize_t) (blur_center.x+
3021 center.x*cos_theta[j]-center.y*sin_theta[j]+0.5),(ssize_t)
3022 (blur_center.y+center.x*sin_theta[j]+center.y*cos_theta[j]+0.5),
3023 1,1,exception);
3024 if (r == (const Quantum *) NULL)
3025 {
3026 status=MagickFalse;
3027 continue;
3028 }
3029 pixel+=r[i];
3030 gamma++;
3031 }
3032 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3033 q[channel]=ClampToQuantum(gamma*pixel);
3034 continue;
3035 }
3036 for (j=0; j < (ssize_t) n; j+=(ssize_t) step)
3037 {
3038 r=GetCacheViewVirtualPixels(image_view, (ssize_t) (blur_center.x+
3039 center.x*cos_theta[j]-center.y*sin_theta[j]+0.5),(ssize_t)
3040 (blur_center.y+center.x*sin_theta[j]+center.y*cos_theta[j]+0.5),
3041 1,1,exception);
3042 if (r == (const Quantum *) NULL)
3043 {
3044 status=MagickFalse;
3045 continue;
3046 }
3047 pixel+=GetPixelAlpha(image,r)*r[i];
3048 gamma+=GetPixelAlpha(image,r);
cristy3ed852e2009-09-05 21:47:34 +00003049 }
cristy1e7aa312011-09-10 20:01:36 +00003050 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3051 q[channel]=ClampToQuantum(gamma*pixel);
3052 }
3053 p+=GetPixelChannels(image);
cristyed231572011-07-14 02:18:59 +00003054 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003055 }
3056 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
3057 status=MagickFalse;
3058 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3059 {
3060 MagickBooleanType
3061 proceed;
3062
cristyb5d5f722009-11-04 03:03:49 +00003063#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00003064 #pragma omp critical (MagickCore_RadialBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00003065#endif
3066 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
3067 if (proceed == MagickFalse)
3068 status=MagickFalse;
3069 }
3070 }
3071 blur_view=DestroyCacheView(blur_view);
3072 image_view=DestroyCacheView(image_view);
3073 cos_theta=(MagickRealType *) RelinquishMagickMemory(cos_theta);
3074 sin_theta=(MagickRealType *) RelinquishMagickMemory(sin_theta);
3075 if (status == MagickFalse)
3076 blur_image=DestroyImage(blur_image);
3077 return(blur_image);
3078}
3079
3080/*
3081%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3082% %
3083% %
3084% %
cristy3ed852e2009-09-05 21:47:34 +00003085% S e l e c t i v e B l u r I m a g e %
3086% %
3087% %
3088% %
3089%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3090%
3091% SelectiveBlurImage() selectively blur pixels within a contrast threshold.
3092% It is similar to the unsharpen mask that sharpens everything with contrast
3093% above a certain threshold.
3094%
3095% The format of the SelectiveBlurImage method is:
3096%
3097% Image *SelectiveBlurImage(const Image *image,const double radius,
cristy1e7aa312011-09-10 20:01:36 +00003098% const double sigma,const double threshold,const double bias,
3099% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003100%
3101% A description of each parameter follows:
3102%
3103% o image: the image.
3104%
cristy3ed852e2009-09-05 21:47:34 +00003105% o radius: the radius of the Gaussian, in pixels, not counting the center
3106% pixel.
3107%
3108% o sigma: the standard deviation of the Gaussian, in pixels.
3109%
3110% o threshold: only pixels within this contrast threshold are included
3111% in the blur operation.
3112%
cristy1e7aa312011-09-10 20:01:36 +00003113% o bias: the bias.
3114%
cristy3ed852e2009-09-05 21:47:34 +00003115% o exception: return any errors or warnings in this structure.
3116%
3117*/
cristyf4ad9df2011-07-08 16:49:03 +00003118MagickExport Image *SelectiveBlurImage(const Image *image,
3119 const double radius,const double sigma,const double threshold,
cristy1e7aa312011-09-10 20:01:36 +00003120 const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003121{
3122#define SelectiveBlurImageTag "SelectiveBlur/Image"
3123
cristy47e00502009-12-17 19:19:57 +00003124 CacheView
3125 *blur_view,
3126 *image_view;
3127
cristy3ed852e2009-09-05 21:47:34 +00003128 double
cristy3ed852e2009-09-05 21:47:34 +00003129 *kernel;
3130
3131 Image
3132 *blur_image;
3133
cristy3ed852e2009-09-05 21:47:34 +00003134 MagickBooleanType
3135 status;
3136
cristybb503372010-05-27 20:51:26 +00003137 MagickOffsetType
3138 progress;
3139
cristybb503372010-05-27 20:51:26 +00003140 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003141 i;
cristy3ed852e2009-09-05 21:47:34 +00003142
cristybb503372010-05-27 20:51:26 +00003143 size_t
cristy1e7aa312011-09-10 20:01:36 +00003144 center,
cristy3ed852e2009-09-05 21:47:34 +00003145 width;
3146
cristybb503372010-05-27 20:51:26 +00003147 ssize_t
3148 j,
3149 u,
3150 v,
3151 y;
3152
cristy3ed852e2009-09-05 21:47:34 +00003153 /*
3154 Initialize blur image attributes.
3155 */
3156 assert(image != (Image *) NULL);
3157 assert(image->signature == MagickSignature);
3158 if (image->debug != MagickFalse)
3159 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3160 assert(exception != (ExceptionInfo *) NULL);
3161 assert(exception->signature == MagickSignature);
3162 width=GetOptimalKernelWidth1D(radius,sigma);
3163 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
3164 if (kernel == (double *) NULL)
3165 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00003166 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00003167 i=0;
cristy47e00502009-12-17 19:19:57 +00003168 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00003169 {
cristy47e00502009-12-17 19:19:57 +00003170 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00003171 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
3172 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00003173 }
3174 if (image->debug != MagickFalse)
3175 {
3176 char
3177 format[MaxTextExtent],
3178 *message;
3179
cristy117ff172010-08-15 21:35:32 +00003180 register const double
3181 *k;
3182
cristybb503372010-05-27 20:51:26 +00003183 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003184 u,
3185 v;
3186
cristy3ed852e2009-09-05 21:47:34 +00003187 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00003188 " SelectiveBlurImage with %.20gx%.20g kernel:",(double) width,(double)
3189 width);
cristy3ed852e2009-09-05 21:47:34 +00003190 message=AcquireString("");
3191 k=kernel;
cristybb503372010-05-27 20:51:26 +00003192 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003193 {
3194 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00003195 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy3ed852e2009-09-05 21:47:34 +00003196 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00003197 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003198 {
cristyb51dff52011-05-19 16:55:47 +00003199 (void) FormatLocaleString(format,MaxTextExtent,"%+f ",*k++);
cristy3ed852e2009-09-05 21:47:34 +00003200 (void) ConcatenateString(&message,format);
3201 }
3202 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
3203 }
3204 message=DestroyString(message);
3205 }
cristy1e7aa312011-09-10 20:01:36 +00003206 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00003207 if (blur_image == (Image *) NULL)
3208 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00003209 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003210 {
cristy3ed852e2009-09-05 21:47:34 +00003211 blur_image=DestroyImage(blur_image);
3212 return((Image *) NULL);
3213 }
3214 /*
3215 Threshold blur image.
3216 */
3217 status=MagickTrue;
3218 progress=0;
cristy1e7aa312011-09-10 20:01:36 +00003219 center=(ssize_t) GetPixelChannels(image)*(image->columns+width)*(width/2L)+
3220 GetPixelChannels(image)*(width/2L);
cristy3ed852e2009-09-05 21:47:34 +00003221 image_view=AcquireCacheView(image);
3222 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003223#if defined(MAGICKCORE_OPENMP_SUPPORT)
3224 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003225#endif
cristybb503372010-05-27 20:51:26 +00003226 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003227 {
cristy4c08aed2011-07-01 19:47:50 +00003228 double
3229 contrast;
3230
cristy3ed852e2009-09-05 21:47:34 +00003231 MagickBooleanType
3232 sync;
3233
cristy4c08aed2011-07-01 19:47:50 +00003234 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003235 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003236
cristy4c08aed2011-07-01 19:47:50 +00003237 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003238 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003239
cristy117ff172010-08-15 21:35:32 +00003240 register ssize_t
3241 x;
3242
cristy3ed852e2009-09-05 21:47:34 +00003243 if (status == MagickFalse)
3244 continue;
cristy117ff172010-08-15 21:35:32 +00003245 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
3246 (width/2L),image->columns+width,width,exception);
cristy3ed852e2009-09-05 21:47:34 +00003247 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3248 exception);
cristy4c08aed2011-07-01 19:47:50 +00003249 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003250 {
3251 status=MagickFalse;
3252 continue;
3253 }
cristybb503372010-05-27 20:51:26 +00003254 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003255 {
cristybb503372010-05-27 20:51:26 +00003256 register ssize_t
cristy1e7aa312011-09-10 20:01:36 +00003257 i;
cristy3ed852e2009-09-05 21:47:34 +00003258
cristy1e7aa312011-09-10 20:01:36 +00003259 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3260 {
3261 MagickRealType
3262 alpha,
3263 gamma,
3264 intensity,
3265 pixel;
cristy117ff172010-08-15 21:35:32 +00003266
cristy1e7aa312011-09-10 20:01:36 +00003267 PixelChannel
3268 channel;
3269
3270 PixelTrait
3271 blur_traits,
3272 traits;
3273
3274 register const double
3275 *restrict k;
3276
3277 register const Quantum
3278 *restrict pixels;
3279
3280 register ssize_t
3281 u;
3282
3283 ssize_t
3284 v;
3285
3286 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
3287 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
3288 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
3289 if ((traits == UndefinedPixelTrait) ||
3290 (blur_traits == UndefinedPixelTrait))
3291 continue;
3292 if ((blur_traits & CopyPixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003293 {
cristy1e7aa312011-09-10 20:01:36 +00003294 q[channel]=p[center+i];
3295 continue;
cristy3ed852e2009-09-05 21:47:34 +00003296 }
cristy1e7aa312011-09-10 20:01:36 +00003297 k=kernel;
3298 pixel=bias;
3299 pixels=p;
3300 intensity=GetPixelIntensity(image,p+center);
3301 gamma=0.0;
3302 if ((blur_traits & BlendPixelTrait) == 0)
cristy3ed852e2009-09-05 21:47:34 +00003303 {
cristy1e7aa312011-09-10 20:01:36 +00003304 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003305 {
cristy1e7aa312011-09-10 20:01:36 +00003306 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003307 {
cristy1e7aa312011-09-10 20:01:36 +00003308 contrast=GetPixelIntensity(image,pixels)-intensity;
3309 if (fabs(contrast) < threshold)
3310 {
3311 pixel+=(*k)*pixels[i];
3312 gamma+=(*k);
3313 }
3314 k++;
3315 pixels+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003316 }
cristy1e7aa312011-09-10 20:01:36 +00003317 pixels+=image->columns*GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003318 }
cristy1e7aa312011-09-10 20:01:36 +00003319 if (fabs((double) gamma) < MagickEpsilon)
3320 {
3321 q[channel]=p[center+i];
3322 continue;
3323 }
3324 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3325 q[channel]=ClampToQuantum(gamma*pixel);
3326 continue;
3327 }
3328 for (v=0; v < (ssize_t) width; v++)
3329 {
3330 for (u=0; u < (ssize_t) width; u++)
3331 {
3332 contrast=GetPixelIntensity(image,pixels)-intensity;
3333 if (fabs(contrast) < threshold)
3334 {
3335 alpha=(MagickRealType) (QuantumScale*
3336 GetPixelAlpha(image,pixels));
3337 pixel+=(*k)*alpha*pixels[i];
3338 gamma+=(*k)*alpha;
3339 }
3340 k++;
3341 pixels+=GetPixelChannels(image);
3342 }
3343 pixels+=image->columns*GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003344 }
cristy1e7aa312011-09-10 20:01:36 +00003345 if (fabs((double) gamma) < MagickEpsilon)
3346 {
3347 q[channel]=p[center+i];
3348 continue;
3349 }
3350 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3351 q[channel]=ClampToQuantum(gamma*pixel);
3352 }
cristyed231572011-07-14 02:18:59 +00003353 p+=GetPixelChannels(image);
3354 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003355 }
3356 sync=SyncCacheViewAuthenticPixels(blur_view,exception);
3357 if (sync == MagickFalse)
3358 status=MagickFalse;
3359 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3360 {
3361 MagickBooleanType
3362 proceed;
3363
cristyb5d5f722009-11-04 03:03:49 +00003364#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00003365 #pragma omp critical (MagickCore_SelectiveBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00003366#endif
3367 proceed=SetImageProgress(image,SelectiveBlurImageTag,progress++,
3368 image->rows);
3369 if (proceed == MagickFalse)
3370 status=MagickFalse;
3371 }
3372 }
3373 blur_image->type=image->type;
3374 blur_view=DestroyCacheView(blur_view);
3375 image_view=DestroyCacheView(image_view);
3376 kernel=(double *) RelinquishMagickMemory(kernel);
3377 if (status == MagickFalse)
3378 blur_image=DestroyImage(blur_image);
3379 return(blur_image);
3380}
3381
3382/*
3383%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3384% %
3385% %
3386% %
3387% S h a d e I m a g e %
3388% %
3389% %
3390% %
3391%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3392%
3393% ShadeImage() shines a distant light on an image to create a
3394% three-dimensional effect. You control the positioning of the light with
3395% azimuth and elevation; azimuth is measured in degrees off the x axis
3396% and elevation is measured in pixels above the Z axis.
3397%
3398% The format of the ShadeImage method is:
3399%
3400% Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3401% const double azimuth,const double elevation,ExceptionInfo *exception)
3402%
3403% A description of each parameter follows:
3404%
3405% o image: the image.
3406%
3407% o gray: A value other than zero shades the intensity of each pixel.
3408%
3409% o azimuth, elevation: Define the light source direction.
3410%
3411% o exception: return any errors or warnings in this structure.
3412%
3413*/
3414MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3415 const double azimuth,const double elevation,ExceptionInfo *exception)
3416{
3417#define ShadeImageTag "Shade/Image"
3418
cristyc4c8d132010-01-07 01:58:38 +00003419 CacheView
3420 *image_view,
3421 *shade_view;
3422
cristy3ed852e2009-09-05 21:47:34 +00003423 Image
3424 *shade_image;
3425
cristy3ed852e2009-09-05 21:47:34 +00003426 MagickBooleanType
3427 status;
3428
cristybb503372010-05-27 20:51:26 +00003429 MagickOffsetType
3430 progress;
3431
cristy3ed852e2009-09-05 21:47:34 +00003432 PrimaryInfo
3433 light;
3434
cristybb503372010-05-27 20:51:26 +00003435 ssize_t
3436 y;
3437
cristy3ed852e2009-09-05 21:47:34 +00003438 /*
3439 Initialize shaded image attributes.
3440 */
3441 assert(image != (const Image *) NULL);
3442 assert(image->signature == MagickSignature);
3443 if (image->debug != MagickFalse)
3444 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3445 assert(exception != (ExceptionInfo *) NULL);
3446 assert(exception->signature == MagickSignature);
3447 shade_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
3448 if (shade_image == (Image *) NULL)
3449 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00003450 if (SetImageStorageClass(shade_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003451 {
cristy3ed852e2009-09-05 21:47:34 +00003452 shade_image=DestroyImage(shade_image);
3453 return((Image *) NULL);
3454 }
3455 /*
3456 Compute the light vector.
3457 */
3458 light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))*
3459 cos(DegreesToRadians(elevation));
3460 light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))*
3461 cos(DegreesToRadians(elevation));
3462 light.z=(double) QuantumRange*sin(DegreesToRadians(elevation));
3463 /*
3464 Shade image.
3465 */
3466 status=MagickTrue;
3467 progress=0;
3468 image_view=AcquireCacheView(image);
3469 shade_view=AcquireCacheView(shade_image);
cristyb5d5f722009-11-04 03:03:49 +00003470#if defined(MAGICKCORE_OPENMP_SUPPORT)
3471 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003472#endif
cristybb503372010-05-27 20:51:26 +00003473 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003474 {
3475 MagickRealType
3476 distance,
3477 normal_distance,
3478 shade;
3479
3480 PrimaryInfo
3481 normal;
3482
cristy4c08aed2011-07-01 19:47:50 +00003483 register const Quantum
cristy1e7aa312011-09-10 20:01:36 +00003484 *restrict center,
cristyc47d1f82009-11-26 01:44:43 +00003485 *restrict p,
cristy1e7aa312011-09-10 20:01:36 +00003486 *restrict post,
3487 *restrict pre;
cristy3ed852e2009-09-05 21:47:34 +00003488
cristy4c08aed2011-07-01 19:47:50 +00003489 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003490 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003491
cristy117ff172010-08-15 21:35:32 +00003492 register ssize_t
3493 x;
3494
cristy3ed852e2009-09-05 21:47:34 +00003495 if (status == MagickFalse)
3496 continue;
3497 p=GetCacheViewVirtualPixels(image_view,-1,y-1,image->columns+2,3,exception);
3498 q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1,
3499 exception);
cristy4c08aed2011-07-01 19:47:50 +00003500 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003501 {
3502 status=MagickFalse;
3503 continue;
3504 }
3505 /*
3506 Shade this row of pixels.
3507 */
3508 normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */
cristy1e7aa312011-09-10 20:01:36 +00003509 pre=p+GetPixelChannels(image);
3510 center=pre+(image->columns+2)*GetPixelChannels(image);
3511 post=center+(image->columns+2)*GetPixelChannels(image);
cristybb503372010-05-27 20:51:26 +00003512 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003513 {
cristy1e7aa312011-09-10 20:01:36 +00003514 register ssize_t
3515 i;
3516
cristy3ed852e2009-09-05 21:47:34 +00003517 /*
3518 Determine the surface normal and compute shading.
3519 */
cristy1e7aa312011-09-10 20:01:36 +00003520 normal.x=(double) (GetPixelIntensity(image,pre-GetPixelChannels(image))+
3521 GetPixelIntensity(image,center-GetPixelChannels(image))+
3522 GetPixelIntensity(image,post-GetPixelChannels(image))-
3523 GetPixelIntensity(image,pre+GetPixelChannels(image))-
3524 GetPixelIntensity(image,center+GetPixelChannels(image))-
3525 GetPixelIntensity(image,post+GetPixelChannels(image)));
3526 normal.y=(double) (GetPixelIntensity(image,post-GetPixelChannels(image))+
3527 GetPixelIntensity(image,post)+GetPixelIntensity(image,post+
3528 GetPixelChannels(image))-GetPixelIntensity(image,pre-
3529 GetPixelChannels(image))-GetPixelIntensity(image,pre)-
3530 GetPixelIntensity(image,pre+GetPixelChannels(image)));
cristy3ed852e2009-09-05 21:47:34 +00003531 if ((normal.x == 0.0) && (normal.y == 0.0))
3532 shade=light.z;
3533 else
3534 {
3535 shade=0.0;
3536 distance=normal.x*light.x+normal.y*light.y+normal.z*light.z;
3537 if (distance > MagickEpsilon)
3538 {
3539 normal_distance=
3540 normal.x*normal.x+normal.y*normal.y+normal.z*normal.z;
3541 if (normal_distance > (MagickEpsilon*MagickEpsilon))
3542 shade=distance/sqrt((double) normal_distance);
3543 }
3544 }
cristy1e7aa312011-09-10 20:01:36 +00003545 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3546 {
3547 PixelChannel
3548 channel;
3549
3550 PixelTrait
3551 shade_traits,
3552 traits;
3553
3554 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
3555 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
3556 shade_traits=GetPixelChannelMapTraits(shade_image,channel);
3557 if ((traits == UndefinedPixelTrait) ||
3558 (shade_traits == UndefinedPixelTrait))
3559 continue;
3560 if ((shade_traits & CopyPixelTrait) != 0)
3561 {
3562 q[channel]=center[i];
3563 continue;
3564 }
3565 if (gray != MagickFalse)
3566 {
3567 q[channel]=ClampToQuantum(shade);
3568 continue;
3569 }
3570 q[channel]=ClampToQuantum(QuantumScale*shade*center[i]);
3571 }
3572 pre+=GetPixelChannels(image);
3573 center+=GetPixelChannels(image);
3574 post+=GetPixelChannels(image);
cristyed231572011-07-14 02:18:59 +00003575 q+=GetPixelChannels(shade_image);
cristy3ed852e2009-09-05 21:47:34 +00003576 }
3577 if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
3578 status=MagickFalse;
3579 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3580 {
3581 MagickBooleanType
3582 proceed;
3583
cristyb5d5f722009-11-04 03:03:49 +00003584#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003585 #pragma omp critical (MagickCore_ShadeImage)
3586#endif
3587 proceed=SetImageProgress(image,ShadeImageTag,progress++,image->rows);
3588 if (proceed == MagickFalse)
3589 status=MagickFalse;
3590 }
3591 }
3592 shade_view=DestroyCacheView(shade_view);
3593 image_view=DestroyCacheView(image_view);
3594 if (status == MagickFalse)
3595 shade_image=DestroyImage(shade_image);
3596 return(shade_image);
3597}
3598
3599/*
3600%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3601% %
3602% %
3603% %
3604% S h a r p e n I m a g e %
3605% %
3606% %
3607% %
3608%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3609%
3610% SharpenImage() sharpens the image. We convolve the image with a Gaussian
3611% operator of the given radius and standard deviation (sigma). For
3612% reasonable results, radius should be larger than sigma. Use a radius of 0
3613% and SharpenImage() selects a suitable radius for you.
3614%
3615% Using a separable kernel would be faster, but the negative weights cancel
3616% out on the corners of the kernel producing often undesirable ringing in the
3617% filtered result; this can be avoided by using a 2D gaussian shaped image
3618% sharpening kernel instead.
3619%
3620% The format of the SharpenImage method is:
3621%
3622% Image *SharpenImage(const Image *image,const double radius,
cristy05c0c9a2011-09-05 23:16:13 +00003623% const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003624%
3625% A description of each parameter follows:
3626%
3627% o image: the image.
3628%
cristy3ed852e2009-09-05 21:47:34 +00003629% o radius: the radius of the Gaussian, in pixels, not counting the center
3630% pixel.
3631%
3632% o sigma: the standard deviation of the Laplacian, in pixels.
3633%
cristy05c0c9a2011-09-05 23:16:13 +00003634% o bias: bias.
3635%
cristy3ed852e2009-09-05 21:47:34 +00003636% o exception: return any errors or warnings in this structure.
3637%
3638*/
cristy3ed852e2009-09-05 21:47:34 +00003639MagickExport Image *SharpenImage(const Image *image,const double radius,
cristy05c0c9a2011-09-05 23:16:13 +00003640 const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003641{
cristy3ed852e2009-09-05 21:47:34 +00003642 double
cristy47e00502009-12-17 19:19:57 +00003643 normalize;
cristy3ed852e2009-09-05 21:47:34 +00003644
3645 Image
3646 *sharp_image;
3647
cristy41cbe682011-07-15 19:12:37 +00003648 KernelInfo
3649 *kernel_info;
3650
cristybb503372010-05-27 20:51:26 +00003651 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003652 i;
3653
cristybb503372010-05-27 20:51:26 +00003654 size_t
cristy3ed852e2009-09-05 21:47:34 +00003655 width;
3656
cristy117ff172010-08-15 21:35:32 +00003657 ssize_t
3658 j,
3659 u,
3660 v;
3661
cristy3ed852e2009-09-05 21:47:34 +00003662 assert(image != (const Image *) NULL);
3663 assert(image->signature == MagickSignature);
3664 if (image->debug != MagickFalse)
3665 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3666 assert(exception != (ExceptionInfo *) NULL);
3667 assert(exception->signature == MagickSignature);
3668 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00003669 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00003670 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003671 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00003672 (void) ResetMagickMemory(kernel_info,0,sizeof(*kernel_info));
3673 kernel_info->width=width;
3674 kernel_info->height=width;
cristy05c0c9a2011-09-05 23:16:13 +00003675 kernel_info->bias=bias;
cristy41cbe682011-07-15 19:12:37 +00003676 kernel_info->signature=MagickSignature;
3677 kernel_info->values=(double *) AcquireAlignedMemory(kernel_info->width,
3678 kernel_info->width*sizeof(*kernel_info->values));
3679 if (kernel_info->values == (double *) NULL)
3680 {
3681 kernel_info=DestroyKernelInfo(kernel_info);
3682 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3683 }
cristy3ed852e2009-09-05 21:47:34 +00003684 normalize=0.0;
cristy41cbe682011-07-15 19:12:37 +00003685 j=(ssize_t) kernel_info->width/2;
cristy47e00502009-12-17 19:19:57 +00003686 i=0;
3687 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00003688 {
cristy47e00502009-12-17 19:19:57 +00003689 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00003690 {
cristy41cbe682011-07-15 19:12:37 +00003691 kernel_info->values[i]=(double) (-exp(-((double) u*u+v*v)/(2.0*
3692 MagickSigma*MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
3693 normalize+=kernel_info->values[i];
cristy3ed852e2009-09-05 21:47:34 +00003694 i++;
3695 }
3696 }
cristy41cbe682011-07-15 19:12:37 +00003697 kernel_info->values[i/2]=(double) ((-2.0)*normalize);
cristy5e6be1e2011-07-16 01:23:39 +00003698 sharp_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00003699 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00003700 return(sharp_image);
3701}
3702
3703/*
3704%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3705% %
3706% %
3707% %
3708% S p r e a d I m a g e %
3709% %
3710% %
3711% %
3712%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3713%
3714% SpreadImage() is a special effects method that randomly displaces each
3715% pixel in a block defined by the radius parameter.
3716%
3717% The format of the SpreadImage method is:
3718%
3719% Image *SpreadImage(const Image *image,const double radius,
cristy5c4e2582011-09-11 19:21:03 +00003720% const PixelInterpolateMethod method,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003721%
3722% A description of each parameter follows:
3723%
3724% o image: the image.
3725%
cristy5c4e2582011-09-11 19:21:03 +00003726% o radius: choose a random pixel in a neighborhood of this extent.
3727%
3728% o method: the pixel interpolation method.
cristy3ed852e2009-09-05 21:47:34 +00003729%
3730% o exception: return any errors or warnings in this structure.
3731%
3732*/
3733MagickExport Image *SpreadImage(const Image *image,const double radius,
cristy5c4e2582011-09-11 19:21:03 +00003734 const PixelInterpolateMethod method,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003735{
3736#define SpreadImageTag "Spread/Image"
3737
cristyfa112112010-01-04 17:48:07 +00003738 CacheView
cristy9f7e7cb2011-03-26 00:49:57 +00003739 *image_view,
3740 *spread_view;
cristyfa112112010-01-04 17:48:07 +00003741
cristy3ed852e2009-09-05 21:47:34 +00003742 Image
3743 *spread_image;
3744
cristy3ed852e2009-09-05 21:47:34 +00003745 MagickBooleanType
3746 status;
3747
cristybb503372010-05-27 20:51:26 +00003748 MagickOffsetType
3749 progress;
3750
cristy3ed852e2009-09-05 21:47:34 +00003751 RandomInfo
cristyfa112112010-01-04 17:48:07 +00003752 **restrict random_info;
cristy3ed852e2009-09-05 21:47:34 +00003753
cristybb503372010-05-27 20:51:26 +00003754 size_t
cristy3ed852e2009-09-05 21:47:34 +00003755 width;
3756
cristybb503372010-05-27 20:51:26 +00003757 ssize_t
3758 y;
3759
cristy3ed852e2009-09-05 21:47:34 +00003760 /*
3761 Initialize spread image attributes.
3762 */
3763 assert(image != (Image *) NULL);
3764 assert(image->signature == MagickSignature);
3765 if (image->debug != MagickFalse)
3766 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3767 assert(exception != (ExceptionInfo *) NULL);
3768 assert(exception->signature == MagickSignature);
3769 spread_image=CloneImage(image,image->columns,image->rows,MagickTrue,
3770 exception);
3771 if (spread_image == (Image *) NULL)
3772 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00003773 if (SetImageStorageClass(spread_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003774 {
cristy3ed852e2009-09-05 21:47:34 +00003775 spread_image=DestroyImage(spread_image);
3776 return((Image *) NULL);
3777 }
3778 /*
3779 Spread image.
3780 */
3781 status=MagickTrue;
3782 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00003783 width=GetOptimalKernelWidth1D(radius,0.5);
cristy3ed852e2009-09-05 21:47:34 +00003784 random_info=AcquireRandomInfoThreadSet();
cristy9f7e7cb2011-03-26 00:49:57 +00003785 image_view=AcquireCacheView(image);
3786 spread_view=AcquireCacheView(spread_image);
cristyb557a152011-02-22 12:14:30 +00003787#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00003788 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00003789#endif
cristybe82ad52011-09-06 01:17:20 +00003790 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003791 {
cristy5c9e6f22010-09-17 17:31:01 +00003792 const int
3793 id = GetOpenMPThreadId();
cristy6ebe97c2010-07-03 01:17:28 +00003794
cristybe82ad52011-09-06 01:17:20 +00003795 register const Quantum
3796 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003797
cristy4c08aed2011-07-01 19:47:50 +00003798 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003799 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003800
cristy117ff172010-08-15 21:35:32 +00003801 register ssize_t
3802 x;
3803
cristy3ed852e2009-09-05 21:47:34 +00003804 if (status == MagickFalse)
3805 continue;
cristybe82ad52011-09-06 01:17:20 +00003806 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy9f7e7cb2011-03-26 00:49:57 +00003807 q=QueueCacheViewAuthenticPixels(spread_view,0,y,spread_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +00003808 exception);
cristybe82ad52011-09-06 01:17:20 +00003809 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003810 {
3811 status=MagickFalse;
3812 continue;
3813 }
cristybe82ad52011-09-06 01:17:20 +00003814 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003815 {
cristybe82ad52011-09-06 01:17:20 +00003816 PointInfo
3817 point;
3818
cristybe82ad52011-09-06 01:17:20 +00003819 point.x=GetPseudoRandomValue(random_info[id]);
3820 point.y=GetPseudoRandomValue(random_info[id]);
cristy5c4e2582011-09-11 19:21:03 +00003821 status=InterpolatePixelChannels(image,image_view,spread_image,method,
3822 (double) x+width*point.x-0.5,(double) y+width*point.y-0.5,q,exception);
cristyed231572011-07-14 02:18:59 +00003823 q+=GetPixelChannels(spread_image);
cristy3ed852e2009-09-05 21:47:34 +00003824 }
cristy9f7e7cb2011-03-26 00:49:57 +00003825 if (SyncCacheViewAuthenticPixels(spread_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003826 status=MagickFalse;
3827 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3828 {
3829 MagickBooleanType
3830 proceed;
3831
cristyb557a152011-02-22 12:14:30 +00003832#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003833 #pragma omp critical (MagickCore_SpreadImage)
3834#endif
3835 proceed=SetImageProgress(image,SpreadImageTag,progress++,image->rows);
3836 if (proceed == MagickFalse)
3837 status=MagickFalse;
3838 }
3839 }
cristy9f7e7cb2011-03-26 00:49:57 +00003840 spread_view=DestroyCacheView(spread_view);
cristy3ed852e2009-09-05 21:47:34 +00003841 image_view=DestroyCacheView(image_view);
3842 random_info=DestroyRandomInfoThreadSet(random_info);
cristy3ed852e2009-09-05 21:47:34 +00003843 return(spread_image);
3844}
3845
3846/*
3847%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3848% %
3849% %
3850% %
cristy0834d642011-03-18 18:26:08 +00003851% S t a t i s t i c I m a g e %
3852% %
3853% %
3854% %
3855%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3856%
3857% StatisticImage() makes each pixel the min / max / median / mode / etc. of
cristy8d752042011-03-19 01:00:36 +00003858% the neighborhood of the specified width and height.
cristy0834d642011-03-18 18:26:08 +00003859%
3860% The format of the StatisticImage method is:
3861%
3862% Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00003863% const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00003864%
3865% A description of each parameter follows:
3866%
3867% o image: the image.
3868%
cristy0834d642011-03-18 18:26:08 +00003869% o type: the statistic type (median, mode, etc.).
3870%
cristy95c38342011-03-18 22:39:51 +00003871% o width: the width of the pixel neighborhood.
3872%
3873% o height: the height of the pixel neighborhood.
cristy0834d642011-03-18 18:26:08 +00003874%
3875% o exception: return any errors or warnings in this structure.
3876%
3877*/
3878
cristyf36cbcb2011-09-07 13:28:22 +00003879typedef struct _SkipNode
cristy733678d2011-03-18 21:29:28 +00003880{
3881 size_t
3882 next[9],
3883 count,
3884 signature;
cristyf36cbcb2011-09-07 13:28:22 +00003885} SkipNode;
cristy733678d2011-03-18 21:29:28 +00003886
3887typedef struct _SkipList
3888{
3889 ssize_t
3890 level;
3891
cristyf36cbcb2011-09-07 13:28:22 +00003892 SkipNode
cristy733678d2011-03-18 21:29:28 +00003893 *nodes;
3894} SkipList;
3895
3896typedef struct _PixelList
3897{
3898 size_t
cristy6fc86bb2011-03-18 23:45:16 +00003899 length,
cristyf36cbcb2011-09-07 13:28:22 +00003900 seed;
cristy733678d2011-03-18 21:29:28 +00003901
3902 SkipList
cristyf36cbcb2011-09-07 13:28:22 +00003903 skip_list;
3904
3905 size_t
3906 signature;
cristy733678d2011-03-18 21:29:28 +00003907} PixelList;
3908
3909static PixelList *DestroyPixelList(PixelList *pixel_list)
3910{
cristy733678d2011-03-18 21:29:28 +00003911 if (pixel_list == (PixelList *) NULL)
3912 return((PixelList *) NULL);
cristyf36cbcb2011-09-07 13:28:22 +00003913 if (pixel_list->skip_list.nodes != (SkipNode *) NULL)
3914 pixel_list->skip_list.nodes=(SkipNode *) RelinquishMagickMemory(
3915 pixel_list->skip_list.nodes);
cristy733678d2011-03-18 21:29:28 +00003916 pixel_list=(PixelList *) RelinquishMagickMemory(pixel_list);
3917 return(pixel_list);
3918}
3919
3920static PixelList **DestroyPixelListThreadSet(PixelList **pixel_list)
3921{
3922 register ssize_t
3923 i;
3924
3925 assert(pixel_list != (PixelList **) NULL);
3926 for (i=0; i < (ssize_t) GetOpenMPMaximumThreads(); i++)
3927 if (pixel_list[i] != (PixelList *) NULL)
3928 pixel_list[i]=DestroyPixelList(pixel_list[i]);
3929 pixel_list=(PixelList **) RelinquishMagickMemory(pixel_list);
3930 return(pixel_list);
3931}
3932
cristy6fc86bb2011-03-18 23:45:16 +00003933static PixelList *AcquirePixelList(const size_t width,const size_t height)
cristy733678d2011-03-18 21:29:28 +00003934{
3935 PixelList
3936 *pixel_list;
3937
cristy733678d2011-03-18 21:29:28 +00003938 pixel_list=(PixelList *) AcquireMagickMemory(sizeof(*pixel_list));
3939 if (pixel_list == (PixelList *) NULL)
3940 return(pixel_list);
3941 (void) ResetMagickMemory((void *) pixel_list,0,sizeof(*pixel_list));
cristy6fc86bb2011-03-18 23:45:16 +00003942 pixel_list->length=width*height;
cristyf36cbcb2011-09-07 13:28:22 +00003943 pixel_list->skip_list.nodes=(SkipNode *) AcquireQuantumMemory(65537UL,
3944 sizeof(*pixel_list->skip_list.nodes));
3945 if (pixel_list->skip_list.nodes == (SkipNode *) NULL)
3946 return(DestroyPixelList(pixel_list));
3947 (void) ResetMagickMemory(pixel_list->skip_list.nodes,0,65537UL*
3948 sizeof(*pixel_list->skip_list.nodes));
cristy733678d2011-03-18 21:29:28 +00003949 pixel_list->signature=MagickSignature;
3950 return(pixel_list);
3951}
3952
cristy6fc86bb2011-03-18 23:45:16 +00003953static PixelList **AcquirePixelListThreadSet(const size_t width,
3954 const size_t height)
cristy733678d2011-03-18 21:29:28 +00003955{
3956 PixelList
3957 **pixel_list;
3958
3959 register ssize_t
3960 i;
3961
3962 size_t
3963 number_threads;
3964
3965 number_threads=GetOpenMPMaximumThreads();
3966 pixel_list=(PixelList **) AcquireQuantumMemory(number_threads,
3967 sizeof(*pixel_list));
3968 if (pixel_list == (PixelList **) NULL)
3969 return((PixelList **) NULL);
3970 (void) ResetMagickMemory(pixel_list,0,number_threads*sizeof(*pixel_list));
3971 for (i=0; i < (ssize_t) number_threads; i++)
3972 {
cristy6fc86bb2011-03-18 23:45:16 +00003973 pixel_list[i]=AcquirePixelList(width,height);
cristy733678d2011-03-18 21:29:28 +00003974 if (pixel_list[i] == (PixelList *) NULL)
3975 return(DestroyPixelListThreadSet(pixel_list));
3976 }
3977 return(pixel_list);
3978}
3979
cristyf36cbcb2011-09-07 13:28:22 +00003980static void AddNodePixelList(PixelList *pixel_list,const size_t color)
cristy733678d2011-03-18 21:29:28 +00003981{
3982 register SkipList
cristyf36cbcb2011-09-07 13:28:22 +00003983 *p;
cristy733678d2011-03-18 21:29:28 +00003984
3985 register ssize_t
3986 level;
3987
3988 size_t
3989 search,
3990 update[9];
3991
3992 /*
3993 Initialize the node.
3994 */
cristyf36cbcb2011-09-07 13:28:22 +00003995 p=(&pixel_list->skip_list);
3996 p->nodes[color].signature=pixel_list->signature;
3997 p->nodes[color].count=1;
cristy733678d2011-03-18 21:29:28 +00003998 /*
3999 Determine where it belongs in the list.
4000 */
4001 search=65536UL;
cristyf36cbcb2011-09-07 13:28:22 +00004002 for (level=p->level; level >= 0; level--)
cristy733678d2011-03-18 21:29:28 +00004003 {
cristyf36cbcb2011-09-07 13:28:22 +00004004 while (p->nodes[search].next[level] < color)
4005 search=p->nodes[search].next[level];
cristy733678d2011-03-18 21:29:28 +00004006 update[level]=search;
4007 }
4008 /*
4009 Generate a pseudo-random level for this node.
4010 */
4011 for (level=0; ; level++)
4012 {
4013 pixel_list->seed=(pixel_list->seed*42893621L)+1L;
4014 if ((pixel_list->seed & 0x300) != 0x300)
4015 break;
4016 }
4017 if (level > 8)
4018 level=8;
cristyf36cbcb2011-09-07 13:28:22 +00004019 if (level > (p->level+2))
4020 level=p->level+2;
cristy733678d2011-03-18 21:29:28 +00004021 /*
4022 If we're raising the list's level, link back to the root node.
4023 */
cristyf36cbcb2011-09-07 13:28:22 +00004024 while (level > p->level)
cristy733678d2011-03-18 21:29:28 +00004025 {
cristyf36cbcb2011-09-07 13:28:22 +00004026 p->level++;
4027 update[p->level]=65536UL;
cristy733678d2011-03-18 21:29:28 +00004028 }
4029 /*
4030 Link the node into the skip-list.
4031 */
4032 do
4033 {
cristyf36cbcb2011-09-07 13:28:22 +00004034 p->nodes[color].next[level]=p->nodes[update[level]].next[level];
4035 p->nodes[update[level]].next[level]=color;
cristy3cba8ca2011-03-19 01:29:12 +00004036 } while (level-- > 0);
cristy733678d2011-03-18 21:29:28 +00004037}
4038
cristy075ff302011-09-07 01:51:24 +00004039static Quantum GetMaximumPixelList(PixelList *pixel_list)
cristy6fc86bb2011-03-18 23:45:16 +00004040{
cristy6fc86bb2011-03-18 23:45:16 +00004041 register SkipList
cristyf36cbcb2011-09-07 13:28:22 +00004042 *p;
cristy6fc86bb2011-03-18 23:45:16 +00004043
4044 size_t
cristyd76c51e2011-03-26 00:21:26 +00004045 color,
4046 maximum;
cristy49f37242011-03-22 18:18:23 +00004047
4048 ssize_t
cristy6fc86bb2011-03-18 23:45:16 +00004049 count;
4050
cristy6fc86bb2011-03-18 23:45:16 +00004051 /*
4052 Find the maximum value for each of the color.
4053 */
cristyf36cbcb2011-09-07 13:28:22 +00004054 p=(&pixel_list->skip_list);
4055 color=65536L;
4056 count=0;
4057 maximum=p->nodes[color].next[0];
4058 do
cristy6fc86bb2011-03-18 23:45:16 +00004059 {
cristyf36cbcb2011-09-07 13:28:22 +00004060 color=p->nodes[color].next[0];
4061 if (color > maximum)
4062 maximum=color;
4063 count+=p->nodes[color].count;
4064 } while (count < (ssize_t) pixel_list->length);
4065 return(ScaleShortToQuantum((unsigned short) maximum));
cristy49f37242011-03-22 18:18:23 +00004066}
4067
cristy075ff302011-09-07 01:51:24 +00004068static Quantum GetMeanPixelList(PixelList *pixel_list)
cristy49f37242011-03-22 18:18:23 +00004069{
cristy80a99a32011-03-30 01:30:23 +00004070 MagickRealType
4071 sum;
4072
cristy49f37242011-03-22 18:18:23 +00004073 register SkipList
cristyf36cbcb2011-09-07 13:28:22 +00004074 *p;
cristy49f37242011-03-22 18:18:23 +00004075
4076 size_t
cristy80a99a32011-03-30 01:30:23 +00004077 color;
cristy49f37242011-03-22 18:18:23 +00004078
4079 ssize_t
4080 count;
4081
cristy49f37242011-03-22 18:18:23 +00004082 /*
4083 Find the mean value for each of the color.
4084 */
cristyf36cbcb2011-09-07 13:28:22 +00004085 p=(&pixel_list->skip_list);
4086 color=65536L;
4087 count=0;
4088 sum=0.0;
4089 do
cristy49f37242011-03-22 18:18:23 +00004090 {
cristyf36cbcb2011-09-07 13:28:22 +00004091 color=p->nodes[color].next[0];
4092 sum+=(MagickRealType) p->nodes[color].count*color;
4093 count+=p->nodes[color].count;
4094 } while (count < (ssize_t) pixel_list->length);
4095 sum/=pixel_list->length;
4096 return(ScaleShortToQuantum((unsigned short) sum));
cristy6fc86bb2011-03-18 23:45:16 +00004097}
4098
cristy075ff302011-09-07 01:51:24 +00004099static Quantum GetMedianPixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004100{
cristy733678d2011-03-18 21:29:28 +00004101 register SkipList
cristyf36cbcb2011-09-07 13:28:22 +00004102 *p;
cristy733678d2011-03-18 21:29:28 +00004103
4104 size_t
cristy49f37242011-03-22 18:18:23 +00004105 color;
4106
4107 ssize_t
cristy733678d2011-03-18 21:29:28 +00004108 count;
4109
cristy733678d2011-03-18 21:29:28 +00004110 /*
4111 Find the median value for each of the color.
4112 */
cristyf36cbcb2011-09-07 13:28:22 +00004113 p=(&pixel_list->skip_list);
4114 color=65536L;
4115 count=0;
4116 do
cristy733678d2011-03-18 21:29:28 +00004117 {
cristyf36cbcb2011-09-07 13:28:22 +00004118 color=p->nodes[color].next[0];
4119 count+=p->nodes[color].count;
4120 } while (count <= (ssize_t) (pixel_list->length >> 1));
4121 return(ScaleShortToQuantum((unsigned short) color));
cristy6fc86bb2011-03-18 23:45:16 +00004122}
4123
cristy075ff302011-09-07 01:51:24 +00004124static Quantum GetMinimumPixelList(PixelList *pixel_list)
cristy6fc86bb2011-03-18 23:45:16 +00004125{
cristy6fc86bb2011-03-18 23:45:16 +00004126 register SkipList
cristyf36cbcb2011-09-07 13:28:22 +00004127 *p;
cristy6fc86bb2011-03-18 23:45:16 +00004128
4129 size_t
cristyd76c51e2011-03-26 00:21:26 +00004130 color,
4131 minimum;
cristy6fc86bb2011-03-18 23:45:16 +00004132
cristy49f37242011-03-22 18:18:23 +00004133 ssize_t
4134 count;
4135
cristy6fc86bb2011-03-18 23:45:16 +00004136 /*
4137 Find the minimum value for each of the color.
4138 */
cristyf36cbcb2011-09-07 13:28:22 +00004139 p=(&pixel_list->skip_list);
4140 count=0;
4141 color=65536UL;
4142 minimum=p->nodes[color].next[0];
4143 do
cristy6fc86bb2011-03-18 23:45:16 +00004144 {
cristyf36cbcb2011-09-07 13:28:22 +00004145 color=p->nodes[color].next[0];
4146 if (color < minimum)
4147 minimum=color;
4148 count+=p->nodes[color].count;
4149 } while (count < (ssize_t) pixel_list->length);
4150 return(ScaleShortToQuantum((unsigned short) minimum));
cristy733678d2011-03-18 21:29:28 +00004151}
4152
cristy075ff302011-09-07 01:51:24 +00004153static Quantum GetModePixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004154{
cristy733678d2011-03-18 21:29:28 +00004155 register SkipList
cristyf36cbcb2011-09-07 13:28:22 +00004156 *p;
cristy733678d2011-03-18 21:29:28 +00004157
4158 size_t
4159 color,
cristy733678d2011-03-18 21:29:28 +00004160 max_count,
cristy6fc86bb2011-03-18 23:45:16 +00004161 mode;
cristy733678d2011-03-18 21:29:28 +00004162
cristy49f37242011-03-22 18:18:23 +00004163 ssize_t
4164 count;
4165
cristy733678d2011-03-18 21:29:28 +00004166 /*
glennrp30d2dc62011-06-25 03:17:16 +00004167 Make each pixel the 'predominant color' of the specified neighborhood.
cristy733678d2011-03-18 21:29:28 +00004168 */
cristyf36cbcb2011-09-07 13:28:22 +00004169 p=(&pixel_list->skip_list);
4170 color=65536L;
4171 mode=color;
4172 max_count=p->nodes[mode].count;
4173 count=0;
4174 do
cristy733678d2011-03-18 21:29:28 +00004175 {
cristyf36cbcb2011-09-07 13:28:22 +00004176 color=p->nodes[color].next[0];
4177 if (p->nodes[color].count > max_count)
4178 {
4179 mode=color;
4180 max_count=p->nodes[mode].count;
4181 }
4182 count+=p->nodes[color].count;
4183 } while (count < (ssize_t) pixel_list->length);
4184 return(ScaleShortToQuantum((unsigned short) mode));
cristy733678d2011-03-18 21:29:28 +00004185}
4186
cristy075ff302011-09-07 01:51:24 +00004187static Quantum GetNonpeakPixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004188{
cristy733678d2011-03-18 21:29:28 +00004189 register SkipList
cristyf36cbcb2011-09-07 13:28:22 +00004190 *p;
cristy733678d2011-03-18 21:29:28 +00004191
4192 size_t
cristy733678d2011-03-18 21:29:28 +00004193 color,
cristy733678d2011-03-18 21:29:28 +00004194 next,
4195 previous;
4196
cristy49f37242011-03-22 18:18:23 +00004197 ssize_t
4198 count;
4199
cristy733678d2011-03-18 21:29:28 +00004200 /*
cristy49f37242011-03-22 18:18:23 +00004201 Finds the non peak value for each of the colors.
cristy733678d2011-03-18 21:29:28 +00004202 */
cristyf36cbcb2011-09-07 13:28:22 +00004203 p=(&pixel_list->skip_list);
4204 color=65536L;
4205 next=p->nodes[color].next[0];
4206 count=0;
4207 do
cristy733678d2011-03-18 21:29:28 +00004208 {
cristyf36cbcb2011-09-07 13:28:22 +00004209 previous=color;
4210 color=next;
4211 next=p->nodes[color].next[0];
4212 count+=p->nodes[color].count;
4213 } while (count <= (ssize_t) (pixel_list->length >> 1));
4214 if ((previous == 65536UL) && (next != 65536UL))
4215 color=next;
4216 else
4217 if ((previous != 65536UL) && (next == 65536UL))
4218 color=previous;
4219 return(ScaleShortToQuantum((unsigned short) color));
cristy733678d2011-03-18 21:29:28 +00004220}
4221
cristy075ff302011-09-07 01:51:24 +00004222static Quantum GetStandardDeviationPixelList(PixelList *pixel_list)
cristy9a68cbb2011-03-29 00:51:23 +00004223{
cristy80a99a32011-03-30 01:30:23 +00004224 MagickRealType
4225 sum,
4226 sum_squared;
4227
cristy9a68cbb2011-03-29 00:51:23 +00004228 register SkipList
cristyf36cbcb2011-09-07 13:28:22 +00004229 *p;
cristy9a68cbb2011-03-29 00:51:23 +00004230
4231 size_t
cristy80a99a32011-03-30 01:30:23 +00004232 color;
cristy9a68cbb2011-03-29 00:51:23 +00004233
4234 ssize_t
4235 count;
4236
cristy9a68cbb2011-03-29 00:51:23 +00004237 /*
cristy80a99a32011-03-30 01:30:23 +00004238 Find the standard-deviation value for each of the color.
cristy9a68cbb2011-03-29 00:51:23 +00004239 */
cristyf36cbcb2011-09-07 13:28:22 +00004240 p=(&pixel_list->skip_list);
4241 color=65536L;
4242 count=0;
4243 sum=0.0;
4244 sum_squared=0.0;
4245 do
cristy9a68cbb2011-03-29 00:51:23 +00004246 {
cristyf36cbcb2011-09-07 13:28:22 +00004247 register ssize_t
4248 i;
cristy80a99a32011-03-30 01:30:23 +00004249
cristyf36cbcb2011-09-07 13:28:22 +00004250 color=p->nodes[color].next[0];
4251 sum+=(MagickRealType) p->nodes[color].count*color;
4252 for (i=0; i < (ssize_t) p->nodes[color].count; i++)
4253 sum_squared+=((MagickRealType) color)*((MagickRealType) color);
4254 count+=p->nodes[color].count;
4255 } while (count < (ssize_t) pixel_list->length);
4256 sum/=pixel_list->length;
4257 sum_squared/=pixel_list->length;
4258 return(ScaleShortToQuantum((unsigned short) sqrt(sum_squared-(sum*sum))));
cristy9a68cbb2011-03-29 00:51:23 +00004259}
4260
cristyf36cbcb2011-09-07 13:28:22 +00004261static inline void InsertPixelList(const Image *image,const Quantum pixel,
cristy4c08aed2011-07-01 19:47:50 +00004262 PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004263{
4264 size_t
4265 signature;
4266
4267 unsigned short
4268 index;
4269
cristyf36cbcb2011-09-07 13:28:22 +00004270 index=ScaleQuantumToShort(pixel);
4271 signature=pixel_list->skip_list.nodes[index].signature;
cristy733678d2011-03-18 21:29:28 +00004272 if (signature == pixel_list->signature)
cristyf36cbcb2011-09-07 13:28:22 +00004273 {
4274 pixel_list->skip_list.nodes[index].count++;
4275 return;
4276 }
4277 AddNodePixelList(pixel_list,index);
cristy733678d2011-03-18 21:29:28 +00004278}
4279
cristy80c99742011-04-04 14:46:39 +00004280static inline MagickRealType MagickAbsoluteValue(const MagickRealType x)
4281{
4282 if (x < 0)
4283 return(-x);
4284 return(x);
4285}
4286
cristyf36cbcb2011-09-07 13:28:22 +00004287static inline size_t MagickMax(const size_t x,const size_t y)
4288{
4289 if (x > y)
4290 return(x);
4291 return(y);
4292}
4293
cristy733678d2011-03-18 21:29:28 +00004294static void ResetPixelList(PixelList *pixel_list)
4295{
4296 int
4297 level;
4298
cristyf36cbcb2011-09-07 13:28:22 +00004299 register SkipNode
cristy733678d2011-03-18 21:29:28 +00004300 *root;
4301
4302 register SkipList
cristyf36cbcb2011-09-07 13:28:22 +00004303 *p;
cristy733678d2011-03-18 21:29:28 +00004304
4305 /*
4306 Reset the skip-list.
4307 */
cristyf36cbcb2011-09-07 13:28:22 +00004308 p=(&pixel_list->skip_list);
4309 root=p->nodes+65536UL;
4310 p->level=0;
4311 for (level=0; level < 9; level++)
4312 root->next[level]=65536UL;
cristy733678d2011-03-18 21:29:28 +00004313 pixel_list->seed=pixel_list->signature++;
4314}
4315
cristy0834d642011-03-18 18:26:08 +00004316MagickExport Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00004317 const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00004318{
cristy0834d642011-03-18 18:26:08 +00004319#define StatisticImageTag "Statistic/Image"
4320
4321 CacheView
4322 *image_view,
4323 *statistic_view;
4324
4325 Image
4326 *statistic_image;
4327
4328 MagickBooleanType
4329 status;
4330
4331 MagickOffsetType
4332 progress;
4333
4334 PixelList
4335 **restrict pixel_list;
4336
cristy0834d642011-03-18 18:26:08 +00004337 ssize_t
cristy075ff302011-09-07 01:51:24 +00004338 center,
cristy0834d642011-03-18 18:26:08 +00004339 y;
4340
4341 /*
4342 Initialize statistics image attributes.
4343 */
4344 assert(image != (Image *) NULL);
4345 assert(image->signature == MagickSignature);
4346 if (image->debug != MagickFalse)
4347 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4348 assert(exception != (ExceptionInfo *) NULL);
4349 assert(exception->signature == MagickSignature);
cristy0834d642011-03-18 18:26:08 +00004350 statistic_image=CloneImage(image,image->columns,image->rows,MagickTrue,
4351 exception);
4352 if (statistic_image == (Image *) NULL)
4353 return((Image *) NULL);
cristyf36cbcb2011-09-07 13:28:22 +00004354 status=SetImageStorageClass(statistic_image,DirectClass,exception);
4355 if (status == MagickFalse)
cristy0834d642011-03-18 18:26:08 +00004356 {
cristy0834d642011-03-18 18:26:08 +00004357 statistic_image=DestroyImage(statistic_image);
4358 return((Image *) NULL);
4359 }
cristyf36cbcb2011-09-07 13:28:22 +00004360 pixel_list=AcquirePixelListThreadSet(MagickMax(width,1),MagickMax(height,1));
cristy0834d642011-03-18 18:26:08 +00004361 if (pixel_list == (PixelList **) NULL)
4362 {
4363 statistic_image=DestroyImage(statistic_image);
4364 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
4365 }
4366 /*
cristy8d752042011-03-19 01:00:36 +00004367 Make each pixel the min / max / median / mode / etc. of the neighborhood.
cristy0834d642011-03-18 18:26:08 +00004368 */
cristyf36cbcb2011-09-07 13:28:22 +00004369 center=(ssize_t) GetPixelChannels(image)*(image->columns+MagickMax(width,1))*
4370 (MagickMax(height,1)/2L)+GetPixelChannels(image)*(MagickMax(width,1)/2L);
cristy0834d642011-03-18 18:26:08 +00004371 status=MagickTrue;
4372 progress=0;
4373 image_view=AcquireCacheView(image);
4374 statistic_view=AcquireCacheView(statistic_image);
4375#if defined(MAGICKCORE_OPENMP_SUPPORT)
4376 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
4377#endif
4378 for (y=0; y < (ssize_t) statistic_image->rows; y++)
4379 {
4380 const int
4381 id = GetOpenMPThreadId();
4382
cristy4c08aed2011-07-01 19:47:50 +00004383 register const Quantum
cristy0834d642011-03-18 18:26:08 +00004384 *restrict p;
4385
cristy4c08aed2011-07-01 19:47:50 +00004386 register Quantum
cristy0834d642011-03-18 18:26:08 +00004387 *restrict q;
4388
4389 register ssize_t
4390 x;
4391
4392 if (status == MagickFalse)
4393 continue;
cristyf36cbcb2011-09-07 13:28:22 +00004394 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) MagickMax(width,1)/2L),y-
4395 (ssize_t) (MagickMax(height,1)/2L),image->columns+MagickMax(width,1),
4396 MagickMax(height,1),exception);
cristy3cba8ca2011-03-19 01:29:12 +00004397 q=QueueCacheViewAuthenticPixels(statistic_view,0,y,statistic_image->columns, 1,exception);
cristy4c08aed2011-07-01 19:47:50 +00004398 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy0834d642011-03-18 18:26:08 +00004399 {
4400 status=MagickFalse;
4401 continue;
4402 }
cristy0834d642011-03-18 18:26:08 +00004403 for (x=0; x < (ssize_t) statistic_image->columns; x++)
4404 {
cristy0834d642011-03-18 18:26:08 +00004405 register ssize_t
cristy075ff302011-09-07 01:51:24 +00004406 i;
cristy0834d642011-03-18 18:26:08 +00004407
cristy075ff302011-09-07 01:51:24 +00004408 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy0834d642011-03-18 18:26:08 +00004409 {
cristy075ff302011-09-07 01:51:24 +00004410 PixelChannel
4411 channel;
4412
4413 PixelTrait
4414 statistic_traits,
4415 traits;
4416
cristyf36cbcb2011-09-07 13:28:22 +00004417 Quantum
4418 pixel;
4419
cristy075ff302011-09-07 01:51:24 +00004420 register const Quantum
4421 *restrict pixels;
4422
4423 register ssize_t
4424 u;
4425
4426 ssize_t
4427 v;
4428
4429 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
4430 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
4431 statistic_traits=GetPixelChannelMapTraits(statistic_image,channel);
4432 if ((traits == UndefinedPixelTrait) ||
4433 (statistic_traits == UndefinedPixelTrait))
4434 continue;
4435 if ((statistic_traits & CopyPixelTrait) != 0)
4436 {
4437 q[channel]=p[center+i];
4438 continue;
4439 }
4440 pixels=p;
4441 ResetPixelList(pixel_list[id]);
cristyf36cbcb2011-09-07 13:28:22 +00004442 for (v=0; v < (ssize_t) MagickMax(height,1); v++)
cristy6fc86bb2011-03-18 23:45:16 +00004443 {
cristyf36cbcb2011-09-07 13:28:22 +00004444 for (u=0; u < (ssize_t) MagickMax(width,1); u++)
cristy075ff302011-09-07 01:51:24 +00004445 {
cristyf36cbcb2011-09-07 13:28:22 +00004446 InsertPixelList(image,pixels[i],pixel_list[id]);
cristy075ff302011-09-07 01:51:24 +00004447 pixels+=GetPixelChannels(image);
4448 }
4449 pixels+=image->columns*GetPixelChannels(image);
cristy6fc86bb2011-03-18 23:45:16 +00004450 }
cristy075ff302011-09-07 01:51:24 +00004451 switch (type)
cristy49f37242011-03-22 18:18:23 +00004452 {
cristy075ff302011-09-07 01:51:24 +00004453 case GradientStatistic:
4454 {
4455 MagickRealType
4456 maximum,
4457 minimum;
4458
cristyf36cbcb2011-09-07 13:28:22 +00004459 minimum=(MagickRealType) GetMinimumPixelList(pixel_list[id]);
4460 maximum=(MagickRealType) GetMaximumPixelList(pixel_list[id]);
4461 pixel=ClampToQuantum(MagickAbsoluteValue(maximum-minimum));
cristy075ff302011-09-07 01:51:24 +00004462 break;
4463 }
4464 case MaximumStatistic:
4465 {
4466 pixel=GetMaximumPixelList(pixel_list[id]);
4467 break;
4468 }
4469 case MeanStatistic:
4470 {
4471 pixel=GetMeanPixelList(pixel_list[id]);
4472 break;
4473 }
4474 case MedianStatistic:
4475 default:
4476 {
4477 pixel=GetMedianPixelList(pixel_list[id]);
4478 break;
4479 }
4480 case MinimumStatistic:
4481 {
4482 pixel=GetMinimumPixelList(pixel_list[id]);
4483 break;
4484 }
4485 case ModeStatistic:
4486 {
4487 pixel=GetModePixelList(pixel_list[id]);
4488 break;
4489 }
4490 case NonpeakStatistic:
4491 {
4492 pixel=GetNonpeakPixelList(pixel_list[id]);
4493 break;
4494 }
4495 case StandardDeviationStatistic:
4496 {
4497 pixel=GetStandardDeviationPixelList(pixel_list[id]);
4498 break;
4499 }
cristy49f37242011-03-22 18:18:23 +00004500 }
cristyf36cbcb2011-09-07 13:28:22 +00004501 q[channel]=pixel;
cristy0834d642011-03-18 18:26:08 +00004502 }
cristyed231572011-07-14 02:18:59 +00004503 p+=GetPixelChannels(image);
4504 q+=GetPixelChannels(statistic_image);
cristy0834d642011-03-18 18:26:08 +00004505 }
4506 if (SyncCacheViewAuthenticPixels(statistic_view,exception) == MagickFalse)
4507 status=MagickFalse;
4508 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4509 {
4510 MagickBooleanType
4511 proceed;
4512
4513#if defined(MAGICKCORE_OPENMP_SUPPORT)
4514 #pragma omp critical (MagickCore_StatisticImage)
4515#endif
4516 proceed=SetImageProgress(image,StatisticImageTag,progress++,
4517 image->rows);
4518 if (proceed == MagickFalse)
4519 status=MagickFalse;
4520 }
4521 }
4522 statistic_view=DestroyCacheView(statistic_view);
4523 image_view=DestroyCacheView(image_view);
4524 pixel_list=DestroyPixelListThreadSet(pixel_list);
4525 return(statistic_image);
4526}
4527
4528/*
4529%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4530% %
4531% %
4532% %
cristy3ed852e2009-09-05 21:47:34 +00004533% U n s h a r p M a s k I m a g e %
4534% %
4535% %
4536% %
4537%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4538%
4539% UnsharpMaskImage() sharpens one or more image channels. We convolve the
4540% image with a Gaussian operator of the given radius and standard deviation
4541% (sigma). For reasonable results, radius should be larger than sigma. Use a
4542% radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
4543%
4544% The format of the UnsharpMaskImage method is:
4545%
4546% Image *UnsharpMaskImage(const Image *image,const double radius,
4547% const double sigma,const double amount,const double threshold,
4548% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00004549%
4550% A description of each parameter follows:
4551%
4552% o image: the image.
4553%
cristy3ed852e2009-09-05 21:47:34 +00004554% o radius: the radius of the Gaussian, in pixels, not counting the center
4555% pixel.
4556%
4557% o sigma: the standard deviation of the Gaussian, in pixels.
4558%
4559% o amount: the percentage of the difference between the original and the
4560% blur image that is added back into the original.
4561%
4562% o threshold: the threshold in pixels needed to apply the diffence amount.
4563%
4564% o exception: return any errors or warnings in this structure.
4565%
4566*/
cristyf4ad9df2011-07-08 16:49:03 +00004567MagickExport Image *UnsharpMaskImage(const Image *image,
4568 const double radius,const double sigma,const double amount,
4569 const double threshold,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00004570{
4571#define SharpenImageTag "Sharpen/Image"
4572
cristyc4c8d132010-01-07 01:58:38 +00004573 CacheView
4574 *image_view,
4575 *unsharp_view;
4576
cristy3ed852e2009-09-05 21:47:34 +00004577 Image
4578 *unsharp_image;
4579
cristy3ed852e2009-09-05 21:47:34 +00004580 MagickBooleanType
4581 status;
4582
cristybb503372010-05-27 20:51:26 +00004583 MagickOffsetType
4584 progress;
4585
cristy3ed852e2009-09-05 21:47:34 +00004586 MagickRealType
4587 quantum_threshold;
4588
cristybb503372010-05-27 20:51:26 +00004589 ssize_t
4590 y;
4591
cristy3ed852e2009-09-05 21:47:34 +00004592 assert(image != (const Image *) NULL);
4593 assert(image->signature == MagickSignature);
4594 if (image->debug != MagickFalse)
4595 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4596 assert(exception != (ExceptionInfo *) NULL);
cristy05c0c9a2011-09-05 23:16:13 +00004597 unsharp_image=BlurImage(image,radius,sigma,image->bias,exception);
cristy3ed852e2009-09-05 21:47:34 +00004598 if (unsharp_image == (Image *) NULL)
4599 return((Image *) NULL);
4600 quantum_threshold=(MagickRealType) QuantumRange*threshold;
4601 /*
4602 Unsharp-mask image.
4603 */
4604 status=MagickTrue;
4605 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00004606 image_view=AcquireCacheView(image);
4607 unsharp_view=AcquireCacheView(unsharp_image);
cristyb5d5f722009-11-04 03:03:49 +00004608#if defined(MAGICKCORE_OPENMP_SUPPORT)
4609 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004610#endif
cristybb503372010-05-27 20:51:26 +00004611 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00004612 {
cristy4c08aed2011-07-01 19:47:50 +00004613 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00004614 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00004615
cristy4c08aed2011-07-01 19:47:50 +00004616 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00004617 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004618
cristy117ff172010-08-15 21:35:32 +00004619 register ssize_t
4620 x;
4621
cristy3ed852e2009-09-05 21:47:34 +00004622 if (status == MagickFalse)
4623 continue;
4624 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
4625 q=GetCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
4626 exception);
cristy4c08aed2011-07-01 19:47:50 +00004627 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00004628 {
4629 status=MagickFalse;
4630 continue;
4631 }
cristybb503372010-05-27 20:51:26 +00004632 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004633 {
cristy7f3a0d12011-09-05 23:27:59 +00004634 register ssize_t
4635 i;
4636
4637 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4638 {
4639 MagickRealType
4640 pixel;
4641
4642 PixelChannel
4643 channel;
4644
4645 PixelTrait
4646 traits,
4647 unsharp_traits;
4648
4649 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
4650 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
4651 unsharp_traits=GetPixelChannelMapTraits(unsharp_image,channel);
4652 if ((traits == UndefinedPixelTrait) ||
4653 (unsharp_traits == UndefinedPixelTrait))
4654 continue;
4655 if ((unsharp_traits & CopyPixelTrait) != 0)
4656 {
4657 q[channel]=p[i];
4658 continue;
4659 }
4660 pixel=p[i]-(MagickRealType) q[channel];
4661 if (fabs(2.0*pixel) < quantum_threshold)
4662 pixel=(MagickRealType) p[i];
4663 else
4664 pixel=(MagickRealType) p[i]+amount*pixel;
4665 q[channel]=ClampToQuantum(pixel);
4666 }
cristyed231572011-07-14 02:18:59 +00004667 p+=GetPixelChannels(image);
4668 q+=GetPixelChannels(unsharp_image);
cristy3ed852e2009-09-05 21:47:34 +00004669 }
4670 if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
4671 status=MagickFalse;
4672 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4673 {
4674 MagickBooleanType
4675 proceed;
4676
cristyb5d5f722009-11-04 03:03:49 +00004677#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00004678 #pragma omp critical (MagickCore_UnsharpMaskImage)
cristy3ed852e2009-09-05 21:47:34 +00004679#endif
4680 proceed=SetImageProgress(image,SharpenImageTag,progress++,image->rows);
4681 if (proceed == MagickFalse)
4682 status=MagickFalse;
4683 }
4684 }
4685 unsharp_image->type=image->type;
4686 unsharp_view=DestroyCacheView(unsharp_view);
4687 image_view=DestroyCacheView(image_view);
4688 if (status == MagickFalse)
4689 unsharp_image=DestroyImage(unsharp_image);
4690 return(unsharp_image);
4691}