blob: 984a25477f100999907f692d45fd3b09f8a949de [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,
cristyaa2c16c2012-03-25 22:21:35 +0000111% const double sigma,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%
122% o exception: return any errors or warnings in this structure.
123%
124*/
125
cristyf89cb1d2011-07-07 01:24:37 +0000126MagickExport MagickBooleanType AdaptiveLevelImage(Image *image,
cristy051718b2011-08-28 22:49:25 +0000127 const char *levels,ExceptionInfo *exception)
cristyf89cb1d2011-07-07 01:24:37 +0000128{
129 double
130 black_point,
131 gamma,
132 white_point;
133
134 GeometryInfo
135 geometry_info;
136
137 MagickBooleanType
138 status;
139
140 MagickStatusType
141 flags;
142
143 /*
144 Parse levels.
145 */
146 if (levels == (char *) NULL)
147 return(MagickFalse);
148 flags=ParseGeometry(levels,&geometry_info);
149 black_point=geometry_info.rho;
150 white_point=(double) QuantumRange;
151 if ((flags & SigmaValue) != 0)
152 white_point=geometry_info.sigma;
153 gamma=1.0;
154 if ((flags & XiValue) != 0)
155 gamma=geometry_info.xi;
156 if ((flags & PercentValue) != 0)
157 {
158 black_point*=(double) image->columns*image->rows/100.0;
159 white_point*=(double) image->columns*image->rows/100.0;
160 }
161 if ((flags & SigmaValue) == 0)
162 white_point=(double) QuantumRange-black_point;
163 if ((flags & AspectValue ) == 0)
cristy7c0a0a42011-08-23 17:57:25 +0000164 status=LevelImage(image,black_point,white_point,gamma,exception);
cristyf89cb1d2011-07-07 01:24:37 +0000165 else
cristy7c0a0a42011-08-23 17:57:25 +0000166 status=LevelizeImage(image,black_point,white_point,gamma,exception);
cristyf89cb1d2011-07-07 01:24:37 +0000167 return(status);
168}
169
cristy4282c702011-11-21 00:01:06 +0000170MagickExport Image *AdaptiveBlurImage(const Image *image,const double radius,
cristyaa2c16c2012-03-25 22:21:35 +0000171 const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000172{
173#define AdaptiveBlurImageTag "Convolve/Image"
174#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
175
cristyc4c8d132010-01-07 01:58:38 +0000176 CacheView
177 *blur_view,
178 *edge_view,
179 *image_view;
180
cristy3ed852e2009-09-05 21:47:34 +0000181 double
cristy47e00502009-12-17 19:19:57 +0000182 **kernel,
183 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000184
185 Image
186 *blur_image,
187 *edge_image,
188 *gaussian_image;
189
cristy3ed852e2009-09-05 21:47:34 +0000190 MagickBooleanType
191 status;
192
cristybb503372010-05-27 20:51:26 +0000193 MagickOffsetType
194 progress;
195
cristybb503372010-05-27 20:51:26 +0000196 register ssize_t
cristy47e00502009-12-17 19:19:57 +0000197 i;
cristy3ed852e2009-09-05 21:47:34 +0000198
cristybb503372010-05-27 20:51:26 +0000199 size_t
cristy3ed852e2009-09-05 21:47:34 +0000200 width;
201
cristybb503372010-05-27 20:51:26 +0000202 ssize_t
203 j,
204 k,
205 u,
206 v,
207 y;
208
cristy3ed852e2009-09-05 21:47:34 +0000209 assert(image != (const Image *) NULL);
210 assert(image->signature == MagickSignature);
211 if (image->debug != MagickFalse)
212 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
213 assert(exception != (ExceptionInfo *) NULL);
214 assert(exception->signature == MagickSignature);
215 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
216 if (blur_image == (Image *) NULL)
217 return((Image *) NULL);
218 if (fabs(sigma) <= MagickEpsilon)
219 return(blur_image);
cristy574cc262011-08-05 01:23:58 +0000220 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000221 {
cristy3ed852e2009-09-05 21:47:34 +0000222 blur_image=DestroyImage(blur_image);
223 return((Image *) NULL);
224 }
225 /*
226 Edge detect the image brighness channel, level, blur, and level again.
227 */
cristy8ae632d2011-09-05 17:29:53 +0000228 edge_image=EdgeImage(image,radius,sigma,exception);
cristy3ed852e2009-09-05 21:47:34 +0000229 if (edge_image == (Image *) NULL)
230 {
231 blur_image=DestroyImage(blur_image);
232 return((Image *) NULL);
233 }
cristy051718b2011-08-28 22:49:25 +0000234 (void) AdaptiveLevelImage(edge_image,"20%,95%",exception);
cristyd89705a2012-01-20 02:52:24 +0000235 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,exception);
cristy3ed852e2009-09-05 21:47:34 +0000236 if (gaussian_image != (Image *) NULL)
237 {
238 edge_image=DestroyImage(edge_image);
239 edge_image=gaussian_image;
240 }
cristy051718b2011-08-28 22:49:25 +0000241 (void) AdaptiveLevelImage(edge_image,"10%,95%",exception);
cristy3ed852e2009-09-05 21:47:34 +0000242 /*
243 Create a set of kernels from maximum (radius,sigma) to minimum.
244 */
245 width=GetOptimalKernelWidth2D(radius,sigma);
cristy219a63a2012-01-17 12:30:33 +0000246 kernel=(double **) AcquireAlignedMemory((size_t) width,sizeof(*kernel));
cristy3ed852e2009-09-05 21:47:34 +0000247 if (kernel == (double **) NULL)
248 {
249 edge_image=DestroyImage(edge_image);
250 blur_image=DestroyImage(blur_image);
251 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
252 }
253 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
cristybb503372010-05-27 20:51:26 +0000254 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000255 {
cristyf5d0f3e2012-01-17 03:20:33 +0000256 kernel[i]=(double *) AcquireAlignedMemory((size_t) (width-i),(width-i)*
cristy3ed852e2009-09-05 21:47:34 +0000257 sizeof(**kernel));
258 if (kernel[i] == (double *) NULL)
259 break;
cristy47e00502009-12-17 19:19:57 +0000260 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000261 j=(ssize_t) (width-i)/2;
cristy47e00502009-12-17 19:19:57 +0000262 k=0;
263 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000264 {
cristy47e00502009-12-17 19:19:57 +0000265 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000266 {
cristy4205a3c2010-09-12 20:19:59 +0000267 kernel[i][k]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
268 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +0000269 normalize+=kernel[i][k];
270 k++;
cristy3ed852e2009-09-05 21:47:34 +0000271 }
272 }
cristy3ed852e2009-09-05 21:47:34 +0000273 if (fabs(normalize) <= MagickEpsilon)
274 normalize=1.0;
275 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000276 for (k=0; k < (j*j); k++)
277 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000278 }
cristybb503372010-05-27 20:51:26 +0000279 if (i < (ssize_t) width)
cristy3ed852e2009-09-05 21:47:34 +0000280 {
281 for (i-=2; i >= 0; i-=2)
cristyf5d0f3e2012-01-17 03:20:33 +0000282 kernel[i]=(double *) RelinquishAlignedMemory(kernel[i]);
cristy219a63a2012-01-17 12:30:33 +0000283 kernel=(double **) RelinquishAlignedMemory(kernel);
cristy3ed852e2009-09-05 21:47:34 +0000284 edge_image=DestroyImage(edge_image);
285 blur_image=DestroyImage(blur_image);
286 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
287 }
288 /*
289 Adaptively blur image.
290 */
291 status=MagickTrue;
292 progress=0;
cristydb070952012-04-20 14:33:00 +0000293 image_view=AcquireVirtualCacheView(image,exception);
294 edge_view=AcquireVirtualCacheView(edge_image,exception);
295 blur_view=AcquireAuthenticCacheView(blur_image,exception);
cristyb5d5f722009-11-04 03:03:49 +0000296#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000297 #pragma omp parallel for schedule(static,4) shared(progress,status) \
298 if ((blur_image->rows*blur_image->columns) > 8192) \
299 num_threads(GetMagickResourceLimit(ThreadResource))
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;
cristyd09f8802012-02-04 16:44:10 +0000347 center=(ssize_t) GetPixelChannels(image)*(width-j)*((width-j)/2L)+
348 GetPixelChannels(image)*((width-j)/2L);
cristy177e41c2012-04-15 15:08:25 +0000349 if (GetPixelMask(image,p) != 0)
350 {
351 q+=GetPixelChannels(blur_image);
352 r+=GetPixelChannels(edge_image);
353 continue;
354 }
cristy4c11c2b2011-09-05 20:17:07 +0000355 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy3ed852e2009-09-05 21:47:34 +0000356 {
cristy4c11c2b2011-09-05 20:17:07 +0000357 MagickRealType
358 alpha,
359 gamma,
360 pixel;
361
362 PixelChannel
363 channel;
364
365 PixelTrait
366 blur_traits,
367 traits;
368
369 register const double
370 *restrict k;
371
372 register const Quantum
373 *restrict pixels;
374
375 register ssize_t
376 u;
377
378 ssize_t
379 v;
380
cristye2a912b2011-12-05 20:02:07 +0000381 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +0000382 traits=GetPixelChannelMapTraits(image,channel);
cristy4c11c2b2011-09-05 20:17:07 +0000383 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
384 if ((traits == UndefinedPixelTrait) ||
385 (blur_traits == UndefinedPixelTrait))
386 continue;
cristy177e41c2012-04-15 15:08:25 +0000387 if ((blur_traits & CopyPixelTrait) != 0)
cristy4c11c2b2011-09-05 20:17:07 +0000388 {
cristy0beccfa2011-09-25 20:47:53 +0000389 SetPixelChannel(blur_image,channel,p[center+i],q);
cristy4c11c2b2011-09-05 20:17:07 +0000390 continue;
391 }
392 k=kernel[j];
393 pixels=p;
cristyaa2c16c2012-03-25 22:21:35 +0000394 pixel=0.0;
cristy4c11c2b2011-09-05 20:17:07 +0000395 gamma=0.0;
396 if ((blur_traits & BlendPixelTrait) == 0)
397 {
398 /*
399 No alpha blending.
400 */
401 for (v=0; v < (ssize_t) (width-j); v++)
402 {
403 for (u=0; u < (ssize_t) (width-j); u++)
404 {
405 pixel+=(*k)*pixels[i];
406 gamma+=(*k);
407 k++;
408 pixels+=GetPixelChannels(image);
409 }
410 }
411 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +0000412 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy4c11c2b2011-09-05 20:17:07 +0000413 continue;
414 }
415 /*
416 Alpha blending.
417 */
418 for (v=0; v < (ssize_t) (width-j); v++)
cristy3ed852e2009-09-05 21:47:34 +0000419 {
cristy4c11c2b2011-09-05 20:17:07 +0000420 for (u=0; u < (ssize_t) (width-j); u++)
421 {
422 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,pixels));
423 pixel+=(*k)*alpha*pixels[i];
424 gamma+=(*k)*alpha;
425 k++;
426 pixels+=GetPixelChannels(image);
427 }
cristy3ed852e2009-09-05 21:47:34 +0000428 }
cristy4c11c2b2011-09-05 20:17:07 +0000429 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +0000430 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy3ed852e2009-09-05 21:47:34 +0000431 }
cristyed231572011-07-14 02:18:59 +0000432 q+=GetPixelChannels(blur_image);
433 r+=GetPixelChannels(edge_image);
cristy3ed852e2009-09-05 21:47:34 +0000434 }
435 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
436 status=MagickFalse;
437 if (image->progress_monitor != (MagickProgressMonitor) NULL)
438 {
439 MagickBooleanType
440 proceed;
441
cristyb5d5f722009-11-04 03:03:49 +0000442#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf8561542012-01-24 00:26:46 +0000443 #pragma omp critical (MagickCore_AdaptiveBlurImage)
cristy3ed852e2009-09-05 21:47:34 +0000444#endif
445 proceed=SetImageProgress(image,AdaptiveBlurImageTag,progress++,
446 image->rows);
447 if (proceed == MagickFalse)
448 status=MagickFalse;
449 }
450 }
451 blur_image->type=image->type;
452 blur_view=DestroyCacheView(blur_view);
453 edge_view=DestroyCacheView(edge_view);
454 image_view=DestroyCacheView(image_view);
455 edge_image=DestroyImage(edge_image);
cristybb503372010-05-27 20:51:26 +0000456 for (i=0; i < (ssize_t) width; i+=2)
cristyf5d0f3e2012-01-17 03:20:33 +0000457 kernel[i]=(double *) RelinquishAlignedMemory(kernel[i]);
cristy219a63a2012-01-17 12:30:33 +0000458 kernel=(double **) RelinquishAlignedMemory(kernel);
cristy3ed852e2009-09-05 21:47:34 +0000459 if (status == MagickFalse)
460 blur_image=DestroyImage(blur_image);
461 return(blur_image);
462}
463
464/*
465%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
466% %
467% %
468% %
469% A d a p t i v e S h a r p e n I m a g e %
470% %
471% %
472% %
473%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
474%
475% AdaptiveSharpenImage() adaptively sharpens the image by sharpening more
476% intensely near image edges and less intensely far from edges. We sharpen the
477% image with a Gaussian operator of the given radius and standard deviation
478% (sigma). For reasonable results, radius should be larger than sigma. Use a
479% radius of 0 and AdaptiveSharpenImage() selects a suitable radius for you.
480%
481% The format of the AdaptiveSharpenImage method is:
482%
483% Image *AdaptiveSharpenImage(const Image *image,const double radius,
cristyaa2c16c2012-03-25 22:21:35 +0000484% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000485%
486% A description of each parameter follows:
487%
488% o image: the image.
489%
cristy3ed852e2009-09-05 21:47:34 +0000490% o radius: the radius of the Gaussian, in pixels, not counting the center
491% pixel.
492%
493% o sigma: the standard deviation of the Laplacian, in pixels.
494%
495% o exception: return any errors or warnings in this structure.
496%
497*/
cristy3ed852e2009-09-05 21:47:34 +0000498MagickExport Image *AdaptiveSharpenImage(const Image *image,const double radius,
cristyaa2c16c2012-03-25 22:21:35 +0000499 const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000500{
cristy3ed852e2009-09-05 21:47:34 +0000501#define AdaptiveSharpenImageTag "Convolve/Image"
502#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
503
cristyc4c8d132010-01-07 01:58:38 +0000504 CacheView
505 *sharp_view,
506 *edge_view,
507 *image_view;
508
cristy3ed852e2009-09-05 21:47:34 +0000509 double
cristy47e00502009-12-17 19:19:57 +0000510 **kernel,
511 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000512
513 Image
514 *sharp_image,
515 *edge_image,
516 *gaussian_image;
517
cristy3ed852e2009-09-05 21:47:34 +0000518 MagickBooleanType
519 status;
520
cristybb503372010-05-27 20:51:26 +0000521 MagickOffsetType
522 progress;
523
cristybb503372010-05-27 20:51:26 +0000524 register ssize_t
cristy47e00502009-12-17 19:19:57 +0000525 i;
cristy3ed852e2009-09-05 21:47:34 +0000526
cristybb503372010-05-27 20:51:26 +0000527 size_t
cristy3ed852e2009-09-05 21:47:34 +0000528 width;
529
cristybb503372010-05-27 20:51:26 +0000530 ssize_t
531 j,
532 k,
533 u,
534 v,
535 y;
536
cristy3ed852e2009-09-05 21:47:34 +0000537 assert(image != (const Image *) NULL);
538 assert(image->signature == MagickSignature);
539 if (image->debug != MagickFalse)
540 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
541 assert(exception != (ExceptionInfo *) NULL);
542 assert(exception->signature == MagickSignature);
543 sharp_image=CloneImage(image,0,0,MagickTrue,exception);
544 if (sharp_image == (Image *) NULL)
545 return((Image *) NULL);
546 if (fabs(sigma) <= MagickEpsilon)
547 return(sharp_image);
cristy574cc262011-08-05 01:23:58 +0000548 if (SetImageStorageClass(sharp_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000549 {
cristy3ed852e2009-09-05 21:47:34 +0000550 sharp_image=DestroyImage(sharp_image);
551 return((Image *) NULL);
552 }
553 /*
554 Edge detect the image brighness channel, level, sharp, and level again.
555 */
cristy8ae632d2011-09-05 17:29:53 +0000556 edge_image=EdgeImage(image,radius,sigma,exception);
cristy3ed852e2009-09-05 21:47:34 +0000557 if (edge_image == (Image *) NULL)
558 {
559 sharp_image=DestroyImage(sharp_image);
560 return((Image *) NULL);
561 }
cristy051718b2011-08-28 22:49:25 +0000562 (void) AdaptiveLevelImage(edge_image,"20%,95%",exception);
cristyd89705a2012-01-20 02:52:24 +0000563 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,exception);
cristy3ed852e2009-09-05 21:47:34 +0000564 if (gaussian_image != (Image *) NULL)
565 {
566 edge_image=DestroyImage(edge_image);
567 edge_image=gaussian_image;
568 }
cristy051718b2011-08-28 22:49:25 +0000569 (void) AdaptiveLevelImage(edge_image,"10%,95%",exception);
cristy3ed852e2009-09-05 21:47:34 +0000570 /*
571 Create a set of kernels from maximum (radius,sigma) to minimum.
572 */
573 width=GetOptimalKernelWidth2D(radius,sigma);
cristy219a63a2012-01-17 12:30:33 +0000574 kernel=(double **) AcquireAlignedMemory((size_t) width,sizeof(*kernel));
cristy3ed852e2009-09-05 21:47:34 +0000575 if (kernel == (double **) NULL)
576 {
577 edge_image=DestroyImage(edge_image);
578 sharp_image=DestroyImage(sharp_image);
579 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
580 }
581 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
cristybb503372010-05-27 20:51:26 +0000582 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000583 {
cristy59d1f6d2012-01-17 03:17:40 +0000584 kernel[i]=(double *) AcquireAlignedMemory((size_t) (width-i),(width-i)*
cristy3ed852e2009-09-05 21:47:34 +0000585 sizeof(**kernel));
586 if (kernel[i] == (double *) NULL)
587 break;
cristy47e00502009-12-17 19:19:57 +0000588 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000589 j=(ssize_t) (width-i)/2;
cristy47e00502009-12-17 19:19:57 +0000590 k=0;
591 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000592 {
cristy47e00502009-12-17 19:19:57 +0000593 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000594 {
cristy4205a3c2010-09-12 20:19:59 +0000595 kernel[i][k]=(double) (-exp(-((double) u*u+v*v)/(2.0*MagickSigma*
596 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +0000597 normalize+=kernel[i][k];
598 k++;
cristy3ed852e2009-09-05 21:47:34 +0000599 }
600 }
cristy3ed852e2009-09-05 21:47:34 +0000601 if (fabs(normalize) <= MagickEpsilon)
602 normalize=1.0;
603 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000604 for (k=0; k < (j*j); k++)
605 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000606 }
cristybb503372010-05-27 20:51:26 +0000607 if (i < (ssize_t) width)
cristy3ed852e2009-09-05 21:47:34 +0000608 {
609 for (i-=2; i >= 0; i-=2)
cristy59d1f6d2012-01-17 03:17:40 +0000610 kernel[i]=(double *) RelinquishAlignedMemory(kernel[i]);
cristy219a63a2012-01-17 12:30:33 +0000611 kernel=(double **) RelinquishAlignedMemory(kernel);
cristy3ed852e2009-09-05 21:47:34 +0000612 edge_image=DestroyImage(edge_image);
613 sharp_image=DestroyImage(sharp_image);
614 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
615 }
616 /*
617 Adaptively sharpen image.
618 */
619 status=MagickTrue;
620 progress=0;
cristydb070952012-04-20 14:33:00 +0000621 image_view=AcquireVirtualCacheView(image,exception);
622 edge_view=AcquireVirtualCacheView(edge_image,exception);
623 sharp_view=AcquireAuthenticCacheView(sharp_image,exception);
cristyb5d5f722009-11-04 03:03:49 +0000624#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000625 #pragma omp parallel for schedule(static,4) shared(progress,status) \
626 if ((sharp_image->rows*sharp_image->columns) > 8192) \
627 num_threads(GetMagickResourceLimit(ThreadResource))
cristy3ed852e2009-09-05 21:47:34 +0000628#endif
cristybb503372010-05-27 20:51:26 +0000629 for (y=0; y < (ssize_t) sharp_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000630 {
cristy4c08aed2011-07-01 19:47:50 +0000631 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000632 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000633
cristy4c08aed2011-07-01 19:47:50 +0000634 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000635 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000636
cristy117ff172010-08-15 21:35:32 +0000637 register ssize_t
638 x;
639
cristy3ed852e2009-09-05 21:47:34 +0000640 if (status == MagickFalse)
641 continue;
642 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
643 q=QueueCacheViewAuthenticPixels(sharp_view,0,y,sharp_image->columns,1,
644 exception);
cristy4c08aed2011-07-01 19:47:50 +0000645 if ((r == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000646 {
647 status=MagickFalse;
648 continue;
649 }
cristybb503372010-05-27 20:51:26 +0000650 for (x=0; x < (ssize_t) sharp_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000651 {
cristy4c11c2b2011-09-05 20:17:07 +0000652 register const Quantum
653 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000654
cristybb503372010-05-27 20:51:26 +0000655 register ssize_t
cristy4c11c2b2011-09-05 20:17:07 +0000656 i;
cristy3ed852e2009-09-05 21:47:34 +0000657
cristy4c11c2b2011-09-05 20:17:07 +0000658 ssize_t
659 center,
660 j;
661
662 j=(ssize_t) ceil((double) width*QuantumScale*
cristy4c08aed2011-07-01 19:47:50 +0000663 GetPixelIntensity(edge_image,r)-0.5);
cristy4c11c2b2011-09-05 20:17:07 +0000664 if (j < 0)
665 j=0;
cristy3ed852e2009-09-05 21:47:34 +0000666 else
cristy4c11c2b2011-09-05 20:17:07 +0000667 if (j > (ssize_t) width)
668 j=(ssize_t) width;
669 if ((j & 0x01) != 0)
670 j--;
671 p=GetCacheViewVirtualPixels(image_view,x-((ssize_t) (width-j)/2L),y-
672 (ssize_t) ((width-j)/2L),width-j,width-j,exception);
cristy4c08aed2011-07-01 19:47:50 +0000673 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000674 break;
cristyd09f8802012-02-04 16:44:10 +0000675 center=(ssize_t) GetPixelChannels(image)*(width-j)*((width-j)/2L)+
676 GetPixelChannels(image)*((width-j)/2);
cristy177e41c2012-04-15 15:08:25 +0000677 if (GetPixelMask(image,p) != 0)
678 {
679 q+=GetPixelChannels(sharp_image);
680 r+=GetPixelChannels(edge_image);
681 continue;
682 }
cristyc94ba6f2012-01-29 23:19:58 +0000683 for (i=0; i < (ssize_t) GetPixelChannels(sharp_image); i++)
cristy3ed852e2009-09-05 21:47:34 +0000684 {
cristy4c11c2b2011-09-05 20:17:07 +0000685 MagickRealType
686 alpha,
687 gamma,
688 pixel;
689
690 PixelChannel
691 channel;
692
693 PixelTrait
694 sharp_traits,
695 traits;
696
697 register const double
698 *restrict k;
699
700 register const Quantum
701 *restrict pixels;
702
703 register ssize_t
704 u;
705
706 ssize_t
707 v;
708
cristye2a912b2011-12-05 20:02:07 +0000709 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +0000710 traits=GetPixelChannelMapTraits(image,channel);
cristy4c11c2b2011-09-05 20:17:07 +0000711 sharp_traits=GetPixelChannelMapTraits(sharp_image,channel);
712 if ((traits == UndefinedPixelTrait) ||
713 (sharp_traits == UndefinedPixelTrait))
714 continue;
cristy177e41c2012-04-15 15:08:25 +0000715 if ((sharp_traits & CopyPixelTrait) != 0)
cristy4c11c2b2011-09-05 20:17:07 +0000716 {
cristy0beccfa2011-09-25 20:47:53 +0000717 SetPixelChannel(sharp_image,channel,p[center+i],q);
cristy4c11c2b2011-09-05 20:17:07 +0000718 continue;
719 }
720 k=kernel[j];
721 pixels=p;
cristyaa2c16c2012-03-25 22:21:35 +0000722 pixel=0.0;
cristy4c11c2b2011-09-05 20:17:07 +0000723 gamma=0.0;
724 if ((sharp_traits & BlendPixelTrait) == 0)
725 {
726 /*
727 No alpha blending.
728 */
729 for (v=0; v < (ssize_t) (width-j); v++)
730 {
731 for (u=0; u < (ssize_t) (width-j); u++)
732 {
733 pixel+=(*k)*pixels[i];
734 gamma+=(*k);
735 k++;
736 pixels+=GetPixelChannels(image);
737 }
738 }
739 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +0000740 SetPixelChannel(sharp_image,channel,ClampToQuantum(gamma*pixel),q);
cristy4c11c2b2011-09-05 20:17:07 +0000741 continue;
742 }
743 /*
744 Alpha blending.
745 */
746 for (v=0; v < (ssize_t) (width-j); v++)
cristy3ed852e2009-09-05 21:47:34 +0000747 {
cristy4c11c2b2011-09-05 20:17:07 +0000748 for (u=0; u < (ssize_t) (width-j); u++)
749 {
750 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,pixels));
751 pixel+=(*k)*alpha*pixels[i];
752 gamma+=(*k)*alpha;
753 k++;
754 pixels+=GetPixelChannels(image);
755 }
cristy3ed852e2009-09-05 21:47:34 +0000756 }
cristy4c11c2b2011-09-05 20:17:07 +0000757 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +0000758 SetPixelChannel(sharp_image,channel,ClampToQuantum(gamma*pixel),q);
cristy3ed852e2009-09-05 21:47:34 +0000759 }
cristyed231572011-07-14 02:18:59 +0000760 q+=GetPixelChannels(sharp_image);
761 r+=GetPixelChannels(edge_image);
cristy3ed852e2009-09-05 21:47:34 +0000762 }
763 if (SyncCacheViewAuthenticPixels(sharp_view,exception) == MagickFalse)
764 status=MagickFalse;
765 if (image->progress_monitor != (MagickProgressMonitor) NULL)
766 {
767 MagickBooleanType
768 proceed;
769
cristyb5d5f722009-11-04 03:03:49 +0000770#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf8561542012-01-24 00:26:46 +0000771 #pragma omp critical (MagickCore_AdaptiveSharpenImage)
cristy3ed852e2009-09-05 21:47:34 +0000772#endif
773 proceed=SetImageProgress(image,AdaptiveSharpenImageTag,progress++,
774 image->rows);
775 if (proceed == MagickFalse)
776 status=MagickFalse;
777 }
778 }
779 sharp_image->type=image->type;
780 sharp_view=DestroyCacheView(sharp_view);
781 edge_view=DestroyCacheView(edge_view);
782 image_view=DestroyCacheView(image_view);
783 edge_image=DestroyImage(edge_image);
cristybb503372010-05-27 20:51:26 +0000784 for (i=0; i < (ssize_t) width; i+=2)
cristy59d1f6d2012-01-17 03:17:40 +0000785 kernel[i]=(double *) RelinquishAlignedMemory(kernel[i]);
cristy219a63a2012-01-17 12:30:33 +0000786 kernel=(double **) RelinquishAlignedMemory(kernel);
cristy3ed852e2009-09-05 21:47:34 +0000787 if (status == MagickFalse)
788 sharp_image=DestroyImage(sharp_image);
789 return(sharp_image);
790}
791
792/*
793%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
794% %
795% %
796% %
797% B l u r I m a g e %
798% %
799% %
800% %
801%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
802%
803% BlurImage() blurs an image. We convolve the image with a Gaussian operator
804% of the given radius and standard deviation (sigma). For reasonable results,
805% the radius should be larger than sigma. Use a radius of 0 and BlurImage()
806% selects a suitable radius for you.
807%
808% BlurImage() differs from GaussianBlurImage() in that it uses a separable
809% kernel which is faster but mathematically equivalent to the non-separable
810% kernel.
811%
812% The format of the BlurImage method is:
813%
814% Image *BlurImage(const Image *image,const double radius,
cristyaa2c16c2012-03-25 22:21:35 +0000815% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000816%
817% A description of each parameter follows:
818%
819% o image: the image.
820%
cristy3ed852e2009-09-05 21:47:34 +0000821% o radius: the radius of the Gaussian, in pixels, not counting the center
822% pixel.
823%
824% o sigma: the standard deviation of the Gaussian, in pixels.
825%
826% o exception: return any errors or warnings in this structure.
827%
828*/
829
cristybb503372010-05-27 20:51:26 +0000830static double *GetBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +0000831{
cristy3ed852e2009-09-05 21:47:34 +0000832 double
cristy47e00502009-12-17 19:19:57 +0000833 *kernel,
834 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000835
cristy117ff172010-08-15 21:35:32 +0000836 register ssize_t
837 i;
838
cristybb503372010-05-27 20:51:26 +0000839 ssize_t
cristy47e00502009-12-17 19:19:57 +0000840 j,
841 k;
cristy3ed852e2009-09-05 21:47:34 +0000842
cristy3ed852e2009-09-05 21:47:34 +0000843 /*
844 Generate a 1-D convolution kernel.
845 */
846 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
cristy8b49f382012-01-17 03:11:03 +0000847 kernel=(double *) AcquireAlignedMemory((size_t) width,sizeof(*kernel));
cristy3ed852e2009-09-05 21:47:34 +0000848 if (kernel == (double *) NULL)
849 return(0);
cristy3ed852e2009-09-05 21:47:34 +0000850 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000851 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +0000852 i=0;
853 for (k=(-j); k <= j; k++)
854 {
cristy4205a3c2010-09-12 20:19:59 +0000855 kernel[i]=(double) (exp(-((double) k*k)/(2.0*MagickSigma*MagickSigma))/
856 (MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +0000857 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +0000858 i++;
859 }
cristybb503372010-05-27 20:51:26 +0000860 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000861 kernel[i]/=normalize;
862 return(kernel);
863}
864
cristyf4ad9df2011-07-08 16:49:03 +0000865MagickExport Image *BlurImage(const Image *image,const double radius,
cristyaa2c16c2012-03-25 22:21:35 +0000866 const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000867{
868#define BlurImageTag "Blur/Image"
869
cristyc4c8d132010-01-07 01:58:38 +0000870 CacheView
871 *blur_view,
872 *image_view;
873
cristy3ed852e2009-09-05 21:47:34 +0000874 double
875 *kernel;
876
877 Image
878 *blur_image;
879
cristy3ed852e2009-09-05 21:47:34 +0000880 MagickBooleanType
881 status;
882
cristybb503372010-05-27 20:51:26 +0000883 MagickOffsetType
884 progress;
885
cristybb503372010-05-27 20:51:26 +0000886 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000887 i;
888
cristybb503372010-05-27 20:51:26 +0000889 size_t
cristy3ed852e2009-09-05 21:47:34 +0000890 width;
891
cristybb503372010-05-27 20:51:26 +0000892 ssize_t
cristyb41a1172011-09-06 00:55:14 +0000893 center,
cristybb503372010-05-27 20:51:26 +0000894 x,
895 y;
896
cristy3ed852e2009-09-05 21:47:34 +0000897 /*
898 Initialize blur image attributes.
899 */
900 assert(image != (Image *) NULL);
901 assert(image->signature == MagickSignature);
902 if (image->debug != MagickFalse)
903 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
904 assert(exception != (ExceptionInfo *) NULL);
905 assert(exception->signature == MagickSignature);
cristyd25c77e2011-09-06 00:10:24 +0000906 blur_image=CloneImage(image,0,0,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +0000907 if (blur_image == (Image *) NULL)
908 return((Image *) NULL);
909 if (fabs(sigma) <= MagickEpsilon)
910 return(blur_image);
cristy574cc262011-08-05 01:23:58 +0000911 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000912 {
cristy3ed852e2009-09-05 21:47:34 +0000913 blur_image=DestroyImage(blur_image);
914 return((Image *) NULL);
915 }
916 width=GetOptimalKernelWidth1D(radius,sigma);
917 kernel=GetBlurKernel(width,sigma);
918 if (kernel == (double *) NULL)
919 {
920 blur_image=DestroyImage(blur_image);
921 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
922 }
923 if (image->debug != MagickFalse)
924 {
925 char
926 format[MaxTextExtent],
927 *message;
928
929 register const double
930 *k;
931
932 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristyef1f9072012-01-20 14:43:06 +0000933 " blur image with kernel width %.20g:",(double) width);
cristy3ed852e2009-09-05 21:47:34 +0000934 message=AcquireString("");
935 k=kernel;
cristybb503372010-05-27 20:51:26 +0000936 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000937 {
938 *message='\0';
cristyb51dff52011-05-19 16:55:47 +0000939 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) i);
cristy3ed852e2009-09-05 21:47:34 +0000940 (void) ConcatenateString(&message,format);
cristyb51dff52011-05-19 16:55:47 +0000941 (void) FormatLocaleString(format,MaxTextExtent,"%g ",*k++);
cristy3ed852e2009-09-05 21:47:34 +0000942 (void) ConcatenateString(&message,format);
943 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
944 }
945 message=DestroyString(message);
946 }
947 /*
948 Blur rows.
949 */
950 status=MagickTrue;
951 progress=0;
cristyb41a1172011-09-06 00:55:14 +0000952 center=(ssize_t) GetPixelChannels(image)*(width/2L);
cristydb070952012-04-20 14:33:00 +0000953 image_view=AcquireVirtualCacheView(image,exception);
954 blur_view=AcquireAuthenticCacheView(blur_image,exception);
cristyb5d5f722009-11-04 03:03:49 +0000955#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000956 #pragma omp parallel for schedule(static,4) shared(progress,status) \
957 if ((image->rows*image->columns) > 8192) \
958 num_threads(GetMagickResourceLimit(ThreadResource))
cristy3ed852e2009-09-05 21:47:34 +0000959#endif
cristyb41a1172011-09-06 00:55:14 +0000960 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000961 {
cristy4c08aed2011-07-01 19:47:50 +0000962 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000963 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000964
cristy4c08aed2011-07-01 19:47:50 +0000965 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000966 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000967
cristy117ff172010-08-15 21:35:32 +0000968 register ssize_t
969 x;
970
cristy3ed852e2009-09-05 21:47:34 +0000971 if (status == MagickFalse)
972 continue;
cristy117ff172010-08-15 21:35:32 +0000973 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y,
974 image->columns+width,1,exception);
cristy8b49f382012-01-17 03:11:03 +0000975 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +0000976 exception);
cristy4c08aed2011-07-01 19:47:50 +0000977 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000978 {
979 status=MagickFalse;
980 continue;
981 }
cristyb41a1172011-09-06 00:55:14 +0000982 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000983 {
cristybb503372010-05-27 20:51:26 +0000984 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000985 i;
986
cristy177e41c2012-04-15 15:08:25 +0000987 if (GetPixelMask(image,p) != 0)
988 {
989 p+=GetPixelChannels(image);
990 q+=GetPixelChannels(blur_image);
991 continue;
992 }
cristyb41a1172011-09-06 00:55:14 +0000993 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
994 {
995 MagickRealType
996 alpha,
997 gamma,
998 pixel;
cristyd25c77e2011-09-06 00:10:24 +0000999
cristyb41a1172011-09-06 00:55:14 +00001000 PixelChannel
1001 channel;
1002
1003 PixelTrait
1004 blur_traits,
1005 traits;
1006
1007 register const double
1008 *restrict k;
1009
1010 register const Quantum
1011 *restrict pixels;
1012
1013 register ssize_t
1014 u;
1015
cristye2a912b2011-12-05 20:02:07 +00001016 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +00001017 traits=GetPixelChannelMapTraits(image,channel);
cristyb41a1172011-09-06 00:55:14 +00001018 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
1019 if ((traits == UndefinedPixelTrait) ||
1020 (blur_traits == UndefinedPixelTrait))
1021 continue;
cristy177e41c2012-04-15 15:08:25 +00001022 if ((blur_traits & CopyPixelTrait) != 0)
cristyd25c77e2011-09-06 00:10:24 +00001023 {
cristy0beccfa2011-09-25 20:47:53 +00001024 SetPixelChannel(blur_image,channel,p[center+i],q);
cristyb41a1172011-09-06 00:55:14 +00001025 continue;
cristyd25c77e2011-09-06 00:10:24 +00001026 }
cristyb41a1172011-09-06 00:55:14 +00001027 k=kernel;
1028 pixels=p;
1029 pixel=0.0;
1030 if ((blur_traits & BlendPixelTrait) == 0)
1031 {
1032 /*
1033 No alpha blending.
1034 */
1035 for (u=0; u < (ssize_t) width; u++)
cristyd25c77e2011-09-06 00:10:24 +00001036 {
cristyb41a1172011-09-06 00:55:14 +00001037 pixel+=(*k)*pixels[i];
1038 k++;
1039 pixels+=GetPixelChannels(image);
cristyd25c77e2011-09-06 00:10:24 +00001040 }
cristy0beccfa2011-09-25 20:47:53 +00001041 SetPixelChannel(blur_image,channel,ClampToQuantum(pixel),q);
cristyb41a1172011-09-06 00:55:14 +00001042 continue;
1043 }
1044 /*
1045 Alpha blending.
1046 */
1047 gamma=0.0;
1048 for (u=0; u < (ssize_t) width; u++)
1049 {
1050 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,pixels));
1051 pixel+=(*k)*alpha*pixels[i];
1052 gamma+=(*k)*alpha;
1053 k++;
1054 pixels+=GetPixelChannels(image);
cristyd25c77e2011-09-06 00:10:24 +00001055 }
cristyb41a1172011-09-06 00:55:14 +00001056 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +00001057 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristyb41a1172011-09-06 00:55:14 +00001058 }
cristyed231572011-07-14 02:18:59 +00001059 p+=GetPixelChannels(image);
1060 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001061 }
1062 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1063 status=MagickFalse;
1064 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1065 {
1066 MagickBooleanType
1067 proceed;
1068
cristyb5d5f722009-11-04 03:03:49 +00001069#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyef1f9072012-01-20 14:43:06 +00001070 #pragma omp critical (MagickCore_BlurImage)
cristy3ed852e2009-09-05 21:47:34 +00001071#endif
1072 proceed=SetImageProgress(image,BlurImageTag,progress++,blur_image->rows+
1073 blur_image->columns);
1074 if (proceed == MagickFalse)
1075 status=MagickFalse;
1076 }
1077 }
1078 blur_view=DestroyCacheView(blur_view);
1079 image_view=DestroyCacheView(image_view);
1080 /*
1081 Blur columns.
1082 */
cristyef1f9072012-01-20 14:43:06 +00001083 center=(ssize_t) GetPixelChannels(blur_image)*(width/2L);
cristydb070952012-04-20 14:33:00 +00001084 image_view=AcquireVirtualCacheView(blur_image,exception);
1085 blur_view=AcquireAuthenticCacheView(blur_image,exception);
cristyb5d5f722009-11-04 03:03:49 +00001086#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001087 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1088 if ((blur_image->rows*blur_image->columns) > 8192) \
1089 num_threads(GetMagickResourceLimit(ThreadResource))
cristy3ed852e2009-09-05 21:47:34 +00001090#endif
cristyb6dc4b72011-12-10 23:29:53 +00001091 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001092 {
cristy4c08aed2011-07-01 19:47:50 +00001093 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001094 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001095
cristy4c08aed2011-07-01 19:47:50 +00001096 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001097 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001098
cristy117ff172010-08-15 21:35:32 +00001099 register ssize_t
1100 y;
1101
cristy3ed852e2009-09-05 21:47:34 +00001102 if (status == MagickFalse)
1103 continue;
cristy117ff172010-08-15 21:35:32 +00001104 p=GetCacheViewVirtualPixels(image_view,x,-((ssize_t) width/2L),1,
cristyb6dc4b72011-12-10 23:29:53 +00001105 blur_image->rows+width,exception);
cristy3ed852e2009-09-05 21:47:34 +00001106 q=GetCacheViewAuthenticPixels(blur_view,x,0,1,blur_image->rows,exception);
cristy4c08aed2011-07-01 19:47:50 +00001107 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001108 {
1109 status=MagickFalse;
1110 continue;
1111 }
cristyb6dc4b72011-12-10 23:29:53 +00001112 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001113 {
cristybb503372010-05-27 20:51:26 +00001114 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001115 i;
1116
cristy177e41c2012-04-15 15:08:25 +00001117 if (GetPixelMask(image,p) != 0)
1118 {
1119 p+=GetPixelChannels(blur_image);
1120 q+=GetPixelChannels(blur_image);
1121 continue;
1122 }
cristyb6dc4b72011-12-10 23:29:53 +00001123 for (i=0; i < (ssize_t) GetPixelChannels(blur_image); i++)
cristyb41a1172011-09-06 00:55:14 +00001124 {
1125 MagickRealType
1126 alpha,
1127 gamma,
1128 pixel;
cristyd25c77e2011-09-06 00:10:24 +00001129
cristyb41a1172011-09-06 00:55:14 +00001130 PixelChannel
1131 channel;
1132
1133 PixelTrait
1134 blur_traits,
1135 traits;
1136
1137 register const double
1138 *restrict k;
1139
1140 register const Quantum
1141 *restrict pixels;
1142
1143 register ssize_t
1144 u;
1145
cristye2a912b2011-12-05 20:02:07 +00001146 channel=GetPixelChannelMapChannel(blur_image,i);
cristyabace412011-12-11 15:56:53 +00001147 traits=GetPixelChannelMapTraits(blur_image,channel);
cristyb41a1172011-09-06 00:55:14 +00001148 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
1149 if ((traits == UndefinedPixelTrait) ||
1150 (blur_traits == UndefinedPixelTrait))
1151 continue;
cristy177e41c2012-04-15 15:08:25 +00001152 if ((blur_traits & CopyPixelTrait) != 0)
cristyd25c77e2011-09-06 00:10:24 +00001153 {
cristy0beccfa2011-09-25 20:47:53 +00001154 SetPixelChannel(blur_image,channel,p[center+i],q);
cristyb41a1172011-09-06 00:55:14 +00001155 continue;
cristyd25c77e2011-09-06 00:10:24 +00001156 }
cristyb41a1172011-09-06 00:55:14 +00001157 k=kernel;
1158 pixels=p;
1159 pixel=0.0;
1160 if ((blur_traits & BlendPixelTrait) == 0)
1161 {
1162 /*
1163 No alpha blending.
1164 */
1165 for (u=0; u < (ssize_t) width; u++)
cristyd25c77e2011-09-06 00:10:24 +00001166 {
cristyb41a1172011-09-06 00:55:14 +00001167 pixel+=(*k)*pixels[i];
1168 k++;
1169 pixels+=GetPixelChannels(blur_image);
cristyd25c77e2011-09-06 00:10:24 +00001170 }
cristy0beccfa2011-09-25 20:47:53 +00001171 SetPixelChannel(blur_image,channel,ClampToQuantum(pixel),q);
cristyb41a1172011-09-06 00:55:14 +00001172 continue;
1173 }
1174 /*
1175 Alpha blending.
1176 */
1177 gamma=0.0;
1178 for (u=0; u < (ssize_t) width; u++)
1179 {
cristyb6dc4b72011-12-10 23:29:53 +00001180 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(blur_image,
1181 pixels));
cristyb41a1172011-09-06 00:55:14 +00001182 pixel+=(*k)*alpha*pixels[i];
1183 gamma+=(*k)*alpha;
1184 k++;
1185 pixels+=GetPixelChannels(blur_image);
cristyd25c77e2011-09-06 00:10:24 +00001186 }
cristyb41a1172011-09-06 00:55:14 +00001187 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +00001188 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristyb41a1172011-09-06 00:55:14 +00001189 }
cristyd25c77e2011-09-06 00:10:24 +00001190 p+=GetPixelChannels(blur_image);
cristyed231572011-07-14 02:18:59 +00001191 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001192 }
1193 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1194 status=MagickFalse;
cristy4c08aed2011-07-01 19:47:50 +00001195 if (blur_image->progress_monitor != (MagickProgressMonitor) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001196 {
1197 MagickBooleanType
1198 proceed;
1199
cristyb5d5f722009-11-04 03:03:49 +00001200#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyef1f9072012-01-20 14:43:06 +00001201 #pragma omp critical (MagickCore_BlurImage)
cristy3ed852e2009-09-05 21:47:34 +00001202#endif
cristy4c08aed2011-07-01 19:47:50 +00001203 proceed=SetImageProgress(blur_image,BlurImageTag,progress++,
1204 blur_image->rows+blur_image->columns);
cristy3ed852e2009-09-05 21:47:34 +00001205 if (proceed == MagickFalse)
1206 status=MagickFalse;
1207 }
1208 }
1209 blur_view=DestroyCacheView(blur_view);
1210 image_view=DestroyCacheView(image_view);
cristy8b49f382012-01-17 03:11:03 +00001211 kernel=(double *) RelinquishAlignedMemory(kernel);
1212 blur_image->type=image->type;
cristy3ed852e2009-09-05 21:47:34 +00001213 if (status == MagickFalse)
1214 blur_image=DestroyImage(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001215 return(blur_image);
1216}
1217
1218/*
1219%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1220% %
1221% %
1222% %
cristyfccdab92009-11-30 16:43:57 +00001223% C o n v o l v e I m a g e %
1224% %
1225% %
1226% %
1227%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1228%
1229% ConvolveImage() applies a custom convolution kernel to the image.
1230%
1231% The format of the ConvolveImage method is:
1232%
cristy5e6be1e2011-07-16 01:23:39 +00001233% Image *ConvolveImage(const Image *image,const KernelInfo *kernel,
1234% ExceptionInfo *exception)
1235%
cristyfccdab92009-11-30 16:43:57 +00001236% A description of each parameter follows:
1237%
1238% o image: the image.
1239%
cristy5e6be1e2011-07-16 01:23:39 +00001240% o kernel: the filtering kernel.
cristyfccdab92009-11-30 16:43:57 +00001241%
1242% o exception: return any errors or warnings in this structure.
1243%
1244*/
cristy5e6be1e2011-07-16 01:23:39 +00001245MagickExport Image *ConvolveImage(const Image *image,
1246 const KernelInfo *kernel_info,ExceptionInfo *exception)
cristyfccdab92009-11-30 16:43:57 +00001247{
cristy1d326872012-03-07 22:21:31 +00001248 return(MorphologyImage(image,ConvolveMorphology,1,kernel_info,exception));
cristyfccdab92009-11-30 16:43:57 +00001249}
1250
1251/*
1252%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1253% %
1254% %
1255% %
cristy3ed852e2009-09-05 21:47:34 +00001256% D e s p e c k l e I m a g e %
1257% %
1258% %
1259% %
1260%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1261%
1262% DespeckleImage() reduces the speckle noise in an image while perserving the
cristyf0ae7762012-01-15 22:04:03 +00001263% edges of the original image. A speckle removing filter uses a complementary % hulling technique (raising pixels that are darker than their surrounding
1264% neighbors, then complementarily lowering pixels that are brighter than their
1265% surrounding neighbors) to reduce the speckle index of that image (reference
1266% Crimmins speckle removal).
cristy3ed852e2009-09-05 21:47:34 +00001267%
1268% The format of the DespeckleImage method is:
1269%
1270% Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1271%
1272% A description of each parameter follows:
1273%
1274% o image: the image.
1275%
1276% o exception: return any errors or warnings in this structure.
1277%
1278*/
1279
cristy5473bf02012-01-16 19:15:08 +00001280static void Hull(const ssize_t x_offset,const ssize_t y_offset,
1281 const size_t columns,const size_t rows,const int polarity,Quantum *restrict f,
1282 Quantum *restrict g)
cristy3ed852e2009-09-05 21:47:34 +00001283{
cristy7b4b8862012-01-16 19:52:19 +00001284 register Quantum
cristy5473bf02012-01-16 19:15:08 +00001285 *p,
1286 *q,
1287 *r,
1288 *s;
cristy78f3de22012-01-16 00:43:18 +00001289
cristy5473bf02012-01-16 19:15:08 +00001290 ssize_t
1291 y;
1292
1293 assert(f != (Quantum *) NULL);
1294 assert(g != (Quantum *) NULL);
1295 p=f+(columns+2);
1296 q=g+(columns+2);
1297 r=p+(y_offset*(columns+2)+x_offset);
cristyff8e85a2012-01-18 13:36:20 +00001298#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001299 #pragma omp parallel for schedule(static) \
1300 if ((rows*columns) > 8192) \
1301 num_threads(GetMagickResourceLimit(ThreadResource))
cristy5473bf02012-01-16 19:15:08 +00001302#endif
1303 for (y=0; y < (ssize_t) rows; y++)
1304 {
cristy7b4b8862012-01-16 19:52:19 +00001305 register ssize_t
cristy5473bf02012-01-16 19:15:08 +00001306 i,
1307 x;
1308
cristy7b4b8862012-01-16 19:52:19 +00001309 SignedQuantum
1310 v;
1311
cristy5473bf02012-01-16 19:15:08 +00001312 i=(2*y+1)+y*columns;
1313 if (polarity > 0)
1314 for (x=0; x < (ssize_t) columns; x++)
1315 {
1316 v=(SignedQuantum) p[i];
1317 if ((SignedQuantum) r[i] >= (v+ScaleCharToQuantum(2)))
1318 v+=ScaleCharToQuantum(1);
1319 q[i]=(Quantum) v;
1320 i++;
1321 }
1322 else
1323 for (x=0; x < (ssize_t) columns; x++)
1324 {
1325 v=(SignedQuantum) p[i];
1326 if ((SignedQuantum) r[i] <= (v-ScaleCharToQuantum(2)))
1327 v-=ScaleCharToQuantum(1);
1328 q[i]=(Quantum) v;
1329 i++;
1330 }
1331 }
1332 p=f+(columns+2);
1333 q=g+(columns+2);
1334 r=q+(y_offset*(columns+2)+x_offset);
1335 s=q-(y_offset*(columns+2)+x_offset);
cristyff8e85a2012-01-18 13:36:20 +00001336#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001337 #pragma omp parallel for schedule(static) \
1338 if ((rows*columns) > 8192) \
1339 num_threads(GetMagickResourceLimit(ThreadResource))
cristy5473bf02012-01-16 19:15:08 +00001340#endif
1341 for (y=0; y < (ssize_t) rows; y++)
1342 {
cristy7b4b8862012-01-16 19:52:19 +00001343 register ssize_t
cristy5473bf02012-01-16 19:15:08 +00001344 i,
1345 x;
1346
cristy7b4b8862012-01-16 19:52:19 +00001347 SignedQuantum
1348 v;
1349
cristy5473bf02012-01-16 19:15:08 +00001350 i=(2*y+1)+y*columns;
1351 if (polarity > 0)
1352 for (x=0; x < (ssize_t) columns; x++)
1353 {
1354 v=(SignedQuantum) q[i];
1355 if (((SignedQuantum) s[i] >= (v+ScaleCharToQuantum(2))) &&
1356 ((SignedQuantum) r[i] > v))
1357 v+=ScaleCharToQuantum(1);
1358 p[i]=(Quantum) v;
1359 i++;
1360 }
1361 else
1362 for (x=0; x < (ssize_t) columns; x++)
1363 {
1364 v=(SignedQuantum) q[i];
1365 if (((SignedQuantum) s[i] <= (v-ScaleCharToQuantum(2))) &&
1366 ((SignedQuantum) r[i] < v))
1367 v-=ScaleCharToQuantum(1);
1368 p[i]=(Quantum) v;
1369 i++;
1370 }
1371 }
cristy3ed852e2009-09-05 21:47:34 +00001372}
1373
1374MagickExport Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1375{
1376#define DespeckleImageTag "Despeckle/Image"
1377
cristy2407fc22009-09-11 00:55:25 +00001378 CacheView
1379 *despeckle_view,
1380 *image_view;
1381
cristy3ed852e2009-09-05 21:47:34 +00001382 Image
1383 *despeckle_image;
1384
cristy3ed852e2009-09-05 21:47:34 +00001385 MagickBooleanType
1386 status;
1387
cristy5473bf02012-01-16 19:15:08 +00001388 Quantum
1389 *restrict buffer,
1390 *restrict pixels;
cristya63e4a92011-09-09 00:47:59 +00001391
cristy5473bf02012-01-16 19:15:08 +00001392 register ssize_t
1393 i;
1394
1395 size_t
1396 length;
cristy117ff172010-08-15 21:35:32 +00001397
cristybb503372010-05-27 20:51:26 +00001398 static const ssize_t
cristy691a29e2009-09-11 00:44:10 +00001399 X[4] = {0, 1, 1,-1},
1400 Y[4] = {1, 0, 1, 1};
cristy3ed852e2009-09-05 21:47:34 +00001401
cristy3ed852e2009-09-05 21:47:34 +00001402 /*
1403 Allocate despeckled image.
1404 */
1405 assert(image != (const Image *) NULL);
1406 assert(image->signature == MagickSignature);
1407 if (image->debug != MagickFalse)
1408 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1409 assert(exception != (ExceptionInfo *) NULL);
1410 assert(exception->signature == MagickSignature);
cristy5473bf02012-01-16 19:15:08 +00001411 despeckle_image=CloneImage(image,0,0,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00001412 if (despeckle_image == (Image *) NULL)
1413 return((Image *) NULL);
cristya63e4a92011-09-09 00:47:59 +00001414 status=SetImageStorageClass(despeckle_image,DirectClass,exception);
1415 if (status == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001416 {
cristy3ed852e2009-09-05 21:47:34 +00001417 despeckle_image=DestroyImage(despeckle_image);
1418 return((Image *) NULL);
1419 }
1420 /*
cristy5473bf02012-01-16 19:15:08 +00001421 Allocate image buffer.
1422 */
1423 length=(size_t) ((image->columns+2)*(image->rows+2));
1424 pixels=(Quantum *) AcquireQuantumMemory(length,sizeof(*pixels));
1425 buffer=(Quantum *) AcquireQuantumMemory(length,sizeof(*buffer));
1426 if ((pixels == (Quantum *) NULL) || (buffer == (Quantum *) NULL))
1427 {
1428 if (buffer != (Quantum *) NULL)
1429 buffer=(Quantum *) RelinquishMagickMemory(buffer);
1430 if (pixels != (Quantum *) NULL)
1431 pixels=(Quantum *) RelinquishMagickMemory(pixels);
1432 despeckle_image=DestroyImage(despeckle_image);
1433 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1434 }
1435 /*
1436 Reduce speckle in the image.
cristy3ed852e2009-09-05 21:47:34 +00001437 */
1438 status=MagickTrue;
cristydb070952012-04-20 14:33:00 +00001439 image_view=AcquireVirtualCacheView(image,exception);
1440 despeckle_view=AcquireAuthenticCacheView(despeckle_image,exception);
cristy5473bf02012-01-16 19:15:08 +00001441 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy3ed852e2009-09-05 21:47:34 +00001442 {
cristy5473bf02012-01-16 19:15:08 +00001443 PixelChannel
1444 channel;
1445
1446 PixelTrait
1447 despeckle_traits,
1448 traits;
cristy3ed852e2009-09-05 21:47:34 +00001449
cristyc1488b52011-02-19 18:54:15 +00001450 register ssize_t
cristy5473bf02012-01-16 19:15:08 +00001451 k,
cristyf0ae7762012-01-15 22:04:03 +00001452 x;
cristyc1488b52011-02-19 18:54:15 +00001453
cristy5473bf02012-01-16 19:15:08 +00001454 ssize_t
1455 j,
1456 y;
1457
1458 if (status == MagickFalse)
1459 continue;
1460 channel=GetPixelChannelMapChannel(image,i);
1461 traits=GetPixelChannelMapTraits(image,channel);
1462 despeckle_traits=GetPixelChannelMapTraits(despeckle_image,channel);
1463 if ((traits == UndefinedPixelTrait) ||
1464 (despeckle_traits == UndefinedPixelTrait))
1465 continue;
1466 if ((despeckle_traits & CopyPixelTrait) != 0)
1467 continue;
1468 (void) ResetMagickMemory(pixels,0,length*sizeof(*pixels));
1469 j=(ssize_t) image->columns+2;
1470 for (y=0; y < (ssize_t) image->rows; y++)
cristyfc830f42012-01-15 02:45:06 +00001471 {
cristy5473bf02012-01-16 19:15:08 +00001472 register const Quantum
1473 *restrict p;
cristyfc830f42012-01-15 02:45:06 +00001474
cristy5473bf02012-01-16 19:15:08 +00001475 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1476 if (p == (const Quantum *) NULL)
cristyf0ae7762012-01-15 22:04:03 +00001477 {
cristy5473bf02012-01-16 19:15:08 +00001478 status=MagickFalse;
1479 continue;
cristyf0ae7762012-01-15 22:04:03 +00001480 }
cristy5473bf02012-01-16 19:15:08 +00001481 j++;
1482 for (x=0; x < (ssize_t) image->columns; x++)
1483 {
1484 pixels[j++]=p[i];
1485 p+=GetPixelChannels(image);
cristyfc830f42012-01-15 02:45:06 +00001486 }
cristy5473bf02012-01-16 19:15:08 +00001487 j++;
cristy3ed852e2009-09-05 21:47:34 +00001488 }
cristy5473bf02012-01-16 19:15:08 +00001489 (void) ResetMagickMemory(buffer,0,length*sizeof(*buffer));
1490 for (k=0; k < 4; k++)
1491 {
1492 Hull(X[k],Y[k],image->columns,image->rows,1,pixels,buffer);
1493 Hull(-X[k],-Y[k],image->columns,image->rows,1,pixels,buffer);
1494 Hull(-X[k],-Y[k],image->columns,image->rows,-1,pixels,buffer);
1495 Hull(X[k],Y[k],image->columns,image->rows,-1,pixels,buffer);
1496 }
1497 j=(ssize_t) image->columns+2;
1498 for (y=0; y < (ssize_t) image->rows; y++)
1499 {
1500 MagickBooleanType
1501 sync;
1502
1503 register Quantum
1504 *restrict q;
1505
cristyd13e8eb2012-01-17 02:36:42 +00001506 q=QueueCacheViewAuthenticPixels(despeckle_view,0,y,
1507 despeckle_image->columns,1,exception);
cristy5473bf02012-01-16 19:15:08 +00001508 if (q == (Quantum *) NULL)
1509 {
1510 status=MagickFalse;
1511 continue;
1512 }
1513 j++;
1514 for (x=0; x < (ssize_t) image->columns; x++)
1515 {
1516 SetPixelChannel(despeckle_image,channel,pixels[j++],q);
1517 q+=GetPixelChannels(despeckle_image);
1518 }
1519 sync=SyncCacheViewAuthenticPixels(despeckle_view,exception);
1520 if (sync == MagickFalse)
1521 status=MagickFalse;
1522 j++;
1523 }
cristy3ed852e2009-09-05 21:47:34 +00001524 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1525 {
1526 MagickBooleanType
1527 proceed;
1528
cristy5473bf02012-01-16 19:15:08 +00001529 proceed=SetImageProgress(image,DespeckleImageTag,(MagickOffsetType) i,
1530 GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00001531 if (proceed == MagickFalse)
1532 status=MagickFalse;
1533 }
1534 }
1535 despeckle_view=DestroyCacheView(despeckle_view);
1536 image_view=DestroyCacheView(image_view);
cristy5473bf02012-01-16 19:15:08 +00001537 buffer=(Quantum *) RelinquishMagickMemory(buffer);
1538 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001539 despeckle_image->type=image->type;
1540 if (status == MagickFalse)
1541 despeckle_image=DestroyImage(despeckle_image);
1542 return(despeckle_image);
1543}
1544
1545/*
1546%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1547% %
1548% %
1549% %
1550% E d g e I m a g e %
1551% %
1552% %
1553% %
1554%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1555%
1556% EdgeImage() finds edges in an image. Radius defines the radius of the
1557% convolution filter. Use a radius of 0 and EdgeImage() selects a suitable
1558% radius for you.
1559%
1560% The format of the EdgeImage method is:
1561%
1562% Image *EdgeImage(const Image *image,const double radius,
cristy8ae632d2011-09-05 17:29:53 +00001563% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001564%
1565% A description of each parameter follows:
1566%
1567% o image: the image.
1568%
1569% o radius: the radius of the pixel neighborhood.
1570%
cristy8ae632d2011-09-05 17:29:53 +00001571% o sigma: the standard deviation of the Gaussian, in pixels.
1572%
cristy3ed852e2009-09-05 21:47:34 +00001573% o exception: return any errors or warnings in this structure.
1574%
1575*/
1576MagickExport Image *EdgeImage(const Image *image,const double radius,
cristy8ae632d2011-09-05 17:29:53 +00001577 const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001578{
1579 Image
1580 *edge_image;
1581
cristy41cbe682011-07-15 19:12:37 +00001582 KernelInfo
1583 *kernel_info;
cristy3ed852e2009-09-05 21:47:34 +00001584
cristybb503372010-05-27 20:51:26 +00001585 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001586 i;
1587
cristybb503372010-05-27 20:51:26 +00001588 size_t
cristy3ed852e2009-09-05 21:47:34 +00001589 width;
1590
cristy41cbe682011-07-15 19:12:37 +00001591 ssize_t
1592 j,
1593 u,
1594 v;
1595
cristy3ed852e2009-09-05 21:47:34 +00001596 assert(image != (const Image *) NULL);
1597 assert(image->signature == MagickSignature);
1598 if (image->debug != MagickFalse)
1599 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1600 assert(exception != (ExceptionInfo *) NULL);
1601 assert(exception->signature == MagickSignature);
cristyfc0ae532011-12-06 15:14:45 +00001602 width=GetOptimalKernelWidth1D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00001603 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00001604 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001605 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00001606 kernel_info->width=width;
1607 kernel_info->height=width;
cristya96f2492011-12-14 18:25:41 +00001608 kernel_info->values=(MagickRealType *) AcquireAlignedMemory(
1609 kernel_info->width,kernel_info->width*sizeof(*kernel_info->values));
1610 if (kernel_info->values == (MagickRealType *) NULL)
cristy41cbe682011-07-15 19:12:37 +00001611 {
1612 kernel_info=DestroyKernelInfo(kernel_info);
1613 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1614 }
1615 j=(ssize_t) kernel_info->width/2;
1616 i=0;
1617 for (v=(-j); v <= j; v++)
1618 {
1619 for (u=(-j); u <= j; u++)
1620 {
1621 kernel_info->values[i]=(-1.0);
1622 i++;
1623 }
1624 }
1625 kernel_info->values[i/2]=(double) (width*width-1.0);
cristy5e6be1e2011-07-16 01:23:39 +00001626 edge_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00001627 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00001628 return(edge_image);
1629}
1630
1631/*
1632%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1633% %
1634% %
1635% %
1636% E m b o s s I m a g e %
1637% %
1638% %
1639% %
1640%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1641%
1642% EmbossImage() returns a grayscale image with a three-dimensional effect.
1643% We convolve the image with a Gaussian operator of the given radius and
1644% standard deviation (sigma). For reasonable results, radius should be
1645% larger than sigma. Use a radius of 0 and Emboss() selects a suitable
1646% radius for you.
1647%
1648% The format of the EmbossImage method is:
1649%
1650% Image *EmbossImage(const Image *image,const double radius,
1651% const double sigma,ExceptionInfo *exception)
1652%
1653% A description of each parameter follows:
1654%
1655% o image: the image.
1656%
1657% o radius: the radius of the pixel neighborhood.
1658%
1659% o sigma: the standard deviation of the Gaussian, in pixels.
1660%
1661% o exception: return any errors or warnings in this structure.
1662%
1663*/
1664MagickExport Image *EmbossImage(const Image *image,const double radius,
1665 const double sigma,ExceptionInfo *exception)
1666{
cristy3ed852e2009-09-05 21:47:34 +00001667 Image
1668 *emboss_image;
1669
cristy41cbe682011-07-15 19:12:37 +00001670 KernelInfo
1671 *kernel_info;
1672
cristybb503372010-05-27 20:51:26 +00001673 register ssize_t
cristy47e00502009-12-17 19:19:57 +00001674 i;
1675
cristybb503372010-05-27 20:51:26 +00001676 size_t
cristy3ed852e2009-09-05 21:47:34 +00001677 width;
1678
cristy117ff172010-08-15 21:35:32 +00001679 ssize_t
1680 j,
1681 k,
1682 u,
1683 v;
1684
cristy41cbe682011-07-15 19:12:37 +00001685 assert(image != (const Image *) NULL);
cristy3ed852e2009-09-05 21:47:34 +00001686 assert(image->signature == MagickSignature);
1687 if (image->debug != MagickFalse)
1688 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1689 assert(exception != (ExceptionInfo *) NULL);
1690 assert(exception->signature == MagickSignature);
cristyfc0ae532011-12-06 15:14:45 +00001691 width=GetOptimalKernelWidth1D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00001692 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00001693 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001694 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00001695 kernel_info->width=width;
1696 kernel_info->height=width;
cristya96f2492011-12-14 18:25:41 +00001697 kernel_info->values=(MagickRealType *) AcquireAlignedMemory(
1698 kernel_info->width,kernel_info->width*sizeof(*kernel_info->values));
1699 if (kernel_info->values == (MagickRealType *) NULL)
cristy41cbe682011-07-15 19:12:37 +00001700 {
1701 kernel_info=DestroyKernelInfo(kernel_info);
1702 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1703 }
1704 j=(ssize_t) kernel_info->width/2;
cristy47e00502009-12-17 19:19:57 +00001705 k=j;
1706 i=0;
1707 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00001708 {
cristy47e00502009-12-17 19:19:57 +00001709 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00001710 {
cristy41cbe682011-07-15 19:12:37 +00001711 kernel_info->values[i]=(double) (((u < 0) || (v < 0) ? -8.0 : 8.0)*
cristy47e00502009-12-17 19:19:57 +00001712 exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
cristy4205a3c2010-09-12 20:19:59 +00001713 (2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +00001714 if (u != k)
cristy41cbe682011-07-15 19:12:37 +00001715 kernel_info->values[i]=0.0;
cristy3ed852e2009-09-05 21:47:34 +00001716 i++;
1717 }
cristy47e00502009-12-17 19:19:57 +00001718 k--;
cristy3ed852e2009-09-05 21:47:34 +00001719 }
cristy5e6be1e2011-07-16 01:23:39 +00001720 emboss_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00001721 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00001722 if (emboss_image != (Image *) NULL)
cristy6d8c3d72011-08-22 01:20:01 +00001723 (void) EqualizeImage(emboss_image,exception);
cristy3ed852e2009-09-05 21:47:34 +00001724 return(emboss_image);
1725}
1726
1727/*
1728%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1729% %
1730% %
1731% %
1732% G a u s s i a n B l u r I m a g e %
1733% %
1734% %
1735% %
1736%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1737%
1738% GaussianBlurImage() blurs an image. We convolve the image with a
1739% Gaussian operator of the given radius and standard deviation (sigma).
1740% For reasonable results, the radius should be larger than sigma. Use a
1741% radius of 0 and GaussianBlurImage() selects a suitable radius for you
1742%
1743% The format of the GaussianBlurImage method is:
1744%
1745% Image *GaussianBlurImage(const Image *image,onst double radius,
cristyd89705a2012-01-20 02:52:24 +00001746% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001747%
1748% A description of each parameter follows:
1749%
1750% o image: the image.
1751%
cristy3ed852e2009-09-05 21:47:34 +00001752% o radius: the radius of the Gaussian, in pixels, not counting the center
1753% pixel.
1754%
1755% o sigma: the standard deviation of the Gaussian, in pixels.
1756%
1757% o exception: return any errors or warnings in this structure.
1758%
1759*/
cristy41cbe682011-07-15 19:12:37 +00001760MagickExport Image *GaussianBlurImage(const Image *image,const double radius,
cristyd89705a2012-01-20 02:52:24 +00001761 const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001762{
cristy3ed852e2009-09-05 21:47:34 +00001763 Image
1764 *blur_image;
1765
cristy41cbe682011-07-15 19:12:37 +00001766 KernelInfo
1767 *kernel_info;
1768
cristybb503372010-05-27 20:51:26 +00001769 register ssize_t
cristy47e00502009-12-17 19:19:57 +00001770 i;
1771
cristybb503372010-05-27 20:51:26 +00001772 size_t
cristy3ed852e2009-09-05 21:47:34 +00001773 width;
1774
cristy117ff172010-08-15 21:35:32 +00001775 ssize_t
1776 j,
1777 u,
1778 v;
1779
cristy3ed852e2009-09-05 21:47:34 +00001780 assert(image != (const Image *) NULL);
1781 assert(image->signature == MagickSignature);
1782 if (image->debug != MagickFalse)
1783 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1784 assert(exception != (ExceptionInfo *) NULL);
1785 assert(exception->signature == MagickSignature);
1786 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00001787 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00001788 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001789 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00001790 (void) ResetMagickMemory(kernel_info,0,sizeof(*kernel_info));
1791 kernel_info->width=width;
1792 kernel_info->height=width;
1793 kernel_info->signature=MagickSignature;
cristya96f2492011-12-14 18:25:41 +00001794 kernel_info->values=(MagickRealType *) AcquireAlignedMemory(
1795 kernel_info->width,kernel_info->width*sizeof(*kernel_info->values));
1796 if (kernel_info->values == (MagickRealType *) NULL)
cristy41cbe682011-07-15 19:12:37 +00001797 {
1798 kernel_info=DestroyKernelInfo(kernel_info);
1799 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1800 }
1801 j=(ssize_t) kernel_info->width/2;
cristy3ed852e2009-09-05 21:47:34 +00001802 i=0;
cristy47e00502009-12-17 19:19:57 +00001803 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00001804 {
cristy47e00502009-12-17 19:19:57 +00001805 for (u=(-j); u <= j; u++)
cristy41cbe682011-07-15 19:12:37 +00001806 {
1807 kernel_info->values[i]=(double) (exp(-((double) u*u+v*v)/(2.0*
1808 MagickSigma*MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
1809 i++;
1810 }
cristy3ed852e2009-09-05 21:47:34 +00001811 }
cristy5e6be1e2011-07-16 01:23:39 +00001812 blur_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00001813 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00001814 return(blur_image);
1815}
1816
1817/*
1818%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1819% %
1820% %
1821% %
cristy3ed852e2009-09-05 21:47:34 +00001822% M o t i o n B l u r I m a g e %
1823% %
1824% %
1825% %
1826%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1827%
1828% MotionBlurImage() simulates motion blur. We convolve the image with a
1829% Gaussian operator of the given radius and standard deviation (sigma).
1830% For reasonable results, radius should be larger than sigma. Use a
1831% radius of 0 and MotionBlurImage() selects a suitable radius for you.
1832% Angle gives the angle of the blurring motion.
1833%
1834% Andrew Protano contributed this effect.
1835%
1836% The format of the MotionBlurImage method is:
1837%
1838% Image *MotionBlurImage(const Image *image,const double radius,
cristyaa2c16c2012-03-25 22:21:35 +00001839% const double sigma,const double angle,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001840%
1841% A description of each parameter follows:
1842%
1843% o image: the image.
1844%
cristy3ed852e2009-09-05 21:47:34 +00001845% o radius: the radius of the Gaussian, in pixels, not counting
1846% the center pixel.
1847%
1848% o sigma: the standard deviation of the Gaussian, in pixels.
1849%
cristycee97112010-05-28 00:44:52 +00001850% o angle: Apply the effect along this angle.
cristy3ed852e2009-09-05 21:47:34 +00001851%
1852% o exception: return any errors or warnings in this structure.
1853%
1854*/
1855
cristybb503372010-05-27 20:51:26 +00001856static double *GetMotionBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +00001857{
cristy3ed852e2009-09-05 21:47:34 +00001858 double
cristy47e00502009-12-17 19:19:57 +00001859 *kernel,
cristy3ed852e2009-09-05 21:47:34 +00001860 normalize;
1861
cristybb503372010-05-27 20:51:26 +00001862 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001863 i;
1864
1865 /*
cristy47e00502009-12-17 19:19:57 +00001866 Generate a 1-D convolution kernel.
cristy3ed852e2009-09-05 21:47:34 +00001867 */
1868 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
cristy8b49f382012-01-17 03:11:03 +00001869 kernel=(double *) AcquireAlignedMemory((size_t) width,sizeof(*kernel));
cristy3ed852e2009-09-05 21:47:34 +00001870 if (kernel == (double *) NULL)
1871 return(kernel);
cristy3ed852e2009-09-05 21:47:34 +00001872 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00001873 for (i=0; i < (ssize_t) width; i++)
cristy47e00502009-12-17 19:19:57 +00001874 {
cristy4205a3c2010-09-12 20:19:59 +00001875 kernel[i]=(double) (exp((-((double) i*i)/(double) (2.0*MagickSigma*
1876 MagickSigma)))/(MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00001877 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +00001878 }
cristybb503372010-05-27 20:51:26 +00001879 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001880 kernel[i]/=normalize;
1881 return(kernel);
1882}
1883
cristya63e4a92011-09-09 00:47:59 +00001884MagickExport Image *MotionBlurImage(const Image *image,const double radius,
cristyaa2c16c2012-03-25 22:21:35 +00001885 const double sigma,const double angle,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001886{
cristyc4c8d132010-01-07 01:58:38 +00001887 CacheView
1888 *blur_view,
cristy8b49f382012-01-17 03:11:03 +00001889 *image_view,
1890 *motion_view;
cristyc4c8d132010-01-07 01:58:38 +00001891
cristy3ed852e2009-09-05 21:47:34 +00001892 double
1893 *kernel;
1894
1895 Image
1896 *blur_image;
1897
cristy3ed852e2009-09-05 21:47:34 +00001898 MagickBooleanType
1899 status;
1900
cristybb503372010-05-27 20:51:26 +00001901 MagickOffsetType
1902 progress;
1903
cristy3ed852e2009-09-05 21:47:34 +00001904 OffsetInfo
1905 *offset;
1906
1907 PointInfo
1908 point;
1909
cristybb503372010-05-27 20:51:26 +00001910 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001911 i;
1912
cristybb503372010-05-27 20:51:26 +00001913 size_t
cristy3ed852e2009-09-05 21:47:34 +00001914 width;
1915
cristybb503372010-05-27 20:51:26 +00001916 ssize_t
1917 y;
1918
cristy3ed852e2009-09-05 21:47:34 +00001919 assert(image != (Image *) NULL);
1920 assert(image->signature == MagickSignature);
1921 if (image->debug != MagickFalse)
1922 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1923 assert(exception != (ExceptionInfo *) NULL);
1924 width=GetOptimalKernelWidth1D(radius,sigma);
1925 kernel=GetMotionBlurKernel(width,sigma);
1926 if (kernel == (double *) NULL)
1927 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1928 offset=(OffsetInfo *) AcquireQuantumMemory(width,sizeof(*offset));
1929 if (offset == (OffsetInfo *) NULL)
1930 {
cristy8b49f382012-01-17 03:11:03 +00001931 kernel=(double *) RelinquishAlignedMemory(kernel);
cristy3ed852e2009-09-05 21:47:34 +00001932 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1933 }
cristy1e7aa312011-09-10 20:01:36 +00001934 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00001935 if (blur_image == (Image *) NULL)
1936 {
cristy8b49f382012-01-17 03:11:03 +00001937 kernel=(double *) RelinquishAlignedMemory(kernel);
cristy3ed852e2009-09-05 21:47:34 +00001938 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
1939 return((Image *) NULL);
1940 }
cristy574cc262011-08-05 01:23:58 +00001941 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001942 {
cristy8b49f382012-01-17 03:11:03 +00001943 kernel=(double *) RelinquishAlignedMemory(kernel);
cristy3ed852e2009-09-05 21:47:34 +00001944 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
cristy3ed852e2009-09-05 21:47:34 +00001945 blur_image=DestroyImage(blur_image);
1946 return((Image *) NULL);
1947 }
1948 point.x=(double) width*sin(DegreesToRadians(angle));
1949 point.y=(double) width*cos(DegreesToRadians(angle));
cristybb503372010-05-27 20:51:26 +00001950 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001951 {
cristybb503372010-05-27 20:51:26 +00001952 offset[i].x=(ssize_t) ceil((double) (i*point.y)/hypot(point.x,point.y)-0.5);
1953 offset[i].y=(ssize_t) ceil((double) (i*point.x)/hypot(point.x,point.y)-0.5);
cristy3ed852e2009-09-05 21:47:34 +00001954 }
1955 /*
1956 Motion blur image.
1957 */
1958 status=MagickTrue;
1959 progress=0;
cristydb070952012-04-20 14:33:00 +00001960 image_view=AcquireVirtualCacheView(image,exception);
1961 motion_view=AcquireVirtualCacheView(image,exception);
1962 blur_view=AcquireAuthenticCacheView(blur_image,exception);
cristyb557a152011-02-22 12:14:30 +00001963#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001964 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1965 if ((image->rows*image->columns) > 8192) \
1966 num_threads(GetMagickResourceLimit(ThreadResource))
cristy3ed852e2009-09-05 21:47:34 +00001967#endif
cristybb503372010-05-27 20:51:26 +00001968 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001969 {
cristyf7ef0252011-09-09 14:50:06 +00001970 register const Quantum
1971 *restrict p;
1972
cristy4c08aed2011-07-01 19:47:50 +00001973 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001974 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001975
cristy117ff172010-08-15 21:35:32 +00001976 register ssize_t
1977 x;
1978
cristy3ed852e2009-09-05 21:47:34 +00001979 if (status == MagickFalse)
1980 continue;
cristy8b49f382012-01-17 03:11:03 +00001981 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1982 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +00001983 exception);
cristyf7ef0252011-09-09 14:50:06 +00001984 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001985 {
1986 status=MagickFalse;
1987 continue;
1988 }
cristybb503372010-05-27 20:51:26 +00001989 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001990 {
cristybb503372010-05-27 20:51:26 +00001991 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001992 i;
1993
cristy177e41c2012-04-15 15:08:25 +00001994 if (GetPixelMask(image,p) != 0)
1995 {
1996 p+=GetPixelChannels(image);
1997 q+=GetPixelChannels(blur_image);
1998 continue;
1999 }
cristyf7ef0252011-09-09 14:50:06 +00002000 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2001 {
2002 MagickRealType
2003 alpha,
2004 gamma,
2005 pixel;
cristy3ed852e2009-09-05 21:47:34 +00002006
cristyf7ef0252011-09-09 14:50:06 +00002007 PixelChannel
2008 channel;
2009
2010 PixelTrait
2011 blur_traits,
2012 traits;
2013
2014 register const Quantum
2015 *restrict r;
2016
2017 register double
2018 *restrict k;
2019
2020 register ssize_t
2021 j;
2022
cristye2a912b2011-12-05 20:02:07 +00002023 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +00002024 traits=GetPixelChannelMapTraits(image,channel);
cristyf7ef0252011-09-09 14:50:06 +00002025 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
2026 if ((traits == UndefinedPixelTrait) ||
2027 (blur_traits == UndefinedPixelTrait))
2028 continue;
cristy177e41c2012-04-15 15:08:25 +00002029 if ((blur_traits & CopyPixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002030 {
cristy0beccfa2011-09-25 20:47:53 +00002031 SetPixelChannel(blur_image,channel,p[i],q);
cristyf7ef0252011-09-09 14:50:06 +00002032 continue;
cristy3ed852e2009-09-05 21:47:34 +00002033 }
cristyf7ef0252011-09-09 14:50:06 +00002034 k=kernel;
cristyaa2c16c2012-03-25 22:21:35 +00002035 pixel=0.0;
cristyf7ef0252011-09-09 14:50:06 +00002036 if ((blur_traits & BlendPixelTrait) == 0)
2037 {
2038 for (j=0; j < (ssize_t) width; j++)
2039 {
cristy8b49f382012-01-17 03:11:03 +00002040 r=GetCacheViewVirtualPixels(motion_view,x+offset[j].x,y+
cristyf7ef0252011-09-09 14:50:06 +00002041 offset[j].y,1,1,exception);
2042 if (r == (const Quantum *) NULL)
2043 {
2044 status=MagickFalse;
2045 continue;
2046 }
2047 pixel+=(*k)*r[i];
2048 k++;
2049 }
cristy0beccfa2011-09-25 20:47:53 +00002050 SetPixelChannel(blur_image,channel,ClampToQuantum(pixel),q);
cristyf7ef0252011-09-09 14:50:06 +00002051 continue;
2052 }
2053 alpha=0.0;
2054 gamma=0.0;
2055 for (j=0; j < (ssize_t) width; j++)
2056 {
cristy8b49f382012-01-17 03:11:03 +00002057 r=GetCacheViewVirtualPixels(motion_view,x+offset[j].x,y+offset[j].y,1,
cristyf7ef0252011-09-09 14:50:06 +00002058 1,exception);
2059 if (r == (const Quantum *) NULL)
2060 {
2061 status=MagickFalse;
2062 continue;
2063 }
2064 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,r));
2065 pixel+=(*k)*alpha*r[i];
2066 gamma+=(*k)*alpha;
2067 k++;
cristy3ed852e2009-09-05 21:47:34 +00002068 }
cristyf7ef0252011-09-09 14:50:06 +00002069 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +00002070 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristyf7ef0252011-09-09 14:50:06 +00002071 }
2072 p+=GetPixelChannels(image);
cristyed231572011-07-14 02:18:59 +00002073 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00002074 }
2075 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
2076 status=MagickFalse;
2077 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2078 {
2079 MagickBooleanType
2080 proceed;
2081
cristyb557a152011-02-22 12:14:30 +00002082#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf8561542012-01-24 00:26:46 +00002083 #pragma omp critical (MagickCore_MotionBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00002084#endif
2085 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
2086 if (proceed == MagickFalse)
2087 status=MagickFalse;
2088 }
2089 }
2090 blur_view=DestroyCacheView(blur_view);
cristy8b49f382012-01-17 03:11:03 +00002091 motion_view=DestroyCacheView(motion_view);
cristy3ed852e2009-09-05 21:47:34 +00002092 image_view=DestroyCacheView(image_view);
cristy8b49f382012-01-17 03:11:03 +00002093 kernel=(double *) RelinquishAlignedMemory(kernel);
cristy3ed852e2009-09-05 21:47:34 +00002094 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2095 if (status == MagickFalse)
2096 blur_image=DestroyImage(blur_image);
2097 return(blur_image);
2098}
2099
2100/*
2101%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2102% %
2103% %
2104% %
2105% P r e v i e w I m a g e %
2106% %
2107% %
2108% %
2109%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2110%
2111% PreviewImage() tiles 9 thumbnails of the specified image with an image
2112% processing operation applied with varying parameters. This may be helpful
2113% pin-pointing an appropriate parameter for a particular image processing
2114% operation.
2115%
2116% The format of the PreviewImages method is:
2117%
2118% Image *PreviewImages(const Image *image,const PreviewType preview,
2119% ExceptionInfo *exception)
2120%
2121% A description of each parameter follows:
2122%
2123% o image: the image.
2124%
2125% o preview: the image processing operation.
2126%
2127% o exception: return any errors or warnings in this structure.
2128%
2129*/
2130MagickExport Image *PreviewImage(const Image *image,const PreviewType preview,
2131 ExceptionInfo *exception)
2132{
2133#define NumberTiles 9
2134#define PreviewImageTag "Preview/Image"
2135#define DefaultPreviewGeometry "204x204+10+10"
2136
2137 char
2138 factor[MaxTextExtent],
2139 label[MaxTextExtent];
2140
2141 double
2142 degrees,
2143 gamma,
2144 percentage,
2145 radius,
2146 sigma,
2147 threshold;
2148
2149 Image
2150 *images,
2151 *montage_image,
2152 *preview_image,
2153 *thumbnail;
2154
2155 ImageInfo
2156 *preview_info;
2157
cristy3ed852e2009-09-05 21:47:34 +00002158 MagickBooleanType
2159 proceed;
2160
2161 MontageInfo
2162 *montage_info;
2163
2164 QuantizeInfo
2165 quantize_info;
2166
2167 RectangleInfo
2168 geometry;
2169
cristybb503372010-05-27 20:51:26 +00002170 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002171 i,
2172 x;
2173
cristybb503372010-05-27 20:51:26 +00002174 size_t
cristy3ed852e2009-09-05 21:47:34 +00002175 colors;
2176
cristy117ff172010-08-15 21:35:32 +00002177 ssize_t
2178 y;
2179
cristy3ed852e2009-09-05 21:47:34 +00002180 /*
2181 Open output image file.
2182 */
2183 assert(image != (Image *) NULL);
2184 assert(image->signature == MagickSignature);
2185 if (image->debug != MagickFalse)
2186 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2187 colors=2;
2188 degrees=0.0;
2189 gamma=(-0.2f);
2190 preview_info=AcquireImageInfo();
2191 SetGeometry(image,&geometry);
2192 (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y,
2193 &geometry.width,&geometry.height);
2194 images=NewImageList();
2195 percentage=12.5;
2196 GetQuantizeInfo(&quantize_info);
2197 radius=0.0;
2198 sigma=1.0;
2199 threshold=0.0;
2200 x=0;
2201 y=0;
2202 for (i=0; i < NumberTiles; i++)
2203 {
2204 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
2205 if (thumbnail == (Image *) NULL)
2206 break;
2207 (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL,
2208 (void *) NULL);
cristyd15e6592011-10-15 00:13:06 +00002209 (void) SetImageProperty(thumbnail,"label",DefaultTileLabel,exception);
cristy3ed852e2009-09-05 21:47:34 +00002210 if (i == (NumberTiles/2))
2211 {
cristy9950d572011-10-01 18:22:35 +00002212 (void) QueryColorCompliance("#dfdfdf",AllCompliance,
2213 &thumbnail->matte_color,exception);
cristy3ed852e2009-09-05 21:47:34 +00002214 AppendImageToList(&images,thumbnail);
2215 continue;
2216 }
2217 switch (preview)
2218 {
2219 case RotatePreview:
2220 {
2221 degrees+=45.0;
2222 preview_image=RotateImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002223 (void) FormatLocaleString(label,MaxTextExtent,"rotate %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002224 break;
2225 }
2226 case ShearPreview:
2227 {
2228 degrees+=5.0;
2229 preview_image=ShearImage(thumbnail,degrees,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002230 (void) FormatLocaleString(label,MaxTextExtent,"shear %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002231 degrees,2.0*degrees);
2232 break;
2233 }
2234 case RollPreview:
2235 {
cristybb503372010-05-27 20:51:26 +00002236 x=(ssize_t) ((i+1)*thumbnail->columns)/NumberTiles;
2237 y=(ssize_t) ((i+1)*thumbnail->rows)/NumberTiles;
cristy3ed852e2009-09-05 21:47:34 +00002238 preview_image=RollImage(thumbnail,x,y,exception);
cristyb51dff52011-05-19 16:55:47 +00002239 (void) FormatLocaleString(label,MaxTextExtent,"roll %+.20gx%+.20g",
cristye8c25f92010-06-03 00:53:06 +00002240 (double) x,(double) y);
cristy3ed852e2009-09-05 21:47:34 +00002241 break;
2242 }
2243 case HuePreview:
2244 {
2245 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2246 if (preview_image == (Image *) NULL)
2247 break;
cristyb51dff52011-05-19 16:55:47 +00002248 (void) FormatLocaleString(factor,MaxTextExtent,"100,100,%g",
cristy3ed852e2009-09-05 21:47:34 +00002249 2.0*percentage);
cristy33bd5152011-08-24 01:42:24 +00002250 (void) ModulateImage(preview_image,factor,exception);
cristyb51dff52011-05-19 16:55:47 +00002251 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002252 break;
2253 }
2254 case SaturationPreview:
2255 {
2256 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2257 if (preview_image == (Image *) NULL)
2258 break;
cristyb51dff52011-05-19 16:55:47 +00002259 (void) FormatLocaleString(factor,MaxTextExtent,"100,%g",
cristy8cd5b312010-01-07 01:10:24 +00002260 2.0*percentage);
cristy33bd5152011-08-24 01:42:24 +00002261 (void) ModulateImage(preview_image,factor,exception);
cristyb51dff52011-05-19 16:55:47 +00002262 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002263 break;
2264 }
2265 case BrightnessPreview:
2266 {
2267 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2268 if (preview_image == (Image *) NULL)
2269 break;
cristyb51dff52011-05-19 16:55:47 +00002270 (void) FormatLocaleString(factor,MaxTextExtent,"%g",2.0*percentage);
cristy33bd5152011-08-24 01:42:24 +00002271 (void) ModulateImage(preview_image,factor,exception);
cristyb51dff52011-05-19 16:55:47 +00002272 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002273 break;
2274 }
2275 case GammaPreview:
2276 default:
2277 {
2278 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2279 if (preview_image == (Image *) NULL)
2280 break;
2281 gamma+=0.4f;
cristyb3e7c6c2011-07-24 01:43:55 +00002282 (void) GammaImage(preview_image,gamma,exception);
cristyb51dff52011-05-19 16:55:47 +00002283 (void) FormatLocaleString(label,MaxTextExtent,"gamma %g",gamma);
cristy3ed852e2009-09-05 21:47:34 +00002284 break;
2285 }
2286 case SpiffPreview:
2287 {
2288 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2289 if (preview_image != (Image *) NULL)
2290 for (x=0; x < i; x++)
cristye23ec9d2011-08-16 18:15:40 +00002291 (void) ContrastImage(preview_image,MagickTrue,exception);
cristyb51dff52011-05-19 16:55:47 +00002292 (void) FormatLocaleString(label,MaxTextExtent,"contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002293 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002294 break;
2295 }
2296 case DullPreview:
2297 {
2298 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2299 if (preview_image == (Image *) NULL)
2300 break;
2301 for (x=0; x < i; x++)
cristye23ec9d2011-08-16 18:15:40 +00002302 (void) ContrastImage(preview_image,MagickFalse,exception);
cristyb51dff52011-05-19 16:55:47 +00002303 (void) FormatLocaleString(label,MaxTextExtent,"+contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002304 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002305 break;
2306 }
2307 case GrayscalePreview:
2308 {
2309 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2310 if (preview_image == (Image *) NULL)
2311 break;
2312 colors<<=1;
2313 quantize_info.number_colors=colors;
2314 quantize_info.colorspace=GRAYColorspace;
cristy018f07f2011-09-04 21:15:19 +00002315 (void) QuantizeImage(&quantize_info,preview_image,exception);
cristyb51dff52011-05-19 16:55:47 +00002316 (void) FormatLocaleString(label,MaxTextExtent,
cristye8c25f92010-06-03 00:53:06 +00002317 "-colorspace gray -colors %.20g",(double) colors);
cristy3ed852e2009-09-05 21:47:34 +00002318 break;
2319 }
2320 case QuantizePreview:
2321 {
2322 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2323 if (preview_image == (Image *) NULL)
2324 break;
2325 colors<<=1;
2326 quantize_info.number_colors=colors;
cristy018f07f2011-09-04 21:15:19 +00002327 (void) QuantizeImage(&quantize_info,preview_image,exception);
cristyb51dff52011-05-19 16:55:47 +00002328 (void) FormatLocaleString(label,MaxTextExtent,"colors %.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00002329 colors);
cristy3ed852e2009-09-05 21:47:34 +00002330 break;
2331 }
2332 case DespecklePreview:
2333 {
2334 for (x=0; x < (i-1); x++)
2335 {
2336 preview_image=DespeckleImage(thumbnail,exception);
2337 if (preview_image == (Image *) NULL)
2338 break;
2339 thumbnail=DestroyImage(thumbnail);
2340 thumbnail=preview_image;
2341 }
2342 preview_image=DespeckleImage(thumbnail,exception);
2343 if (preview_image == (Image *) NULL)
2344 break;
cristyb51dff52011-05-19 16:55:47 +00002345 (void) FormatLocaleString(label,MaxTextExtent,"despeckle (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002346 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002347 break;
2348 }
2349 case ReduceNoisePreview:
2350 {
cristy95c38342011-03-18 22:39:51 +00002351 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) radius,
2352 (size_t) radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002353 (void) FormatLocaleString(label,MaxTextExtent,"noise %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002354 break;
2355 }
2356 case AddNoisePreview:
2357 {
2358 switch ((int) i)
2359 {
2360 case 0:
2361 {
2362 (void) CopyMagickString(factor,"uniform",MaxTextExtent);
2363 break;
2364 }
2365 case 1:
2366 {
2367 (void) CopyMagickString(factor,"gaussian",MaxTextExtent);
2368 break;
2369 }
2370 case 2:
2371 {
2372 (void) CopyMagickString(factor,"multiplicative",MaxTextExtent);
2373 break;
2374 }
2375 case 3:
2376 {
2377 (void) CopyMagickString(factor,"impulse",MaxTextExtent);
2378 break;
2379 }
2380 case 4:
2381 {
2382 (void) CopyMagickString(factor,"laplacian",MaxTextExtent);
2383 break;
2384 }
2385 case 5:
2386 {
2387 (void) CopyMagickString(factor,"Poisson",MaxTextExtent);
2388 break;
2389 }
2390 default:
2391 {
2392 (void) CopyMagickString(thumbnail->magick,"NULL",MaxTextExtent);
2393 break;
2394 }
2395 }
cristyd76c51e2011-03-26 00:21:26 +00002396 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) i,
2397 (size_t) i,exception);
cristyb51dff52011-05-19 16:55:47 +00002398 (void) FormatLocaleString(label,MaxTextExtent,"+noise %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002399 break;
2400 }
2401 case SharpenPreview:
2402 {
cristyaa2c16c2012-03-25 22:21:35 +00002403 preview_image=SharpenImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00002404 (void) FormatLocaleString(label,MaxTextExtent,"sharpen %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002405 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002406 break;
2407 }
2408 case BlurPreview:
2409 {
cristyaa2c16c2012-03-25 22:21:35 +00002410 preview_image=BlurImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00002411 (void) FormatLocaleString(label,MaxTextExtent,"blur %gx%g",radius,
cristy3ed852e2009-09-05 21:47:34 +00002412 sigma);
2413 break;
2414 }
2415 case ThresholdPreview:
2416 {
2417 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2418 if (preview_image == (Image *) NULL)
2419 break;
cristye941a752011-10-15 01:52:48 +00002420 (void) BilevelImage(thumbnail,(double) (percentage*((MagickRealType)
2421 QuantumRange+1.0))/100.0,exception);
cristyb51dff52011-05-19 16:55:47 +00002422 (void) FormatLocaleString(label,MaxTextExtent,"threshold %g",
cristy3ed852e2009-09-05 21:47:34 +00002423 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
2424 break;
2425 }
2426 case EdgeDetectPreview:
2427 {
cristy8ae632d2011-09-05 17:29:53 +00002428 preview_image=EdgeImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00002429 (void) FormatLocaleString(label,MaxTextExtent,"edge %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002430 break;
2431 }
2432 case SpreadPreview:
2433 {
cristy5c4e2582011-09-11 19:21:03 +00002434 preview_image=SpreadImage(thumbnail,radius,thumbnail->interpolate,
2435 exception);
cristyb51dff52011-05-19 16:55:47 +00002436 (void) FormatLocaleString(label,MaxTextExtent,"spread %g",
cristy8cd5b312010-01-07 01:10:24 +00002437 radius+0.5);
cristy3ed852e2009-09-05 21:47:34 +00002438 break;
2439 }
2440 case SolarizePreview:
2441 {
2442 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2443 if (preview_image == (Image *) NULL)
2444 break;
2445 (void) SolarizeImage(preview_image,(double) QuantumRange*
cristy5cbc0162011-08-29 00:36:28 +00002446 percentage/100.0,exception);
cristyb51dff52011-05-19 16:55:47 +00002447 (void) FormatLocaleString(label,MaxTextExtent,"solarize %g",
cristy3ed852e2009-09-05 21:47:34 +00002448 (QuantumRange*percentage)/100.0);
2449 break;
2450 }
2451 case ShadePreview:
2452 {
2453 degrees+=10.0;
2454 preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees,
2455 exception);
cristyb51dff52011-05-19 16:55:47 +00002456 (void) FormatLocaleString(label,MaxTextExtent,"shade %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002457 degrees,degrees);
cristy3ed852e2009-09-05 21:47:34 +00002458 break;
2459 }
2460 case RaisePreview:
2461 {
2462 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2463 if (preview_image == (Image *) NULL)
2464 break;
cristybb503372010-05-27 20:51:26 +00002465 geometry.width=(size_t) (2*i+2);
2466 geometry.height=(size_t) (2*i+2);
cristy3ed852e2009-09-05 21:47:34 +00002467 geometry.x=i/2;
2468 geometry.y=i/2;
cristy6170ac32011-08-28 14:15:37 +00002469 (void) RaiseImage(preview_image,&geometry,MagickTrue,exception);
cristyb51dff52011-05-19 16:55:47 +00002470 (void) FormatLocaleString(label,MaxTextExtent,
cristy6d8abba2010-06-03 01:10:47 +00002471 "raise %.20gx%.20g%+.20g%+.20g",(double) geometry.width,(double)
cristye8c25f92010-06-03 00:53:06 +00002472 geometry.height,(double) geometry.x,(double) geometry.y);
cristy3ed852e2009-09-05 21:47:34 +00002473 break;
2474 }
2475 case SegmentPreview:
2476 {
2477 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2478 if (preview_image == (Image *) NULL)
2479 break;
2480 threshold+=0.4f;
cristyc511e882012-04-16 21:11:14 +00002481 (void) SegmentImage(preview_image,sRGBColorspace,MagickFalse,threshold,
cristy018f07f2011-09-04 21:15:19 +00002482 threshold,exception);
cristyb51dff52011-05-19 16:55:47 +00002483 (void) FormatLocaleString(label,MaxTextExtent,"segment %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002484 threshold,threshold);
2485 break;
2486 }
2487 case SwirlPreview:
2488 {
cristy76f512e2011-09-12 01:26:56 +00002489 preview_image=SwirlImage(thumbnail,degrees,image->interpolate,
2490 exception);
cristyb51dff52011-05-19 16:55:47 +00002491 (void) FormatLocaleString(label,MaxTextExtent,"swirl %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002492 degrees+=45.0;
2493 break;
2494 }
2495 case ImplodePreview:
2496 {
2497 degrees+=0.1f;
cristy76f512e2011-09-12 01:26:56 +00002498 preview_image=ImplodeImage(thumbnail,degrees,image->interpolate,
2499 exception);
cristyb51dff52011-05-19 16:55:47 +00002500 (void) FormatLocaleString(label,MaxTextExtent,"implode %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002501 break;
2502 }
2503 case WavePreview:
2504 {
2505 degrees+=5.0f;
cristy5c4e2582011-09-11 19:21:03 +00002506 preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,
2507 image->interpolate,exception);
cristyb51dff52011-05-19 16:55:47 +00002508 (void) FormatLocaleString(label,MaxTextExtent,"wave %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002509 0.5*degrees,2.0*degrees);
cristy3ed852e2009-09-05 21:47:34 +00002510 break;
2511 }
2512 case OilPaintPreview:
2513 {
cristy14973ba2011-08-27 23:48:07 +00002514 preview_image=OilPaintImage(thumbnail,(double) radius,(double) sigma,
2515 exception);
2516 (void) FormatLocaleString(label,MaxTextExtent,"charcoal %gx%g",
2517 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002518 break;
2519 }
2520 case CharcoalDrawingPreview:
2521 {
2522 preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma,
cristyaa2c16c2012-03-25 22:21:35 +00002523 exception);
cristyb51dff52011-05-19 16:55:47 +00002524 (void) FormatLocaleString(label,MaxTextExtent,"charcoal %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002525 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002526 break;
2527 }
2528 case JPEGPreview:
2529 {
2530 char
2531 filename[MaxTextExtent];
2532
2533 int
2534 file;
2535
2536 MagickBooleanType
2537 status;
2538
2539 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2540 if (preview_image == (Image *) NULL)
2541 break;
cristybb503372010-05-27 20:51:26 +00002542 preview_info->quality=(size_t) percentage;
cristyb51dff52011-05-19 16:55:47 +00002543 (void) FormatLocaleString(factor,MaxTextExtent,"%.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00002544 preview_info->quality);
cristy3ed852e2009-09-05 21:47:34 +00002545 file=AcquireUniqueFileResource(filename);
2546 if (file != -1)
2547 file=close(file)-1;
cristyb51dff52011-05-19 16:55:47 +00002548 (void) FormatLocaleString(preview_image->filename,MaxTextExtent,
cristy3ed852e2009-09-05 21:47:34 +00002549 "jpeg:%s",filename);
cristy6f9e0d32011-08-28 16:32:09 +00002550 status=WriteImage(preview_info,preview_image,exception);
cristy3ed852e2009-09-05 21:47:34 +00002551 if (status != MagickFalse)
2552 {
2553 Image
2554 *quality_image;
2555
2556 (void) CopyMagickString(preview_info->filename,
2557 preview_image->filename,MaxTextExtent);
2558 quality_image=ReadImage(preview_info,exception);
2559 if (quality_image != (Image *) NULL)
2560 {
2561 preview_image=DestroyImage(preview_image);
2562 preview_image=quality_image;
2563 }
2564 }
2565 (void) RelinquishUniqueFileResource(preview_image->filename);
2566 if ((GetBlobSize(preview_image)/1024) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00002567 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%gmb ",
cristy3ed852e2009-09-05 21:47:34 +00002568 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
2569 1024.0/1024.0);
2570 else
2571 if (GetBlobSize(preview_image) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00002572 (void) FormatLocaleString(label,MaxTextExtent,
cristye7f51092010-01-17 00:39:37 +00002573 "quality %s\n%gkb ",factor,(double) ((MagickOffsetType)
cristy8cd5b312010-01-07 01:10:24 +00002574 GetBlobSize(preview_image))/1024.0);
cristy3ed852e2009-09-05 21:47:34 +00002575 else
cristyb51dff52011-05-19 16:55:47 +00002576 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%.20gb ",
cristy54ea5732011-06-10 12:39:53 +00002577 factor,(double) ((MagickOffsetType) GetBlobSize(thumbnail)));
cristy3ed852e2009-09-05 21:47:34 +00002578 break;
2579 }
2580 }
2581 thumbnail=DestroyImage(thumbnail);
2582 percentage+=12.5;
2583 radius+=0.5;
2584 sigma+=0.25;
2585 if (preview_image == (Image *) NULL)
2586 break;
2587 (void) DeleteImageProperty(preview_image,"label");
cristyd15e6592011-10-15 00:13:06 +00002588 (void) SetImageProperty(preview_image,"label",label,exception);
cristy3ed852e2009-09-05 21:47:34 +00002589 AppendImageToList(&images,preview_image);
cristybb503372010-05-27 20:51:26 +00002590 proceed=SetImageProgress(image,PreviewImageTag,(MagickOffsetType) i,
2591 NumberTiles);
cristy3ed852e2009-09-05 21:47:34 +00002592 if (proceed == MagickFalse)
2593 break;
2594 }
2595 if (images == (Image *) NULL)
2596 {
2597 preview_info=DestroyImageInfo(preview_info);
2598 return((Image *) NULL);
2599 }
2600 /*
2601 Create the montage.
2602 */
2603 montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL);
2604 (void) CopyMagickString(montage_info->filename,image->filename,MaxTextExtent);
2605 montage_info->shadow=MagickTrue;
2606 (void) CloneString(&montage_info->tile,"3x3");
2607 (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry);
2608 (void) CloneString(&montage_info->frame,DefaultTileFrame);
2609 montage_image=MontageImages(images,montage_info,exception);
2610 montage_info=DestroyMontageInfo(montage_info);
2611 images=DestroyImageList(images);
2612 if (montage_image == (Image *) NULL)
2613 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2614 if (montage_image->montage != (char *) NULL)
2615 {
2616 /*
2617 Free image directory.
2618 */
2619 montage_image->montage=(char *) RelinquishMagickMemory(
2620 montage_image->montage);
2621 if (image->directory != (char *) NULL)
2622 montage_image->directory=(char *) RelinquishMagickMemory(
2623 montage_image->directory);
2624 }
2625 preview_info=DestroyImageInfo(preview_info);
2626 return(montage_image);
2627}
2628
2629/*
2630%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2631% %
2632% %
2633% %
2634% R a d i a l B l u r I m a g e %
2635% %
2636% %
2637% %
2638%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2639%
2640% RadialBlurImage() applies a radial blur to the image.
2641%
2642% Andrew Protano contributed this effect.
2643%
2644% The format of the RadialBlurImage method is:
2645%
2646% Image *RadialBlurImage(const Image *image,const double angle,
cristyaa2c16c2012-03-25 22:21:35 +00002647% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002648%
2649% A description of each parameter follows:
2650%
2651% o image: the image.
2652%
cristy3ed852e2009-09-05 21:47:34 +00002653% o angle: the angle of the radial blur.
2654%
cristy6435bd92011-09-10 02:10:07 +00002655% o blur: the blur.
2656%
cristy3ed852e2009-09-05 21:47:34 +00002657% o exception: return any errors or warnings in this structure.
2658%
2659*/
cristy4282c702011-11-21 00:01:06 +00002660MagickExport Image *RadialBlurImage(const Image *image,const double angle,
cristyaa2c16c2012-03-25 22:21:35 +00002661 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002662{
cristyc4c8d132010-01-07 01:58:38 +00002663 CacheView
2664 *blur_view,
cristy8b49f382012-01-17 03:11:03 +00002665 *image_view,
2666 *radial_view;
cristyc4c8d132010-01-07 01:58:38 +00002667
cristy3ed852e2009-09-05 21:47:34 +00002668 Image
2669 *blur_image;
2670
cristy3ed852e2009-09-05 21:47:34 +00002671 MagickBooleanType
2672 status;
2673
cristybb503372010-05-27 20:51:26 +00002674 MagickOffsetType
2675 progress;
2676
cristy3ed852e2009-09-05 21:47:34 +00002677 MagickRealType
2678 blur_radius,
2679 *cos_theta,
2680 offset,
2681 *sin_theta,
2682 theta;
2683
2684 PointInfo
2685 blur_center;
2686
cristybb503372010-05-27 20:51:26 +00002687 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002688 i;
2689
cristybb503372010-05-27 20:51:26 +00002690 size_t
cristy3ed852e2009-09-05 21:47:34 +00002691 n;
2692
cristybb503372010-05-27 20:51:26 +00002693 ssize_t
2694 y;
2695
cristy3ed852e2009-09-05 21:47:34 +00002696 /*
2697 Allocate blur image.
2698 */
2699 assert(image != (Image *) NULL);
2700 assert(image->signature == MagickSignature);
2701 if (image->debug != MagickFalse)
2702 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2703 assert(exception != (ExceptionInfo *) NULL);
2704 assert(exception->signature == MagickSignature);
cristy1e7aa312011-09-10 20:01:36 +00002705 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00002706 if (blur_image == (Image *) NULL)
2707 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00002708 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00002709 {
cristy3ed852e2009-09-05 21:47:34 +00002710 blur_image=DestroyImage(blur_image);
2711 return((Image *) NULL);
2712 }
2713 blur_center.x=(double) image->columns/2.0;
2714 blur_center.y=(double) image->rows/2.0;
2715 blur_radius=hypot(blur_center.x,blur_center.y);
cristy117ff172010-08-15 21:35:32 +00002716 n=(size_t) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+2UL);
cristy3ed852e2009-09-05 21:47:34 +00002717 theta=DegreesToRadians(angle)/(MagickRealType) (n-1);
2718 cos_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
2719 sizeof(*cos_theta));
2720 sin_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
2721 sizeof(*sin_theta));
2722 if ((cos_theta == (MagickRealType *) NULL) ||
2723 (sin_theta == (MagickRealType *) NULL))
2724 {
2725 blur_image=DestroyImage(blur_image);
2726 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2727 }
2728 offset=theta*(MagickRealType) (n-1)/2.0;
cristybb503372010-05-27 20:51:26 +00002729 for (i=0; i < (ssize_t) n; i++)
cristy3ed852e2009-09-05 21:47:34 +00002730 {
2731 cos_theta[i]=cos((double) (theta*i-offset));
2732 sin_theta[i]=sin((double) (theta*i-offset));
2733 }
2734 /*
2735 Radial blur image.
2736 */
2737 status=MagickTrue;
2738 progress=0;
cristydb070952012-04-20 14:33:00 +00002739 image_view=AcquireVirtualCacheView(image,exception);
2740 radial_view=AcquireVirtualCacheView(image,exception);
2741 blur_view=AcquireAuthenticCacheView(blur_image,exception);
cristyb5d5f722009-11-04 03:03:49 +00002742#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00002743 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2744 if ((image->rows*image->columns) > 8192) \
2745 num_threads(GetMagickResourceLimit(ThreadResource))
cristy3ed852e2009-09-05 21:47:34 +00002746#endif
cristy1e7aa312011-09-10 20:01:36 +00002747 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002748 {
cristy1e7aa312011-09-10 20:01:36 +00002749 register const Quantum
2750 *restrict p;
2751
cristy4c08aed2011-07-01 19:47:50 +00002752 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002753 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002754
cristy117ff172010-08-15 21:35:32 +00002755 register ssize_t
2756 x;
2757
cristy3ed852e2009-09-05 21:47:34 +00002758 if (status == MagickFalse)
2759 continue;
cristy8b49f382012-01-17 03:11:03 +00002760 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
2761 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +00002762 exception);
cristy1e7aa312011-09-10 20:01:36 +00002763 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00002764 {
2765 status=MagickFalse;
2766 continue;
2767 }
cristy1e7aa312011-09-10 20:01:36 +00002768 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002769 {
cristy3ed852e2009-09-05 21:47:34 +00002770 MagickRealType
cristy3ed852e2009-09-05 21:47:34 +00002771 radius;
2772
cristy3ed852e2009-09-05 21:47:34 +00002773 PointInfo
2774 center;
2775
cristybb503372010-05-27 20:51:26 +00002776 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002777 i;
2778
cristybb503372010-05-27 20:51:26 +00002779 size_t
cristy3ed852e2009-09-05 21:47:34 +00002780 step;
2781
2782 center.x=(double) x-blur_center.x;
2783 center.y=(double) y-blur_center.y;
2784 radius=hypot((double) center.x,center.y);
2785 if (radius == 0)
2786 step=1;
2787 else
2788 {
cristybb503372010-05-27 20:51:26 +00002789 step=(size_t) (blur_radius/radius);
cristy3ed852e2009-09-05 21:47:34 +00002790 if (step == 0)
2791 step=1;
2792 else
2793 if (step >= n)
2794 step=n-1;
2795 }
cristy177e41c2012-04-15 15:08:25 +00002796 if (GetPixelMask(image,p) != 0)
2797 {
2798 p+=GetPixelChannels(image);
2799 q+=GetPixelChannels(blur_image);
2800 continue;
2801 }
cristy1e7aa312011-09-10 20:01:36 +00002802 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2803 {
2804 MagickRealType
2805 gamma,
2806 pixel;
cristy3ed852e2009-09-05 21:47:34 +00002807
cristy1e7aa312011-09-10 20:01:36 +00002808 PixelChannel
2809 channel;
2810
2811 PixelTrait
2812 blur_traits,
2813 traits;
2814
2815 register const Quantum
2816 *restrict r;
2817
2818 register ssize_t
2819 j;
2820
cristye2a912b2011-12-05 20:02:07 +00002821 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +00002822 traits=GetPixelChannelMapTraits(image,channel);
cristy1e7aa312011-09-10 20:01:36 +00002823 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
2824 if ((traits == UndefinedPixelTrait) ||
2825 (blur_traits == UndefinedPixelTrait))
2826 continue;
cristy177e41c2012-04-15 15:08:25 +00002827 if ((blur_traits & CopyPixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002828 {
cristy0beccfa2011-09-25 20:47:53 +00002829 SetPixelChannel(blur_image,channel,p[i],q);
cristy1e7aa312011-09-10 20:01:36 +00002830 continue;
cristy3ed852e2009-09-05 21:47:34 +00002831 }
cristy1e7aa312011-09-10 20:01:36 +00002832 gamma=0.0;
cristyaa2c16c2012-03-25 22:21:35 +00002833 pixel=0.0;
cristy1e7aa312011-09-10 20:01:36 +00002834 if ((blur_traits & BlendPixelTrait) == 0)
2835 {
2836 for (j=0; j < (ssize_t) n; j+=(ssize_t) step)
2837 {
cristy8b49f382012-01-17 03:11:03 +00002838 r=GetCacheViewVirtualPixels(radial_view, (ssize_t) (blur_center.x+
cristy1e7aa312011-09-10 20:01:36 +00002839 center.x*cos_theta[j]-center.y*sin_theta[j]+0.5),(ssize_t)
2840 (blur_center.y+center.x*sin_theta[j]+center.y*cos_theta[j]+0.5),
2841 1,1,exception);
2842 if (r == (const Quantum *) NULL)
2843 {
2844 status=MagickFalse;
2845 continue;
2846 }
2847 pixel+=r[i];
2848 gamma++;
2849 }
2850 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +00002851 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy1e7aa312011-09-10 20:01:36 +00002852 continue;
2853 }
2854 for (j=0; j < (ssize_t) n; j+=(ssize_t) step)
2855 {
cristy8b49f382012-01-17 03:11:03 +00002856 r=GetCacheViewVirtualPixels(radial_view, (ssize_t) (blur_center.x+
cristy1e7aa312011-09-10 20:01:36 +00002857 center.x*cos_theta[j]-center.y*sin_theta[j]+0.5),(ssize_t)
2858 (blur_center.y+center.x*sin_theta[j]+center.y*cos_theta[j]+0.5),
2859 1,1,exception);
2860 if (r == (const Quantum *) NULL)
2861 {
2862 status=MagickFalse;
2863 continue;
2864 }
2865 pixel+=GetPixelAlpha(image,r)*r[i];
2866 gamma+=GetPixelAlpha(image,r);
cristy3ed852e2009-09-05 21:47:34 +00002867 }
cristy1e7aa312011-09-10 20:01:36 +00002868 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +00002869 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy1e7aa312011-09-10 20:01:36 +00002870 }
2871 p+=GetPixelChannels(image);
cristyed231572011-07-14 02:18:59 +00002872 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00002873 }
2874 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
2875 status=MagickFalse;
2876 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2877 {
2878 MagickBooleanType
2879 proceed;
2880
cristyb5d5f722009-11-04 03:03:49 +00002881#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf8561542012-01-24 00:26:46 +00002882 #pragma omp critical (MagickCore_RadialBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00002883#endif
2884 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
2885 if (proceed == MagickFalse)
2886 status=MagickFalse;
2887 }
2888 }
2889 blur_view=DestroyCacheView(blur_view);
cristy8b49f382012-01-17 03:11:03 +00002890 radial_view=DestroyCacheView(radial_view);
cristy3ed852e2009-09-05 21:47:34 +00002891 image_view=DestroyCacheView(image_view);
2892 cos_theta=(MagickRealType *) RelinquishMagickMemory(cos_theta);
2893 sin_theta=(MagickRealType *) RelinquishMagickMemory(sin_theta);
2894 if (status == MagickFalse)
2895 blur_image=DestroyImage(blur_image);
2896 return(blur_image);
2897}
2898
2899/*
2900%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2901% %
2902% %
2903% %
cristy3ed852e2009-09-05 21:47:34 +00002904% S e l e c t i v e B l u r I m a g e %
2905% %
2906% %
2907% %
2908%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2909%
2910% SelectiveBlurImage() selectively blur pixels within a contrast threshold.
2911% It is similar to the unsharpen mask that sharpens everything with contrast
2912% above a certain threshold.
2913%
2914% The format of the SelectiveBlurImage method is:
2915%
2916% Image *SelectiveBlurImage(const Image *image,const double radius,
cristyaa2c16c2012-03-25 22:21:35 +00002917% const double sigma,const double threshold,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002918%
2919% A description of each parameter follows:
2920%
2921% o image: the image.
2922%
cristy3ed852e2009-09-05 21:47:34 +00002923% o radius: the radius of the Gaussian, in pixels, not counting the center
2924% pixel.
2925%
2926% o sigma: the standard deviation of the Gaussian, in pixels.
2927%
2928% o threshold: only pixels within this contrast threshold are included
2929% in the blur operation.
2930%
2931% o exception: return any errors or warnings in this structure.
2932%
2933*/
cristy4282c702011-11-21 00:01:06 +00002934MagickExport Image *SelectiveBlurImage(const Image *image,const double radius,
cristyaa2c16c2012-03-25 22:21:35 +00002935 const double sigma,const double threshold,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002936{
2937#define SelectiveBlurImageTag "SelectiveBlur/Image"
2938
cristy47e00502009-12-17 19:19:57 +00002939 CacheView
2940 *blur_view,
2941 *image_view;
2942
cristy3ed852e2009-09-05 21:47:34 +00002943 double
cristy3ed852e2009-09-05 21:47:34 +00002944 *kernel;
2945
2946 Image
2947 *blur_image;
2948
cristy3ed852e2009-09-05 21:47:34 +00002949 MagickBooleanType
2950 status;
2951
cristybb503372010-05-27 20:51:26 +00002952 MagickOffsetType
2953 progress;
2954
cristybb503372010-05-27 20:51:26 +00002955 register ssize_t
cristy47e00502009-12-17 19:19:57 +00002956 i;
cristy3ed852e2009-09-05 21:47:34 +00002957
cristybb503372010-05-27 20:51:26 +00002958 size_t
cristy3ed852e2009-09-05 21:47:34 +00002959 width;
2960
cristybb503372010-05-27 20:51:26 +00002961 ssize_t
cristyc8523c12011-09-13 00:02:53 +00002962 center,
cristybb503372010-05-27 20:51:26 +00002963 j,
2964 u,
2965 v,
2966 y;
2967
cristy3ed852e2009-09-05 21:47:34 +00002968 /*
2969 Initialize blur image attributes.
2970 */
2971 assert(image != (Image *) NULL);
2972 assert(image->signature == MagickSignature);
2973 if (image->debug != MagickFalse)
2974 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2975 assert(exception != (ExceptionInfo *) NULL);
2976 assert(exception->signature == MagickSignature);
2977 width=GetOptimalKernelWidth1D(radius,sigma);
cristy8b49f382012-01-17 03:11:03 +00002978 kernel=(double *) AcquireAlignedMemory((size_t) width,width*sizeof(*kernel));
cristy3ed852e2009-09-05 21:47:34 +00002979 if (kernel == (double *) NULL)
2980 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00002981 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00002982 i=0;
cristy47e00502009-12-17 19:19:57 +00002983 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002984 {
cristy47e00502009-12-17 19:19:57 +00002985 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00002986 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
2987 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00002988 }
2989 if (image->debug != MagickFalse)
2990 {
2991 char
2992 format[MaxTextExtent],
2993 *message;
2994
cristy117ff172010-08-15 21:35:32 +00002995 register const double
2996 *k;
2997
cristybb503372010-05-27 20:51:26 +00002998 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002999 u,
3000 v;
3001
cristy3ed852e2009-09-05 21:47:34 +00003002 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00003003 " SelectiveBlurImage with %.20gx%.20g kernel:",(double) width,(double)
3004 width);
cristy3ed852e2009-09-05 21:47:34 +00003005 message=AcquireString("");
3006 k=kernel;
cristybb503372010-05-27 20:51:26 +00003007 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003008 {
3009 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00003010 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy3ed852e2009-09-05 21:47:34 +00003011 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00003012 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003013 {
cristyb51dff52011-05-19 16:55:47 +00003014 (void) FormatLocaleString(format,MaxTextExtent,"%+f ",*k++);
cristy3ed852e2009-09-05 21:47:34 +00003015 (void) ConcatenateString(&message,format);
3016 }
3017 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
3018 }
3019 message=DestroyString(message);
3020 }
cristy1e7aa312011-09-10 20:01:36 +00003021 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00003022 if (blur_image == (Image *) NULL)
3023 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00003024 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003025 {
cristy3ed852e2009-09-05 21:47:34 +00003026 blur_image=DestroyImage(blur_image);
3027 return((Image *) NULL);
3028 }
3029 /*
3030 Threshold blur image.
3031 */
3032 status=MagickTrue;
3033 progress=0;
cristyc8523c12011-09-13 00:02:53 +00003034 center=(ssize_t) (GetPixelChannels(image)*(image->columns+width)*(width/2L)+
3035 GetPixelChannels(image)*(width/2L));
cristydb070952012-04-20 14:33:00 +00003036 image_view=AcquireVirtualCacheView(image,exception);
3037 blur_view=AcquireAuthenticCacheView(blur_image,exception);
cristyb5d5f722009-11-04 03:03:49 +00003038#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00003039 #pragma omp parallel for schedule(static,4) shared(progress,status) \
3040 if ((image->rows*image->columns) > 8192) \
3041 num_threads(GetMagickResourceLimit(ThreadResource))
cristy3ed852e2009-09-05 21:47:34 +00003042#endif
cristybb503372010-05-27 20:51:26 +00003043 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003044 {
cristy4c08aed2011-07-01 19:47:50 +00003045 double
3046 contrast;
3047
cristy3ed852e2009-09-05 21:47:34 +00003048 MagickBooleanType
3049 sync;
3050
cristy4c08aed2011-07-01 19:47:50 +00003051 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003052 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003053
cristy4c08aed2011-07-01 19:47:50 +00003054 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003055 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003056
cristy117ff172010-08-15 21:35:32 +00003057 register ssize_t
3058 x;
3059
cristy3ed852e2009-09-05 21:47:34 +00003060 if (status == MagickFalse)
3061 continue;
cristy117ff172010-08-15 21:35:32 +00003062 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
3063 (width/2L),image->columns+width,width,exception);
cristy8b49f382012-01-17 03:11:03 +00003064 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +00003065 exception);
cristy4c08aed2011-07-01 19:47:50 +00003066 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003067 {
3068 status=MagickFalse;
3069 continue;
3070 }
cristybb503372010-05-27 20:51:26 +00003071 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003072 {
cristybb503372010-05-27 20:51:26 +00003073 register ssize_t
cristy1e7aa312011-09-10 20:01:36 +00003074 i;
cristy3ed852e2009-09-05 21:47:34 +00003075
cristy177e41c2012-04-15 15:08:25 +00003076 if (GetPixelMask(image,p) != 0)
3077 {
3078 p+=GetPixelChannels(image);
3079 q+=GetPixelChannels(blur_image);
3080 continue;
3081 }
cristy1e7aa312011-09-10 20:01:36 +00003082 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3083 {
3084 MagickRealType
3085 alpha,
3086 gamma,
3087 intensity,
3088 pixel;
cristy117ff172010-08-15 21:35:32 +00003089
cristy1e7aa312011-09-10 20:01:36 +00003090 PixelChannel
3091 channel;
3092
3093 PixelTrait
3094 blur_traits,
3095 traits;
3096
3097 register const double
3098 *restrict k;
3099
3100 register const Quantum
3101 *restrict pixels;
3102
3103 register ssize_t
3104 u;
3105
3106 ssize_t
3107 v;
3108
cristye2a912b2011-12-05 20:02:07 +00003109 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +00003110 traits=GetPixelChannelMapTraits(image,channel);
cristy1e7aa312011-09-10 20:01:36 +00003111 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
3112 if ((traits == UndefinedPixelTrait) ||
3113 (blur_traits == UndefinedPixelTrait))
3114 continue;
cristy177e41c2012-04-15 15:08:25 +00003115 if ((blur_traits & CopyPixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003116 {
cristy0beccfa2011-09-25 20:47:53 +00003117 SetPixelChannel(blur_image,channel,p[center+i],q);
cristy1e7aa312011-09-10 20:01:36 +00003118 continue;
cristy3ed852e2009-09-05 21:47:34 +00003119 }
cristy1e7aa312011-09-10 20:01:36 +00003120 k=kernel;
cristyaa2c16c2012-03-25 22:21:35 +00003121 pixel=0.0;
cristy1e7aa312011-09-10 20:01:36 +00003122 pixels=p;
cristyc8523c12011-09-13 00:02:53 +00003123 intensity=(MagickRealType) GetPixelIntensity(image,p+center);
cristy1e7aa312011-09-10 20:01:36 +00003124 gamma=0.0;
3125 if ((blur_traits & BlendPixelTrait) == 0)
cristy3ed852e2009-09-05 21:47:34 +00003126 {
cristy1e7aa312011-09-10 20:01:36 +00003127 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003128 {
cristy1e7aa312011-09-10 20:01:36 +00003129 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003130 {
cristy1e7aa312011-09-10 20:01:36 +00003131 contrast=GetPixelIntensity(image,pixels)-intensity;
3132 if (fabs(contrast) < threshold)
3133 {
3134 pixel+=(*k)*pixels[i];
3135 gamma+=(*k);
3136 }
3137 k++;
3138 pixels+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003139 }
cristy1e7aa312011-09-10 20:01:36 +00003140 pixels+=image->columns*GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003141 }
cristy1e7aa312011-09-10 20:01:36 +00003142 if (fabs((double) gamma) < MagickEpsilon)
3143 {
cristy0beccfa2011-09-25 20:47:53 +00003144 SetPixelChannel(blur_image,channel,p[center+i],q);
cristy1e7aa312011-09-10 20:01:36 +00003145 continue;
3146 }
3147 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +00003148 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy1e7aa312011-09-10 20:01:36 +00003149 continue;
3150 }
3151 for (v=0; v < (ssize_t) width; v++)
3152 {
3153 for (u=0; u < (ssize_t) width; u++)
3154 {
3155 contrast=GetPixelIntensity(image,pixels)-intensity;
3156 if (fabs(contrast) < threshold)
3157 {
3158 alpha=(MagickRealType) (QuantumScale*
3159 GetPixelAlpha(image,pixels));
3160 pixel+=(*k)*alpha*pixels[i];
3161 gamma+=(*k)*alpha;
3162 }
3163 k++;
3164 pixels+=GetPixelChannels(image);
3165 }
3166 pixels+=image->columns*GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003167 }
cristy1e7aa312011-09-10 20:01:36 +00003168 if (fabs((double) gamma) < MagickEpsilon)
3169 {
cristy0beccfa2011-09-25 20:47:53 +00003170 SetPixelChannel(blur_image,channel,p[center+i],q);
cristy1e7aa312011-09-10 20:01:36 +00003171 continue;
3172 }
3173 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +00003174 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy1e7aa312011-09-10 20:01:36 +00003175 }
cristyed231572011-07-14 02:18:59 +00003176 p+=GetPixelChannels(image);
3177 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003178 }
3179 sync=SyncCacheViewAuthenticPixels(blur_view,exception);
3180 if (sync == MagickFalse)
3181 status=MagickFalse;
3182 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3183 {
3184 MagickBooleanType
3185 proceed;
3186
cristyb5d5f722009-11-04 03:03:49 +00003187#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf8561542012-01-24 00:26:46 +00003188 #pragma omp critical (MagickCore_SelectiveBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00003189#endif
3190 proceed=SetImageProgress(image,SelectiveBlurImageTag,progress++,
3191 image->rows);
3192 if (proceed == MagickFalse)
3193 status=MagickFalse;
3194 }
3195 }
3196 blur_image->type=image->type;
3197 blur_view=DestroyCacheView(blur_view);
3198 image_view=DestroyCacheView(image_view);
cristy8b49f382012-01-17 03:11:03 +00003199 kernel=(double *) RelinquishAlignedMemory(kernel);
cristy3ed852e2009-09-05 21:47:34 +00003200 if (status == MagickFalse)
3201 blur_image=DestroyImage(blur_image);
3202 return(blur_image);
3203}
3204
3205/*
3206%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3207% %
3208% %
3209% %
3210% S h a d e I m a g e %
3211% %
3212% %
3213% %
3214%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3215%
3216% ShadeImage() shines a distant light on an image to create a
3217% three-dimensional effect. You control the positioning of the light with
3218% azimuth and elevation; azimuth is measured in degrees off the x axis
3219% and elevation is measured in pixels above the Z axis.
3220%
3221% The format of the ShadeImage method is:
3222%
3223% Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3224% const double azimuth,const double elevation,ExceptionInfo *exception)
3225%
3226% A description of each parameter follows:
3227%
3228% o image: the image.
3229%
3230% o gray: A value other than zero shades the intensity of each pixel.
3231%
3232% o azimuth, elevation: Define the light source direction.
3233%
3234% o exception: return any errors or warnings in this structure.
3235%
3236*/
3237MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3238 const double azimuth,const double elevation,ExceptionInfo *exception)
3239{
3240#define ShadeImageTag "Shade/Image"
3241
cristyc4c8d132010-01-07 01:58:38 +00003242 CacheView
3243 *image_view,
3244 *shade_view;
3245
cristy3ed852e2009-09-05 21:47:34 +00003246 Image
3247 *shade_image;
3248
cristy3ed852e2009-09-05 21:47:34 +00003249 MagickBooleanType
3250 status;
3251
cristybb503372010-05-27 20:51:26 +00003252 MagickOffsetType
3253 progress;
3254
cristy3ed852e2009-09-05 21:47:34 +00003255 PrimaryInfo
3256 light;
3257
cristybb503372010-05-27 20:51:26 +00003258 ssize_t
3259 y;
3260
cristy3ed852e2009-09-05 21:47:34 +00003261 /*
3262 Initialize shaded image attributes.
3263 */
3264 assert(image != (const Image *) NULL);
3265 assert(image->signature == MagickSignature);
3266 if (image->debug != MagickFalse)
3267 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3268 assert(exception != (ExceptionInfo *) NULL);
3269 assert(exception->signature == MagickSignature);
3270 shade_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
3271 if (shade_image == (Image *) NULL)
3272 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00003273 if (SetImageStorageClass(shade_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003274 {
cristy3ed852e2009-09-05 21:47:34 +00003275 shade_image=DestroyImage(shade_image);
3276 return((Image *) NULL);
3277 }
3278 /*
3279 Compute the light vector.
3280 */
3281 light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))*
3282 cos(DegreesToRadians(elevation));
3283 light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))*
3284 cos(DegreesToRadians(elevation));
3285 light.z=(double) QuantumRange*sin(DegreesToRadians(elevation));
3286 /*
3287 Shade image.
3288 */
3289 status=MagickTrue;
3290 progress=0;
cristydb070952012-04-20 14:33:00 +00003291 image_view=AcquireVirtualCacheView(image,exception);
3292 shade_view=AcquireAuthenticCacheView(shade_image,exception);
cristyb5d5f722009-11-04 03:03:49 +00003293#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00003294 #pragma omp parallel for schedule(static,4) shared(progress,status) \
3295 if ((image->rows*image->columns) > 8192) \
3296 num_threads(GetMagickResourceLimit(ThreadResource))
cristy3ed852e2009-09-05 21:47:34 +00003297#endif
cristybb503372010-05-27 20:51:26 +00003298 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003299 {
3300 MagickRealType
3301 distance,
3302 normal_distance,
3303 shade;
3304
3305 PrimaryInfo
3306 normal;
3307
cristy4c08aed2011-07-01 19:47:50 +00003308 register const Quantum
cristy1e7aa312011-09-10 20:01:36 +00003309 *restrict center,
cristyc47d1f82009-11-26 01:44:43 +00003310 *restrict p,
cristy1e7aa312011-09-10 20:01:36 +00003311 *restrict post,
3312 *restrict pre;
cristy3ed852e2009-09-05 21:47:34 +00003313
cristy4c08aed2011-07-01 19:47:50 +00003314 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003315 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003316
cristy117ff172010-08-15 21:35:32 +00003317 register ssize_t
3318 x;
3319
cristy3ed852e2009-09-05 21:47:34 +00003320 if (status == MagickFalse)
3321 continue;
3322 p=GetCacheViewVirtualPixels(image_view,-1,y-1,image->columns+2,3,exception);
3323 q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1,
3324 exception);
cristy4c08aed2011-07-01 19:47:50 +00003325 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003326 {
3327 status=MagickFalse;
3328 continue;
3329 }
3330 /*
3331 Shade this row of pixels.
3332 */
3333 normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */
cristy1e7aa312011-09-10 20:01:36 +00003334 pre=p+GetPixelChannels(image);
3335 center=pre+(image->columns+2)*GetPixelChannels(image);
3336 post=center+(image->columns+2)*GetPixelChannels(image);
cristybb503372010-05-27 20:51:26 +00003337 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003338 {
cristy1e7aa312011-09-10 20:01:36 +00003339 register ssize_t
3340 i;
3341
cristy3ed852e2009-09-05 21:47:34 +00003342 /*
3343 Determine the surface normal and compute shading.
3344 */
cristy1e7aa312011-09-10 20:01:36 +00003345 normal.x=(double) (GetPixelIntensity(image,pre-GetPixelChannels(image))+
3346 GetPixelIntensity(image,center-GetPixelChannels(image))+
3347 GetPixelIntensity(image,post-GetPixelChannels(image))-
3348 GetPixelIntensity(image,pre+GetPixelChannels(image))-
3349 GetPixelIntensity(image,center+GetPixelChannels(image))-
3350 GetPixelIntensity(image,post+GetPixelChannels(image)));
3351 normal.y=(double) (GetPixelIntensity(image,post-GetPixelChannels(image))+
3352 GetPixelIntensity(image,post)+GetPixelIntensity(image,post+
3353 GetPixelChannels(image))-GetPixelIntensity(image,pre-
3354 GetPixelChannels(image))-GetPixelIntensity(image,pre)-
3355 GetPixelIntensity(image,pre+GetPixelChannels(image)));
cristy3ed852e2009-09-05 21:47:34 +00003356 if ((normal.x == 0.0) && (normal.y == 0.0))
3357 shade=light.z;
3358 else
3359 {
3360 shade=0.0;
3361 distance=normal.x*light.x+normal.y*light.y+normal.z*light.z;
3362 if (distance > MagickEpsilon)
3363 {
3364 normal_distance=
3365 normal.x*normal.x+normal.y*normal.y+normal.z*normal.z;
3366 if (normal_distance > (MagickEpsilon*MagickEpsilon))
3367 shade=distance/sqrt((double) normal_distance);
3368 }
3369 }
cristy177e41c2012-04-15 15:08:25 +00003370 if (GetPixelMask(image,p) != 0)
3371 {
3372 pre+=GetPixelChannels(image);
3373 center+=GetPixelChannels(image);
3374 post+=GetPixelChannels(image);
3375 q+=GetPixelChannels(shade_image);
3376 continue;
3377 }
cristy1e7aa312011-09-10 20:01:36 +00003378 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3379 {
3380 PixelChannel
3381 channel;
3382
3383 PixelTrait
3384 shade_traits,
3385 traits;
3386
cristye2a912b2011-12-05 20:02:07 +00003387 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +00003388 traits=GetPixelChannelMapTraits(image,channel);
cristy1e7aa312011-09-10 20:01:36 +00003389 shade_traits=GetPixelChannelMapTraits(shade_image,channel);
3390 if ((traits == UndefinedPixelTrait) ||
3391 (shade_traits == UndefinedPixelTrait))
3392 continue;
cristy177e41c2012-04-15 15:08:25 +00003393 if ((shade_traits & CopyPixelTrait) != 0)
cristy1e7aa312011-09-10 20:01:36 +00003394 {
cristy0beccfa2011-09-25 20:47:53 +00003395 SetPixelChannel(shade_image,channel,center[i],q);
cristy1e7aa312011-09-10 20:01:36 +00003396 continue;
3397 }
3398 if (gray != MagickFalse)
3399 {
cristy0beccfa2011-09-25 20:47:53 +00003400 SetPixelChannel(shade_image,channel,ClampToQuantum(shade),q);
cristy1e7aa312011-09-10 20:01:36 +00003401 continue;
3402 }
cristy0beccfa2011-09-25 20:47:53 +00003403 SetPixelChannel(shade_image,channel,ClampToQuantum(QuantumScale*shade*
3404 center[i]),q);
cristy1e7aa312011-09-10 20:01:36 +00003405 }
3406 pre+=GetPixelChannels(image);
3407 center+=GetPixelChannels(image);
3408 post+=GetPixelChannels(image);
cristyed231572011-07-14 02:18:59 +00003409 q+=GetPixelChannels(shade_image);
cristy3ed852e2009-09-05 21:47:34 +00003410 }
3411 if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
3412 status=MagickFalse;
3413 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3414 {
3415 MagickBooleanType
3416 proceed;
3417
cristyb5d5f722009-11-04 03:03:49 +00003418#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf8561542012-01-24 00:26:46 +00003419 #pragma omp critical (MagickCore_ShadeImage)
cristy3ed852e2009-09-05 21:47:34 +00003420#endif
3421 proceed=SetImageProgress(image,ShadeImageTag,progress++,image->rows);
3422 if (proceed == MagickFalse)
3423 status=MagickFalse;
3424 }
3425 }
3426 shade_view=DestroyCacheView(shade_view);
3427 image_view=DestroyCacheView(image_view);
3428 if (status == MagickFalse)
3429 shade_image=DestroyImage(shade_image);
3430 return(shade_image);
3431}
3432
3433/*
3434%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3435% %
3436% %
3437% %
3438% S h a r p e n I m a g e %
3439% %
3440% %
3441% %
3442%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3443%
3444% SharpenImage() sharpens the image. We convolve the image with a Gaussian
3445% operator of the given radius and standard deviation (sigma). For
3446% reasonable results, radius should be larger than sigma. Use a radius of 0
3447% and SharpenImage() selects a suitable radius for you.
3448%
3449% Using a separable kernel would be faster, but the negative weights cancel
3450% out on the corners of the kernel producing often undesirable ringing in the
3451% filtered result; this can be avoided by using a 2D gaussian shaped image
3452% sharpening kernel instead.
3453%
3454% The format of the SharpenImage method is:
3455%
3456% Image *SharpenImage(const Image *image,const double radius,
cristyaa2c16c2012-03-25 22:21:35 +00003457% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003458%
3459% A description of each parameter follows:
3460%
3461% o image: the image.
3462%
cristy3ed852e2009-09-05 21:47:34 +00003463% o radius: the radius of the Gaussian, in pixels, not counting the center
3464% pixel.
3465%
3466% o sigma: the standard deviation of the Laplacian, in pixels.
3467%
3468% o exception: return any errors or warnings in this structure.
3469%
3470*/
cristy3ed852e2009-09-05 21:47:34 +00003471MagickExport Image *SharpenImage(const Image *image,const double radius,
cristyaa2c16c2012-03-25 22:21:35 +00003472 const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003473{
cristy3ed852e2009-09-05 21:47:34 +00003474 double
cristy47e00502009-12-17 19:19:57 +00003475 normalize;
cristy3ed852e2009-09-05 21:47:34 +00003476
3477 Image
3478 *sharp_image;
3479
cristy41cbe682011-07-15 19:12:37 +00003480 KernelInfo
3481 *kernel_info;
3482
cristybb503372010-05-27 20:51:26 +00003483 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003484 i;
3485
cristybb503372010-05-27 20:51:26 +00003486 size_t
cristy3ed852e2009-09-05 21:47:34 +00003487 width;
3488
cristy117ff172010-08-15 21:35:32 +00003489 ssize_t
3490 j,
3491 u,
3492 v;
3493
cristy3ed852e2009-09-05 21:47:34 +00003494 assert(image != (const Image *) NULL);
3495 assert(image->signature == MagickSignature);
3496 if (image->debug != MagickFalse)
3497 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3498 assert(exception != (ExceptionInfo *) NULL);
3499 assert(exception->signature == MagickSignature);
3500 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00003501 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00003502 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003503 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00003504 (void) ResetMagickMemory(kernel_info,0,sizeof(*kernel_info));
3505 kernel_info->width=width;
3506 kernel_info->height=width;
3507 kernel_info->signature=MagickSignature;
cristya96f2492011-12-14 18:25:41 +00003508 kernel_info->values=(MagickRealType *) AcquireAlignedMemory(
3509 kernel_info->width,kernel_info->width*sizeof(*kernel_info->values));
3510 if (kernel_info->values == (MagickRealType *) NULL)
cristy41cbe682011-07-15 19:12:37 +00003511 {
3512 kernel_info=DestroyKernelInfo(kernel_info);
3513 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3514 }
cristy3ed852e2009-09-05 21:47:34 +00003515 normalize=0.0;
cristy41cbe682011-07-15 19:12:37 +00003516 j=(ssize_t) kernel_info->width/2;
cristy47e00502009-12-17 19:19:57 +00003517 i=0;
3518 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00003519 {
cristy47e00502009-12-17 19:19:57 +00003520 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00003521 {
cristy41cbe682011-07-15 19:12:37 +00003522 kernel_info->values[i]=(double) (-exp(-((double) u*u+v*v)/(2.0*
3523 MagickSigma*MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
3524 normalize+=kernel_info->values[i];
cristy3ed852e2009-09-05 21:47:34 +00003525 i++;
3526 }
3527 }
cristy41cbe682011-07-15 19:12:37 +00003528 kernel_info->values[i/2]=(double) ((-2.0)*normalize);
cristy5e6be1e2011-07-16 01:23:39 +00003529 sharp_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00003530 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00003531 return(sharp_image);
3532}
3533
3534/*
3535%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3536% %
3537% %
3538% %
3539% S p r e a d I m a g e %
3540% %
3541% %
3542% %
3543%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3544%
3545% SpreadImage() is a special effects method that randomly displaces each
3546% pixel in a block defined by the radius parameter.
3547%
3548% The format of the SpreadImage method is:
3549%
3550% Image *SpreadImage(const Image *image,const double radius,
cristy5c4e2582011-09-11 19:21:03 +00003551% const PixelInterpolateMethod method,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003552%
3553% A description of each parameter follows:
3554%
3555% o image: the image.
3556%
cristy5c4e2582011-09-11 19:21:03 +00003557% o radius: choose a random pixel in a neighborhood of this extent.
3558%
3559% o method: the pixel interpolation method.
cristy3ed852e2009-09-05 21:47:34 +00003560%
3561% o exception: return any errors or warnings in this structure.
3562%
3563*/
3564MagickExport Image *SpreadImage(const Image *image,const double radius,
cristy5c4e2582011-09-11 19:21:03 +00003565 const PixelInterpolateMethod method,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003566{
3567#define SpreadImageTag "Spread/Image"
3568
cristyfa112112010-01-04 17:48:07 +00003569 CacheView
cristy9f7e7cb2011-03-26 00:49:57 +00003570 *image_view,
3571 *spread_view;
cristyfa112112010-01-04 17:48:07 +00003572
cristy3ed852e2009-09-05 21:47:34 +00003573 Image
3574 *spread_image;
3575
cristy3ed852e2009-09-05 21:47:34 +00003576 MagickBooleanType
3577 status;
3578
cristybb503372010-05-27 20:51:26 +00003579 MagickOffsetType
3580 progress;
3581
cristy3ed852e2009-09-05 21:47:34 +00003582 RandomInfo
cristyfa112112010-01-04 17:48:07 +00003583 **restrict random_info;
cristy3ed852e2009-09-05 21:47:34 +00003584
cristybb503372010-05-27 20:51:26 +00003585 size_t
cristy3ed852e2009-09-05 21:47:34 +00003586 width;
3587
cristybb503372010-05-27 20:51:26 +00003588 ssize_t
3589 y;
3590
cristy57340e02012-05-05 00:53:23 +00003591 unsigned long
3592 key;
3593
cristy3ed852e2009-09-05 21:47:34 +00003594 /*
3595 Initialize spread image attributes.
3596 */
3597 assert(image != (Image *) NULL);
3598 assert(image->signature == MagickSignature);
3599 if (image->debug != MagickFalse)
3600 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3601 assert(exception != (ExceptionInfo *) NULL);
3602 assert(exception->signature == MagickSignature);
3603 spread_image=CloneImage(image,image->columns,image->rows,MagickTrue,
3604 exception);
3605 if (spread_image == (Image *) NULL)
3606 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00003607 if (SetImageStorageClass(spread_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003608 {
cristy3ed852e2009-09-05 21:47:34 +00003609 spread_image=DestroyImage(spread_image);
3610 return((Image *) NULL);
3611 }
3612 /*
3613 Spread image.
3614 */
3615 status=MagickTrue;
3616 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00003617 width=GetOptimalKernelWidth1D(radius,0.5);
cristy3ed852e2009-09-05 21:47:34 +00003618 random_info=AcquireRandomInfoThreadSet();
cristy57340e02012-05-05 00:53:23 +00003619 key=GetRandomSecretKey(random_info[0]);
cristydb070952012-04-20 14:33:00 +00003620 image_view=AcquireVirtualCacheView(image,exception);
3621 spread_view=AcquireAuthenticCacheView(spread_image,exception);
cristyb557a152011-02-22 12:14:30 +00003622#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy57340e02012-05-05 00:53:23 +00003623 #pragma omp parallel for schedule(static,8) shared(progress,status) \
cristyac245f82012-05-05 17:13:57 +00003624 if (((image->rows*image->columns) > 8192) && (key == ~0UL)) \
3625 num_threads(GetMagickResourceLimit(ThreadResource))
cristy3ed852e2009-09-05 21:47:34 +00003626#endif
cristybe82ad52011-09-06 01:17:20 +00003627 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003628 {
cristy5c9e6f22010-09-17 17:31:01 +00003629 const int
3630 id = GetOpenMPThreadId();
cristy6ebe97c2010-07-03 01:17:28 +00003631
cristybe82ad52011-09-06 01:17:20 +00003632 register const Quantum
3633 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003634
cristy4c08aed2011-07-01 19:47:50 +00003635 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003636 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003637
cristy117ff172010-08-15 21:35:32 +00003638 register ssize_t
3639 x;
3640
cristy3ed852e2009-09-05 21:47:34 +00003641 if (status == MagickFalse)
3642 continue;
cristybe82ad52011-09-06 01:17:20 +00003643 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy9f7e7cb2011-03-26 00:49:57 +00003644 q=QueueCacheViewAuthenticPixels(spread_view,0,y,spread_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +00003645 exception);
cristybe82ad52011-09-06 01:17:20 +00003646 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003647 {
3648 status=MagickFalse;
3649 continue;
3650 }
cristybe82ad52011-09-06 01:17:20 +00003651 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003652 {
cristybe82ad52011-09-06 01:17:20 +00003653 PointInfo
3654 point;
3655
cristybe82ad52011-09-06 01:17:20 +00003656 point.x=GetPseudoRandomValue(random_info[id]);
3657 point.y=GetPseudoRandomValue(random_info[id]);
cristy5c4e2582011-09-11 19:21:03 +00003658 status=InterpolatePixelChannels(image,image_view,spread_image,method,
3659 (double) x+width*point.x-0.5,(double) y+width*point.y-0.5,q,exception);
cristyed231572011-07-14 02:18:59 +00003660 q+=GetPixelChannels(spread_image);
cristy3ed852e2009-09-05 21:47:34 +00003661 }
cristy9f7e7cb2011-03-26 00:49:57 +00003662 if (SyncCacheViewAuthenticPixels(spread_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003663 status=MagickFalse;
3664 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3665 {
3666 MagickBooleanType
3667 proceed;
3668
cristyb557a152011-02-22 12:14:30 +00003669#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf8561542012-01-24 00:26:46 +00003670 #pragma omp critical (MagickCore_SpreadImage)
cristy3ed852e2009-09-05 21:47:34 +00003671#endif
3672 proceed=SetImageProgress(image,SpreadImageTag,progress++,image->rows);
3673 if (proceed == MagickFalse)
3674 status=MagickFalse;
3675 }
3676 }
cristy9f7e7cb2011-03-26 00:49:57 +00003677 spread_view=DestroyCacheView(spread_view);
cristy3ed852e2009-09-05 21:47:34 +00003678 image_view=DestroyCacheView(image_view);
3679 random_info=DestroyRandomInfoThreadSet(random_info);
cristy3ed852e2009-09-05 21:47:34 +00003680 return(spread_image);
3681}
3682
3683/*
3684%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3685% %
3686% %
3687% %
3688% U n s h a r p M a s k I m a g e %
3689% %
3690% %
3691% %
3692%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3693%
3694% UnsharpMaskImage() sharpens one or more image channels. We convolve the
3695% image with a Gaussian operator of the given radius and standard deviation
3696% (sigma). For reasonable results, radius should be larger than sigma. Use a
3697% radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
3698%
3699% The format of the UnsharpMaskImage method is:
3700%
3701% Image *UnsharpMaskImage(const Image *image,const double radius,
3702% const double sigma,const double amount,const double threshold,
3703% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003704%
3705% A description of each parameter follows:
3706%
3707% o image: the image.
3708%
cristy3ed852e2009-09-05 21:47:34 +00003709% o radius: the radius of the Gaussian, in pixels, not counting the center
3710% pixel.
3711%
3712% o sigma: the standard deviation of the Gaussian, in pixels.
3713%
3714% o amount: the percentage of the difference between the original and the
3715% blur image that is added back into the original.
3716%
3717% o threshold: the threshold in pixels needed to apply the diffence amount.
3718%
3719% o exception: return any errors or warnings in this structure.
3720%
3721*/
cristy31bb6272011-11-20 23:57:25 +00003722MagickExport Image *UnsharpMaskImage(const Image *image,const double radius,
3723 const double sigma,const double amount,const double threshold,
3724 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003725{
3726#define SharpenImageTag "Sharpen/Image"
3727
cristyc4c8d132010-01-07 01:58:38 +00003728 CacheView
3729 *image_view,
3730 *unsharp_view;
3731
cristy3ed852e2009-09-05 21:47:34 +00003732 Image
3733 *unsharp_image;
3734
cristy3ed852e2009-09-05 21:47:34 +00003735 MagickBooleanType
3736 status;
3737
cristybb503372010-05-27 20:51:26 +00003738 MagickOffsetType
3739 progress;
3740
cristy3ed852e2009-09-05 21:47:34 +00003741 MagickRealType
3742 quantum_threshold;
3743
cristybb503372010-05-27 20:51:26 +00003744 ssize_t
3745 y;
3746
cristy3ed852e2009-09-05 21:47:34 +00003747 assert(image != (const Image *) NULL);
3748 assert(image->signature == MagickSignature);
3749 if (image->debug != MagickFalse)
3750 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3751 assert(exception != (ExceptionInfo *) NULL);
cristyaa2c16c2012-03-25 22:21:35 +00003752 unsharp_image=BlurImage(image,radius,sigma,exception);
cristy3ed852e2009-09-05 21:47:34 +00003753 if (unsharp_image == (Image *) NULL)
3754 return((Image *) NULL);
3755 quantum_threshold=(MagickRealType) QuantumRange*threshold;
3756 /*
3757 Unsharp-mask image.
3758 */
3759 status=MagickTrue;
3760 progress=0;
cristydb070952012-04-20 14:33:00 +00003761 image_view=AcquireVirtualCacheView(image,exception);
3762 unsharp_view=AcquireAuthenticCacheView(unsharp_image,exception);
cristyb5d5f722009-11-04 03:03:49 +00003763#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00003764 #pragma omp parallel for schedule(static,4) shared(progress,status) \
3765 if ((image->rows*image->columns) > 8192) \
3766 num_threads(GetMagickResourceLimit(ThreadResource))
cristy3ed852e2009-09-05 21:47:34 +00003767#endif
cristybb503372010-05-27 20:51:26 +00003768 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003769 {
cristy4c08aed2011-07-01 19:47:50 +00003770 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003771 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003772
cristy4c08aed2011-07-01 19:47:50 +00003773 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003774 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003775
cristy117ff172010-08-15 21:35:32 +00003776 register ssize_t
3777 x;
3778
cristy3ed852e2009-09-05 21:47:34 +00003779 if (status == MagickFalse)
3780 continue;
3781 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy8b49f382012-01-17 03:11:03 +00003782 q=QueueCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +00003783 exception);
cristy4c08aed2011-07-01 19:47:50 +00003784 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003785 {
3786 status=MagickFalse;
3787 continue;
3788 }
cristybb503372010-05-27 20:51:26 +00003789 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003790 {
cristy7f3a0d12011-09-05 23:27:59 +00003791 register ssize_t
3792 i;
3793
cristy177e41c2012-04-15 15:08:25 +00003794 if (GetPixelMask(image,p) != 0)
3795 {
3796 p+=GetPixelChannels(image);
3797 q+=GetPixelChannels(unsharp_image);
3798 continue;
3799 }
cristy7f3a0d12011-09-05 23:27:59 +00003800 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3801 {
3802 MagickRealType
3803 pixel;
3804
3805 PixelChannel
3806 channel;
3807
3808 PixelTrait
3809 traits,
3810 unsharp_traits;
3811
cristye2a912b2011-12-05 20:02:07 +00003812 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +00003813 traits=GetPixelChannelMapTraits(image,channel);
cristy7f3a0d12011-09-05 23:27:59 +00003814 unsharp_traits=GetPixelChannelMapTraits(unsharp_image,channel);
3815 if ((traits == UndefinedPixelTrait) ||
3816 (unsharp_traits == UndefinedPixelTrait))
3817 continue;
cristy177e41c2012-04-15 15:08:25 +00003818 if ((unsharp_traits & CopyPixelTrait) != 0)
cristy7f3a0d12011-09-05 23:27:59 +00003819 {
cristy0beccfa2011-09-25 20:47:53 +00003820 SetPixelChannel(unsharp_image,channel,p[i],q);
cristy7f3a0d12011-09-05 23:27:59 +00003821 continue;
3822 }
cristy0beccfa2011-09-25 20:47:53 +00003823 pixel=p[i]-(MagickRealType) GetPixelChannel(unsharp_image,channel,q);
cristy7f3a0d12011-09-05 23:27:59 +00003824 if (fabs(2.0*pixel) < quantum_threshold)
3825 pixel=(MagickRealType) p[i];
3826 else
3827 pixel=(MagickRealType) p[i]+amount*pixel;
cristy0beccfa2011-09-25 20:47:53 +00003828 SetPixelChannel(unsharp_image,channel,ClampToQuantum(pixel),q);
cristy7f3a0d12011-09-05 23:27:59 +00003829 }
cristyed231572011-07-14 02:18:59 +00003830 p+=GetPixelChannels(image);
3831 q+=GetPixelChannels(unsharp_image);
cristy3ed852e2009-09-05 21:47:34 +00003832 }
3833 if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
3834 status=MagickFalse;
3835 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3836 {
3837 MagickBooleanType
3838 proceed;
3839
cristyb5d5f722009-11-04 03:03:49 +00003840#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf8561542012-01-24 00:26:46 +00003841 #pragma omp critical (MagickCore_UnsharpMaskImage)
cristy3ed852e2009-09-05 21:47:34 +00003842#endif
3843 proceed=SetImageProgress(image,SharpenImageTag,progress++,image->rows);
3844 if (proceed == MagickFalse)
3845 status=MagickFalse;
3846 }
3847 }
3848 unsharp_image->type=image->type;
3849 unsharp_view=DestroyCacheView(unsharp_view);
3850 image_view=DestroyCacheView(image_view);
3851 if (status == MagickFalse)
3852 unsharp_image=DestroyImage(unsharp_image);
3853 return(unsharp_image);
3854}