blob: 318775fa5aa2d40047b973ebc33dcb947800fae7 [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
cristyb41a1172011-09-06 00:55:14 +0000882 center,
cristybb503372010-05-27 20:51:26 +0000883 x,
884 y;
885
cristy3ed852e2009-09-05 21:47:34 +0000886 /*
887 Initialize blur image attributes.
888 */
889 assert(image != (Image *) NULL);
890 assert(image->signature == MagickSignature);
891 if (image->debug != MagickFalse)
892 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
893 assert(exception != (ExceptionInfo *) NULL);
894 assert(exception->signature == MagickSignature);
cristyd25c77e2011-09-06 00:10:24 +0000895 blur_image=CloneImage(image,0,0,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +0000896 if (blur_image == (Image *) NULL)
897 return((Image *) NULL);
898 if (fabs(sigma) <= MagickEpsilon)
899 return(blur_image);
cristy574cc262011-08-05 01:23:58 +0000900 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000901 {
cristy3ed852e2009-09-05 21:47:34 +0000902 blur_image=DestroyImage(blur_image);
903 return((Image *) NULL);
904 }
905 width=GetOptimalKernelWidth1D(radius,sigma);
906 kernel=GetBlurKernel(width,sigma);
907 if (kernel == (double *) NULL)
908 {
909 blur_image=DestroyImage(blur_image);
910 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
911 }
912 if (image->debug != MagickFalse)
913 {
914 char
915 format[MaxTextExtent],
916 *message;
917
918 register const double
919 *k;
920
921 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +0000922 " BlurImage with %.20g kernel:",(double) width);
cristy3ed852e2009-09-05 21:47:34 +0000923 message=AcquireString("");
924 k=kernel;
cristybb503372010-05-27 20:51:26 +0000925 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000926 {
927 *message='\0';
cristyb51dff52011-05-19 16:55:47 +0000928 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) i);
cristy3ed852e2009-09-05 21:47:34 +0000929 (void) ConcatenateString(&message,format);
cristyb51dff52011-05-19 16:55:47 +0000930 (void) FormatLocaleString(format,MaxTextExtent,"%g ",*k++);
cristy3ed852e2009-09-05 21:47:34 +0000931 (void) ConcatenateString(&message,format);
932 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
933 }
934 message=DestroyString(message);
935 }
936 /*
937 Blur rows.
938 */
939 status=MagickTrue;
940 progress=0;
cristyb41a1172011-09-06 00:55:14 +0000941 center=(ssize_t) GetPixelChannels(image)*(width/2L);
cristy3ed852e2009-09-05 21:47:34 +0000942 image_view=AcquireCacheView(image);
943 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000944#if defined(MAGICKCORE_OPENMP_SUPPORT)
945 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000946#endif
cristyb41a1172011-09-06 00:55:14 +0000947 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000948 {
cristy4c08aed2011-07-01 19:47:50 +0000949 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000950 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000951
cristy4c08aed2011-07-01 19:47:50 +0000952 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000953 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000954
cristy117ff172010-08-15 21:35:32 +0000955 register ssize_t
956 x;
957
cristy3ed852e2009-09-05 21:47:34 +0000958 if (status == MagickFalse)
959 continue;
cristy117ff172010-08-15 21:35:32 +0000960 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y,
961 image->columns+width,1,exception);
cristy3ed852e2009-09-05 21:47:34 +0000962 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
963 exception);
cristy4c08aed2011-07-01 19:47:50 +0000964 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000965 {
966 status=MagickFalse;
967 continue;
968 }
cristyb41a1172011-09-06 00:55:14 +0000969 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000970 {
cristybb503372010-05-27 20:51:26 +0000971 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000972 i;
973
cristyb41a1172011-09-06 00:55:14 +0000974 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
975 {
976 MagickRealType
977 alpha,
978 gamma,
979 pixel;
cristyd25c77e2011-09-06 00:10:24 +0000980
cristyb41a1172011-09-06 00:55:14 +0000981 PixelChannel
982 channel;
983
984 PixelTrait
985 blur_traits,
986 traits;
987
988 register const double
989 *restrict k;
990
991 register const Quantum
992 *restrict pixels;
993
994 register ssize_t
995 u;
996
997 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
998 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
999 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
1000 if ((traits == UndefinedPixelTrait) ||
1001 (blur_traits == UndefinedPixelTrait))
1002 continue;
1003 if ((blur_traits & CopyPixelTrait) != 0)
cristyd25c77e2011-09-06 00:10:24 +00001004 {
cristyb41a1172011-09-06 00:55:14 +00001005 q[channel]=p[center+i];
1006 continue;
cristyd25c77e2011-09-06 00:10:24 +00001007 }
cristyb41a1172011-09-06 00:55:14 +00001008 k=kernel;
1009 pixels=p;
1010 pixel=0.0;
1011 if ((blur_traits & BlendPixelTrait) == 0)
1012 {
1013 /*
1014 No alpha blending.
1015 */
1016 for (u=0; u < (ssize_t) width; u++)
cristyd25c77e2011-09-06 00:10:24 +00001017 {
cristyb41a1172011-09-06 00:55:14 +00001018 pixel+=(*k)*pixels[i];
1019 k++;
1020 pixels+=GetPixelChannels(image);
cristyd25c77e2011-09-06 00:10:24 +00001021 }
cristyb41a1172011-09-06 00:55:14 +00001022 q[channel]=ClampToQuantum(pixel);
1023 continue;
1024 }
1025 /*
1026 Alpha blending.
1027 */
1028 gamma=0.0;
1029 for (u=0; u < (ssize_t) width; u++)
1030 {
1031 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,pixels));
1032 pixel+=(*k)*alpha*pixels[i];
1033 gamma+=(*k)*alpha;
1034 k++;
1035 pixels+=GetPixelChannels(image);
cristyd25c77e2011-09-06 00:10:24 +00001036 }
cristyb41a1172011-09-06 00:55:14 +00001037 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1038 q[channel]=ClampToQuantum(gamma*pixel);
1039 }
cristyed231572011-07-14 02:18:59 +00001040 p+=GetPixelChannels(image);
1041 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001042 }
1043 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1044 status=MagickFalse;
1045 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1046 {
1047 MagickBooleanType
1048 proceed;
1049
cristyb5d5f722009-11-04 03:03:49 +00001050#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00001051 #pragma omp critical (MagickCore_BlurImage)
cristy3ed852e2009-09-05 21:47:34 +00001052#endif
1053 proceed=SetImageProgress(image,BlurImageTag,progress++,blur_image->rows+
1054 blur_image->columns);
1055 if (proceed == MagickFalse)
1056 status=MagickFalse;
1057 }
1058 }
1059 blur_view=DestroyCacheView(blur_view);
1060 image_view=DestroyCacheView(image_view);
1061 /*
1062 Blur columns.
1063 */
1064 image_view=AcquireCacheView(blur_image);
1065 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00001066#if defined(MAGICKCORE_OPENMP_SUPPORT)
1067 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001068#endif
cristyb41a1172011-09-06 00:55:14 +00001069 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001070 {
cristy4c08aed2011-07-01 19:47:50 +00001071 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001072 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001073
cristy4c08aed2011-07-01 19:47:50 +00001074 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001075 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001076
cristy117ff172010-08-15 21:35:32 +00001077 register ssize_t
1078 y;
1079
cristy3ed852e2009-09-05 21:47:34 +00001080 if (status == MagickFalse)
1081 continue;
cristy117ff172010-08-15 21:35:32 +00001082 p=GetCacheViewVirtualPixels(image_view,x,-((ssize_t) width/2L),1,
1083 image->rows+width,exception);
cristy3ed852e2009-09-05 21:47:34 +00001084 q=GetCacheViewAuthenticPixels(blur_view,x,0,1,blur_image->rows,exception);
cristy4c08aed2011-07-01 19:47:50 +00001085 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001086 {
1087 status=MagickFalse;
1088 continue;
1089 }
cristyb41a1172011-09-06 00:55:14 +00001090 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001091 {
cristybb503372010-05-27 20:51:26 +00001092 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001093 i;
1094
cristyb41a1172011-09-06 00:55:14 +00001095 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1096 {
1097 MagickRealType
1098 alpha,
1099 gamma,
1100 pixel;
cristyd25c77e2011-09-06 00:10:24 +00001101
cristyb41a1172011-09-06 00:55:14 +00001102 PixelChannel
1103 channel;
1104
1105 PixelTrait
1106 blur_traits,
1107 traits;
1108
1109 register const double
1110 *restrict k;
1111
1112 register const Quantum
1113 *restrict pixels;
1114
1115 register ssize_t
1116 u;
1117
1118 traits=GetPixelChannelMapTraits(blur_image,(PixelChannel) i);
1119 channel=GetPixelChannelMapChannel(blur_image,(PixelChannel) i);
1120 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
1121 if ((traits == UndefinedPixelTrait) ||
1122 (blur_traits == UndefinedPixelTrait))
1123 continue;
1124 if ((blur_traits & CopyPixelTrait) != 0)
cristyd25c77e2011-09-06 00:10:24 +00001125 {
cristyb41a1172011-09-06 00:55:14 +00001126 q[channel]=p[center+i];
1127 continue;
cristyd25c77e2011-09-06 00:10:24 +00001128 }
cristyb41a1172011-09-06 00:55:14 +00001129 k=kernel;
1130 pixels=p;
1131 pixel=0.0;
1132 if ((blur_traits & BlendPixelTrait) == 0)
1133 {
1134 /*
1135 No alpha blending.
1136 */
1137 for (u=0; u < (ssize_t) width; u++)
cristyd25c77e2011-09-06 00:10:24 +00001138 {
cristyb41a1172011-09-06 00:55:14 +00001139 pixel+=(*k)*pixels[i];
1140 k++;
1141 pixels+=GetPixelChannels(blur_image);
cristyd25c77e2011-09-06 00:10:24 +00001142 }
cristyb41a1172011-09-06 00:55:14 +00001143 q[channel]=ClampToQuantum(pixel);
1144 continue;
1145 }
1146 /*
1147 Alpha blending.
1148 */
1149 gamma=0.0;
1150 for (u=0; u < (ssize_t) width; u++)
1151 {
1152 alpha=(MagickRealType) (QuantumScale*
1153 GetPixelAlpha(blur_image,pixels));
1154 pixel+=(*k)*alpha*pixels[i];
1155 gamma+=(*k)*alpha;
1156 k++;
1157 pixels+=GetPixelChannels(blur_image);
cristyd25c77e2011-09-06 00:10:24 +00001158 }
cristyb41a1172011-09-06 00:55:14 +00001159 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1160 q[channel]=ClampToQuantum(gamma*pixel);
1161 }
cristyd25c77e2011-09-06 00:10:24 +00001162 p+=GetPixelChannels(blur_image);
cristyed231572011-07-14 02:18:59 +00001163 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001164 }
1165 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1166 status=MagickFalse;
cristy4c08aed2011-07-01 19:47:50 +00001167 if (blur_image->progress_monitor != (MagickProgressMonitor) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001168 {
1169 MagickBooleanType
1170 proceed;
1171
cristyb5d5f722009-11-04 03:03:49 +00001172#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00001173 #pragma omp critical (MagickCore_BlurImage)
cristy3ed852e2009-09-05 21:47:34 +00001174#endif
cristy4c08aed2011-07-01 19:47:50 +00001175 proceed=SetImageProgress(blur_image,BlurImageTag,progress++,
1176 blur_image->rows+blur_image->columns);
cristy3ed852e2009-09-05 21:47:34 +00001177 if (proceed == MagickFalse)
1178 status=MagickFalse;
1179 }
1180 }
1181 blur_view=DestroyCacheView(blur_view);
1182 image_view=DestroyCacheView(image_view);
1183 kernel=(double *) RelinquishMagickMemory(kernel);
1184 if (status == MagickFalse)
1185 blur_image=DestroyImage(blur_image);
1186 blur_image->type=image->type;
1187 return(blur_image);
1188}
1189
1190/*
1191%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1192% %
1193% %
1194% %
cristyfccdab92009-11-30 16:43:57 +00001195% C o n v o l v e I m a g e %
1196% %
1197% %
1198% %
1199%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1200%
1201% ConvolveImage() applies a custom convolution kernel to the image.
1202%
1203% The format of the ConvolveImage method is:
1204%
cristy5e6be1e2011-07-16 01:23:39 +00001205% Image *ConvolveImage(const Image *image,const KernelInfo *kernel,
1206% ExceptionInfo *exception)
1207%
cristyfccdab92009-11-30 16:43:57 +00001208% A description of each parameter follows:
1209%
1210% o image: the image.
1211%
cristy5e6be1e2011-07-16 01:23:39 +00001212% o kernel: the filtering kernel.
cristyfccdab92009-11-30 16:43:57 +00001213%
1214% o exception: return any errors or warnings in this structure.
1215%
1216*/
cristy5e6be1e2011-07-16 01:23:39 +00001217MagickExport Image *ConvolveImage(const Image *image,
1218 const KernelInfo *kernel_info,ExceptionInfo *exception)
cristyfccdab92009-11-30 16:43:57 +00001219{
cristyfccdab92009-11-30 16:43:57 +00001220#define ConvolveImageTag "Convolve/Image"
1221
cristyc4c8d132010-01-07 01:58:38 +00001222 CacheView
1223 *convolve_view,
cristy105ba3c2011-07-18 02:28:38 +00001224 *image_view;
cristyc4c8d132010-01-07 01:58:38 +00001225
cristyfccdab92009-11-30 16:43:57 +00001226 Image
1227 *convolve_image;
1228
cristyfccdab92009-11-30 16:43:57 +00001229 MagickBooleanType
1230 status;
1231
cristybb503372010-05-27 20:51:26 +00001232 MagickOffsetType
1233 progress;
1234
cristybb503372010-05-27 20:51:26 +00001235 ssize_t
cristy574cc262011-08-05 01:23:58 +00001236 center,
cristybb503372010-05-27 20:51:26 +00001237 y;
1238
cristyfccdab92009-11-30 16:43:57 +00001239 /*
1240 Initialize convolve image attributes.
1241 */
1242 assert(image != (Image *) NULL);
1243 assert(image->signature == MagickSignature);
1244 if (image->debug != MagickFalse)
1245 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1246 assert(exception != (ExceptionInfo *) NULL);
1247 assert(exception->signature == MagickSignature);
cristy5e6be1e2011-07-16 01:23:39 +00001248 if ((kernel_info->width % 2) == 0)
cristyfccdab92009-11-30 16:43:57 +00001249 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
cristy08429172011-07-14 17:18:16 +00001250 convolve_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1251 exception);
cristyfccdab92009-11-30 16:43:57 +00001252 if (convolve_image == (Image *) NULL)
1253 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00001254 if (SetImageStorageClass(convolve_image,DirectClass,exception) == MagickFalse)
cristyfccdab92009-11-30 16:43:57 +00001255 {
cristyfccdab92009-11-30 16:43:57 +00001256 convolve_image=DestroyImage(convolve_image);
1257 return((Image *) NULL);
1258 }
1259 if (image->debug != MagickFalse)
1260 {
1261 char
1262 format[MaxTextExtent],
1263 *message;
1264
cristy117ff172010-08-15 21:35:32 +00001265 register const double
1266 *k;
1267
cristy4e154852011-07-14 13:28:53 +00001268 register ssize_t
1269 u;
1270
cristybb503372010-05-27 20:51:26 +00001271 ssize_t
cristyfccdab92009-11-30 16:43:57 +00001272 v;
1273
cristyfccdab92009-11-30 16:43:57 +00001274 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristy5e6be1e2011-07-16 01:23:39 +00001275 " ConvolveImage with %.20gx%.20g kernel:",(double) kernel_info->width,
1276 (double) kernel_info->height);
cristyfccdab92009-11-30 16:43:57 +00001277 message=AcquireString("");
cristy5e6be1e2011-07-16 01:23:39 +00001278 k=kernel_info->values;
1279 for (v=0; v < (ssize_t) kernel_info->width; v++)
cristyfccdab92009-11-30 16:43:57 +00001280 {
1281 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00001282 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristyfccdab92009-11-30 16:43:57 +00001283 (void) ConcatenateString(&message,format);
cristy5e6be1e2011-07-16 01:23:39 +00001284 for (u=0; u < (ssize_t) kernel_info->height; u++)
cristyfccdab92009-11-30 16:43:57 +00001285 {
cristyb51dff52011-05-19 16:55:47 +00001286 (void) FormatLocaleString(format,MaxTextExtent,"%g ",*k++);
cristyfccdab92009-11-30 16:43:57 +00001287 (void) ConcatenateString(&message,format);
1288 }
1289 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
1290 }
1291 message=DestroyString(message);
1292 }
1293 /*
cristyfccdab92009-11-30 16:43:57 +00001294 Convolve image.
1295 */
cristy574cc262011-08-05 01:23:58 +00001296 center=(ssize_t) GetPixelChannels(image)*(image->columns+kernel_info->width)*
1297 (kernel_info->height/2L)+GetPixelChannels(image)*(kernel_info->width/2);
cristyfccdab92009-11-30 16:43:57 +00001298 status=MagickTrue;
1299 progress=0;
cristyfccdab92009-11-30 16:43:57 +00001300 image_view=AcquireCacheView(image);
1301 convolve_view=AcquireCacheView(convolve_image);
1302#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy175653e2011-07-10 23:13:34 +00001303 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristyfccdab92009-11-30 16:43:57 +00001304#endif
cristybb503372010-05-27 20:51:26 +00001305 for (y=0; y < (ssize_t) image->rows; y++)
cristyfccdab92009-11-30 16:43:57 +00001306 {
cristy4c08aed2011-07-01 19:47:50 +00001307 register const Quantum
cristy105ba3c2011-07-18 02:28:38 +00001308 *restrict p;
cristyfccdab92009-11-30 16:43:57 +00001309
cristy4c08aed2011-07-01 19:47:50 +00001310 register Quantum
cristyfccdab92009-11-30 16:43:57 +00001311 *restrict q;
1312
cristy117ff172010-08-15 21:35:32 +00001313 register ssize_t
1314 x;
1315
cristyfccdab92009-11-30 16:43:57 +00001316 if (status == MagickFalse)
1317 continue;
cristy105ba3c2011-07-18 02:28:38 +00001318 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) kernel_info->width/2L),y-
1319 (ssize_t) (kernel_info->height/2L),image->columns+kernel_info->width,
1320 kernel_info->height,exception);
cristy08429172011-07-14 17:18:16 +00001321 q=QueueCacheViewAuthenticPixels(convolve_view,0,y,convolve_image->columns,1,
cristyfccdab92009-11-30 16:43:57 +00001322 exception);
cristy105ba3c2011-07-18 02:28:38 +00001323 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristyfccdab92009-11-30 16:43:57 +00001324 {
1325 status=MagickFalse;
1326 continue;
1327 }
cristybb503372010-05-27 20:51:26 +00001328 for (x=0; x < (ssize_t) image->columns; x++)
cristyfccdab92009-11-30 16:43:57 +00001329 {
cristybb503372010-05-27 20:51:26 +00001330 register ssize_t
cristyed231572011-07-14 02:18:59 +00001331 i;
cristyfccdab92009-11-30 16:43:57 +00001332
cristya30d9ba2011-07-23 21:00:48 +00001333 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristyed231572011-07-14 02:18:59 +00001334 {
cristyed231572011-07-14 02:18:59 +00001335 MagickRealType
cristy4e154852011-07-14 13:28:53 +00001336 alpha,
1337 gamma,
cristyed231572011-07-14 02:18:59 +00001338 pixel;
1339
1340 PixelChannel
1341 channel;
1342
1343 PixelTrait
1344 convolve_traits,
1345 traits;
1346
1347 register const double
1348 *restrict k;
1349
1350 register const Quantum
cristyeb52cde2011-07-17 01:52:52 +00001351 *restrict pixels;
cristyed231572011-07-14 02:18:59 +00001352
1353 register ssize_t
1354 u;
1355
1356 ssize_t
1357 v;
1358
cristy30301712011-07-18 15:06:51 +00001359 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
cristy30301712011-07-18 15:06:51 +00001360 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
cristy4e154852011-07-14 13:28:53 +00001361 convolve_traits=GetPixelChannelMapTraits(convolve_image,channel);
cristy010d7d12011-08-31 01:02:48 +00001362 if ((traits == UndefinedPixelTrait) ||
1363 (convolve_traits == UndefinedPixelTrait))
cristy4e154852011-07-14 13:28:53 +00001364 continue;
1365 if ((convolve_traits & CopyPixelTrait) != 0)
1366 {
cristyf7dc44c2011-07-20 14:41:15 +00001367 q[channel]=p[center+i];
cristy4e154852011-07-14 13:28:53 +00001368 continue;
1369 }
cristy5e6be1e2011-07-16 01:23:39 +00001370 k=kernel_info->values;
cristy105ba3c2011-07-18 02:28:38 +00001371 pixels=p;
cristy0a922382011-07-16 15:30:34 +00001372 pixel=kernel_info->bias;
cristy222b19c2011-08-04 01:35:11 +00001373 if ((convolve_traits & BlendPixelTrait) == 0)
cristyfccdab92009-11-30 16:43:57 +00001374 {
cristyed231572011-07-14 02:18:59 +00001375 /*
cristy4e154852011-07-14 13:28:53 +00001376 No alpha blending.
cristyed231572011-07-14 02:18:59 +00001377 */
cristyeb52cde2011-07-17 01:52:52 +00001378 for (v=0; v < (ssize_t) kernel_info->height; v++)
cristyfccdab92009-11-30 16:43:57 +00001379 {
cristyeb52cde2011-07-17 01:52:52 +00001380 for (u=0; u < (ssize_t) kernel_info->width; u++)
cristy175653e2011-07-10 23:13:34 +00001381 {
cristyeb52cde2011-07-17 01:52:52 +00001382 pixel+=(*k)*pixels[i];
cristyed231572011-07-14 02:18:59 +00001383 k++;
cristya30d9ba2011-07-23 21:00:48 +00001384 pixels+=GetPixelChannels(image);
cristy175653e2011-07-10 23:13:34 +00001385 }
cristya30d9ba2011-07-23 21:00:48 +00001386 pixels+=image->columns*GetPixelChannels(image);
cristy175653e2011-07-10 23:13:34 +00001387 }
cristyf7dc44c2011-07-20 14:41:15 +00001388 q[channel]=ClampToQuantum(pixel);
cristy4e154852011-07-14 13:28:53 +00001389 continue;
cristyed231572011-07-14 02:18:59 +00001390 }
cristy4e154852011-07-14 13:28:53 +00001391 /*
1392 Alpha blending.
1393 */
1394 gamma=0.0;
cristyeb52cde2011-07-17 01:52:52 +00001395 for (v=0; v < (ssize_t) kernel_info->height; v++)
cristy4e154852011-07-14 13:28:53 +00001396 {
cristyeb52cde2011-07-17 01:52:52 +00001397 for (u=0; u < (ssize_t) kernel_info->width; u++)
cristy4e154852011-07-14 13:28:53 +00001398 {
cristyeb52cde2011-07-17 01:52:52 +00001399 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,pixels));
1400 pixel+=(*k)*alpha*pixels[i];
cristy4e154852011-07-14 13:28:53 +00001401 gamma+=(*k)*alpha;
1402 k++;
cristya30d9ba2011-07-23 21:00:48 +00001403 pixels+=GetPixelChannels(image);
cristy4e154852011-07-14 13:28:53 +00001404 }
cristya30d9ba2011-07-23 21:00:48 +00001405 pixels+=image->columns*GetPixelChannels(image);
cristy4e154852011-07-14 13:28:53 +00001406 }
cristy1ce96d02011-07-14 17:57:24 +00001407 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristye7a41c92011-07-20 21:31:01 +00001408 q[channel]=ClampToQuantum(gamma*pixel);
cristyed231572011-07-14 02:18:59 +00001409 }
cristya30d9ba2011-07-23 21:00:48 +00001410 p+=GetPixelChannels(image);
1411 q+=GetPixelChannels(convolve_image);
cristyfccdab92009-11-30 16:43:57 +00001412 }
cristyed231572011-07-14 02:18:59 +00001413 if (SyncCacheViewAuthenticPixels(convolve_view,exception) == MagickFalse)
cristyfccdab92009-11-30 16:43:57 +00001414 status=MagickFalse;
1415 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1416 {
1417 MagickBooleanType
1418 proceed;
1419
1420#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00001421 #pragma omp critical (MagickCore_ConvolveImage)
cristyfccdab92009-11-30 16:43:57 +00001422#endif
1423 proceed=SetImageProgress(image,ConvolveImageTag,progress++,image->rows);
1424 if (proceed == MagickFalse)
1425 status=MagickFalse;
1426 }
1427 }
1428 convolve_image->type=image->type;
1429 convolve_view=DestroyCacheView(convolve_view);
1430 image_view=DestroyCacheView(image_view);
cristyfccdab92009-11-30 16:43:57 +00001431 if (status == MagickFalse)
1432 convolve_image=DestroyImage(convolve_image);
1433 return(convolve_image);
1434}
1435
1436/*
1437%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1438% %
1439% %
1440% %
cristy3ed852e2009-09-05 21:47:34 +00001441% D e s p e c k l e I m a g e %
1442% %
1443% %
1444% %
1445%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1446%
1447% DespeckleImage() reduces the speckle noise in an image while perserving the
1448% edges of the original image.
1449%
1450% The format of the DespeckleImage method is:
1451%
1452% Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1453%
1454% A description of each parameter follows:
1455%
1456% o image: the image.
1457%
1458% o exception: return any errors or warnings in this structure.
1459%
1460*/
1461
cristybb503372010-05-27 20:51:26 +00001462static void Hull(const ssize_t x_offset,const ssize_t y_offset,
1463 const size_t columns,const size_t rows,Quantum *f,Quantum *g,
cristy3ed852e2009-09-05 21:47:34 +00001464 const int polarity)
1465{
cristy3ed852e2009-09-05 21:47:34 +00001466 MagickRealType
1467 v;
1468
cristy3ed852e2009-09-05 21:47:34 +00001469 register Quantum
1470 *p,
1471 *q,
1472 *r,
1473 *s;
1474
cristy117ff172010-08-15 21:35:32 +00001475 register ssize_t
1476 x;
1477
1478 ssize_t
1479 y;
1480
cristy3ed852e2009-09-05 21:47:34 +00001481 assert(f != (Quantum *) NULL);
1482 assert(g != (Quantum *) NULL);
1483 p=f+(columns+2);
1484 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001485 r=p+(y_offset*((ssize_t) columns+2)+x_offset);
1486 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001487 {
1488 p++;
1489 q++;
1490 r++;
1491 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001492 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001493 {
1494 v=(MagickRealType) (*p);
1495 if ((MagickRealType) *r >= (v+(MagickRealType) ScaleCharToQuantum(2)))
1496 v+=ScaleCharToQuantum(1);
1497 *q=(Quantum) v;
1498 p++;
1499 q++;
1500 r++;
1501 }
1502 else
cristybb503372010-05-27 20:51:26 +00001503 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001504 {
1505 v=(MagickRealType) (*p);
1506 if ((MagickRealType) *r <= (v-(MagickRealType) ScaleCharToQuantum(2)))
cristybb503372010-05-27 20:51:26 +00001507 v-=(ssize_t) ScaleCharToQuantum(1);
cristy3ed852e2009-09-05 21:47:34 +00001508 *q=(Quantum) v;
1509 p++;
1510 q++;
1511 r++;
1512 }
1513 p++;
1514 q++;
1515 r++;
1516 }
1517 p=f+(columns+2);
1518 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001519 r=q+(y_offset*((ssize_t) columns+2)+x_offset);
1520 s=q-(y_offset*((ssize_t) columns+2)+x_offset);
1521 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001522 {
1523 p++;
1524 q++;
1525 r++;
1526 s++;
1527 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001528 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001529 {
1530 v=(MagickRealType) (*q);
1531 if (((MagickRealType) *s >=
1532 (v+(MagickRealType) ScaleCharToQuantum(2))) &&
1533 ((MagickRealType) *r > v))
1534 v+=ScaleCharToQuantum(1);
1535 *p=(Quantum) v;
1536 p++;
1537 q++;
1538 r++;
1539 s++;
1540 }
1541 else
cristybb503372010-05-27 20:51:26 +00001542 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001543 {
1544 v=(MagickRealType) (*q);
1545 if (((MagickRealType) *s <=
1546 (v-(MagickRealType) ScaleCharToQuantum(2))) &&
1547 ((MagickRealType) *r < v))
1548 v-=(MagickRealType) ScaleCharToQuantum(1);
1549 *p=(Quantum) v;
1550 p++;
1551 q++;
1552 r++;
1553 s++;
1554 }
1555 p++;
1556 q++;
1557 r++;
1558 s++;
1559 }
1560}
1561
1562MagickExport Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1563{
1564#define DespeckleImageTag "Despeckle/Image"
1565
cristy2407fc22009-09-11 00:55:25 +00001566 CacheView
1567 *despeckle_view,
1568 *image_view;
1569
cristy3ed852e2009-09-05 21:47:34 +00001570 Image
1571 *despeckle_image;
1572
cristy3ed852e2009-09-05 21:47:34 +00001573 MagickBooleanType
1574 status;
1575
cristya58c3172011-02-19 19:23:11 +00001576 register ssize_t
1577 i;
1578
cristy3ed852e2009-09-05 21:47:34 +00001579 Quantum
cristy65b9f392011-02-22 14:22:54 +00001580 *restrict buffers,
1581 *restrict pixels;
cristy3ed852e2009-09-05 21:47:34 +00001582
1583 size_t
cristya58c3172011-02-19 19:23:11 +00001584 length,
1585 number_channels;
cristy117ff172010-08-15 21:35:32 +00001586
cristybb503372010-05-27 20:51:26 +00001587 static const ssize_t
cristy691a29e2009-09-11 00:44:10 +00001588 X[4] = {0, 1, 1,-1},
1589 Y[4] = {1, 0, 1, 1};
cristy3ed852e2009-09-05 21:47:34 +00001590
cristy3ed852e2009-09-05 21:47:34 +00001591 /*
1592 Allocate despeckled image.
1593 */
1594 assert(image != (const Image *) NULL);
1595 assert(image->signature == MagickSignature);
1596 if (image->debug != MagickFalse)
1597 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1598 assert(exception != (ExceptionInfo *) NULL);
1599 assert(exception->signature == MagickSignature);
1600 despeckle_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1601 exception);
1602 if (despeckle_image == (Image *) NULL)
1603 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00001604 if (SetImageStorageClass(despeckle_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001605 {
cristy3ed852e2009-09-05 21:47:34 +00001606 despeckle_image=DestroyImage(despeckle_image);
1607 return((Image *) NULL);
1608 }
1609 /*
1610 Allocate image buffers.
1611 */
1612 length=(size_t) ((image->columns+2)*(image->rows+2));
cristy65b9f392011-02-22 14:22:54 +00001613 pixels=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1614 buffers=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1615 if ((pixels == (Quantum *) NULL) || (buffers == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001616 {
cristy65b9f392011-02-22 14:22:54 +00001617 if (buffers != (Quantum *) NULL)
1618 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1619 if (pixels != (Quantum *) NULL)
1620 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001621 despeckle_image=DestroyImage(despeckle_image);
1622 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1623 }
1624 /*
1625 Reduce speckle in the image.
1626 */
1627 status=MagickTrue;
cristy109695a2011-02-19 19:38:14 +00001628 number_channels=(size_t) (image->colorspace == CMYKColorspace ? 5 : 4);
cristy3ed852e2009-09-05 21:47:34 +00001629 image_view=AcquireCacheView(image);
1630 despeckle_view=AcquireCacheView(despeckle_image);
cristy8df3d002011-02-19 19:40:59 +00001631 for (i=0; i < (ssize_t) number_channels; i++)
cristy3ed852e2009-09-05 21:47:34 +00001632 {
cristy3ed852e2009-09-05 21:47:34 +00001633 register Quantum
1634 *buffer,
1635 *pixel;
1636
cristyc1488b52011-02-19 18:54:15 +00001637 register ssize_t
cristya58c3172011-02-19 19:23:11 +00001638 k,
cristyc1488b52011-02-19 18:54:15 +00001639 x;
1640
cristy117ff172010-08-15 21:35:32 +00001641 ssize_t
1642 j,
1643 y;
1644
cristy3ed852e2009-09-05 21:47:34 +00001645 if (status == MagickFalse)
1646 continue;
cristy65b9f392011-02-22 14:22:54 +00001647 pixel=pixels;
cristy3ed852e2009-09-05 21:47:34 +00001648 (void) ResetMagickMemory(pixel,0,length*sizeof(*pixel));
cristy65b9f392011-02-22 14:22:54 +00001649 buffer=buffers;
cristybb503372010-05-27 20:51:26 +00001650 j=(ssize_t) image->columns+2;
1651 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001652 {
cristy4c08aed2011-07-01 19:47:50 +00001653 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001654 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001655
1656 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001657 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001658 break;
1659 j++;
cristybb503372010-05-27 20:51:26 +00001660 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001661 {
cristya58c3172011-02-19 19:23:11 +00001662 switch (i)
cristy3ed852e2009-09-05 21:47:34 +00001663 {
cristy4c08aed2011-07-01 19:47:50 +00001664 case 0: pixel[j]=GetPixelRed(image,p); break;
1665 case 1: pixel[j]=GetPixelGreen(image,p); break;
1666 case 2: pixel[j]=GetPixelBlue(image,p); break;
1667 case 3: pixel[j]=GetPixelAlpha(image,p); break;
1668 case 4: pixel[j]=GetPixelBlack(image,p); break;
cristy3ed852e2009-09-05 21:47:34 +00001669 default: break;
1670 }
cristyed231572011-07-14 02:18:59 +00001671 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001672 j++;
1673 }
1674 j++;
1675 }
cristy3ed852e2009-09-05 21:47:34 +00001676 (void) ResetMagickMemory(buffer,0,length*sizeof(*buffer));
cristya58c3172011-02-19 19:23:11 +00001677 for (k=0; k < 4; k++)
cristy3ed852e2009-09-05 21:47:34 +00001678 {
cristya58c3172011-02-19 19:23:11 +00001679 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,1);
1680 Hull(-X[k],-Y[k],image->columns,image->rows,pixel,buffer,1);
1681 Hull(-X[k],-Y[k],image->columns,image->rows,pixel,buffer,-1);
1682 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,-1);
cristy3ed852e2009-09-05 21:47:34 +00001683 }
cristybb503372010-05-27 20:51:26 +00001684 j=(ssize_t) image->columns+2;
1685 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001686 {
1687 MagickBooleanType
1688 sync;
1689
cristy4c08aed2011-07-01 19:47:50 +00001690 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001691 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001692
1693 q=GetCacheViewAuthenticPixels(despeckle_view,0,y,despeckle_image->columns,
1694 1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001695 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001696 break;
1697 j++;
cristybb503372010-05-27 20:51:26 +00001698 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001699 {
cristya58c3172011-02-19 19:23:11 +00001700 switch (i)
cristy3ed852e2009-09-05 21:47:34 +00001701 {
cristy4c08aed2011-07-01 19:47:50 +00001702 case 0: SetPixelRed(despeckle_image,pixel[j],q); break;
1703 case 1: SetPixelGreen(despeckle_image,pixel[j],q); break;
1704 case 2: SetPixelBlue(despeckle_image,pixel[j],q); break;
1705 case 3: SetPixelAlpha(despeckle_image,pixel[j],q); break;
1706 case 4: SetPixelBlack(despeckle_image,pixel[j],q); break;
cristy3ed852e2009-09-05 21:47:34 +00001707 default: break;
1708 }
cristyed231572011-07-14 02:18:59 +00001709 q+=GetPixelChannels(despeckle_image);
cristy3ed852e2009-09-05 21:47:34 +00001710 j++;
1711 }
1712 sync=SyncCacheViewAuthenticPixels(despeckle_view,exception);
1713 if (sync == MagickFalse)
1714 {
1715 status=MagickFalse;
1716 break;
1717 }
1718 j++;
1719 }
1720 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1721 {
1722 MagickBooleanType
1723 proceed;
1724
cristya58c3172011-02-19 19:23:11 +00001725 proceed=SetImageProgress(image,DespeckleImageTag,(MagickOffsetType) i,
1726 number_channels);
cristy3ed852e2009-09-05 21:47:34 +00001727 if (proceed == MagickFalse)
1728 status=MagickFalse;
1729 }
1730 }
1731 despeckle_view=DestroyCacheView(despeckle_view);
1732 image_view=DestroyCacheView(image_view);
cristy65b9f392011-02-22 14:22:54 +00001733 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1734 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001735 despeckle_image->type=image->type;
1736 if (status == MagickFalse)
1737 despeckle_image=DestroyImage(despeckle_image);
1738 return(despeckle_image);
1739}
1740
1741/*
1742%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1743% %
1744% %
1745% %
1746% E d g e I m a g e %
1747% %
1748% %
1749% %
1750%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1751%
1752% EdgeImage() finds edges in an image. Radius defines the radius of the
1753% convolution filter. Use a radius of 0 and EdgeImage() selects a suitable
1754% radius for you.
1755%
1756% The format of the EdgeImage method is:
1757%
1758% Image *EdgeImage(const Image *image,const double radius,
cristy8ae632d2011-09-05 17:29:53 +00001759% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001760%
1761% A description of each parameter follows:
1762%
1763% o image: the image.
1764%
1765% o radius: the radius of the pixel neighborhood.
1766%
cristy8ae632d2011-09-05 17:29:53 +00001767% o sigma: the standard deviation of the Gaussian, in pixels.
1768%
cristy3ed852e2009-09-05 21:47:34 +00001769% o exception: return any errors or warnings in this structure.
1770%
1771*/
1772MagickExport Image *EdgeImage(const Image *image,const double radius,
cristy8ae632d2011-09-05 17:29:53 +00001773 const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001774{
1775 Image
1776 *edge_image;
1777
cristy41cbe682011-07-15 19:12:37 +00001778 KernelInfo
1779 *kernel_info;
cristy3ed852e2009-09-05 21:47:34 +00001780
cristybb503372010-05-27 20:51:26 +00001781 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001782 i;
1783
cristybb503372010-05-27 20:51:26 +00001784 size_t
cristy3ed852e2009-09-05 21:47:34 +00001785 width;
1786
cristy41cbe682011-07-15 19:12:37 +00001787 ssize_t
1788 j,
1789 u,
1790 v;
1791
cristy3ed852e2009-09-05 21:47:34 +00001792 assert(image != (const Image *) NULL);
1793 assert(image->signature == MagickSignature);
1794 if (image->debug != MagickFalse)
1795 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1796 assert(exception != (ExceptionInfo *) NULL);
1797 assert(exception->signature == MagickSignature);
cristy8ae632d2011-09-05 17:29:53 +00001798 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00001799 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00001800 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001801 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00001802 kernel_info->width=width;
1803 kernel_info->height=width;
cristy41cbe682011-07-15 19:12:37 +00001804 kernel_info->values=(double *) AcquireAlignedMemory(kernel_info->width,
1805 kernel_info->width*sizeof(*kernel_info->values));
1806 if (kernel_info->values == (double *) NULL)
1807 {
1808 kernel_info=DestroyKernelInfo(kernel_info);
1809 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1810 }
1811 j=(ssize_t) kernel_info->width/2;
1812 i=0;
1813 for (v=(-j); v <= j; v++)
1814 {
1815 for (u=(-j); u <= j; u++)
1816 {
1817 kernel_info->values[i]=(-1.0);
1818 i++;
1819 }
1820 }
1821 kernel_info->values[i/2]=(double) (width*width-1.0);
cristy0a922382011-07-16 15:30:34 +00001822 kernel_info->bias=image->bias;
cristy5e6be1e2011-07-16 01:23:39 +00001823 edge_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00001824 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00001825 return(edge_image);
1826}
1827
1828/*
1829%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1830% %
1831% %
1832% %
1833% E m b o s s I m a g e %
1834% %
1835% %
1836% %
1837%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1838%
1839% EmbossImage() returns a grayscale image with a three-dimensional effect.
1840% We convolve the image with a Gaussian operator of the given radius and
1841% standard deviation (sigma). For reasonable results, radius should be
1842% larger than sigma. Use a radius of 0 and Emboss() selects a suitable
1843% radius for you.
1844%
1845% The format of the EmbossImage method is:
1846%
1847% Image *EmbossImage(const Image *image,const double radius,
1848% const double sigma,ExceptionInfo *exception)
1849%
1850% A description of each parameter follows:
1851%
1852% o image: the image.
1853%
1854% o radius: the radius of the pixel neighborhood.
1855%
1856% o sigma: the standard deviation of the Gaussian, in pixels.
1857%
1858% o exception: return any errors or warnings in this structure.
1859%
1860*/
1861MagickExport Image *EmbossImage(const Image *image,const double radius,
1862 const double sigma,ExceptionInfo *exception)
1863{
cristy3ed852e2009-09-05 21:47:34 +00001864 Image
1865 *emboss_image;
1866
cristy41cbe682011-07-15 19:12:37 +00001867 KernelInfo
1868 *kernel_info;
1869
cristybb503372010-05-27 20:51:26 +00001870 register ssize_t
cristy47e00502009-12-17 19:19:57 +00001871 i;
1872
cristybb503372010-05-27 20:51:26 +00001873 size_t
cristy3ed852e2009-09-05 21:47:34 +00001874 width;
1875
cristy117ff172010-08-15 21:35:32 +00001876 ssize_t
1877 j,
1878 k,
1879 u,
1880 v;
1881
cristy41cbe682011-07-15 19:12:37 +00001882 assert(image != (const Image *) NULL);
cristy3ed852e2009-09-05 21:47:34 +00001883 assert(image->signature == MagickSignature);
1884 if (image->debug != MagickFalse)
1885 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1886 assert(exception != (ExceptionInfo *) NULL);
1887 assert(exception->signature == MagickSignature);
1888 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00001889 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00001890 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001891 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00001892 kernel_info->width=width;
1893 kernel_info->height=width;
cristy41cbe682011-07-15 19:12:37 +00001894 kernel_info->values=(double *) AcquireAlignedMemory(kernel_info->width,
1895 kernel_info->width*sizeof(*kernel_info->values));
1896 if (kernel_info->values == (double *) NULL)
1897 {
1898 kernel_info=DestroyKernelInfo(kernel_info);
1899 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1900 }
1901 j=(ssize_t) kernel_info->width/2;
cristy47e00502009-12-17 19:19:57 +00001902 k=j;
1903 i=0;
1904 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00001905 {
cristy47e00502009-12-17 19:19:57 +00001906 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00001907 {
cristy41cbe682011-07-15 19:12:37 +00001908 kernel_info->values[i]=(double) (((u < 0) || (v < 0) ? -8.0 : 8.0)*
cristy47e00502009-12-17 19:19:57 +00001909 exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
cristy4205a3c2010-09-12 20:19:59 +00001910 (2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +00001911 if (u != k)
cristy41cbe682011-07-15 19:12:37 +00001912 kernel_info->values[i]=0.0;
cristy3ed852e2009-09-05 21:47:34 +00001913 i++;
1914 }
cristy47e00502009-12-17 19:19:57 +00001915 k--;
cristy3ed852e2009-09-05 21:47:34 +00001916 }
cristy0a922382011-07-16 15:30:34 +00001917 kernel_info->bias=image->bias;
cristy5e6be1e2011-07-16 01:23:39 +00001918 emboss_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00001919 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00001920 if (emboss_image != (Image *) NULL)
cristy6d8c3d72011-08-22 01:20:01 +00001921 (void) EqualizeImage(emboss_image,exception);
cristy3ed852e2009-09-05 21:47:34 +00001922 return(emboss_image);
1923}
1924
1925/*
1926%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1927% %
1928% %
1929% %
1930% G a u s s i a n B l u r I m a g e %
1931% %
1932% %
1933% %
1934%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1935%
1936% GaussianBlurImage() blurs an image. We convolve the image with a
1937% Gaussian operator of the given radius and standard deviation (sigma).
1938% For reasonable results, the radius should be larger than sigma. Use a
1939% radius of 0 and GaussianBlurImage() selects a suitable radius for you
1940%
1941% The format of the GaussianBlurImage method is:
1942%
1943% Image *GaussianBlurImage(const Image *image,onst double radius,
cristy05c0c9a2011-09-05 23:16:13 +00001944% const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001945%
1946% A description of each parameter follows:
1947%
1948% o image: the image.
1949%
cristy3ed852e2009-09-05 21:47:34 +00001950% o radius: the radius of the Gaussian, in pixels, not counting the center
1951% pixel.
1952%
1953% o sigma: the standard deviation of the Gaussian, in pixels.
1954%
cristy05c0c9a2011-09-05 23:16:13 +00001955% o bias: the bias.
1956%
cristy3ed852e2009-09-05 21:47:34 +00001957% o exception: return any errors or warnings in this structure.
1958%
1959*/
cristy41cbe682011-07-15 19:12:37 +00001960MagickExport Image *GaussianBlurImage(const Image *image,const double radius,
cristy05c0c9a2011-09-05 23:16:13 +00001961 const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001962{
cristy3ed852e2009-09-05 21:47:34 +00001963 Image
1964 *blur_image;
1965
cristy41cbe682011-07-15 19:12:37 +00001966 KernelInfo
1967 *kernel_info;
1968
cristybb503372010-05-27 20:51:26 +00001969 register ssize_t
cristy47e00502009-12-17 19:19:57 +00001970 i;
1971
cristybb503372010-05-27 20:51:26 +00001972 size_t
cristy3ed852e2009-09-05 21:47:34 +00001973 width;
1974
cristy117ff172010-08-15 21:35:32 +00001975 ssize_t
1976 j,
1977 u,
1978 v;
1979
cristy3ed852e2009-09-05 21:47:34 +00001980 assert(image != (const Image *) NULL);
1981 assert(image->signature == MagickSignature);
1982 if (image->debug != MagickFalse)
1983 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1984 assert(exception != (ExceptionInfo *) NULL);
1985 assert(exception->signature == MagickSignature);
1986 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00001987 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00001988 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001989 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00001990 (void) ResetMagickMemory(kernel_info,0,sizeof(*kernel_info));
1991 kernel_info->width=width;
1992 kernel_info->height=width;
cristy05c0c9a2011-09-05 23:16:13 +00001993 kernel_info->bias=bias;
cristy41cbe682011-07-15 19:12:37 +00001994 kernel_info->signature=MagickSignature;
1995 kernel_info->values=(double *) AcquireAlignedMemory(kernel_info->width,
1996 kernel_info->width*sizeof(*kernel_info->values));
1997 if (kernel_info->values == (double *) NULL)
1998 {
1999 kernel_info=DestroyKernelInfo(kernel_info);
2000 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2001 }
2002 j=(ssize_t) kernel_info->width/2;
cristy3ed852e2009-09-05 21:47:34 +00002003 i=0;
cristy47e00502009-12-17 19:19:57 +00002004 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002005 {
cristy47e00502009-12-17 19:19:57 +00002006 for (u=(-j); u <= j; u++)
cristy41cbe682011-07-15 19:12:37 +00002007 {
2008 kernel_info->values[i]=(double) (exp(-((double) u*u+v*v)/(2.0*
2009 MagickSigma*MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
2010 i++;
2011 }
cristy3ed852e2009-09-05 21:47:34 +00002012 }
cristy5e6be1e2011-07-16 01:23:39 +00002013 blur_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00002014 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00002015 return(blur_image);
2016}
2017
2018/*
2019%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2020% %
2021% %
2022% %
cristy3ed852e2009-09-05 21:47:34 +00002023% M o t i o n B l u r I m a g e %
2024% %
2025% %
2026% %
2027%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2028%
2029% MotionBlurImage() simulates motion blur. We convolve the image with a
2030% Gaussian operator of the given radius and standard deviation (sigma).
2031% For reasonable results, radius should be larger than sigma. Use a
2032% radius of 0 and MotionBlurImage() selects a suitable radius for you.
2033% Angle gives the angle of the blurring motion.
2034%
2035% Andrew Protano contributed this effect.
2036%
2037% The format of the MotionBlurImage method is:
2038%
2039% Image *MotionBlurImage(const Image *image,const double radius,
2040% const double sigma,const double angle,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002041%
2042% A description of each parameter follows:
2043%
2044% o image: the image.
2045%
cristy3ed852e2009-09-05 21:47:34 +00002046% o radius: the radius of the Gaussian, in pixels, not counting
2047% the center pixel.
2048%
2049% o sigma: the standard deviation of the Gaussian, in pixels.
2050%
cristycee97112010-05-28 00:44:52 +00002051% o angle: Apply the effect along this angle.
cristy3ed852e2009-09-05 21:47:34 +00002052%
2053% o exception: return any errors or warnings in this structure.
2054%
2055*/
2056
cristybb503372010-05-27 20:51:26 +00002057static double *GetMotionBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +00002058{
cristy3ed852e2009-09-05 21:47:34 +00002059 double
cristy47e00502009-12-17 19:19:57 +00002060 *kernel,
cristy3ed852e2009-09-05 21:47:34 +00002061 normalize;
2062
cristybb503372010-05-27 20:51:26 +00002063 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002064 i;
2065
2066 /*
cristy47e00502009-12-17 19:19:57 +00002067 Generate a 1-D convolution kernel.
cristy3ed852e2009-09-05 21:47:34 +00002068 */
2069 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
2070 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
2071 if (kernel == (double *) NULL)
2072 return(kernel);
cristy3ed852e2009-09-05 21:47:34 +00002073 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00002074 for (i=0; i < (ssize_t) width; i++)
cristy47e00502009-12-17 19:19:57 +00002075 {
cristy4205a3c2010-09-12 20:19:59 +00002076 kernel[i]=(double) (exp((-((double) i*i)/(double) (2.0*MagickSigma*
2077 MagickSigma)))/(MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00002078 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +00002079 }
cristybb503372010-05-27 20:51:26 +00002080 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002081 kernel[i]/=normalize;
2082 return(kernel);
2083}
2084
cristyf4ad9df2011-07-08 16:49:03 +00002085MagickExport Image *MotionBlurImage(const Image *image,
2086 const double radius,const double sigma,const double angle,
2087 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002088{
cristyc4c8d132010-01-07 01:58:38 +00002089 CacheView
2090 *blur_view,
2091 *image_view;
2092
cristy3ed852e2009-09-05 21:47:34 +00002093 double
2094 *kernel;
2095
2096 Image
2097 *blur_image;
2098
cristy3ed852e2009-09-05 21:47:34 +00002099 MagickBooleanType
2100 status;
2101
cristybb503372010-05-27 20:51:26 +00002102 MagickOffsetType
2103 progress;
2104
cristy4c08aed2011-07-01 19:47:50 +00002105 PixelInfo
cristyddd82202009-11-03 20:14:50 +00002106 bias;
cristy3ed852e2009-09-05 21:47:34 +00002107
2108 OffsetInfo
2109 *offset;
2110
2111 PointInfo
2112 point;
2113
cristybb503372010-05-27 20:51:26 +00002114 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002115 i;
2116
cristybb503372010-05-27 20:51:26 +00002117 size_t
cristy3ed852e2009-09-05 21:47:34 +00002118 width;
2119
cristybb503372010-05-27 20:51:26 +00002120 ssize_t
2121 y;
2122
cristy3ed852e2009-09-05 21:47:34 +00002123 assert(image != (Image *) NULL);
2124 assert(image->signature == MagickSignature);
2125 if (image->debug != MagickFalse)
2126 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2127 assert(exception != (ExceptionInfo *) NULL);
2128 width=GetOptimalKernelWidth1D(radius,sigma);
2129 kernel=GetMotionBlurKernel(width,sigma);
2130 if (kernel == (double *) NULL)
2131 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2132 offset=(OffsetInfo *) AcquireQuantumMemory(width,sizeof(*offset));
2133 if (offset == (OffsetInfo *) NULL)
2134 {
2135 kernel=(double *) RelinquishMagickMemory(kernel);
2136 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2137 }
2138 blur_image=CloneImage(image,0,0,MagickTrue,exception);
2139 if (blur_image == (Image *) NULL)
2140 {
2141 kernel=(double *) RelinquishMagickMemory(kernel);
2142 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2143 return((Image *) NULL);
2144 }
cristy574cc262011-08-05 01:23:58 +00002145 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00002146 {
2147 kernel=(double *) RelinquishMagickMemory(kernel);
2148 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
cristy3ed852e2009-09-05 21:47:34 +00002149 blur_image=DestroyImage(blur_image);
2150 return((Image *) NULL);
2151 }
2152 point.x=(double) width*sin(DegreesToRadians(angle));
2153 point.y=(double) width*cos(DegreesToRadians(angle));
cristybb503372010-05-27 20:51:26 +00002154 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002155 {
cristybb503372010-05-27 20:51:26 +00002156 offset[i].x=(ssize_t) ceil((double) (i*point.y)/hypot(point.x,point.y)-0.5);
2157 offset[i].y=(ssize_t) ceil((double) (i*point.x)/hypot(point.x,point.y)-0.5);
cristy3ed852e2009-09-05 21:47:34 +00002158 }
2159 /*
2160 Motion blur image.
2161 */
2162 status=MagickTrue;
2163 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00002164 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00002165 image_view=AcquireCacheView(image);
2166 blur_view=AcquireCacheView(blur_image);
cristyb557a152011-02-22 12:14:30 +00002167#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00002168 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00002169#endif
cristybb503372010-05-27 20:51:26 +00002170 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002171 {
cristy4c08aed2011-07-01 19:47:50 +00002172 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002173 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002174
cristy117ff172010-08-15 21:35:32 +00002175 register ssize_t
2176 x;
2177
cristy3ed852e2009-09-05 21:47:34 +00002178 if (status == MagickFalse)
2179 continue;
2180 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
2181 exception);
cristyacd2ed22011-08-30 01:44:23 +00002182 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002183 {
2184 status=MagickFalse;
2185 continue;
2186 }
cristybb503372010-05-27 20:51:26 +00002187 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002188 {
cristy4c08aed2011-07-01 19:47:50 +00002189 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002190 qixel;
2191
2192 PixelPacket
2193 pixel;
2194
2195 register double
cristyc47d1f82009-11-26 01:44:43 +00002196 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00002197
cristybb503372010-05-27 20:51:26 +00002198 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002199 i;
2200
cristy3ed852e2009-09-05 21:47:34 +00002201 k=kernel;
cristyddd82202009-11-03 20:14:50 +00002202 qixel=bias;
cristyed231572011-07-14 02:18:59 +00002203 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00002204 {
cristybb503372010-05-27 20:51:26 +00002205 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002206 {
2207 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
2208 offset[i].y,&pixel,exception);
2209 qixel.red+=(*k)*pixel.red;
2210 qixel.green+=(*k)*pixel.green;
2211 qixel.blue+=(*k)*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00002212 qixel.alpha+=(*k)*pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00002213 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00002214 qixel.black+=(*k)*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00002215 k++;
2216 }
cristyed231572011-07-14 02:18:59 +00002217 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002218 SetPixelRed(blur_image,
2219 ClampToQuantum(qixel.red),q);
cristyed231572011-07-14 02:18:59 +00002220 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002221 SetPixelGreen(blur_image,
2222 ClampToQuantum(qixel.green),q);
cristyed231572011-07-14 02:18:59 +00002223 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002224 SetPixelBlue(blur_image,
2225 ClampToQuantum(qixel.blue),q);
cristyed231572011-07-14 02:18:59 +00002226 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002227 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002228 SetPixelBlack(blur_image,
2229 ClampToQuantum(qixel.black),q);
cristyed231572011-07-14 02:18:59 +00002230 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002231 SetPixelAlpha(blur_image,
2232 ClampToQuantum(qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00002233 }
2234 else
2235 {
2236 MagickRealType
2237 alpha,
2238 gamma;
2239
2240 alpha=0.0;
2241 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00002242 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002243 {
2244 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
2245 offset[i].y,&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00002246 alpha=(MagickRealType) (QuantumScale*pixel.alpha);
cristy3ed852e2009-09-05 21:47:34 +00002247 qixel.red+=(*k)*alpha*pixel.red;
2248 qixel.green+=(*k)*alpha*pixel.green;
2249 qixel.blue+=(*k)*alpha*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00002250 qixel.alpha+=(*k)*pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00002251 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00002252 qixel.black+=(*k)*alpha*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00002253 gamma+=(*k)*alpha;
2254 k++;
2255 }
2256 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +00002257 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002258 SetPixelRed(blur_image,
2259 ClampToQuantum(gamma*qixel.red),q);
cristyed231572011-07-14 02:18:59 +00002260 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002261 SetPixelGreen(blur_image,
2262 ClampToQuantum(gamma*qixel.green),q);
cristyed231572011-07-14 02:18:59 +00002263 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002264 SetPixelBlue(blur_image,
2265 ClampToQuantum(gamma*qixel.blue),q);
cristyed231572011-07-14 02:18:59 +00002266 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002267 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002268 SetPixelBlack(blur_image,
2269 ClampToQuantum(gamma*qixel.black),q);
cristyed231572011-07-14 02:18:59 +00002270 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002271 SetPixelAlpha(blur_image,
2272 ClampToQuantum(qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00002273 }
cristyed231572011-07-14 02:18:59 +00002274 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00002275 }
2276 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
2277 status=MagickFalse;
2278 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2279 {
2280 MagickBooleanType
2281 proceed;
2282
cristyb557a152011-02-22 12:14:30 +00002283#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00002284 #pragma omp critical (MagickCore_MotionBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00002285#endif
2286 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
2287 if (proceed == MagickFalse)
2288 status=MagickFalse;
2289 }
2290 }
2291 blur_view=DestroyCacheView(blur_view);
2292 image_view=DestroyCacheView(image_view);
2293 kernel=(double *) RelinquishMagickMemory(kernel);
2294 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2295 if (status == MagickFalse)
2296 blur_image=DestroyImage(blur_image);
2297 return(blur_image);
2298}
2299
2300/*
2301%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2302% %
2303% %
2304% %
2305% P r e v i e w I m a g e %
2306% %
2307% %
2308% %
2309%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2310%
2311% PreviewImage() tiles 9 thumbnails of the specified image with an image
2312% processing operation applied with varying parameters. This may be helpful
2313% pin-pointing an appropriate parameter for a particular image processing
2314% operation.
2315%
2316% The format of the PreviewImages method is:
2317%
2318% Image *PreviewImages(const Image *image,const PreviewType preview,
2319% ExceptionInfo *exception)
2320%
2321% A description of each parameter follows:
2322%
2323% o image: the image.
2324%
2325% o preview: the image processing operation.
2326%
2327% o exception: return any errors or warnings in this structure.
2328%
2329*/
2330MagickExport Image *PreviewImage(const Image *image,const PreviewType preview,
2331 ExceptionInfo *exception)
2332{
2333#define NumberTiles 9
2334#define PreviewImageTag "Preview/Image"
2335#define DefaultPreviewGeometry "204x204+10+10"
2336
2337 char
2338 factor[MaxTextExtent],
2339 label[MaxTextExtent];
2340
2341 double
2342 degrees,
2343 gamma,
2344 percentage,
2345 radius,
2346 sigma,
2347 threshold;
2348
2349 Image
2350 *images,
2351 *montage_image,
2352 *preview_image,
2353 *thumbnail;
2354
2355 ImageInfo
2356 *preview_info;
2357
cristy3ed852e2009-09-05 21:47:34 +00002358 MagickBooleanType
2359 proceed;
2360
2361 MontageInfo
2362 *montage_info;
2363
2364 QuantizeInfo
2365 quantize_info;
2366
2367 RectangleInfo
2368 geometry;
2369
cristybb503372010-05-27 20:51:26 +00002370 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002371 i,
2372 x;
2373
cristybb503372010-05-27 20:51:26 +00002374 size_t
cristy3ed852e2009-09-05 21:47:34 +00002375 colors;
2376
cristy117ff172010-08-15 21:35:32 +00002377 ssize_t
2378 y;
2379
cristy3ed852e2009-09-05 21:47:34 +00002380 /*
2381 Open output image file.
2382 */
2383 assert(image != (Image *) NULL);
2384 assert(image->signature == MagickSignature);
2385 if (image->debug != MagickFalse)
2386 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2387 colors=2;
2388 degrees=0.0;
2389 gamma=(-0.2f);
2390 preview_info=AcquireImageInfo();
2391 SetGeometry(image,&geometry);
2392 (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y,
2393 &geometry.width,&geometry.height);
2394 images=NewImageList();
2395 percentage=12.5;
2396 GetQuantizeInfo(&quantize_info);
2397 radius=0.0;
2398 sigma=1.0;
2399 threshold=0.0;
2400 x=0;
2401 y=0;
2402 for (i=0; i < NumberTiles; i++)
2403 {
2404 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
2405 if (thumbnail == (Image *) NULL)
2406 break;
2407 (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL,
2408 (void *) NULL);
2409 (void) SetImageProperty(thumbnail,"label",DefaultTileLabel);
2410 if (i == (NumberTiles/2))
2411 {
2412 (void) QueryColorDatabase("#dfdfdf",&thumbnail->matte_color,exception);
2413 AppendImageToList(&images,thumbnail);
2414 continue;
2415 }
2416 switch (preview)
2417 {
2418 case RotatePreview:
2419 {
2420 degrees+=45.0;
2421 preview_image=RotateImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002422 (void) FormatLocaleString(label,MaxTextExtent,"rotate %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002423 break;
2424 }
2425 case ShearPreview:
2426 {
2427 degrees+=5.0;
2428 preview_image=ShearImage(thumbnail,degrees,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002429 (void) FormatLocaleString(label,MaxTextExtent,"shear %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002430 degrees,2.0*degrees);
2431 break;
2432 }
2433 case RollPreview:
2434 {
cristybb503372010-05-27 20:51:26 +00002435 x=(ssize_t) ((i+1)*thumbnail->columns)/NumberTiles;
2436 y=(ssize_t) ((i+1)*thumbnail->rows)/NumberTiles;
cristy3ed852e2009-09-05 21:47:34 +00002437 preview_image=RollImage(thumbnail,x,y,exception);
cristyb51dff52011-05-19 16:55:47 +00002438 (void) FormatLocaleString(label,MaxTextExtent,"roll %+.20gx%+.20g",
cristye8c25f92010-06-03 00:53:06 +00002439 (double) x,(double) y);
cristy3ed852e2009-09-05 21:47:34 +00002440 break;
2441 }
2442 case HuePreview:
2443 {
2444 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2445 if (preview_image == (Image *) NULL)
2446 break;
cristyb51dff52011-05-19 16:55:47 +00002447 (void) FormatLocaleString(factor,MaxTextExtent,"100,100,%g",
cristy3ed852e2009-09-05 21:47:34 +00002448 2.0*percentage);
cristy33bd5152011-08-24 01:42:24 +00002449 (void) ModulateImage(preview_image,factor,exception);
cristyb51dff52011-05-19 16:55:47 +00002450 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002451 break;
2452 }
2453 case SaturationPreview:
2454 {
2455 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2456 if (preview_image == (Image *) NULL)
2457 break;
cristyb51dff52011-05-19 16:55:47 +00002458 (void) FormatLocaleString(factor,MaxTextExtent,"100,%g",
cristy8cd5b312010-01-07 01:10:24 +00002459 2.0*percentage);
cristy33bd5152011-08-24 01:42:24 +00002460 (void) ModulateImage(preview_image,factor,exception);
cristyb51dff52011-05-19 16:55:47 +00002461 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002462 break;
2463 }
2464 case BrightnessPreview:
2465 {
2466 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2467 if (preview_image == (Image *) NULL)
2468 break;
cristyb51dff52011-05-19 16:55:47 +00002469 (void) FormatLocaleString(factor,MaxTextExtent,"%g",2.0*percentage);
cristy33bd5152011-08-24 01:42:24 +00002470 (void) ModulateImage(preview_image,factor,exception);
cristyb51dff52011-05-19 16:55:47 +00002471 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002472 break;
2473 }
2474 case GammaPreview:
2475 default:
2476 {
2477 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2478 if (preview_image == (Image *) NULL)
2479 break;
2480 gamma+=0.4f;
cristyb3e7c6c2011-07-24 01:43:55 +00002481 (void) GammaImage(preview_image,gamma,exception);
cristyb51dff52011-05-19 16:55:47 +00002482 (void) FormatLocaleString(label,MaxTextExtent,"gamma %g",gamma);
cristy3ed852e2009-09-05 21:47:34 +00002483 break;
2484 }
2485 case SpiffPreview:
2486 {
2487 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2488 if (preview_image != (Image *) NULL)
2489 for (x=0; x < i; x++)
cristye23ec9d2011-08-16 18:15:40 +00002490 (void) ContrastImage(preview_image,MagickTrue,exception);
cristyb51dff52011-05-19 16:55:47 +00002491 (void) FormatLocaleString(label,MaxTextExtent,"contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002492 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002493 break;
2494 }
2495 case DullPreview:
2496 {
2497 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2498 if (preview_image == (Image *) NULL)
2499 break;
2500 for (x=0; x < i; x++)
cristye23ec9d2011-08-16 18:15:40 +00002501 (void) ContrastImage(preview_image,MagickFalse,exception);
cristyb51dff52011-05-19 16:55:47 +00002502 (void) FormatLocaleString(label,MaxTextExtent,"+contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002503 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002504 break;
2505 }
2506 case GrayscalePreview:
2507 {
2508 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2509 if (preview_image == (Image *) NULL)
2510 break;
2511 colors<<=1;
2512 quantize_info.number_colors=colors;
2513 quantize_info.colorspace=GRAYColorspace;
cristy018f07f2011-09-04 21:15:19 +00002514 (void) QuantizeImage(&quantize_info,preview_image,exception);
cristyb51dff52011-05-19 16:55:47 +00002515 (void) FormatLocaleString(label,MaxTextExtent,
cristye8c25f92010-06-03 00:53:06 +00002516 "-colorspace gray -colors %.20g",(double) colors);
cristy3ed852e2009-09-05 21:47:34 +00002517 break;
2518 }
2519 case QuantizePreview:
2520 {
2521 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2522 if (preview_image == (Image *) NULL)
2523 break;
2524 colors<<=1;
2525 quantize_info.number_colors=colors;
cristy018f07f2011-09-04 21:15:19 +00002526 (void) QuantizeImage(&quantize_info,preview_image,exception);
cristyb51dff52011-05-19 16:55:47 +00002527 (void) FormatLocaleString(label,MaxTextExtent,"colors %.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00002528 colors);
cristy3ed852e2009-09-05 21:47:34 +00002529 break;
2530 }
2531 case DespecklePreview:
2532 {
2533 for (x=0; x < (i-1); x++)
2534 {
2535 preview_image=DespeckleImage(thumbnail,exception);
2536 if (preview_image == (Image *) NULL)
2537 break;
2538 thumbnail=DestroyImage(thumbnail);
2539 thumbnail=preview_image;
2540 }
2541 preview_image=DespeckleImage(thumbnail,exception);
2542 if (preview_image == (Image *) NULL)
2543 break;
cristyb51dff52011-05-19 16:55:47 +00002544 (void) FormatLocaleString(label,MaxTextExtent,"despeckle (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002545 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002546 break;
2547 }
2548 case ReduceNoisePreview:
2549 {
cristy95c38342011-03-18 22:39:51 +00002550 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) radius,
2551 (size_t) radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002552 (void) FormatLocaleString(label,MaxTextExtent,"noise %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002553 break;
2554 }
2555 case AddNoisePreview:
2556 {
2557 switch ((int) i)
2558 {
2559 case 0:
2560 {
2561 (void) CopyMagickString(factor,"uniform",MaxTextExtent);
2562 break;
2563 }
2564 case 1:
2565 {
2566 (void) CopyMagickString(factor,"gaussian",MaxTextExtent);
2567 break;
2568 }
2569 case 2:
2570 {
2571 (void) CopyMagickString(factor,"multiplicative",MaxTextExtent);
2572 break;
2573 }
2574 case 3:
2575 {
2576 (void) CopyMagickString(factor,"impulse",MaxTextExtent);
2577 break;
2578 }
2579 case 4:
2580 {
2581 (void) CopyMagickString(factor,"laplacian",MaxTextExtent);
2582 break;
2583 }
2584 case 5:
2585 {
2586 (void) CopyMagickString(factor,"Poisson",MaxTextExtent);
2587 break;
2588 }
2589 default:
2590 {
2591 (void) CopyMagickString(thumbnail->magick,"NULL",MaxTextExtent);
2592 break;
2593 }
2594 }
cristyd76c51e2011-03-26 00:21:26 +00002595 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) i,
2596 (size_t) i,exception);
cristyb51dff52011-05-19 16:55:47 +00002597 (void) FormatLocaleString(label,MaxTextExtent,"+noise %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002598 break;
2599 }
2600 case SharpenPreview:
2601 {
cristy05c0c9a2011-09-05 23:16:13 +00002602 preview_image=SharpenImage(thumbnail,radius,sigma,image->bias,
2603 exception);
cristyb51dff52011-05-19 16:55:47 +00002604 (void) FormatLocaleString(label,MaxTextExtent,"sharpen %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002605 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002606 break;
2607 }
2608 case BlurPreview:
2609 {
cristy05c0c9a2011-09-05 23:16:13 +00002610 preview_image=BlurImage(thumbnail,radius,sigma,image->bias,exception);
cristyb51dff52011-05-19 16:55:47 +00002611 (void) FormatLocaleString(label,MaxTextExtent,"blur %gx%g",radius,
cristy3ed852e2009-09-05 21:47:34 +00002612 sigma);
2613 break;
2614 }
2615 case ThresholdPreview:
2616 {
2617 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2618 if (preview_image == (Image *) NULL)
2619 break;
2620 (void) BilevelImage(thumbnail,
2621 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
cristyb51dff52011-05-19 16:55:47 +00002622 (void) FormatLocaleString(label,MaxTextExtent,"threshold %g",
cristy3ed852e2009-09-05 21:47:34 +00002623 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
2624 break;
2625 }
2626 case EdgeDetectPreview:
2627 {
cristy8ae632d2011-09-05 17:29:53 +00002628 preview_image=EdgeImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00002629 (void) FormatLocaleString(label,MaxTextExtent,"edge %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002630 break;
2631 }
2632 case SpreadPreview:
2633 {
2634 preview_image=SpreadImage(thumbnail,radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002635 (void) FormatLocaleString(label,MaxTextExtent,"spread %g",
cristy8cd5b312010-01-07 01:10:24 +00002636 radius+0.5);
cristy3ed852e2009-09-05 21:47:34 +00002637 break;
2638 }
2639 case SolarizePreview:
2640 {
2641 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2642 if (preview_image == (Image *) NULL)
2643 break;
2644 (void) SolarizeImage(preview_image,(double) QuantumRange*
cristy5cbc0162011-08-29 00:36:28 +00002645 percentage/100.0,exception);
cristyb51dff52011-05-19 16:55:47 +00002646 (void) FormatLocaleString(label,MaxTextExtent,"solarize %g",
cristy3ed852e2009-09-05 21:47:34 +00002647 (QuantumRange*percentage)/100.0);
2648 break;
2649 }
2650 case ShadePreview:
2651 {
2652 degrees+=10.0;
2653 preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees,
2654 exception);
cristyb51dff52011-05-19 16:55:47 +00002655 (void) FormatLocaleString(label,MaxTextExtent,"shade %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002656 degrees,degrees);
cristy3ed852e2009-09-05 21:47:34 +00002657 break;
2658 }
2659 case RaisePreview:
2660 {
2661 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2662 if (preview_image == (Image *) NULL)
2663 break;
cristybb503372010-05-27 20:51:26 +00002664 geometry.width=(size_t) (2*i+2);
2665 geometry.height=(size_t) (2*i+2);
cristy3ed852e2009-09-05 21:47:34 +00002666 geometry.x=i/2;
2667 geometry.y=i/2;
cristy6170ac32011-08-28 14:15:37 +00002668 (void) RaiseImage(preview_image,&geometry,MagickTrue,exception);
cristyb51dff52011-05-19 16:55:47 +00002669 (void) FormatLocaleString(label,MaxTextExtent,
cristy6d8abba2010-06-03 01:10:47 +00002670 "raise %.20gx%.20g%+.20g%+.20g",(double) geometry.width,(double)
cristye8c25f92010-06-03 00:53:06 +00002671 geometry.height,(double) geometry.x,(double) geometry.y);
cristy3ed852e2009-09-05 21:47:34 +00002672 break;
2673 }
2674 case SegmentPreview:
2675 {
2676 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2677 if (preview_image == (Image *) NULL)
2678 break;
2679 threshold+=0.4f;
2680 (void) SegmentImage(preview_image,RGBColorspace,MagickFalse,threshold,
cristy018f07f2011-09-04 21:15:19 +00002681 threshold,exception);
cristyb51dff52011-05-19 16:55:47 +00002682 (void) FormatLocaleString(label,MaxTextExtent,"segment %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002683 threshold,threshold);
2684 break;
2685 }
2686 case SwirlPreview:
2687 {
2688 preview_image=SwirlImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002689 (void) FormatLocaleString(label,MaxTextExtent,"swirl %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002690 degrees+=45.0;
2691 break;
2692 }
2693 case ImplodePreview:
2694 {
2695 degrees+=0.1f;
2696 preview_image=ImplodeImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002697 (void) FormatLocaleString(label,MaxTextExtent,"implode %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002698 break;
2699 }
2700 case WavePreview:
2701 {
2702 degrees+=5.0f;
2703 preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002704 (void) FormatLocaleString(label,MaxTextExtent,"wave %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002705 0.5*degrees,2.0*degrees);
cristy3ed852e2009-09-05 21:47:34 +00002706 break;
2707 }
2708 case OilPaintPreview:
2709 {
cristy14973ba2011-08-27 23:48:07 +00002710 preview_image=OilPaintImage(thumbnail,(double) radius,(double) sigma,
2711 exception);
2712 (void) FormatLocaleString(label,MaxTextExtent,"charcoal %gx%g",
2713 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002714 break;
2715 }
2716 case CharcoalDrawingPreview:
2717 {
2718 preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma,
cristy05c0c9a2011-09-05 23:16:13 +00002719 image->bias,exception);
cristyb51dff52011-05-19 16:55:47 +00002720 (void) FormatLocaleString(label,MaxTextExtent,"charcoal %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002721 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002722 break;
2723 }
2724 case JPEGPreview:
2725 {
2726 char
2727 filename[MaxTextExtent];
2728
2729 int
2730 file;
2731
2732 MagickBooleanType
2733 status;
2734
2735 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2736 if (preview_image == (Image *) NULL)
2737 break;
cristybb503372010-05-27 20:51:26 +00002738 preview_info->quality=(size_t) percentage;
cristyb51dff52011-05-19 16:55:47 +00002739 (void) FormatLocaleString(factor,MaxTextExtent,"%.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00002740 preview_info->quality);
cristy3ed852e2009-09-05 21:47:34 +00002741 file=AcquireUniqueFileResource(filename);
2742 if (file != -1)
2743 file=close(file)-1;
cristyb51dff52011-05-19 16:55:47 +00002744 (void) FormatLocaleString(preview_image->filename,MaxTextExtent,
cristy3ed852e2009-09-05 21:47:34 +00002745 "jpeg:%s",filename);
cristy6f9e0d32011-08-28 16:32:09 +00002746 status=WriteImage(preview_info,preview_image,exception);
cristy3ed852e2009-09-05 21:47:34 +00002747 if (status != MagickFalse)
2748 {
2749 Image
2750 *quality_image;
2751
2752 (void) CopyMagickString(preview_info->filename,
2753 preview_image->filename,MaxTextExtent);
2754 quality_image=ReadImage(preview_info,exception);
2755 if (quality_image != (Image *) NULL)
2756 {
2757 preview_image=DestroyImage(preview_image);
2758 preview_image=quality_image;
2759 }
2760 }
2761 (void) RelinquishUniqueFileResource(preview_image->filename);
2762 if ((GetBlobSize(preview_image)/1024) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00002763 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%gmb ",
cristy3ed852e2009-09-05 21:47:34 +00002764 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
2765 1024.0/1024.0);
2766 else
2767 if (GetBlobSize(preview_image) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00002768 (void) FormatLocaleString(label,MaxTextExtent,
cristye7f51092010-01-17 00:39:37 +00002769 "quality %s\n%gkb ",factor,(double) ((MagickOffsetType)
cristy8cd5b312010-01-07 01:10:24 +00002770 GetBlobSize(preview_image))/1024.0);
cristy3ed852e2009-09-05 21:47:34 +00002771 else
cristyb51dff52011-05-19 16:55:47 +00002772 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%.20gb ",
cristy54ea5732011-06-10 12:39:53 +00002773 factor,(double) ((MagickOffsetType) GetBlobSize(thumbnail)));
cristy3ed852e2009-09-05 21:47:34 +00002774 break;
2775 }
2776 }
2777 thumbnail=DestroyImage(thumbnail);
2778 percentage+=12.5;
2779 radius+=0.5;
2780 sigma+=0.25;
2781 if (preview_image == (Image *) NULL)
2782 break;
2783 (void) DeleteImageProperty(preview_image,"label");
2784 (void) SetImageProperty(preview_image,"label",label);
2785 AppendImageToList(&images,preview_image);
cristybb503372010-05-27 20:51:26 +00002786 proceed=SetImageProgress(image,PreviewImageTag,(MagickOffsetType) i,
2787 NumberTiles);
cristy3ed852e2009-09-05 21:47:34 +00002788 if (proceed == MagickFalse)
2789 break;
2790 }
2791 if (images == (Image *) NULL)
2792 {
2793 preview_info=DestroyImageInfo(preview_info);
2794 return((Image *) NULL);
2795 }
2796 /*
2797 Create the montage.
2798 */
2799 montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL);
2800 (void) CopyMagickString(montage_info->filename,image->filename,MaxTextExtent);
2801 montage_info->shadow=MagickTrue;
2802 (void) CloneString(&montage_info->tile,"3x3");
2803 (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry);
2804 (void) CloneString(&montage_info->frame,DefaultTileFrame);
2805 montage_image=MontageImages(images,montage_info,exception);
2806 montage_info=DestroyMontageInfo(montage_info);
2807 images=DestroyImageList(images);
2808 if (montage_image == (Image *) NULL)
2809 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2810 if (montage_image->montage != (char *) NULL)
2811 {
2812 /*
2813 Free image directory.
2814 */
2815 montage_image->montage=(char *) RelinquishMagickMemory(
2816 montage_image->montage);
2817 if (image->directory != (char *) NULL)
2818 montage_image->directory=(char *) RelinquishMagickMemory(
2819 montage_image->directory);
2820 }
2821 preview_info=DestroyImageInfo(preview_info);
2822 return(montage_image);
2823}
2824
2825/*
2826%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2827% %
2828% %
2829% %
2830% R a d i a l B l u r I m a g e %
2831% %
2832% %
2833% %
2834%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2835%
2836% RadialBlurImage() applies a radial blur to the image.
2837%
2838% Andrew Protano contributed this effect.
2839%
2840% The format of the RadialBlurImage method is:
2841%
2842% Image *RadialBlurImage(const Image *image,const double angle,
2843% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002844%
2845% A description of each parameter follows:
2846%
2847% o image: the image.
2848%
cristy3ed852e2009-09-05 21:47:34 +00002849% o angle: the angle of the radial blur.
2850%
2851% o exception: return any errors or warnings in this structure.
2852%
2853*/
cristyf4ad9df2011-07-08 16:49:03 +00002854MagickExport Image *RadialBlurImage(const Image *image,
2855 const double angle,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002856{
cristyc4c8d132010-01-07 01:58:38 +00002857 CacheView
2858 *blur_view,
2859 *image_view;
2860
cristy3ed852e2009-09-05 21:47:34 +00002861 Image
2862 *blur_image;
2863
cristy3ed852e2009-09-05 21:47:34 +00002864 MagickBooleanType
2865 status;
2866
cristybb503372010-05-27 20:51:26 +00002867 MagickOffsetType
2868 progress;
2869
cristy4c08aed2011-07-01 19:47:50 +00002870 PixelInfo
cristyddd82202009-11-03 20:14:50 +00002871 bias;
cristy3ed852e2009-09-05 21:47:34 +00002872
2873 MagickRealType
2874 blur_radius,
2875 *cos_theta,
2876 offset,
2877 *sin_theta,
2878 theta;
2879
2880 PointInfo
2881 blur_center;
2882
cristybb503372010-05-27 20:51:26 +00002883 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002884 i;
2885
cristybb503372010-05-27 20:51:26 +00002886 size_t
cristy3ed852e2009-09-05 21:47:34 +00002887 n;
2888
cristybb503372010-05-27 20:51:26 +00002889 ssize_t
2890 y;
2891
cristy3ed852e2009-09-05 21:47:34 +00002892 /*
2893 Allocate blur image.
2894 */
2895 assert(image != (Image *) NULL);
2896 assert(image->signature == MagickSignature);
2897 if (image->debug != MagickFalse)
2898 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2899 assert(exception != (ExceptionInfo *) NULL);
2900 assert(exception->signature == MagickSignature);
2901 blur_image=CloneImage(image,0,0,MagickTrue,exception);
2902 if (blur_image == (Image *) NULL)
2903 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00002904 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00002905 {
cristy3ed852e2009-09-05 21:47:34 +00002906 blur_image=DestroyImage(blur_image);
2907 return((Image *) NULL);
2908 }
2909 blur_center.x=(double) image->columns/2.0;
2910 blur_center.y=(double) image->rows/2.0;
2911 blur_radius=hypot(blur_center.x,blur_center.y);
cristy117ff172010-08-15 21:35:32 +00002912 n=(size_t) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+2UL);
cristy3ed852e2009-09-05 21:47:34 +00002913 theta=DegreesToRadians(angle)/(MagickRealType) (n-1);
2914 cos_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
2915 sizeof(*cos_theta));
2916 sin_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
2917 sizeof(*sin_theta));
2918 if ((cos_theta == (MagickRealType *) NULL) ||
2919 (sin_theta == (MagickRealType *) NULL))
2920 {
2921 blur_image=DestroyImage(blur_image);
2922 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2923 }
2924 offset=theta*(MagickRealType) (n-1)/2.0;
cristybb503372010-05-27 20:51:26 +00002925 for (i=0; i < (ssize_t) n; i++)
cristy3ed852e2009-09-05 21:47:34 +00002926 {
2927 cos_theta[i]=cos((double) (theta*i-offset));
2928 sin_theta[i]=sin((double) (theta*i-offset));
2929 }
2930 /*
2931 Radial blur image.
2932 */
2933 status=MagickTrue;
2934 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00002935 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00002936 image_view=AcquireCacheView(image);
2937 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00002938#if defined(MAGICKCORE_OPENMP_SUPPORT)
2939 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002940#endif
cristybb503372010-05-27 20:51:26 +00002941 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002942 {
cristy4c08aed2011-07-01 19:47:50 +00002943 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002944 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002945
cristy117ff172010-08-15 21:35:32 +00002946 register ssize_t
2947 x;
2948
cristy3ed852e2009-09-05 21:47:34 +00002949 if (status == MagickFalse)
2950 continue;
2951 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
2952 exception);
cristyacd2ed22011-08-30 01:44:23 +00002953 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002954 {
2955 status=MagickFalse;
2956 continue;
2957 }
cristybb503372010-05-27 20:51:26 +00002958 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002959 {
cristy4c08aed2011-07-01 19:47:50 +00002960 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002961 qixel;
2962
2963 MagickRealType
2964 normalize,
2965 radius;
2966
2967 PixelPacket
2968 pixel;
2969
2970 PointInfo
2971 center;
2972
cristybb503372010-05-27 20:51:26 +00002973 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002974 i;
2975
cristybb503372010-05-27 20:51:26 +00002976 size_t
cristy3ed852e2009-09-05 21:47:34 +00002977 step;
2978
2979 center.x=(double) x-blur_center.x;
2980 center.y=(double) y-blur_center.y;
2981 radius=hypot((double) center.x,center.y);
2982 if (radius == 0)
2983 step=1;
2984 else
2985 {
cristybb503372010-05-27 20:51:26 +00002986 step=(size_t) (blur_radius/radius);
cristy3ed852e2009-09-05 21:47:34 +00002987 if (step == 0)
2988 step=1;
2989 else
2990 if (step >= n)
2991 step=n-1;
2992 }
2993 normalize=0.0;
cristyddd82202009-11-03 20:14:50 +00002994 qixel=bias;
cristyed231572011-07-14 02:18:59 +00002995 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00002996 {
cristyeaedf062010-05-29 22:36:02 +00002997 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00002998 {
cristyeaedf062010-05-29 22:36:02 +00002999 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
3000 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
3001 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
3002 cos_theta[i]+0.5),&pixel,exception);
cristy3ed852e2009-09-05 21:47:34 +00003003 qixel.red+=pixel.red;
3004 qixel.green+=pixel.green;
3005 qixel.blue+=pixel.blue;
cristy3ed852e2009-09-05 21:47:34 +00003006 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00003007 qixel.black+=pixel.black;
3008 qixel.alpha+=pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00003009 normalize+=1.0;
3010 }
3011 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
3012 normalize);
cristyed231572011-07-14 02:18:59 +00003013 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003014 SetPixelRed(blur_image,
3015 ClampToQuantum(normalize*qixel.red),q);
cristyed231572011-07-14 02:18:59 +00003016 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003017 SetPixelGreen(blur_image,
3018 ClampToQuantum(normalize*qixel.green),q);
cristyed231572011-07-14 02:18:59 +00003019 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003020 SetPixelBlue(blur_image,
3021 ClampToQuantum(normalize*qixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003022 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003023 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003024 SetPixelBlack(blur_image,
3025 ClampToQuantum(normalize*qixel.black),q);
cristyed231572011-07-14 02:18:59 +00003026 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003027 SetPixelAlpha(blur_image,
3028 ClampToQuantum(normalize*qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003029 }
3030 else
3031 {
3032 MagickRealType
3033 alpha,
3034 gamma;
3035
3036 alpha=1.0;
3037 gamma=0.0;
cristyeaedf062010-05-29 22:36:02 +00003038 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00003039 {
cristyeaedf062010-05-29 22:36:02 +00003040 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
3041 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
3042 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
3043 cos_theta[i]+0.5),&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00003044 alpha=(MagickRealType) (QuantumScale*pixel.alpha);
cristy3ed852e2009-09-05 21:47:34 +00003045 qixel.red+=alpha*pixel.red;
3046 qixel.green+=alpha*pixel.green;
3047 qixel.blue+=alpha*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00003048 qixel.alpha+=pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00003049 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00003050 qixel.black+=alpha*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00003051 gamma+=alpha;
3052 normalize+=1.0;
3053 }
3054 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3055 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
3056 normalize);
cristyed231572011-07-14 02:18:59 +00003057 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003058 SetPixelRed(blur_image,
3059 ClampToQuantum(gamma*qixel.red),q);
cristyed231572011-07-14 02:18:59 +00003060 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003061 SetPixelGreen(blur_image,
3062 ClampToQuantum(gamma*qixel.green),q);
cristyed231572011-07-14 02:18:59 +00003063 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003064 SetPixelBlue(blur_image,
3065 ClampToQuantum(gamma*qixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003066 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003067 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003068 SetPixelBlack(blur_image,
3069 ClampToQuantum(gamma*qixel.black),q);
cristyed231572011-07-14 02:18:59 +00003070 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003071 SetPixelAlpha(blur_image,
3072 ClampToQuantum(normalize*qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003073 }
cristyed231572011-07-14 02:18:59 +00003074 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003075 }
3076 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
3077 status=MagickFalse;
3078 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3079 {
3080 MagickBooleanType
3081 proceed;
3082
cristyb5d5f722009-11-04 03:03:49 +00003083#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00003084 #pragma omp critical (MagickCore_RadialBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00003085#endif
3086 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
3087 if (proceed == MagickFalse)
3088 status=MagickFalse;
3089 }
3090 }
3091 blur_view=DestroyCacheView(blur_view);
3092 image_view=DestroyCacheView(image_view);
3093 cos_theta=(MagickRealType *) RelinquishMagickMemory(cos_theta);
3094 sin_theta=(MagickRealType *) RelinquishMagickMemory(sin_theta);
3095 if (status == MagickFalse)
3096 blur_image=DestroyImage(blur_image);
3097 return(blur_image);
3098}
3099
3100/*
3101%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3102% %
3103% %
3104% %
cristy3ed852e2009-09-05 21:47:34 +00003105% S e l e c t i v e B l u r I m a g e %
3106% %
3107% %
3108% %
3109%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3110%
3111% SelectiveBlurImage() selectively blur pixels within a contrast threshold.
3112% It is similar to the unsharpen mask that sharpens everything with contrast
3113% above a certain threshold.
3114%
3115% The format of the SelectiveBlurImage method is:
3116%
3117% Image *SelectiveBlurImage(const Image *image,const double radius,
3118% const double sigma,const double threshold,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003119%
3120% A description of each parameter follows:
3121%
3122% o image: the image.
3123%
cristy3ed852e2009-09-05 21:47:34 +00003124% o radius: the radius of the Gaussian, in pixels, not counting the center
3125% pixel.
3126%
3127% o sigma: the standard deviation of the Gaussian, in pixels.
3128%
3129% o threshold: only pixels within this contrast threshold are included
3130% in the blur operation.
3131%
3132% o exception: return any errors or warnings in this structure.
3133%
3134*/
cristyf4ad9df2011-07-08 16:49:03 +00003135MagickExport Image *SelectiveBlurImage(const Image *image,
3136 const double radius,const double sigma,const double threshold,
3137 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003138{
3139#define SelectiveBlurImageTag "SelectiveBlur/Image"
3140
cristy47e00502009-12-17 19:19:57 +00003141 CacheView
3142 *blur_view,
3143 *image_view;
3144
cristy3ed852e2009-09-05 21:47:34 +00003145 double
cristy3ed852e2009-09-05 21:47:34 +00003146 *kernel;
3147
3148 Image
3149 *blur_image;
3150
cristy3ed852e2009-09-05 21:47:34 +00003151 MagickBooleanType
3152 status;
3153
cristybb503372010-05-27 20:51:26 +00003154 MagickOffsetType
3155 progress;
3156
cristy4c08aed2011-07-01 19:47:50 +00003157 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003158 bias;
3159
cristybb503372010-05-27 20:51:26 +00003160 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003161 i;
cristy3ed852e2009-09-05 21:47:34 +00003162
cristybb503372010-05-27 20:51:26 +00003163 size_t
cristy3ed852e2009-09-05 21:47:34 +00003164 width;
3165
cristybb503372010-05-27 20:51:26 +00003166 ssize_t
3167 j,
3168 u,
3169 v,
3170 y;
3171
cristy3ed852e2009-09-05 21:47:34 +00003172 /*
3173 Initialize blur image attributes.
3174 */
3175 assert(image != (Image *) NULL);
3176 assert(image->signature == MagickSignature);
3177 if (image->debug != MagickFalse)
3178 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3179 assert(exception != (ExceptionInfo *) NULL);
3180 assert(exception->signature == MagickSignature);
3181 width=GetOptimalKernelWidth1D(radius,sigma);
3182 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
3183 if (kernel == (double *) NULL)
3184 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00003185 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00003186 i=0;
cristy47e00502009-12-17 19:19:57 +00003187 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00003188 {
cristy47e00502009-12-17 19:19:57 +00003189 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00003190 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
3191 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00003192 }
3193 if (image->debug != MagickFalse)
3194 {
3195 char
3196 format[MaxTextExtent],
3197 *message;
3198
cristy117ff172010-08-15 21:35:32 +00003199 register const double
3200 *k;
3201
cristybb503372010-05-27 20:51:26 +00003202 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003203 u,
3204 v;
3205
cristy3ed852e2009-09-05 21:47:34 +00003206 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00003207 " SelectiveBlurImage with %.20gx%.20g kernel:",(double) width,(double)
3208 width);
cristy3ed852e2009-09-05 21:47:34 +00003209 message=AcquireString("");
3210 k=kernel;
cristybb503372010-05-27 20:51:26 +00003211 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003212 {
3213 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00003214 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy3ed852e2009-09-05 21:47:34 +00003215 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00003216 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003217 {
cristyb51dff52011-05-19 16:55:47 +00003218 (void) FormatLocaleString(format,MaxTextExtent,"%+f ",*k++);
cristy3ed852e2009-09-05 21:47:34 +00003219 (void) ConcatenateString(&message,format);
3220 }
3221 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
3222 }
3223 message=DestroyString(message);
3224 }
3225 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3226 if (blur_image == (Image *) NULL)
3227 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00003228 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003229 {
cristy3ed852e2009-09-05 21:47:34 +00003230 blur_image=DestroyImage(blur_image);
3231 return((Image *) NULL);
3232 }
3233 /*
3234 Threshold blur image.
3235 */
3236 status=MagickTrue;
3237 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00003238 GetPixelInfo(image,&bias);
3239 SetPixelInfoBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003240 image_view=AcquireCacheView(image);
3241 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003242#if defined(MAGICKCORE_OPENMP_SUPPORT)
3243 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003244#endif
cristybb503372010-05-27 20:51:26 +00003245 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003246 {
cristy4c08aed2011-07-01 19:47:50 +00003247 double
3248 contrast;
3249
cristy3ed852e2009-09-05 21:47:34 +00003250 MagickBooleanType
3251 sync;
3252
3253 MagickRealType
3254 gamma;
3255
cristy4c08aed2011-07-01 19:47:50 +00003256 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003257 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003258
cristy4c08aed2011-07-01 19:47:50 +00003259 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003260 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003261
cristy117ff172010-08-15 21:35:32 +00003262 register ssize_t
3263 x;
3264
cristy3ed852e2009-09-05 21:47:34 +00003265 if (status == MagickFalse)
3266 continue;
cristy117ff172010-08-15 21:35:32 +00003267 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
3268 (width/2L),image->columns+width,width,exception);
cristy3ed852e2009-09-05 21:47:34 +00003269 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3270 exception);
cristy4c08aed2011-07-01 19:47:50 +00003271 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003272 {
3273 status=MagickFalse;
3274 continue;
3275 }
cristybb503372010-05-27 20:51:26 +00003276 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003277 {
cristy4c08aed2011-07-01 19:47:50 +00003278 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003279 pixel;
3280
3281 register const double
cristyc47d1f82009-11-26 01:44:43 +00003282 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00003283
cristybb503372010-05-27 20:51:26 +00003284 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003285 u;
3286
cristy117ff172010-08-15 21:35:32 +00003287 ssize_t
3288 j,
3289 v;
3290
cristyddd82202009-11-03 20:14:50 +00003291 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00003292 k=kernel;
3293 gamma=0.0;
3294 j=0;
cristyed231572011-07-14 02:18:59 +00003295 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00003296 {
cristybb503372010-05-27 20:51:26 +00003297 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003298 {
cristybb503372010-05-27 20:51:26 +00003299 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003300 {
cristyed231572011-07-14 02:18:59 +00003301 contrast=GetPixelIntensity(image,p+(u+j)*GetPixelChannels(image))-
cristy4c08aed2011-07-01 19:47:50 +00003302 (double) GetPixelIntensity(blur_image,q);
3303 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003304 {
cristy4c08aed2011-07-01 19:47:50 +00003305 pixel.red+=(*k)*
cristyed231572011-07-14 02:18:59 +00003306 GetPixelRed(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003307 pixel.green+=(*k)*
cristyed231572011-07-14 02:18:59 +00003308 GetPixelGreen(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003309 pixel.blue+=(*k)*
cristyed231572011-07-14 02:18:59 +00003310 GetPixelBlue(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003311 if (image->colorspace == CMYKColorspace)
3312 pixel.black+=(*k)*
cristyed231572011-07-14 02:18:59 +00003313 GetPixelBlack(image,p+(u+j)*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003314 gamma+=(*k);
3315 k++;
3316 }
3317 }
cristyd99b0962010-05-29 23:14:26 +00003318 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003319 }
3320 if (gamma != 0.0)
3321 {
3322 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +00003323 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003324 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +00003325 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003326 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +00003327 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003328 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003329 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00003330 (image->colorspace == CMYKColorspace))
3331 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00003332 }
cristyed231572011-07-14 02:18:59 +00003333 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003334 {
3335 gamma=0.0;
3336 j=0;
cristybb503372010-05-27 20:51:26 +00003337 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003338 {
cristybb503372010-05-27 20:51:26 +00003339 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003340 {
cristy4c08aed2011-07-01 19:47:50 +00003341 contrast=GetPixelIntensity(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003342 GetPixelChannels(image))-(double)
cristy4c08aed2011-07-01 19:47:50 +00003343 GetPixelIntensity(blur_image,q);
3344 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003345 {
cristy4c08aed2011-07-01 19:47:50 +00003346 pixel.alpha+=(*k)*
cristyed231572011-07-14 02:18:59 +00003347 GetPixelAlpha(image,p+(u+j)*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003348 gamma+=(*k);
3349 k++;
3350 }
3351 }
cristyeaedf062010-05-29 22:36:02 +00003352 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003353 }
3354 if (gamma != 0.0)
3355 {
3356 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
3357 gamma);
cristy4c08aed2011-07-01 19:47:50 +00003358 SetPixelAlpha(blur_image,ClampToQuantum(gamma*pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003359 }
3360 }
3361 }
3362 else
3363 {
3364 MagickRealType
3365 alpha;
3366
cristybb503372010-05-27 20:51:26 +00003367 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003368 {
cristybb503372010-05-27 20:51:26 +00003369 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003370 {
cristy4c08aed2011-07-01 19:47:50 +00003371 contrast=GetPixelIntensity(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003372 GetPixelChannels(image))-(double)
cristy4c08aed2011-07-01 19:47:50 +00003373 GetPixelIntensity(blur_image,q);
3374 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003375 {
cristy4c08aed2011-07-01 19:47:50 +00003376 alpha=(MagickRealType) (QuantumScale*
cristyed231572011-07-14 02:18:59 +00003377 GetPixelAlpha(image,p+(u+j)*GetPixelChannels(image)));
cristy4c08aed2011-07-01 19:47:50 +00003378 pixel.red+=(*k)*alpha*
cristyed231572011-07-14 02:18:59 +00003379 GetPixelRed(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003380 pixel.green+=(*k)*alpha*GetPixelGreen(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003381 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003382 pixel.blue+=(*k)*alpha*GetPixelBlue(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003383 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003384 pixel.alpha+=(*k)*GetPixelAlpha(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003385 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003386 if (image->colorspace == CMYKColorspace)
3387 pixel.black+=(*k)*GetPixelBlack(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003388 GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003389 gamma+=(*k)*alpha;
3390 k++;
3391 }
3392 }
cristyeaedf062010-05-29 22:36:02 +00003393 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003394 }
3395 if (gamma != 0.0)
3396 {
3397 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +00003398 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003399 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +00003400 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003401 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +00003402 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003403 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003404 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00003405 (image->colorspace == CMYKColorspace))
3406 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00003407 }
cristyed231572011-07-14 02:18:59 +00003408 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003409 {
3410 gamma=0.0;
3411 j=0;
cristybb503372010-05-27 20:51:26 +00003412 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003413 {
cristybb503372010-05-27 20:51:26 +00003414 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003415 {
cristy4c08aed2011-07-01 19:47:50 +00003416 contrast=GetPixelIntensity(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003417 GetPixelChannels(image))-(double)
cristy4c08aed2011-07-01 19:47:50 +00003418 GetPixelIntensity(blur_image,q);
3419 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003420 {
cristy4c08aed2011-07-01 19:47:50 +00003421 pixel.alpha+=(*k)*
cristyed231572011-07-14 02:18:59 +00003422 GetPixelAlpha(image,p+(u+j)*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003423 gamma+=(*k);
3424 k++;
3425 }
3426 }
cristyeaedf062010-05-29 22:36:02 +00003427 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003428 }
3429 if (gamma != 0.0)
3430 {
3431 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
3432 gamma);
cristy4c08aed2011-07-01 19:47:50 +00003433 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003434 }
3435 }
3436 }
cristyed231572011-07-14 02:18:59 +00003437 p+=GetPixelChannels(image);
3438 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003439 }
3440 sync=SyncCacheViewAuthenticPixels(blur_view,exception);
3441 if (sync == MagickFalse)
3442 status=MagickFalse;
3443 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3444 {
3445 MagickBooleanType
3446 proceed;
3447
cristyb5d5f722009-11-04 03:03:49 +00003448#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00003449 #pragma omp critical (MagickCore_SelectiveBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00003450#endif
3451 proceed=SetImageProgress(image,SelectiveBlurImageTag,progress++,
3452 image->rows);
3453 if (proceed == MagickFalse)
3454 status=MagickFalse;
3455 }
3456 }
3457 blur_image->type=image->type;
3458 blur_view=DestroyCacheView(blur_view);
3459 image_view=DestroyCacheView(image_view);
3460 kernel=(double *) RelinquishMagickMemory(kernel);
3461 if (status == MagickFalse)
3462 blur_image=DestroyImage(blur_image);
3463 return(blur_image);
3464}
3465
3466/*
3467%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3468% %
3469% %
3470% %
3471% S h a d e I m a g e %
3472% %
3473% %
3474% %
3475%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3476%
3477% ShadeImage() shines a distant light on an image to create a
3478% three-dimensional effect. You control the positioning of the light with
3479% azimuth and elevation; azimuth is measured in degrees off the x axis
3480% and elevation is measured in pixels above the Z axis.
3481%
3482% The format of the ShadeImage method is:
3483%
3484% Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3485% const double azimuth,const double elevation,ExceptionInfo *exception)
3486%
3487% A description of each parameter follows:
3488%
3489% o image: the image.
3490%
3491% o gray: A value other than zero shades the intensity of each pixel.
3492%
3493% o azimuth, elevation: Define the light source direction.
3494%
3495% o exception: return any errors or warnings in this structure.
3496%
3497*/
3498MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3499 const double azimuth,const double elevation,ExceptionInfo *exception)
3500{
3501#define ShadeImageTag "Shade/Image"
3502
cristyc4c8d132010-01-07 01:58:38 +00003503 CacheView
3504 *image_view,
3505 *shade_view;
3506
cristy3ed852e2009-09-05 21:47:34 +00003507 Image
3508 *shade_image;
3509
cristy3ed852e2009-09-05 21:47:34 +00003510 MagickBooleanType
3511 status;
3512
cristybb503372010-05-27 20:51:26 +00003513 MagickOffsetType
3514 progress;
3515
cristy3ed852e2009-09-05 21:47:34 +00003516 PrimaryInfo
3517 light;
3518
cristybb503372010-05-27 20:51:26 +00003519 ssize_t
3520 y;
3521
cristy3ed852e2009-09-05 21:47:34 +00003522 /*
3523 Initialize shaded image attributes.
3524 */
3525 assert(image != (const Image *) NULL);
3526 assert(image->signature == MagickSignature);
3527 if (image->debug != MagickFalse)
3528 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3529 assert(exception != (ExceptionInfo *) NULL);
3530 assert(exception->signature == MagickSignature);
3531 shade_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
3532 if (shade_image == (Image *) NULL)
3533 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00003534 if (SetImageStorageClass(shade_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003535 {
cristy3ed852e2009-09-05 21:47:34 +00003536 shade_image=DestroyImage(shade_image);
3537 return((Image *) NULL);
3538 }
3539 /*
3540 Compute the light vector.
3541 */
3542 light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))*
3543 cos(DegreesToRadians(elevation));
3544 light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))*
3545 cos(DegreesToRadians(elevation));
3546 light.z=(double) QuantumRange*sin(DegreesToRadians(elevation));
3547 /*
3548 Shade image.
3549 */
3550 status=MagickTrue;
3551 progress=0;
3552 image_view=AcquireCacheView(image);
3553 shade_view=AcquireCacheView(shade_image);
cristyb5d5f722009-11-04 03:03:49 +00003554#if defined(MAGICKCORE_OPENMP_SUPPORT)
3555 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003556#endif
cristybb503372010-05-27 20:51:26 +00003557 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003558 {
3559 MagickRealType
3560 distance,
3561 normal_distance,
3562 shade;
3563
3564 PrimaryInfo
3565 normal;
3566
cristy4c08aed2011-07-01 19:47:50 +00003567 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003568 *restrict p,
3569 *restrict s0,
3570 *restrict s1,
3571 *restrict s2;
cristy3ed852e2009-09-05 21:47:34 +00003572
cristy4c08aed2011-07-01 19:47:50 +00003573 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003574 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003575
cristy117ff172010-08-15 21:35:32 +00003576 register ssize_t
3577 x;
3578
cristy3ed852e2009-09-05 21:47:34 +00003579 if (status == MagickFalse)
3580 continue;
3581 p=GetCacheViewVirtualPixels(image_view,-1,y-1,image->columns+2,3,exception);
3582 q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1,
3583 exception);
cristy4c08aed2011-07-01 19:47:50 +00003584 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003585 {
3586 status=MagickFalse;
3587 continue;
3588 }
3589 /*
3590 Shade this row of pixels.
3591 */
3592 normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */
cristyed231572011-07-14 02:18:59 +00003593 s0=p+GetPixelChannels(image);
3594 s1=s0+(image->columns+2)*GetPixelChannels(image);
3595 s2=s1+(image->columns+2)*GetPixelChannels(image);
cristybb503372010-05-27 20:51:26 +00003596 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003597 {
3598 /*
3599 Determine the surface normal and compute shading.
3600 */
cristyed231572011-07-14 02:18:59 +00003601 normal.x=(double) (GetPixelIntensity(image,s0-GetPixelChannels(image))+
3602 GetPixelIntensity(image,s1-GetPixelChannels(image))+
3603 GetPixelIntensity(image,s2-GetPixelChannels(image))-
3604 GetPixelIntensity(image,s0+GetPixelChannels(image))-
3605 GetPixelIntensity(image,s1+GetPixelChannels(image))-
3606 GetPixelIntensity(image,s2+GetPixelChannels(image)));
3607 normal.y=(double) (GetPixelIntensity(image,s2-GetPixelChannels(image))+
cristy4c08aed2011-07-01 19:47:50 +00003608 GetPixelIntensity(image,s2)+
cristyed231572011-07-14 02:18:59 +00003609 GetPixelIntensity(image,s2+GetPixelChannels(image))-
3610 GetPixelIntensity(image,s0-GetPixelChannels(image))-
cristy4c08aed2011-07-01 19:47:50 +00003611 GetPixelIntensity(image,s0)-
cristyed231572011-07-14 02:18:59 +00003612 GetPixelIntensity(image,s0+GetPixelChannels(image)));
cristy3ed852e2009-09-05 21:47:34 +00003613 if ((normal.x == 0.0) && (normal.y == 0.0))
3614 shade=light.z;
3615 else
3616 {
3617 shade=0.0;
3618 distance=normal.x*light.x+normal.y*light.y+normal.z*light.z;
3619 if (distance > MagickEpsilon)
3620 {
3621 normal_distance=
3622 normal.x*normal.x+normal.y*normal.y+normal.z*normal.z;
3623 if (normal_distance > (MagickEpsilon*MagickEpsilon))
3624 shade=distance/sqrt((double) normal_distance);
3625 }
3626 }
3627 if (gray != MagickFalse)
3628 {
cristy4c08aed2011-07-01 19:47:50 +00003629 SetPixelRed(shade_image,ClampToQuantum(shade),q);
3630 SetPixelGreen(shade_image,ClampToQuantum(shade),q);
3631 SetPixelBlue(shade_image,ClampToQuantum(shade),q);
cristy3ed852e2009-09-05 21:47:34 +00003632 }
3633 else
3634 {
cristy4c08aed2011-07-01 19:47:50 +00003635 SetPixelRed(shade_image,ClampToQuantum(QuantumScale*shade*
3636 GetPixelRed(image,s1)),q);
3637 SetPixelGreen(shade_image,ClampToQuantum(QuantumScale*shade*
3638 GetPixelGreen(image,s1)),q);
3639 SetPixelBlue(shade_image,ClampToQuantum(QuantumScale*shade*
3640 GetPixelBlue(image,s1)),q);
cristy3ed852e2009-09-05 21:47:34 +00003641 }
cristy4c08aed2011-07-01 19:47:50 +00003642 SetPixelAlpha(shade_image,GetPixelAlpha(image,s1),q);
cristyed231572011-07-14 02:18:59 +00003643 s0+=GetPixelChannels(image);
3644 s1+=GetPixelChannels(image);
3645 s2+=GetPixelChannels(image);
3646 q+=GetPixelChannels(shade_image);
cristy3ed852e2009-09-05 21:47:34 +00003647 }
3648 if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
3649 status=MagickFalse;
3650 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3651 {
3652 MagickBooleanType
3653 proceed;
3654
cristyb5d5f722009-11-04 03:03:49 +00003655#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003656 #pragma omp critical (MagickCore_ShadeImage)
3657#endif
3658 proceed=SetImageProgress(image,ShadeImageTag,progress++,image->rows);
3659 if (proceed == MagickFalse)
3660 status=MagickFalse;
3661 }
3662 }
3663 shade_view=DestroyCacheView(shade_view);
3664 image_view=DestroyCacheView(image_view);
3665 if (status == MagickFalse)
3666 shade_image=DestroyImage(shade_image);
3667 return(shade_image);
3668}
3669
3670/*
3671%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3672% %
3673% %
3674% %
3675% S h a r p e n I m a g e %
3676% %
3677% %
3678% %
3679%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3680%
3681% SharpenImage() sharpens the image. We convolve the image with a Gaussian
3682% operator of the given radius and standard deviation (sigma). For
3683% reasonable results, radius should be larger than sigma. Use a radius of 0
3684% and SharpenImage() selects a suitable radius for you.
3685%
3686% Using a separable kernel would be faster, but the negative weights cancel
3687% out on the corners of the kernel producing often undesirable ringing in the
3688% filtered result; this can be avoided by using a 2D gaussian shaped image
3689% sharpening kernel instead.
3690%
3691% The format of the SharpenImage method is:
3692%
3693% Image *SharpenImage(const Image *image,const double radius,
cristy05c0c9a2011-09-05 23:16:13 +00003694% const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003695%
3696% A description of each parameter follows:
3697%
3698% o image: the image.
3699%
cristy3ed852e2009-09-05 21:47:34 +00003700% o radius: the radius of the Gaussian, in pixels, not counting the center
3701% pixel.
3702%
3703% o sigma: the standard deviation of the Laplacian, in pixels.
3704%
cristy05c0c9a2011-09-05 23:16:13 +00003705% o bias: bias.
3706%
cristy3ed852e2009-09-05 21:47:34 +00003707% o exception: return any errors or warnings in this structure.
3708%
3709*/
cristy3ed852e2009-09-05 21:47:34 +00003710MagickExport Image *SharpenImage(const Image *image,const double radius,
cristy05c0c9a2011-09-05 23:16:13 +00003711 const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003712{
cristy3ed852e2009-09-05 21:47:34 +00003713 double
cristy47e00502009-12-17 19:19:57 +00003714 normalize;
cristy3ed852e2009-09-05 21:47:34 +00003715
3716 Image
3717 *sharp_image;
3718
cristy41cbe682011-07-15 19:12:37 +00003719 KernelInfo
3720 *kernel_info;
3721
cristybb503372010-05-27 20:51:26 +00003722 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003723 i;
3724
cristybb503372010-05-27 20:51:26 +00003725 size_t
cristy3ed852e2009-09-05 21:47:34 +00003726 width;
3727
cristy117ff172010-08-15 21:35:32 +00003728 ssize_t
3729 j,
3730 u,
3731 v;
3732
cristy3ed852e2009-09-05 21:47:34 +00003733 assert(image != (const Image *) NULL);
3734 assert(image->signature == MagickSignature);
3735 if (image->debug != MagickFalse)
3736 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3737 assert(exception != (ExceptionInfo *) NULL);
3738 assert(exception->signature == MagickSignature);
3739 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00003740 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00003741 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003742 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00003743 (void) ResetMagickMemory(kernel_info,0,sizeof(*kernel_info));
3744 kernel_info->width=width;
3745 kernel_info->height=width;
cristy05c0c9a2011-09-05 23:16:13 +00003746 kernel_info->bias=bias;
cristy41cbe682011-07-15 19:12:37 +00003747 kernel_info->signature=MagickSignature;
3748 kernel_info->values=(double *) AcquireAlignedMemory(kernel_info->width,
3749 kernel_info->width*sizeof(*kernel_info->values));
3750 if (kernel_info->values == (double *) NULL)
3751 {
3752 kernel_info=DestroyKernelInfo(kernel_info);
3753 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3754 }
cristy3ed852e2009-09-05 21:47:34 +00003755 normalize=0.0;
cristy41cbe682011-07-15 19:12:37 +00003756 j=(ssize_t) kernel_info->width/2;
cristy47e00502009-12-17 19:19:57 +00003757 i=0;
3758 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00003759 {
cristy47e00502009-12-17 19:19:57 +00003760 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00003761 {
cristy41cbe682011-07-15 19:12:37 +00003762 kernel_info->values[i]=(double) (-exp(-((double) u*u+v*v)/(2.0*
3763 MagickSigma*MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
3764 normalize+=kernel_info->values[i];
cristy3ed852e2009-09-05 21:47:34 +00003765 i++;
3766 }
3767 }
cristy41cbe682011-07-15 19:12:37 +00003768 kernel_info->values[i/2]=(double) ((-2.0)*normalize);
cristy5e6be1e2011-07-16 01:23:39 +00003769 sharp_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00003770 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00003771 return(sharp_image);
3772}
3773
3774/*
3775%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3776% %
3777% %
3778% %
3779% S p r e a d I m a g e %
3780% %
3781% %
3782% %
3783%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3784%
3785% SpreadImage() is a special effects method that randomly displaces each
3786% pixel in a block defined by the radius parameter.
3787%
3788% The format of the SpreadImage method is:
3789%
3790% Image *SpreadImage(const Image *image,const double radius,
3791% ExceptionInfo *exception)
3792%
3793% A description of each parameter follows:
3794%
3795% o image: the image.
3796%
3797% o radius: Choose a random pixel in a neighborhood of this extent.
3798%
3799% o exception: return any errors or warnings in this structure.
3800%
3801*/
3802MagickExport Image *SpreadImage(const Image *image,const double radius,
3803 ExceptionInfo *exception)
3804{
3805#define SpreadImageTag "Spread/Image"
3806
cristyfa112112010-01-04 17:48:07 +00003807 CacheView
cristy9f7e7cb2011-03-26 00:49:57 +00003808 *image_view,
3809 *spread_view;
cristyfa112112010-01-04 17:48:07 +00003810
cristy3ed852e2009-09-05 21:47:34 +00003811 Image
3812 *spread_image;
3813
cristy3ed852e2009-09-05 21:47:34 +00003814 MagickBooleanType
3815 status;
3816
cristybb503372010-05-27 20:51:26 +00003817 MagickOffsetType
3818 progress;
3819
cristy4c08aed2011-07-01 19:47:50 +00003820 PixelInfo
cristyddd82202009-11-03 20:14:50 +00003821 bias;
cristy3ed852e2009-09-05 21:47:34 +00003822
3823 RandomInfo
cristyfa112112010-01-04 17:48:07 +00003824 **restrict random_info;
cristy3ed852e2009-09-05 21:47:34 +00003825
cristybb503372010-05-27 20:51:26 +00003826 size_t
cristy3ed852e2009-09-05 21:47:34 +00003827 width;
3828
cristybb503372010-05-27 20:51:26 +00003829 ssize_t
3830 y;
3831
cristy3ed852e2009-09-05 21:47:34 +00003832 /*
3833 Initialize spread image attributes.
3834 */
3835 assert(image != (Image *) NULL);
3836 assert(image->signature == MagickSignature);
3837 if (image->debug != MagickFalse)
3838 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3839 assert(exception != (ExceptionInfo *) NULL);
3840 assert(exception->signature == MagickSignature);
3841 spread_image=CloneImage(image,image->columns,image->rows,MagickTrue,
3842 exception);
3843 if (spread_image == (Image *) NULL)
3844 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00003845 if (SetImageStorageClass(spread_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003846 {
cristy3ed852e2009-09-05 21:47:34 +00003847 spread_image=DestroyImage(spread_image);
3848 return((Image *) NULL);
3849 }
3850 /*
3851 Spread image.
3852 */
3853 status=MagickTrue;
3854 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00003855 GetPixelInfo(spread_image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003856 width=GetOptimalKernelWidth1D(radius,0.5);
cristy3ed852e2009-09-05 21:47:34 +00003857 random_info=AcquireRandomInfoThreadSet();
cristy9f7e7cb2011-03-26 00:49:57 +00003858 image_view=AcquireCacheView(image);
3859 spread_view=AcquireCacheView(spread_image);
cristyb557a152011-02-22 12:14:30 +00003860#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00003861 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00003862#endif
cristybb503372010-05-27 20:51:26 +00003863 for (y=0; y < (ssize_t) spread_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003864 {
cristy5c9e6f22010-09-17 17:31:01 +00003865 const int
3866 id = GetOpenMPThreadId();
cristy6ebe97c2010-07-03 01:17:28 +00003867
cristy4c08aed2011-07-01 19:47:50 +00003868 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003869 pixel;
3870
cristy4c08aed2011-07-01 19:47:50 +00003871 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003872 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003873
cristy117ff172010-08-15 21:35:32 +00003874 register ssize_t
3875 x;
3876
cristy3ed852e2009-09-05 21:47:34 +00003877 if (status == MagickFalse)
3878 continue;
cristy9f7e7cb2011-03-26 00:49:57 +00003879 q=QueueCacheViewAuthenticPixels(spread_view,0,y,spread_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +00003880 exception);
cristyacd2ed22011-08-30 01:44:23 +00003881 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003882 {
3883 status=MagickFalse;
3884 continue;
3885 }
cristyddd82202009-11-03 20:14:50 +00003886 pixel=bias;
cristybb503372010-05-27 20:51:26 +00003887 for (x=0; x < (ssize_t) spread_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003888 {
cristy4c08aed2011-07-01 19:47:50 +00003889 (void) InterpolatePixelInfo(image,image_view,
cristy8a7c3e82011-03-26 02:10:53 +00003890 UndefinedInterpolatePixel,(double) x+width*(GetPseudoRandomValue(
3891 random_info[id])-0.5),(double) y+width*(GetPseudoRandomValue(
3892 random_info[id])-0.5),&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00003893 SetPixelPixelInfo(spread_image,&pixel,q);
cristyed231572011-07-14 02:18:59 +00003894 q+=GetPixelChannels(spread_image);
cristy3ed852e2009-09-05 21:47:34 +00003895 }
cristy9f7e7cb2011-03-26 00:49:57 +00003896 if (SyncCacheViewAuthenticPixels(spread_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003897 status=MagickFalse;
3898 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3899 {
3900 MagickBooleanType
3901 proceed;
3902
cristyb557a152011-02-22 12:14:30 +00003903#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003904 #pragma omp critical (MagickCore_SpreadImage)
3905#endif
3906 proceed=SetImageProgress(image,SpreadImageTag,progress++,image->rows);
3907 if (proceed == MagickFalse)
3908 status=MagickFalse;
3909 }
3910 }
cristy9f7e7cb2011-03-26 00:49:57 +00003911 spread_view=DestroyCacheView(spread_view);
cristy3ed852e2009-09-05 21:47:34 +00003912 image_view=DestroyCacheView(image_view);
3913 random_info=DestroyRandomInfoThreadSet(random_info);
cristy3ed852e2009-09-05 21:47:34 +00003914 return(spread_image);
3915}
3916
3917/*
3918%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3919% %
3920% %
3921% %
cristy0834d642011-03-18 18:26:08 +00003922% S t a t i s t i c I m a g e %
3923% %
3924% %
3925% %
3926%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3927%
3928% StatisticImage() makes each pixel the min / max / median / mode / etc. of
cristy8d752042011-03-19 01:00:36 +00003929% the neighborhood of the specified width and height.
cristy0834d642011-03-18 18:26:08 +00003930%
3931% The format of the StatisticImage method is:
3932%
3933% Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00003934% const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00003935%
3936% A description of each parameter follows:
3937%
3938% o image: the image.
3939%
cristy0834d642011-03-18 18:26:08 +00003940% o type: the statistic type (median, mode, etc.).
3941%
cristy95c38342011-03-18 22:39:51 +00003942% o width: the width of the pixel neighborhood.
3943%
3944% o height: the height of the pixel neighborhood.
cristy0834d642011-03-18 18:26:08 +00003945%
3946% o exception: return any errors or warnings in this structure.
3947%
3948*/
3949
cristy733678d2011-03-18 21:29:28 +00003950#define ListChannels 5
3951
3952typedef struct _ListNode
3953{
3954 size_t
3955 next[9],
3956 count,
3957 signature;
3958} ListNode;
3959
3960typedef struct _SkipList
3961{
3962 ssize_t
3963 level;
3964
3965 ListNode
3966 *nodes;
3967} SkipList;
3968
3969typedef struct _PixelList
3970{
3971 size_t
cristy6fc86bb2011-03-18 23:45:16 +00003972 length,
cristy733678d2011-03-18 21:29:28 +00003973 seed,
3974 signature;
3975
3976 SkipList
3977 lists[ListChannels];
3978} PixelList;
3979
3980static PixelList *DestroyPixelList(PixelList *pixel_list)
3981{
3982 register ssize_t
3983 i;
3984
3985 if (pixel_list == (PixelList *) NULL)
3986 return((PixelList *) NULL);
3987 for (i=0; i < ListChannels; i++)
3988 if (pixel_list->lists[i].nodes != (ListNode *) NULL)
3989 pixel_list->lists[i].nodes=(ListNode *) RelinquishMagickMemory(
3990 pixel_list->lists[i].nodes);
3991 pixel_list=(PixelList *) RelinquishMagickMemory(pixel_list);
3992 return(pixel_list);
3993}
3994
3995static PixelList **DestroyPixelListThreadSet(PixelList **pixel_list)
3996{
3997 register ssize_t
3998 i;
3999
4000 assert(pixel_list != (PixelList **) NULL);
4001 for (i=0; i < (ssize_t) GetOpenMPMaximumThreads(); i++)
4002 if (pixel_list[i] != (PixelList *) NULL)
4003 pixel_list[i]=DestroyPixelList(pixel_list[i]);
4004 pixel_list=(PixelList **) RelinquishMagickMemory(pixel_list);
4005 return(pixel_list);
4006}
4007
cristy6fc86bb2011-03-18 23:45:16 +00004008static PixelList *AcquirePixelList(const size_t width,const size_t height)
cristy733678d2011-03-18 21:29:28 +00004009{
4010 PixelList
4011 *pixel_list;
4012
4013 register ssize_t
4014 i;
4015
4016 pixel_list=(PixelList *) AcquireMagickMemory(sizeof(*pixel_list));
4017 if (pixel_list == (PixelList *) NULL)
4018 return(pixel_list);
4019 (void) ResetMagickMemory((void *) pixel_list,0,sizeof(*pixel_list));
cristy6fc86bb2011-03-18 23:45:16 +00004020 pixel_list->length=width*height;
cristy733678d2011-03-18 21:29:28 +00004021 for (i=0; i < ListChannels; i++)
4022 {
4023 pixel_list->lists[i].nodes=(ListNode *) AcquireQuantumMemory(65537UL,
4024 sizeof(*pixel_list->lists[i].nodes));
4025 if (pixel_list->lists[i].nodes == (ListNode *) NULL)
4026 return(DestroyPixelList(pixel_list));
4027 (void) ResetMagickMemory(pixel_list->lists[i].nodes,0,65537UL*
4028 sizeof(*pixel_list->lists[i].nodes));
4029 }
4030 pixel_list->signature=MagickSignature;
4031 return(pixel_list);
4032}
4033
cristy6fc86bb2011-03-18 23:45:16 +00004034static PixelList **AcquirePixelListThreadSet(const size_t width,
4035 const size_t height)
cristy733678d2011-03-18 21:29:28 +00004036{
4037 PixelList
4038 **pixel_list;
4039
4040 register ssize_t
4041 i;
4042
4043 size_t
4044 number_threads;
4045
4046 number_threads=GetOpenMPMaximumThreads();
4047 pixel_list=(PixelList **) AcquireQuantumMemory(number_threads,
4048 sizeof(*pixel_list));
4049 if (pixel_list == (PixelList **) NULL)
4050 return((PixelList **) NULL);
4051 (void) ResetMagickMemory(pixel_list,0,number_threads*sizeof(*pixel_list));
4052 for (i=0; i < (ssize_t) number_threads; i++)
4053 {
cristy6fc86bb2011-03-18 23:45:16 +00004054 pixel_list[i]=AcquirePixelList(width,height);
cristy733678d2011-03-18 21:29:28 +00004055 if (pixel_list[i] == (PixelList *) NULL)
4056 return(DestroyPixelListThreadSet(pixel_list));
4057 }
4058 return(pixel_list);
4059}
4060
4061static void AddNodePixelList(PixelList *pixel_list,const ssize_t channel,
4062 const size_t color)
4063{
4064 register SkipList
4065 *list;
4066
4067 register ssize_t
4068 level;
4069
4070 size_t
4071 search,
4072 update[9];
4073
4074 /*
4075 Initialize the node.
4076 */
4077 list=pixel_list->lists+channel;
4078 list->nodes[color].signature=pixel_list->signature;
4079 list->nodes[color].count=1;
4080 /*
4081 Determine where it belongs in the list.
4082 */
4083 search=65536UL;
4084 for (level=list->level; level >= 0; level--)
4085 {
4086 while (list->nodes[search].next[level] < color)
4087 search=list->nodes[search].next[level];
4088 update[level]=search;
4089 }
4090 /*
4091 Generate a pseudo-random level for this node.
4092 */
4093 for (level=0; ; level++)
4094 {
4095 pixel_list->seed=(pixel_list->seed*42893621L)+1L;
4096 if ((pixel_list->seed & 0x300) != 0x300)
4097 break;
4098 }
4099 if (level > 8)
4100 level=8;
4101 if (level > (list->level+2))
4102 level=list->level+2;
4103 /*
4104 If we're raising the list's level, link back to the root node.
4105 */
4106 while (level > list->level)
4107 {
4108 list->level++;
4109 update[list->level]=65536UL;
4110 }
4111 /*
4112 Link the node into the skip-list.
4113 */
4114 do
4115 {
4116 list->nodes[color].next[level]=list->nodes[update[level]].next[level];
4117 list->nodes[update[level]].next[level]=color;
cristy3cba8ca2011-03-19 01:29:12 +00004118 } while (level-- > 0);
cristy733678d2011-03-18 21:29:28 +00004119}
4120
cristy4c08aed2011-07-01 19:47:50 +00004121static PixelInfo GetMaximumPixelList(PixelList *pixel_list)
cristy6fc86bb2011-03-18 23:45:16 +00004122{
cristy4c08aed2011-07-01 19:47:50 +00004123 PixelInfo
cristy6fc86bb2011-03-18 23:45:16 +00004124 pixel;
4125
4126 register SkipList
4127 *list;
4128
4129 register ssize_t
4130 channel;
4131
4132 size_t
cristyd76c51e2011-03-26 00:21:26 +00004133 color,
4134 maximum;
cristy49f37242011-03-22 18:18:23 +00004135
4136 ssize_t
cristy6fc86bb2011-03-18 23:45:16 +00004137 count;
4138
4139 unsigned short
cristyd76c51e2011-03-26 00:21:26 +00004140 channels[ListChannels];
cristy6fc86bb2011-03-18 23:45:16 +00004141
4142 /*
4143 Find the maximum value for each of the color.
4144 */
4145 for (channel=0; channel < 5; channel++)
4146 {
4147 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004148 color=65536L;
cristy6fc86bb2011-03-18 23:45:16 +00004149 count=0;
cristy49f37242011-03-22 18:18:23 +00004150 maximum=list->nodes[color].next[0];
cristy6fc86bb2011-03-18 23:45:16 +00004151 do
4152 {
4153 color=list->nodes[color].next[0];
cristy49f37242011-03-22 18:18:23 +00004154 if (color > maximum)
4155 maximum=color;
cristy6fc86bb2011-03-18 23:45:16 +00004156 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004157 } while (count < (ssize_t) pixel_list->length);
cristy49f37242011-03-22 18:18:23 +00004158 channels[channel]=(unsigned short) maximum;
4159 }
cristy4c08aed2011-07-01 19:47:50 +00004160 GetPixelInfo((const Image *) NULL,&pixel);
cristy49f37242011-03-22 18:18:23 +00004161 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4162 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4163 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004164 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4165 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy49f37242011-03-22 18:18:23 +00004166 return(pixel);
4167}
4168
cristy4c08aed2011-07-01 19:47:50 +00004169static PixelInfo GetMeanPixelList(PixelList *pixel_list)
cristy49f37242011-03-22 18:18:23 +00004170{
cristy4c08aed2011-07-01 19:47:50 +00004171 PixelInfo
cristy49f37242011-03-22 18:18:23 +00004172 pixel;
4173
cristy80a99a32011-03-30 01:30:23 +00004174 MagickRealType
4175 sum;
4176
cristy49f37242011-03-22 18:18:23 +00004177 register SkipList
4178 *list;
4179
4180 register ssize_t
4181 channel;
4182
4183 size_t
cristy80a99a32011-03-30 01:30:23 +00004184 color;
cristy49f37242011-03-22 18:18:23 +00004185
4186 ssize_t
4187 count;
4188
4189 unsigned short
4190 channels[ListChannels];
4191
4192 /*
4193 Find the mean value for each of the color.
4194 */
4195 for (channel=0; channel < 5; channel++)
4196 {
4197 list=pixel_list->lists+channel;
4198 color=65536L;
4199 count=0;
cristy80a99a32011-03-30 01:30:23 +00004200 sum=0.0;
cristy49f37242011-03-22 18:18:23 +00004201 do
4202 {
4203 color=list->nodes[color].next[0];
cristy80a99a32011-03-30 01:30:23 +00004204 sum+=(MagickRealType) list->nodes[color].count*color;
cristy49f37242011-03-22 18:18:23 +00004205 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004206 } while (count < (ssize_t) pixel_list->length);
cristy80a99a32011-03-30 01:30:23 +00004207 sum/=pixel_list->length;
4208 channels[channel]=(unsigned short) sum;
cristy6fc86bb2011-03-18 23:45:16 +00004209 }
cristy4c08aed2011-07-01 19:47:50 +00004210 GetPixelInfo((const Image *) NULL,&pixel);
cristy6fc86bb2011-03-18 23:45:16 +00004211 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4212 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4213 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004214 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4215 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy6fc86bb2011-03-18 23:45:16 +00004216 return(pixel);
4217}
4218
cristy4c08aed2011-07-01 19:47:50 +00004219static PixelInfo GetMedianPixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004220{
cristy4c08aed2011-07-01 19:47:50 +00004221 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004222 pixel;
4223
4224 register SkipList
4225 *list;
4226
4227 register ssize_t
4228 channel;
4229
4230 size_t
cristy49f37242011-03-22 18:18:23 +00004231 color;
4232
4233 ssize_t
cristy733678d2011-03-18 21:29:28 +00004234 count;
4235
4236 unsigned short
4237 channels[ListChannels];
4238
4239 /*
4240 Find the median value for each of the color.
4241 */
cristy733678d2011-03-18 21:29:28 +00004242 for (channel=0; channel < 5; channel++)
4243 {
4244 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004245 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004246 count=0;
4247 do
4248 {
4249 color=list->nodes[color].next[0];
4250 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004251 } while (count <= (ssize_t) (pixel_list->length >> 1));
cristy6fc86bb2011-03-18 23:45:16 +00004252 channels[channel]=(unsigned short) color;
4253 }
cristy4c08aed2011-07-01 19:47:50 +00004254 GetPixelInfo((const Image *) NULL,&pixel);
cristy6fc86bb2011-03-18 23:45:16 +00004255 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4256 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4257 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004258 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4259 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy6fc86bb2011-03-18 23:45:16 +00004260 return(pixel);
4261}
4262
cristy4c08aed2011-07-01 19:47:50 +00004263static PixelInfo GetMinimumPixelList(PixelList *pixel_list)
cristy6fc86bb2011-03-18 23:45:16 +00004264{
cristy4c08aed2011-07-01 19:47:50 +00004265 PixelInfo
cristy6fc86bb2011-03-18 23:45:16 +00004266 pixel;
4267
4268 register SkipList
4269 *list;
4270
4271 register ssize_t
4272 channel;
4273
4274 size_t
cristyd76c51e2011-03-26 00:21:26 +00004275 color,
4276 minimum;
cristy6fc86bb2011-03-18 23:45:16 +00004277
cristy49f37242011-03-22 18:18:23 +00004278 ssize_t
4279 count;
4280
cristy6fc86bb2011-03-18 23:45:16 +00004281 unsigned short
cristyd76c51e2011-03-26 00:21:26 +00004282 channels[ListChannels];
cristy6fc86bb2011-03-18 23:45:16 +00004283
4284 /*
4285 Find the minimum value for each of the color.
4286 */
4287 for (channel=0; channel < 5; channel++)
4288 {
4289 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004290 count=0;
cristy6fc86bb2011-03-18 23:45:16 +00004291 color=65536UL;
cristy49f37242011-03-22 18:18:23 +00004292 minimum=list->nodes[color].next[0];
4293 do
4294 {
4295 color=list->nodes[color].next[0];
4296 if (color < minimum)
4297 minimum=color;
4298 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004299 } while (count < (ssize_t) pixel_list->length);
cristy49f37242011-03-22 18:18:23 +00004300 channels[channel]=(unsigned short) minimum;
cristy733678d2011-03-18 21:29:28 +00004301 }
cristy4c08aed2011-07-01 19:47:50 +00004302 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004303 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4304 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4305 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004306 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4307 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy733678d2011-03-18 21:29:28 +00004308 return(pixel);
4309}
4310
cristy4c08aed2011-07-01 19:47:50 +00004311static PixelInfo GetModePixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004312{
cristy4c08aed2011-07-01 19:47:50 +00004313 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004314 pixel;
4315
4316 register SkipList
4317 *list;
4318
4319 register ssize_t
4320 channel;
4321
4322 size_t
4323 color,
cristy733678d2011-03-18 21:29:28 +00004324 max_count,
cristy6fc86bb2011-03-18 23:45:16 +00004325 mode;
cristy733678d2011-03-18 21:29:28 +00004326
cristy49f37242011-03-22 18:18:23 +00004327 ssize_t
4328 count;
4329
cristy733678d2011-03-18 21:29:28 +00004330 unsigned short
4331 channels[5];
4332
4333 /*
glennrp30d2dc62011-06-25 03:17:16 +00004334 Make each pixel the 'predominant color' of the specified neighborhood.
cristy733678d2011-03-18 21:29:28 +00004335 */
cristy733678d2011-03-18 21:29:28 +00004336 for (channel=0; channel < 5; channel++)
4337 {
4338 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004339 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004340 mode=color;
4341 max_count=list->nodes[mode].count;
4342 count=0;
4343 do
4344 {
4345 color=list->nodes[color].next[0];
4346 if (list->nodes[color].count > max_count)
4347 {
4348 mode=color;
4349 max_count=list->nodes[mode].count;
4350 }
4351 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004352 } while (count < (ssize_t) pixel_list->length);
cristy733678d2011-03-18 21:29:28 +00004353 channels[channel]=(unsigned short) mode;
4354 }
cristy4c08aed2011-07-01 19:47:50 +00004355 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004356 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4357 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4358 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004359 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4360 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy733678d2011-03-18 21:29:28 +00004361 return(pixel);
4362}
4363
cristy4c08aed2011-07-01 19:47:50 +00004364static PixelInfo GetNonpeakPixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004365{
cristy4c08aed2011-07-01 19:47:50 +00004366 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004367 pixel;
4368
4369 register SkipList
4370 *list;
4371
4372 register ssize_t
4373 channel;
4374
4375 size_t
cristy733678d2011-03-18 21:29:28 +00004376 color,
cristy733678d2011-03-18 21:29:28 +00004377 next,
4378 previous;
4379
cristy49f37242011-03-22 18:18:23 +00004380 ssize_t
4381 count;
4382
cristy733678d2011-03-18 21:29:28 +00004383 unsigned short
4384 channels[5];
4385
4386 /*
cristy49f37242011-03-22 18:18:23 +00004387 Finds the non peak value for each of the colors.
cristy733678d2011-03-18 21:29:28 +00004388 */
cristy733678d2011-03-18 21:29:28 +00004389 for (channel=0; channel < 5; channel++)
4390 {
4391 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004392 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004393 next=list->nodes[color].next[0];
4394 count=0;
4395 do
4396 {
4397 previous=color;
4398 color=next;
4399 next=list->nodes[color].next[0];
4400 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004401 } while (count <= (ssize_t) (pixel_list->length >> 1));
cristy733678d2011-03-18 21:29:28 +00004402 if ((previous == 65536UL) && (next != 65536UL))
4403 color=next;
4404 else
4405 if ((previous != 65536UL) && (next == 65536UL))
4406 color=previous;
4407 channels[channel]=(unsigned short) color;
4408 }
cristy4c08aed2011-07-01 19:47:50 +00004409 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004410 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4411 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4412 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004413 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4414 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy733678d2011-03-18 21:29:28 +00004415 return(pixel);
4416}
4417
cristy4c08aed2011-07-01 19:47:50 +00004418static PixelInfo GetStandardDeviationPixelList(PixelList *pixel_list)
cristy9a68cbb2011-03-29 00:51:23 +00004419{
cristy4c08aed2011-07-01 19:47:50 +00004420 PixelInfo
cristy9a68cbb2011-03-29 00:51:23 +00004421 pixel;
4422
cristy80a99a32011-03-30 01:30:23 +00004423 MagickRealType
4424 sum,
4425 sum_squared;
4426
cristy9a68cbb2011-03-29 00:51:23 +00004427 register SkipList
4428 *list;
4429
4430 register ssize_t
4431 channel;
4432
4433 size_t
cristy80a99a32011-03-30 01:30:23 +00004434 color;
cristy9a68cbb2011-03-29 00:51:23 +00004435
4436 ssize_t
4437 count;
4438
4439 unsigned short
4440 channels[ListChannels];
4441
4442 /*
cristy80a99a32011-03-30 01:30:23 +00004443 Find the standard-deviation value for each of the color.
cristy9a68cbb2011-03-29 00:51:23 +00004444 */
4445 for (channel=0; channel < 5; channel++)
4446 {
4447 list=pixel_list->lists+channel;
4448 color=65536L;
4449 count=0;
cristy80a99a32011-03-30 01:30:23 +00004450 sum=0.0;
4451 sum_squared=0.0;
cristy9a68cbb2011-03-29 00:51:23 +00004452 do
4453 {
cristy80a99a32011-03-30 01:30:23 +00004454 register ssize_t
4455 i;
4456
cristy9a68cbb2011-03-29 00:51:23 +00004457 color=list->nodes[color].next[0];
cristy80a99a32011-03-30 01:30:23 +00004458 sum+=(MagickRealType) list->nodes[color].count*color;
4459 for (i=0; i < (ssize_t) list->nodes[color].count; i++)
4460 sum_squared+=((MagickRealType) color)*((MagickRealType) color);
cristy9a68cbb2011-03-29 00:51:23 +00004461 count+=list->nodes[color].count;
4462 } while (count < (ssize_t) pixel_list->length);
cristy80a99a32011-03-30 01:30:23 +00004463 sum/=pixel_list->length;
4464 sum_squared/=pixel_list->length;
4465 channels[channel]=(unsigned short) sqrt(sum_squared-(sum*sum));
cristy9a68cbb2011-03-29 00:51:23 +00004466 }
cristy4c08aed2011-07-01 19:47:50 +00004467 GetPixelInfo((const Image *) NULL,&pixel);
cristy9a68cbb2011-03-29 00:51:23 +00004468 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4469 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4470 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004471 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4472 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy9a68cbb2011-03-29 00:51:23 +00004473 return(pixel);
4474}
4475
cristy4c08aed2011-07-01 19:47:50 +00004476static inline void InsertPixelList(const Image *image,const Quantum *pixel,
4477 PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004478{
4479 size_t
4480 signature;
4481
4482 unsigned short
4483 index;
4484
cristy4c08aed2011-07-01 19:47:50 +00004485 index=ScaleQuantumToShort(GetPixelRed(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004486 signature=pixel_list->lists[0].nodes[index].signature;
4487 if (signature == pixel_list->signature)
4488 pixel_list->lists[0].nodes[index].count++;
4489 else
4490 AddNodePixelList(pixel_list,0,index);
cristy4c08aed2011-07-01 19:47:50 +00004491 index=ScaleQuantumToShort(GetPixelGreen(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004492 signature=pixel_list->lists[1].nodes[index].signature;
4493 if (signature == pixel_list->signature)
4494 pixel_list->lists[1].nodes[index].count++;
4495 else
4496 AddNodePixelList(pixel_list,1,index);
cristy4c08aed2011-07-01 19:47:50 +00004497 index=ScaleQuantumToShort(GetPixelBlue(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004498 signature=pixel_list->lists[2].nodes[index].signature;
4499 if (signature == pixel_list->signature)
4500 pixel_list->lists[2].nodes[index].count++;
4501 else
4502 AddNodePixelList(pixel_list,2,index);
cristy4c08aed2011-07-01 19:47:50 +00004503 index=ScaleQuantumToShort(GetPixelAlpha(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004504 signature=pixel_list->lists[3].nodes[index].signature;
4505 if (signature == pixel_list->signature)
4506 pixel_list->lists[3].nodes[index].count++;
4507 else
4508 AddNodePixelList(pixel_list,3,index);
4509 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00004510 index=ScaleQuantumToShort(GetPixelBlack(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004511 signature=pixel_list->lists[4].nodes[index].signature;
4512 if (signature == pixel_list->signature)
4513 pixel_list->lists[4].nodes[index].count++;
4514 else
4515 AddNodePixelList(pixel_list,4,index);
4516}
4517
cristy80c99742011-04-04 14:46:39 +00004518static inline MagickRealType MagickAbsoluteValue(const MagickRealType x)
4519{
4520 if (x < 0)
4521 return(-x);
4522 return(x);
4523}
4524
cristy733678d2011-03-18 21:29:28 +00004525static void ResetPixelList(PixelList *pixel_list)
4526{
4527 int
4528 level;
4529
4530 register ListNode
4531 *root;
4532
4533 register SkipList
4534 *list;
4535
4536 register ssize_t
4537 channel;
4538
4539 /*
4540 Reset the skip-list.
4541 */
4542 for (channel=0; channel < 5; channel++)
4543 {
4544 list=pixel_list->lists+channel;
4545 root=list->nodes+65536UL;
4546 list->level=0;
4547 for (level=0; level < 9; level++)
4548 root->next[level]=65536UL;
4549 }
4550 pixel_list->seed=pixel_list->signature++;
4551}
4552
cristy0834d642011-03-18 18:26:08 +00004553MagickExport Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00004554 const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00004555{
cristy3cba8ca2011-03-19 01:29:12 +00004556#define StatisticWidth \
cristyd76c51e2011-03-26 00:21:26 +00004557 (width == 0 ? GetOptimalKernelWidth2D((double) width,0.5) : width)
cristy3cba8ca2011-03-19 01:29:12 +00004558#define StatisticHeight \
cristyd76c51e2011-03-26 00:21:26 +00004559 (height == 0 ? GetOptimalKernelWidth2D((double) height,0.5) : height)
cristy0834d642011-03-18 18:26:08 +00004560#define StatisticImageTag "Statistic/Image"
4561
4562 CacheView
4563 *image_view,
4564 *statistic_view;
4565
4566 Image
4567 *statistic_image;
4568
4569 MagickBooleanType
4570 status;
4571
4572 MagickOffsetType
4573 progress;
4574
4575 PixelList
4576 **restrict pixel_list;
4577
cristy0834d642011-03-18 18:26:08 +00004578 ssize_t
4579 y;
4580
4581 /*
4582 Initialize statistics image attributes.
4583 */
4584 assert(image != (Image *) NULL);
4585 assert(image->signature == MagickSignature);
4586 if (image->debug != MagickFalse)
4587 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4588 assert(exception != (ExceptionInfo *) NULL);
4589 assert(exception->signature == MagickSignature);
cristy0834d642011-03-18 18:26:08 +00004590 statistic_image=CloneImage(image,image->columns,image->rows,MagickTrue,
4591 exception);
4592 if (statistic_image == (Image *) NULL)
4593 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00004594 if (SetImageStorageClass(statistic_image,DirectClass,exception) == MagickFalse)
cristy0834d642011-03-18 18:26:08 +00004595 {
cristy0834d642011-03-18 18:26:08 +00004596 statistic_image=DestroyImage(statistic_image);
4597 return((Image *) NULL);
4598 }
cristy6fc86bb2011-03-18 23:45:16 +00004599 pixel_list=AcquirePixelListThreadSet(StatisticWidth,StatisticHeight);
cristy0834d642011-03-18 18:26:08 +00004600 if (pixel_list == (PixelList **) NULL)
4601 {
4602 statistic_image=DestroyImage(statistic_image);
4603 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
4604 }
4605 /*
cristy8d752042011-03-19 01:00:36 +00004606 Make each pixel the min / max / median / mode / etc. of the neighborhood.
cristy0834d642011-03-18 18:26:08 +00004607 */
4608 status=MagickTrue;
4609 progress=0;
4610 image_view=AcquireCacheView(image);
4611 statistic_view=AcquireCacheView(statistic_image);
4612#if defined(MAGICKCORE_OPENMP_SUPPORT)
4613 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
4614#endif
4615 for (y=0; y < (ssize_t) statistic_image->rows; y++)
4616 {
4617 const int
4618 id = GetOpenMPThreadId();
4619
cristy4c08aed2011-07-01 19:47:50 +00004620 register const Quantum
cristy0834d642011-03-18 18:26:08 +00004621 *restrict p;
4622
cristy4c08aed2011-07-01 19:47:50 +00004623 register Quantum
cristy0834d642011-03-18 18:26:08 +00004624 *restrict q;
4625
4626 register ssize_t
4627 x;
4628
4629 if (status == MagickFalse)
4630 continue;
cristy6fc86bb2011-03-18 23:45:16 +00004631 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) StatisticWidth/2L),y-
4632 (ssize_t) (StatisticHeight/2L),image->columns+StatisticWidth,
4633 StatisticHeight,exception);
cristy3cba8ca2011-03-19 01:29:12 +00004634 q=QueueCacheViewAuthenticPixels(statistic_view,0,y,statistic_image->columns, 1,exception);
cristy4c08aed2011-07-01 19:47:50 +00004635 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy0834d642011-03-18 18:26:08 +00004636 {
4637 status=MagickFalse;
4638 continue;
4639 }
cristy0834d642011-03-18 18:26:08 +00004640 for (x=0; x < (ssize_t) statistic_image->columns; x++)
4641 {
cristy4c08aed2011-07-01 19:47:50 +00004642 PixelInfo
cristy0834d642011-03-18 18:26:08 +00004643 pixel;
4644
cristy4c08aed2011-07-01 19:47:50 +00004645 register const Quantum
cristy6e3026a2011-03-19 00:54:38 +00004646 *restrict r;
4647
cristy0834d642011-03-18 18:26:08 +00004648 register ssize_t
4649 u,
4650 v;
4651
4652 r=p;
cristy0834d642011-03-18 18:26:08 +00004653 ResetPixelList(pixel_list[id]);
cristy6e4c3292011-03-19 00:53:55 +00004654 for (v=0; v < (ssize_t) StatisticHeight; v++)
cristy0834d642011-03-18 18:26:08 +00004655 {
cristy6e4c3292011-03-19 00:53:55 +00004656 for (u=0; u < (ssize_t) StatisticWidth; u++)
cristyed231572011-07-14 02:18:59 +00004657 InsertPixelList(image,r+u*GetPixelChannels(image),pixel_list[id]);
4658 r+=(image->columns+StatisticWidth)*GetPixelChannels(image);
cristy0834d642011-03-18 18:26:08 +00004659 }
cristy4c08aed2011-07-01 19:47:50 +00004660 GetPixelInfo(image,&pixel);
cristy490408a2011-07-07 14:42:05 +00004661 SetPixelInfo(image,p+(StatisticWidth*StatisticHeight/2)*
cristyed231572011-07-14 02:18:59 +00004662 GetPixelChannels(image),&pixel);
cristy0834d642011-03-18 18:26:08 +00004663 switch (type)
4664 {
cristy80c99742011-04-04 14:46:39 +00004665 case GradientStatistic:
4666 {
cristy4c08aed2011-07-01 19:47:50 +00004667 PixelInfo
cristy80c99742011-04-04 14:46:39 +00004668 maximum,
4669 minimum;
4670
4671 minimum=GetMinimumPixelList(pixel_list[id]);
4672 maximum=GetMaximumPixelList(pixel_list[id]);
4673 pixel.red=MagickAbsoluteValue(maximum.red-minimum.red);
4674 pixel.green=MagickAbsoluteValue(maximum.green-minimum.green);
4675 pixel.blue=MagickAbsoluteValue(maximum.blue-minimum.blue);
cristy4c08aed2011-07-01 19:47:50 +00004676 pixel.alpha=MagickAbsoluteValue(maximum.alpha-minimum.alpha);
cristy80c99742011-04-04 14:46:39 +00004677 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00004678 pixel.black=MagickAbsoluteValue(maximum.black-minimum.black);
cristy80c99742011-04-04 14:46:39 +00004679 break;
4680 }
cristy6fc86bb2011-03-18 23:45:16 +00004681 case MaximumStatistic:
4682 {
4683 pixel=GetMaximumPixelList(pixel_list[id]);
4684 break;
4685 }
cristy49f37242011-03-22 18:18:23 +00004686 case MeanStatistic:
4687 {
4688 pixel=GetMeanPixelList(pixel_list[id]);
4689 break;
4690 }
cristyf2ad14a2011-03-18 18:57:25 +00004691 case MedianStatistic:
cristy6fc86bb2011-03-18 23:45:16 +00004692 default:
cristyf2ad14a2011-03-18 18:57:25 +00004693 {
4694 pixel=GetMedianPixelList(pixel_list[id]);
4695 break;
4696 }
cristy6fc86bb2011-03-18 23:45:16 +00004697 case MinimumStatistic:
4698 {
4699 pixel=GetMinimumPixelList(pixel_list[id]);
4700 break;
4701 }
cristyf2ad14a2011-03-18 18:57:25 +00004702 case ModeStatistic:
4703 {
4704 pixel=GetModePixelList(pixel_list[id]);
4705 break;
4706 }
4707 case NonpeakStatistic:
4708 {
4709 pixel=GetNonpeakPixelList(pixel_list[id]);
4710 break;
4711 }
cristy9a68cbb2011-03-29 00:51:23 +00004712 case StandardDeviationStatistic:
4713 {
4714 pixel=GetStandardDeviationPixelList(pixel_list[id]);
4715 break;
4716 }
cristy0834d642011-03-18 18:26:08 +00004717 }
cristyed231572011-07-14 02:18:59 +00004718 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00004719 SetPixelRed(statistic_image,ClampToQuantum(pixel.red),q);
cristyed231572011-07-14 02:18:59 +00004720 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00004721 SetPixelGreen(statistic_image,ClampToQuantum(pixel.green),q);
cristyed231572011-07-14 02:18:59 +00004722 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00004723 SetPixelBlue(statistic_image,ClampToQuantum(pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00004724 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy0834d642011-03-18 18:26:08 +00004725 (image->colorspace == CMYKColorspace))
cristy490408a2011-07-07 14:42:05 +00004726 SetPixelBlack(statistic_image,ClampToQuantum(pixel.black),q);
cristyed231572011-07-14 02:18:59 +00004727 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00004728 (image->matte != MagickFalse))
cristy490408a2011-07-07 14:42:05 +00004729 SetPixelAlpha(statistic_image,ClampToQuantum(pixel.alpha),q);
cristyed231572011-07-14 02:18:59 +00004730 p+=GetPixelChannels(image);
4731 q+=GetPixelChannels(statistic_image);
cristy0834d642011-03-18 18:26:08 +00004732 }
4733 if (SyncCacheViewAuthenticPixels(statistic_view,exception) == MagickFalse)
4734 status=MagickFalse;
4735 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4736 {
4737 MagickBooleanType
4738 proceed;
4739
4740#if defined(MAGICKCORE_OPENMP_SUPPORT)
4741 #pragma omp critical (MagickCore_StatisticImage)
4742#endif
4743 proceed=SetImageProgress(image,StatisticImageTag,progress++,
4744 image->rows);
4745 if (proceed == MagickFalse)
4746 status=MagickFalse;
4747 }
4748 }
4749 statistic_view=DestroyCacheView(statistic_view);
4750 image_view=DestroyCacheView(image_view);
4751 pixel_list=DestroyPixelListThreadSet(pixel_list);
4752 return(statistic_image);
4753}
4754
4755/*
4756%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4757% %
4758% %
4759% %
cristy3ed852e2009-09-05 21:47:34 +00004760% U n s h a r p M a s k I m a g e %
4761% %
4762% %
4763% %
4764%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4765%
4766% UnsharpMaskImage() sharpens one or more image channels. We convolve the
4767% image with a Gaussian operator of the given radius and standard deviation
4768% (sigma). For reasonable results, radius should be larger than sigma. Use a
4769% radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
4770%
4771% The format of the UnsharpMaskImage method is:
4772%
4773% Image *UnsharpMaskImage(const Image *image,const double radius,
4774% const double sigma,const double amount,const double threshold,
4775% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00004776%
4777% A description of each parameter follows:
4778%
4779% o image: the image.
4780%
cristy3ed852e2009-09-05 21:47:34 +00004781% o radius: the radius of the Gaussian, in pixels, not counting the center
4782% pixel.
4783%
4784% o sigma: the standard deviation of the Gaussian, in pixels.
4785%
4786% o amount: the percentage of the difference between the original and the
4787% blur image that is added back into the original.
4788%
4789% o threshold: the threshold in pixels needed to apply the diffence amount.
4790%
4791% o exception: return any errors or warnings in this structure.
4792%
4793*/
cristyf4ad9df2011-07-08 16:49:03 +00004794MagickExport Image *UnsharpMaskImage(const Image *image,
4795 const double radius,const double sigma,const double amount,
4796 const double threshold,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00004797{
4798#define SharpenImageTag "Sharpen/Image"
4799
cristyc4c8d132010-01-07 01:58:38 +00004800 CacheView
4801 *image_view,
4802 *unsharp_view;
4803
cristy3ed852e2009-09-05 21:47:34 +00004804 Image
4805 *unsharp_image;
4806
cristy3ed852e2009-09-05 21:47:34 +00004807 MagickBooleanType
4808 status;
4809
cristybb503372010-05-27 20:51:26 +00004810 MagickOffsetType
4811 progress;
4812
cristy3ed852e2009-09-05 21:47:34 +00004813 MagickRealType
4814 quantum_threshold;
4815
cristybb503372010-05-27 20:51:26 +00004816 ssize_t
4817 y;
4818
cristy3ed852e2009-09-05 21:47:34 +00004819 assert(image != (const Image *) NULL);
4820 assert(image->signature == MagickSignature);
4821 if (image->debug != MagickFalse)
4822 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4823 assert(exception != (ExceptionInfo *) NULL);
cristy05c0c9a2011-09-05 23:16:13 +00004824 unsharp_image=BlurImage(image,radius,sigma,image->bias,exception);
cristy3ed852e2009-09-05 21:47:34 +00004825 if (unsharp_image == (Image *) NULL)
4826 return((Image *) NULL);
4827 quantum_threshold=(MagickRealType) QuantumRange*threshold;
4828 /*
4829 Unsharp-mask image.
4830 */
4831 status=MagickTrue;
4832 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00004833 image_view=AcquireCacheView(image);
4834 unsharp_view=AcquireCacheView(unsharp_image);
cristyb5d5f722009-11-04 03:03:49 +00004835#if defined(MAGICKCORE_OPENMP_SUPPORT)
4836 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004837#endif
cristybb503372010-05-27 20:51:26 +00004838 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00004839 {
cristy4c08aed2011-07-01 19:47:50 +00004840 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00004841 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00004842
cristy4c08aed2011-07-01 19:47:50 +00004843 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00004844 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004845
cristy117ff172010-08-15 21:35:32 +00004846 register ssize_t
4847 x;
4848
cristy3ed852e2009-09-05 21:47:34 +00004849 if (status == MagickFalse)
4850 continue;
4851 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
4852 q=GetCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
4853 exception);
cristy4c08aed2011-07-01 19:47:50 +00004854 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00004855 {
4856 status=MagickFalse;
4857 continue;
4858 }
cristybb503372010-05-27 20:51:26 +00004859 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004860 {
cristy7f3a0d12011-09-05 23:27:59 +00004861 register ssize_t
4862 i;
4863
4864 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4865 {
4866 MagickRealType
4867 pixel;
4868
4869 PixelChannel
4870 channel;
4871
4872 PixelTrait
4873 traits,
4874 unsharp_traits;
4875
4876 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
4877 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
4878 unsharp_traits=GetPixelChannelMapTraits(unsharp_image,channel);
4879 if ((traits == UndefinedPixelTrait) ||
4880 (unsharp_traits == UndefinedPixelTrait))
4881 continue;
4882 if ((unsharp_traits & CopyPixelTrait) != 0)
4883 {
4884 q[channel]=p[i];
4885 continue;
4886 }
4887 pixel=p[i]-(MagickRealType) q[channel];
4888 if (fabs(2.0*pixel) < quantum_threshold)
4889 pixel=(MagickRealType) p[i];
4890 else
4891 pixel=(MagickRealType) p[i]+amount*pixel;
4892 q[channel]=ClampToQuantum(pixel);
4893 }
cristyed231572011-07-14 02:18:59 +00004894 p+=GetPixelChannels(image);
4895 q+=GetPixelChannels(unsharp_image);
cristy3ed852e2009-09-05 21:47:34 +00004896 }
4897 if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
4898 status=MagickFalse;
4899 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4900 {
4901 MagickBooleanType
4902 proceed;
4903
cristyb5d5f722009-11-04 03:03:49 +00004904#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00004905 #pragma omp critical (MagickCore_UnsharpMaskImage)
cristy3ed852e2009-09-05 21:47:34 +00004906#endif
4907 proceed=SetImageProgress(image,SharpenImageTag,progress++,image->rows);
4908 if (proceed == MagickFalse)
4909 status=MagickFalse;
4910 }
4911 }
4912 unsharp_image->type=image->type;
4913 unsharp_view=DestroyCacheView(unsharp_view);
4914 image_view=DestroyCacheView(image_view);
4915 if (status == MagickFalse)
4916 unsharp_image=DestroyImage(unsharp_image);
4917 return(unsharp_image);
4918}