blob: 9d5152802a7485747e684bccca02989aca94d2f1 [file] [log] [blame]
cristy3ed852e2009-09-05 21:47:34 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% EEEEE FFFFF FFFFF EEEEE CCCC TTTTT %
7% E F F E C T %
8% EEE FFF FFF EEE C T %
9% E F F E C T %
10% EEEEE F F EEEEE CCCC T %
11% %
12% %
13% MagickCore Image Effects Methods %
14% %
15% Software Design %
16% John Cristy %
17% October 1996 %
18% %
19% %
cristy1454be72011-12-19 01:52:48 +000020% Copyright 1999-2012 ImageMagick Studio LLC, a non-profit organization %
cristy3ed852e2009-09-05 21:47:34 +000021% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
26% http://www.imagemagick.org/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
36%
37%
38*/
39
40/*
41 Include declarations.
42*/
cristy4c08aed2011-07-01 19:47:50 +000043#include "MagickCore/studio.h"
44#include "MagickCore/accelerate.h"
45#include "MagickCore/blob.h"
46#include "MagickCore/cache-view.h"
47#include "MagickCore/color.h"
48#include "MagickCore/color-private.h"
49#include "MagickCore/colorspace.h"
50#include "MagickCore/constitute.h"
51#include "MagickCore/decorate.h"
cristyc53413d2011-11-17 13:04:26 +000052#include "MagickCore/distort.h"
cristy4c08aed2011-07-01 19:47:50 +000053#include "MagickCore/draw.h"
54#include "MagickCore/enhance.h"
55#include "MagickCore/exception.h"
56#include "MagickCore/exception-private.h"
57#include "MagickCore/effect.h"
58#include "MagickCore/fx.h"
59#include "MagickCore/gem.h"
cristy8ea81222011-09-04 10:33:32 +000060#include "MagickCore/gem-private.h"
cristy4c08aed2011-07-01 19:47:50 +000061#include "MagickCore/geometry.h"
62#include "MagickCore/image-private.h"
63#include "MagickCore/list.h"
64#include "MagickCore/log.h"
65#include "MagickCore/memory_.h"
66#include "MagickCore/monitor.h"
67#include "MagickCore/monitor-private.h"
68#include "MagickCore/montage.h"
69#include "MagickCore/morphology.h"
70#include "MagickCore/paint.h"
71#include "MagickCore/pixel-accessor.h"
72#include "MagickCore/property.h"
73#include "MagickCore/quantize.h"
74#include "MagickCore/quantum.h"
75#include "MagickCore/quantum-private.h"
76#include "MagickCore/random_.h"
77#include "MagickCore/random-private.h"
78#include "MagickCore/resample.h"
79#include "MagickCore/resample-private.h"
80#include "MagickCore/resize.h"
81#include "MagickCore/resource_.h"
82#include "MagickCore/segment.h"
cristy31bbf2f2011-11-17 13:19:37 +000083#include "MagickCore/shear.h"
cristy4c08aed2011-07-01 19:47:50 +000084#include "MagickCore/signature-private.h"
cristy99bd5232011-12-07 14:38:20 +000085#include "MagickCore/statistic.h"
cristy4c08aed2011-07-01 19:47:50 +000086#include "MagickCore/string_.h"
87#include "MagickCore/thread-private.h"
88#include "MagickCore/transform.h"
89#include "MagickCore/threshold.h"
cristy3ed852e2009-09-05 21:47:34 +000090
91/*
92%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
93% %
94% %
95% %
96% A d a p t i v e B l u r I m a g e %
97% %
98% %
99% %
100%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
101%
102% AdaptiveBlurImage() adaptively blurs the image by blurring less
103% intensely near image edges and more intensely far from edges. We blur the
104% image with a Gaussian operator of the given radius and standard deviation
105% (sigma). For reasonable results, radius should be larger than sigma. Use a
106% radius of 0 and AdaptiveBlurImage() selects a suitable radius for you.
107%
108% The format of the AdaptiveBlurImage method is:
109%
110% Image *AdaptiveBlurImage(const Image *image,const double radius,
cristy4c11c2b2011-09-05 20:17:07 +0000111% const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000112%
113% A description of each parameter follows:
114%
115% o image: the image.
116%
cristy3ed852e2009-09-05 21:47:34 +0000117% o radius: the radius of the Gaussian, in pixels, not counting the center
118% pixel.
119%
120% o sigma: the standard deviation of the Laplacian, in pixels.
121%
cristy4c11c2b2011-09-05 20:17:07 +0000122% o bias: the bias.
123%
cristy3ed852e2009-09-05 21:47:34 +0000124% o exception: return any errors or warnings in this structure.
125%
126*/
127
cristyf89cb1d2011-07-07 01:24:37 +0000128MagickExport MagickBooleanType AdaptiveLevelImage(Image *image,
cristy051718b2011-08-28 22:49:25 +0000129 const char *levels,ExceptionInfo *exception)
cristyf89cb1d2011-07-07 01:24:37 +0000130{
131 double
132 black_point,
133 gamma,
134 white_point;
135
136 GeometryInfo
137 geometry_info;
138
139 MagickBooleanType
140 status;
141
142 MagickStatusType
143 flags;
144
145 /*
146 Parse levels.
147 */
148 if (levels == (char *) NULL)
149 return(MagickFalse);
150 flags=ParseGeometry(levels,&geometry_info);
151 black_point=geometry_info.rho;
152 white_point=(double) QuantumRange;
153 if ((flags & SigmaValue) != 0)
154 white_point=geometry_info.sigma;
155 gamma=1.0;
156 if ((flags & XiValue) != 0)
157 gamma=geometry_info.xi;
158 if ((flags & PercentValue) != 0)
159 {
160 black_point*=(double) image->columns*image->rows/100.0;
161 white_point*=(double) image->columns*image->rows/100.0;
162 }
163 if ((flags & SigmaValue) == 0)
164 white_point=(double) QuantumRange-black_point;
165 if ((flags & AspectValue ) == 0)
cristy7c0a0a42011-08-23 17:57:25 +0000166 status=LevelImage(image,black_point,white_point,gamma,exception);
cristyf89cb1d2011-07-07 01:24:37 +0000167 else
cristy7c0a0a42011-08-23 17:57:25 +0000168 status=LevelizeImage(image,black_point,white_point,gamma,exception);
cristyf89cb1d2011-07-07 01:24:37 +0000169 return(status);
170}
171
cristy4282c702011-11-21 00:01:06 +0000172MagickExport Image *AdaptiveBlurImage(const Image *image,const double radius,
173 const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000174{
175#define AdaptiveBlurImageTag "Convolve/Image"
176#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
177
cristyc4c8d132010-01-07 01:58:38 +0000178 CacheView
179 *blur_view,
180 *edge_view,
181 *image_view;
182
cristy3ed852e2009-09-05 21:47:34 +0000183 double
cristy47e00502009-12-17 19:19:57 +0000184 **kernel,
185 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000186
187 Image
188 *blur_image,
189 *edge_image,
190 *gaussian_image;
191
cristy3ed852e2009-09-05 21:47:34 +0000192 MagickBooleanType
193 status;
194
cristybb503372010-05-27 20:51:26 +0000195 MagickOffsetType
196 progress;
197
cristybb503372010-05-27 20:51:26 +0000198 register ssize_t
cristy47e00502009-12-17 19:19:57 +0000199 i;
cristy3ed852e2009-09-05 21:47:34 +0000200
cristybb503372010-05-27 20:51:26 +0000201 size_t
cristy3ed852e2009-09-05 21:47:34 +0000202 width;
203
cristybb503372010-05-27 20:51:26 +0000204 ssize_t
205 j,
206 k,
207 u,
208 v,
209 y;
210
cristy3ed852e2009-09-05 21:47:34 +0000211 assert(image != (const Image *) NULL);
212 assert(image->signature == MagickSignature);
213 if (image->debug != MagickFalse)
214 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
215 assert(exception != (ExceptionInfo *) NULL);
216 assert(exception->signature == MagickSignature);
217 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
218 if (blur_image == (Image *) NULL)
219 return((Image *) NULL);
220 if (fabs(sigma) <= MagickEpsilon)
221 return(blur_image);
cristy574cc262011-08-05 01:23:58 +0000222 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000223 {
cristy3ed852e2009-09-05 21:47:34 +0000224 blur_image=DestroyImage(blur_image);
225 return((Image *) NULL);
226 }
227 /*
228 Edge detect the image brighness channel, level, blur, and level again.
229 */
cristy8ae632d2011-09-05 17:29:53 +0000230 edge_image=EdgeImage(image,radius,sigma,exception);
cristy3ed852e2009-09-05 21:47:34 +0000231 if (edge_image == (Image *) NULL)
232 {
233 blur_image=DestroyImage(blur_image);
234 return((Image *) NULL);
235 }
cristy051718b2011-08-28 22:49:25 +0000236 (void) AdaptiveLevelImage(edge_image,"20%,95%",exception);
cristy05c0c9a2011-09-05 23:16:13 +0000237 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,bias,exception);
cristy3ed852e2009-09-05 21:47:34 +0000238 if (gaussian_image != (Image *) NULL)
239 {
240 edge_image=DestroyImage(edge_image);
241 edge_image=gaussian_image;
242 }
cristy051718b2011-08-28 22:49:25 +0000243 (void) AdaptiveLevelImage(edge_image,"10%,95%",exception);
cristy3ed852e2009-09-05 21:47:34 +0000244 /*
245 Create a set of kernels from maximum (radius,sigma) to minimum.
246 */
247 width=GetOptimalKernelWidth2D(radius,sigma);
248 kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
249 if (kernel == (double **) NULL)
250 {
251 edge_image=DestroyImage(edge_image);
252 blur_image=DestroyImage(blur_image);
253 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
254 }
255 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
cristybb503372010-05-27 20:51:26 +0000256 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000257 {
258 kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)*
259 sizeof(**kernel));
260 if (kernel[i] == (double *) NULL)
261 break;
cristy47e00502009-12-17 19:19:57 +0000262 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000263 j=(ssize_t) (width-i)/2;
cristy47e00502009-12-17 19:19:57 +0000264 k=0;
265 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000266 {
cristy47e00502009-12-17 19:19:57 +0000267 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000268 {
cristy4205a3c2010-09-12 20:19:59 +0000269 kernel[i][k]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
270 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +0000271 normalize+=kernel[i][k];
272 k++;
cristy3ed852e2009-09-05 21:47:34 +0000273 }
274 }
cristy3ed852e2009-09-05 21:47:34 +0000275 if (fabs(normalize) <= MagickEpsilon)
276 normalize=1.0;
277 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000278 for (k=0; k < (j*j); k++)
279 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000280 }
cristybb503372010-05-27 20:51:26 +0000281 if (i < (ssize_t) width)
cristy3ed852e2009-09-05 21:47:34 +0000282 {
283 for (i-=2; i >= 0; i-=2)
284 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
285 kernel=(double **) RelinquishMagickMemory(kernel);
286 edge_image=DestroyImage(edge_image);
287 blur_image=DestroyImage(blur_image);
288 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
289 }
290 /*
291 Adaptively blur image.
292 */
293 status=MagickTrue;
294 progress=0;
cristy3ed852e2009-09-05 21:47:34 +0000295 image_view=AcquireCacheView(image);
296 edge_view=AcquireCacheView(edge_image);
297 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000298#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +0000299 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000300#endif
cristybb503372010-05-27 20:51:26 +0000301 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000302 {
cristy4c08aed2011-07-01 19:47:50 +0000303 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000304 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000305
cristy4c08aed2011-07-01 19:47:50 +0000306 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000307 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000308
cristy117ff172010-08-15 21:35:32 +0000309 register ssize_t
310 x;
311
cristy3ed852e2009-09-05 21:47:34 +0000312 if (status == MagickFalse)
313 continue;
314 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
315 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
316 exception);
cristyacd2ed22011-08-30 01:44:23 +0000317 if ((r == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000318 {
319 status=MagickFalse;
320 continue;
321 }
cristybb503372010-05-27 20:51:26 +0000322 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000323 {
cristy4c11c2b2011-09-05 20:17:07 +0000324 register const Quantum
325 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000326
cristybb503372010-05-27 20:51:26 +0000327 register ssize_t
cristy4c11c2b2011-09-05 20:17:07 +0000328 i;
cristy3ed852e2009-09-05 21:47:34 +0000329
cristy4c11c2b2011-09-05 20:17:07 +0000330 ssize_t
331 center,
332 j;
333
334 j=(ssize_t) ceil((double) width*QuantumScale*
cristy4c08aed2011-07-01 19:47:50 +0000335 GetPixelIntensity(edge_image,r)-0.5);
cristy4c11c2b2011-09-05 20:17:07 +0000336 if (j < 0)
337 j=0;
cristy3ed852e2009-09-05 21:47:34 +0000338 else
cristy4c11c2b2011-09-05 20:17:07 +0000339 if (j > (ssize_t) width)
340 j=(ssize_t) width;
341 if ((j & 0x01) != 0)
342 j--;
343 p=GetCacheViewVirtualPixels(image_view,x-((ssize_t) (width-j)/2L),y-
344 (ssize_t) ((width-j)/2L),width-j,width-j,exception);
cristy4c08aed2011-07-01 19:47:50 +0000345 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000346 break;
cristy4c11c2b2011-09-05 20:17:07 +0000347 center=(ssize_t) GetPixelChannels(image)*(width-j)*
cristy075ff302011-09-07 01:51:24 +0000348 ((width-j)/2L)+GetPixelChannels(image)*((width-j)/2L);
cristy4c11c2b2011-09-05 20:17:07 +0000349 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy3ed852e2009-09-05 21:47:34 +0000350 {
cristy4c11c2b2011-09-05 20:17:07 +0000351 MagickRealType
352 alpha,
353 gamma,
354 pixel;
355
356 PixelChannel
357 channel;
358
359 PixelTrait
360 blur_traits,
361 traits;
362
363 register const double
364 *restrict k;
365
366 register const Quantum
367 *restrict pixels;
368
369 register ssize_t
370 u;
371
372 ssize_t
373 v;
374
cristye2a912b2011-12-05 20:02:07 +0000375 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +0000376 traits=GetPixelChannelMapTraits(image,channel);
cristy4c11c2b2011-09-05 20:17:07 +0000377 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
378 if ((traits == UndefinedPixelTrait) ||
379 (blur_traits == UndefinedPixelTrait))
380 continue;
381 if ((blur_traits & CopyPixelTrait) != 0)
382 {
cristy0beccfa2011-09-25 20:47:53 +0000383 SetPixelChannel(blur_image,channel,p[center+i],q);
cristy4c11c2b2011-09-05 20:17:07 +0000384 continue;
385 }
386 k=kernel[j];
387 pixels=p;
388 pixel=bias;
389 gamma=0.0;
390 if ((blur_traits & BlendPixelTrait) == 0)
391 {
392 /*
393 No alpha blending.
394 */
395 for (v=0; v < (ssize_t) (width-j); v++)
396 {
397 for (u=0; u < (ssize_t) (width-j); u++)
398 {
399 pixel+=(*k)*pixels[i];
400 gamma+=(*k);
401 k++;
402 pixels+=GetPixelChannels(image);
403 }
404 }
405 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +0000406 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy4c11c2b2011-09-05 20:17:07 +0000407 continue;
408 }
409 /*
410 Alpha blending.
411 */
412 for (v=0; v < (ssize_t) (width-j); v++)
cristy3ed852e2009-09-05 21:47:34 +0000413 {
cristy4c11c2b2011-09-05 20:17:07 +0000414 for (u=0; u < (ssize_t) (width-j); u++)
415 {
416 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,pixels));
417 pixel+=(*k)*alpha*pixels[i];
418 gamma+=(*k)*alpha;
419 k++;
420 pixels+=GetPixelChannels(image);
421 }
cristy3ed852e2009-09-05 21:47:34 +0000422 }
cristy4c11c2b2011-09-05 20:17:07 +0000423 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +0000424 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy3ed852e2009-09-05 21:47:34 +0000425 }
cristyed231572011-07-14 02:18:59 +0000426 q+=GetPixelChannels(blur_image);
427 r+=GetPixelChannels(edge_image);
cristy3ed852e2009-09-05 21:47:34 +0000428 }
429 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
430 status=MagickFalse;
431 if (image->progress_monitor != (MagickProgressMonitor) NULL)
432 {
433 MagickBooleanType
434 proceed;
435
cristyb5d5f722009-11-04 03:03:49 +0000436#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy8ae632d2011-09-05 17:29:53 +0000437 #pragma omp critical (MagickCore_AdaptiveBlurImage)
cristy3ed852e2009-09-05 21:47:34 +0000438#endif
439 proceed=SetImageProgress(image,AdaptiveBlurImageTag,progress++,
440 image->rows);
441 if (proceed == MagickFalse)
442 status=MagickFalse;
443 }
444 }
445 blur_image->type=image->type;
446 blur_view=DestroyCacheView(blur_view);
447 edge_view=DestroyCacheView(edge_view);
448 image_view=DestroyCacheView(image_view);
449 edge_image=DestroyImage(edge_image);
cristybb503372010-05-27 20:51:26 +0000450 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000451 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
452 kernel=(double **) RelinquishMagickMemory(kernel);
453 if (status == MagickFalse)
454 blur_image=DestroyImage(blur_image);
455 return(blur_image);
456}
457
458/*
459%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
460% %
461% %
462% %
463% A d a p t i v e S h a r p e n I m a g e %
464% %
465% %
466% %
467%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
468%
469% AdaptiveSharpenImage() adaptively sharpens the image by sharpening more
470% intensely near image edges and less intensely far from edges. We sharpen the
471% image with a Gaussian operator of the given radius and standard deviation
472% (sigma). For reasonable results, radius should be larger than sigma. Use a
473% radius of 0 and AdaptiveSharpenImage() selects a suitable radius for you.
474%
475% The format of the AdaptiveSharpenImage method is:
476%
477% Image *AdaptiveSharpenImage(const Image *image,const double radius,
cristy4c11c2b2011-09-05 20:17:07 +0000478% const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000479%
480% A description of each parameter follows:
481%
482% o image: the image.
483%
cristy3ed852e2009-09-05 21:47:34 +0000484% o radius: the radius of the Gaussian, in pixels, not counting the center
485% pixel.
486%
487% o sigma: the standard deviation of the Laplacian, in pixels.
488%
cristy4c11c2b2011-09-05 20:17:07 +0000489% o bias: the bias.
490%
cristy3ed852e2009-09-05 21:47:34 +0000491% o exception: return any errors or warnings in this structure.
492%
493*/
cristy3ed852e2009-09-05 21:47:34 +0000494MagickExport Image *AdaptiveSharpenImage(const Image *image,const double radius,
cristy4c11c2b2011-09-05 20:17:07 +0000495 const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000496{
cristy3ed852e2009-09-05 21:47:34 +0000497#define AdaptiveSharpenImageTag "Convolve/Image"
498#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
499
cristyc4c8d132010-01-07 01:58:38 +0000500 CacheView
501 *sharp_view,
502 *edge_view,
503 *image_view;
504
cristy3ed852e2009-09-05 21:47:34 +0000505 double
cristy47e00502009-12-17 19:19:57 +0000506 **kernel,
507 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000508
509 Image
510 *sharp_image,
511 *edge_image,
512 *gaussian_image;
513
cristy3ed852e2009-09-05 21:47:34 +0000514 MagickBooleanType
515 status;
516
cristybb503372010-05-27 20:51:26 +0000517 MagickOffsetType
518 progress;
519
cristybb503372010-05-27 20:51:26 +0000520 register ssize_t
cristy47e00502009-12-17 19:19:57 +0000521 i;
cristy3ed852e2009-09-05 21:47:34 +0000522
cristybb503372010-05-27 20:51:26 +0000523 size_t
cristy3ed852e2009-09-05 21:47:34 +0000524 width;
525
cristybb503372010-05-27 20:51:26 +0000526 ssize_t
527 j,
528 k,
529 u,
530 v,
531 y;
532
cristy3ed852e2009-09-05 21:47:34 +0000533 assert(image != (const Image *) NULL);
534 assert(image->signature == MagickSignature);
535 if (image->debug != MagickFalse)
536 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
537 assert(exception != (ExceptionInfo *) NULL);
538 assert(exception->signature == MagickSignature);
539 sharp_image=CloneImage(image,0,0,MagickTrue,exception);
540 if (sharp_image == (Image *) NULL)
541 return((Image *) NULL);
542 if (fabs(sigma) <= MagickEpsilon)
543 return(sharp_image);
cristy574cc262011-08-05 01:23:58 +0000544 if (SetImageStorageClass(sharp_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000545 {
cristy3ed852e2009-09-05 21:47:34 +0000546 sharp_image=DestroyImage(sharp_image);
547 return((Image *) NULL);
548 }
549 /*
550 Edge detect the image brighness channel, level, sharp, and level again.
551 */
cristy8ae632d2011-09-05 17:29:53 +0000552 edge_image=EdgeImage(image,radius,sigma,exception);
cristy3ed852e2009-09-05 21:47:34 +0000553 if (edge_image == (Image *) NULL)
554 {
555 sharp_image=DestroyImage(sharp_image);
556 return((Image *) NULL);
557 }
cristy051718b2011-08-28 22:49:25 +0000558 (void) AdaptiveLevelImage(edge_image,"20%,95%",exception);
cristy05c0c9a2011-09-05 23:16:13 +0000559 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,bias,exception);
cristy3ed852e2009-09-05 21:47:34 +0000560 if (gaussian_image != (Image *) NULL)
561 {
562 edge_image=DestroyImage(edge_image);
563 edge_image=gaussian_image;
564 }
cristy051718b2011-08-28 22:49:25 +0000565 (void) AdaptiveLevelImage(edge_image,"10%,95%",exception);
cristy3ed852e2009-09-05 21:47:34 +0000566 /*
567 Create a set of kernels from maximum (radius,sigma) to minimum.
568 */
569 width=GetOptimalKernelWidth2D(radius,sigma);
570 kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
571 if (kernel == (double **) NULL)
572 {
573 edge_image=DestroyImage(edge_image);
574 sharp_image=DestroyImage(sharp_image);
575 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
576 }
577 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
cristybb503372010-05-27 20:51:26 +0000578 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000579 {
580 kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)*
581 sizeof(**kernel));
582 if (kernel[i] == (double *) NULL)
583 break;
cristy47e00502009-12-17 19:19:57 +0000584 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000585 j=(ssize_t) (width-i)/2;
cristy47e00502009-12-17 19:19:57 +0000586 k=0;
587 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000588 {
cristy47e00502009-12-17 19:19:57 +0000589 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000590 {
cristy4205a3c2010-09-12 20:19:59 +0000591 kernel[i][k]=(double) (-exp(-((double) u*u+v*v)/(2.0*MagickSigma*
592 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +0000593 normalize+=kernel[i][k];
594 k++;
cristy3ed852e2009-09-05 21:47:34 +0000595 }
596 }
cristy3ed852e2009-09-05 21:47:34 +0000597 if (fabs(normalize) <= MagickEpsilon)
598 normalize=1.0;
599 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000600 for (k=0; k < (j*j); k++)
601 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000602 }
cristybb503372010-05-27 20:51:26 +0000603 if (i < (ssize_t) width)
cristy3ed852e2009-09-05 21:47:34 +0000604 {
605 for (i-=2; i >= 0; i-=2)
606 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
607 kernel=(double **) RelinquishMagickMemory(kernel);
608 edge_image=DestroyImage(edge_image);
609 sharp_image=DestroyImage(sharp_image);
610 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
611 }
612 /*
613 Adaptively sharpen image.
614 */
615 status=MagickTrue;
616 progress=0;
cristy3ed852e2009-09-05 21:47:34 +0000617 image_view=AcquireCacheView(image);
618 edge_view=AcquireCacheView(edge_image);
619 sharp_view=AcquireCacheView(sharp_image);
cristyb5d5f722009-11-04 03:03:49 +0000620#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +0000621 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000622#endif
cristybb503372010-05-27 20:51:26 +0000623 for (y=0; y < (ssize_t) sharp_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000624 {
cristy4c08aed2011-07-01 19:47:50 +0000625 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000626 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000627
cristy4c08aed2011-07-01 19:47:50 +0000628 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000629 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000630
cristy117ff172010-08-15 21:35:32 +0000631 register ssize_t
632 x;
633
cristy3ed852e2009-09-05 21:47:34 +0000634 if (status == MagickFalse)
635 continue;
636 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
637 q=QueueCacheViewAuthenticPixels(sharp_view,0,y,sharp_image->columns,1,
638 exception);
cristy4c08aed2011-07-01 19:47:50 +0000639 if ((r == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000640 {
641 status=MagickFalse;
642 continue;
643 }
cristybb503372010-05-27 20:51:26 +0000644 for (x=0; x < (ssize_t) sharp_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000645 {
cristy4c11c2b2011-09-05 20:17:07 +0000646 register const Quantum
647 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000648
cristybb503372010-05-27 20:51:26 +0000649 register ssize_t
cristy4c11c2b2011-09-05 20:17:07 +0000650 i;
cristy3ed852e2009-09-05 21:47:34 +0000651
cristy4c11c2b2011-09-05 20:17:07 +0000652 ssize_t
653 center,
654 j;
655
656 j=(ssize_t) ceil((double) width*QuantumScale*
cristy4c08aed2011-07-01 19:47:50 +0000657 GetPixelIntensity(edge_image,r)-0.5);
cristy4c11c2b2011-09-05 20:17:07 +0000658 if (j < 0)
659 j=0;
cristy3ed852e2009-09-05 21:47:34 +0000660 else
cristy4c11c2b2011-09-05 20:17:07 +0000661 if (j > (ssize_t) width)
662 j=(ssize_t) width;
663 if ((j & 0x01) != 0)
664 j--;
665 p=GetCacheViewVirtualPixels(image_view,x-((ssize_t) (width-j)/2L),y-
666 (ssize_t) ((width-j)/2L),width-j,width-j,exception);
cristy4c08aed2011-07-01 19:47:50 +0000667 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000668 break;
cristy4c11c2b2011-09-05 20:17:07 +0000669 center=(ssize_t) GetPixelChannels(image)*(width-j)*
670 ((width-j)/2L)+GetPixelChannels(image)*((width-j)/2);
671 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy3ed852e2009-09-05 21:47:34 +0000672 {
cristy4c11c2b2011-09-05 20:17:07 +0000673 MagickRealType
674 alpha,
675 gamma,
676 pixel;
677
678 PixelChannel
679 channel;
680
681 PixelTrait
682 sharp_traits,
683 traits;
684
685 register const double
686 *restrict k;
687
688 register const Quantum
689 *restrict pixels;
690
691 register ssize_t
692 u;
693
694 ssize_t
695 v;
696
cristye2a912b2011-12-05 20:02:07 +0000697 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +0000698 traits=GetPixelChannelMapTraits(image,channel);
cristy4c11c2b2011-09-05 20:17:07 +0000699 sharp_traits=GetPixelChannelMapTraits(sharp_image,channel);
700 if ((traits == UndefinedPixelTrait) ||
701 (sharp_traits == UndefinedPixelTrait))
702 continue;
703 if ((sharp_traits & CopyPixelTrait) != 0)
704 {
cristy0beccfa2011-09-25 20:47:53 +0000705 SetPixelChannel(sharp_image,channel,p[center+i],q);
cristy4c11c2b2011-09-05 20:17:07 +0000706 continue;
707 }
708 k=kernel[j];
709 pixels=p;
710 pixel=bias;
711 gamma=0.0;
712 if ((sharp_traits & BlendPixelTrait) == 0)
713 {
714 /*
715 No alpha blending.
716 */
717 for (v=0; v < (ssize_t) (width-j); v++)
718 {
719 for (u=0; u < (ssize_t) (width-j); u++)
720 {
721 pixel+=(*k)*pixels[i];
722 gamma+=(*k);
723 k++;
724 pixels+=GetPixelChannels(image);
725 }
726 }
727 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +0000728 SetPixelChannel(sharp_image,channel,ClampToQuantum(gamma*pixel),q);
cristy4c11c2b2011-09-05 20:17:07 +0000729 continue;
730 }
731 /*
732 Alpha blending.
733 */
734 for (v=0; v < (ssize_t) (width-j); v++)
cristy3ed852e2009-09-05 21:47:34 +0000735 {
cristy4c11c2b2011-09-05 20:17:07 +0000736 for (u=0; u < (ssize_t) (width-j); u++)
737 {
738 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,pixels));
739 pixel+=(*k)*alpha*pixels[i];
740 gamma+=(*k)*alpha;
741 k++;
742 pixels+=GetPixelChannels(image);
743 }
cristy3ed852e2009-09-05 21:47:34 +0000744 }
cristy4c11c2b2011-09-05 20:17:07 +0000745 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +0000746 SetPixelChannel(sharp_image,channel,ClampToQuantum(gamma*pixel),q);
cristy3ed852e2009-09-05 21:47:34 +0000747 }
cristyed231572011-07-14 02:18:59 +0000748 q+=GetPixelChannels(sharp_image);
749 r+=GetPixelChannels(edge_image);
cristy3ed852e2009-09-05 21:47:34 +0000750 }
751 if (SyncCacheViewAuthenticPixels(sharp_view,exception) == MagickFalse)
752 status=MagickFalse;
753 if (image->progress_monitor != (MagickProgressMonitor) NULL)
754 {
755 MagickBooleanType
756 proceed;
757
cristyb5d5f722009-11-04 03:03:49 +0000758#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +0000759 #pragma omp critical (MagickCore_AdaptiveSharpenImage)
cristy3ed852e2009-09-05 21:47:34 +0000760#endif
761 proceed=SetImageProgress(image,AdaptiveSharpenImageTag,progress++,
762 image->rows);
763 if (proceed == MagickFalse)
764 status=MagickFalse;
765 }
766 }
767 sharp_image->type=image->type;
768 sharp_view=DestroyCacheView(sharp_view);
769 edge_view=DestroyCacheView(edge_view);
770 image_view=DestroyCacheView(image_view);
771 edge_image=DestroyImage(edge_image);
cristybb503372010-05-27 20:51:26 +0000772 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000773 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
774 kernel=(double **) RelinquishMagickMemory(kernel);
775 if (status == MagickFalse)
776 sharp_image=DestroyImage(sharp_image);
777 return(sharp_image);
778}
779
780/*
781%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
782% %
783% %
784% %
785% B l u r I m a g e %
786% %
787% %
788% %
789%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
790%
791% BlurImage() blurs an image. We convolve the image with a Gaussian operator
792% of the given radius and standard deviation (sigma). For reasonable results,
793% the radius should be larger than sigma. Use a radius of 0 and BlurImage()
794% selects a suitable radius for you.
795%
796% BlurImage() differs from GaussianBlurImage() in that it uses a separable
797% kernel which is faster but mathematically equivalent to the non-separable
798% kernel.
799%
800% The format of the BlurImage method is:
801%
802% Image *BlurImage(const Image *image,const double radius,
cristy05c0c9a2011-09-05 23:16:13 +0000803% const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000804%
805% A description of each parameter follows:
806%
807% o image: the image.
808%
cristy3ed852e2009-09-05 21:47:34 +0000809% o radius: the radius of the Gaussian, in pixels, not counting the center
810% pixel.
811%
812% o sigma: the standard deviation of the Gaussian, in pixels.
813%
cristy05c0c9a2011-09-05 23:16:13 +0000814% o bias: the bias.
815%
cristy3ed852e2009-09-05 21:47:34 +0000816% o exception: return any errors or warnings in this structure.
817%
818*/
819
cristybb503372010-05-27 20:51:26 +0000820static double *GetBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +0000821{
cristy3ed852e2009-09-05 21:47:34 +0000822 double
cristy47e00502009-12-17 19:19:57 +0000823 *kernel,
824 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000825
cristy117ff172010-08-15 21:35:32 +0000826 register ssize_t
827 i;
828
cristybb503372010-05-27 20:51:26 +0000829 ssize_t
cristy47e00502009-12-17 19:19:57 +0000830 j,
831 k;
cristy3ed852e2009-09-05 21:47:34 +0000832
cristy3ed852e2009-09-05 21:47:34 +0000833 /*
834 Generate a 1-D convolution kernel.
835 */
836 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
837 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
838 if (kernel == (double *) NULL)
839 return(0);
cristy3ed852e2009-09-05 21:47:34 +0000840 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000841 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +0000842 i=0;
843 for (k=(-j); k <= j; k++)
844 {
cristy4205a3c2010-09-12 20:19:59 +0000845 kernel[i]=(double) (exp(-((double) k*k)/(2.0*MagickSigma*MagickSigma))/
846 (MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +0000847 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +0000848 i++;
849 }
cristybb503372010-05-27 20:51:26 +0000850 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000851 kernel[i]/=normalize;
852 return(kernel);
853}
854
cristyf4ad9df2011-07-08 16:49:03 +0000855MagickExport Image *BlurImage(const Image *image,const double radius,
cristy05c0c9a2011-09-05 23:16:13 +0000856 const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000857{
858#define BlurImageTag "Blur/Image"
859
cristyc4c8d132010-01-07 01:58:38 +0000860 CacheView
861 *blur_view,
862 *image_view;
863
cristy3ed852e2009-09-05 21:47:34 +0000864 double
865 *kernel;
866
867 Image
868 *blur_image;
869
cristy3ed852e2009-09-05 21:47:34 +0000870 MagickBooleanType
871 status;
872
cristybb503372010-05-27 20:51:26 +0000873 MagickOffsetType
874 progress;
875
cristybb503372010-05-27 20:51:26 +0000876 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000877 i;
878
cristybb503372010-05-27 20:51:26 +0000879 size_t
cristy3ed852e2009-09-05 21:47:34 +0000880 width;
881
cristybb503372010-05-27 20:51:26 +0000882 ssize_t
cristyb41a1172011-09-06 00:55:14 +0000883 center,
cristybb503372010-05-27 20:51:26 +0000884 x,
885 y;
886
cristy3ed852e2009-09-05 21:47:34 +0000887 /*
888 Initialize blur image attributes.
889 */
890 assert(image != (Image *) NULL);
891 assert(image->signature == MagickSignature);
892 if (image->debug != MagickFalse)
893 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
894 assert(exception != (ExceptionInfo *) NULL);
895 assert(exception->signature == MagickSignature);
cristyd25c77e2011-09-06 00:10:24 +0000896 blur_image=CloneImage(image,0,0,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +0000897 if (blur_image == (Image *) NULL)
898 return((Image *) NULL);
899 if (fabs(sigma) <= MagickEpsilon)
900 return(blur_image);
cristy574cc262011-08-05 01:23:58 +0000901 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000902 {
cristy3ed852e2009-09-05 21:47:34 +0000903 blur_image=DestroyImage(blur_image);
904 return((Image *) NULL);
905 }
906 width=GetOptimalKernelWidth1D(radius,sigma);
907 kernel=GetBlurKernel(width,sigma);
908 if (kernel == (double *) NULL)
909 {
910 blur_image=DestroyImage(blur_image);
911 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
912 }
913 if (image->debug != MagickFalse)
914 {
915 char
916 format[MaxTextExtent],
917 *message;
918
919 register const double
920 *k;
921
922 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +0000923 " BlurImage with %.20g kernel:",(double) width);
cristy3ed852e2009-09-05 21:47:34 +0000924 message=AcquireString("");
925 k=kernel;
cristybb503372010-05-27 20:51:26 +0000926 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000927 {
928 *message='\0';
cristyb51dff52011-05-19 16:55:47 +0000929 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) i);
cristy3ed852e2009-09-05 21:47:34 +0000930 (void) ConcatenateString(&message,format);
cristyb51dff52011-05-19 16:55:47 +0000931 (void) FormatLocaleString(format,MaxTextExtent,"%g ",*k++);
cristy3ed852e2009-09-05 21:47:34 +0000932 (void) ConcatenateString(&message,format);
933 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
934 }
935 message=DestroyString(message);
936 }
937 /*
938 Blur rows.
939 */
940 status=MagickTrue;
941 progress=0;
cristyb41a1172011-09-06 00:55:14 +0000942 center=(ssize_t) GetPixelChannels(image)*(width/2L);
cristy3ed852e2009-09-05 21:47:34 +0000943 image_view=AcquireCacheView(image);
944 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000945#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +0000946 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000947#endif
cristyb41a1172011-09-06 00:55:14 +0000948 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000949 {
cristy4c08aed2011-07-01 19:47:50 +0000950 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000951 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000952
cristy4c08aed2011-07-01 19:47:50 +0000953 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000954 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000955
cristy117ff172010-08-15 21:35:32 +0000956 register ssize_t
957 x;
958
cristy3ed852e2009-09-05 21:47:34 +0000959 if (status == MagickFalse)
960 continue;
cristy117ff172010-08-15 21:35:32 +0000961 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y,
962 image->columns+width,1,exception);
cristy3ed852e2009-09-05 21:47:34 +0000963 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
964 exception);
cristy4c08aed2011-07-01 19:47:50 +0000965 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000966 {
967 status=MagickFalse;
968 continue;
969 }
cristyb41a1172011-09-06 00:55:14 +0000970 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000971 {
cristybb503372010-05-27 20:51:26 +0000972 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000973 i;
974
cristyb41a1172011-09-06 00:55:14 +0000975 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
976 {
977 MagickRealType
978 alpha,
979 gamma,
980 pixel;
cristyd25c77e2011-09-06 00:10:24 +0000981
cristyb41a1172011-09-06 00:55:14 +0000982 PixelChannel
983 channel;
984
985 PixelTrait
986 blur_traits,
987 traits;
988
989 register const double
990 *restrict k;
991
992 register const Quantum
993 *restrict pixels;
994
995 register ssize_t
996 u;
997
cristye2a912b2011-12-05 20:02:07 +0000998 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +0000999 traits=GetPixelChannelMapTraits(image,channel);
cristyb41a1172011-09-06 00:55:14 +00001000 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
1001 if ((traits == UndefinedPixelTrait) ||
1002 (blur_traits == UndefinedPixelTrait))
1003 continue;
1004 if ((blur_traits & CopyPixelTrait) != 0)
cristyd25c77e2011-09-06 00:10:24 +00001005 {
cristy0beccfa2011-09-25 20:47:53 +00001006 SetPixelChannel(blur_image,channel,p[center+i],q);
cristyb41a1172011-09-06 00:55:14 +00001007 continue;
cristyd25c77e2011-09-06 00:10:24 +00001008 }
cristyb41a1172011-09-06 00:55:14 +00001009 k=kernel;
1010 pixels=p;
1011 pixel=0.0;
1012 if ((blur_traits & BlendPixelTrait) == 0)
1013 {
1014 /*
1015 No alpha blending.
1016 */
1017 for (u=0; u < (ssize_t) width; u++)
cristyd25c77e2011-09-06 00:10:24 +00001018 {
cristyb41a1172011-09-06 00:55:14 +00001019 pixel+=(*k)*pixels[i];
1020 k++;
1021 pixels+=GetPixelChannels(image);
cristyd25c77e2011-09-06 00:10:24 +00001022 }
cristy0beccfa2011-09-25 20:47:53 +00001023 SetPixelChannel(blur_image,channel,ClampToQuantum(pixel),q);
cristyb41a1172011-09-06 00:55:14 +00001024 continue;
1025 }
1026 /*
1027 Alpha blending.
1028 */
1029 gamma=0.0;
1030 for (u=0; u < (ssize_t) width; u++)
1031 {
1032 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,pixels));
1033 pixel+=(*k)*alpha*pixels[i];
1034 gamma+=(*k)*alpha;
1035 k++;
1036 pixels+=GetPixelChannels(image);
cristyd25c77e2011-09-06 00:10:24 +00001037 }
cristyb41a1172011-09-06 00:55:14 +00001038 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +00001039 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristyb41a1172011-09-06 00:55:14 +00001040 }
cristyed231572011-07-14 02:18:59 +00001041 p+=GetPixelChannels(image);
1042 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001043 }
1044 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1045 status=MagickFalse;
1046 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1047 {
1048 MagickBooleanType
1049 proceed;
1050
cristyb5d5f722009-11-04 03:03:49 +00001051#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00001052 #pragma omp critical (MagickCore_BlurImage)
cristy3ed852e2009-09-05 21:47:34 +00001053#endif
1054 proceed=SetImageProgress(image,BlurImageTag,progress++,blur_image->rows+
1055 blur_image->columns);
1056 if (proceed == MagickFalse)
1057 status=MagickFalse;
1058 }
1059 }
1060 blur_view=DestroyCacheView(blur_view);
1061 image_view=DestroyCacheView(image_view);
1062 /*
1063 Blur columns.
1064 */
1065 image_view=AcquireCacheView(blur_image);
1066 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00001067#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +00001068 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001069#endif
cristyb6dc4b72011-12-10 23:29:53 +00001070 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001071 {
cristy4c08aed2011-07-01 19:47:50 +00001072 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001073 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001074
cristy4c08aed2011-07-01 19:47:50 +00001075 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001076 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001077
cristy117ff172010-08-15 21:35:32 +00001078 register ssize_t
1079 y;
1080
cristy3ed852e2009-09-05 21:47:34 +00001081 if (status == MagickFalse)
1082 continue;
cristy117ff172010-08-15 21:35:32 +00001083 p=GetCacheViewVirtualPixels(image_view,x,-((ssize_t) width/2L),1,
cristyb6dc4b72011-12-10 23:29:53 +00001084 blur_image->rows+width,exception);
cristy3ed852e2009-09-05 21:47:34 +00001085 q=GetCacheViewAuthenticPixels(blur_view,x,0,1,blur_image->rows,exception);
cristy4c08aed2011-07-01 19:47:50 +00001086 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001087 {
1088 status=MagickFalse;
1089 continue;
1090 }
cristyb6dc4b72011-12-10 23:29:53 +00001091 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001092 {
cristybb503372010-05-27 20:51:26 +00001093 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001094 i;
1095
cristyb6dc4b72011-12-10 23:29:53 +00001096 for (i=0; i < (ssize_t) GetPixelChannels(blur_image); i++)
cristyb41a1172011-09-06 00:55:14 +00001097 {
1098 MagickRealType
1099 alpha,
1100 gamma,
1101 pixel;
cristyd25c77e2011-09-06 00:10:24 +00001102
cristyb41a1172011-09-06 00:55:14 +00001103 PixelChannel
1104 channel;
1105
1106 PixelTrait
1107 blur_traits,
1108 traits;
1109
1110 register const double
1111 *restrict k;
1112
1113 register const Quantum
1114 *restrict pixels;
1115
1116 register ssize_t
1117 u;
1118
cristye2a912b2011-12-05 20:02:07 +00001119 channel=GetPixelChannelMapChannel(blur_image,i);
cristyabace412011-12-11 15:56:53 +00001120 traits=GetPixelChannelMapTraits(blur_image,channel);
cristyb41a1172011-09-06 00:55:14 +00001121 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
1122 if ((traits == UndefinedPixelTrait) ||
1123 (blur_traits == UndefinedPixelTrait))
1124 continue;
1125 if ((blur_traits & CopyPixelTrait) != 0)
cristyd25c77e2011-09-06 00:10:24 +00001126 {
cristy0beccfa2011-09-25 20:47:53 +00001127 SetPixelChannel(blur_image,channel,p[center+i],q);
cristyb41a1172011-09-06 00:55:14 +00001128 continue;
cristyd25c77e2011-09-06 00:10:24 +00001129 }
cristyb41a1172011-09-06 00:55:14 +00001130 k=kernel;
1131 pixels=p;
1132 pixel=0.0;
1133 if ((blur_traits & BlendPixelTrait) == 0)
1134 {
1135 /*
1136 No alpha blending.
1137 */
1138 for (u=0; u < (ssize_t) width; u++)
cristyd25c77e2011-09-06 00:10:24 +00001139 {
cristyb41a1172011-09-06 00:55:14 +00001140 pixel+=(*k)*pixels[i];
1141 k++;
1142 pixels+=GetPixelChannels(blur_image);
cristyd25c77e2011-09-06 00:10:24 +00001143 }
cristy0beccfa2011-09-25 20:47:53 +00001144 SetPixelChannel(blur_image,channel,ClampToQuantum(pixel),q);
cristyb41a1172011-09-06 00:55:14 +00001145 continue;
1146 }
1147 /*
1148 Alpha blending.
1149 */
1150 gamma=0.0;
1151 for (u=0; u < (ssize_t) width; u++)
1152 {
cristyb6dc4b72011-12-10 23:29:53 +00001153 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(blur_image,
1154 pixels));
cristyb41a1172011-09-06 00:55:14 +00001155 pixel+=(*k)*alpha*pixels[i];
1156 gamma+=(*k)*alpha;
1157 k++;
1158 pixels+=GetPixelChannels(blur_image);
cristyd25c77e2011-09-06 00:10:24 +00001159 }
cristyb41a1172011-09-06 00:55:14 +00001160 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +00001161 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristyb41a1172011-09-06 00:55:14 +00001162 }
cristyd25c77e2011-09-06 00:10:24 +00001163 p+=GetPixelChannels(blur_image);
cristyed231572011-07-14 02:18:59 +00001164 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001165 }
1166 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1167 status=MagickFalse;
cristy4c08aed2011-07-01 19:47:50 +00001168 if (blur_image->progress_monitor != (MagickProgressMonitor) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001169 {
1170 MagickBooleanType
1171 proceed;
1172
cristyb5d5f722009-11-04 03:03:49 +00001173#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00001174 #pragma omp critical (MagickCore_BlurImage)
cristy3ed852e2009-09-05 21:47:34 +00001175#endif
cristy4c08aed2011-07-01 19:47:50 +00001176 proceed=SetImageProgress(blur_image,BlurImageTag,progress++,
1177 blur_image->rows+blur_image->columns);
cristy3ed852e2009-09-05 21:47:34 +00001178 if (proceed == MagickFalse)
1179 status=MagickFalse;
1180 }
1181 }
1182 blur_view=DestroyCacheView(blur_view);
1183 image_view=DestroyCacheView(image_view);
1184 kernel=(double *) RelinquishMagickMemory(kernel);
1185 if (status == MagickFalse)
1186 blur_image=DestroyImage(blur_image);
1187 blur_image->type=image->type;
1188 return(blur_image);
1189}
1190
1191/*
1192%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1193% %
1194% %
1195% %
cristyfccdab92009-11-30 16:43:57 +00001196% C o n v o l v e I m a g e %
1197% %
1198% %
1199% %
1200%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1201%
1202% ConvolveImage() applies a custom convolution kernel to the image.
1203%
1204% The format of the ConvolveImage method is:
1205%
cristy5e6be1e2011-07-16 01:23:39 +00001206% Image *ConvolveImage(const Image *image,const KernelInfo *kernel,
1207% ExceptionInfo *exception)
1208%
cristyfccdab92009-11-30 16:43:57 +00001209% A description of each parameter follows:
1210%
1211% o image: the image.
1212%
cristy5e6be1e2011-07-16 01:23:39 +00001213% o kernel: the filtering kernel.
cristyfccdab92009-11-30 16:43:57 +00001214%
1215% o exception: return any errors or warnings in this structure.
1216%
1217*/
cristy5e6be1e2011-07-16 01:23:39 +00001218MagickExport Image *ConvolveImage(const Image *image,
1219 const KernelInfo *kernel_info,ExceptionInfo *exception)
cristyfccdab92009-11-30 16:43:57 +00001220{
cristyfccdab92009-11-30 16:43:57 +00001221#define ConvolveImageTag "Convolve/Image"
1222
cristyc4c8d132010-01-07 01:58:38 +00001223 CacheView
1224 *convolve_view,
cristy105ba3c2011-07-18 02:28:38 +00001225 *image_view;
cristyc4c8d132010-01-07 01:58:38 +00001226
cristyfccdab92009-11-30 16:43:57 +00001227 Image
1228 *convolve_image;
1229
cristyfccdab92009-11-30 16:43:57 +00001230 MagickBooleanType
1231 status;
1232
cristybb503372010-05-27 20:51:26 +00001233 MagickOffsetType
1234 progress;
1235
cristybb503372010-05-27 20:51:26 +00001236 ssize_t
cristy574cc262011-08-05 01:23:58 +00001237 center,
cristybb503372010-05-27 20:51:26 +00001238 y;
1239
cristyfccdab92009-11-30 16:43:57 +00001240 /*
1241 Initialize convolve image attributes.
1242 */
1243 assert(image != (Image *) NULL);
1244 assert(image->signature == MagickSignature);
1245 if (image->debug != MagickFalse)
1246 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1247 assert(exception != (ExceptionInfo *) NULL);
1248 assert(exception->signature == MagickSignature);
cristy5e6be1e2011-07-16 01:23:39 +00001249 if ((kernel_info->width % 2) == 0)
cristyfccdab92009-11-30 16:43:57 +00001250 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
cristy08429172011-07-14 17:18:16 +00001251 convolve_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1252 exception);
cristyfccdab92009-11-30 16:43:57 +00001253 if (convolve_image == (Image *) NULL)
1254 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00001255 if (SetImageStorageClass(convolve_image,DirectClass,exception) == MagickFalse)
cristyfccdab92009-11-30 16:43:57 +00001256 {
cristyfccdab92009-11-30 16:43:57 +00001257 convolve_image=DestroyImage(convolve_image);
1258 return((Image *) NULL);
1259 }
1260 if (image->debug != MagickFalse)
1261 {
1262 char
1263 format[MaxTextExtent],
1264 *message;
1265
cristya96f2492011-12-14 18:25:41 +00001266 register const MagickRealType
cristy117ff172010-08-15 21:35:32 +00001267 *k;
1268
cristy4e154852011-07-14 13:28:53 +00001269 register ssize_t
1270 u;
1271
cristybb503372010-05-27 20:51:26 +00001272 ssize_t
cristyfccdab92009-11-30 16:43:57 +00001273 v;
1274
cristyfccdab92009-11-30 16:43:57 +00001275 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristy5e6be1e2011-07-16 01:23:39 +00001276 " ConvolveImage with %.20gx%.20g kernel:",(double) kernel_info->width,
1277 (double) kernel_info->height);
cristyfccdab92009-11-30 16:43:57 +00001278 message=AcquireString("");
cristy5e6be1e2011-07-16 01:23:39 +00001279 k=kernel_info->values;
1280 for (v=0; v < (ssize_t) kernel_info->width; v++)
cristyfccdab92009-11-30 16:43:57 +00001281 {
1282 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00001283 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristyfccdab92009-11-30 16:43:57 +00001284 (void) ConcatenateString(&message,format);
cristy5e6be1e2011-07-16 01:23:39 +00001285 for (u=0; u < (ssize_t) kernel_info->height; u++)
cristyfccdab92009-11-30 16:43:57 +00001286 {
cristyb51dff52011-05-19 16:55:47 +00001287 (void) FormatLocaleString(format,MaxTextExtent,"%g ",*k++);
cristyfccdab92009-11-30 16:43:57 +00001288 (void) ConcatenateString(&message,format);
1289 }
1290 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
1291 }
1292 message=DestroyString(message);
1293 }
cristy7ea27962011-09-12 18:12:49 +00001294 status=AccelerateConvolveImage(image,kernel_info,convolve_image,exception);
1295 if (status == MagickTrue)
1296 return(convolve_image);
cristyfccdab92009-11-30 16:43:57 +00001297 /*
cristyfccdab92009-11-30 16:43:57 +00001298 Convolve image.
1299 */
cristy574cc262011-08-05 01:23:58 +00001300 center=(ssize_t) GetPixelChannels(image)*(image->columns+kernel_info->width)*
cristy075ff302011-09-07 01:51:24 +00001301 (kernel_info->height/2L)+GetPixelChannels(image)*(kernel_info->width/2L);
cristyfccdab92009-11-30 16:43:57 +00001302 status=MagickTrue;
1303 progress=0;
cristyfccdab92009-11-30 16:43:57 +00001304 image_view=AcquireCacheView(image);
1305 convolve_view=AcquireCacheView(convolve_image);
1306#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +00001307 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristyfccdab92009-11-30 16:43:57 +00001308#endif
cristybb503372010-05-27 20:51:26 +00001309 for (y=0; y < (ssize_t) image->rows; y++)
cristyfccdab92009-11-30 16:43:57 +00001310 {
cristy4c08aed2011-07-01 19:47:50 +00001311 register const Quantum
cristy105ba3c2011-07-18 02:28:38 +00001312 *restrict p;
cristyfccdab92009-11-30 16:43:57 +00001313
cristy4c08aed2011-07-01 19:47:50 +00001314 register Quantum
cristyfccdab92009-11-30 16:43:57 +00001315 *restrict q;
1316
cristy117ff172010-08-15 21:35:32 +00001317 register ssize_t
1318 x;
1319
cristyfccdab92009-11-30 16:43:57 +00001320 if (status == MagickFalse)
1321 continue;
cristy105ba3c2011-07-18 02:28:38 +00001322 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) kernel_info->width/2L),y-
1323 (ssize_t) (kernel_info->height/2L),image->columns+kernel_info->width,
1324 kernel_info->height,exception);
cristy08429172011-07-14 17:18:16 +00001325 q=QueueCacheViewAuthenticPixels(convolve_view,0,y,convolve_image->columns,1,
cristyfccdab92009-11-30 16:43:57 +00001326 exception);
cristy105ba3c2011-07-18 02:28:38 +00001327 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristyfccdab92009-11-30 16:43:57 +00001328 {
1329 status=MagickFalse;
1330 continue;
1331 }
cristybb503372010-05-27 20:51:26 +00001332 for (x=0; x < (ssize_t) image->columns; x++)
cristyfccdab92009-11-30 16:43:57 +00001333 {
cristybb503372010-05-27 20:51:26 +00001334 register ssize_t
cristyed231572011-07-14 02:18:59 +00001335 i;
cristyfccdab92009-11-30 16:43:57 +00001336
cristya30d9ba2011-07-23 21:00:48 +00001337 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristyed231572011-07-14 02:18:59 +00001338 {
cristyed231572011-07-14 02:18:59 +00001339 MagickRealType
cristy4e154852011-07-14 13:28:53 +00001340 alpha,
1341 gamma,
cristyed231572011-07-14 02:18:59 +00001342 pixel;
1343
1344 PixelChannel
1345 channel;
1346
1347 PixelTrait
1348 convolve_traits,
1349 traits;
1350
cristya96f2492011-12-14 18:25:41 +00001351 register const MagickRealType
cristyed231572011-07-14 02:18:59 +00001352 *restrict k;
1353
1354 register const Quantum
cristyeb52cde2011-07-17 01:52:52 +00001355 *restrict pixels;
cristyed231572011-07-14 02:18:59 +00001356
1357 register ssize_t
1358 u;
1359
1360 ssize_t
1361 v;
1362
cristye2a912b2011-12-05 20:02:07 +00001363 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +00001364 traits=GetPixelChannelMapTraits(image,channel);
cristy4e154852011-07-14 13:28:53 +00001365 convolve_traits=GetPixelChannelMapTraits(convolve_image,channel);
cristy010d7d12011-08-31 01:02:48 +00001366 if ((traits == UndefinedPixelTrait) ||
1367 (convolve_traits == UndefinedPixelTrait))
cristy4e154852011-07-14 13:28:53 +00001368 continue;
1369 if ((convolve_traits & CopyPixelTrait) != 0)
1370 {
cristy0beccfa2011-09-25 20:47:53 +00001371 SetPixelChannel(convolve_image,channel,p[center+i],q);
cristy4e154852011-07-14 13:28:53 +00001372 continue;
1373 }
cristy5e6be1e2011-07-16 01:23:39 +00001374 k=kernel_info->values;
cristy105ba3c2011-07-18 02:28:38 +00001375 pixels=p;
cristy0a922382011-07-16 15:30:34 +00001376 pixel=kernel_info->bias;
cristy222b19c2011-08-04 01:35:11 +00001377 if ((convolve_traits & BlendPixelTrait) == 0)
cristyfccdab92009-11-30 16:43:57 +00001378 {
cristyed231572011-07-14 02:18:59 +00001379 /*
cristy4e154852011-07-14 13:28:53 +00001380 No alpha blending.
cristyed231572011-07-14 02:18:59 +00001381 */
cristyeb52cde2011-07-17 01:52:52 +00001382 for (v=0; v < (ssize_t) kernel_info->height; v++)
cristyfccdab92009-11-30 16:43:57 +00001383 {
cristyeb52cde2011-07-17 01:52:52 +00001384 for (u=0; u < (ssize_t) kernel_info->width; u++)
cristy175653e2011-07-10 23:13:34 +00001385 {
cristyeb52cde2011-07-17 01:52:52 +00001386 pixel+=(*k)*pixels[i];
cristyed231572011-07-14 02:18:59 +00001387 k++;
cristya30d9ba2011-07-23 21:00:48 +00001388 pixels+=GetPixelChannels(image);
cristy175653e2011-07-10 23:13:34 +00001389 }
cristya30d9ba2011-07-23 21:00:48 +00001390 pixels+=image->columns*GetPixelChannels(image);
cristy175653e2011-07-10 23:13:34 +00001391 }
cristy0beccfa2011-09-25 20:47:53 +00001392 SetPixelChannel(convolve_image,channel,ClampToQuantum(pixel),q);
cristy4e154852011-07-14 13:28:53 +00001393 continue;
cristyed231572011-07-14 02:18:59 +00001394 }
cristy4e154852011-07-14 13:28:53 +00001395 /*
1396 Alpha blending.
1397 */
1398 gamma=0.0;
cristyeb52cde2011-07-17 01:52:52 +00001399 for (v=0; v < (ssize_t) kernel_info->height; v++)
cristy4e154852011-07-14 13:28:53 +00001400 {
cristyeb52cde2011-07-17 01:52:52 +00001401 for (u=0; u < (ssize_t) kernel_info->width; u++)
cristy4e154852011-07-14 13:28:53 +00001402 {
cristyeb52cde2011-07-17 01:52:52 +00001403 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,pixels));
1404 pixel+=(*k)*alpha*pixels[i];
cristy4e154852011-07-14 13:28:53 +00001405 gamma+=(*k)*alpha;
1406 k++;
cristya30d9ba2011-07-23 21:00:48 +00001407 pixels+=GetPixelChannels(image);
cristy4e154852011-07-14 13:28:53 +00001408 }
cristya30d9ba2011-07-23 21:00:48 +00001409 pixels+=image->columns*GetPixelChannels(image);
cristy4e154852011-07-14 13:28:53 +00001410 }
cristy1ce96d02011-07-14 17:57:24 +00001411 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +00001412 SetPixelChannel(convolve_image,channel,ClampToQuantum(gamma*pixel),q);
cristyed231572011-07-14 02:18:59 +00001413 }
cristya30d9ba2011-07-23 21:00:48 +00001414 p+=GetPixelChannels(image);
1415 q+=GetPixelChannels(convolve_image);
cristyfccdab92009-11-30 16:43:57 +00001416 }
cristyed231572011-07-14 02:18:59 +00001417 if (SyncCacheViewAuthenticPixels(convolve_view,exception) == MagickFalse)
cristyfccdab92009-11-30 16:43:57 +00001418 status=MagickFalse;
1419 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1420 {
1421 MagickBooleanType
1422 proceed;
1423
1424#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00001425 #pragma omp critical (MagickCore_ConvolveImage)
cristyfccdab92009-11-30 16:43:57 +00001426#endif
1427 proceed=SetImageProgress(image,ConvolveImageTag,progress++,image->rows);
1428 if (proceed == MagickFalse)
1429 status=MagickFalse;
1430 }
1431 }
1432 convolve_image->type=image->type;
1433 convolve_view=DestroyCacheView(convolve_view);
1434 image_view=DestroyCacheView(image_view);
cristyfccdab92009-11-30 16:43:57 +00001435 if (status == MagickFalse)
1436 convolve_image=DestroyImage(convolve_image);
1437 return(convolve_image);
1438}
1439
1440/*
1441%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1442% %
1443% %
1444% %
cristy3ed852e2009-09-05 21:47:34 +00001445% D e s p e c k l e I m a g e %
1446% %
1447% %
1448% %
1449%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1450%
1451% DespeckleImage() reduces the speckle noise in an image while perserving the
cristyf0ae7762012-01-15 22:04:03 +00001452% edges of the original image. A speckle removing filter uses a complementary % hulling technique (raising pixels that are darker than their surrounding
1453% neighbors, then complementarily lowering pixels that are brighter than their
1454% surrounding neighbors) to reduce the speckle index of that image (reference
1455% Crimmins speckle removal).
cristy3ed852e2009-09-05 21:47:34 +00001456%
1457% The format of the DespeckleImage method is:
1458%
1459% Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1460%
1461% A description of each parameter follows:
1462%
1463% o image: the image.
1464%
1465% o exception: return any errors or warnings in this structure.
1466%
1467*/
1468
cristy78f3de22012-01-16 00:43:18 +00001469static void inline Hull(const ssize_t x,const ssize_t y,const int polarity,
cristyf0ae7762012-01-15 22:04:03 +00001470 Quantum *pixels)
cristy3ed852e2009-09-05 21:47:34 +00001471{
cristyf0ae7762012-01-15 22:04:03 +00001472 double
cristy78f3de22012-01-16 00:43:18 +00001473 pixel;
cristy3ed852e2009-09-05 21:47:34 +00001474
cristy78f3de22012-01-16 00:43:18 +00001475 Quantum
1476 *a,
1477 *b,
1478 *c;
1479
1480 b=pixels+4;
1481 a=b-(y*3)-x;
1482 c=b+(y*3)+x;
1483 pixel=(double) *b;
cristyf0ae7762012-01-15 22:04:03 +00001484 if (polarity > 0)
1485 {
cristy78f3de22012-01-16 00:43:18 +00001486 if ((double) *c >= (pixel+ScaleCharToQuantum(2)))
1487 pixel+=ScaleCharToQuantum(1);
cristyf0ae7762012-01-15 22:04:03 +00001488 }
1489 else
cristy78f3de22012-01-16 00:43:18 +00001490 if ((double) *c <= (pixel-ScaleCharToQuantum(2)))
1491 pixel-=ScaleCharToQuantum(1);
1492 if (polarity > 0)
cristyf0ae7762012-01-15 22:04:03 +00001493 {
cristy78f3de22012-01-16 00:43:18 +00001494 if (((double) *a >= (pixel+ScaleCharToQuantum(2))) &&
1495 ((double) *c > pixel))
1496 pixel+=ScaleCharToQuantum(1);
cristyf0ae7762012-01-15 22:04:03 +00001497 }
cristy78f3de22012-01-16 00:43:18 +00001498 else
1499 if (((double) *a <= (pixel-ScaleCharToQuantum(2))) && ((double) *c < pixel))
1500 pixel-=ScaleCharToQuantum(1);
1501 pixels[4]=ClampToQuantum(pixel);
cristy3ed852e2009-09-05 21:47:34 +00001502}
1503
1504MagickExport Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1505{
1506#define DespeckleImageTag "Despeckle/Image"
1507
cristy2407fc22009-09-11 00:55:25 +00001508 CacheView
1509 *despeckle_view,
1510 *image_view;
1511
cristy3ed852e2009-09-05 21:47:34 +00001512 Image
1513 *despeckle_image;
1514
cristy3ed852e2009-09-05 21:47:34 +00001515 MagickBooleanType
1516 status;
1517
cristyfc830f42012-01-15 02:45:06 +00001518 MagickOffsetType
1519 progress;
cristya63e4a92011-09-09 00:47:59 +00001520
cristyfc830f42012-01-15 02:45:06 +00001521 ssize_t
1522 y;
cristy117ff172010-08-15 21:35:32 +00001523
cristybb503372010-05-27 20:51:26 +00001524 static const ssize_t
cristy691a29e2009-09-11 00:44:10 +00001525 X[4] = {0, 1, 1,-1},
1526 Y[4] = {1, 0, 1, 1};
cristy3ed852e2009-09-05 21:47:34 +00001527
cristy3ed852e2009-09-05 21:47:34 +00001528 /*
1529 Allocate despeckled image.
1530 */
1531 assert(image != (const Image *) NULL);
1532 assert(image->signature == MagickSignature);
1533 if (image->debug != MagickFalse)
1534 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1535 assert(exception != (ExceptionInfo *) NULL);
1536 assert(exception->signature == MagickSignature);
cristyf0ae7762012-01-15 22:04:03 +00001537 despeckle_image=CloneImage(image,0,0,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00001538 if (despeckle_image == (Image *) NULL)
1539 return((Image *) NULL);
cristya63e4a92011-09-09 00:47:59 +00001540 status=SetImageStorageClass(despeckle_image,DirectClass,exception);
1541 if (status == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001542 {
cristy3ed852e2009-09-05 21:47:34 +00001543 despeckle_image=DestroyImage(despeckle_image);
1544 return((Image *) NULL);
1545 }
1546 /*
cristyf0ae7762012-01-15 22:04:03 +00001547 Remove speckle from the image.
cristy3ed852e2009-09-05 21:47:34 +00001548 */
1549 status=MagickTrue;
cristyfc830f42012-01-15 02:45:06 +00001550 progress=0;
cristyf0ae7762012-01-15 22:04:03 +00001551 image_view=AcquireCacheView(despeckle_image);
cristy3ed852e2009-09-05 21:47:34 +00001552 despeckle_view=AcquireCacheView(despeckle_image);
cristyfc830f42012-01-15 02:45:06 +00001553#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf0ae7762012-01-15 22:04:03 +00001554 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristyfc830f42012-01-15 02:45:06 +00001555#endif
cristyf0ae7762012-01-15 22:04:03 +00001556 for (y=0; y < (ssize_t) despeckle_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001557 {
cristy3ed852e2009-09-05 21:47:34 +00001558 register Quantum
cristyfc830f42012-01-15 02:45:06 +00001559 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001560
cristyc1488b52011-02-19 18:54:15 +00001561 register ssize_t
cristyf0ae7762012-01-15 22:04:03 +00001562 x;
cristyc1488b52011-02-19 18:54:15 +00001563
cristyfc830f42012-01-15 02:45:06 +00001564 q=GetCacheViewAuthenticPixels(despeckle_view,0,y,despeckle_image->columns,1,
1565 exception);
cristyf0ae7762012-01-15 22:04:03 +00001566 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001567 {
cristya63e4a92011-09-09 00:47:59 +00001568 status=MagickFalse;
cristyfc830f42012-01-15 02:45:06 +00001569 continue;
1570 }
cristyf0ae7762012-01-15 22:04:03 +00001571 for (x=0; x < (ssize_t) despeckle_image->columns; x++)
cristyfc830f42012-01-15 02:45:06 +00001572 {
cristyfc830f42012-01-15 02:45:06 +00001573 register ssize_t
cristyf0ae7762012-01-15 22:04:03 +00001574 i;
cristyfc830f42012-01-15 02:45:06 +00001575
cristyf0ae7762012-01-15 22:04:03 +00001576 for (i=0; i < (ssize_t) GetPixelChannels(despeckle_image); i++)
cristyfc830f42012-01-15 02:45:06 +00001577 {
cristyf0ae7762012-01-15 22:04:03 +00001578 PixelChannel
1579 channel;
1580
1581 PixelTrait
1582 traits;
1583
1584 Quantum
1585 pixels[9];
1586
1587 register const Quantum
1588 *restrict p;
1589
1590 register ssize_t
1591 j;
1592
1593 channel=GetPixelChannelMapChannel(despeckle_image,i);
1594 traits=GetPixelChannelMapTraits(despeckle_image,channel);
1595 if (traits == UndefinedPixelTrait)
1596 continue;
1597 p=GetCacheViewVirtualPixels(image_view,x-1,y-1,3,3,exception);
1598 if (p == (const Quantum *) NULL)
1599 {
1600 status=MagickFalse;
1601 continue;
1602 }
1603 if ((traits & CopyPixelTrait) != 0)
1604 continue;
1605 for (j=0; j < 9; j++)
1606 pixels[j]=p[j*GetPixelChannels(despeckle_image)+i];
1607 for (j=0; j < 4; j++)
1608 {
1609 Hull(X[j],Y[j],1,pixels);
1610 Hull(-X[j],-Y[j],1,pixels);
1611 Hull(-X[j],-Y[j],-1,pixels);
1612 Hull(X[j],Y[j],-1,pixels);
1613 }
1614 q[i]=pixels[4];
cristyfc830f42012-01-15 02:45:06 +00001615 }
cristyf0ae7762012-01-15 22:04:03 +00001616 q+=GetPixelChannels(despeckle_image);
cristy3ed852e2009-09-05 21:47:34 +00001617 }
cristyfc830f42012-01-15 02:45:06 +00001618 if (SyncCacheViewAuthenticPixels(despeckle_view,exception) == MagickFalse)
1619 status=MagickFalse;
cristy3ed852e2009-09-05 21:47:34 +00001620 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1621 {
1622 MagickBooleanType
1623 proceed;
1624
cristyfc830f42012-01-15 02:45:06 +00001625#if defined(MAGICKCORE_OPENMP_SUPPORT)
1626 #pragma omp critical (MagickCore_DespeckleImage)
1627#endif
cristyf0ae7762012-01-15 22:04:03 +00001628 proceed=SetImageProgress(despeckle_image,DespeckleImageTag,progress,
1629 despeckle_image->rows);
cristy3ed852e2009-09-05 21:47:34 +00001630 if (proceed == MagickFalse)
1631 status=MagickFalse;
1632 }
1633 }
1634 despeckle_view=DestroyCacheView(despeckle_view);
1635 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00001636 despeckle_image->type=image->type;
1637 if (status == MagickFalse)
1638 despeckle_image=DestroyImage(despeckle_image);
1639 return(despeckle_image);
1640}
1641
1642/*
1643%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1644% %
1645% %
1646% %
1647% E d g e I m a g e %
1648% %
1649% %
1650% %
1651%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1652%
1653% EdgeImage() finds edges in an image. Radius defines the radius of the
1654% convolution filter. Use a radius of 0 and EdgeImage() selects a suitable
1655% radius for you.
1656%
1657% The format of the EdgeImage method is:
1658%
1659% Image *EdgeImage(const Image *image,const double radius,
cristy8ae632d2011-09-05 17:29:53 +00001660% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001661%
1662% A description of each parameter follows:
1663%
1664% o image: the image.
1665%
1666% o radius: the radius of the pixel neighborhood.
1667%
cristy8ae632d2011-09-05 17:29:53 +00001668% o sigma: the standard deviation of the Gaussian, in pixels.
1669%
cristy3ed852e2009-09-05 21:47:34 +00001670% o exception: return any errors or warnings in this structure.
1671%
1672*/
1673MagickExport Image *EdgeImage(const Image *image,const double radius,
cristy8ae632d2011-09-05 17:29:53 +00001674 const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001675{
1676 Image
1677 *edge_image;
1678
cristy41cbe682011-07-15 19:12:37 +00001679 KernelInfo
1680 *kernel_info;
cristy3ed852e2009-09-05 21:47:34 +00001681
cristybb503372010-05-27 20:51:26 +00001682 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001683 i;
1684
cristybb503372010-05-27 20:51:26 +00001685 size_t
cristy3ed852e2009-09-05 21:47:34 +00001686 width;
1687
cristy41cbe682011-07-15 19:12:37 +00001688 ssize_t
1689 j,
1690 u,
1691 v;
1692
cristy3ed852e2009-09-05 21:47:34 +00001693 assert(image != (const Image *) NULL);
1694 assert(image->signature == MagickSignature);
1695 if (image->debug != MagickFalse)
1696 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1697 assert(exception != (ExceptionInfo *) NULL);
1698 assert(exception->signature == MagickSignature);
cristyfc0ae532011-12-06 15:14:45 +00001699 width=GetOptimalKernelWidth1D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00001700 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00001701 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001702 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00001703 kernel_info->width=width;
1704 kernel_info->height=width;
cristya96f2492011-12-14 18:25:41 +00001705 kernel_info->values=(MagickRealType *) AcquireAlignedMemory(
1706 kernel_info->width,kernel_info->width*sizeof(*kernel_info->values));
1707 if (kernel_info->values == (MagickRealType *) NULL)
cristy41cbe682011-07-15 19:12:37 +00001708 {
1709 kernel_info=DestroyKernelInfo(kernel_info);
1710 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1711 }
1712 j=(ssize_t) kernel_info->width/2;
1713 i=0;
1714 for (v=(-j); v <= j; v++)
1715 {
1716 for (u=(-j); u <= j; u++)
1717 {
1718 kernel_info->values[i]=(-1.0);
1719 i++;
1720 }
1721 }
1722 kernel_info->values[i/2]=(double) (width*width-1.0);
anthony736a1602011-10-06 12:38:17 +00001723 kernel_info->bias=image->bias; /* FUTURE: User bias on a edge image? */
cristy5e6be1e2011-07-16 01:23:39 +00001724 edge_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00001725 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00001726 return(edge_image);
1727}
1728
1729/*
1730%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1731% %
1732% %
1733% %
1734% E m b o s s I m a g e %
1735% %
1736% %
1737% %
1738%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1739%
1740% EmbossImage() returns a grayscale image with a three-dimensional effect.
1741% We convolve the image with a Gaussian operator of the given radius and
1742% standard deviation (sigma). For reasonable results, radius should be
1743% larger than sigma. Use a radius of 0 and Emboss() selects a suitable
1744% radius for you.
1745%
1746% The format of the EmbossImage method is:
1747%
1748% Image *EmbossImage(const Image *image,const double radius,
1749% const double sigma,ExceptionInfo *exception)
1750%
1751% A description of each parameter follows:
1752%
1753% o image: the image.
1754%
1755% o radius: the radius of the pixel neighborhood.
1756%
1757% o sigma: the standard deviation of the Gaussian, in pixels.
1758%
1759% o exception: return any errors or warnings in this structure.
1760%
1761*/
1762MagickExport Image *EmbossImage(const Image *image,const double radius,
1763 const double sigma,ExceptionInfo *exception)
1764{
cristy3ed852e2009-09-05 21:47:34 +00001765 Image
1766 *emboss_image;
1767
cristy41cbe682011-07-15 19:12:37 +00001768 KernelInfo
1769 *kernel_info;
1770
cristybb503372010-05-27 20:51:26 +00001771 register ssize_t
cristy47e00502009-12-17 19:19:57 +00001772 i;
1773
cristybb503372010-05-27 20:51:26 +00001774 size_t
cristy3ed852e2009-09-05 21:47:34 +00001775 width;
1776
cristy117ff172010-08-15 21:35:32 +00001777 ssize_t
1778 j,
1779 k,
1780 u,
1781 v;
1782
cristy41cbe682011-07-15 19:12:37 +00001783 assert(image != (const Image *) NULL);
cristy3ed852e2009-09-05 21:47:34 +00001784 assert(image->signature == MagickSignature);
1785 if (image->debug != MagickFalse)
1786 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1787 assert(exception != (ExceptionInfo *) NULL);
1788 assert(exception->signature == MagickSignature);
cristyfc0ae532011-12-06 15:14:45 +00001789 width=GetOptimalKernelWidth1D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00001790 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00001791 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001792 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00001793 kernel_info->width=width;
1794 kernel_info->height=width;
cristya96f2492011-12-14 18:25:41 +00001795 kernel_info->values=(MagickRealType *) AcquireAlignedMemory(
1796 kernel_info->width,kernel_info->width*sizeof(*kernel_info->values));
1797 if (kernel_info->values == (MagickRealType *) NULL)
cristy41cbe682011-07-15 19:12:37 +00001798 {
1799 kernel_info=DestroyKernelInfo(kernel_info);
1800 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1801 }
1802 j=(ssize_t) kernel_info->width/2;
cristy47e00502009-12-17 19:19:57 +00001803 k=j;
1804 i=0;
1805 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00001806 {
cristy47e00502009-12-17 19:19:57 +00001807 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00001808 {
cristy41cbe682011-07-15 19:12:37 +00001809 kernel_info->values[i]=(double) (((u < 0) || (v < 0) ? -8.0 : 8.0)*
cristy47e00502009-12-17 19:19:57 +00001810 exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
cristy4205a3c2010-09-12 20:19:59 +00001811 (2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +00001812 if (u != k)
cristy41cbe682011-07-15 19:12:37 +00001813 kernel_info->values[i]=0.0;
cristy3ed852e2009-09-05 21:47:34 +00001814 i++;
1815 }
cristy47e00502009-12-17 19:19:57 +00001816 k--;
cristy3ed852e2009-09-05 21:47:34 +00001817 }
anthony736a1602011-10-06 12:38:17 +00001818 kernel_info->bias=image->bias; /* FUTURE: user bias on an edge image */
cristy5e6be1e2011-07-16 01:23:39 +00001819 emboss_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00001820 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00001821 if (emboss_image != (Image *) NULL)
cristy6d8c3d72011-08-22 01:20:01 +00001822 (void) EqualizeImage(emboss_image,exception);
cristy3ed852e2009-09-05 21:47:34 +00001823 return(emboss_image);
1824}
1825
1826/*
1827%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1828% %
1829% %
1830% %
1831% G a u s s i a n B l u r I m a g e %
1832% %
1833% %
1834% %
1835%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1836%
1837% GaussianBlurImage() blurs an image. We convolve the image with a
1838% Gaussian operator of the given radius and standard deviation (sigma).
1839% For reasonable results, the radius should be larger than sigma. Use a
1840% radius of 0 and GaussianBlurImage() selects a suitable radius for you
1841%
1842% The format of the GaussianBlurImage method is:
1843%
1844% Image *GaussianBlurImage(const Image *image,onst double radius,
cristy05c0c9a2011-09-05 23:16:13 +00001845% const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001846%
1847% A description of each parameter follows:
1848%
1849% o image: the image.
1850%
cristy3ed852e2009-09-05 21:47:34 +00001851% o radius: the radius of the Gaussian, in pixels, not counting the center
1852% pixel.
1853%
1854% o sigma: the standard deviation of the Gaussian, in pixels.
1855%
cristy05c0c9a2011-09-05 23:16:13 +00001856% o bias: the bias.
1857%
cristy3ed852e2009-09-05 21:47:34 +00001858% o exception: return any errors or warnings in this structure.
1859%
1860*/
cristy41cbe682011-07-15 19:12:37 +00001861MagickExport Image *GaussianBlurImage(const Image *image,const double radius,
cristy05c0c9a2011-09-05 23:16:13 +00001862 const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001863{
cristy3ed852e2009-09-05 21:47:34 +00001864 Image
1865 *blur_image;
1866
cristy41cbe682011-07-15 19:12:37 +00001867 KernelInfo
1868 *kernel_info;
1869
cristybb503372010-05-27 20:51:26 +00001870 register ssize_t
cristy47e00502009-12-17 19:19:57 +00001871 i;
1872
cristybb503372010-05-27 20:51:26 +00001873 size_t
cristy3ed852e2009-09-05 21:47:34 +00001874 width;
1875
cristy117ff172010-08-15 21:35:32 +00001876 ssize_t
1877 j,
1878 u,
1879 v;
1880
cristy3ed852e2009-09-05 21:47:34 +00001881 assert(image != (const Image *) NULL);
1882 assert(image->signature == MagickSignature);
1883 if (image->debug != MagickFalse)
1884 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1885 assert(exception != (ExceptionInfo *) NULL);
1886 assert(exception->signature == MagickSignature);
1887 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00001888 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00001889 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001890 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00001891 (void) ResetMagickMemory(kernel_info,0,sizeof(*kernel_info));
1892 kernel_info->width=width;
1893 kernel_info->height=width;
anthony736a1602011-10-06 12:38:17 +00001894 kernel_info->bias=bias; /* FUTURE: user bias on Gaussian Blur! non-sense */
cristy41cbe682011-07-15 19:12:37 +00001895 kernel_info->signature=MagickSignature;
cristya96f2492011-12-14 18:25:41 +00001896 kernel_info->values=(MagickRealType *) AcquireAlignedMemory(
1897 kernel_info->width,kernel_info->width*sizeof(*kernel_info->values));
1898 if (kernel_info->values == (MagickRealType *) NULL)
cristy41cbe682011-07-15 19:12:37 +00001899 {
1900 kernel_info=DestroyKernelInfo(kernel_info);
1901 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1902 }
1903 j=(ssize_t) kernel_info->width/2;
cristy3ed852e2009-09-05 21:47:34 +00001904 i=0;
cristy47e00502009-12-17 19:19:57 +00001905 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00001906 {
cristy47e00502009-12-17 19:19:57 +00001907 for (u=(-j); u <= j; u++)
cristy41cbe682011-07-15 19:12:37 +00001908 {
1909 kernel_info->values[i]=(double) (exp(-((double) u*u+v*v)/(2.0*
1910 MagickSigma*MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
1911 i++;
1912 }
cristy3ed852e2009-09-05 21:47:34 +00001913 }
cristy5e6be1e2011-07-16 01:23:39 +00001914 blur_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00001915 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00001916 return(blur_image);
1917}
1918
1919/*
1920%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1921% %
1922% %
1923% %
cristy3ed852e2009-09-05 21:47:34 +00001924% M o t i o n B l u r I m a g e %
1925% %
1926% %
1927% %
1928%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1929%
1930% MotionBlurImage() simulates motion blur. We convolve the image with a
1931% Gaussian operator of the given radius and standard deviation (sigma).
1932% For reasonable results, radius should be larger than sigma. Use a
1933% radius of 0 and MotionBlurImage() selects a suitable radius for you.
1934% Angle gives the angle of the blurring motion.
1935%
1936% Andrew Protano contributed this effect.
1937%
1938% The format of the MotionBlurImage method is:
1939%
1940% Image *MotionBlurImage(const Image *image,const double radius,
cristyf7ef0252011-09-09 14:50:06 +00001941% const double sigma,const double angle,const double bias,
1942% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001943%
1944% A description of each parameter follows:
1945%
1946% o image: the image.
1947%
cristy3ed852e2009-09-05 21:47:34 +00001948% o radius: the radius of the Gaussian, in pixels, not counting
1949% the center pixel.
1950%
1951% o sigma: the standard deviation of the Gaussian, in pixels.
1952%
cristycee97112010-05-28 00:44:52 +00001953% o angle: Apply the effect along this angle.
cristy3ed852e2009-09-05 21:47:34 +00001954%
cristyf7ef0252011-09-09 14:50:06 +00001955% o bias: the bias.
1956%
cristy3ed852e2009-09-05 21:47:34 +00001957% o exception: return any errors or warnings in this structure.
1958%
1959*/
1960
cristybb503372010-05-27 20:51:26 +00001961static double *GetMotionBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +00001962{
cristy3ed852e2009-09-05 21:47:34 +00001963 double
cristy47e00502009-12-17 19:19:57 +00001964 *kernel,
cristy3ed852e2009-09-05 21:47:34 +00001965 normalize;
1966
cristybb503372010-05-27 20:51:26 +00001967 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001968 i;
1969
1970 /*
cristy47e00502009-12-17 19:19:57 +00001971 Generate a 1-D convolution kernel.
cristy3ed852e2009-09-05 21:47:34 +00001972 */
1973 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
1974 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
1975 if (kernel == (double *) NULL)
1976 return(kernel);
cristy3ed852e2009-09-05 21:47:34 +00001977 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00001978 for (i=0; i < (ssize_t) width; i++)
cristy47e00502009-12-17 19:19:57 +00001979 {
cristy4205a3c2010-09-12 20:19:59 +00001980 kernel[i]=(double) (exp((-((double) i*i)/(double) (2.0*MagickSigma*
1981 MagickSigma)))/(MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00001982 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +00001983 }
cristybb503372010-05-27 20:51:26 +00001984 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001985 kernel[i]/=normalize;
1986 return(kernel);
1987}
1988
cristya63e4a92011-09-09 00:47:59 +00001989MagickExport Image *MotionBlurImage(const Image *image,const double radius,
cristyf7ef0252011-09-09 14:50:06 +00001990 const double sigma,const double angle,const double bias,
1991 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001992{
cristyc4c8d132010-01-07 01:58:38 +00001993 CacheView
1994 *blur_view,
1995 *image_view;
1996
cristy3ed852e2009-09-05 21:47:34 +00001997 double
1998 *kernel;
1999
2000 Image
2001 *blur_image;
2002
cristy3ed852e2009-09-05 21:47:34 +00002003 MagickBooleanType
2004 status;
2005
cristybb503372010-05-27 20:51:26 +00002006 MagickOffsetType
2007 progress;
2008
cristy3ed852e2009-09-05 21:47:34 +00002009 OffsetInfo
2010 *offset;
2011
2012 PointInfo
2013 point;
2014
cristybb503372010-05-27 20:51:26 +00002015 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002016 i;
2017
cristybb503372010-05-27 20:51:26 +00002018 size_t
cristy3ed852e2009-09-05 21:47:34 +00002019 width;
2020
cristybb503372010-05-27 20:51:26 +00002021 ssize_t
2022 y;
2023
cristy3ed852e2009-09-05 21:47:34 +00002024 assert(image != (Image *) NULL);
2025 assert(image->signature == MagickSignature);
2026 if (image->debug != MagickFalse)
2027 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2028 assert(exception != (ExceptionInfo *) NULL);
2029 width=GetOptimalKernelWidth1D(radius,sigma);
2030 kernel=GetMotionBlurKernel(width,sigma);
2031 if (kernel == (double *) NULL)
2032 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2033 offset=(OffsetInfo *) AcquireQuantumMemory(width,sizeof(*offset));
2034 if (offset == (OffsetInfo *) NULL)
2035 {
2036 kernel=(double *) RelinquishMagickMemory(kernel);
2037 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2038 }
cristy1e7aa312011-09-10 20:01:36 +00002039 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00002040 if (blur_image == (Image *) NULL)
2041 {
2042 kernel=(double *) RelinquishMagickMemory(kernel);
2043 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2044 return((Image *) NULL);
2045 }
cristy574cc262011-08-05 01:23:58 +00002046 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00002047 {
2048 kernel=(double *) RelinquishMagickMemory(kernel);
2049 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
cristy3ed852e2009-09-05 21:47:34 +00002050 blur_image=DestroyImage(blur_image);
2051 return((Image *) NULL);
2052 }
2053 point.x=(double) width*sin(DegreesToRadians(angle));
2054 point.y=(double) width*cos(DegreesToRadians(angle));
cristybb503372010-05-27 20:51:26 +00002055 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002056 {
cristybb503372010-05-27 20:51:26 +00002057 offset[i].x=(ssize_t) ceil((double) (i*point.y)/hypot(point.x,point.y)-0.5);
2058 offset[i].y=(ssize_t) ceil((double) (i*point.x)/hypot(point.x,point.y)-0.5);
cristy3ed852e2009-09-05 21:47:34 +00002059 }
2060 /*
2061 Motion blur image.
2062 */
2063 status=MagickTrue;
2064 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00002065 image_view=AcquireCacheView(image);
2066 blur_view=AcquireCacheView(blur_image);
cristyb557a152011-02-22 12:14:30 +00002067#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy13e98362012-01-12 02:08:08 +00002068 #pragma omp parallel for schedule(static,1) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002069#endif
cristybb503372010-05-27 20:51:26 +00002070 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002071 {
cristyf7ef0252011-09-09 14:50:06 +00002072 register const Quantum
2073 *restrict p;
2074
cristy4c08aed2011-07-01 19:47:50 +00002075 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002076 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002077
cristy117ff172010-08-15 21:35:32 +00002078 register ssize_t
2079 x;
2080
cristy3ed852e2009-09-05 21:47:34 +00002081 if (status == MagickFalse)
2082 continue;
cristyf7ef0252011-09-09 14:50:06 +00002083 p=GetCacheViewVirtualPixels(blur_view,0,y,image->columns,1,exception);
cristy3ed852e2009-09-05 21:47:34 +00002084 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
2085 exception);
cristyf7ef0252011-09-09 14:50:06 +00002086 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00002087 {
2088 status=MagickFalse;
2089 continue;
2090 }
cristybb503372010-05-27 20:51:26 +00002091 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002092 {
cristybb503372010-05-27 20:51:26 +00002093 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002094 i;
2095
cristyf7ef0252011-09-09 14:50:06 +00002096 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2097 {
2098 MagickRealType
2099 alpha,
2100 gamma,
2101 pixel;
cristy3ed852e2009-09-05 21:47:34 +00002102
cristyf7ef0252011-09-09 14:50:06 +00002103 PixelChannel
2104 channel;
2105
2106 PixelTrait
2107 blur_traits,
2108 traits;
2109
2110 register const Quantum
2111 *restrict r;
2112
2113 register double
2114 *restrict k;
2115
2116 register ssize_t
2117 j;
2118
cristye2a912b2011-12-05 20:02:07 +00002119 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +00002120 traits=GetPixelChannelMapTraits(image,channel);
cristyf7ef0252011-09-09 14:50:06 +00002121 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
2122 if ((traits == UndefinedPixelTrait) ||
2123 (blur_traits == UndefinedPixelTrait))
2124 continue;
2125 if ((blur_traits & CopyPixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002126 {
cristy0beccfa2011-09-25 20:47:53 +00002127 SetPixelChannel(blur_image,channel,p[i],q);
cristyf7ef0252011-09-09 14:50:06 +00002128 continue;
cristy3ed852e2009-09-05 21:47:34 +00002129 }
cristyf7ef0252011-09-09 14:50:06 +00002130 k=kernel;
2131 pixel=bias;
2132 if ((blur_traits & BlendPixelTrait) == 0)
2133 {
2134 for (j=0; j < (ssize_t) width; j++)
2135 {
2136 r=GetCacheViewVirtualPixels(image_view,x+offset[j].x,y+
2137 offset[j].y,1,1,exception);
2138 if (r == (const Quantum *) NULL)
2139 {
2140 status=MagickFalse;
2141 continue;
2142 }
2143 pixel+=(*k)*r[i];
2144 k++;
2145 }
cristy0beccfa2011-09-25 20:47:53 +00002146 SetPixelChannel(blur_image,channel,ClampToQuantum(pixel),q);
cristyf7ef0252011-09-09 14:50:06 +00002147 continue;
2148 }
2149 alpha=0.0;
2150 gamma=0.0;
2151 for (j=0; j < (ssize_t) width; j++)
2152 {
2153 r=GetCacheViewVirtualPixels(image_view,x+offset[j].x,y+offset[j].y,1,
2154 1,exception);
2155 if (r == (const Quantum *) NULL)
2156 {
2157 status=MagickFalse;
2158 continue;
2159 }
2160 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,r));
2161 pixel+=(*k)*alpha*r[i];
2162 gamma+=(*k)*alpha;
2163 k++;
cristy3ed852e2009-09-05 21:47:34 +00002164 }
cristyf7ef0252011-09-09 14:50:06 +00002165 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +00002166 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristyf7ef0252011-09-09 14:50:06 +00002167 }
2168 p+=GetPixelChannels(image);
cristyed231572011-07-14 02:18:59 +00002169 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00002170 }
2171 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
2172 status=MagickFalse;
2173 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2174 {
2175 MagickBooleanType
2176 proceed;
2177
cristyb557a152011-02-22 12:14:30 +00002178#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00002179 #pragma omp critical (MagickCore_MotionBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00002180#endif
2181 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
2182 if (proceed == MagickFalse)
2183 status=MagickFalse;
2184 }
2185 }
2186 blur_view=DestroyCacheView(blur_view);
2187 image_view=DestroyCacheView(image_view);
2188 kernel=(double *) RelinquishMagickMemory(kernel);
2189 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2190 if (status == MagickFalse)
2191 blur_image=DestroyImage(blur_image);
2192 return(blur_image);
2193}
2194
2195/*
2196%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2197% %
2198% %
2199% %
2200% P r e v i e w I m a g e %
2201% %
2202% %
2203% %
2204%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2205%
2206% PreviewImage() tiles 9 thumbnails of the specified image with an image
2207% processing operation applied with varying parameters. This may be helpful
2208% pin-pointing an appropriate parameter for a particular image processing
2209% operation.
2210%
2211% The format of the PreviewImages method is:
2212%
2213% Image *PreviewImages(const Image *image,const PreviewType preview,
2214% ExceptionInfo *exception)
2215%
2216% A description of each parameter follows:
2217%
2218% o image: the image.
2219%
2220% o preview: the image processing operation.
2221%
2222% o exception: return any errors or warnings in this structure.
2223%
2224*/
2225MagickExport Image *PreviewImage(const Image *image,const PreviewType preview,
2226 ExceptionInfo *exception)
2227{
2228#define NumberTiles 9
2229#define PreviewImageTag "Preview/Image"
2230#define DefaultPreviewGeometry "204x204+10+10"
2231
2232 char
2233 factor[MaxTextExtent],
2234 label[MaxTextExtent];
2235
2236 double
2237 degrees,
2238 gamma,
2239 percentage,
2240 radius,
2241 sigma,
2242 threshold;
2243
2244 Image
2245 *images,
2246 *montage_image,
2247 *preview_image,
2248 *thumbnail;
2249
2250 ImageInfo
2251 *preview_info;
2252
cristy3ed852e2009-09-05 21:47:34 +00002253 MagickBooleanType
2254 proceed;
2255
2256 MontageInfo
2257 *montage_info;
2258
2259 QuantizeInfo
2260 quantize_info;
2261
2262 RectangleInfo
2263 geometry;
2264
cristybb503372010-05-27 20:51:26 +00002265 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002266 i,
2267 x;
2268
cristybb503372010-05-27 20:51:26 +00002269 size_t
cristy3ed852e2009-09-05 21:47:34 +00002270 colors;
2271
cristy117ff172010-08-15 21:35:32 +00002272 ssize_t
2273 y;
2274
cristy3ed852e2009-09-05 21:47:34 +00002275 /*
2276 Open output image file.
2277 */
2278 assert(image != (Image *) NULL);
2279 assert(image->signature == MagickSignature);
2280 if (image->debug != MagickFalse)
2281 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2282 colors=2;
2283 degrees=0.0;
2284 gamma=(-0.2f);
2285 preview_info=AcquireImageInfo();
2286 SetGeometry(image,&geometry);
2287 (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y,
2288 &geometry.width,&geometry.height);
2289 images=NewImageList();
2290 percentage=12.5;
2291 GetQuantizeInfo(&quantize_info);
2292 radius=0.0;
2293 sigma=1.0;
2294 threshold=0.0;
2295 x=0;
2296 y=0;
2297 for (i=0; i < NumberTiles; i++)
2298 {
2299 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
2300 if (thumbnail == (Image *) NULL)
2301 break;
2302 (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL,
2303 (void *) NULL);
cristyd15e6592011-10-15 00:13:06 +00002304 (void) SetImageProperty(thumbnail,"label",DefaultTileLabel,exception);
cristy3ed852e2009-09-05 21:47:34 +00002305 if (i == (NumberTiles/2))
2306 {
cristy9950d572011-10-01 18:22:35 +00002307 (void) QueryColorCompliance("#dfdfdf",AllCompliance,
2308 &thumbnail->matte_color,exception);
cristy3ed852e2009-09-05 21:47:34 +00002309 AppendImageToList(&images,thumbnail);
2310 continue;
2311 }
2312 switch (preview)
2313 {
2314 case RotatePreview:
2315 {
2316 degrees+=45.0;
2317 preview_image=RotateImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002318 (void) FormatLocaleString(label,MaxTextExtent,"rotate %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002319 break;
2320 }
2321 case ShearPreview:
2322 {
2323 degrees+=5.0;
2324 preview_image=ShearImage(thumbnail,degrees,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002325 (void) FormatLocaleString(label,MaxTextExtent,"shear %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002326 degrees,2.0*degrees);
2327 break;
2328 }
2329 case RollPreview:
2330 {
cristybb503372010-05-27 20:51:26 +00002331 x=(ssize_t) ((i+1)*thumbnail->columns)/NumberTiles;
2332 y=(ssize_t) ((i+1)*thumbnail->rows)/NumberTiles;
cristy3ed852e2009-09-05 21:47:34 +00002333 preview_image=RollImage(thumbnail,x,y,exception);
cristyb51dff52011-05-19 16:55:47 +00002334 (void) FormatLocaleString(label,MaxTextExtent,"roll %+.20gx%+.20g",
cristye8c25f92010-06-03 00:53:06 +00002335 (double) x,(double) y);
cristy3ed852e2009-09-05 21:47:34 +00002336 break;
2337 }
2338 case HuePreview:
2339 {
2340 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2341 if (preview_image == (Image *) NULL)
2342 break;
cristyb51dff52011-05-19 16:55:47 +00002343 (void) FormatLocaleString(factor,MaxTextExtent,"100,100,%g",
cristy3ed852e2009-09-05 21:47:34 +00002344 2.0*percentage);
cristy33bd5152011-08-24 01:42:24 +00002345 (void) ModulateImage(preview_image,factor,exception);
cristyb51dff52011-05-19 16:55:47 +00002346 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002347 break;
2348 }
2349 case SaturationPreview:
2350 {
2351 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2352 if (preview_image == (Image *) NULL)
2353 break;
cristyb51dff52011-05-19 16:55:47 +00002354 (void) FormatLocaleString(factor,MaxTextExtent,"100,%g",
cristy8cd5b312010-01-07 01:10:24 +00002355 2.0*percentage);
cristy33bd5152011-08-24 01:42:24 +00002356 (void) ModulateImage(preview_image,factor,exception);
cristyb51dff52011-05-19 16:55:47 +00002357 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002358 break;
2359 }
2360 case BrightnessPreview:
2361 {
2362 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2363 if (preview_image == (Image *) NULL)
2364 break;
cristyb51dff52011-05-19 16:55:47 +00002365 (void) FormatLocaleString(factor,MaxTextExtent,"%g",2.0*percentage);
cristy33bd5152011-08-24 01:42:24 +00002366 (void) ModulateImage(preview_image,factor,exception);
cristyb51dff52011-05-19 16:55:47 +00002367 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002368 break;
2369 }
2370 case GammaPreview:
2371 default:
2372 {
2373 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2374 if (preview_image == (Image *) NULL)
2375 break;
2376 gamma+=0.4f;
cristyb3e7c6c2011-07-24 01:43:55 +00002377 (void) GammaImage(preview_image,gamma,exception);
cristyb51dff52011-05-19 16:55:47 +00002378 (void) FormatLocaleString(label,MaxTextExtent,"gamma %g",gamma);
cristy3ed852e2009-09-05 21:47:34 +00002379 break;
2380 }
2381 case SpiffPreview:
2382 {
2383 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2384 if (preview_image != (Image *) NULL)
2385 for (x=0; x < i; x++)
cristye23ec9d2011-08-16 18:15:40 +00002386 (void) ContrastImage(preview_image,MagickTrue,exception);
cristyb51dff52011-05-19 16:55:47 +00002387 (void) FormatLocaleString(label,MaxTextExtent,"contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002388 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002389 break;
2390 }
2391 case DullPreview:
2392 {
2393 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2394 if (preview_image == (Image *) NULL)
2395 break;
2396 for (x=0; x < i; x++)
cristye23ec9d2011-08-16 18:15:40 +00002397 (void) ContrastImage(preview_image,MagickFalse,exception);
cristyb51dff52011-05-19 16:55:47 +00002398 (void) FormatLocaleString(label,MaxTextExtent,"+contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002399 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002400 break;
2401 }
2402 case GrayscalePreview:
2403 {
2404 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2405 if (preview_image == (Image *) NULL)
2406 break;
2407 colors<<=1;
2408 quantize_info.number_colors=colors;
2409 quantize_info.colorspace=GRAYColorspace;
cristy018f07f2011-09-04 21:15:19 +00002410 (void) QuantizeImage(&quantize_info,preview_image,exception);
cristyb51dff52011-05-19 16:55:47 +00002411 (void) FormatLocaleString(label,MaxTextExtent,
cristye8c25f92010-06-03 00:53:06 +00002412 "-colorspace gray -colors %.20g",(double) colors);
cristy3ed852e2009-09-05 21:47:34 +00002413 break;
2414 }
2415 case QuantizePreview:
2416 {
2417 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2418 if (preview_image == (Image *) NULL)
2419 break;
2420 colors<<=1;
2421 quantize_info.number_colors=colors;
cristy018f07f2011-09-04 21:15:19 +00002422 (void) QuantizeImage(&quantize_info,preview_image,exception);
cristyb51dff52011-05-19 16:55:47 +00002423 (void) FormatLocaleString(label,MaxTextExtent,"colors %.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00002424 colors);
cristy3ed852e2009-09-05 21:47:34 +00002425 break;
2426 }
2427 case DespecklePreview:
2428 {
2429 for (x=0; x < (i-1); x++)
2430 {
2431 preview_image=DespeckleImage(thumbnail,exception);
2432 if (preview_image == (Image *) NULL)
2433 break;
2434 thumbnail=DestroyImage(thumbnail);
2435 thumbnail=preview_image;
2436 }
2437 preview_image=DespeckleImage(thumbnail,exception);
2438 if (preview_image == (Image *) NULL)
2439 break;
cristyb51dff52011-05-19 16:55:47 +00002440 (void) FormatLocaleString(label,MaxTextExtent,"despeckle (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002441 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002442 break;
2443 }
2444 case ReduceNoisePreview:
2445 {
cristy95c38342011-03-18 22:39:51 +00002446 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) radius,
2447 (size_t) radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002448 (void) FormatLocaleString(label,MaxTextExtent,"noise %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002449 break;
2450 }
2451 case AddNoisePreview:
2452 {
2453 switch ((int) i)
2454 {
2455 case 0:
2456 {
2457 (void) CopyMagickString(factor,"uniform",MaxTextExtent);
2458 break;
2459 }
2460 case 1:
2461 {
2462 (void) CopyMagickString(factor,"gaussian",MaxTextExtent);
2463 break;
2464 }
2465 case 2:
2466 {
2467 (void) CopyMagickString(factor,"multiplicative",MaxTextExtent);
2468 break;
2469 }
2470 case 3:
2471 {
2472 (void) CopyMagickString(factor,"impulse",MaxTextExtent);
2473 break;
2474 }
2475 case 4:
2476 {
2477 (void) CopyMagickString(factor,"laplacian",MaxTextExtent);
2478 break;
2479 }
2480 case 5:
2481 {
2482 (void) CopyMagickString(factor,"Poisson",MaxTextExtent);
2483 break;
2484 }
2485 default:
2486 {
2487 (void) CopyMagickString(thumbnail->magick,"NULL",MaxTextExtent);
2488 break;
2489 }
2490 }
cristyd76c51e2011-03-26 00:21:26 +00002491 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) i,
2492 (size_t) i,exception);
cristyb51dff52011-05-19 16:55:47 +00002493 (void) FormatLocaleString(label,MaxTextExtent,"+noise %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002494 break;
2495 }
2496 case SharpenPreview:
2497 {
anthony736a1602011-10-06 12:38:17 +00002498 /* FUTURE: user bias on sharpen! This is non-sensical! */
cristy05c0c9a2011-09-05 23:16:13 +00002499 preview_image=SharpenImage(thumbnail,radius,sigma,image->bias,
2500 exception);
cristyb51dff52011-05-19 16:55:47 +00002501 (void) FormatLocaleString(label,MaxTextExtent,"sharpen %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002502 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002503 break;
2504 }
2505 case BlurPreview:
2506 {
anthony736a1602011-10-06 12:38:17 +00002507 /* FUTURE: user bias on blur! This is non-sensical! */
cristy05c0c9a2011-09-05 23:16:13 +00002508 preview_image=BlurImage(thumbnail,radius,sigma,image->bias,exception);
cristyb51dff52011-05-19 16:55:47 +00002509 (void) FormatLocaleString(label,MaxTextExtent,"blur %gx%g",radius,
cristy3ed852e2009-09-05 21:47:34 +00002510 sigma);
2511 break;
2512 }
2513 case ThresholdPreview:
2514 {
2515 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2516 if (preview_image == (Image *) NULL)
2517 break;
cristye941a752011-10-15 01:52:48 +00002518 (void) BilevelImage(thumbnail,(double) (percentage*((MagickRealType)
2519 QuantumRange+1.0))/100.0,exception);
cristyb51dff52011-05-19 16:55:47 +00002520 (void) FormatLocaleString(label,MaxTextExtent,"threshold %g",
cristy3ed852e2009-09-05 21:47:34 +00002521 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
2522 break;
2523 }
2524 case EdgeDetectPreview:
2525 {
cristy8ae632d2011-09-05 17:29:53 +00002526 preview_image=EdgeImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00002527 (void) FormatLocaleString(label,MaxTextExtent,"edge %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002528 break;
2529 }
2530 case SpreadPreview:
2531 {
cristy5c4e2582011-09-11 19:21:03 +00002532 preview_image=SpreadImage(thumbnail,radius,thumbnail->interpolate,
2533 exception);
cristyb51dff52011-05-19 16:55:47 +00002534 (void) FormatLocaleString(label,MaxTextExtent,"spread %g",
cristy8cd5b312010-01-07 01:10:24 +00002535 radius+0.5);
cristy3ed852e2009-09-05 21:47:34 +00002536 break;
2537 }
2538 case SolarizePreview:
2539 {
2540 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2541 if (preview_image == (Image *) NULL)
2542 break;
2543 (void) SolarizeImage(preview_image,(double) QuantumRange*
cristy5cbc0162011-08-29 00:36:28 +00002544 percentage/100.0,exception);
cristyb51dff52011-05-19 16:55:47 +00002545 (void) FormatLocaleString(label,MaxTextExtent,"solarize %g",
cristy3ed852e2009-09-05 21:47:34 +00002546 (QuantumRange*percentage)/100.0);
2547 break;
2548 }
2549 case ShadePreview:
2550 {
2551 degrees+=10.0;
2552 preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees,
2553 exception);
cristyb51dff52011-05-19 16:55:47 +00002554 (void) FormatLocaleString(label,MaxTextExtent,"shade %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002555 degrees,degrees);
cristy3ed852e2009-09-05 21:47:34 +00002556 break;
2557 }
2558 case RaisePreview:
2559 {
2560 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2561 if (preview_image == (Image *) NULL)
2562 break;
cristybb503372010-05-27 20:51:26 +00002563 geometry.width=(size_t) (2*i+2);
2564 geometry.height=(size_t) (2*i+2);
cristy3ed852e2009-09-05 21:47:34 +00002565 geometry.x=i/2;
2566 geometry.y=i/2;
cristy6170ac32011-08-28 14:15:37 +00002567 (void) RaiseImage(preview_image,&geometry,MagickTrue,exception);
cristyb51dff52011-05-19 16:55:47 +00002568 (void) FormatLocaleString(label,MaxTextExtent,
cristy6d8abba2010-06-03 01:10:47 +00002569 "raise %.20gx%.20g%+.20g%+.20g",(double) geometry.width,(double)
cristye8c25f92010-06-03 00:53:06 +00002570 geometry.height,(double) geometry.x,(double) geometry.y);
cristy3ed852e2009-09-05 21:47:34 +00002571 break;
2572 }
2573 case SegmentPreview:
2574 {
2575 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2576 if (preview_image == (Image *) NULL)
2577 break;
2578 threshold+=0.4f;
2579 (void) SegmentImage(preview_image,RGBColorspace,MagickFalse,threshold,
cristy018f07f2011-09-04 21:15:19 +00002580 threshold,exception);
cristyb51dff52011-05-19 16:55:47 +00002581 (void) FormatLocaleString(label,MaxTextExtent,"segment %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002582 threshold,threshold);
2583 break;
2584 }
2585 case SwirlPreview:
2586 {
cristy76f512e2011-09-12 01:26:56 +00002587 preview_image=SwirlImage(thumbnail,degrees,image->interpolate,
2588 exception);
cristyb51dff52011-05-19 16:55:47 +00002589 (void) FormatLocaleString(label,MaxTextExtent,"swirl %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002590 degrees+=45.0;
2591 break;
2592 }
2593 case ImplodePreview:
2594 {
2595 degrees+=0.1f;
cristy76f512e2011-09-12 01:26:56 +00002596 preview_image=ImplodeImage(thumbnail,degrees,image->interpolate,
2597 exception);
cristyb51dff52011-05-19 16:55:47 +00002598 (void) FormatLocaleString(label,MaxTextExtent,"implode %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002599 break;
2600 }
2601 case WavePreview:
2602 {
2603 degrees+=5.0f;
cristy5c4e2582011-09-11 19:21:03 +00002604 preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,
2605 image->interpolate,exception);
cristyb51dff52011-05-19 16:55:47 +00002606 (void) FormatLocaleString(label,MaxTextExtent,"wave %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002607 0.5*degrees,2.0*degrees);
cristy3ed852e2009-09-05 21:47:34 +00002608 break;
2609 }
2610 case OilPaintPreview:
2611 {
cristy14973ba2011-08-27 23:48:07 +00002612 preview_image=OilPaintImage(thumbnail,(double) radius,(double) sigma,
2613 exception);
2614 (void) FormatLocaleString(label,MaxTextExtent,"charcoal %gx%g",
2615 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002616 break;
2617 }
2618 case CharcoalDrawingPreview:
2619 {
anthony736a1602011-10-06 12:38:17 +00002620 /* FUTURE: user bias on charcoal! This is non-sensical! */
cristy3ed852e2009-09-05 21:47:34 +00002621 preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma,
cristy05c0c9a2011-09-05 23:16:13 +00002622 image->bias,exception);
cristyb51dff52011-05-19 16:55:47 +00002623 (void) FormatLocaleString(label,MaxTextExtent,"charcoal %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002624 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002625 break;
2626 }
2627 case JPEGPreview:
2628 {
2629 char
2630 filename[MaxTextExtent];
2631
2632 int
2633 file;
2634
2635 MagickBooleanType
2636 status;
2637
2638 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2639 if (preview_image == (Image *) NULL)
2640 break;
cristybb503372010-05-27 20:51:26 +00002641 preview_info->quality=(size_t) percentage;
cristyb51dff52011-05-19 16:55:47 +00002642 (void) FormatLocaleString(factor,MaxTextExtent,"%.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00002643 preview_info->quality);
cristy3ed852e2009-09-05 21:47:34 +00002644 file=AcquireUniqueFileResource(filename);
2645 if (file != -1)
2646 file=close(file)-1;
cristyb51dff52011-05-19 16:55:47 +00002647 (void) FormatLocaleString(preview_image->filename,MaxTextExtent,
cristy3ed852e2009-09-05 21:47:34 +00002648 "jpeg:%s",filename);
cristy6f9e0d32011-08-28 16:32:09 +00002649 status=WriteImage(preview_info,preview_image,exception);
cristy3ed852e2009-09-05 21:47:34 +00002650 if (status != MagickFalse)
2651 {
2652 Image
2653 *quality_image;
2654
2655 (void) CopyMagickString(preview_info->filename,
2656 preview_image->filename,MaxTextExtent);
2657 quality_image=ReadImage(preview_info,exception);
2658 if (quality_image != (Image *) NULL)
2659 {
2660 preview_image=DestroyImage(preview_image);
2661 preview_image=quality_image;
2662 }
2663 }
2664 (void) RelinquishUniqueFileResource(preview_image->filename);
2665 if ((GetBlobSize(preview_image)/1024) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00002666 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%gmb ",
cristy3ed852e2009-09-05 21:47:34 +00002667 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
2668 1024.0/1024.0);
2669 else
2670 if (GetBlobSize(preview_image) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00002671 (void) FormatLocaleString(label,MaxTextExtent,
cristye7f51092010-01-17 00:39:37 +00002672 "quality %s\n%gkb ",factor,(double) ((MagickOffsetType)
cristy8cd5b312010-01-07 01:10:24 +00002673 GetBlobSize(preview_image))/1024.0);
cristy3ed852e2009-09-05 21:47:34 +00002674 else
cristyb51dff52011-05-19 16:55:47 +00002675 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%.20gb ",
cristy54ea5732011-06-10 12:39:53 +00002676 factor,(double) ((MagickOffsetType) GetBlobSize(thumbnail)));
cristy3ed852e2009-09-05 21:47:34 +00002677 break;
2678 }
2679 }
2680 thumbnail=DestroyImage(thumbnail);
2681 percentage+=12.5;
2682 radius+=0.5;
2683 sigma+=0.25;
2684 if (preview_image == (Image *) NULL)
2685 break;
2686 (void) DeleteImageProperty(preview_image,"label");
cristyd15e6592011-10-15 00:13:06 +00002687 (void) SetImageProperty(preview_image,"label",label,exception);
cristy3ed852e2009-09-05 21:47:34 +00002688 AppendImageToList(&images,preview_image);
cristybb503372010-05-27 20:51:26 +00002689 proceed=SetImageProgress(image,PreviewImageTag,(MagickOffsetType) i,
2690 NumberTiles);
cristy3ed852e2009-09-05 21:47:34 +00002691 if (proceed == MagickFalse)
2692 break;
2693 }
2694 if (images == (Image *) NULL)
2695 {
2696 preview_info=DestroyImageInfo(preview_info);
2697 return((Image *) NULL);
2698 }
2699 /*
2700 Create the montage.
2701 */
2702 montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL);
2703 (void) CopyMagickString(montage_info->filename,image->filename,MaxTextExtent);
2704 montage_info->shadow=MagickTrue;
2705 (void) CloneString(&montage_info->tile,"3x3");
2706 (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry);
2707 (void) CloneString(&montage_info->frame,DefaultTileFrame);
2708 montage_image=MontageImages(images,montage_info,exception);
2709 montage_info=DestroyMontageInfo(montage_info);
2710 images=DestroyImageList(images);
2711 if (montage_image == (Image *) NULL)
2712 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2713 if (montage_image->montage != (char *) NULL)
2714 {
2715 /*
2716 Free image directory.
2717 */
2718 montage_image->montage=(char *) RelinquishMagickMemory(
2719 montage_image->montage);
2720 if (image->directory != (char *) NULL)
2721 montage_image->directory=(char *) RelinquishMagickMemory(
2722 montage_image->directory);
2723 }
2724 preview_info=DestroyImageInfo(preview_info);
2725 return(montage_image);
2726}
2727
2728/*
2729%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2730% %
2731% %
2732% %
2733% R a d i a l B l u r I m a g e %
2734% %
2735% %
2736% %
2737%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2738%
2739% RadialBlurImage() applies a radial blur to the image.
2740%
2741% Andrew Protano contributed this effect.
2742%
2743% The format of the RadialBlurImage method is:
2744%
2745% Image *RadialBlurImage(const Image *image,const double angle,
cristy6435bd92011-09-10 02:10:07 +00002746% const double blur,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002747%
2748% A description of each parameter follows:
2749%
2750% o image: the image.
2751%
cristy3ed852e2009-09-05 21:47:34 +00002752% o angle: the angle of the radial blur.
2753%
cristy6435bd92011-09-10 02:10:07 +00002754% o blur: the blur.
2755%
cristy3ed852e2009-09-05 21:47:34 +00002756% o exception: return any errors or warnings in this structure.
2757%
2758*/
cristy4282c702011-11-21 00:01:06 +00002759MagickExport Image *RadialBlurImage(const Image *image,const double angle,
2760 const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002761{
cristyc4c8d132010-01-07 01:58:38 +00002762 CacheView
2763 *blur_view,
2764 *image_view;
2765
cristy3ed852e2009-09-05 21:47:34 +00002766 Image
2767 *blur_image;
2768
cristy3ed852e2009-09-05 21:47:34 +00002769 MagickBooleanType
2770 status;
2771
cristybb503372010-05-27 20:51:26 +00002772 MagickOffsetType
2773 progress;
2774
cristy3ed852e2009-09-05 21:47:34 +00002775 MagickRealType
2776 blur_radius,
2777 *cos_theta,
2778 offset,
2779 *sin_theta,
2780 theta;
2781
2782 PointInfo
2783 blur_center;
2784
cristybb503372010-05-27 20:51:26 +00002785 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002786 i;
2787
cristybb503372010-05-27 20:51:26 +00002788 size_t
cristy3ed852e2009-09-05 21:47:34 +00002789 n;
2790
cristybb503372010-05-27 20:51:26 +00002791 ssize_t
2792 y;
2793
cristy3ed852e2009-09-05 21:47:34 +00002794 /*
2795 Allocate blur image.
2796 */
2797 assert(image != (Image *) NULL);
2798 assert(image->signature == MagickSignature);
2799 if (image->debug != MagickFalse)
2800 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2801 assert(exception != (ExceptionInfo *) NULL);
2802 assert(exception->signature == MagickSignature);
cristy1e7aa312011-09-10 20:01:36 +00002803 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00002804 if (blur_image == (Image *) NULL)
2805 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00002806 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00002807 {
cristy3ed852e2009-09-05 21:47:34 +00002808 blur_image=DestroyImage(blur_image);
2809 return((Image *) NULL);
2810 }
2811 blur_center.x=(double) image->columns/2.0;
2812 blur_center.y=(double) image->rows/2.0;
2813 blur_radius=hypot(blur_center.x,blur_center.y);
cristy117ff172010-08-15 21:35:32 +00002814 n=(size_t) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+2UL);
cristy3ed852e2009-09-05 21:47:34 +00002815 theta=DegreesToRadians(angle)/(MagickRealType) (n-1);
2816 cos_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
2817 sizeof(*cos_theta));
2818 sin_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
2819 sizeof(*sin_theta));
2820 if ((cos_theta == (MagickRealType *) NULL) ||
2821 (sin_theta == (MagickRealType *) NULL))
2822 {
2823 blur_image=DestroyImage(blur_image);
2824 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2825 }
2826 offset=theta*(MagickRealType) (n-1)/2.0;
cristybb503372010-05-27 20:51:26 +00002827 for (i=0; i < (ssize_t) n; i++)
cristy3ed852e2009-09-05 21:47:34 +00002828 {
2829 cos_theta[i]=cos((double) (theta*i-offset));
2830 sin_theta[i]=sin((double) (theta*i-offset));
2831 }
2832 /*
2833 Radial blur image.
2834 */
2835 status=MagickTrue;
2836 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00002837 image_view=AcquireCacheView(image);
2838 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00002839#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +00002840 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002841#endif
cristy1e7aa312011-09-10 20:01:36 +00002842 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002843 {
cristy1e7aa312011-09-10 20:01:36 +00002844 register const Quantum
2845 *restrict p;
2846
cristy4c08aed2011-07-01 19:47:50 +00002847 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002848 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002849
cristy117ff172010-08-15 21:35:32 +00002850 register ssize_t
2851 x;
2852
cristy3ed852e2009-09-05 21:47:34 +00002853 if (status == MagickFalse)
2854 continue;
cristy1e7aa312011-09-10 20:01:36 +00002855 p=GetCacheViewVirtualPixels(blur_view,0,y,image->columns,1,exception);
cristy3ed852e2009-09-05 21:47:34 +00002856 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
2857 exception);
cristy1e7aa312011-09-10 20:01:36 +00002858 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00002859 {
2860 status=MagickFalse;
2861 continue;
2862 }
cristy1e7aa312011-09-10 20:01:36 +00002863 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002864 {
cristy3ed852e2009-09-05 21:47:34 +00002865 MagickRealType
cristy3ed852e2009-09-05 21:47:34 +00002866 radius;
2867
cristy3ed852e2009-09-05 21:47:34 +00002868 PointInfo
2869 center;
2870
cristybb503372010-05-27 20:51:26 +00002871 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002872 i;
2873
cristybb503372010-05-27 20:51:26 +00002874 size_t
cristy3ed852e2009-09-05 21:47:34 +00002875 step;
2876
2877 center.x=(double) x-blur_center.x;
2878 center.y=(double) y-blur_center.y;
2879 radius=hypot((double) center.x,center.y);
2880 if (radius == 0)
2881 step=1;
2882 else
2883 {
cristybb503372010-05-27 20:51:26 +00002884 step=(size_t) (blur_radius/radius);
cristy3ed852e2009-09-05 21:47:34 +00002885 if (step == 0)
2886 step=1;
2887 else
2888 if (step >= n)
2889 step=n-1;
2890 }
cristy1e7aa312011-09-10 20:01:36 +00002891 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2892 {
2893 MagickRealType
2894 gamma,
2895 pixel;
cristy3ed852e2009-09-05 21:47:34 +00002896
cristy1e7aa312011-09-10 20:01:36 +00002897 PixelChannel
2898 channel;
2899
2900 PixelTrait
2901 blur_traits,
2902 traits;
2903
2904 register const Quantum
2905 *restrict r;
2906
2907 register ssize_t
2908 j;
2909
cristye2a912b2011-12-05 20:02:07 +00002910 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +00002911 traits=GetPixelChannelMapTraits(image,channel);
cristy1e7aa312011-09-10 20:01:36 +00002912 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
2913 if ((traits == UndefinedPixelTrait) ||
2914 (blur_traits == UndefinedPixelTrait))
2915 continue;
2916 if ((blur_traits & CopyPixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002917 {
cristy0beccfa2011-09-25 20:47:53 +00002918 SetPixelChannel(blur_image,channel,p[i],q);
cristy1e7aa312011-09-10 20:01:36 +00002919 continue;
cristy3ed852e2009-09-05 21:47:34 +00002920 }
cristy1e7aa312011-09-10 20:01:36 +00002921 gamma=0.0;
2922 pixel=bias;
2923 if ((blur_traits & BlendPixelTrait) == 0)
2924 {
2925 for (j=0; j < (ssize_t) n; j+=(ssize_t) step)
2926 {
2927 r=GetCacheViewVirtualPixels(image_view, (ssize_t) (blur_center.x+
2928 center.x*cos_theta[j]-center.y*sin_theta[j]+0.5),(ssize_t)
2929 (blur_center.y+center.x*sin_theta[j]+center.y*cos_theta[j]+0.5),
2930 1,1,exception);
2931 if (r == (const Quantum *) NULL)
2932 {
2933 status=MagickFalse;
2934 continue;
2935 }
2936 pixel+=r[i];
2937 gamma++;
2938 }
2939 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +00002940 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy1e7aa312011-09-10 20:01:36 +00002941 continue;
2942 }
2943 for (j=0; j < (ssize_t) n; j+=(ssize_t) step)
2944 {
2945 r=GetCacheViewVirtualPixels(image_view, (ssize_t) (blur_center.x+
2946 center.x*cos_theta[j]-center.y*sin_theta[j]+0.5),(ssize_t)
2947 (blur_center.y+center.x*sin_theta[j]+center.y*cos_theta[j]+0.5),
2948 1,1,exception);
2949 if (r == (const Quantum *) NULL)
2950 {
2951 status=MagickFalse;
2952 continue;
2953 }
2954 pixel+=GetPixelAlpha(image,r)*r[i];
2955 gamma+=GetPixelAlpha(image,r);
cristy3ed852e2009-09-05 21:47:34 +00002956 }
cristy1e7aa312011-09-10 20:01:36 +00002957 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +00002958 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy1e7aa312011-09-10 20:01:36 +00002959 }
2960 p+=GetPixelChannels(image);
cristyed231572011-07-14 02:18:59 +00002961 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00002962 }
2963 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
2964 status=MagickFalse;
2965 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2966 {
2967 MagickBooleanType
2968 proceed;
2969
cristyb5d5f722009-11-04 03:03:49 +00002970#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00002971 #pragma omp critical (MagickCore_RadialBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00002972#endif
2973 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
2974 if (proceed == MagickFalse)
2975 status=MagickFalse;
2976 }
2977 }
2978 blur_view=DestroyCacheView(blur_view);
2979 image_view=DestroyCacheView(image_view);
2980 cos_theta=(MagickRealType *) RelinquishMagickMemory(cos_theta);
2981 sin_theta=(MagickRealType *) RelinquishMagickMemory(sin_theta);
2982 if (status == MagickFalse)
2983 blur_image=DestroyImage(blur_image);
2984 return(blur_image);
2985}
2986
2987/*
2988%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2989% %
2990% %
2991% %
cristy3ed852e2009-09-05 21:47:34 +00002992% S e l e c t i v e B l u r I m a g e %
2993% %
2994% %
2995% %
2996%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2997%
2998% SelectiveBlurImage() selectively blur pixels within a contrast threshold.
2999% It is similar to the unsharpen mask that sharpens everything with contrast
3000% above a certain threshold.
3001%
3002% The format of the SelectiveBlurImage method is:
3003%
3004% Image *SelectiveBlurImage(const Image *image,const double radius,
cristy1e7aa312011-09-10 20:01:36 +00003005% const double sigma,const double threshold,const double bias,
3006% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003007%
3008% A description of each parameter follows:
3009%
3010% o image: the image.
3011%
cristy3ed852e2009-09-05 21:47:34 +00003012% o radius: the radius of the Gaussian, in pixels, not counting the center
3013% pixel.
3014%
3015% o sigma: the standard deviation of the Gaussian, in pixels.
3016%
3017% o threshold: only pixels within this contrast threshold are included
3018% in the blur operation.
3019%
cristy1e7aa312011-09-10 20:01:36 +00003020% o bias: the bias.
3021%
cristy3ed852e2009-09-05 21:47:34 +00003022% o exception: return any errors or warnings in this structure.
3023%
3024*/
cristy4282c702011-11-21 00:01:06 +00003025MagickExport Image *SelectiveBlurImage(const Image *image,const double radius,
3026 const double sigma,const double threshold,const double bias,
3027 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003028{
3029#define SelectiveBlurImageTag "SelectiveBlur/Image"
3030
cristy47e00502009-12-17 19:19:57 +00003031 CacheView
3032 *blur_view,
3033 *image_view;
3034
cristy3ed852e2009-09-05 21:47:34 +00003035 double
cristy3ed852e2009-09-05 21:47:34 +00003036 *kernel;
3037
3038 Image
3039 *blur_image;
3040
cristy3ed852e2009-09-05 21:47:34 +00003041 MagickBooleanType
3042 status;
3043
cristybb503372010-05-27 20:51:26 +00003044 MagickOffsetType
3045 progress;
3046
cristybb503372010-05-27 20:51:26 +00003047 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003048 i;
cristy3ed852e2009-09-05 21:47:34 +00003049
cristybb503372010-05-27 20:51:26 +00003050 size_t
cristy3ed852e2009-09-05 21:47:34 +00003051 width;
3052
cristybb503372010-05-27 20:51:26 +00003053 ssize_t
cristyc8523c12011-09-13 00:02:53 +00003054 center,
cristybb503372010-05-27 20:51:26 +00003055 j,
3056 u,
3057 v,
3058 y;
3059
cristy3ed852e2009-09-05 21:47:34 +00003060 /*
3061 Initialize blur image attributes.
3062 */
3063 assert(image != (Image *) NULL);
3064 assert(image->signature == MagickSignature);
3065 if (image->debug != MagickFalse)
3066 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3067 assert(exception != (ExceptionInfo *) NULL);
3068 assert(exception->signature == MagickSignature);
3069 width=GetOptimalKernelWidth1D(radius,sigma);
3070 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
3071 if (kernel == (double *) NULL)
3072 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00003073 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00003074 i=0;
cristy47e00502009-12-17 19:19:57 +00003075 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00003076 {
cristy47e00502009-12-17 19:19:57 +00003077 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00003078 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
3079 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00003080 }
3081 if (image->debug != MagickFalse)
3082 {
3083 char
3084 format[MaxTextExtent],
3085 *message;
3086
cristy117ff172010-08-15 21:35:32 +00003087 register const double
3088 *k;
3089
cristybb503372010-05-27 20:51:26 +00003090 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003091 u,
3092 v;
3093
cristy3ed852e2009-09-05 21:47:34 +00003094 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00003095 " SelectiveBlurImage with %.20gx%.20g kernel:",(double) width,(double)
3096 width);
cristy3ed852e2009-09-05 21:47:34 +00003097 message=AcquireString("");
3098 k=kernel;
cristybb503372010-05-27 20:51:26 +00003099 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003100 {
3101 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00003102 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy3ed852e2009-09-05 21:47:34 +00003103 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00003104 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003105 {
cristyb51dff52011-05-19 16:55:47 +00003106 (void) FormatLocaleString(format,MaxTextExtent,"%+f ",*k++);
cristy3ed852e2009-09-05 21:47:34 +00003107 (void) ConcatenateString(&message,format);
3108 }
3109 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
3110 }
3111 message=DestroyString(message);
3112 }
cristy1e7aa312011-09-10 20:01:36 +00003113 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00003114 if (blur_image == (Image *) NULL)
3115 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00003116 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003117 {
cristy3ed852e2009-09-05 21:47:34 +00003118 blur_image=DestroyImage(blur_image);
3119 return((Image *) NULL);
3120 }
3121 /*
3122 Threshold blur image.
3123 */
3124 status=MagickTrue;
3125 progress=0;
cristyc8523c12011-09-13 00:02:53 +00003126 center=(ssize_t) (GetPixelChannels(image)*(image->columns+width)*(width/2L)+
3127 GetPixelChannels(image)*(width/2L));
cristy3ed852e2009-09-05 21:47:34 +00003128 image_view=AcquireCacheView(image);
3129 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003130#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +00003131 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003132#endif
cristybb503372010-05-27 20:51:26 +00003133 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003134 {
cristy4c08aed2011-07-01 19:47:50 +00003135 double
3136 contrast;
3137
cristy3ed852e2009-09-05 21:47:34 +00003138 MagickBooleanType
3139 sync;
3140
cristy4c08aed2011-07-01 19:47:50 +00003141 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003142 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003143
cristy4c08aed2011-07-01 19:47:50 +00003144 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003145 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003146
cristy117ff172010-08-15 21:35:32 +00003147 register ssize_t
3148 x;
3149
cristy3ed852e2009-09-05 21:47:34 +00003150 if (status == MagickFalse)
3151 continue;
cristy117ff172010-08-15 21:35:32 +00003152 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
3153 (width/2L),image->columns+width,width,exception);
cristy3ed852e2009-09-05 21:47:34 +00003154 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3155 exception);
cristy4c08aed2011-07-01 19:47:50 +00003156 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003157 {
3158 status=MagickFalse;
3159 continue;
3160 }
cristybb503372010-05-27 20:51:26 +00003161 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003162 {
cristybb503372010-05-27 20:51:26 +00003163 register ssize_t
cristy1e7aa312011-09-10 20:01:36 +00003164 i;
cristy3ed852e2009-09-05 21:47:34 +00003165
cristy1e7aa312011-09-10 20:01:36 +00003166 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3167 {
3168 MagickRealType
3169 alpha,
3170 gamma,
3171 intensity,
3172 pixel;
cristy117ff172010-08-15 21:35:32 +00003173
cristy1e7aa312011-09-10 20:01:36 +00003174 PixelChannel
3175 channel;
3176
3177 PixelTrait
3178 blur_traits,
3179 traits;
3180
3181 register const double
3182 *restrict k;
3183
3184 register const Quantum
3185 *restrict pixels;
3186
3187 register ssize_t
3188 u;
3189
3190 ssize_t
3191 v;
3192
cristye2a912b2011-12-05 20:02:07 +00003193 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +00003194 traits=GetPixelChannelMapTraits(image,channel);
cristy1e7aa312011-09-10 20:01:36 +00003195 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
3196 if ((traits == UndefinedPixelTrait) ||
3197 (blur_traits == UndefinedPixelTrait))
3198 continue;
3199 if ((blur_traits & CopyPixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003200 {
cristy0beccfa2011-09-25 20:47:53 +00003201 SetPixelChannel(blur_image,channel,p[center+i],q);
cristy1e7aa312011-09-10 20:01:36 +00003202 continue;
cristy3ed852e2009-09-05 21:47:34 +00003203 }
cristy1e7aa312011-09-10 20:01:36 +00003204 k=kernel;
3205 pixel=bias;
3206 pixels=p;
cristyc8523c12011-09-13 00:02:53 +00003207 intensity=(MagickRealType) GetPixelIntensity(image,p+center);
cristy1e7aa312011-09-10 20:01:36 +00003208 gamma=0.0;
3209 if ((blur_traits & BlendPixelTrait) == 0)
cristy3ed852e2009-09-05 21:47:34 +00003210 {
cristy1e7aa312011-09-10 20:01:36 +00003211 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003212 {
cristy1e7aa312011-09-10 20:01:36 +00003213 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003214 {
cristy1e7aa312011-09-10 20:01:36 +00003215 contrast=GetPixelIntensity(image,pixels)-intensity;
3216 if (fabs(contrast) < threshold)
3217 {
3218 pixel+=(*k)*pixels[i];
3219 gamma+=(*k);
3220 }
3221 k++;
3222 pixels+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003223 }
cristy1e7aa312011-09-10 20:01:36 +00003224 pixels+=image->columns*GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003225 }
cristy1e7aa312011-09-10 20:01:36 +00003226 if (fabs((double) gamma) < MagickEpsilon)
3227 {
cristy0beccfa2011-09-25 20:47:53 +00003228 SetPixelChannel(blur_image,channel,p[center+i],q);
cristy1e7aa312011-09-10 20:01:36 +00003229 continue;
3230 }
3231 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +00003232 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy1e7aa312011-09-10 20:01:36 +00003233 continue;
3234 }
3235 for (v=0; v < (ssize_t) width; v++)
3236 {
3237 for (u=0; u < (ssize_t) width; u++)
3238 {
3239 contrast=GetPixelIntensity(image,pixels)-intensity;
3240 if (fabs(contrast) < threshold)
3241 {
3242 alpha=(MagickRealType) (QuantumScale*
3243 GetPixelAlpha(image,pixels));
3244 pixel+=(*k)*alpha*pixels[i];
3245 gamma+=(*k)*alpha;
3246 }
3247 k++;
3248 pixels+=GetPixelChannels(image);
3249 }
3250 pixels+=image->columns*GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003251 }
cristy1e7aa312011-09-10 20:01:36 +00003252 if (fabs((double) gamma) < MagickEpsilon)
3253 {
cristy0beccfa2011-09-25 20:47:53 +00003254 SetPixelChannel(blur_image,channel,p[center+i],q);
cristy1e7aa312011-09-10 20:01:36 +00003255 continue;
3256 }
3257 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristy0beccfa2011-09-25 20:47:53 +00003258 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy1e7aa312011-09-10 20:01:36 +00003259 }
cristyed231572011-07-14 02:18:59 +00003260 p+=GetPixelChannels(image);
3261 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003262 }
3263 sync=SyncCacheViewAuthenticPixels(blur_view,exception);
3264 if (sync == MagickFalse)
3265 status=MagickFalse;
3266 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3267 {
3268 MagickBooleanType
3269 proceed;
3270
cristyb5d5f722009-11-04 03:03:49 +00003271#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00003272 #pragma omp critical (MagickCore_SelectiveBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00003273#endif
3274 proceed=SetImageProgress(image,SelectiveBlurImageTag,progress++,
3275 image->rows);
3276 if (proceed == MagickFalse)
3277 status=MagickFalse;
3278 }
3279 }
3280 blur_image->type=image->type;
3281 blur_view=DestroyCacheView(blur_view);
3282 image_view=DestroyCacheView(image_view);
3283 kernel=(double *) RelinquishMagickMemory(kernel);
3284 if (status == MagickFalse)
3285 blur_image=DestroyImage(blur_image);
3286 return(blur_image);
3287}
3288
3289/*
3290%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3291% %
3292% %
3293% %
3294% S h a d e I m a g e %
3295% %
3296% %
3297% %
3298%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3299%
3300% ShadeImage() shines a distant light on an image to create a
3301% three-dimensional effect. You control the positioning of the light with
3302% azimuth and elevation; azimuth is measured in degrees off the x axis
3303% and elevation is measured in pixels above the Z axis.
3304%
3305% The format of the ShadeImage method is:
3306%
3307% Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3308% const double azimuth,const double elevation,ExceptionInfo *exception)
3309%
3310% A description of each parameter follows:
3311%
3312% o image: the image.
3313%
3314% o gray: A value other than zero shades the intensity of each pixel.
3315%
3316% o azimuth, elevation: Define the light source direction.
3317%
3318% o exception: return any errors or warnings in this structure.
3319%
3320*/
3321MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3322 const double azimuth,const double elevation,ExceptionInfo *exception)
3323{
3324#define ShadeImageTag "Shade/Image"
3325
cristyc4c8d132010-01-07 01:58:38 +00003326 CacheView
3327 *image_view,
3328 *shade_view;
3329
cristy3ed852e2009-09-05 21:47:34 +00003330 Image
3331 *shade_image;
3332
cristy3ed852e2009-09-05 21:47:34 +00003333 MagickBooleanType
3334 status;
3335
cristybb503372010-05-27 20:51:26 +00003336 MagickOffsetType
3337 progress;
3338
cristy3ed852e2009-09-05 21:47:34 +00003339 PrimaryInfo
3340 light;
3341
cristybb503372010-05-27 20:51:26 +00003342 ssize_t
3343 y;
3344
cristy3ed852e2009-09-05 21:47:34 +00003345 /*
3346 Initialize shaded image attributes.
3347 */
3348 assert(image != (const Image *) NULL);
3349 assert(image->signature == MagickSignature);
3350 if (image->debug != MagickFalse)
3351 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3352 assert(exception != (ExceptionInfo *) NULL);
3353 assert(exception->signature == MagickSignature);
3354 shade_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
3355 if (shade_image == (Image *) NULL)
3356 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00003357 if (SetImageStorageClass(shade_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003358 {
cristy3ed852e2009-09-05 21:47:34 +00003359 shade_image=DestroyImage(shade_image);
3360 return((Image *) NULL);
3361 }
3362 /*
3363 Compute the light vector.
3364 */
3365 light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))*
3366 cos(DegreesToRadians(elevation));
3367 light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))*
3368 cos(DegreesToRadians(elevation));
3369 light.z=(double) QuantumRange*sin(DegreesToRadians(elevation));
3370 /*
3371 Shade image.
3372 */
3373 status=MagickTrue;
3374 progress=0;
3375 image_view=AcquireCacheView(image);
3376 shade_view=AcquireCacheView(shade_image);
cristyb5d5f722009-11-04 03:03:49 +00003377#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +00003378 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003379#endif
cristybb503372010-05-27 20:51:26 +00003380 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003381 {
3382 MagickRealType
3383 distance,
3384 normal_distance,
3385 shade;
3386
3387 PrimaryInfo
3388 normal;
3389
cristy4c08aed2011-07-01 19:47:50 +00003390 register const Quantum
cristy1e7aa312011-09-10 20:01:36 +00003391 *restrict center,
cristyc47d1f82009-11-26 01:44:43 +00003392 *restrict p,
cristy1e7aa312011-09-10 20:01:36 +00003393 *restrict post,
3394 *restrict pre;
cristy3ed852e2009-09-05 21:47:34 +00003395
cristy4c08aed2011-07-01 19:47:50 +00003396 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003397 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003398
cristy117ff172010-08-15 21:35:32 +00003399 register ssize_t
3400 x;
3401
cristy3ed852e2009-09-05 21:47:34 +00003402 if (status == MagickFalse)
3403 continue;
3404 p=GetCacheViewVirtualPixels(image_view,-1,y-1,image->columns+2,3,exception);
3405 q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1,
3406 exception);
cristy4c08aed2011-07-01 19:47:50 +00003407 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003408 {
3409 status=MagickFalse;
3410 continue;
3411 }
3412 /*
3413 Shade this row of pixels.
3414 */
3415 normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */
cristy1e7aa312011-09-10 20:01:36 +00003416 pre=p+GetPixelChannels(image);
3417 center=pre+(image->columns+2)*GetPixelChannels(image);
3418 post=center+(image->columns+2)*GetPixelChannels(image);
cristybb503372010-05-27 20:51:26 +00003419 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003420 {
cristy1e7aa312011-09-10 20:01:36 +00003421 register ssize_t
3422 i;
3423
cristy3ed852e2009-09-05 21:47:34 +00003424 /*
3425 Determine the surface normal and compute shading.
3426 */
cristy1e7aa312011-09-10 20:01:36 +00003427 normal.x=(double) (GetPixelIntensity(image,pre-GetPixelChannels(image))+
3428 GetPixelIntensity(image,center-GetPixelChannels(image))+
3429 GetPixelIntensity(image,post-GetPixelChannels(image))-
3430 GetPixelIntensity(image,pre+GetPixelChannels(image))-
3431 GetPixelIntensity(image,center+GetPixelChannels(image))-
3432 GetPixelIntensity(image,post+GetPixelChannels(image)));
3433 normal.y=(double) (GetPixelIntensity(image,post-GetPixelChannels(image))+
3434 GetPixelIntensity(image,post)+GetPixelIntensity(image,post+
3435 GetPixelChannels(image))-GetPixelIntensity(image,pre-
3436 GetPixelChannels(image))-GetPixelIntensity(image,pre)-
3437 GetPixelIntensity(image,pre+GetPixelChannels(image)));
cristy3ed852e2009-09-05 21:47:34 +00003438 if ((normal.x == 0.0) && (normal.y == 0.0))
3439 shade=light.z;
3440 else
3441 {
3442 shade=0.0;
3443 distance=normal.x*light.x+normal.y*light.y+normal.z*light.z;
3444 if (distance > MagickEpsilon)
3445 {
3446 normal_distance=
3447 normal.x*normal.x+normal.y*normal.y+normal.z*normal.z;
3448 if (normal_distance > (MagickEpsilon*MagickEpsilon))
3449 shade=distance/sqrt((double) normal_distance);
3450 }
3451 }
cristy1e7aa312011-09-10 20:01:36 +00003452 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3453 {
3454 PixelChannel
3455 channel;
3456
3457 PixelTrait
3458 shade_traits,
3459 traits;
3460
cristye2a912b2011-12-05 20:02:07 +00003461 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +00003462 traits=GetPixelChannelMapTraits(image,channel);
cristy1e7aa312011-09-10 20:01:36 +00003463 shade_traits=GetPixelChannelMapTraits(shade_image,channel);
3464 if ((traits == UndefinedPixelTrait) ||
3465 (shade_traits == UndefinedPixelTrait))
3466 continue;
3467 if ((shade_traits & CopyPixelTrait) != 0)
3468 {
cristy0beccfa2011-09-25 20:47:53 +00003469 SetPixelChannel(shade_image,channel,center[i],q);
cristy1e7aa312011-09-10 20:01:36 +00003470 continue;
3471 }
3472 if (gray != MagickFalse)
3473 {
cristy0beccfa2011-09-25 20:47:53 +00003474 SetPixelChannel(shade_image,channel,ClampToQuantum(shade),q);
cristy1e7aa312011-09-10 20:01:36 +00003475 continue;
3476 }
cristy0beccfa2011-09-25 20:47:53 +00003477 SetPixelChannel(shade_image,channel,ClampToQuantum(QuantumScale*shade*
3478 center[i]),q);
cristy1e7aa312011-09-10 20:01:36 +00003479 }
3480 pre+=GetPixelChannels(image);
3481 center+=GetPixelChannels(image);
3482 post+=GetPixelChannels(image);
cristyed231572011-07-14 02:18:59 +00003483 q+=GetPixelChannels(shade_image);
cristy3ed852e2009-09-05 21:47:34 +00003484 }
3485 if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
3486 status=MagickFalse;
3487 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3488 {
3489 MagickBooleanType
3490 proceed;
3491
cristyb5d5f722009-11-04 03:03:49 +00003492#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003493 #pragma omp critical (MagickCore_ShadeImage)
3494#endif
3495 proceed=SetImageProgress(image,ShadeImageTag,progress++,image->rows);
3496 if (proceed == MagickFalse)
3497 status=MagickFalse;
3498 }
3499 }
3500 shade_view=DestroyCacheView(shade_view);
3501 image_view=DestroyCacheView(image_view);
3502 if (status == MagickFalse)
3503 shade_image=DestroyImage(shade_image);
3504 return(shade_image);
3505}
3506
3507/*
3508%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3509% %
3510% %
3511% %
3512% S h a r p e n I m a g e %
3513% %
3514% %
3515% %
3516%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3517%
3518% SharpenImage() sharpens the image. We convolve the image with a Gaussian
3519% operator of the given radius and standard deviation (sigma). For
3520% reasonable results, radius should be larger than sigma. Use a radius of 0
3521% and SharpenImage() selects a suitable radius for you.
3522%
3523% Using a separable kernel would be faster, but the negative weights cancel
3524% out on the corners of the kernel producing often undesirable ringing in the
3525% filtered result; this can be avoided by using a 2D gaussian shaped image
3526% sharpening kernel instead.
3527%
3528% The format of the SharpenImage method is:
3529%
3530% Image *SharpenImage(const Image *image,const double radius,
cristy05c0c9a2011-09-05 23:16:13 +00003531% const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003532%
3533% A description of each parameter follows:
3534%
3535% o image: the image.
3536%
cristy3ed852e2009-09-05 21:47:34 +00003537% o radius: the radius of the Gaussian, in pixels, not counting the center
3538% pixel.
3539%
3540% o sigma: the standard deviation of the Laplacian, in pixels.
3541%
cristy05c0c9a2011-09-05 23:16:13 +00003542% o bias: bias.
3543%
cristy3ed852e2009-09-05 21:47:34 +00003544% o exception: return any errors or warnings in this structure.
3545%
3546*/
cristy3ed852e2009-09-05 21:47:34 +00003547MagickExport Image *SharpenImage(const Image *image,const double radius,
cristy05c0c9a2011-09-05 23:16:13 +00003548 const double sigma,const double bias,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003549{
cristy3ed852e2009-09-05 21:47:34 +00003550 double
cristy47e00502009-12-17 19:19:57 +00003551 normalize;
cristy3ed852e2009-09-05 21:47:34 +00003552
3553 Image
3554 *sharp_image;
3555
cristy41cbe682011-07-15 19:12:37 +00003556 KernelInfo
3557 *kernel_info;
3558
cristybb503372010-05-27 20:51:26 +00003559 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003560 i;
3561
cristybb503372010-05-27 20:51:26 +00003562 size_t
cristy3ed852e2009-09-05 21:47:34 +00003563 width;
3564
cristy117ff172010-08-15 21:35:32 +00003565 ssize_t
3566 j,
3567 u,
3568 v;
3569
cristy3ed852e2009-09-05 21:47:34 +00003570 assert(image != (const Image *) NULL);
3571 assert(image->signature == MagickSignature);
3572 if (image->debug != MagickFalse)
3573 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3574 assert(exception != (ExceptionInfo *) NULL);
3575 assert(exception->signature == MagickSignature);
3576 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00003577 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00003578 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003579 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00003580 (void) ResetMagickMemory(kernel_info,0,sizeof(*kernel_info));
3581 kernel_info->width=width;
3582 kernel_info->height=width;
anthony736a1602011-10-06 12:38:17 +00003583 kernel_info->bias=bias; /* FUTURE: user bias - non-sensical! */
cristy41cbe682011-07-15 19:12:37 +00003584 kernel_info->signature=MagickSignature;
cristya96f2492011-12-14 18:25:41 +00003585 kernel_info->values=(MagickRealType *) AcquireAlignedMemory(
3586 kernel_info->width,kernel_info->width*sizeof(*kernel_info->values));
3587 if (kernel_info->values == (MagickRealType *) NULL)
cristy41cbe682011-07-15 19:12:37 +00003588 {
3589 kernel_info=DestroyKernelInfo(kernel_info);
3590 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3591 }
cristy3ed852e2009-09-05 21:47:34 +00003592 normalize=0.0;
cristy41cbe682011-07-15 19:12:37 +00003593 j=(ssize_t) kernel_info->width/2;
cristy47e00502009-12-17 19:19:57 +00003594 i=0;
3595 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00003596 {
cristy47e00502009-12-17 19:19:57 +00003597 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00003598 {
cristy41cbe682011-07-15 19:12:37 +00003599 kernel_info->values[i]=(double) (-exp(-((double) u*u+v*v)/(2.0*
3600 MagickSigma*MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
3601 normalize+=kernel_info->values[i];
cristy3ed852e2009-09-05 21:47:34 +00003602 i++;
3603 }
3604 }
cristy41cbe682011-07-15 19:12:37 +00003605 kernel_info->values[i/2]=(double) ((-2.0)*normalize);
cristy5e6be1e2011-07-16 01:23:39 +00003606 sharp_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00003607 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00003608 return(sharp_image);
3609}
3610
3611/*
3612%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3613% %
3614% %
3615% %
3616% S p r e a d I m a g e %
3617% %
3618% %
3619% %
3620%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3621%
3622% SpreadImage() is a special effects method that randomly displaces each
3623% pixel in a block defined by the radius parameter.
3624%
3625% The format of the SpreadImage method is:
3626%
3627% Image *SpreadImage(const Image *image,const double radius,
cristy5c4e2582011-09-11 19:21:03 +00003628% const PixelInterpolateMethod method,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003629%
3630% A description of each parameter follows:
3631%
3632% o image: the image.
3633%
cristy5c4e2582011-09-11 19:21:03 +00003634% o radius: choose a random pixel in a neighborhood of this extent.
3635%
3636% o method: the pixel interpolation method.
cristy3ed852e2009-09-05 21:47:34 +00003637%
3638% o exception: return any errors or warnings in this structure.
3639%
3640*/
3641MagickExport Image *SpreadImage(const Image *image,const double radius,
cristy5c4e2582011-09-11 19:21:03 +00003642 const PixelInterpolateMethod method,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003643{
3644#define SpreadImageTag "Spread/Image"
3645
cristyfa112112010-01-04 17:48:07 +00003646 CacheView
cristy9f7e7cb2011-03-26 00:49:57 +00003647 *image_view,
3648 *spread_view;
cristyfa112112010-01-04 17:48:07 +00003649
cristy3ed852e2009-09-05 21:47:34 +00003650 Image
3651 *spread_image;
3652
cristy3ed852e2009-09-05 21:47:34 +00003653 MagickBooleanType
3654 status;
3655
cristybb503372010-05-27 20:51:26 +00003656 MagickOffsetType
3657 progress;
3658
cristy3ed852e2009-09-05 21:47:34 +00003659 RandomInfo
cristyfa112112010-01-04 17:48:07 +00003660 **restrict random_info;
cristy3ed852e2009-09-05 21:47:34 +00003661
cristybb503372010-05-27 20:51:26 +00003662 size_t
cristy3ed852e2009-09-05 21:47:34 +00003663 width;
3664
cristybb503372010-05-27 20:51:26 +00003665 ssize_t
3666 y;
3667
cristy3ed852e2009-09-05 21:47:34 +00003668 /*
3669 Initialize spread image attributes.
3670 */
3671 assert(image != (Image *) NULL);
3672 assert(image->signature == MagickSignature);
3673 if (image->debug != MagickFalse)
3674 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3675 assert(exception != (ExceptionInfo *) NULL);
3676 assert(exception->signature == MagickSignature);
3677 spread_image=CloneImage(image,image->columns,image->rows,MagickTrue,
3678 exception);
3679 if (spread_image == (Image *) NULL)
3680 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00003681 if (SetImageStorageClass(spread_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003682 {
cristy3ed852e2009-09-05 21:47:34 +00003683 spread_image=DestroyImage(spread_image);
3684 return((Image *) NULL);
3685 }
3686 /*
3687 Spread image.
3688 */
3689 status=MagickTrue;
3690 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00003691 width=GetOptimalKernelWidth1D(radius,0.5);
cristy3ed852e2009-09-05 21:47:34 +00003692 random_info=AcquireRandomInfoThreadSet();
cristy9f7e7cb2011-03-26 00:49:57 +00003693 image_view=AcquireCacheView(image);
3694 spread_view=AcquireCacheView(spread_image);
cristyb557a152011-02-22 12:14:30 +00003695#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy13e98362012-01-12 02:08:08 +00003696 #pragma omp parallel for schedule(static,1) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003697#endif
cristybe82ad52011-09-06 01:17:20 +00003698 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003699 {
cristy5c9e6f22010-09-17 17:31:01 +00003700 const int
3701 id = GetOpenMPThreadId();
cristy6ebe97c2010-07-03 01:17:28 +00003702
cristybe82ad52011-09-06 01:17:20 +00003703 register const Quantum
3704 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003705
cristy4c08aed2011-07-01 19:47:50 +00003706 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003707 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003708
cristy117ff172010-08-15 21:35:32 +00003709 register ssize_t
3710 x;
3711
cristy3ed852e2009-09-05 21:47:34 +00003712 if (status == MagickFalse)
3713 continue;
cristybe82ad52011-09-06 01:17:20 +00003714 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy9f7e7cb2011-03-26 00:49:57 +00003715 q=QueueCacheViewAuthenticPixels(spread_view,0,y,spread_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +00003716 exception);
cristybe82ad52011-09-06 01:17:20 +00003717 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003718 {
3719 status=MagickFalse;
3720 continue;
3721 }
cristybe82ad52011-09-06 01:17:20 +00003722 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003723 {
cristybe82ad52011-09-06 01:17:20 +00003724 PointInfo
3725 point;
3726
cristybe82ad52011-09-06 01:17:20 +00003727 point.x=GetPseudoRandomValue(random_info[id]);
3728 point.y=GetPseudoRandomValue(random_info[id]);
cristy5c4e2582011-09-11 19:21:03 +00003729 status=InterpolatePixelChannels(image,image_view,spread_image,method,
3730 (double) x+width*point.x-0.5,(double) y+width*point.y-0.5,q,exception);
cristyed231572011-07-14 02:18:59 +00003731 q+=GetPixelChannels(spread_image);
cristy3ed852e2009-09-05 21:47:34 +00003732 }
cristy9f7e7cb2011-03-26 00:49:57 +00003733 if (SyncCacheViewAuthenticPixels(spread_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003734 status=MagickFalse;
3735 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3736 {
3737 MagickBooleanType
3738 proceed;
3739
cristyb557a152011-02-22 12:14:30 +00003740#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003741 #pragma omp critical (MagickCore_SpreadImage)
3742#endif
3743 proceed=SetImageProgress(image,SpreadImageTag,progress++,image->rows);
3744 if (proceed == MagickFalse)
3745 status=MagickFalse;
3746 }
3747 }
cristy9f7e7cb2011-03-26 00:49:57 +00003748 spread_view=DestroyCacheView(spread_view);
cristy3ed852e2009-09-05 21:47:34 +00003749 image_view=DestroyCacheView(image_view);
3750 random_info=DestroyRandomInfoThreadSet(random_info);
cristy3ed852e2009-09-05 21:47:34 +00003751 return(spread_image);
3752}
3753
3754/*
3755%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3756% %
3757% %
3758% %
3759% U n s h a r p M a s k I m a g e %
3760% %
3761% %
3762% %
3763%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3764%
3765% UnsharpMaskImage() sharpens one or more image channels. We convolve the
3766% image with a Gaussian operator of the given radius and standard deviation
3767% (sigma). For reasonable results, radius should be larger than sigma. Use a
3768% radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
3769%
3770% The format of the UnsharpMaskImage method is:
3771%
3772% Image *UnsharpMaskImage(const Image *image,const double radius,
3773% const double sigma,const double amount,const double threshold,
3774% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003775%
3776% A description of each parameter follows:
3777%
3778% o image: the image.
3779%
cristy3ed852e2009-09-05 21:47:34 +00003780% o radius: the radius of the Gaussian, in pixels, not counting the center
3781% pixel.
3782%
3783% o sigma: the standard deviation of the Gaussian, in pixels.
3784%
3785% o amount: the percentage of the difference between the original and the
3786% blur image that is added back into the original.
3787%
3788% o threshold: the threshold in pixels needed to apply the diffence amount.
3789%
3790% o exception: return any errors or warnings in this structure.
3791%
3792*/
cristy31bb6272011-11-20 23:57:25 +00003793MagickExport Image *UnsharpMaskImage(const Image *image,const double radius,
3794 const double sigma,const double amount,const double threshold,
3795 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003796{
3797#define SharpenImageTag "Sharpen/Image"
3798
cristyc4c8d132010-01-07 01:58:38 +00003799 CacheView
3800 *image_view,
3801 *unsharp_view;
3802
cristy3ed852e2009-09-05 21:47:34 +00003803 Image
3804 *unsharp_image;
3805
cristy3ed852e2009-09-05 21:47:34 +00003806 MagickBooleanType
3807 status;
3808
cristybb503372010-05-27 20:51:26 +00003809 MagickOffsetType
3810 progress;
3811
cristy3ed852e2009-09-05 21:47:34 +00003812 MagickRealType
3813 quantum_threshold;
3814
cristybb503372010-05-27 20:51:26 +00003815 ssize_t
3816 y;
3817
cristy3ed852e2009-09-05 21:47:34 +00003818 assert(image != (const Image *) NULL);
3819 assert(image->signature == MagickSignature);
3820 if (image->debug != MagickFalse)
3821 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3822 assert(exception != (ExceptionInfo *) NULL);
anthony736a1602011-10-06 12:38:17 +00003823
3824
3825 /* FUTURE: use of bias on sharpen is non-sensical */
cristy05c0c9a2011-09-05 23:16:13 +00003826 unsharp_image=BlurImage(image,radius,sigma,image->bias,exception);
anthony736a1602011-10-06 12:38:17 +00003827
cristy3ed852e2009-09-05 21:47:34 +00003828 if (unsharp_image == (Image *) NULL)
3829 return((Image *) NULL);
3830 quantum_threshold=(MagickRealType) QuantumRange*threshold;
3831 /*
3832 Unsharp-mask image.
3833 */
3834 status=MagickTrue;
3835 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00003836 image_view=AcquireCacheView(image);
3837 unsharp_view=AcquireCacheView(unsharp_image);
cristyb5d5f722009-11-04 03:03:49 +00003838#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +00003839 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003840#endif
cristybb503372010-05-27 20:51:26 +00003841 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003842 {
cristy4c08aed2011-07-01 19:47:50 +00003843 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003844 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003845
cristy4c08aed2011-07-01 19:47:50 +00003846 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003847 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003848
cristy117ff172010-08-15 21:35:32 +00003849 register ssize_t
3850 x;
3851
cristy3ed852e2009-09-05 21:47:34 +00003852 if (status == MagickFalse)
3853 continue;
3854 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
3855 q=GetCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
3856 exception);
cristy4c08aed2011-07-01 19:47:50 +00003857 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003858 {
3859 status=MagickFalse;
3860 continue;
3861 }
cristybb503372010-05-27 20:51:26 +00003862 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003863 {
cristy7f3a0d12011-09-05 23:27:59 +00003864 register ssize_t
3865 i;
3866
3867 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3868 {
3869 MagickRealType
3870 pixel;
3871
3872 PixelChannel
3873 channel;
3874
3875 PixelTrait
3876 traits,
3877 unsharp_traits;
3878
cristye2a912b2011-12-05 20:02:07 +00003879 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +00003880 traits=GetPixelChannelMapTraits(image,channel);
cristy7f3a0d12011-09-05 23:27:59 +00003881 unsharp_traits=GetPixelChannelMapTraits(unsharp_image,channel);
3882 if ((traits == UndefinedPixelTrait) ||
3883 (unsharp_traits == UndefinedPixelTrait))
3884 continue;
3885 if ((unsharp_traits & CopyPixelTrait) != 0)
3886 {
cristy0beccfa2011-09-25 20:47:53 +00003887 SetPixelChannel(unsharp_image,channel,p[i],q);
cristy7f3a0d12011-09-05 23:27:59 +00003888 continue;
3889 }
cristy0beccfa2011-09-25 20:47:53 +00003890 pixel=p[i]-(MagickRealType) GetPixelChannel(unsharp_image,channel,q);
cristy7f3a0d12011-09-05 23:27:59 +00003891 if (fabs(2.0*pixel) < quantum_threshold)
3892 pixel=(MagickRealType) p[i];
3893 else
3894 pixel=(MagickRealType) p[i]+amount*pixel;
cristy0beccfa2011-09-25 20:47:53 +00003895 SetPixelChannel(unsharp_image,channel,ClampToQuantum(pixel),q);
cristy7f3a0d12011-09-05 23:27:59 +00003896 }
cristyed231572011-07-14 02:18:59 +00003897 p+=GetPixelChannels(image);
3898 q+=GetPixelChannels(unsharp_image);
cristy3ed852e2009-09-05 21:47:34 +00003899 }
3900 if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
3901 status=MagickFalse;
3902 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3903 {
3904 MagickBooleanType
3905 proceed;
3906
cristyb5d5f722009-11-04 03:03:49 +00003907#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00003908 #pragma omp critical (MagickCore_UnsharpMaskImage)
cristy3ed852e2009-09-05 21:47:34 +00003909#endif
3910 proceed=SetImageProgress(image,SharpenImageTag,progress++,image->rows);
3911 if (proceed == MagickFalse)
3912 status=MagickFalse;
3913 }
3914 }
3915 unsharp_image->type=image->type;
3916 unsharp_view=DestroyCacheView(unsharp_view);
3917 image_view=DestroyCacheView(image_view);
3918 if (status == MagickFalse)
3919 unsharp_image=DestroyImage(unsharp_image);
3920 return(unsharp_image);
3921}