blob: 98ea3197aa32d82e5fabd459461d834e6d552491 [file] [log] [blame]
cristy3ed852e2009-09-05 21:47:34 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% EEEEE FFFFF FFFFF EEEEE CCCC TTTTT %
7% E F F E C T %
8% EEE FFF FFF EEE C T %
9% E F F E C T %
10% EEEEE F F EEEEE CCCC T %
11% %
12% %
13% MagickCore Image Effects Methods %
14% %
15% Software Design %
16% John Cristy %
17% October 1996 %
18% %
19% %
cristy7e41fe82010-12-04 23:12:08 +000020% Copyright 1999-2011 ImageMagick Studio LLC, a non-profit organization %
cristy3ed852e2009-09-05 21:47:34 +000021% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
26% http://www.imagemagick.org/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
36%
37%
38*/
39
40/*
41 Include declarations.
42*/
cristy4c08aed2011-07-01 19:47:50 +000043#include "MagickCore/studio.h"
44#include "MagickCore/accelerate.h"
45#include "MagickCore/blob.h"
46#include "MagickCore/cache-view.h"
47#include "MagickCore/color.h"
48#include "MagickCore/color-private.h"
49#include "MagickCore/colorspace.h"
50#include "MagickCore/constitute.h"
51#include "MagickCore/decorate.h"
52#include "MagickCore/draw.h"
53#include "MagickCore/enhance.h"
54#include "MagickCore/exception.h"
55#include "MagickCore/exception-private.h"
56#include "MagickCore/effect.h"
57#include "MagickCore/fx.h"
58#include "MagickCore/gem.h"
cristy8ea81222011-09-04 10:33:32 +000059#include "MagickCore/gem-private.h"
cristy4c08aed2011-07-01 19:47:50 +000060#include "MagickCore/geometry.h"
61#include "MagickCore/image-private.h"
62#include "MagickCore/list.h"
63#include "MagickCore/log.h"
64#include "MagickCore/memory_.h"
65#include "MagickCore/monitor.h"
66#include "MagickCore/monitor-private.h"
67#include "MagickCore/montage.h"
68#include "MagickCore/morphology.h"
69#include "MagickCore/paint.h"
70#include "MagickCore/pixel-accessor.h"
71#include "MagickCore/property.h"
72#include "MagickCore/quantize.h"
73#include "MagickCore/quantum.h"
74#include "MagickCore/quantum-private.h"
75#include "MagickCore/random_.h"
76#include "MagickCore/random-private.h"
77#include "MagickCore/resample.h"
78#include "MagickCore/resample-private.h"
79#include "MagickCore/resize.h"
80#include "MagickCore/resource_.h"
81#include "MagickCore/segment.h"
82#include "MagickCore/shear.h"
83#include "MagickCore/signature-private.h"
84#include "MagickCore/string_.h"
85#include "MagickCore/thread-private.h"
86#include "MagickCore/transform.h"
87#include "MagickCore/threshold.h"
cristy3ed852e2009-09-05 21:47:34 +000088
89/*
90%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
91% %
92% %
93% %
94% A d a p t i v e B l u r I m a g e %
95% %
96% %
97% %
98%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
99%
100% AdaptiveBlurImage() adaptively blurs the image by blurring less
101% intensely near image edges and more intensely far from edges. We blur the
102% image with a Gaussian operator of the given radius and standard deviation
103% (sigma). For reasonable results, radius should be larger than sigma. Use a
104% radius of 0 and AdaptiveBlurImage() selects a suitable radius for you.
105%
106% The format of the AdaptiveBlurImage method is:
107%
108% Image *AdaptiveBlurImage(const Image *image,const double radius,
cristy4c11c2b2011-09-05 20:17:07 +0000109% const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000110%
111% A description of each parameter follows:
112%
113% o image: the image.
114%
cristy3ed852e2009-09-05 21:47:34 +0000115% o radius: the radius of the Gaussian, in pixels, not counting the center
116% pixel.
117%
118% o sigma: the standard deviation of the Laplacian, in pixels.
119%
cristy4c11c2b2011-09-05 20:17:07 +0000120% o bias: the bias.
121%
cristy3ed852e2009-09-05 21:47:34 +0000122% o exception: return any errors or warnings in this structure.
123%
124*/
125
cristyf89cb1d2011-07-07 01:24:37 +0000126MagickExport MagickBooleanType AdaptiveLevelImage(Image *image,
cristy051718b2011-08-28 22:49:25 +0000127 const char *levels,ExceptionInfo *exception)
cristyf89cb1d2011-07-07 01:24:37 +0000128{
129 double
130 black_point,
131 gamma,
132 white_point;
133
134 GeometryInfo
135 geometry_info;
136
137 MagickBooleanType
138 status;
139
140 MagickStatusType
141 flags;
142
143 /*
144 Parse levels.
145 */
146 if (levels == (char *) NULL)
147 return(MagickFalse);
148 flags=ParseGeometry(levels,&geometry_info);
149 black_point=geometry_info.rho;
150 white_point=(double) QuantumRange;
151 if ((flags & SigmaValue) != 0)
152 white_point=geometry_info.sigma;
153 gamma=1.0;
154 if ((flags & XiValue) != 0)
155 gamma=geometry_info.xi;
156 if ((flags & PercentValue) != 0)
157 {
158 black_point*=(double) image->columns*image->rows/100.0;
159 white_point*=(double) image->columns*image->rows/100.0;
160 }
161 if ((flags & SigmaValue) == 0)
162 white_point=(double) QuantumRange-black_point;
163 if ((flags & AspectValue ) == 0)
cristy7c0a0a42011-08-23 17:57:25 +0000164 status=LevelImage(image,black_point,white_point,gamma,exception);
cristyf89cb1d2011-07-07 01:24:37 +0000165 else
cristy7c0a0a42011-08-23 17:57:25 +0000166 status=LevelizeImage(image,black_point,white_point,gamma,exception);
cristyf89cb1d2011-07-07 01:24:37 +0000167 return(status);
168}
169
cristyf4ad9df2011-07-08 16:49:03 +0000170MagickExport Image *AdaptiveBlurImage(const Image *image,
cristy4c11c2b2011-09-05 20:17:07 +0000171 const double radius,const double sigma,const double bias,
172 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000173{
174#define AdaptiveBlurImageTag "Convolve/Image"
175#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
176
cristyc4c8d132010-01-07 01:58:38 +0000177 CacheView
178 *blur_view,
179 *edge_view,
180 *image_view;
181
cristy3ed852e2009-09-05 21:47:34 +0000182 double
cristy47e00502009-12-17 19:19:57 +0000183 **kernel,
184 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000185
186 Image
187 *blur_image,
188 *edge_image,
189 *gaussian_image;
190
cristy3ed852e2009-09-05 21:47:34 +0000191 MagickBooleanType
192 status;
193
cristybb503372010-05-27 20:51:26 +0000194 MagickOffsetType
195 progress;
196
cristybb503372010-05-27 20:51:26 +0000197 register ssize_t
cristy47e00502009-12-17 19:19:57 +0000198 i;
cristy3ed852e2009-09-05 21:47:34 +0000199
cristybb503372010-05-27 20:51:26 +0000200 size_t
cristy3ed852e2009-09-05 21:47:34 +0000201 width;
202
cristybb503372010-05-27 20:51:26 +0000203 ssize_t
204 j,
205 k,
206 u,
207 v,
208 y;
209
cristy3ed852e2009-09-05 21:47:34 +0000210 assert(image != (const Image *) NULL);
211 assert(image->signature == MagickSignature);
212 if (image->debug != MagickFalse)
213 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
214 assert(exception != (ExceptionInfo *) NULL);
215 assert(exception->signature == MagickSignature);
216 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
217 if (blur_image == (Image *) NULL)
218 return((Image *) NULL);
219 if (fabs(sigma) <= MagickEpsilon)
220 return(blur_image);
cristy574cc262011-08-05 01:23:58 +0000221 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000222 {
cristy3ed852e2009-09-05 21:47:34 +0000223 blur_image=DestroyImage(blur_image);
224 return((Image *) NULL);
225 }
226 /*
227 Edge detect the image brighness channel, level, blur, and level again.
228 */
cristy8ae632d2011-09-05 17:29:53 +0000229 edge_image=EdgeImage(image,radius,sigma,exception);
cristy3ed852e2009-09-05 21:47:34 +0000230 if (edge_image == (Image *) NULL)
231 {
232 blur_image=DestroyImage(blur_image);
233 return((Image *) NULL);
234 }
cristy051718b2011-08-28 22:49:25 +0000235 (void) AdaptiveLevelImage(edge_image,"20%,95%",exception);
cristy05c0c9a2011-09-05 23:16:13 +0000236 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,bias,exception);
cristy3ed852e2009-09-05 21:47:34 +0000237 if (gaussian_image != (Image *) NULL)
238 {
239 edge_image=DestroyImage(edge_image);
240 edge_image=gaussian_image;
241 }
cristy051718b2011-08-28 22:49:25 +0000242 (void) AdaptiveLevelImage(edge_image,"10%,95%",exception);
cristy3ed852e2009-09-05 21:47:34 +0000243 /*
244 Create a set of kernels from maximum (radius,sigma) to minimum.
245 */
246 width=GetOptimalKernelWidth2D(radius,sigma);
247 kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
248 if (kernel == (double **) NULL)
249 {
250 edge_image=DestroyImage(edge_image);
251 blur_image=DestroyImage(blur_image);
252 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
253 }
254 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
cristybb503372010-05-27 20:51:26 +0000255 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000256 {
257 kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)*
258 sizeof(**kernel));
259 if (kernel[i] == (double *) NULL)
260 break;
cristy47e00502009-12-17 19:19:57 +0000261 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000262 j=(ssize_t) (width-i)/2;
cristy47e00502009-12-17 19:19:57 +0000263 k=0;
264 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000265 {
cristy47e00502009-12-17 19:19:57 +0000266 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000267 {
cristy4205a3c2010-09-12 20:19:59 +0000268 kernel[i][k]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
269 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +0000270 normalize+=kernel[i][k];
271 k++;
cristy3ed852e2009-09-05 21:47:34 +0000272 }
273 }
cristy3ed852e2009-09-05 21:47:34 +0000274 if (fabs(normalize) <= MagickEpsilon)
275 normalize=1.0;
276 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000277 for (k=0; k < (j*j); k++)
278 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000279 }
cristybb503372010-05-27 20:51:26 +0000280 if (i < (ssize_t) width)
cristy3ed852e2009-09-05 21:47:34 +0000281 {
282 for (i-=2; i >= 0; i-=2)
283 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
284 kernel=(double **) RelinquishMagickMemory(kernel);
285 edge_image=DestroyImage(edge_image);
286 blur_image=DestroyImage(blur_image);
287 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
288 }
289 /*
290 Adaptively blur image.
291 */
292 status=MagickTrue;
293 progress=0;
cristy3ed852e2009-09-05 21:47:34 +0000294 image_view=AcquireCacheView(image);
295 edge_view=AcquireCacheView(edge_image);
296 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000297#if defined(MAGICKCORE_OPENMP_SUPPORT)
298 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000299#endif
cristybb503372010-05-27 20:51:26 +0000300 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000301 {
cristy4c08aed2011-07-01 19:47:50 +0000302 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000303 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000304
cristy4c08aed2011-07-01 19:47:50 +0000305 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000306 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000307
cristy117ff172010-08-15 21:35:32 +0000308 register ssize_t
309 x;
310
cristy3ed852e2009-09-05 21:47:34 +0000311 if (status == MagickFalse)
312 continue;
313 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
314 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
315 exception);
cristyacd2ed22011-08-30 01:44:23 +0000316 if ((r == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000317 {
318 status=MagickFalse;
319 continue;
320 }
cristybb503372010-05-27 20:51:26 +0000321 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000322 {
cristy4c11c2b2011-09-05 20:17:07 +0000323 register const Quantum
324 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000325
cristybb503372010-05-27 20:51:26 +0000326 register ssize_t
cristy4c11c2b2011-09-05 20:17:07 +0000327 i;
cristy3ed852e2009-09-05 21:47:34 +0000328
cristy4c11c2b2011-09-05 20:17:07 +0000329 ssize_t
330 center,
331 j;
332
333 j=(ssize_t) ceil((double) width*QuantumScale*
cristy4c08aed2011-07-01 19:47:50 +0000334 GetPixelIntensity(edge_image,r)-0.5);
cristy4c11c2b2011-09-05 20:17:07 +0000335 if (j < 0)
336 j=0;
cristy3ed852e2009-09-05 21:47:34 +0000337 else
cristy4c11c2b2011-09-05 20:17:07 +0000338 if (j > (ssize_t) width)
339 j=(ssize_t) width;
340 if ((j & 0x01) != 0)
341 j--;
342 p=GetCacheViewVirtualPixels(image_view,x-((ssize_t) (width-j)/2L),y-
343 (ssize_t) ((width-j)/2L),width-j,width-j,exception);
cristy4c08aed2011-07-01 19:47:50 +0000344 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000345 break;
cristy4c11c2b2011-09-05 20:17:07 +0000346 center=(ssize_t) GetPixelChannels(image)*(width-j)*
cristy075ff302011-09-07 01:51:24 +0000347 ((width-j)/2L)+GetPixelChannels(image)*((width-j)/2L);
cristy4c11c2b2011-09-05 20:17:07 +0000348 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy3ed852e2009-09-05 21:47:34 +0000349 {
cristy4c11c2b2011-09-05 20:17:07 +0000350 MagickRealType
351 alpha,
352 gamma,
353 pixel;
354
355 PixelChannel
356 channel;
357
358 PixelTrait
359 blur_traits,
360 traits;
361
362 register const double
363 *restrict k;
364
365 register const Quantum
366 *restrict pixels;
367
368 register ssize_t
369 u;
370
371 ssize_t
372 v;
373
374 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
375 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
376 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
377 if ((traits == UndefinedPixelTrait) ||
378 (blur_traits == UndefinedPixelTrait))
379 continue;
380 if ((blur_traits & CopyPixelTrait) != 0)
381 {
382 q[channel]=p[center+i];
383 continue;
384 }
385 k=kernel[j];
386 pixels=p;
387 pixel=bias;
388 gamma=0.0;
389 if ((blur_traits & BlendPixelTrait) == 0)
390 {
391 /*
392 No alpha blending.
393 */
394 for (v=0; v < (ssize_t) (width-j); v++)
395 {
396 for (u=0; u < (ssize_t) (width-j); u++)
397 {
398 pixel+=(*k)*pixels[i];
399 gamma+=(*k);
400 k++;
401 pixels+=GetPixelChannels(image);
402 }
403 }
404 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
405 q[channel]=ClampToQuantum(gamma*pixel);
406 continue;
407 }
408 /*
409 Alpha blending.
410 */
411 for (v=0; v < (ssize_t) (width-j); v++)
cristy3ed852e2009-09-05 21:47:34 +0000412 {
cristy4c11c2b2011-09-05 20:17:07 +0000413 for (u=0; u < (ssize_t) (width-j); u++)
414 {
415 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,pixels));
416 pixel+=(*k)*alpha*pixels[i];
417 gamma+=(*k)*alpha;
418 k++;
419 pixels+=GetPixelChannels(image);
420 }
cristy3ed852e2009-09-05 21:47:34 +0000421 }
cristy4c11c2b2011-09-05 20:17:07 +0000422 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
423 q[channel]=ClampToQuantum(gamma*pixel);
cristy3ed852e2009-09-05 21:47:34 +0000424 }
cristyed231572011-07-14 02:18:59 +0000425 q+=GetPixelChannels(blur_image);
426 r+=GetPixelChannels(edge_image);
cristy3ed852e2009-09-05 21:47:34 +0000427 }
428 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
429 status=MagickFalse;
430 if (image->progress_monitor != (MagickProgressMonitor) NULL)
431 {
432 MagickBooleanType
433 proceed;
434
cristyb5d5f722009-11-04 03:03:49 +0000435#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy8ae632d2011-09-05 17:29:53 +0000436 #pragma omp critical (MagickCore_AdaptiveBlurImage)
cristy3ed852e2009-09-05 21:47:34 +0000437#endif
438 proceed=SetImageProgress(image,AdaptiveBlurImageTag,progress++,
439 image->rows);
440 if (proceed == MagickFalse)
441 status=MagickFalse;
442 }
443 }
444 blur_image->type=image->type;
445 blur_view=DestroyCacheView(blur_view);
446 edge_view=DestroyCacheView(edge_view);
447 image_view=DestroyCacheView(image_view);
448 edge_image=DestroyImage(edge_image);
cristybb503372010-05-27 20:51:26 +0000449 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000450 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
451 kernel=(double **) RelinquishMagickMemory(kernel);
452 if (status == MagickFalse)
453 blur_image=DestroyImage(blur_image);
454 return(blur_image);
455}
456
457/*
458%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
459% %
460% %
461% %
462% A d a p t i v e S h a r p e n I m a g e %
463% %
464% %
465% %
466%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
467%
468% AdaptiveSharpenImage() adaptively sharpens the image by sharpening more
469% intensely near image edges and less intensely far from edges. We sharpen the
470% image with a Gaussian operator of the given radius and standard deviation
471% (sigma). For reasonable results, radius should be larger than sigma. Use a
472% radius of 0 and AdaptiveSharpenImage() selects a suitable radius for you.
473%
474% The format of the AdaptiveSharpenImage method is:
475%
476% Image *AdaptiveSharpenImage(const Image *image,const double radius,
cristy4c11c2b2011-09-05 20:17:07 +0000477% const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000478%
479% A description of each parameter follows:
480%
481% o image: the image.
482%
cristy3ed852e2009-09-05 21:47:34 +0000483% o radius: the radius of the Gaussian, in pixels, not counting the center
484% pixel.
485%
486% o sigma: the standard deviation of the Laplacian, in pixels.
487%
cristy4c11c2b2011-09-05 20:17:07 +0000488% o bias: the bias.
489%
cristy3ed852e2009-09-05 21:47:34 +0000490% o exception: return any errors or warnings in this structure.
491%
492*/
cristy3ed852e2009-09-05 21:47:34 +0000493MagickExport Image *AdaptiveSharpenImage(const Image *image,const double radius,
cristy4c11c2b2011-09-05 20:17:07 +0000494 const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000495{
cristy3ed852e2009-09-05 21:47:34 +0000496#define AdaptiveSharpenImageTag "Convolve/Image"
497#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
498
cristyc4c8d132010-01-07 01:58:38 +0000499 CacheView
500 *sharp_view,
501 *edge_view,
502 *image_view;
503
cristy3ed852e2009-09-05 21:47:34 +0000504 double
cristy47e00502009-12-17 19:19:57 +0000505 **kernel,
506 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000507
508 Image
509 *sharp_image,
510 *edge_image,
511 *gaussian_image;
512
cristy3ed852e2009-09-05 21:47:34 +0000513 MagickBooleanType
514 status;
515
cristybb503372010-05-27 20:51:26 +0000516 MagickOffsetType
517 progress;
518
cristybb503372010-05-27 20:51:26 +0000519 register ssize_t
cristy47e00502009-12-17 19:19:57 +0000520 i;
cristy3ed852e2009-09-05 21:47:34 +0000521
cristybb503372010-05-27 20:51:26 +0000522 size_t
cristy3ed852e2009-09-05 21:47:34 +0000523 width;
524
cristybb503372010-05-27 20:51:26 +0000525 ssize_t
526 j,
527 k,
528 u,
529 v,
530 y;
531
cristy3ed852e2009-09-05 21:47:34 +0000532 assert(image != (const Image *) NULL);
533 assert(image->signature == MagickSignature);
534 if (image->debug != MagickFalse)
535 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
536 assert(exception != (ExceptionInfo *) NULL);
537 assert(exception->signature == MagickSignature);
538 sharp_image=CloneImage(image,0,0,MagickTrue,exception);
539 if (sharp_image == (Image *) NULL)
540 return((Image *) NULL);
541 if (fabs(sigma) <= MagickEpsilon)
542 return(sharp_image);
cristy574cc262011-08-05 01:23:58 +0000543 if (SetImageStorageClass(sharp_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000544 {
cristy3ed852e2009-09-05 21:47:34 +0000545 sharp_image=DestroyImage(sharp_image);
546 return((Image *) NULL);
547 }
548 /*
549 Edge detect the image brighness channel, level, sharp, and level again.
550 */
cristy8ae632d2011-09-05 17:29:53 +0000551 edge_image=EdgeImage(image,radius,sigma,exception);
cristy3ed852e2009-09-05 21:47:34 +0000552 if (edge_image == (Image *) NULL)
553 {
554 sharp_image=DestroyImage(sharp_image);
555 return((Image *) NULL);
556 }
cristy051718b2011-08-28 22:49:25 +0000557 (void) AdaptiveLevelImage(edge_image,"20%,95%",exception);
cristy05c0c9a2011-09-05 23:16:13 +0000558 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,bias,exception);
cristy3ed852e2009-09-05 21:47:34 +0000559 if (gaussian_image != (Image *) NULL)
560 {
561 edge_image=DestroyImage(edge_image);
562 edge_image=gaussian_image;
563 }
cristy051718b2011-08-28 22:49:25 +0000564 (void) AdaptiveLevelImage(edge_image,"10%,95%",exception);
cristy3ed852e2009-09-05 21:47:34 +0000565 /*
566 Create a set of kernels from maximum (radius,sigma) to minimum.
567 */
568 width=GetOptimalKernelWidth2D(radius,sigma);
569 kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
570 if (kernel == (double **) NULL)
571 {
572 edge_image=DestroyImage(edge_image);
573 sharp_image=DestroyImage(sharp_image);
574 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
575 }
576 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
cristybb503372010-05-27 20:51:26 +0000577 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000578 {
579 kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)*
580 sizeof(**kernel));
581 if (kernel[i] == (double *) NULL)
582 break;
cristy47e00502009-12-17 19:19:57 +0000583 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000584 j=(ssize_t) (width-i)/2;
cristy47e00502009-12-17 19:19:57 +0000585 k=0;
586 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000587 {
cristy47e00502009-12-17 19:19:57 +0000588 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000589 {
cristy4205a3c2010-09-12 20:19:59 +0000590 kernel[i][k]=(double) (-exp(-((double) u*u+v*v)/(2.0*MagickSigma*
591 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +0000592 normalize+=kernel[i][k];
593 k++;
cristy3ed852e2009-09-05 21:47:34 +0000594 }
595 }
cristy3ed852e2009-09-05 21:47:34 +0000596 if (fabs(normalize) <= MagickEpsilon)
597 normalize=1.0;
598 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000599 for (k=0; k < (j*j); k++)
600 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000601 }
cristybb503372010-05-27 20:51:26 +0000602 if (i < (ssize_t) width)
cristy3ed852e2009-09-05 21:47:34 +0000603 {
604 for (i-=2; i >= 0; i-=2)
605 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
606 kernel=(double **) RelinquishMagickMemory(kernel);
607 edge_image=DestroyImage(edge_image);
608 sharp_image=DestroyImage(sharp_image);
609 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
610 }
611 /*
612 Adaptively sharpen image.
613 */
614 status=MagickTrue;
615 progress=0;
cristy3ed852e2009-09-05 21:47:34 +0000616 image_view=AcquireCacheView(image);
617 edge_view=AcquireCacheView(edge_image);
618 sharp_view=AcquireCacheView(sharp_image);
cristyb5d5f722009-11-04 03:03:49 +0000619#if defined(MAGICKCORE_OPENMP_SUPPORT)
620 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000621#endif
cristybb503372010-05-27 20:51:26 +0000622 for (y=0; y < (ssize_t) sharp_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000623 {
cristy4c08aed2011-07-01 19:47:50 +0000624 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000625 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000626
cristy4c08aed2011-07-01 19:47:50 +0000627 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000628 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000629
cristy117ff172010-08-15 21:35:32 +0000630 register ssize_t
631 x;
632
cristy3ed852e2009-09-05 21:47:34 +0000633 if (status == MagickFalse)
634 continue;
635 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
636 q=QueueCacheViewAuthenticPixels(sharp_view,0,y,sharp_image->columns,1,
637 exception);
cristy4c08aed2011-07-01 19:47:50 +0000638 if ((r == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000639 {
640 status=MagickFalse;
641 continue;
642 }
cristybb503372010-05-27 20:51:26 +0000643 for (x=0; x < (ssize_t) sharp_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000644 {
cristy4c11c2b2011-09-05 20:17:07 +0000645 register const Quantum
646 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000647
cristybb503372010-05-27 20:51:26 +0000648 register ssize_t
cristy4c11c2b2011-09-05 20:17:07 +0000649 i;
cristy3ed852e2009-09-05 21:47:34 +0000650
cristy4c11c2b2011-09-05 20:17:07 +0000651 ssize_t
652 center,
653 j;
654
655 j=(ssize_t) ceil((double) width*QuantumScale*
cristy4c08aed2011-07-01 19:47:50 +0000656 GetPixelIntensity(edge_image,r)-0.5);
cristy4c11c2b2011-09-05 20:17:07 +0000657 if (j < 0)
658 j=0;
cristy3ed852e2009-09-05 21:47:34 +0000659 else
cristy4c11c2b2011-09-05 20:17:07 +0000660 if (j > (ssize_t) width)
661 j=(ssize_t) width;
662 if ((j & 0x01) != 0)
663 j--;
664 p=GetCacheViewVirtualPixels(image_view,x-((ssize_t) (width-j)/2L),y-
665 (ssize_t) ((width-j)/2L),width-j,width-j,exception);
cristy4c08aed2011-07-01 19:47:50 +0000666 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000667 break;
cristy4c11c2b2011-09-05 20:17:07 +0000668 center=(ssize_t) GetPixelChannels(image)*(width-j)*
669 ((width-j)/2L)+GetPixelChannels(image)*((width-j)/2);
670 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy3ed852e2009-09-05 21:47:34 +0000671 {
cristy4c11c2b2011-09-05 20:17:07 +0000672 MagickRealType
673 alpha,
674 gamma,
675 pixel;
676
677 PixelChannel
678 channel;
679
680 PixelTrait
681 sharp_traits,
682 traits;
683
684 register const double
685 *restrict k;
686
687 register const Quantum
688 *restrict pixels;
689
690 register ssize_t
691 u;
692
693 ssize_t
694 v;
695
696 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
697 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
698 sharp_traits=GetPixelChannelMapTraits(sharp_image,channel);
699 if ((traits == UndefinedPixelTrait) ||
700 (sharp_traits == UndefinedPixelTrait))
701 continue;
702 if ((sharp_traits & CopyPixelTrait) != 0)
703 {
704 q[channel]=p[center+i];
705 continue;
706 }
707 k=kernel[j];
708 pixels=p;
709 pixel=bias;
710 gamma=0.0;
711 if ((sharp_traits & BlendPixelTrait) == 0)
712 {
713 /*
714 No alpha blending.
715 */
716 for (v=0; v < (ssize_t) (width-j); v++)
717 {
718 for (u=0; u < (ssize_t) (width-j); u++)
719 {
720 pixel+=(*k)*pixels[i];
721 gamma+=(*k);
722 k++;
723 pixels+=GetPixelChannels(image);
724 }
725 }
726 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
727 q[channel]=ClampToQuantum(gamma*pixel);
728 continue;
729 }
730 /*
731 Alpha blending.
732 */
733 for (v=0; v < (ssize_t) (width-j); v++)
cristy3ed852e2009-09-05 21:47:34 +0000734 {
cristy4c11c2b2011-09-05 20:17:07 +0000735 for (u=0; u < (ssize_t) (width-j); u++)
736 {
737 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,pixels));
738 pixel+=(*k)*alpha*pixels[i];
739 gamma+=(*k)*alpha;
740 k++;
741 pixels+=GetPixelChannels(image);
742 }
cristy3ed852e2009-09-05 21:47:34 +0000743 }
cristy4c11c2b2011-09-05 20:17:07 +0000744 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
745 q[channel]=ClampToQuantum(gamma*pixel);
cristy3ed852e2009-09-05 21:47:34 +0000746 }
cristyed231572011-07-14 02:18:59 +0000747 q+=GetPixelChannels(sharp_image);
748 r+=GetPixelChannels(edge_image);
cristy3ed852e2009-09-05 21:47:34 +0000749 }
750 if (SyncCacheViewAuthenticPixels(sharp_view,exception) == MagickFalse)
751 status=MagickFalse;
752 if (image->progress_monitor != (MagickProgressMonitor) NULL)
753 {
754 MagickBooleanType
755 proceed;
756
cristyb5d5f722009-11-04 03:03:49 +0000757#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +0000758 #pragma omp critical (MagickCore_AdaptiveSharpenImage)
cristy3ed852e2009-09-05 21:47:34 +0000759#endif
760 proceed=SetImageProgress(image,AdaptiveSharpenImageTag,progress++,
761 image->rows);
762 if (proceed == MagickFalse)
763 status=MagickFalse;
764 }
765 }
766 sharp_image->type=image->type;
767 sharp_view=DestroyCacheView(sharp_view);
768 edge_view=DestroyCacheView(edge_view);
769 image_view=DestroyCacheView(image_view);
770 edge_image=DestroyImage(edge_image);
cristybb503372010-05-27 20:51:26 +0000771 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000772 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
773 kernel=(double **) RelinquishMagickMemory(kernel);
774 if (status == MagickFalse)
775 sharp_image=DestroyImage(sharp_image);
776 return(sharp_image);
777}
778
779/*
780%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
781% %
782% %
783% %
784% B l u r I m a g e %
785% %
786% %
787% %
788%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
789%
790% BlurImage() blurs an image. We convolve the image with a Gaussian operator
791% of the given radius and standard deviation (sigma). For reasonable results,
792% the radius should be larger than sigma. Use a radius of 0 and BlurImage()
793% selects a suitable radius for you.
794%
795% BlurImage() differs from GaussianBlurImage() in that it uses a separable
796% kernel which is faster but mathematically equivalent to the non-separable
797% kernel.
798%
799% The format of the BlurImage method is:
800%
801% Image *BlurImage(const Image *image,const double radius,
cristy05c0c9a2011-09-05 23:16:13 +0000802% const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000803%
804% A description of each parameter follows:
805%
806% o image: the image.
807%
cristy3ed852e2009-09-05 21:47:34 +0000808% o radius: the radius of the Gaussian, in pixels, not counting the center
809% pixel.
810%
811% o sigma: the standard deviation of the Gaussian, in pixels.
812%
cristy05c0c9a2011-09-05 23:16:13 +0000813% o bias: the bias.
814%
cristy3ed852e2009-09-05 21:47:34 +0000815% o exception: return any errors or warnings in this structure.
816%
817*/
818
cristybb503372010-05-27 20:51:26 +0000819static double *GetBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +0000820{
cristy3ed852e2009-09-05 21:47:34 +0000821 double
cristy47e00502009-12-17 19:19:57 +0000822 *kernel,
823 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000824
cristy117ff172010-08-15 21:35:32 +0000825 register ssize_t
826 i;
827
cristybb503372010-05-27 20:51:26 +0000828 ssize_t
cristy47e00502009-12-17 19:19:57 +0000829 j,
830 k;
cristy3ed852e2009-09-05 21:47:34 +0000831
cristy3ed852e2009-09-05 21:47:34 +0000832 /*
833 Generate a 1-D convolution kernel.
834 */
835 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
836 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
837 if (kernel == (double *) NULL)
838 return(0);
cristy3ed852e2009-09-05 21:47:34 +0000839 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000840 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +0000841 i=0;
842 for (k=(-j); k <= j; k++)
843 {
cristy4205a3c2010-09-12 20:19:59 +0000844 kernel[i]=(double) (exp(-((double) k*k)/(2.0*MagickSigma*MagickSigma))/
845 (MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +0000846 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +0000847 i++;
848 }
cristybb503372010-05-27 20:51:26 +0000849 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000850 kernel[i]/=normalize;
851 return(kernel);
852}
853
cristyf4ad9df2011-07-08 16:49:03 +0000854MagickExport Image *BlurImage(const Image *image,const double radius,
cristy05c0c9a2011-09-05 23:16:13 +0000855 const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000856{
857#define BlurImageTag "Blur/Image"
858
cristyc4c8d132010-01-07 01:58:38 +0000859 CacheView
860 *blur_view,
861 *image_view;
862
cristy3ed852e2009-09-05 21:47:34 +0000863 double
864 *kernel;
865
866 Image
867 *blur_image;
868
cristy3ed852e2009-09-05 21:47:34 +0000869 MagickBooleanType
870 status;
871
cristybb503372010-05-27 20:51:26 +0000872 MagickOffsetType
873 progress;
874
cristybb503372010-05-27 20:51:26 +0000875 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000876 i;
877
cristybb503372010-05-27 20:51:26 +0000878 size_t
cristy3ed852e2009-09-05 21:47:34 +0000879 width;
880
cristybb503372010-05-27 20:51:26 +0000881 ssize_t
cristyb41a1172011-09-06 00:55:14 +0000882 center,
cristybb503372010-05-27 20:51:26 +0000883 x,
884 y;
885
cristy3ed852e2009-09-05 21:47:34 +0000886 /*
887 Initialize blur image attributes.
888 */
889 assert(image != (Image *) NULL);
890 assert(image->signature == MagickSignature);
891 if (image->debug != MagickFalse)
892 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
893 assert(exception != (ExceptionInfo *) NULL);
894 assert(exception->signature == MagickSignature);
cristyd25c77e2011-09-06 00:10:24 +0000895 blur_image=CloneImage(image,0,0,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +0000896 if (blur_image == (Image *) NULL)
897 return((Image *) NULL);
898 if (fabs(sigma) <= MagickEpsilon)
899 return(blur_image);
cristy574cc262011-08-05 01:23:58 +0000900 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000901 {
cristy3ed852e2009-09-05 21:47:34 +0000902 blur_image=DestroyImage(blur_image);
903 return((Image *) NULL);
904 }
905 width=GetOptimalKernelWidth1D(radius,sigma);
906 kernel=GetBlurKernel(width,sigma);
907 if (kernel == (double *) NULL)
908 {
909 blur_image=DestroyImage(blur_image);
910 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
911 }
912 if (image->debug != MagickFalse)
913 {
914 char
915 format[MaxTextExtent],
916 *message;
917
918 register const double
919 *k;
920
921 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +0000922 " BlurImage with %.20g kernel:",(double) width);
cristy3ed852e2009-09-05 21:47:34 +0000923 message=AcquireString("");
924 k=kernel;
cristybb503372010-05-27 20:51:26 +0000925 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000926 {
927 *message='\0';
cristyb51dff52011-05-19 16:55:47 +0000928 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) i);
cristy3ed852e2009-09-05 21:47:34 +0000929 (void) ConcatenateString(&message,format);
cristyb51dff52011-05-19 16:55:47 +0000930 (void) FormatLocaleString(format,MaxTextExtent,"%g ",*k++);
cristy3ed852e2009-09-05 21:47:34 +0000931 (void) ConcatenateString(&message,format);
932 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
933 }
934 message=DestroyString(message);
935 }
936 /*
937 Blur rows.
938 */
939 status=MagickTrue;
940 progress=0;
cristyb41a1172011-09-06 00:55:14 +0000941 center=(ssize_t) GetPixelChannels(image)*(width/2L);
cristy3ed852e2009-09-05 21:47:34 +0000942 image_view=AcquireCacheView(image);
943 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000944#if defined(MAGICKCORE_OPENMP_SUPPORT)
945 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000946#endif
cristyb41a1172011-09-06 00:55:14 +0000947 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000948 {
cristy4c08aed2011-07-01 19:47:50 +0000949 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000950 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000951
cristy4c08aed2011-07-01 19:47:50 +0000952 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000953 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000954
cristy117ff172010-08-15 21:35:32 +0000955 register ssize_t
956 x;
957
cristy3ed852e2009-09-05 21:47:34 +0000958 if (status == MagickFalse)
959 continue;
cristy117ff172010-08-15 21:35:32 +0000960 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y,
961 image->columns+width,1,exception);
cristy3ed852e2009-09-05 21:47:34 +0000962 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
963 exception);
cristy4c08aed2011-07-01 19:47:50 +0000964 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000965 {
966 status=MagickFalse;
967 continue;
968 }
cristyb41a1172011-09-06 00:55:14 +0000969 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000970 {
cristybb503372010-05-27 20:51:26 +0000971 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000972 i;
973
cristyb41a1172011-09-06 00:55:14 +0000974 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
975 {
976 MagickRealType
977 alpha,
978 gamma,
979 pixel;
cristyd25c77e2011-09-06 00:10:24 +0000980
cristyb41a1172011-09-06 00:55:14 +0000981 PixelChannel
982 channel;
983
984 PixelTrait
985 blur_traits,
986 traits;
987
988 register const double
989 *restrict k;
990
991 register const Quantum
992 *restrict pixels;
993
994 register ssize_t
995 u;
996
997 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
998 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
999 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
1000 if ((traits == UndefinedPixelTrait) ||
1001 (blur_traits == UndefinedPixelTrait))
1002 continue;
1003 if ((blur_traits & CopyPixelTrait) != 0)
cristyd25c77e2011-09-06 00:10:24 +00001004 {
cristyb41a1172011-09-06 00:55:14 +00001005 q[channel]=p[center+i];
1006 continue;
cristyd25c77e2011-09-06 00:10:24 +00001007 }
cristyb41a1172011-09-06 00:55:14 +00001008 k=kernel;
1009 pixels=p;
1010 pixel=0.0;
1011 if ((blur_traits & BlendPixelTrait) == 0)
1012 {
1013 /*
1014 No alpha blending.
1015 */
1016 for (u=0; u < (ssize_t) width; u++)
cristyd25c77e2011-09-06 00:10:24 +00001017 {
cristyb41a1172011-09-06 00:55:14 +00001018 pixel+=(*k)*pixels[i];
1019 k++;
1020 pixels+=GetPixelChannels(image);
cristyd25c77e2011-09-06 00:10:24 +00001021 }
cristyb41a1172011-09-06 00:55:14 +00001022 q[channel]=ClampToQuantum(pixel);
1023 continue;
1024 }
1025 /*
1026 Alpha blending.
1027 */
1028 gamma=0.0;
1029 for (u=0; u < (ssize_t) width; u++)
1030 {
1031 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,pixels));
1032 pixel+=(*k)*alpha*pixels[i];
1033 gamma+=(*k)*alpha;
1034 k++;
1035 pixels+=GetPixelChannels(image);
cristyd25c77e2011-09-06 00:10:24 +00001036 }
cristyb41a1172011-09-06 00:55:14 +00001037 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1038 q[channel]=ClampToQuantum(gamma*pixel);
1039 }
cristyed231572011-07-14 02:18:59 +00001040 p+=GetPixelChannels(image);
1041 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001042 }
1043 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1044 status=MagickFalse;
1045 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1046 {
1047 MagickBooleanType
1048 proceed;
1049
cristyb5d5f722009-11-04 03:03:49 +00001050#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00001051 #pragma omp critical (MagickCore_BlurImage)
cristy3ed852e2009-09-05 21:47:34 +00001052#endif
1053 proceed=SetImageProgress(image,BlurImageTag,progress++,blur_image->rows+
1054 blur_image->columns);
1055 if (proceed == MagickFalse)
1056 status=MagickFalse;
1057 }
1058 }
1059 blur_view=DestroyCacheView(blur_view);
1060 image_view=DestroyCacheView(image_view);
1061 /*
1062 Blur columns.
1063 */
1064 image_view=AcquireCacheView(blur_image);
1065 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00001066#if defined(MAGICKCORE_OPENMP_SUPPORT)
1067 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001068#endif
cristyb41a1172011-09-06 00:55:14 +00001069 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001070 {
cristy4c08aed2011-07-01 19:47:50 +00001071 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001072 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001073
cristy4c08aed2011-07-01 19:47:50 +00001074 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001075 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001076
cristy117ff172010-08-15 21:35:32 +00001077 register ssize_t
1078 y;
1079
cristy3ed852e2009-09-05 21:47:34 +00001080 if (status == MagickFalse)
1081 continue;
cristy117ff172010-08-15 21:35:32 +00001082 p=GetCacheViewVirtualPixels(image_view,x,-((ssize_t) width/2L),1,
1083 image->rows+width,exception);
cristy3ed852e2009-09-05 21:47:34 +00001084 q=GetCacheViewAuthenticPixels(blur_view,x,0,1,blur_image->rows,exception);
cristy4c08aed2011-07-01 19:47:50 +00001085 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001086 {
1087 status=MagickFalse;
1088 continue;
1089 }
cristyb41a1172011-09-06 00:55:14 +00001090 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001091 {
cristybb503372010-05-27 20:51:26 +00001092 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001093 i;
1094
cristyb41a1172011-09-06 00:55:14 +00001095 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1096 {
1097 MagickRealType
1098 alpha,
1099 gamma,
1100 pixel;
cristyd25c77e2011-09-06 00:10:24 +00001101
cristyb41a1172011-09-06 00:55:14 +00001102 PixelChannel
1103 channel;
1104
1105 PixelTrait
1106 blur_traits,
1107 traits;
1108
1109 register const double
1110 *restrict k;
1111
1112 register const Quantum
1113 *restrict pixels;
1114
1115 register ssize_t
1116 u;
1117
1118 traits=GetPixelChannelMapTraits(blur_image,(PixelChannel) i);
1119 channel=GetPixelChannelMapChannel(blur_image,(PixelChannel) i);
1120 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
1121 if ((traits == UndefinedPixelTrait) ||
1122 (blur_traits == UndefinedPixelTrait))
1123 continue;
1124 if ((blur_traits & CopyPixelTrait) != 0)
cristyd25c77e2011-09-06 00:10:24 +00001125 {
cristyb41a1172011-09-06 00:55:14 +00001126 q[channel]=p[center+i];
1127 continue;
cristyd25c77e2011-09-06 00:10:24 +00001128 }
cristyb41a1172011-09-06 00:55:14 +00001129 k=kernel;
1130 pixels=p;
1131 pixel=0.0;
1132 if ((blur_traits & BlendPixelTrait) == 0)
1133 {
1134 /*
1135 No alpha blending.
1136 */
1137 for (u=0; u < (ssize_t) width; u++)
cristyd25c77e2011-09-06 00:10:24 +00001138 {
cristyb41a1172011-09-06 00:55:14 +00001139 pixel+=(*k)*pixels[i];
1140 k++;
1141 pixels+=GetPixelChannels(blur_image);
cristyd25c77e2011-09-06 00:10:24 +00001142 }
cristyb41a1172011-09-06 00:55:14 +00001143 q[channel]=ClampToQuantum(pixel);
1144 continue;
1145 }
1146 /*
1147 Alpha blending.
1148 */
1149 gamma=0.0;
1150 for (u=0; u < (ssize_t) width; u++)
1151 {
1152 alpha=(MagickRealType) (QuantumScale*
1153 GetPixelAlpha(blur_image,pixels));
1154 pixel+=(*k)*alpha*pixels[i];
1155 gamma+=(*k)*alpha;
1156 k++;
1157 pixels+=GetPixelChannels(blur_image);
cristyd25c77e2011-09-06 00:10:24 +00001158 }
cristyb41a1172011-09-06 00:55:14 +00001159 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1160 q[channel]=ClampToQuantum(gamma*pixel);
1161 }
cristyd25c77e2011-09-06 00:10:24 +00001162 p+=GetPixelChannels(blur_image);
cristyed231572011-07-14 02:18:59 +00001163 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001164 }
1165 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1166 status=MagickFalse;
cristy4c08aed2011-07-01 19:47:50 +00001167 if (blur_image->progress_monitor != (MagickProgressMonitor) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001168 {
1169 MagickBooleanType
1170 proceed;
1171
cristyb5d5f722009-11-04 03:03:49 +00001172#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00001173 #pragma omp critical (MagickCore_BlurImage)
cristy3ed852e2009-09-05 21:47:34 +00001174#endif
cristy4c08aed2011-07-01 19:47:50 +00001175 proceed=SetImageProgress(blur_image,BlurImageTag,progress++,
1176 blur_image->rows+blur_image->columns);
cristy3ed852e2009-09-05 21:47:34 +00001177 if (proceed == MagickFalse)
1178 status=MagickFalse;
1179 }
1180 }
1181 blur_view=DestroyCacheView(blur_view);
1182 image_view=DestroyCacheView(image_view);
1183 kernel=(double *) RelinquishMagickMemory(kernel);
1184 if (status == MagickFalse)
1185 blur_image=DestroyImage(blur_image);
1186 blur_image->type=image->type;
1187 return(blur_image);
1188}
1189
1190/*
1191%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1192% %
1193% %
1194% %
cristyfccdab92009-11-30 16:43:57 +00001195% C o n v o l v e I m a g e %
1196% %
1197% %
1198% %
1199%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1200%
1201% ConvolveImage() applies a custom convolution kernel to the image.
1202%
1203% The format of the ConvolveImage method is:
1204%
cristy5e6be1e2011-07-16 01:23:39 +00001205% Image *ConvolveImage(const Image *image,const KernelInfo *kernel,
1206% ExceptionInfo *exception)
1207%
cristyfccdab92009-11-30 16:43:57 +00001208% A description of each parameter follows:
1209%
1210% o image: the image.
1211%
cristy5e6be1e2011-07-16 01:23:39 +00001212% o kernel: the filtering kernel.
cristyfccdab92009-11-30 16:43:57 +00001213%
1214% o exception: return any errors or warnings in this structure.
1215%
1216*/
cristy5e6be1e2011-07-16 01:23:39 +00001217MagickExport Image *ConvolveImage(const Image *image,
1218 const KernelInfo *kernel_info,ExceptionInfo *exception)
cristyfccdab92009-11-30 16:43:57 +00001219{
cristyfccdab92009-11-30 16:43:57 +00001220#define ConvolveImageTag "Convolve/Image"
1221
cristyc4c8d132010-01-07 01:58:38 +00001222 CacheView
1223 *convolve_view,
cristy105ba3c2011-07-18 02:28:38 +00001224 *image_view;
cristyc4c8d132010-01-07 01:58:38 +00001225
cristyfccdab92009-11-30 16:43:57 +00001226 Image
1227 *convolve_image;
1228
cristyfccdab92009-11-30 16:43:57 +00001229 MagickBooleanType
1230 status;
1231
cristybb503372010-05-27 20:51:26 +00001232 MagickOffsetType
1233 progress;
1234
cristybb503372010-05-27 20:51:26 +00001235 ssize_t
cristy574cc262011-08-05 01:23:58 +00001236 center,
cristybb503372010-05-27 20:51:26 +00001237 y;
1238
cristyfccdab92009-11-30 16:43:57 +00001239 /*
1240 Initialize convolve image attributes.
1241 */
1242 assert(image != (Image *) NULL);
1243 assert(image->signature == MagickSignature);
1244 if (image->debug != MagickFalse)
1245 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1246 assert(exception != (ExceptionInfo *) NULL);
1247 assert(exception->signature == MagickSignature);
cristy5e6be1e2011-07-16 01:23:39 +00001248 if ((kernel_info->width % 2) == 0)
cristyfccdab92009-11-30 16:43:57 +00001249 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
cristy08429172011-07-14 17:18:16 +00001250 convolve_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1251 exception);
cristyfccdab92009-11-30 16:43:57 +00001252 if (convolve_image == (Image *) NULL)
1253 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00001254 if (SetImageStorageClass(convolve_image,DirectClass,exception) == MagickFalse)
cristyfccdab92009-11-30 16:43:57 +00001255 {
cristyfccdab92009-11-30 16:43:57 +00001256 convolve_image=DestroyImage(convolve_image);
1257 return((Image *) NULL);
1258 }
1259 if (image->debug != MagickFalse)
1260 {
1261 char
1262 format[MaxTextExtent],
1263 *message;
1264
cristy117ff172010-08-15 21:35:32 +00001265 register const double
1266 *k;
1267
cristy4e154852011-07-14 13:28:53 +00001268 register ssize_t
1269 u;
1270
cristybb503372010-05-27 20:51:26 +00001271 ssize_t
cristyfccdab92009-11-30 16:43:57 +00001272 v;
1273
cristyfccdab92009-11-30 16:43:57 +00001274 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristy5e6be1e2011-07-16 01:23:39 +00001275 " ConvolveImage with %.20gx%.20g kernel:",(double) kernel_info->width,
1276 (double) kernel_info->height);
cristyfccdab92009-11-30 16:43:57 +00001277 message=AcquireString("");
cristy5e6be1e2011-07-16 01:23:39 +00001278 k=kernel_info->values;
1279 for (v=0; v < (ssize_t) kernel_info->width; v++)
cristyfccdab92009-11-30 16:43:57 +00001280 {
1281 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00001282 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristyfccdab92009-11-30 16:43:57 +00001283 (void) ConcatenateString(&message,format);
cristy5e6be1e2011-07-16 01:23:39 +00001284 for (u=0; u < (ssize_t) kernel_info->height; u++)
cristyfccdab92009-11-30 16:43:57 +00001285 {
cristyb51dff52011-05-19 16:55:47 +00001286 (void) FormatLocaleString(format,MaxTextExtent,"%g ",*k++);
cristyfccdab92009-11-30 16:43:57 +00001287 (void) ConcatenateString(&message,format);
1288 }
1289 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
1290 }
1291 message=DestroyString(message);
1292 }
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 {
cristyf7dc44c2011-07-20 14:41:15 +00001370 q[channel]=p[center+i];
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 }
cristyf7dc44c2011-07-20 14:41:15 +00001391 q[channel]=ClampToQuantum(pixel);
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);
cristye7a41c92011-07-20 21:31:01 +00001411 q[channel]=ClampToQuantum(gamma*pixel);
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 {
cristya63e4a92011-09-09 00:47:59 +00001713 q[channel]=pixel[j++];
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 {
cristyf7ef0252011-09-09 14:50:06 +00002227 q[channel]=p[i];
2228 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 }
2246 q[channel]=ClampToQuantum(pixel);
2247 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);
2266 q[channel]=ClampToQuantum(gamma*pixel);
2267 }
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 {
2407 (void) QueryColorDatabase("#dfdfdf",&thumbnail->matte_color,exception);
2408 AppendImageToList(&images,thumbnail);
2409 continue;
2410 }
2411 switch (preview)
2412 {
2413 case RotatePreview:
2414 {
2415 degrees+=45.0;
2416 preview_image=RotateImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002417 (void) FormatLocaleString(label,MaxTextExtent,"rotate %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002418 break;
2419 }
2420 case ShearPreview:
2421 {
2422 degrees+=5.0;
2423 preview_image=ShearImage(thumbnail,degrees,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002424 (void) FormatLocaleString(label,MaxTextExtent,"shear %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002425 degrees,2.0*degrees);
2426 break;
2427 }
2428 case RollPreview:
2429 {
cristybb503372010-05-27 20:51:26 +00002430 x=(ssize_t) ((i+1)*thumbnail->columns)/NumberTiles;
2431 y=(ssize_t) ((i+1)*thumbnail->rows)/NumberTiles;
cristy3ed852e2009-09-05 21:47:34 +00002432 preview_image=RollImage(thumbnail,x,y,exception);
cristyb51dff52011-05-19 16:55:47 +00002433 (void) FormatLocaleString(label,MaxTextExtent,"roll %+.20gx%+.20g",
cristye8c25f92010-06-03 00:53:06 +00002434 (double) x,(double) y);
cristy3ed852e2009-09-05 21:47:34 +00002435 break;
2436 }
2437 case HuePreview:
2438 {
2439 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2440 if (preview_image == (Image *) NULL)
2441 break;
cristyb51dff52011-05-19 16:55:47 +00002442 (void) FormatLocaleString(factor,MaxTextExtent,"100,100,%g",
cristy3ed852e2009-09-05 21:47:34 +00002443 2.0*percentage);
cristy33bd5152011-08-24 01:42:24 +00002444 (void) ModulateImage(preview_image,factor,exception);
cristyb51dff52011-05-19 16:55:47 +00002445 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002446 break;
2447 }
2448 case SaturationPreview:
2449 {
2450 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2451 if (preview_image == (Image *) NULL)
2452 break;
cristyb51dff52011-05-19 16:55:47 +00002453 (void) FormatLocaleString(factor,MaxTextExtent,"100,%g",
cristy8cd5b312010-01-07 01:10:24 +00002454 2.0*percentage);
cristy33bd5152011-08-24 01:42:24 +00002455 (void) ModulateImage(preview_image,factor,exception);
cristyb51dff52011-05-19 16:55:47 +00002456 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002457 break;
2458 }
2459 case BrightnessPreview:
2460 {
2461 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2462 if (preview_image == (Image *) NULL)
2463 break;
cristyb51dff52011-05-19 16:55:47 +00002464 (void) FormatLocaleString(factor,MaxTextExtent,"%g",2.0*percentage);
cristy33bd5152011-08-24 01:42:24 +00002465 (void) ModulateImage(preview_image,factor,exception);
cristyb51dff52011-05-19 16:55:47 +00002466 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002467 break;
2468 }
2469 case GammaPreview:
2470 default:
2471 {
2472 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2473 if (preview_image == (Image *) NULL)
2474 break;
2475 gamma+=0.4f;
cristyb3e7c6c2011-07-24 01:43:55 +00002476 (void) GammaImage(preview_image,gamma,exception);
cristyb51dff52011-05-19 16:55:47 +00002477 (void) FormatLocaleString(label,MaxTextExtent,"gamma %g",gamma);
cristy3ed852e2009-09-05 21:47:34 +00002478 break;
2479 }
2480 case SpiffPreview:
2481 {
2482 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2483 if (preview_image != (Image *) NULL)
2484 for (x=0; x < i; x++)
cristye23ec9d2011-08-16 18:15:40 +00002485 (void) ContrastImage(preview_image,MagickTrue,exception);
cristyb51dff52011-05-19 16:55:47 +00002486 (void) FormatLocaleString(label,MaxTextExtent,"contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002487 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002488 break;
2489 }
2490 case DullPreview:
2491 {
2492 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2493 if (preview_image == (Image *) NULL)
2494 break;
2495 for (x=0; x < i; x++)
cristye23ec9d2011-08-16 18:15:40 +00002496 (void) ContrastImage(preview_image,MagickFalse,exception);
cristyb51dff52011-05-19 16:55:47 +00002497 (void) FormatLocaleString(label,MaxTextExtent,"+contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002498 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002499 break;
2500 }
2501 case GrayscalePreview:
2502 {
2503 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2504 if (preview_image == (Image *) NULL)
2505 break;
2506 colors<<=1;
2507 quantize_info.number_colors=colors;
2508 quantize_info.colorspace=GRAYColorspace;
cristy018f07f2011-09-04 21:15:19 +00002509 (void) QuantizeImage(&quantize_info,preview_image,exception);
cristyb51dff52011-05-19 16:55:47 +00002510 (void) FormatLocaleString(label,MaxTextExtent,
cristye8c25f92010-06-03 00:53:06 +00002511 "-colorspace gray -colors %.20g",(double) colors);
cristy3ed852e2009-09-05 21:47:34 +00002512 break;
2513 }
2514 case QuantizePreview:
2515 {
2516 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2517 if (preview_image == (Image *) NULL)
2518 break;
2519 colors<<=1;
2520 quantize_info.number_colors=colors;
cristy018f07f2011-09-04 21:15:19 +00002521 (void) QuantizeImage(&quantize_info,preview_image,exception);
cristyb51dff52011-05-19 16:55:47 +00002522 (void) FormatLocaleString(label,MaxTextExtent,"colors %.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00002523 colors);
cristy3ed852e2009-09-05 21:47:34 +00002524 break;
2525 }
2526 case DespecklePreview:
2527 {
2528 for (x=0; x < (i-1); x++)
2529 {
2530 preview_image=DespeckleImage(thumbnail,exception);
2531 if (preview_image == (Image *) NULL)
2532 break;
2533 thumbnail=DestroyImage(thumbnail);
2534 thumbnail=preview_image;
2535 }
2536 preview_image=DespeckleImage(thumbnail,exception);
2537 if (preview_image == (Image *) NULL)
2538 break;
cristyb51dff52011-05-19 16:55:47 +00002539 (void) FormatLocaleString(label,MaxTextExtent,"despeckle (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002540 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002541 break;
2542 }
2543 case ReduceNoisePreview:
2544 {
cristy95c38342011-03-18 22:39:51 +00002545 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) radius,
2546 (size_t) radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002547 (void) FormatLocaleString(label,MaxTextExtent,"noise %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002548 break;
2549 }
2550 case AddNoisePreview:
2551 {
2552 switch ((int) i)
2553 {
2554 case 0:
2555 {
2556 (void) CopyMagickString(factor,"uniform",MaxTextExtent);
2557 break;
2558 }
2559 case 1:
2560 {
2561 (void) CopyMagickString(factor,"gaussian",MaxTextExtent);
2562 break;
2563 }
2564 case 2:
2565 {
2566 (void) CopyMagickString(factor,"multiplicative",MaxTextExtent);
2567 break;
2568 }
2569 case 3:
2570 {
2571 (void) CopyMagickString(factor,"impulse",MaxTextExtent);
2572 break;
2573 }
2574 case 4:
2575 {
2576 (void) CopyMagickString(factor,"laplacian",MaxTextExtent);
2577 break;
2578 }
2579 case 5:
2580 {
2581 (void) CopyMagickString(factor,"Poisson",MaxTextExtent);
2582 break;
2583 }
2584 default:
2585 {
2586 (void) CopyMagickString(thumbnail->magick,"NULL",MaxTextExtent);
2587 break;
2588 }
2589 }
cristyd76c51e2011-03-26 00:21:26 +00002590 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) i,
2591 (size_t) i,exception);
cristyb51dff52011-05-19 16:55:47 +00002592 (void) FormatLocaleString(label,MaxTextExtent,"+noise %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002593 break;
2594 }
2595 case SharpenPreview:
2596 {
cristy05c0c9a2011-09-05 23:16:13 +00002597 preview_image=SharpenImage(thumbnail,radius,sigma,image->bias,
2598 exception);
cristyb51dff52011-05-19 16:55:47 +00002599 (void) FormatLocaleString(label,MaxTextExtent,"sharpen %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002600 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002601 break;
2602 }
2603 case BlurPreview:
2604 {
cristy05c0c9a2011-09-05 23:16:13 +00002605 preview_image=BlurImage(thumbnail,radius,sigma,image->bias,exception);
cristyb51dff52011-05-19 16:55:47 +00002606 (void) FormatLocaleString(label,MaxTextExtent,"blur %gx%g",radius,
cristy3ed852e2009-09-05 21:47:34 +00002607 sigma);
2608 break;
2609 }
2610 case ThresholdPreview:
2611 {
2612 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2613 if (preview_image == (Image *) NULL)
2614 break;
2615 (void) BilevelImage(thumbnail,
2616 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
cristyb51dff52011-05-19 16:55:47 +00002617 (void) FormatLocaleString(label,MaxTextExtent,"threshold %g",
cristy3ed852e2009-09-05 21:47:34 +00002618 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
2619 break;
2620 }
2621 case EdgeDetectPreview:
2622 {
cristy8ae632d2011-09-05 17:29:53 +00002623 preview_image=EdgeImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00002624 (void) FormatLocaleString(label,MaxTextExtent,"edge %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002625 break;
2626 }
2627 case SpreadPreview:
2628 {
cristy5c4e2582011-09-11 19:21:03 +00002629 preview_image=SpreadImage(thumbnail,radius,thumbnail->interpolate,
2630 exception);
cristyb51dff52011-05-19 16:55:47 +00002631 (void) FormatLocaleString(label,MaxTextExtent,"spread %g",
cristy8cd5b312010-01-07 01:10:24 +00002632 radius+0.5);
cristy3ed852e2009-09-05 21:47:34 +00002633 break;
2634 }
2635 case SolarizePreview:
2636 {
2637 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2638 if (preview_image == (Image *) NULL)
2639 break;
2640 (void) SolarizeImage(preview_image,(double) QuantumRange*
cristy5cbc0162011-08-29 00:36:28 +00002641 percentage/100.0,exception);
cristyb51dff52011-05-19 16:55:47 +00002642 (void) FormatLocaleString(label,MaxTextExtent,"solarize %g",
cristy3ed852e2009-09-05 21:47:34 +00002643 (QuantumRange*percentage)/100.0);
2644 break;
2645 }
2646 case ShadePreview:
2647 {
2648 degrees+=10.0;
2649 preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees,
2650 exception);
cristyb51dff52011-05-19 16:55:47 +00002651 (void) FormatLocaleString(label,MaxTextExtent,"shade %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002652 degrees,degrees);
cristy3ed852e2009-09-05 21:47:34 +00002653 break;
2654 }
2655 case RaisePreview:
2656 {
2657 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2658 if (preview_image == (Image *) NULL)
2659 break;
cristybb503372010-05-27 20:51:26 +00002660 geometry.width=(size_t) (2*i+2);
2661 geometry.height=(size_t) (2*i+2);
cristy3ed852e2009-09-05 21:47:34 +00002662 geometry.x=i/2;
2663 geometry.y=i/2;
cristy6170ac32011-08-28 14:15:37 +00002664 (void) RaiseImage(preview_image,&geometry,MagickTrue,exception);
cristyb51dff52011-05-19 16:55:47 +00002665 (void) FormatLocaleString(label,MaxTextExtent,
cristy6d8abba2010-06-03 01:10:47 +00002666 "raise %.20gx%.20g%+.20g%+.20g",(double) geometry.width,(double)
cristye8c25f92010-06-03 00:53:06 +00002667 geometry.height,(double) geometry.x,(double) geometry.y);
cristy3ed852e2009-09-05 21:47:34 +00002668 break;
2669 }
2670 case SegmentPreview:
2671 {
2672 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2673 if (preview_image == (Image *) NULL)
2674 break;
2675 threshold+=0.4f;
2676 (void) SegmentImage(preview_image,RGBColorspace,MagickFalse,threshold,
cristy018f07f2011-09-04 21:15:19 +00002677 threshold,exception);
cristyb51dff52011-05-19 16:55:47 +00002678 (void) FormatLocaleString(label,MaxTextExtent,"segment %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002679 threshold,threshold);
2680 break;
2681 }
2682 case SwirlPreview:
2683 {
cristy76f512e2011-09-12 01:26:56 +00002684 preview_image=SwirlImage(thumbnail,degrees,image->interpolate,
2685 exception);
cristyb51dff52011-05-19 16:55:47 +00002686 (void) FormatLocaleString(label,MaxTextExtent,"swirl %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002687 degrees+=45.0;
2688 break;
2689 }
2690 case ImplodePreview:
2691 {
2692 degrees+=0.1f;
cristy76f512e2011-09-12 01:26:56 +00002693 preview_image=ImplodeImage(thumbnail,degrees,image->interpolate,
2694 exception);
cristyb51dff52011-05-19 16:55:47 +00002695 (void) FormatLocaleString(label,MaxTextExtent,"implode %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002696 break;
2697 }
2698 case WavePreview:
2699 {
2700 degrees+=5.0f;
cristy5c4e2582011-09-11 19:21:03 +00002701 preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,
2702 image->interpolate,exception);
cristyb51dff52011-05-19 16:55:47 +00002703 (void) FormatLocaleString(label,MaxTextExtent,"wave %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002704 0.5*degrees,2.0*degrees);
cristy3ed852e2009-09-05 21:47:34 +00002705 break;
2706 }
2707 case OilPaintPreview:
2708 {
cristy14973ba2011-08-27 23:48:07 +00002709 preview_image=OilPaintImage(thumbnail,(double) radius,(double) sigma,
2710 exception);
2711 (void) FormatLocaleString(label,MaxTextExtent,"charcoal %gx%g",
2712 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002713 break;
2714 }
2715 case CharcoalDrawingPreview:
2716 {
2717 preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma,
cristy05c0c9a2011-09-05 23:16:13 +00002718 image->bias,exception);
cristyb51dff52011-05-19 16:55:47 +00002719 (void) FormatLocaleString(label,MaxTextExtent,"charcoal %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002720 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002721 break;
2722 }
2723 case JPEGPreview:
2724 {
2725 char
2726 filename[MaxTextExtent];
2727
2728 int
2729 file;
2730
2731 MagickBooleanType
2732 status;
2733
2734 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2735 if (preview_image == (Image *) NULL)
2736 break;
cristybb503372010-05-27 20:51:26 +00002737 preview_info->quality=(size_t) percentage;
cristyb51dff52011-05-19 16:55:47 +00002738 (void) FormatLocaleString(factor,MaxTextExtent,"%.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00002739 preview_info->quality);
cristy3ed852e2009-09-05 21:47:34 +00002740 file=AcquireUniqueFileResource(filename);
2741 if (file != -1)
2742 file=close(file)-1;
cristyb51dff52011-05-19 16:55:47 +00002743 (void) FormatLocaleString(preview_image->filename,MaxTextExtent,
cristy3ed852e2009-09-05 21:47:34 +00002744 "jpeg:%s",filename);
cristy6f9e0d32011-08-28 16:32:09 +00002745 status=WriteImage(preview_info,preview_image,exception);
cristy3ed852e2009-09-05 21:47:34 +00002746 if (status != MagickFalse)
2747 {
2748 Image
2749 *quality_image;
2750
2751 (void) CopyMagickString(preview_info->filename,
2752 preview_image->filename,MaxTextExtent);
2753 quality_image=ReadImage(preview_info,exception);
2754 if (quality_image != (Image *) NULL)
2755 {
2756 preview_image=DestroyImage(preview_image);
2757 preview_image=quality_image;
2758 }
2759 }
2760 (void) RelinquishUniqueFileResource(preview_image->filename);
2761 if ((GetBlobSize(preview_image)/1024) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00002762 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%gmb ",
cristy3ed852e2009-09-05 21:47:34 +00002763 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
2764 1024.0/1024.0);
2765 else
2766 if (GetBlobSize(preview_image) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00002767 (void) FormatLocaleString(label,MaxTextExtent,
cristye7f51092010-01-17 00:39:37 +00002768 "quality %s\n%gkb ",factor,(double) ((MagickOffsetType)
cristy8cd5b312010-01-07 01:10:24 +00002769 GetBlobSize(preview_image))/1024.0);
cristy3ed852e2009-09-05 21:47:34 +00002770 else
cristyb51dff52011-05-19 16:55:47 +00002771 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%.20gb ",
cristy54ea5732011-06-10 12:39:53 +00002772 factor,(double) ((MagickOffsetType) GetBlobSize(thumbnail)));
cristy3ed852e2009-09-05 21:47:34 +00002773 break;
2774 }
2775 }
2776 thumbnail=DestroyImage(thumbnail);
2777 percentage+=12.5;
2778 radius+=0.5;
2779 sigma+=0.25;
2780 if (preview_image == (Image *) NULL)
2781 break;
2782 (void) DeleteImageProperty(preview_image,"label");
2783 (void) SetImageProperty(preview_image,"label",label);
2784 AppendImageToList(&images,preview_image);
cristybb503372010-05-27 20:51:26 +00002785 proceed=SetImageProgress(image,PreviewImageTag,(MagickOffsetType) i,
2786 NumberTiles);
cristy3ed852e2009-09-05 21:47:34 +00002787 if (proceed == MagickFalse)
2788 break;
2789 }
2790 if (images == (Image *) NULL)
2791 {
2792 preview_info=DestroyImageInfo(preview_info);
2793 return((Image *) NULL);
2794 }
2795 /*
2796 Create the montage.
2797 */
2798 montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL);
2799 (void) CopyMagickString(montage_info->filename,image->filename,MaxTextExtent);
2800 montage_info->shadow=MagickTrue;
2801 (void) CloneString(&montage_info->tile,"3x3");
2802 (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry);
2803 (void) CloneString(&montage_info->frame,DefaultTileFrame);
2804 montage_image=MontageImages(images,montage_info,exception);
2805 montage_info=DestroyMontageInfo(montage_info);
2806 images=DestroyImageList(images);
2807 if (montage_image == (Image *) NULL)
2808 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2809 if (montage_image->montage != (char *) NULL)
2810 {
2811 /*
2812 Free image directory.
2813 */
2814 montage_image->montage=(char *) RelinquishMagickMemory(
2815 montage_image->montage);
2816 if (image->directory != (char *) NULL)
2817 montage_image->directory=(char *) RelinquishMagickMemory(
2818 montage_image->directory);
2819 }
2820 preview_info=DestroyImageInfo(preview_info);
2821 return(montage_image);
2822}
2823
2824/*
2825%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2826% %
2827% %
2828% %
2829% R a d i a l B l u r I m a g e %
2830% %
2831% %
2832% %
2833%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2834%
2835% RadialBlurImage() applies a radial blur to the image.
2836%
2837% Andrew Protano contributed this effect.
2838%
2839% The format of the RadialBlurImage method is:
2840%
2841% Image *RadialBlurImage(const Image *image,const double angle,
cristy6435bd92011-09-10 02:10:07 +00002842% const double blur,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002843%
2844% A description of each parameter follows:
2845%
2846% o image: the image.
2847%
cristy3ed852e2009-09-05 21:47:34 +00002848% o angle: the angle of the radial blur.
2849%
cristy6435bd92011-09-10 02:10:07 +00002850% o blur: the blur.
2851%
cristy3ed852e2009-09-05 21:47:34 +00002852% o exception: return any errors or warnings in this structure.
2853%
2854*/
cristyf4ad9df2011-07-08 16:49:03 +00002855MagickExport Image *RadialBlurImage(const Image *image,
cristy6435bd92011-09-10 02:10:07 +00002856 const double angle,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002857{
cristyc4c8d132010-01-07 01:58:38 +00002858 CacheView
2859 *blur_view,
2860 *image_view;
2861
cristy3ed852e2009-09-05 21:47:34 +00002862 Image
2863 *blur_image;
2864
cristy3ed852e2009-09-05 21:47:34 +00002865 MagickBooleanType
2866 status;
2867
cristybb503372010-05-27 20:51:26 +00002868 MagickOffsetType
2869 progress;
2870
cristy3ed852e2009-09-05 21:47:34 +00002871 MagickRealType
2872 blur_radius,
2873 *cos_theta,
2874 offset,
2875 *sin_theta,
2876 theta;
2877
2878 PointInfo
2879 blur_center;
2880
cristybb503372010-05-27 20:51:26 +00002881 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002882 i;
2883
cristybb503372010-05-27 20:51:26 +00002884 size_t
cristy3ed852e2009-09-05 21:47:34 +00002885 n;
2886
cristybb503372010-05-27 20:51:26 +00002887 ssize_t
2888 y;
2889
cristy3ed852e2009-09-05 21:47:34 +00002890 /*
2891 Allocate blur image.
2892 */
2893 assert(image != (Image *) NULL);
2894 assert(image->signature == MagickSignature);
2895 if (image->debug != MagickFalse)
2896 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2897 assert(exception != (ExceptionInfo *) NULL);
2898 assert(exception->signature == MagickSignature);
cristy1e7aa312011-09-10 20:01:36 +00002899 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00002900 if (blur_image == (Image *) NULL)
2901 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00002902 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00002903 {
cristy3ed852e2009-09-05 21:47:34 +00002904 blur_image=DestroyImage(blur_image);
2905 return((Image *) NULL);
2906 }
2907 blur_center.x=(double) image->columns/2.0;
2908 blur_center.y=(double) image->rows/2.0;
2909 blur_radius=hypot(blur_center.x,blur_center.y);
cristy117ff172010-08-15 21:35:32 +00002910 n=(size_t) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+2UL);
cristy3ed852e2009-09-05 21:47:34 +00002911 theta=DegreesToRadians(angle)/(MagickRealType) (n-1);
2912 cos_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
2913 sizeof(*cos_theta));
2914 sin_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
2915 sizeof(*sin_theta));
2916 if ((cos_theta == (MagickRealType *) NULL) ||
2917 (sin_theta == (MagickRealType *) NULL))
2918 {
2919 blur_image=DestroyImage(blur_image);
2920 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2921 }
2922 offset=theta*(MagickRealType) (n-1)/2.0;
cristybb503372010-05-27 20:51:26 +00002923 for (i=0; i < (ssize_t) n; i++)
cristy3ed852e2009-09-05 21:47:34 +00002924 {
2925 cos_theta[i]=cos((double) (theta*i-offset));
2926 sin_theta[i]=sin((double) (theta*i-offset));
2927 }
2928 /*
2929 Radial blur image.
2930 */
2931 status=MagickTrue;
2932 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00002933 image_view=AcquireCacheView(image);
2934 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00002935#if defined(MAGICKCORE_OPENMP_SUPPORT)
2936 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002937#endif
cristy1e7aa312011-09-10 20:01:36 +00002938 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002939 {
cristy1e7aa312011-09-10 20:01:36 +00002940 register const Quantum
2941 *restrict p;
2942
cristy4c08aed2011-07-01 19:47:50 +00002943 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002944 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002945
cristy117ff172010-08-15 21:35:32 +00002946 register ssize_t
2947 x;
2948
cristy3ed852e2009-09-05 21:47:34 +00002949 if (status == MagickFalse)
2950 continue;
cristy1e7aa312011-09-10 20:01:36 +00002951 p=GetCacheViewVirtualPixels(blur_view,0,y,image->columns,1,exception);
cristy3ed852e2009-09-05 21:47:34 +00002952 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
2953 exception);
cristy1e7aa312011-09-10 20:01:36 +00002954 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00002955 {
2956 status=MagickFalse;
2957 continue;
2958 }
cristy1e7aa312011-09-10 20:01:36 +00002959 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002960 {
cristy3ed852e2009-09-05 21:47:34 +00002961 MagickRealType
cristy3ed852e2009-09-05 21:47:34 +00002962 radius;
2963
cristy3ed852e2009-09-05 21:47:34 +00002964 PointInfo
2965 center;
2966
cristybb503372010-05-27 20:51:26 +00002967 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002968 i;
2969
cristybb503372010-05-27 20:51:26 +00002970 size_t
cristy3ed852e2009-09-05 21:47:34 +00002971 step;
2972
2973 center.x=(double) x-blur_center.x;
2974 center.y=(double) y-blur_center.y;
2975 radius=hypot((double) center.x,center.y);
2976 if (radius == 0)
2977 step=1;
2978 else
2979 {
cristybb503372010-05-27 20:51:26 +00002980 step=(size_t) (blur_radius/radius);
cristy3ed852e2009-09-05 21:47:34 +00002981 if (step == 0)
2982 step=1;
2983 else
2984 if (step >= n)
2985 step=n-1;
2986 }
cristy1e7aa312011-09-10 20:01:36 +00002987 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2988 {
2989 MagickRealType
2990 gamma,
2991 pixel;
cristy3ed852e2009-09-05 21:47:34 +00002992
cristy1e7aa312011-09-10 20:01:36 +00002993 PixelChannel
2994 channel;
2995
2996 PixelTrait
2997 blur_traits,
2998 traits;
2999
3000 register const Quantum
3001 *restrict r;
3002
3003 register ssize_t
3004 j;
3005
3006 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
3007 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
3008 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
3009 if ((traits == UndefinedPixelTrait) ||
3010 (blur_traits == UndefinedPixelTrait))
3011 continue;
3012 if ((blur_traits & CopyPixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003013 {
cristy1e7aa312011-09-10 20:01:36 +00003014 q[channel]=p[i];
3015 continue;
cristy3ed852e2009-09-05 21:47:34 +00003016 }
cristy1e7aa312011-09-10 20:01:36 +00003017 gamma=0.0;
3018 pixel=bias;
3019 if ((blur_traits & BlendPixelTrait) == 0)
3020 {
3021 for (j=0; j < (ssize_t) n; j+=(ssize_t) step)
3022 {
3023 r=GetCacheViewVirtualPixels(image_view, (ssize_t) (blur_center.x+
3024 center.x*cos_theta[j]-center.y*sin_theta[j]+0.5),(ssize_t)
3025 (blur_center.y+center.x*sin_theta[j]+center.y*cos_theta[j]+0.5),
3026 1,1,exception);
3027 if (r == (const Quantum *) NULL)
3028 {
3029 status=MagickFalse;
3030 continue;
3031 }
3032 pixel+=r[i];
3033 gamma++;
3034 }
3035 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3036 q[channel]=ClampToQuantum(gamma*pixel);
3037 continue;
3038 }
3039 for (j=0; j < (ssize_t) n; j+=(ssize_t) step)
3040 {
3041 r=GetCacheViewVirtualPixels(image_view, (ssize_t) (blur_center.x+
3042 center.x*cos_theta[j]-center.y*sin_theta[j]+0.5),(ssize_t)
3043 (blur_center.y+center.x*sin_theta[j]+center.y*cos_theta[j]+0.5),
3044 1,1,exception);
3045 if (r == (const Quantum *) NULL)
3046 {
3047 status=MagickFalse;
3048 continue;
3049 }
3050 pixel+=GetPixelAlpha(image,r)*r[i];
3051 gamma+=GetPixelAlpha(image,r);
cristy3ed852e2009-09-05 21:47:34 +00003052 }
cristy1e7aa312011-09-10 20:01:36 +00003053 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3054 q[channel]=ClampToQuantum(gamma*pixel);
3055 }
3056 p+=GetPixelChannels(image);
cristyed231572011-07-14 02:18:59 +00003057 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003058 }
3059 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
3060 status=MagickFalse;
3061 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3062 {
3063 MagickBooleanType
3064 proceed;
3065
cristyb5d5f722009-11-04 03:03:49 +00003066#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00003067 #pragma omp critical (MagickCore_RadialBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00003068#endif
3069 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
3070 if (proceed == MagickFalse)
3071 status=MagickFalse;
3072 }
3073 }
3074 blur_view=DestroyCacheView(blur_view);
3075 image_view=DestroyCacheView(image_view);
3076 cos_theta=(MagickRealType *) RelinquishMagickMemory(cos_theta);
3077 sin_theta=(MagickRealType *) RelinquishMagickMemory(sin_theta);
3078 if (status == MagickFalse)
3079 blur_image=DestroyImage(blur_image);
3080 return(blur_image);
3081}
3082
3083/*
3084%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3085% %
3086% %
3087% %
cristy3ed852e2009-09-05 21:47:34 +00003088% S e l e c t i v e B l u r I m a g e %
3089% %
3090% %
3091% %
3092%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3093%
3094% SelectiveBlurImage() selectively blur pixels within a contrast threshold.
3095% It is similar to the unsharpen mask that sharpens everything with contrast
3096% above a certain threshold.
3097%
3098% The format of the SelectiveBlurImage method is:
3099%
3100% Image *SelectiveBlurImage(const Image *image,const double radius,
cristy1e7aa312011-09-10 20:01:36 +00003101% const double sigma,const double threshold,const double bias,
3102% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003103%
3104% A description of each parameter follows:
3105%
3106% o image: the image.
3107%
cristy3ed852e2009-09-05 21:47:34 +00003108% o radius: the radius of the Gaussian, in pixels, not counting the center
3109% pixel.
3110%
3111% o sigma: the standard deviation of the Gaussian, in pixels.
3112%
3113% o threshold: only pixels within this contrast threshold are included
3114% in the blur operation.
3115%
cristy1e7aa312011-09-10 20:01:36 +00003116% o bias: the bias.
3117%
cristy3ed852e2009-09-05 21:47:34 +00003118% o exception: return any errors or warnings in this structure.
3119%
3120*/
cristyf4ad9df2011-07-08 16:49:03 +00003121MagickExport Image *SelectiveBlurImage(const Image *image,
3122 const double radius,const double sigma,const double threshold,
cristy1e7aa312011-09-10 20:01:36 +00003123 const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003124{
3125#define SelectiveBlurImageTag "SelectiveBlur/Image"
3126
cristy47e00502009-12-17 19:19:57 +00003127 CacheView
3128 *blur_view,
3129 *image_view;
3130
cristy3ed852e2009-09-05 21:47:34 +00003131 double
cristy3ed852e2009-09-05 21:47:34 +00003132 *kernel;
3133
3134 Image
3135 *blur_image;
3136
cristy3ed852e2009-09-05 21:47:34 +00003137 MagickBooleanType
3138 status;
3139
cristybb503372010-05-27 20:51:26 +00003140 MagickOffsetType
3141 progress;
3142
cristybb503372010-05-27 20:51:26 +00003143 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003144 i;
cristy3ed852e2009-09-05 21:47:34 +00003145
cristybb503372010-05-27 20:51:26 +00003146 size_t
cristy3ed852e2009-09-05 21:47:34 +00003147 width;
3148
cristybb503372010-05-27 20:51:26 +00003149 ssize_t
cristyc8523c12011-09-13 00:02:53 +00003150 center,
cristybb503372010-05-27 20:51:26 +00003151 j,
3152 u,
3153 v,
3154 y;
3155
cristy3ed852e2009-09-05 21:47:34 +00003156 /*
3157 Initialize blur image attributes.
3158 */
3159 assert(image != (Image *) NULL);
3160 assert(image->signature == MagickSignature);
3161 if (image->debug != MagickFalse)
3162 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3163 assert(exception != (ExceptionInfo *) NULL);
3164 assert(exception->signature == MagickSignature);
3165 width=GetOptimalKernelWidth1D(radius,sigma);
3166 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
3167 if (kernel == (double *) NULL)
3168 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00003169 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00003170 i=0;
cristy47e00502009-12-17 19:19:57 +00003171 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00003172 {
cristy47e00502009-12-17 19:19:57 +00003173 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00003174 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
3175 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00003176 }
3177 if (image->debug != MagickFalse)
3178 {
3179 char
3180 format[MaxTextExtent],
3181 *message;
3182
cristy117ff172010-08-15 21:35:32 +00003183 register const double
3184 *k;
3185
cristybb503372010-05-27 20:51:26 +00003186 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003187 u,
3188 v;
3189
cristy3ed852e2009-09-05 21:47:34 +00003190 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00003191 " SelectiveBlurImage with %.20gx%.20g kernel:",(double) width,(double)
3192 width);
cristy3ed852e2009-09-05 21:47:34 +00003193 message=AcquireString("");
3194 k=kernel;
cristybb503372010-05-27 20:51:26 +00003195 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003196 {
3197 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00003198 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy3ed852e2009-09-05 21:47:34 +00003199 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00003200 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003201 {
cristyb51dff52011-05-19 16:55:47 +00003202 (void) FormatLocaleString(format,MaxTextExtent,"%+f ",*k++);
cristy3ed852e2009-09-05 21:47:34 +00003203 (void) ConcatenateString(&message,format);
3204 }
3205 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
3206 }
3207 message=DestroyString(message);
3208 }
cristy1e7aa312011-09-10 20:01:36 +00003209 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00003210 if (blur_image == (Image *) NULL)
3211 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00003212 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003213 {
cristy3ed852e2009-09-05 21:47:34 +00003214 blur_image=DestroyImage(blur_image);
3215 return((Image *) NULL);
3216 }
3217 /*
3218 Threshold blur image.
3219 */
3220 status=MagickTrue;
3221 progress=0;
cristyc8523c12011-09-13 00:02:53 +00003222 center=(ssize_t) (GetPixelChannels(image)*(image->columns+width)*(width/2L)+
3223 GetPixelChannels(image)*(width/2L));
cristy3ed852e2009-09-05 21:47:34 +00003224 image_view=AcquireCacheView(image);
3225 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003226#if defined(MAGICKCORE_OPENMP_SUPPORT)
3227 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003228#endif
cristybb503372010-05-27 20:51:26 +00003229 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003230 {
cristy4c08aed2011-07-01 19:47:50 +00003231 double
3232 contrast;
3233
cristy3ed852e2009-09-05 21:47:34 +00003234 MagickBooleanType
3235 sync;
3236
cristy4c08aed2011-07-01 19:47:50 +00003237 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003238 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003239
cristy4c08aed2011-07-01 19:47:50 +00003240 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003241 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003242
cristy117ff172010-08-15 21:35:32 +00003243 register ssize_t
3244 x;
3245
cristy3ed852e2009-09-05 21:47:34 +00003246 if (status == MagickFalse)
3247 continue;
cristy117ff172010-08-15 21:35:32 +00003248 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
3249 (width/2L),image->columns+width,width,exception);
cristy3ed852e2009-09-05 21:47:34 +00003250 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3251 exception);
cristy4c08aed2011-07-01 19:47:50 +00003252 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003253 {
3254 status=MagickFalse;
3255 continue;
3256 }
cristybb503372010-05-27 20:51:26 +00003257 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003258 {
cristybb503372010-05-27 20:51:26 +00003259 register ssize_t
cristy1e7aa312011-09-10 20:01:36 +00003260 i;
cristy3ed852e2009-09-05 21:47:34 +00003261
cristy1e7aa312011-09-10 20:01:36 +00003262 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3263 {
3264 MagickRealType
3265 alpha,
3266 gamma,
3267 intensity,
3268 pixel;
cristy117ff172010-08-15 21:35:32 +00003269
cristy1e7aa312011-09-10 20:01:36 +00003270 PixelChannel
3271 channel;
3272
3273 PixelTrait
3274 blur_traits,
3275 traits;
3276
3277 register const double
3278 *restrict k;
3279
3280 register const Quantum
3281 *restrict pixels;
3282
3283 register ssize_t
3284 u;
3285
3286 ssize_t
3287 v;
3288
3289 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
3290 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
3291 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
3292 if ((traits == UndefinedPixelTrait) ||
3293 (blur_traits == UndefinedPixelTrait))
3294 continue;
3295 if ((blur_traits & CopyPixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003296 {
cristy1e7aa312011-09-10 20:01:36 +00003297 q[channel]=p[center+i];
3298 continue;
cristy3ed852e2009-09-05 21:47:34 +00003299 }
cristy1e7aa312011-09-10 20:01:36 +00003300 k=kernel;
3301 pixel=bias;
3302 pixels=p;
cristyc8523c12011-09-13 00:02:53 +00003303 intensity=(MagickRealType) GetPixelIntensity(image,p+center);
cristy1e7aa312011-09-10 20:01:36 +00003304 gamma=0.0;
3305 if ((blur_traits & BlendPixelTrait) == 0)
cristy3ed852e2009-09-05 21:47:34 +00003306 {
cristy1e7aa312011-09-10 20:01:36 +00003307 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003308 {
cristy1e7aa312011-09-10 20:01:36 +00003309 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003310 {
cristy1e7aa312011-09-10 20:01:36 +00003311 contrast=GetPixelIntensity(image,pixels)-intensity;
3312 if (fabs(contrast) < threshold)
3313 {
3314 pixel+=(*k)*pixels[i];
3315 gamma+=(*k);
3316 }
3317 k++;
3318 pixels+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003319 }
cristy1e7aa312011-09-10 20:01:36 +00003320 pixels+=image->columns*GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003321 }
cristy1e7aa312011-09-10 20:01:36 +00003322 if (fabs((double) gamma) < MagickEpsilon)
3323 {
3324 q[channel]=p[center+i];
3325 continue;
3326 }
3327 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3328 q[channel]=ClampToQuantum(gamma*pixel);
3329 continue;
3330 }
3331 for (v=0; v < (ssize_t) width; v++)
3332 {
3333 for (u=0; u < (ssize_t) width; u++)
3334 {
3335 contrast=GetPixelIntensity(image,pixels)-intensity;
3336 if (fabs(contrast) < threshold)
3337 {
3338 alpha=(MagickRealType) (QuantumScale*
3339 GetPixelAlpha(image,pixels));
3340 pixel+=(*k)*alpha*pixels[i];
3341 gamma+=(*k)*alpha;
3342 }
3343 k++;
3344 pixels+=GetPixelChannels(image);
3345 }
3346 pixels+=image->columns*GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003347 }
cristy1e7aa312011-09-10 20:01:36 +00003348 if (fabs((double) gamma) < MagickEpsilon)
3349 {
3350 q[channel]=p[center+i];
3351 continue;
3352 }
3353 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3354 q[channel]=ClampToQuantum(gamma*pixel);
3355 }
cristyed231572011-07-14 02:18:59 +00003356 p+=GetPixelChannels(image);
3357 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003358 }
3359 sync=SyncCacheViewAuthenticPixels(blur_view,exception);
3360 if (sync == MagickFalse)
3361 status=MagickFalse;
3362 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3363 {
3364 MagickBooleanType
3365 proceed;
3366
cristyb5d5f722009-11-04 03:03:49 +00003367#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00003368 #pragma omp critical (MagickCore_SelectiveBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00003369#endif
3370 proceed=SetImageProgress(image,SelectiveBlurImageTag,progress++,
3371 image->rows);
3372 if (proceed == MagickFalse)
3373 status=MagickFalse;
3374 }
3375 }
3376 blur_image->type=image->type;
3377 blur_view=DestroyCacheView(blur_view);
3378 image_view=DestroyCacheView(image_view);
3379 kernel=(double *) RelinquishMagickMemory(kernel);
3380 if (status == MagickFalse)
3381 blur_image=DestroyImage(blur_image);
3382 return(blur_image);
3383}
3384
3385/*
3386%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3387% %
3388% %
3389% %
3390% S h a d e I m a g e %
3391% %
3392% %
3393% %
3394%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3395%
3396% ShadeImage() shines a distant light on an image to create a
3397% three-dimensional effect. You control the positioning of the light with
3398% azimuth and elevation; azimuth is measured in degrees off the x axis
3399% and elevation is measured in pixels above the Z axis.
3400%
3401% The format of the ShadeImage method is:
3402%
3403% Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3404% const double azimuth,const double elevation,ExceptionInfo *exception)
3405%
3406% A description of each parameter follows:
3407%
3408% o image: the image.
3409%
3410% o gray: A value other than zero shades the intensity of each pixel.
3411%
3412% o azimuth, elevation: Define the light source direction.
3413%
3414% o exception: return any errors or warnings in this structure.
3415%
3416*/
3417MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3418 const double azimuth,const double elevation,ExceptionInfo *exception)
3419{
3420#define ShadeImageTag "Shade/Image"
3421
cristyc4c8d132010-01-07 01:58:38 +00003422 CacheView
3423 *image_view,
3424 *shade_view;
3425
cristy3ed852e2009-09-05 21:47:34 +00003426 Image
3427 *shade_image;
3428
cristy3ed852e2009-09-05 21:47:34 +00003429 MagickBooleanType
3430 status;
3431
cristybb503372010-05-27 20:51:26 +00003432 MagickOffsetType
3433 progress;
3434
cristy3ed852e2009-09-05 21:47:34 +00003435 PrimaryInfo
3436 light;
3437
cristybb503372010-05-27 20:51:26 +00003438 ssize_t
3439 y;
3440
cristy3ed852e2009-09-05 21:47:34 +00003441 /*
3442 Initialize shaded image attributes.
3443 */
3444 assert(image != (const Image *) NULL);
3445 assert(image->signature == MagickSignature);
3446 if (image->debug != MagickFalse)
3447 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3448 assert(exception != (ExceptionInfo *) NULL);
3449 assert(exception->signature == MagickSignature);
3450 shade_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
3451 if (shade_image == (Image *) NULL)
3452 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00003453 if (SetImageStorageClass(shade_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003454 {
cristy3ed852e2009-09-05 21:47:34 +00003455 shade_image=DestroyImage(shade_image);
3456 return((Image *) NULL);
3457 }
3458 /*
3459 Compute the light vector.
3460 */
3461 light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))*
3462 cos(DegreesToRadians(elevation));
3463 light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))*
3464 cos(DegreesToRadians(elevation));
3465 light.z=(double) QuantumRange*sin(DegreesToRadians(elevation));
3466 /*
3467 Shade image.
3468 */
3469 status=MagickTrue;
3470 progress=0;
3471 image_view=AcquireCacheView(image);
3472 shade_view=AcquireCacheView(shade_image);
cristyb5d5f722009-11-04 03:03:49 +00003473#if defined(MAGICKCORE_OPENMP_SUPPORT)
3474 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003475#endif
cristybb503372010-05-27 20:51:26 +00003476 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003477 {
3478 MagickRealType
3479 distance,
3480 normal_distance,
3481 shade;
3482
3483 PrimaryInfo
3484 normal;
3485
cristy4c08aed2011-07-01 19:47:50 +00003486 register const Quantum
cristy1e7aa312011-09-10 20:01:36 +00003487 *restrict center,
cristyc47d1f82009-11-26 01:44:43 +00003488 *restrict p,
cristy1e7aa312011-09-10 20:01:36 +00003489 *restrict post,
3490 *restrict pre;
cristy3ed852e2009-09-05 21:47:34 +00003491
cristy4c08aed2011-07-01 19:47:50 +00003492 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003493 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003494
cristy117ff172010-08-15 21:35:32 +00003495 register ssize_t
3496 x;
3497
cristy3ed852e2009-09-05 21:47:34 +00003498 if (status == MagickFalse)
3499 continue;
3500 p=GetCacheViewVirtualPixels(image_view,-1,y-1,image->columns+2,3,exception);
3501 q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1,
3502 exception);
cristy4c08aed2011-07-01 19:47:50 +00003503 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003504 {
3505 status=MagickFalse;
3506 continue;
3507 }
3508 /*
3509 Shade this row of pixels.
3510 */
3511 normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */
cristy1e7aa312011-09-10 20:01:36 +00003512 pre=p+GetPixelChannels(image);
3513 center=pre+(image->columns+2)*GetPixelChannels(image);
3514 post=center+(image->columns+2)*GetPixelChannels(image);
cristybb503372010-05-27 20:51:26 +00003515 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003516 {
cristy1e7aa312011-09-10 20:01:36 +00003517 register ssize_t
3518 i;
3519
cristy3ed852e2009-09-05 21:47:34 +00003520 /*
3521 Determine the surface normal and compute shading.
3522 */
cristy1e7aa312011-09-10 20:01:36 +00003523 normal.x=(double) (GetPixelIntensity(image,pre-GetPixelChannels(image))+
3524 GetPixelIntensity(image,center-GetPixelChannels(image))+
3525 GetPixelIntensity(image,post-GetPixelChannels(image))-
3526 GetPixelIntensity(image,pre+GetPixelChannels(image))-
3527 GetPixelIntensity(image,center+GetPixelChannels(image))-
3528 GetPixelIntensity(image,post+GetPixelChannels(image)));
3529 normal.y=(double) (GetPixelIntensity(image,post-GetPixelChannels(image))+
3530 GetPixelIntensity(image,post)+GetPixelIntensity(image,post+
3531 GetPixelChannels(image))-GetPixelIntensity(image,pre-
3532 GetPixelChannels(image))-GetPixelIntensity(image,pre)-
3533 GetPixelIntensity(image,pre+GetPixelChannels(image)));
cristy3ed852e2009-09-05 21:47:34 +00003534 if ((normal.x == 0.0) && (normal.y == 0.0))
3535 shade=light.z;
3536 else
3537 {
3538 shade=0.0;
3539 distance=normal.x*light.x+normal.y*light.y+normal.z*light.z;
3540 if (distance > MagickEpsilon)
3541 {
3542 normal_distance=
3543 normal.x*normal.x+normal.y*normal.y+normal.z*normal.z;
3544 if (normal_distance > (MagickEpsilon*MagickEpsilon))
3545 shade=distance/sqrt((double) normal_distance);
3546 }
3547 }
cristy1e7aa312011-09-10 20:01:36 +00003548 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3549 {
3550 PixelChannel
3551 channel;
3552
3553 PixelTrait
3554 shade_traits,
3555 traits;
3556
3557 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
3558 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
3559 shade_traits=GetPixelChannelMapTraits(shade_image,channel);
3560 if ((traits == UndefinedPixelTrait) ||
3561 (shade_traits == UndefinedPixelTrait))
3562 continue;
3563 if ((shade_traits & CopyPixelTrait) != 0)
3564 {
3565 q[channel]=center[i];
3566 continue;
3567 }
3568 if (gray != MagickFalse)
3569 {
3570 q[channel]=ClampToQuantum(shade);
3571 continue;
3572 }
3573 q[channel]=ClampToQuantum(QuantumScale*shade*center[i]);
3574 }
3575 pre+=GetPixelChannels(image);
3576 center+=GetPixelChannels(image);
3577 post+=GetPixelChannels(image);
cristyed231572011-07-14 02:18:59 +00003578 q+=GetPixelChannels(shade_image);
cristy3ed852e2009-09-05 21:47:34 +00003579 }
3580 if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
3581 status=MagickFalse;
3582 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3583 {
3584 MagickBooleanType
3585 proceed;
3586
cristyb5d5f722009-11-04 03:03:49 +00003587#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003588 #pragma omp critical (MagickCore_ShadeImage)
3589#endif
3590 proceed=SetImageProgress(image,ShadeImageTag,progress++,image->rows);
3591 if (proceed == MagickFalse)
3592 status=MagickFalse;
3593 }
3594 }
3595 shade_view=DestroyCacheView(shade_view);
3596 image_view=DestroyCacheView(image_view);
3597 if (status == MagickFalse)
3598 shade_image=DestroyImage(shade_image);
3599 return(shade_image);
3600}
3601
3602/*
3603%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3604% %
3605% %
3606% %
3607% S h a r p e n I m a g e %
3608% %
3609% %
3610% %
3611%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3612%
3613% SharpenImage() sharpens the image. We convolve the image with a Gaussian
3614% operator of the given radius and standard deviation (sigma). For
3615% reasonable results, radius should be larger than sigma. Use a radius of 0
3616% and SharpenImage() selects a suitable radius for you.
3617%
3618% Using a separable kernel would be faster, but the negative weights cancel
3619% out on the corners of the kernel producing often undesirable ringing in the
3620% filtered result; this can be avoided by using a 2D gaussian shaped image
3621% sharpening kernel instead.
3622%
3623% The format of the SharpenImage method is:
3624%
3625% Image *SharpenImage(const Image *image,const double radius,
cristy05c0c9a2011-09-05 23:16:13 +00003626% const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003627%
3628% A description of each parameter follows:
3629%
3630% o image: the image.
3631%
cristy3ed852e2009-09-05 21:47:34 +00003632% o radius: the radius of the Gaussian, in pixels, not counting the center
3633% pixel.
3634%
3635% o sigma: the standard deviation of the Laplacian, in pixels.
3636%
cristy05c0c9a2011-09-05 23:16:13 +00003637% o bias: bias.
3638%
cristy3ed852e2009-09-05 21:47:34 +00003639% o exception: return any errors or warnings in this structure.
3640%
3641*/
cristy3ed852e2009-09-05 21:47:34 +00003642MagickExport Image *SharpenImage(const Image *image,const double radius,
cristy05c0c9a2011-09-05 23:16:13 +00003643 const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003644{
cristy3ed852e2009-09-05 21:47:34 +00003645 double
cristy47e00502009-12-17 19:19:57 +00003646 normalize;
cristy3ed852e2009-09-05 21:47:34 +00003647
3648 Image
3649 *sharp_image;
3650
cristy41cbe682011-07-15 19:12:37 +00003651 KernelInfo
3652 *kernel_info;
3653
cristybb503372010-05-27 20:51:26 +00003654 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003655 i;
3656
cristybb503372010-05-27 20:51:26 +00003657 size_t
cristy3ed852e2009-09-05 21:47:34 +00003658 width;
3659
cristy117ff172010-08-15 21:35:32 +00003660 ssize_t
3661 j,
3662 u,
3663 v;
3664
cristy3ed852e2009-09-05 21:47:34 +00003665 assert(image != (const Image *) NULL);
3666 assert(image->signature == MagickSignature);
3667 if (image->debug != MagickFalse)
3668 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3669 assert(exception != (ExceptionInfo *) NULL);
3670 assert(exception->signature == MagickSignature);
3671 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00003672 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00003673 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003674 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00003675 (void) ResetMagickMemory(kernel_info,0,sizeof(*kernel_info));
3676 kernel_info->width=width;
3677 kernel_info->height=width;
cristy05c0c9a2011-09-05 23:16:13 +00003678 kernel_info->bias=bias;
cristy41cbe682011-07-15 19:12:37 +00003679 kernel_info->signature=MagickSignature;
cristy9f752c02011-09-14 19:44:03 +00003680 kernel_info->values=(double *) AcquireAlignedMemory(kernel_info->width,
cristy41cbe682011-07-15 19:12:37 +00003681 kernel_info->width*sizeof(*kernel_info->values));
3682 if (kernel_info->values == (double *) NULL)
3683 {
3684 kernel_info=DestroyKernelInfo(kernel_info);
3685 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3686 }
cristy3ed852e2009-09-05 21:47:34 +00003687 normalize=0.0;
cristy41cbe682011-07-15 19:12:37 +00003688 j=(ssize_t) kernel_info->width/2;
cristy47e00502009-12-17 19:19:57 +00003689 i=0;
3690 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00003691 {
cristy47e00502009-12-17 19:19:57 +00003692 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00003693 {
cristy41cbe682011-07-15 19:12:37 +00003694 kernel_info->values[i]=(double) (-exp(-((double) u*u+v*v)/(2.0*
3695 MagickSigma*MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
3696 normalize+=kernel_info->values[i];
cristy3ed852e2009-09-05 21:47:34 +00003697 i++;
3698 }
3699 }
cristy41cbe682011-07-15 19:12:37 +00003700 kernel_info->values[i/2]=(double) ((-2.0)*normalize);
cristy5e6be1e2011-07-16 01:23:39 +00003701 sharp_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00003702 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00003703 return(sharp_image);
3704}
3705
3706/*
3707%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3708% %
3709% %
3710% %
3711% S p r e a d I m a g e %
3712% %
3713% %
3714% %
3715%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3716%
3717% SpreadImage() is a special effects method that randomly displaces each
3718% pixel in a block defined by the radius parameter.
3719%
3720% The format of the SpreadImage method is:
3721%
3722% Image *SpreadImage(const Image *image,const double radius,
cristy5c4e2582011-09-11 19:21:03 +00003723% const PixelInterpolateMethod method,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003724%
3725% A description of each parameter follows:
3726%
3727% o image: the image.
3728%
cristy5c4e2582011-09-11 19:21:03 +00003729% o radius: choose a random pixel in a neighborhood of this extent.
3730%
3731% o method: the pixel interpolation method.
cristy3ed852e2009-09-05 21:47:34 +00003732%
3733% o exception: return any errors or warnings in this structure.
3734%
3735*/
3736MagickExport Image *SpreadImage(const Image *image,const double radius,
cristy5c4e2582011-09-11 19:21:03 +00003737 const PixelInterpolateMethod method,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003738{
3739#define SpreadImageTag "Spread/Image"
3740
cristyfa112112010-01-04 17:48:07 +00003741 CacheView
cristy9f7e7cb2011-03-26 00:49:57 +00003742 *image_view,
3743 *spread_view;
cristyfa112112010-01-04 17:48:07 +00003744
cristy3ed852e2009-09-05 21:47:34 +00003745 Image
3746 *spread_image;
3747
cristy3ed852e2009-09-05 21:47:34 +00003748 MagickBooleanType
3749 status;
3750
cristybb503372010-05-27 20:51:26 +00003751 MagickOffsetType
3752 progress;
3753
cristy3ed852e2009-09-05 21:47:34 +00003754 RandomInfo
cristyfa112112010-01-04 17:48:07 +00003755 **restrict random_info;
cristy3ed852e2009-09-05 21:47:34 +00003756
cristybb503372010-05-27 20:51:26 +00003757 size_t
cristy3ed852e2009-09-05 21:47:34 +00003758 width;
3759
cristybb503372010-05-27 20:51:26 +00003760 ssize_t
3761 y;
3762
cristy3ed852e2009-09-05 21:47:34 +00003763 /*
3764 Initialize spread image attributes.
3765 */
3766 assert(image != (Image *) NULL);
3767 assert(image->signature == MagickSignature);
3768 if (image->debug != MagickFalse)
3769 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3770 assert(exception != (ExceptionInfo *) NULL);
3771 assert(exception->signature == MagickSignature);
3772 spread_image=CloneImage(image,image->columns,image->rows,MagickTrue,
3773 exception);
3774 if (spread_image == (Image *) NULL)
3775 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00003776 if (SetImageStorageClass(spread_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003777 {
cristy3ed852e2009-09-05 21:47:34 +00003778 spread_image=DestroyImage(spread_image);
3779 return((Image *) NULL);
3780 }
3781 /*
3782 Spread image.
3783 */
3784 status=MagickTrue;
3785 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00003786 width=GetOptimalKernelWidth1D(radius,0.5);
cristy3ed852e2009-09-05 21:47:34 +00003787 random_info=AcquireRandomInfoThreadSet();
cristy9f7e7cb2011-03-26 00:49:57 +00003788 image_view=AcquireCacheView(image);
3789 spread_view=AcquireCacheView(spread_image);
cristyb557a152011-02-22 12:14:30 +00003790#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00003791 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00003792#endif
cristybe82ad52011-09-06 01:17:20 +00003793 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003794 {
cristy5c9e6f22010-09-17 17:31:01 +00003795 const int
3796 id = GetOpenMPThreadId();
cristy6ebe97c2010-07-03 01:17:28 +00003797
cristybe82ad52011-09-06 01:17:20 +00003798 register const Quantum
3799 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003800
cristy4c08aed2011-07-01 19:47:50 +00003801 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003802 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003803
cristy117ff172010-08-15 21:35:32 +00003804 register ssize_t
3805 x;
3806
cristy3ed852e2009-09-05 21:47:34 +00003807 if (status == MagickFalse)
3808 continue;
cristybe82ad52011-09-06 01:17:20 +00003809 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy9f7e7cb2011-03-26 00:49:57 +00003810 q=QueueCacheViewAuthenticPixels(spread_view,0,y,spread_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +00003811 exception);
cristybe82ad52011-09-06 01:17:20 +00003812 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003813 {
3814 status=MagickFalse;
3815 continue;
3816 }
cristybe82ad52011-09-06 01:17:20 +00003817 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003818 {
cristybe82ad52011-09-06 01:17:20 +00003819 PointInfo
3820 point;
3821
cristybe82ad52011-09-06 01:17:20 +00003822 point.x=GetPseudoRandomValue(random_info[id]);
3823 point.y=GetPseudoRandomValue(random_info[id]);
cristy5c4e2582011-09-11 19:21:03 +00003824 status=InterpolatePixelChannels(image,image_view,spread_image,method,
3825 (double) x+width*point.x-0.5,(double) y+width*point.y-0.5,q,exception);
cristyed231572011-07-14 02:18:59 +00003826 q+=GetPixelChannels(spread_image);
cristy3ed852e2009-09-05 21:47:34 +00003827 }
cristy9f7e7cb2011-03-26 00:49:57 +00003828 if (SyncCacheViewAuthenticPixels(spread_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003829 status=MagickFalse;
3830 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3831 {
3832 MagickBooleanType
3833 proceed;
3834
cristyb557a152011-02-22 12:14:30 +00003835#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003836 #pragma omp critical (MagickCore_SpreadImage)
3837#endif
3838 proceed=SetImageProgress(image,SpreadImageTag,progress++,image->rows);
3839 if (proceed == MagickFalse)
3840 status=MagickFalse;
3841 }
3842 }
cristy9f7e7cb2011-03-26 00:49:57 +00003843 spread_view=DestroyCacheView(spread_view);
cristy3ed852e2009-09-05 21:47:34 +00003844 image_view=DestroyCacheView(image_view);
3845 random_info=DestroyRandomInfoThreadSet(random_info);
cristy3ed852e2009-09-05 21:47:34 +00003846 return(spread_image);
3847}
3848
3849/*
3850%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3851% %
3852% %
3853% %
cristy0834d642011-03-18 18:26:08 +00003854% S t a t i s t i c I m a g e %
3855% %
3856% %
3857% %
3858%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3859%
3860% StatisticImage() makes each pixel the min / max / median / mode / etc. of
cristy8d752042011-03-19 01:00:36 +00003861% the neighborhood of the specified width and height.
cristy0834d642011-03-18 18:26:08 +00003862%
3863% The format of the StatisticImage method is:
3864%
3865% Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00003866% const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00003867%
3868% A description of each parameter follows:
3869%
3870% o image: the image.
3871%
cristy0834d642011-03-18 18:26:08 +00003872% o type: the statistic type (median, mode, etc.).
3873%
cristy95c38342011-03-18 22:39:51 +00003874% o width: the width of the pixel neighborhood.
3875%
3876% o height: the height of the pixel neighborhood.
cristy0834d642011-03-18 18:26:08 +00003877%
3878% o exception: return any errors or warnings in this structure.
3879%
3880*/
3881
cristyf36cbcb2011-09-07 13:28:22 +00003882typedef struct _SkipNode
cristy733678d2011-03-18 21:29:28 +00003883{
3884 size_t
3885 next[9],
3886 count,
3887 signature;
cristyf36cbcb2011-09-07 13:28:22 +00003888} SkipNode;
cristy733678d2011-03-18 21:29:28 +00003889
3890typedef struct _SkipList
3891{
3892 ssize_t
3893 level;
3894
cristyf36cbcb2011-09-07 13:28:22 +00003895 SkipNode
cristy733678d2011-03-18 21:29:28 +00003896 *nodes;
3897} SkipList;
3898
3899typedef struct _PixelList
3900{
3901 size_t
cristy6fc86bb2011-03-18 23:45:16 +00003902 length,
cristyf36cbcb2011-09-07 13:28:22 +00003903 seed;
cristy733678d2011-03-18 21:29:28 +00003904
3905 SkipList
cristyf36cbcb2011-09-07 13:28:22 +00003906 skip_list;
3907
3908 size_t
3909 signature;
cristy733678d2011-03-18 21:29:28 +00003910} PixelList;
3911
3912static PixelList *DestroyPixelList(PixelList *pixel_list)
3913{
cristy733678d2011-03-18 21:29:28 +00003914 if (pixel_list == (PixelList *) NULL)
3915 return((PixelList *) NULL);
cristyf36cbcb2011-09-07 13:28:22 +00003916 if (pixel_list->skip_list.nodes != (SkipNode *) NULL)
3917 pixel_list->skip_list.nodes=(SkipNode *) RelinquishMagickMemory(
3918 pixel_list->skip_list.nodes);
cristy733678d2011-03-18 21:29:28 +00003919 pixel_list=(PixelList *) RelinquishMagickMemory(pixel_list);
3920 return(pixel_list);
3921}
3922
3923static PixelList **DestroyPixelListThreadSet(PixelList **pixel_list)
3924{
3925 register ssize_t
3926 i;
3927
3928 assert(pixel_list != (PixelList **) NULL);
3929 for (i=0; i < (ssize_t) GetOpenMPMaximumThreads(); i++)
3930 if (pixel_list[i] != (PixelList *) NULL)
3931 pixel_list[i]=DestroyPixelList(pixel_list[i]);
3932 pixel_list=(PixelList **) RelinquishMagickMemory(pixel_list);
3933 return(pixel_list);
3934}
3935
cristy6fc86bb2011-03-18 23:45:16 +00003936static PixelList *AcquirePixelList(const size_t width,const size_t height)
cristy733678d2011-03-18 21:29:28 +00003937{
3938 PixelList
3939 *pixel_list;
3940
cristy733678d2011-03-18 21:29:28 +00003941 pixel_list=(PixelList *) AcquireMagickMemory(sizeof(*pixel_list));
3942 if (pixel_list == (PixelList *) NULL)
3943 return(pixel_list);
3944 (void) ResetMagickMemory((void *) pixel_list,0,sizeof(*pixel_list));
cristy6fc86bb2011-03-18 23:45:16 +00003945 pixel_list->length=width*height;
cristyf36cbcb2011-09-07 13:28:22 +00003946 pixel_list->skip_list.nodes=(SkipNode *) AcquireQuantumMemory(65537UL,
3947 sizeof(*pixel_list->skip_list.nodes));
3948 if (pixel_list->skip_list.nodes == (SkipNode *) NULL)
3949 return(DestroyPixelList(pixel_list));
3950 (void) ResetMagickMemory(pixel_list->skip_list.nodes,0,65537UL*
3951 sizeof(*pixel_list->skip_list.nodes));
cristy733678d2011-03-18 21:29:28 +00003952 pixel_list->signature=MagickSignature;
3953 return(pixel_list);
3954}
3955
cristy6fc86bb2011-03-18 23:45:16 +00003956static PixelList **AcquirePixelListThreadSet(const size_t width,
3957 const size_t height)
cristy733678d2011-03-18 21:29:28 +00003958{
3959 PixelList
3960 **pixel_list;
3961
3962 register ssize_t
3963 i;
3964
3965 size_t
3966 number_threads;
3967
3968 number_threads=GetOpenMPMaximumThreads();
3969 pixel_list=(PixelList **) AcquireQuantumMemory(number_threads,
3970 sizeof(*pixel_list));
3971 if (pixel_list == (PixelList **) NULL)
3972 return((PixelList **) NULL);
3973 (void) ResetMagickMemory(pixel_list,0,number_threads*sizeof(*pixel_list));
3974 for (i=0; i < (ssize_t) number_threads; i++)
3975 {
cristy6fc86bb2011-03-18 23:45:16 +00003976 pixel_list[i]=AcquirePixelList(width,height);
cristy733678d2011-03-18 21:29:28 +00003977 if (pixel_list[i] == (PixelList *) NULL)
3978 return(DestroyPixelListThreadSet(pixel_list));
3979 }
3980 return(pixel_list);
3981}
3982
cristyf36cbcb2011-09-07 13:28:22 +00003983static void AddNodePixelList(PixelList *pixel_list,const size_t color)
cristy733678d2011-03-18 21:29:28 +00003984{
3985 register SkipList
cristyf36cbcb2011-09-07 13:28:22 +00003986 *p;
cristy733678d2011-03-18 21:29:28 +00003987
3988 register ssize_t
3989 level;
3990
3991 size_t
3992 search,
3993 update[9];
3994
3995 /*
3996 Initialize the node.
3997 */
cristyf36cbcb2011-09-07 13:28:22 +00003998 p=(&pixel_list->skip_list);
3999 p->nodes[color].signature=pixel_list->signature;
4000 p->nodes[color].count=1;
cristy733678d2011-03-18 21:29:28 +00004001 /*
4002 Determine where it belongs in the list.
4003 */
4004 search=65536UL;
cristyf36cbcb2011-09-07 13:28:22 +00004005 for (level=p->level; level >= 0; level--)
cristy733678d2011-03-18 21:29:28 +00004006 {
cristyf36cbcb2011-09-07 13:28:22 +00004007 while (p->nodes[search].next[level] < color)
4008 search=p->nodes[search].next[level];
cristy733678d2011-03-18 21:29:28 +00004009 update[level]=search;
4010 }
4011 /*
4012 Generate a pseudo-random level for this node.
4013 */
4014 for (level=0; ; level++)
4015 {
4016 pixel_list->seed=(pixel_list->seed*42893621L)+1L;
4017 if ((pixel_list->seed & 0x300) != 0x300)
4018 break;
4019 }
4020 if (level > 8)
4021 level=8;
cristyf36cbcb2011-09-07 13:28:22 +00004022 if (level > (p->level+2))
4023 level=p->level+2;
cristy733678d2011-03-18 21:29:28 +00004024 /*
4025 If we're raising the list's level, link back to the root node.
4026 */
cristyf36cbcb2011-09-07 13:28:22 +00004027 while (level > p->level)
cristy733678d2011-03-18 21:29:28 +00004028 {
cristyf36cbcb2011-09-07 13:28:22 +00004029 p->level++;
4030 update[p->level]=65536UL;
cristy733678d2011-03-18 21:29:28 +00004031 }
4032 /*
4033 Link the node into the skip-list.
4034 */
4035 do
4036 {
cristyf36cbcb2011-09-07 13:28:22 +00004037 p->nodes[color].next[level]=p->nodes[update[level]].next[level];
4038 p->nodes[update[level]].next[level]=color;
cristy3cba8ca2011-03-19 01:29:12 +00004039 } while (level-- > 0);
cristy733678d2011-03-18 21:29:28 +00004040}
4041
cristy075ff302011-09-07 01:51:24 +00004042static Quantum GetMaximumPixelList(PixelList *pixel_list)
cristy6fc86bb2011-03-18 23:45:16 +00004043{
cristy6fc86bb2011-03-18 23:45:16 +00004044 register SkipList
cristyf36cbcb2011-09-07 13:28:22 +00004045 *p;
cristy6fc86bb2011-03-18 23:45:16 +00004046
4047 size_t
cristyd76c51e2011-03-26 00:21:26 +00004048 color,
4049 maximum;
cristy49f37242011-03-22 18:18:23 +00004050
4051 ssize_t
cristy6fc86bb2011-03-18 23:45:16 +00004052 count;
4053
cristy6fc86bb2011-03-18 23:45:16 +00004054 /*
4055 Find the maximum value for each of the color.
4056 */
cristyf36cbcb2011-09-07 13:28:22 +00004057 p=(&pixel_list->skip_list);
4058 color=65536L;
4059 count=0;
4060 maximum=p->nodes[color].next[0];
4061 do
cristy6fc86bb2011-03-18 23:45:16 +00004062 {
cristyf36cbcb2011-09-07 13:28:22 +00004063 color=p->nodes[color].next[0];
4064 if (color > maximum)
4065 maximum=color;
4066 count+=p->nodes[color].count;
4067 } while (count < (ssize_t) pixel_list->length);
4068 return(ScaleShortToQuantum((unsigned short) maximum));
cristy49f37242011-03-22 18:18:23 +00004069}
4070
cristy075ff302011-09-07 01:51:24 +00004071static Quantum GetMeanPixelList(PixelList *pixel_list)
cristy49f37242011-03-22 18:18:23 +00004072{
cristy80a99a32011-03-30 01:30:23 +00004073 MagickRealType
4074 sum;
4075
cristy49f37242011-03-22 18:18:23 +00004076 register SkipList
cristyf36cbcb2011-09-07 13:28:22 +00004077 *p;
cristy49f37242011-03-22 18:18:23 +00004078
4079 size_t
cristy80a99a32011-03-30 01:30:23 +00004080 color;
cristy49f37242011-03-22 18:18:23 +00004081
4082 ssize_t
4083 count;
4084
cristy49f37242011-03-22 18:18:23 +00004085 /*
4086 Find the mean value for each of the color.
4087 */
cristyf36cbcb2011-09-07 13:28:22 +00004088 p=(&pixel_list->skip_list);
4089 color=65536L;
4090 count=0;
4091 sum=0.0;
4092 do
cristy49f37242011-03-22 18:18:23 +00004093 {
cristyf36cbcb2011-09-07 13:28:22 +00004094 color=p->nodes[color].next[0];
4095 sum+=(MagickRealType) p->nodes[color].count*color;
4096 count+=p->nodes[color].count;
4097 } while (count < (ssize_t) pixel_list->length);
4098 sum/=pixel_list->length;
4099 return(ScaleShortToQuantum((unsigned short) sum));
cristy6fc86bb2011-03-18 23:45:16 +00004100}
4101
cristy075ff302011-09-07 01:51:24 +00004102static Quantum GetMedianPixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004103{
cristy733678d2011-03-18 21:29:28 +00004104 register SkipList
cristyf36cbcb2011-09-07 13:28:22 +00004105 *p;
cristy733678d2011-03-18 21:29:28 +00004106
4107 size_t
cristy49f37242011-03-22 18:18:23 +00004108 color;
4109
4110 ssize_t
cristy733678d2011-03-18 21:29:28 +00004111 count;
4112
cristy733678d2011-03-18 21:29:28 +00004113 /*
4114 Find the median value for each of the color.
4115 */
cristyf36cbcb2011-09-07 13:28:22 +00004116 p=(&pixel_list->skip_list);
4117 color=65536L;
4118 count=0;
4119 do
cristy733678d2011-03-18 21:29:28 +00004120 {
cristyf36cbcb2011-09-07 13:28:22 +00004121 color=p->nodes[color].next[0];
4122 count+=p->nodes[color].count;
4123 } while (count <= (ssize_t) (pixel_list->length >> 1));
4124 return(ScaleShortToQuantum((unsigned short) color));
cristy6fc86bb2011-03-18 23:45:16 +00004125}
4126
cristy075ff302011-09-07 01:51:24 +00004127static Quantum GetMinimumPixelList(PixelList *pixel_list)
cristy6fc86bb2011-03-18 23:45:16 +00004128{
cristy6fc86bb2011-03-18 23:45:16 +00004129 register SkipList
cristyf36cbcb2011-09-07 13:28:22 +00004130 *p;
cristy6fc86bb2011-03-18 23:45:16 +00004131
4132 size_t
cristyd76c51e2011-03-26 00:21:26 +00004133 color,
4134 minimum;
cristy6fc86bb2011-03-18 23:45:16 +00004135
cristy49f37242011-03-22 18:18:23 +00004136 ssize_t
4137 count;
4138
cristy6fc86bb2011-03-18 23:45:16 +00004139 /*
4140 Find the minimum value for each of the color.
4141 */
cristyf36cbcb2011-09-07 13:28:22 +00004142 p=(&pixel_list->skip_list);
4143 count=0;
4144 color=65536UL;
4145 minimum=p->nodes[color].next[0];
4146 do
cristy6fc86bb2011-03-18 23:45:16 +00004147 {
cristyf36cbcb2011-09-07 13:28:22 +00004148 color=p->nodes[color].next[0];
4149 if (color < minimum)
4150 minimum=color;
4151 count+=p->nodes[color].count;
4152 } while (count < (ssize_t) pixel_list->length);
4153 return(ScaleShortToQuantum((unsigned short) minimum));
cristy733678d2011-03-18 21:29:28 +00004154}
4155
cristy075ff302011-09-07 01:51:24 +00004156static Quantum GetModePixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004157{
cristy733678d2011-03-18 21:29:28 +00004158 register SkipList
cristyf36cbcb2011-09-07 13:28:22 +00004159 *p;
cristy733678d2011-03-18 21:29:28 +00004160
4161 size_t
4162 color,
cristy733678d2011-03-18 21:29:28 +00004163 max_count,
cristy6fc86bb2011-03-18 23:45:16 +00004164 mode;
cristy733678d2011-03-18 21:29:28 +00004165
cristy49f37242011-03-22 18:18:23 +00004166 ssize_t
4167 count;
4168
cristy733678d2011-03-18 21:29:28 +00004169 /*
glennrp30d2dc62011-06-25 03:17:16 +00004170 Make each pixel the 'predominant color' of the specified neighborhood.
cristy733678d2011-03-18 21:29:28 +00004171 */
cristyf36cbcb2011-09-07 13:28:22 +00004172 p=(&pixel_list->skip_list);
4173 color=65536L;
4174 mode=color;
4175 max_count=p->nodes[mode].count;
4176 count=0;
4177 do
cristy733678d2011-03-18 21:29:28 +00004178 {
cristyf36cbcb2011-09-07 13:28:22 +00004179 color=p->nodes[color].next[0];
4180 if (p->nodes[color].count > max_count)
4181 {
4182 mode=color;
4183 max_count=p->nodes[mode].count;
4184 }
4185 count+=p->nodes[color].count;
4186 } while (count < (ssize_t) pixel_list->length);
4187 return(ScaleShortToQuantum((unsigned short) mode));
cristy733678d2011-03-18 21:29:28 +00004188}
4189
cristy075ff302011-09-07 01:51:24 +00004190static Quantum GetNonpeakPixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004191{
cristy733678d2011-03-18 21:29:28 +00004192 register SkipList
cristyf36cbcb2011-09-07 13:28:22 +00004193 *p;
cristy733678d2011-03-18 21:29:28 +00004194
4195 size_t
cristy733678d2011-03-18 21:29:28 +00004196 color,
cristy733678d2011-03-18 21:29:28 +00004197 next,
4198 previous;
4199
cristy49f37242011-03-22 18:18:23 +00004200 ssize_t
4201 count;
4202
cristy733678d2011-03-18 21:29:28 +00004203 /*
cristy49f37242011-03-22 18:18:23 +00004204 Finds the non peak value for each of the colors.
cristy733678d2011-03-18 21:29:28 +00004205 */
cristyf36cbcb2011-09-07 13:28:22 +00004206 p=(&pixel_list->skip_list);
4207 color=65536L;
4208 next=p->nodes[color].next[0];
4209 count=0;
4210 do
cristy733678d2011-03-18 21:29:28 +00004211 {
cristyf36cbcb2011-09-07 13:28:22 +00004212 previous=color;
4213 color=next;
4214 next=p->nodes[color].next[0];
4215 count+=p->nodes[color].count;
4216 } while (count <= (ssize_t) (pixel_list->length >> 1));
4217 if ((previous == 65536UL) && (next != 65536UL))
4218 color=next;
4219 else
4220 if ((previous != 65536UL) && (next == 65536UL))
4221 color=previous;
4222 return(ScaleShortToQuantum((unsigned short) color));
cristy733678d2011-03-18 21:29:28 +00004223}
4224
cristy075ff302011-09-07 01:51:24 +00004225static Quantum GetStandardDeviationPixelList(PixelList *pixel_list)
cristy9a68cbb2011-03-29 00:51:23 +00004226{
cristy80a99a32011-03-30 01:30:23 +00004227 MagickRealType
4228 sum,
4229 sum_squared;
4230
cristy9a68cbb2011-03-29 00:51:23 +00004231 register SkipList
cristyf36cbcb2011-09-07 13:28:22 +00004232 *p;
cristy9a68cbb2011-03-29 00:51:23 +00004233
4234 size_t
cristy80a99a32011-03-30 01:30:23 +00004235 color;
cristy9a68cbb2011-03-29 00:51:23 +00004236
4237 ssize_t
4238 count;
4239
cristy9a68cbb2011-03-29 00:51:23 +00004240 /*
cristy80a99a32011-03-30 01:30:23 +00004241 Find the standard-deviation value for each of the color.
cristy9a68cbb2011-03-29 00:51:23 +00004242 */
cristyf36cbcb2011-09-07 13:28:22 +00004243 p=(&pixel_list->skip_list);
4244 color=65536L;
4245 count=0;
4246 sum=0.0;
4247 sum_squared=0.0;
4248 do
cristy9a68cbb2011-03-29 00:51:23 +00004249 {
cristyf36cbcb2011-09-07 13:28:22 +00004250 register ssize_t
4251 i;
cristy80a99a32011-03-30 01:30:23 +00004252
cristyf36cbcb2011-09-07 13:28:22 +00004253 color=p->nodes[color].next[0];
4254 sum+=(MagickRealType) p->nodes[color].count*color;
4255 for (i=0; i < (ssize_t) p->nodes[color].count; i++)
4256 sum_squared+=((MagickRealType) color)*((MagickRealType) color);
4257 count+=p->nodes[color].count;
4258 } while (count < (ssize_t) pixel_list->length);
4259 sum/=pixel_list->length;
4260 sum_squared/=pixel_list->length;
4261 return(ScaleShortToQuantum((unsigned short) sqrt(sum_squared-(sum*sum))));
cristy9a68cbb2011-03-29 00:51:23 +00004262}
4263
cristyf36cbcb2011-09-07 13:28:22 +00004264static inline void InsertPixelList(const Image *image,const Quantum pixel,
cristy4c08aed2011-07-01 19:47:50 +00004265 PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004266{
4267 size_t
4268 signature;
4269
4270 unsigned short
4271 index;
4272
cristyf36cbcb2011-09-07 13:28:22 +00004273 index=ScaleQuantumToShort(pixel);
4274 signature=pixel_list->skip_list.nodes[index].signature;
cristy733678d2011-03-18 21:29:28 +00004275 if (signature == pixel_list->signature)
cristyf36cbcb2011-09-07 13:28:22 +00004276 {
4277 pixel_list->skip_list.nodes[index].count++;
4278 return;
4279 }
4280 AddNodePixelList(pixel_list,index);
cristy733678d2011-03-18 21:29:28 +00004281}
4282
cristy80c99742011-04-04 14:46:39 +00004283static inline MagickRealType MagickAbsoluteValue(const MagickRealType x)
4284{
4285 if (x < 0)
4286 return(-x);
4287 return(x);
4288}
4289
cristyf36cbcb2011-09-07 13:28:22 +00004290static inline size_t MagickMax(const size_t x,const size_t y)
4291{
4292 if (x > y)
4293 return(x);
4294 return(y);
4295}
4296
cristy733678d2011-03-18 21:29:28 +00004297static void ResetPixelList(PixelList *pixel_list)
4298{
4299 int
4300 level;
4301
cristyf36cbcb2011-09-07 13:28:22 +00004302 register SkipNode
cristy733678d2011-03-18 21:29:28 +00004303 *root;
4304
4305 register SkipList
cristyf36cbcb2011-09-07 13:28:22 +00004306 *p;
cristy733678d2011-03-18 21:29:28 +00004307
4308 /*
4309 Reset the skip-list.
4310 */
cristyf36cbcb2011-09-07 13:28:22 +00004311 p=(&pixel_list->skip_list);
4312 root=p->nodes+65536UL;
4313 p->level=0;
4314 for (level=0; level < 9; level++)
4315 root->next[level]=65536UL;
cristy733678d2011-03-18 21:29:28 +00004316 pixel_list->seed=pixel_list->signature++;
4317}
4318
cristy0834d642011-03-18 18:26:08 +00004319MagickExport Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00004320 const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00004321{
cristy0834d642011-03-18 18:26:08 +00004322#define StatisticImageTag "Statistic/Image"
4323
4324 CacheView
4325 *image_view,
4326 *statistic_view;
4327
4328 Image
4329 *statistic_image;
4330
4331 MagickBooleanType
4332 status;
4333
4334 MagickOffsetType
4335 progress;
4336
4337 PixelList
4338 **restrict pixel_list;
4339
cristy0834d642011-03-18 18:26:08 +00004340 ssize_t
cristy075ff302011-09-07 01:51:24 +00004341 center,
cristy0834d642011-03-18 18:26:08 +00004342 y;
4343
4344 /*
4345 Initialize statistics image attributes.
4346 */
4347 assert(image != (Image *) NULL);
4348 assert(image->signature == MagickSignature);
4349 if (image->debug != MagickFalse)
4350 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4351 assert(exception != (ExceptionInfo *) NULL);
4352 assert(exception->signature == MagickSignature);
cristy0834d642011-03-18 18:26:08 +00004353 statistic_image=CloneImage(image,image->columns,image->rows,MagickTrue,
4354 exception);
4355 if (statistic_image == (Image *) NULL)
4356 return((Image *) NULL);
cristyf36cbcb2011-09-07 13:28:22 +00004357 status=SetImageStorageClass(statistic_image,DirectClass,exception);
4358 if (status == MagickFalse)
cristy0834d642011-03-18 18:26:08 +00004359 {
cristy0834d642011-03-18 18:26:08 +00004360 statistic_image=DestroyImage(statistic_image);
4361 return((Image *) NULL);
4362 }
cristyf36cbcb2011-09-07 13:28:22 +00004363 pixel_list=AcquirePixelListThreadSet(MagickMax(width,1),MagickMax(height,1));
cristy0834d642011-03-18 18:26:08 +00004364 if (pixel_list == (PixelList **) NULL)
4365 {
4366 statistic_image=DestroyImage(statistic_image);
4367 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
4368 }
4369 /*
cristy8d752042011-03-19 01:00:36 +00004370 Make each pixel the min / max / median / mode / etc. of the neighborhood.
cristy0834d642011-03-18 18:26:08 +00004371 */
cristyf36cbcb2011-09-07 13:28:22 +00004372 center=(ssize_t) GetPixelChannels(image)*(image->columns+MagickMax(width,1))*
4373 (MagickMax(height,1)/2L)+GetPixelChannels(image)*(MagickMax(width,1)/2L);
cristy0834d642011-03-18 18:26:08 +00004374 status=MagickTrue;
4375 progress=0;
4376 image_view=AcquireCacheView(image);
4377 statistic_view=AcquireCacheView(statistic_image);
4378#if defined(MAGICKCORE_OPENMP_SUPPORT)
4379 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
4380#endif
4381 for (y=0; y < (ssize_t) statistic_image->rows; y++)
4382 {
4383 const int
4384 id = GetOpenMPThreadId();
4385
cristy4c08aed2011-07-01 19:47:50 +00004386 register const Quantum
cristy0834d642011-03-18 18:26:08 +00004387 *restrict p;
4388
cristy4c08aed2011-07-01 19:47:50 +00004389 register Quantum
cristy0834d642011-03-18 18:26:08 +00004390 *restrict q;
4391
4392 register ssize_t
4393 x;
4394
4395 if (status == MagickFalse)
4396 continue;
cristyf36cbcb2011-09-07 13:28:22 +00004397 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) MagickMax(width,1)/2L),y-
4398 (ssize_t) (MagickMax(height,1)/2L),image->columns+MagickMax(width,1),
4399 MagickMax(height,1),exception);
cristy3cba8ca2011-03-19 01:29:12 +00004400 q=QueueCacheViewAuthenticPixels(statistic_view,0,y,statistic_image->columns, 1,exception);
cristy4c08aed2011-07-01 19:47:50 +00004401 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy0834d642011-03-18 18:26:08 +00004402 {
4403 status=MagickFalse;
4404 continue;
4405 }
cristy0834d642011-03-18 18:26:08 +00004406 for (x=0; x < (ssize_t) statistic_image->columns; x++)
4407 {
cristy0834d642011-03-18 18:26:08 +00004408 register ssize_t
cristy075ff302011-09-07 01:51:24 +00004409 i;
cristy0834d642011-03-18 18:26:08 +00004410
cristy075ff302011-09-07 01:51:24 +00004411 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy0834d642011-03-18 18:26:08 +00004412 {
cristy075ff302011-09-07 01:51:24 +00004413 PixelChannel
4414 channel;
4415
4416 PixelTrait
4417 statistic_traits,
4418 traits;
4419
cristyf36cbcb2011-09-07 13:28:22 +00004420 Quantum
4421 pixel;
4422
cristy075ff302011-09-07 01:51:24 +00004423 register const Quantum
4424 *restrict pixels;
4425
4426 register ssize_t
4427 u;
4428
4429 ssize_t
4430 v;
4431
4432 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
4433 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
4434 statistic_traits=GetPixelChannelMapTraits(statistic_image,channel);
4435 if ((traits == UndefinedPixelTrait) ||
4436 (statistic_traits == UndefinedPixelTrait))
4437 continue;
4438 if ((statistic_traits & CopyPixelTrait) != 0)
4439 {
4440 q[channel]=p[center+i];
4441 continue;
4442 }
4443 pixels=p;
4444 ResetPixelList(pixel_list[id]);
cristyf36cbcb2011-09-07 13:28:22 +00004445 for (v=0; v < (ssize_t) MagickMax(height,1); v++)
cristy6fc86bb2011-03-18 23:45:16 +00004446 {
cristyf36cbcb2011-09-07 13:28:22 +00004447 for (u=0; u < (ssize_t) MagickMax(width,1); u++)
cristy075ff302011-09-07 01:51:24 +00004448 {
cristyf36cbcb2011-09-07 13:28:22 +00004449 InsertPixelList(image,pixels[i],pixel_list[id]);
cristy075ff302011-09-07 01:51:24 +00004450 pixels+=GetPixelChannels(image);
4451 }
4452 pixels+=image->columns*GetPixelChannels(image);
cristy6fc86bb2011-03-18 23:45:16 +00004453 }
cristy075ff302011-09-07 01:51:24 +00004454 switch (type)
cristy49f37242011-03-22 18:18:23 +00004455 {
cristy075ff302011-09-07 01:51:24 +00004456 case GradientStatistic:
4457 {
4458 MagickRealType
4459 maximum,
4460 minimum;
4461
cristyf36cbcb2011-09-07 13:28:22 +00004462 minimum=(MagickRealType) GetMinimumPixelList(pixel_list[id]);
4463 maximum=(MagickRealType) GetMaximumPixelList(pixel_list[id]);
4464 pixel=ClampToQuantum(MagickAbsoluteValue(maximum-minimum));
cristy075ff302011-09-07 01:51:24 +00004465 break;
4466 }
4467 case MaximumStatistic:
4468 {
4469 pixel=GetMaximumPixelList(pixel_list[id]);
4470 break;
4471 }
4472 case MeanStatistic:
4473 {
4474 pixel=GetMeanPixelList(pixel_list[id]);
4475 break;
4476 }
4477 case MedianStatistic:
4478 default:
4479 {
4480 pixel=GetMedianPixelList(pixel_list[id]);
4481 break;
4482 }
4483 case MinimumStatistic:
4484 {
4485 pixel=GetMinimumPixelList(pixel_list[id]);
4486 break;
4487 }
4488 case ModeStatistic:
4489 {
4490 pixel=GetModePixelList(pixel_list[id]);
4491 break;
4492 }
4493 case NonpeakStatistic:
4494 {
4495 pixel=GetNonpeakPixelList(pixel_list[id]);
4496 break;
4497 }
4498 case StandardDeviationStatistic:
4499 {
4500 pixel=GetStandardDeviationPixelList(pixel_list[id]);
4501 break;
4502 }
cristy49f37242011-03-22 18:18:23 +00004503 }
cristyf36cbcb2011-09-07 13:28:22 +00004504 q[channel]=pixel;
cristy0834d642011-03-18 18:26:08 +00004505 }
cristyed231572011-07-14 02:18:59 +00004506 p+=GetPixelChannels(image);
4507 q+=GetPixelChannels(statistic_image);
cristy0834d642011-03-18 18:26:08 +00004508 }
4509 if (SyncCacheViewAuthenticPixels(statistic_view,exception) == MagickFalse)
4510 status=MagickFalse;
4511 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4512 {
4513 MagickBooleanType
4514 proceed;
4515
4516#if defined(MAGICKCORE_OPENMP_SUPPORT)
4517 #pragma omp critical (MagickCore_StatisticImage)
4518#endif
4519 proceed=SetImageProgress(image,StatisticImageTag,progress++,
4520 image->rows);
4521 if (proceed == MagickFalse)
4522 status=MagickFalse;
4523 }
4524 }
4525 statistic_view=DestroyCacheView(statistic_view);
4526 image_view=DestroyCacheView(image_view);
4527 pixel_list=DestroyPixelListThreadSet(pixel_list);
4528 return(statistic_image);
4529}
4530
4531/*
4532%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4533% %
4534% %
4535% %
cristy3ed852e2009-09-05 21:47:34 +00004536% U n s h a r p M a s k I m a g e %
4537% %
4538% %
4539% %
4540%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4541%
4542% UnsharpMaskImage() sharpens one or more image channels. We convolve the
4543% image with a Gaussian operator of the given radius and standard deviation
4544% (sigma). For reasonable results, radius should be larger than sigma. Use a
4545% radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
4546%
4547% The format of the UnsharpMaskImage method is:
4548%
4549% Image *UnsharpMaskImage(const Image *image,const double radius,
4550% const double sigma,const double amount,const double threshold,
4551% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00004552%
4553% A description of each parameter follows:
4554%
4555% o image: the image.
4556%
cristy3ed852e2009-09-05 21:47:34 +00004557% o radius: the radius of the Gaussian, in pixels, not counting the center
4558% pixel.
4559%
4560% o sigma: the standard deviation of the Gaussian, in pixels.
4561%
4562% o amount: the percentage of the difference between the original and the
4563% blur image that is added back into the original.
4564%
4565% o threshold: the threshold in pixels needed to apply the diffence amount.
4566%
4567% o exception: return any errors or warnings in this structure.
4568%
4569*/
cristyf4ad9df2011-07-08 16:49:03 +00004570MagickExport Image *UnsharpMaskImage(const Image *image,
4571 const double radius,const double sigma,const double amount,
4572 const double threshold,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00004573{
4574#define SharpenImageTag "Sharpen/Image"
4575
cristyc4c8d132010-01-07 01:58:38 +00004576 CacheView
4577 *image_view,
4578 *unsharp_view;
4579
cristy3ed852e2009-09-05 21:47:34 +00004580 Image
4581 *unsharp_image;
4582
cristy3ed852e2009-09-05 21:47:34 +00004583 MagickBooleanType
4584 status;
4585
cristybb503372010-05-27 20:51:26 +00004586 MagickOffsetType
4587 progress;
4588
cristy3ed852e2009-09-05 21:47:34 +00004589 MagickRealType
4590 quantum_threshold;
4591
cristybb503372010-05-27 20:51:26 +00004592 ssize_t
4593 y;
4594
cristy3ed852e2009-09-05 21:47:34 +00004595 assert(image != (const Image *) NULL);
4596 assert(image->signature == MagickSignature);
4597 if (image->debug != MagickFalse)
4598 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4599 assert(exception != (ExceptionInfo *) NULL);
cristy05c0c9a2011-09-05 23:16:13 +00004600 unsharp_image=BlurImage(image,radius,sigma,image->bias,exception);
cristy3ed852e2009-09-05 21:47:34 +00004601 if (unsharp_image == (Image *) NULL)
4602 return((Image *) NULL);
4603 quantum_threshold=(MagickRealType) QuantumRange*threshold;
4604 /*
4605 Unsharp-mask image.
4606 */
4607 status=MagickTrue;
4608 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00004609 image_view=AcquireCacheView(image);
4610 unsharp_view=AcquireCacheView(unsharp_image);
cristyb5d5f722009-11-04 03:03:49 +00004611#if defined(MAGICKCORE_OPENMP_SUPPORT)
4612 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004613#endif
cristybb503372010-05-27 20:51:26 +00004614 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00004615 {
cristy4c08aed2011-07-01 19:47:50 +00004616 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00004617 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00004618
cristy4c08aed2011-07-01 19:47:50 +00004619 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00004620 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004621
cristy117ff172010-08-15 21:35:32 +00004622 register ssize_t
4623 x;
4624
cristy3ed852e2009-09-05 21:47:34 +00004625 if (status == MagickFalse)
4626 continue;
4627 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
4628 q=GetCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
4629 exception);
cristy4c08aed2011-07-01 19:47:50 +00004630 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00004631 {
4632 status=MagickFalse;
4633 continue;
4634 }
cristybb503372010-05-27 20:51:26 +00004635 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004636 {
cristy7f3a0d12011-09-05 23:27:59 +00004637 register ssize_t
4638 i;
4639
4640 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4641 {
4642 MagickRealType
4643 pixel;
4644
4645 PixelChannel
4646 channel;
4647
4648 PixelTrait
4649 traits,
4650 unsharp_traits;
4651
4652 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
4653 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
4654 unsharp_traits=GetPixelChannelMapTraits(unsharp_image,channel);
4655 if ((traits == UndefinedPixelTrait) ||
4656 (unsharp_traits == UndefinedPixelTrait))
4657 continue;
4658 if ((unsharp_traits & CopyPixelTrait) != 0)
4659 {
4660 q[channel]=p[i];
4661 continue;
4662 }
4663 pixel=p[i]-(MagickRealType) q[channel];
4664 if (fabs(2.0*pixel) < quantum_threshold)
4665 pixel=(MagickRealType) p[i];
4666 else
4667 pixel=(MagickRealType) p[i]+amount*pixel;
4668 q[channel]=ClampToQuantum(pixel);
4669 }
cristyed231572011-07-14 02:18:59 +00004670 p+=GetPixelChannels(image);
4671 q+=GetPixelChannels(unsharp_image);
cristy3ed852e2009-09-05 21:47:34 +00004672 }
4673 if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
4674 status=MagickFalse;
4675 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4676 {
4677 MagickBooleanType
4678 proceed;
4679
cristyb5d5f722009-11-04 03:03:49 +00004680#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00004681 #pragma omp critical (MagickCore_UnsharpMaskImage)
cristy3ed852e2009-09-05 21:47:34 +00004682#endif
4683 proceed=SetImageProgress(image,SharpenImageTag,progress++,image->rows);
4684 if (proceed == MagickFalse)
4685 status=MagickFalse;
4686 }
4687 }
4688 unsharp_image->type=image->type;
4689 unsharp_view=DestroyCacheView(unsharp_view);
4690 image_view=DestroyCacheView(image_view);
4691 if (status == MagickFalse)
4692 unsharp_image=DestroyImage(unsharp_image);
4693 return(unsharp_image);
4694}