blob: d759f309efb67c917838aa848df9c7c6c0a1ac9d [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"
cristyc53413d2011-11-17 13:04:26 +000052#include "MagickCore/distort.h"
cristy4c08aed2011-07-01 19:47:50 +000053#include "MagickCore/draw.h"
54#include "MagickCore/enhance.h"
55#include "MagickCore/exception.h"
56#include "MagickCore/exception-private.h"
57#include "MagickCore/effect.h"
58#include "MagickCore/fx.h"
59#include "MagickCore/gem.h"
cristy8ea81222011-09-04 10:33:32 +000060#include "MagickCore/gem-private.h"
cristy4c08aed2011-07-01 19:47:50 +000061#include "MagickCore/geometry.h"
62#include "MagickCore/image-private.h"
63#include "MagickCore/list.h"
64#include "MagickCore/log.h"
65#include "MagickCore/memory_.h"
66#include "MagickCore/monitor.h"
67#include "MagickCore/monitor-private.h"
68#include "MagickCore/montage.h"
69#include "MagickCore/morphology.h"
70#include "MagickCore/paint.h"
71#include "MagickCore/pixel-accessor.h"
72#include "MagickCore/property.h"
73#include "MagickCore/quantize.h"
74#include "MagickCore/quantum.h"
75#include "MagickCore/quantum-private.h"
76#include "MagickCore/random_.h"
77#include "MagickCore/random-private.h"
78#include "MagickCore/resample.h"
79#include "MagickCore/resample-private.h"
80#include "MagickCore/resize.h"
81#include "MagickCore/resource_.h"
82#include "MagickCore/segment.h"
cristy31bbf2f2011-11-17 13:19:37 +000083#include "MagickCore/shear.h"
cristy4c08aed2011-07-01 19:47:50 +000084#include "MagickCore/signature-private.h"
85#include "MagickCore/string_.h"
86#include "MagickCore/thread-private.h"
87#include "MagickCore/transform.h"
88#include "MagickCore/threshold.h"
cristy3ed852e2009-09-05 21:47:34 +000089
90/*
91%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
92% %
93% %
94% %
95% A d a p t i v e B l u r I m a g e %
96% %
97% %
98% %
99%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
100%
101% AdaptiveBlurImage() adaptively blurs the image by blurring less
102% intensely near image edges and more intensely far from edges. We blur the
103% image with a Gaussian operator of the given radius and standard deviation
104% (sigma). For reasonable results, radius should be larger than sigma. Use a
105% radius of 0 and AdaptiveBlurImage() selects a suitable radius for you.
106%
107% The format of the AdaptiveBlurImage method is:
108%
109% Image *AdaptiveBlurImage(const Image *image,const double radius,
cristy4c11c2b2011-09-05 20:17:07 +0000110% const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000111%
112% A description of each parameter follows:
113%
114% o image: the image.
115%
cristy3ed852e2009-09-05 21:47:34 +0000116% o radius: the radius of the Gaussian, in pixels, not counting the center
117% pixel.
118%
119% o sigma: the standard deviation of the Laplacian, in pixels.
120%
cristy4c11c2b2011-09-05 20:17:07 +0000121% o bias: the bias.
122%
cristy3ed852e2009-09-05 21:47:34 +0000123% o exception: return any errors or warnings in this structure.
124%
125*/
126
cristyf89cb1d2011-07-07 01:24:37 +0000127MagickExport MagickBooleanType AdaptiveLevelImage(Image *image,
cristy051718b2011-08-28 22:49:25 +0000128 const char *levels,ExceptionInfo *exception)
cristyf89cb1d2011-07-07 01:24:37 +0000129{
130 double
131 black_point,
132 gamma,
133 white_point;
134
135 GeometryInfo
136 geometry_info;
137
138 MagickBooleanType
139 status;
140
141 MagickStatusType
142 flags;
143
144 /*
145 Parse levels.
146 */
147 if (levels == (char *) NULL)
148 return(MagickFalse);
149 flags=ParseGeometry(levels,&geometry_info);
150 black_point=geometry_info.rho;
151 white_point=(double) QuantumRange;
152 if ((flags & SigmaValue) != 0)
153 white_point=geometry_info.sigma;
154 gamma=1.0;
155 if ((flags & XiValue) != 0)
156 gamma=geometry_info.xi;
157 if ((flags & PercentValue) != 0)
158 {
159 black_point*=(double) image->columns*image->rows/100.0;
160 white_point*=(double) image->columns*image->rows/100.0;
161 }
162 if ((flags & SigmaValue) == 0)
163 white_point=(double) QuantumRange-black_point;
164 if ((flags & AspectValue ) == 0)
cristy7c0a0a42011-08-23 17:57:25 +0000165 status=LevelImage(image,black_point,white_point,gamma,exception);
cristyf89cb1d2011-07-07 01:24:37 +0000166 else
cristy7c0a0a42011-08-23 17:57:25 +0000167 status=LevelizeImage(image,black_point,white_point,gamma,exception);
cristyf89cb1d2011-07-07 01:24:37 +0000168 return(status);
169}
170
cristy4282c702011-11-21 00:01:06 +0000171MagickExport Image *AdaptiveBlurImage(const Image *image,const double radius,
172 const double sigma,const double bias,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
cristye2a912b2011-12-05 20:02:07 +0000374 traits=GetPixelChannelMapTraits(image,i);
375 channel=GetPixelChannelMapChannel(image,i);
cristy4c11c2b2011-09-05 20:17:07 +0000376 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
377 if ((traits == UndefinedPixelTrait) ||
378 (blur_traits == UndefinedPixelTrait))
379 continue;
380 if ((blur_traits & CopyPixelTrait) != 0)
381 {
cristy0beccfa2011-09-25 20:47:53 +0000382 SetPixelChannel(blur_image,channel,p[center+i],q);
cristy4c11c2b2011-09-05 20:17:07 +0000383 continue;
384 }
385 k=kernel[j];
386 pixels=p;
387 pixel=bias;
388 gamma=0.0;
389 if ((blur_traits & BlendPixelTrait) == 0)
390 {
391 /*
392 No alpha blending.
393 */
394 for (v=0; v < (ssize_t) (width-j); v++)
395 {
396 for (u=0; u < (ssize_t) (width-j); u++)
397 {
398 pixel+=(*k)*pixels[i];
399 gamma+=(*k);
400 k++;
401 pixels+=GetPixelChannels(image);
402 }
403 }
404 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +0000405 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy4c11c2b2011-09-05 20:17:07 +0000406 continue;
407 }
408 /*
409 Alpha blending.
410 */
411 for (v=0; v < (ssize_t) (width-j); v++)
cristy3ed852e2009-09-05 21:47:34 +0000412 {
cristy4c11c2b2011-09-05 20:17:07 +0000413 for (u=0; u < (ssize_t) (width-j); u++)
414 {
415 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,pixels));
416 pixel+=(*k)*alpha*pixels[i];
417 gamma+=(*k)*alpha;
418 k++;
419 pixels+=GetPixelChannels(image);
420 }
cristy3ed852e2009-09-05 21:47:34 +0000421 }
cristy4c11c2b2011-09-05 20:17:07 +0000422 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +0000423 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy3ed852e2009-09-05 21:47:34 +0000424 }
cristyed231572011-07-14 02:18:59 +0000425 q+=GetPixelChannels(blur_image);
426 r+=GetPixelChannels(edge_image);
cristy3ed852e2009-09-05 21:47:34 +0000427 }
428 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
429 status=MagickFalse;
430 if (image->progress_monitor != (MagickProgressMonitor) NULL)
431 {
432 MagickBooleanType
433 proceed;
434
cristyb5d5f722009-11-04 03:03:49 +0000435#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy8ae632d2011-09-05 17:29:53 +0000436 #pragma omp critical (MagickCore_AdaptiveBlurImage)
cristy3ed852e2009-09-05 21:47:34 +0000437#endif
438 proceed=SetImageProgress(image,AdaptiveBlurImageTag,progress++,
439 image->rows);
440 if (proceed == MagickFalse)
441 status=MagickFalse;
442 }
443 }
444 blur_image->type=image->type;
445 blur_view=DestroyCacheView(blur_view);
446 edge_view=DestroyCacheView(edge_view);
447 image_view=DestroyCacheView(image_view);
448 edge_image=DestroyImage(edge_image);
cristybb503372010-05-27 20:51:26 +0000449 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000450 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
451 kernel=(double **) RelinquishMagickMemory(kernel);
452 if (status == MagickFalse)
453 blur_image=DestroyImage(blur_image);
454 return(blur_image);
455}
456
457/*
458%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
459% %
460% %
461% %
462% A d a p t i v e S h a r p e n I m a g e %
463% %
464% %
465% %
466%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
467%
468% AdaptiveSharpenImage() adaptively sharpens the image by sharpening more
469% intensely near image edges and less intensely far from edges. We sharpen the
470% image with a Gaussian operator of the given radius and standard deviation
471% (sigma). For reasonable results, radius should be larger than sigma. Use a
472% radius of 0 and AdaptiveSharpenImage() selects a suitable radius for you.
473%
474% The format of the AdaptiveSharpenImage method is:
475%
476% Image *AdaptiveSharpenImage(const Image *image,const double radius,
cristy4c11c2b2011-09-05 20:17:07 +0000477% const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000478%
479% A description of each parameter follows:
480%
481% o image: the image.
482%
cristy3ed852e2009-09-05 21:47:34 +0000483% o radius: the radius of the Gaussian, in pixels, not counting the center
484% pixel.
485%
486% o sigma: the standard deviation of the Laplacian, in pixels.
487%
cristy4c11c2b2011-09-05 20:17:07 +0000488% o bias: the bias.
489%
cristy3ed852e2009-09-05 21:47:34 +0000490% o exception: return any errors or warnings in this structure.
491%
492*/
cristy3ed852e2009-09-05 21:47:34 +0000493MagickExport Image *AdaptiveSharpenImage(const Image *image,const double radius,
cristy4c11c2b2011-09-05 20:17:07 +0000494 const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000495{
cristy3ed852e2009-09-05 21:47:34 +0000496#define AdaptiveSharpenImageTag "Convolve/Image"
497#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
498
cristyc4c8d132010-01-07 01:58:38 +0000499 CacheView
500 *sharp_view,
501 *edge_view,
502 *image_view;
503
cristy3ed852e2009-09-05 21:47:34 +0000504 double
cristy47e00502009-12-17 19:19:57 +0000505 **kernel,
506 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000507
508 Image
509 *sharp_image,
510 *edge_image,
511 *gaussian_image;
512
cristy3ed852e2009-09-05 21:47:34 +0000513 MagickBooleanType
514 status;
515
cristybb503372010-05-27 20:51:26 +0000516 MagickOffsetType
517 progress;
518
cristybb503372010-05-27 20:51:26 +0000519 register ssize_t
cristy47e00502009-12-17 19:19:57 +0000520 i;
cristy3ed852e2009-09-05 21:47:34 +0000521
cristybb503372010-05-27 20:51:26 +0000522 size_t
cristy3ed852e2009-09-05 21:47:34 +0000523 width;
524
cristybb503372010-05-27 20:51:26 +0000525 ssize_t
526 j,
527 k,
528 u,
529 v,
530 y;
531
cristy3ed852e2009-09-05 21:47:34 +0000532 assert(image != (const Image *) NULL);
533 assert(image->signature == MagickSignature);
534 if (image->debug != MagickFalse)
535 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
536 assert(exception != (ExceptionInfo *) NULL);
537 assert(exception->signature == MagickSignature);
538 sharp_image=CloneImage(image,0,0,MagickTrue,exception);
539 if (sharp_image == (Image *) NULL)
540 return((Image *) NULL);
541 if (fabs(sigma) <= MagickEpsilon)
542 return(sharp_image);
cristy574cc262011-08-05 01:23:58 +0000543 if (SetImageStorageClass(sharp_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000544 {
cristy3ed852e2009-09-05 21:47:34 +0000545 sharp_image=DestroyImage(sharp_image);
546 return((Image *) NULL);
547 }
548 /*
549 Edge detect the image brighness channel, level, sharp, and level again.
550 */
cristy8ae632d2011-09-05 17:29:53 +0000551 edge_image=EdgeImage(image,radius,sigma,exception);
cristy3ed852e2009-09-05 21:47:34 +0000552 if (edge_image == (Image *) NULL)
553 {
554 sharp_image=DestroyImage(sharp_image);
555 return((Image *) NULL);
556 }
cristy051718b2011-08-28 22:49:25 +0000557 (void) AdaptiveLevelImage(edge_image,"20%,95%",exception);
cristy05c0c9a2011-09-05 23:16:13 +0000558 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,bias,exception);
cristy3ed852e2009-09-05 21:47:34 +0000559 if (gaussian_image != (Image *) NULL)
560 {
561 edge_image=DestroyImage(edge_image);
562 edge_image=gaussian_image;
563 }
cristy051718b2011-08-28 22:49:25 +0000564 (void) AdaptiveLevelImage(edge_image,"10%,95%",exception);
cristy3ed852e2009-09-05 21:47:34 +0000565 /*
566 Create a set of kernels from maximum (radius,sigma) to minimum.
567 */
568 width=GetOptimalKernelWidth2D(radius,sigma);
569 kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
570 if (kernel == (double **) NULL)
571 {
572 edge_image=DestroyImage(edge_image);
573 sharp_image=DestroyImage(sharp_image);
574 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
575 }
576 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
cristybb503372010-05-27 20:51:26 +0000577 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000578 {
579 kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)*
580 sizeof(**kernel));
581 if (kernel[i] == (double *) NULL)
582 break;
cristy47e00502009-12-17 19:19:57 +0000583 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000584 j=(ssize_t) (width-i)/2;
cristy47e00502009-12-17 19:19:57 +0000585 k=0;
586 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000587 {
cristy47e00502009-12-17 19:19:57 +0000588 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000589 {
cristy4205a3c2010-09-12 20:19:59 +0000590 kernel[i][k]=(double) (-exp(-((double) u*u+v*v)/(2.0*MagickSigma*
591 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +0000592 normalize+=kernel[i][k];
593 k++;
cristy3ed852e2009-09-05 21:47:34 +0000594 }
595 }
cristy3ed852e2009-09-05 21:47:34 +0000596 if (fabs(normalize) <= MagickEpsilon)
597 normalize=1.0;
598 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000599 for (k=0; k < (j*j); k++)
600 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000601 }
cristybb503372010-05-27 20:51:26 +0000602 if (i < (ssize_t) width)
cristy3ed852e2009-09-05 21:47:34 +0000603 {
604 for (i-=2; i >= 0; i-=2)
605 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
606 kernel=(double **) RelinquishMagickMemory(kernel);
607 edge_image=DestroyImage(edge_image);
608 sharp_image=DestroyImage(sharp_image);
609 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
610 }
611 /*
612 Adaptively sharpen image.
613 */
614 status=MagickTrue;
615 progress=0;
cristy3ed852e2009-09-05 21:47:34 +0000616 image_view=AcquireCacheView(image);
617 edge_view=AcquireCacheView(edge_image);
618 sharp_view=AcquireCacheView(sharp_image);
cristyb5d5f722009-11-04 03:03:49 +0000619#if defined(MAGICKCORE_OPENMP_SUPPORT)
620 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000621#endif
cristybb503372010-05-27 20:51:26 +0000622 for (y=0; y < (ssize_t) sharp_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000623 {
cristy4c08aed2011-07-01 19:47:50 +0000624 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000625 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000626
cristy4c08aed2011-07-01 19:47:50 +0000627 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000628 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000629
cristy117ff172010-08-15 21:35:32 +0000630 register ssize_t
631 x;
632
cristy3ed852e2009-09-05 21:47:34 +0000633 if (status == MagickFalse)
634 continue;
635 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
636 q=QueueCacheViewAuthenticPixels(sharp_view,0,y,sharp_image->columns,1,
637 exception);
cristy4c08aed2011-07-01 19:47:50 +0000638 if ((r == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000639 {
640 status=MagickFalse;
641 continue;
642 }
cristybb503372010-05-27 20:51:26 +0000643 for (x=0; x < (ssize_t) sharp_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000644 {
cristy4c11c2b2011-09-05 20:17:07 +0000645 register const Quantum
646 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000647
cristybb503372010-05-27 20:51:26 +0000648 register ssize_t
cristy4c11c2b2011-09-05 20:17:07 +0000649 i;
cristy3ed852e2009-09-05 21:47:34 +0000650
cristy4c11c2b2011-09-05 20:17:07 +0000651 ssize_t
652 center,
653 j;
654
655 j=(ssize_t) ceil((double) width*QuantumScale*
cristy4c08aed2011-07-01 19:47:50 +0000656 GetPixelIntensity(edge_image,r)-0.5);
cristy4c11c2b2011-09-05 20:17:07 +0000657 if (j < 0)
658 j=0;
cristy3ed852e2009-09-05 21:47:34 +0000659 else
cristy4c11c2b2011-09-05 20:17:07 +0000660 if (j > (ssize_t) width)
661 j=(ssize_t) width;
662 if ((j & 0x01) != 0)
663 j--;
664 p=GetCacheViewVirtualPixels(image_view,x-((ssize_t) (width-j)/2L),y-
665 (ssize_t) ((width-j)/2L),width-j,width-j,exception);
cristy4c08aed2011-07-01 19:47:50 +0000666 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000667 break;
cristy4c11c2b2011-09-05 20:17:07 +0000668 center=(ssize_t) GetPixelChannels(image)*(width-j)*
669 ((width-j)/2L)+GetPixelChannels(image)*((width-j)/2);
670 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy3ed852e2009-09-05 21:47:34 +0000671 {
cristy4c11c2b2011-09-05 20:17:07 +0000672 MagickRealType
673 alpha,
674 gamma,
675 pixel;
676
677 PixelChannel
678 channel;
679
680 PixelTrait
681 sharp_traits,
682 traits;
683
684 register const double
685 *restrict k;
686
687 register const Quantum
688 *restrict pixels;
689
690 register ssize_t
691 u;
692
693 ssize_t
694 v;
695
cristye2a912b2011-12-05 20:02:07 +0000696 traits=GetPixelChannelMapTraits(image,i);
697 channel=GetPixelChannelMapChannel(image,i);
cristy4c11c2b2011-09-05 20:17:07 +0000698 sharp_traits=GetPixelChannelMapTraits(sharp_image,channel);
699 if ((traits == UndefinedPixelTrait) ||
700 (sharp_traits == UndefinedPixelTrait))
701 continue;
702 if ((sharp_traits & CopyPixelTrait) != 0)
703 {
cristy0beccfa2011-09-25 20:47:53 +0000704 SetPixelChannel(sharp_image,channel,p[center+i],q);
cristy4c11c2b2011-09-05 20:17:07 +0000705 continue;
706 }
707 k=kernel[j];
708 pixels=p;
709 pixel=bias;
710 gamma=0.0;
711 if ((sharp_traits & BlendPixelTrait) == 0)
712 {
713 /*
714 No alpha blending.
715 */
716 for (v=0; v < (ssize_t) (width-j); v++)
717 {
718 for (u=0; u < (ssize_t) (width-j); u++)
719 {
720 pixel+=(*k)*pixels[i];
721 gamma+=(*k);
722 k++;
723 pixels+=GetPixelChannels(image);
724 }
725 }
726 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +0000727 SetPixelChannel(sharp_image,channel,ClampToQuantum(gamma*pixel),q);
cristy4c11c2b2011-09-05 20:17:07 +0000728 continue;
729 }
730 /*
731 Alpha blending.
732 */
733 for (v=0; v < (ssize_t) (width-j); v++)
cristy3ed852e2009-09-05 21:47:34 +0000734 {
cristy4c11c2b2011-09-05 20:17:07 +0000735 for (u=0; u < (ssize_t) (width-j); u++)
736 {
737 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,pixels));
738 pixel+=(*k)*alpha*pixels[i];
739 gamma+=(*k)*alpha;
740 k++;
741 pixels+=GetPixelChannels(image);
742 }
cristy3ed852e2009-09-05 21:47:34 +0000743 }
cristy4c11c2b2011-09-05 20:17:07 +0000744 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +0000745 SetPixelChannel(sharp_image,channel,ClampToQuantum(gamma*pixel),q);
cristy3ed852e2009-09-05 21:47:34 +0000746 }
cristyed231572011-07-14 02:18:59 +0000747 q+=GetPixelChannels(sharp_image);
748 r+=GetPixelChannels(edge_image);
cristy3ed852e2009-09-05 21:47:34 +0000749 }
750 if (SyncCacheViewAuthenticPixels(sharp_view,exception) == MagickFalse)
751 status=MagickFalse;
752 if (image->progress_monitor != (MagickProgressMonitor) NULL)
753 {
754 MagickBooleanType
755 proceed;
756
cristyb5d5f722009-11-04 03:03:49 +0000757#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +0000758 #pragma omp critical (MagickCore_AdaptiveSharpenImage)
cristy3ed852e2009-09-05 21:47:34 +0000759#endif
760 proceed=SetImageProgress(image,AdaptiveSharpenImageTag,progress++,
761 image->rows);
762 if (proceed == MagickFalse)
763 status=MagickFalse;
764 }
765 }
766 sharp_image->type=image->type;
767 sharp_view=DestroyCacheView(sharp_view);
768 edge_view=DestroyCacheView(edge_view);
769 image_view=DestroyCacheView(image_view);
770 edge_image=DestroyImage(edge_image);
cristybb503372010-05-27 20:51:26 +0000771 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000772 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
773 kernel=(double **) RelinquishMagickMemory(kernel);
774 if (status == MagickFalse)
775 sharp_image=DestroyImage(sharp_image);
776 return(sharp_image);
777}
778
779/*
780%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
781% %
782% %
783% %
784% B l u r I m a g e %
785% %
786% %
787% %
788%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
789%
790% BlurImage() blurs an image. We convolve the image with a Gaussian operator
791% of the given radius and standard deviation (sigma). For reasonable results,
792% the radius should be larger than sigma. Use a radius of 0 and BlurImage()
793% selects a suitable radius for you.
794%
795% BlurImage() differs from GaussianBlurImage() in that it uses a separable
796% kernel which is faster but mathematically equivalent to the non-separable
797% kernel.
798%
799% The format of the BlurImage method is:
800%
801% Image *BlurImage(const Image *image,const double radius,
cristy05c0c9a2011-09-05 23:16:13 +0000802% const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000803%
804% A description of each parameter follows:
805%
806% o image: the image.
807%
cristy3ed852e2009-09-05 21:47:34 +0000808% o radius: the radius of the Gaussian, in pixels, not counting the center
809% pixel.
810%
811% o sigma: the standard deviation of the Gaussian, in pixels.
812%
cristy05c0c9a2011-09-05 23:16:13 +0000813% o bias: the bias.
814%
cristy3ed852e2009-09-05 21:47:34 +0000815% o exception: return any errors or warnings in this structure.
816%
817*/
818
cristybb503372010-05-27 20:51:26 +0000819static double *GetBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +0000820{
cristy3ed852e2009-09-05 21:47:34 +0000821 double
cristy47e00502009-12-17 19:19:57 +0000822 *kernel,
823 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000824
cristy117ff172010-08-15 21:35:32 +0000825 register ssize_t
826 i;
827
cristybb503372010-05-27 20:51:26 +0000828 ssize_t
cristy47e00502009-12-17 19:19:57 +0000829 j,
830 k;
cristy3ed852e2009-09-05 21:47:34 +0000831
cristy3ed852e2009-09-05 21:47:34 +0000832 /*
833 Generate a 1-D convolution kernel.
834 */
835 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
836 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
837 if (kernel == (double *) NULL)
838 return(0);
cristy3ed852e2009-09-05 21:47:34 +0000839 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000840 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +0000841 i=0;
842 for (k=(-j); k <= j; k++)
843 {
cristy4205a3c2010-09-12 20:19:59 +0000844 kernel[i]=(double) (exp(-((double) k*k)/(2.0*MagickSigma*MagickSigma))/
845 (MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +0000846 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +0000847 i++;
848 }
cristybb503372010-05-27 20:51:26 +0000849 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000850 kernel[i]/=normalize;
851 return(kernel);
852}
853
cristyf4ad9df2011-07-08 16:49:03 +0000854MagickExport Image *BlurImage(const Image *image,const double radius,
cristy05c0c9a2011-09-05 23:16:13 +0000855 const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000856{
857#define BlurImageTag "Blur/Image"
858
cristyc4c8d132010-01-07 01:58:38 +0000859 CacheView
860 *blur_view,
861 *image_view;
862
cristy3ed852e2009-09-05 21:47:34 +0000863 double
864 *kernel;
865
866 Image
867 *blur_image;
868
cristy3ed852e2009-09-05 21:47:34 +0000869 MagickBooleanType
870 status;
871
cristybb503372010-05-27 20:51:26 +0000872 MagickOffsetType
873 progress;
874
cristybb503372010-05-27 20:51:26 +0000875 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000876 i;
877
cristybb503372010-05-27 20:51:26 +0000878 size_t
cristy3ed852e2009-09-05 21:47:34 +0000879 width;
880
cristybb503372010-05-27 20:51:26 +0000881 ssize_t
cristyb41a1172011-09-06 00:55:14 +0000882 center,
cristybb503372010-05-27 20:51:26 +0000883 x,
884 y;
885
cristy3ed852e2009-09-05 21:47:34 +0000886 /*
887 Initialize blur image attributes.
888 */
889 assert(image != (Image *) NULL);
890 assert(image->signature == MagickSignature);
891 if (image->debug != MagickFalse)
892 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
893 assert(exception != (ExceptionInfo *) NULL);
894 assert(exception->signature == MagickSignature);
cristyd25c77e2011-09-06 00:10:24 +0000895 blur_image=CloneImage(image,0,0,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +0000896 if (blur_image == (Image *) NULL)
897 return((Image *) NULL);
898 if (fabs(sigma) <= MagickEpsilon)
899 return(blur_image);
cristy574cc262011-08-05 01:23:58 +0000900 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000901 {
cristy3ed852e2009-09-05 21:47:34 +0000902 blur_image=DestroyImage(blur_image);
903 return((Image *) NULL);
904 }
905 width=GetOptimalKernelWidth1D(radius,sigma);
906 kernel=GetBlurKernel(width,sigma);
907 if (kernel == (double *) NULL)
908 {
909 blur_image=DestroyImage(blur_image);
910 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
911 }
912 if (image->debug != MagickFalse)
913 {
914 char
915 format[MaxTextExtent],
916 *message;
917
918 register const double
919 *k;
920
921 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +0000922 " BlurImage with %.20g kernel:",(double) width);
cristy3ed852e2009-09-05 21:47:34 +0000923 message=AcquireString("");
924 k=kernel;
cristybb503372010-05-27 20:51:26 +0000925 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000926 {
927 *message='\0';
cristyb51dff52011-05-19 16:55:47 +0000928 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) i);
cristy3ed852e2009-09-05 21:47:34 +0000929 (void) ConcatenateString(&message,format);
cristyb51dff52011-05-19 16:55:47 +0000930 (void) FormatLocaleString(format,MaxTextExtent,"%g ",*k++);
cristy3ed852e2009-09-05 21:47:34 +0000931 (void) ConcatenateString(&message,format);
932 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
933 }
934 message=DestroyString(message);
935 }
936 /*
937 Blur rows.
938 */
939 status=MagickTrue;
940 progress=0;
cristyb41a1172011-09-06 00:55:14 +0000941 center=(ssize_t) GetPixelChannels(image)*(width/2L);
cristy3ed852e2009-09-05 21:47:34 +0000942 image_view=AcquireCacheView(image);
943 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000944#if defined(MAGICKCORE_OPENMP_SUPPORT)
945 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000946#endif
cristyb41a1172011-09-06 00:55:14 +0000947 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000948 {
cristy4c08aed2011-07-01 19:47:50 +0000949 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000950 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000951
cristy4c08aed2011-07-01 19:47:50 +0000952 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000953 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000954
cristy117ff172010-08-15 21:35:32 +0000955 register ssize_t
956 x;
957
cristy3ed852e2009-09-05 21:47:34 +0000958 if (status == MagickFalse)
959 continue;
cristy117ff172010-08-15 21:35:32 +0000960 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y,
961 image->columns+width,1,exception);
cristy3ed852e2009-09-05 21:47:34 +0000962 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
963 exception);
cristy4c08aed2011-07-01 19:47:50 +0000964 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000965 {
966 status=MagickFalse;
967 continue;
968 }
cristyb41a1172011-09-06 00:55:14 +0000969 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000970 {
cristybb503372010-05-27 20:51:26 +0000971 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000972 i;
973
cristyb41a1172011-09-06 00:55:14 +0000974 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
975 {
976 MagickRealType
977 alpha,
978 gamma,
979 pixel;
cristyd25c77e2011-09-06 00:10:24 +0000980
cristyb41a1172011-09-06 00:55:14 +0000981 PixelChannel
982 channel;
983
984 PixelTrait
985 blur_traits,
986 traits;
987
988 register const double
989 *restrict k;
990
991 register const Quantum
992 *restrict pixels;
993
994 register ssize_t
995 u;
996
cristye2a912b2011-12-05 20:02:07 +0000997 traits=GetPixelChannelMapTraits(image,i);
998 channel=GetPixelChannelMapChannel(image,i);
cristyb41a1172011-09-06 00:55:14 +0000999 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
1000 if ((traits == UndefinedPixelTrait) ||
1001 (blur_traits == UndefinedPixelTrait))
1002 continue;
1003 if ((blur_traits & CopyPixelTrait) != 0)
cristyd25c77e2011-09-06 00:10:24 +00001004 {
cristy0beccfa2011-09-25 20:47:53 +00001005 SetPixelChannel(blur_image,channel,p[center+i],q);
cristyb41a1172011-09-06 00:55:14 +00001006 continue;
cristyd25c77e2011-09-06 00:10:24 +00001007 }
cristyb41a1172011-09-06 00:55:14 +00001008 k=kernel;
1009 pixels=p;
1010 pixel=0.0;
1011 if ((blur_traits & BlendPixelTrait) == 0)
1012 {
1013 /*
1014 No alpha blending.
1015 */
1016 for (u=0; u < (ssize_t) width; u++)
cristyd25c77e2011-09-06 00:10:24 +00001017 {
cristyb41a1172011-09-06 00:55:14 +00001018 pixel+=(*k)*pixels[i];
1019 k++;
1020 pixels+=GetPixelChannels(image);
cristyd25c77e2011-09-06 00:10:24 +00001021 }
cristy0beccfa2011-09-25 20:47:53 +00001022 SetPixelChannel(blur_image,channel,ClampToQuantum(pixel),q);
cristyb41a1172011-09-06 00:55:14 +00001023 continue;
1024 }
1025 /*
1026 Alpha blending.
1027 */
1028 gamma=0.0;
1029 for (u=0; u < (ssize_t) width; u++)
1030 {
1031 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,pixels));
1032 pixel+=(*k)*alpha*pixels[i];
1033 gamma+=(*k)*alpha;
1034 k++;
1035 pixels+=GetPixelChannels(image);
cristyd25c77e2011-09-06 00:10:24 +00001036 }
cristyb41a1172011-09-06 00:55:14 +00001037 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +00001038 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristyb41a1172011-09-06 00:55:14 +00001039 }
cristyed231572011-07-14 02:18:59 +00001040 p+=GetPixelChannels(image);
1041 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001042 }
1043 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1044 status=MagickFalse;
1045 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1046 {
1047 MagickBooleanType
1048 proceed;
1049
cristyb5d5f722009-11-04 03:03:49 +00001050#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00001051 #pragma omp critical (MagickCore_BlurImage)
cristy3ed852e2009-09-05 21:47:34 +00001052#endif
1053 proceed=SetImageProgress(image,BlurImageTag,progress++,blur_image->rows+
1054 blur_image->columns);
1055 if (proceed == MagickFalse)
1056 status=MagickFalse;
1057 }
1058 }
1059 blur_view=DestroyCacheView(blur_view);
1060 image_view=DestroyCacheView(image_view);
1061 /*
1062 Blur columns.
1063 */
1064 image_view=AcquireCacheView(blur_image);
1065 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00001066#if defined(MAGICKCORE_OPENMP_SUPPORT)
1067 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001068#endif
cristyb41a1172011-09-06 00:55:14 +00001069 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001070 {
cristy4c08aed2011-07-01 19:47:50 +00001071 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001072 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001073
cristy4c08aed2011-07-01 19:47:50 +00001074 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001075 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001076
cristy117ff172010-08-15 21:35:32 +00001077 register ssize_t
1078 y;
1079
cristy3ed852e2009-09-05 21:47:34 +00001080 if (status == MagickFalse)
1081 continue;
cristy117ff172010-08-15 21:35:32 +00001082 p=GetCacheViewVirtualPixels(image_view,x,-((ssize_t) width/2L),1,
1083 image->rows+width,exception);
cristy3ed852e2009-09-05 21:47:34 +00001084 q=GetCacheViewAuthenticPixels(blur_view,x,0,1,blur_image->rows,exception);
cristy4c08aed2011-07-01 19:47:50 +00001085 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001086 {
1087 status=MagickFalse;
1088 continue;
1089 }
cristyb41a1172011-09-06 00:55:14 +00001090 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001091 {
cristybb503372010-05-27 20:51:26 +00001092 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001093 i;
1094
cristyb41a1172011-09-06 00:55:14 +00001095 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1096 {
1097 MagickRealType
1098 alpha,
1099 gamma,
1100 pixel;
cristyd25c77e2011-09-06 00:10:24 +00001101
cristyb41a1172011-09-06 00:55:14 +00001102 PixelChannel
1103 channel;
1104
1105 PixelTrait
1106 blur_traits,
1107 traits;
1108
1109 register const double
1110 *restrict k;
1111
1112 register const Quantum
1113 *restrict pixels;
1114
1115 register ssize_t
1116 u;
1117
cristye2a912b2011-12-05 20:02:07 +00001118 traits=GetPixelChannelMapTraits(blur_image,i);
1119 channel=GetPixelChannelMapChannel(blur_image,i);
cristyb41a1172011-09-06 00:55:14 +00001120 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
1121 if ((traits == UndefinedPixelTrait) ||
1122 (blur_traits == UndefinedPixelTrait))
1123 continue;
1124 if ((blur_traits & CopyPixelTrait) != 0)
cristyd25c77e2011-09-06 00:10:24 +00001125 {
cristy0beccfa2011-09-25 20:47:53 +00001126 SetPixelChannel(blur_image,channel,p[center+i],q);
cristyb41a1172011-09-06 00:55:14 +00001127 continue;
cristyd25c77e2011-09-06 00:10:24 +00001128 }
cristyb41a1172011-09-06 00:55:14 +00001129 k=kernel;
1130 pixels=p;
1131 pixel=0.0;
1132 if ((blur_traits & BlendPixelTrait) == 0)
1133 {
1134 /*
1135 No alpha blending.
1136 */
1137 for (u=0; u < (ssize_t) width; u++)
cristyd25c77e2011-09-06 00:10:24 +00001138 {
cristyb41a1172011-09-06 00:55:14 +00001139 pixel+=(*k)*pixels[i];
1140 k++;
1141 pixels+=GetPixelChannels(blur_image);
cristyd25c77e2011-09-06 00:10:24 +00001142 }
cristy0beccfa2011-09-25 20:47:53 +00001143 SetPixelChannel(blur_image,channel,ClampToQuantum(pixel),q);
cristyb41a1172011-09-06 00:55:14 +00001144 continue;
1145 }
1146 /*
1147 Alpha blending.
1148 */
1149 gamma=0.0;
1150 for (u=0; u < (ssize_t) width; u++)
1151 {
1152 alpha=(MagickRealType) (QuantumScale*
1153 GetPixelAlpha(blur_image,pixels));
1154 pixel+=(*k)*alpha*pixels[i];
1155 gamma+=(*k)*alpha;
1156 k++;
1157 pixels+=GetPixelChannels(blur_image);
cristyd25c77e2011-09-06 00:10:24 +00001158 }
cristyb41a1172011-09-06 00:55:14 +00001159 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +00001160 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristyb41a1172011-09-06 00:55:14 +00001161 }
cristyd25c77e2011-09-06 00:10:24 +00001162 p+=GetPixelChannels(blur_image);
cristyed231572011-07-14 02:18:59 +00001163 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001164 }
1165 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1166 status=MagickFalse;
cristy4c08aed2011-07-01 19:47:50 +00001167 if (blur_image->progress_monitor != (MagickProgressMonitor) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001168 {
1169 MagickBooleanType
1170 proceed;
1171
cristyb5d5f722009-11-04 03:03:49 +00001172#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00001173 #pragma omp critical (MagickCore_BlurImage)
cristy3ed852e2009-09-05 21:47:34 +00001174#endif
cristy4c08aed2011-07-01 19:47:50 +00001175 proceed=SetImageProgress(blur_image,BlurImageTag,progress++,
1176 blur_image->rows+blur_image->columns);
cristy3ed852e2009-09-05 21:47:34 +00001177 if (proceed == MagickFalse)
1178 status=MagickFalse;
1179 }
1180 }
1181 blur_view=DestroyCacheView(blur_view);
1182 image_view=DestroyCacheView(image_view);
1183 kernel=(double *) RelinquishMagickMemory(kernel);
1184 if (status == MagickFalse)
1185 blur_image=DestroyImage(blur_image);
1186 blur_image->type=image->type;
1187 return(blur_image);
1188}
1189
1190/*
1191%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1192% %
1193% %
1194% %
cristyfccdab92009-11-30 16:43:57 +00001195% C o n v o l v e I m a g e %
1196% %
1197% %
1198% %
1199%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1200%
1201% ConvolveImage() applies a custom convolution kernel to the image.
1202%
1203% The format of the ConvolveImage method is:
1204%
cristy5e6be1e2011-07-16 01:23:39 +00001205% Image *ConvolveImage(const Image *image,const KernelInfo *kernel,
1206% ExceptionInfo *exception)
1207%
cristyfccdab92009-11-30 16:43:57 +00001208% A description of each parameter follows:
1209%
1210% o image: the image.
1211%
cristy5e6be1e2011-07-16 01:23:39 +00001212% o kernel: the filtering kernel.
cristyfccdab92009-11-30 16:43:57 +00001213%
1214% o exception: return any errors or warnings in this structure.
1215%
1216*/
cristy5e6be1e2011-07-16 01:23:39 +00001217MagickExport Image *ConvolveImage(const Image *image,
1218 const KernelInfo *kernel_info,ExceptionInfo *exception)
cristyfccdab92009-11-30 16:43:57 +00001219{
cristyfccdab92009-11-30 16:43:57 +00001220#define ConvolveImageTag "Convolve/Image"
1221
cristyc4c8d132010-01-07 01:58:38 +00001222 CacheView
1223 *convolve_view,
cristy105ba3c2011-07-18 02:28:38 +00001224 *image_view;
cristyc4c8d132010-01-07 01:58:38 +00001225
cristyfccdab92009-11-30 16:43:57 +00001226 Image
1227 *convolve_image;
1228
cristyfccdab92009-11-30 16:43:57 +00001229 MagickBooleanType
1230 status;
1231
cristybb503372010-05-27 20:51:26 +00001232 MagickOffsetType
1233 progress;
1234
cristybb503372010-05-27 20:51:26 +00001235 ssize_t
cristy574cc262011-08-05 01:23:58 +00001236 center,
cristybb503372010-05-27 20:51:26 +00001237 y;
1238
cristyfccdab92009-11-30 16:43:57 +00001239 /*
1240 Initialize convolve image attributes.
1241 */
1242 assert(image != (Image *) NULL);
1243 assert(image->signature == MagickSignature);
1244 if (image->debug != MagickFalse)
1245 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1246 assert(exception != (ExceptionInfo *) NULL);
1247 assert(exception->signature == MagickSignature);
cristy5e6be1e2011-07-16 01:23:39 +00001248 if ((kernel_info->width % 2) == 0)
cristyfccdab92009-11-30 16:43:57 +00001249 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
cristy08429172011-07-14 17:18:16 +00001250 convolve_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1251 exception);
cristyfccdab92009-11-30 16:43:57 +00001252 if (convolve_image == (Image *) NULL)
1253 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00001254 if (SetImageStorageClass(convolve_image,DirectClass,exception) == MagickFalse)
cristyfccdab92009-11-30 16:43:57 +00001255 {
cristyfccdab92009-11-30 16:43:57 +00001256 convolve_image=DestroyImage(convolve_image);
1257 return((Image *) NULL);
1258 }
1259 if (image->debug != MagickFalse)
1260 {
1261 char
1262 format[MaxTextExtent],
1263 *message;
1264
cristy117ff172010-08-15 21:35:32 +00001265 register const double
1266 *k;
1267
cristy4e154852011-07-14 13:28:53 +00001268 register ssize_t
1269 u;
1270
cristybb503372010-05-27 20:51:26 +00001271 ssize_t
cristyfccdab92009-11-30 16:43:57 +00001272 v;
1273
cristyfccdab92009-11-30 16:43:57 +00001274 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristy5e6be1e2011-07-16 01:23:39 +00001275 " ConvolveImage with %.20gx%.20g kernel:",(double) kernel_info->width,
1276 (double) kernel_info->height);
cristyfccdab92009-11-30 16:43:57 +00001277 message=AcquireString("");
cristy5e6be1e2011-07-16 01:23:39 +00001278 k=kernel_info->values;
1279 for (v=0; v < (ssize_t) kernel_info->width; v++)
cristyfccdab92009-11-30 16:43:57 +00001280 {
1281 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00001282 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristyfccdab92009-11-30 16:43:57 +00001283 (void) ConcatenateString(&message,format);
cristy5e6be1e2011-07-16 01:23:39 +00001284 for (u=0; u < (ssize_t) kernel_info->height; u++)
cristyfccdab92009-11-30 16:43:57 +00001285 {
cristyb51dff52011-05-19 16:55:47 +00001286 (void) FormatLocaleString(format,MaxTextExtent,"%g ",*k++);
cristyfccdab92009-11-30 16:43:57 +00001287 (void) ConcatenateString(&message,format);
1288 }
1289 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
1290 }
1291 message=DestroyString(message);
1292 }
cristy7ea27962011-09-12 18:12:49 +00001293 status=AccelerateConvolveImage(image,kernel_info,convolve_image,exception);
1294 if (status == MagickTrue)
1295 return(convolve_image);
cristyfccdab92009-11-30 16:43:57 +00001296 /*
cristyfccdab92009-11-30 16:43:57 +00001297 Convolve image.
1298 */
cristy574cc262011-08-05 01:23:58 +00001299 center=(ssize_t) GetPixelChannels(image)*(image->columns+kernel_info->width)*
cristy075ff302011-09-07 01:51:24 +00001300 (kernel_info->height/2L)+GetPixelChannels(image)*(kernel_info->width/2L);
cristyfccdab92009-11-30 16:43:57 +00001301 status=MagickTrue;
1302 progress=0;
cristyfccdab92009-11-30 16:43:57 +00001303 image_view=AcquireCacheView(image);
1304 convolve_view=AcquireCacheView(convolve_image);
1305#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy175653e2011-07-10 23:13:34 +00001306 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristyfccdab92009-11-30 16:43:57 +00001307#endif
cristybb503372010-05-27 20:51:26 +00001308 for (y=0; y < (ssize_t) image->rows; y++)
cristyfccdab92009-11-30 16:43:57 +00001309 {
cristy4c08aed2011-07-01 19:47:50 +00001310 register const Quantum
cristy105ba3c2011-07-18 02:28:38 +00001311 *restrict p;
cristyfccdab92009-11-30 16:43:57 +00001312
cristy4c08aed2011-07-01 19:47:50 +00001313 register Quantum
cristyfccdab92009-11-30 16:43:57 +00001314 *restrict q;
1315
cristy117ff172010-08-15 21:35:32 +00001316 register ssize_t
1317 x;
1318
cristyfccdab92009-11-30 16:43:57 +00001319 if (status == MagickFalse)
1320 continue;
cristy105ba3c2011-07-18 02:28:38 +00001321 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) kernel_info->width/2L),y-
1322 (ssize_t) (kernel_info->height/2L),image->columns+kernel_info->width,
1323 kernel_info->height,exception);
cristy08429172011-07-14 17:18:16 +00001324 q=QueueCacheViewAuthenticPixels(convolve_view,0,y,convolve_image->columns,1,
cristyfccdab92009-11-30 16:43:57 +00001325 exception);
cristy105ba3c2011-07-18 02:28:38 +00001326 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristyfccdab92009-11-30 16:43:57 +00001327 {
1328 status=MagickFalse;
1329 continue;
1330 }
cristybb503372010-05-27 20:51:26 +00001331 for (x=0; x < (ssize_t) image->columns; x++)
cristyfccdab92009-11-30 16:43:57 +00001332 {
cristybb503372010-05-27 20:51:26 +00001333 register ssize_t
cristyed231572011-07-14 02:18:59 +00001334 i;
cristyfccdab92009-11-30 16:43:57 +00001335
cristya30d9ba2011-07-23 21:00:48 +00001336 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristyed231572011-07-14 02:18:59 +00001337 {
cristyed231572011-07-14 02:18:59 +00001338 MagickRealType
cristy4e154852011-07-14 13:28:53 +00001339 alpha,
1340 gamma,
cristyed231572011-07-14 02:18:59 +00001341 pixel;
1342
1343 PixelChannel
1344 channel;
1345
1346 PixelTrait
1347 convolve_traits,
1348 traits;
1349
1350 register const double
1351 *restrict k;
1352
1353 register const Quantum
cristyeb52cde2011-07-17 01:52:52 +00001354 *restrict pixels;
cristyed231572011-07-14 02:18:59 +00001355
1356 register ssize_t
1357 u;
1358
1359 ssize_t
1360 v;
1361
cristye2a912b2011-12-05 20:02:07 +00001362 traits=GetPixelChannelMapTraits(image,i);
1363 channel=GetPixelChannelMapChannel(image,i);
cristy4e154852011-07-14 13:28:53 +00001364 convolve_traits=GetPixelChannelMapTraits(convolve_image,channel);
cristy010d7d12011-08-31 01:02:48 +00001365 if ((traits == UndefinedPixelTrait) ||
1366 (convolve_traits == UndefinedPixelTrait))
cristy4e154852011-07-14 13:28:53 +00001367 continue;
1368 if ((convolve_traits & CopyPixelTrait) != 0)
1369 {
cristy0beccfa2011-09-25 20:47:53 +00001370 SetPixelChannel(convolve_image,channel,p[center+i],q);
cristy4e154852011-07-14 13:28:53 +00001371 continue;
1372 }
cristy5e6be1e2011-07-16 01:23:39 +00001373 k=kernel_info->values;
cristy105ba3c2011-07-18 02:28:38 +00001374 pixels=p;
cristy0a922382011-07-16 15:30:34 +00001375 pixel=kernel_info->bias;
cristy222b19c2011-08-04 01:35:11 +00001376 if ((convolve_traits & BlendPixelTrait) == 0)
cristyfccdab92009-11-30 16:43:57 +00001377 {
cristyed231572011-07-14 02:18:59 +00001378 /*
cristy4e154852011-07-14 13:28:53 +00001379 No alpha blending.
cristyed231572011-07-14 02:18:59 +00001380 */
cristyeb52cde2011-07-17 01:52:52 +00001381 for (v=0; v < (ssize_t) kernel_info->height; v++)
cristyfccdab92009-11-30 16:43:57 +00001382 {
cristyeb52cde2011-07-17 01:52:52 +00001383 for (u=0; u < (ssize_t) kernel_info->width; u++)
cristy175653e2011-07-10 23:13:34 +00001384 {
cristyeb52cde2011-07-17 01:52:52 +00001385 pixel+=(*k)*pixels[i];
cristyed231572011-07-14 02:18:59 +00001386 k++;
cristya30d9ba2011-07-23 21:00:48 +00001387 pixels+=GetPixelChannels(image);
cristy175653e2011-07-10 23:13:34 +00001388 }
cristya30d9ba2011-07-23 21:00:48 +00001389 pixels+=image->columns*GetPixelChannels(image);
cristy175653e2011-07-10 23:13:34 +00001390 }
cristy0beccfa2011-09-25 20:47:53 +00001391 SetPixelChannel(convolve_image,channel,ClampToQuantum(pixel),q);
cristy4e154852011-07-14 13:28:53 +00001392 continue;
cristyed231572011-07-14 02:18:59 +00001393 }
cristy4e154852011-07-14 13:28:53 +00001394 /*
1395 Alpha blending.
1396 */
1397 gamma=0.0;
cristyeb52cde2011-07-17 01:52:52 +00001398 for (v=0; v < (ssize_t) kernel_info->height; v++)
cristy4e154852011-07-14 13:28:53 +00001399 {
cristyeb52cde2011-07-17 01:52:52 +00001400 for (u=0; u < (ssize_t) kernel_info->width; u++)
cristy4e154852011-07-14 13:28:53 +00001401 {
cristyeb52cde2011-07-17 01:52:52 +00001402 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,pixels));
1403 pixel+=(*k)*alpha*pixels[i];
cristy4e154852011-07-14 13:28:53 +00001404 gamma+=(*k)*alpha;
1405 k++;
cristya30d9ba2011-07-23 21:00:48 +00001406 pixels+=GetPixelChannels(image);
cristy4e154852011-07-14 13:28:53 +00001407 }
cristya30d9ba2011-07-23 21:00:48 +00001408 pixels+=image->columns*GetPixelChannels(image);
cristy4e154852011-07-14 13:28:53 +00001409 }
cristy1ce96d02011-07-14 17:57:24 +00001410 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +00001411 SetPixelChannel(convolve_image,channel,ClampToQuantum(gamma*pixel),q);
cristyed231572011-07-14 02:18:59 +00001412 }
cristya30d9ba2011-07-23 21:00:48 +00001413 p+=GetPixelChannels(image);
1414 q+=GetPixelChannels(convolve_image);
cristyfccdab92009-11-30 16:43:57 +00001415 }
cristyed231572011-07-14 02:18:59 +00001416 if (SyncCacheViewAuthenticPixels(convolve_view,exception) == MagickFalse)
cristyfccdab92009-11-30 16:43:57 +00001417 status=MagickFalse;
1418 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1419 {
1420 MagickBooleanType
1421 proceed;
1422
1423#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00001424 #pragma omp critical (MagickCore_ConvolveImage)
cristyfccdab92009-11-30 16:43:57 +00001425#endif
1426 proceed=SetImageProgress(image,ConvolveImageTag,progress++,image->rows);
1427 if (proceed == MagickFalse)
1428 status=MagickFalse;
1429 }
1430 }
1431 convolve_image->type=image->type;
1432 convolve_view=DestroyCacheView(convolve_view);
1433 image_view=DestroyCacheView(image_view);
cristyfccdab92009-11-30 16:43:57 +00001434 if (status == MagickFalse)
1435 convolve_image=DestroyImage(convolve_image);
1436 return(convolve_image);
1437}
1438
1439/*
1440%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1441% %
1442% %
1443% %
cristy3ed852e2009-09-05 21:47:34 +00001444% D e s p e c k l e I m a g e %
1445% %
1446% %
1447% %
1448%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1449%
1450% DespeckleImage() reduces the speckle noise in an image while perserving the
1451% edges of the original image.
1452%
1453% The format of the DespeckleImage method is:
1454%
1455% Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1456%
1457% A description of each parameter follows:
1458%
1459% o image: the image.
1460%
1461% o exception: return any errors or warnings in this structure.
1462%
1463*/
1464
cristybb503372010-05-27 20:51:26 +00001465static void Hull(const ssize_t x_offset,const ssize_t y_offset,
1466 const size_t columns,const size_t rows,Quantum *f,Quantum *g,
cristy3ed852e2009-09-05 21:47:34 +00001467 const int polarity)
1468{
cristy3ed852e2009-09-05 21:47:34 +00001469 MagickRealType
1470 v;
1471
cristy3ed852e2009-09-05 21:47:34 +00001472 register Quantum
1473 *p,
1474 *q,
1475 *r,
1476 *s;
1477
cristy117ff172010-08-15 21:35:32 +00001478 register ssize_t
1479 x;
1480
1481 ssize_t
1482 y;
1483
cristy3ed852e2009-09-05 21:47:34 +00001484 assert(f != (Quantum *) NULL);
1485 assert(g != (Quantum *) NULL);
1486 p=f+(columns+2);
1487 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001488 r=p+(y_offset*((ssize_t) columns+2)+x_offset);
1489 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001490 {
1491 p++;
1492 q++;
1493 r++;
1494 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001495 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001496 {
1497 v=(MagickRealType) (*p);
1498 if ((MagickRealType) *r >= (v+(MagickRealType) ScaleCharToQuantum(2)))
1499 v+=ScaleCharToQuantum(1);
1500 *q=(Quantum) v;
1501 p++;
1502 q++;
1503 r++;
1504 }
1505 else
cristybb503372010-05-27 20:51:26 +00001506 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001507 {
1508 v=(MagickRealType) (*p);
1509 if ((MagickRealType) *r <= (v-(MagickRealType) ScaleCharToQuantum(2)))
cristybb503372010-05-27 20:51:26 +00001510 v-=(ssize_t) ScaleCharToQuantum(1);
cristy3ed852e2009-09-05 21:47:34 +00001511 *q=(Quantum) v;
1512 p++;
1513 q++;
1514 r++;
1515 }
1516 p++;
1517 q++;
1518 r++;
1519 }
1520 p=f+(columns+2);
1521 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001522 r=q+(y_offset*((ssize_t) columns+2)+x_offset);
1523 s=q-(y_offset*((ssize_t) columns+2)+x_offset);
1524 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001525 {
1526 p++;
1527 q++;
1528 r++;
1529 s++;
1530 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001531 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001532 {
1533 v=(MagickRealType) (*q);
1534 if (((MagickRealType) *s >=
1535 (v+(MagickRealType) ScaleCharToQuantum(2))) &&
1536 ((MagickRealType) *r > v))
1537 v+=ScaleCharToQuantum(1);
1538 *p=(Quantum) v;
1539 p++;
1540 q++;
1541 r++;
1542 s++;
1543 }
1544 else
cristybb503372010-05-27 20:51:26 +00001545 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001546 {
1547 v=(MagickRealType) (*q);
1548 if (((MagickRealType) *s <=
1549 (v-(MagickRealType) ScaleCharToQuantum(2))) &&
1550 ((MagickRealType) *r < v))
1551 v-=(MagickRealType) ScaleCharToQuantum(1);
1552 *p=(Quantum) v;
1553 p++;
1554 q++;
1555 r++;
1556 s++;
1557 }
1558 p++;
1559 q++;
1560 r++;
1561 s++;
1562 }
1563}
1564
1565MagickExport Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1566{
1567#define DespeckleImageTag "Despeckle/Image"
1568
cristy2407fc22009-09-11 00:55:25 +00001569 CacheView
1570 *despeckle_view,
1571 *image_view;
1572
cristy3ed852e2009-09-05 21:47:34 +00001573 Image
1574 *despeckle_image;
1575
cristy3ed852e2009-09-05 21:47:34 +00001576 MagickBooleanType
1577 status;
1578
1579 Quantum
cristy65b9f392011-02-22 14:22:54 +00001580 *restrict buffers,
1581 *restrict pixels;
cristy3ed852e2009-09-05 21:47:34 +00001582
cristya63e4a92011-09-09 00:47:59 +00001583 register ssize_t
1584 i;
1585
cristy3ed852e2009-09-05 21:47:34 +00001586 size_t
cristya63e4a92011-09-09 00:47:59 +00001587 length;
cristy117ff172010-08-15 21:35:32 +00001588
cristybb503372010-05-27 20:51:26 +00001589 static const ssize_t
cristy691a29e2009-09-11 00:44:10 +00001590 X[4] = {0, 1, 1,-1},
1591 Y[4] = {1, 0, 1, 1};
cristy3ed852e2009-09-05 21:47:34 +00001592
cristy3ed852e2009-09-05 21:47:34 +00001593 /*
1594 Allocate despeckled image.
1595 */
1596 assert(image != (const Image *) NULL);
1597 assert(image->signature == MagickSignature);
1598 if (image->debug != MagickFalse)
1599 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1600 assert(exception != (ExceptionInfo *) NULL);
1601 assert(exception->signature == MagickSignature);
cristya63e4a92011-09-09 00:47:59 +00001602 despeckle_image=CloneImage(image,0,0,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00001603 if (despeckle_image == (Image *) NULL)
1604 return((Image *) NULL);
cristya63e4a92011-09-09 00:47:59 +00001605 status=SetImageStorageClass(despeckle_image,DirectClass,exception);
1606 if (status == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001607 {
cristy3ed852e2009-09-05 21:47:34 +00001608 despeckle_image=DestroyImage(despeckle_image);
1609 return((Image *) NULL);
1610 }
1611 /*
1612 Allocate image buffers.
1613 */
1614 length=(size_t) ((image->columns+2)*(image->rows+2));
cristy65b9f392011-02-22 14:22:54 +00001615 pixels=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1616 buffers=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1617 if ((pixels == (Quantum *) NULL) || (buffers == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001618 {
cristy65b9f392011-02-22 14:22:54 +00001619 if (buffers != (Quantum *) NULL)
1620 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1621 if (pixels != (Quantum *) NULL)
1622 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001623 despeckle_image=DestroyImage(despeckle_image);
1624 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1625 }
1626 /*
1627 Reduce speckle in the image.
1628 */
1629 status=MagickTrue;
1630 image_view=AcquireCacheView(image);
1631 despeckle_view=AcquireCacheView(despeckle_image);
cristya63e4a92011-09-09 00:47:59 +00001632 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy3ed852e2009-09-05 21:47:34 +00001633 {
cristya63e4a92011-09-09 00:47:59 +00001634 PixelChannel
1635 channel;
1636
1637 PixelTrait
1638 despeckle_traits,
1639 traits;
1640
cristy3ed852e2009-09-05 21:47:34 +00001641 register Quantum
1642 *buffer,
1643 *pixel;
1644
cristyc1488b52011-02-19 18:54:15 +00001645 register ssize_t
cristya58c3172011-02-19 19:23:11 +00001646 k,
cristyc1488b52011-02-19 18:54:15 +00001647 x;
1648
cristy117ff172010-08-15 21:35:32 +00001649 ssize_t
1650 j,
1651 y;
1652
cristy3ed852e2009-09-05 21:47:34 +00001653 if (status == MagickFalse)
1654 continue;
cristye2a912b2011-12-05 20:02:07 +00001655 traits=GetPixelChannelMapTraits(image,i);
1656 channel=GetPixelChannelMapChannel(image,i);
cristya63e4a92011-09-09 00:47:59 +00001657 despeckle_traits=GetPixelChannelMapTraits(despeckle_image,channel);
1658 if ((traits == UndefinedPixelTrait) ||
1659 (despeckle_traits == UndefinedPixelTrait))
1660 continue;
1661 if ((despeckle_traits & CopyPixelTrait) != 0)
1662 continue;
cristy65b9f392011-02-22 14:22:54 +00001663 pixel=pixels;
cristy3ed852e2009-09-05 21:47:34 +00001664 (void) ResetMagickMemory(pixel,0,length*sizeof(*pixel));
cristy65b9f392011-02-22 14:22:54 +00001665 buffer=buffers;
cristybb503372010-05-27 20:51:26 +00001666 j=(ssize_t) image->columns+2;
1667 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001668 {
cristy4c08aed2011-07-01 19:47:50 +00001669 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001670 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001671
1672 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001673 if (p == (const Quantum *) NULL)
cristya63e4a92011-09-09 00:47:59 +00001674 {
1675 status=MagickFalse;
1676 continue;
1677 }
cristy3ed852e2009-09-05 21:47:34 +00001678 j++;
cristybb503372010-05-27 20:51:26 +00001679 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001680 {
cristya63e4a92011-09-09 00:47:59 +00001681 pixel[j++]=p[i];
cristyed231572011-07-14 02:18:59 +00001682 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001683 }
1684 j++;
1685 }
cristy3ed852e2009-09-05 21:47:34 +00001686 (void) ResetMagickMemory(buffer,0,length*sizeof(*buffer));
cristya58c3172011-02-19 19:23:11 +00001687 for (k=0; k < 4; k++)
cristy3ed852e2009-09-05 21:47:34 +00001688 {
cristya58c3172011-02-19 19:23:11 +00001689 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,1);
1690 Hull(-X[k],-Y[k],image->columns,image->rows,pixel,buffer,1);
1691 Hull(-X[k],-Y[k],image->columns,image->rows,pixel,buffer,-1);
1692 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,-1);
cristy3ed852e2009-09-05 21:47:34 +00001693 }
cristybb503372010-05-27 20:51:26 +00001694 j=(ssize_t) image->columns+2;
1695 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001696 {
1697 MagickBooleanType
1698 sync;
1699
cristy4c08aed2011-07-01 19:47:50 +00001700 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001701 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001702
1703 q=GetCacheViewAuthenticPixels(despeckle_view,0,y,despeckle_image->columns,
1704 1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001705 if (q == (Quantum *) NULL)
cristya63e4a92011-09-09 00:47:59 +00001706 {
1707 status=MagickFalse;
1708 continue;
1709 }
cristy3ed852e2009-09-05 21:47:34 +00001710 j++;
cristybb503372010-05-27 20:51:26 +00001711 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001712 {
cristy0beccfa2011-09-25 20:47:53 +00001713 SetPixelChannel(despeckle_image,channel,pixel[j++],q);
cristyed231572011-07-14 02:18:59 +00001714 q+=GetPixelChannels(despeckle_image);
cristy3ed852e2009-09-05 21:47:34 +00001715 }
1716 sync=SyncCacheViewAuthenticPixels(despeckle_view,exception);
1717 if (sync == MagickFalse)
cristya63e4a92011-09-09 00:47:59 +00001718 status=MagickFalse;
cristy3ed852e2009-09-05 21:47:34 +00001719 j++;
1720 }
1721 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1722 {
1723 MagickBooleanType
1724 proceed;
1725
cristya58c3172011-02-19 19:23:11 +00001726 proceed=SetImageProgress(image,DespeckleImageTag,(MagickOffsetType) i,
cristya63e4a92011-09-09 00:47:59 +00001727 GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00001728 if (proceed == MagickFalse)
1729 status=MagickFalse;
1730 }
1731 }
1732 despeckle_view=DestroyCacheView(despeckle_view);
1733 image_view=DestroyCacheView(image_view);
cristy65b9f392011-02-22 14:22:54 +00001734 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1735 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001736 despeckle_image->type=image->type;
1737 if (status == MagickFalse)
1738 despeckle_image=DestroyImage(despeckle_image);
1739 return(despeckle_image);
1740}
1741
1742/*
1743%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1744% %
1745% %
1746% %
1747% E d g e I m a g e %
1748% %
1749% %
1750% %
1751%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1752%
1753% EdgeImage() finds edges in an image. Radius defines the radius of the
1754% convolution filter. Use a radius of 0 and EdgeImage() selects a suitable
1755% radius for you.
1756%
1757% The format of the EdgeImage method is:
1758%
1759% Image *EdgeImage(const Image *image,const double radius,
cristy8ae632d2011-09-05 17:29:53 +00001760% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001761%
1762% A description of each parameter follows:
1763%
1764% o image: the image.
1765%
1766% o radius: the radius of the pixel neighborhood.
1767%
cristy8ae632d2011-09-05 17:29:53 +00001768% o sigma: the standard deviation of the Gaussian, in pixels.
1769%
cristy3ed852e2009-09-05 21:47:34 +00001770% o exception: return any errors or warnings in this structure.
1771%
1772*/
1773MagickExport Image *EdgeImage(const Image *image,const double radius,
cristy8ae632d2011-09-05 17:29:53 +00001774 const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001775{
1776 Image
1777 *edge_image;
1778
cristy41cbe682011-07-15 19:12:37 +00001779 KernelInfo
1780 *kernel_info;
cristy3ed852e2009-09-05 21:47:34 +00001781
cristybb503372010-05-27 20:51:26 +00001782 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001783 i;
1784
cristybb503372010-05-27 20:51:26 +00001785 size_t
cristy3ed852e2009-09-05 21:47:34 +00001786 width;
1787
cristy41cbe682011-07-15 19:12:37 +00001788 ssize_t
1789 j,
1790 u,
1791 v;
1792
cristy3ed852e2009-09-05 21:47:34 +00001793 assert(image != (const Image *) NULL);
1794 assert(image->signature == MagickSignature);
1795 if (image->debug != MagickFalse)
1796 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1797 assert(exception != (ExceptionInfo *) NULL);
1798 assert(exception->signature == MagickSignature);
cristy8ae632d2011-09-05 17:29:53 +00001799 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00001800 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00001801 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001802 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00001803 kernel_info->width=width;
1804 kernel_info->height=width;
cristy9f752c02011-09-14 19:44:03 +00001805 kernel_info->values=(double *) AcquireAlignedMemory(kernel_info->width,
cristy41cbe682011-07-15 19:12:37 +00001806 kernel_info->width*sizeof(*kernel_info->values));
1807 if (kernel_info->values == (double *) NULL)
1808 {
1809 kernel_info=DestroyKernelInfo(kernel_info);
1810 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1811 }
1812 j=(ssize_t) kernel_info->width/2;
1813 i=0;
1814 for (v=(-j); v <= j; v++)
1815 {
1816 for (u=(-j); u <= j; u++)
1817 {
1818 kernel_info->values[i]=(-1.0);
1819 i++;
1820 }
1821 }
1822 kernel_info->values[i/2]=(double) (width*width-1.0);
anthony736a1602011-10-06 12:38:17 +00001823 kernel_info->bias=image->bias; /* FUTURE: User bias on a edge image? */
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 }
anthony736a1602011-10-06 12:38:17 +00001918 kernel_info->bias=image->bias; /* FUTURE: user bias on an edge image */
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;
anthony736a1602011-10-06 12:38:17 +00001994 kernel_info->bias=bias; /* FUTURE: user bias on Gaussian Blur! non-sense */
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
cristye2a912b2011-12-05 20:02:07 +00002219 traits=GetPixelChannelMapTraits(image,i);
2220 channel=GetPixelChannelMapChannel(image,i);
cristyf7ef0252011-09-09 14:50:06 +00002221 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
2222 if ((traits == UndefinedPixelTrait) ||
2223 (blur_traits == UndefinedPixelTrait))
2224 continue;
2225 if ((blur_traits & CopyPixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002226 {
cristy0beccfa2011-09-25 20:47:53 +00002227 SetPixelChannel(blur_image,channel,p[i],q);
cristyf7ef0252011-09-09 14:50:06 +00002228 continue;
cristy3ed852e2009-09-05 21:47:34 +00002229 }
cristyf7ef0252011-09-09 14:50:06 +00002230 k=kernel;
2231 pixel=bias;
2232 if ((blur_traits & BlendPixelTrait) == 0)
2233 {
2234 for (j=0; j < (ssize_t) width; j++)
2235 {
2236 r=GetCacheViewVirtualPixels(image_view,x+offset[j].x,y+
2237 offset[j].y,1,1,exception);
2238 if (r == (const Quantum *) NULL)
2239 {
2240 status=MagickFalse;
2241 continue;
2242 }
2243 pixel+=(*k)*r[i];
2244 k++;
2245 }
cristy0beccfa2011-09-25 20:47:53 +00002246 SetPixelChannel(blur_image,channel,ClampToQuantum(pixel),q);
cristyf7ef0252011-09-09 14:50:06 +00002247 continue;
2248 }
2249 alpha=0.0;
2250 gamma=0.0;
2251 for (j=0; j < (ssize_t) width; j++)
2252 {
2253 r=GetCacheViewVirtualPixels(image_view,x+offset[j].x,y+offset[j].y,1,
2254 1,exception);
2255 if (r == (const Quantum *) NULL)
2256 {
2257 status=MagickFalse;
2258 continue;
2259 }
2260 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,r));
2261 pixel+=(*k)*alpha*r[i];
2262 gamma+=(*k)*alpha;
2263 k++;
cristy3ed852e2009-09-05 21:47:34 +00002264 }
cristyf7ef0252011-09-09 14:50:06 +00002265 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +00002266 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristyf7ef0252011-09-09 14:50:06 +00002267 }
2268 p+=GetPixelChannels(image);
cristyed231572011-07-14 02:18:59 +00002269 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00002270 }
2271 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
2272 status=MagickFalse;
2273 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2274 {
2275 MagickBooleanType
2276 proceed;
2277
cristyb557a152011-02-22 12:14:30 +00002278#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00002279 #pragma omp critical (MagickCore_MotionBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00002280#endif
2281 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
2282 if (proceed == MagickFalse)
2283 status=MagickFalse;
2284 }
2285 }
2286 blur_view=DestroyCacheView(blur_view);
2287 image_view=DestroyCacheView(image_view);
2288 kernel=(double *) RelinquishMagickMemory(kernel);
2289 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2290 if (status == MagickFalse)
2291 blur_image=DestroyImage(blur_image);
2292 return(blur_image);
2293}
2294
2295/*
2296%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2297% %
2298% %
2299% %
2300% P r e v i e w I m a g e %
2301% %
2302% %
2303% %
2304%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2305%
2306% PreviewImage() tiles 9 thumbnails of the specified image with an image
2307% processing operation applied with varying parameters. This may be helpful
2308% pin-pointing an appropriate parameter for a particular image processing
2309% operation.
2310%
2311% The format of the PreviewImages method is:
2312%
2313% Image *PreviewImages(const Image *image,const PreviewType preview,
2314% ExceptionInfo *exception)
2315%
2316% A description of each parameter follows:
2317%
2318% o image: the image.
2319%
2320% o preview: the image processing operation.
2321%
2322% o exception: return any errors or warnings in this structure.
2323%
2324*/
2325MagickExport Image *PreviewImage(const Image *image,const PreviewType preview,
2326 ExceptionInfo *exception)
2327{
2328#define NumberTiles 9
2329#define PreviewImageTag "Preview/Image"
2330#define DefaultPreviewGeometry "204x204+10+10"
2331
2332 char
2333 factor[MaxTextExtent],
2334 label[MaxTextExtent];
2335
2336 double
2337 degrees,
2338 gamma,
2339 percentage,
2340 radius,
2341 sigma,
2342 threshold;
2343
2344 Image
2345 *images,
2346 *montage_image,
2347 *preview_image,
2348 *thumbnail;
2349
2350 ImageInfo
2351 *preview_info;
2352
cristy3ed852e2009-09-05 21:47:34 +00002353 MagickBooleanType
2354 proceed;
2355
2356 MontageInfo
2357 *montage_info;
2358
2359 QuantizeInfo
2360 quantize_info;
2361
2362 RectangleInfo
2363 geometry;
2364
cristybb503372010-05-27 20:51:26 +00002365 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002366 i,
2367 x;
2368
cristybb503372010-05-27 20:51:26 +00002369 size_t
cristy3ed852e2009-09-05 21:47:34 +00002370 colors;
2371
cristy117ff172010-08-15 21:35:32 +00002372 ssize_t
2373 y;
2374
cristy3ed852e2009-09-05 21:47:34 +00002375 /*
2376 Open output image file.
2377 */
2378 assert(image != (Image *) NULL);
2379 assert(image->signature == MagickSignature);
2380 if (image->debug != MagickFalse)
2381 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2382 colors=2;
2383 degrees=0.0;
2384 gamma=(-0.2f);
2385 preview_info=AcquireImageInfo();
2386 SetGeometry(image,&geometry);
2387 (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y,
2388 &geometry.width,&geometry.height);
2389 images=NewImageList();
2390 percentage=12.5;
2391 GetQuantizeInfo(&quantize_info);
2392 radius=0.0;
2393 sigma=1.0;
2394 threshold=0.0;
2395 x=0;
2396 y=0;
2397 for (i=0; i < NumberTiles; i++)
2398 {
2399 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
2400 if (thumbnail == (Image *) NULL)
2401 break;
2402 (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL,
2403 (void *) NULL);
cristyd15e6592011-10-15 00:13:06 +00002404 (void) SetImageProperty(thumbnail,"label",DefaultTileLabel,exception);
cristy3ed852e2009-09-05 21:47:34 +00002405 if (i == (NumberTiles/2))
2406 {
cristy9950d572011-10-01 18:22:35 +00002407 (void) QueryColorCompliance("#dfdfdf",AllCompliance,
2408 &thumbnail->matte_color,exception);
cristy3ed852e2009-09-05 21:47:34 +00002409 AppendImageToList(&images,thumbnail);
2410 continue;
2411 }
2412 switch (preview)
2413 {
2414 case RotatePreview:
2415 {
2416 degrees+=45.0;
2417 preview_image=RotateImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002418 (void) FormatLocaleString(label,MaxTextExtent,"rotate %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002419 break;
2420 }
2421 case ShearPreview:
2422 {
2423 degrees+=5.0;
2424 preview_image=ShearImage(thumbnail,degrees,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002425 (void) FormatLocaleString(label,MaxTextExtent,"shear %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002426 degrees,2.0*degrees);
2427 break;
2428 }
2429 case RollPreview:
2430 {
cristybb503372010-05-27 20:51:26 +00002431 x=(ssize_t) ((i+1)*thumbnail->columns)/NumberTiles;
2432 y=(ssize_t) ((i+1)*thumbnail->rows)/NumberTiles;
cristy3ed852e2009-09-05 21:47:34 +00002433 preview_image=RollImage(thumbnail,x,y,exception);
cristyb51dff52011-05-19 16:55:47 +00002434 (void) FormatLocaleString(label,MaxTextExtent,"roll %+.20gx%+.20g",
cristye8c25f92010-06-03 00:53:06 +00002435 (double) x,(double) y);
cristy3ed852e2009-09-05 21:47:34 +00002436 break;
2437 }
2438 case HuePreview:
2439 {
2440 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2441 if (preview_image == (Image *) NULL)
2442 break;
cristyb51dff52011-05-19 16:55:47 +00002443 (void) FormatLocaleString(factor,MaxTextExtent,"100,100,%g",
cristy3ed852e2009-09-05 21:47:34 +00002444 2.0*percentage);
cristy33bd5152011-08-24 01:42:24 +00002445 (void) ModulateImage(preview_image,factor,exception);
cristyb51dff52011-05-19 16:55:47 +00002446 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002447 break;
2448 }
2449 case SaturationPreview:
2450 {
2451 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2452 if (preview_image == (Image *) NULL)
2453 break;
cristyb51dff52011-05-19 16:55:47 +00002454 (void) FormatLocaleString(factor,MaxTextExtent,"100,%g",
cristy8cd5b312010-01-07 01:10:24 +00002455 2.0*percentage);
cristy33bd5152011-08-24 01:42:24 +00002456 (void) ModulateImage(preview_image,factor,exception);
cristyb51dff52011-05-19 16:55:47 +00002457 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002458 break;
2459 }
2460 case BrightnessPreview:
2461 {
2462 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2463 if (preview_image == (Image *) NULL)
2464 break;
cristyb51dff52011-05-19 16:55:47 +00002465 (void) FormatLocaleString(factor,MaxTextExtent,"%g",2.0*percentage);
cristy33bd5152011-08-24 01:42:24 +00002466 (void) ModulateImage(preview_image,factor,exception);
cristyb51dff52011-05-19 16:55:47 +00002467 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002468 break;
2469 }
2470 case GammaPreview:
2471 default:
2472 {
2473 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2474 if (preview_image == (Image *) NULL)
2475 break;
2476 gamma+=0.4f;
cristyb3e7c6c2011-07-24 01:43:55 +00002477 (void) GammaImage(preview_image,gamma,exception);
cristyb51dff52011-05-19 16:55:47 +00002478 (void) FormatLocaleString(label,MaxTextExtent,"gamma %g",gamma);
cristy3ed852e2009-09-05 21:47:34 +00002479 break;
2480 }
2481 case SpiffPreview:
2482 {
2483 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2484 if (preview_image != (Image *) NULL)
2485 for (x=0; x < i; x++)
cristye23ec9d2011-08-16 18:15:40 +00002486 (void) ContrastImage(preview_image,MagickTrue,exception);
cristyb51dff52011-05-19 16:55:47 +00002487 (void) FormatLocaleString(label,MaxTextExtent,"contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002488 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002489 break;
2490 }
2491 case DullPreview:
2492 {
2493 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2494 if (preview_image == (Image *) NULL)
2495 break;
2496 for (x=0; x < i; x++)
cristye23ec9d2011-08-16 18:15:40 +00002497 (void) ContrastImage(preview_image,MagickFalse,exception);
cristyb51dff52011-05-19 16:55:47 +00002498 (void) FormatLocaleString(label,MaxTextExtent,"+contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002499 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002500 break;
2501 }
2502 case GrayscalePreview:
2503 {
2504 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2505 if (preview_image == (Image *) NULL)
2506 break;
2507 colors<<=1;
2508 quantize_info.number_colors=colors;
2509 quantize_info.colorspace=GRAYColorspace;
cristy018f07f2011-09-04 21:15:19 +00002510 (void) QuantizeImage(&quantize_info,preview_image,exception);
cristyb51dff52011-05-19 16:55:47 +00002511 (void) FormatLocaleString(label,MaxTextExtent,
cristye8c25f92010-06-03 00:53:06 +00002512 "-colorspace gray -colors %.20g",(double) colors);
cristy3ed852e2009-09-05 21:47:34 +00002513 break;
2514 }
2515 case QuantizePreview:
2516 {
2517 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2518 if (preview_image == (Image *) NULL)
2519 break;
2520 colors<<=1;
2521 quantize_info.number_colors=colors;
cristy018f07f2011-09-04 21:15:19 +00002522 (void) QuantizeImage(&quantize_info,preview_image,exception);
cristyb51dff52011-05-19 16:55:47 +00002523 (void) FormatLocaleString(label,MaxTextExtent,"colors %.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00002524 colors);
cristy3ed852e2009-09-05 21:47:34 +00002525 break;
2526 }
2527 case DespecklePreview:
2528 {
2529 for (x=0; x < (i-1); x++)
2530 {
2531 preview_image=DespeckleImage(thumbnail,exception);
2532 if (preview_image == (Image *) NULL)
2533 break;
2534 thumbnail=DestroyImage(thumbnail);
2535 thumbnail=preview_image;
2536 }
2537 preview_image=DespeckleImage(thumbnail,exception);
2538 if (preview_image == (Image *) NULL)
2539 break;
cristyb51dff52011-05-19 16:55:47 +00002540 (void) FormatLocaleString(label,MaxTextExtent,"despeckle (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002541 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002542 break;
2543 }
2544 case ReduceNoisePreview:
2545 {
cristy95c38342011-03-18 22:39:51 +00002546 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) radius,
2547 (size_t) radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002548 (void) FormatLocaleString(label,MaxTextExtent,"noise %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002549 break;
2550 }
2551 case AddNoisePreview:
2552 {
2553 switch ((int) i)
2554 {
2555 case 0:
2556 {
2557 (void) CopyMagickString(factor,"uniform",MaxTextExtent);
2558 break;
2559 }
2560 case 1:
2561 {
2562 (void) CopyMagickString(factor,"gaussian",MaxTextExtent);
2563 break;
2564 }
2565 case 2:
2566 {
2567 (void) CopyMagickString(factor,"multiplicative",MaxTextExtent);
2568 break;
2569 }
2570 case 3:
2571 {
2572 (void) CopyMagickString(factor,"impulse",MaxTextExtent);
2573 break;
2574 }
2575 case 4:
2576 {
2577 (void) CopyMagickString(factor,"laplacian",MaxTextExtent);
2578 break;
2579 }
2580 case 5:
2581 {
2582 (void) CopyMagickString(factor,"Poisson",MaxTextExtent);
2583 break;
2584 }
2585 default:
2586 {
2587 (void) CopyMagickString(thumbnail->magick,"NULL",MaxTextExtent);
2588 break;
2589 }
2590 }
cristyd76c51e2011-03-26 00:21:26 +00002591 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) i,
2592 (size_t) i,exception);
cristyb51dff52011-05-19 16:55:47 +00002593 (void) FormatLocaleString(label,MaxTextExtent,"+noise %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002594 break;
2595 }
2596 case SharpenPreview:
2597 {
anthony736a1602011-10-06 12:38:17 +00002598 /* FUTURE: user bias on sharpen! This is non-sensical! */
cristy05c0c9a2011-09-05 23:16:13 +00002599 preview_image=SharpenImage(thumbnail,radius,sigma,image->bias,
2600 exception);
cristyb51dff52011-05-19 16:55:47 +00002601 (void) FormatLocaleString(label,MaxTextExtent,"sharpen %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002602 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002603 break;
2604 }
2605 case BlurPreview:
2606 {
anthony736a1602011-10-06 12:38:17 +00002607 /* FUTURE: user bias on blur! This is non-sensical! */
cristy05c0c9a2011-09-05 23:16:13 +00002608 preview_image=BlurImage(thumbnail,radius,sigma,image->bias,exception);
cristyb51dff52011-05-19 16:55:47 +00002609 (void) FormatLocaleString(label,MaxTextExtent,"blur %gx%g",radius,
cristy3ed852e2009-09-05 21:47:34 +00002610 sigma);
2611 break;
2612 }
2613 case ThresholdPreview:
2614 {
2615 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2616 if (preview_image == (Image *) NULL)
2617 break;
cristye941a752011-10-15 01:52:48 +00002618 (void) BilevelImage(thumbnail,(double) (percentage*((MagickRealType)
2619 QuantumRange+1.0))/100.0,exception);
cristyb51dff52011-05-19 16:55:47 +00002620 (void) FormatLocaleString(label,MaxTextExtent,"threshold %g",
cristy3ed852e2009-09-05 21:47:34 +00002621 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
2622 break;
2623 }
2624 case EdgeDetectPreview:
2625 {
cristy8ae632d2011-09-05 17:29:53 +00002626 preview_image=EdgeImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00002627 (void) FormatLocaleString(label,MaxTextExtent,"edge %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002628 break;
2629 }
2630 case SpreadPreview:
2631 {
cristy5c4e2582011-09-11 19:21:03 +00002632 preview_image=SpreadImage(thumbnail,radius,thumbnail->interpolate,
2633 exception);
cristyb51dff52011-05-19 16:55:47 +00002634 (void) FormatLocaleString(label,MaxTextExtent,"spread %g",
cristy8cd5b312010-01-07 01:10:24 +00002635 radius+0.5);
cristy3ed852e2009-09-05 21:47:34 +00002636 break;
2637 }
2638 case SolarizePreview:
2639 {
2640 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2641 if (preview_image == (Image *) NULL)
2642 break;
2643 (void) SolarizeImage(preview_image,(double) QuantumRange*
cristy5cbc0162011-08-29 00:36:28 +00002644 percentage/100.0,exception);
cristyb51dff52011-05-19 16:55:47 +00002645 (void) FormatLocaleString(label,MaxTextExtent,"solarize %g",
cristy3ed852e2009-09-05 21:47:34 +00002646 (QuantumRange*percentage)/100.0);
2647 break;
2648 }
2649 case ShadePreview:
2650 {
2651 degrees+=10.0;
2652 preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees,
2653 exception);
cristyb51dff52011-05-19 16:55:47 +00002654 (void) FormatLocaleString(label,MaxTextExtent,"shade %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002655 degrees,degrees);
cristy3ed852e2009-09-05 21:47:34 +00002656 break;
2657 }
2658 case RaisePreview:
2659 {
2660 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2661 if (preview_image == (Image *) NULL)
2662 break;
cristybb503372010-05-27 20:51:26 +00002663 geometry.width=(size_t) (2*i+2);
2664 geometry.height=(size_t) (2*i+2);
cristy3ed852e2009-09-05 21:47:34 +00002665 geometry.x=i/2;
2666 geometry.y=i/2;
cristy6170ac32011-08-28 14:15:37 +00002667 (void) RaiseImage(preview_image,&geometry,MagickTrue,exception);
cristyb51dff52011-05-19 16:55:47 +00002668 (void) FormatLocaleString(label,MaxTextExtent,
cristy6d8abba2010-06-03 01:10:47 +00002669 "raise %.20gx%.20g%+.20g%+.20g",(double) geometry.width,(double)
cristye8c25f92010-06-03 00:53:06 +00002670 geometry.height,(double) geometry.x,(double) geometry.y);
cristy3ed852e2009-09-05 21:47:34 +00002671 break;
2672 }
2673 case SegmentPreview:
2674 {
2675 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2676 if (preview_image == (Image *) NULL)
2677 break;
2678 threshold+=0.4f;
2679 (void) SegmentImage(preview_image,RGBColorspace,MagickFalse,threshold,
cristy018f07f2011-09-04 21:15:19 +00002680 threshold,exception);
cristyb51dff52011-05-19 16:55:47 +00002681 (void) FormatLocaleString(label,MaxTextExtent,"segment %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002682 threshold,threshold);
2683 break;
2684 }
2685 case SwirlPreview:
2686 {
cristy76f512e2011-09-12 01:26:56 +00002687 preview_image=SwirlImage(thumbnail,degrees,image->interpolate,
2688 exception);
cristyb51dff52011-05-19 16:55:47 +00002689 (void) FormatLocaleString(label,MaxTextExtent,"swirl %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002690 degrees+=45.0;
2691 break;
2692 }
2693 case ImplodePreview:
2694 {
2695 degrees+=0.1f;
cristy76f512e2011-09-12 01:26:56 +00002696 preview_image=ImplodeImage(thumbnail,degrees,image->interpolate,
2697 exception);
cristyb51dff52011-05-19 16:55:47 +00002698 (void) FormatLocaleString(label,MaxTextExtent,"implode %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002699 break;
2700 }
2701 case WavePreview:
2702 {
2703 degrees+=5.0f;
cristy5c4e2582011-09-11 19:21:03 +00002704 preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,
2705 image->interpolate,exception);
cristyb51dff52011-05-19 16:55:47 +00002706 (void) FormatLocaleString(label,MaxTextExtent,"wave %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002707 0.5*degrees,2.0*degrees);
cristy3ed852e2009-09-05 21:47:34 +00002708 break;
2709 }
2710 case OilPaintPreview:
2711 {
cristy14973ba2011-08-27 23:48:07 +00002712 preview_image=OilPaintImage(thumbnail,(double) radius,(double) sigma,
2713 exception);
2714 (void) FormatLocaleString(label,MaxTextExtent,"charcoal %gx%g",
2715 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002716 break;
2717 }
2718 case CharcoalDrawingPreview:
2719 {
anthony736a1602011-10-06 12:38:17 +00002720 /* FUTURE: user bias on charcoal! This is non-sensical! */
cristy3ed852e2009-09-05 21:47:34 +00002721 preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma,
cristy05c0c9a2011-09-05 23:16:13 +00002722 image->bias,exception);
cristyb51dff52011-05-19 16:55:47 +00002723 (void) FormatLocaleString(label,MaxTextExtent,"charcoal %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002724 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002725 break;
2726 }
2727 case JPEGPreview:
2728 {
2729 char
2730 filename[MaxTextExtent];
2731
2732 int
2733 file;
2734
2735 MagickBooleanType
2736 status;
2737
2738 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2739 if (preview_image == (Image *) NULL)
2740 break;
cristybb503372010-05-27 20:51:26 +00002741 preview_info->quality=(size_t) percentage;
cristyb51dff52011-05-19 16:55:47 +00002742 (void) FormatLocaleString(factor,MaxTextExtent,"%.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00002743 preview_info->quality);
cristy3ed852e2009-09-05 21:47:34 +00002744 file=AcquireUniqueFileResource(filename);
2745 if (file != -1)
2746 file=close(file)-1;
cristyb51dff52011-05-19 16:55:47 +00002747 (void) FormatLocaleString(preview_image->filename,MaxTextExtent,
cristy3ed852e2009-09-05 21:47:34 +00002748 "jpeg:%s",filename);
cristy6f9e0d32011-08-28 16:32:09 +00002749 status=WriteImage(preview_info,preview_image,exception);
cristy3ed852e2009-09-05 21:47:34 +00002750 if (status != MagickFalse)
2751 {
2752 Image
2753 *quality_image;
2754
2755 (void) CopyMagickString(preview_info->filename,
2756 preview_image->filename,MaxTextExtent);
2757 quality_image=ReadImage(preview_info,exception);
2758 if (quality_image != (Image *) NULL)
2759 {
2760 preview_image=DestroyImage(preview_image);
2761 preview_image=quality_image;
2762 }
2763 }
2764 (void) RelinquishUniqueFileResource(preview_image->filename);
2765 if ((GetBlobSize(preview_image)/1024) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00002766 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%gmb ",
cristy3ed852e2009-09-05 21:47:34 +00002767 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
2768 1024.0/1024.0);
2769 else
2770 if (GetBlobSize(preview_image) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00002771 (void) FormatLocaleString(label,MaxTextExtent,
cristye7f51092010-01-17 00:39:37 +00002772 "quality %s\n%gkb ",factor,(double) ((MagickOffsetType)
cristy8cd5b312010-01-07 01:10:24 +00002773 GetBlobSize(preview_image))/1024.0);
cristy3ed852e2009-09-05 21:47:34 +00002774 else
cristyb51dff52011-05-19 16:55:47 +00002775 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%.20gb ",
cristy54ea5732011-06-10 12:39:53 +00002776 factor,(double) ((MagickOffsetType) GetBlobSize(thumbnail)));
cristy3ed852e2009-09-05 21:47:34 +00002777 break;
2778 }
2779 }
2780 thumbnail=DestroyImage(thumbnail);
2781 percentage+=12.5;
2782 radius+=0.5;
2783 sigma+=0.25;
2784 if (preview_image == (Image *) NULL)
2785 break;
2786 (void) DeleteImageProperty(preview_image,"label");
cristyd15e6592011-10-15 00:13:06 +00002787 (void) SetImageProperty(preview_image,"label",label,exception);
cristy3ed852e2009-09-05 21:47:34 +00002788 AppendImageToList(&images,preview_image);
cristybb503372010-05-27 20:51:26 +00002789 proceed=SetImageProgress(image,PreviewImageTag,(MagickOffsetType) i,
2790 NumberTiles);
cristy3ed852e2009-09-05 21:47:34 +00002791 if (proceed == MagickFalse)
2792 break;
2793 }
2794 if (images == (Image *) NULL)
2795 {
2796 preview_info=DestroyImageInfo(preview_info);
2797 return((Image *) NULL);
2798 }
2799 /*
2800 Create the montage.
2801 */
2802 montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL);
2803 (void) CopyMagickString(montage_info->filename,image->filename,MaxTextExtent);
2804 montage_info->shadow=MagickTrue;
2805 (void) CloneString(&montage_info->tile,"3x3");
2806 (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry);
2807 (void) CloneString(&montage_info->frame,DefaultTileFrame);
2808 montage_image=MontageImages(images,montage_info,exception);
2809 montage_info=DestroyMontageInfo(montage_info);
2810 images=DestroyImageList(images);
2811 if (montage_image == (Image *) NULL)
2812 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2813 if (montage_image->montage != (char *) NULL)
2814 {
2815 /*
2816 Free image directory.
2817 */
2818 montage_image->montage=(char *) RelinquishMagickMemory(
2819 montage_image->montage);
2820 if (image->directory != (char *) NULL)
2821 montage_image->directory=(char *) RelinquishMagickMemory(
2822 montage_image->directory);
2823 }
2824 preview_info=DestroyImageInfo(preview_info);
2825 return(montage_image);
2826}
2827
2828/*
2829%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2830% %
2831% %
2832% %
2833% R a d i a l B l u r I m a g e %
2834% %
2835% %
2836% %
2837%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2838%
2839% RadialBlurImage() applies a radial blur to the image.
2840%
2841% Andrew Protano contributed this effect.
2842%
2843% The format of the RadialBlurImage method is:
2844%
2845% Image *RadialBlurImage(const Image *image,const double angle,
cristy6435bd92011-09-10 02:10:07 +00002846% const double blur,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002847%
2848% A description of each parameter follows:
2849%
2850% o image: the image.
2851%
cristy3ed852e2009-09-05 21:47:34 +00002852% o angle: the angle of the radial blur.
2853%
cristy6435bd92011-09-10 02:10:07 +00002854% o blur: the blur.
2855%
cristy3ed852e2009-09-05 21:47:34 +00002856% o exception: return any errors or warnings in this structure.
2857%
2858*/
cristy4282c702011-11-21 00:01:06 +00002859MagickExport Image *RadialBlurImage(const Image *image,const double angle,
2860 const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002861{
cristyc4c8d132010-01-07 01:58:38 +00002862 CacheView
2863 *blur_view,
2864 *image_view;
2865
cristy3ed852e2009-09-05 21:47:34 +00002866 Image
2867 *blur_image;
2868
cristy3ed852e2009-09-05 21:47:34 +00002869 MagickBooleanType
2870 status;
2871
cristybb503372010-05-27 20:51:26 +00002872 MagickOffsetType
2873 progress;
2874
cristy3ed852e2009-09-05 21:47:34 +00002875 MagickRealType
2876 blur_radius,
2877 *cos_theta,
2878 offset,
2879 *sin_theta,
2880 theta;
2881
2882 PointInfo
2883 blur_center;
2884
cristybb503372010-05-27 20:51:26 +00002885 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002886 i;
2887
cristybb503372010-05-27 20:51:26 +00002888 size_t
cristy3ed852e2009-09-05 21:47:34 +00002889 n;
2890
cristybb503372010-05-27 20:51:26 +00002891 ssize_t
2892 y;
2893
cristy3ed852e2009-09-05 21:47:34 +00002894 /*
2895 Allocate blur image.
2896 */
2897 assert(image != (Image *) NULL);
2898 assert(image->signature == MagickSignature);
2899 if (image->debug != MagickFalse)
2900 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2901 assert(exception != (ExceptionInfo *) NULL);
2902 assert(exception->signature == MagickSignature);
cristy1e7aa312011-09-10 20:01:36 +00002903 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00002904 if (blur_image == (Image *) NULL)
2905 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00002906 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00002907 {
cristy3ed852e2009-09-05 21:47:34 +00002908 blur_image=DestroyImage(blur_image);
2909 return((Image *) NULL);
2910 }
2911 blur_center.x=(double) image->columns/2.0;
2912 blur_center.y=(double) image->rows/2.0;
2913 blur_radius=hypot(blur_center.x,blur_center.y);
cristy117ff172010-08-15 21:35:32 +00002914 n=(size_t) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+2UL);
cristy3ed852e2009-09-05 21:47:34 +00002915 theta=DegreesToRadians(angle)/(MagickRealType) (n-1);
2916 cos_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
2917 sizeof(*cos_theta));
2918 sin_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
2919 sizeof(*sin_theta));
2920 if ((cos_theta == (MagickRealType *) NULL) ||
2921 (sin_theta == (MagickRealType *) NULL))
2922 {
2923 blur_image=DestroyImage(blur_image);
2924 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2925 }
2926 offset=theta*(MagickRealType) (n-1)/2.0;
cristybb503372010-05-27 20:51:26 +00002927 for (i=0; i < (ssize_t) n; i++)
cristy3ed852e2009-09-05 21:47:34 +00002928 {
2929 cos_theta[i]=cos((double) (theta*i-offset));
2930 sin_theta[i]=sin((double) (theta*i-offset));
2931 }
2932 /*
2933 Radial blur image.
2934 */
2935 status=MagickTrue;
2936 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00002937 image_view=AcquireCacheView(image);
2938 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00002939#if defined(MAGICKCORE_OPENMP_SUPPORT)
2940 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002941#endif
cristy1e7aa312011-09-10 20:01:36 +00002942 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002943 {
cristy1e7aa312011-09-10 20:01:36 +00002944 register const Quantum
2945 *restrict p;
2946
cristy4c08aed2011-07-01 19:47:50 +00002947 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002948 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002949
cristy117ff172010-08-15 21:35:32 +00002950 register ssize_t
2951 x;
2952
cristy3ed852e2009-09-05 21:47:34 +00002953 if (status == MagickFalse)
2954 continue;
cristy1e7aa312011-09-10 20:01:36 +00002955 p=GetCacheViewVirtualPixels(blur_view,0,y,image->columns,1,exception);
cristy3ed852e2009-09-05 21:47:34 +00002956 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
2957 exception);
cristy1e7aa312011-09-10 20:01:36 +00002958 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00002959 {
2960 status=MagickFalse;
2961 continue;
2962 }
cristy1e7aa312011-09-10 20:01:36 +00002963 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002964 {
cristy3ed852e2009-09-05 21:47:34 +00002965 MagickRealType
cristy3ed852e2009-09-05 21:47:34 +00002966 radius;
2967
cristy3ed852e2009-09-05 21:47:34 +00002968 PointInfo
2969 center;
2970
cristybb503372010-05-27 20:51:26 +00002971 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002972 i;
2973
cristybb503372010-05-27 20:51:26 +00002974 size_t
cristy3ed852e2009-09-05 21:47:34 +00002975 step;
2976
2977 center.x=(double) x-blur_center.x;
2978 center.y=(double) y-blur_center.y;
2979 radius=hypot((double) center.x,center.y);
2980 if (radius == 0)
2981 step=1;
2982 else
2983 {
cristybb503372010-05-27 20:51:26 +00002984 step=(size_t) (blur_radius/radius);
cristy3ed852e2009-09-05 21:47:34 +00002985 if (step == 0)
2986 step=1;
2987 else
2988 if (step >= n)
2989 step=n-1;
2990 }
cristy1e7aa312011-09-10 20:01:36 +00002991 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2992 {
2993 MagickRealType
2994 gamma,
2995 pixel;
cristy3ed852e2009-09-05 21:47:34 +00002996
cristy1e7aa312011-09-10 20:01:36 +00002997 PixelChannel
2998 channel;
2999
3000 PixelTrait
3001 blur_traits,
3002 traits;
3003
3004 register const Quantum
3005 *restrict r;
3006
3007 register ssize_t
3008 j;
3009
cristye2a912b2011-12-05 20:02:07 +00003010 traits=GetPixelChannelMapTraits(image,i);
3011 channel=GetPixelChannelMapChannel(image,i);
cristy1e7aa312011-09-10 20:01:36 +00003012 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
3013 if ((traits == UndefinedPixelTrait) ||
3014 (blur_traits == UndefinedPixelTrait))
3015 continue;
3016 if ((blur_traits & CopyPixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003017 {
cristy0beccfa2011-09-25 20:47:53 +00003018 SetPixelChannel(blur_image,channel,p[i],q);
cristy1e7aa312011-09-10 20:01:36 +00003019 continue;
cristy3ed852e2009-09-05 21:47:34 +00003020 }
cristy1e7aa312011-09-10 20:01:36 +00003021 gamma=0.0;
3022 pixel=bias;
3023 if ((blur_traits & BlendPixelTrait) == 0)
3024 {
3025 for (j=0; j < (ssize_t) n; j+=(ssize_t) step)
3026 {
3027 r=GetCacheViewVirtualPixels(image_view, (ssize_t) (blur_center.x+
3028 center.x*cos_theta[j]-center.y*sin_theta[j]+0.5),(ssize_t)
3029 (blur_center.y+center.x*sin_theta[j]+center.y*cos_theta[j]+0.5),
3030 1,1,exception);
3031 if (r == (const Quantum *) NULL)
3032 {
3033 status=MagickFalse;
3034 continue;
3035 }
3036 pixel+=r[i];
3037 gamma++;
3038 }
3039 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +00003040 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy1e7aa312011-09-10 20:01:36 +00003041 continue;
3042 }
3043 for (j=0; j < (ssize_t) n; j+=(ssize_t) step)
3044 {
3045 r=GetCacheViewVirtualPixels(image_view, (ssize_t) (blur_center.x+
3046 center.x*cos_theta[j]-center.y*sin_theta[j]+0.5),(ssize_t)
3047 (blur_center.y+center.x*sin_theta[j]+center.y*cos_theta[j]+0.5),
3048 1,1,exception);
3049 if (r == (const Quantum *) NULL)
3050 {
3051 status=MagickFalse;
3052 continue;
3053 }
3054 pixel+=GetPixelAlpha(image,r)*r[i];
3055 gamma+=GetPixelAlpha(image,r);
cristy3ed852e2009-09-05 21:47:34 +00003056 }
cristy1e7aa312011-09-10 20:01:36 +00003057 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +00003058 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy1e7aa312011-09-10 20:01:36 +00003059 }
3060 p+=GetPixelChannels(image);
cristyed231572011-07-14 02:18:59 +00003061 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003062 }
3063 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
3064 status=MagickFalse;
3065 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3066 {
3067 MagickBooleanType
3068 proceed;
3069
cristyb5d5f722009-11-04 03:03:49 +00003070#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00003071 #pragma omp critical (MagickCore_RadialBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00003072#endif
3073 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
3074 if (proceed == MagickFalse)
3075 status=MagickFalse;
3076 }
3077 }
3078 blur_view=DestroyCacheView(blur_view);
3079 image_view=DestroyCacheView(image_view);
3080 cos_theta=(MagickRealType *) RelinquishMagickMemory(cos_theta);
3081 sin_theta=(MagickRealType *) RelinquishMagickMemory(sin_theta);
3082 if (status == MagickFalse)
3083 blur_image=DestroyImage(blur_image);
3084 return(blur_image);
3085}
3086
3087/*
3088%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3089% %
3090% %
3091% %
cristy3ed852e2009-09-05 21:47:34 +00003092% S e l e c t i v e B l u r I m a g e %
3093% %
3094% %
3095% %
3096%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3097%
3098% SelectiveBlurImage() selectively blur pixels within a contrast threshold.
3099% It is similar to the unsharpen mask that sharpens everything with contrast
3100% above a certain threshold.
3101%
3102% The format of the SelectiveBlurImage method is:
3103%
3104% Image *SelectiveBlurImage(const Image *image,const double radius,
cristy1e7aa312011-09-10 20:01:36 +00003105% const double sigma,const double threshold,const double bias,
3106% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003107%
3108% A description of each parameter follows:
3109%
3110% o image: the image.
3111%
cristy3ed852e2009-09-05 21:47:34 +00003112% o radius: the radius of the Gaussian, in pixels, not counting the center
3113% pixel.
3114%
3115% o sigma: the standard deviation of the Gaussian, in pixels.
3116%
3117% o threshold: only pixels within this contrast threshold are included
3118% in the blur operation.
3119%
cristy1e7aa312011-09-10 20:01:36 +00003120% o bias: the bias.
3121%
cristy3ed852e2009-09-05 21:47:34 +00003122% o exception: return any errors or warnings in this structure.
3123%
3124*/
cristy4282c702011-11-21 00:01:06 +00003125MagickExport Image *SelectiveBlurImage(const Image *image,const double radius,
3126 const double sigma,const double threshold,const double bias,
3127 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003128{
3129#define SelectiveBlurImageTag "SelectiveBlur/Image"
3130
cristy47e00502009-12-17 19:19:57 +00003131 CacheView
3132 *blur_view,
3133 *image_view;
3134
cristy3ed852e2009-09-05 21:47:34 +00003135 double
cristy3ed852e2009-09-05 21:47:34 +00003136 *kernel;
3137
3138 Image
3139 *blur_image;
3140
cristy3ed852e2009-09-05 21:47:34 +00003141 MagickBooleanType
3142 status;
3143
cristybb503372010-05-27 20:51:26 +00003144 MagickOffsetType
3145 progress;
3146
cristybb503372010-05-27 20:51:26 +00003147 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003148 i;
cristy3ed852e2009-09-05 21:47:34 +00003149
cristybb503372010-05-27 20:51:26 +00003150 size_t
cristy3ed852e2009-09-05 21:47:34 +00003151 width;
3152
cristybb503372010-05-27 20:51:26 +00003153 ssize_t
cristyc8523c12011-09-13 00:02:53 +00003154 center,
cristybb503372010-05-27 20:51:26 +00003155 j,
3156 u,
3157 v,
3158 y;
3159
cristy3ed852e2009-09-05 21:47:34 +00003160 /*
3161 Initialize blur image attributes.
3162 */
3163 assert(image != (Image *) NULL);
3164 assert(image->signature == MagickSignature);
3165 if (image->debug != MagickFalse)
3166 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3167 assert(exception != (ExceptionInfo *) NULL);
3168 assert(exception->signature == MagickSignature);
3169 width=GetOptimalKernelWidth1D(radius,sigma);
3170 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
3171 if (kernel == (double *) NULL)
3172 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00003173 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00003174 i=0;
cristy47e00502009-12-17 19:19:57 +00003175 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00003176 {
cristy47e00502009-12-17 19:19:57 +00003177 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00003178 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
3179 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00003180 }
3181 if (image->debug != MagickFalse)
3182 {
3183 char
3184 format[MaxTextExtent],
3185 *message;
3186
cristy117ff172010-08-15 21:35:32 +00003187 register const double
3188 *k;
3189
cristybb503372010-05-27 20:51:26 +00003190 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003191 u,
3192 v;
3193
cristy3ed852e2009-09-05 21:47:34 +00003194 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00003195 " SelectiveBlurImage with %.20gx%.20g kernel:",(double) width,(double)
3196 width);
cristy3ed852e2009-09-05 21:47:34 +00003197 message=AcquireString("");
3198 k=kernel;
cristybb503372010-05-27 20:51:26 +00003199 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003200 {
3201 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00003202 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy3ed852e2009-09-05 21:47:34 +00003203 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00003204 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003205 {
cristyb51dff52011-05-19 16:55:47 +00003206 (void) FormatLocaleString(format,MaxTextExtent,"%+f ",*k++);
cristy3ed852e2009-09-05 21:47:34 +00003207 (void) ConcatenateString(&message,format);
3208 }
3209 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
3210 }
3211 message=DestroyString(message);
3212 }
cristy1e7aa312011-09-10 20:01:36 +00003213 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00003214 if (blur_image == (Image *) NULL)
3215 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00003216 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003217 {
cristy3ed852e2009-09-05 21:47:34 +00003218 blur_image=DestroyImage(blur_image);
3219 return((Image *) NULL);
3220 }
3221 /*
3222 Threshold blur image.
3223 */
3224 status=MagickTrue;
3225 progress=0;
cristyc8523c12011-09-13 00:02:53 +00003226 center=(ssize_t) (GetPixelChannels(image)*(image->columns+width)*(width/2L)+
3227 GetPixelChannels(image)*(width/2L));
cristy3ed852e2009-09-05 21:47:34 +00003228 image_view=AcquireCacheView(image);
3229 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003230#if defined(MAGICKCORE_OPENMP_SUPPORT)
3231 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003232#endif
cristybb503372010-05-27 20:51:26 +00003233 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003234 {
cristy4c08aed2011-07-01 19:47:50 +00003235 double
3236 contrast;
3237
cristy3ed852e2009-09-05 21:47:34 +00003238 MagickBooleanType
3239 sync;
3240
cristy4c08aed2011-07-01 19:47:50 +00003241 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003242 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003243
cristy4c08aed2011-07-01 19:47:50 +00003244 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003245 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003246
cristy117ff172010-08-15 21:35:32 +00003247 register ssize_t
3248 x;
3249
cristy3ed852e2009-09-05 21:47:34 +00003250 if (status == MagickFalse)
3251 continue;
cristy117ff172010-08-15 21:35:32 +00003252 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
3253 (width/2L),image->columns+width,width,exception);
cristy3ed852e2009-09-05 21:47:34 +00003254 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3255 exception);
cristy4c08aed2011-07-01 19:47:50 +00003256 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003257 {
3258 status=MagickFalse;
3259 continue;
3260 }
cristybb503372010-05-27 20:51:26 +00003261 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003262 {
cristybb503372010-05-27 20:51:26 +00003263 register ssize_t
cristy1e7aa312011-09-10 20:01:36 +00003264 i;
cristy3ed852e2009-09-05 21:47:34 +00003265
cristy1e7aa312011-09-10 20:01:36 +00003266 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3267 {
3268 MagickRealType
3269 alpha,
3270 gamma,
3271 intensity,
3272 pixel;
cristy117ff172010-08-15 21:35:32 +00003273
cristy1e7aa312011-09-10 20:01:36 +00003274 PixelChannel
3275 channel;
3276
3277 PixelTrait
3278 blur_traits,
3279 traits;
3280
3281 register const double
3282 *restrict k;
3283
3284 register const Quantum
3285 *restrict pixels;
3286
3287 register ssize_t
3288 u;
3289
3290 ssize_t
3291 v;
3292
cristye2a912b2011-12-05 20:02:07 +00003293 traits=GetPixelChannelMapTraits(image,i);
3294 channel=GetPixelChannelMapChannel(image,i);
cristy1e7aa312011-09-10 20:01:36 +00003295 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
3296 if ((traits == UndefinedPixelTrait) ||
3297 (blur_traits == UndefinedPixelTrait))
3298 continue;
3299 if ((blur_traits & CopyPixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003300 {
cristy0beccfa2011-09-25 20:47:53 +00003301 SetPixelChannel(blur_image,channel,p[center+i],q);
cristy1e7aa312011-09-10 20:01:36 +00003302 continue;
cristy3ed852e2009-09-05 21:47:34 +00003303 }
cristy1e7aa312011-09-10 20:01:36 +00003304 k=kernel;
3305 pixel=bias;
3306 pixels=p;
cristyc8523c12011-09-13 00:02:53 +00003307 intensity=(MagickRealType) GetPixelIntensity(image,p+center);
cristy1e7aa312011-09-10 20:01:36 +00003308 gamma=0.0;
3309 if ((blur_traits & BlendPixelTrait) == 0)
cristy3ed852e2009-09-05 21:47:34 +00003310 {
cristy1e7aa312011-09-10 20:01:36 +00003311 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003312 {
cristy1e7aa312011-09-10 20:01:36 +00003313 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003314 {
cristy1e7aa312011-09-10 20:01:36 +00003315 contrast=GetPixelIntensity(image,pixels)-intensity;
3316 if (fabs(contrast) < threshold)
3317 {
3318 pixel+=(*k)*pixels[i];
3319 gamma+=(*k);
3320 }
3321 k++;
3322 pixels+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003323 }
cristy1e7aa312011-09-10 20:01:36 +00003324 pixels+=image->columns*GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003325 }
cristy1e7aa312011-09-10 20:01:36 +00003326 if (fabs((double) gamma) < MagickEpsilon)
3327 {
cristy0beccfa2011-09-25 20:47:53 +00003328 SetPixelChannel(blur_image,channel,p[center+i],q);
cristy1e7aa312011-09-10 20:01:36 +00003329 continue;
3330 }
3331 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +00003332 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy1e7aa312011-09-10 20:01:36 +00003333 continue;
3334 }
3335 for (v=0; v < (ssize_t) width; v++)
3336 {
3337 for (u=0; u < (ssize_t) width; u++)
3338 {
3339 contrast=GetPixelIntensity(image,pixels)-intensity;
3340 if (fabs(contrast) < threshold)
3341 {
3342 alpha=(MagickRealType) (QuantumScale*
3343 GetPixelAlpha(image,pixels));
3344 pixel+=(*k)*alpha*pixels[i];
3345 gamma+=(*k)*alpha;
3346 }
3347 k++;
3348 pixels+=GetPixelChannels(image);
3349 }
3350 pixels+=image->columns*GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003351 }
cristy1e7aa312011-09-10 20:01:36 +00003352 if (fabs((double) gamma) < MagickEpsilon)
3353 {
cristy0beccfa2011-09-25 20:47:53 +00003354 SetPixelChannel(blur_image,channel,p[center+i],q);
cristy1e7aa312011-09-10 20:01:36 +00003355 continue;
3356 }
3357 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +00003358 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy1e7aa312011-09-10 20:01:36 +00003359 }
cristyed231572011-07-14 02:18:59 +00003360 p+=GetPixelChannels(image);
3361 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003362 }
3363 sync=SyncCacheViewAuthenticPixels(blur_view,exception);
3364 if (sync == MagickFalse)
3365 status=MagickFalse;
3366 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3367 {
3368 MagickBooleanType
3369 proceed;
3370
cristyb5d5f722009-11-04 03:03:49 +00003371#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00003372 #pragma omp critical (MagickCore_SelectiveBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00003373#endif
3374 proceed=SetImageProgress(image,SelectiveBlurImageTag,progress++,
3375 image->rows);
3376 if (proceed == MagickFalse)
3377 status=MagickFalse;
3378 }
3379 }
3380 blur_image->type=image->type;
3381 blur_view=DestroyCacheView(blur_view);
3382 image_view=DestroyCacheView(image_view);
3383 kernel=(double *) RelinquishMagickMemory(kernel);
3384 if (status == MagickFalse)
3385 blur_image=DestroyImage(blur_image);
3386 return(blur_image);
3387}
3388
3389/*
3390%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3391% %
3392% %
3393% %
3394% S h a d e I m a g e %
3395% %
3396% %
3397% %
3398%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3399%
3400% ShadeImage() shines a distant light on an image to create a
3401% three-dimensional effect. You control the positioning of the light with
3402% azimuth and elevation; azimuth is measured in degrees off the x axis
3403% and elevation is measured in pixels above the Z axis.
3404%
3405% The format of the ShadeImage method is:
3406%
3407% Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3408% const double azimuth,const double elevation,ExceptionInfo *exception)
3409%
3410% A description of each parameter follows:
3411%
3412% o image: the image.
3413%
3414% o gray: A value other than zero shades the intensity of each pixel.
3415%
3416% o azimuth, elevation: Define the light source direction.
3417%
3418% o exception: return any errors or warnings in this structure.
3419%
3420*/
3421MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3422 const double azimuth,const double elevation,ExceptionInfo *exception)
3423{
3424#define ShadeImageTag "Shade/Image"
3425
cristyc4c8d132010-01-07 01:58:38 +00003426 CacheView
3427 *image_view,
3428 *shade_view;
3429
cristy3ed852e2009-09-05 21:47:34 +00003430 Image
3431 *shade_image;
3432
cristy3ed852e2009-09-05 21:47:34 +00003433 MagickBooleanType
3434 status;
3435
cristybb503372010-05-27 20:51:26 +00003436 MagickOffsetType
3437 progress;
3438
cristy3ed852e2009-09-05 21:47:34 +00003439 PrimaryInfo
3440 light;
3441
cristybb503372010-05-27 20:51:26 +00003442 ssize_t
3443 y;
3444
cristy3ed852e2009-09-05 21:47:34 +00003445 /*
3446 Initialize shaded image attributes.
3447 */
3448 assert(image != (const Image *) NULL);
3449 assert(image->signature == MagickSignature);
3450 if (image->debug != MagickFalse)
3451 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3452 assert(exception != (ExceptionInfo *) NULL);
3453 assert(exception->signature == MagickSignature);
3454 shade_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
3455 if (shade_image == (Image *) NULL)
3456 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00003457 if (SetImageStorageClass(shade_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003458 {
cristy3ed852e2009-09-05 21:47:34 +00003459 shade_image=DestroyImage(shade_image);
3460 return((Image *) NULL);
3461 }
3462 /*
3463 Compute the light vector.
3464 */
3465 light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))*
3466 cos(DegreesToRadians(elevation));
3467 light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))*
3468 cos(DegreesToRadians(elevation));
3469 light.z=(double) QuantumRange*sin(DegreesToRadians(elevation));
3470 /*
3471 Shade image.
3472 */
3473 status=MagickTrue;
3474 progress=0;
3475 image_view=AcquireCacheView(image);
3476 shade_view=AcquireCacheView(shade_image);
cristyb5d5f722009-11-04 03:03:49 +00003477#if defined(MAGICKCORE_OPENMP_SUPPORT)
3478 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003479#endif
cristybb503372010-05-27 20:51:26 +00003480 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003481 {
3482 MagickRealType
3483 distance,
3484 normal_distance,
3485 shade;
3486
3487 PrimaryInfo
3488 normal;
3489
cristy4c08aed2011-07-01 19:47:50 +00003490 register const Quantum
cristy1e7aa312011-09-10 20:01:36 +00003491 *restrict center,
cristyc47d1f82009-11-26 01:44:43 +00003492 *restrict p,
cristy1e7aa312011-09-10 20:01:36 +00003493 *restrict post,
3494 *restrict pre;
cristy3ed852e2009-09-05 21:47:34 +00003495
cristy4c08aed2011-07-01 19:47:50 +00003496 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003497 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003498
cristy117ff172010-08-15 21:35:32 +00003499 register ssize_t
3500 x;
3501
cristy3ed852e2009-09-05 21:47:34 +00003502 if (status == MagickFalse)
3503 continue;
3504 p=GetCacheViewVirtualPixels(image_view,-1,y-1,image->columns+2,3,exception);
3505 q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1,
3506 exception);
cristy4c08aed2011-07-01 19:47:50 +00003507 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003508 {
3509 status=MagickFalse;
3510 continue;
3511 }
3512 /*
3513 Shade this row of pixels.
3514 */
3515 normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */
cristy1e7aa312011-09-10 20:01:36 +00003516 pre=p+GetPixelChannels(image);
3517 center=pre+(image->columns+2)*GetPixelChannels(image);
3518 post=center+(image->columns+2)*GetPixelChannels(image);
cristybb503372010-05-27 20:51:26 +00003519 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003520 {
cristy1e7aa312011-09-10 20:01:36 +00003521 register ssize_t
3522 i;
3523
cristy3ed852e2009-09-05 21:47:34 +00003524 /*
3525 Determine the surface normal and compute shading.
3526 */
cristy1e7aa312011-09-10 20:01:36 +00003527 normal.x=(double) (GetPixelIntensity(image,pre-GetPixelChannels(image))+
3528 GetPixelIntensity(image,center-GetPixelChannels(image))+
3529 GetPixelIntensity(image,post-GetPixelChannels(image))-
3530 GetPixelIntensity(image,pre+GetPixelChannels(image))-
3531 GetPixelIntensity(image,center+GetPixelChannels(image))-
3532 GetPixelIntensity(image,post+GetPixelChannels(image)));
3533 normal.y=(double) (GetPixelIntensity(image,post-GetPixelChannels(image))+
3534 GetPixelIntensity(image,post)+GetPixelIntensity(image,post+
3535 GetPixelChannels(image))-GetPixelIntensity(image,pre-
3536 GetPixelChannels(image))-GetPixelIntensity(image,pre)-
3537 GetPixelIntensity(image,pre+GetPixelChannels(image)));
cristy3ed852e2009-09-05 21:47:34 +00003538 if ((normal.x == 0.0) && (normal.y == 0.0))
3539 shade=light.z;
3540 else
3541 {
3542 shade=0.0;
3543 distance=normal.x*light.x+normal.y*light.y+normal.z*light.z;
3544 if (distance > MagickEpsilon)
3545 {
3546 normal_distance=
3547 normal.x*normal.x+normal.y*normal.y+normal.z*normal.z;
3548 if (normal_distance > (MagickEpsilon*MagickEpsilon))
3549 shade=distance/sqrt((double) normal_distance);
3550 }
3551 }
cristy1e7aa312011-09-10 20:01:36 +00003552 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3553 {
3554 PixelChannel
3555 channel;
3556
3557 PixelTrait
3558 shade_traits,
3559 traits;
3560
cristye2a912b2011-12-05 20:02:07 +00003561 traits=GetPixelChannelMapTraits(image,i);
3562 channel=GetPixelChannelMapChannel(image,i);
cristy1e7aa312011-09-10 20:01:36 +00003563 shade_traits=GetPixelChannelMapTraits(shade_image,channel);
3564 if ((traits == UndefinedPixelTrait) ||
3565 (shade_traits == UndefinedPixelTrait))
3566 continue;
3567 if ((shade_traits & CopyPixelTrait) != 0)
3568 {
cristy0beccfa2011-09-25 20:47:53 +00003569 SetPixelChannel(shade_image,channel,center[i],q);
cristy1e7aa312011-09-10 20:01:36 +00003570 continue;
3571 }
3572 if (gray != MagickFalse)
3573 {
cristy0beccfa2011-09-25 20:47:53 +00003574 SetPixelChannel(shade_image,channel,ClampToQuantum(shade),q);
cristy1e7aa312011-09-10 20:01:36 +00003575 continue;
3576 }
cristy0beccfa2011-09-25 20:47:53 +00003577 SetPixelChannel(shade_image,channel,ClampToQuantum(QuantumScale*shade*
3578 center[i]),q);
cristy1e7aa312011-09-10 20:01:36 +00003579 }
3580 pre+=GetPixelChannels(image);
3581 center+=GetPixelChannels(image);
3582 post+=GetPixelChannels(image);
cristyed231572011-07-14 02:18:59 +00003583 q+=GetPixelChannels(shade_image);
cristy3ed852e2009-09-05 21:47:34 +00003584 }
3585 if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
3586 status=MagickFalse;
3587 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3588 {
3589 MagickBooleanType
3590 proceed;
3591
cristyb5d5f722009-11-04 03:03:49 +00003592#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003593 #pragma omp critical (MagickCore_ShadeImage)
3594#endif
3595 proceed=SetImageProgress(image,ShadeImageTag,progress++,image->rows);
3596 if (proceed == MagickFalse)
3597 status=MagickFalse;
3598 }
3599 }
3600 shade_view=DestroyCacheView(shade_view);
3601 image_view=DestroyCacheView(image_view);
3602 if (status == MagickFalse)
3603 shade_image=DestroyImage(shade_image);
3604 return(shade_image);
3605}
3606
3607/*
3608%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3609% %
3610% %
3611% %
3612% S h a r p e n I m a g e %
3613% %
3614% %
3615% %
3616%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3617%
3618% SharpenImage() sharpens the image. We convolve the image with a Gaussian
3619% operator of the given radius and standard deviation (sigma). For
3620% reasonable results, radius should be larger than sigma. Use a radius of 0
3621% and SharpenImage() selects a suitable radius for you.
3622%
3623% Using a separable kernel would be faster, but the negative weights cancel
3624% out on the corners of the kernel producing often undesirable ringing in the
3625% filtered result; this can be avoided by using a 2D gaussian shaped image
3626% sharpening kernel instead.
3627%
3628% The format of the SharpenImage method is:
3629%
3630% Image *SharpenImage(const Image *image,const double radius,
cristy05c0c9a2011-09-05 23:16:13 +00003631% const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003632%
3633% A description of each parameter follows:
3634%
3635% o image: the image.
3636%
cristy3ed852e2009-09-05 21:47:34 +00003637% o radius: the radius of the Gaussian, in pixels, not counting the center
3638% pixel.
3639%
3640% o sigma: the standard deviation of the Laplacian, in pixels.
3641%
cristy05c0c9a2011-09-05 23:16:13 +00003642% o bias: bias.
3643%
cristy3ed852e2009-09-05 21:47:34 +00003644% o exception: return any errors or warnings in this structure.
3645%
3646*/
cristy3ed852e2009-09-05 21:47:34 +00003647MagickExport Image *SharpenImage(const Image *image,const double radius,
cristy05c0c9a2011-09-05 23:16:13 +00003648 const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003649{
cristy3ed852e2009-09-05 21:47:34 +00003650 double
cristy47e00502009-12-17 19:19:57 +00003651 normalize;
cristy3ed852e2009-09-05 21:47:34 +00003652
3653 Image
3654 *sharp_image;
3655
cristy41cbe682011-07-15 19:12:37 +00003656 KernelInfo
3657 *kernel_info;
3658
cristybb503372010-05-27 20:51:26 +00003659 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003660 i;
3661
cristybb503372010-05-27 20:51:26 +00003662 size_t
cristy3ed852e2009-09-05 21:47:34 +00003663 width;
3664
cristy117ff172010-08-15 21:35:32 +00003665 ssize_t
3666 j,
3667 u,
3668 v;
3669
cristy3ed852e2009-09-05 21:47:34 +00003670 assert(image != (const Image *) NULL);
3671 assert(image->signature == MagickSignature);
3672 if (image->debug != MagickFalse)
3673 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3674 assert(exception != (ExceptionInfo *) NULL);
3675 assert(exception->signature == MagickSignature);
3676 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00003677 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00003678 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003679 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00003680 (void) ResetMagickMemory(kernel_info,0,sizeof(*kernel_info));
3681 kernel_info->width=width;
3682 kernel_info->height=width;
anthony736a1602011-10-06 12:38:17 +00003683 kernel_info->bias=bias; /* FUTURE: user bias - non-sensical! */
cristy41cbe682011-07-15 19:12:37 +00003684 kernel_info->signature=MagickSignature;
cristy9f752c02011-09-14 19:44:03 +00003685 kernel_info->values=(double *) AcquireAlignedMemory(kernel_info->width,
cristy41cbe682011-07-15 19:12:37 +00003686 kernel_info->width*sizeof(*kernel_info->values));
3687 if (kernel_info->values == (double *) NULL)
3688 {
3689 kernel_info=DestroyKernelInfo(kernel_info);
3690 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3691 }
cristy3ed852e2009-09-05 21:47:34 +00003692 normalize=0.0;
cristy41cbe682011-07-15 19:12:37 +00003693 j=(ssize_t) kernel_info->width/2;
cristy47e00502009-12-17 19:19:57 +00003694 i=0;
3695 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00003696 {
cristy47e00502009-12-17 19:19:57 +00003697 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00003698 {
cristy41cbe682011-07-15 19:12:37 +00003699 kernel_info->values[i]=(double) (-exp(-((double) u*u+v*v)/(2.0*
3700 MagickSigma*MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
3701 normalize+=kernel_info->values[i];
cristy3ed852e2009-09-05 21:47:34 +00003702 i++;
3703 }
3704 }
cristy41cbe682011-07-15 19:12:37 +00003705 kernel_info->values[i/2]=(double) ((-2.0)*normalize);
cristy5e6be1e2011-07-16 01:23:39 +00003706 sharp_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00003707 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00003708 return(sharp_image);
3709}
3710
3711/*
3712%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3713% %
3714% %
3715% %
3716% S p r e a d I m a g e %
3717% %
3718% %
3719% %
3720%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3721%
3722% SpreadImage() is a special effects method that randomly displaces each
3723% pixel in a block defined by the radius parameter.
3724%
3725% The format of the SpreadImage method is:
3726%
3727% Image *SpreadImage(const Image *image,const double radius,
cristy5c4e2582011-09-11 19:21:03 +00003728% const PixelInterpolateMethod method,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003729%
3730% A description of each parameter follows:
3731%
3732% o image: the image.
3733%
cristy5c4e2582011-09-11 19:21:03 +00003734% o radius: choose a random pixel in a neighborhood of this extent.
3735%
3736% o method: the pixel interpolation method.
cristy3ed852e2009-09-05 21:47:34 +00003737%
3738% o exception: return any errors or warnings in this structure.
3739%
3740*/
3741MagickExport Image *SpreadImage(const Image *image,const double radius,
cristy5c4e2582011-09-11 19:21:03 +00003742 const PixelInterpolateMethod method,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003743{
3744#define SpreadImageTag "Spread/Image"
3745
cristyfa112112010-01-04 17:48:07 +00003746 CacheView
cristy9f7e7cb2011-03-26 00:49:57 +00003747 *image_view,
3748 *spread_view;
cristyfa112112010-01-04 17:48:07 +00003749
cristy3ed852e2009-09-05 21:47:34 +00003750 Image
3751 *spread_image;
3752
cristy3ed852e2009-09-05 21:47:34 +00003753 MagickBooleanType
3754 status;
3755
cristybb503372010-05-27 20:51:26 +00003756 MagickOffsetType
3757 progress;
3758
cristy3ed852e2009-09-05 21:47:34 +00003759 RandomInfo
cristyfa112112010-01-04 17:48:07 +00003760 **restrict random_info;
cristy3ed852e2009-09-05 21:47:34 +00003761
cristybb503372010-05-27 20:51:26 +00003762 size_t
cristy3ed852e2009-09-05 21:47:34 +00003763 width;
3764
cristybb503372010-05-27 20:51:26 +00003765 ssize_t
3766 y;
3767
cristy3ed852e2009-09-05 21:47:34 +00003768 /*
3769 Initialize spread image attributes.
3770 */
3771 assert(image != (Image *) NULL);
3772 assert(image->signature == MagickSignature);
3773 if (image->debug != MagickFalse)
3774 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3775 assert(exception != (ExceptionInfo *) NULL);
3776 assert(exception->signature == MagickSignature);
3777 spread_image=CloneImage(image,image->columns,image->rows,MagickTrue,
3778 exception);
3779 if (spread_image == (Image *) NULL)
3780 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00003781 if (SetImageStorageClass(spread_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003782 {
cristy3ed852e2009-09-05 21:47:34 +00003783 spread_image=DestroyImage(spread_image);
3784 return((Image *) NULL);
3785 }
3786 /*
3787 Spread image.
3788 */
3789 status=MagickTrue;
3790 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00003791 width=GetOptimalKernelWidth1D(radius,0.5);
cristy3ed852e2009-09-05 21:47:34 +00003792 random_info=AcquireRandomInfoThreadSet();
cristy9f7e7cb2011-03-26 00:49:57 +00003793 image_view=AcquireCacheView(image);
3794 spread_view=AcquireCacheView(spread_image);
cristyb557a152011-02-22 12:14:30 +00003795#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00003796 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00003797#endif
cristybe82ad52011-09-06 01:17:20 +00003798 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003799 {
cristy5c9e6f22010-09-17 17:31:01 +00003800 const int
3801 id = GetOpenMPThreadId();
cristy6ebe97c2010-07-03 01:17:28 +00003802
cristybe82ad52011-09-06 01:17:20 +00003803 register const Quantum
3804 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003805
cristy4c08aed2011-07-01 19:47:50 +00003806 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003807 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003808
cristy117ff172010-08-15 21:35:32 +00003809 register ssize_t
3810 x;
3811
cristy3ed852e2009-09-05 21:47:34 +00003812 if (status == MagickFalse)
3813 continue;
cristybe82ad52011-09-06 01:17:20 +00003814 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy9f7e7cb2011-03-26 00:49:57 +00003815 q=QueueCacheViewAuthenticPixels(spread_view,0,y,spread_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +00003816 exception);
cristybe82ad52011-09-06 01:17:20 +00003817 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003818 {
3819 status=MagickFalse;
3820 continue;
3821 }
cristybe82ad52011-09-06 01:17:20 +00003822 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003823 {
cristybe82ad52011-09-06 01:17:20 +00003824 PointInfo
3825 point;
3826
cristybe82ad52011-09-06 01:17:20 +00003827 point.x=GetPseudoRandomValue(random_info[id]);
3828 point.y=GetPseudoRandomValue(random_info[id]);
cristy5c4e2582011-09-11 19:21:03 +00003829 status=InterpolatePixelChannels(image,image_view,spread_image,method,
3830 (double) x+width*point.x-0.5,(double) y+width*point.y-0.5,q,exception);
cristyed231572011-07-14 02:18:59 +00003831 q+=GetPixelChannels(spread_image);
cristy3ed852e2009-09-05 21:47:34 +00003832 }
cristy9f7e7cb2011-03-26 00:49:57 +00003833 if (SyncCacheViewAuthenticPixels(spread_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003834 status=MagickFalse;
3835 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3836 {
3837 MagickBooleanType
3838 proceed;
3839
cristyb557a152011-02-22 12:14:30 +00003840#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003841 #pragma omp critical (MagickCore_SpreadImage)
3842#endif
3843 proceed=SetImageProgress(image,SpreadImageTag,progress++,image->rows);
3844 if (proceed == MagickFalse)
3845 status=MagickFalse;
3846 }
3847 }
cristy9f7e7cb2011-03-26 00:49:57 +00003848 spread_view=DestroyCacheView(spread_view);
cristy3ed852e2009-09-05 21:47:34 +00003849 image_view=DestroyCacheView(image_view);
3850 random_info=DestroyRandomInfoThreadSet(random_info);
cristy3ed852e2009-09-05 21:47:34 +00003851 return(spread_image);
3852}
3853
3854/*
3855%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3856% %
3857% %
3858% %
cristy0834d642011-03-18 18:26:08 +00003859% S t a t i s t i c I m a g e %
3860% %
3861% %
3862% %
3863%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3864%
3865% StatisticImage() makes each pixel the min / max / median / mode / etc. of
cristy8d752042011-03-19 01:00:36 +00003866% the neighborhood of the specified width and height.
cristy0834d642011-03-18 18:26:08 +00003867%
3868% The format of the StatisticImage method is:
3869%
3870% Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00003871% const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00003872%
3873% A description of each parameter follows:
3874%
3875% o image: the image.
3876%
cristy0834d642011-03-18 18:26:08 +00003877% o type: the statistic type (median, mode, etc.).
3878%
cristy95c38342011-03-18 22:39:51 +00003879% o width: the width of the pixel neighborhood.
3880%
3881% o height: the height of the pixel neighborhood.
cristy0834d642011-03-18 18:26:08 +00003882%
3883% o exception: return any errors or warnings in this structure.
3884%
3885*/
3886
cristyf36cbcb2011-09-07 13:28:22 +00003887typedef struct _SkipNode
cristy733678d2011-03-18 21:29:28 +00003888{
3889 size_t
3890 next[9],
3891 count,
3892 signature;
cristyf36cbcb2011-09-07 13:28:22 +00003893} SkipNode;
cristy733678d2011-03-18 21:29:28 +00003894
3895typedef struct _SkipList
3896{
3897 ssize_t
3898 level;
3899
cristyf36cbcb2011-09-07 13:28:22 +00003900 SkipNode
cristy733678d2011-03-18 21:29:28 +00003901 *nodes;
3902} SkipList;
3903
3904typedef struct _PixelList
3905{
3906 size_t
cristy6fc86bb2011-03-18 23:45:16 +00003907 length,
cristyf36cbcb2011-09-07 13:28:22 +00003908 seed;
cristy733678d2011-03-18 21:29:28 +00003909
3910 SkipList
cristyf36cbcb2011-09-07 13:28:22 +00003911 skip_list;
3912
3913 size_t
3914 signature;
cristy733678d2011-03-18 21:29:28 +00003915} PixelList;
3916
3917static PixelList *DestroyPixelList(PixelList *pixel_list)
3918{
cristy733678d2011-03-18 21:29:28 +00003919 if (pixel_list == (PixelList *) NULL)
3920 return((PixelList *) NULL);
cristyf36cbcb2011-09-07 13:28:22 +00003921 if (pixel_list->skip_list.nodes != (SkipNode *) NULL)
3922 pixel_list->skip_list.nodes=(SkipNode *) RelinquishMagickMemory(
3923 pixel_list->skip_list.nodes);
cristy733678d2011-03-18 21:29:28 +00003924 pixel_list=(PixelList *) RelinquishMagickMemory(pixel_list);
3925 return(pixel_list);
3926}
3927
3928static PixelList **DestroyPixelListThreadSet(PixelList **pixel_list)
3929{
3930 register ssize_t
3931 i;
3932
3933 assert(pixel_list != (PixelList **) NULL);
3934 for (i=0; i < (ssize_t) GetOpenMPMaximumThreads(); i++)
3935 if (pixel_list[i] != (PixelList *) NULL)
3936 pixel_list[i]=DestroyPixelList(pixel_list[i]);
3937 pixel_list=(PixelList **) RelinquishMagickMemory(pixel_list);
3938 return(pixel_list);
3939}
3940
cristy6fc86bb2011-03-18 23:45:16 +00003941static PixelList *AcquirePixelList(const size_t width,const size_t height)
cristy733678d2011-03-18 21:29:28 +00003942{
3943 PixelList
3944 *pixel_list;
3945
cristy733678d2011-03-18 21:29:28 +00003946 pixel_list=(PixelList *) AcquireMagickMemory(sizeof(*pixel_list));
3947 if (pixel_list == (PixelList *) NULL)
3948 return(pixel_list);
3949 (void) ResetMagickMemory((void *) pixel_list,0,sizeof(*pixel_list));
cristy6fc86bb2011-03-18 23:45:16 +00003950 pixel_list->length=width*height;
cristyf36cbcb2011-09-07 13:28:22 +00003951 pixel_list->skip_list.nodes=(SkipNode *) AcquireQuantumMemory(65537UL,
3952 sizeof(*pixel_list->skip_list.nodes));
3953 if (pixel_list->skip_list.nodes == (SkipNode *) NULL)
3954 return(DestroyPixelList(pixel_list));
3955 (void) ResetMagickMemory(pixel_list->skip_list.nodes,0,65537UL*
3956 sizeof(*pixel_list->skip_list.nodes));
cristy733678d2011-03-18 21:29:28 +00003957 pixel_list->signature=MagickSignature;
3958 return(pixel_list);
3959}
3960
cristy6fc86bb2011-03-18 23:45:16 +00003961static PixelList **AcquirePixelListThreadSet(const size_t width,
3962 const size_t height)
cristy733678d2011-03-18 21:29:28 +00003963{
3964 PixelList
3965 **pixel_list;
3966
3967 register ssize_t
3968 i;
3969
3970 size_t
3971 number_threads;
3972
3973 number_threads=GetOpenMPMaximumThreads();
3974 pixel_list=(PixelList **) AcquireQuantumMemory(number_threads,
3975 sizeof(*pixel_list));
3976 if (pixel_list == (PixelList **) NULL)
3977 return((PixelList **) NULL);
3978 (void) ResetMagickMemory(pixel_list,0,number_threads*sizeof(*pixel_list));
3979 for (i=0; i < (ssize_t) number_threads; i++)
3980 {
cristy6fc86bb2011-03-18 23:45:16 +00003981 pixel_list[i]=AcquirePixelList(width,height);
cristy733678d2011-03-18 21:29:28 +00003982 if (pixel_list[i] == (PixelList *) NULL)
3983 return(DestroyPixelListThreadSet(pixel_list));
3984 }
3985 return(pixel_list);
3986}
3987
cristyf36cbcb2011-09-07 13:28:22 +00003988static void AddNodePixelList(PixelList *pixel_list,const size_t color)
cristy733678d2011-03-18 21:29:28 +00003989{
3990 register SkipList
cristyf36cbcb2011-09-07 13:28:22 +00003991 *p;
cristy733678d2011-03-18 21:29:28 +00003992
3993 register ssize_t
3994 level;
3995
3996 size_t
3997 search,
3998 update[9];
3999
4000 /*
4001 Initialize the node.
4002 */
cristyf36cbcb2011-09-07 13:28:22 +00004003 p=(&pixel_list->skip_list);
4004 p->nodes[color].signature=pixel_list->signature;
4005 p->nodes[color].count=1;
cristy733678d2011-03-18 21:29:28 +00004006 /*
4007 Determine where it belongs in the list.
4008 */
4009 search=65536UL;
cristyf36cbcb2011-09-07 13:28:22 +00004010 for (level=p->level; level >= 0; level--)
cristy733678d2011-03-18 21:29:28 +00004011 {
cristyf36cbcb2011-09-07 13:28:22 +00004012 while (p->nodes[search].next[level] < color)
4013 search=p->nodes[search].next[level];
cristy733678d2011-03-18 21:29:28 +00004014 update[level]=search;
4015 }
4016 /*
4017 Generate a pseudo-random level for this node.
4018 */
4019 for (level=0; ; level++)
4020 {
4021 pixel_list->seed=(pixel_list->seed*42893621L)+1L;
4022 if ((pixel_list->seed & 0x300) != 0x300)
4023 break;
4024 }
4025 if (level > 8)
4026 level=8;
cristyf36cbcb2011-09-07 13:28:22 +00004027 if (level > (p->level+2))
4028 level=p->level+2;
cristy733678d2011-03-18 21:29:28 +00004029 /*
4030 If we're raising the list's level, link back to the root node.
4031 */
cristyf36cbcb2011-09-07 13:28:22 +00004032 while (level > p->level)
cristy733678d2011-03-18 21:29:28 +00004033 {
cristyf36cbcb2011-09-07 13:28:22 +00004034 p->level++;
4035 update[p->level]=65536UL;
cristy733678d2011-03-18 21:29:28 +00004036 }
4037 /*
4038 Link the node into the skip-list.
4039 */
4040 do
4041 {
cristyf36cbcb2011-09-07 13:28:22 +00004042 p->nodes[color].next[level]=p->nodes[update[level]].next[level];
4043 p->nodes[update[level]].next[level]=color;
cristy3cba8ca2011-03-19 01:29:12 +00004044 } while (level-- > 0);
cristy733678d2011-03-18 21:29:28 +00004045}
4046
cristy4ba31632011-11-26 16:03:02 +00004047static inline void GetMaximumPixelList(PixelList *pixel_list,Quantum *pixel)
cristy6fc86bb2011-03-18 23:45:16 +00004048{
cristy6fc86bb2011-03-18 23:45:16 +00004049 register SkipList
cristyf36cbcb2011-09-07 13:28:22 +00004050 *p;
cristy6fc86bb2011-03-18 23:45:16 +00004051
4052 size_t
cristyd76c51e2011-03-26 00:21:26 +00004053 color,
4054 maximum;
cristy49f37242011-03-22 18:18:23 +00004055
4056 ssize_t
cristy6fc86bb2011-03-18 23:45:16 +00004057 count;
4058
cristy6fc86bb2011-03-18 23:45:16 +00004059 /*
4060 Find the maximum value for each of the color.
4061 */
cristyf36cbcb2011-09-07 13:28:22 +00004062 p=(&pixel_list->skip_list);
4063 color=65536L;
4064 count=0;
4065 maximum=p->nodes[color].next[0];
4066 do
cristy6fc86bb2011-03-18 23:45:16 +00004067 {
cristyf36cbcb2011-09-07 13:28:22 +00004068 color=p->nodes[color].next[0];
4069 if (color > maximum)
4070 maximum=color;
4071 count+=p->nodes[color].count;
4072 } while (count < (ssize_t) pixel_list->length);
cristy4ba31632011-11-26 16:03:02 +00004073 *pixel=ScaleShortToQuantum((unsigned short) maximum);
cristy49f37242011-03-22 18:18:23 +00004074}
4075
cristy4ba31632011-11-26 16:03:02 +00004076static inline void GetMeanPixelList(PixelList *pixel_list,Quantum *pixel)
cristy49f37242011-03-22 18:18:23 +00004077{
cristy80a99a32011-03-30 01:30:23 +00004078 MagickRealType
4079 sum;
4080
cristy49f37242011-03-22 18:18:23 +00004081 register SkipList
cristyf36cbcb2011-09-07 13:28:22 +00004082 *p;
cristy49f37242011-03-22 18:18:23 +00004083
4084 size_t
cristy80a99a32011-03-30 01:30:23 +00004085 color;
cristy49f37242011-03-22 18:18:23 +00004086
4087 ssize_t
4088 count;
4089
cristy49f37242011-03-22 18:18:23 +00004090 /*
4091 Find the mean value for each of the color.
4092 */
cristyf36cbcb2011-09-07 13:28:22 +00004093 p=(&pixel_list->skip_list);
4094 color=65536L;
4095 count=0;
4096 sum=0.0;
4097 do
cristy49f37242011-03-22 18:18:23 +00004098 {
cristyf36cbcb2011-09-07 13:28:22 +00004099 color=p->nodes[color].next[0];
4100 sum+=(MagickRealType) p->nodes[color].count*color;
4101 count+=p->nodes[color].count;
4102 } while (count < (ssize_t) pixel_list->length);
4103 sum/=pixel_list->length;
cristy4ba31632011-11-26 16:03:02 +00004104 *pixel=ScaleShortToQuantum((unsigned short) sum);
cristy6fc86bb2011-03-18 23:45:16 +00004105}
4106
cristy4ba31632011-11-26 16:03:02 +00004107static inline void GetMedianPixelList(PixelList *pixel_list,Quantum *pixel)
cristy733678d2011-03-18 21:29:28 +00004108{
cristy733678d2011-03-18 21:29:28 +00004109 register SkipList
cristyf36cbcb2011-09-07 13:28:22 +00004110 *p;
cristy733678d2011-03-18 21:29:28 +00004111
4112 size_t
cristy49f37242011-03-22 18:18:23 +00004113 color;
4114
4115 ssize_t
cristy733678d2011-03-18 21:29:28 +00004116 count;
4117
cristy733678d2011-03-18 21:29:28 +00004118 /*
4119 Find the median value for each of the color.
4120 */
cristyf36cbcb2011-09-07 13:28:22 +00004121 p=(&pixel_list->skip_list);
4122 color=65536L;
4123 count=0;
4124 do
cristy733678d2011-03-18 21:29:28 +00004125 {
cristyf36cbcb2011-09-07 13:28:22 +00004126 color=p->nodes[color].next[0];
4127 count+=p->nodes[color].count;
4128 } while (count <= (ssize_t) (pixel_list->length >> 1));
cristy4ba31632011-11-26 16:03:02 +00004129 *pixel=ScaleShortToQuantum((unsigned short) color);
cristy6fc86bb2011-03-18 23:45:16 +00004130}
4131
cristy4ba31632011-11-26 16:03:02 +00004132static inline void GetMinimumPixelList(PixelList *pixel_list,Quantum *pixel)
cristy6fc86bb2011-03-18 23:45:16 +00004133{
cristy6fc86bb2011-03-18 23:45:16 +00004134 register SkipList
cristyf36cbcb2011-09-07 13:28:22 +00004135 *p;
cristy6fc86bb2011-03-18 23:45:16 +00004136
4137 size_t
cristyd76c51e2011-03-26 00:21:26 +00004138 color,
4139 minimum;
cristy6fc86bb2011-03-18 23:45:16 +00004140
cristy49f37242011-03-22 18:18:23 +00004141 ssize_t
4142 count;
4143
cristy6fc86bb2011-03-18 23:45:16 +00004144 /*
4145 Find the minimum value for each of the color.
4146 */
cristyf36cbcb2011-09-07 13:28:22 +00004147 p=(&pixel_list->skip_list);
4148 count=0;
4149 color=65536UL;
4150 minimum=p->nodes[color].next[0];
4151 do
cristy6fc86bb2011-03-18 23:45:16 +00004152 {
cristyf36cbcb2011-09-07 13:28:22 +00004153 color=p->nodes[color].next[0];
4154 if (color < minimum)
4155 minimum=color;
4156 count+=p->nodes[color].count;
4157 } while (count < (ssize_t) pixel_list->length);
cristy4ba31632011-11-26 16:03:02 +00004158 *pixel=ScaleShortToQuantum((unsigned short) minimum);
cristy733678d2011-03-18 21:29:28 +00004159}
4160
cristy4ba31632011-11-26 16:03:02 +00004161static inline void GetModePixelList(PixelList *pixel_list,Quantum *pixel)
cristy733678d2011-03-18 21:29:28 +00004162{
cristy733678d2011-03-18 21:29:28 +00004163 register SkipList
cristyf36cbcb2011-09-07 13:28:22 +00004164 *p;
cristy733678d2011-03-18 21:29:28 +00004165
4166 size_t
4167 color,
cristy733678d2011-03-18 21:29:28 +00004168 max_count,
cristy6fc86bb2011-03-18 23:45:16 +00004169 mode;
cristy733678d2011-03-18 21:29:28 +00004170
cristy49f37242011-03-22 18:18:23 +00004171 ssize_t
4172 count;
4173
cristy733678d2011-03-18 21:29:28 +00004174 /*
glennrp30d2dc62011-06-25 03:17:16 +00004175 Make each pixel the 'predominant color' of the specified neighborhood.
cristy733678d2011-03-18 21:29:28 +00004176 */
cristyf36cbcb2011-09-07 13:28:22 +00004177 p=(&pixel_list->skip_list);
4178 color=65536L;
4179 mode=color;
4180 max_count=p->nodes[mode].count;
4181 count=0;
4182 do
cristy733678d2011-03-18 21:29:28 +00004183 {
cristyf36cbcb2011-09-07 13:28:22 +00004184 color=p->nodes[color].next[0];
4185 if (p->nodes[color].count > max_count)
4186 {
4187 mode=color;
4188 max_count=p->nodes[mode].count;
4189 }
4190 count+=p->nodes[color].count;
4191 } while (count < (ssize_t) pixel_list->length);
cristy4ba31632011-11-26 16:03:02 +00004192 *pixel=ScaleShortToQuantum((unsigned short) mode);
cristy733678d2011-03-18 21:29:28 +00004193}
4194
cristy4ba31632011-11-26 16:03:02 +00004195static inline void GetNonpeakPixelList(PixelList *pixel_list,Quantum *pixel)
cristy733678d2011-03-18 21:29:28 +00004196{
cristy733678d2011-03-18 21:29:28 +00004197 register SkipList
cristyf36cbcb2011-09-07 13:28:22 +00004198 *p;
cristy733678d2011-03-18 21:29:28 +00004199
4200 size_t
cristy733678d2011-03-18 21:29:28 +00004201 color,
cristy733678d2011-03-18 21:29:28 +00004202 next,
4203 previous;
4204
cristy49f37242011-03-22 18:18:23 +00004205 ssize_t
4206 count;
4207
cristy733678d2011-03-18 21:29:28 +00004208 /*
cristy49f37242011-03-22 18:18:23 +00004209 Finds the non peak value for each of the colors.
cristy733678d2011-03-18 21:29:28 +00004210 */
cristyf36cbcb2011-09-07 13:28:22 +00004211 p=(&pixel_list->skip_list);
4212 color=65536L;
4213 next=p->nodes[color].next[0];
4214 count=0;
4215 do
cristy733678d2011-03-18 21:29:28 +00004216 {
cristyf36cbcb2011-09-07 13:28:22 +00004217 previous=color;
4218 color=next;
4219 next=p->nodes[color].next[0];
4220 count+=p->nodes[color].count;
4221 } while (count <= (ssize_t) (pixel_list->length >> 1));
4222 if ((previous == 65536UL) && (next != 65536UL))
4223 color=next;
4224 else
4225 if ((previous != 65536UL) && (next == 65536UL))
4226 color=previous;
cristy4ba31632011-11-26 16:03:02 +00004227 *pixel=ScaleShortToQuantum((unsigned short) color);
cristy733678d2011-03-18 21:29:28 +00004228}
4229
cristy4ba31632011-11-26 16:03:02 +00004230static inline void GetStandardDeviationPixelList(PixelList *pixel_list,
4231 Quantum *pixel)
cristy9a68cbb2011-03-29 00:51:23 +00004232{
cristy80a99a32011-03-30 01:30:23 +00004233 MagickRealType
4234 sum,
4235 sum_squared;
4236
cristy9a68cbb2011-03-29 00:51:23 +00004237 register SkipList
cristyf36cbcb2011-09-07 13:28:22 +00004238 *p;
cristy9a68cbb2011-03-29 00:51:23 +00004239
4240 size_t
cristy80a99a32011-03-30 01:30:23 +00004241 color;
cristy9a68cbb2011-03-29 00:51:23 +00004242
4243 ssize_t
4244 count;
4245
cristy9a68cbb2011-03-29 00:51:23 +00004246 /*
cristy80a99a32011-03-30 01:30:23 +00004247 Find the standard-deviation value for each of the color.
cristy9a68cbb2011-03-29 00:51:23 +00004248 */
cristyf36cbcb2011-09-07 13:28:22 +00004249 p=(&pixel_list->skip_list);
4250 color=65536L;
4251 count=0;
4252 sum=0.0;
4253 sum_squared=0.0;
4254 do
cristy9a68cbb2011-03-29 00:51:23 +00004255 {
cristyf36cbcb2011-09-07 13:28:22 +00004256 register ssize_t
4257 i;
cristy80a99a32011-03-30 01:30:23 +00004258
cristyf36cbcb2011-09-07 13:28:22 +00004259 color=p->nodes[color].next[0];
4260 sum+=(MagickRealType) p->nodes[color].count*color;
4261 for (i=0; i < (ssize_t) p->nodes[color].count; i++)
4262 sum_squared+=((MagickRealType) color)*((MagickRealType) color);
4263 count+=p->nodes[color].count;
4264 } while (count < (ssize_t) pixel_list->length);
4265 sum/=pixel_list->length;
4266 sum_squared/=pixel_list->length;
cristy4ba31632011-11-26 16:03:02 +00004267 *pixel=ScaleShortToQuantum((unsigned short) sqrt(sum_squared-(sum*sum)));
cristy9a68cbb2011-03-29 00:51:23 +00004268}
4269
cristyf36cbcb2011-09-07 13:28:22 +00004270static inline void InsertPixelList(const Image *image,const Quantum pixel,
cristy4c08aed2011-07-01 19:47:50 +00004271 PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004272{
4273 size_t
4274 signature;
4275
4276 unsigned short
4277 index;
4278
cristyf36cbcb2011-09-07 13:28:22 +00004279 index=ScaleQuantumToShort(pixel);
4280 signature=pixel_list->skip_list.nodes[index].signature;
cristy733678d2011-03-18 21:29:28 +00004281 if (signature == pixel_list->signature)
cristyf36cbcb2011-09-07 13:28:22 +00004282 {
4283 pixel_list->skip_list.nodes[index].count++;
4284 return;
4285 }
4286 AddNodePixelList(pixel_list,index);
cristy733678d2011-03-18 21:29:28 +00004287}
4288
cristy80c99742011-04-04 14:46:39 +00004289static inline MagickRealType MagickAbsoluteValue(const MagickRealType x)
4290{
4291 if (x < 0)
4292 return(-x);
4293 return(x);
4294}
4295
cristyf36cbcb2011-09-07 13:28:22 +00004296static inline size_t MagickMax(const size_t x,const size_t y)
4297{
4298 if (x > y)
4299 return(x);
4300 return(y);
4301}
4302
cristy733678d2011-03-18 21:29:28 +00004303static void ResetPixelList(PixelList *pixel_list)
4304{
4305 int
4306 level;
4307
cristyf36cbcb2011-09-07 13:28:22 +00004308 register SkipNode
cristy733678d2011-03-18 21:29:28 +00004309 *root;
4310
4311 register SkipList
cristyf36cbcb2011-09-07 13:28:22 +00004312 *p;
cristy733678d2011-03-18 21:29:28 +00004313
4314 /*
4315 Reset the skip-list.
4316 */
cristyf36cbcb2011-09-07 13:28:22 +00004317 p=(&pixel_list->skip_list);
4318 root=p->nodes+65536UL;
4319 p->level=0;
4320 for (level=0; level < 9; level++)
4321 root->next[level]=65536UL;
cristy733678d2011-03-18 21:29:28 +00004322 pixel_list->seed=pixel_list->signature++;
4323}
4324
cristy0834d642011-03-18 18:26:08 +00004325MagickExport Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00004326 const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00004327{
cristy0834d642011-03-18 18:26:08 +00004328#define StatisticImageTag "Statistic/Image"
4329
4330 CacheView
4331 *image_view,
4332 *statistic_view;
4333
4334 Image
4335 *statistic_image;
4336
4337 MagickBooleanType
4338 status;
4339
4340 MagickOffsetType
4341 progress;
4342
4343 PixelList
4344 **restrict pixel_list;
4345
cristy0834d642011-03-18 18:26:08 +00004346 ssize_t
cristy075ff302011-09-07 01:51:24 +00004347 center,
cristy0834d642011-03-18 18:26:08 +00004348 y;
4349
4350 /*
4351 Initialize statistics image attributes.
4352 */
4353 assert(image != (Image *) NULL);
4354 assert(image->signature == MagickSignature);
4355 if (image->debug != MagickFalse)
4356 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4357 assert(exception != (ExceptionInfo *) NULL);
4358 assert(exception->signature == MagickSignature);
cristy0834d642011-03-18 18:26:08 +00004359 statistic_image=CloneImage(image,image->columns,image->rows,MagickTrue,
4360 exception);
4361 if (statistic_image == (Image *) NULL)
4362 return((Image *) NULL);
cristyf36cbcb2011-09-07 13:28:22 +00004363 status=SetImageStorageClass(statistic_image,DirectClass,exception);
4364 if (status == MagickFalse)
cristy0834d642011-03-18 18:26:08 +00004365 {
cristy0834d642011-03-18 18:26:08 +00004366 statistic_image=DestroyImage(statistic_image);
4367 return((Image *) NULL);
4368 }
cristyf36cbcb2011-09-07 13:28:22 +00004369 pixel_list=AcquirePixelListThreadSet(MagickMax(width,1),MagickMax(height,1));
cristy0834d642011-03-18 18:26:08 +00004370 if (pixel_list == (PixelList **) NULL)
4371 {
4372 statistic_image=DestroyImage(statistic_image);
4373 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
4374 }
4375 /*
cristy8d752042011-03-19 01:00:36 +00004376 Make each pixel the min / max / median / mode / etc. of the neighborhood.
cristy0834d642011-03-18 18:26:08 +00004377 */
cristyf36cbcb2011-09-07 13:28:22 +00004378 center=(ssize_t) GetPixelChannels(image)*(image->columns+MagickMax(width,1))*
4379 (MagickMax(height,1)/2L)+GetPixelChannels(image)*(MagickMax(width,1)/2L);
cristy0834d642011-03-18 18:26:08 +00004380 status=MagickTrue;
4381 progress=0;
4382 image_view=AcquireCacheView(image);
4383 statistic_view=AcquireCacheView(statistic_image);
4384#if defined(MAGICKCORE_OPENMP_SUPPORT)
4385 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
4386#endif
4387 for (y=0; y < (ssize_t) statistic_image->rows; y++)
4388 {
4389 const int
4390 id = GetOpenMPThreadId();
4391
cristy4c08aed2011-07-01 19:47:50 +00004392 register const Quantum
cristy0834d642011-03-18 18:26:08 +00004393 *restrict p;
4394
cristy4c08aed2011-07-01 19:47:50 +00004395 register Quantum
cristy0834d642011-03-18 18:26:08 +00004396 *restrict q;
4397
4398 register ssize_t
4399 x;
4400
4401 if (status == MagickFalse)
4402 continue;
cristyf36cbcb2011-09-07 13:28:22 +00004403 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) MagickMax(width,1)/2L),y-
4404 (ssize_t) (MagickMax(height,1)/2L),image->columns+MagickMax(width,1),
4405 MagickMax(height,1),exception);
cristy3cba8ca2011-03-19 01:29:12 +00004406 q=QueueCacheViewAuthenticPixels(statistic_view,0,y,statistic_image->columns, 1,exception);
cristy4c08aed2011-07-01 19:47:50 +00004407 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy0834d642011-03-18 18:26:08 +00004408 {
4409 status=MagickFalse;
4410 continue;
4411 }
cristy0834d642011-03-18 18:26:08 +00004412 for (x=0; x < (ssize_t) statistic_image->columns; x++)
4413 {
cristy0834d642011-03-18 18:26:08 +00004414 register ssize_t
cristy075ff302011-09-07 01:51:24 +00004415 i;
cristy0834d642011-03-18 18:26:08 +00004416
cristy075ff302011-09-07 01:51:24 +00004417 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy0834d642011-03-18 18:26:08 +00004418 {
cristy075ff302011-09-07 01:51:24 +00004419 PixelChannel
4420 channel;
4421
4422 PixelTrait
4423 statistic_traits,
4424 traits;
4425
cristyf36cbcb2011-09-07 13:28:22 +00004426 Quantum
4427 pixel;
4428
cristy075ff302011-09-07 01:51:24 +00004429 register const Quantum
4430 *restrict pixels;
4431
4432 register ssize_t
4433 u;
4434
4435 ssize_t
4436 v;
4437
cristye2a912b2011-12-05 20:02:07 +00004438 traits=GetPixelChannelMapTraits(image,i);
4439 channel=GetPixelChannelMapChannel(image,i);
cristy075ff302011-09-07 01:51:24 +00004440 statistic_traits=GetPixelChannelMapTraits(statistic_image,channel);
4441 if ((traits == UndefinedPixelTrait) ||
4442 (statistic_traits == UndefinedPixelTrait))
4443 continue;
4444 if ((statistic_traits & CopyPixelTrait) != 0)
4445 {
cristy0beccfa2011-09-25 20:47:53 +00004446 SetPixelChannel(statistic_image,channel,p[center+i],q);
cristy075ff302011-09-07 01:51:24 +00004447 continue;
4448 }
4449 pixels=p;
4450 ResetPixelList(pixel_list[id]);
cristyf36cbcb2011-09-07 13:28:22 +00004451 for (v=0; v < (ssize_t) MagickMax(height,1); v++)
cristy6fc86bb2011-03-18 23:45:16 +00004452 {
cristyf36cbcb2011-09-07 13:28:22 +00004453 for (u=0; u < (ssize_t) MagickMax(width,1); u++)
cristy075ff302011-09-07 01:51:24 +00004454 {
cristyf36cbcb2011-09-07 13:28:22 +00004455 InsertPixelList(image,pixels[i],pixel_list[id]);
cristy075ff302011-09-07 01:51:24 +00004456 pixels+=GetPixelChannels(image);
4457 }
4458 pixels+=image->columns*GetPixelChannels(image);
cristy6fc86bb2011-03-18 23:45:16 +00004459 }
cristy075ff302011-09-07 01:51:24 +00004460 switch (type)
cristy49f37242011-03-22 18:18:23 +00004461 {
cristy075ff302011-09-07 01:51:24 +00004462 case GradientStatistic:
4463 {
4464 MagickRealType
4465 maximum,
4466 minimum;
4467
cristy4ba31632011-11-26 16:03:02 +00004468 GetMinimumPixelList(pixel_list[id],&pixel);
4469 minimum=(MagickRealType) pixel;
4470 GetMaximumPixelList(pixel_list[id],&pixel);
4471 maximum=(MagickRealType) pixel;
cristyf36cbcb2011-09-07 13:28:22 +00004472 pixel=ClampToQuantum(MagickAbsoluteValue(maximum-minimum));
cristy075ff302011-09-07 01:51:24 +00004473 break;
4474 }
4475 case MaximumStatistic:
4476 {
cristy4ba31632011-11-26 16:03:02 +00004477 GetMaximumPixelList(pixel_list[id],&pixel);
cristy075ff302011-09-07 01:51:24 +00004478 break;
4479 }
4480 case MeanStatistic:
4481 {
cristy4ba31632011-11-26 16:03:02 +00004482 GetMeanPixelList(pixel_list[id],&pixel);
cristy075ff302011-09-07 01:51:24 +00004483 break;
4484 }
4485 case MedianStatistic:
4486 default:
4487 {
cristy4ba31632011-11-26 16:03:02 +00004488 GetMedianPixelList(pixel_list[id],&pixel);
cristy075ff302011-09-07 01:51:24 +00004489 break;
4490 }
4491 case MinimumStatistic:
4492 {
cristy4ba31632011-11-26 16:03:02 +00004493 GetMinimumPixelList(pixel_list[id],&pixel);
cristy075ff302011-09-07 01:51:24 +00004494 break;
4495 }
4496 case ModeStatistic:
4497 {
cristy4ba31632011-11-26 16:03:02 +00004498 GetModePixelList(pixel_list[id],&pixel);
cristy075ff302011-09-07 01:51:24 +00004499 break;
4500 }
4501 case NonpeakStatistic:
4502 {
cristy4ba31632011-11-26 16:03:02 +00004503 GetNonpeakPixelList(pixel_list[id],&pixel);
cristy075ff302011-09-07 01:51:24 +00004504 break;
4505 }
4506 case StandardDeviationStatistic:
4507 {
cristy4ba31632011-11-26 16:03:02 +00004508 GetStandardDeviationPixelList(pixel_list[id],&pixel);
cristy075ff302011-09-07 01:51:24 +00004509 break;
4510 }
cristy49f37242011-03-22 18:18:23 +00004511 }
cristy0beccfa2011-09-25 20:47:53 +00004512 SetPixelChannel(statistic_image,channel,pixel,q);
cristy0834d642011-03-18 18:26:08 +00004513 }
cristyed231572011-07-14 02:18:59 +00004514 p+=GetPixelChannels(image);
4515 q+=GetPixelChannels(statistic_image);
cristy0834d642011-03-18 18:26:08 +00004516 }
4517 if (SyncCacheViewAuthenticPixels(statistic_view,exception) == MagickFalse)
4518 status=MagickFalse;
4519 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4520 {
4521 MagickBooleanType
4522 proceed;
4523
4524#if defined(MAGICKCORE_OPENMP_SUPPORT)
4525 #pragma omp critical (MagickCore_StatisticImage)
4526#endif
4527 proceed=SetImageProgress(image,StatisticImageTag,progress++,
4528 image->rows);
4529 if (proceed == MagickFalse)
4530 status=MagickFalse;
4531 }
4532 }
4533 statistic_view=DestroyCacheView(statistic_view);
4534 image_view=DestroyCacheView(image_view);
4535 pixel_list=DestroyPixelListThreadSet(pixel_list);
4536 return(statistic_image);
4537}
4538
4539/*
4540%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4541% %
4542% %
4543% %
cristy3ed852e2009-09-05 21:47:34 +00004544% U n s h a r p M a s k I m a g e %
4545% %
4546% %
4547% %
4548%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4549%
4550% UnsharpMaskImage() sharpens one or more image channels. We convolve the
4551% image with a Gaussian operator of the given radius and standard deviation
4552% (sigma). For reasonable results, radius should be larger than sigma. Use a
4553% radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
4554%
4555% The format of the UnsharpMaskImage method is:
4556%
4557% Image *UnsharpMaskImage(const Image *image,const double radius,
4558% const double sigma,const double amount,const double threshold,
4559% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00004560%
4561% A description of each parameter follows:
4562%
4563% o image: the image.
4564%
cristy3ed852e2009-09-05 21:47:34 +00004565% o radius: the radius of the Gaussian, in pixels, not counting the center
4566% pixel.
4567%
4568% o sigma: the standard deviation of the Gaussian, in pixels.
4569%
4570% o amount: the percentage of the difference between the original and the
4571% blur image that is added back into the original.
4572%
4573% o threshold: the threshold in pixels needed to apply the diffence amount.
4574%
4575% o exception: return any errors or warnings in this structure.
4576%
4577*/
cristy31bb6272011-11-20 23:57:25 +00004578MagickExport Image *UnsharpMaskImage(const Image *image,const double radius,
4579 const double sigma,const double amount,const double threshold,
4580 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00004581{
4582#define SharpenImageTag "Sharpen/Image"
4583
cristyc4c8d132010-01-07 01:58:38 +00004584 CacheView
4585 *image_view,
4586 *unsharp_view;
4587
cristy3ed852e2009-09-05 21:47:34 +00004588 Image
4589 *unsharp_image;
4590
cristy3ed852e2009-09-05 21:47:34 +00004591 MagickBooleanType
4592 status;
4593
cristybb503372010-05-27 20:51:26 +00004594 MagickOffsetType
4595 progress;
4596
cristy3ed852e2009-09-05 21:47:34 +00004597 MagickRealType
4598 quantum_threshold;
4599
cristybb503372010-05-27 20:51:26 +00004600 ssize_t
4601 y;
4602
cristy3ed852e2009-09-05 21:47:34 +00004603 assert(image != (const Image *) NULL);
4604 assert(image->signature == MagickSignature);
4605 if (image->debug != MagickFalse)
4606 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4607 assert(exception != (ExceptionInfo *) NULL);
anthony736a1602011-10-06 12:38:17 +00004608
4609
4610 /* FUTURE: use of bias on sharpen is non-sensical */
cristy05c0c9a2011-09-05 23:16:13 +00004611 unsharp_image=BlurImage(image,radius,sigma,image->bias,exception);
anthony736a1602011-10-06 12:38:17 +00004612
cristy3ed852e2009-09-05 21:47:34 +00004613 if (unsharp_image == (Image *) NULL)
4614 return((Image *) NULL);
4615 quantum_threshold=(MagickRealType) QuantumRange*threshold;
4616 /*
4617 Unsharp-mask image.
4618 */
4619 status=MagickTrue;
4620 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00004621 image_view=AcquireCacheView(image);
4622 unsharp_view=AcquireCacheView(unsharp_image);
cristyb5d5f722009-11-04 03:03:49 +00004623#if defined(MAGICKCORE_OPENMP_SUPPORT)
4624 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004625#endif
cristybb503372010-05-27 20:51:26 +00004626 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00004627 {
cristy4c08aed2011-07-01 19:47:50 +00004628 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00004629 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00004630
cristy4c08aed2011-07-01 19:47:50 +00004631 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00004632 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004633
cristy117ff172010-08-15 21:35:32 +00004634 register ssize_t
4635 x;
4636
cristy3ed852e2009-09-05 21:47:34 +00004637 if (status == MagickFalse)
4638 continue;
4639 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
4640 q=GetCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
4641 exception);
cristy4c08aed2011-07-01 19:47:50 +00004642 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00004643 {
4644 status=MagickFalse;
4645 continue;
4646 }
cristybb503372010-05-27 20:51:26 +00004647 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004648 {
cristy7f3a0d12011-09-05 23:27:59 +00004649 register ssize_t
4650 i;
4651
4652 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4653 {
4654 MagickRealType
4655 pixel;
4656
4657 PixelChannel
4658 channel;
4659
4660 PixelTrait
4661 traits,
4662 unsharp_traits;
4663
cristye2a912b2011-12-05 20:02:07 +00004664 traits=GetPixelChannelMapTraits(image,i);
4665 channel=GetPixelChannelMapChannel(image,i);
cristy7f3a0d12011-09-05 23:27:59 +00004666 unsharp_traits=GetPixelChannelMapTraits(unsharp_image,channel);
4667 if ((traits == UndefinedPixelTrait) ||
4668 (unsharp_traits == UndefinedPixelTrait))
4669 continue;
4670 if ((unsharp_traits & CopyPixelTrait) != 0)
4671 {
cristy0beccfa2011-09-25 20:47:53 +00004672 SetPixelChannel(unsharp_image,channel,p[i],q);
cristy7f3a0d12011-09-05 23:27:59 +00004673 continue;
4674 }
cristy0beccfa2011-09-25 20:47:53 +00004675 pixel=p[i]-(MagickRealType) GetPixelChannel(unsharp_image,channel,q);
cristy7f3a0d12011-09-05 23:27:59 +00004676 if (fabs(2.0*pixel) < quantum_threshold)
4677 pixel=(MagickRealType) p[i];
4678 else
4679 pixel=(MagickRealType) p[i]+amount*pixel;
cristy0beccfa2011-09-25 20:47:53 +00004680 SetPixelChannel(unsharp_image,channel,ClampToQuantum(pixel),q);
cristy7f3a0d12011-09-05 23:27:59 +00004681 }
cristyed231572011-07-14 02:18:59 +00004682 p+=GetPixelChannels(image);
4683 q+=GetPixelChannels(unsharp_image);
cristy3ed852e2009-09-05 21:47:34 +00004684 }
4685 if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
4686 status=MagickFalse;
4687 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4688 {
4689 MagickBooleanType
4690 proceed;
4691
cristyb5d5f722009-11-04 03:03:49 +00004692#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00004693 #pragma omp critical (MagickCore_UnsharpMaskImage)
cristy3ed852e2009-09-05 21:47:34 +00004694#endif
4695 proceed=SetImageProgress(image,SharpenImageTag,progress++,image->rows);
4696 if (proceed == MagickFalse)
4697 status=MagickFalse;
4698 }
4699 }
4700 unsharp_image->type=image->type;
4701 unsharp_view=DestroyCacheView(unsharp_view);
4702 image_view=DestroyCacheView(image_view);
4703 if (status == MagickFalse)
4704 unsharp_image=DestroyImage(unsharp_image);
4705 return(unsharp_image);
4706}