blob: 7544803de48898fef7541bd5c8a8f369d8d0dc68 [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% %
cristy1454be72011-12-19 01:52:48 +000020% Copyright 1999-2012 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"
cristy99bd5232011-12-07 14:38:20 +000085#include "MagickCore/statistic.h"
cristy4c08aed2011-07-01 19:47:50 +000086#include "MagickCore/string_.h"
87#include "MagickCore/thread-private.h"
88#include "MagickCore/transform.h"
89#include "MagickCore/threshold.h"
cristy3ed852e2009-09-05 21:47:34 +000090
91/*
92%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
93% %
94% %
95% %
96% A d a p t i v e B l u r I m a g e %
97% %
98% %
99% %
100%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
101%
102% AdaptiveBlurImage() adaptively blurs the image by blurring less
103% intensely near image edges and more intensely far from edges. We blur the
104% image with a Gaussian operator of the given radius and standard deviation
105% (sigma). For reasonable results, radius should be larger than sigma. Use a
106% radius of 0 and AdaptiveBlurImage() selects a suitable radius for you.
107%
108% The format of the AdaptiveBlurImage method is:
109%
110% Image *AdaptiveBlurImage(const Image *image,const double radius,
cristy4c11c2b2011-09-05 20:17:07 +0000111% const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000112%
113% A description of each parameter follows:
114%
115% o image: the image.
116%
cristy3ed852e2009-09-05 21:47:34 +0000117% o radius: the radius of the Gaussian, in pixels, not counting the center
118% pixel.
119%
120% o sigma: the standard deviation of the Laplacian, in pixels.
121%
cristy4c11c2b2011-09-05 20:17:07 +0000122% o bias: the bias.
123%
cristy3ed852e2009-09-05 21:47:34 +0000124% o exception: return any errors or warnings in this structure.
125%
126*/
127
cristyf89cb1d2011-07-07 01:24:37 +0000128MagickExport MagickBooleanType AdaptiveLevelImage(Image *image,
cristy051718b2011-08-28 22:49:25 +0000129 const char *levels,ExceptionInfo *exception)
cristyf89cb1d2011-07-07 01:24:37 +0000130{
131 double
132 black_point,
133 gamma,
134 white_point;
135
136 GeometryInfo
137 geometry_info;
138
139 MagickBooleanType
140 status;
141
142 MagickStatusType
143 flags;
144
145 /*
146 Parse levels.
147 */
148 if (levels == (char *) NULL)
149 return(MagickFalse);
150 flags=ParseGeometry(levels,&geometry_info);
151 black_point=geometry_info.rho;
152 white_point=(double) QuantumRange;
153 if ((flags & SigmaValue) != 0)
154 white_point=geometry_info.sigma;
155 gamma=1.0;
156 if ((flags & XiValue) != 0)
157 gamma=geometry_info.xi;
158 if ((flags & PercentValue) != 0)
159 {
160 black_point*=(double) image->columns*image->rows/100.0;
161 white_point*=(double) image->columns*image->rows/100.0;
162 }
163 if ((flags & SigmaValue) == 0)
164 white_point=(double) QuantumRange-black_point;
165 if ((flags & AspectValue ) == 0)
cristy7c0a0a42011-08-23 17:57:25 +0000166 status=LevelImage(image,black_point,white_point,gamma,exception);
cristyf89cb1d2011-07-07 01:24:37 +0000167 else
cristy7c0a0a42011-08-23 17:57:25 +0000168 status=LevelizeImage(image,black_point,white_point,gamma,exception);
cristyf89cb1d2011-07-07 01:24:37 +0000169 return(status);
170}
171
cristy4282c702011-11-21 00:01:06 +0000172MagickExport Image *AdaptiveBlurImage(const Image *image,const double radius,
173 const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000174{
175#define AdaptiveBlurImageTag "Convolve/Image"
176#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
177
cristyc4c8d132010-01-07 01:58:38 +0000178 CacheView
179 *blur_view,
180 *edge_view,
181 *image_view;
182
cristy3ed852e2009-09-05 21:47:34 +0000183 double
cristy47e00502009-12-17 19:19:57 +0000184 **kernel,
185 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000186
187 Image
188 *blur_image,
189 *edge_image,
190 *gaussian_image;
191
cristy3ed852e2009-09-05 21:47:34 +0000192 MagickBooleanType
193 status;
194
cristybb503372010-05-27 20:51:26 +0000195 MagickOffsetType
196 progress;
197
cristybb503372010-05-27 20:51:26 +0000198 register ssize_t
cristy47e00502009-12-17 19:19:57 +0000199 i;
cristy3ed852e2009-09-05 21:47:34 +0000200
cristybb503372010-05-27 20:51:26 +0000201 size_t
cristy3ed852e2009-09-05 21:47:34 +0000202 width;
203
cristybb503372010-05-27 20:51:26 +0000204 ssize_t
205 j,
206 k,
207 u,
208 v,
209 y;
210
cristy3ed852e2009-09-05 21:47:34 +0000211 assert(image != (const Image *) NULL);
212 assert(image->signature == MagickSignature);
213 if (image->debug != MagickFalse)
214 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
215 assert(exception != (ExceptionInfo *) NULL);
216 assert(exception->signature == MagickSignature);
217 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
218 if (blur_image == (Image *) NULL)
219 return((Image *) NULL);
220 if (fabs(sigma) <= MagickEpsilon)
221 return(blur_image);
cristy574cc262011-08-05 01:23:58 +0000222 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000223 {
cristy3ed852e2009-09-05 21:47:34 +0000224 blur_image=DestroyImage(blur_image);
225 return((Image *) NULL);
226 }
227 /*
228 Edge detect the image brighness channel, level, blur, and level again.
229 */
cristy8ae632d2011-09-05 17:29:53 +0000230 edge_image=EdgeImage(image,radius,sigma,exception);
cristy3ed852e2009-09-05 21:47:34 +0000231 if (edge_image == (Image *) NULL)
232 {
233 blur_image=DestroyImage(blur_image);
234 return((Image *) NULL);
235 }
cristy051718b2011-08-28 22:49:25 +0000236 (void) AdaptiveLevelImage(edge_image,"20%,95%",exception);
cristy05c0c9a2011-09-05 23:16:13 +0000237 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,bias,exception);
cristy3ed852e2009-09-05 21:47:34 +0000238 if (gaussian_image != (Image *) NULL)
239 {
240 edge_image=DestroyImage(edge_image);
241 edge_image=gaussian_image;
242 }
cristy051718b2011-08-28 22:49:25 +0000243 (void) AdaptiveLevelImage(edge_image,"10%,95%",exception);
cristy3ed852e2009-09-05 21:47:34 +0000244 /*
245 Create a set of kernels from maximum (radius,sigma) to minimum.
246 */
247 width=GetOptimalKernelWidth2D(radius,sigma);
248 kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
249 if (kernel == (double **) NULL)
250 {
251 edge_image=DestroyImage(edge_image);
252 blur_image=DestroyImage(blur_image);
253 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
254 }
255 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
cristybb503372010-05-27 20:51:26 +0000256 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000257 {
258 kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)*
259 sizeof(**kernel));
260 if (kernel[i] == (double *) NULL)
261 break;
cristy47e00502009-12-17 19:19:57 +0000262 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000263 j=(ssize_t) (width-i)/2;
cristy47e00502009-12-17 19:19:57 +0000264 k=0;
265 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000266 {
cristy47e00502009-12-17 19:19:57 +0000267 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000268 {
cristy4205a3c2010-09-12 20:19:59 +0000269 kernel[i][k]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
270 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +0000271 normalize+=kernel[i][k];
272 k++;
cristy3ed852e2009-09-05 21:47:34 +0000273 }
274 }
cristy3ed852e2009-09-05 21:47:34 +0000275 if (fabs(normalize) <= MagickEpsilon)
276 normalize=1.0;
277 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000278 for (k=0; k < (j*j); k++)
279 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000280 }
cristybb503372010-05-27 20:51:26 +0000281 if (i < (ssize_t) width)
cristy3ed852e2009-09-05 21:47:34 +0000282 {
283 for (i-=2; i >= 0; i-=2)
284 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
285 kernel=(double **) RelinquishMagickMemory(kernel);
286 edge_image=DestroyImage(edge_image);
287 blur_image=DestroyImage(blur_image);
288 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
289 }
290 /*
291 Adaptively blur image.
292 */
293 status=MagickTrue;
294 progress=0;
cristy3ed852e2009-09-05 21:47:34 +0000295 image_view=AcquireCacheView(image);
296 edge_view=AcquireCacheView(edge_image);
297 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000298#if defined(MAGICKCORE_OPENMP_SUPPORT)
299 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000300#endif
cristybb503372010-05-27 20:51:26 +0000301 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000302 {
cristy4c08aed2011-07-01 19:47:50 +0000303 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000304 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000305
cristy4c08aed2011-07-01 19:47:50 +0000306 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000307 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000308
cristy117ff172010-08-15 21:35:32 +0000309 register ssize_t
310 x;
311
cristy3ed852e2009-09-05 21:47:34 +0000312 if (status == MagickFalse)
313 continue;
314 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
315 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
316 exception);
cristyacd2ed22011-08-30 01:44:23 +0000317 if ((r == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000318 {
319 status=MagickFalse;
320 continue;
321 }
cristybb503372010-05-27 20:51:26 +0000322 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000323 {
cristy4c11c2b2011-09-05 20:17:07 +0000324 register const Quantum
325 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000326
cristybb503372010-05-27 20:51:26 +0000327 register ssize_t
cristy4c11c2b2011-09-05 20:17:07 +0000328 i;
cristy3ed852e2009-09-05 21:47:34 +0000329
cristy4c11c2b2011-09-05 20:17:07 +0000330 ssize_t
331 center,
332 j;
333
334 j=(ssize_t) ceil((double) width*QuantumScale*
cristy4c08aed2011-07-01 19:47:50 +0000335 GetPixelIntensity(edge_image,r)-0.5);
cristy4c11c2b2011-09-05 20:17:07 +0000336 if (j < 0)
337 j=0;
cristy3ed852e2009-09-05 21:47:34 +0000338 else
cristy4c11c2b2011-09-05 20:17:07 +0000339 if (j > (ssize_t) width)
340 j=(ssize_t) width;
341 if ((j & 0x01) != 0)
342 j--;
343 p=GetCacheViewVirtualPixels(image_view,x-((ssize_t) (width-j)/2L),y-
344 (ssize_t) ((width-j)/2L),width-j,width-j,exception);
cristy4c08aed2011-07-01 19:47:50 +0000345 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000346 break;
cristy4c11c2b2011-09-05 20:17:07 +0000347 center=(ssize_t) GetPixelChannels(image)*(width-j)*
cristy075ff302011-09-07 01:51:24 +0000348 ((width-j)/2L)+GetPixelChannels(image)*((width-j)/2L);
cristy4c11c2b2011-09-05 20:17:07 +0000349 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy3ed852e2009-09-05 21:47:34 +0000350 {
cristy4c11c2b2011-09-05 20:17:07 +0000351 MagickRealType
352 alpha,
353 gamma,
354 pixel;
355
356 PixelChannel
357 channel;
358
359 PixelTrait
360 blur_traits,
361 traits;
362
363 register const double
364 *restrict k;
365
366 register const Quantum
367 *restrict pixels;
368
369 register ssize_t
370 u;
371
372 ssize_t
373 v;
374
cristye2a912b2011-12-05 20:02:07 +0000375 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +0000376 traits=GetPixelChannelMapTraits(image,channel);
cristy4c11c2b2011-09-05 20:17:07 +0000377 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
378 if ((traits == UndefinedPixelTrait) ||
379 (blur_traits == UndefinedPixelTrait))
380 continue;
381 if ((blur_traits & CopyPixelTrait) != 0)
382 {
cristy0beccfa2011-09-25 20:47:53 +0000383 SetPixelChannel(blur_image,channel,p[center+i],q);
cristy4c11c2b2011-09-05 20:17:07 +0000384 continue;
385 }
386 k=kernel[j];
387 pixels=p;
388 pixel=bias;
389 gamma=0.0;
390 if ((blur_traits & BlendPixelTrait) == 0)
391 {
392 /*
393 No alpha blending.
394 */
395 for (v=0; v < (ssize_t) (width-j); v++)
396 {
397 for (u=0; u < (ssize_t) (width-j); u++)
398 {
399 pixel+=(*k)*pixels[i];
400 gamma+=(*k);
401 k++;
402 pixels+=GetPixelChannels(image);
403 }
404 }
405 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +0000406 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy4c11c2b2011-09-05 20:17:07 +0000407 continue;
408 }
409 /*
410 Alpha blending.
411 */
412 for (v=0; v < (ssize_t) (width-j); v++)
cristy3ed852e2009-09-05 21:47:34 +0000413 {
cristy4c11c2b2011-09-05 20:17:07 +0000414 for (u=0; u < (ssize_t) (width-j); u++)
415 {
416 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,pixels));
417 pixel+=(*k)*alpha*pixels[i];
418 gamma+=(*k)*alpha;
419 k++;
420 pixels+=GetPixelChannels(image);
421 }
cristy3ed852e2009-09-05 21:47:34 +0000422 }
cristy4c11c2b2011-09-05 20:17:07 +0000423 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +0000424 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy3ed852e2009-09-05 21:47:34 +0000425 }
cristyed231572011-07-14 02:18:59 +0000426 q+=GetPixelChannels(blur_image);
427 r+=GetPixelChannels(edge_image);
cristy3ed852e2009-09-05 21:47:34 +0000428 }
429 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
430 status=MagickFalse;
431 if (image->progress_monitor != (MagickProgressMonitor) NULL)
432 {
433 MagickBooleanType
434 proceed;
435
cristyb5d5f722009-11-04 03:03:49 +0000436#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy8ae632d2011-09-05 17:29:53 +0000437 #pragma omp critical (MagickCore_AdaptiveBlurImage)
cristy3ed852e2009-09-05 21:47:34 +0000438#endif
439 proceed=SetImageProgress(image,AdaptiveBlurImageTag,progress++,
440 image->rows);
441 if (proceed == MagickFalse)
442 status=MagickFalse;
443 }
444 }
445 blur_image->type=image->type;
446 blur_view=DestroyCacheView(blur_view);
447 edge_view=DestroyCacheView(edge_view);
448 image_view=DestroyCacheView(image_view);
449 edge_image=DestroyImage(edge_image);
cristybb503372010-05-27 20:51:26 +0000450 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000451 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
452 kernel=(double **) RelinquishMagickMemory(kernel);
453 if (status == MagickFalse)
454 blur_image=DestroyImage(blur_image);
455 return(blur_image);
456}
457
458/*
459%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
460% %
461% %
462% %
463% A d a p t i v e S h a r p e n I m a g e %
464% %
465% %
466% %
467%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
468%
469% AdaptiveSharpenImage() adaptively sharpens the image by sharpening more
470% intensely near image edges and less intensely far from edges. We sharpen the
471% image with a Gaussian operator of the given radius and standard deviation
472% (sigma). For reasonable results, radius should be larger than sigma. Use a
473% radius of 0 and AdaptiveSharpenImage() selects a suitable radius for you.
474%
475% The format of the AdaptiveSharpenImage method is:
476%
477% Image *AdaptiveSharpenImage(const Image *image,const double radius,
cristy4c11c2b2011-09-05 20:17:07 +0000478% const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000479%
480% A description of each parameter follows:
481%
482% o image: the image.
483%
cristy3ed852e2009-09-05 21:47:34 +0000484% o radius: the radius of the Gaussian, in pixels, not counting the center
485% pixel.
486%
487% o sigma: the standard deviation of the Laplacian, in pixels.
488%
cristy4c11c2b2011-09-05 20:17:07 +0000489% o bias: the bias.
490%
cristy3ed852e2009-09-05 21:47:34 +0000491% o exception: return any errors or warnings in this structure.
492%
493*/
cristy3ed852e2009-09-05 21:47:34 +0000494MagickExport Image *AdaptiveSharpenImage(const Image *image,const double radius,
cristy4c11c2b2011-09-05 20:17:07 +0000495 const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000496{
cristy3ed852e2009-09-05 21:47:34 +0000497#define AdaptiveSharpenImageTag "Convolve/Image"
498#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
499
cristyc4c8d132010-01-07 01:58:38 +0000500 CacheView
501 *sharp_view,
502 *edge_view,
503 *image_view;
504
cristy3ed852e2009-09-05 21:47:34 +0000505 double
cristy47e00502009-12-17 19:19:57 +0000506 **kernel,
507 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000508
509 Image
510 *sharp_image,
511 *edge_image,
512 *gaussian_image;
513
cristy3ed852e2009-09-05 21:47:34 +0000514 MagickBooleanType
515 status;
516
cristybb503372010-05-27 20:51:26 +0000517 MagickOffsetType
518 progress;
519
cristybb503372010-05-27 20:51:26 +0000520 register ssize_t
cristy47e00502009-12-17 19:19:57 +0000521 i;
cristy3ed852e2009-09-05 21:47:34 +0000522
cristybb503372010-05-27 20:51:26 +0000523 size_t
cristy3ed852e2009-09-05 21:47:34 +0000524 width;
525
cristybb503372010-05-27 20:51:26 +0000526 ssize_t
527 j,
528 k,
529 u,
530 v,
531 y;
532
cristy3ed852e2009-09-05 21:47:34 +0000533 assert(image != (const Image *) NULL);
534 assert(image->signature == MagickSignature);
535 if (image->debug != MagickFalse)
536 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
537 assert(exception != (ExceptionInfo *) NULL);
538 assert(exception->signature == MagickSignature);
539 sharp_image=CloneImage(image,0,0,MagickTrue,exception);
540 if (sharp_image == (Image *) NULL)
541 return((Image *) NULL);
542 if (fabs(sigma) <= MagickEpsilon)
543 return(sharp_image);
cristy574cc262011-08-05 01:23:58 +0000544 if (SetImageStorageClass(sharp_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000545 {
cristy3ed852e2009-09-05 21:47:34 +0000546 sharp_image=DestroyImage(sharp_image);
547 return((Image *) NULL);
548 }
549 /*
550 Edge detect the image brighness channel, level, sharp, and level again.
551 */
cristy8ae632d2011-09-05 17:29:53 +0000552 edge_image=EdgeImage(image,radius,sigma,exception);
cristy3ed852e2009-09-05 21:47:34 +0000553 if (edge_image == (Image *) NULL)
554 {
555 sharp_image=DestroyImage(sharp_image);
556 return((Image *) NULL);
557 }
cristy051718b2011-08-28 22:49:25 +0000558 (void) AdaptiveLevelImage(edge_image,"20%,95%",exception);
cristy05c0c9a2011-09-05 23:16:13 +0000559 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,bias,exception);
cristy3ed852e2009-09-05 21:47:34 +0000560 if (gaussian_image != (Image *) NULL)
561 {
562 edge_image=DestroyImage(edge_image);
563 edge_image=gaussian_image;
564 }
cristy051718b2011-08-28 22:49:25 +0000565 (void) AdaptiveLevelImage(edge_image,"10%,95%",exception);
cristy3ed852e2009-09-05 21:47:34 +0000566 /*
567 Create a set of kernels from maximum (radius,sigma) to minimum.
568 */
569 width=GetOptimalKernelWidth2D(radius,sigma);
570 kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
571 if (kernel == (double **) NULL)
572 {
573 edge_image=DestroyImage(edge_image);
574 sharp_image=DestroyImage(sharp_image);
575 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
576 }
577 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
cristybb503372010-05-27 20:51:26 +0000578 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000579 {
580 kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)*
581 sizeof(**kernel));
582 if (kernel[i] == (double *) NULL)
583 break;
cristy47e00502009-12-17 19:19:57 +0000584 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000585 j=(ssize_t) (width-i)/2;
cristy47e00502009-12-17 19:19:57 +0000586 k=0;
587 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000588 {
cristy47e00502009-12-17 19:19:57 +0000589 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000590 {
cristy4205a3c2010-09-12 20:19:59 +0000591 kernel[i][k]=(double) (-exp(-((double) u*u+v*v)/(2.0*MagickSigma*
592 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +0000593 normalize+=kernel[i][k];
594 k++;
cristy3ed852e2009-09-05 21:47:34 +0000595 }
596 }
cristy3ed852e2009-09-05 21:47:34 +0000597 if (fabs(normalize) <= MagickEpsilon)
598 normalize=1.0;
599 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000600 for (k=0; k < (j*j); k++)
601 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000602 }
cristybb503372010-05-27 20:51:26 +0000603 if (i < (ssize_t) width)
cristy3ed852e2009-09-05 21:47:34 +0000604 {
605 for (i-=2; i >= 0; i-=2)
606 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
607 kernel=(double **) RelinquishMagickMemory(kernel);
608 edge_image=DestroyImage(edge_image);
609 sharp_image=DestroyImage(sharp_image);
610 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
611 }
612 /*
613 Adaptively sharpen image.
614 */
615 status=MagickTrue;
616 progress=0;
cristy3ed852e2009-09-05 21:47:34 +0000617 image_view=AcquireCacheView(image);
618 edge_view=AcquireCacheView(edge_image);
619 sharp_view=AcquireCacheView(sharp_image);
cristyb5d5f722009-11-04 03:03:49 +0000620#if defined(MAGICKCORE_OPENMP_SUPPORT)
621 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000622#endif
cristybb503372010-05-27 20:51:26 +0000623 for (y=0; y < (ssize_t) sharp_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000624 {
cristy4c08aed2011-07-01 19:47:50 +0000625 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000626 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000627
cristy4c08aed2011-07-01 19:47:50 +0000628 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000629 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000630
cristy117ff172010-08-15 21:35:32 +0000631 register ssize_t
632 x;
633
cristy3ed852e2009-09-05 21:47:34 +0000634 if (status == MagickFalse)
635 continue;
636 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
637 q=QueueCacheViewAuthenticPixels(sharp_view,0,y,sharp_image->columns,1,
638 exception);
cristy4c08aed2011-07-01 19:47:50 +0000639 if ((r == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000640 {
641 status=MagickFalse;
642 continue;
643 }
cristybb503372010-05-27 20:51:26 +0000644 for (x=0; x < (ssize_t) sharp_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000645 {
cristy4c11c2b2011-09-05 20:17:07 +0000646 register const Quantum
647 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000648
cristybb503372010-05-27 20:51:26 +0000649 register ssize_t
cristy4c11c2b2011-09-05 20:17:07 +0000650 i;
cristy3ed852e2009-09-05 21:47:34 +0000651
cristy4c11c2b2011-09-05 20:17:07 +0000652 ssize_t
653 center,
654 j;
655
656 j=(ssize_t) ceil((double) width*QuantumScale*
cristy4c08aed2011-07-01 19:47:50 +0000657 GetPixelIntensity(edge_image,r)-0.5);
cristy4c11c2b2011-09-05 20:17:07 +0000658 if (j < 0)
659 j=0;
cristy3ed852e2009-09-05 21:47:34 +0000660 else
cristy4c11c2b2011-09-05 20:17:07 +0000661 if (j > (ssize_t) width)
662 j=(ssize_t) width;
663 if ((j & 0x01) != 0)
664 j--;
665 p=GetCacheViewVirtualPixels(image_view,x-((ssize_t) (width-j)/2L),y-
666 (ssize_t) ((width-j)/2L),width-j,width-j,exception);
cristy4c08aed2011-07-01 19:47:50 +0000667 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000668 break;
cristy4c11c2b2011-09-05 20:17:07 +0000669 center=(ssize_t) GetPixelChannels(image)*(width-j)*
670 ((width-j)/2L)+GetPixelChannels(image)*((width-j)/2);
671 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy3ed852e2009-09-05 21:47:34 +0000672 {
cristy4c11c2b2011-09-05 20:17:07 +0000673 MagickRealType
674 alpha,
675 gamma,
676 pixel;
677
678 PixelChannel
679 channel;
680
681 PixelTrait
682 sharp_traits,
683 traits;
684
685 register const double
686 *restrict k;
687
688 register const Quantum
689 *restrict pixels;
690
691 register ssize_t
692 u;
693
694 ssize_t
695 v;
696
cristye2a912b2011-12-05 20:02:07 +0000697 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +0000698 traits=GetPixelChannelMapTraits(image,channel);
cristy4c11c2b2011-09-05 20:17:07 +0000699 sharp_traits=GetPixelChannelMapTraits(sharp_image,channel);
700 if ((traits == UndefinedPixelTrait) ||
701 (sharp_traits == UndefinedPixelTrait))
702 continue;
703 if ((sharp_traits & CopyPixelTrait) != 0)
704 {
cristy0beccfa2011-09-25 20:47:53 +0000705 SetPixelChannel(sharp_image,channel,p[center+i],q);
cristy4c11c2b2011-09-05 20:17:07 +0000706 continue;
707 }
708 k=kernel[j];
709 pixels=p;
710 pixel=bias;
711 gamma=0.0;
712 if ((sharp_traits & BlendPixelTrait) == 0)
713 {
714 /*
715 No alpha blending.
716 */
717 for (v=0; v < (ssize_t) (width-j); v++)
718 {
719 for (u=0; u < (ssize_t) (width-j); u++)
720 {
721 pixel+=(*k)*pixels[i];
722 gamma+=(*k);
723 k++;
724 pixels+=GetPixelChannels(image);
725 }
726 }
727 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +0000728 SetPixelChannel(sharp_image,channel,ClampToQuantum(gamma*pixel),q);
cristy4c11c2b2011-09-05 20:17:07 +0000729 continue;
730 }
731 /*
732 Alpha blending.
733 */
734 for (v=0; v < (ssize_t) (width-j); v++)
cristy3ed852e2009-09-05 21:47:34 +0000735 {
cristy4c11c2b2011-09-05 20:17:07 +0000736 for (u=0; u < (ssize_t) (width-j); u++)
737 {
738 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,pixels));
739 pixel+=(*k)*alpha*pixels[i];
740 gamma+=(*k)*alpha;
741 k++;
742 pixels+=GetPixelChannels(image);
743 }
cristy3ed852e2009-09-05 21:47:34 +0000744 }
cristy4c11c2b2011-09-05 20:17:07 +0000745 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +0000746 SetPixelChannel(sharp_image,channel,ClampToQuantum(gamma*pixel),q);
cristy3ed852e2009-09-05 21:47:34 +0000747 }
cristyed231572011-07-14 02:18:59 +0000748 q+=GetPixelChannels(sharp_image);
749 r+=GetPixelChannels(edge_image);
cristy3ed852e2009-09-05 21:47:34 +0000750 }
751 if (SyncCacheViewAuthenticPixels(sharp_view,exception) == MagickFalse)
752 status=MagickFalse;
753 if (image->progress_monitor != (MagickProgressMonitor) NULL)
754 {
755 MagickBooleanType
756 proceed;
757
cristyb5d5f722009-11-04 03:03:49 +0000758#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +0000759 #pragma omp critical (MagickCore_AdaptiveSharpenImage)
cristy3ed852e2009-09-05 21:47:34 +0000760#endif
761 proceed=SetImageProgress(image,AdaptiveSharpenImageTag,progress++,
762 image->rows);
763 if (proceed == MagickFalse)
764 status=MagickFalse;
765 }
766 }
767 sharp_image->type=image->type;
768 sharp_view=DestroyCacheView(sharp_view);
769 edge_view=DestroyCacheView(edge_view);
770 image_view=DestroyCacheView(image_view);
771 edge_image=DestroyImage(edge_image);
cristybb503372010-05-27 20:51:26 +0000772 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000773 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
774 kernel=(double **) RelinquishMagickMemory(kernel);
775 if (status == MagickFalse)
776 sharp_image=DestroyImage(sharp_image);
777 return(sharp_image);
778}
779
780/*
781%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
782% %
783% %
784% %
785% B l u r I m a g e %
786% %
787% %
788% %
789%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
790%
791% BlurImage() blurs an image. We convolve the image with a Gaussian operator
792% of the given radius and standard deviation (sigma). For reasonable results,
793% the radius should be larger than sigma. Use a radius of 0 and BlurImage()
794% selects a suitable radius for you.
795%
796% BlurImage() differs from GaussianBlurImage() in that it uses a separable
797% kernel which is faster but mathematically equivalent to the non-separable
798% kernel.
799%
800% The format of the BlurImage method is:
801%
802% Image *BlurImage(const Image *image,const double radius,
cristy05c0c9a2011-09-05 23:16:13 +0000803% const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000804%
805% A description of each parameter follows:
806%
807% o image: the image.
808%
cristy3ed852e2009-09-05 21:47:34 +0000809% o radius: the radius of the Gaussian, in pixels, not counting the center
810% pixel.
811%
812% o sigma: the standard deviation of the Gaussian, in pixels.
813%
cristy05c0c9a2011-09-05 23:16:13 +0000814% o bias: the bias.
815%
cristy3ed852e2009-09-05 21:47:34 +0000816% o exception: return any errors or warnings in this structure.
817%
818*/
819
cristybb503372010-05-27 20:51:26 +0000820static double *GetBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +0000821{
cristy3ed852e2009-09-05 21:47:34 +0000822 double
cristy47e00502009-12-17 19:19:57 +0000823 *kernel,
824 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000825
cristy117ff172010-08-15 21:35:32 +0000826 register ssize_t
827 i;
828
cristybb503372010-05-27 20:51:26 +0000829 ssize_t
cristy47e00502009-12-17 19:19:57 +0000830 j,
831 k;
cristy3ed852e2009-09-05 21:47:34 +0000832
cristy3ed852e2009-09-05 21:47:34 +0000833 /*
834 Generate a 1-D convolution kernel.
835 */
836 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
837 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
838 if (kernel == (double *) NULL)
839 return(0);
cristy3ed852e2009-09-05 21:47:34 +0000840 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000841 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +0000842 i=0;
843 for (k=(-j); k <= j; k++)
844 {
cristy4205a3c2010-09-12 20:19:59 +0000845 kernel[i]=(double) (exp(-((double) k*k)/(2.0*MagickSigma*MagickSigma))/
846 (MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +0000847 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +0000848 i++;
849 }
cristybb503372010-05-27 20:51:26 +0000850 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000851 kernel[i]/=normalize;
852 return(kernel);
853}
854
cristyf4ad9df2011-07-08 16:49:03 +0000855MagickExport Image *BlurImage(const Image *image,const double radius,
cristy05c0c9a2011-09-05 23:16:13 +0000856 const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000857{
858#define BlurImageTag "Blur/Image"
859
cristyc4c8d132010-01-07 01:58:38 +0000860 CacheView
861 *blur_view,
862 *image_view;
863
cristy3ed852e2009-09-05 21:47:34 +0000864 double
865 *kernel;
866
867 Image
868 *blur_image;
869
cristy3ed852e2009-09-05 21:47:34 +0000870 MagickBooleanType
871 status;
872
cristybb503372010-05-27 20:51:26 +0000873 MagickOffsetType
874 progress;
875
cristybb503372010-05-27 20:51:26 +0000876 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000877 i;
878
cristybb503372010-05-27 20:51:26 +0000879 size_t
cristy3ed852e2009-09-05 21:47:34 +0000880 width;
881
cristybb503372010-05-27 20:51:26 +0000882 ssize_t
cristyb41a1172011-09-06 00:55:14 +0000883 center,
cristybb503372010-05-27 20:51:26 +0000884 x,
885 y;
886
cristy3ed852e2009-09-05 21:47:34 +0000887 /*
888 Initialize blur image attributes.
889 */
890 assert(image != (Image *) NULL);
891 assert(image->signature == MagickSignature);
892 if (image->debug != MagickFalse)
893 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
894 assert(exception != (ExceptionInfo *) NULL);
895 assert(exception->signature == MagickSignature);
cristyd25c77e2011-09-06 00:10:24 +0000896 blur_image=CloneImage(image,0,0,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +0000897 if (blur_image == (Image *) NULL)
898 return((Image *) NULL);
899 if (fabs(sigma) <= MagickEpsilon)
900 return(blur_image);
cristy574cc262011-08-05 01:23:58 +0000901 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000902 {
cristy3ed852e2009-09-05 21:47:34 +0000903 blur_image=DestroyImage(blur_image);
904 return((Image *) NULL);
905 }
906 width=GetOptimalKernelWidth1D(radius,sigma);
907 kernel=GetBlurKernel(width,sigma);
908 if (kernel == (double *) NULL)
909 {
910 blur_image=DestroyImage(blur_image);
911 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
912 }
913 if (image->debug != MagickFalse)
914 {
915 char
916 format[MaxTextExtent],
917 *message;
918
919 register const double
920 *k;
921
922 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +0000923 " BlurImage with %.20g kernel:",(double) width);
cristy3ed852e2009-09-05 21:47:34 +0000924 message=AcquireString("");
925 k=kernel;
cristybb503372010-05-27 20:51:26 +0000926 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000927 {
928 *message='\0';
cristyb51dff52011-05-19 16:55:47 +0000929 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) i);
cristy3ed852e2009-09-05 21:47:34 +0000930 (void) ConcatenateString(&message,format);
cristyb51dff52011-05-19 16:55:47 +0000931 (void) FormatLocaleString(format,MaxTextExtent,"%g ",*k++);
cristy3ed852e2009-09-05 21:47:34 +0000932 (void) ConcatenateString(&message,format);
933 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
934 }
935 message=DestroyString(message);
936 }
937 /*
938 Blur rows.
939 */
940 status=MagickTrue;
941 progress=0;
cristyb41a1172011-09-06 00:55:14 +0000942 center=(ssize_t) GetPixelChannels(image)*(width/2L);
cristy3ed852e2009-09-05 21:47:34 +0000943 image_view=AcquireCacheView(image);
944 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000945#if defined(MAGICKCORE_OPENMP_SUPPORT)
946 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000947#endif
cristyb41a1172011-09-06 00:55:14 +0000948 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000949 {
cristy4c08aed2011-07-01 19:47:50 +0000950 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000951 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000952
cristy4c08aed2011-07-01 19:47:50 +0000953 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000954 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000955
cristy117ff172010-08-15 21:35:32 +0000956 register ssize_t
957 x;
958
cristy3ed852e2009-09-05 21:47:34 +0000959 if (status == MagickFalse)
960 continue;
cristy117ff172010-08-15 21:35:32 +0000961 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y,
962 image->columns+width,1,exception);
cristy3ed852e2009-09-05 21:47:34 +0000963 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
964 exception);
cristy4c08aed2011-07-01 19:47:50 +0000965 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000966 {
967 status=MagickFalse;
968 continue;
969 }
cristyb41a1172011-09-06 00:55:14 +0000970 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000971 {
cristybb503372010-05-27 20:51:26 +0000972 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000973 i;
974
cristyb41a1172011-09-06 00:55:14 +0000975 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
976 {
977 MagickRealType
978 alpha,
979 gamma,
980 pixel;
cristyd25c77e2011-09-06 00:10:24 +0000981
cristyb41a1172011-09-06 00:55:14 +0000982 PixelChannel
983 channel;
984
985 PixelTrait
986 blur_traits,
987 traits;
988
989 register const double
990 *restrict k;
991
992 register const Quantum
993 *restrict pixels;
994
995 register ssize_t
996 u;
997
cristye2a912b2011-12-05 20:02:07 +0000998 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +0000999 traits=GetPixelChannelMapTraits(image,channel);
cristyb41a1172011-09-06 00:55:14 +00001000 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
1001 if ((traits == UndefinedPixelTrait) ||
1002 (blur_traits == UndefinedPixelTrait))
1003 continue;
1004 if ((blur_traits & CopyPixelTrait) != 0)
cristyd25c77e2011-09-06 00:10:24 +00001005 {
cristy0beccfa2011-09-25 20:47:53 +00001006 SetPixelChannel(blur_image,channel,p[center+i],q);
cristyb41a1172011-09-06 00:55:14 +00001007 continue;
cristyd25c77e2011-09-06 00:10:24 +00001008 }
cristyb41a1172011-09-06 00:55:14 +00001009 k=kernel;
1010 pixels=p;
1011 pixel=0.0;
1012 if ((blur_traits & BlendPixelTrait) == 0)
1013 {
1014 /*
1015 No alpha blending.
1016 */
1017 for (u=0; u < (ssize_t) width; u++)
cristyd25c77e2011-09-06 00:10:24 +00001018 {
cristyb41a1172011-09-06 00:55:14 +00001019 pixel+=(*k)*pixels[i];
1020 k++;
1021 pixels+=GetPixelChannels(image);
cristyd25c77e2011-09-06 00:10:24 +00001022 }
cristy0beccfa2011-09-25 20:47:53 +00001023 SetPixelChannel(blur_image,channel,ClampToQuantum(pixel),q);
cristyb41a1172011-09-06 00:55:14 +00001024 continue;
1025 }
1026 /*
1027 Alpha blending.
1028 */
1029 gamma=0.0;
1030 for (u=0; u < (ssize_t) width; u++)
1031 {
1032 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,pixels));
1033 pixel+=(*k)*alpha*pixels[i];
1034 gamma+=(*k)*alpha;
1035 k++;
1036 pixels+=GetPixelChannels(image);
cristyd25c77e2011-09-06 00:10:24 +00001037 }
cristyb41a1172011-09-06 00:55:14 +00001038 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +00001039 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristyb41a1172011-09-06 00:55:14 +00001040 }
cristyed231572011-07-14 02:18:59 +00001041 p+=GetPixelChannels(image);
1042 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001043 }
1044 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1045 status=MagickFalse;
1046 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1047 {
1048 MagickBooleanType
1049 proceed;
1050
cristyb5d5f722009-11-04 03:03:49 +00001051#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00001052 #pragma omp critical (MagickCore_BlurImage)
cristy3ed852e2009-09-05 21:47:34 +00001053#endif
1054 proceed=SetImageProgress(image,BlurImageTag,progress++,blur_image->rows+
1055 blur_image->columns);
1056 if (proceed == MagickFalse)
1057 status=MagickFalse;
1058 }
1059 }
1060 blur_view=DestroyCacheView(blur_view);
1061 image_view=DestroyCacheView(image_view);
1062 /*
1063 Blur columns.
1064 */
1065 image_view=AcquireCacheView(blur_image);
1066 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00001067#if defined(MAGICKCORE_OPENMP_SUPPORT)
1068 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001069#endif
cristyb6dc4b72011-12-10 23:29:53 +00001070 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001071 {
cristy4c08aed2011-07-01 19:47:50 +00001072 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001073 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001074
cristy4c08aed2011-07-01 19:47:50 +00001075 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001076 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001077
cristy117ff172010-08-15 21:35:32 +00001078 register ssize_t
1079 y;
1080
cristy3ed852e2009-09-05 21:47:34 +00001081 if (status == MagickFalse)
1082 continue;
cristy117ff172010-08-15 21:35:32 +00001083 p=GetCacheViewVirtualPixels(image_view,x,-((ssize_t) width/2L),1,
cristyb6dc4b72011-12-10 23:29:53 +00001084 blur_image->rows+width,exception);
cristy3ed852e2009-09-05 21:47:34 +00001085 q=GetCacheViewAuthenticPixels(blur_view,x,0,1,blur_image->rows,exception);
cristy4c08aed2011-07-01 19:47:50 +00001086 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001087 {
1088 status=MagickFalse;
1089 continue;
1090 }
cristyb6dc4b72011-12-10 23:29:53 +00001091 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001092 {
cristybb503372010-05-27 20:51:26 +00001093 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001094 i;
1095
cristyb6dc4b72011-12-10 23:29:53 +00001096 for (i=0; i < (ssize_t) GetPixelChannels(blur_image); i++)
cristyb41a1172011-09-06 00:55:14 +00001097 {
1098 MagickRealType
1099 alpha,
1100 gamma,
1101 pixel;
cristyd25c77e2011-09-06 00:10:24 +00001102
cristyb41a1172011-09-06 00:55:14 +00001103 PixelChannel
1104 channel;
1105
1106 PixelTrait
1107 blur_traits,
1108 traits;
1109
1110 register const double
1111 *restrict k;
1112
1113 register const Quantum
1114 *restrict pixels;
1115
1116 register ssize_t
1117 u;
1118
cristye2a912b2011-12-05 20:02:07 +00001119 channel=GetPixelChannelMapChannel(blur_image,i);
cristyabace412011-12-11 15:56:53 +00001120 traits=GetPixelChannelMapTraits(blur_image,channel);
cristyb41a1172011-09-06 00:55:14 +00001121 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
1122 if ((traits == UndefinedPixelTrait) ||
1123 (blur_traits == UndefinedPixelTrait))
1124 continue;
1125 if ((blur_traits & CopyPixelTrait) != 0)
cristyd25c77e2011-09-06 00:10:24 +00001126 {
cristy0beccfa2011-09-25 20:47:53 +00001127 SetPixelChannel(blur_image,channel,p[center+i],q);
cristyb41a1172011-09-06 00:55:14 +00001128 continue;
cristyd25c77e2011-09-06 00:10:24 +00001129 }
cristyb41a1172011-09-06 00:55:14 +00001130 k=kernel;
1131 pixels=p;
1132 pixel=0.0;
1133 if ((blur_traits & BlendPixelTrait) == 0)
1134 {
1135 /*
1136 No alpha blending.
1137 */
1138 for (u=0; u < (ssize_t) width; u++)
cristyd25c77e2011-09-06 00:10:24 +00001139 {
cristyb41a1172011-09-06 00:55:14 +00001140 pixel+=(*k)*pixels[i];
1141 k++;
1142 pixels+=GetPixelChannels(blur_image);
cristyd25c77e2011-09-06 00:10:24 +00001143 }
cristy0beccfa2011-09-25 20:47:53 +00001144 SetPixelChannel(blur_image,channel,ClampToQuantum(pixel),q);
cristyb41a1172011-09-06 00:55:14 +00001145 continue;
1146 }
1147 /*
1148 Alpha blending.
1149 */
1150 gamma=0.0;
1151 for (u=0; u < (ssize_t) width; u++)
1152 {
cristyb6dc4b72011-12-10 23:29:53 +00001153 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(blur_image,
1154 pixels));
cristyb41a1172011-09-06 00:55:14 +00001155 pixel+=(*k)*alpha*pixels[i];
1156 gamma+=(*k)*alpha;
1157 k++;
1158 pixels+=GetPixelChannels(blur_image);
cristyd25c77e2011-09-06 00:10:24 +00001159 }
cristyb41a1172011-09-06 00:55:14 +00001160 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +00001161 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristyb41a1172011-09-06 00:55:14 +00001162 }
cristyd25c77e2011-09-06 00:10:24 +00001163 p+=GetPixelChannels(blur_image);
cristyed231572011-07-14 02:18:59 +00001164 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001165 }
1166 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1167 status=MagickFalse;
cristy4c08aed2011-07-01 19:47:50 +00001168 if (blur_image->progress_monitor != (MagickProgressMonitor) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001169 {
1170 MagickBooleanType
1171 proceed;
1172
cristyb5d5f722009-11-04 03:03:49 +00001173#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00001174 #pragma omp critical (MagickCore_BlurImage)
cristy3ed852e2009-09-05 21:47:34 +00001175#endif
cristy4c08aed2011-07-01 19:47:50 +00001176 proceed=SetImageProgress(blur_image,BlurImageTag,progress++,
1177 blur_image->rows+blur_image->columns);
cristy3ed852e2009-09-05 21:47:34 +00001178 if (proceed == MagickFalse)
1179 status=MagickFalse;
1180 }
1181 }
1182 blur_view=DestroyCacheView(blur_view);
1183 image_view=DestroyCacheView(image_view);
1184 kernel=(double *) RelinquishMagickMemory(kernel);
1185 if (status == MagickFalse)
1186 blur_image=DestroyImage(blur_image);
1187 blur_image->type=image->type;
1188 return(blur_image);
1189}
1190
1191/*
1192%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1193% %
1194% %
1195% %
cristyfccdab92009-11-30 16:43:57 +00001196% C o n v o l v e I m a g e %
1197% %
1198% %
1199% %
1200%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1201%
1202% ConvolveImage() applies a custom convolution kernel to the image.
1203%
1204% The format of the ConvolveImage method is:
1205%
cristy5e6be1e2011-07-16 01:23:39 +00001206% Image *ConvolveImage(const Image *image,const KernelInfo *kernel,
1207% ExceptionInfo *exception)
1208%
cristyfccdab92009-11-30 16:43:57 +00001209% A description of each parameter follows:
1210%
1211% o image: the image.
1212%
cristy5e6be1e2011-07-16 01:23:39 +00001213% o kernel: the filtering kernel.
cristyfccdab92009-11-30 16:43:57 +00001214%
1215% o exception: return any errors or warnings in this structure.
1216%
1217*/
cristy5e6be1e2011-07-16 01:23:39 +00001218MagickExport Image *ConvolveImage(const Image *image,
1219 const KernelInfo *kernel_info,ExceptionInfo *exception)
cristyfccdab92009-11-30 16:43:57 +00001220{
cristyfccdab92009-11-30 16:43:57 +00001221#define ConvolveImageTag "Convolve/Image"
1222
cristyc4c8d132010-01-07 01:58:38 +00001223 CacheView
1224 *convolve_view,
cristy105ba3c2011-07-18 02:28:38 +00001225 *image_view;
cristyc4c8d132010-01-07 01:58:38 +00001226
cristyfccdab92009-11-30 16:43:57 +00001227 Image
1228 *convolve_image;
1229
cristyfccdab92009-11-30 16:43:57 +00001230 MagickBooleanType
1231 status;
1232
cristybb503372010-05-27 20:51:26 +00001233 MagickOffsetType
1234 progress;
1235
cristybb503372010-05-27 20:51:26 +00001236 ssize_t
cristy574cc262011-08-05 01:23:58 +00001237 center,
cristybb503372010-05-27 20:51:26 +00001238 y;
1239
cristyfccdab92009-11-30 16:43:57 +00001240 /*
1241 Initialize convolve image attributes.
1242 */
1243 assert(image != (Image *) NULL);
1244 assert(image->signature == MagickSignature);
1245 if (image->debug != MagickFalse)
1246 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1247 assert(exception != (ExceptionInfo *) NULL);
1248 assert(exception->signature == MagickSignature);
cristy5e6be1e2011-07-16 01:23:39 +00001249 if ((kernel_info->width % 2) == 0)
cristyfccdab92009-11-30 16:43:57 +00001250 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
cristy08429172011-07-14 17:18:16 +00001251 convolve_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1252 exception);
cristyfccdab92009-11-30 16:43:57 +00001253 if (convolve_image == (Image *) NULL)
1254 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00001255 if (SetImageStorageClass(convolve_image,DirectClass,exception) == MagickFalse)
cristyfccdab92009-11-30 16:43:57 +00001256 {
cristyfccdab92009-11-30 16:43:57 +00001257 convolve_image=DestroyImage(convolve_image);
1258 return((Image *) NULL);
1259 }
1260 if (image->debug != MagickFalse)
1261 {
1262 char
1263 format[MaxTextExtent],
1264 *message;
1265
cristya96f2492011-12-14 18:25:41 +00001266 register const MagickRealType
cristy117ff172010-08-15 21:35:32 +00001267 *k;
1268
cristy4e154852011-07-14 13:28:53 +00001269 register ssize_t
1270 u;
1271
cristybb503372010-05-27 20:51:26 +00001272 ssize_t
cristyfccdab92009-11-30 16:43:57 +00001273 v;
1274
cristyfccdab92009-11-30 16:43:57 +00001275 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristy5e6be1e2011-07-16 01:23:39 +00001276 " ConvolveImage with %.20gx%.20g kernel:",(double) kernel_info->width,
1277 (double) kernel_info->height);
cristyfccdab92009-11-30 16:43:57 +00001278 message=AcquireString("");
cristy5e6be1e2011-07-16 01:23:39 +00001279 k=kernel_info->values;
1280 for (v=0; v < (ssize_t) kernel_info->width; v++)
cristyfccdab92009-11-30 16:43:57 +00001281 {
1282 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00001283 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristyfccdab92009-11-30 16:43:57 +00001284 (void) ConcatenateString(&message,format);
cristy5e6be1e2011-07-16 01:23:39 +00001285 for (u=0; u < (ssize_t) kernel_info->height; u++)
cristyfccdab92009-11-30 16:43:57 +00001286 {
cristyb51dff52011-05-19 16:55:47 +00001287 (void) FormatLocaleString(format,MaxTextExtent,"%g ",*k++);
cristyfccdab92009-11-30 16:43:57 +00001288 (void) ConcatenateString(&message,format);
1289 }
1290 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
1291 }
1292 message=DestroyString(message);
1293 }
cristy7ea27962011-09-12 18:12:49 +00001294 status=AccelerateConvolveImage(image,kernel_info,convolve_image,exception);
1295 if (status == MagickTrue)
1296 return(convolve_image);
cristyfccdab92009-11-30 16:43:57 +00001297 /*
cristyfccdab92009-11-30 16:43:57 +00001298 Convolve image.
1299 */
cristy574cc262011-08-05 01:23:58 +00001300 center=(ssize_t) GetPixelChannels(image)*(image->columns+kernel_info->width)*
cristy075ff302011-09-07 01:51:24 +00001301 (kernel_info->height/2L)+GetPixelChannels(image)*(kernel_info->width/2L);
cristyfccdab92009-11-30 16:43:57 +00001302 status=MagickTrue;
1303 progress=0;
cristyfccdab92009-11-30 16:43:57 +00001304 image_view=AcquireCacheView(image);
1305 convolve_view=AcquireCacheView(convolve_image);
1306#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy175653e2011-07-10 23:13:34 +00001307 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristyfccdab92009-11-30 16:43:57 +00001308#endif
cristybb503372010-05-27 20:51:26 +00001309 for (y=0; y < (ssize_t) image->rows; y++)
cristyfccdab92009-11-30 16:43:57 +00001310 {
cristy4c08aed2011-07-01 19:47:50 +00001311 register const Quantum
cristy105ba3c2011-07-18 02:28:38 +00001312 *restrict p;
cristyfccdab92009-11-30 16:43:57 +00001313
cristy4c08aed2011-07-01 19:47:50 +00001314 register Quantum
cristyfccdab92009-11-30 16:43:57 +00001315 *restrict q;
1316
cristy117ff172010-08-15 21:35:32 +00001317 register ssize_t
1318 x;
1319
cristyfccdab92009-11-30 16:43:57 +00001320 if (status == MagickFalse)
1321 continue;
cristy105ba3c2011-07-18 02:28:38 +00001322 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) kernel_info->width/2L),y-
1323 (ssize_t) (kernel_info->height/2L),image->columns+kernel_info->width,
1324 kernel_info->height,exception);
cristy08429172011-07-14 17:18:16 +00001325 q=QueueCacheViewAuthenticPixels(convolve_view,0,y,convolve_image->columns,1,
cristyfccdab92009-11-30 16:43:57 +00001326 exception);
cristy105ba3c2011-07-18 02:28:38 +00001327 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristyfccdab92009-11-30 16:43:57 +00001328 {
1329 status=MagickFalse;
1330 continue;
1331 }
cristybb503372010-05-27 20:51:26 +00001332 for (x=0; x < (ssize_t) image->columns; x++)
cristyfccdab92009-11-30 16:43:57 +00001333 {
cristybb503372010-05-27 20:51:26 +00001334 register ssize_t
cristyed231572011-07-14 02:18:59 +00001335 i;
cristyfccdab92009-11-30 16:43:57 +00001336
cristya30d9ba2011-07-23 21:00:48 +00001337 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristyed231572011-07-14 02:18:59 +00001338 {
cristyed231572011-07-14 02:18:59 +00001339 MagickRealType
cristy4e154852011-07-14 13:28:53 +00001340 alpha,
1341 gamma,
cristyed231572011-07-14 02:18:59 +00001342 pixel;
1343
1344 PixelChannel
1345 channel;
1346
1347 PixelTrait
1348 convolve_traits,
1349 traits;
1350
cristya96f2492011-12-14 18:25:41 +00001351 register const MagickRealType
cristyed231572011-07-14 02:18:59 +00001352 *restrict k;
1353
1354 register const Quantum
cristyeb52cde2011-07-17 01:52:52 +00001355 *restrict pixels;
cristyed231572011-07-14 02:18:59 +00001356
1357 register ssize_t
1358 u;
1359
1360 ssize_t
1361 v;
1362
cristye2a912b2011-12-05 20:02:07 +00001363 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +00001364 traits=GetPixelChannelMapTraits(image,channel);
cristy4e154852011-07-14 13:28:53 +00001365 convolve_traits=GetPixelChannelMapTraits(convolve_image,channel);
cristy010d7d12011-08-31 01:02:48 +00001366 if ((traits == UndefinedPixelTrait) ||
1367 (convolve_traits == UndefinedPixelTrait))
cristy4e154852011-07-14 13:28:53 +00001368 continue;
1369 if ((convolve_traits & CopyPixelTrait) != 0)
1370 {
cristy0beccfa2011-09-25 20:47:53 +00001371 SetPixelChannel(convolve_image,channel,p[center+i],q);
cristy4e154852011-07-14 13:28:53 +00001372 continue;
1373 }
cristy5e6be1e2011-07-16 01:23:39 +00001374 k=kernel_info->values;
cristy105ba3c2011-07-18 02:28:38 +00001375 pixels=p;
cristy0a922382011-07-16 15:30:34 +00001376 pixel=kernel_info->bias;
cristy222b19c2011-08-04 01:35:11 +00001377 if ((convolve_traits & BlendPixelTrait) == 0)
cristyfccdab92009-11-30 16:43:57 +00001378 {
cristyed231572011-07-14 02:18:59 +00001379 /*
cristy4e154852011-07-14 13:28:53 +00001380 No alpha blending.
cristyed231572011-07-14 02:18:59 +00001381 */
cristyeb52cde2011-07-17 01:52:52 +00001382 for (v=0; v < (ssize_t) kernel_info->height; v++)
cristyfccdab92009-11-30 16:43:57 +00001383 {
cristyeb52cde2011-07-17 01:52:52 +00001384 for (u=0; u < (ssize_t) kernel_info->width; u++)
cristy175653e2011-07-10 23:13:34 +00001385 {
cristyeb52cde2011-07-17 01:52:52 +00001386 pixel+=(*k)*pixels[i];
cristyed231572011-07-14 02:18:59 +00001387 k++;
cristya30d9ba2011-07-23 21:00:48 +00001388 pixels+=GetPixelChannels(image);
cristy175653e2011-07-10 23:13:34 +00001389 }
cristya30d9ba2011-07-23 21:00:48 +00001390 pixels+=image->columns*GetPixelChannels(image);
cristy175653e2011-07-10 23:13:34 +00001391 }
cristy0beccfa2011-09-25 20:47:53 +00001392 SetPixelChannel(convolve_image,channel,ClampToQuantum(pixel),q);
cristy4e154852011-07-14 13:28:53 +00001393 continue;
cristyed231572011-07-14 02:18:59 +00001394 }
cristy4e154852011-07-14 13:28:53 +00001395 /*
1396 Alpha blending.
1397 */
1398 gamma=0.0;
cristyeb52cde2011-07-17 01:52:52 +00001399 for (v=0; v < (ssize_t) kernel_info->height; v++)
cristy4e154852011-07-14 13:28:53 +00001400 {
cristyeb52cde2011-07-17 01:52:52 +00001401 for (u=0; u < (ssize_t) kernel_info->width; u++)
cristy4e154852011-07-14 13:28:53 +00001402 {
cristyeb52cde2011-07-17 01:52:52 +00001403 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,pixels));
1404 pixel+=(*k)*alpha*pixels[i];
cristy4e154852011-07-14 13:28:53 +00001405 gamma+=(*k)*alpha;
1406 k++;
cristya30d9ba2011-07-23 21:00:48 +00001407 pixels+=GetPixelChannels(image);
cristy4e154852011-07-14 13:28:53 +00001408 }
cristya30d9ba2011-07-23 21:00:48 +00001409 pixels+=image->columns*GetPixelChannels(image);
cristy4e154852011-07-14 13:28:53 +00001410 }
cristy1ce96d02011-07-14 17:57:24 +00001411 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +00001412 SetPixelChannel(convolve_image,channel,ClampToQuantum(gamma*pixel),q);
cristyed231572011-07-14 02:18:59 +00001413 }
cristya30d9ba2011-07-23 21:00:48 +00001414 p+=GetPixelChannels(image);
1415 q+=GetPixelChannels(convolve_image);
cristyfccdab92009-11-30 16:43:57 +00001416 }
cristyed231572011-07-14 02:18:59 +00001417 if (SyncCacheViewAuthenticPixels(convolve_view,exception) == MagickFalse)
cristyfccdab92009-11-30 16:43:57 +00001418 status=MagickFalse;
1419 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1420 {
1421 MagickBooleanType
1422 proceed;
1423
1424#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00001425 #pragma omp critical (MagickCore_ConvolveImage)
cristyfccdab92009-11-30 16:43:57 +00001426#endif
1427 proceed=SetImageProgress(image,ConvolveImageTag,progress++,image->rows);
1428 if (proceed == MagickFalse)
1429 status=MagickFalse;
1430 }
1431 }
1432 convolve_image->type=image->type;
1433 convolve_view=DestroyCacheView(convolve_view);
1434 image_view=DestroyCacheView(image_view);
cristyfccdab92009-11-30 16:43:57 +00001435 if (status == MagickFalse)
1436 convolve_image=DestroyImage(convolve_image);
1437 return(convolve_image);
1438}
1439
1440/*
1441%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1442% %
1443% %
1444% %
cristy3ed852e2009-09-05 21:47:34 +00001445% D e s p e c k l e I m a g e %
1446% %
1447% %
1448% %
1449%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1450%
1451% DespeckleImage() reduces the speckle noise in an image while perserving the
1452% edges of the original image.
1453%
1454% The format of the DespeckleImage method is:
1455%
1456% Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1457%
1458% A description of each parameter follows:
1459%
1460% o image: the image.
1461%
1462% o exception: return any errors or warnings in this structure.
1463%
1464*/
1465
cristybb503372010-05-27 20:51:26 +00001466static void Hull(const ssize_t x_offset,const ssize_t y_offset,
1467 const size_t columns,const size_t rows,Quantum *f,Quantum *g,
cristy3ed852e2009-09-05 21:47:34 +00001468 const int polarity)
1469{
cristy3ed852e2009-09-05 21:47:34 +00001470 MagickRealType
1471 v;
1472
cristy3ed852e2009-09-05 21:47:34 +00001473 register Quantum
1474 *p,
1475 *q,
1476 *r,
1477 *s;
1478
cristy117ff172010-08-15 21:35:32 +00001479 register ssize_t
1480 x;
1481
1482 ssize_t
1483 y;
1484
cristy3ed852e2009-09-05 21:47:34 +00001485 assert(f != (Quantum *) NULL);
1486 assert(g != (Quantum *) NULL);
1487 p=f+(columns+2);
1488 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001489 r=p+(y_offset*((ssize_t) columns+2)+x_offset);
1490 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001491 {
1492 p++;
1493 q++;
1494 r++;
1495 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001496 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001497 {
1498 v=(MagickRealType) (*p);
1499 if ((MagickRealType) *r >= (v+(MagickRealType) ScaleCharToQuantum(2)))
1500 v+=ScaleCharToQuantum(1);
1501 *q=(Quantum) v;
1502 p++;
1503 q++;
1504 r++;
1505 }
1506 else
cristybb503372010-05-27 20:51:26 +00001507 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001508 {
1509 v=(MagickRealType) (*p);
1510 if ((MagickRealType) *r <= (v-(MagickRealType) ScaleCharToQuantum(2)))
cristybb503372010-05-27 20:51:26 +00001511 v-=(ssize_t) ScaleCharToQuantum(1);
cristy3ed852e2009-09-05 21:47:34 +00001512 *q=(Quantum) v;
1513 p++;
1514 q++;
1515 r++;
1516 }
1517 p++;
1518 q++;
1519 r++;
1520 }
1521 p=f+(columns+2);
1522 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001523 r=q+(y_offset*((ssize_t) columns+2)+x_offset);
1524 s=q-(y_offset*((ssize_t) columns+2)+x_offset);
1525 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001526 {
1527 p++;
1528 q++;
1529 r++;
1530 s++;
1531 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001532 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001533 {
1534 v=(MagickRealType) (*q);
1535 if (((MagickRealType) *s >=
1536 (v+(MagickRealType) ScaleCharToQuantum(2))) &&
1537 ((MagickRealType) *r > v))
1538 v+=ScaleCharToQuantum(1);
1539 *p=(Quantum) v;
1540 p++;
1541 q++;
1542 r++;
1543 s++;
1544 }
1545 else
cristybb503372010-05-27 20:51:26 +00001546 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001547 {
1548 v=(MagickRealType) (*q);
1549 if (((MagickRealType) *s <=
1550 (v-(MagickRealType) ScaleCharToQuantum(2))) &&
1551 ((MagickRealType) *r < v))
1552 v-=(MagickRealType) ScaleCharToQuantum(1);
1553 *p=(Quantum) v;
1554 p++;
1555 q++;
1556 r++;
1557 s++;
1558 }
1559 p++;
1560 q++;
1561 r++;
1562 s++;
1563 }
1564}
1565
1566MagickExport Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1567{
1568#define DespeckleImageTag "Despeckle/Image"
1569
cristy2407fc22009-09-11 00:55:25 +00001570 CacheView
1571 *despeckle_view,
1572 *image_view;
1573
cristy3ed852e2009-09-05 21:47:34 +00001574 Image
1575 *despeckle_image;
1576
cristy3ed852e2009-09-05 21:47:34 +00001577 MagickBooleanType
1578 status;
1579
1580 Quantum
cristy65b9f392011-02-22 14:22:54 +00001581 *restrict buffers,
1582 *restrict pixels;
cristy3ed852e2009-09-05 21:47:34 +00001583
cristya63e4a92011-09-09 00:47:59 +00001584 register ssize_t
1585 i;
1586
cristy3ed852e2009-09-05 21:47:34 +00001587 size_t
cristya63e4a92011-09-09 00:47:59 +00001588 length;
cristy117ff172010-08-15 21:35:32 +00001589
cristybb503372010-05-27 20:51:26 +00001590 static const ssize_t
cristy691a29e2009-09-11 00:44:10 +00001591 X[4] = {0, 1, 1,-1},
1592 Y[4] = {1, 0, 1, 1};
cristy3ed852e2009-09-05 21:47:34 +00001593
cristy3ed852e2009-09-05 21:47:34 +00001594 /*
1595 Allocate despeckled image.
1596 */
1597 assert(image != (const Image *) NULL);
1598 assert(image->signature == MagickSignature);
1599 if (image->debug != MagickFalse)
1600 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1601 assert(exception != (ExceptionInfo *) NULL);
1602 assert(exception->signature == MagickSignature);
cristya63e4a92011-09-09 00:47:59 +00001603 despeckle_image=CloneImage(image,0,0,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00001604 if (despeckle_image == (Image *) NULL)
1605 return((Image *) NULL);
cristya63e4a92011-09-09 00:47:59 +00001606 status=SetImageStorageClass(despeckle_image,DirectClass,exception);
1607 if (status == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001608 {
cristy3ed852e2009-09-05 21:47:34 +00001609 despeckle_image=DestroyImage(despeckle_image);
1610 return((Image *) NULL);
1611 }
1612 /*
1613 Allocate image buffers.
1614 */
1615 length=(size_t) ((image->columns+2)*(image->rows+2));
cristy65b9f392011-02-22 14:22:54 +00001616 pixels=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1617 buffers=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1618 if ((pixels == (Quantum *) NULL) || (buffers == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001619 {
cristy65b9f392011-02-22 14:22:54 +00001620 if (buffers != (Quantum *) NULL)
1621 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1622 if (pixels != (Quantum *) NULL)
1623 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001624 despeckle_image=DestroyImage(despeckle_image);
1625 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1626 }
1627 /*
1628 Reduce speckle in the image.
1629 */
1630 status=MagickTrue;
1631 image_view=AcquireCacheView(image);
1632 despeckle_view=AcquireCacheView(despeckle_image);
cristya63e4a92011-09-09 00:47:59 +00001633 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy3ed852e2009-09-05 21:47:34 +00001634 {
cristya63e4a92011-09-09 00:47:59 +00001635 PixelChannel
1636 channel;
1637
1638 PixelTrait
1639 despeckle_traits,
1640 traits;
1641
cristy3ed852e2009-09-05 21:47:34 +00001642 register Quantum
1643 *buffer,
1644 *pixel;
1645
cristyc1488b52011-02-19 18:54:15 +00001646 register ssize_t
cristya58c3172011-02-19 19:23:11 +00001647 k,
cristyc1488b52011-02-19 18:54:15 +00001648 x;
1649
cristy117ff172010-08-15 21:35:32 +00001650 ssize_t
1651 j,
1652 y;
1653
cristy3ed852e2009-09-05 21:47:34 +00001654 if (status == MagickFalse)
1655 continue;
cristye2a912b2011-12-05 20:02:07 +00001656 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +00001657 traits=GetPixelChannelMapTraits(image,channel);
cristya63e4a92011-09-09 00:47:59 +00001658 despeckle_traits=GetPixelChannelMapTraits(despeckle_image,channel);
1659 if ((traits == UndefinedPixelTrait) ||
1660 (despeckle_traits == UndefinedPixelTrait))
1661 continue;
1662 if ((despeckle_traits & CopyPixelTrait) != 0)
1663 continue;
cristy65b9f392011-02-22 14:22:54 +00001664 pixel=pixels;
cristy3ed852e2009-09-05 21:47:34 +00001665 (void) ResetMagickMemory(pixel,0,length*sizeof(*pixel));
cristy65b9f392011-02-22 14:22:54 +00001666 buffer=buffers;
cristybb503372010-05-27 20:51:26 +00001667 j=(ssize_t) image->columns+2;
1668 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001669 {
cristy4c08aed2011-07-01 19:47:50 +00001670 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001671 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001672
1673 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001674 if (p == (const Quantum *) NULL)
cristya63e4a92011-09-09 00:47:59 +00001675 {
1676 status=MagickFalse;
1677 continue;
1678 }
cristy3ed852e2009-09-05 21:47:34 +00001679 j++;
cristybb503372010-05-27 20:51:26 +00001680 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001681 {
cristya63e4a92011-09-09 00:47:59 +00001682 pixel[j++]=p[i];
cristyed231572011-07-14 02:18:59 +00001683 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001684 }
1685 j++;
1686 }
cristy3ed852e2009-09-05 21:47:34 +00001687 (void) ResetMagickMemory(buffer,0,length*sizeof(*buffer));
cristya58c3172011-02-19 19:23:11 +00001688 for (k=0; k < 4; k++)
cristy3ed852e2009-09-05 21:47:34 +00001689 {
cristya58c3172011-02-19 19:23:11 +00001690 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);
1693 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,-1);
cristy3ed852e2009-09-05 21:47:34 +00001694 }
cristybb503372010-05-27 20:51:26 +00001695 j=(ssize_t) image->columns+2;
1696 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001697 {
1698 MagickBooleanType
1699 sync;
1700
cristy4c08aed2011-07-01 19:47:50 +00001701 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001702 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001703
1704 q=GetCacheViewAuthenticPixels(despeckle_view,0,y,despeckle_image->columns,
1705 1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001706 if (q == (Quantum *) NULL)
cristya63e4a92011-09-09 00:47:59 +00001707 {
1708 status=MagickFalse;
1709 continue;
1710 }
cristy3ed852e2009-09-05 21:47:34 +00001711 j++;
cristybb503372010-05-27 20:51:26 +00001712 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001713 {
cristy0beccfa2011-09-25 20:47:53 +00001714 SetPixelChannel(despeckle_image,channel,pixel[j++],q);
cristyed231572011-07-14 02:18:59 +00001715 q+=GetPixelChannels(despeckle_image);
cristy3ed852e2009-09-05 21:47:34 +00001716 }
1717 sync=SyncCacheViewAuthenticPixels(despeckle_view,exception);
1718 if (sync == MagickFalse)
cristya63e4a92011-09-09 00:47:59 +00001719 status=MagickFalse;
cristy3ed852e2009-09-05 21:47:34 +00001720 j++;
1721 }
1722 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1723 {
1724 MagickBooleanType
1725 proceed;
1726
cristya58c3172011-02-19 19:23:11 +00001727 proceed=SetImageProgress(image,DespeckleImageTag,(MagickOffsetType) i,
cristya63e4a92011-09-09 00:47:59 +00001728 GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00001729 if (proceed == MagickFalse)
1730 status=MagickFalse;
1731 }
1732 }
1733 despeckle_view=DestroyCacheView(despeckle_view);
1734 image_view=DestroyCacheView(image_view);
cristy65b9f392011-02-22 14:22:54 +00001735 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1736 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001737 despeckle_image->type=image->type;
1738 if (status == MagickFalse)
1739 despeckle_image=DestroyImage(despeckle_image);
1740 return(despeckle_image);
1741}
1742
1743/*
1744%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1745% %
1746% %
1747% %
1748% E d g e I m a g e %
1749% %
1750% %
1751% %
1752%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1753%
1754% EdgeImage() finds edges in an image. Radius defines the radius of the
1755% convolution filter. Use a radius of 0 and EdgeImage() selects a suitable
1756% radius for you.
1757%
1758% The format of the EdgeImage method is:
1759%
1760% Image *EdgeImage(const Image *image,const double radius,
cristy8ae632d2011-09-05 17:29:53 +00001761% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001762%
1763% A description of each parameter follows:
1764%
1765% o image: the image.
1766%
1767% o radius: the radius of the pixel neighborhood.
1768%
cristy8ae632d2011-09-05 17:29:53 +00001769% o sigma: the standard deviation of the Gaussian, in pixels.
1770%
cristy3ed852e2009-09-05 21:47:34 +00001771% o exception: return any errors or warnings in this structure.
1772%
1773*/
1774MagickExport Image *EdgeImage(const Image *image,const double radius,
cristy8ae632d2011-09-05 17:29:53 +00001775 const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001776{
1777 Image
1778 *edge_image;
1779
cristy41cbe682011-07-15 19:12:37 +00001780 KernelInfo
1781 *kernel_info;
cristy3ed852e2009-09-05 21:47:34 +00001782
cristybb503372010-05-27 20:51:26 +00001783 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001784 i;
1785
cristybb503372010-05-27 20:51:26 +00001786 size_t
cristy3ed852e2009-09-05 21:47:34 +00001787 width;
1788
cristy41cbe682011-07-15 19:12:37 +00001789 ssize_t
1790 j,
1791 u,
1792 v;
1793
cristy3ed852e2009-09-05 21:47:34 +00001794 assert(image != (const Image *) NULL);
1795 assert(image->signature == MagickSignature);
1796 if (image->debug != MagickFalse)
1797 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1798 assert(exception != (ExceptionInfo *) NULL);
1799 assert(exception->signature == MagickSignature);
cristyfc0ae532011-12-06 15:14:45 +00001800 width=GetOptimalKernelWidth1D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00001801 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00001802 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001803 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00001804 kernel_info->width=width;
1805 kernel_info->height=width;
cristya96f2492011-12-14 18:25:41 +00001806 kernel_info->values=(MagickRealType *) AcquireAlignedMemory(
1807 kernel_info->width,kernel_info->width*sizeof(*kernel_info->values));
1808 if (kernel_info->values == (MagickRealType *) NULL)
cristy41cbe682011-07-15 19:12:37 +00001809 {
1810 kernel_info=DestroyKernelInfo(kernel_info);
1811 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1812 }
1813 j=(ssize_t) kernel_info->width/2;
1814 i=0;
1815 for (v=(-j); v <= j; v++)
1816 {
1817 for (u=(-j); u <= j; u++)
1818 {
1819 kernel_info->values[i]=(-1.0);
1820 i++;
1821 }
1822 }
1823 kernel_info->values[i/2]=(double) (width*width-1.0);
anthony736a1602011-10-06 12:38:17 +00001824 kernel_info->bias=image->bias; /* FUTURE: User bias on a edge image? */
cristy5e6be1e2011-07-16 01:23:39 +00001825 edge_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00001826 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00001827 return(edge_image);
1828}
1829
1830/*
1831%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1832% %
1833% %
1834% %
1835% E m b o s s I m a g e %
1836% %
1837% %
1838% %
1839%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1840%
1841% EmbossImage() returns a grayscale image with a three-dimensional effect.
1842% We convolve the image with a Gaussian operator of the given radius and
1843% standard deviation (sigma). For reasonable results, radius should be
1844% larger than sigma. Use a radius of 0 and Emboss() selects a suitable
1845% radius for you.
1846%
1847% The format of the EmbossImage method is:
1848%
1849% Image *EmbossImage(const Image *image,const double radius,
1850% const double sigma,ExceptionInfo *exception)
1851%
1852% A description of each parameter follows:
1853%
1854% o image: the image.
1855%
1856% o radius: the radius of the pixel neighborhood.
1857%
1858% o sigma: the standard deviation of the Gaussian, in pixels.
1859%
1860% o exception: return any errors or warnings in this structure.
1861%
1862*/
1863MagickExport Image *EmbossImage(const Image *image,const double radius,
1864 const double sigma,ExceptionInfo *exception)
1865{
cristy3ed852e2009-09-05 21:47:34 +00001866 Image
1867 *emboss_image;
1868
cristy41cbe682011-07-15 19:12:37 +00001869 KernelInfo
1870 *kernel_info;
1871
cristybb503372010-05-27 20:51:26 +00001872 register ssize_t
cristy47e00502009-12-17 19:19:57 +00001873 i;
1874
cristybb503372010-05-27 20:51:26 +00001875 size_t
cristy3ed852e2009-09-05 21:47:34 +00001876 width;
1877
cristy117ff172010-08-15 21:35:32 +00001878 ssize_t
1879 j,
1880 k,
1881 u,
1882 v;
1883
cristy41cbe682011-07-15 19:12:37 +00001884 assert(image != (const Image *) NULL);
cristy3ed852e2009-09-05 21:47:34 +00001885 assert(image->signature == MagickSignature);
1886 if (image->debug != MagickFalse)
1887 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1888 assert(exception != (ExceptionInfo *) NULL);
1889 assert(exception->signature == MagickSignature);
cristyfc0ae532011-12-06 15:14:45 +00001890 width=GetOptimalKernelWidth1D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00001891 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00001892 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001893 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00001894 kernel_info->width=width;
1895 kernel_info->height=width;
cristya96f2492011-12-14 18:25:41 +00001896 kernel_info->values=(MagickRealType *) AcquireAlignedMemory(
1897 kernel_info->width,kernel_info->width*sizeof(*kernel_info->values));
1898 if (kernel_info->values == (MagickRealType *) NULL)
cristy41cbe682011-07-15 19:12:37 +00001899 {
1900 kernel_info=DestroyKernelInfo(kernel_info);
1901 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1902 }
1903 j=(ssize_t) kernel_info->width/2;
cristy47e00502009-12-17 19:19:57 +00001904 k=j;
1905 i=0;
1906 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00001907 {
cristy47e00502009-12-17 19:19:57 +00001908 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00001909 {
cristy41cbe682011-07-15 19:12:37 +00001910 kernel_info->values[i]=(double) (((u < 0) || (v < 0) ? -8.0 : 8.0)*
cristy47e00502009-12-17 19:19:57 +00001911 exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
cristy4205a3c2010-09-12 20:19:59 +00001912 (2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +00001913 if (u != k)
cristy41cbe682011-07-15 19:12:37 +00001914 kernel_info->values[i]=0.0;
cristy3ed852e2009-09-05 21:47:34 +00001915 i++;
1916 }
cristy47e00502009-12-17 19:19:57 +00001917 k--;
cristy3ed852e2009-09-05 21:47:34 +00001918 }
anthony736a1602011-10-06 12:38:17 +00001919 kernel_info->bias=image->bias; /* FUTURE: user bias on an edge image */
cristy5e6be1e2011-07-16 01:23:39 +00001920 emboss_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00001921 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00001922 if (emboss_image != (Image *) NULL)
cristy6d8c3d72011-08-22 01:20:01 +00001923 (void) EqualizeImage(emboss_image,exception);
cristy3ed852e2009-09-05 21:47:34 +00001924 return(emboss_image);
1925}
1926
1927/*
1928%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1929% %
1930% %
1931% %
1932% G a u s s i a n B l u r I m a g e %
1933% %
1934% %
1935% %
1936%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1937%
1938% GaussianBlurImage() blurs an image. We convolve the image with a
1939% Gaussian operator of the given radius and standard deviation (sigma).
1940% For reasonable results, the radius should be larger than sigma. Use a
1941% radius of 0 and GaussianBlurImage() selects a suitable radius for you
1942%
1943% The format of the GaussianBlurImage method is:
1944%
1945% Image *GaussianBlurImage(const Image *image,onst double radius,
cristy05c0c9a2011-09-05 23:16:13 +00001946% const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001947%
1948% A description of each parameter follows:
1949%
1950% o image: the image.
1951%
cristy3ed852e2009-09-05 21:47:34 +00001952% o radius: the radius of the Gaussian, in pixels, not counting the center
1953% pixel.
1954%
1955% o sigma: the standard deviation of the Gaussian, in pixels.
1956%
cristy05c0c9a2011-09-05 23:16:13 +00001957% o bias: the bias.
1958%
cristy3ed852e2009-09-05 21:47:34 +00001959% o exception: return any errors or warnings in this structure.
1960%
1961*/
cristy41cbe682011-07-15 19:12:37 +00001962MagickExport Image *GaussianBlurImage(const Image *image,const double radius,
cristy05c0c9a2011-09-05 23:16:13 +00001963 const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001964{
cristy3ed852e2009-09-05 21:47:34 +00001965 Image
1966 *blur_image;
1967
cristy41cbe682011-07-15 19:12:37 +00001968 KernelInfo
1969 *kernel_info;
1970
cristybb503372010-05-27 20:51:26 +00001971 register ssize_t
cristy47e00502009-12-17 19:19:57 +00001972 i;
1973
cristybb503372010-05-27 20:51:26 +00001974 size_t
cristy3ed852e2009-09-05 21:47:34 +00001975 width;
1976
cristy117ff172010-08-15 21:35:32 +00001977 ssize_t
1978 j,
1979 u,
1980 v;
1981
cristy3ed852e2009-09-05 21:47:34 +00001982 assert(image != (const Image *) NULL);
1983 assert(image->signature == MagickSignature);
1984 if (image->debug != MagickFalse)
1985 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1986 assert(exception != (ExceptionInfo *) NULL);
1987 assert(exception->signature == MagickSignature);
1988 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00001989 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00001990 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001991 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00001992 (void) ResetMagickMemory(kernel_info,0,sizeof(*kernel_info));
1993 kernel_info->width=width;
1994 kernel_info->height=width;
anthony736a1602011-10-06 12:38:17 +00001995 kernel_info->bias=bias; /* FUTURE: user bias on Gaussian Blur! non-sense */
cristy41cbe682011-07-15 19:12:37 +00001996 kernel_info->signature=MagickSignature;
cristya96f2492011-12-14 18:25:41 +00001997 kernel_info->values=(MagickRealType *) AcquireAlignedMemory(
1998 kernel_info->width,kernel_info->width*sizeof(*kernel_info->values));
1999 if (kernel_info->values == (MagickRealType *) NULL)
cristy41cbe682011-07-15 19:12:37 +00002000 {
2001 kernel_info=DestroyKernelInfo(kernel_info);
2002 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2003 }
2004 j=(ssize_t) kernel_info->width/2;
cristy3ed852e2009-09-05 21:47:34 +00002005 i=0;
cristy47e00502009-12-17 19:19:57 +00002006 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002007 {
cristy47e00502009-12-17 19:19:57 +00002008 for (u=(-j); u <= j; u++)
cristy41cbe682011-07-15 19:12:37 +00002009 {
2010 kernel_info->values[i]=(double) (exp(-((double) u*u+v*v)/(2.0*
2011 MagickSigma*MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
2012 i++;
2013 }
cristy3ed852e2009-09-05 21:47:34 +00002014 }
cristy5e6be1e2011-07-16 01:23:39 +00002015 blur_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00002016 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00002017 return(blur_image);
2018}
2019
2020/*
2021%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2022% %
2023% %
2024% %
cristy3ed852e2009-09-05 21:47:34 +00002025% M o t i o n B l u r I m a g e %
2026% %
2027% %
2028% %
2029%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2030%
2031% MotionBlurImage() simulates motion blur. We convolve the image with a
2032% Gaussian operator of the given radius and standard deviation (sigma).
2033% For reasonable results, radius should be larger than sigma. Use a
2034% radius of 0 and MotionBlurImage() selects a suitable radius for you.
2035% Angle gives the angle of the blurring motion.
2036%
2037% Andrew Protano contributed this effect.
2038%
2039% The format of the MotionBlurImage method is:
2040%
2041% Image *MotionBlurImage(const Image *image,const double radius,
cristyf7ef0252011-09-09 14:50:06 +00002042% const double sigma,const double angle,const double bias,
2043% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002044%
2045% A description of each parameter follows:
2046%
2047% o image: the image.
2048%
cristy3ed852e2009-09-05 21:47:34 +00002049% o radius: the radius of the Gaussian, in pixels, not counting
2050% the center pixel.
2051%
2052% o sigma: the standard deviation of the Gaussian, in pixels.
2053%
cristycee97112010-05-28 00:44:52 +00002054% o angle: Apply the effect along this angle.
cristy3ed852e2009-09-05 21:47:34 +00002055%
cristyf7ef0252011-09-09 14:50:06 +00002056% o bias: the bias.
2057%
cristy3ed852e2009-09-05 21:47:34 +00002058% o exception: return any errors or warnings in this structure.
2059%
2060*/
2061
cristybb503372010-05-27 20:51:26 +00002062static double *GetMotionBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +00002063{
cristy3ed852e2009-09-05 21:47:34 +00002064 double
cristy47e00502009-12-17 19:19:57 +00002065 *kernel,
cristy3ed852e2009-09-05 21:47:34 +00002066 normalize;
2067
cristybb503372010-05-27 20:51:26 +00002068 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002069 i;
2070
2071 /*
cristy47e00502009-12-17 19:19:57 +00002072 Generate a 1-D convolution kernel.
cristy3ed852e2009-09-05 21:47:34 +00002073 */
2074 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
2075 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
2076 if (kernel == (double *) NULL)
2077 return(kernel);
cristy3ed852e2009-09-05 21:47:34 +00002078 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00002079 for (i=0; i < (ssize_t) width; i++)
cristy47e00502009-12-17 19:19:57 +00002080 {
cristy4205a3c2010-09-12 20:19:59 +00002081 kernel[i]=(double) (exp((-((double) i*i)/(double) (2.0*MagickSigma*
2082 MagickSigma)))/(MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00002083 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +00002084 }
cristybb503372010-05-27 20:51:26 +00002085 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002086 kernel[i]/=normalize;
2087 return(kernel);
2088}
2089
cristya63e4a92011-09-09 00:47:59 +00002090MagickExport Image *MotionBlurImage(const Image *image,const double radius,
cristyf7ef0252011-09-09 14:50:06 +00002091 const double sigma,const double angle,const double bias,
2092 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002093{
cristyc4c8d132010-01-07 01:58:38 +00002094 CacheView
2095 *blur_view,
2096 *image_view;
2097
cristy3ed852e2009-09-05 21:47:34 +00002098 double
2099 *kernel;
2100
2101 Image
2102 *blur_image;
2103
cristy3ed852e2009-09-05 21:47:34 +00002104 MagickBooleanType
2105 status;
2106
cristybb503372010-05-27 20:51:26 +00002107 MagickOffsetType
2108 progress;
2109
cristy3ed852e2009-09-05 21:47:34 +00002110 OffsetInfo
2111 *offset;
2112
2113 PointInfo
2114 point;
2115
cristybb503372010-05-27 20:51:26 +00002116 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002117 i;
2118
cristybb503372010-05-27 20:51:26 +00002119 size_t
cristy3ed852e2009-09-05 21:47:34 +00002120 width;
2121
cristybb503372010-05-27 20:51:26 +00002122 ssize_t
2123 y;
2124
cristy3ed852e2009-09-05 21:47:34 +00002125 assert(image != (Image *) NULL);
2126 assert(image->signature == MagickSignature);
2127 if (image->debug != MagickFalse)
2128 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2129 assert(exception != (ExceptionInfo *) NULL);
2130 width=GetOptimalKernelWidth1D(radius,sigma);
2131 kernel=GetMotionBlurKernel(width,sigma);
2132 if (kernel == (double *) NULL)
2133 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2134 offset=(OffsetInfo *) AcquireQuantumMemory(width,sizeof(*offset));
2135 if (offset == (OffsetInfo *) NULL)
2136 {
2137 kernel=(double *) RelinquishMagickMemory(kernel);
2138 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2139 }
cristy1e7aa312011-09-10 20:01:36 +00002140 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00002141 if (blur_image == (Image *) NULL)
2142 {
2143 kernel=(double *) RelinquishMagickMemory(kernel);
2144 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2145 return((Image *) NULL);
2146 }
cristy574cc262011-08-05 01:23:58 +00002147 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00002148 {
2149 kernel=(double *) RelinquishMagickMemory(kernel);
2150 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
cristy3ed852e2009-09-05 21:47:34 +00002151 blur_image=DestroyImage(blur_image);
2152 return((Image *) NULL);
2153 }
2154 point.x=(double) width*sin(DegreesToRadians(angle));
2155 point.y=(double) width*cos(DegreesToRadians(angle));
cristybb503372010-05-27 20:51:26 +00002156 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002157 {
cristybb503372010-05-27 20:51:26 +00002158 offset[i].x=(ssize_t) ceil((double) (i*point.y)/hypot(point.x,point.y)-0.5);
2159 offset[i].y=(ssize_t) ceil((double) (i*point.x)/hypot(point.x,point.y)-0.5);
cristy3ed852e2009-09-05 21:47:34 +00002160 }
2161 /*
2162 Motion blur image.
2163 */
2164 status=MagickTrue;
2165 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00002166 image_view=AcquireCacheView(image);
2167 blur_view=AcquireCacheView(blur_image);
cristyb557a152011-02-22 12:14:30 +00002168#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00002169 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00002170#endif
cristybb503372010-05-27 20:51:26 +00002171 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002172 {
cristyf7ef0252011-09-09 14:50:06 +00002173 register const Quantum
2174 *restrict p;
2175
cristy4c08aed2011-07-01 19:47:50 +00002176 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002177 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002178
cristy117ff172010-08-15 21:35:32 +00002179 register ssize_t
2180 x;
2181
cristy3ed852e2009-09-05 21:47:34 +00002182 if (status == MagickFalse)
2183 continue;
cristyf7ef0252011-09-09 14:50:06 +00002184 p=GetCacheViewVirtualPixels(blur_view,0,y,image->columns,1,exception);
cristy3ed852e2009-09-05 21:47:34 +00002185 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
2186 exception);
cristyf7ef0252011-09-09 14:50:06 +00002187 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00002188 {
2189 status=MagickFalse;
2190 continue;
2191 }
cristybb503372010-05-27 20:51:26 +00002192 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002193 {
cristybb503372010-05-27 20:51:26 +00002194 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002195 i;
2196
cristyf7ef0252011-09-09 14:50:06 +00002197 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2198 {
2199 MagickRealType
2200 alpha,
2201 gamma,
2202 pixel;
cristy3ed852e2009-09-05 21:47:34 +00002203
cristyf7ef0252011-09-09 14:50:06 +00002204 PixelChannel
2205 channel;
2206
2207 PixelTrait
2208 blur_traits,
2209 traits;
2210
2211 register const Quantum
2212 *restrict r;
2213
2214 register double
2215 *restrict k;
2216
2217 register ssize_t
2218 j;
2219
cristye2a912b2011-12-05 20:02:07 +00002220 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +00002221 traits=GetPixelChannelMapTraits(image,channel);
cristyf7ef0252011-09-09 14:50:06 +00002222 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
2223 if ((traits == UndefinedPixelTrait) ||
2224 (blur_traits == UndefinedPixelTrait))
2225 continue;
2226 if ((blur_traits & CopyPixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002227 {
cristy0beccfa2011-09-25 20:47:53 +00002228 SetPixelChannel(blur_image,channel,p[i],q);
cristyf7ef0252011-09-09 14:50:06 +00002229 continue;
cristy3ed852e2009-09-05 21:47:34 +00002230 }
cristyf7ef0252011-09-09 14:50:06 +00002231 k=kernel;
2232 pixel=bias;
2233 if ((blur_traits & BlendPixelTrait) == 0)
2234 {
2235 for (j=0; j < (ssize_t) width; j++)
2236 {
2237 r=GetCacheViewVirtualPixels(image_view,x+offset[j].x,y+
2238 offset[j].y,1,1,exception);
2239 if (r == (const Quantum *) NULL)
2240 {
2241 status=MagickFalse;
2242 continue;
2243 }
2244 pixel+=(*k)*r[i];
2245 k++;
2246 }
cristy0beccfa2011-09-25 20:47:53 +00002247 SetPixelChannel(blur_image,channel,ClampToQuantum(pixel),q);
cristyf7ef0252011-09-09 14:50:06 +00002248 continue;
2249 }
2250 alpha=0.0;
2251 gamma=0.0;
2252 for (j=0; j < (ssize_t) width; j++)
2253 {
2254 r=GetCacheViewVirtualPixels(image_view,x+offset[j].x,y+offset[j].y,1,
2255 1,exception);
2256 if (r == (const Quantum *) NULL)
2257 {
2258 status=MagickFalse;
2259 continue;
2260 }
2261 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,r));
2262 pixel+=(*k)*alpha*r[i];
2263 gamma+=(*k)*alpha;
2264 k++;
cristy3ed852e2009-09-05 21:47:34 +00002265 }
cristyf7ef0252011-09-09 14:50:06 +00002266 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +00002267 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristyf7ef0252011-09-09 14:50:06 +00002268 }
2269 p+=GetPixelChannels(image);
cristyed231572011-07-14 02:18:59 +00002270 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00002271 }
2272 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
2273 status=MagickFalse;
2274 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2275 {
2276 MagickBooleanType
2277 proceed;
2278
cristyb557a152011-02-22 12:14:30 +00002279#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00002280 #pragma omp critical (MagickCore_MotionBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00002281#endif
2282 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
2283 if (proceed == MagickFalse)
2284 status=MagickFalse;
2285 }
2286 }
2287 blur_view=DestroyCacheView(blur_view);
2288 image_view=DestroyCacheView(image_view);
2289 kernel=(double *) RelinquishMagickMemory(kernel);
2290 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2291 if (status == MagickFalse)
2292 blur_image=DestroyImage(blur_image);
2293 return(blur_image);
2294}
2295
2296/*
2297%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2298% %
2299% %
2300% %
2301% P r e v i e w I m a g e %
2302% %
2303% %
2304% %
2305%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2306%
2307% PreviewImage() tiles 9 thumbnails of the specified image with an image
2308% processing operation applied with varying parameters. This may be helpful
2309% pin-pointing an appropriate parameter for a particular image processing
2310% operation.
2311%
2312% The format of the PreviewImages method is:
2313%
2314% Image *PreviewImages(const Image *image,const PreviewType preview,
2315% ExceptionInfo *exception)
2316%
2317% A description of each parameter follows:
2318%
2319% o image: the image.
2320%
2321% o preview: the image processing operation.
2322%
2323% o exception: return any errors or warnings in this structure.
2324%
2325*/
2326MagickExport Image *PreviewImage(const Image *image,const PreviewType preview,
2327 ExceptionInfo *exception)
2328{
2329#define NumberTiles 9
2330#define PreviewImageTag "Preview/Image"
2331#define DefaultPreviewGeometry "204x204+10+10"
2332
2333 char
2334 factor[MaxTextExtent],
2335 label[MaxTextExtent];
2336
2337 double
2338 degrees,
2339 gamma,
2340 percentage,
2341 radius,
2342 sigma,
2343 threshold;
2344
2345 Image
2346 *images,
2347 *montage_image,
2348 *preview_image,
2349 *thumbnail;
2350
2351 ImageInfo
2352 *preview_info;
2353
cristy3ed852e2009-09-05 21:47:34 +00002354 MagickBooleanType
2355 proceed;
2356
2357 MontageInfo
2358 *montage_info;
2359
2360 QuantizeInfo
2361 quantize_info;
2362
2363 RectangleInfo
2364 geometry;
2365
cristybb503372010-05-27 20:51:26 +00002366 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002367 i,
2368 x;
2369
cristybb503372010-05-27 20:51:26 +00002370 size_t
cristy3ed852e2009-09-05 21:47:34 +00002371 colors;
2372
cristy117ff172010-08-15 21:35:32 +00002373 ssize_t
2374 y;
2375
cristy3ed852e2009-09-05 21:47:34 +00002376 /*
2377 Open output image file.
2378 */
2379 assert(image != (Image *) NULL);
2380 assert(image->signature == MagickSignature);
2381 if (image->debug != MagickFalse)
2382 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2383 colors=2;
2384 degrees=0.0;
2385 gamma=(-0.2f);
2386 preview_info=AcquireImageInfo();
2387 SetGeometry(image,&geometry);
2388 (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y,
2389 &geometry.width,&geometry.height);
2390 images=NewImageList();
2391 percentage=12.5;
2392 GetQuantizeInfo(&quantize_info);
2393 radius=0.0;
2394 sigma=1.0;
2395 threshold=0.0;
2396 x=0;
2397 y=0;
2398 for (i=0; i < NumberTiles; i++)
2399 {
2400 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
2401 if (thumbnail == (Image *) NULL)
2402 break;
2403 (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL,
2404 (void *) NULL);
cristyd15e6592011-10-15 00:13:06 +00002405 (void) SetImageProperty(thumbnail,"label",DefaultTileLabel,exception);
cristy3ed852e2009-09-05 21:47:34 +00002406 if (i == (NumberTiles/2))
2407 {
cristy9950d572011-10-01 18:22:35 +00002408 (void) QueryColorCompliance("#dfdfdf",AllCompliance,
2409 &thumbnail->matte_color,exception);
cristy3ed852e2009-09-05 21:47:34 +00002410 AppendImageToList(&images,thumbnail);
2411 continue;
2412 }
2413 switch (preview)
2414 {
2415 case RotatePreview:
2416 {
2417 degrees+=45.0;
2418 preview_image=RotateImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002419 (void) FormatLocaleString(label,MaxTextExtent,"rotate %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002420 break;
2421 }
2422 case ShearPreview:
2423 {
2424 degrees+=5.0;
2425 preview_image=ShearImage(thumbnail,degrees,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002426 (void) FormatLocaleString(label,MaxTextExtent,"shear %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002427 degrees,2.0*degrees);
2428 break;
2429 }
2430 case RollPreview:
2431 {
cristybb503372010-05-27 20:51:26 +00002432 x=(ssize_t) ((i+1)*thumbnail->columns)/NumberTiles;
2433 y=(ssize_t) ((i+1)*thumbnail->rows)/NumberTiles;
cristy3ed852e2009-09-05 21:47:34 +00002434 preview_image=RollImage(thumbnail,x,y,exception);
cristyb51dff52011-05-19 16:55:47 +00002435 (void) FormatLocaleString(label,MaxTextExtent,"roll %+.20gx%+.20g",
cristye8c25f92010-06-03 00:53:06 +00002436 (double) x,(double) y);
cristy3ed852e2009-09-05 21:47:34 +00002437 break;
2438 }
2439 case HuePreview:
2440 {
2441 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2442 if (preview_image == (Image *) NULL)
2443 break;
cristyb51dff52011-05-19 16:55:47 +00002444 (void) FormatLocaleString(factor,MaxTextExtent,"100,100,%g",
cristy3ed852e2009-09-05 21:47:34 +00002445 2.0*percentage);
cristy33bd5152011-08-24 01:42:24 +00002446 (void) ModulateImage(preview_image,factor,exception);
cristyb51dff52011-05-19 16:55:47 +00002447 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002448 break;
2449 }
2450 case SaturationPreview:
2451 {
2452 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2453 if (preview_image == (Image *) NULL)
2454 break;
cristyb51dff52011-05-19 16:55:47 +00002455 (void) FormatLocaleString(factor,MaxTextExtent,"100,%g",
cristy8cd5b312010-01-07 01:10:24 +00002456 2.0*percentage);
cristy33bd5152011-08-24 01:42:24 +00002457 (void) ModulateImage(preview_image,factor,exception);
cristyb51dff52011-05-19 16:55:47 +00002458 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002459 break;
2460 }
2461 case BrightnessPreview:
2462 {
2463 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2464 if (preview_image == (Image *) NULL)
2465 break;
cristyb51dff52011-05-19 16:55:47 +00002466 (void) FormatLocaleString(factor,MaxTextExtent,"%g",2.0*percentage);
cristy33bd5152011-08-24 01:42:24 +00002467 (void) ModulateImage(preview_image,factor,exception);
cristyb51dff52011-05-19 16:55:47 +00002468 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002469 break;
2470 }
2471 case GammaPreview:
2472 default:
2473 {
2474 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2475 if (preview_image == (Image *) NULL)
2476 break;
2477 gamma+=0.4f;
cristyb3e7c6c2011-07-24 01:43:55 +00002478 (void) GammaImage(preview_image,gamma,exception);
cristyb51dff52011-05-19 16:55:47 +00002479 (void) FormatLocaleString(label,MaxTextExtent,"gamma %g",gamma);
cristy3ed852e2009-09-05 21:47:34 +00002480 break;
2481 }
2482 case SpiffPreview:
2483 {
2484 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2485 if (preview_image != (Image *) NULL)
2486 for (x=0; x < i; x++)
cristye23ec9d2011-08-16 18:15:40 +00002487 (void) ContrastImage(preview_image,MagickTrue,exception);
cristyb51dff52011-05-19 16:55:47 +00002488 (void) FormatLocaleString(label,MaxTextExtent,"contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002489 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002490 break;
2491 }
2492 case DullPreview:
2493 {
2494 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2495 if (preview_image == (Image *) NULL)
2496 break;
2497 for (x=0; x < i; x++)
cristye23ec9d2011-08-16 18:15:40 +00002498 (void) ContrastImage(preview_image,MagickFalse,exception);
cristyb51dff52011-05-19 16:55:47 +00002499 (void) FormatLocaleString(label,MaxTextExtent,"+contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002500 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002501 break;
2502 }
2503 case GrayscalePreview:
2504 {
2505 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2506 if (preview_image == (Image *) NULL)
2507 break;
2508 colors<<=1;
2509 quantize_info.number_colors=colors;
2510 quantize_info.colorspace=GRAYColorspace;
cristy018f07f2011-09-04 21:15:19 +00002511 (void) QuantizeImage(&quantize_info,preview_image,exception);
cristyb51dff52011-05-19 16:55:47 +00002512 (void) FormatLocaleString(label,MaxTextExtent,
cristye8c25f92010-06-03 00:53:06 +00002513 "-colorspace gray -colors %.20g",(double) colors);
cristy3ed852e2009-09-05 21:47:34 +00002514 break;
2515 }
2516 case QuantizePreview:
2517 {
2518 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2519 if (preview_image == (Image *) NULL)
2520 break;
2521 colors<<=1;
2522 quantize_info.number_colors=colors;
cristy018f07f2011-09-04 21:15:19 +00002523 (void) QuantizeImage(&quantize_info,preview_image,exception);
cristyb51dff52011-05-19 16:55:47 +00002524 (void) FormatLocaleString(label,MaxTextExtent,"colors %.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00002525 colors);
cristy3ed852e2009-09-05 21:47:34 +00002526 break;
2527 }
2528 case DespecklePreview:
2529 {
2530 for (x=0; x < (i-1); x++)
2531 {
2532 preview_image=DespeckleImage(thumbnail,exception);
2533 if (preview_image == (Image *) NULL)
2534 break;
2535 thumbnail=DestroyImage(thumbnail);
2536 thumbnail=preview_image;
2537 }
2538 preview_image=DespeckleImage(thumbnail,exception);
2539 if (preview_image == (Image *) NULL)
2540 break;
cristyb51dff52011-05-19 16:55:47 +00002541 (void) FormatLocaleString(label,MaxTextExtent,"despeckle (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002542 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002543 break;
2544 }
2545 case ReduceNoisePreview:
2546 {
cristy95c38342011-03-18 22:39:51 +00002547 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) radius,
2548 (size_t) radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002549 (void) FormatLocaleString(label,MaxTextExtent,"noise %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002550 break;
2551 }
2552 case AddNoisePreview:
2553 {
2554 switch ((int) i)
2555 {
2556 case 0:
2557 {
2558 (void) CopyMagickString(factor,"uniform",MaxTextExtent);
2559 break;
2560 }
2561 case 1:
2562 {
2563 (void) CopyMagickString(factor,"gaussian",MaxTextExtent);
2564 break;
2565 }
2566 case 2:
2567 {
2568 (void) CopyMagickString(factor,"multiplicative",MaxTextExtent);
2569 break;
2570 }
2571 case 3:
2572 {
2573 (void) CopyMagickString(factor,"impulse",MaxTextExtent);
2574 break;
2575 }
2576 case 4:
2577 {
2578 (void) CopyMagickString(factor,"laplacian",MaxTextExtent);
2579 break;
2580 }
2581 case 5:
2582 {
2583 (void) CopyMagickString(factor,"Poisson",MaxTextExtent);
2584 break;
2585 }
2586 default:
2587 {
2588 (void) CopyMagickString(thumbnail->magick,"NULL",MaxTextExtent);
2589 break;
2590 }
2591 }
cristyd76c51e2011-03-26 00:21:26 +00002592 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) i,
2593 (size_t) i,exception);
cristyb51dff52011-05-19 16:55:47 +00002594 (void) FormatLocaleString(label,MaxTextExtent,"+noise %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002595 break;
2596 }
2597 case SharpenPreview:
2598 {
anthony736a1602011-10-06 12:38:17 +00002599 /* FUTURE: user bias on sharpen! This is non-sensical! */
cristy05c0c9a2011-09-05 23:16:13 +00002600 preview_image=SharpenImage(thumbnail,radius,sigma,image->bias,
2601 exception);
cristyb51dff52011-05-19 16:55:47 +00002602 (void) FormatLocaleString(label,MaxTextExtent,"sharpen %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002603 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002604 break;
2605 }
2606 case BlurPreview:
2607 {
anthony736a1602011-10-06 12:38:17 +00002608 /* FUTURE: user bias on blur! This is non-sensical! */
cristy05c0c9a2011-09-05 23:16:13 +00002609 preview_image=BlurImage(thumbnail,radius,sigma,image->bias,exception);
cristyb51dff52011-05-19 16:55:47 +00002610 (void) FormatLocaleString(label,MaxTextExtent,"blur %gx%g",radius,
cristy3ed852e2009-09-05 21:47:34 +00002611 sigma);
2612 break;
2613 }
2614 case ThresholdPreview:
2615 {
2616 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2617 if (preview_image == (Image *) NULL)
2618 break;
cristye941a752011-10-15 01:52:48 +00002619 (void) BilevelImage(thumbnail,(double) (percentage*((MagickRealType)
2620 QuantumRange+1.0))/100.0,exception);
cristyb51dff52011-05-19 16:55:47 +00002621 (void) FormatLocaleString(label,MaxTextExtent,"threshold %g",
cristy3ed852e2009-09-05 21:47:34 +00002622 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
2623 break;
2624 }
2625 case EdgeDetectPreview:
2626 {
cristy8ae632d2011-09-05 17:29:53 +00002627 preview_image=EdgeImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00002628 (void) FormatLocaleString(label,MaxTextExtent,"edge %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002629 break;
2630 }
2631 case SpreadPreview:
2632 {
cristy5c4e2582011-09-11 19:21:03 +00002633 preview_image=SpreadImage(thumbnail,radius,thumbnail->interpolate,
2634 exception);
cristyb51dff52011-05-19 16:55:47 +00002635 (void) FormatLocaleString(label,MaxTextExtent,"spread %g",
cristy8cd5b312010-01-07 01:10:24 +00002636 radius+0.5);
cristy3ed852e2009-09-05 21:47:34 +00002637 break;
2638 }
2639 case SolarizePreview:
2640 {
2641 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2642 if (preview_image == (Image *) NULL)
2643 break;
2644 (void) SolarizeImage(preview_image,(double) QuantumRange*
cristy5cbc0162011-08-29 00:36:28 +00002645 percentage/100.0,exception);
cristyb51dff52011-05-19 16:55:47 +00002646 (void) FormatLocaleString(label,MaxTextExtent,"solarize %g",
cristy3ed852e2009-09-05 21:47:34 +00002647 (QuantumRange*percentage)/100.0);
2648 break;
2649 }
2650 case ShadePreview:
2651 {
2652 degrees+=10.0;
2653 preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees,
2654 exception);
cristyb51dff52011-05-19 16:55:47 +00002655 (void) FormatLocaleString(label,MaxTextExtent,"shade %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002656 degrees,degrees);
cristy3ed852e2009-09-05 21:47:34 +00002657 break;
2658 }
2659 case RaisePreview:
2660 {
2661 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2662 if (preview_image == (Image *) NULL)
2663 break;
cristybb503372010-05-27 20:51:26 +00002664 geometry.width=(size_t) (2*i+2);
2665 geometry.height=(size_t) (2*i+2);
cristy3ed852e2009-09-05 21:47:34 +00002666 geometry.x=i/2;
2667 geometry.y=i/2;
cristy6170ac32011-08-28 14:15:37 +00002668 (void) RaiseImage(preview_image,&geometry,MagickTrue,exception);
cristyb51dff52011-05-19 16:55:47 +00002669 (void) FormatLocaleString(label,MaxTextExtent,
cristy6d8abba2010-06-03 01:10:47 +00002670 "raise %.20gx%.20g%+.20g%+.20g",(double) geometry.width,(double)
cristye8c25f92010-06-03 00:53:06 +00002671 geometry.height,(double) geometry.x,(double) geometry.y);
cristy3ed852e2009-09-05 21:47:34 +00002672 break;
2673 }
2674 case SegmentPreview:
2675 {
2676 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2677 if (preview_image == (Image *) NULL)
2678 break;
2679 threshold+=0.4f;
2680 (void) SegmentImage(preview_image,RGBColorspace,MagickFalse,threshold,
cristy018f07f2011-09-04 21:15:19 +00002681 threshold,exception);
cristyb51dff52011-05-19 16:55:47 +00002682 (void) FormatLocaleString(label,MaxTextExtent,"segment %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002683 threshold,threshold);
2684 break;
2685 }
2686 case SwirlPreview:
2687 {
cristy76f512e2011-09-12 01:26:56 +00002688 preview_image=SwirlImage(thumbnail,degrees,image->interpolate,
2689 exception);
cristyb51dff52011-05-19 16:55:47 +00002690 (void) FormatLocaleString(label,MaxTextExtent,"swirl %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002691 degrees+=45.0;
2692 break;
2693 }
2694 case ImplodePreview:
2695 {
2696 degrees+=0.1f;
cristy76f512e2011-09-12 01:26:56 +00002697 preview_image=ImplodeImage(thumbnail,degrees,image->interpolate,
2698 exception);
cristyb51dff52011-05-19 16:55:47 +00002699 (void) FormatLocaleString(label,MaxTextExtent,"implode %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002700 break;
2701 }
2702 case WavePreview:
2703 {
2704 degrees+=5.0f;
cristy5c4e2582011-09-11 19:21:03 +00002705 preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,
2706 image->interpolate,exception);
cristyb51dff52011-05-19 16:55:47 +00002707 (void) FormatLocaleString(label,MaxTextExtent,"wave %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002708 0.5*degrees,2.0*degrees);
cristy3ed852e2009-09-05 21:47:34 +00002709 break;
2710 }
2711 case OilPaintPreview:
2712 {
cristy14973ba2011-08-27 23:48:07 +00002713 preview_image=OilPaintImage(thumbnail,(double) radius,(double) sigma,
2714 exception);
2715 (void) FormatLocaleString(label,MaxTextExtent,"charcoal %gx%g",
2716 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002717 break;
2718 }
2719 case CharcoalDrawingPreview:
2720 {
anthony736a1602011-10-06 12:38:17 +00002721 /* FUTURE: user bias on charcoal! This is non-sensical! */
cristy3ed852e2009-09-05 21:47:34 +00002722 preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma,
cristy05c0c9a2011-09-05 23:16:13 +00002723 image->bias,exception);
cristyb51dff52011-05-19 16:55:47 +00002724 (void) FormatLocaleString(label,MaxTextExtent,"charcoal %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002725 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002726 break;
2727 }
2728 case JPEGPreview:
2729 {
2730 char
2731 filename[MaxTextExtent];
2732
2733 int
2734 file;
2735
2736 MagickBooleanType
2737 status;
2738
2739 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2740 if (preview_image == (Image *) NULL)
2741 break;
cristybb503372010-05-27 20:51:26 +00002742 preview_info->quality=(size_t) percentage;
cristyb51dff52011-05-19 16:55:47 +00002743 (void) FormatLocaleString(factor,MaxTextExtent,"%.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00002744 preview_info->quality);
cristy3ed852e2009-09-05 21:47:34 +00002745 file=AcquireUniqueFileResource(filename);
2746 if (file != -1)
2747 file=close(file)-1;
cristyb51dff52011-05-19 16:55:47 +00002748 (void) FormatLocaleString(preview_image->filename,MaxTextExtent,
cristy3ed852e2009-09-05 21:47:34 +00002749 "jpeg:%s",filename);
cristy6f9e0d32011-08-28 16:32:09 +00002750 status=WriteImage(preview_info,preview_image,exception);
cristy3ed852e2009-09-05 21:47:34 +00002751 if (status != MagickFalse)
2752 {
2753 Image
2754 *quality_image;
2755
2756 (void) CopyMagickString(preview_info->filename,
2757 preview_image->filename,MaxTextExtent);
2758 quality_image=ReadImage(preview_info,exception);
2759 if (quality_image != (Image *) NULL)
2760 {
2761 preview_image=DestroyImage(preview_image);
2762 preview_image=quality_image;
2763 }
2764 }
2765 (void) RelinquishUniqueFileResource(preview_image->filename);
2766 if ((GetBlobSize(preview_image)/1024) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00002767 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%gmb ",
cristy3ed852e2009-09-05 21:47:34 +00002768 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
2769 1024.0/1024.0);
2770 else
2771 if (GetBlobSize(preview_image) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00002772 (void) FormatLocaleString(label,MaxTextExtent,
cristye7f51092010-01-17 00:39:37 +00002773 "quality %s\n%gkb ",factor,(double) ((MagickOffsetType)
cristy8cd5b312010-01-07 01:10:24 +00002774 GetBlobSize(preview_image))/1024.0);
cristy3ed852e2009-09-05 21:47:34 +00002775 else
cristyb51dff52011-05-19 16:55:47 +00002776 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%.20gb ",
cristy54ea5732011-06-10 12:39:53 +00002777 factor,(double) ((MagickOffsetType) GetBlobSize(thumbnail)));
cristy3ed852e2009-09-05 21:47:34 +00002778 break;
2779 }
2780 }
2781 thumbnail=DestroyImage(thumbnail);
2782 percentage+=12.5;
2783 radius+=0.5;
2784 sigma+=0.25;
2785 if (preview_image == (Image *) NULL)
2786 break;
2787 (void) DeleteImageProperty(preview_image,"label");
cristyd15e6592011-10-15 00:13:06 +00002788 (void) SetImageProperty(preview_image,"label",label,exception);
cristy3ed852e2009-09-05 21:47:34 +00002789 AppendImageToList(&images,preview_image);
cristybb503372010-05-27 20:51:26 +00002790 proceed=SetImageProgress(image,PreviewImageTag,(MagickOffsetType) i,
2791 NumberTiles);
cristy3ed852e2009-09-05 21:47:34 +00002792 if (proceed == MagickFalse)
2793 break;
2794 }
2795 if (images == (Image *) NULL)
2796 {
2797 preview_info=DestroyImageInfo(preview_info);
2798 return((Image *) NULL);
2799 }
2800 /*
2801 Create the montage.
2802 */
2803 montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL);
2804 (void) CopyMagickString(montage_info->filename,image->filename,MaxTextExtent);
2805 montage_info->shadow=MagickTrue;
2806 (void) CloneString(&montage_info->tile,"3x3");
2807 (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry);
2808 (void) CloneString(&montage_info->frame,DefaultTileFrame);
2809 montage_image=MontageImages(images,montage_info,exception);
2810 montage_info=DestroyMontageInfo(montage_info);
2811 images=DestroyImageList(images);
2812 if (montage_image == (Image *) NULL)
2813 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2814 if (montage_image->montage != (char *) NULL)
2815 {
2816 /*
2817 Free image directory.
2818 */
2819 montage_image->montage=(char *) RelinquishMagickMemory(
2820 montage_image->montage);
2821 if (image->directory != (char *) NULL)
2822 montage_image->directory=(char *) RelinquishMagickMemory(
2823 montage_image->directory);
2824 }
2825 preview_info=DestroyImageInfo(preview_info);
2826 return(montage_image);
2827}
2828
2829/*
2830%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2831% %
2832% %
2833% %
2834% R a d i a l B l u r I m a g e %
2835% %
2836% %
2837% %
2838%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2839%
2840% RadialBlurImage() applies a radial blur to the image.
2841%
2842% Andrew Protano contributed this effect.
2843%
2844% The format of the RadialBlurImage method is:
2845%
2846% Image *RadialBlurImage(const Image *image,const double angle,
cristy6435bd92011-09-10 02:10:07 +00002847% const double blur,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002848%
2849% A description of each parameter follows:
2850%
2851% o image: the image.
2852%
cristy3ed852e2009-09-05 21:47:34 +00002853% o angle: the angle of the radial blur.
2854%
cristy6435bd92011-09-10 02:10:07 +00002855% o blur: the blur.
2856%
cristy3ed852e2009-09-05 21:47:34 +00002857% o exception: return any errors or warnings in this structure.
2858%
2859*/
cristy4282c702011-11-21 00:01:06 +00002860MagickExport Image *RadialBlurImage(const Image *image,const double angle,
2861 const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002862{
cristyc4c8d132010-01-07 01:58:38 +00002863 CacheView
2864 *blur_view,
2865 *image_view;
2866
cristy3ed852e2009-09-05 21:47:34 +00002867 Image
2868 *blur_image;
2869
cristy3ed852e2009-09-05 21:47:34 +00002870 MagickBooleanType
2871 status;
2872
cristybb503372010-05-27 20:51:26 +00002873 MagickOffsetType
2874 progress;
2875
cristy3ed852e2009-09-05 21:47:34 +00002876 MagickRealType
2877 blur_radius,
2878 *cos_theta,
2879 offset,
2880 *sin_theta,
2881 theta;
2882
2883 PointInfo
2884 blur_center;
2885
cristybb503372010-05-27 20:51:26 +00002886 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002887 i;
2888
cristybb503372010-05-27 20:51:26 +00002889 size_t
cristy3ed852e2009-09-05 21:47:34 +00002890 n;
2891
cristybb503372010-05-27 20:51:26 +00002892 ssize_t
2893 y;
2894
cristy3ed852e2009-09-05 21:47:34 +00002895 /*
2896 Allocate blur image.
2897 */
2898 assert(image != (Image *) NULL);
2899 assert(image->signature == MagickSignature);
2900 if (image->debug != MagickFalse)
2901 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2902 assert(exception != (ExceptionInfo *) NULL);
2903 assert(exception->signature == MagickSignature);
cristy1e7aa312011-09-10 20:01:36 +00002904 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00002905 if (blur_image == (Image *) NULL)
2906 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00002907 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00002908 {
cristy3ed852e2009-09-05 21:47:34 +00002909 blur_image=DestroyImage(blur_image);
2910 return((Image *) NULL);
2911 }
2912 blur_center.x=(double) image->columns/2.0;
2913 blur_center.y=(double) image->rows/2.0;
2914 blur_radius=hypot(blur_center.x,blur_center.y);
cristy117ff172010-08-15 21:35:32 +00002915 n=(size_t) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+2UL);
cristy3ed852e2009-09-05 21:47:34 +00002916 theta=DegreesToRadians(angle)/(MagickRealType) (n-1);
2917 cos_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
2918 sizeof(*cos_theta));
2919 sin_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
2920 sizeof(*sin_theta));
2921 if ((cos_theta == (MagickRealType *) NULL) ||
2922 (sin_theta == (MagickRealType *) NULL))
2923 {
2924 blur_image=DestroyImage(blur_image);
2925 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2926 }
2927 offset=theta*(MagickRealType) (n-1)/2.0;
cristybb503372010-05-27 20:51:26 +00002928 for (i=0; i < (ssize_t) n; i++)
cristy3ed852e2009-09-05 21:47:34 +00002929 {
2930 cos_theta[i]=cos((double) (theta*i-offset));
2931 sin_theta[i]=sin((double) (theta*i-offset));
2932 }
2933 /*
2934 Radial blur image.
2935 */
2936 status=MagickTrue;
2937 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00002938 image_view=AcquireCacheView(image);
2939 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00002940#if defined(MAGICKCORE_OPENMP_SUPPORT)
2941 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002942#endif
cristy1e7aa312011-09-10 20:01:36 +00002943 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002944 {
cristy1e7aa312011-09-10 20:01:36 +00002945 register const Quantum
2946 *restrict p;
2947
cristy4c08aed2011-07-01 19:47:50 +00002948 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002949 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002950
cristy117ff172010-08-15 21:35:32 +00002951 register ssize_t
2952 x;
2953
cristy3ed852e2009-09-05 21:47:34 +00002954 if (status == MagickFalse)
2955 continue;
cristy1e7aa312011-09-10 20:01:36 +00002956 p=GetCacheViewVirtualPixels(blur_view,0,y,image->columns,1,exception);
cristy3ed852e2009-09-05 21:47:34 +00002957 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
2958 exception);
cristy1e7aa312011-09-10 20:01:36 +00002959 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00002960 {
2961 status=MagickFalse;
2962 continue;
2963 }
cristy1e7aa312011-09-10 20:01:36 +00002964 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002965 {
cristy3ed852e2009-09-05 21:47:34 +00002966 MagickRealType
cristy3ed852e2009-09-05 21:47:34 +00002967 radius;
2968
cristy3ed852e2009-09-05 21:47:34 +00002969 PointInfo
2970 center;
2971
cristybb503372010-05-27 20:51:26 +00002972 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002973 i;
2974
cristybb503372010-05-27 20:51:26 +00002975 size_t
cristy3ed852e2009-09-05 21:47:34 +00002976 step;
2977
2978 center.x=(double) x-blur_center.x;
2979 center.y=(double) y-blur_center.y;
2980 radius=hypot((double) center.x,center.y);
2981 if (radius == 0)
2982 step=1;
2983 else
2984 {
cristybb503372010-05-27 20:51:26 +00002985 step=(size_t) (blur_radius/radius);
cristy3ed852e2009-09-05 21:47:34 +00002986 if (step == 0)
2987 step=1;
2988 else
2989 if (step >= n)
2990 step=n-1;
2991 }
cristy1e7aa312011-09-10 20:01:36 +00002992 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2993 {
2994 MagickRealType
2995 gamma,
2996 pixel;
cristy3ed852e2009-09-05 21:47:34 +00002997
cristy1e7aa312011-09-10 20:01:36 +00002998 PixelChannel
2999 channel;
3000
3001 PixelTrait
3002 blur_traits,
3003 traits;
3004
3005 register const Quantum
3006 *restrict r;
3007
3008 register ssize_t
3009 j;
3010
cristye2a912b2011-12-05 20:02:07 +00003011 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +00003012 traits=GetPixelChannelMapTraits(image,channel);
cristy1e7aa312011-09-10 20:01:36 +00003013 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
3014 if ((traits == UndefinedPixelTrait) ||
3015 (blur_traits == UndefinedPixelTrait))
3016 continue;
3017 if ((blur_traits & CopyPixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003018 {
cristy0beccfa2011-09-25 20:47:53 +00003019 SetPixelChannel(blur_image,channel,p[i],q);
cristy1e7aa312011-09-10 20:01:36 +00003020 continue;
cristy3ed852e2009-09-05 21:47:34 +00003021 }
cristy1e7aa312011-09-10 20:01:36 +00003022 gamma=0.0;
3023 pixel=bias;
3024 if ((blur_traits & BlendPixelTrait) == 0)
3025 {
3026 for (j=0; j < (ssize_t) n; j+=(ssize_t) step)
3027 {
3028 r=GetCacheViewVirtualPixels(image_view, (ssize_t) (blur_center.x+
3029 center.x*cos_theta[j]-center.y*sin_theta[j]+0.5),(ssize_t)
3030 (blur_center.y+center.x*sin_theta[j]+center.y*cos_theta[j]+0.5),
3031 1,1,exception);
3032 if (r == (const Quantum *) NULL)
3033 {
3034 status=MagickFalse;
3035 continue;
3036 }
3037 pixel+=r[i];
3038 gamma++;
3039 }
3040 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +00003041 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy1e7aa312011-09-10 20:01:36 +00003042 continue;
3043 }
3044 for (j=0; j < (ssize_t) n; j+=(ssize_t) step)
3045 {
3046 r=GetCacheViewVirtualPixels(image_view, (ssize_t) (blur_center.x+
3047 center.x*cos_theta[j]-center.y*sin_theta[j]+0.5),(ssize_t)
3048 (blur_center.y+center.x*sin_theta[j]+center.y*cos_theta[j]+0.5),
3049 1,1,exception);
3050 if (r == (const Quantum *) NULL)
3051 {
3052 status=MagickFalse;
3053 continue;
3054 }
3055 pixel+=GetPixelAlpha(image,r)*r[i];
3056 gamma+=GetPixelAlpha(image,r);
cristy3ed852e2009-09-05 21:47:34 +00003057 }
cristy1e7aa312011-09-10 20:01:36 +00003058 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +00003059 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy1e7aa312011-09-10 20:01:36 +00003060 }
3061 p+=GetPixelChannels(image);
cristyed231572011-07-14 02:18:59 +00003062 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003063 }
3064 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
3065 status=MagickFalse;
3066 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3067 {
3068 MagickBooleanType
3069 proceed;
3070
cristyb5d5f722009-11-04 03:03:49 +00003071#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00003072 #pragma omp critical (MagickCore_RadialBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00003073#endif
3074 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
3075 if (proceed == MagickFalse)
3076 status=MagickFalse;
3077 }
3078 }
3079 blur_view=DestroyCacheView(blur_view);
3080 image_view=DestroyCacheView(image_view);
3081 cos_theta=(MagickRealType *) RelinquishMagickMemory(cos_theta);
3082 sin_theta=(MagickRealType *) RelinquishMagickMemory(sin_theta);
3083 if (status == MagickFalse)
3084 blur_image=DestroyImage(blur_image);
3085 return(blur_image);
3086}
3087
3088/*
3089%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3090% %
3091% %
3092% %
cristy3ed852e2009-09-05 21:47:34 +00003093% S e l e c t i v e B l u r I m a g e %
3094% %
3095% %
3096% %
3097%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3098%
3099% SelectiveBlurImage() selectively blur pixels within a contrast threshold.
3100% It is similar to the unsharpen mask that sharpens everything with contrast
3101% above a certain threshold.
3102%
3103% The format of the SelectiveBlurImage method is:
3104%
3105% Image *SelectiveBlurImage(const Image *image,const double radius,
cristy1e7aa312011-09-10 20:01:36 +00003106% const double sigma,const double threshold,const double bias,
3107% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003108%
3109% A description of each parameter follows:
3110%
3111% o image: the image.
3112%
cristy3ed852e2009-09-05 21:47:34 +00003113% o radius: the radius of the Gaussian, in pixels, not counting the center
3114% pixel.
3115%
3116% o sigma: the standard deviation of the Gaussian, in pixels.
3117%
3118% o threshold: only pixels within this contrast threshold are included
3119% in the blur operation.
3120%
cristy1e7aa312011-09-10 20:01:36 +00003121% o bias: the bias.
3122%
cristy3ed852e2009-09-05 21:47:34 +00003123% o exception: return any errors or warnings in this structure.
3124%
3125*/
cristy4282c702011-11-21 00:01:06 +00003126MagickExport Image *SelectiveBlurImage(const Image *image,const double radius,
3127 const double sigma,const double threshold,const double bias,
3128 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003129{
3130#define SelectiveBlurImageTag "SelectiveBlur/Image"
3131
cristy47e00502009-12-17 19:19:57 +00003132 CacheView
3133 *blur_view,
3134 *image_view;
3135
cristy3ed852e2009-09-05 21:47:34 +00003136 double
cristy3ed852e2009-09-05 21:47:34 +00003137 *kernel;
3138
3139 Image
3140 *blur_image;
3141
cristy3ed852e2009-09-05 21:47:34 +00003142 MagickBooleanType
3143 status;
3144
cristybb503372010-05-27 20:51:26 +00003145 MagickOffsetType
3146 progress;
3147
cristybb503372010-05-27 20:51:26 +00003148 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003149 i;
cristy3ed852e2009-09-05 21:47:34 +00003150
cristybb503372010-05-27 20:51:26 +00003151 size_t
cristy3ed852e2009-09-05 21:47:34 +00003152 width;
3153
cristybb503372010-05-27 20:51:26 +00003154 ssize_t
cristyc8523c12011-09-13 00:02:53 +00003155 center,
cristybb503372010-05-27 20:51:26 +00003156 j,
3157 u,
3158 v,
3159 y;
3160
cristy3ed852e2009-09-05 21:47:34 +00003161 /*
3162 Initialize blur image attributes.
3163 */
3164 assert(image != (Image *) NULL);
3165 assert(image->signature == MagickSignature);
3166 if (image->debug != MagickFalse)
3167 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3168 assert(exception != (ExceptionInfo *) NULL);
3169 assert(exception->signature == MagickSignature);
3170 width=GetOptimalKernelWidth1D(radius,sigma);
3171 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
3172 if (kernel == (double *) NULL)
3173 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00003174 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00003175 i=0;
cristy47e00502009-12-17 19:19:57 +00003176 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00003177 {
cristy47e00502009-12-17 19:19:57 +00003178 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00003179 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
3180 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00003181 }
3182 if (image->debug != MagickFalse)
3183 {
3184 char
3185 format[MaxTextExtent],
3186 *message;
3187
cristy117ff172010-08-15 21:35:32 +00003188 register const double
3189 *k;
3190
cristybb503372010-05-27 20:51:26 +00003191 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003192 u,
3193 v;
3194
cristy3ed852e2009-09-05 21:47:34 +00003195 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00003196 " SelectiveBlurImage with %.20gx%.20g kernel:",(double) width,(double)
3197 width);
cristy3ed852e2009-09-05 21:47:34 +00003198 message=AcquireString("");
3199 k=kernel;
cristybb503372010-05-27 20:51:26 +00003200 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003201 {
3202 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00003203 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy3ed852e2009-09-05 21:47:34 +00003204 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00003205 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003206 {
cristyb51dff52011-05-19 16:55:47 +00003207 (void) FormatLocaleString(format,MaxTextExtent,"%+f ",*k++);
cristy3ed852e2009-09-05 21:47:34 +00003208 (void) ConcatenateString(&message,format);
3209 }
3210 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
3211 }
3212 message=DestroyString(message);
3213 }
cristy1e7aa312011-09-10 20:01:36 +00003214 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00003215 if (blur_image == (Image *) NULL)
3216 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00003217 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003218 {
cristy3ed852e2009-09-05 21:47:34 +00003219 blur_image=DestroyImage(blur_image);
3220 return((Image *) NULL);
3221 }
3222 /*
3223 Threshold blur image.
3224 */
3225 status=MagickTrue;
3226 progress=0;
cristyc8523c12011-09-13 00:02:53 +00003227 center=(ssize_t) (GetPixelChannels(image)*(image->columns+width)*(width/2L)+
3228 GetPixelChannels(image)*(width/2L));
cristy3ed852e2009-09-05 21:47:34 +00003229 image_view=AcquireCacheView(image);
3230 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003231#if defined(MAGICKCORE_OPENMP_SUPPORT)
3232 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003233#endif
cristybb503372010-05-27 20:51:26 +00003234 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003235 {
cristy4c08aed2011-07-01 19:47:50 +00003236 double
3237 contrast;
3238
cristy3ed852e2009-09-05 21:47:34 +00003239 MagickBooleanType
3240 sync;
3241
cristy4c08aed2011-07-01 19:47:50 +00003242 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003243 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003244
cristy4c08aed2011-07-01 19:47:50 +00003245 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003246 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003247
cristy117ff172010-08-15 21:35:32 +00003248 register ssize_t
3249 x;
3250
cristy3ed852e2009-09-05 21:47:34 +00003251 if (status == MagickFalse)
3252 continue;
cristy117ff172010-08-15 21:35:32 +00003253 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
3254 (width/2L),image->columns+width,width,exception);
cristy3ed852e2009-09-05 21:47:34 +00003255 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3256 exception);
cristy4c08aed2011-07-01 19:47:50 +00003257 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003258 {
3259 status=MagickFalse;
3260 continue;
3261 }
cristybb503372010-05-27 20:51:26 +00003262 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003263 {
cristybb503372010-05-27 20:51:26 +00003264 register ssize_t
cristy1e7aa312011-09-10 20:01:36 +00003265 i;
cristy3ed852e2009-09-05 21:47:34 +00003266
cristy1e7aa312011-09-10 20:01:36 +00003267 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3268 {
3269 MagickRealType
3270 alpha,
3271 gamma,
3272 intensity,
3273 pixel;
cristy117ff172010-08-15 21:35:32 +00003274
cristy1e7aa312011-09-10 20:01:36 +00003275 PixelChannel
3276 channel;
3277
3278 PixelTrait
3279 blur_traits,
3280 traits;
3281
3282 register const double
3283 *restrict k;
3284
3285 register const Quantum
3286 *restrict pixels;
3287
3288 register ssize_t
3289 u;
3290
3291 ssize_t
3292 v;
3293
cristye2a912b2011-12-05 20:02:07 +00003294 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +00003295 traits=GetPixelChannelMapTraits(image,channel);
cristy1e7aa312011-09-10 20:01:36 +00003296 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
3297 if ((traits == UndefinedPixelTrait) ||
3298 (blur_traits == UndefinedPixelTrait))
3299 continue;
3300 if ((blur_traits & CopyPixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003301 {
cristy0beccfa2011-09-25 20:47:53 +00003302 SetPixelChannel(blur_image,channel,p[center+i],q);
cristy1e7aa312011-09-10 20:01:36 +00003303 continue;
cristy3ed852e2009-09-05 21:47:34 +00003304 }
cristy1e7aa312011-09-10 20:01:36 +00003305 k=kernel;
3306 pixel=bias;
3307 pixels=p;
cristyc8523c12011-09-13 00:02:53 +00003308 intensity=(MagickRealType) GetPixelIntensity(image,p+center);
cristy1e7aa312011-09-10 20:01:36 +00003309 gamma=0.0;
3310 if ((blur_traits & BlendPixelTrait) == 0)
cristy3ed852e2009-09-05 21:47:34 +00003311 {
cristy1e7aa312011-09-10 20:01:36 +00003312 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003313 {
cristy1e7aa312011-09-10 20:01:36 +00003314 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003315 {
cristy1e7aa312011-09-10 20:01:36 +00003316 contrast=GetPixelIntensity(image,pixels)-intensity;
3317 if (fabs(contrast) < threshold)
3318 {
3319 pixel+=(*k)*pixels[i];
3320 gamma+=(*k);
3321 }
3322 k++;
3323 pixels+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003324 }
cristy1e7aa312011-09-10 20:01:36 +00003325 pixels+=image->columns*GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003326 }
cristy1e7aa312011-09-10 20:01:36 +00003327 if (fabs((double) gamma) < MagickEpsilon)
3328 {
cristy0beccfa2011-09-25 20:47:53 +00003329 SetPixelChannel(blur_image,channel,p[center+i],q);
cristy1e7aa312011-09-10 20:01:36 +00003330 continue;
3331 }
3332 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +00003333 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy1e7aa312011-09-10 20:01:36 +00003334 continue;
3335 }
3336 for (v=0; v < (ssize_t) width; v++)
3337 {
3338 for (u=0; u < (ssize_t) width; u++)
3339 {
3340 contrast=GetPixelIntensity(image,pixels)-intensity;
3341 if (fabs(contrast) < threshold)
3342 {
3343 alpha=(MagickRealType) (QuantumScale*
3344 GetPixelAlpha(image,pixels));
3345 pixel+=(*k)*alpha*pixels[i];
3346 gamma+=(*k)*alpha;
3347 }
3348 k++;
3349 pixels+=GetPixelChannels(image);
3350 }
3351 pixels+=image->columns*GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003352 }
cristy1e7aa312011-09-10 20:01:36 +00003353 if (fabs((double) gamma) < MagickEpsilon)
3354 {
cristy0beccfa2011-09-25 20:47:53 +00003355 SetPixelChannel(blur_image,channel,p[center+i],q);
cristy1e7aa312011-09-10 20:01:36 +00003356 continue;
3357 }
3358 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +00003359 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy1e7aa312011-09-10 20:01:36 +00003360 }
cristyed231572011-07-14 02:18:59 +00003361 p+=GetPixelChannels(image);
3362 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003363 }
3364 sync=SyncCacheViewAuthenticPixels(blur_view,exception);
3365 if (sync == MagickFalse)
3366 status=MagickFalse;
3367 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3368 {
3369 MagickBooleanType
3370 proceed;
3371
cristyb5d5f722009-11-04 03:03:49 +00003372#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00003373 #pragma omp critical (MagickCore_SelectiveBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00003374#endif
3375 proceed=SetImageProgress(image,SelectiveBlurImageTag,progress++,
3376 image->rows);
3377 if (proceed == MagickFalse)
3378 status=MagickFalse;
3379 }
3380 }
3381 blur_image->type=image->type;
3382 blur_view=DestroyCacheView(blur_view);
3383 image_view=DestroyCacheView(image_view);
3384 kernel=(double *) RelinquishMagickMemory(kernel);
3385 if (status == MagickFalse)
3386 blur_image=DestroyImage(blur_image);
3387 return(blur_image);
3388}
3389
3390/*
3391%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3392% %
3393% %
3394% %
3395% S h a d e I m a g e %
3396% %
3397% %
3398% %
3399%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3400%
3401% ShadeImage() shines a distant light on an image to create a
3402% three-dimensional effect. You control the positioning of the light with
3403% azimuth and elevation; azimuth is measured in degrees off the x axis
3404% and elevation is measured in pixels above the Z axis.
3405%
3406% The format of the ShadeImage method is:
3407%
3408% Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3409% const double azimuth,const double elevation,ExceptionInfo *exception)
3410%
3411% A description of each parameter follows:
3412%
3413% o image: the image.
3414%
3415% o gray: A value other than zero shades the intensity of each pixel.
3416%
3417% o azimuth, elevation: Define the light source direction.
3418%
3419% o exception: return any errors or warnings in this structure.
3420%
3421*/
3422MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3423 const double azimuth,const double elevation,ExceptionInfo *exception)
3424{
3425#define ShadeImageTag "Shade/Image"
3426
cristyc4c8d132010-01-07 01:58:38 +00003427 CacheView
3428 *image_view,
3429 *shade_view;
3430
cristy3ed852e2009-09-05 21:47:34 +00003431 Image
3432 *shade_image;
3433
cristy3ed852e2009-09-05 21:47:34 +00003434 MagickBooleanType
3435 status;
3436
cristybb503372010-05-27 20:51:26 +00003437 MagickOffsetType
3438 progress;
3439
cristy3ed852e2009-09-05 21:47:34 +00003440 PrimaryInfo
3441 light;
3442
cristybb503372010-05-27 20:51:26 +00003443 ssize_t
3444 y;
3445
cristy3ed852e2009-09-05 21:47:34 +00003446 /*
3447 Initialize shaded image attributes.
3448 */
3449 assert(image != (const Image *) NULL);
3450 assert(image->signature == MagickSignature);
3451 if (image->debug != MagickFalse)
3452 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3453 assert(exception != (ExceptionInfo *) NULL);
3454 assert(exception->signature == MagickSignature);
3455 shade_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
3456 if (shade_image == (Image *) NULL)
3457 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00003458 if (SetImageStorageClass(shade_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003459 {
cristy3ed852e2009-09-05 21:47:34 +00003460 shade_image=DestroyImage(shade_image);
3461 return((Image *) NULL);
3462 }
3463 /*
3464 Compute the light vector.
3465 */
3466 light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))*
3467 cos(DegreesToRadians(elevation));
3468 light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))*
3469 cos(DegreesToRadians(elevation));
3470 light.z=(double) QuantumRange*sin(DegreesToRadians(elevation));
3471 /*
3472 Shade image.
3473 */
3474 status=MagickTrue;
3475 progress=0;
3476 image_view=AcquireCacheView(image);
3477 shade_view=AcquireCacheView(shade_image);
cristyb5d5f722009-11-04 03:03:49 +00003478#if defined(MAGICKCORE_OPENMP_SUPPORT)
3479 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003480#endif
cristybb503372010-05-27 20:51:26 +00003481 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003482 {
3483 MagickRealType
3484 distance,
3485 normal_distance,
3486 shade;
3487
3488 PrimaryInfo
3489 normal;
3490
cristy4c08aed2011-07-01 19:47:50 +00003491 register const Quantum
cristy1e7aa312011-09-10 20:01:36 +00003492 *restrict center,
cristyc47d1f82009-11-26 01:44:43 +00003493 *restrict p,
cristy1e7aa312011-09-10 20:01:36 +00003494 *restrict post,
3495 *restrict pre;
cristy3ed852e2009-09-05 21:47:34 +00003496
cristy4c08aed2011-07-01 19:47:50 +00003497 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003498 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003499
cristy117ff172010-08-15 21:35:32 +00003500 register ssize_t
3501 x;
3502
cristy3ed852e2009-09-05 21:47:34 +00003503 if (status == MagickFalse)
3504 continue;
3505 p=GetCacheViewVirtualPixels(image_view,-1,y-1,image->columns+2,3,exception);
3506 q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1,
3507 exception);
cristy4c08aed2011-07-01 19:47:50 +00003508 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003509 {
3510 status=MagickFalse;
3511 continue;
3512 }
3513 /*
3514 Shade this row of pixels.
3515 */
3516 normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */
cristy1e7aa312011-09-10 20:01:36 +00003517 pre=p+GetPixelChannels(image);
3518 center=pre+(image->columns+2)*GetPixelChannels(image);
3519 post=center+(image->columns+2)*GetPixelChannels(image);
cristybb503372010-05-27 20:51:26 +00003520 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003521 {
cristy1e7aa312011-09-10 20:01:36 +00003522 register ssize_t
3523 i;
3524
cristy3ed852e2009-09-05 21:47:34 +00003525 /*
3526 Determine the surface normal and compute shading.
3527 */
cristy1e7aa312011-09-10 20:01:36 +00003528 normal.x=(double) (GetPixelIntensity(image,pre-GetPixelChannels(image))+
3529 GetPixelIntensity(image,center-GetPixelChannels(image))+
3530 GetPixelIntensity(image,post-GetPixelChannels(image))-
3531 GetPixelIntensity(image,pre+GetPixelChannels(image))-
3532 GetPixelIntensity(image,center+GetPixelChannels(image))-
3533 GetPixelIntensity(image,post+GetPixelChannels(image)));
3534 normal.y=(double) (GetPixelIntensity(image,post-GetPixelChannels(image))+
3535 GetPixelIntensity(image,post)+GetPixelIntensity(image,post+
3536 GetPixelChannels(image))-GetPixelIntensity(image,pre-
3537 GetPixelChannels(image))-GetPixelIntensity(image,pre)-
3538 GetPixelIntensity(image,pre+GetPixelChannels(image)));
cristy3ed852e2009-09-05 21:47:34 +00003539 if ((normal.x == 0.0) && (normal.y == 0.0))
3540 shade=light.z;
3541 else
3542 {
3543 shade=0.0;
3544 distance=normal.x*light.x+normal.y*light.y+normal.z*light.z;
3545 if (distance > MagickEpsilon)
3546 {
3547 normal_distance=
3548 normal.x*normal.x+normal.y*normal.y+normal.z*normal.z;
3549 if (normal_distance > (MagickEpsilon*MagickEpsilon))
3550 shade=distance/sqrt((double) normal_distance);
3551 }
3552 }
cristy1e7aa312011-09-10 20:01:36 +00003553 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3554 {
3555 PixelChannel
3556 channel;
3557
3558 PixelTrait
3559 shade_traits,
3560 traits;
3561
cristye2a912b2011-12-05 20:02:07 +00003562 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +00003563 traits=GetPixelChannelMapTraits(image,channel);
cristy1e7aa312011-09-10 20:01:36 +00003564 shade_traits=GetPixelChannelMapTraits(shade_image,channel);
3565 if ((traits == UndefinedPixelTrait) ||
3566 (shade_traits == UndefinedPixelTrait))
3567 continue;
3568 if ((shade_traits & CopyPixelTrait) != 0)
3569 {
cristy0beccfa2011-09-25 20:47:53 +00003570 SetPixelChannel(shade_image,channel,center[i],q);
cristy1e7aa312011-09-10 20:01:36 +00003571 continue;
3572 }
3573 if (gray != MagickFalse)
3574 {
cristy0beccfa2011-09-25 20:47:53 +00003575 SetPixelChannel(shade_image,channel,ClampToQuantum(shade),q);
cristy1e7aa312011-09-10 20:01:36 +00003576 continue;
3577 }
cristy0beccfa2011-09-25 20:47:53 +00003578 SetPixelChannel(shade_image,channel,ClampToQuantum(QuantumScale*shade*
3579 center[i]),q);
cristy1e7aa312011-09-10 20:01:36 +00003580 }
3581 pre+=GetPixelChannels(image);
3582 center+=GetPixelChannels(image);
3583 post+=GetPixelChannels(image);
cristyed231572011-07-14 02:18:59 +00003584 q+=GetPixelChannels(shade_image);
cristy3ed852e2009-09-05 21:47:34 +00003585 }
3586 if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
3587 status=MagickFalse;
3588 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3589 {
3590 MagickBooleanType
3591 proceed;
3592
cristyb5d5f722009-11-04 03:03:49 +00003593#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003594 #pragma omp critical (MagickCore_ShadeImage)
3595#endif
3596 proceed=SetImageProgress(image,ShadeImageTag,progress++,image->rows);
3597 if (proceed == MagickFalse)
3598 status=MagickFalse;
3599 }
3600 }
3601 shade_view=DestroyCacheView(shade_view);
3602 image_view=DestroyCacheView(image_view);
3603 if (status == MagickFalse)
3604 shade_image=DestroyImage(shade_image);
3605 return(shade_image);
3606}
3607
3608/*
3609%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3610% %
3611% %
3612% %
3613% S h a r p e n I m a g e %
3614% %
3615% %
3616% %
3617%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3618%
3619% SharpenImage() sharpens the image. We convolve the image with a Gaussian
3620% operator of the given radius and standard deviation (sigma). For
3621% reasonable results, radius should be larger than sigma. Use a radius of 0
3622% and SharpenImage() selects a suitable radius for you.
3623%
3624% Using a separable kernel would be faster, but the negative weights cancel
3625% out on the corners of the kernel producing often undesirable ringing in the
3626% filtered result; this can be avoided by using a 2D gaussian shaped image
3627% sharpening kernel instead.
3628%
3629% The format of the SharpenImage method is:
3630%
3631% Image *SharpenImage(const Image *image,const double radius,
cristy05c0c9a2011-09-05 23:16:13 +00003632% const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003633%
3634% A description of each parameter follows:
3635%
3636% o image: the image.
3637%
cristy3ed852e2009-09-05 21:47:34 +00003638% o radius: the radius of the Gaussian, in pixels, not counting the center
3639% pixel.
3640%
3641% o sigma: the standard deviation of the Laplacian, in pixels.
3642%
cristy05c0c9a2011-09-05 23:16:13 +00003643% o bias: bias.
3644%
cristy3ed852e2009-09-05 21:47:34 +00003645% o exception: return any errors or warnings in this structure.
3646%
3647*/
cristy3ed852e2009-09-05 21:47:34 +00003648MagickExport Image *SharpenImage(const Image *image,const double radius,
cristy05c0c9a2011-09-05 23:16:13 +00003649 const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003650{
cristy3ed852e2009-09-05 21:47:34 +00003651 double
cristy47e00502009-12-17 19:19:57 +00003652 normalize;
cristy3ed852e2009-09-05 21:47:34 +00003653
3654 Image
3655 *sharp_image;
3656
cristy41cbe682011-07-15 19:12:37 +00003657 KernelInfo
3658 *kernel_info;
3659
cristybb503372010-05-27 20:51:26 +00003660 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003661 i;
3662
cristybb503372010-05-27 20:51:26 +00003663 size_t
cristy3ed852e2009-09-05 21:47:34 +00003664 width;
3665
cristy117ff172010-08-15 21:35:32 +00003666 ssize_t
3667 j,
3668 u,
3669 v;
3670
cristy3ed852e2009-09-05 21:47:34 +00003671 assert(image != (const Image *) NULL);
3672 assert(image->signature == MagickSignature);
3673 if (image->debug != MagickFalse)
3674 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3675 assert(exception != (ExceptionInfo *) NULL);
3676 assert(exception->signature == MagickSignature);
3677 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00003678 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00003679 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003680 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00003681 (void) ResetMagickMemory(kernel_info,0,sizeof(*kernel_info));
3682 kernel_info->width=width;
3683 kernel_info->height=width;
anthony736a1602011-10-06 12:38:17 +00003684 kernel_info->bias=bias; /* FUTURE: user bias - non-sensical! */
cristy41cbe682011-07-15 19:12:37 +00003685 kernel_info->signature=MagickSignature;
cristya96f2492011-12-14 18:25:41 +00003686 kernel_info->values=(MagickRealType *) AcquireAlignedMemory(
3687 kernel_info->width,kernel_info->width*sizeof(*kernel_info->values));
3688 if (kernel_info->values == (MagickRealType *) NULL)
cristy41cbe682011-07-15 19:12:37 +00003689 {
3690 kernel_info=DestroyKernelInfo(kernel_info);
3691 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3692 }
cristy3ed852e2009-09-05 21:47:34 +00003693 normalize=0.0;
cristy41cbe682011-07-15 19:12:37 +00003694 j=(ssize_t) kernel_info->width/2;
cristy47e00502009-12-17 19:19:57 +00003695 i=0;
3696 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00003697 {
cristy47e00502009-12-17 19:19:57 +00003698 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00003699 {
cristy41cbe682011-07-15 19:12:37 +00003700 kernel_info->values[i]=(double) (-exp(-((double) u*u+v*v)/(2.0*
3701 MagickSigma*MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
3702 normalize+=kernel_info->values[i];
cristy3ed852e2009-09-05 21:47:34 +00003703 i++;
3704 }
3705 }
cristy41cbe682011-07-15 19:12:37 +00003706 kernel_info->values[i/2]=(double) ((-2.0)*normalize);
cristy5e6be1e2011-07-16 01:23:39 +00003707 sharp_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00003708 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00003709 return(sharp_image);
3710}
3711
3712/*
3713%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3714% %
3715% %
3716% %
3717% S p r e a d I m a g e %
3718% %
3719% %
3720% %
3721%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3722%
3723% SpreadImage() is a special effects method that randomly displaces each
3724% pixel in a block defined by the radius parameter.
3725%
3726% The format of the SpreadImage method is:
3727%
3728% Image *SpreadImage(const Image *image,const double radius,
cristy5c4e2582011-09-11 19:21:03 +00003729% const PixelInterpolateMethod method,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003730%
3731% A description of each parameter follows:
3732%
3733% o image: the image.
3734%
cristy5c4e2582011-09-11 19:21:03 +00003735% o radius: choose a random pixel in a neighborhood of this extent.
3736%
3737% o method: the pixel interpolation method.
cristy3ed852e2009-09-05 21:47:34 +00003738%
3739% o exception: return any errors or warnings in this structure.
3740%
3741*/
3742MagickExport Image *SpreadImage(const Image *image,const double radius,
cristy5c4e2582011-09-11 19:21:03 +00003743 const PixelInterpolateMethod method,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003744{
3745#define SpreadImageTag "Spread/Image"
3746
cristyfa112112010-01-04 17:48:07 +00003747 CacheView
cristy9f7e7cb2011-03-26 00:49:57 +00003748 *image_view,
3749 *spread_view;
cristyfa112112010-01-04 17:48:07 +00003750
cristy3ed852e2009-09-05 21:47:34 +00003751 Image
3752 *spread_image;
3753
cristy3ed852e2009-09-05 21:47:34 +00003754 MagickBooleanType
3755 status;
3756
cristybb503372010-05-27 20:51:26 +00003757 MagickOffsetType
3758 progress;
3759
cristy3ed852e2009-09-05 21:47:34 +00003760 RandomInfo
cristyfa112112010-01-04 17:48:07 +00003761 **restrict random_info;
cristy3ed852e2009-09-05 21:47:34 +00003762
cristybb503372010-05-27 20:51:26 +00003763 size_t
cristy3ed852e2009-09-05 21:47:34 +00003764 width;
3765
cristybb503372010-05-27 20:51:26 +00003766 ssize_t
3767 y;
3768
cristy3ed852e2009-09-05 21:47:34 +00003769 /*
3770 Initialize spread image attributes.
3771 */
3772 assert(image != (Image *) NULL);
3773 assert(image->signature == MagickSignature);
3774 if (image->debug != MagickFalse)
3775 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3776 assert(exception != (ExceptionInfo *) NULL);
3777 assert(exception->signature == MagickSignature);
3778 spread_image=CloneImage(image,image->columns,image->rows,MagickTrue,
3779 exception);
3780 if (spread_image == (Image *) NULL)
3781 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00003782 if (SetImageStorageClass(spread_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003783 {
cristy3ed852e2009-09-05 21:47:34 +00003784 spread_image=DestroyImage(spread_image);
3785 return((Image *) NULL);
3786 }
3787 /*
3788 Spread image.
3789 */
3790 status=MagickTrue;
3791 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00003792 width=GetOptimalKernelWidth1D(radius,0.5);
cristy3ed852e2009-09-05 21:47:34 +00003793 random_info=AcquireRandomInfoThreadSet();
cristy9f7e7cb2011-03-26 00:49:57 +00003794 image_view=AcquireCacheView(image);
3795 spread_view=AcquireCacheView(spread_image);
cristyb557a152011-02-22 12:14:30 +00003796#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00003797 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00003798#endif
cristybe82ad52011-09-06 01:17:20 +00003799 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003800 {
cristy5c9e6f22010-09-17 17:31:01 +00003801 const int
3802 id = GetOpenMPThreadId();
cristy6ebe97c2010-07-03 01:17:28 +00003803
cristybe82ad52011-09-06 01:17:20 +00003804 register const Quantum
3805 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003806
cristy4c08aed2011-07-01 19:47:50 +00003807 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003808 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003809
cristy117ff172010-08-15 21:35:32 +00003810 register ssize_t
3811 x;
3812
cristy3ed852e2009-09-05 21:47:34 +00003813 if (status == MagickFalse)
3814 continue;
cristybe82ad52011-09-06 01:17:20 +00003815 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy9f7e7cb2011-03-26 00:49:57 +00003816 q=QueueCacheViewAuthenticPixels(spread_view,0,y,spread_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +00003817 exception);
cristybe82ad52011-09-06 01:17:20 +00003818 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003819 {
3820 status=MagickFalse;
3821 continue;
3822 }
cristybe82ad52011-09-06 01:17:20 +00003823 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003824 {
cristybe82ad52011-09-06 01:17:20 +00003825 PointInfo
3826 point;
3827
cristybe82ad52011-09-06 01:17:20 +00003828 point.x=GetPseudoRandomValue(random_info[id]);
3829 point.y=GetPseudoRandomValue(random_info[id]);
cristy5c4e2582011-09-11 19:21:03 +00003830 status=InterpolatePixelChannels(image,image_view,spread_image,method,
3831 (double) x+width*point.x-0.5,(double) y+width*point.y-0.5,q,exception);
cristyed231572011-07-14 02:18:59 +00003832 q+=GetPixelChannels(spread_image);
cristy3ed852e2009-09-05 21:47:34 +00003833 }
cristy9f7e7cb2011-03-26 00:49:57 +00003834 if (SyncCacheViewAuthenticPixels(spread_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003835 status=MagickFalse;
3836 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3837 {
3838 MagickBooleanType
3839 proceed;
3840
cristyb557a152011-02-22 12:14:30 +00003841#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003842 #pragma omp critical (MagickCore_SpreadImage)
3843#endif
3844 proceed=SetImageProgress(image,SpreadImageTag,progress++,image->rows);
3845 if (proceed == MagickFalse)
3846 status=MagickFalse;
3847 }
3848 }
cristy9f7e7cb2011-03-26 00:49:57 +00003849 spread_view=DestroyCacheView(spread_view);
cristy3ed852e2009-09-05 21:47:34 +00003850 image_view=DestroyCacheView(image_view);
3851 random_info=DestroyRandomInfoThreadSet(random_info);
cristy3ed852e2009-09-05 21:47:34 +00003852 return(spread_image);
3853}
3854
3855/*
3856%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3857% %
3858% %
3859% %
3860% U n s h a r p M a s k I m a g e %
3861% %
3862% %
3863% %
3864%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3865%
3866% UnsharpMaskImage() sharpens one or more image channels. We convolve the
3867% image with a Gaussian operator of the given radius and standard deviation
3868% (sigma). For reasonable results, radius should be larger than sigma. Use a
3869% radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
3870%
3871% The format of the UnsharpMaskImage method is:
3872%
3873% Image *UnsharpMaskImage(const Image *image,const double radius,
3874% const double sigma,const double amount,const double threshold,
3875% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003876%
3877% A description of each parameter follows:
3878%
3879% o image: the image.
3880%
cristy3ed852e2009-09-05 21:47:34 +00003881% o radius: the radius of the Gaussian, in pixels, not counting the center
3882% pixel.
3883%
3884% o sigma: the standard deviation of the Gaussian, in pixels.
3885%
3886% o amount: the percentage of the difference between the original and the
3887% blur image that is added back into the original.
3888%
3889% o threshold: the threshold in pixels needed to apply the diffence amount.
3890%
3891% o exception: return any errors or warnings in this structure.
3892%
3893*/
cristy31bb6272011-11-20 23:57:25 +00003894MagickExport Image *UnsharpMaskImage(const Image *image,const double radius,
3895 const double sigma,const double amount,const double threshold,
3896 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003897{
3898#define SharpenImageTag "Sharpen/Image"
3899
cristyc4c8d132010-01-07 01:58:38 +00003900 CacheView
3901 *image_view,
3902 *unsharp_view;
3903
cristy3ed852e2009-09-05 21:47:34 +00003904 Image
3905 *unsharp_image;
3906
cristy3ed852e2009-09-05 21:47:34 +00003907 MagickBooleanType
3908 status;
3909
cristybb503372010-05-27 20:51:26 +00003910 MagickOffsetType
3911 progress;
3912
cristy3ed852e2009-09-05 21:47:34 +00003913 MagickRealType
3914 quantum_threshold;
3915
cristybb503372010-05-27 20:51:26 +00003916 ssize_t
3917 y;
3918
cristy3ed852e2009-09-05 21:47:34 +00003919 assert(image != (const Image *) NULL);
3920 assert(image->signature == MagickSignature);
3921 if (image->debug != MagickFalse)
3922 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3923 assert(exception != (ExceptionInfo *) NULL);
anthony736a1602011-10-06 12:38:17 +00003924
3925
3926 /* FUTURE: use of bias on sharpen is non-sensical */
cristy05c0c9a2011-09-05 23:16:13 +00003927 unsharp_image=BlurImage(image,radius,sigma,image->bias,exception);
anthony736a1602011-10-06 12:38:17 +00003928
cristy3ed852e2009-09-05 21:47:34 +00003929 if (unsharp_image == (Image *) NULL)
3930 return((Image *) NULL);
3931 quantum_threshold=(MagickRealType) QuantumRange*threshold;
3932 /*
3933 Unsharp-mask image.
3934 */
3935 status=MagickTrue;
3936 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00003937 image_view=AcquireCacheView(image);
3938 unsharp_view=AcquireCacheView(unsharp_image);
cristyb5d5f722009-11-04 03:03:49 +00003939#if defined(MAGICKCORE_OPENMP_SUPPORT)
3940 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003941#endif
cristybb503372010-05-27 20:51:26 +00003942 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003943 {
cristy4c08aed2011-07-01 19:47:50 +00003944 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003945 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003946
cristy4c08aed2011-07-01 19:47:50 +00003947 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003948 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003949
cristy117ff172010-08-15 21:35:32 +00003950 register ssize_t
3951 x;
3952
cristy3ed852e2009-09-05 21:47:34 +00003953 if (status == MagickFalse)
3954 continue;
3955 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
3956 q=GetCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
3957 exception);
cristy4c08aed2011-07-01 19:47:50 +00003958 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003959 {
3960 status=MagickFalse;
3961 continue;
3962 }
cristybb503372010-05-27 20:51:26 +00003963 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003964 {
cristy7f3a0d12011-09-05 23:27:59 +00003965 register ssize_t
3966 i;
3967
3968 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3969 {
3970 MagickRealType
3971 pixel;
3972
3973 PixelChannel
3974 channel;
3975
3976 PixelTrait
3977 traits,
3978 unsharp_traits;
3979
cristye2a912b2011-12-05 20:02:07 +00003980 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +00003981 traits=GetPixelChannelMapTraits(image,channel);
cristy7f3a0d12011-09-05 23:27:59 +00003982 unsharp_traits=GetPixelChannelMapTraits(unsharp_image,channel);
3983 if ((traits == UndefinedPixelTrait) ||
3984 (unsharp_traits == UndefinedPixelTrait))
3985 continue;
3986 if ((unsharp_traits & CopyPixelTrait) != 0)
3987 {
cristy0beccfa2011-09-25 20:47:53 +00003988 SetPixelChannel(unsharp_image,channel,p[i],q);
cristy7f3a0d12011-09-05 23:27:59 +00003989 continue;
3990 }
cristy0beccfa2011-09-25 20:47:53 +00003991 pixel=p[i]-(MagickRealType) GetPixelChannel(unsharp_image,channel,q);
cristy7f3a0d12011-09-05 23:27:59 +00003992 if (fabs(2.0*pixel) < quantum_threshold)
3993 pixel=(MagickRealType) p[i];
3994 else
3995 pixel=(MagickRealType) p[i]+amount*pixel;
cristy0beccfa2011-09-25 20:47:53 +00003996 SetPixelChannel(unsharp_image,channel,ClampToQuantum(pixel),q);
cristy7f3a0d12011-09-05 23:27:59 +00003997 }
cristyed231572011-07-14 02:18:59 +00003998 p+=GetPixelChannels(image);
3999 q+=GetPixelChannels(unsharp_image);
cristy3ed852e2009-09-05 21:47:34 +00004000 }
4001 if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
4002 status=MagickFalse;
4003 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4004 {
4005 MagickBooleanType
4006 proceed;
4007
cristyb5d5f722009-11-04 03:03:49 +00004008#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00004009 #pragma omp critical (MagickCore_UnsharpMaskImage)
cristy3ed852e2009-09-05 21:47:34 +00004010#endif
4011 proceed=SetImageProgress(image,SharpenImageTag,progress++,image->rows);
4012 if (proceed == MagickFalse)
4013 status=MagickFalse;
4014 }
4015 }
4016 unsharp_image->type=image->type;
4017 unsharp_view=DestroyCacheView(unsharp_view);
4018 image_view=DestroyCacheView(image_view);
4019 if (status == MagickFalse)
4020 unsharp_image=DestroyImage(unsharp_image);
4021 return(unsharp_image);
4022}