blob: deb7d5290c7eff1a54a53d68516dd259866fda63 [file] [log] [blame]
cristy3ed852e2009-09-05 21:47:34 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% EEEEE FFFFF FFFFF EEEEE CCCC TTTTT %
7% E F F E C T %
8% EEE FFF FFF EEE C T %
9% E F F E C T %
10% EEEEE F F EEEEE CCCC T %
11% %
12% %
13% MagickCore Image Effects Methods %
14% %
15% Software Design %
16% John Cristy %
17% October 1996 %
18% %
19% %
cristy7e41fe82010-12-04 23:12:08 +000020% Copyright 1999-2011 ImageMagick Studio LLC, a non-profit organization %
cristy3ed852e2009-09-05 21:47:34 +000021% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
26% http://www.imagemagick.org/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
36%
37%
38*/
39
40/*
41 Include declarations.
42*/
cristy4c08aed2011-07-01 19:47:50 +000043#include "MagickCore/studio.h"
44#include "MagickCore/accelerate.h"
45#include "MagickCore/blob.h"
46#include "MagickCore/cache-view.h"
47#include "MagickCore/color.h"
48#include "MagickCore/color-private.h"
49#include "MagickCore/colorspace.h"
50#include "MagickCore/constitute.h"
51#include "MagickCore/decorate.h"
52#include "MagickCore/draw.h"
53#include "MagickCore/enhance.h"
54#include "MagickCore/exception.h"
55#include "MagickCore/exception-private.h"
56#include "MagickCore/effect.h"
57#include "MagickCore/fx.h"
58#include "MagickCore/gem.h"
cristy8ea81222011-09-04 10:33:32 +000059#include "MagickCore/gem-private.h"
cristy4c08aed2011-07-01 19:47:50 +000060#include "MagickCore/geometry.h"
61#include "MagickCore/image-private.h"
62#include "MagickCore/list.h"
63#include "MagickCore/log.h"
64#include "MagickCore/memory_.h"
65#include "MagickCore/monitor.h"
66#include "MagickCore/monitor-private.h"
67#include "MagickCore/montage.h"
68#include "MagickCore/morphology.h"
69#include "MagickCore/paint.h"
70#include "MagickCore/pixel-accessor.h"
71#include "MagickCore/property.h"
72#include "MagickCore/quantize.h"
73#include "MagickCore/quantum.h"
74#include "MagickCore/quantum-private.h"
75#include "MagickCore/random_.h"
76#include "MagickCore/random-private.h"
77#include "MagickCore/resample.h"
78#include "MagickCore/resample-private.h"
79#include "MagickCore/resize.h"
80#include "MagickCore/resource_.h"
81#include "MagickCore/segment.h"
82#include "MagickCore/shear.h"
83#include "MagickCore/signature-private.h"
84#include "MagickCore/string_.h"
85#include "MagickCore/thread-private.h"
86#include "MagickCore/transform.h"
87#include "MagickCore/threshold.h"
cristy3ed852e2009-09-05 21:47:34 +000088
89/*
90%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
91% %
92% %
93% %
94% A d a p t i v e B l u r I m a g e %
95% %
96% %
97% %
98%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
99%
100% AdaptiveBlurImage() adaptively blurs the image by blurring less
101% intensely near image edges and more intensely far from edges. We blur the
102% image with a Gaussian operator of the given radius and standard deviation
103% (sigma). For reasonable results, radius should be larger than sigma. Use a
104% radius of 0 and AdaptiveBlurImage() selects a suitable radius for you.
105%
106% The format of the AdaptiveBlurImage method is:
107%
108% Image *AdaptiveBlurImage(const Image *image,const double radius,
cristy4c11c2b2011-09-05 20:17:07 +0000109% const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000110%
111% A description of each parameter follows:
112%
113% o image: the image.
114%
cristy3ed852e2009-09-05 21:47:34 +0000115% o radius: the radius of the Gaussian, in pixels, not counting the center
116% pixel.
117%
118% o sigma: the standard deviation of the Laplacian, in pixels.
119%
cristy4c11c2b2011-09-05 20:17:07 +0000120% o bias: the bias.
121%
cristy3ed852e2009-09-05 21:47:34 +0000122% o exception: return any errors or warnings in this structure.
123%
124*/
125
cristyf89cb1d2011-07-07 01:24:37 +0000126MagickExport MagickBooleanType AdaptiveLevelImage(Image *image,
cristy051718b2011-08-28 22:49:25 +0000127 const char *levels,ExceptionInfo *exception)
cristyf89cb1d2011-07-07 01:24:37 +0000128{
129 double
130 black_point,
131 gamma,
132 white_point;
133
134 GeometryInfo
135 geometry_info;
136
137 MagickBooleanType
138 status;
139
140 MagickStatusType
141 flags;
142
143 /*
144 Parse levels.
145 */
146 if (levels == (char *) NULL)
147 return(MagickFalse);
148 flags=ParseGeometry(levels,&geometry_info);
149 black_point=geometry_info.rho;
150 white_point=(double) QuantumRange;
151 if ((flags & SigmaValue) != 0)
152 white_point=geometry_info.sigma;
153 gamma=1.0;
154 if ((flags & XiValue) != 0)
155 gamma=geometry_info.xi;
156 if ((flags & PercentValue) != 0)
157 {
158 black_point*=(double) image->columns*image->rows/100.0;
159 white_point*=(double) image->columns*image->rows/100.0;
160 }
161 if ((flags & SigmaValue) == 0)
162 white_point=(double) QuantumRange-black_point;
163 if ((flags & AspectValue ) == 0)
cristy7c0a0a42011-08-23 17:57:25 +0000164 status=LevelImage(image,black_point,white_point,gamma,exception);
cristyf89cb1d2011-07-07 01:24:37 +0000165 else
cristy7c0a0a42011-08-23 17:57:25 +0000166 status=LevelizeImage(image,black_point,white_point,gamma,exception);
cristyf89cb1d2011-07-07 01:24:37 +0000167 return(status);
168}
169
cristyf4ad9df2011-07-08 16:49:03 +0000170MagickExport Image *AdaptiveBlurImage(const Image *image,
cristy4c11c2b2011-09-05 20:17:07 +0000171 const double radius,const double sigma,const double bias,
172 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000173{
174#define AdaptiveBlurImageTag "Convolve/Image"
175#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
176
cristyc4c8d132010-01-07 01:58:38 +0000177 CacheView
178 *blur_view,
179 *edge_view,
180 *image_view;
181
cristy3ed852e2009-09-05 21:47:34 +0000182 double
cristy47e00502009-12-17 19:19:57 +0000183 **kernel,
184 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000185
186 Image
187 *blur_image,
188 *edge_image,
189 *gaussian_image;
190
cristy3ed852e2009-09-05 21:47:34 +0000191 MagickBooleanType
192 status;
193
cristybb503372010-05-27 20:51:26 +0000194 MagickOffsetType
195 progress;
196
cristybb503372010-05-27 20:51:26 +0000197 register ssize_t
cristy47e00502009-12-17 19:19:57 +0000198 i;
cristy3ed852e2009-09-05 21:47:34 +0000199
cristybb503372010-05-27 20:51:26 +0000200 size_t
cristy3ed852e2009-09-05 21:47:34 +0000201 width;
202
cristybb503372010-05-27 20:51:26 +0000203 ssize_t
204 j,
205 k,
206 u,
207 v,
208 y;
209
cristy3ed852e2009-09-05 21:47:34 +0000210 assert(image != (const Image *) NULL);
211 assert(image->signature == MagickSignature);
212 if (image->debug != MagickFalse)
213 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
214 assert(exception != (ExceptionInfo *) NULL);
215 assert(exception->signature == MagickSignature);
216 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
217 if (blur_image == (Image *) NULL)
218 return((Image *) NULL);
219 if (fabs(sigma) <= MagickEpsilon)
220 return(blur_image);
cristy574cc262011-08-05 01:23:58 +0000221 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000222 {
cristy3ed852e2009-09-05 21:47:34 +0000223 blur_image=DestroyImage(blur_image);
224 return((Image *) NULL);
225 }
226 /*
227 Edge detect the image brighness channel, level, blur, and level again.
228 */
cristy8ae632d2011-09-05 17:29:53 +0000229 edge_image=EdgeImage(image,radius,sigma,exception);
cristy3ed852e2009-09-05 21:47:34 +0000230 if (edge_image == (Image *) NULL)
231 {
232 blur_image=DestroyImage(blur_image);
233 return((Image *) NULL);
234 }
cristy051718b2011-08-28 22:49:25 +0000235 (void) AdaptiveLevelImage(edge_image,"20%,95%",exception);
cristy05c0c9a2011-09-05 23:16:13 +0000236 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,bias,exception);
cristy3ed852e2009-09-05 21:47:34 +0000237 if (gaussian_image != (Image *) NULL)
238 {
239 edge_image=DestroyImage(edge_image);
240 edge_image=gaussian_image;
241 }
cristy051718b2011-08-28 22:49:25 +0000242 (void) AdaptiveLevelImage(edge_image,"10%,95%",exception);
cristy3ed852e2009-09-05 21:47:34 +0000243 /*
244 Create a set of kernels from maximum (radius,sigma) to minimum.
245 */
246 width=GetOptimalKernelWidth2D(radius,sigma);
247 kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
248 if (kernel == (double **) NULL)
249 {
250 edge_image=DestroyImage(edge_image);
251 blur_image=DestroyImage(blur_image);
252 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
253 }
254 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
cristybb503372010-05-27 20:51:26 +0000255 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000256 {
257 kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)*
258 sizeof(**kernel));
259 if (kernel[i] == (double *) NULL)
260 break;
cristy47e00502009-12-17 19:19:57 +0000261 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000262 j=(ssize_t) (width-i)/2;
cristy47e00502009-12-17 19:19:57 +0000263 k=0;
264 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000265 {
cristy47e00502009-12-17 19:19:57 +0000266 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000267 {
cristy4205a3c2010-09-12 20:19:59 +0000268 kernel[i][k]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
269 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +0000270 normalize+=kernel[i][k];
271 k++;
cristy3ed852e2009-09-05 21:47:34 +0000272 }
273 }
cristy3ed852e2009-09-05 21:47:34 +0000274 if (fabs(normalize) <= MagickEpsilon)
275 normalize=1.0;
276 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000277 for (k=0; k < (j*j); k++)
278 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000279 }
cristybb503372010-05-27 20:51:26 +0000280 if (i < (ssize_t) width)
cristy3ed852e2009-09-05 21:47:34 +0000281 {
282 for (i-=2; i >= 0; i-=2)
283 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
284 kernel=(double **) RelinquishMagickMemory(kernel);
285 edge_image=DestroyImage(edge_image);
286 blur_image=DestroyImage(blur_image);
287 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
288 }
289 /*
290 Adaptively blur image.
291 */
292 status=MagickTrue;
293 progress=0;
cristy3ed852e2009-09-05 21:47:34 +0000294 image_view=AcquireCacheView(image);
295 edge_view=AcquireCacheView(edge_image);
296 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000297#if defined(MAGICKCORE_OPENMP_SUPPORT)
298 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000299#endif
cristybb503372010-05-27 20:51:26 +0000300 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000301 {
cristy4c08aed2011-07-01 19:47:50 +0000302 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000303 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000304
cristy4c08aed2011-07-01 19:47:50 +0000305 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000306 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000307
cristy117ff172010-08-15 21:35:32 +0000308 register ssize_t
309 x;
310
cristy3ed852e2009-09-05 21:47:34 +0000311 if (status == MagickFalse)
312 continue;
313 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
314 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
315 exception);
cristyacd2ed22011-08-30 01:44:23 +0000316 if ((r == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000317 {
318 status=MagickFalse;
319 continue;
320 }
cristybb503372010-05-27 20:51:26 +0000321 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000322 {
cristy4c11c2b2011-09-05 20:17:07 +0000323 register const Quantum
324 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000325
cristybb503372010-05-27 20:51:26 +0000326 register ssize_t
cristy4c11c2b2011-09-05 20:17:07 +0000327 i;
cristy3ed852e2009-09-05 21:47:34 +0000328
cristy4c11c2b2011-09-05 20:17:07 +0000329 ssize_t
330 center,
331 j;
332
333 j=(ssize_t) ceil((double) width*QuantumScale*
cristy4c08aed2011-07-01 19:47:50 +0000334 GetPixelIntensity(edge_image,r)-0.5);
cristy4c11c2b2011-09-05 20:17:07 +0000335 if (j < 0)
336 j=0;
cristy3ed852e2009-09-05 21:47:34 +0000337 else
cristy4c11c2b2011-09-05 20:17:07 +0000338 if (j > (ssize_t) width)
339 j=(ssize_t) width;
340 if ((j & 0x01) != 0)
341 j--;
342 p=GetCacheViewVirtualPixels(image_view,x-((ssize_t) (width-j)/2L),y-
343 (ssize_t) ((width-j)/2L),width-j,width-j,exception);
cristy4c08aed2011-07-01 19:47:50 +0000344 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000345 break;
cristy4c11c2b2011-09-05 20:17:07 +0000346 center=(ssize_t) GetPixelChannels(image)*(width-j)*
cristy075ff302011-09-07 01:51:24 +0000347 ((width-j)/2L)+GetPixelChannels(image)*((width-j)/2L);
cristy4c11c2b2011-09-05 20:17:07 +0000348 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy3ed852e2009-09-05 21:47:34 +0000349 {
cristy4c11c2b2011-09-05 20:17:07 +0000350 MagickRealType
351 alpha,
352 gamma,
353 pixel;
354
355 PixelChannel
356 channel;
357
358 PixelTrait
359 blur_traits,
360 traits;
361
362 register const double
363 *restrict k;
364
365 register const Quantum
366 *restrict pixels;
367
368 register ssize_t
369 u;
370
371 ssize_t
372 v;
373
374 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
375 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
376 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
377 if ((traits == UndefinedPixelTrait) ||
378 (blur_traits == UndefinedPixelTrait))
379 continue;
380 if ((blur_traits & CopyPixelTrait) != 0)
381 {
cristy0beccfa2011-09-25 20:47:53 +0000382 SetPixelChannel(blur_image,channel,p[center+i],q);
cristy4c11c2b2011-09-05 20:17:07 +0000383 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);
cristy0beccfa2011-09-25 20:47:53 +0000405 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy4c11c2b2011-09-05 20:17:07 +0000406 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);
cristy0beccfa2011-09-25 20:47:53 +0000423 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
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 {
cristy0beccfa2011-09-25 20:47:53 +0000704 SetPixelChannel(sharp_image,channel,p[center+i],q);
cristy4c11c2b2011-09-05 20:17:07 +0000705 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);
cristy0beccfa2011-09-25 20:47:53 +0000727 SetPixelChannel(sharp_image,channel,ClampToQuantum(gamma*pixel),q);
cristy4c11c2b2011-09-05 20:17:07 +0000728 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);
cristy0beccfa2011-09-25 20:47:53 +0000745 SetPixelChannel(sharp_image,channel,ClampToQuantum(gamma*pixel),q);
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 {
cristy0beccfa2011-09-25 20:47:53 +00001005 SetPixelChannel(blur_image,channel,p[center+i],q);
cristyb41a1172011-09-06 00:55:14 +00001006 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 }
cristy0beccfa2011-09-25 20:47:53 +00001022 SetPixelChannel(blur_image,channel,ClampToQuantum(pixel),q);
cristyb41a1172011-09-06 00:55:14 +00001023 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);
cristy0beccfa2011-09-25 20:47:53 +00001038 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristyb41a1172011-09-06 00:55:14 +00001039 }
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 {
cristy0beccfa2011-09-25 20:47:53 +00001126 SetPixelChannel(blur_image,channel,p[center+i],q);
cristyb41a1172011-09-06 00:55:14 +00001127 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 }
cristy0beccfa2011-09-25 20:47:53 +00001143 SetPixelChannel(blur_image,channel,ClampToQuantum(pixel),q);
cristyb41a1172011-09-06 00:55:14 +00001144 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);
cristy0beccfa2011-09-25 20:47:53 +00001160 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristyb41a1172011-09-06 00:55:14 +00001161 }
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 }
cristy7ea27962011-09-12 18:12:49 +00001293 status=AccelerateConvolveImage(image,kernel_info,convolve_image,exception);
1294 if (status == MagickTrue)
1295 return(convolve_image);
cristyfccdab92009-11-30 16:43:57 +00001296 /*
cristyfccdab92009-11-30 16:43:57 +00001297 Convolve image.
1298 */
cristy574cc262011-08-05 01:23:58 +00001299 center=(ssize_t) GetPixelChannels(image)*(image->columns+kernel_info->width)*
cristy075ff302011-09-07 01:51:24 +00001300 (kernel_info->height/2L)+GetPixelChannels(image)*(kernel_info->width/2L);
cristyfccdab92009-11-30 16:43:57 +00001301 status=MagickTrue;
1302 progress=0;
cristyfccdab92009-11-30 16:43:57 +00001303 image_view=AcquireCacheView(image);
1304 convolve_view=AcquireCacheView(convolve_image);
1305#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy175653e2011-07-10 23:13:34 +00001306 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristyfccdab92009-11-30 16:43:57 +00001307#endif
cristybb503372010-05-27 20:51:26 +00001308 for (y=0; y < (ssize_t) image->rows; y++)
cristyfccdab92009-11-30 16:43:57 +00001309 {
cristy4c08aed2011-07-01 19:47:50 +00001310 register const Quantum
cristy105ba3c2011-07-18 02:28:38 +00001311 *restrict p;
cristyfccdab92009-11-30 16:43:57 +00001312
cristy4c08aed2011-07-01 19:47:50 +00001313 register Quantum
cristyfccdab92009-11-30 16:43:57 +00001314 *restrict q;
1315
cristy117ff172010-08-15 21:35:32 +00001316 register ssize_t
1317 x;
1318
cristyfccdab92009-11-30 16:43:57 +00001319 if (status == MagickFalse)
1320 continue;
cristy105ba3c2011-07-18 02:28:38 +00001321 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) kernel_info->width/2L),y-
1322 (ssize_t) (kernel_info->height/2L),image->columns+kernel_info->width,
1323 kernel_info->height,exception);
cristy08429172011-07-14 17:18:16 +00001324 q=QueueCacheViewAuthenticPixels(convolve_view,0,y,convolve_image->columns,1,
cristyfccdab92009-11-30 16:43:57 +00001325 exception);
cristy105ba3c2011-07-18 02:28:38 +00001326 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristyfccdab92009-11-30 16:43:57 +00001327 {
1328 status=MagickFalse;
1329 continue;
1330 }
cristybb503372010-05-27 20:51:26 +00001331 for (x=0; x < (ssize_t) image->columns; x++)
cristyfccdab92009-11-30 16:43:57 +00001332 {
cristybb503372010-05-27 20:51:26 +00001333 register ssize_t
cristyed231572011-07-14 02:18:59 +00001334 i;
cristyfccdab92009-11-30 16:43:57 +00001335
cristya30d9ba2011-07-23 21:00:48 +00001336 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristyed231572011-07-14 02:18:59 +00001337 {
cristyed231572011-07-14 02:18:59 +00001338 MagickRealType
cristy4e154852011-07-14 13:28:53 +00001339 alpha,
1340 gamma,
cristyed231572011-07-14 02:18:59 +00001341 pixel;
1342
1343 PixelChannel
1344 channel;
1345
1346 PixelTrait
1347 convolve_traits,
1348 traits;
1349
1350 register const double
1351 *restrict k;
1352
1353 register const Quantum
cristyeb52cde2011-07-17 01:52:52 +00001354 *restrict pixels;
cristyed231572011-07-14 02:18:59 +00001355
1356 register ssize_t
1357 u;
1358
1359 ssize_t
1360 v;
1361
cristy30301712011-07-18 15:06:51 +00001362 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
cristy30301712011-07-18 15:06:51 +00001363 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
cristy4e154852011-07-14 13:28:53 +00001364 convolve_traits=GetPixelChannelMapTraits(convolve_image,channel);
cristy010d7d12011-08-31 01:02:48 +00001365 if ((traits == UndefinedPixelTrait) ||
1366 (convolve_traits == UndefinedPixelTrait))
cristy4e154852011-07-14 13:28:53 +00001367 continue;
1368 if ((convolve_traits & CopyPixelTrait) != 0)
1369 {
cristy0beccfa2011-09-25 20:47:53 +00001370 SetPixelChannel(convolve_image,channel,p[center+i],q);
cristy4e154852011-07-14 13:28:53 +00001371 continue;
1372 }
cristy5e6be1e2011-07-16 01:23:39 +00001373 k=kernel_info->values;
cristy105ba3c2011-07-18 02:28:38 +00001374 pixels=p;
cristy0a922382011-07-16 15:30:34 +00001375 pixel=kernel_info->bias;
cristy222b19c2011-08-04 01:35:11 +00001376 if ((convolve_traits & BlendPixelTrait) == 0)
cristyfccdab92009-11-30 16:43:57 +00001377 {
cristyed231572011-07-14 02:18:59 +00001378 /*
cristy4e154852011-07-14 13:28:53 +00001379 No alpha blending.
cristyed231572011-07-14 02:18:59 +00001380 */
cristyeb52cde2011-07-17 01:52:52 +00001381 for (v=0; v < (ssize_t) kernel_info->height; v++)
cristyfccdab92009-11-30 16:43:57 +00001382 {
cristyeb52cde2011-07-17 01:52:52 +00001383 for (u=0; u < (ssize_t) kernel_info->width; u++)
cristy175653e2011-07-10 23:13:34 +00001384 {
cristyeb52cde2011-07-17 01:52:52 +00001385 pixel+=(*k)*pixels[i];
cristyed231572011-07-14 02:18:59 +00001386 k++;
cristya30d9ba2011-07-23 21:00:48 +00001387 pixels+=GetPixelChannels(image);
cristy175653e2011-07-10 23:13:34 +00001388 }
cristya30d9ba2011-07-23 21:00:48 +00001389 pixels+=image->columns*GetPixelChannels(image);
cristy175653e2011-07-10 23:13:34 +00001390 }
cristy0beccfa2011-09-25 20:47:53 +00001391 SetPixelChannel(convolve_image,channel,ClampToQuantum(pixel),q);
cristy4e154852011-07-14 13:28:53 +00001392 continue;
cristyed231572011-07-14 02:18:59 +00001393 }
cristy4e154852011-07-14 13:28:53 +00001394 /*
1395 Alpha blending.
1396 */
1397 gamma=0.0;
cristyeb52cde2011-07-17 01:52:52 +00001398 for (v=0; v < (ssize_t) kernel_info->height; v++)
cristy4e154852011-07-14 13:28:53 +00001399 {
cristyeb52cde2011-07-17 01:52:52 +00001400 for (u=0; u < (ssize_t) kernel_info->width; u++)
cristy4e154852011-07-14 13:28:53 +00001401 {
cristyeb52cde2011-07-17 01:52:52 +00001402 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,pixels));
1403 pixel+=(*k)*alpha*pixels[i];
cristy4e154852011-07-14 13:28:53 +00001404 gamma+=(*k)*alpha;
1405 k++;
cristya30d9ba2011-07-23 21:00:48 +00001406 pixels+=GetPixelChannels(image);
cristy4e154852011-07-14 13:28:53 +00001407 }
cristya30d9ba2011-07-23 21:00:48 +00001408 pixels+=image->columns*GetPixelChannels(image);
cristy4e154852011-07-14 13:28:53 +00001409 }
cristy1ce96d02011-07-14 17:57:24 +00001410 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +00001411 SetPixelChannel(convolve_image,channel,ClampToQuantum(gamma*pixel),q);
cristyed231572011-07-14 02:18:59 +00001412 }
cristya30d9ba2011-07-23 21:00:48 +00001413 p+=GetPixelChannels(image);
1414 q+=GetPixelChannels(convolve_image);
cristyfccdab92009-11-30 16:43:57 +00001415 }
cristyed231572011-07-14 02:18:59 +00001416 if (SyncCacheViewAuthenticPixels(convolve_view,exception) == MagickFalse)
cristyfccdab92009-11-30 16:43:57 +00001417 status=MagickFalse;
1418 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1419 {
1420 MagickBooleanType
1421 proceed;
1422
1423#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00001424 #pragma omp critical (MagickCore_ConvolveImage)
cristyfccdab92009-11-30 16:43:57 +00001425#endif
1426 proceed=SetImageProgress(image,ConvolveImageTag,progress++,image->rows);
1427 if (proceed == MagickFalse)
1428 status=MagickFalse;
1429 }
1430 }
1431 convolve_image->type=image->type;
1432 convolve_view=DestroyCacheView(convolve_view);
1433 image_view=DestroyCacheView(image_view);
cristyfccdab92009-11-30 16:43:57 +00001434 if (status == MagickFalse)
1435 convolve_image=DestroyImage(convolve_image);
1436 return(convolve_image);
1437}
1438
1439/*
1440%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1441% %
1442% %
1443% %
cristy3ed852e2009-09-05 21:47:34 +00001444% D e s p e c k l e I m a g e %
1445% %
1446% %
1447% %
1448%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1449%
1450% DespeckleImage() reduces the speckle noise in an image while perserving the
1451% edges of the original image.
1452%
1453% The format of the DespeckleImage method is:
1454%
1455% Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1456%
1457% A description of each parameter follows:
1458%
1459% o image: the image.
1460%
1461% o exception: return any errors or warnings in this structure.
1462%
1463*/
1464
cristybb503372010-05-27 20:51:26 +00001465static void Hull(const ssize_t x_offset,const ssize_t y_offset,
1466 const size_t columns,const size_t rows,Quantum *f,Quantum *g,
cristy3ed852e2009-09-05 21:47:34 +00001467 const int polarity)
1468{
cristy3ed852e2009-09-05 21:47:34 +00001469 MagickRealType
1470 v;
1471
cristy3ed852e2009-09-05 21:47:34 +00001472 register Quantum
1473 *p,
1474 *q,
1475 *r,
1476 *s;
1477
cristy117ff172010-08-15 21:35:32 +00001478 register ssize_t
1479 x;
1480
1481 ssize_t
1482 y;
1483
cristy3ed852e2009-09-05 21:47:34 +00001484 assert(f != (Quantum *) NULL);
1485 assert(g != (Quantum *) NULL);
1486 p=f+(columns+2);
1487 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001488 r=p+(y_offset*((ssize_t) columns+2)+x_offset);
1489 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001490 {
1491 p++;
1492 q++;
1493 r++;
1494 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001495 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001496 {
1497 v=(MagickRealType) (*p);
1498 if ((MagickRealType) *r >= (v+(MagickRealType) ScaleCharToQuantum(2)))
1499 v+=ScaleCharToQuantum(1);
1500 *q=(Quantum) v;
1501 p++;
1502 q++;
1503 r++;
1504 }
1505 else
cristybb503372010-05-27 20:51:26 +00001506 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001507 {
1508 v=(MagickRealType) (*p);
1509 if ((MagickRealType) *r <= (v-(MagickRealType) ScaleCharToQuantum(2)))
cristybb503372010-05-27 20:51:26 +00001510 v-=(ssize_t) ScaleCharToQuantum(1);
cristy3ed852e2009-09-05 21:47:34 +00001511 *q=(Quantum) v;
1512 p++;
1513 q++;
1514 r++;
1515 }
1516 p++;
1517 q++;
1518 r++;
1519 }
1520 p=f+(columns+2);
1521 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001522 r=q+(y_offset*((ssize_t) columns+2)+x_offset);
1523 s=q-(y_offset*((ssize_t) columns+2)+x_offset);
1524 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001525 {
1526 p++;
1527 q++;
1528 r++;
1529 s++;
1530 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001531 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001532 {
1533 v=(MagickRealType) (*q);
1534 if (((MagickRealType) *s >=
1535 (v+(MagickRealType) ScaleCharToQuantum(2))) &&
1536 ((MagickRealType) *r > v))
1537 v+=ScaleCharToQuantum(1);
1538 *p=(Quantum) v;
1539 p++;
1540 q++;
1541 r++;
1542 s++;
1543 }
1544 else
cristybb503372010-05-27 20:51:26 +00001545 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001546 {
1547 v=(MagickRealType) (*q);
1548 if (((MagickRealType) *s <=
1549 (v-(MagickRealType) ScaleCharToQuantum(2))) &&
1550 ((MagickRealType) *r < v))
1551 v-=(MagickRealType) ScaleCharToQuantum(1);
1552 *p=(Quantum) v;
1553 p++;
1554 q++;
1555 r++;
1556 s++;
1557 }
1558 p++;
1559 q++;
1560 r++;
1561 s++;
1562 }
1563}
1564
1565MagickExport Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1566{
1567#define DespeckleImageTag "Despeckle/Image"
1568
cristy2407fc22009-09-11 00:55:25 +00001569 CacheView
1570 *despeckle_view,
1571 *image_view;
1572
cristy3ed852e2009-09-05 21:47:34 +00001573 Image
1574 *despeckle_image;
1575
cristy3ed852e2009-09-05 21:47:34 +00001576 MagickBooleanType
1577 status;
1578
1579 Quantum
cristy65b9f392011-02-22 14:22:54 +00001580 *restrict buffers,
1581 *restrict pixels;
cristy3ed852e2009-09-05 21:47:34 +00001582
cristya63e4a92011-09-09 00:47:59 +00001583 register ssize_t
1584 i;
1585
cristy3ed852e2009-09-05 21:47:34 +00001586 size_t
cristya63e4a92011-09-09 00:47:59 +00001587 length;
cristy117ff172010-08-15 21:35:32 +00001588
cristybb503372010-05-27 20:51:26 +00001589 static const ssize_t
cristy691a29e2009-09-11 00:44:10 +00001590 X[4] = {0, 1, 1,-1},
1591 Y[4] = {1, 0, 1, 1};
cristy3ed852e2009-09-05 21:47:34 +00001592
cristy3ed852e2009-09-05 21:47:34 +00001593 /*
1594 Allocate despeckled image.
1595 */
1596 assert(image != (const Image *) NULL);
1597 assert(image->signature == MagickSignature);
1598 if (image->debug != MagickFalse)
1599 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1600 assert(exception != (ExceptionInfo *) NULL);
1601 assert(exception->signature == MagickSignature);
cristya63e4a92011-09-09 00:47:59 +00001602 despeckle_image=CloneImage(image,0,0,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00001603 if (despeckle_image == (Image *) NULL)
1604 return((Image *) NULL);
cristya63e4a92011-09-09 00:47:59 +00001605 status=SetImageStorageClass(despeckle_image,DirectClass,exception);
1606 if (status == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001607 {
cristy3ed852e2009-09-05 21:47:34 +00001608 despeckle_image=DestroyImage(despeckle_image);
1609 return((Image *) NULL);
1610 }
1611 /*
1612 Allocate image buffers.
1613 */
1614 length=(size_t) ((image->columns+2)*(image->rows+2));
cristy65b9f392011-02-22 14:22:54 +00001615 pixels=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1616 buffers=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1617 if ((pixels == (Quantum *) NULL) || (buffers == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001618 {
cristy65b9f392011-02-22 14:22:54 +00001619 if (buffers != (Quantum *) NULL)
1620 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1621 if (pixels != (Quantum *) NULL)
1622 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001623 despeckle_image=DestroyImage(despeckle_image);
1624 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1625 }
1626 /*
1627 Reduce speckle in the image.
1628 */
1629 status=MagickTrue;
1630 image_view=AcquireCacheView(image);
1631 despeckle_view=AcquireCacheView(despeckle_image);
cristya63e4a92011-09-09 00:47:59 +00001632 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy3ed852e2009-09-05 21:47:34 +00001633 {
cristya63e4a92011-09-09 00:47:59 +00001634 PixelChannel
1635 channel;
1636
1637 PixelTrait
1638 despeckle_traits,
1639 traits;
1640
cristy3ed852e2009-09-05 21:47:34 +00001641 register Quantum
1642 *buffer,
1643 *pixel;
1644
cristyc1488b52011-02-19 18:54:15 +00001645 register ssize_t
cristya58c3172011-02-19 19:23:11 +00001646 k,
cristyc1488b52011-02-19 18:54:15 +00001647 x;
1648
cristy117ff172010-08-15 21:35:32 +00001649 ssize_t
1650 j,
1651 y;
1652
cristy3ed852e2009-09-05 21:47:34 +00001653 if (status == MagickFalse)
1654 continue;
cristya63e4a92011-09-09 00:47:59 +00001655 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
1656 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
1657 despeckle_traits=GetPixelChannelMapTraits(despeckle_image,channel);
1658 if ((traits == UndefinedPixelTrait) ||
1659 (despeckle_traits == UndefinedPixelTrait))
1660 continue;
1661 if ((despeckle_traits & CopyPixelTrait) != 0)
1662 continue;
cristy65b9f392011-02-22 14:22:54 +00001663 pixel=pixels;
cristy3ed852e2009-09-05 21:47:34 +00001664 (void) ResetMagickMemory(pixel,0,length*sizeof(*pixel));
cristy65b9f392011-02-22 14:22:54 +00001665 buffer=buffers;
cristybb503372010-05-27 20:51:26 +00001666 j=(ssize_t) image->columns+2;
1667 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001668 {
cristy4c08aed2011-07-01 19:47:50 +00001669 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001670 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001671
1672 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001673 if (p == (const Quantum *) NULL)
cristya63e4a92011-09-09 00:47:59 +00001674 {
1675 status=MagickFalse;
1676 continue;
1677 }
cristy3ed852e2009-09-05 21:47:34 +00001678 j++;
cristybb503372010-05-27 20:51:26 +00001679 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001680 {
cristya63e4a92011-09-09 00:47:59 +00001681 pixel[j++]=p[i];
cristyed231572011-07-14 02:18:59 +00001682 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001683 }
1684 j++;
1685 }
cristy3ed852e2009-09-05 21:47:34 +00001686 (void) ResetMagickMemory(buffer,0,length*sizeof(*buffer));
cristya58c3172011-02-19 19:23:11 +00001687 for (k=0; k < 4; k++)
cristy3ed852e2009-09-05 21:47:34 +00001688 {
cristya58c3172011-02-19 19:23:11 +00001689 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,1);
1690 Hull(-X[k],-Y[k],image->columns,image->rows,pixel,buffer,1);
1691 Hull(-X[k],-Y[k],image->columns,image->rows,pixel,buffer,-1);
1692 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,-1);
cristy3ed852e2009-09-05 21:47:34 +00001693 }
cristybb503372010-05-27 20:51:26 +00001694 j=(ssize_t) image->columns+2;
1695 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001696 {
1697 MagickBooleanType
1698 sync;
1699
cristy4c08aed2011-07-01 19:47:50 +00001700 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001701 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001702
1703 q=GetCacheViewAuthenticPixels(despeckle_view,0,y,despeckle_image->columns,
1704 1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001705 if (q == (Quantum *) NULL)
cristya63e4a92011-09-09 00:47:59 +00001706 {
1707 status=MagickFalse;
1708 continue;
1709 }
cristy3ed852e2009-09-05 21:47:34 +00001710 j++;
cristybb503372010-05-27 20:51:26 +00001711 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001712 {
cristy0beccfa2011-09-25 20:47:53 +00001713 SetPixelChannel(despeckle_image,channel,pixel[j++],q);
cristyed231572011-07-14 02:18:59 +00001714 q+=GetPixelChannels(despeckle_image);
cristy3ed852e2009-09-05 21:47:34 +00001715 }
1716 sync=SyncCacheViewAuthenticPixels(despeckle_view,exception);
1717 if (sync == MagickFalse)
cristya63e4a92011-09-09 00:47:59 +00001718 status=MagickFalse;
cristy3ed852e2009-09-05 21:47:34 +00001719 j++;
1720 }
1721 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1722 {
1723 MagickBooleanType
1724 proceed;
1725
cristya58c3172011-02-19 19:23:11 +00001726 proceed=SetImageProgress(image,DespeckleImageTag,(MagickOffsetType) i,
cristya63e4a92011-09-09 00:47:59 +00001727 GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00001728 if (proceed == MagickFalse)
1729 status=MagickFalse;
1730 }
1731 }
1732 despeckle_view=DestroyCacheView(despeckle_view);
1733 image_view=DestroyCacheView(image_view);
cristy65b9f392011-02-22 14:22:54 +00001734 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1735 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001736 despeckle_image->type=image->type;
1737 if (status == MagickFalse)
1738 despeckle_image=DestroyImage(despeckle_image);
1739 return(despeckle_image);
1740}
1741
1742/*
1743%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1744% %
1745% %
1746% %
1747% E d g e I m a g e %
1748% %
1749% %
1750% %
1751%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1752%
1753% EdgeImage() finds edges in an image. Radius defines the radius of the
1754% convolution filter. Use a radius of 0 and EdgeImage() selects a suitable
1755% radius for you.
1756%
1757% The format of the EdgeImage method is:
1758%
1759% Image *EdgeImage(const Image *image,const double radius,
cristy8ae632d2011-09-05 17:29:53 +00001760% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001761%
1762% A description of each parameter follows:
1763%
1764% o image: the image.
1765%
1766% o radius: the radius of the pixel neighborhood.
1767%
cristy8ae632d2011-09-05 17:29:53 +00001768% o sigma: the standard deviation of the Gaussian, in pixels.
1769%
cristy3ed852e2009-09-05 21:47:34 +00001770% o exception: return any errors or warnings in this structure.
1771%
1772*/
1773MagickExport Image *EdgeImage(const Image *image,const double radius,
cristy8ae632d2011-09-05 17:29:53 +00001774 const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001775{
1776 Image
1777 *edge_image;
1778
cristy41cbe682011-07-15 19:12:37 +00001779 KernelInfo
1780 *kernel_info;
cristy3ed852e2009-09-05 21:47:34 +00001781
cristybb503372010-05-27 20:51:26 +00001782 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001783 i;
1784
cristybb503372010-05-27 20:51:26 +00001785 size_t
cristy3ed852e2009-09-05 21:47:34 +00001786 width;
1787
cristy41cbe682011-07-15 19:12:37 +00001788 ssize_t
1789 j,
1790 u,
1791 v;
1792
cristy3ed852e2009-09-05 21:47:34 +00001793 assert(image != (const Image *) NULL);
1794 assert(image->signature == MagickSignature);
1795 if (image->debug != MagickFalse)
1796 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1797 assert(exception != (ExceptionInfo *) NULL);
1798 assert(exception->signature == MagickSignature);
cristy8ae632d2011-09-05 17:29:53 +00001799 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00001800 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00001801 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001802 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00001803 kernel_info->width=width;
1804 kernel_info->height=width;
cristy9f752c02011-09-14 19:44:03 +00001805 kernel_info->values=(double *) AcquireAlignedMemory(kernel_info->width,
cristy41cbe682011-07-15 19:12:37 +00001806 kernel_info->width*sizeof(*kernel_info->values));
1807 if (kernel_info->values == (double *) NULL)
1808 {
1809 kernel_info=DestroyKernelInfo(kernel_info);
1810 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1811 }
1812 j=(ssize_t) kernel_info->width/2;
1813 i=0;
1814 for (v=(-j); v <= j; v++)
1815 {
1816 for (u=(-j); u <= j; u++)
1817 {
1818 kernel_info->values[i]=(-1.0);
1819 i++;
1820 }
1821 }
1822 kernel_info->values[i/2]=(double) (width*width-1.0);
cristy0a922382011-07-16 15:30:34 +00001823 kernel_info->bias=image->bias;
cristy5e6be1e2011-07-16 01:23:39 +00001824 edge_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00001825 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00001826 return(edge_image);
1827}
1828
1829/*
1830%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1831% %
1832% %
1833% %
1834% E m b o s s I m a g e %
1835% %
1836% %
1837% %
1838%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1839%
1840% EmbossImage() returns a grayscale image with a three-dimensional effect.
1841% We convolve the image with a Gaussian operator of the given radius and
1842% standard deviation (sigma). For reasonable results, radius should be
1843% larger than sigma. Use a radius of 0 and Emboss() selects a suitable
1844% radius for you.
1845%
1846% The format of the EmbossImage method is:
1847%
1848% Image *EmbossImage(const Image *image,const double radius,
1849% const double sigma,ExceptionInfo *exception)
1850%
1851% A description of each parameter follows:
1852%
1853% o image: the image.
1854%
1855% o radius: the radius of the pixel neighborhood.
1856%
1857% o sigma: the standard deviation of the Gaussian, in pixels.
1858%
1859% o exception: return any errors or warnings in this structure.
1860%
1861*/
1862MagickExport Image *EmbossImage(const Image *image,const double radius,
1863 const double sigma,ExceptionInfo *exception)
1864{
cristy3ed852e2009-09-05 21:47:34 +00001865 Image
1866 *emboss_image;
1867
cristy41cbe682011-07-15 19:12:37 +00001868 KernelInfo
1869 *kernel_info;
1870
cristybb503372010-05-27 20:51:26 +00001871 register ssize_t
cristy47e00502009-12-17 19:19:57 +00001872 i;
1873
cristybb503372010-05-27 20:51:26 +00001874 size_t
cristy3ed852e2009-09-05 21:47:34 +00001875 width;
1876
cristy117ff172010-08-15 21:35:32 +00001877 ssize_t
1878 j,
1879 k,
1880 u,
1881 v;
1882
cristy41cbe682011-07-15 19:12:37 +00001883 assert(image != (const Image *) NULL);
cristy3ed852e2009-09-05 21:47:34 +00001884 assert(image->signature == MagickSignature);
1885 if (image->debug != MagickFalse)
1886 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1887 assert(exception != (ExceptionInfo *) NULL);
1888 assert(exception->signature == MagickSignature);
1889 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00001890 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00001891 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001892 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00001893 kernel_info->width=width;
1894 kernel_info->height=width;
cristy9f752c02011-09-14 19:44:03 +00001895 kernel_info->values=(double *) AcquireAlignedMemory(kernel_info->width,
cristy41cbe682011-07-15 19:12:37 +00001896 kernel_info->width*sizeof(*kernel_info->values));
1897 if (kernel_info->values == (double *) NULL)
1898 {
1899 kernel_info=DestroyKernelInfo(kernel_info);
1900 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1901 }
1902 j=(ssize_t) kernel_info->width/2;
cristy47e00502009-12-17 19:19:57 +00001903 k=j;
1904 i=0;
1905 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00001906 {
cristy47e00502009-12-17 19:19:57 +00001907 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00001908 {
cristy41cbe682011-07-15 19:12:37 +00001909 kernel_info->values[i]=(double) (((u < 0) || (v < 0) ? -8.0 : 8.0)*
cristy47e00502009-12-17 19:19:57 +00001910 exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
cristy4205a3c2010-09-12 20:19:59 +00001911 (2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +00001912 if (u != k)
cristy41cbe682011-07-15 19:12:37 +00001913 kernel_info->values[i]=0.0;
cristy3ed852e2009-09-05 21:47:34 +00001914 i++;
1915 }
cristy47e00502009-12-17 19:19:57 +00001916 k--;
cristy3ed852e2009-09-05 21:47:34 +00001917 }
cristy0a922382011-07-16 15:30:34 +00001918 kernel_info->bias=image->bias;
cristy5e6be1e2011-07-16 01:23:39 +00001919 emboss_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00001920 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00001921 if (emboss_image != (Image *) NULL)
cristy6d8c3d72011-08-22 01:20:01 +00001922 (void) EqualizeImage(emboss_image,exception);
cristy3ed852e2009-09-05 21:47:34 +00001923 return(emboss_image);
1924}
1925
1926/*
1927%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1928% %
1929% %
1930% %
1931% G a u s s i a n B l u r I m a g e %
1932% %
1933% %
1934% %
1935%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1936%
1937% GaussianBlurImage() blurs an image. We convolve the image with a
1938% Gaussian operator of the given radius and standard deviation (sigma).
1939% For reasonable results, the radius should be larger than sigma. Use a
1940% radius of 0 and GaussianBlurImage() selects a suitable radius for you
1941%
1942% The format of the GaussianBlurImage method is:
1943%
1944% Image *GaussianBlurImage(const Image *image,onst double radius,
cristy05c0c9a2011-09-05 23:16:13 +00001945% const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001946%
1947% A description of each parameter follows:
1948%
1949% o image: the image.
1950%
cristy3ed852e2009-09-05 21:47:34 +00001951% o radius: the radius of the Gaussian, in pixels, not counting the center
1952% pixel.
1953%
1954% o sigma: the standard deviation of the Gaussian, in pixels.
1955%
cristy05c0c9a2011-09-05 23:16:13 +00001956% o bias: the bias.
1957%
cristy3ed852e2009-09-05 21:47:34 +00001958% o exception: return any errors or warnings in this structure.
1959%
1960*/
cristy41cbe682011-07-15 19:12:37 +00001961MagickExport Image *GaussianBlurImage(const Image *image,const double radius,
cristy05c0c9a2011-09-05 23:16:13 +00001962 const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001963{
cristy3ed852e2009-09-05 21:47:34 +00001964 Image
1965 *blur_image;
1966
cristy41cbe682011-07-15 19:12:37 +00001967 KernelInfo
1968 *kernel_info;
1969
cristybb503372010-05-27 20:51:26 +00001970 register ssize_t
cristy47e00502009-12-17 19:19:57 +00001971 i;
1972
cristybb503372010-05-27 20:51:26 +00001973 size_t
cristy3ed852e2009-09-05 21:47:34 +00001974 width;
1975
cristy117ff172010-08-15 21:35:32 +00001976 ssize_t
1977 j,
1978 u,
1979 v;
1980
cristy3ed852e2009-09-05 21:47:34 +00001981 assert(image != (const Image *) NULL);
1982 assert(image->signature == MagickSignature);
1983 if (image->debug != MagickFalse)
1984 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1985 assert(exception != (ExceptionInfo *) NULL);
1986 assert(exception->signature == MagickSignature);
1987 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00001988 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00001989 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001990 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00001991 (void) ResetMagickMemory(kernel_info,0,sizeof(*kernel_info));
1992 kernel_info->width=width;
1993 kernel_info->height=width;
cristy05c0c9a2011-09-05 23:16:13 +00001994 kernel_info->bias=bias;
cristy41cbe682011-07-15 19:12:37 +00001995 kernel_info->signature=MagickSignature;
cristy9f752c02011-09-14 19:44:03 +00001996 kernel_info->values=(double *) AcquireAlignedMemory(kernel_info->width,
cristy41cbe682011-07-15 19:12:37 +00001997 kernel_info->width*sizeof(*kernel_info->values));
1998 if (kernel_info->values == (double *) NULL)
1999 {
2000 kernel_info=DestroyKernelInfo(kernel_info);
2001 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2002 }
2003 j=(ssize_t) kernel_info->width/2;
cristy3ed852e2009-09-05 21:47:34 +00002004 i=0;
cristy47e00502009-12-17 19:19:57 +00002005 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002006 {
cristy47e00502009-12-17 19:19:57 +00002007 for (u=(-j); u <= j; u++)
cristy41cbe682011-07-15 19:12:37 +00002008 {
2009 kernel_info->values[i]=(double) (exp(-((double) u*u+v*v)/(2.0*
2010 MagickSigma*MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
2011 i++;
2012 }
cristy3ed852e2009-09-05 21:47:34 +00002013 }
cristy5e6be1e2011-07-16 01:23:39 +00002014 blur_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00002015 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00002016 return(blur_image);
2017}
2018
2019/*
2020%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2021% %
2022% %
2023% %
cristy3ed852e2009-09-05 21:47:34 +00002024% M o t i o n B l u r I m a g e %
2025% %
2026% %
2027% %
2028%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2029%
2030% MotionBlurImage() simulates motion blur. We convolve the image with a
2031% Gaussian operator of the given radius and standard deviation (sigma).
2032% For reasonable results, radius should be larger than sigma. Use a
2033% radius of 0 and MotionBlurImage() selects a suitable radius for you.
2034% Angle gives the angle of the blurring motion.
2035%
2036% Andrew Protano contributed this effect.
2037%
2038% The format of the MotionBlurImage method is:
2039%
2040% Image *MotionBlurImage(const Image *image,const double radius,
cristyf7ef0252011-09-09 14:50:06 +00002041% const double sigma,const double angle,const double bias,
2042% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002043%
2044% A description of each parameter follows:
2045%
2046% o image: the image.
2047%
cristy3ed852e2009-09-05 21:47:34 +00002048% o radius: the radius of the Gaussian, in pixels, not counting
2049% the center pixel.
2050%
2051% o sigma: the standard deviation of the Gaussian, in pixels.
2052%
cristycee97112010-05-28 00:44:52 +00002053% o angle: Apply the effect along this angle.
cristy3ed852e2009-09-05 21:47:34 +00002054%
cristyf7ef0252011-09-09 14:50:06 +00002055% o bias: the bias.
2056%
cristy3ed852e2009-09-05 21:47:34 +00002057% o exception: return any errors or warnings in this structure.
2058%
2059*/
2060
cristybb503372010-05-27 20:51:26 +00002061static double *GetMotionBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +00002062{
cristy3ed852e2009-09-05 21:47:34 +00002063 double
cristy47e00502009-12-17 19:19:57 +00002064 *kernel,
cristy3ed852e2009-09-05 21:47:34 +00002065 normalize;
2066
cristybb503372010-05-27 20:51:26 +00002067 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002068 i;
2069
2070 /*
cristy47e00502009-12-17 19:19:57 +00002071 Generate a 1-D convolution kernel.
cristy3ed852e2009-09-05 21:47:34 +00002072 */
2073 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
2074 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
2075 if (kernel == (double *) NULL)
2076 return(kernel);
cristy3ed852e2009-09-05 21:47:34 +00002077 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00002078 for (i=0; i < (ssize_t) width; i++)
cristy47e00502009-12-17 19:19:57 +00002079 {
cristy4205a3c2010-09-12 20:19:59 +00002080 kernel[i]=(double) (exp((-((double) i*i)/(double) (2.0*MagickSigma*
2081 MagickSigma)))/(MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00002082 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +00002083 }
cristybb503372010-05-27 20:51:26 +00002084 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002085 kernel[i]/=normalize;
2086 return(kernel);
2087}
2088
cristya63e4a92011-09-09 00:47:59 +00002089MagickExport Image *MotionBlurImage(const Image *image,const double radius,
cristyf7ef0252011-09-09 14:50:06 +00002090 const double sigma,const double angle,const double bias,
2091 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002092{
cristyc4c8d132010-01-07 01:58:38 +00002093 CacheView
2094 *blur_view,
2095 *image_view;
2096
cristy3ed852e2009-09-05 21:47:34 +00002097 double
2098 *kernel;
2099
2100 Image
2101 *blur_image;
2102
cristy3ed852e2009-09-05 21:47:34 +00002103 MagickBooleanType
2104 status;
2105
cristybb503372010-05-27 20:51:26 +00002106 MagickOffsetType
2107 progress;
2108
cristy3ed852e2009-09-05 21:47:34 +00002109 OffsetInfo
2110 *offset;
2111
2112 PointInfo
2113 point;
2114
cristybb503372010-05-27 20:51:26 +00002115 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002116 i;
2117
cristybb503372010-05-27 20:51:26 +00002118 size_t
cristy3ed852e2009-09-05 21:47:34 +00002119 width;
2120
cristybb503372010-05-27 20:51:26 +00002121 ssize_t
2122 y;
2123
cristy3ed852e2009-09-05 21:47:34 +00002124 assert(image != (Image *) NULL);
2125 assert(image->signature == MagickSignature);
2126 if (image->debug != MagickFalse)
2127 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2128 assert(exception != (ExceptionInfo *) NULL);
2129 width=GetOptimalKernelWidth1D(radius,sigma);
2130 kernel=GetMotionBlurKernel(width,sigma);
2131 if (kernel == (double *) NULL)
2132 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2133 offset=(OffsetInfo *) AcquireQuantumMemory(width,sizeof(*offset));
2134 if (offset == (OffsetInfo *) NULL)
2135 {
2136 kernel=(double *) RelinquishMagickMemory(kernel);
2137 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2138 }
cristy1e7aa312011-09-10 20:01:36 +00002139 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00002140 if (blur_image == (Image *) NULL)
2141 {
2142 kernel=(double *) RelinquishMagickMemory(kernel);
2143 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2144 return((Image *) NULL);
2145 }
cristy574cc262011-08-05 01:23:58 +00002146 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00002147 {
2148 kernel=(double *) RelinquishMagickMemory(kernel);
2149 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
cristy3ed852e2009-09-05 21:47:34 +00002150 blur_image=DestroyImage(blur_image);
2151 return((Image *) NULL);
2152 }
2153 point.x=(double) width*sin(DegreesToRadians(angle));
2154 point.y=(double) width*cos(DegreesToRadians(angle));
cristybb503372010-05-27 20:51:26 +00002155 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002156 {
cristybb503372010-05-27 20:51:26 +00002157 offset[i].x=(ssize_t) ceil((double) (i*point.y)/hypot(point.x,point.y)-0.5);
2158 offset[i].y=(ssize_t) ceil((double) (i*point.x)/hypot(point.x,point.y)-0.5);
cristy3ed852e2009-09-05 21:47:34 +00002159 }
2160 /*
2161 Motion blur image.
2162 */
2163 status=MagickTrue;
2164 progress=0;
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 {
cristyf7ef0252011-09-09 14:50:06 +00002172 register const Quantum
2173 *restrict p;
2174
cristy4c08aed2011-07-01 19:47:50 +00002175 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002176 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002177
cristy117ff172010-08-15 21:35:32 +00002178 register ssize_t
2179 x;
2180
cristy3ed852e2009-09-05 21:47:34 +00002181 if (status == MagickFalse)
2182 continue;
cristyf7ef0252011-09-09 14:50:06 +00002183 p=GetCacheViewVirtualPixels(blur_view,0,y,image->columns,1,exception);
cristy3ed852e2009-09-05 21:47:34 +00002184 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
2185 exception);
cristyf7ef0252011-09-09 14:50:06 +00002186 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00002187 {
2188 status=MagickFalse;
2189 continue;
2190 }
cristybb503372010-05-27 20:51:26 +00002191 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002192 {
cristybb503372010-05-27 20:51:26 +00002193 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002194 i;
2195
cristyf7ef0252011-09-09 14:50:06 +00002196 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2197 {
2198 MagickRealType
2199 alpha,
2200 gamma,
2201 pixel;
cristy3ed852e2009-09-05 21:47:34 +00002202
cristyf7ef0252011-09-09 14:50:06 +00002203 PixelChannel
2204 channel;
2205
2206 PixelTrait
2207 blur_traits,
2208 traits;
2209
2210 register const Quantum
2211 *restrict r;
2212
2213 register double
2214 *restrict k;
2215
2216 register ssize_t
2217 j;
2218
2219 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
2220 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
2221 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
2222 if ((traits == UndefinedPixelTrait) ||
2223 (blur_traits == UndefinedPixelTrait))
2224 continue;
2225 if ((blur_traits & CopyPixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002226 {
cristy0beccfa2011-09-25 20:47:53 +00002227 SetPixelChannel(blur_image,channel,p[i],q);
cristyf7ef0252011-09-09 14:50:06 +00002228 continue;
cristy3ed852e2009-09-05 21:47:34 +00002229 }
cristyf7ef0252011-09-09 14:50:06 +00002230 k=kernel;
2231 pixel=bias;
2232 if ((blur_traits & BlendPixelTrait) == 0)
2233 {
2234 for (j=0; j < (ssize_t) width; j++)
2235 {
2236 r=GetCacheViewVirtualPixels(image_view,x+offset[j].x,y+
2237 offset[j].y,1,1,exception);
2238 if (r == (const Quantum *) NULL)
2239 {
2240 status=MagickFalse;
2241 continue;
2242 }
2243 pixel+=(*k)*r[i];
2244 k++;
2245 }
cristy0beccfa2011-09-25 20:47:53 +00002246 SetPixelChannel(blur_image,channel,ClampToQuantum(pixel),q);
cristyf7ef0252011-09-09 14:50:06 +00002247 continue;
2248 }
2249 alpha=0.0;
2250 gamma=0.0;
2251 for (j=0; j < (ssize_t) width; j++)
2252 {
2253 r=GetCacheViewVirtualPixels(image_view,x+offset[j].x,y+offset[j].y,1,
2254 1,exception);
2255 if (r == (const Quantum *) NULL)
2256 {
2257 status=MagickFalse;
2258 continue;
2259 }
2260 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,r));
2261 pixel+=(*k)*alpha*r[i];
2262 gamma+=(*k)*alpha;
2263 k++;
cristy3ed852e2009-09-05 21:47:34 +00002264 }
cristyf7ef0252011-09-09 14:50:06 +00002265 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +00002266 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristyf7ef0252011-09-09 14:50:06 +00002267 }
2268 p+=GetPixelChannels(image);
cristyed231572011-07-14 02:18:59 +00002269 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00002270 }
2271 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
2272 status=MagickFalse;
2273 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2274 {
2275 MagickBooleanType
2276 proceed;
2277
cristyb557a152011-02-22 12:14:30 +00002278#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00002279 #pragma omp critical (MagickCore_MotionBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00002280#endif
2281 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
2282 if (proceed == MagickFalse)
2283 status=MagickFalse;
2284 }
2285 }
2286 blur_view=DestroyCacheView(blur_view);
2287 image_view=DestroyCacheView(image_view);
2288 kernel=(double *) RelinquishMagickMemory(kernel);
2289 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2290 if (status == MagickFalse)
2291 blur_image=DestroyImage(blur_image);
2292 return(blur_image);
2293}
2294
2295/*
2296%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2297% %
2298% %
2299% %
2300% P r e v i e w I m a g e %
2301% %
2302% %
2303% %
2304%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2305%
2306% PreviewImage() tiles 9 thumbnails of the specified image with an image
2307% processing operation applied with varying parameters. This may be helpful
2308% pin-pointing an appropriate parameter for a particular image processing
2309% operation.
2310%
2311% The format of the PreviewImages method is:
2312%
2313% Image *PreviewImages(const Image *image,const PreviewType preview,
2314% ExceptionInfo *exception)
2315%
2316% A description of each parameter follows:
2317%
2318% o image: the image.
2319%
2320% o preview: the image processing operation.
2321%
2322% o exception: return any errors or warnings in this structure.
2323%
2324*/
2325MagickExport Image *PreviewImage(const Image *image,const PreviewType preview,
2326 ExceptionInfo *exception)
2327{
2328#define NumberTiles 9
2329#define PreviewImageTag "Preview/Image"
2330#define DefaultPreviewGeometry "204x204+10+10"
2331
2332 char
2333 factor[MaxTextExtent],
2334 label[MaxTextExtent];
2335
2336 double
2337 degrees,
2338 gamma,
2339 percentage,
2340 radius,
2341 sigma,
2342 threshold;
2343
2344 Image
2345 *images,
2346 *montage_image,
2347 *preview_image,
2348 *thumbnail;
2349
2350 ImageInfo
2351 *preview_info;
2352
cristy3ed852e2009-09-05 21:47:34 +00002353 MagickBooleanType
2354 proceed;
2355
2356 MontageInfo
2357 *montage_info;
2358
2359 QuantizeInfo
2360 quantize_info;
2361
2362 RectangleInfo
2363 geometry;
2364
cristybb503372010-05-27 20:51:26 +00002365 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002366 i,
2367 x;
2368
cristybb503372010-05-27 20:51:26 +00002369 size_t
cristy3ed852e2009-09-05 21:47:34 +00002370 colors;
2371
cristy117ff172010-08-15 21:35:32 +00002372 ssize_t
2373 y;
2374
cristy3ed852e2009-09-05 21:47:34 +00002375 /*
2376 Open output image file.
2377 */
2378 assert(image != (Image *) NULL);
2379 assert(image->signature == MagickSignature);
2380 if (image->debug != MagickFalse)
2381 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2382 colors=2;
2383 degrees=0.0;
2384 gamma=(-0.2f);
2385 preview_info=AcquireImageInfo();
2386 SetGeometry(image,&geometry);
2387 (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y,
2388 &geometry.width,&geometry.height);
2389 images=NewImageList();
2390 percentage=12.5;
2391 GetQuantizeInfo(&quantize_info);
2392 radius=0.0;
2393 sigma=1.0;
2394 threshold=0.0;
2395 x=0;
2396 y=0;
2397 for (i=0; i < NumberTiles; i++)
2398 {
2399 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
2400 if (thumbnail == (Image *) NULL)
2401 break;
2402 (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL,
2403 (void *) NULL);
2404 (void) SetImageProperty(thumbnail,"label",DefaultTileLabel);
2405 if (i == (NumberTiles/2))
2406 {
cristy9950d572011-10-01 18:22:35 +00002407 (void) QueryColorCompliance("#dfdfdf",AllCompliance,
2408 &thumbnail->matte_color,exception);
cristy3ed852e2009-09-05 21:47:34 +00002409 AppendImageToList(&images,thumbnail);
2410 continue;
2411 }
2412 switch (preview)
2413 {
2414 case RotatePreview:
2415 {
2416 degrees+=45.0;
2417 preview_image=RotateImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002418 (void) FormatLocaleString(label,MaxTextExtent,"rotate %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002419 break;
2420 }
2421 case ShearPreview:
2422 {
2423 degrees+=5.0;
2424 preview_image=ShearImage(thumbnail,degrees,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002425 (void) FormatLocaleString(label,MaxTextExtent,"shear %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002426 degrees,2.0*degrees);
2427 break;
2428 }
2429 case RollPreview:
2430 {
cristybb503372010-05-27 20:51:26 +00002431 x=(ssize_t) ((i+1)*thumbnail->columns)/NumberTiles;
2432 y=(ssize_t) ((i+1)*thumbnail->rows)/NumberTiles;
cristy3ed852e2009-09-05 21:47:34 +00002433 preview_image=RollImage(thumbnail,x,y,exception);
cristyb51dff52011-05-19 16:55:47 +00002434 (void) FormatLocaleString(label,MaxTextExtent,"roll %+.20gx%+.20g",
cristye8c25f92010-06-03 00:53:06 +00002435 (double) x,(double) y);
cristy3ed852e2009-09-05 21:47:34 +00002436 break;
2437 }
2438 case HuePreview:
2439 {
2440 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2441 if (preview_image == (Image *) NULL)
2442 break;
cristyb51dff52011-05-19 16:55:47 +00002443 (void) FormatLocaleString(factor,MaxTextExtent,"100,100,%g",
cristy3ed852e2009-09-05 21:47:34 +00002444 2.0*percentage);
cristy33bd5152011-08-24 01:42:24 +00002445 (void) ModulateImage(preview_image,factor,exception);
cristyb51dff52011-05-19 16:55:47 +00002446 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002447 break;
2448 }
2449 case SaturationPreview:
2450 {
2451 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2452 if (preview_image == (Image *) NULL)
2453 break;
cristyb51dff52011-05-19 16:55:47 +00002454 (void) FormatLocaleString(factor,MaxTextExtent,"100,%g",
cristy8cd5b312010-01-07 01:10:24 +00002455 2.0*percentage);
cristy33bd5152011-08-24 01:42:24 +00002456 (void) ModulateImage(preview_image,factor,exception);
cristyb51dff52011-05-19 16:55:47 +00002457 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002458 break;
2459 }
2460 case BrightnessPreview:
2461 {
2462 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2463 if (preview_image == (Image *) NULL)
2464 break;
cristyb51dff52011-05-19 16:55:47 +00002465 (void) FormatLocaleString(factor,MaxTextExtent,"%g",2.0*percentage);
cristy33bd5152011-08-24 01:42:24 +00002466 (void) ModulateImage(preview_image,factor,exception);
cristyb51dff52011-05-19 16:55:47 +00002467 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002468 break;
2469 }
2470 case GammaPreview:
2471 default:
2472 {
2473 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2474 if (preview_image == (Image *) NULL)
2475 break;
2476 gamma+=0.4f;
cristyb3e7c6c2011-07-24 01:43:55 +00002477 (void) GammaImage(preview_image,gamma,exception);
cristyb51dff52011-05-19 16:55:47 +00002478 (void) FormatLocaleString(label,MaxTextExtent,"gamma %g",gamma);
cristy3ed852e2009-09-05 21:47:34 +00002479 break;
2480 }
2481 case SpiffPreview:
2482 {
2483 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2484 if (preview_image != (Image *) NULL)
2485 for (x=0; x < i; x++)
cristye23ec9d2011-08-16 18:15:40 +00002486 (void) ContrastImage(preview_image,MagickTrue,exception);
cristyb51dff52011-05-19 16:55:47 +00002487 (void) FormatLocaleString(label,MaxTextExtent,"contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002488 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002489 break;
2490 }
2491 case DullPreview:
2492 {
2493 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2494 if (preview_image == (Image *) NULL)
2495 break;
2496 for (x=0; x < i; x++)
cristye23ec9d2011-08-16 18:15:40 +00002497 (void) ContrastImage(preview_image,MagickFalse,exception);
cristyb51dff52011-05-19 16:55:47 +00002498 (void) FormatLocaleString(label,MaxTextExtent,"+contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002499 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002500 break;
2501 }
2502 case GrayscalePreview:
2503 {
2504 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2505 if (preview_image == (Image *) NULL)
2506 break;
2507 colors<<=1;
2508 quantize_info.number_colors=colors;
2509 quantize_info.colorspace=GRAYColorspace;
cristy018f07f2011-09-04 21:15:19 +00002510 (void) QuantizeImage(&quantize_info,preview_image,exception);
cristyb51dff52011-05-19 16:55:47 +00002511 (void) FormatLocaleString(label,MaxTextExtent,
cristye8c25f92010-06-03 00:53:06 +00002512 "-colorspace gray -colors %.20g",(double) colors);
cristy3ed852e2009-09-05 21:47:34 +00002513 break;
2514 }
2515 case QuantizePreview:
2516 {
2517 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2518 if (preview_image == (Image *) NULL)
2519 break;
2520 colors<<=1;
2521 quantize_info.number_colors=colors;
cristy018f07f2011-09-04 21:15:19 +00002522 (void) QuantizeImage(&quantize_info,preview_image,exception);
cristyb51dff52011-05-19 16:55:47 +00002523 (void) FormatLocaleString(label,MaxTextExtent,"colors %.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00002524 colors);
cristy3ed852e2009-09-05 21:47:34 +00002525 break;
2526 }
2527 case DespecklePreview:
2528 {
2529 for (x=0; x < (i-1); x++)
2530 {
2531 preview_image=DespeckleImage(thumbnail,exception);
2532 if (preview_image == (Image *) NULL)
2533 break;
2534 thumbnail=DestroyImage(thumbnail);
2535 thumbnail=preview_image;
2536 }
2537 preview_image=DespeckleImage(thumbnail,exception);
2538 if (preview_image == (Image *) NULL)
2539 break;
cristyb51dff52011-05-19 16:55:47 +00002540 (void) FormatLocaleString(label,MaxTextExtent,"despeckle (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002541 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002542 break;
2543 }
2544 case ReduceNoisePreview:
2545 {
cristy95c38342011-03-18 22:39:51 +00002546 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) radius,
2547 (size_t) radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002548 (void) FormatLocaleString(label,MaxTextExtent,"noise %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002549 break;
2550 }
2551 case AddNoisePreview:
2552 {
2553 switch ((int) i)
2554 {
2555 case 0:
2556 {
2557 (void) CopyMagickString(factor,"uniform",MaxTextExtent);
2558 break;
2559 }
2560 case 1:
2561 {
2562 (void) CopyMagickString(factor,"gaussian",MaxTextExtent);
2563 break;
2564 }
2565 case 2:
2566 {
2567 (void) CopyMagickString(factor,"multiplicative",MaxTextExtent);
2568 break;
2569 }
2570 case 3:
2571 {
2572 (void) CopyMagickString(factor,"impulse",MaxTextExtent);
2573 break;
2574 }
2575 case 4:
2576 {
2577 (void) CopyMagickString(factor,"laplacian",MaxTextExtent);
2578 break;
2579 }
2580 case 5:
2581 {
2582 (void) CopyMagickString(factor,"Poisson",MaxTextExtent);
2583 break;
2584 }
2585 default:
2586 {
2587 (void) CopyMagickString(thumbnail->magick,"NULL",MaxTextExtent);
2588 break;
2589 }
2590 }
cristyd76c51e2011-03-26 00:21:26 +00002591 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) i,
2592 (size_t) i,exception);
cristyb51dff52011-05-19 16:55:47 +00002593 (void) FormatLocaleString(label,MaxTextExtent,"+noise %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002594 break;
2595 }
2596 case SharpenPreview:
2597 {
cristy05c0c9a2011-09-05 23:16:13 +00002598 preview_image=SharpenImage(thumbnail,radius,sigma,image->bias,
2599 exception);
cristyb51dff52011-05-19 16:55:47 +00002600 (void) FormatLocaleString(label,MaxTextExtent,"sharpen %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002601 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002602 break;
2603 }
2604 case BlurPreview:
2605 {
cristy05c0c9a2011-09-05 23:16:13 +00002606 preview_image=BlurImage(thumbnail,radius,sigma,image->bias,exception);
cristyb51dff52011-05-19 16:55:47 +00002607 (void) FormatLocaleString(label,MaxTextExtent,"blur %gx%g",radius,
cristy3ed852e2009-09-05 21:47:34 +00002608 sigma);
2609 break;
2610 }
2611 case ThresholdPreview:
2612 {
2613 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2614 if (preview_image == (Image *) NULL)
2615 break;
2616 (void) BilevelImage(thumbnail,
2617 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
cristyb51dff52011-05-19 16:55:47 +00002618 (void) FormatLocaleString(label,MaxTextExtent,"threshold %g",
cristy3ed852e2009-09-05 21:47:34 +00002619 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
2620 break;
2621 }
2622 case EdgeDetectPreview:
2623 {
cristy8ae632d2011-09-05 17:29:53 +00002624 preview_image=EdgeImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00002625 (void) FormatLocaleString(label,MaxTextExtent,"edge %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002626 break;
2627 }
2628 case SpreadPreview:
2629 {
cristy5c4e2582011-09-11 19:21:03 +00002630 preview_image=SpreadImage(thumbnail,radius,thumbnail->interpolate,
2631 exception);
cristyb51dff52011-05-19 16:55:47 +00002632 (void) FormatLocaleString(label,MaxTextExtent,"spread %g",
cristy8cd5b312010-01-07 01:10:24 +00002633 radius+0.5);
cristy3ed852e2009-09-05 21:47:34 +00002634 break;
2635 }
2636 case SolarizePreview:
2637 {
2638 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2639 if (preview_image == (Image *) NULL)
2640 break;
2641 (void) SolarizeImage(preview_image,(double) QuantumRange*
cristy5cbc0162011-08-29 00:36:28 +00002642 percentage/100.0,exception);
cristyb51dff52011-05-19 16:55:47 +00002643 (void) FormatLocaleString(label,MaxTextExtent,"solarize %g",
cristy3ed852e2009-09-05 21:47:34 +00002644 (QuantumRange*percentage)/100.0);
2645 break;
2646 }
2647 case ShadePreview:
2648 {
2649 degrees+=10.0;
2650 preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees,
2651 exception);
cristyb51dff52011-05-19 16:55:47 +00002652 (void) FormatLocaleString(label,MaxTextExtent,"shade %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002653 degrees,degrees);
cristy3ed852e2009-09-05 21:47:34 +00002654 break;
2655 }
2656 case RaisePreview:
2657 {
2658 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2659 if (preview_image == (Image *) NULL)
2660 break;
cristybb503372010-05-27 20:51:26 +00002661 geometry.width=(size_t) (2*i+2);
2662 geometry.height=(size_t) (2*i+2);
cristy3ed852e2009-09-05 21:47:34 +00002663 geometry.x=i/2;
2664 geometry.y=i/2;
cristy6170ac32011-08-28 14:15:37 +00002665 (void) RaiseImage(preview_image,&geometry,MagickTrue,exception);
cristyb51dff52011-05-19 16:55:47 +00002666 (void) FormatLocaleString(label,MaxTextExtent,
cristy6d8abba2010-06-03 01:10:47 +00002667 "raise %.20gx%.20g%+.20g%+.20g",(double) geometry.width,(double)
cristye8c25f92010-06-03 00:53:06 +00002668 geometry.height,(double) geometry.x,(double) geometry.y);
cristy3ed852e2009-09-05 21:47:34 +00002669 break;
2670 }
2671 case SegmentPreview:
2672 {
2673 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2674 if (preview_image == (Image *) NULL)
2675 break;
2676 threshold+=0.4f;
2677 (void) SegmentImage(preview_image,RGBColorspace,MagickFalse,threshold,
cristy018f07f2011-09-04 21:15:19 +00002678 threshold,exception);
cristyb51dff52011-05-19 16:55:47 +00002679 (void) FormatLocaleString(label,MaxTextExtent,"segment %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002680 threshold,threshold);
2681 break;
2682 }
2683 case SwirlPreview:
2684 {
cristy76f512e2011-09-12 01:26:56 +00002685 preview_image=SwirlImage(thumbnail,degrees,image->interpolate,
2686 exception);
cristyb51dff52011-05-19 16:55:47 +00002687 (void) FormatLocaleString(label,MaxTextExtent,"swirl %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002688 degrees+=45.0;
2689 break;
2690 }
2691 case ImplodePreview:
2692 {
2693 degrees+=0.1f;
cristy76f512e2011-09-12 01:26:56 +00002694 preview_image=ImplodeImage(thumbnail,degrees,image->interpolate,
2695 exception);
cristyb51dff52011-05-19 16:55:47 +00002696 (void) FormatLocaleString(label,MaxTextExtent,"implode %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002697 break;
2698 }
2699 case WavePreview:
2700 {
2701 degrees+=5.0f;
cristy5c4e2582011-09-11 19:21:03 +00002702 preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,
2703 image->interpolate,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,
cristy6435bd92011-09-10 02:10:07 +00002843% const double blur,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%
cristy6435bd92011-09-10 02:10:07 +00002851% o blur: the blur.
2852%
cristy3ed852e2009-09-05 21:47:34 +00002853% o exception: return any errors or warnings in this structure.
2854%
2855*/
cristyf4ad9df2011-07-08 16:49:03 +00002856MagickExport Image *RadialBlurImage(const Image *image,
cristy6435bd92011-09-10 02:10:07 +00002857 const double angle,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002858{
cristyc4c8d132010-01-07 01:58:38 +00002859 CacheView
2860 *blur_view,
2861 *image_view;
2862
cristy3ed852e2009-09-05 21:47:34 +00002863 Image
2864 *blur_image;
2865
cristy3ed852e2009-09-05 21:47:34 +00002866 MagickBooleanType
2867 status;
2868
cristybb503372010-05-27 20:51:26 +00002869 MagickOffsetType
2870 progress;
2871
cristy3ed852e2009-09-05 21:47:34 +00002872 MagickRealType
2873 blur_radius,
2874 *cos_theta,
2875 offset,
2876 *sin_theta,
2877 theta;
2878
2879 PointInfo
2880 blur_center;
2881
cristybb503372010-05-27 20:51:26 +00002882 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002883 i;
2884
cristybb503372010-05-27 20:51:26 +00002885 size_t
cristy3ed852e2009-09-05 21:47:34 +00002886 n;
2887
cristybb503372010-05-27 20:51:26 +00002888 ssize_t
2889 y;
2890
cristy3ed852e2009-09-05 21:47:34 +00002891 /*
2892 Allocate blur image.
2893 */
2894 assert(image != (Image *) NULL);
2895 assert(image->signature == MagickSignature);
2896 if (image->debug != MagickFalse)
2897 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2898 assert(exception != (ExceptionInfo *) NULL);
2899 assert(exception->signature == MagickSignature);
cristy1e7aa312011-09-10 20:01:36 +00002900 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00002901 if (blur_image == (Image *) NULL)
2902 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00002903 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00002904 {
cristy3ed852e2009-09-05 21:47:34 +00002905 blur_image=DestroyImage(blur_image);
2906 return((Image *) NULL);
2907 }
2908 blur_center.x=(double) image->columns/2.0;
2909 blur_center.y=(double) image->rows/2.0;
2910 blur_radius=hypot(blur_center.x,blur_center.y);
cristy117ff172010-08-15 21:35:32 +00002911 n=(size_t) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+2UL);
cristy3ed852e2009-09-05 21:47:34 +00002912 theta=DegreesToRadians(angle)/(MagickRealType) (n-1);
2913 cos_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
2914 sizeof(*cos_theta));
2915 sin_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
2916 sizeof(*sin_theta));
2917 if ((cos_theta == (MagickRealType *) NULL) ||
2918 (sin_theta == (MagickRealType *) NULL))
2919 {
2920 blur_image=DestroyImage(blur_image);
2921 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2922 }
2923 offset=theta*(MagickRealType) (n-1)/2.0;
cristybb503372010-05-27 20:51:26 +00002924 for (i=0; i < (ssize_t) n; i++)
cristy3ed852e2009-09-05 21:47:34 +00002925 {
2926 cos_theta[i]=cos((double) (theta*i-offset));
2927 sin_theta[i]=sin((double) (theta*i-offset));
2928 }
2929 /*
2930 Radial blur image.
2931 */
2932 status=MagickTrue;
2933 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00002934 image_view=AcquireCacheView(image);
2935 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00002936#if defined(MAGICKCORE_OPENMP_SUPPORT)
2937 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002938#endif
cristy1e7aa312011-09-10 20:01:36 +00002939 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002940 {
cristy1e7aa312011-09-10 20:01:36 +00002941 register const Quantum
2942 *restrict p;
2943
cristy4c08aed2011-07-01 19:47:50 +00002944 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002945 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002946
cristy117ff172010-08-15 21:35:32 +00002947 register ssize_t
2948 x;
2949
cristy3ed852e2009-09-05 21:47:34 +00002950 if (status == MagickFalse)
2951 continue;
cristy1e7aa312011-09-10 20:01:36 +00002952 p=GetCacheViewVirtualPixels(blur_view,0,y,image->columns,1,exception);
cristy3ed852e2009-09-05 21:47:34 +00002953 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
2954 exception);
cristy1e7aa312011-09-10 20:01:36 +00002955 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00002956 {
2957 status=MagickFalse;
2958 continue;
2959 }
cristy1e7aa312011-09-10 20:01:36 +00002960 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002961 {
cristy3ed852e2009-09-05 21:47:34 +00002962 MagickRealType
cristy3ed852e2009-09-05 21:47:34 +00002963 radius;
2964
cristy3ed852e2009-09-05 21:47:34 +00002965 PointInfo
2966 center;
2967
cristybb503372010-05-27 20:51:26 +00002968 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002969 i;
2970
cristybb503372010-05-27 20:51:26 +00002971 size_t
cristy3ed852e2009-09-05 21:47:34 +00002972 step;
2973
2974 center.x=(double) x-blur_center.x;
2975 center.y=(double) y-blur_center.y;
2976 radius=hypot((double) center.x,center.y);
2977 if (radius == 0)
2978 step=1;
2979 else
2980 {
cristybb503372010-05-27 20:51:26 +00002981 step=(size_t) (blur_radius/radius);
cristy3ed852e2009-09-05 21:47:34 +00002982 if (step == 0)
2983 step=1;
2984 else
2985 if (step >= n)
2986 step=n-1;
2987 }
cristy1e7aa312011-09-10 20:01:36 +00002988 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2989 {
2990 MagickRealType
2991 gamma,
2992 pixel;
cristy3ed852e2009-09-05 21:47:34 +00002993
cristy1e7aa312011-09-10 20:01:36 +00002994 PixelChannel
2995 channel;
2996
2997 PixelTrait
2998 blur_traits,
2999 traits;
3000
3001 register const Quantum
3002 *restrict r;
3003
3004 register ssize_t
3005 j;
3006
3007 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
3008 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
3009 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
3010 if ((traits == UndefinedPixelTrait) ||
3011 (blur_traits == UndefinedPixelTrait))
3012 continue;
3013 if ((blur_traits & CopyPixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003014 {
cristy0beccfa2011-09-25 20:47:53 +00003015 SetPixelChannel(blur_image,channel,p[i],q);
cristy1e7aa312011-09-10 20:01:36 +00003016 continue;
cristy3ed852e2009-09-05 21:47:34 +00003017 }
cristy1e7aa312011-09-10 20:01:36 +00003018 gamma=0.0;
3019 pixel=bias;
3020 if ((blur_traits & BlendPixelTrait) == 0)
3021 {
3022 for (j=0; j < (ssize_t) n; j+=(ssize_t) step)
3023 {
3024 r=GetCacheViewVirtualPixels(image_view, (ssize_t) (blur_center.x+
3025 center.x*cos_theta[j]-center.y*sin_theta[j]+0.5),(ssize_t)
3026 (blur_center.y+center.x*sin_theta[j]+center.y*cos_theta[j]+0.5),
3027 1,1,exception);
3028 if (r == (const Quantum *) NULL)
3029 {
3030 status=MagickFalse;
3031 continue;
3032 }
3033 pixel+=r[i];
3034 gamma++;
3035 }
3036 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +00003037 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy1e7aa312011-09-10 20:01:36 +00003038 continue;
3039 }
3040 for (j=0; j < (ssize_t) n; j+=(ssize_t) step)
3041 {
3042 r=GetCacheViewVirtualPixels(image_view, (ssize_t) (blur_center.x+
3043 center.x*cos_theta[j]-center.y*sin_theta[j]+0.5),(ssize_t)
3044 (blur_center.y+center.x*sin_theta[j]+center.y*cos_theta[j]+0.5),
3045 1,1,exception);
3046 if (r == (const Quantum *) NULL)
3047 {
3048 status=MagickFalse;
3049 continue;
3050 }
3051 pixel+=GetPixelAlpha(image,r)*r[i];
3052 gamma+=GetPixelAlpha(image,r);
cristy3ed852e2009-09-05 21:47:34 +00003053 }
cristy1e7aa312011-09-10 20:01:36 +00003054 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +00003055 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy1e7aa312011-09-10 20:01:36 +00003056 }
3057 p+=GetPixelChannels(image);
cristyed231572011-07-14 02:18:59 +00003058 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003059 }
3060 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
3061 status=MagickFalse;
3062 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3063 {
3064 MagickBooleanType
3065 proceed;
3066
cristyb5d5f722009-11-04 03:03:49 +00003067#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00003068 #pragma omp critical (MagickCore_RadialBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00003069#endif
3070 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
3071 if (proceed == MagickFalse)
3072 status=MagickFalse;
3073 }
3074 }
3075 blur_view=DestroyCacheView(blur_view);
3076 image_view=DestroyCacheView(image_view);
3077 cos_theta=(MagickRealType *) RelinquishMagickMemory(cos_theta);
3078 sin_theta=(MagickRealType *) RelinquishMagickMemory(sin_theta);
3079 if (status == MagickFalse)
3080 blur_image=DestroyImage(blur_image);
3081 return(blur_image);
3082}
3083
3084/*
3085%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3086% %
3087% %
3088% %
cristy3ed852e2009-09-05 21:47:34 +00003089% S e l e c t i v e B l u r I m a g e %
3090% %
3091% %
3092% %
3093%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3094%
3095% SelectiveBlurImage() selectively blur pixels within a contrast threshold.
3096% It is similar to the unsharpen mask that sharpens everything with contrast
3097% above a certain threshold.
3098%
3099% The format of the SelectiveBlurImage method is:
3100%
3101% Image *SelectiveBlurImage(const Image *image,const double radius,
cristy1e7aa312011-09-10 20:01:36 +00003102% const double sigma,const double threshold,const double bias,
3103% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003104%
3105% A description of each parameter follows:
3106%
3107% o image: the image.
3108%
cristy3ed852e2009-09-05 21:47:34 +00003109% o radius: the radius of the Gaussian, in pixels, not counting the center
3110% pixel.
3111%
3112% o sigma: the standard deviation of the Gaussian, in pixels.
3113%
3114% o threshold: only pixels within this contrast threshold are included
3115% in the blur operation.
3116%
cristy1e7aa312011-09-10 20:01:36 +00003117% o bias: the bias.
3118%
cristy3ed852e2009-09-05 21:47:34 +00003119% o exception: return any errors or warnings in this structure.
3120%
3121*/
cristyf4ad9df2011-07-08 16:49:03 +00003122MagickExport Image *SelectiveBlurImage(const Image *image,
3123 const double radius,const double sigma,const double threshold,
cristy1e7aa312011-09-10 20:01:36 +00003124 const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003125{
3126#define SelectiveBlurImageTag "SelectiveBlur/Image"
3127
cristy47e00502009-12-17 19:19:57 +00003128 CacheView
3129 *blur_view,
3130 *image_view;
3131
cristy3ed852e2009-09-05 21:47:34 +00003132 double
cristy3ed852e2009-09-05 21:47:34 +00003133 *kernel;
3134
3135 Image
3136 *blur_image;
3137
cristy3ed852e2009-09-05 21:47:34 +00003138 MagickBooleanType
3139 status;
3140
cristybb503372010-05-27 20:51:26 +00003141 MagickOffsetType
3142 progress;
3143
cristybb503372010-05-27 20:51:26 +00003144 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003145 i;
cristy3ed852e2009-09-05 21:47:34 +00003146
cristybb503372010-05-27 20:51:26 +00003147 size_t
cristy3ed852e2009-09-05 21:47:34 +00003148 width;
3149
cristybb503372010-05-27 20:51:26 +00003150 ssize_t
cristyc8523c12011-09-13 00:02:53 +00003151 center,
cristybb503372010-05-27 20:51:26 +00003152 j,
3153 u,
3154 v,
3155 y;
3156
cristy3ed852e2009-09-05 21:47:34 +00003157 /*
3158 Initialize blur image attributes.
3159 */
3160 assert(image != (Image *) NULL);
3161 assert(image->signature == MagickSignature);
3162 if (image->debug != MagickFalse)
3163 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3164 assert(exception != (ExceptionInfo *) NULL);
3165 assert(exception->signature == MagickSignature);
3166 width=GetOptimalKernelWidth1D(radius,sigma);
3167 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
3168 if (kernel == (double *) NULL)
3169 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00003170 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00003171 i=0;
cristy47e00502009-12-17 19:19:57 +00003172 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00003173 {
cristy47e00502009-12-17 19:19:57 +00003174 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00003175 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
3176 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00003177 }
3178 if (image->debug != MagickFalse)
3179 {
3180 char
3181 format[MaxTextExtent],
3182 *message;
3183
cristy117ff172010-08-15 21:35:32 +00003184 register const double
3185 *k;
3186
cristybb503372010-05-27 20:51:26 +00003187 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003188 u,
3189 v;
3190
cristy3ed852e2009-09-05 21:47:34 +00003191 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00003192 " SelectiveBlurImage with %.20gx%.20g kernel:",(double) width,(double)
3193 width);
cristy3ed852e2009-09-05 21:47:34 +00003194 message=AcquireString("");
3195 k=kernel;
cristybb503372010-05-27 20:51:26 +00003196 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003197 {
3198 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00003199 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy3ed852e2009-09-05 21:47:34 +00003200 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00003201 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003202 {
cristyb51dff52011-05-19 16:55:47 +00003203 (void) FormatLocaleString(format,MaxTextExtent,"%+f ",*k++);
cristy3ed852e2009-09-05 21:47:34 +00003204 (void) ConcatenateString(&message,format);
3205 }
3206 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
3207 }
3208 message=DestroyString(message);
3209 }
cristy1e7aa312011-09-10 20:01:36 +00003210 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00003211 if (blur_image == (Image *) NULL)
3212 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00003213 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003214 {
cristy3ed852e2009-09-05 21:47:34 +00003215 blur_image=DestroyImage(blur_image);
3216 return((Image *) NULL);
3217 }
3218 /*
3219 Threshold blur image.
3220 */
3221 status=MagickTrue;
3222 progress=0;
cristyc8523c12011-09-13 00:02:53 +00003223 center=(ssize_t) (GetPixelChannels(image)*(image->columns+width)*(width/2L)+
3224 GetPixelChannels(image)*(width/2L));
cristy3ed852e2009-09-05 21:47:34 +00003225 image_view=AcquireCacheView(image);
3226 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003227#if defined(MAGICKCORE_OPENMP_SUPPORT)
3228 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003229#endif
cristybb503372010-05-27 20:51:26 +00003230 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003231 {
cristy4c08aed2011-07-01 19:47:50 +00003232 double
3233 contrast;
3234
cristy3ed852e2009-09-05 21:47:34 +00003235 MagickBooleanType
3236 sync;
3237
cristy4c08aed2011-07-01 19:47:50 +00003238 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003239 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003240
cristy4c08aed2011-07-01 19:47:50 +00003241 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003242 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003243
cristy117ff172010-08-15 21:35:32 +00003244 register ssize_t
3245 x;
3246
cristy3ed852e2009-09-05 21:47:34 +00003247 if (status == MagickFalse)
3248 continue;
cristy117ff172010-08-15 21:35:32 +00003249 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
3250 (width/2L),image->columns+width,width,exception);
cristy3ed852e2009-09-05 21:47:34 +00003251 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3252 exception);
cristy4c08aed2011-07-01 19:47:50 +00003253 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003254 {
3255 status=MagickFalse;
3256 continue;
3257 }
cristybb503372010-05-27 20:51:26 +00003258 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003259 {
cristybb503372010-05-27 20:51:26 +00003260 register ssize_t
cristy1e7aa312011-09-10 20:01:36 +00003261 i;
cristy3ed852e2009-09-05 21:47:34 +00003262
cristy1e7aa312011-09-10 20:01:36 +00003263 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3264 {
3265 MagickRealType
3266 alpha,
3267 gamma,
3268 intensity,
3269 pixel;
cristy117ff172010-08-15 21:35:32 +00003270
cristy1e7aa312011-09-10 20:01:36 +00003271 PixelChannel
3272 channel;
3273
3274 PixelTrait
3275 blur_traits,
3276 traits;
3277
3278 register const double
3279 *restrict k;
3280
3281 register const Quantum
3282 *restrict pixels;
3283
3284 register ssize_t
3285 u;
3286
3287 ssize_t
3288 v;
3289
3290 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
3291 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
3292 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
3293 if ((traits == UndefinedPixelTrait) ||
3294 (blur_traits == UndefinedPixelTrait))
3295 continue;
3296 if ((blur_traits & CopyPixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003297 {
cristy0beccfa2011-09-25 20:47:53 +00003298 SetPixelChannel(blur_image,channel,p[center+i],q);
cristy1e7aa312011-09-10 20:01:36 +00003299 continue;
cristy3ed852e2009-09-05 21:47:34 +00003300 }
cristy1e7aa312011-09-10 20:01:36 +00003301 k=kernel;
3302 pixel=bias;
3303 pixels=p;
cristyc8523c12011-09-13 00:02:53 +00003304 intensity=(MagickRealType) GetPixelIntensity(image,p+center);
cristy1e7aa312011-09-10 20:01:36 +00003305 gamma=0.0;
3306 if ((blur_traits & BlendPixelTrait) == 0)
cristy3ed852e2009-09-05 21:47:34 +00003307 {
cristy1e7aa312011-09-10 20:01:36 +00003308 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003309 {
cristy1e7aa312011-09-10 20:01:36 +00003310 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003311 {
cristy1e7aa312011-09-10 20:01:36 +00003312 contrast=GetPixelIntensity(image,pixels)-intensity;
3313 if (fabs(contrast) < threshold)
3314 {
3315 pixel+=(*k)*pixels[i];
3316 gamma+=(*k);
3317 }
3318 k++;
3319 pixels+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003320 }
cristy1e7aa312011-09-10 20:01:36 +00003321 pixels+=image->columns*GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003322 }
cristy1e7aa312011-09-10 20:01:36 +00003323 if (fabs((double) gamma) < MagickEpsilon)
3324 {
cristy0beccfa2011-09-25 20:47:53 +00003325 SetPixelChannel(blur_image,channel,p[center+i],q);
cristy1e7aa312011-09-10 20:01:36 +00003326 continue;
3327 }
3328 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +00003329 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy1e7aa312011-09-10 20:01:36 +00003330 continue;
3331 }
3332 for (v=0; v < (ssize_t) width; v++)
3333 {
3334 for (u=0; u < (ssize_t) width; u++)
3335 {
3336 contrast=GetPixelIntensity(image,pixels)-intensity;
3337 if (fabs(contrast) < threshold)
3338 {
3339 alpha=(MagickRealType) (QuantumScale*
3340 GetPixelAlpha(image,pixels));
3341 pixel+=(*k)*alpha*pixels[i];
3342 gamma+=(*k)*alpha;
3343 }
3344 k++;
3345 pixels+=GetPixelChannels(image);
3346 }
3347 pixels+=image->columns*GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003348 }
cristy1e7aa312011-09-10 20:01:36 +00003349 if (fabs((double) gamma) < MagickEpsilon)
3350 {
cristy0beccfa2011-09-25 20:47:53 +00003351 SetPixelChannel(blur_image,channel,p[center+i],q);
cristy1e7aa312011-09-10 20:01:36 +00003352 continue;
3353 }
3354 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +00003355 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy1e7aa312011-09-10 20:01:36 +00003356 }
cristyed231572011-07-14 02:18:59 +00003357 p+=GetPixelChannels(image);
3358 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003359 }
3360 sync=SyncCacheViewAuthenticPixels(blur_view,exception);
3361 if (sync == MagickFalse)
3362 status=MagickFalse;
3363 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3364 {
3365 MagickBooleanType
3366 proceed;
3367
cristyb5d5f722009-11-04 03:03:49 +00003368#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00003369 #pragma omp critical (MagickCore_SelectiveBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00003370#endif
3371 proceed=SetImageProgress(image,SelectiveBlurImageTag,progress++,
3372 image->rows);
3373 if (proceed == MagickFalse)
3374 status=MagickFalse;
3375 }
3376 }
3377 blur_image->type=image->type;
3378 blur_view=DestroyCacheView(blur_view);
3379 image_view=DestroyCacheView(image_view);
3380 kernel=(double *) RelinquishMagickMemory(kernel);
3381 if (status == MagickFalse)
3382 blur_image=DestroyImage(blur_image);
3383 return(blur_image);
3384}
3385
3386/*
3387%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3388% %
3389% %
3390% %
3391% S h a d e I m a g e %
3392% %
3393% %
3394% %
3395%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3396%
3397% ShadeImage() shines a distant light on an image to create a
3398% three-dimensional effect. You control the positioning of the light with
3399% azimuth and elevation; azimuth is measured in degrees off the x axis
3400% and elevation is measured in pixels above the Z axis.
3401%
3402% The format of the ShadeImage method is:
3403%
3404% Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3405% const double azimuth,const double elevation,ExceptionInfo *exception)
3406%
3407% A description of each parameter follows:
3408%
3409% o image: the image.
3410%
3411% o gray: A value other than zero shades the intensity of each pixel.
3412%
3413% o azimuth, elevation: Define the light source direction.
3414%
3415% o exception: return any errors or warnings in this structure.
3416%
3417*/
3418MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3419 const double azimuth,const double elevation,ExceptionInfo *exception)
3420{
3421#define ShadeImageTag "Shade/Image"
3422
cristyc4c8d132010-01-07 01:58:38 +00003423 CacheView
3424 *image_view,
3425 *shade_view;
3426
cristy3ed852e2009-09-05 21:47:34 +00003427 Image
3428 *shade_image;
3429
cristy3ed852e2009-09-05 21:47:34 +00003430 MagickBooleanType
3431 status;
3432
cristybb503372010-05-27 20:51:26 +00003433 MagickOffsetType
3434 progress;
3435
cristy3ed852e2009-09-05 21:47:34 +00003436 PrimaryInfo
3437 light;
3438
cristybb503372010-05-27 20:51:26 +00003439 ssize_t
3440 y;
3441
cristy3ed852e2009-09-05 21:47:34 +00003442 /*
3443 Initialize shaded image attributes.
3444 */
3445 assert(image != (const Image *) NULL);
3446 assert(image->signature == MagickSignature);
3447 if (image->debug != MagickFalse)
3448 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3449 assert(exception != (ExceptionInfo *) NULL);
3450 assert(exception->signature == MagickSignature);
3451 shade_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
3452 if (shade_image == (Image *) NULL)
3453 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00003454 if (SetImageStorageClass(shade_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003455 {
cristy3ed852e2009-09-05 21:47:34 +00003456 shade_image=DestroyImage(shade_image);
3457 return((Image *) NULL);
3458 }
3459 /*
3460 Compute the light vector.
3461 */
3462 light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))*
3463 cos(DegreesToRadians(elevation));
3464 light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))*
3465 cos(DegreesToRadians(elevation));
3466 light.z=(double) QuantumRange*sin(DegreesToRadians(elevation));
3467 /*
3468 Shade image.
3469 */
3470 status=MagickTrue;
3471 progress=0;
3472 image_view=AcquireCacheView(image);
3473 shade_view=AcquireCacheView(shade_image);
cristyb5d5f722009-11-04 03:03:49 +00003474#if defined(MAGICKCORE_OPENMP_SUPPORT)
3475 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003476#endif
cristybb503372010-05-27 20:51:26 +00003477 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003478 {
3479 MagickRealType
3480 distance,
3481 normal_distance,
3482 shade;
3483
3484 PrimaryInfo
3485 normal;
3486
cristy4c08aed2011-07-01 19:47:50 +00003487 register const Quantum
cristy1e7aa312011-09-10 20:01:36 +00003488 *restrict center,
cristyc47d1f82009-11-26 01:44:43 +00003489 *restrict p,
cristy1e7aa312011-09-10 20:01:36 +00003490 *restrict post,
3491 *restrict pre;
cristy3ed852e2009-09-05 21:47:34 +00003492
cristy4c08aed2011-07-01 19:47:50 +00003493 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003494 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003495
cristy117ff172010-08-15 21:35:32 +00003496 register ssize_t
3497 x;
3498
cristy3ed852e2009-09-05 21:47:34 +00003499 if (status == MagickFalse)
3500 continue;
3501 p=GetCacheViewVirtualPixels(image_view,-1,y-1,image->columns+2,3,exception);
3502 q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1,
3503 exception);
cristy4c08aed2011-07-01 19:47:50 +00003504 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003505 {
3506 status=MagickFalse;
3507 continue;
3508 }
3509 /*
3510 Shade this row of pixels.
3511 */
3512 normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */
cristy1e7aa312011-09-10 20:01:36 +00003513 pre=p+GetPixelChannels(image);
3514 center=pre+(image->columns+2)*GetPixelChannels(image);
3515 post=center+(image->columns+2)*GetPixelChannels(image);
cristybb503372010-05-27 20:51:26 +00003516 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003517 {
cristy1e7aa312011-09-10 20:01:36 +00003518 register ssize_t
3519 i;
3520
cristy3ed852e2009-09-05 21:47:34 +00003521 /*
3522 Determine the surface normal and compute shading.
3523 */
cristy1e7aa312011-09-10 20:01:36 +00003524 normal.x=(double) (GetPixelIntensity(image,pre-GetPixelChannels(image))+
3525 GetPixelIntensity(image,center-GetPixelChannels(image))+
3526 GetPixelIntensity(image,post-GetPixelChannels(image))-
3527 GetPixelIntensity(image,pre+GetPixelChannels(image))-
3528 GetPixelIntensity(image,center+GetPixelChannels(image))-
3529 GetPixelIntensity(image,post+GetPixelChannels(image)));
3530 normal.y=(double) (GetPixelIntensity(image,post-GetPixelChannels(image))+
3531 GetPixelIntensity(image,post)+GetPixelIntensity(image,post+
3532 GetPixelChannels(image))-GetPixelIntensity(image,pre-
3533 GetPixelChannels(image))-GetPixelIntensity(image,pre)-
3534 GetPixelIntensity(image,pre+GetPixelChannels(image)));
cristy3ed852e2009-09-05 21:47:34 +00003535 if ((normal.x == 0.0) && (normal.y == 0.0))
3536 shade=light.z;
3537 else
3538 {
3539 shade=0.0;
3540 distance=normal.x*light.x+normal.y*light.y+normal.z*light.z;
3541 if (distance > MagickEpsilon)
3542 {
3543 normal_distance=
3544 normal.x*normal.x+normal.y*normal.y+normal.z*normal.z;
3545 if (normal_distance > (MagickEpsilon*MagickEpsilon))
3546 shade=distance/sqrt((double) normal_distance);
3547 }
3548 }
cristy1e7aa312011-09-10 20:01:36 +00003549 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3550 {
3551 PixelChannel
3552 channel;
3553
3554 PixelTrait
3555 shade_traits,
3556 traits;
3557
3558 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
3559 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
3560 shade_traits=GetPixelChannelMapTraits(shade_image,channel);
3561 if ((traits == UndefinedPixelTrait) ||
3562 (shade_traits == UndefinedPixelTrait))
3563 continue;
3564 if ((shade_traits & CopyPixelTrait) != 0)
3565 {
cristy0beccfa2011-09-25 20:47:53 +00003566 SetPixelChannel(shade_image,channel,center[i],q);
cristy1e7aa312011-09-10 20:01:36 +00003567 continue;
3568 }
3569 if (gray != MagickFalse)
3570 {
cristy0beccfa2011-09-25 20:47:53 +00003571 SetPixelChannel(shade_image,channel,ClampToQuantum(shade),q);
cristy1e7aa312011-09-10 20:01:36 +00003572 continue;
3573 }
cristy0beccfa2011-09-25 20:47:53 +00003574 SetPixelChannel(shade_image,channel,ClampToQuantum(QuantumScale*shade*
3575 center[i]),q);
cristy1e7aa312011-09-10 20:01:36 +00003576 }
3577 pre+=GetPixelChannels(image);
3578 center+=GetPixelChannels(image);
3579 post+=GetPixelChannels(image);
cristyed231572011-07-14 02:18:59 +00003580 q+=GetPixelChannels(shade_image);
cristy3ed852e2009-09-05 21:47:34 +00003581 }
3582 if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
3583 status=MagickFalse;
3584 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3585 {
3586 MagickBooleanType
3587 proceed;
3588
cristyb5d5f722009-11-04 03:03:49 +00003589#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003590 #pragma omp critical (MagickCore_ShadeImage)
3591#endif
3592 proceed=SetImageProgress(image,ShadeImageTag,progress++,image->rows);
3593 if (proceed == MagickFalse)
3594 status=MagickFalse;
3595 }
3596 }
3597 shade_view=DestroyCacheView(shade_view);
3598 image_view=DestroyCacheView(image_view);
3599 if (status == MagickFalse)
3600 shade_image=DestroyImage(shade_image);
3601 return(shade_image);
3602}
3603
3604/*
3605%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3606% %
3607% %
3608% %
3609% S h a r p e n I m a g e %
3610% %
3611% %
3612% %
3613%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3614%
3615% SharpenImage() sharpens the image. We convolve the image with a Gaussian
3616% operator of the given radius and standard deviation (sigma). For
3617% reasonable results, radius should be larger than sigma. Use a radius of 0
3618% and SharpenImage() selects a suitable radius for you.
3619%
3620% Using a separable kernel would be faster, but the negative weights cancel
3621% out on the corners of the kernel producing often undesirable ringing in the
3622% filtered result; this can be avoided by using a 2D gaussian shaped image
3623% sharpening kernel instead.
3624%
3625% The format of the SharpenImage method is:
3626%
3627% Image *SharpenImage(const Image *image,const double radius,
cristy05c0c9a2011-09-05 23:16:13 +00003628% const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003629%
3630% A description of each parameter follows:
3631%
3632% o image: the image.
3633%
cristy3ed852e2009-09-05 21:47:34 +00003634% o radius: the radius of the Gaussian, in pixels, not counting the center
3635% pixel.
3636%
3637% o sigma: the standard deviation of the Laplacian, in pixels.
3638%
cristy05c0c9a2011-09-05 23:16:13 +00003639% o bias: bias.
3640%
cristy3ed852e2009-09-05 21:47:34 +00003641% o exception: return any errors or warnings in this structure.
3642%
3643*/
cristy3ed852e2009-09-05 21:47:34 +00003644MagickExport Image *SharpenImage(const Image *image,const double radius,
cristy05c0c9a2011-09-05 23:16:13 +00003645 const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003646{
cristy3ed852e2009-09-05 21:47:34 +00003647 double
cristy47e00502009-12-17 19:19:57 +00003648 normalize;
cristy3ed852e2009-09-05 21:47:34 +00003649
3650 Image
3651 *sharp_image;
3652
cristy41cbe682011-07-15 19:12:37 +00003653 KernelInfo
3654 *kernel_info;
3655
cristybb503372010-05-27 20:51:26 +00003656 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003657 i;
3658
cristybb503372010-05-27 20:51:26 +00003659 size_t
cristy3ed852e2009-09-05 21:47:34 +00003660 width;
3661
cristy117ff172010-08-15 21:35:32 +00003662 ssize_t
3663 j,
3664 u,
3665 v;
3666
cristy3ed852e2009-09-05 21:47:34 +00003667 assert(image != (const Image *) NULL);
3668 assert(image->signature == MagickSignature);
3669 if (image->debug != MagickFalse)
3670 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3671 assert(exception != (ExceptionInfo *) NULL);
3672 assert(exception->signature == MagickSignature);
3673 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00003674 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00003675 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003676 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00003677 (void) ResetMagickMemory(kernel_info,0,sizeof(*kernel_info));
3678 kernel_info->width=width;
3679 kernel_info->height=width;
cristy05c0c9a2011-09-05 23:16:13 +00003680 kernel_info->bias=bias;
cristy41cbe682011-07-15 19:12:37 +00003681 kernel_info->signature=MagickSignature;
cristy9f752c02011-09-14 19:44:03 +00003682 kernel_info->values=(double *) AcquireAlignedMemory(kernel_info->width,
cristy41cbe682011-07-15 19:12:37 +00003683 kernel_info->width*sizeof(*kernel_info->values));
3684 if (kernel_info->values == (double *) NULL)
3685 {
3686 kernel_info=DestroyKernelInfo(kernel_info);
3687 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3688 }
cristy3ed852e2009-09-05 21:47:34 +00003689 normalize=0.0;
cristy41cbe682011-07-15 19:12:37 +00003690 j=(ssize_t) kernel_info->width/2;
cristy47e00502009-12-17 19:19:57 +00003691 i=0;
3692 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00003693 {
cristy47e00502009-12-17 19:19:57 +00003694 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00003695 {
cristy41cbe682011-07-15 19:12:37 +00003696 kernel_info->values[i]=(double) (-exp(-((double) u*u+v*v)/(2.0*
3697 MagickSigma*MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
3698 normalize+=kernel_info->values[i];
cristy3ed852e2009-09-05 21:47:34 +00003699 i++;
3700 }
3701 }
cristy41cbe682011-07-15 19:12:37 +00003702 kernel_info->values[i/2]=(double) ((-2.0)*normalize);
cristy5e6be1e2011-07-16 01:23:39 +00003703 sharp_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00003704 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00003705 return(sharp_image);
3706}
3707
3708/*
3709%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3710% %
3711% %
3712% %
3713% S p r e a d I m a g e %
3714% %
3715% %
3716% %
3717%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3718%
3719% SpreadImage() is a special effects method that randomly displaces each
3720% pixel in a block defined by the radius parameter.
3721%
3722% The format of the SpreadImage method is:
3723%
3724% Image *SpreadImage(const Image *image,const double radius,
cristy5c4e2582011-09-11 19:21:03 +00003725% const PixelInterpolateMethod method,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003726%
3727% A description of each parameter follows:
3728%
3729% o image: the image.
3730%
cristy5c4e2582011-09-11 19:21:03 +00003731% o radius: choose a random pixel in a neighborhood of this extent.
3732%
3733% o method: the pixel interpolation method.
cristy3ed852e2009-09-05 21:47:34 +00003734%
3735% o exception: return any errors or warnings in this structure.
3736%
3737*/
3738MagickExport Image *SpreadImage(const Image *image,const double radius,
cristy5c4e2582011-09-11 19:21:03 +00003739 const PixelInterpolateMethod method,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003740{
3741#define SpreadImageTag "Spread/Image"
3742
cristyfa112112010-01-04 17:48:07 +00003743 CacheView
cristy9f7e7cb2011-03-26 00:49:57 +00003744 *image_view,
3745 *spread_view;
cristyfa112112010-01-04 17:48:07 +00003746
cristy3ed852e2009-09-05 21:47:34 +00003747 Image
3748 *spread_image;
3749
cristy3ed852e2009-09-05 21:47:34 +00003750 MagickBooleanType
3751 status;
3752
cristybb503372010-05-27 20:51:26 +00003753 MagickOffsetType
3754 progress;
3755
cristy3ed852e2009-09-05 21:47:34 +00003756 RandomInfo
cristyfa112112010-01-04 17:48:07 +00003757 **restrict random_info;
cristy3ed852e2009-09-05 21:47:34 +00003758
cristybb503372010-05-27 20:51:26 +00003759 size_t
cristy3ed852e2009-09-05 21:47:34 +00003760 width;
3761
cristybb503372010-05-27 20:51:26 +00003762 ssize_t
3763 y;
3764
cristy3ed852e2009-09-05 21:47:34 +00003765 /*
3766 Initialize spread image attributes.
3767 */
3768 assert(image != (Image *) NULL);
3769 assert(image->signature == MagickSignature);
3770 if (image->debug != MagickFalse)
3771 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3772 assert(exception != (ExceptionInfo *) NULL);
3773 assert(exception->signature == MagickSignature);
3774 spread_image=CloneImage(image,image->columns,image->rows,MagickTrue,
3775 exception);
3776 if (spread_image == (Image *) NULL)
3777 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00003778 if (SetImageStorageClass(spread_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003779 {
cristy3ed852e2009-09-05 21:47:34 +00003780 spread_image=DestroyImage(spread_image);
3781 return((Image *) NULL);
3782 }
3783 /*
3784 Spread image.
3785 */
3786 status=MagickTrue;
3787 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00003788 width=GetOptimalKernelWidth1D(radius,0.5);
cristy3ed852e2009-09-05 21:47:34 +00003789 random_info=AcquireRandomInfoThreadSet();
cristy9f7e7cb2011-03-26 00:49:57 +00003790 image_view=AcquireCacheView(image);
3791 spread_view=AcquireCacheView(spread_image);
cristyb557a152011-02-22 12:14:30 +00003792#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00003793 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00003794#endif
cristybe82ad52011-09-06 01:17:20 +00003795 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003796 {
cristy5c9e6f22010-09-17 17:31:01 +00003797 const int
3798 id = GetOpenMPThreadId();
cristy6ebe97c2010-07-03 01:17:28 +00003799
cristybe82ad52011-09-06 01:17:20 +00003800 register const Quantum
3801 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003802
cristy4c08aed2011-07-01 19:47:50 +00003803 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003804 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003805
cristy117ff172010-08-15 21:35:32 +00003806 register ssize_t
3807 x;
3808
cristy3ed852e2009-09-05 21:47:34 +00003809 if (status == MagickFalse)
3810 continue;
cristybe82ad52011-09-06 01:17:20 +00003811 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy9f7e7cb2011-03-26 00:49:57 +00003812 q=QueueCacheViewAuthenticPixels(spread_view,0,y,spread_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +00003813 exception);
cristybe82ad52011-09-06 01:17:20 +00003814 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003815 {
3816 status=MagickFalse;
3817 continue;
3818 }
cristybe82ad52011-09-06 01:17:20 +00003819 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003820 {
cristybe82ad52011-09-06 01:17:20 +00003821 PointInfo
3822 point;
3823
cristybe82ad52011-09-06 01:17:20 +00003824 point.x=GetPseudoRandomValue(random_info[id]);
3825 point.y=GetPseudoRandomValue(random_info[id]);
cristy5c4e2582011-09-11 19:21:03 +00003826 status=InterpolatePixelChannels(image,image_view,spread_image,method,
3827 (double) x+width*point.x-0.5,(double) y+width*point.y-0.5,q,exception);
cristyed231572011-07-14 02:18:59 +00003828 q+=GetPixelChannels(spread_image);
cristy3ed852e2009-09-05 21:47:34 +00003829 }
cristy9f7e7cb2011-03-26 00:49:57 +00003830 if (SyncCacheViewAuthenticPixels(spread_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003831 status=MagickFalse;
3832 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3833 {
3834 MagickBooleanType
3835 proceed;
3836
cristyb557a152011-02-22 12:14:30 +00003837#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003838 #pragma omp critical (MagickCore_SpreadImage)
3839#endif
3840 proceed=SetImageProgress(image,SpreadImageTag,progress++,image->rows);
3841 if (proceed == MagickFalse)
3842 status=MagickFalse;
3843 }
3844 }
cristy9f7e7cb2011-03-26 00:49:57 +00003845 spread_view=DestroyCacheView(spread_view);
cristy3ed852e2009-09-05 21:47:34 +00003846 image_view=DestroyCacheView(image_view);
3847 random_info=DestroyRandomInfoThreadSet(random_info);
cristy3ed852e2009-09-05 21:47:34 +00003848 return(spread_image);
3849}
3850
3851/*
3852%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3853% %
3854% %
3855% %
cristy0834d642011-03-18 18:26:08 +00003856% S t a t i s t i c I m a g e %
3857% %
3858% %
3859% %
3860%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3861%
3862% StatisticImage() makes each pixel the min / max / median / mode / etc. of
cristy8d752042011-03-19 01:00:36 +00003863% the neighborhood of the specified width and height.
cristy0834d642011-03-18 18:26:08 +00003864%
3865% The format of the StatisticImage method is:
3866%
3867% Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00003868% const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00003869%
3870% A description of each parameter follows:
3871%
3872% o image: the image.
3873%
cristy0834d642011-03-18 18:26:08 +00003874% o type: the statistic type (median, mode, etc.).
3875%
cristy95c38342011-03-18 22:39:51 +00003876% o width: the width of the pixel neighborhood.
3877%
3878% o height: the height of the pixel neighborhood.
cristy0834d642011-03-18 18:26:08 +00003879%
3880% o exception: return any errors or warnings in this structure.
3881%
3882*/
3883
cristyf36cbcb2011-09-07 13:28:22 +00003884typedef struct _SkipNode
cristy733678d2011-03-18 21:29:28 +00003885{
3886 size_t
3887 next[9],
3888 count,
3889 signature;
cristyf36cbcb2011-09-07 13:28:22 +00003890} SkipNode;
cristy733678d2011-03-18 21:29:28 +00003891
3892typedef struct _SkipList
3893{
3894 ssize_t
3895 level;
3896
cristyf36cbcb2011-09-07 13:28:22 +00003897 SkipNode
cristy733678d2011-03-18 21:29:28 +00003898 *nodes;
3899} SkipList;
3900
3901typedef struct _PixelList
3902{
3903 size_t
cristy6fc86bb2011-03-18 23:45:16 +00003904 length,
cristyf36cbcb2011-09-07 13:28:22 +00003905 seed;
cristy733678d2011-03-18 21:29:28 +00003906
3907 SkipList
cristyf36cbcb2011-09-07 13:28:22 +00003908 skip_list;
3909
3910 size_t
3911 signature;
cristy733678d2011-03-18 21:29:28 +00003912} PixelList;
3913
3914static PixelList *DestroyPixelList(PixelList *pixel_list)
3915{
cristy733678d2011-03-18 21:29:28 +00003916 if (pixel_list == (PixelList *) NULL)
3917 return((PixelList *) NULL);
cristyf36cbcb2011-09-07 13:28:22 +00003918 if (pixel_list->skip_list.nodes != (SkipNode *) NULL)
3919 pixel_list->skip_list.nodes=(SkipNode *) RelinquishMagickMemory(
3920 pixel_list->skip_list.nodes);
cristy733678d2011-03-18 21:29:28 +00003921 pixel_list=(PixelList *) RelinquishMagickMemory(pixel_list);
3922 return(pixel_list);
3923}
3924
3925static PixelList **DestroyPixelListThreadSet(PixelList **pixel_list)
3926{
3927 register ssize_t
3928 i;
3929
3930 assert(pixel_list != (PixelList **) NULL);
3931 for (i=0; i < (ssize_t) GetOpenMPMaximumThreads(); i++)
3932 if (pixel_list[i] != (PixelList *) NULL)
3933 pixel_list[i]=DestroyPixelList(pixel_list[i]);
3934 pixel_list=(PixelList **) RelinquishMagickMemory(pixel_list);
3935 return(pixel_list);
3936}
3937
cristy6fc86bb2011-03-18 23:45:16 +00003938static PixelList *AcquirePixelList(const size_t width,const size_t height)
cristy733678d2011-03-18 21:29:28 +00003939{
3940 PixelList
3941 *pixel_list;
3942
cristy733678d2011-03-18 21:29:28 +00003943 pixel_list=(PixelList *) AcquireMagickMemory(sizeof(*pixel_list));
3944 if (pixel_list == (PixelList *) NULL)
3945 return(pixel_list);
3946 (void) ResetMagickMemory((void *) pixel_list,0,sizeof(*pixel_list));
cristy6fc86bb2011-03-18 23:45:16 +00003947 pixel_list->length=width*height;
cristyf36cbcb2011-09-07 13:28:22 +00003948 pixel_list->skip_list.nodes=(SkipNode *) AcquireQuantumMemory(65537UL,
3949 sizeof(*pixel_list->skip_list.nodes));
3950 if (pixel_list->skip_list.nodes == (SkipNode *) NULL)
3951 return(DestroyPixelList(pixel_list));
3952 (void) ResetMagickMemory(pixel_list->skip_list.nodes,0,65537UL*
3953 sizeof(*pixel_list->skip_list.nodes));
cristy733678d2011-03-18 21:29:28 +00003954 pixel_list->signature=MagickSignature;
3955 return(pixel_list);
3956}
3957
cristy6fc86bb2011-03-18 23:45:16 +00003958static PixelList **AcquirePixelListThreadSet(const size_t width,
3959 const size_t height)
cristy733678d2011-03-18 21:29:28 +00003960{
3961 PixelList
3962 **pixel_list;
3963
3964 register ssize_t
3965 i;
3966
3967 size_t
3968 number_threads;
3969
3970 number_threads=GetOpenMPMaximumThreads();
3971 pixel_list=(PixelList **) AcquireQuantumMemory(number_threads,
3972 sizeof(*pixel_list));
3973 if (pixel_list == (PixelList **) NULL)
3974 return((PixelList **) NULL);
3975 (void) ResetMagickMemory(pixel_list,0,number_threads*sizeof(*pixel_list));
3976 for (i=0; i < (ssize_t) number_threads; i++)
3977 {
cristy6fc86bb2011-03-18 23:45:16 +00003978 pixel_list[i]=AcquirePixelList(width,height);
cristy733678d2011-03-18 21:29:28 +00003979 if (pixel_list[i] == (PixelList *) NULL)
3980 return(DestroyPixelListThreadSet(pixel_list));
3981 }
3982 return(pixel_list);
3983}
3984
cristyf36cbcb2011-09-07 13:28:22 +00003985static void AddNodePixelList(PixelList *pixel_list,const size_t color)
cristy733678d2011-03-18 21:29:28 +00003986{
3987 register SkipList
cristyf36cbcb2011-09-07 13:28:22 +00003988 *p;
cristy733678d2011-03-18 21:29:28 +00003989
3990 register ssize_t
3991 level;
3992
3993 size_t
3994 search,
3995 update[9];
3996
3997 /*
3998 Initialize the node.
3999 */
cristyf36cbcb2011-09-07 13:28:22 +00004000 p=(&pixel_list->skip_list);
4001 p->nodes[color].signature=pixel_list->signature;
4002 p->nodes[color].count=1;
cristy733678d2011-03-18 21:29:28 +00004003 /*
4004 Determine where it belongs in the list.
4005 */
4006 search=65536UL;
cristyf36cbcb2011-09-07 13:28:22 +00004007 for (level=p->level; level >= 0; level--)
cristy733678d2011-03-18 21:29:28 +00004008 {
cristyf36cbcb2011-09-07 13:28:22 +00004009 while (p->nodes[search].next[level] < color)
4010 search=p->nodes[search].next[level];
cristy733678d2011-03-18 21:29:28 +00004011 update[level]=search;
4012 }
4013 /*
4014 Generate a pseudo-random level for this node.
4015 */
4016 for (level=0; ; level++)
4017 {
4018 pixel_list->seed=(pixel_list->seed*42893621L)+1L;
4019 if ((pixel_list->seed & 0x300) != 0x300)
4020 break;
4021 }
4022 if (level > 8)
4023 level=8;
cristyf36cbcb2011-09-07 13:28:22 +00004024 if (level > (p->level+2))
4025 level=p->level+2;
cristy733678d2011-03-18 21:29:28 +00004026 /*
4027 If we're raising the list's level, link back to the root node.
4028 */
cristyf36cbcb2011-09-07 13:28:22 +00004029 while (level > p->level)
cristy733678d2011-03-18 21:29:28 +00004030 {
cristyf36cbcb2011-09-07 13:28:22 +00004031 p->level++;
4032 update[p->level]=65536UL;
cristy733678d2011-03-18 21:29:28 +00004033 }
4034 /*
4035 Link the node into the skip-list.
4036 */
4037 do
4038 {
cristyf36cbcb2011-09-07 13:28:22 +00004039 p->nodes[color].next[level]=p->nodes[update[level]].next[level];
4040 p->nodes[update[level]].next[level]=color;
cristy3cba8ca2011-03-19 01:29:12 +00004041 } while (level-- > 0);
cristy733678d2011-03-18 21:29:28 +00004042}
4043
cristy075ff302011-09-07 01:51:24 +00004044static Quantum GetMaximumPixelList(PixelList *pixel_list)
cristy6fc86bb2011-03-18 23:45:16 +00004045{
cristy6fc86bb2011-03-18 23:45:16 +00004046 register SkipList
cristyf36cbcb2011-09-07 13:28:22 +00004047 *p;
cristy6fc86bb2011-03-18 23:45:16 +00004048
4049 size_t
cristyd76c51e2011-03-26 00:21:26 +00004050 color,
4051 maximum;
cristy49f37242011-03-22 18:18:23 +00004052
4053 ssize_t
cristy6fc86bb2011-03-18 23:45:16 +00004054 count;
4055
cristy6fc86bb2011-03-18 23:45:16 +00004056 /*
4057 Find the maximum value for each of the color.
4058 */
cristyf36cbcb2011-09-07 13:28:22 +00004059 p=(&pixel_list->skip_list);
4060 color=65536L;
4061 count=0;
4062 maximum=p->nodes[color].next[0];
4063 do
cristy6fc86bb2011-03-18 23:45:16 +00004064 {
cristyf36cbcb2011-09-07 13:28:22 +00004065 color=p->nodes[color].next[0];
4066 if (color > maximum)
4067 maximum=color;
4068 count+=p->nodes[color].count;
4069 } while (count < (ssize_t) pixel_list->length);
4070 return(ScaleShortToQuantum((unsigned short) maximum));
cristy49f37242011-03-22 18:18:23 +00004071}
4072
cristy075ff302011-09-07 01:51:24 +00004073static Quantum GetMeanPixelList(PixelList *pixel_list)
cristy49f37242011-03-22 18:18:23 +00004074{
cristy80a99a32011-03-30 01:30:23 +00004075 MagickRealType
4076 sum;
4077
cristy49f37242011-03-22 18:18:23 +00004078 register SkipList
cristyf36cbcb2011-09-07 13:28:22 +00004079 *p;
cristy49f37242011-03-22 18:18:23 +00004080
4081 size_t
cristy80a99a32011-03-30 01:30:23 +00004082 color;
cristy49f37242011-03-22 18:18:23 +00004083
4084 ssize_t
4085 count;
4086
cristy49f37242011-03-22 18:18:23 +00004087 /*
4088 Find the mean value for each of the color.
4089 */
cristyf36cbcb2011-09-07 13:28:22 +00004090 p=(&pixel_list->skip_list);
4091 color=65536L;
4092 count=0;
4093 sum=0.0;
4094 do
cristy49f37242011-03-22 18:18:23 +00004095 {
cristyf36cbcb2011-09-07 13:28:22 +00004096 color=p->nodes[color].next[0];
4097 sum+=(MagickRealType) p->nodes[color].count*color;
4098 count+=p->nodes[color].count;
4099 } while (count < (ssize_t) pixel_list->length);
4100 sum/=pixel_list->length;
4101 return(ScaleShortToQuantum((unsigned short) sum));
cristy6fc86bb2011-03-18 23:45:16 +00004102}
4103
cristy075ff302011-09-07 01:51:24 +00004104static Quantum GetMedianPixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004105{
cristy733678d2011-03-18 21:29:28 +00004106 register SkipList
cristyf36cbcb2011-09-07 13:28:22 +00004107 *p;
cristy733678d2011-03-18 21:29:28 +00004108
4109 size_t
cristy49f37242011-03-22 18:18:23 +00004110 color;
4111
4112 ssize_t
cristy733678d2011-03-18 21:29:28 +00004113 count;
4114
cristy733678d2011-03-18 21:29:28 +00004115 /*
4116 Find the median value for each of the color.
4117 */
cristyf36cbcb2011-09-07 13:28:22 +00004118 p=(&pixel_list->skip_list);
4119 color=65536L;
4120 count=0;
4121 do
cristy733678d2011-03-18 21:29:28 +00004122 {
cristyf36cbcb2011-09-07 13:28:22 +00004123 color=p->nodes[color].next[0];
4124 count+=p->nodes[color].count;
4125 } while (count <= (ssize_t) (pixel_list->length >> 1));
4126 return(ScaleShortToQuantum((unsigned short) color));
cristy6fc86bb2011-03-18 23:45:16 +00004127}
4128
cristy075ff302011-09-07 01:51:24 +00004129static Quantum GetMinimumPixelList(PixelList *pixel_list)
cristy6fc86bb2011-03-18 23:45:16 +00004130{
cristy6fc86bb2011-03-18 23:45:16 +00004131 register SkipList
cristyf36cbcb2011-09-07 13:28:22 +00004132 *p;
cristy6fc86bb2011-03-18 23:45:16 +00004133
4134 size_t
cristyd76c51e2011-03-26 00:21:26 +00004135 color,
4136 minimum;
cristy6fc86bb2011-03-18 23:45:16 +00004137
cristy49f37242011-03-22 18:18:23 +00004138 ssize_t
4139 count;
4140
cristy6fc86bb2011-03-18 23:45:16 +00004141 /*
4142 Find the minimum value for each of the color.
4143 */
cristyf36cbcb2011-09-07 13:28:22 +00004144 p=(&pixel_list->skip_list);
4145 count=0;
4146 color=65536UL;
4147 minimum=p->nodes[color].next[0];
4148 do
cristy6fc86bb2011-03-18 23:45:16 +00004149 {
cristyf36cbcb2011-09-07 13:28:22 +00004150 color=p->nodes[color].next[0];
4151 if (color < minimum)
4152 minimum=color;
4153 count+=p->nodes[color].count;
4154 } while (count < (ssize_t) pixel_list->length);
4155 return(ScaleShortToQuantum((unsigned short) minimum));
cristy733678d2011-03-18 21:29:28 +00004156}
4157
cristy075ff302011-09-07 01:51:24 +00004158static Quantum GetModePixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004159{
cristy733678d2011-03-18 21:29:28 +00004160 register SkipList
cristyf36cbcb2011-09-07 13:28:22 +00004161 *p;
cristy733678d2011-03-18 21:29:28 +00004162
4163 size_t
4164 color,
cristy733678d2011-03-18 21:29:28 +00004165 max_count,
cristy6fc86bb2011-03-18 23:45:16 +00004166 mode;
cristy733678d2011-03-18 21:29:28 +00004167
cristy49f37242011-03-22 18:18:23 +00004168 ssize_t
4169 count;
4170
cristy733678d2011-03-18 21:29:28 +00004171 /*
glennrp30d2dc62011-06-25 03:17:16 +00004172 Make each pixel the 'predominant color' of the specified neighborhood.
cristy733678d2011-03-18 21:29:28 +00004173 */
cristyf36cbcb2011-09-07 13:28:22 +00004174 p=(&pixel_list->skip_list);
4175 color=65536L;
4176 mode=color;
4177 max_count=p->nodes[mode].count;
4178 count=0;
4179 do
cristy733678d2011-03-18 21:29:28 +00004180 {
cristyf36cbcb2011-09-07 13:28:22 +00004181 color=p->nodes[color].next[0];
4182 if (p->nodes[color].count > max_count)
4183 {
4184 mode=color;
4185 max_count=p->nodes[mode].count;
4186 }
4187 count+=p->nodes[color].count;
4188 } while (count < (ssize_t) pixel_list->length);
4189 return(ScaleShortToQuantum((unsigned short) mode));
cristy733678d2011-03-18 21:29:28 +00004190}
4191
cristy075ff302011-09-07 01:51:24 +00004192static Quantum GetNonpeakPixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004193{
cristy733678d2011-03-18 21:29:28 +00004194 register SkipList
cristyf36cbcb2011-09-07 13:28:22 +00004195 *p;
cristy733678d2011-03-18 21:29:28 +00004196
4197 size_t
cristy733678d2011-03-18 21:29:28 +00004198 color,
cristy733678d2011-03-18 21:29:28 +00004199 next,
4200 previous;
4201
cristy49f37242011-03-22 18:18:23 +00004202 ssize_t
4203 count;
4204
cristy733678d2011-03-18 21:29:28 +00004205 /*
cristy49f37242011-03-22 18:18:23 +00004206 Finds the non peak value for each of the colors.
cristy733678d2011-03-18 21:29:28 +00004207 */
cristyf36cbcb2011-09-07 13:28:22 +00004208 p=(&pixel_list->skip_list);
4209 color=65536L;
4210 next=p->nodes[color].next[0];
4211 count=0;
4212 do
cristy733678d2011-03-18 21:29:28 +00004213 {
cristyf36cbcb2011-09-07 13:28:22 +00004214 previous=color;
4215 color=next;
4216 next=p->nodes[color].next[0];
4217 count+=p->nodes[color].count;
4218 } while (count <= (ssize_t) (pixel_list->length >> 1));
4219 if ((previous == 65536UL) && (next != 65536UL))
4220 color=next;
4221 else
4222 if ((previous != 65536UL) && (next == 65536UL))
4223 color=previous;
4224 return(ScaleShortToQuantum((unsigned short) color));
cristy733678d2011-03-18 21:29:28 +00004225}
4226
cristy075ff302011-09-07 01:51:24 +00004227static Quantum GetStandardDeviationPixelList(PixelList *pixel_list)
cristy9a68cbb2011-03-29 00:51:23 +00004228{
cristy80a99a32011-03-30 01:30:23 +00004229 MagickRealType
4230 sum,
4231 sum_squared;
4232
cristy9a68cbb2011-03-29 00:51:23 +00004233 register SkipList
cristyf36cbcb2011-09-07 13:28:22 +00004234 *p;
cristy9a68cbb2011-03-29 00:51:23 +00004235
4236 size_t
cristy80a99a32011-03-30 01:30:23 +00004237 color;
cristy9a68cbb2011-03-29 00:51:23 +00004238
4239 ssize_t
4240 count;
4241
cristy9a68cbb2011-03-29 00:51:23 +00004242 /*
cristy80a99a32011-03-30 01:30:23 +00004243 Find the standard-deviation value for each of the color.
cristy9a68cbb2011-03-29 00:51:23 +00004244 */
cristyf36cbcb2011-09-07 13:28:22 +00004245 p=(&pixel_list->skip_list);
4246 color=65536L;
4247 count=0;
4248 sum=0.0;
4249 sum_squared=0.0;
4250 do
cristy9a68cbb2011-03-29 00:51:23 +00004251 {
cristyf36cbcb2011-09-07 13:28:22 +00004252 register ssize_t
4253 i;
cristy80a99a32011-03-30 01:30:23 +00004254
cristyf36cbcb2011-09-07 13:28:22 +00004255 color=p->nodes[color].next[0];
4256 sum+=(MagickRealType) p->nodes[color].count*color;
4257 for (i=0; i < (ssize_t) p->nodes[color].count; i++)
4258 sum_squared+=((MagickRealType) color)*((MagickRealType) color);
4259 count+=p->nodes[color].count;
4260 } while (count < (ssize_t) pixel_list->length);
4261 sum/=pixel_list->length;
4262 sum_squared/=pixel_list->length;
4263 return(ScaleShortToQuantum((unsigned short) sqrt(sum_squared-(sum*sum))));
cristy9a68cbb2011-03-29 00:51:23 +00004264}
4265
cristyf36cbcb2011-09-07 13:28:22 +00004266static inline void InsertPixelList(const Image *image,const Quantum pixel,
cristy4c08aed2011-07-01 19:47:50 +00004267 PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004268{
4269 size_t
4270 signature;
4271
4272 unsigned short
4273 index;
4274
cristyf36cbcb2011-09-07 13:28:22 +00004275 index=ScaleQuantumToShort(pixel);
4276 signature=pixel_list->skip_list.nodes[index].signature;
cristy733678d2011-03-18 21:29:28 +00004277 if (signature == pixel_list->signature)
cristyf36cbcb2011-09-07 13:28:22 +00004278 {
4279 pixel_list->skip_list.nodes[index].count++;
4280 return;
4281 }
4282 AddNodePixelList(pixel_list,index);
cristy733678d2011-03-18 21:29:28 +00004283}
4284
cristy80c99742011-04-04 14:46:39 +00004285static inline MagickRealType MagickAbsoluteValue(const MagickRealType x)
4286{
4287 if (x < 0)
4288 return(-x);
4289 return(x);
4290}
4291
cristyf36cbcb2011-09-07 13:28:22 +00004292static inline size_t MagickMax(const size_t x,const size_t y)
4293{
4294 if (x > y)
4295 return(x);
4296 return(y);
4297}
4298
cristy733678d2011-03-18 21:29:28 +00004299static void ResetPixelList(PixelList *pixel_list)
4300{
4301 int
4302 level;
4303
cristyf36cbcb2011-09-07 13:28:22 +00004304 register SkipNode
cristy733678d2011-03-18 21:29:28 +00004305 *root;
4306
4307 register SkipList
cristyf36cbcb2011-09-07 13:28:22 +00004308 *p;
cristy733678d2011-03-18 21:29:28 +00004309
4310 /*
4311 Reset the skip-list.
4312 */
cristyf36cbcb2011-09-07 13:28:22 +00004313 p=(&pixel_list->skip_list);
4314 root=p->nodes+65536UL;
4315 p->level=0;
4316 for (level=0; level < 9; level++)
4317 root->next[level]=65536UL;
cristy733678d2011-03-18 21:29:28 +00004318 pixel_list->seed=pixel_list->signature++;
4319}
4320
cristy0834d642011-03-18 18:26:08 +00004321MagickExport Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00004322 const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00004323{
cristy0834d642011-03-18 18:26:08 +00004324#define StatisticImageTag "Statistic/Image"
4325
4326 CacheView
4327 *image_view,
4328 *statistic_view;
4329
4330 Image
4331 *statistic_image;
4332
4333 MagickBooleanType
4334 status;
4335
4336 MagickOffsetType
4337 progress;
4338
4339 PixelList
4340 **restrict pixel_list;
4341
cristy0834d642011-03-18 18:26:08 +00004342 ssize_t
cristy075ff302011-09-07 01:51:24 +00004343 center,
cristy0834d642011-03-18 18:26:08 +00004344 y;
4345
4346 /*
4347 Initialize statistics image attributes.
4348 */
4349 assert(image != (Image *) NULL);
4350 assert(image->signature == MagickSignature);
4351 if (image->debug != MagickFalse)
4352 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4353 assert(exception != (ExceptionInfo *) NULL);
4354 assert(exception->signature == MagickSignature);
cristy0834d642011-03-18 18:26:08 +00004355 statistic_image=CloneImage(image,image->columns,image->rows,MagickTrue,
4356 exception);
4357 if (statistic_image == (Image *) NULL)
4358 return((Image *) NULL);
cristyf36cbcb2011-09-07 13:28:22 +00004359 status=SetImageStorageClass(statistic_image,DirectClass,exception);
4360 if (status == MagickFalse)
cristy0834d642011-03-18 18:26:08 +00004361 {
cristy0834d642011-03-18 18:26:08 +00004362 statistic_image=DestroyImage(statistic_image);
4363 return((Image *) NULL);
4364 }
cristyf36cbcb2011-09-07 13:28:22 +00004365 pixel_list=AcquirePixelListThreadSet(MagickMax(width,1),MagickMax(height,1));
cristy0834d642011-03-18 18:26:08 +00004366 if (pixel_list == (PixelList **) NULL)
4367 {
4368 statistic_image=DestroyImage(statistic_image);
4369 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
4370 }
4371 /*
cristy8d752042011-03-19 01:00:36 +00004372 Make each pixel the min / max / median / mode / etc. of the neighborhood.
cristy0834d642011-03-18 18:26:08 +00004373 */
cristyf36cbcb2011-09-07 13:28:22 +00004374 center=(ssize_t) GetPixelChannels(image)*(image->columns+MagickMax(width,1))*
4375 (MagickMax(height,1)/2L)+GetPixelChannels(image)*(MagickMax(width,1)/2L);
cristy0834d642011-03-18 18:26:08 +00004376 status=MagickTrue;
4377 progress=0;
4378 image_view=AcquireCacheView(image);
4379 statistic_view=AcquireCacheView(statistic_image);
4380#if defined(MAGICKCORE_OPENMP_SUPPORT)
4381 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
4382#endif
4383 for (y=0; y < (ssize_t) statistic_image->rows; y++)
4384 {
4385 const int
4386 id = GetOpenMPThreadId();
4387
cristy4c08aed2011-07-01 19:47:50 +00004388 register const Quantum
cristy0834d642011-03-18 18:26:08 +00004389 *restrict p;
4390
cristy4c08aed2011-07-01 19:47:50 +00004391 register Quantum
cristy0834d642011-03-18 18:26:08 +00004392 *restrict q;
4393
4394 register ssize_t
4395 x;
4396
4397 if (status == MagickFalse)
4398 continue;
cristyf36cbcb2011-09-07 13:28:22 +00004399 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) MagickMax(width,1)/2L),y-
4400 (ssize_t) (MagickMax(height,1)/2L),image->columns+MagickMax(width,1),
4401 MagickMax(height,1),exception);
cristy3cba8ca2011-03-19 01:29:12 +00004402 q=QueueCacheViewAuthenticPixels(statistic_view,0,y,statistic_image->columns, 1,exception);
cristy4c08aed2011-07-01 19:47:50 +00004403 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy0834d642011-03-18 18:26:08 +00004404 {
4405 status=MagickFalse;
4406 continue;
4407 }
cristy0834d642011-03-18 18:26:08 +00004408 for (x=0; x < (ssize_t) statistic_image->columns; x++)
4409 {
cristy0834d642011-03-18 18:26:08 +00004410 register ssize_t
cristy075ff302011-09-07 01:51:24 +00004411 i;
cristy0834d642011-03-18 18:26:08 +00004412
cristy075ff302011-09-07 01:51:24 +00004413 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy0834d642011-03-18 18:26:08 +00004414 {
cristy075ff302011-09-07 01:51:24 +00004415 PixelChannel
4416 channel;
4417
4418 PixelTrait
4419 statistic_traits,
4420 traits;
4421
cristyf36cbcb2011-09-07 13:28:22 +00004422 Quantum
4423 pixel;
4424
cristy075ff302011-09-07 01:51:24 +00004425 register const Quantum
4426 *restrict pixels;
4427
4428 register ssize_t
4429 u;
4430
4431 ssize_t
4432 v;
4433
4434 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
4435 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
4436 statistic_traits=GetPixelChannelMapTraits(statistic_image,channel);
4437 if ((traits == UndefinedPixelTrait) ||
4438 (statistic_traits == UndefinedPixelTrait))
4439 continue;
4440 if ((statistic_traits & CopyPixelTrait) != 0)
4441 {
cristy0beccfa2011-09-25 20:47:53 +00004442 SetPixelChannel(statistic_image,channel,p[center+i],q);
cristy075ff302011-09-07 01:51:24 +00004443 continue;
4444 }
4445 pixels=p;
4446 ResetPixelList(pixel_list[id]);
cristyf36cbcb2011-09-07 13:28:22 +00004447 for (v=0; v < (ssize_t) MagickMax(height,1); v++)
cristy6fc86bb2011-03-18 23:45:16 +00004448 {
cristyf36cbcb2011-09-07 13:28:22 +00004449 for (u=0; u < (ssize_t) MagickMax(width,1); u++)
cristy075ff302011-09-07 01:51:24 +00004450 {
cristyf36cbcb2011-09-07 13:28:22 +00004451 InsertPixelList(image,pixels[i],pixel_list[id]);
cristy075ff302011-09-07 01:51:24 +00004452 pixels+=GetPixelChannels(image);
4453 }
4454 pixels+=image->columns*GetPixelChannels(image);
cristy6fc86bb2011-03-18 23:45:16 +00004455 }
cristy075ff302011-09-07 01:51:24 +00004456 switch (type)
cristy49f37242011-03-22 18:18:23 +00004457 {
cristy075ff302011-09-07 01:51:24 +00004458 case GradientStatistic:
4459 {
4460 MagickRealType
4461 maximum,
4462 minimum;
4463
cristyf36cbcb2011-09-07 13:28:22 +00004464 minimum=(MagickRealType) GetMinimumPixelList(pixel_list[id]);
4465 maximum=(MagickRealType) GetMaximumPixelList(pixel_list[id]);
4466 pixel=ClampToQuantum(MagickAbsoluteValue(maximum-minimum));
cristy075ff302011-09-07 01:51:24 +00004467 break;
4468 }
4469 case MaximumStatistic:
4470 {
4471 pixel=GetMaximumPixelList(pixel_list[id]);
4472 break;
4473 }
4474 case MeanStatistic:
4475 {
4476 pixel=GetMeanPixelList(pixel_list[id]);
4477 break;
4478 }
4479 case MedianStatistic:
4480 default:
4481 {
4482 pixel=GetMedianPixelList(pixel_list[id]);
4483 break;
4484 }
4485 case MinimumStatistic:
4486 {
4487 pixel=GetMinimumPixelList(pixel_list[id]);
4488 break;
4489 }
4490 case ModeStatistic:
4491 {
4492 pixel=GetModePixelList(pixel_list[id]);
4493 break;
4494 }
4495 case NonpeakStatistic:
4496 {
4497 pixel=GetNonpeakPixelList(pixel_list[id]);
4498 break;
4499 }
4500 case StandardDeviationStatistic:
4501 {
4502 pixel=GetStandardDeviationPixelList(pixel_list[id]);
4503 break;
4504 }
cristy49f37242011-03-22 18:18:23 +00004505 }
cristy0beccfa2011-09-25 20:47:53 +00004506 SetPixelChannel(statistic_image,channel,pixel,q);
cristy0834d642011-03-18 18:26:08 +00004507 }
cristyed231572011-07-14 02:18:59 +00004508 p+=GetPixelChannels(image);
4509 q+=GetPixelChannels(statistic_image);
cristy0834d642011-03-18 18:26:08 +00004510 }
4511 if (SyncCacheViewAuthenticPixels(statistic_view,exception) == MagickFalse)
4512 status=MagickFalse;
4513 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4514 {
4515 MagickBooleanType
4516 proceed;
4517
4518#if defined(MAGICKCORE_OPENMP_SUPPORT)
4519 #pragma omp critical (MagickCore_StatisticImage)
4520#endif
4521 proceed=SetImageProgress(image,StatisticImageTag,progress++,
4522 image->rows);
4523 if (proceed == MagickFalse)
4524 status=MagickFalse;
4525 }
4526 }
4527 statistic_view=DestroyCacheView(statistic_view);
4528 image_view=DestroyCacheView(image_view);
4529 pixel_list=DestroyPixelListThreadSet(pixel_list);
4530 return(statistic_image);
4531}
4532
4533/*
4534%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4535% %
4536% %
4537% %
cristy3ed852e2009-09-05 21:47:34 +00004538% U n s h a r p M a s k I m a g e %
4539% %
4540% %
4541% %
4542%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4543%
4544% UnsharpMaskImage() sharpens one or more image channels. We convolve the
4545% image with a Gaussian operator of the given radius and standard deviation
4546% (sigma). For reasonable results, radius should be larger than sigma. Use a
4547% radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
4548%
4549% The format of the UnsharpMaskImage method is:
4550%
4551% Image *UnsharpMaskImage(const Image *image,const double radius,
4552% const double sigma,const double amount,const double threshold,
4553% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00004554%
4555% A description of each parameter follows:
4556%
4557% o image: the image.
4558%
cristy3ed852e2009-09-05 21:47:34 +00004559% o radius: the radius of the Gaussian, in pixels, not counting the center
4560% pixel.
4561%
4562% o sigma: the standard deviation of the Gaussian, in pixels.
4563%
4564% o amount: the percentage of the difference between the original and the
4565% blur image that is added back into the original.
4566%
4567% o threshold: the threshold in pixels needed to apply the diffence amount.
4568%
4569% o exception: return any errors or warnings in this structure.
4570%
4571*/
cristyf4ad9df2011-07-08 16:49:03 +00004572MagickExport Image *UnsharpMaskImage(const Image *image,
4573 const double radius,const double sigma,const double amount,
4574 const double threshold,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00004575{
4576#define SharpenImageTag "Sharpen/Image"
4577
cristyc4c8d132010-01-07 01:58:38 +00004578 CacheView
4579 *image_view,
4580 *unsharp_view;
4581
cristy3ed852e2009-09-05 21:47:34 +00004582 Image
4583 *unsharp_image;
4584
cristy3ed852e2009-09-05 21:47:34 +00004585 MagickBooleanType
4586 status;
4587
cristybb503372010-05-27 20:51:26 +00004588 MagickOffsetType
4589 progress;
4590
cristy3ed852e2009-09-05 21:47:34 +00004591 MagickRealType
4592 quantum_threshold;
4593
cristybb503372010-05-27 20:51:26 +00004594 ssize_t
4595 y;
4596
cristy3ed852e2009-09-05 21:47:34 +00004597 assert(image != (const Image *) NULL);
4598 assert(image->signature == MagickSignature);
4599 if (image->debug != MagickFalse)
4600 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4601 assert(exception != (ExceptionInfo *) NULL);
cristy05c0c9a2011-09-05 23:16:13 +00004602 unsharp_image=BlurImage(image,radius,sigma,image->bias,exception);
cristy3ed852e2009-09-05 21:47:34 +00004603 if (unsharp_image == (Image *) NULL)
4604 return((Image *) NULL);
4605 quantum_threshold=(MagickRealType) QuantumRange*threshold;
4606 /*
4607 Unsharp-mask image.
4608 */
4609 status=MagickTrue;
4610 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00004611 image_view=AcquireCacheView(image);
4612 unsharp_view=AcquireCacheView(unsharp_image);
cristyb5d5f722009-11-04 03:03:49 +00004613#if defined(MAGICKCORE_OPENMP_SUPPORT)
4614 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004615#endif
cristybb503372010-05-27 20:51:26 +00004616 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00004617 {
cristy4c08aed2011-07-01 19:47:50 +00004618 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00004619 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00004620
cristy4c08aed2011-07-01 19:47:50 +00004621 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00004622 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004623
cristy117ff172010-08-15 21:35:32 +00004624 register ssize_t
4625 x;
4626
cristy3ed852e2009-09-05 21:47:34 +00004627 if (status == MagickFalse)
4628 continue;
4629 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
4630 q=GetCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
4631 exception);
cristy4c08aed2011-07-01 19:47:50 +00004632 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00004633 {
4634 status=MagickFalse;
4635 continue;
4636 }
cristybb503372010-05-27 20:51:26 +00004637 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004638 {
cristy7f3a0d12011-09-05 23:27:59 +00004639 register ssize_t
4640 i;
4641
4642 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4643 {
4644 MagickRealType
4645 pixel;
4646
4647 PixelChannel
4648 channel;
4649
4650 PixelTrait
4651 traits,
4652 unsharp_traits;
4653
4654 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
4655 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
4656 unsharp_traits=GetPixelChannelMapTraits(unsharp_image,channel);
4657 if ((traits == UndefinedPixelTrait) ||
4658 (unsharp_traits == UndefinedPixelTrait))
4659 continue;
4660 if ((unsharp_traits & CopyPixelTrait) != 0)
4661 {
cristy0beccfa2011-09-25 20:47:53 +00004662 SetPixelChannel(unsharp_image,channel,p[i],q);
cristy7f3a0d12011-09-05 23:27:59 +00004663 continue;
4664 }
cristy0beccfa2011-09-25 20:47:53 +00004665 pixel=p[i]-(MagickRealType) GetPixelChannel(unsharp_image,channel,q);
cristy7f3a0d12011-09-05 23:27:59 +00004666 if (fabs(2.0*pixel) < quantum_threshold)
4667 pixel=(MagickRealType) p[i];
4668 else
4669 pixel=(MagickRealType) p[i]+amount*pixel;
cristy0beccfa2011-09-25 20:47:53 +00004670 SetPixelChannel(unsharp_image,channel,ClampToQuantum(pixel),q);
cristy7f3a0d12011-09-05 23:27:59 +00004671 }
cristyed231572011-07-14 02:18:59 +00004672 p+=GetPixelChannels(image);
4673 q+=GetPixelChannels(unsharp_image);
cristy3ed852e2009-09-05 21:47:34 +00004674 }
4675 if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
4676 status=MagickFalse;
4677 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4678 {
4679 MagickBooleanType
4680 proceed;
4681
cristyb5d5f722009-11-04 03:03:49 +00004682#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00004683 #pragma omp critical (MagickCore_UnsharpMaskImage)
cristy3ed852e2009-09-05 21:47:34 +00004684#endif
4685 proceed=SetImageProgress(image,SharpenImageTag,progress++,image->rows);
4686 if (proceed == MagickFalse)
4687 status=MagickFalse;
4688 }
4689 }
4690 unsharp_image->type=image->type;
4691 unsharp_view=DestroyCacheView(unsharp_view);
4692 image_view=DestroyCacheView(image_view);
4693 if (status == MagickFalse)
4694 unsharp_image=DestroyImage(unsharp_image);
4695 return(unsharp_image);
4696}