blob: 161ef7158718cdd11046b12365b2b339bc3e0512 [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)*
347 ((width-j)/2L)+GetPixelChannels(image)*((width-j)/2);
348 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
cristy05c0c9a2011-09-05 23:16:13 +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);
895 blur_image=CloneImage(image,0,0,MagickTrue,exception);
896 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;
cristy05c0c9a2011-09-05 23:16:13 +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
cristybb503372010-05-27 20:51:26 +0000947 for (y=0; y < (ssize_t) blur_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 }
cristy05c0c9a2011-09-05 23:16:13 +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
cristy05c0c9a2011-09-05 23:16:13 +0000974 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
975 {
976 MagickRealType
977 alpha,
978 gamma,
979 pixel;
cristy3ed852e2009-09-05 21:47:34 +0000980
cristy05c0c9a2011-09-05 23:16:13 +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)
cristy3ed852e2009-09-05 21:47:34 +00001004 {
cristy05c0c9a2011-09-05 23:16:13 +00001005 q[channel]=p[center+i];
1006 continue;
cristy3ed852e2009-09-05 21:47:34 +00001007 }
cristy05c0c9a2011-09-05 23:16:13 +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++)
cristy3ed852e2009-09-05 21:47:34 +00001017 {
cristy05c0c9a2011-09-05 23:16:13 +00001018 pixel+=(*k)*pixels[i];
1019 k++;
1020 pixels+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001021 }
cristy05c0c9a2011-09-05 23:16:13 +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);
cristy3ed852e2009-09-05 21:47:34 +00001036 }
cristy05c0c9a2011-09-05 23:16:13 +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
cristybb503372010-05-27 20:51:26 +00001069 for (x=0; x < (ssize_t) blur_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 }
cristy05c0c9a2011-09-05 23:16:13 +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
cristy05c0c9a2011-09-05 23:16:13 +00001095 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1096 {
1097 MagickRealType
1098 alpha,
1099 gamma,
1100 pixel;
cristy3ed852e2009-09-05 21:47:34 +00001101
cristy05c0c9a2011-09-05 23:16:13 +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(image,(PixelChannel) i);
1119 channel=GetPixelChannelMapChannel(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)
cristy3ed852e2009-09-05 21:47:34 +00001125 {
cristy05c0c9a2011-09-05 23:16:13 +00001126 q[channel]=p[center+i];
1127 continue;
cristy3ed852e2009-09-05 21:47:34 +00001128 }
cristy05c0c9a2011-09-05 23:16:13 +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++)
cristy3ed852e2009-09-05 21:47:34 +00001138 {
cristy05c0c9a2011-09-05 23:16:13 +00001139 pixel+=(*k)*pixels[i];
1140 k++;
1141 pixels+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001142 }
cristy05c0c9a2011-09-05 23:16:13 +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*GetPixelAlpha(image,pixels));
1153 pixel+=(*k)*alpha*pixels[i];
1154 gamma+=(*k)*alpha;
1155 k++;
1156 pixels+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001157 }
cristy05c0c9a2011-09-05 23:16:13 +00001158 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1159 q[channel]=ClampToQuantum(gamma*pixel);
1160 }
1161 p+=GetPixelChannels(image);
cristyed231572011-07-14 02:18:59 +00001162 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001163 }
1164 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1165 status=MagickFalse;
cristy4c08aed2011-07-01 19:47:50 +00001166 if (blur_image->progress_monitor != (MagickProgressMonitor) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001167 {
1168 MagickBooleanType
1169 proceed;
1170
cristyb5d5f722009-11-04 03:03:49 +00001171#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00001172 #pragma omp critical (MagickCore_BlurImage)
cristy3ed852e2009-09-05 21:47:34 +00001173#endif
cristy4c08aed2011-07-01 19:47:50 +00001174 proceed=SetImageProgress(blur_image,BlurImageTag,progress++,
1175 blur_image->rows+blur_image->columns);
cristy3ed852e2009-09-05 21:47:34 +00001176 if (proceed == MagickFalse)
1177 status=MagickFalse;
1178 }
1179 }
1180 blur_view=DestroyCacheView(blur_view);
1181 image_view=DestroyCacheView(image_view);
1182 kernel=(double *) RelinquishMagickMemory(kernel);
1183 if (status == MagickFalse)
1184 blur_image=DestroyImage(blur_image);
1185 blur_image->type=image->type;
1186 return(blur_image);
1187}
1188
1189/*
1190%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1191% %
1192% %
1193% %
cristyfccdab92009-11-30 16:43:57 +00001194% C o n v o l v e I m a g e %
1195% %
1196% %
1197% %
1198%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1199%
1200% ConvolveImage() applies a custom convolution kernel to the image.
1201%
1202% The format of the ConvolveImage method is:
1203%
cristy5e6be1e2011-07-16 01:23:39 +00001204% Image *ConvolveImage(const Image *image,const KernelInfo *kernel,
1205% ExceptionInfo *exception)
1206%
cristyfccdab92009-11-30 16:43:57 +00001207% A description of each parameter follows:
1208%
1209% o image: the image.
1210%
cristy5e6be1e2011-07-16 01:23:39 +00001211% o kernel: the filtering kernel.
cristyfccdab92009-11-30 16:43:57 +00001212%
1213% o exception: return any errors or warnings in this structure.
1214%
1215*/
cristy5e6be1e2011-07-16 01:23:39 +00001216MagickExport Image *ConvolveImage(const Image *image,
1217 const KernelInfo *kernel_info,ExceptionInfo *exception)
cristyfccdab92009-11-30 16:43:57 +00001218{
cristyfccdab92009-11-30 16:43:57 +00001219#define ConvolveImageTag "Convolve/Image"
1220
cristyc4c8d132010-01-07 01:58:38 +00001221 CacheView
1222 *convolve_view,
cristy105ba3c2011-07-18 02:28:38 +00001223 *image_view;
cristyc4c8d132010-01-07 01:58:38 +00001224
cristyfccdab92009-11-30 16:43:57 +00001225 Image
1226 *convolve_image;
1227
cristyfccdab92009-11-30 16:43:57 +00001228 MagickBooleanType
1229 status;
1230
cristybb503372010-05-27 20:51:26 +00001231 MagickOffsetType
1232 progress;
1233
cristybb503372010-05-27 20:51:26 +00001234 ssize_t
cristy574cc262011-08-05 01:23:58 +00001235 center,
cristybb503372010-05-27 20:51:26 +00001236 y;
1237
cristyfccdab92009-11-30 16:43:57 +00001238 /*
1239 Initialize convolve image attributes.
1240 */
1241 assert(image != (Image *) NULL);
1242 assert(image->signature == MagickSignature);
1243 if (image->debug != MagickFalse)
1244 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1245 assert(exception != (ExceptionInfo *) NULL);
1246 assert(exception->signature == MagickSignature);
cristy5e6be1e2011-07-16 01:23:39 +00001247 if ((kernel_info->width % 2) == 0)
cristyfccdab92009-11-30 16:43:57 +00001248 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
cristy08429172011-07-14 17:18:16 +00001249 convolve_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1250 exception);
cristyfccdab92009-11-30 16:43:57 +00001251 if (convolve_image == (Image *) NULL)
1252 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00001253 if (SetImageStorageClass(convolve_image,DirectClass,exception) == MagickFalse)
cristyfccdab92009-11-30 16:43:57 +00001254 {
cristyfccdab92009-11-30 16:43:57 +00001255 convolve_image=DestroyImage(convolve_image);
1256 return((Image *) NULL);
1257 }
1258 if (image->debug != MagickFalse)
1259 {
1260 char
1261 format[MaxTextExtent],
1262 *message;
1263
cristy117ff172010-08-15 21:35:32 +00001264 register const double
1265 *k;
1266
cristy4e154852011-07-14 13:28:53 +00001267 register ssize_t
1268 u;
1269
cristybb503372010-05-27 20:51:26 +00001270 ssize_t
cristyfccdab92009-11-30 16:43:57 +00001271 v;
1272
cristyfccdab92009-11-30 16:43:57 +00001273 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristy5e6be1e2011-07-16 01:23:39 +00001274 " ConvolveImage with %.20gx%.20g kernel:",(double) kernel_info->width,
1275 (double) kernel_info->height);
cristyfccdab92009-11-30 16:43:57 +00001276 message=AcquireString("");
cristy5e6be1e2011-07-16 01:23:39 +00001277 k=kernel_info->values;
1278 for (v=0; v < (ssize_t) kernel_info->width; v++)
cristyfccdab92009-11-30 16:43:57 +00001279 {
1280 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00001281 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristyfccdab92009-11-30 16:43:57 +00001282 (void) ConcatenateString(&message,format);
cristy5e6be1e2011-07-16 01:23:39 +00001283 for (u=0; u < (ssize_t) kernel_info->height; u++)
cristyfccdab92009-11-30 16:43:57 +00001284 {
cristyb51dff52011-05-19 16:55:47 +00001285 (void) FormatLocaleString(format,MaxTextExtent,"%g ",*k++);
cristyfccdab92009-11-30 16:43:57 +00001286 (void) ConcatenateString(&message,format);
1287 }
1288 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
1289 }
1290 message=DestroyString(message);
1291 }
1292 /*
cristyfccdab92009-11-30 16:43:57 +00001293 Convolve image.
1294 */
cristy574cc262011-08-05 01:23:58 +00001295 center=(ssize_t) GetPixelChannels(image)*(image->columns+kernel_info->width)*
1296 (kernel_info->height/2L)+GetPixelChannels(image)*(kernel_info->width/2);
cristyfccdab92009-11-30 16:43:57 +00001297 status=MagickTrue;
1298 progress=0;
cristyfccdab92009-11-30 16:43:57 +00001299 image_view=AcquireCacheView(image);
1300 convolve_view=AcquireCacheView(convolve_image);
1301#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy175653e2011-07-10 23:13:34 +00001302 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristyfccdab92009-11-30 16:43:57 +00001303#endif
cristybb503372010-05-27 20:51:26 +00001304 for (y=0; y < (ssize_t) image->rows; y++)
cristyfccdab92009-11-30 16:43:57 +00001305 {
cristy4c08aed2011-07-01 19:47:50 +00001306 register const Quantum
cristy105ba3c2011-07-18 02:28:38 +00001307 *restrict p;
cristyfccdab92009-11-30 16:43:57 +00001308
cristy4c08aed2011-07-01 19:47:50 +00001309 register Quantum
cristyfccdab92009-11-30 16:43:57 +00001310 *restrict q;
1311
cristy117ff172010-08-15 21:35:32 +00001312 register ssize_t
1313 x;
1314
cristyfccdab92009-11-30 16:43:57 +00001315 if (status == MagickFalse)
1316 continue;
cristy105ba3c2011-07-18 02:28:38 +00001317 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) kernel_info->width/2L),y-
1318 (ssize_t) (kernel_info->height/2L),image->columns+kernel_info->width,
1319 kernel_info->height,exception);
cristy08429172011-07-14 17:18:16 +00001320 q=QueueCacheViewAuthenticPixels(convolve_view,0,y,convolve_image->columns,1,
cristyfccdab92009-11-30 16:43:57 +00001321 exception);
cristy105ba3c2011-07-18 02:28:38 +00001322 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristyfccdab92009-11-30 16:43:57 +00001323 {
1324 status=MagickFalse;
1325 continue;
1326 }
cristybb503372010-05-27 20:51:26 +00001327 for (x=0; x < (ssize_t) image->columns; x++)
cristyfccdab92009-11-30 16:43:57 +00001328 {
cristybb503372010-05-27 20:51:26 +00001329 register ssize_t
cristyed231572011-07-14 02:18:59 +00001330 i;
cristyfccdab92009-11-30 16:43:57 +00001331
cristya30d9ba2011-07-23 21:00:48 +00001332 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristyed231572011-07-14 02:18:59 +00001333 {
cristyed231572011-07-14 02:18:59 +00001334 MagickRealType
cristy4e154852011-07-14 13:28:53 +00001335 alpha,
1336 gamma,
cristyed231572011-07-14 02:18:59 +00001337 pixel;
1338
1339 PixelChannel
1340 channel;
1341
1342 PixelTrait
1343 convolve_traits,
1344 traits;
1345
1346 register const double
1347 *restrict k;
1348
1349 register const Quantum
cristyeb52cde2011-07-17 01:52:52 +00001350 *restrict pixels;
cristyed231572011-07-14 02:18:59 +00001351
1352 register ssize_t
1353 u;
1354
1355 ssize_t
1356 v;
1357
cristy30301712011-07-18 15:06:51 +00001358 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
cristy30301712011-07-18 15:06:51 +00001359 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
cristy4e154852011-07-14 13:28:53 +00001360 convolve_traits=GetPixelChannelMapTraits(convolve_image,channel);
cristy010d7d12011-08-31 01:02:48 +00001361 if ((traits == UndefinedPixelTrait) ||
1362 (convolve_traits == UndefinedPixelTrait))
cristy4e154852011-07-14 13:28:53 +00001363 continue;
1364 if ((convolve_traits & CopyPixelTrait) != 0)
1365 {
cristyf7dc44c2011-07-20 14:41:15 +00001366 q[channel]=p[center+i];
cristy4e154852011-07-14 13:28:53 +00001367 continue;
1368 }
cristy5e6be1e2011-07-16 01:23:39 +00001369 k=kernel_info->values;
cristy105ba3c2011-07-18 02:28:38 +00001370 pixels=p;
cristy0a922382011-07-16 15:30:34 +00001371 pixel=kernel_info->bias;
cristy222b19c2011-08-04 01:35:11 +00001372 if ((convolve_traits & BlendPixelTrait) == 0)
cristyfccdab92009-11-30 16:43:57 +00001373 {
cristyed231572011-07-14 02:18:59 +00001374 /*
cristy4e154852011-07-14 13:28:53 +00001375 No alpha blending.
cristyed231572011-07-14 02:18:59 +00001376 */
cristyeb52cde2011-07-17 01:52:52 +00001377 for (v=0; v < (ssize_t) kernel_info->height; v++)
cristyfccdab92009-11-30 16:43:57 +00001378 {
cristyeb52cde2011-07-17 01:52:52 +00001379 for (u=0; u < (ssize_t) kernel_info->width; u++)
cristy175653e2011-07-10 23:13:34 +00001380 {
cristyeb52cde2011-07-17 01:52:52 +00001381 pixel+=(*k)*pixels[i];
cristyed231572011-07-14 02:18:59 +00001382 k++;
cristya30d9ba2011-07-23 21:00:48 +00001383 pixels+=GetPixelChannels(image);
cristy175653e2011-07-10 23:13:34 +00001384 }
cristya30d9ba2011-07-23 21:00:48 +00001385 pixels+=image->columns*GetPixelChannels(image);
cristy175653e2011-07-10 23:13:34 +00001386 }
cristyf7dc44c2011-07-20 14:41:15 +00001387 q[channel]=ClampToQuantum(pixel);
cristy4e154852011-07-14 13:28:53 +00001388 continue;
cristyed231572011-07-14 02:18:59 +00001389 }
cristy4e154852011-07-14 13:28:53 +00001390 /*
1391 Alpha blending.
1392 */
1393 gamma=0.0;
cristyeb52cde2011-07-17 01:52:52 +00001394 for (v=0; v < (ssize_t) kernel_info->height; v++)
cristy4e154852011-07-14 13:28:53 +00001395 {
cristyeb52cde2011-07-17 01:52:52 +00001396 for (u=0; u < (ssize_t) kernel_info->width; u++)
cristy4e154852011-07-14 13:28:53 +00001397 {
cristyeb52cde2011-07-17 01:52:52 +00001398 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,pixels));
1399 pixel+=(*k)*alpha*pixels[i];
cristy4e154852011-07-14 13:28:53 +00001400 gamma+=(*k)*alpha;
1401 k++;
cristya30d9ba2011-07-23 21:00:48 +00001402 pixels+=GetPixelChannels(image);
cristy4e154852011-07-14 13:28:53 +00001403 }
cristya30d9ba2011-07-23 21:00:48 +00001404 pixels+=image->columns*GetPixelChannels(image);
cristy4e154852011-07-14 13:28:53 +00001405 }
cristy1ce96d02011-07-14 17:57:24 +00001406 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristye7a41c92011-07-20 21:31:01 +00001407 q[channel]=ClampToQuantum(gamma*pixel);
cristyed231572011-07-14 02:18:59 +00001408 }
cristya30d9ba2011-07-23 21:00:48 +00001409 p+=GetPixelChannels(image);
1410 q+=GetPixelChannels(convolve_image);
cristyfccdab92009-11-30 16:43:57 +00001411 }
cristyed231572011-07-14 02:18:59 +00001412 if (SyncCacheViewAuthenticPixels(convolve_view,exception) == MagickFalse)
cristyfccdab92009-11-30 16:43:57 +00001413 status=MagickFalse;
1414 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1415 {
1416 MagickBooleanType
1417 proceed;
1418
1419#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00001420 #pragma omp critical (MagickCore_ConvolveImage)
cristyfccdab92009-11-30 16:43:57 +00001421#endif
1422 proceed=SetImageProgress(image,ConvolveImageTag,progress++,image->rows);
1423 if (proceed == MagickFalse)
1424 status=MagickFalse;
1425 }
1426 }
1427 convolve_image->type=image->type;
1428 convolve_view=DestroyCacheView(convolve_view);
1429 image_view=DestroyCacheView(image_view);
cristyfccdab92009-11-30 16:43:57 +00001430 if (status == MagickFalse)
1431 convolve_image=DestroyImage(convolve_image);
1432 return(convolve_image);
1433}
1434
1435/*
1436%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1437% %
1438% %
1439% %
cristy3ed852e2009-09-05 21:47:34 +00001440% D e s p e c k l e I m a g e %
1441% %
1442% %
1443% %
1444%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1445%
1446% DespeckleImage() reduces the speckle noise in an image while perserving the
1447% edges of the original image.
1448%
1449% The format of the DespeckleImage method is:
1450%
1451% Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1452%
1453% A description of each parameter follows:
1454%
1455% o image: the image.
1456%
1457% o exception: return any errors or warnings in this structure.
1458%
1459*/
1460
cristybb503372010-05-27 20:51:26 +00001461static void Hull(const ssize_t x_offset,const ssize_t y_offset,
1462 const size_t columns,const size_t rows,Quantum *f,Quantum *g,
cristy3ed852e2009-09-05 21:47:34 +00001463 const int polarity)
1464{
cristy3ed852e2009-09-05 21:47:34 +00001465 MagickRealType
1466 v;
1467
cristy3ed852e2009-09-05 21:47:34 +00001468 register Quantum
1469 *p,
1470 *q,
1471 *r,
1472 *s;
1473
cristy117ff172010-08-15 21:35:32 +00001474 register ssize_t
1475 x;
1476
1477 ssize_t
1478 y;
1479
cristy3ed852e2009-09-05 21:47:34 +00001480 assert(f != (Quantum *) NULL);
1481 assert(g != (Quantum *) NULL);
1482 p=f+(columns+2);
1483 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001484 r=p+(y_offset*((ssize_t) columns+2)+x_offset);
1485 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001486 {
1487 p++;
1488 q++;
1489 r++;
1490 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001491 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001492 {
1493 v=(MagickRealType) (*p);
1494 if ((MagickRealType) *r >= (v+(MagickRealType) ScaleCharToQuantum(2)))
1495 v+=ScaleCharToQuantum(1);
1496 *q=(Quantum) v;
1497 p++;
1498 q++;
1499 r++;
1500 }
1501 else
cristybb503372010-05-27 20:51:26 +00001502 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001503 {
1504 v=(MagickRealType) (*p);
1505 if ((MagickRealType) *r <= (v-(MagickRealType) ScaleCharToQuantum(2)))
cristybb503372010-05-27 20:51:26 +00001506 v-=(ssize_t) ScaleCharToQuantum(1);
cristy3ed852e2009-09-05 21:47:34 +00001507 *q=(Quantum) v;
1508 p++;
1509 q++;
1510 r++;
1511 }
1512 p++;
1513 q++;
1514 r++;
1515 }
1516 p=f+(columns+2);
1517 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001518 r=q+(y_offset*((ssize_t) columns+2)+x_offset);
1519 s=q-(y_offset*((ssize_t) columns+2)+x_offset);
1520 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001521 {
1522 p++;
1523 q++;
1524 r++;
1525 s++;
1526 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001527 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001528 {
1529 v=(MagickRealType) (*q);
1530 if (((MagickRealType) *s >=
1531 (v+(MagickRealType) ScaleCharToQuantum(2))) &&
1532 ((MagickRealType) *r > v))
1533 v+=ScaleCharToQuantum(1);
1534 *p=(Quantum) v;
1535 p++;
1536 q++;
1537 r++;
1538 s++;
1539 }
1540 else
cristybb503372010-05-27 20:51:26 +00001541 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001542 {
1543 v=(MagickRealType) (*q);
1544 if (((MagickRealType) *s <=
1545 (v-(MagickRealType) ScaleCharToQuantum(2))) &&
1546 ((MagickRealType) *r < v))
1547 v-=(MagickRealType) ScaleCharToQuantum(1);
1548 *p=(Quantum) v;
1549 p++;
1550 q++;
1551 r++;
1552 s++;
1553 }
1554 p++;
1555 q++;
1556 r++;
1557 s++;
1558 }
1559}
1560
1561MagickExport Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1562{
1563#define DespeckleImageTag "Despeckle/Image"
1564
cristy2407fc22009-09-11 00:55:25 +00001565 CacheView
1566 *despeckle_view,
1567 *image_view;
1568
cristy3ed852e2009-09-05 21:47:34 +00001569 Image
1570 *despeckle_image;
1571
cristy3ed852e2009-09-05 21:47:34 +00001572 MagickBooleanType
1573 status;
1574
cristya58c3172011-02-19 19:23:11 +00001575 register ssize_t
1576 i;
1577
cristy3ed852e2009-09-05 21:47:34 +00001578 Quantum
cristy65b9f392011-02-22 14:22:54 +00001579 *restrict buffers,
1580 *restrict pixels;
cristy3ed852e2009-09-05 21:47:34 +00001581
1582 size_t
cristya58c3172011-02-19 19:23:11 +00001583 length,
1584 number_channels;
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);
1599 despeckle_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1600 exception);
1601 if (despeckle_image == (Image *) NULL)
1602 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00001603 if (SetImageStorageClass(despeckle_image,DirectClass,exception) == 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;
cristy109695a2011-02-19 19:38:14 +00001627 number_channels=(size_t) (image->colorspace == CMYKColorspace ? 5 : 4);
cristy3ed852e2009-09-05 21:47:34 +00001628 image_view=AcquireCacheView(image);
1629 despeckle_view=AcquireCacheView(despeckle_image);
cristy8df3d002011-02-19 19:40:59 +00001630 for (i=0; i < (ssize_t) number_channels; i++)
cristy3ed852e2009-09-05 21:47:34 +00001631 {
cristy3ed852e2009-09-05 21:47:34 +00001632 register Quantum
1633 *buffer,
1634 *pixel;
1635
cristyc1488b52011-02-19 18:54:15 +00001636 register ssize_t
cristya58c3172011-02-19 19:23:11 +00001637 k,
cristyc1488b52011-02-19 18:54:15 +00001638 x;
1639
cristy117ff172010-08-15 21:35:32 +00001640 ssize_t
1641 j,
1642 y;
1643
cristy3ed852e2009-09-05 21:47:34 +00001644 if (status == MagickFalse)
1645 continue;
cristy65b9f392011-02-22 14:22:54 +00001646 pixel=pixels;
cristy3ed852e2009-09-05 21:47:34 +00001647 (void) ResetMagickMemory(pixel,0,length*sizeof(*pixel));
cristy65b9f392011-02-22 14:22:54 +00001648 buffer=buffers;
cristybb503372010-05-27 20:51:26 +00001649 j=(ssize_t) image->columns+2;
1650 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001651 {
cristy4c08aed2011-07-01 19:47:50 +00001652 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001653 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001654
1655 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001656 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001657 break;
1658 j++;
cristybb503372010-05-27 20:51:26 +00001659 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001660 {
cristya58c3172011-02-19 19:23:11 +00001661 switch (i)
cristy3ed852e2009-09-05 21:47:34 +00001662 {
cristy4c08aed2011-07-01 19:47:50 +00001663 case 0: pixel[j]=GetPixelRed(image,p); break;
1664 case 1: pixel[j]=GetPixelGreen(image,p); break;
1665 case 2: pixel[j]=GetPixelBlue(image,p); break;
1666 case 3: pixel[j]=GetPixelAlpha(image,p); break;
1667 case 4: pixel[j]=GetPixelBlack(image,p); break;
cristy3ed852e2009-09-05 21:47:34 +00001668 default: break;
1669 }
cristyed231572011-07-14 02:18:59 +00001670 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001671 j++;
1672 }
1673 j++;
1674 }
cristy3ed852e2009-09-05 21:47:34 +00001675 (void) ResetMagickMemory(buffer,0,length*sizeof(*buffer));
cristya58c3172011-02-19 19:23:11 +00001676 for (k=0; k < 4; k++)
cristy3ed852e2009-09-05 21:47:34 +00001677 {
cristya58c3172011-02-19 19:23:11 +00001678 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,1);
1679 Hull(-X[k],-Y[k],image->columns,image->rows,pixel,buffer,1);
1680 Hull(-X[k],-Y[k],image->columns,image->rows,pixel,buffer,-1);
1681 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,-1);
cristy3ed852e2009-09-05 21:47:34 +00001682 }
cristybb503372010-05-27 20:51:26 +00001683 j=(ssize_t) image->columns+2;
1684 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001685 {
1686 MagickBooleanType
1687 sync;
1688
cristy4c08aed2011-07-01 19:47:50 +00001689 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001690 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001691
1692 q=GetCacheViewAuthenticPixels(despeckle_view,0,y,despeckle_image->columns,
1693 1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001694 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001695 break;
1696 j++;
cristybb503372010-05-27 20:51:26 +00001697 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001698 {
cristya58c3172011-02-19 19:23:11 +00001699 switch (i)
cristy3ed852e2009-09-05 21:47:34 +00001700 {
cristy4c08aed2011-07-01 19:47:50 +00001701 case 0: SetPixelRed(despeckle_image,pixel[j],q); break;
1702 case 1: SetPixelGreen(despeckle_image,pixel[j],q); break;
1703 case 2: SetPixelBlue(despeckle_image,pixel[j],q); break;
1704 case 3: SetPixelAlpha(despeckle_image,pixel[j],q); break;
1705 case 4: SetPixelBlack(despeckle_image,pixel[j],q); break;
cristy3ed852e2009-09-05 21:47:34 +00001706 default: break;
1707 }
cristyed231572011-07-14 02:18:59 +00001708 q+=GetPixelChannels(despeckle_image);
cristy3ed852e2009-09-05 21:47:34 +00001709 j++;
1710 }
1711 sync=SyncCacheViewAuthenticPixels(despeckle_view,exception);
1712 if (sync == MagickFalse)
1713 {
1714 status=MagickFalse;
1715 break;
1716 }
1717 j++;
1718 }
1719 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1720 {
1721 MagickBooleanType
1722 proceed;
1723
cristya58c3172011-02-19 19:23:11 +00001724 proceed=SetImageProgress(image,DespeckleImageTag,(MagickOffsetType) i,
1725 number_channels);
cristy3ed852e2009-09-05 21:47:34 +00001726 if (proceed == MagickFalse)
1727 status=MagickFalse;
1728 }
1729 }
1730 despeckle_view=DestroyCacheView(despeckle_view);
1731 image_view=DestroyCacheView(image_view);
cristy65b9f392011-02-22 14:22:54 +00001732 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1733 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001734 despeckle_image->type=image->type;
1735 if (status == MagickFalse)
1736 despeckle_image=DestroyImage(despeckle_image);
1737 return(despeckle_image);
1738}
1739
1740/*
1741%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1742% %
1743% %
1744% %
1745% E d g e I m a g e %
1746% %
1747% %
1748% %
1749%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1750%
1751% EdgeImage() finds edges in an image. Radius defines the radius of the
1752% convolution filter. Use a radius of 0 and EdgeImage() selects a suitable
1753% radius for you.
1754%
1755% The format of the EdgeImage method is:
1756%
1757% Image *EdgeImage(const Image *image,const double radius,
cristy8ae632d2011-09-05 17:29:53 +00001758% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001759%
1760% A description of each parameter follows:
1761%
1762% o image: the image.
1763%
1764% o radius: the radius of the pixel neighborhood.
1765%
cristy8ae632d2011-09-05 17:29:53 +00001766% o sigma: the standard deviation of the Gaussian, in pixels.
1767%
cristy3ed852e2009-09-05 21:47:34 +00001768% o exception: return any errors or warnings in this structure.
1769%
1770*/
1771MagickExport Image *EdgeImage(const Image *image,const double radius,
cristy8ae632d2011-09-05 17:29:53 +00001772 const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001773{
1774 Image
1775 *edge_image;
1776
cristy41cbe682011-07-15 19:12:37 +00001777 KernelInfo
1778 *kernel_info;
cristy3ed852e2009-09-05 21:47:34 +00001779
cristybb503372010-05-27 20:51:26 +00001780 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001781 i;
1782
cristybb503372010-05-27 20:51:26 +00001783 size_t
cristy3ed852e2009-09-05 21:47:34 +00001784 width;
1785
cristy41cbe682011-07-15 19:12:37 +00001786 ssize_t
1787 j,
1788 u,
1789 v;
1790
cristy3ed852e2009-09-05 21:47:34 +00001791 assert(image != (const Image *) NULL);
1792 assert(image->signature == MagickSignature);
1793 if (image->debug != MagickFalse)
1794 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1795 assert(exception != (ExceptionInfo *) NULL);
1796 assert(exception->signature == MagickSignature);
cristy8ae632d2011-09-05 17:29:53 +00001797 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00001798 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00001799 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001800 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00001801 kernel_info->width=width;
1802 kernel_info->height=width;
cristy41cbe682011-07-15 19:12:37 +00001803 kernel_info->values=(double *) AcquireAlignedMemory(kernel_info->width,
1804 kernel_info->width*sizeof(*kernel_info->values));
1805 if (kernel_info->values == (double *) NULL)
1806 {
1807 kernel_info=DestroyKernelInfo(kernel_info);
1808 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1809 }
1810 j=(ssize_t) kernel_info->width/2;
1811 i=0;
1812 for (v=(-j); v <= j; v++)
1813 {
1814 for (u=(-j); u <= j; u++)
1815 {
1816 kernel_info->values[i]=(-1.0);
1817 i++;
1818 }
1819 }
1820 kernel_info->values[i/2]=(double) (width*width-1.0);
cristy0a922382011-07-16 15:30:34 +00001821 kernel_info->bias=image->bias;
cristy5e6be1e2011-07-16 01:23:39 +00001822 edge_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00001823 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00001824 return(edge_image);
1825}
1826
1827/*
1828%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1829% %
1830% %
1831% %
1832% E m b o s s I m a g e %
1833% %
1834% %
1835% %
1836%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1837%
1838% EmbossImage() returns a grayscale image with a three-dimensional effect.
1839% We convolve the image with a Gaussian operator of the given radius and
1840% standard deviation (sigma). For reasonable results, radius should be
1841% larger than sigma. Use a radius of 0 and Emboss() selects a suitable
1842% radius for you.
1843%
1844% The format of the EmbossImage method is:
1845%
1846% Image *EmbossImage(const Image *image,const double radius,
1847% const double sigma,ExceptionInfo *exception)
1848%
1849% A description of each parameter follows:
1850%
1851% o image: the image.
1852%
1853% o radius: the radius of the pixel neighborhood.
1854%
1855% o sigma: the standard deviation of the Gaussian, in pixels.
1856%
1857% o exception: return any errors or warnings in this structure.
1858%
1859*/
1860MagickExport Image *EmbossImage(const Image *image,const double radius,
1861 const double sigma,ExceptionInfo *exception)
1862{
cristy3ed852e2009-09-05 21:47:34 +00001863 Image
1864 *emboss_image;
1865
cristy41cbe682011-07-15 19:12:37 +00001866 KernelInfo
1867 *kernel_info;
1868
cristybb503372010-05-27 20:51:26 +00001869 register ssize_t
cristy47e00502009-12-17 19:19:57 +00001870 i;
1871
cristybb503372010-05-27 20:51:26 +00001872 size_t
cristy3ed852e2009-09-05 21:47:34 +00001873 width;
1874
cristy117ff172010-08-15 21:35:32 +00001875 ssize_t
1876 j,
1877 k,
1878 u,
1879 v;
1880
cristy41cbe682011-07-15 19:12:37 +00001881 assert(image != (const Image *) NULL);
cristy3ed852e2009-09-05 21:47:34 +00001882 assert(image->signature == MagickSignature);
1883 if (image->debug != MagickFalse)
1884 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1885 assert(exception != (ExceptionInfo *) NULL);
1886 assert(exception->signature == MagickSignature);
1887 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00001888 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00001889 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001890 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00001891 kernel_info->width=width;
1892 kernel_info->height=width;
cristy41cbe682011-07-15 19:12:37 +00001893 kernel_info->values=(double *) AcquireAlignedMemory(kernel_info->width,
1894 kernel_info->width*sizeof(*kernel_info->values));
1895 if (kernel_info->values == (double *) NULL)
1896 {
1897 kernel_info=DestroyKernelInfo(kernel_info);
1898 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1899 }
1900 j=(ssize_t) kernel_info->width/2;
cristy47e00502009-12-17 19:19:57 +00001901 k=j;
1902 i=0;
1903 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00001904 {
cristy47e00502009-12-17 19:19:57 +00001905 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00001906 {
cristy41cbe682011-07-15 19:12:37 +00001907 kernel_info->values[i]=(double) (((u < 0) || (v < 0) ? -8.0 : 8.0)*
cristy47e00502009-12-17 19:19:57 +00001908 exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
cristy4205a3c2010-09-12 20:19:59 +00001909 (2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +00001910 if (u != k)
cristy41cbe682011-07-15 19:12:37 +00001911 kernel_info->values[i]=0.0;
cristy3ed852e2009-09-05 21:47:34 +00001912 i++;
1913 }
cristy47e00502009-12-17 19:19:57 +00001914 k--;
cristy3ed852e2009-09-05 21:47:34 +00001915 }
cristy0a922382011-07-16 15:30:34 +00001916 kernel_info->bias=image->bias;
cristy5e6be1e2011-07-16 01:23:39 +00001917 emboss_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00001918 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00001919 if (emboss_image != (Image *) NULL)
cristy6d8c3d72011-08-22 01:20:01 +00001920 (void) EqualizeImage(emboss_image,exception);
cristy3ed852e2009-09-05 21:47:34 +00001921 return(emboss_image);
1922}
1923
1924/*
1925%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1926% %
1927% %
1928% %
1929% G a u s s i a n B l u r I m a g e %
1930% %
1931% %
1932% %
1933%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1934%
1935% GaussianBlurImage() blurs an image. We convolve the image with a
1936% Gaussian operator of the given radius and standard deviation (sigma).
1937% For reasonable results, the radius should be larger than sigma. Use a
1938% radius of 0 and GaussianBlurImage() selects a suitable radius for you
1939%
1940% The format of the GaussianBlurImage method is:
1941%
1942% Image *GaussianBlurImage(const Image *image,onst double radius,
cristy05c0c9a2011-09-05 23:16:13 +00001943% const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001944%
1945% A description of each parameter follows:
1946%
1947% o image: the image.
1948%
cristy3ed852e2009-09-05 21:47:34 +00001949% o radius: the radius of the Gaussian, in pixels, not counting the center
1950% pixel.
1951%
1952% o sigma: the standard deviation of the Gaussian, in pixels.
1953%
cristy05c0c9a2011-09-05 23:16:13 +00001954% o bias: the bias.
1955%
cristy3ed852e2009-09-05 21:47:34 +00001956% o exception: return any errors or warnings in this structure.
1957%
1958*/
cristy41cbe682011-07-15 19:12:37 +00001959MagickExport Image *GaussianBlurImage(const Image *image,const double radius,
cristy05c0c9a2011-09-05 23:16:13 +00001960 const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001961{
cristy3ed852e2009-09-05 21:47:34 +00001962 Image
1963 *blur_image;
1964
cristy41cbe682011-07-15 19:12:37 +00001965 KernelInfo
1966 *kernel_info;
1967
cristybb503372010-05-27 20:51:26 +00001968 register ssize_t
cristy47e00502009-12-17 19:19:57 +00001969 i;
1970
cristybb503372010-05-27 20:51:26 +00001971 size_t
cristy3ed852e2009-09-05 21:47:34 +00001972 width;
1973
cristy117ff172010-08-15 21:35:32 +00001974 ssize_t
1975 j,
1976 u,
1977 v;
1978
cristy3ed852e2009-09-05 21:47:34 +00001979 assert(image != (const Image *) NULL);
1980 assert(image->signature == MagickSignature);
1981 if (image->debug != MagickFalse)
1982 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1983 assert(exception != (ExceptionInfo *) NULL);
1984 assert(exception->signature == MagickSignature);
1985 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00001986 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00001987 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001988 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00001989 (void) ResetMagickMemory(kernel_info,0,sizeof(*kernel_info));
1990 kernel_info->width=width;
1991 kernel_info->height=width;
cristy05c0c9a2011-09-05 23:16:13 +00001992 kernel_info->bias=bias;
cristy41cbe682011-07-15 19:12:37 +00001993 kernel_info->signature=MagickSignature;
1994 kernel_info->values=(double *) AcquireAlignedMemory(kernel_info->width,
1995 kernel_info->width*sizeof(*kernel_info->values));
1996 if (kernel_info->values == (double *) NULL)
1997 {
1998 kernel_info=DestroyKernelInfo(kernel_info);
1999 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2000 }
2001 j=(ssize_t) kernel_info->width/2;
cristy3ed852e2009-09-05 21:47:34 +00002002 i=0;
cristy47e00502009-12-17 19:19:57 +00002003 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002004 {
cristy47e00502009-12-17 19:19:57 +00002005 for (u=(-j); u <= j; u++)
cristy41cbe682011-07-15 19:12:37 +00002006 {
2007 kernel_info->values[i]=(double) (exp(-((double) u*u+v*v)/(2.0*
2008 MagickSigma*MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
2009 i++;
2010 }
cristy3ed852e2009-09-05 21:47:34 +00002011 }
cristy5e6be1e2011-07-16 01:23:39 +00002012 blur_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00002013 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00002014 return(blur_image);
2015}
2016
2017/*
2018%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2019% %
2020% %
2021% %
cristy3ed852e2009-09-05 21:47:34 +00002022% M o t i o n B l u r I m a g e %
2023% %
2024% %
2025% %
2026%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2027%
2028% MotionBlurImage() simulates motion blur. We convolve the image with a
2029% Gaussian operator of the given radius and standard deviation (sigma).
2030% For reasonable results, radius should be larger than sigma. Use a
2031% radius of 0 and MotionBlurImage() selects a suitable radius for you.
2032% Angle gives the angle of the blurring motion.
2033%
2034% Andrew Protano contributed this effect.
2035%
2036% The format of the MotionBlurImage method is:
2037%
2038% Image *MotionBlurImage(const Image *image,const double radius,
2039% const double sigma,const double angle,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%
2052% o exception: return any errors or warnings in this structure.
2053%
2054*/
2055
cristybb503372010-05-27 20:51:26 +00002056static double *GetMotionBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +00002057{
cristy3ed852e2009-09-05 21:47:34 +00002058 double
cristy47e00502009-12-17 19:19:57 +00002059 *kernel,
cristy3ed852e2009-09-05 21:47:34 +00002060 normalize;
2061
cristybb503372010-05-27 20:51:26 +00002062 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002063 i;
2064
2065 /*
cristy47e00502009-12-17 19:19:57 +00002066 Generate a 1-D convolution kernel.
cristy3ed852e2009-09-05 21:47:34 +00002067 */
2068 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
2069 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
2070 if (kernel == (double *) NULL)
2071 return(kernel);
cristy3ed852e2009-09-05 21:47:34 +00002072 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00002073 for (i=0; i < (ssize_t) width; i++)
cristy47e00502009-12-17 19:19:57 +00002074 {
cristy4205a3c2010-09-12 20:19:59 +00002075 kernel[i]=(double) (exp((-((double) i*i)/(double) (2.0*MagickSigma*
2076 MagickSigma)))/(MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00002077 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +00002078 }
cristybb503372010-05-27 20:51:26 +00002079 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002080 kernel[i]/=normalize;
2081 return(kernel);
2082}
2083
cristyf4ad9df2011-07-08 16:49:03 +00002084MagickExport Image *MotionBlurImage(const Image *image,
2085 const double radius,const double sigma,const double angle,
2086 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002087{
cristyc4c8d132010-01-07 01:58:38 +00002088 CacheView
2089 *blur_view,
2090 *image_view;
2091
cristy3ed852e2009-09-05 21:47:34 +00002092 double
2093 *kernel;
2094
2095 Image
2096 *blur_image;
2097
cristy3ed852e2009-09-05 21:47:34 +00002098 MagickBooleanType
2099 status;
2100
cristybb503372010-05-27 20:51:26 +00002101 MagickOffsetType
2102 progress;
2103
cristy4c08aed2011-07-01 19:47:50 +00002104 PixelInfo
cristyddd82202009-11-03 20:14:50 +00002105 bias;
cristy3ed852e2009-09-05 21:47:34 +00002106
2107 OffsetInfo
2108 *offset;
2109
2110 PointInfo
2111 point;
2112
cristybb503372010-05-27 20:51:26 +00002113 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002114 i;
2115
cristybb503372010-05-27 20:51:26 +00002116 size_t
cristy3ed852e2009-09-05 21:47:34 +00002117 width;
2118
cristybb503372010-05-27 20:51:26 +00002119 ssize_t
2120 y;
2121
cristy3ed852e2009-09-05 21:47:34 +00002122 assert(image != (Image *) NULL);
2123 assert(image->signature == MagickSignature);
2124 if (image->debug != MagickFalse)
2125 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2126 assert(exception != (ExceptionInfo *) NULL);
2127 width=GetOptimalKernelWidth1D(radius,sigma);
2128 kernel=GetMotionBlurKernel(width,sigma);
2129 if (kernel == (double *) NULL)
2130 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2131 offset=(OffsetInfo *) AcquireQuantumMemory(width,sizeof(*offset));
2132 if (offset == (OffsetInfo *) NULL)
2133 {
2134 kernel=(double *) RelinquishMagickMemory(kernel);
2135 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2136 }
2137 blur_image=CloneImage(image,0,0,MagickTrue,exception);
2138 if (blur_image == (Image *) NULL)
2139 {
2140 kernel=(double *) RelinquishMagickMemory(kernel);
2141 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2142 return((Image *) NULL);
2143 }
cristy574cc262011-08-05 01:23:58 +00002144 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00002145 {
2146 kernel=(double *) RelinquishMagickMemory(kernel);
2147 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
cristy3ed852e2009-09-05 21:47:34 +00002148 blur_image=DestroyImage(blur_image);
2149 return((Image *) NULL);
2150 }
2151 point.x=(double) width*sin(DegreesToRadians(angle));
2152 point.y=(double) width*cos(DegreesToRadians(angle));
cristybb503372010-05-27 20:51:26 +00002153 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002154 {
cristybb503372010-05-27 20:51:26 +00002155 offset[i].x=(ssize_t) ceil((double) (i*point.y)/hypot(point.x,point.y)-0.5);
2156 offset[i].y=(ssize_t) ceil((double) (i*point.x)/hypot(point.x,point.y)-0.5);
cristy3ed852e2009-09-05 21:47:34 +00002157 }
2158 /*
2159 Motion blur image.
2160 */
2161 status=MagickTrue;
2162 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00002163 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00002164 image_view=AcquireCacheView(image);
2165 blur_view=AcquireCacheView(blur_image);
cristyb557a152011-02-22 12:14:30 +00002166#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00002167 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00002168#endif
cristybb503372010-05-27 20:51:26 +00002169 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002170 {
cristy4c08aed2011-07-01 19:47:50 +00002171 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002172 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002173
cristy117ff172010-08-15 21:35:32 +00002174 register ssize_t
2175 x;
2176
cristy3ed852e2009-09-05 21:47:34 +00002177 if (status == MagickFalse)
2178 continue;
2179 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
2180 exception);
cristyacd2ed22011-08-30 01:44:23 +00002181 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002182 {
2183 status=MagickFalse;
2184 continue;
2185 }
cristybb503372010-05-27 20:51:26 +00002186 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002187 {
cristy4c08aed2011-07-01 19:47:50 +00002188 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002189 qixel;
2190
2191 PixelPacket
2192 pixel;
2193
2194 register double
cristyc47d1f82009-11-26 01:44:43 +00002195 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00002196
cristybb503372010-05-27 20:51:26 +00002197 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002198 i;
2199
cristy3ed852e2009-09-05 21:47:34 +00002200 k=kernel;
cristyddd82202009-11-03 20:14:50 +00002201 qixel=bias;
cristyed231572011-07-14 02:18:59 +00002202 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00002203 {
cristybb503372010-05-27 20:51:26 +00002204 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002205 {
2206 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
2207 offset[i].y,&pixel,exception);
2208 qixel.red+=(*k)*pixel.red;
2209 qixel.green+=(*k)*pixel.green;
2210 qixel.blue+=(*k)*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00002211 qixel.alpha+=(*k)*pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00002212 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00002213 qixel.black+=(*k)*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00002214 k++;
2215 }
cristyed231572011-07-14 02:18:59 +00002216 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002217 SetPixelRed(blur_image,
2218 ClampToQuantum(qixel.red),q);
cristyed231572011-07-14 02:18:59 +00002219 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002220 SetPixelGreen(blur_image,
2221 ClampToQuantum(qixel.green),q);
cristyed231572011-07-14 02:18:59 +00002222 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002223 SetPixelBlue(blur_image,
2224 ClampToQuantum(qixel.blue),q);
cristyed231572011-07-14 02:18:59 +00002225 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002226 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002227 SetPixelBlack(blur_image,
2228 ClampToQuantum(qixel.black),q);
cristyed231572011-07-14 02:18:59 +00002229 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002230 SetPixelAlpha(blur_image,
2231 ClampToQuantum(qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00002232 }
2233 else
2234 {
2235 MagickRealType
2236 alpha,
2237 gamma;
2238
2239 alpha=0.0;
2240 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00002241 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002242 {
2243 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
2244 offset[i].y,&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00002245 alpha=(MagickRealType) (QuantumScale*pixel.alpha);
cristy3ed852e2009-09-05 21:47:34 +00002246 qixel.red+=(*k)*alpha*pixel.red;
2247 qixel.green+=(*k)*alpha*pixel.green;
2248 qixel.blue+=(*k)*alpha*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00002249 qixel.alpha+=(*k)*pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00002250 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00002251 qixel.black+=(*k)*alpha*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00002252 gamma+=(*k)*alpha;
2253 k++;
2254 }
2255 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +00002256 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002257 SetPixelRed(blur_image,
2258 ClampToQuantum(gamma*qixel.red),q);
cristyed231572011-07-14 02:18:59 +00002259 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002260 SetPixelGreen(blur_image,
2261 ClampToQuantum(gamma*qixel.green),q);
cristyed231572011-07-14 02:18:59 +00002262 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002263 SetPixelBlue(blur_image,
2264 ClampToQuantum(gamma*qixel.blue),q);
cristyed231572011-07-14 02:18:59 +00002265 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002266 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002267 SetPixelBlack(blur_image,
2268 ClampToQuantum(gamma*qixel.black),q);
cristyed231572011-07-14 02:18:59 +00002269 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002270 SetPixelAlpha(blur_image,
2271 ClampToQuantum(qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00002272 }
cristyed231572011-07-14 02:18:59 +00002273 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00002274 }
2275 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
2276 status=MagickFalse;
2277 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2278 {
2279 MagickBooleanType
2280 proceed;
2281
cristyb557a152011-02-22 12:14:30 +00002282#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00002283 #pragma omp critical (MagickCore_MotionBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00002284#endif
2285 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
2286 if (proceed == MagickFalse)
2287 status=MagickFalse;
2288 }
2289 }
2290 blur_view=DestroyCacheView(blur_view);
2291 image_view=DestroyCacheView(image_view);
2292 kernel=(double *) RelinquishMagickMemory(kernel);
2293 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2294 if (status == MagickFalse)
2295 blur_image=DestroyImage(blur_image);
2296 return(blur_image);
2297}
2298
2299/*
2300%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2301% %
2302% %
2303% %
2304% P r e v i e w I m a g e %
2305% %
2306% %
2307% %
2308%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2309%
2310% PreviewImage() tiles 9 thumbnails of the specified image with an image
2311% processing operation applied with varying parameters. This may be helpful
2312% pin-pointing an appropriate parameter for a particular image processing
2313% operation.
2314%
2315% The format of the PreviewImages method is:
2316%
2317% Image *PreviewImages(const Image *image,const PreviewType preview,
2318% ExceptionInfo *exception)
2319%
2320% A description of each parameter follows:
2321%
2322% o image: the image.
2323%
2324% o preview: the image processing operation.
2325%
2326% o exception: return any errors or warnings in this structure.
2327%
2328*/
2329MagickExport Image *PreviewImage(const Image *image,const PreviewType preview,
2330 ExceptionInfo *exception)
2331{
2332#define NumberTiles 9
2333#define PreviewImageTag "Preview/Image"
2334#define DefaultPreviewGeometry "204x204+10+10"
2335
2336 char
2337 factor[MaxTextExtent],
2338 label[MaxTextExtent];
2339
2340 double
2341 degrees,
2342 gamma,
2343 percentage,
2344 radius,
2345 sigma,
2346 threshold;
2347
2348 Image
2349 *images,
2350 *montage_image,
2351 *preview_image,
2352 *thumbnail;
2353
2354 ImageInfo
2355 *preview_info;
2356
cristy3ed852e2009-09-05 21:47:34 +00002357 MagickBooleanType
2358 proceed;
2359
2360 MontageInfo
2361 *montage_info;
2362
2363 QuantizeInfo
2364 quantize_info;
2365
2366 RectangleInfo
2367 geometry;
2368
cristybb503372010-05-27 20:51:26 +00002369 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002370 i,
2371 x;
2372
cristybb503372010-05-27 20:51:26 +00002373 size_t
cristy3ed852e2009-09-05 21:47:34 +00002374 colors;
2375
cristy117ff172010-08-15 21:35:32 +00002376 ssize_t
2377 y;
2378
cristy3ed852e2009-09-05 21:47:34 +00002379 /*
2380 Open output image file.
2381 */
2382 assert(image != (Image *) NULL);
2383 assert(image->signature == MagickSignature);
2384 if (image->debug != MagickFalse)
2385 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2386 colors=2;
2387 degrees=0.0;
2388 gamma=(-0.2f);
2389 preview_info=AcquireImageInfo();
2390 SetGeometry(image,&geometry);
2391 (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y,
2392 &geometry.width,&geometry.height);
2393 images=NewImageList();
2394 percentage=12.5;
2395 GetQuantizeInfo(&quantize_info);
2396 radius=0.0;
2397 sigma=1.0;
2398 threshold=0.0;
2399 x=0;
2400 y=0;
2401 for (i=0; i < NumberTiles; i++)
2402 {
2403 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
2404 if (thumbnail == (Image *) NULL)
2405 break;
2406 (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL,
2407 (void *) NULL);
2408 (void) SetImageProperty(thumbnail,"label",DefaultTileLabel);
2409 if (i == (NumberTiles/2))
2410 {
2411 (void) QueryColorDatabase("#dfdfdf",&thumbnail->matte_color,exception);
2412 AppendImageToList(&images,thumbnail);
2413 continue;
2414 }
2415 switch (preview)
2416 {
2417 case RotatePreview:
2418 {
2419 degrees+=45.0;
2420 preview_image=RotateImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002421 (void) FormatLocaleString(label,MaxTextExtent,"rotate %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002422 break;
2423 }
2424 case ShearPreview:
2425 {
2426 degrees+=5.0;
2427 preview_image=ShearImage(thumbnail,degrees,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002428 (void) FormatLocaleString(label,MaxTextExtent,"shear %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002429 degrees,2.0*degrees);
2430 break;
2431 }
2432 case RollPreview:
2433 {
cristybb503372010-05-27 20:51:26 +00002434 x=(ssize_t) ((i+1)*thumbnail->columns)/NumberTiles;
2435 y=(ssize_t) ((i+1)*thumbnail->rows)/NumberTiles;
cristy3ed852e2009-09-05 21:47:34 +00002436 preview_image=RollImage(thumbnail,x,y,exception);
cristyb51dff52011-05-19 16:55:47 +00002437 (void) FormatLocaleString(label,MaxTextExtent,"roll %+.20gx%+.20g",
cristye8c25f92010-06-03 00:53:06 +00002438 (double) x,(double) y);
cristy3ed852e2009-09-05 21:47:34 +00002439 break;
2440 }
2441 case HuePreview:
2442 {
2443 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2444 if (preview_image == (Image *) NULL)
2445 break;
cristyb51dff52011-05-19 16:55:47 +00002446 (void) FormatLocaleString(factor,MaxTextExtent,"100,100,%g",
cristy3ed852e2009-09-05 21:47:34 +00002447 2.0*percentage);
cristy33bd5152011-08-24 01:42:24 +00002448 (void) ModulateImage(preview_image,factor,exception);
cristyb51dff52011-05-19 16:55:47 +00002449 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002450 break;
2451 }
2452 case SaturationPreview:
2453 {
2454 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2455 if (preview_image == (Image *) NULL)
2456 break;
cristyb51dff52011-05-19 16:55:47 +00002457 (void) FormatLocaleString(factor,MaxTextExtent,"100,%g",
cristy8cd5b312010-01-07 01:10:24 +00002458 2.0*percentage);
cristy33bd5152011-08-24 01:42:24 +00002459 (void) ModulateImage(preview_image,factor,exception);
cristyb51dff52011-05-19 16:55:47 +00002460 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002461 break;
2462 }
2463 case BrightnessPreview:
2464 {
2465 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2466 if (preview_image == (Image *) NULL)
2467 break;
cristyb51dff52011-05-19 16:55:47 +00002468 (void) FormatLocaleString(factor,MaxTextExtent,"%g",2.0*percentage);
cristy33bd5152011-08-24 01:42:24 +00002469 (void) ModulateImage(preview_image,factor,exception);
cristyb51dff52011-05-19 16:55:47 +00002470 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002471 break;
2472 }
2473 case GammaPreview:
2474 default:
2475 {
2476 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2477 if (preview_image == (Image *) NULL)
2478 break;
2479 gamma+=0.4f;
cristyb3e7c6c2011-07-24 01:43:55 +00002480 (void) GammaImage(preview_image,gamma,exception);
cristyb51dff52011-05-19 16:55:47 +00002481 (void) FormatLocaleString(label,MaxTextExtent,"gamma %g",gamma);
cristy3ed852e2009-09-05 21:47:34 +00002482 break;
2483 }
2484 case SpiffPreview:
2485 {
2486 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2487 if (preview_image != (Image *) NULL)
2488 for (x=0; x < i; x++)
cristye23ec9d2011-08-16 18:15:40 +00002489 (void) ContrastImage(preview_image,MagickTrue,exception);
cristyb51dff52011-05-19 16:55:47 +00002490 (void) FormatLocaleString(label,MaxTextExtent,"contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002491 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002492 break;
2493 }
2494 case DullPreview:
2495 {
2496 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2497 if (preview_image == (Image *) NULL)
2498 break;
2499 for (x=0; x < i; x++)
cristye23ec9d2011-08-16 18:15:40 +00002500 (void) ContrastImage(preview_image,MagickFalse,exception);
cristyb51dff52011-05-19 16:55:47 +00002501 (void) FormatLocaleString(label,MaxTextExtent,"+contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002502 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002503 break;
2504 }
2505 case GrayscalePreview:
2506 {
2507 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2508 if (preview_image == (Image *) NULL)
2509 break;
2510 colors<<=1;
2511 quantize_info.number_colors=colors;
2512 quantize_info.colorspace=GRAYColorspace;
cristy018f07f2011-09-04 21:15:19 +00002513 (void) QuantizeImage(&quantize_info,preview_image,exception);
cristyb51dff52011-05-19 16:55:47 +00002514 (void) FormatLocaleString(label,MaxTextExtent,
cristye8c25f92010-06-03 00:53:06 +00002515 "-colorspace gray -colors %.20g",(double) colors);
cristy3ed852e2009-09-05 21:47:34 +00002516 break;
2517 }
2518 case QuantizePreview:
2519 {
2520 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2521 if (preview_image == (Image *) NULL)
2522 break;
2523 colors<<=1;
2524 quantize_info.number_colors=colors;
cristy018f07f2011-09-04 21:15:19 +00002525 (void) QuantizeImage(&quantize_info,preview_image,exception);
cristyb51dff52011-05-19 16:55:47 +00002526 (void) FormatLocaleString(label,MaxTextExtent,"colors %.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00002527 colors);
cristy3ed852e2009-09-05 21:47:34 +00002528 break;
2529 }
2530 case DespecklePreview:
2531 {
2532 for (x=0; x < (i-1); x++)
2533 {
2534 preview_image=DespeckleImage(thumbnail,exception);
2535 if (preview_image == (Image *) NULL)
2536 break;
2537 thumbnail=DestroyImage(thumbnail);
2538 thumbnail=preview_image;
2539 }
2540 preview_image=DespeckleImage(thumbnail,exception);
2541 if (preview_image == (Image *) NULL)
2542 break;
cristyb51dff52011-05-19 16:55:47 +00002543 (void) FormatLocaleString(label,MaxTextExtent,"despeckle (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002544 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002545 break;
2546 }
2547 case ReduceNoisePreview:
2548 {
cristy95c38342011-03-18 22:39:51 +00002549 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) radius,
2550 (size_t) radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002551 (void) FormatLocaleString(label,MaxTextExtent,"noise %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002552 break;
2553 }
2554 case AddNoisePreview:
2555 {
2556 switch ((int) i)
2557 {
2558 case 0:
2559 {
2560 (void) CopyMagickString(factor,"uniform",MaxTextExtent);
2561 break;
2562 }
2563 case 1:
2564 {
2565 (void) CopyMagickString(factor,"gaussian",MaxTextExtent);
2566 break;
2567 }
2568 case 2:
2569 {
2570 (void) CopyMagickString(factor,"multiplicative",MaxTextExtent);
2571 break;
2572 }
2573 case 3:
2574 {
2575 (void) CopyMagickString(factor,"impulse",MaxTextExtent);
2576 break;
2577 }
2578 case 4:
2579 {
2580 (void) CopyMagickString(factor,"laplacian",MaxTextExtent);
2581 break;
2582 }
2583 case 5:
2584 {
2585 (void) CopyMagickString(factor,"Poisson",MaxTextExtent);
2586 break;
2587 }
2588 default:
2589 {
2590 (void) CopyMagickString(thumbnail->magick,"NULL",MaxTextExtent);
2591 break;
2592 }
2593 }
cristyd76c51e2011-03-26 00:21:26 +00002594 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) i,
2595 (size_t) i,exception);
cristyb51dff52011-05-19 16:55:47 +00002596 (void) FormatLocaleString(label,MaxTextExtent,"+noise %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002597 break;
2598 }
2599 case SharpenPreview:
2600 {
cristy05c0c9a2011-09-05 23:16:13 +00002601 preview_image=SharpenImage(thumbnail,radius,sigma,image->bias,
2602 exception);
cristyb51dff52011-05-19 16:55:47 +00002603 (void) FormatLocaleString(label,MaxTextExtent,"sharpen %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002604 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002605 break;
2606 }
2607 case BlurPreview:
2608 {
cristy05c0c9a2011-09-05 23:16:13 +00002609 preview_image=BlurImage(thumbnail,radius,sigma,image->bias,exception);
cristyb51dff52011-05-19 16:55:47 +00002610 (void) FormatLocaleString(label,MaxTextExtent,"blur %gx%g",radius,
cristy3ed852e2009-09-05 21:47:34 +00002611 sigma);
2612 break;
2613 }
2614 case ThresholdPreview:
2615 {
2616 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2617 if (preview_image == (Image *) NULL)
2618 break;
2619 (void) BilevelImage(thumbnail,
2620 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
cristyb51dff52011-05-19 16:55:47 +00002621 (void) FormatLocaleString(label,MaxTextExtent,"threshold %g",
cristy3ed852e2009-09-05 21:47:34 +00002622 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
2623 break;
2624 }
2625 case EdgeDetectPreview:
2626 {
cristy8ae632d2011-09-05 17:29:53 +00002627 preview_image=EdgeImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00002628 (void) FormatLocaleString(label,MaxTextExtent,"edge %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002629 break;
2630 }
2631 case SpreadPreview:
2632 {
2633 preview_image=SpreadImage(thumbnail,radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002634 (void) FormatLocaleString(label,MaxTextExtent,"spread %g",
cristy8cd5b312010-01-07 01:10:24 +00002635 radius+0.5);
cristy3ed852e2009-09-05 21:47:34 +00002636 break;
2637 }
2638 case SolarizePreview:
2639 {
2640 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2641 if (preview_image == (Image *) NULL)
2642 break;
2643 (void) SolarizeImage(preview_image,(double) QuantumRange*
cristy5cbc0162011-08-29 00:36:28 +00002644 percentage/100.0,exception);
cristyb51dff52011-05-19 16:55:47 +00002645 (void) FormatLocaleString(label,MaxTextExtent,"solarize %g",
cristy3ed852e2009-09-05 21:47:34 +00002646 (QuantumRange*percentage)/100.0);
2647 break;
2648 }
2649 case ShadePreview:
2650 {
2651 degrees+=10.0;
2652 preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees,
2653 exception);
cristyb51dff52011-05-19 16:55:47 +00002654 (void) FormatLocaleString(label,MaxTextExtent,"shade %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002655 degrees,degrees);
cristy3ed852e2009-09-05 21:47:34 +00002656 break;
2657 }
2658 case RaisePreview:
2659 {
2660 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2661 if (preview_image == (Image *) NULL)
2662 break;
cristybb503372010-05-27 20:51:26 +00002663 geometry.width=(size_t) (2*i+2);
2664 geometry.height=(size_t) (2*i+2);
cristy3ed852e2009-09-05 21:47:34 +00002665 geometry.x=i/2;
2666 geometry.y=i/2;
cristy6170ac32011-08-28 14:15:37 +00002667 (void) RaiseImage(preview_image,&geometry,MagickTrue,exception);
cristyb51dff52011-05-19 16:55:47 +00002668 (void) FormatLocaleString(label,MaxTextExtent,
cristy6d8abba2010-06-03 01:10:47 +00002669 "raise %.20gx%.20g%+.20g%+.20g",(double) geometry.width,(double)
cristye8c25f92010-06-03 00:53:06 +00002670 geometry.height,(double) geometry.x,(double) geometry.y);
cristy3ed852e2009-09-05 21:47:34 +00002671 break;
2672 }
2673 case SegmentPreview:
2674 {
2675 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2676 if (preview_image == (Image *) NULL)
2677 break;
2678 threshold+=0.4f;
2679 (void) SegmentImage(preview_image,RGBColorspace,MagickFalse,threshold,
cristy018f07f2011-09-04 21:15:19 +00002680 threshold,exception);
cristyb51dff52011-05-19 16:55:47 +00002681 (void) FormatLocaleString(label,MaxTextExtent,"segment %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002682 threshold,threshold);
2683 break;
2684 }
2685 case SwirlPreview:
2686 {
2687 preview_image=SwirlImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002688 (void) FormatLocaleString(label,MaxTextExtent,"swirl %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002689 degrees+=45.0;
2690 break;
2691 }
2692 case ImplodePreview:
2693 {
2694 degrees+=0.1f;
2695 preview_image=ImplodeImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002696 (void) FormatLocaleString(label,MaxTextExtent,"implode %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002697 break;
2698 }
2699 case WavePreview:
2700 {
2701 degrees+=5.0f;
2702 preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002703 (void) FormatLocaleString(label,MaxTextExtent,"wave %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002704 0.5*degrees,2.0*degrees);
cristy3ed852e2009-09-05 21:47:34 +00002705 break;
2706 }
2707 case OilPaintPreview:
2708 {
cristy14973ba2011-08-27 23:48:07 +00002709 preview_image=OilPaintImage(thumbnail,(double) radius,(double) sigma,
2710 exception);
2711 (void) FormatLocaleString(label,MaxTextExtent,"charcoal %gx%g",
2712 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002713 break;
2714 }
2715 case CharcoalDrawingPreview:
2716 {
2717 preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma,
cristy05c0c9a2011-09-05 23:16:13 +00002718 image->bias,exception);
cristyb51dff52011-05-19 16:55:47 +00002719 (void) FormatLocaleString(label,MaxTextExtent,"charcoal %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002720 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002721 break;
2722 }
2723 case JPEGPreview:
2724 {
2725 char
2726 filename[MaxTextExtent];
2727
2728 int
2729 file;
2730
2731 MagickBooleanType
2732 status;
2733
2734 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2735 if (preview_image == (Image *) NULL)
2736 break;
cristybb503372010-05-27 20:51:26 +00002737 preview_info->quality=(size_t) percentage;
cristyb51dff52011-05-19 16:55:47 +00002738 (void) FormatLocaleString(factor,MaxTextExtent,"%.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00002739 preview_info->quality);
cristy3ed852e2009-09-05 21:47:34 +00002740 file=AcquireUniqueFileResource(filename);
2741 if (file != -1)
2742 file=close(file)-1;
cristyb51dff52011-05-19 16:55:47 +00002743 (void) FormatLocaleString(preview_image->filename,MaxTextExtent,
cristy3ed852e2009-09-05 21:47:34 +00002744 "jpeg:%s",filename);
cristy6f9e0d32011-08-28 16:32:09 +00002745 status=WriteImage(preview_info,preview_image,exception);
cristy3ed852e2009-09-05 21:47:34 +00002746 if (status != MagickFalse)
2747 {
2748 Image
2749 *quality_image;
2750
2751 (void) CopyMagickString(preview_info->filename,
2752 preview_image->filename,MaxTextExtent);
2753 quality_image=ReadImage(preview_info,exception);
2754 if (quality_image != (Image *) NULL)
2755 {
2756 preview_image=DestroyImage(preview_image);
2757 preview_image=quality_image;
2758 }
2759 }
2760 (void) RelinquishUniqueFileResource(preview_image->filename);
2761 if ((GetBlobSize(preview_image)/1024) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00002762 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%gmb ",
cristy3ed852e2009-09-05 21:47:34 +00002763 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
2764 1024.0/1024.0);
2765 else
2766 if (GetBlobSize(preview_image) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00002767 (void) FormatLocaleString(label,MaxTextExtent,
cristye7f51092010-01-17 00:39:37 +00002768 "quality %s\n%gkb ",factor,(double) ((MagickOffsetType)
cristy8cd5b312010-01-07 01:10:24 +00002769 GetBlobSize(preview_image))/1024.0);
cristy3ed852e2009-09-05 21:47:34 +00002770 else
cristyb51dff52011-05-19 16:55:47 +00002771 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%.20gb ",
cristy54ea5732011-06-10 12:39:53 +00002772 factor,(double) ((MagickOffsetType) GetBlobSize(thumbnail)));
cristy3ed852e2009-09-05 21:47:34 +00002773 break;
2774 }
2775 }
2776 thumbnail=DestroyImage(thumbnail);
2777 percentage+=12.5;
2778 radius+=0.5;
2779 sigma+=0.25;
2780 if (preview_image == (Image *) NULL)
2781 break;
2782 (void) DeleteImageProperty(preview_image,"label");
2783 (void) SetImageProperty(preview_image,"label",label);
2784 AppendImageToList(&images,preview_image);
cristybb503372010-05-27 20:51:26 +00002785 proceed=SetImageProgress(image,PreviewImageTag,(MagickOffsetType) i,
2786 NumberTiles);
cristy3ed852e2009-09-05 21:47:34 +00002787 if (proceed == MagickFalse)
2788 break;
2789 }
2790 if (images == (Image *) NULL)
2791 {
2792 preview_info=DestroyImageInfo(preview_info);
2793 return((Image *) NULL);
2794 }
2795 /*
2796 Create the montage.
2797 */
2798 montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL);
2799 (void) CopyMagickString(montage_info->filename,image->filename,MaxTextExtent);
2800 montage_info->shadow=MagickTrue;
2801 (void) CloneString(&montage_info->tile,"3x3");
2802 (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry);
2803 (void) CloneString(&montage_info->frame,DefaultTileFrame);
2804 montage_image=MontageImages(images,montage_info,exception);
2805 montage_info=DestroyMontageInfo(montage_info);
2806 images=DestroyImageList(images);
2807 if (montage_image == (Image *) NULL)
2808 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2809 if (montage_image->montage != (char *) NULL)
2810 {
2811 /*
2812 Free image directory.
2813 */
2814 montage_image->montage=(char *) RelinquishMagickMemory(
2815 montage_image->montage);
2816 if (image->directory != (char *) NULL)
2817 montage_image->directory=(char *) RelinquishMagickMemory(
2818 montage_image->directory);
2819 }
2820 preview_info=DestroyImageInfo(preview_info);
2821 return(montage_image);
2822}
2823
2824/*
2825%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2826% %
2827% %
2828% %
2829% R a d i a l B l u r I m a g e %
2830% %
2831% %
2832% %
2833%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2834%
2835% RadialBlurImage() applies a radial blur to the image.
2836%
2837% Andrew Protano contributed this effect.
2838%
2839% The format of the RadialBlurImage method is:
2840%
2841% Image *RadialBlurImage(const Image *image,const double angle,
2842% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002843%
2844% A description of each parameter follows:
2845%
2846% o image: the image.
2847%
cristy3ed852e2009-09-05 21:47:34 +00002848% o angle: the angle of the radial blur.
2849%
2850% o exception: return any errors or warnings in this structure.
2851%
2852*/
cristyf4ad9df2011-07-08 16:49:03 +00002853MagickExport Image *RadialBlurImage(const Image *image,
2854 const double angle,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002855{
cristyc4c8d132010-01-07 01:58:38 +00002856 CacheView
2857 *blur_view,
2858 *image_view;
2859
cristy3ed852e2009-09-05 21:47:34 +00002860 Image
2861 *blur_image;
2862
cristy3ed852e2009-09-05 21:47:34 +00002863 MagickBooleanType
2864 status;
2865
cristybb503372010-05-27 20:51:26 +00002866 MagickOffsetType
2867 progress;
2868
cristy4c08aed2011-07-01 19:47:50 +00002869 PixelInfo
cristyddd82202009-11-03 20:14:50 +00002870 bias;
cristy3ed852e2009-09-05 21:47:34 +00002871
2872 MagickRealType
2873 blur_radius,
2874 *cos_theta,
2875 offset,
2876 *sin_theta,
2877 theta;
2878
2879 PointInfo
2880 blur_center;
2881
cristybb503372010-05-27 20:51:26 +00002882 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002883 i;
2884
cristybb503372010-05-27 20:51:26 +00002885 size_t
cristy3ed852e2009-09-05 21:47:34 +00002886 n;
2887
cristybb503372010-05-27 20:51:26 +00002888 ssize_t
2889 y;
2890
cristy3ed852e2009-09-05 21:47:34 +00002891 /*
2892 Allocate blur image.
2893 */
2894 assert(image != (Image *) NULL);
2895 assert(image->signature == MagickSignature);
2896 if (image->debug != MagickFalse)
2897 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2898 assert(exception != (ExceptionInfo *) NULL);
2899 assert(exception->signature == MagickSignature);
2900 blur_image=CloneImage(image,0,0,MagickTrue,exception);
2901 if (blur_image == (Image *) NULL)
2902 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00002903 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00002904 {
cristy3ed852e2009-09-05 21:47:34 +00002905 blur_image=DestroyImage(blur_image);
2906 return((Image *) NULL);
2907 }
2908 blur_center.x=(double) image->columns/2.0;
2909 blur_center.y=(double) image->rows/2.0;
2910 blur_radius=hypot(blur_center.x,blur_center.y);
cristy117ff172010-08-15 21:35:32 +00002911 n=(size_t) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+2UL);
cristy3ed852e2009-09-05 21:47:34 +00002912 theta=DegreesToRadians(angle)/(MagickRealType) (n-1);
2913 cos_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
2914 sizeof(*cos_theta));
2915 sin_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
2916 sizeof(*sin_theta));
2917 if ((cos_theta == (MagickRealType *) NULL) ||
2918 (sin_theta == (MagickRealType *) NULL))
2919 {
2920 blur_image=DestroyImage(blur_image);
2921 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2922 }
2923 offset=theta*(MagickRealType) (n-1)/2.0;
cristybb503372010-05-27 20:51:26 +00002924 for (i=0; i < (ssize_t) n; i++)
cristy3ed852e2009-09-05 21:47:34 +00002925 {
2926 cos_theta[i]=cos((double) (theta*i-offset));
2927 sin_theta[i]=sin((double) (theta*i-offset));
2928 }
2929 /*
2930 Radial blur image.
2931 */
2932 status=MagickTrue;
2933 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00002934 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00002935 image_view=AcquireCacheView(image);
2936 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00002937#if defined(MAGICKCORE_OPENMP_SUPPORT)
2938 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002939#endif
cristybb503372010-05-27 20:51:26 +00002940 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002941 {
cristy4c08aed2011-07-01 19:47:50 +00002942 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002943 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002944
cristy117ff172010-08-15 21:35:32 +00002945 register ssize_t
2946 x;
2947
cristy3ed852e2009-09-05 21:47:34 +00002948 if (status == MagickFalse)
2949 continue;
2950 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
2951 exception);
cristyacd2ed22011-08-30 01:44:23 +00002952 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002953 {
2954 status=MagickFalse;
2955 continue;
2956 }
cristybb503372010-05-27 20:51:26 +00002957 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002958 {
cristy4c08aed2011-07-01 19:47:50 +00002959 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002960 qixel;
2961
2962 MagickRealType
2963 normalize,
2964 radius;
2965
2966 PixelPacket
2967 pixel;
2968
2969 PointInfo
2970 center;
2971
cristybb503372010-05-27 20:51:26 +00002972 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002973 i;
2974
cristybb503372010-05-27 20:51:26 +00002975 size_t
cristy3ed852e2009-09-05 21:47:34 +00002976 step;
2977
2978 center.x=(double) x-blur_center.x;
2979 center.y=(double) y-blur_center.y;
2980 radius=hypot((double) center.x,center.y);
2981 if (radius == 0)
2982 step=1;
2983 else
2984 {
cristybb503372010-05-27 20:51:26 +00002985 step=(size_t) (blur_radius/radius);
cristy3ed852e2009-09-05 21:47:34 +00002986 if (step == 0)
2987 step=1;
2988 else
2989 if (step >= n)
2990 step=n-1;
2991 }
2992 normalize=0.0;
cristyddd82202009-11-03 20:14:50 +00002993 qixel=bias;
cristyed231572011-07-14 02:18:59 +00002994 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00002995 {
cristyeaedf062010-05-29 22:36:02 +00002996 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00002997 {
cristyeaedf062010-05-29 22:36:02 +00002998 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
2999 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
3000 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
3001 cos_theta[i]+0.5),&pixel,exception);
cristy3ed852e2009-09-05 21:47:34 +00003002 qixel.red+=pixel.red;
3003 qixel.green+=pixel.green;
3004 qixel.blue+=pixel.blue;
cristy3ed852e2009-09-05 21:47:34 +00003005 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00003006 qixel.black+=pixel.black;
3007 qixel.alpha+=pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00003008 normalize+=1.0;
3009 }
3010 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
3011 normalize);
cristyed231572011-07-14 02:18:59 +00003012 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003013 SetPixelRed(blur_image,
3014 ClampToQuantum(normalize*qixel.red),q);
cristyed231572011-07-14 02:18:59 +00003015 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003016 SetPixelGreen(blur_image,
3017 ClampToQuantum(normalize*qixel.green),q);
cristyed231572011-07-14 02:18:59 +00003018 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003019 SetPixelBlue(blur_image,
3020 ClampToQuantum(normalize*qixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003021 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003022 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003023 SetPixelBlack(blur_image,
3024 ClampToQuantum(normalize*qixel.black),q);
cristyed231572011-07-14 02:18:59 +00003025 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003026 SetPixelAlpha(blur_image,
3027 ClampToQuantum(normalize*qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003028 }
3029 else
3030 {
3031 MagickRealType
3032 alpha,
3033 gamma;
3034
3035 alpha=1.0;
3036 gamma=0.0;
cristyeaedf062010-05-29 22:36:02 +00003037 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00003038 {
cristyeaedf062010-05-29 22:36:02 +00003039 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
3040 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
3041 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
3042 cos_theta[i]+0.5),&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00003043 alpha=(MagickRealType) (QuantumScale*pixel.alpha);
cristy3ed852e2009-09-05 21:47:34 +00003044 qixel.red+=alpha*pixel.red;
3045 qixel.green+=alpha*pixel.green;
3046 qixel.blue+=alpha*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00003047 qixel.alpha+=pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00003048 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00003049 qixel.black+=alpha*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00003050 gamma+=alpha;
3051 normalize+=1.0;
3052 }
3053 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3054 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
3055 normalize);
cristyed231572011-07-14 02:18:59 +00003056 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003057 SetPixelRed(blur_image,
3058 ClampToQuantum(gamma*qixel.red),q);
cristyed231572011-07-14 02:18:59 +00003059 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003060 SetPixelGreen(blur_image,
3061 ClampToQuantum(gamma*qixel.green),q);
cristyed231572011-07-14 02:18:59 +00003062 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003063 SetPixelBlue(blur_image,
3064 ClampToQuantum(gamma*qixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003065 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003066 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003067 SetPixelBlack(blur_image,
3068 ClampToQuantum(gamma*qixel.black),q);
cristyed231572011-07-14 02:18:59 +00003069 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003070 SetPixelAlpha(blur_image,
3071 ClampToQuantum(normalize*qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003072 }
cristyed231572011-07-14 02:18:59 +00003073 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003074 }
3075 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
3076 status=MagickFalse;
3077 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3078 {
3079 MagickBooleanType
3080 proceed;
3081
cristyb5d5f722009-11-04 03:03:49 +00003082#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00003083 #pragma omp critical (MagickCore_RadialBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00003084#endif
3085 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
3086 if (proceed == MagickFalse)
3087 status=MagickFalse;
3088 }
3089 }
3090 blur_view=DestroyCacheView(blur_view);
3091 image_view=DestroyCacheView(image_view);
3092 cos_theta=(MagickRealType *) RelinquishMagickMemory(cos_theta);
3093 sin_theta=(MagickRealType *) RelinquishMagickMemory(sin_theta);
3094 if (status == MagickFalse)
3095 blur_image=DestroyImage(blur_image);
3096 return(blur_image);
3097}
3098
3099/*
3100%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3101% %
3102% %
3103% %
cristy3ed852e2009-09-05 21:47:34 +00003104% S e l e c t i v e B l u r I m a g e %
3105% %
3106% %
3107% %
3108%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3109%
3110% SelectiveBlurImage() selectively blur pixels within a contrast threshold.
3111% It is similar to the unsharpen mask that sharpens everything with contrast
3112% above a certain threshold.
3113%
3114% The format of the SelectiveBlurImage method is:
3115%
3116% Image *SelectiveBlurImage(const Image *image,const double radius,
3117% const double sigma,const double threshold,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003118%
3119% A description of each parameter follows:
3120%
3121% o image: the image.
3122%
cristy3ed852e2009-09-05 21:47:34 +00003123% o radius: the radius of the Gaussian, in pixels, not counting the center
3124% pixel.
3125%
3126% o sigma: the standard deviation of the Gaussian, in pixels.
3127%
3128% o threshold: only pixels within this contrast threshold are included
3129% in the blur operation.
3130%
3131% o exception: return any errors or warnings in this structure.
3132%
3133*/
cristyf4ad9df2011-07-08 16:49:03 +00003134MagickExport Image *SelectiveBlurImage(const Image *image,
3135 const double radius,const double sigma,const double threshold,
3136 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003137{
3138#define SelectiveBlurImageTag "SelectiveBlur/Image"
3139
cristy47e00502009-12-17 19:19:57 +00003140 CacheView
3141 *blur_view,
3142 *image_view;
3143
cristy3ed852e2009-09-05 21:47:34 +00003144 double
cristy3ed852e2009-09-05 21:47:34 +00003145 *kernel;
3146
3147 Image
3148 *blur_image;
3149
cristy3ed852e2009-09-05 21:47:34 +00003150 MagickBooleanType
3151 status;
3152
cristybb503372010-05-27 20:51:26 +00003153 MagickOffsetType
3154 progress;
3155
cristy4c08aed2011-07-01 19:47:50 +00003156 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003157 bias;
3158
cristybb503372010-05-27 20:51:26 +00003159 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003160 i;
cristy3ed852e2009-09-05 21:47:34 +00003161
cristybb503372010-05-27 20:51:26 +00003162 size_t
cristy3ed852e2009-09-05 21:47:34 +00003163 width;
3164
cristybb503372010-05-27 20:51:26 +00003165 ssize_t
3166 j,
3167 u,
3168 v,
3169 y;
3170
cristy3ed852e2009-09-05 21:47:34 +00003171 /*
3172 Initialize blur image attributes.
3173 */
3174 assert(image != (Image *) NULL);
3175 assert(image->signature == MagickSignature);
3176 if (image->debug != MagickFalse)
3177 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3178 assert(exception != (ExceptionInfo *) NULL);
3179 assert(exception->signature == MagickSignature);
3180 width=GetOptimalKernelWidth1D(radius,sigma);
3181 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
3182 if (kernel == (double *) NULL)
3183 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00003184 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00003185 i=0;
cristy47e00502009-12-17 19:19:57 +00003186 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00003187 {
cristy47e00502009-12-17 19:19:57 +00003188 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00003189 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
3190 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00003191 }
3192 if (image->debug != MagickFalse)
3193 {
3194 char
3195 format[MaxTextExtent],
3196 *message;
3197
cristy117ff172010-08-15 21:35:32 +00003198 register const double
3199 *k;
3200
cristybb503372010-05-27 20:51:26 +00003201 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003202 u,
3203 v;
3204
cristy3ed852e2009-09-05 21:47:34 +00003205 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00003206 " SelectiveBlurImage with %.20gx%.20g kernel:",(double) width,(double)
3207 width);
cristy3ed852e2009-09-05 21:47:34 +00003208 message=AcquireString("");
3209 k=kernel;
cristybb503372010-05-27 20:51:26 +00003210 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003211 {
3212 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00003213 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy3ed852e2009-09-05 21:47:34 +00003214 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00003215 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003216 {
cristyb51dff52011-05-19 16:55:47 +00003217 (void) FormatLocaleString(format,MaxTextExtent,"%+f ",*k++);
cristy3ed852e2009-09-05 21:47:34 +00003218 (void) ConcatenateString(&message,format);
3219 }
3220 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
3221 }
3222 message=DestroyString(message);
3223 }
3224 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3225 if (blur_image == (Image *) NULL)
3226 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00003227 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003228 {
cristy3ed852e2009-09-05 21:47:34 +00003229 blur_image=DestroyImage(blur_image);
3230 return((Image *) NULL);
3231 }
3232 /*
3233 Threshold blur image.
3234 */
3235 status=MagickTrue;
3236 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00003237 GetPixelInfo(image,&bias);
3238 SetPixelInfoBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003239 image_view=AcquireCacheView(image);
3240 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003241#if defined(MAGICKCORE_OPENMP_SUPPORT)
3242 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003243#endif
cristybb503372010-05-27 20:51:26 +00003244 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003245 {
cristy4c08aed2011-07-01 19:47:50 +00003246 double
3247 contrast;
3248
cristy3ed852e2009-09-05 21:47:34 +00003249 MagickBooleanType
3250 sync;
3251
3252 MagickRealType
3253 gamma;
3254
cristy4c08aed2011-07-01 19:47:50 +00003255 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003256 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003257
cristy4c08aed2011-07-01 19:47:50 +00003258 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003259 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003260
cristy117ff172010-08-15 21:35:32 +00003261 register ssize_t
3262 x;
3263
cristy3ed852e2009-09-05 21:47:34 +00003264 if (status == MagickFalse)
3265 continue;
cristy117ff172010-08-15 21:35:32 +00003266 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
3267 (width/2L),image->columns+width,width,exception);
cristy3ed852e2009-09-05 21:47:34 +00003268 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3269 exception);
cristy4c08aed2011-07-01 19:47:50 +00003270 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003271 {
3272 status=MagickFalse;
3273 continue;
3274 }
cristybb503372010-05-27 20:51:26 +00003275 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003276 {
cristy4c08aed2011-07-01 19:47:50 +00003277 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003278 pixel;
3279
3280 register const double
cristyc47d1f82009-11-26 01:44:43 +00003281 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00003282
cristybb503372010-05-27 20:51:26 +00003283 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003284 u;
3285
cristy117ff172010-08-15 21:35:32 +00003286 ssize_t
3287 j,
3288 v;
3289
cristyddd82202009-11-03 20:14:50 +00003290 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00003291 k=kernel;
3292 gamma=0.0;
3293 j=0;
cristyed231572011-07-14 02:18:59 +00003294 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00003295 {
cristybb503372010-05-27 20:51:26 +00003296 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003297 {
cristybb503372010-05-27 20:51:26 +00003298 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003299 {
cristyed231572011-07-14 02:18:59 +00003300 contrast=GetPixelIntensity(image,p+(u+j)*GetPixelChannels(image))-
cristy4c08aed2011-07-01 19:47:50 +00003301 (double) GetPixelIntensity(blur_image,q);
3302 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003303 {
cristy4c08aed2011-07-01 19:47:50 +00003304 pixel.red+=(*k)*
cristyed231572011-07-14 02:18:59 +00003305 GetPixelRed(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003306 pixel.green+=(*k)*
cristyed231572011-07-14 02:18:59 +00003307 GetPixelGreen(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003308 pixel.blue+=(*k)*
cristyed231572011-07-14 02:18:59 +00003309 GetPixelBlue(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003310 if (image->colorspace == CMYKColorspace)
3311 pixel.black+=(*k)*
cristyed231572011-07-14 02:18:59 +00003312 GetPixelBlack(image,p+(u+j)*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003313 gamma+=(*k);
3314 k++;
3315 }
3316 }
cristyd99b0962010-05-29 23:14:26 +00003317 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003318 }
3319 if (gamma != 0.0)
3320 {
3321 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +00003322 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003323 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +00003324 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003325 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +00003326 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003327 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003328 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00003329 (image->colorspace == CMYKColorspace))
3330 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00003331 }
cristyed231572011-07-14 02:18:59 +00003332 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003333 {
3334 gamma=0.0;
3335 j=0;
cristybb503372010-05-27 20:51:26 +00003336 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003337 {
cristybb503372010-05-27 20:51:26 +00003338 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003339 {
cristy4c08aed2011-07-01 19:47:50 +00003340 contrast=GetPixelIntensity(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003341 GetPixelChannels(image))-(double)
cristy4c08aed2011-07-01 19:47:50 +00003342 GetPixelIntensity(blur_image,q);
3343 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003344 {
cristy4c08aed2011-07-01 19:47:50 +00003345 pixel.alpha+=(*k)*
cristyed231572011-07-14 02:18:59 +00003346 GetPixelAlpha(image,p+(u+j)*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003347 gamma+=(*k);
3348 k++;
3349 }
3350 }
cristyeaedf062010-05-29 22:36:02 +00003351 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003352 }
3353 if (gamma != 0.0)
3354 {
3355 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
3356 gamma);
cristy4c08aed2011-07-01 19:47:50 +00003357 SetPixelAlpha(blur_image,ClampToQuantum(gamma*pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003358 }
3359 }
3360 }
3361 else
3362 {
3363 MagickRealType
3364 alpha;
3365
cristybb503372010-05-27 20:51:26 +00003366 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003367 {
cristybb503372010-05-27 20:51:26 +00003368 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003369 {
cristy4c08aed2011-07-01 19:47:50 +00003370 contrast=GetPixelIntensity(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003371 GetPixelChannels(image))-(double)
cristy4c08aed2011-07-01 19:47:50 +00003372 GetPixelIntensity(blur_image,q);
3373 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003374 {
cristy4c08aed2011-07-01 19:47:50 +00003375 alpha=(MagickRealType) (QuantumScale*
cristyed231572011-07-14 02:18:59 +00003376 GetPixelAlpha(image,p+(u+j)*GetPixelChannels(image)));
cristy4c08aed2011-07-01 19:47:50 +00003377 pixel.red+=(*k)*alpha*
cristyed231572011-07-14 02:18:59 +00003378 GetPixelRed(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003379 pixel.green+=(*k)*alpha*GetPixelGreen(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003380 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003381 pixel.blue+=(*k)*alpha*GetPixelBlue(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003382 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003383 pixel.alpha+=(*k)*GetPixelAlpha(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003384 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003385 if (image->colorspace == CMYKColorspace)
3386 pixel.black+=(*k)*GetPixelBlack(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003387 GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003388 gamma+=(*k)*alpha;
3389 k++;
3390 }
3391 }
cristyeaedf062010-05-29 22:36:02 +00003392 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003393 }
3394 if (gamma != 0.0)
3395 {
3396 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +00003397 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003398 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +00003399 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003400 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +00003401 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003402 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003403 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00003404 (image->colorspace == CMYKColorspace))
3405 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00003406 }
cristyed231572011-07-14 02:18:59 +00003407 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003408 {
3409 gamma=0.0;
3410 j=0;
cristybb503372010-05-27 20:51:26 +00003411 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003412 {
cristybb503372010-05-27 20:51:26 +00003413 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003414 {
cristy4c08aed2011-07-01 19:47:50 +00003415 contrast=GetPixelIntensity(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003416 GetPixelChannels(image))-(double)
cristy4c08aed2011-07-01 19:47:50 +00003417 GetPixelIntensity(blur_image,q);
3418 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003419 {
cristy4c08aed2011-07-01 19:47:50 +00003420 pixel.alpha+=(*k)*
cristyed231572011-07-14 02:18:59 +00003421 GetPixelAlpha(image,p+(u+j)*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003422 gamma+=(*k);
3423 k++;
3424 }
3425 }
cristyeaedf062010-05-29 22:36:02 +00003426 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003427 }
3428 if (gamma != 0.0)
3429 {
3430 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
3431 gamma);
cristy4c08aed2011-07-01 19:47:50 +00003432 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003433 }
3434 }
3435 }
cristyed231572011-07-14 02:18:59 +00003436 p+=GetPixelChannels(image);
3437 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003438 }
3439 sync=SyncCacheViewAuthenticPixels(blur_view,exception);
3440 if (sync == MagickFalse)
3441 status=MagickFalse;
3442 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3443 {
3444 MagickBooleanType
3445 proceed;
3446
cristyb5d5f722009-11-04 03:03:49 +00003447#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00003448 #pragma omp critical (MagickCore_SelectiveBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00003449#endif
3450 proceed=SetImageProgress(image,SelectiveBlurImageTag,progress++,
3451 image->rows);
3452 if (proceed == MagickFalse)
3453 status=MagickFalse;
3454 }
3455 }
3456 blur_image->type=image->type;
3457 blur_view=DestroyCacheView(blur_view);
3458 image_view=DestroyCacheView(image_view);
3459 kernel=(double *) RelinquishMagickMemory(kernel);
3460 if (status == MagickFalse)
3461 blur_image=DestroyImage(blur_image);
3462 return(blur_image);
3463}
3464
3465/*
3466%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3467% %
3468% %
3469% %
3470% S h a d e I m a g e %
3471% %
3472% %
3473% %
3474%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3475%
3476% ShadeImage() shines a distant light on an image to create a
3477% three-dimensional effect. You control the positioning of the light with
3478% azimuth and elevation; azimuth is measured in degrees off the x axis
3479% and elevation is measured in pixels above the Z axis.
3480%
3481% The format of the ShadeImage method is:
3482%
3483% Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3484% const double azimuth,const double elevation,ExceptionInfo *exception)
3485%
3486% A description of each parameter follows:
3487%
3488% o image: the image.
3489%
3490% o gray: A value other than zero shades the intensity of each pixel.
3491%
3492% o azimuth, elevation: Define the light source direction.
3493%
3494% o exception: return any errors or warnings in this structure.
3495%
3496*/
3497MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3498 const double azimuth,const double elevation,ExceptionInfo *exception)
3499{
3500#define ShadeImageTag "Shade/Image"
3501
cristyc4c8d132010-01-07 01:58:38 +00003502 CacheView
3503 *image_view,
3504 *shade_view;
3505
cristy3ed852e2009-09-05 21:47:34 +00003506 Image
3507 *shade_image;
3508
cristy3ed852e2009-09-05 21:47:34 +00003509 MagickBooleanType
3510 status;
3511
cristybb503372010-05-27 20:51:26 +00003512 MagickOffsetType
3513 progress;
3514
cristy3ed852e2009-09-05 21:47:34 +00003515 PrimaryInfo
3516 light;
3517
cristybb503372010-05-27 20:51:26 +00003518 ssize_t
3519 y;
3520
cristy3ed852e2009-09-05 21:47:34 +00003521 /*
3522 Initialize shaded image attributes.
3523 */
3524 assert(image != (const Image *) NULL);
3525 assert(image->signature == MagickSignature);
3526 if (image->debug != MagickFalse)
3527 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3528 assert(exception != (ExceptionInfo *) NULL);
3529 assert(exception->signature == MagickSignature);
3530 shade_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
3531 if (shade_image == (Image *) NULL)
3532 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00003533 if (SetImageStorageClass(shade_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003534 {
cristy3ed852e2009-09-05 21:47:34 +00003535 shade_image=DestroyImage(shade_image);
3536 return((Image *) NULL);
3537 }
3538 /*
3539 Compute the light vector.
3540 */
3541 light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))*
3542 cos(DegreesToRadians(elevation));
3543 light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))*
3544 cos(DegreesToRadians(elevation));
3545 light.z=(double) QuantumRange*sin(DegreesToRadians(elevation));
3546 /*
3547 Shade image.
3548 */
3549 status=MagickTrue;
3550 progress=0;
3551 image_view=AcquireCacheView(image);
3552 shade_view=AcquireCacheView(shade_image);
cristyb5d5f722009-11-04 03:03:49 +00003553#if defined(MAGICKCORE_OPENMP_SUPPORT)
3554 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003555#endif
cristybb503372010-05-27 20:51:26 +00003556 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003557 {
3558 MagickRealType
3559 distance,
3560 normal_distance,
3561 shade;
3562
3563 PrimaryInfo
3564 normal;
3565
cristy4c08aed2011-07-01 19:47:50 +00003566 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003567 *restrict p,
3568 *restrict s0,
3569 *restrict s1,
3570 *restrict s2;
cristy3ed852e2009-09-05 21:47:34 +00003571
cristy4c08aed2011-07-01 19:47:50 +00003572 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003573 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003574
cristy117ff172010-08-15 21:35:32 +00003575 register ssize_t
3576 x;
3577
cristy3ed852e2009-09-05 21:47:34 +00003578 if (status == MagickFalse)
3579 continue;
3580 p=GetCacheViewVirtualPixels(image_view,-1,y-1,image->columns+2,3,exception);
3581 q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1,
3582 exception);
cristy4c08aed2011-07-01 19:47:50 +00003583 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003584 {
3585 status=MagickFalse;
3586 continue;
3587 }
3588 /*
3589 Shade this row of pixels.
3590 */
3591 normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */
cristyed231572011-07-14 02:18:59 +00003592 s0=p+GetPixelChannels(image);
3593 s1=s0+(image->columns+2)*GetPixelChannels(image);
3594 s2=s1+(image->columns+2)*GetPixelChannels(image);
cristybb503372010-05-27 20:51:26 +00003595 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003596 {
3597 /*
3598 Determine the surface normal and compute shading.
3599 */
cristyed231572011-07-14 02:18:59 +00003600 normal.x=(double) (GetPixelIntensity(image,s0-GetPixelChannels(image))+
3601 GetPixelIntensity(image,s1-GetPixelChannels(image))+
3602 GetPixelIntensity(image,s2-GetPixelChannels(image))-
3603 GetPixelIntensity(image,s0+GetPixelChannels(image))-
3604 GetPixelIntensity(image,s1+GetPixelChannels(image))-
3605 GetPixelIntensity(image,s2+GetPixelChannels(image)));
3606 normal.y=(double) (GetPixelIntensity(image,s2-GetPixelChannels(image))+
cristy4c08aed2011-07-01 19:47:50 +00003607 GetPixelIntensity(image,s2)+
cristyed231572011-07-14 02:18:59 +00003608 GetPixelIntensity(image,s2+GetPixelChannels(image))-
3609 GetPixelIntensity(image,s0-GetPixelChannels(image))-
cristy4c08aed2011-07-01 19:47:50 +00003610 GetPixelIntensity(image,s0)-
cristyed231572011-07-14 02:18:59 +00003611 GetPixelIntensity(image,s0+GetPixelChannels(image)));
cristy3ed852e2009-09-05 21:47:34 +00003612 if ((normal.x == 0.0) && (normal.y == 0.0))
3613 shade=light.z;
3614 else
3615 {
3616 shade=0.0;
3617 distance=normal.x*light.x+normal.y*light.y+normal.z*light.z;
3618 if (distance > MagickEpsilon)
3619 {
3620 normal_distance=
3621 normal.x*normal.x+normal.y*normal.y+normal.z*normal.z;
3622 if (normal_distance > (MagickEpsilon*MagickEpsilon))
3623 shade=distance/sqrt((double) normal_distance);
3624 }
3625 }
3626 if (gray != MagickFalse)
3627 {
cristy4c08aed2011-07-01 19:47:50 +00003628 SetPixelRed(shade_image,ClampToQuantum(shade),q);
3629 SetPixelGreen(shade_image,ClampToQuantum(shade),q);
3630 SetPixelBlue(shade_image,ClampToQuantum(shade),q);
cristy3ed852e2009-09-05 21:47:34 +00003631 }
3632 else
3633 {
cristy4c08aed2011-07-01 19:47:50 +00003634 SetPixelRed(shade_image,ClampToQuantum(QuantumScale*shade*
3635 GetPixelRed(image,s1)),q);
3636 SetPixelGreen(shade_image,ClampToQuantum(QuantumScale*shade*
3637 GetPixelGreen(image,s1)),q);
3638 SetPixelBlue(shade_image,ClampToQuantum(QuantumScale*shade*
3639 GetPixelBlue(image,s1)),q);
cristy3ed852e2009-09-05 21:47:34 +00003640 }
cristy4c08aed2011-07-01 19:47:50 +00003641 SetPixelAlpha(shade_image,GetPixelAlpha(image,s1),q);
cristyed231572011-07-14 02:18:59 +00003642 s0+=GetPixelChannels(image);
3643 s1+=GetPixelChannels(image);
3644 s2+=GetPixelChannels(image);
3645 q+=GetPixelChannels(shade_image);
cristy3ed852e2009-09-05 21:47:34 +00003646 }
3647 if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
3648 status=MagickFalse;
3649 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3650 {
3651 MagickBooleanType
3652 proceed;
3653
cristyb5d5f722009-11-04 03:03:49 +00003654#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003655 #pragma omp critical (MagickCore_ShadeImage)
3656#endif
3657 proceed=SetImageProgress(image,ShadeImageTag,progress++,image->rows);
3658 if (proceed == MagickFalse)
3659 status=MagickFalse;
3660 }
3661 }
3662 shade_view=DestroyCacheView(shade_view);
3663 image_view=DestroyCacheView(image_view);
3664 if (status == MagickFalse)
3665 shade_image=DestroyImage(shade_image);
3666 return(shade_image);
3667}
3668
3669/*
3670%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3671% %
3672% %
3673% %
3674% S h a r p e n I m a g e %
3675% %
3676% %
3677% %
3678%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3679%
3680% SharpenImage() sharpens the image. We convolve the image with a Gaussian
3681% operator of the given radius and standard deviation (sigma). For
3682% reasonable results, radius should be larger than sigma. Use a radius of 0
3683% and SharpenImage() selects a suitable radius for you.
3684%
3685% Using a separable kernel would be faster, but the negative weights cancel
3686% out on the corners of the kernel producing often undesirable ringing in the
3687% filtered result; this can be avoided by using a 2D gaussian shaped image
3688% sharpening kernel instead.
3689%
3690% The format of the SharpenImage method is:
3691%
3692% Image *SharpenImage(const Image *image,const double radius,
cristy05c0c9a2011-09-05 23:16:13 +00003693% const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003694%
3695% A description of each parameter follows:
3696%
3697% o image: the image.
3698%
cristy3ed852e2009-09-05 21:47:34 +00003699% o radius: the radius of the Gaussian, in pixels, not counting the center
3700% pixel.
3701%
3702% o sigma: the standard deviation of the Laplacian, in pixels.
3703%
cristy05c0c9a2011-09-05 23:16:13 +00003704% o bias: bias.
3705%
cristy3ed852e2009-09-05 21:47:34 +00003706% o exception: return any errors or warnings in this structure.
3707%
3708*/
cristy3ed852e2009-09-05 21:47:34 +00003709MagickExport Image *SharpenImage(const Image *image,const double radius,
cristy05c0c9a2011-09-05 23:16:13 +00003710 const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003711{
cristy3ed852e2009-09-05 21:47:34 +00003712 double
cristy47e00502009-12-17 19:19:57 +00003713 normalize;
cristy3ed852e2009-09-05 21:47:34 +00003714
3715 Image
3716 *sharp_image;
3717
cristy41cbe682011-07-15 19:12:37 +00003718 KernelInfo
3719 *kernel_info;
3720
cristybb503372010-05-27 20:51:26 +00003721 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003722 i;
3723
cristybb503372010-05-27 20:51:26 +00003724 size_t
cristy3ed852e2009-09-05 21:47:34 +00003725 width;
3726
cristy117ff172010-08-15 21:35:32 +00003727 ssize_t
3728 j,
3729 u,
3730 v;
3731
cristy3ed852e2009-09-05 21:47:34 +00003732 assert(image != (const Image *) NULL);
3733 assert(image->signature == MagickSignature);
3734 if (image->debug != MagickFalse)
3735 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3736 assert(exception != (ExceptionInfo *) NULL);
3737 assert(exception->signature == MagickSignature);
3738 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00003739 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00003740 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003741 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00003742 (void) ResetMagickMemory(kernel_info,0,sizeof(*kernel_info));
3743 kernel_info->width=width;
3744 kernel_info->height=width;
cristy05c0c9a2011-09-05 23:16:13 +00003745 kernel_info->bias=bias;
cristy41cbe682011-07-15 19:12:37 +00003746 kernel_info->signature=MagickSignature;
3747 kernel_info->values=(double *) AcquireAlignedMemory(kernel_info->width,
3748 kernel_info->width*sizeof(*kernel_info->values));
3749 if (kernel_info->values == (double *) NULL)
3750 {
3751 kernel_info=DestroyKernelInfo(kernel_info);
3752 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3753 }
cristy3ed852e2009-09-05 21:47:34 +00003754 normalize=0.0;
cristy41cbe682011-07-15 19:12:37 +00003755 j=(ssize_t) kernel_info->width/2;
cristy47e00502009-12-17 19:19:57 +00003756 i=0;
3757 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00003758 {
cristy47e00502009-12-17 19:19:57 +00003759 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00003760 {
cristy41cbe682011-07-15 19:12:37 +00003761 kernel_info->values[i]=(double) (-exp(-((double) u*u+v*v)/(2.0*
3762 MagickSigma*MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
3763 normalize+=kernel_info->values[i];
cristy3ed852e2009-09-05 21:47:34 +00003764 i++;
3765 }
3766 }
cristy41cbe682011-07-15 19:12:37 +00003767 kernel_info->values[i/2]=(double) ((-2.0)*normalize);
cristy5e6be1e2011-07-16 01:23:39 +00003768 sharp_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00003769 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00003770 return(sharp_image);
3771}
3772
3773/*
3774%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3775% %
3776% %
3777% %
3778% S p r e a d I m a g e %
3779% %
3780% %
3781% %
3782%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3783%
3784% SpreadImage() is a special effects method that randomly displaces each
3785% pixel in a block defined by the radius parameter.
3786%
3787% The format of the SpreadImage method is:
3788%
3789% Image *SpreadImage(const Image *image,const double radius,
3790% ExceptionInfo *exception)
3791%
3792% A description of each parameter follows:
3793%
3794% o image: the image.
3795%
3796% o radius: Choose a random pixel in a neighborhood of this extent.
3797%
3798% o exception: return any errors or warnings in this structure.
3799%
3800*/
3801MagickExport Image *SpreadImage(const Image *image,const double radius,
3802 ExceptionInfo *exception)
3803{
3804#define SpreadImageTag "Spread/Image"
3805
cristyfa112112010-01-04 17:48:07 +00003806 CacheView
cristy9f7e7cb2011-03-26 00:49:57 +00003807 *image_view,
3808 *spread_view;
cristyfa112112010-01-04 17:48:07 +00003809
cristy3ed852e2009-09-05 21:47:34 +00003810 Image
3811 *spread_image;
3812
cristy3ed852e2009-09-05 21:47:34 +00003813 MagickBooleanType
3814 status;
3815
cristybb503372010-05-27 20:51:26 +00003816 MagickOffsetType
3817 progress;
3818
cristy4c08aed2011-07-01 19:47:50 +00003819 PixelInfo
cristyddd82202009-11-03 20:14:50 +00003820 bias;
cristy3ed852e2009-09-05 21:47:34 +00003821
3822 RandomInfo
cristyfa112112010-01-04 17:48:07 +00003823 **restrict random_info;
cristy3ed852e2009-09-05 21:47:34 +00003824
cristybb503372010-05-27 20:51:26 +00003825 size_t
cristy3ed852e2009-09-05 21:47:34 +00003826 width;
3827
cristybb503372010-05-27 20:51:26 +00003828 ssize_t
3829 y;
3830
cristy3ed852e2009-09-05 21:47:34 +00003831 /*
3832 Initialize spread image attributes.
3833 */
3834 assert(image != (Image *) NULL);
3835 assert(image->signature == MagickSignature);
3836 if (image->debug != MagickFalse)
3837 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3838 assert(exception != (ExceptionInfo *) NULL);
3839 assert(exception->signature == MagickSignature);
3840 spread_image=CloneImage(image,image->columns,image->rows,MagickTrue,
3841 exception);
3842 if (spread_image == (Image *) NULL)
3843 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00003844 if (SetImageStorageClass(spread_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003845 {
cristy3ed852e2009-09-05 21:47:34 +00003846 spread_image=DestroyImage(spread_image);
3847 return((Image *) NULL);
3848 }
3849 /*
3850 Spread image.
3851 */
3852 status=MagickTrue;
3853 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00003854 GetPixelInfo(spread_image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003855 width=GetOptimalKernelWidth1D(radius,0.5);
cristy3ed852e2009-09-05 21:47:34 +00003856 random_info=AcquireRandomInfoThreadSet();
cristy9f7e7cb2011-03-26 00:49:57 +00003857 image_view=AcquireCacheView(image);
3858 spread_view=AcquireCacheView(spread_image);
cristyb557a152011-02-22 12:14:30 +00003859#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00003860 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00003861#endif
cristybb503372010-05-27 20:51:26 +00003862 for (y=0; y < (ssize_t) spread_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003863 {
cristy5c9e6f22010-09-17 17:31:01 +00003864 const int
3865 id = GetOpenMPThreadId();
cristy6ebe97c2010-07-03 01:17:28 +00003866
cristy4c08aed2011-07-01 19:47:50 +00003867 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003868 pixel;
3869
cristy4c08aed2011-07-01 19:47:50 +00003870 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003871 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003872
cristy117ff172010-08-15 21:35:32 +00003873 register ssize_t
3874 x;
3875
cristy3ed852e2009-09-05 21:47:34 +00003876 if (status == MagickFalse)
3877 continue;
cristy9f7e7cb2011-03-26 00:49:57 +00003878 q=QueueCacheViewAuthenticPixels(spread_view,0,y,spread_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +00003879 exception);
cristyacd2ed22011-08-30 01:44:23 +00003880 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003881 {
3882 status=MagickFalse;
3883 continue;
3884 }
cristyddd82202009-11-03 20:14:50 +00003885 pixel=bias;
cristybb503372010-05-27 20:51:26 +00003886 for (x=0; x < (ssize_t) spread_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003887 {
cristy4c08aed2011-07-01 19:47:50 +00003888 (void) InterpolatePixelInfo(image,image_view,
cristy8a7c3e82011-03-26 02:10:53 +00003889 UndefinedInterpolatePixel,(double) x+width*(GetPseudoRandomValue(
3890 random_info[id])-0.5),(double) y+width*(GetPseudoRandomValue(
3891 random_info[id])-0.5),&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00003892 SetPixelPixelInfo(spread_image,&pixel,q);
cristyed231572011-07-14 02:18:59 +00003893 q+=GetPixelChannels(spread_image);
cristy3ed852e2009-09-05 21:47:34 +00003894 }
cristy9f7e7cb2011-03-26 00:49:57 +00003895 if (SyncCacheViewAuthenticPixels(spread_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003896 status=MagickFalse;
3897 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3898 {
3899 MagickBooleanType
3900 proceed;
3901
cristyb557a152011-02-22 12:14:30 +00003902#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003903 #pragma omp critical (MagickCore_SpreadImage)
3904#endif
3905 proceed=SetImageProgress(image,SpreadImageTag,progress++,image->rows);
3906 if (proceed == MagickFalse)
3907 status=MagickFalse;
3908 }
3909 }
cristy9f7e7cb2011-03-26 00:49:57 +00003910 spread_view=DestroyCacheView(spread_view);
cristy3ed852e2009-09-05 21:47:34 +00003911 image_view=DestroyCacheView(image_view);
3912 random_info=DestroyRandomInfoThreadSet(random_info);
cristy3ed852e2009-09-05 21:47:34 +00003913 return(spread_image);
3914}
3915
3916/*
3917%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3918% %
3919% %
3920% %
cristy0834d642011-03-18 18:26:08 +00003921% S t a t i s t i c I m a g e %
3922% %
3923% %
3924% %
3925%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3926%
3927% StatisticImage() makes each pixel the min / max / median / mode / etc. of
cristy8d752042011-03-19 01:00:36 +00003928% the neighborhood of the specified width and height.
cristy0834d642011-03-18 18:26:08 +00003929%
3930% The format of the StatisticImage method is:
3931%
3932% Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00003933% const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00003934%
3935% A description of each parameter follows:
3936%
3937% o image: the image.
3938%
cristy0834d642011-03-18 18:26:08 +00003939% o type: the statistic type (median, mode, etc.).
3940%
cristy95c38342011-03-18 22:39:51 +00003941% o width: the width of the pixel neighborhood.
3942%
3943% o height: the height of the pixel neighborhood.
cristy0834d642011-03-18 18:26:08 +00003944%
3945% o exception: return any errors or warnings in this structure.
3946%
3947*/
3948
cristy733678d2011-03-18 21:29:28 +00003949#define ListChannels 5
3950
3951typedef struct _ListNode
3952{
3953 size_t
3954 next[9],
3955 count,
3956 signature;
3957} ListNode;
3958
3959typedef struct _SkipList
3960{
3961 ssize_t
3962 level;
3963
3964 ListNode
3965 *nodes;
3966} SkipList;
3967
3968typedef struct _PixelList
3969{
3970 size_t
cristy6fc86bb2011-03-18 23:45:16 +00003971 length,
cristy733678d2011-03-18 21:29:28 +00003972 seed,
3973 signature;
3974
3975 SkipList
3976 lists[ListChannels];
3977} PixelList;
3978
3979static PixelList *DestroyPixelList(PixelList *pixel_list)
3980{
3981 register ssize_t
3982 i;
3983
3984 if (pixel_list == (PixelList *) NULL)
3985 return((PixelList *) NULL);
3986 for (i=0; i < ListChannels; i++)
3987 if (pixel_list->lists[i].nodes != (ListNode *) NULL)
3988 pixel_list->lists[i].nodes=(ListNode *) RelinquishMagickMemory(
3989 pixel_list->lists[i].nodes);
3990 pixel_list=(PixelList *) RelinquishMagickMemory(pixel_list);
3991 return(pixel_list);
3992}
3993
3994static PixelList **DestroyPixelListThreadSet(PixelList **pixel_list)
3995{
3996 register ssize_t
3997 i;
3998
3999 assert(pixel_list != (PixelList **) NULL);
4000 for (i=0; i < (ssize_t) GetOpenMPMaximumThreads(); i++)
4001 if (pixel_list[i] != (PixelList *) NULL)
4002 pixel_list[i]=DestroyPixelList(pixel_list[i]);
4003 pixel_list=(PixelList **) RelinquishMagickMemory(pixel_list);
4004 return(pixel_list);
4005}
4006
cristy6fc86bb2011-03-18 23:45:16 +00004007static PixelList *AcquirePixelList(const size_t width,const size_t height)
cristy733678d2011-03-18 21:29:28 +00004008{
4009 PixelList
4010 *pixel_list;
4011
4012 register ssize_t
4013 i;
4014
4015 pixel_list=(PixelList *) AcquireMagickMemory(sizeof(*pixel_list));
4016 if (pixel_list == (PixelList *) NULL)
4017 return(pixel_list);
4018 (void) ResetMagickMemory((void *) pixel_list,0,sizeof(*pixel_list));
cristy6fc86bb2011-03-18 23:45:16 +00004019 pixel_list->length=width*height;
cristy733678d2011-03-18 21:29:28 +00004020 for (i=0; i < ListChannels; i++)
4021 {
4022 pixel_list->lists[i].nodes=(ListNode *) AcquireQuantumMemory(65537UL,
4023 sizeof(*pixel_list->lists[i].nodes));
4024 if (pixel_list->lists[i].nodes == (ListNode *) NULL)
4025 return(DestroyPixelList(pixel_list));
4026 (void) ResetMagickMemory(pixel_list->lists[i].nodes,0,65537UL*
4027 sizeof(*pixel_list->lists[i].nodes));
4028 }
4029 pixel_list->signature=MagickSignature;
4030 return(pixel_list);
4031}
4032
cristy6fc86bb2011-03-18 23:45:16 +00004033static PixelList **AcquirePixelListThreadSet(const size_t width,
4034 const size_t height)
cristy733678d2011-03-18 21:29:28 +00004035{
4036 PixelList
4037 **pixel_list;
4038
4039 register ssize_t
4040 i;
4041
4042 size_t
4043 number_threads;
4044
4045 number_threads=GetOpenMPMaximumThreads();
4046 pixel_list=(PixelList **) AcquireQuantumMemory(number_threads,
4047 sizeof(*pixel_list));
4048 if (pixel_list == (PixelList **) NULL)
4049 return((PixelList **) NULL);
4050 (void) ResetMagickMemory(pixel_list,0,number_threads*sizeof(*pixel_list));
4051 for (i=0; i < (ssize_t) number_threads; i++)
4052 {
cristy6fc86bb2011-03-18 23:45:16 +00004053 pixel_list[i]=AcquirePixelList(width,height);
cristy733678d2011-03-18 21:29:28 +00004054 if (pixel_list[i] == (PixelList *) NULL)
4055 return(DestroyPixelListThreadSet(pixel_list));
4056 }
4057 return(pixel_list);
4058}
4059
4060static void AddNodePixelList(PixelList *pixel_list,const ssize_t channel,
4061 const size_t color)
4062{
4063 register SkipList
4064 *list;
4065
4066 register ssize_t
4067 level;
4068
4069 size_t
4070 search,
4071 update[9];
4072
4073 /*
4074 Initialize the node.
4075 */
4076 list=pixel_list->lists+channel;
4077 list->nodes[color].signature=pixel_list->signature;
4078 list->nodes[color].count=1;
4079 /*
4080 Determine where it belongs in the list.
4081 */
4082 search=65536UL;
4083 for (level=list->level; level >= 0; level--)
4084 {
4085 while (list->nodes[search].next[level] < color)
4086 search=list->nodes[search].next[level];
4087 update[level]=search;
4088 }
4089 /*
4090 Generate a pseudo-random level for this node.
4091 */
4092 for (level=0; ; level++)
4093 {
4094 pixel_list->seed=(pixel_list->seed*42893621L)+1L;
4095 if ((pixel_list->seed & 0x300) != 0x300)
4096 break;
4097 }
4098 if (level > 8)
4099 level=8;
4100 if (level > (list->level+2))
4101 level=list->level+2;
4102 /*
4103 If we're raising the list's level, link back to the root node.
4104 */
4105 while (level > list->level)
4106 {
4107 list->level++;
4108 update[list->level]=65536UL;
4109 }
4110 /*
4111 Link the node into the skip-list.
4112 */
4113 do
4114 {
4115 list->nodes[color].next[level]=list->nodes[update[level]].next[level];
4116 list->nodes[update[level]].next[level]=color;
cristy3cba8ca2011-03-19 01:29:12 +00004117 } while (level-- > 0);
cristy733678d2011-03-18 21:29:28 +00004118}
4119
cristy4c08aed2011-07-01 19:47:50 +00004120static PixelInfo GetMaximumPixelList(PixelList *pixel_list)
cristy6fc86bb2011-03-18 23:45:16 +00004121{
cristy4c08aed2011-07-01 19:47:50 +00004122 PixelInfo
cristy6fc86bb2011-03-18 23:45:16 +00004123 pixel;
4124
4125 register SkipList
4126 *list;
4127
4128 register ssize_t
4129 channel;
4130
4131 size_t
cristyd76c51e2011-03-26 00:21:26 +00004132 color,
4133 maximum;
cristy49f37242011-03-22 18:18:23 +00004134
4135 ssize_t
cristy6fc86bb2011-03-18 23:45:16 +00004136 count;
4137
4138 unsigned short
cristyd76c51e2011-03-26 00:21:26 +00004139 channels[ListChannels];
cristy6fc86bb2011-03-18 23:45:16 +00004140
4141 /*
4142 Find the maximum value for each of the color.
4143 */
4144 for (channel=0; channel < 5; channel++)
4145 {
4146 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004147 color=65536L;
cristy6fc86bb2011-03-18 23:45:16 +00004148 count=0;
cristy49f37242011-03-22 18:18:23 +00004149 maximum=list->nodes[color].next[0];
cristy6fc86bb2011-03-18 23:45:16 +00004150 do
4151 {
4152 color=list->nodes[color].next[0];
cristy49f37242011-03-22 18:18:23 +00004153 if (color > maximum)
4154 maximum=color;
cristy6fc86bb2011-03-18 23:45:16 +00004155 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004156 } while (count < (ssize_t) pixel_list->length);
cristy49f37242011-03-22 18:18:23 +00004157 channels[channel]=(unsigned short) maximum;
4158 }
cristy4c08aed2011-07-01 19:47:50 +00004159 GetPixelInfo((const Image *) NULL,&pixel);
cristy49f37242011-03-22 18:18:23 +00004160 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4161 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4162 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004163 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4164 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy49f37242011-03-22 18:18:23 +00004165 return(pixel);
4166}
4167
cristy4c08aed2011-07-01 19:47:50 +00004168static PixelInfo GetMeanPixelList(PixelList *pixel_list)
cristy49f37242011-03-22 18:18:23 +00004169{
cristy4c08aed2011-07-01 19:47:50 +00004170 PixelInfo
cristy49f37242011-03-22 18:18:23 +00004171 pixel;
4172
cristy80a99a32011-03-30 01:30:23 +00004173 MagickRealType
4174 sum;
4175
cristy49f37242011-03-22 18:18:23 +00004176 register SkipList
4177 *list;
4178
4179 register ssize_t
4180 channel;
4181
4182 size_t
cristy80a99a32011-03-30 01:30:23 +00004183 color;
cristy49f37242011-03-22 18:18:23 +00004184
4185 ssize_t
4186 count;
4187
4188 unsigned short
4189 channels[ListChannels];
4190
4191 /*
4192 Find the mean value for each of the color.
4193 */
4194 for (channel=0; channel < 5; channel++)
4195 {
4196 list=pixel_list->lists+channel;
4197 color=65536L;
4198 count=0;
cristy80a99a32011-03-30 01:30:23 +00004199 sum=0.0;
cristy49f37242011-03-22 18:18:23 +00004200 do
4201 {
4202 color=list->nodes[color].next[0];
cristy80a99a32011-03-30 01:30:23 +00004203 sum+=(MagickRealType) list->nodes[color].count*color;
cristy49f37242011-03-22 18:18:23 +00004204 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004205 } while (count < (ssize_t) pixel_list->length);
cristy80a99a32011-03-30 01:30:23 +00004206 sum/=pixel_list->length;
4207 channels[channel]=(unsigned short) sum;
cristy6fc86bb2011-03-18 23:45:16 +00004208 }
cristy4c08aed2011-07-01 19:47:50 +00004209 GetPixelInfo((const Image *) NULL,&pixel);
cristy6fc86bb2011-03-18 23:45:16 +00004210 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4211 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4212 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004213 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4214 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy6fc86bb2011-03-18 23:45:16 +00004215 return(pixel);
4216}
4217
cristy4c08aed2011-07-01 19:47:50 +00004218static PixelInfo GetMedianPixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004219{
cristy4c08aed2011-07-01 19:47:50 +00004220 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004221 pixel;
4222
4223 register SkipList
4224 *list;
4225
4226 register ssize_t
4227 channel;
4228
4229 size_t
cristy49f37242011-03-22 18:18:23 +00004230 color;
4231
4232 ssize_t
cristy733678d2011-03-18 21:29:28 +00004233 count;
4234
4235 unsigned short
4236 channels[ListChannels];
4237
4238 /*
4239 Find the median value for each of the color.
4240 */
cristy733678d2011-03-18 21:29:28 +00004241 for (channel=0; channel < 5; channel++)
4242 {
4243 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004244 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004245 count=0;
4246 do
4247 {
4248 color=list->nodes[color].next[0];
4249 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004250 } while (count <= (ssize_t) (pixel_list->length >> 1));
cristy6fc86bb2011-03-18 23:45:16 +00004251 channels[channel]=(unsigned short) color;
4252 }
cristy4c08aed2011-07-01 19:47:50 +00004253 GetPixelInfo((const Image *) NULL,&pixel);
cristy6fc86bb2011-03-18 23:45:16 +00004254 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4255 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4256 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004257 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4258 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy6fc86bb2011-03-18 23:45:16 +00004259 return(pixel);
4260}
4261
cristy4c08aed2011-07-01 19:47:50 +00004262static PixelInfo GetMinimumPixelList(PixelList *pixel_list)
cristy6fc86bb2011-03-18 23:45:16 +00004263{
cristy4c08aed2011-07-01 19:47:50 +00004264 PixelInfo
cristy6fc86bb2011-03-18 23:45:16 +00004265 pixel;
4266
4267 register SkipList
4268 *list;
4269
4270 register ssize_t
4271 channel;
4272
4273 size_t
cristyd76c51e2011-03-26 00:21:26 +00004274 color,
4275 minimum;
cristy6fc86bb2011-03-18 23:45:16 +00004276
cristy49f37242011-03-22 18:18:23 +00004277 ssize_t
4278 count;
4279
cristy6fc86bb2011-03-18 23:45:16 +00004280 unsigned short
cristyd76c51e2011-03-26 00:21:26 +00004281 channels[ListChannels];
cristy6fc86bb2011-03-18 23:45:16 +00004282
4283 /*
4284 Find the minimum value for each of the color.
4285 */
4286 for (channel=0; channel < 5; channel++)
4287 {
4288 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004289 count=0;
cristy6fc86bb2011-03-18 23:45:16 +00004290 color=65536UL;
cristy49f37242011-03-22 18:18:23 +00004291 minimum=list->nodes[color].next[0];
4292 do
4293 {
4294 color=list->nodes[color].next[0];
4295 if (color < minimum)
4296 minimum=color;
4297 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004298 } while (count < (ssize_t) pixel_list->length);
cristy49f37242011-03-22 18:18:23 +00004299 channels[channel]=(unsigned short) minimum;
cristy733678d2011-03-18 21:29:28 +00004300 }
cristy4c08aed2011-07-01 19:47:50 +00004301 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004302 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4303 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4304 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004305 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4306 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy733678d2011-03-18 21:29:28 +00004307 return(pixel);
4308}
4309
cristy4c08aed2011-07-01 19:47:50 +00004310static PixelInfo GetModePixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004311{
cristy4c08aed2011-07-01 19:47:50 +00004312 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004313 pixel;
4314
4315 register SkipList
4316 *list;
4317
4318 register ssize_t
4319 channel;
4320
4321 size_t
4322 color,
cristy733678d2011-03-18 21:29:28 +00004323 max_count,
cristy6fc86bb2011-03-18 23:45:16 +00004324 mode;
cristy733678d2011-03-18 21:29:28 +00004325
cristy49f37242011-03-22 18:18:23 +00004326 ssize_t
4327 count;
4328
cristy733678d2011-03-18 21:29:28 +00004329 unsigned short
4330 channels[5];
4331
4332 /*
glennrp30d2dc62011-06-25 03:17:16 +00004333 Make each pixel the 'predominant color' of the specified neighborhood.
cristy733678d2011-03-18 21:29:28 +00004334 */
cristy733678d2011-03-18 21:29:28 +00004335 for (channel=0; channel < 5; channel++)
4336 {
4337 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004338 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004339 mode=color;
4340 max_count=list->nodes[mode].count;
4341 count=0;
4342 do
4343 {
4344 color=list->nodes[color].next[0];
4345 if (list->nodes[color].count > max_count)
4346 {
4347 mode=color;
4348 max_count=list->nodes[mode].count;
4349 }
4350 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004351 } while (count < (ssize_t) pixel_list->length);
cristy733678d2011-03-18 21:29:28 +00004352 channels[channel]=(unsigned short) mode;
4353 }
cristy4c08aed2011-07-01 19:47:50 +00004354 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004355 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4356 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4357 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004358 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4359 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy733678d2011-03-18 21:29:28 +00004360 return(pixel);
4361}
4362
cristy4c08aed2011-07-01 19:47:50 +00004363static PixelInfo GetNonpeakPixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004364{
cristy4c08aed2011-07-01 19:47:50 +00004365 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004366 pixel;
4367
4368 register SkipList
4369 *list;
4370
4371 register ssize_t
4372 channel;
4373
4374 size_t
cristy733678d2011-03-18 21:29:28 +00004375 color,
cristy733678d2011-03-18 21:29:28 +00004376 next,
4377 previous;
4378
cristy49f37242011-03-22 18:18:23 +00004379 ssize_t
4380 count;
4381
cristy733678d2011-03-18 21:29:28 +00004382 unsigned short
4383 channels[5];
4384
4385 /*
cristy49f37242011-03-22 18:18:23 +00004386 Finds the non peak value for each of the colors.
cristy733678d2011-03-18 21:29:28 +00004387 */
cristy733678d2011-03-18 21:29:28 +00004388 for (channel=0; channel < 5; channel++)
4389 {
4390 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004391 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004392 next=list->nodes[color].next[0];
4393 count=0;
4394 do
4395 {
4396 previous=color;
4397 color=next;
4398 next=list->nodes[color].next[0];
4399 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004400 } while (count <= (ssize_t) (pixel_list->length >> 1));
cristy733678d2011-03-18 21:29:28 +00004401 if ((previous == 65536UL) && (next != 65536UL))
4402 color=next;
4403 else
4404 if ((previous != 65536UL) && (next == 65536UL))
4405 color=previous;
4406 channels[channel]=(unsigned short) color;
4407 }
cristy4c08aed2011-07-01 19:47:50 +00004408 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004409 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4410 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4411 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004412 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4413 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy733678d2011-03-18 21:29:28 +00004414 return(pixel);
4415}
4416
cristy4c08aed2011-07-01 19:47:50 +00004417static PixelInfo GetStandardDeviationPixelList(PixelList *pixel_list)
cristy9a68cbb2011-03-29 00:51:23 +00004418{
cristy4c08aed2011-07-01 19:47:50 +00004419 PixelInfo
cristy9a68cbb2011-03-29 00:51:23 +00004420 pixel;
4421
cristy80a99a32011-03-30 01:30:23 +00004422 MagickRealType
4423 sum,
4424 sum_squared;
4425
cristy9a68cbb2011-03-29 00:51:23 +00004426 register SkipList
4427 *list;
4428
4429 register ssize_t
4430 channel;
4431
4432 size_t
cristy80a99a32011-03-30 01:30:23 +00004433 color;
cristy9a68cbb2011-03-29 00:51:23 +00004434
4435 ssize_t
4436 count;
4437
4438 unsigned short
4439 channels[ListChannels];
4440
4441 /*
cristy80a99a32011-03-30 01:30:23 +00004442 Find the standard-deviation value for each of the color.
cristy9a68cbb2011-03-29 00:51:23 +00004443 */
4444 for (channel=0; channel < 5; channel++)
4445 {
4446 list=pixel_list->lists+channel;
4447 color=65536L;
4448 count=0;
cristy80a99a32011-03-30 01:30:23 +00004449 sum=0.0;
4450 sum_squared=0.0;
cristy9a68cbb2011-03-29 00:51:23 +00004451 do
4452 {
cristy80a99a32011-03-30 01:30:23 +00004453 register ssize_t
4454 i;
4455
cristy9a68cbb2011-03-29 00:51:23 +00004456 color=list->nodes[color].next[0];
cristy80a99a32011-03-30 01:30:23 +00004457 sum+=(MagickRealType) list->nodes[color].count*color;
4458 for (i=0; i < (ssize_t) list->nodes[color].count; i++)
4459 sum_squared+=((MagickRealType) color)*((MagickRealType) color);
cristy9a68cbb2011-03-29 00:51:23 +00004460 count+=list->nodes[color].count;
4461 } while (count < (ssize_t) pixel_list->length);
cristy80a99a32011-03-30 01:30:23 +00004462 sum/=pixel_list->length;
4463 sum_squared/=pixel_list->length;
4464 channels[channel]=(unsigned short) sqrt(sum_squared-(sum*sum));
cristy9a68cbb2011-03-29 00:51:23 +00004465 }
cristy4c08aed2011-07-01 19:47:50 +00004466 GetPixelInfo((const Image *) NULL,&pixel);
cristy9a68cbb2011-03-29 00:51:23 +00004467 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4468 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4469 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004470 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4471 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy9a68cbb2011-03-29 00:51:23 +00004472 return(pixel);
4473}
4474
cristy4c08aed2011-07-01 19:47:50 +00004475static inline void InsertPixelList(const Image *image,const Quantum *pixel,
4476 PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004477{
4478 size_t
4479 signature;
4480
4481 unsigned short
4482 index;
4483
cristy4c08aed2011-07-01 19:47:50 +00004484 index=ScaleQuantumToShort(GetPixelRed(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004485 signature=pixel_list->lists[0].nodes[index].signature;
4486 if (signature == pixel_list->signature)
4487 pixel_list->lists[0].nodes[index].count++;
4488 else
4489 AddNodePixelList(pixel_list,0,index);
cristy4c08aed2011-07-01 19:47:50 +00004490 index=ScaleQuantumToShort(GetPixelGreen(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004491 signature=pixel_list->lists[1].nodes[index].signature;
4492 if (signature == pixel_list->signature)
4493 pixel_list->lists[1].nodes[index].count++;
4494 else
4495 AddNodePixelList(pixel_list,1,index);
cristy4c08aed2011-07-01 19:47:50 +00004496 index=ScaleQuantumToShort(GetPixelBlue(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004497 signature=pixel_list->lists[2].nodes[index].signature;
4498 if (signature == pixel_list->signature)
4499 pixel_list->lists[2].nodes[index].count++;
4500 else
4501 AddNodePixelList(pixel_list,2,index);
cristy4c08aed2011-07-01 19:47:50 +00004502 index=ScaleQuantumToShort(GetPixelAlpha(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004503 signature=pixel_list->lists[3].nodes[index].signature;
4504 if (signature == pixel_list->signature)
4505 pixel_list->lists[3].nodes[index].count++;
4506 else
4507 AddNodePixelList(pixel_list,3,index);
4508 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00004509 index=ScaleQuantumToShort(GetPixelBlack(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004510 signature=pixel_list->lists[4].nodes[index].signature;
4511 if (signature == pixel_list->signature)
4512 pixel_list->lists[4].nodes[index].count++;
4513 else
4514 AddNodePixelList(pixel_list,4,index);
4515}
4516
cristy80c99742011-04-04 14:46:39 +00004517static inline MagickRealType MagickAbsoluteValue(const MagickRealType x)
4518{
4519 if (x < 0)
4520 return(-x);
4521 return(x);
4522}
4523
cristy733678d2011-03-18 21:29:28 +00004524static void ResetPixelList(PixelList *pixel_list)
4525{
4526 int
4527 level;
4528
4529 register ListNode
4530 *root;
4531
4532 register SkipList
4533 *list;
4534
4535 register ssize_t
4536 channel;
4537
4538 /*
4539 Reset the skip-list.
4540 */
4541 for (channel=0; channel < 5; channel++)
4542 {
4543 list=pixel_list->lists+channel;
4544 root=list->nodes+65536UL;
4545 list->level=0;
4546 for (level=0; level < 9; level++)
4547 root->next[level]=65536UL;
4548 }
4549 pixel_list->seed=pixel_list->signature++;
4550}
4551
cristy0834d642011-03-18 18:26:08 +00004552MagickExport Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00004553 const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00004554{
cristy3cba8ca2011-03-19 01:29:12 +00004555#define StatisticWidth \
cristyd76c51e2011-03-26 00:21:26 +00004556 (width == 0 ? GetOptimalKernelWidth2D((double) width,0.5) : width)
cristy3cba8ca2011-03-19 01:29:12 +00004557#define StatisticHeight \
cristyd76c51e2011-03-26 00:21:26 +00004558 (height == 0 ? GetOptimalKernelWidth2D((double) height,0.5) : height)
cristy0834d642011-03-18 18:26:08 +00004559#define StatisticImageTag "Statistic/Image"
4560
4561 CacheView
4562 *image_view,
4563 *statistic_view;
4564
4565 Image
4566 *statistic_image;
4567
4568 MagickBooleanType
4569 status;
4570
4571 MagickOffsetType
4572 progress;
4573
4574 PixelList
4575 **restrict pixel_list;
4576
cristy0834d642011-03-18 18:26:08 +00004577 ssize_t
4578 y;
4579
4580 /*
4581 Initialize statistics image attributes.
4582 */
4583 assert(image != (Image *) NULL);
4584 assert(image->signature == MagickSignature);
4585 if (image->debug != MagickFalse)
4586 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4587 assert(exception != (ExceptionInfo *) NULL);
4588 assert(exception->signature == MagickSignature);
cristy0834d642011-03-18 18:26:08 +00004589 statistic_image=CloneImage(image,image->columns,image->rows,MagickTrue,
4590 exception);
4591 if (statistic_image == (Image *) NULL)
4592 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00004593 if (SetImageStorageClass(statistic_image,DirectClass,exception) == MagickFalse)
cristy0834d642011-03-18 18:26:08 +00004594 {
cristy0834d642011-03-18 18:26:08 +00004595 statistic_image=DestroyImage(statistic_image);
4596 return((Image *) NULL);
4597 }
cristy6fc86bb2011-03-18 23:45:16 +00004598 pixel_list=AcquirePixelListThreadSet(StatisticWidth,StatisticHeight);
cristy0834d642011-03-18 18:26:08 +00004599 if (pixel_list == (PixelList **) NULL)
4600 {
4601 statistic_image=DestroyImage(statistic_image);
4602 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
4603 }
4604 /*
cristy8d752042011-03-19 01:00:36 +00004605 Make each pixel the min / max / median / mode / etc. of the neighborhood.
cristy0834d642011-03-18 18:26:08 +00004606 */
4607 status=MagickTrue;
4608 progress=0;
4609 image_view=AcquireCacheView(image);
4610 statistic_view=AcquireCacheView(statistic_image);
4611#if defined(MAGICKCORE_OPENMP_SUPPORT)
4612 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
4613#endif
4614 for (y=0; y < (ssize_t) statistic_image->rows; y++)
4615 {
4616 const int
4617 id = GetOpenMPThreadId();
4618
cristy4c08aed2011-07-01 19:47:50 +00004619 register const Quantum
cristy0834d642011-03-18 18:26:08 +00004620 *restrict p;
4621
cristy4c08aed2011-07-01 19:47:50 +00004622 register Quantum
cristy0834d642011-03-18 18:26:08 +00004623 *restrict q;
4624
4625 register ssize_t
4626 x;
4627
4628 if (status == MagickFalse)
4629 continue;
cristy6fc86bb2011-03-18 23:45:16 +00004630 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) StatisticWidth/2L),y-
4631 (ssize_t) (StatisticHeight/2L),image->columns+StatisticWidth,
4632 StatisticHeight,exception);
cristy3cba8ca2011-03-19 01:29:12 +00004633 q=QueueCacheViewAuthenticPixels(statistic_view,0,y,statistic_image->columns, 1,exception);
cristy4c08aed2011-07-01 19:47:50 +00004634 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy0834d642011-03-18 18:26:08 +00004635 {
4636 status=MagickFalse;
4637 continue;
4638 }
cristy0834d642011-03-18 18:26:08 +00004639 for (x=0; x < (ssize_t) statistic_image->columns; x++)
4640 {
cristy4c08aed2011-07-01 19:47:50 +00004641 PixelInfo
cristy0834d642011-03-18 18:26:08 +00004642 pixel;
4643
cristy4c08aed2011-07-01 19:47:50 +00004644 register const Quantum
cristy6e3026a2011-03-19 00:54:38 +00004645 *restrict r;
4646
cristy0834d642011-03-18 18:26:08 +00004647 register ssize_t
4648 u,
4649 v;
4650
4651 r=p;
cristy0834d642011-03-18 18:26:08 +00004652 ResetPixelList(pixel_list[id]);
cristy6e4c3292011-03-19 00:53:55 +00004653 for (v=0; v < (ssize_t) StatisticHeight; v++)
cristy0834d642011-03-18 18:26:08 +00004654 {
cristy6e4c3292011-03-19 00:53:55 +00004655 for (u=0; u < (ssize_t) StatisticWidth; u++)
cristyed231572011-07-14 02:18:59 +00004656 InsertPixelList(image,r+u*GetPixelChannels(image),pixel_list[id]);
4657 r+=(image->columns+StatisticWidth)*GetPixelChannels(image);
cristy0834d642011-03-18 18:26:08 +00004658 }
cristy4c08aed2011-07-01 19:47:50 +00004659 GetPixelInfo(image,&pixel);
cristy490408a2011-07-07 14:42:05 +00004660 SetPixelInfo(image,p+(StatisticWidth*StatisticHeight/2)*
cristyed231572011-07-14 02:18:59 +00004661 GetPixelChannels(image),&pixel);
cristy0834d642011-03-18 18:26:08 +00004662 switch (type)
4663 {
cristy80c99742011-04-04 14:46:39 +00004664 case GradientStatistic:
4665 {
cristy4c08aed2011-07-01 19:47:50 +00004666 PixelInfo
cristy80c99742011-04-04 14:46:39 +00004667 maximum,
4668 minimum;
4669
4670 minimum=GetMinimumPixelList(pixel_list[id]);
4671 maximum=GetMaximumPixelList(pixel_list[id]);
4672 pixel.red=MagickAbsoluteValue(maximum.red-minimum.red);
4673 pixel.green=MagickAbsoluteValue(maximum.green-minimum.green);
4674 pixel.blue=MagickAbsoluteValue(maximum.blue-minimum.blue);
cristy4c08aed2011-07-01 19:47:50 +00004675 pixel.alpha=MagickAbsoluteValue(maximum.alpha-minimum.alpha);
cristy80c99742011-04-04 14:46:39 +00004676 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00004677 pixel.black=MagickAbsoluteValue(maximum.black-minimum.black);
cristy80c99742011-04-04 14:46:39 +00004678 break;
4679 }
cristy6fc86bb2011-03-18 23:45:16 +00004680 case MaximumStatistic:
4681 {
4682 pixel=GetMaximumPixelList(pixel_list[id]);
4683 break;
4684 }
cristy49f37242011-03-22 18:18:23 +00004685 case MeanStatistic:
4686 {
4687 pixel=GetMeanPixelList(pixel_list[id]);
4688 break;
4689 }
cristyf2ad14a2011-03-18 18:57:25 +00004690 case MedianStatistic:
cristy6fc86bb2011-03-18 23:45:16 +00004691 default:
cristyf2ad14a2011-03-18 18:57:25 +00004692 {
4693 pixel=GetMedianPixelList(pixel_list[id]);
4694 break;
4695 }
cristy6fc86bb2011-03-18 23:45:16 +00004696 case MinimumStatistic:
4697 {
4698 pixel=GetMinimumPixelList(pixel_list[id]);
4699 break;
4700 }
cristyf2ad14a2011-03-18 18:57:25 +00004701 case ModeStatistic:
4702 {
4703 pixel=GetModePixelList(pixel_list[id]);
4704 break;
4705 }
4706 case NonpeakStatistic:
4707 {
4708 pixel=GetNonpeakPixelList(pixel_list[id]);
4709 break;
4710 }
cristy9a68cbb2011-03-29 00:51:23 +00004711 case StandardDeviationStatistic:
4712 {
4713 pixel=GetStandardDeviationPixelList(pixel_list[id]);
4714 break;
4715 }
cristy0834d642011-03-18 18:26:08 +00004716 }
cristyed231572011-07-14 02:18:59 +00004717 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00004718 SetPixelRed(statistic_image,ClampToQuantum(pixel.red),q);
cristyed231572011-07-14 02:18:59 +00004719 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00004720 SetPixelGreen(statistic_image,ClampToQuantum(pixel.green),q);
cristyed231572011-07-14 02:18:59 +00004721 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00004722 SetPixelBlue(statistic_image,ClampToQuantum(pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00004723 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy0834d642011-03-18 18:26:08 +00004724 (image->colorspace == CMYKColorspace))
cristy490408a2011-07-07 14:42:05 +00004725 SetPixelBlack(statistic_image,ClampToQuantum(pixel.black),q);
cristyed231572011-07-14 02:18:59 +00004726 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00004727 (image->matte != MagickFalse))
cristy490408a2011-07-07 14:42:05 +00004728 SetPixelAlpha(statistic_image,ClampToQuantum(pixel.alpha),q);
cristyed231572011-07-14 02:18:59 +00004729 p+=GetPixelChannels(image);
4730 q+=GetPixelChannels(statistic_image);
cristy0834d642011-03-18 18:26:08 +00004731 }
4732 if (SyncCacheViewAuthenticPixels(statistic_view,exception) == MagickFalse)
4733 status=MagickFalse;
4734 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4735 {
4736 MagickBooleanType
4737 proceed;
4738
4739#if defined(MAGICKCORE_OPENMP_SUPPORT)
4740 #pragma omp critical (MagickCore_StatisticImage)
4741#endif
4742 proceed=SetImageProgress(image,StatisticImageTag,progress++,
4743 image->rows);
4744 if (proceed == MagickFalse)
4745 status=MagickFalse;
4746 }
4747 }
4748 statistic_view=DestroyCacheView(statistic_view);
4749 image_view=DestroyCacheView(image_view);
4750 pixel_list=DestroyPixelListThreadSet(pixel_list);
4751 return(statistic_image);
4752}
4753
4754/*
4755%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4756% %
4757% %
4758% %
cristy3ed852e2009-09-05 21:47:34 +00004759% U n s h a r p M a s k I m a g e %
4760% %
4761% %
4762% %
4763%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4764%
4765% UnsharpMaskImage() sharpens one or more image channels. We convolve the
4766% image with a Gaussian operator of the given radius and standard deviation
4767% (sigma). For reasonable results, radius should be larger than sigma. Use a
4768% radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
4769%
4770% The format of the UnsharpMaskImage method is:
4771%
4772% Image *UnsharpMaskImage(const Image *image,const double radius,
4773% const double sigma,const double amount,const double threshold,
4774% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00004775%
4776% A description of each parameter follows:
4777%
4778% o image: the image.
4779%
cristy3ed852e2009-09-05 21:47:34 +00004780% o radius: the radius of the Gaussian, in pixels, not counting the center
4781% pixel.
4782%
4783% o sigma: the standard deviation of the Gaussian, in pixels.
4784%
4785% o amount: the percentage of the difference between the original and the
4786% blur image that is added back into the original.
4787%
4788% o threshold: the threshold in pixels needed to apply the diffence amount.
4789%
4790% o exception: return any errors or warnings in this structure.
4791%
4792*/
cristyf4ad9df2011-07-08 16:49:03 +00004793MagickExport Image *UnsharpMaskImage(const Image *image,
4794 const double radius,const double sigma,const double amount,
4795 const double threshold,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00004796{
4797#define SharpenImageTag "Sharpen/Image"
4798
cristyc4c8d132010-01-07 01:58:38 +00004799 CacheView
4800 *image_view,
4801 *unsharp_view;
4802
cristy3ed852e2009-09-05 21:47:34 +00004803 Image
4804 *unsharp_image;
4805
cristy3ed852e2009-09-05 21:47:34 +00004806 MagickBooleanType
4807 status;
4808
cristybb503372010-05-27 20:51:26 +00004809 MagickOffsetType
4810 progress;
4811
cristy4c08aed2011-07-01 19:47:50 +00004812 PixelInfo
cristyddd82202009-11-03 20:14:50 +00004813 bias;
cristy3ed852e2009-09-05 21:47:34 +00004814
4815 MagickRealType
4816 quantum_threshold;
4817
cristybb503372010-05-27 20:51:26 +00004818 ssize_t
4819 y;
4820
cristy3ed852e2009-09-05 21:47:34 +00004821 assert(image != (const Image *) NULL);
4822 assert(image->signature == MagickSignature);
4823 if (image->debug != MagickFalse)
4824 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4825 assert(exception != (ExceptionInfo *) NULL);
cristy05c0c9a2011-09-05 23:16:13 +00004826 unsharp_image=BlurImage(image,radius,sigma,image->bias,exception);
cristy3ed852e2009-09-05 21:47:34 +00004827 if (unsharp_image == (Image *) NULL)
4828 return((Image *) NULL);
4829 quantum_threshold=(MagickRealType) QuantumRange*threshold;
4830 /*
4831 Unsharp-mask image.
4832 */
4833 status=MagickTrue;
4834 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00004835 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00004836 image_view=AcquireCacheView(image);
4837 unsharp_view=AcquireCacheView(unsharp_image);
cristyb5d5f722009-11-04 03:03:49 +00004838#if defined(MAGICKCORE_OPENMP_SUPPORT)
4839 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004840#endif
cristybb503372010-05-27 20:51:26 +00004841 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00004842 {
cristy4c08aed2011-07-01 19:47:50 +00004843 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00004844 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00004845
cristy4c08aed2011-07-01 19:47:50 +00004846 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00004847 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004848
cristy117ff172010-08-15 21:35:32 +00004849 register ssize_t
4850 x;
4851
cristy3ed852e2009-09-05 21:47:34 +00004852 if (status == MagickFalse)
4853 continue;
4854 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
4855 q=GetCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
4856 exception);
cristy4c08aed2011-07-01 19:47:50 +00004857 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00004858 {
4859 status=MagickFalse;
4860 continue;
4861 }
cristybb503372010-05-27 20:51:26 +00004862 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004863 {
cristy7f3a0d12011-09-05 23:27:59 +00004864 register ssize_t
4865 i;
4866
4867 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4868 {
4869 MagickRealType
4870 pixel;
4871
4872 PixelChannel
4873 channel;
4874
4875 PixelTrait
4876 traits,
4877 unsharp_traits;
4878
4879 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
4880 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
4881 unsharp_traits=GetPixelChannelMapTraits(unsharp_image,channel);
4882 if ((traits == UndefinedPixelTrait) ||
4883 (unsharp_traits == UndefinedPixelTrait))
4884 continue;
4885 if ((unsharp_traits & CopyPixelTrait) != 0)
4886 {
4887 q[channel]=p[i];
4888 continue;
4889 }
4890 pixel=p[i]-(MagickRealType) q[channel];
4891 if (fabs(2.0*pixel) < quantum_threshold)
4892 pixel=(MagickRealType) p[i];
4893 else
4894 pixel=(MagickRealType) p[i]+amount*pixel;
4895 q[channel]=ClampToQuantum(pixel);
4896 }
cristyed231572011-07-14 02:18:59 +00004897 p+=GetPixelChannels(image);
4898 q+=GetPixelChannels(unsharp_image);
cristy3ed852e2009-09-05 21:47:34 +00004899 }
4900 if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
4901 status=MagickFalse;
4902 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4903 {
4904 MagickBooleanType
4905 proceed;
4906
cristyb5d5f722009-11-04 03:03:49 +00004907#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00004908 #pragma omp critical (MagickCore_UnsharpMaskImage)
cristy3ed852e2009-09-05 21:47:34 +00004909#endif
4910 proceed=SetImageProgress(image,SharpenImageTag,progress++,image->rows);
4911 if (proceed == MagickFalse)
4912 status=MagickFalse;
4913 }
4914 }
4915 unsharp_image->type=image->type;
4916 unsharp_view=DestroyCacheView(unsharp_view);
4917 image_view=DestroyCacheView(image_view);
4918 if (status == MagickFalse)
4919 unsharp_image=DestroyImage(unsharp_image);
4920 return(unsharp_image);
4921}