blob: ba2ba28b99cba4297f2220b7b7aa725f1135c567 [file] [log] [blame]
cristy3ed852e2009-09-05 21:47:34 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% EEEEE FFFFF FFFFF EEEEE CCCC TTTTT %
7% E F F E C T %
8% EEE FFF FFF EEE C T %
9% E F F E C T %
10% EEEEE F F EEEEE CCCC T %
11% %
12% %
13% MagickCore Image Effects Methods %
14% %
15% Software Design %
16% John Cristy %
17% October 1996 %
18% %
19% %
cristy7e41fe82010-12-04 23:12:08 +000020% Copyright 1999-2011 ImageMagick Studio LLC, a non-profit organization %
cristy3ed852e2009-09-05 21:47:34 +000021% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
26% http://www.imagemagick.org/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
36%
37%
38*/
39
40/*
41 Include declarations.
42*/
43#include "magick/studio.h"
cristyd43a46b2010-01-21 02:13:41 +000044#include "magick/accelerate.h"
cristy3ed852e2009-09-05 21:47:34 +000045#include "magick/blob.h"
46#include "magick/cache-view.h"
47#include "magick/color.h"
48#include "magick/color-private.h"
49#include "magick/colorspace.h"
50#include "magick/constitute.h"
51#include "magick/decorate.h"
52#include "magick/draw.h"
53#include "magick/enhance.h"
54#include "magick/exception.h"
55#include "magick/exception-private.h"
56#include "magick/effect.h"
57#include "magick/fx.h"
58#include "magick/gem.h"
59#include "magick/geometry.h"
60#include "magick/image-private.h"
61#include "magick/list.h"
62#include "magick/log.h"
63#include "magick/memory_.h"
64#include "magick/monitor.h"
65#include "magick/monitor-private.h"
66#include "magick/montage.h"
cristy6771f1e2010-03-05 19:43:39 +000067#include "magick/morphology.h"
cristy3ed852e2009-09-05 21:47:34 +000068#include "magick/paint.h"
69#include "magick/pixel-private.h"
70#include "magick/property.h"
71#include "magick/quantize.h"
72#include "magick/quantum.h"
73#include "magick/random_.h"
74#include "magick/random-private.h"
75#include "magick/resample.h"
76#include "magick/resample-private.h"
77#include "magick/resize.h"
78#include "magick/resource_.h"
79#include "magick/segment.h"
80#include "magick/shear.h"
81#include "magick/signature-private.h"
82#include "magick/string_.h"
83#include "magick/thread-private.h"
84#include "magick/transform.h"
85#include "magick/threshold.h"
86
87/*
88%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
89% %
90% %
91% %
92% A d a p t i v e B l u r I m a g e %
93% %
94% %
95% %
96%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
97%
98% AdaptiveBlurImage() adaptively blurs the image by blurring less
99% intensely near image edges and more intensely far from edges. We blur the
100% image with a Gaussian operator of the given radius and standard deviation
101% (sigma). For reasonable results, radius should be larger than sigma. Use a
102% radius of 0 and AdaptiveBlurImage() selects a suitable radius for you.
103%
104% The format of the AdaptiveBlurImage method is:
105%
106% Image *AdaptiveBlurImage(const Image *image,const double radius,
107% const double sigma,ExceptionInfo *exception)
108% Image *AdaptiveBlurImageChannel(const Image *image,
109% const ChannelType channel,double radius,const double sigma,
110% ExceptionInfo *exception)
111%
112% A description of each parameter follows:
113%
114% o image: the image.
115%
116% o channel: the channel type.
117%
118% o radius: the radius of the Gaussian, in pixels, not counting the center
119% pixel.
120%
121% o sigma: the standard deviation of the Laplacian, in pixels.
122%
123% o exception: return any errors or warnings in this structure.
124%
125*/
126
127MagickExport Image *AdaptiveBlurImage(const Image *image,const double radius,
128 const double sigma,ExceptionInfo *exception)
129{
130 Image
131 *blur_image;
132
133 blur_image=AdaptiveBlurImageChannel(image,DefaultChannels,radius,sigma,
134 exception);
135 return(blur_image);
136}
137
138MagickExport Image *AdaptiveBlurImageChannel(const Image *image,
139 const ChannelType channel,const double radius,const double sigma,
140 ExceptionInfo *exception)
141{
142#define AdaptiveBlurImageTag "Convolve/Image"
143#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
144
cristyc4c8d132010-01-07 01:58:38 +0000145 CacheView
146 *blur_view,
147 *edge_view,
148 *image_view;
149
cristy3ed852e2009-09-05 21:47:34 +0000150 double
cristy47e00502009-12-17 19:19:57 +0000151 **kernel,
152 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000153
154 Image
155 *blur_image,
156 *edge_image,
157 *gaussian_image;
158
cristy3ed852e2009-09-05 21:47:34 +0000159 MagickBooleanType
160 status;
161
cristybb503372010-05-27 20:51:26 +0000162 MagickOffsetType
163 progress;
164
cristy3ed852e2009-09-05 21:47:34 +0000165 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +0000166 bias;
cristy3ed852e2009-09-05 21:47:34 +0000167
cristybb503372010-05-27 20:51:26 +0000168 register ssize_t
cristy47e00502009-12-17 19:19:57 +0000169 i;
cristy3ed852e2009-09-05 21:47:34 +0000170
cristybb503372010-05-27 20:51:26 +0000171 size_t
cristy3ed852e2009-09-05 21:47:34 +0000172 width;
173
cristybb503372010-05-27 20:51:26 +0000174 ssize_t
175 j,
176 k,
177 u,
178 v,
179 y;
180
cristy3ed852e2009-09-05 21:47:34 +0000181 assert(image != (const Image *) NULL);
182 assert(image->signature == MagickSignature);
183 if (image->debug != MagickFalse)
184 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
185 assert(exception != (ExceptionInfo *) NULL);
186 assert(exception->signature == MagickSignature);
187 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
188 if (blur_image == (Image *) NULL)
189 return((Image *) NULL);
190 if (fabs(sigma) <= MagickEpsilon)
191 return(blur_image);
192 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
193 {
194 InheritException(exception,&blur_image->exception);
195 blur_image=DestroyImage(blur_image);
196 return((Image *) NULL);
197 }
198 /*
199 Edge detect the image brighness channel, level, blur, and level again.
200 */
201 edge_image=EdgeImage(image,radius,exception);
202 if (edge_image == (Image *) NULL)
203 {
204 blur_image=DestroyImage(blur_image);
205 return((Image *) NULL);
206 }
207 (void) LevelImage(edge_image,"20%,95%");
208 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,exception);
209 if (gaussian_image != (Image *) NULL)
210 {
211 edge_image=DestroyImage(edge_image);
212 edge_image=gaussian_image;
213 }
214 (void) LevelImage(edge_image,"10%,95%");
215 /*
216 Create a set of kernels from maximum (radius,sigma) to minimum.
217 */
218 width=GetOptimalKernelWidth2D(radius,sigma);
219 kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
220 if (kernel == (double **) NULL)
221 {
222 edge_image=DestroyImage(edge_image);
223 blur_image=DestroyImage(blur_image);
224 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
225 }
226 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
cristybb503372010-05-27 20:51:26 +0000227 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000228 {
229 kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)*
230 sizeof(**kernel));
231 if (kernel[i] == (double *) NULL)
232 break;
cristy47e00502009-12-17 19:19:57 +0000233 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000234 j=(ssize_t) (width-i)/2;
cristy47e00502009-12-17 19:19:57 +0000235 k=0;
236 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000237 {
cristy47e00502009-12-17 19:19:57 +0000238 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000239 {
cristy4205a3c2010-09-12 20:19:59 +0000240 kernel[i][k]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
241 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +0000242 normalize+=kernel[i][k];
243 k++;
cristy3ed852e2009-09-05 21:47:34 +0000244 }
245 }
cristy3ed852e2009-09-05 21:47:34 +0000246 if (fabs(normalize) <= MagickEpsilon)
247 normalize=1.0;
248 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000249 for (k=0; k < (j*j); k++)
250 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000251 }
cristybb503372010-05-27 20:51:26 +0000252 if (i < (ssize_t) width)
cristy3ed852e2009-09-05 21:47:34 +0000253 {
254 for (i-=2; i >= 0; i-=2)
255 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
256 kernel=(double **) RelinquishMagickMemory(kernel);
257 edge_image=DestroyImage(edge_image);
258 blur_image=DestroyImage(blur_image);
259 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
260 }
261 /*
262 Adaptively blur image.
263 */
264 status=MagickTrue;
265 progress=0;
cristyddd82202009-11-03 20:14:50 +0000266 GetMagickPixelPacket(image,&bias);
267 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000268 image_view=AcquireCacheView(image);
269 edge_view=AcquireCacheView(edge_image);
270 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000271#if defined(MAGICKCORE_OPENMP_SUPPORT)
272 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000273#endif
cristybb503372010-05-27 20:51:26 +0000274 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000275 {
276 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000277 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000278
279 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000280 *restrict p,
281 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000282
283 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000284 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000285
cristy3ed852e2009-09-05 21:47:34 +0000286 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000287 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000288
cristy117ff172010-08-15 21:35:32 +0000289 register ssize_t
290 x;
291
cristy3ed852e2009-09-05 21:47:34 +0000292 if (status == MagickFalse)
293 continue;
294 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
295 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
296 exception);
297 if ((r == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
298 {
299 status=MagickFalse;
300 continue;
301 }
302 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
cristybb503372010-05-27 20:51:26 +0000303 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000304 {
305 MagickPixelPacket
306 pixel;
307
308 MagickRealType
309 alpha,
310 gamma;
311
312 register const double
cristyc47d1f82009-11-26 01:44:43 +0000313 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000314
cristybb503372010-05-27 20:51:26 +0000315 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000316 i,
317 u,
318 v;
319
320 gamma=0.0;
cristybb503372010-05-27 20:51:26 +0000321 i=(ssize_t) ceil((double) width*QuantumScale*PixelIntensity(r)-0.5);
cristy3ed852e2009-09-05 21:47:34 +0000322 if (i < 0)
323 i=0;
324 else
cristybb503372010-05-27 20:51:26 +0000325 if (i > (ssize_t) width)
326 i=(ssize_t) width;
cristy3ed852e2009-09-05 21:47:34 +0000327 if ((i & 0x01) != 0)
328 i--;
cristya21afde2010-07-02 00:45:40 +0000329 p=GetCacheViewVirtualPixels(image_view,x-((ssize_t) (width-i)/2L),y-
330 (ssize_t) ((width-i)/2L),width-i,width-i,exception);
cristy3ed852e2009-09-05 21:47:34 +0000331 if (p == (const PixelPacket *) NULL)
332 break;
333 indexes=GetCacheViewVirtualIndexQueue(image_view);
cristyddd82202009-11-03 20:14:50 +0000334 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +0000335 k=kernel[i];
cristybb503372010-05-27 20:51:26 +0000336 for (v=0; v < (ssize_t) (width-i); v++)
cristy3ed852e2009-09-05 21:47:34 +0000337 {
cristybb503372010-05-27 20:51:26 +0000338 for (u=0; u < (ssize_t) (width-i); u++)
cristy3ed852e2009-09-05 21:47:34 +0000339 {
340 alpha=1.0;
341 if (((channel & OpacityChannel) != 0) &&
342 (image->matte != MagickFalse))
cristy46f08202010-01-10 04:04:21 +0000343 alpha=(MagickRealType) (QuantumScale*GetAlphaPixelComponent(p));
cristy3ed852e2009-09-05 21:47:34 +0000344 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000345 pixel.red+=(*k)*alpha*GetRedPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000346 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000347 pixel.green+=(*k)*alpha*GetGreenPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000348 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000349 pixel.blue+=(*k)*alpha*GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000350 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000351 pixel.opacity+=(*k)*GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000352 if (((channel & IndexChannel) != 0) &&
353 (image->colorspace == CMYKColorspace))
354 pixel.index+=(*k)*alpha*indexes[x+(width-i)*v+u];
355 gamma+=(*k)*alpha;
356 k++;
357 p++;
358 }
359 }
360 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
361 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000362 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000363 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000364 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000365 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000366 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000367 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000368 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000369 if (((channel & IndexChannel) != 0) &&
370 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +0000371 blur_indexes[x]=ClampToQuantum(gamma*GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000372 q++;
373 r++;
374 }
375 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
376 status=MagickFalse;
377 if (image->progress_monitor != (MagickProgressMonitor) NULL)
378 {
379 MagickBooleanType
380 proceed;
381
cristyb5d5f722009-11-04 03:03:49 +0000382#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000383 #pragma omp critical (MagickCore_AdaptiveBlurImageChannel)
384#endif
385 proceed=SetImageProgress(image,AdaptiveBlurImageTag,progress++,
386 image->rows);
387 if (proceed == MagickFalse)
388 status=MagickFalse;
389 }
390 }
391 blur_image->type=image->type;
392 blur_view=DestroyCacheView(blur_view);
393 edge_view=DestroyCacheView(edge_view);
394 image_view=DestroyCacheView(image_view);
395 edge_image=DestroyImage(edge_image);
cristybb503372010-05-27 20:51:26 +0000396 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000397 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
398 kernel=(double **) RelinquishMagickMemory(kernel);
399 if (status == MagickFalse)
400 blur_image=DestroyImage(blur_image);
401 return(blur_image);
402}
403
404/*
405%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
406% %
407% %
408% %
409% A d a p t i v e S h a r p e n I m a g e %
410% %
411% %
412% %
413%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
414%
415% AdaptiveSharpenImage() adaptively sharpens the image by sharpening more
416% intensely near image edges and less intensely far from edges. We sharpen the
417% image with a Gaussian operator of the given radius and standard deviation
418% (sigma). For reasonable results, radius should be larger than sigma. Use a
419% radius of 0 and AdaptiveSharpenImage() selects a suitable radius for you.
420%
421% The format of the AdaptiveSharpenImage method is:
422%
423% Image *AdaptiveSharpenImage(const Image *image,const double radius,
424% const double sigma,ExceptionInfo *exception)
425% Image *AdaptiveSharpenImageChannel(const Image *image,
426% const ChannelType channel,double radius,const double sigma,
427% ExceptionInfo *exception)
428%
429% A description of each parameter follows:
430%
431% o image: the image.
432%
433% o channel: the channel type.
434%
435% o radius: the radius of the Gaussian, in pixels, not counting the center
436% pixel.
437%
438% o sigma: the standard deviation of the Laplacian, in pixels.
439%
440% o exception: return any errors or warnings in this structure.
441%
442*/
443
444MagickExport Image *AdaptiveSharpenImage(const Image *image,const double radius,
445 const double sigma,ExceptionInfo *exception)
446{
447 Image
448 *sharp_image;
449
450 sharp_image=AdaptiveSharpenImageChannel(image,DefaultChannels,radius,sigma,
451 exception);
452 return(sharp_image);
453}
454
455MagickExport Image *AdaptiveSharpenImageChannel(const Image *image,
456 const ChannelType channel,const double radius,const double sigma,
457 ExceptionInfo *exception)
458{
459#define AdaptiveSharpenImageTag "Convolve/Image"
460#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
461
cristyc4c8d132010-01-07 01:58:38 +0000462 CacheView
463 *sharp_view,
464 *edge_view,
465 *image_view;
466
cristy3ed852e2009-09-05 21:47:34 +0000467 double
cristy47e00502009-12-17 19:19:57 +0000468 **kernel,
469 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000470
471 Image
472 *sharp_image,
473 *edge_image,
474 *gaussian_image;
475
cristy3ed852e2009-09-05 21:47:34 +0000476 MagickBooleanType
477 status;
478
cristybb503372010-05-27 20:51:26 +0000479 MagickOffsetType
480 progress;
481
cristy3ed852e2009-09-05 21:47:34 +0000482 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +0000483 bias;
cristy3ed852e2009-09-05 21:47:34 +0000484
cristybb503372010-05-27 20:51:26 +0000485 register ssize_t
cristy47e00502009-12-17 19:19:57 +0000486 i;
cristy3ed852e2009-09-05 21:47:34 +0000487
cristybb503372010-05-27 20:51:26 +0000488 size_t
cristy3ed852e2009-09-05 21:47:34 +0000489 width;
490
cristybb503372010-05-27 20:51:26 +0000491 ssize_t
492 j,
493 k,
494 u,
495 v,
496 y;
497
cristy3ed852e2009-09-05 21:47:34 +0000498 assert(image != (const Image *) NULL);
499 assert(image->signature == MagickSignature);
500 if (image->debug != MagickFalse)
501 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
502 assert(exception != (ExceptionInfo *) NULL);
503 assert(exception->signature == MagickSignature);
504 sharp_image=CloneImage(image,0,0,MagickTrue,exception);
505 if (sharp_image == (Image *) NULL)
506 return((Image *) NULL);
507 if (fabs(sigma) <= MagickEpsilon)
508 return(sharp_image);
509 if (SetImageStorageClass(sharp_image,DirectClass) == MagickFalse)
510 {
511 InheritException(exception,&sharp_image->exception);
512 sharp_image=DestroyImage(sharp_image);
513 return((Image *) NULL);
514 }
515 /*
516 Edge detect the image brighness channel, level, sharp, and level again.
517 */
518 edge_image=EdgeImage(image,radius,exception);
519 if (edge_image == (Image *) NULL)
520 {
521 sharp_image=DestroyImage(sharp_image);
522 return((Image *) NULL);
523 }
524 (void) LevelImage(edge_image,"20%,95%");
525 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,exception);
526 if (gaussian_image != (Image *) NULL)
527 {
528 edge_image=DestroyImage(edge_image);
529 edge_image=gaussian_image;
530 }
531 (void) LevelImage(edge_image,"10%,95%");
532 /*
533 Create a set of kernels from maximum (radius,sigma) to minimum.
534 */
535 width=GetOptimalKernelWidth2D(radius,sigma);
536 kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
537 if (kernel == (double **) NULL)
538 {
539 edge_image=DestroyImage(edge_image);
540 sharp_image=DestroyImage(sharp_image);
541 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
542 }
543 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
cristybb503372010-05-27 20:51:26 +0000544 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000545 {
546 kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)*
547 sizeof(**kernel));
548 if (kernel[i] == (double *) NULL)
549 break;
cristy47e00502009-12-17 19:19:57 +0000550 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000551 j=(ssize_t) (width-i)/2;
cristy47e00502009-12-17 19:19:57 +0000552 k=0;
553 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000554 {
cristy47e00502009-12-17 19:19:57 +0000555 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000556 {
cristy4205a3c2010-09-12 20:19:59 +0000557 kernel[i][k]=(double) (-exp(-((double) u*u+v*v)/(2.0*MagickSigma*
558 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +0000559 normalize+=kernel[i][k];
560 k++;
cristy3ed852e2009-09-05 21:47:34 +0000561 }
562 }
cristy3ed852e2009-09-05 21:47:34 +0000563 if (fabs(normalize) <= MagickEpsilon)
564 normalize=1.0;
565 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000566 for (k=0; k < (j*j); k++)
567 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000568 }
cristybb503372010-05-27 20:51:26 +0000569 if (i < (ssize_t) width)
cristy3ed852e2009-09-05 21:47:34 +0000570 {
571 for (i-=2; i >= 0; i-=2)
572 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
573 kernel=(double **) RelinquishMagickMemory(kernel);
574 edge_image=DestroyImage(edge_image);
575 sharp_image=DestroyImage(sharp_image);
576 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
577 }
578 /*
579 Adaptively sharpen image.
580 */
581 status=MagickTrue;
582 progress=0;
cristyddd82202009-11-03 20:14:50 +0000583 GetMagickPixelPacket(image,&bias);
584 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000585 image_view=AcquireCacheView(image);
586 edge_view=AcquireCacheView(edge_image);
587 sharp_view=AcquireCacheView(sharp_image);
cristyb5d5f722009-11-04 03:03:49 +0000588#if defined(MAGICKCORE_OPENMP_SUPPORT)
589 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000590#endif
cristybb503372010-05-27 20:51:26 +0000591 for (y=0; y < (ssize_t) sharp_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000592 {
593 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000594 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000595
596 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000597 *restrict p,
598 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000599
600 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000601 *restrict sharp_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000602
cristy3ed852e2009-09-05 21:47:34 +0000603 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000604 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000605
cristy117ff172010-08-15 21:35:32 +0000606 register ssize_t
607 x;
608
cristy3ed852e2009-09-05 21:47:34 +0000609 if (status == MagickFalse)
610 continue;
611 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
612 q=QueueCacheViewAuthenticPixels(sharp_view,0,y,sharp_image->columns,1,
613 exception);
614 if ((r == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
615 {
616 status=MagickFalse;
617 continue;
618 }
619 sharp_indexes=GetCacheViewAuthenticIndexQueue(sharp_view);
cristybb503372010-05-27 20:51:26 +0000620 for (x=0; x < (ssize_t) sharp_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000621 {
622 MagickPixelPacket
623 pixel;
624
625 MagickRealType
626 alpha,
627 gamma;
628
629 register const double
cristyc47d1f82009-11-26 01:44:43 +0000630 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000631
cristybb503372010-05-27 20:51:26 +0000632 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000633 i,
634 u,
635 v;
636
637 gamma=0.0;
cristybb503372010-05-27 20:51:26 +0000638 i=(ssize_t) ceil((double) width*(QuantumRange-QuantumScale*
cristy1f9ce9f2010-04-28 11:55:12 +0000639 PixelIntensity(r))-0.5);
cristy3ed852e2009-09-05 21:47:34 +0000640 if (i < 0)
641 i=0;
642 else
cristybb503372010-05-27 20:51:26 +0000643 if (i > (ssize_t) width)
644 i=(ssize_t) width;
cristy3ed852e2009-09-05 21:47:34 +0000645 if ((i & 0x01) != 0)
646 i--;
cristy117ff172010-08-15 21:35:32 +0000647 p=GetCacheViewVirtualPixels(image_view,x-((ssize_t) (width-i)/2L),y-
648 (ssize_t) ((width-i)/2L),width-i,width-i,exception);
cristy3ed852e2009-09-05 21:47:34 +0000649 if (p == (const PixelPacket *) NULL)
650 break;
651 indexes=GetCacheViewVirtualIndexQueue(image_view);
652 k=kernel[i];
cristyddd82202009-11-03 20:14:50 +0000653 pixel=bias;
cristybb503372010-05-27 20:51:26 +0000654 for (v=0; v < (ssize_t) (width-i); v++)
cristy3ed852e2009-09-05 21:47:34 +0000655 {
cristybb503372010-05-27 20:51:26 +0000656 for (u=0; u < (ssize_t) (width-i); u++)
cristy3ed852e2009-09-05 21:47:34 +0000657 {
658 alpha=1.0;
659 if (((channel & OpacityChannel) != 0) &&
660 (image->matte != MagickFalse))
cristy46f08202010-01-10 04:04:21 +0000661 alpha=(MagickRealType) (QuantumScale*GetAlphaPixelComponent(p));
cristy3ed852e2009-09-05 21:47:34 +0000662 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000663 pixel.red+=(*k)*alpha*GetRedPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000664 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000665 pixel.green+=(*k)*alpha*GetGreenPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000666 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000667 pixel.blue+=(*k)*alpha*GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000668 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000669 pixel.opacity+=(*k)*GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000670 if (((channel & IndexChannel) != 0) &&
671 (image->colorspace == CMYKColorspace))
672 pixel.index+=(*k)*alpha*indexes[x+(width-i)*v+u];
673 gamma+=(*k)*alpha;
674 k++;
675 p++;
676 }
677 }
678 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
679 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000680 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000681 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000682 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000683 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000684 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000685 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000686 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000687 if (((channel & IndexChannel) != 0) &&
688 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +0000689 sharp_indexes[x]=ClampToQuantum(gamma*GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000690 q++;
691 r++;
692 }
693 if (SyncCacheViewAuthenticPixels(sharp_view,exception) == MagickFalse)
694 status=MagickFalse;
695 if (image->progress_monitor != (MagickProgressMonitor) NULL)
696 {
697 MagickBooleanType
698 proceed;
699
cristyb5d5f722009-11-04 03:03:49 +0000700#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000701 #pragma omp critical (MagickCore_AdaptiveSharpenImageChannel)
702#endif
703 proceed=SetImageProgress(image,AdaptiveSharpenImageTag,progress++,
704 image->rows);
705 if (proceed == MagickFalse)
706 status=MagickFalse;
707 }
708 }
709 sharp_image->type=image->type;
710 sharp_view=DestroyCacheView(sharp_view);
711 edge_view=DestroyCacheView(edge_view);
712 image_view=DestroyCacheView(image_view);
713 edge_image=DestroyImage(edge_image);
cristybb503372010-05-27 20:51:26 +0000714 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000715 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
716 kernel=(double **) RelinquishMagickMemory(kernel);
717 if (status == MagickFalse)
718 sharp_image=DestroyImage(sharp_image);
719 return(sharp_image);
720}
721
722/*
723%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
724% %
725% %
726% %
727% B l u r I m a g e %
728% %
729% %
730% %
731%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
732%
733% BlurImage() blurs an image. We convolve the image with a Gaussian operator
734% of the given radius and standard deviation (sigma). For reasonable results,
735% the radius should be larger than sigma. Use a radius of 0 and BlurImage()
736% selects a suitable radius for you.
737%
738% BlurImage() differs from GaussianBlurImage() in that it uses a separable
739% kernel which is faster but mathematically equivalent to the non-separable
740% kernel.
741%
742% The format of the BlurImage method is:
743%
744% Image *BlurImage(const Image *image,const double radius,
745% const double sigma,ExceptionInfo *exception)
746% Image *BlurImageChannel(const Image *image,const ChannelType channel,
747% const double radius,const double sigma,ExceptionInfo *exception)
748%
749% A description of each parameter follows:
750%
751% o image: the image.
752%
753% o channel: the channel type.
754%
755% o radius: the radius of the Gaussian, in pixels, not counting the center
756% pixel.
757%
758% o sigma: the standard deviation of the Gaussian, in pixels.
759%
760% o exception: return any errors or warnings in this structure.
761%
762*/
763
764MagickExport Image *BlurImage(const Image *image,const double radius,
765 const double sigma,ExceptionInfo *exception)
766{
767 Image
768 *blur_image;
769
770 blur_image=BlurImageChannel(image,DefaultChannels,radius,sigma,exception);
771 return(blur_image);
772}
773
cristybb503372010-05-27 20:51:26 +0000774static double *GetBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +0000775{
cristy3ed852e2009-09-05 21:47:34 +0000776 double
cristy47e00502009-12-17 19:19:57 +0000777 *kernel,
778 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000779
cristy117ff172010-08-15 21:35:32 +0000780 register ssize_t
781 i;
782
cristybb503372010-05-27 20:51:26 +0000783 ssize_t
cristy47e00502009-12-17 19:19:57 +0000784 j,
785 k;
cristy3ed852e2009-09-05 21:47:34 +0000786
cristy3ed852e2009-09-05 21:47:34 +0000787 /*
788 Generate a 1-D convolution kernel.
789 */
790 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
791 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
792 if (kernel == (double *) NULL)
793 return(0);
cristy3ed852e2009-09-05 21:47:34 +0000794 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000795 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +0000796 i=0;
797 for (k=(-j); k <= j; k++)
798 {
cristy4205a3c2010-09-12 20:19:59 +0000799 kernel[i]=(double) (exp(-((double) k*k)/(2.0*MagickSigma*MagickSigma))/
800 (MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +0000801 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +0000802 i++;
803 }
cristybb503372010-05-27 20:51:26 +0000804 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000805 kernel[i]/=normalize;
806 return(kernel);
807}
808
809MagickExport Image *BlurImageChannel(const Image *image,
810 const ChannelType channel,const double radius,const double sigma,
811 ExceptionInfo *exception)
812{
813#define BlurImageTag "Blur/Image"
814
cristyc4c8d132010-01-07 01:58:38 +0000815 CacheView
816 *blur_view,
817 *image_view;
818
cristy3ed852e2009-09-05 21:47:34 +0000819 double
820 *kernel;
821
822 Image
823 *blur_image;
824
cristy3ed852e2009-09-05 21:47:34 +0000825 MagickBooleanType
826 status;
827
cristybb503372010-05-27 20:51:26 +0000828 MagickOffsetType
829 progress;
830
cristy3ed852e2009-09-05 21:47:34 +0000831 MagickPixelPacket
cristy3ed852e2009-09-05 21:47:34 +0000832 bias;
833
cristybb503372010-05-27 20:51:26 +0000834 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000835 i;
836
cristybb503372010-05-27 20:51:26 +0000837 size_t
cristy3ed852e2009-09-05 21:47:34 +0000838 width;
839
cristybb503372010-05-27 20:51:26 +0000840 ssize_t
841 x,
842 y;
843
cristy3ed852e2009-09-05 21:47:34 +0000844 /*
845 Initialize blur image attributes.
846 */
847 assert(image != (Image *) NULL);
848 assert(image->signature == MagickSignature);
849 if (image->debug != MagickFalse)
850 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
851 assert(exception != (ExceptionInfo *) NULL);
852 assert(exception->signature == MagickSignature);
853 blur_image=CloneImage(image,0,0,MagickTrue,exception);
854 if (blur_image == (Image *) NULL)
855 return((Image *) NULL);
856 if (fabs(sigma) <= MagickEpsilon)
857 return(blur_image);
858 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
859 {
860 InheritException(exception,&blur_image->exception);
861 blur_image=DestroyImage(blur_image);
862 return((Image *) NULL);
863 }
864 width=GetOptimalKernelWidth1D(radius,sigma);
865 kernel=GetBlurKernel(width,sigma);
866 if (kernel == (double *) NULL)
867 {
868 blur_image=DestroyImage(blur_image);
869 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
870 }
871 if (image->debug != MagickFalse)
872 {
873 char
874 format[MaxTextExtent],
875 *message;
876
877 register const double
878 *k;
879
880 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +0000881 " BlurImage with %.20g kernel:",(double) width);
cristy3ed852e2009-09-05 21:47:34 +0000882 message=AcquireString("");
883 k=kernel;
cristybb503372010-05-27 20:51:26 +0000884 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000885 {
886 *message='\0';
cristye8c25f92010-06-03 00:53:06 +0000887 (void) FormatMagickString(format,MaxTextExtent,"%.20g: ",(double) i);
cristy3ed852e2009-09-05 21:47:34 +0000888 (void) ConcatenateString(&message,format);
cristye7f51092010-01-17 00:39:37 +0000889 (void) FormatMagickString(format,MaxTextExtent,"%g ",*k++);
cristy3ed852e2009-09-05 21:47:34 +0000890 (void) ConcatenateString(&message,format);
891 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
892 }
893 message=DestroyString(message);
894 }
895 /*
896 Blur rows.
897 */
898 status=MagickTrue;
899 progress=0;
cristyddd82202009-11-03 20:14:50 +0000900 GetMagickPixelPacket(image,&bias);
901 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000902 image_view=AcquireCacheView(image);
903 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000904#if defined(MAGICKCORE_OPENMP_SUPPORT)
905 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000906#endif
cristybb503372010-05-27 20:51:26 +0000907 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000908 {
909 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000910 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000911
912 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000913 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000914
915 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000916 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000917
cristy3ed852e2009-09-05 21:47:34 +0000918 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000919 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000920
cristy117ff172010-08-15 21:35:32 +0000921 register ssize_t
922 x;
923
cristy3ed852e2009-09-05 21:47:34 +0000924 if (status == MagickFalse)
925 continue;
cristy117ff172010-08-15 21:35:32 +0000926 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y,
927 image->columns+width,1,exception);
cristy3ed852e2009-09-05 21:47:34 +0000928 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
929 exception);
930 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
931 {
932 status=MagickFalse;
933 continue;
934 }
935 indexes=GetCacheViewVirtualIndexQueue(image_view);
936 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
cristybb503372010-05-27 20:51:26 +0000937 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000938 {
939 MagickPixelPacket
940 pixel;
941
942 register const double
cristyc47d1f82009-11-26 01:44:43 +0000943 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000944
945 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000946 *restrict kernel_pixels;
cristy3ed852e2009-09-05 21:47:34 +0000947
cristybb503372010-05-27 20:51:26 +0000948 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000949 i;
950
cristyddd82202009-11-03 20:14:50 +0000951 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +0000952 k=kernel;
953 kernel_pixels=p;
954 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
955 {
cristybb503372010-05-27 20:51:26 +0000956 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000957 {
958 pixel.red+=(*k)*kernel_pixels->red;
959 pixel.green+=(*k)*kernel_pixels->green;
960 pixel.blue+=(*k)*kernel_pixels->blue;
961 k++;
962 kernel_pixels++;
963 }
964 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000965 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000966 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000967 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000968 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000969 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000970 if ((channel & OpacityChannel) != 0)
971 {
972 k=kernel;
973 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +0000974 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000975 {
976 pixel.opacity+=(*k)*kernel_pixels->opacity;
977 k++;
978 kernel_pixels++;
979 }
cristyce70c172010-01-07 17:15:30 +0000980 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000981 }
982 if (((channel & IndexChannel) != 0) &&
983 (image->colorspace == CMYKColorspace))
984 {
985 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000986 *restrict kernel_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000987
988 k=kernel;
cristy9d314ff2011-03-09 01:30:28 +0000989 kernel_indexes=indexes;
cristybb503372010-05-27 20:51:26 +0000990 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000991 {
992 pixel.index+=(*k)*(*kernel_indexes);
993 k++;
994 kernel_indexes++;
995 }
cristyce70c172010-01-07 17:15:30 +0000996 blur_indexes[x]=ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +0000997 }
998 }
999 else
1000 {
1001 MagickRealType
1002 alpha,
1003 gamma;
1004
1005 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00001006 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001007 {
cristy46f08202010-01-10 04:04:21 +00001008 alpha=(MagickRealType) (QuantumScale*
1009 GetAlphaPixelComponent(kernel_pixels));
cristy3ed852e2009-09-05 21:47:34 +00001010 pixel.red+=(*k)*alpha*kernel_pixels->red;
1011 pixel.green+=(*k)*alpha*kernel_pixels->green;
1012 pixel.blue+=(*k)*alpha*kernel_pixels->blue;
1013 gamma+=(*k)*alpha;
1014 k++;
1015 kernel_pixels++;
1016 }
1017 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1018 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001019 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001020 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001021 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001022 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001023 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001024 if ((channel & OpacityChannel) != 0)
1025 {
1026 k=kernel;
1027 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001028 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001029 {
1030 pixel.opacity+=(*k)*kernel_pixels->opacity;
1031 k++;
1032 kernel_pixels++;
1033 }
cristyce70c172010-01-07 17:15:30 +00001034 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001035 }
1036 if (((channel & IndexChannel) != 0) &&
1037 (image->colorspace == CMYKColorspace))
1038 {
1039 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001040 *restrict kernel_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001041
1042 k=kernel;
1043 kernel_pixels=p;
cristy9d314ff2011-03-09 01:30:28 +00001044 kernel_indexes=indexes;
cristybb503372010-05-27 20:51:26 +00001045 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001046 {
cristy46f08202010-01-10 04:04:21 +00001047 alpha=(MagickRealType) (QuantumScale*
1048 GetAlphaPixelComponent(kernel_pixels));
cristy3ed852e2009-09-05 21:47:34 +00001049 pixel.index+=(*k)*alpha*(*kernel_indexes);
1050 k++;
1051 kernel_pixels++;
1052 kernel_indexes++;
1053 }
cristy46f08202010-01-10 04:04:21 +00001054 blur_indexes[x]=ClampToQuantum(gamma*
1055 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001056 }
1057 }
cristy9d314ff2011-03-09 01:30:28 +00001058 indexes++;
cristy3ed852e2009-09-05 21:47:34 +00001059 p++;
1060 q++;
1061 }
1062 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1063 status=MagickFalse;
1064 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1065 {
1066 MagickBooleanType
1067 proceed;
1068
cristyb5d5f722009-11-04 03:03:49 +00001069#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001070 #pragma omp critical (MagickCore_BlurImageChannel)
1071#endif
1072 proceed=SetImageProgress(image,BlurImageTag,progress++,blur_image->rows+
1073 blur_image->columns);
1074 if (proceed == MagickFalse)
1075 status=MagickFalse;
1076 }
1077 }
1078 blur_view=DestroyCacheView(blur_view);
1079 image_view=DestroyCacheView(image_view);
1080 /*
1081 Blur columns.
1082 */
1083 image_view=AcquireCacheView(blur_image);
1084 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00001085#if defined(MAGICKCORE_OPENMP_SUPPORT)
1086 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001087#endif
cristybb503372010-05-27 20:51:26 +00001088 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001089 {
1090 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001091 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001092
1093 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001094 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001095
1096 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001097 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001098
cristy3ed852e2009-09-05 21:47:34 +00001099 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001100 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001101
cristy117ff172010-08-15 21:35:32 +00001102 register ssize_t
1103 y;
1104
cristy3ed852e2009-09-05 21:47:34 +00001105 if (status == MagickFalse)
1106 continue;
cristy117ff172010-08-15 21:35:32 +00001107 p=GetCacheViewVirtualPixels(image_view,x,-((ssize_t) width/2L),1,
1108 image->rows+width,exception);
cristy3ed852e2009-09-05 21:47:34 +00001109 q=GetCacheViewAuthenticPixels(blur_view,x,0,1,blur_image->rows,exception);
1110 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1111 {
1112 status=MagickFalse;
1113 continue;
1114 }
1115 indexes=GetCacheViewVirtualIndexQueue(image_view);
1116 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
cristybb503372010-05-27 20:51:26 +00001117 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001118 {
1119 MagickPixelPacket
1120 pixel;
1121
1122 register const double
cristyc47d1f82009-11-26 01:44:43 +00001123 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00001124
1125 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001126 *restrict kernel_pixels;
cristy3ed852e2009-09-05 21:47:34 +00001127
cristybb503372010-05-27 20:51:26 +00001128 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001129 i;
1130
cristyddd82202009-11-03 20:14:50 +00001131 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00001132 k=kernel;
1133 kernel_pixels=p;
1134 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
1135 {
cristybb503372010-05-27 20:51:26 +00001136 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001137 {
1138 pixel.red+=(*k)*kernel_pixels->red;
1139 pixel.green+=(*k)*kernel_pixels->green;
1140 pixel.blue+=(*k)*kernel_pixels->blue;
1141 k++;
1142 kernel_pixels++;
1143 }
1144 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001145 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001146 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001147 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001148 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001149 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001150 if ((channel & OpacityChannel) != 0)
1151 {
1152 k=kernel;
1153 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001154 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001155 {
1156 pixel.opacity+=(*k)*kernel_pixels->opacity;
1157 k++;
1158 kernel_pixels++;
1159 }
cristyce70c172010-01-07 17:15:30 +00001160 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001161 }
1162 if (((channel & IndexChannel) != 0) &&
1163 (image->colorspace == CMYKColorspace))
1164 {
1165 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001166 *restrict kernel_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001167
1168 k=kernel;
cristy9d314ff2011-03-09 01:30:28 +00001169 kernel_indexes=indexes;
cristybb503372010-05-27 20:51:26 +00001170 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001171 {
1172 pixel.index+=(*k)*(*kernel_indexes);
1173 k++;
1174 kernel_indexes++;
1175 }
cristyce70c172010-01-07 17:15:30 +00001176 blur_indexes[y]=ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00001177 }
1178 }
1179 else
1180 {
1181 MagickRealType
1182 alpha,
1183 gamma;
1184
1185 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00001186 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001187 {
cristy46f08202010-01-10 04:04:21 +00001188 alpha=(MagickRealType) (QuantumScale*
1189 GetAlphaPixelComponent(kernel_pixels));
cristy3ed852e2009-09-05 21:47:34 +00001190 pixel.red+=(*k)*alpha*kernel_pixels->red;
1191 pixel.green+=(*k)*alpha*kernel_pixels->green;
1192 pixel.blue+=(*k)*alpha*kernel_pixels->blue;
1193 gamma+=(*k)*alpha;
1194 k++;
1195 kernel_pixels++;
1196 }
1197 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1198 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001199 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001200 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001201 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001202 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001203 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001204 if ((channel & OpacityChannel) != 0)
1205 {
1206 k=kernel;
1207 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001208 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001209 {
1210 pixel.opacity+=(*k)*kernel_pixels->opacity;
1211 k++;
1212 kernel_pixels++;
1213 }
cristyce70c172010-01-07 17:15:30 +00001214 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001215 }
1216 if (((channel & IndexChannel) != 0) &&
1217 (image->colorspace == CMYKColorspace))
1218 {
1219 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001220 *restrict kernel_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001221
1222 k=kernel;
1223 kernel_pixels=p;
cristy9d314ff2011-03-09 01:30:28 +00001224 kernel_indexes=indexes;
cristybb503372010-05-27 20:51:26 +00001225 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001226 {
cristy46f08202010-01-10 04:04:21 +00001227 alpha=(MagickRealType) (QuantumScale*
1228 GetAlphaPixelComponent(kernel_pixels));
cristy3ed852e2009-09-05 21:47:34 +00001229 pixel.index+=(*k)*alpha*(*kernel_indexes);
1230 k++;
1231 kernel_pixels++;
1232 kernel_indexes++;
1233 }
cristy46f08202010-01-10 04:04:21 +00001234 blur_indexes[y]=ClampToQuantum(gamma*
1235 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001236 }
1237 }
cristy9d314ff2011-03-09 01:30:28 +00001238 indexes++;
cristy3ed852e2009-09-05 21:47:34 +00001239 p++;
1240 q++;
1241 }
1242 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1243 status=MagickFalse;
1244 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1245 {
1246 MagickBooleanType
1247 proceed;
1248
cristyb5d5f722009-11-04 03:03:49 +00001249#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001250 #pragma omp critical (MagickCore_BlurImageChannel)
1251#endif
1252 proceed=SetImageProgress(image,BlurImageTag,progress++,blur_image->rows+
1253 blur_image->columns);
1254 if (proceed == MagickFalse)
1255 status=MagickFalse;
1256 }
1257 }
1258 blur_view=DestroyCacheView(blur_view);
1259 image_view=DestroyCacheView(image_view);
1260 kernel=(double *) RelinquishMagickMemory(kernel);
1261 if (status == MagickFalse)
1262 blur_image=DestroyImage(blur_image);
1263 blur_image->type=image->type;
1264 return(blur_image);
1265}
1266
1267/*
1268%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1269% %
1270% %
1271% %
cristyfccdab92009-11-30 16:43:57 +00001272% C o n v o l v e I m a g e %
1273% %
1274% %
1275% %
1276%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1277%
1278% ConvolveImage() applies a custom convolution kernel to the image.
1279%
1280% The format of the ConvolveImage method is:
1281%
cristybb503372010-05-27 20:51:26 +00001282% Image *ConvolveImage(const Image *image,const size_t order,
cristyfccdab92009-11-30 16:43:57 +00001283% const double *kernel,ExceptionInfo *exception)
1284% Image *ConvolveImageChannel(const Image *image,const ChannelType channel,
cristy117ff172010-08-15 21:35:32 +00001285% const size_t order,const double *kernel,ExceptionInfo *exception)
cristyfccdab92009-11-30 16:43:57 +00001286%
1287% A description of each parameter follows:
1288%
1289% o image: the image.
1290%
1291% o channel: the channel type.
1292%
1293% o order: the number of columns and rows in the filter kernel.
1294%
1295% o kernel: An array of double representing the convolution kernel.
1296%
1297% o exception: return any errors or warnings in this structure.
1298%
1299*/
1300
cristybb503372010-05-27 20:51:26 +00001301MagickExport Image *ConvolveImage(const Image *image,const size_t order,
cristyfccdab92009-11-30 16:43:57 +00001302 const double *kernel,ExceptionInfo *exception)
1303{
1304 Image
1305 *convolve_image;
1306
1307 convolve_image=ConvolveImageChannel(image,DefaultChannels,order,kernel,
1308 exception);
1309 return(convolve_image);
1310}
1311
1312MagickExport Image *ConvolveImageChannel(const Image *image,
cristybb503372010-05-27 20:51:26 +00001313 const ChannelType channel,const size_t order,const double *kernel,
cristyfccdab92009-11-30 16:43:57 +00001314 ExceptionInfo *exception)
1315{
1316#define ConvolveImageTag "Convolve/Image"
1317
cristyc4c8d132010-01-07 01:58:38 +00001318 CacheView
1319 *convolve_view,
1320 *image_view;
1321
cristyfccdab92009-11-30 16:43:57 +00001322 double
1323 *normal_kernel;
1324
1325 Image
1326 *convolve_image;
1327
cristyfccdab92009-11-30 16:43:57 +00001328 MagickBooleanType
1329 status;
1330
cristybb503372010-05-27 20:51:26 +00001331 MagickOffsetType
1332 progress;
1333
cristyfccdab92009-11-30 16:43:57 +00001334 MagickPixelPacket
1335 bias;
1336
1337 MagickRealType
1338 gamma;
1339
cristybb503372010-05-27 20:51:26 +00001340 register ssize_t
cristyfccdab92009-11-30 16:43:57 +00001341 i;
1342
cristybb503372010-05-27 20:51:26 +00001343 size_t
cristyfccdab92009-11-30 16:43:57 +00001344 width;
1345
cristybb503372010-05-27 20:51:26 +00001346 ssize_t
1347 y;
1348
cristyfccdab92009-11-30 16:43:57 +00001349 /*
1350 Initialize convolve image attributes.
1351 */
1352 assert(image != (Image *) NULL);
1353 assert(image->signature == MagickSignature);
1354 if (image->debug != MagickFalse)
1355 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1356 assert(exception != (ExceptionInfo *) NULL);
1357 assert(exception->signature == MagickSignature);
1358 width=order;
1359 if ((width % 2) == 0)
1360 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
1361 convolve_image=CloneImage(image,0,0,MagickTrue,exception);
1362 if (convolve_image == (Image *) NULL)
1363 return((Image *) NULL);
1364 if (SetImageStorageClass(convolve_image,DirectClass) == MagickFalse)
1365 {
1366 InheritException(exception,&convolve_image->exception);
1367 convolve_image=DestroyImage(convolve_image);
1368 return((Image *) NULL);
1369 }
1370 if (image->debug != MagickFalse)
1371 {
1372 char
1373 format[MaxTextExtent],
1374 *message;
1375
cristy117ff172010-08-15 21:35:32 +00001376 register const double
1377 *k;
1378
cristybb503372010-05-27 20:51:26 +00001379 ssize_t
cristyfccdab92009-11-30 16:43:57 +00001380 u,
1381 v;
1382
cristyfccdab92009-11-30 16:43:57 +00001383 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00001384 " ConvolveImage with %.20gx%.20g kernel:",(double) width,(double)
1385 width);
cristyfccdab92009-11-30 16:43:57 +00001386 message=AcquireString("");
1387 k=kernel;
cristybb503372010-05-27 20:51:26 +00001388 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001389 {
1390 *message='\0';
cristye8c25f92010-06-03 00:53:06 +00001391 (void) FormatMagickString(format,MaxTextExtent,"%.20g: ",(double) v);
cristyfccdab92009-11-30 16:43:57 +00001392 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00001393 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001394 {
cristye7f51092010-01-17 00:39:37 +00001395 (void) FormatMagickString(format,MaxTextExtent,"%g ",*k++);
cristyfccdab92009-11-30 16:43:57 +00001396 (void) ConcatenateString(&message,format);
1397 }
1398 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
1399 }
1400 message=DestroyString(message);
1401 }
1402 /*
1403 Normalize kernel.
1404 */
1405 normal_kernel=(double *) AcquireQuantumMemory(width*width,
1406 sizeof(*normal_kernel));
1407 if (normal_kernel == (double *) NULL)
1408 {
1409 convolve_image=DestroyImage(convolve_image);
1410 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1411 }
1412 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00001413 for (i=0; i < (ssize_t) (width*width); i++)
cristyfccdab92009-11-30 16:43:57 +00001414 gamma+=kernel[i];
1415 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristybb503372010-05-27 20:51:26 +00001416 for (i=0; i < (ssize_t) (width*width); i++)
cristyfccdab92009-11-30 16:43:57 +00001417 normal_kernel[i]=gamma*kernel[i];
1418 /*
1419 Convolve image.
1420 */
1421 status=MagickTrue;
1422 progress=0;
1423 GetMagickPixelPacket(image,&bias);
1424 SetMagickPixelPacketBias(image,&bias);
1425 image_view=AcquireCacheView(image);
1426 convolve_view=AcquireCacheView(convolve_image);
1427#if defined(MAGICKCORE_OPENMP_SUPPORT)
1428 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1429#endif
cristybb503372010-05-27 20:51:26 +00001430 for (y=0; y < (ssize_t) image->rows; y++)
cristyfccdab92009-11-30 16:43:57 +00001431 {
1432 MagickBooleanType
1433 sync;
1434
1435 register const IndexPacket
1436 *restrict indexes;
1437
1438 register const PixelPacket
1439 *restrict p;
1440
1441 register IndexPacket
1442 *restrict convolve_indexes;
1443
cristyfccdab92009-11-30 16:43:57 +00001444 register PixelPacket
1445 *restrict q;
1446
cristy117ff172010-08-15 21:35:32 +00001447 register ssize_t
1448 x;
1449
cristyfccdab92009-11-30 16:43:57 +00001450 if (status == MagickFalse)
1451 continue;
cristyce889302010-06-30 19:16:36 +00001452 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
1453 (width/2L),image->columns+width,width,exception);
cristyfccdab92009-11-30 16:43:57 +00001454 q=GetCacheViewAuthenticPixels(convolve_view,0,y,convolve_image->columns,1,
1455 exception);
1456 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1457 {
1458 status=MagickFalse;
1459 continue;
1460 }
1461 indexes=GetCacheViewVirtualIndexQueue(image_view);
1462 convolve_indexes=GetCacheViewAuthenticIndexQueue(convolve_view);
cristybb503372010-05-27 20:51:26 +00001463 for (x=0; x < (ssize_t) image->columns; x++)
cristyfccdab92009-11-30 16:43:57 +00001464 {
cristyfccdab92009-11-30 16:43:57 +00001465 MagickPixelPacket
1466 pixel;
1467
1468 register const double
1469 *restrict k;
1470
1471 register const PixelPacket
1472 *restrict kernel_pixels;
1473
cristybb503372010-05-27 20:51:26 +00001474 register ssize_t
cristyfccdab92009-11-30 16:43:57 +00001475 u;
1476
cristy117ff172010-08-15 21:35:32 +00001477 ssize_t
1478 v;
1479
cristyfccdab92009-11-30 16:43:57 +00001480 pixel=bias;
1481 k=normal_kernel;
1482 kernel_pixels=p;
1483 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
1484 {
cristybb503372010-05-27 20:51:26 +00001485 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001486 {
cristybb503372010-05-27 20:51:26 +00001487 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001488 {
1489 pixel.red+=(*k)*kernel_pixels[u].red;
1490 pixel.green+=(*k)*kernel_pixels[u].green;
1491 pixel.blue+=(*k)*kernel_pixels[u].blue;
1492 k++;
1493 }
1494 kernel_pixels+=image->columns+width;
1495 }
1496 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001497 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001498 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001499 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001500 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001501 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001502 if ((channel & OpacityChannel) != 0)
1503 {
1504 k=normal_kernel;
1505 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001506 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001507 {
cristybb503372010-05-27 20:51:26 +00001508 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001509 {
1510 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
1511 k++;
1512 }
1513 kernel_pixels+=image->columns+width;
1514 }
cristyce70c172010-01-07 17:15:30 +00001515 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001516 }
1517 if (((channel & IndexChannel) != 0) &&
1518 (image->colorspace == CMYKColorspace))
1519 {
1520 register const IndexPacket
1521 *restrict kernel_indexes;
1522
1523 k=normal_kernel;
cristy9d314ff2011-03-09 01:30:28 +00001524 kernel_indexes=indexes;
cristybb503372010-05-27 20:51:26 +00001525 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001526 {
cristybb503372010-05-27 20:51:26 +00001527 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001528 {
1529 pixel.index+=(*k)*kernel_indexes[u];
1530 k++;
1531 }
1532 kernel_indexes+=image->columns+width;
1533 }
cristyce70c172010-01-07 17:15:30 +00001534 convolve_indexes[x]=ClampToQuantum(pixel.index);
cristyfccdab92009-11-30 16:43:57 +00001535 }
1536 }
1537 else
1538 {
1539 MagickRealType
1540 alpha,
1541 gamma;
1542
1543 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00001544 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001545 {
cristybb503372010-05-27 20:51:26 +00001546 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001547 {
1548 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
1549 kernel_pixels[u].opacity));
1550 pixel.red+=(*k)*alpha*kernel_pixels[u].red;
1551 pixel.green+=(*k)*alpha*kernel_pixels[u].green;
1552 pixel.blue+=(*k)*alpha*kernel_pixels[u].blue;
cristyfccdab92009-11-30 16:43:57 +00001553 gamma+=(*k)*alpha;
1554 k++;
1555 }
1556 kernel_pixels+=image->columns+width;
1557 }
1558 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1559 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001560 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001561 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001562 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001563 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001564 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001565 if ((channel & OpacityChannel) != 0)
1566 {
1567 k=normal_kernel;
1568 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001569 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001570 {
cristybb503372010-05-27 20:51:26 +00001571 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001572 {
1573 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
1574 k++;
1575 }
1576 kernel_pixels+=image->columns+width;
1577 }
cristyce70c172010-01-07 17:15:30 +00001578 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001579 }
1580 if (((channel & IndexChannel) != 0) &&
1581 (image->colorspace == CMYKColorspace))
1582 {
1583 register const IndexPacket
1584 *restrict kernel_indexes;
1585
1586 k=normal_kernel;
1587 kernel_pixels=p;
cristy9d314ff2011-03-09 01:30:28 +00001588 kernel_indexes=indexes;
cristybb503372010-05-27 20:51:26 +00001589 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001590 {
cristybb503372010-05-27 20:51:26 +00001591 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001592 {
1593 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
1594 kernel_pixels[u].opacity));
1595 pixel.index+=(*k)*alpha*kernel_indexes[u];
1596 k++;
1597 }
1598 kernel_pixels+=image->columns+width;
1599 kernel_indexes+=image->columns+width;
1600 }
cristy24b06da2010-01-09 23:05:56 +00001601 convolve_indexes[x]=ClampToQuantum(gamma*
1602 GetIndexPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001603 }
1604 }
cristy9d314ff2011-03-09 01:30:28 +00001605 indexes++;
cristyfccdab92009-11-30 16:43:57 +00001606 p++;
1607 q++;
1608 }
1609 sync=SyncCacheViewAuthenticPixels(convolve_view,exception);
1610 if (sync == MagickFalse)
1611 status=MagickFalse;
1612 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1613 {
1614 MagickBooleanType
1615 proceed;
1616
1617#if defined(MAGICKCORE_OPENMP_SUPPORT)
1618 #pragma omp critical (MagickCore_ConvolveImageChannel)
1619#endif
1620 proceed=SetImageProgress(image,ConvolveImageTag,progress++,image->rows);
1621 if (proceed == MagickFalse)
1622 status=MagickFalse;
1623 }
1624 }
1625 convolve_image->type=image->type;
1626 convolve_view=DestroyCacheView(convolve_view);
1627 image_view=DestroyCacheView(image_view);
1628 normal_kernel=(double *) RelinquishMagickMemory(normal_kernel);
1629 if (status == MagickFalse)
1630 convolve_image=DestroyImage(convolve_image);
1631 return(convolve_image);
1632}
1633
1634/*
1635%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1636% %
1637% %
1638% %
cristy3ed852e2009-09-05 21:47:34 +00001639% D e s p e c k l e I m a g e %
1640% %
1641% %
1642% %
1643%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1644%
1645% DespeckleImage() reduces the speckle noise in an image while perserving the
1646% edges of the original image.
1647%
1648% The format of the DespeckleImage method is:
1649%
1650% Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1651%
1652% A description of each parameter follows:
1653%
1654% o image: the image.
1655%
1656% o exception: return any errors or warnings in this structure.
1657%
1658*/
1659
cristybb503372010-05-27 20:51:26 +00001660static void Hull(const ssize_t x_offset,const ssize_t y_offset,
1661 const size_t columns,const size_t rows,Quantum *f,Quantum *g,
cristy3ed852e2009-09-05 21:47:34 +00001662 const int polarity)
1663{
cristy3ed852e2009-09-05 21:47:34 +00001664 MagickRealType
1665 v;
1666
cristy3ed852e2009-09-05 21:47:34 +00001667 register Quantum
1668 *p,
1669 *q,
1670 *r,
1671 *s;
1672
cristy117ff172010-08-15 21:35:32 +00001673 register ssize_t
1674 x;
1675
1676 ssize_t
1677 y;
1678
cristy3ed852e2009-09-05 21:47:34 +00001679 assert(f != (Quantum *) NULL);
1680 assert(g != (Quantum *) NULL);
1681 p=f+(columns+2);
1682 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001683 r=p+(y_offset*((ssize_t) columns+2)+x_offset);
1684 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001685 {
1686 p++;
1687 q++;
1688 r++;
1689 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001690 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001691 {
1692 v=(MagickRealType) (*p);
1693 if ((MagickRealType) *r >= (v+(MagickRealType) ScaleCharToQuantum(2)))
1694 v+=ScaleCharToQuantum(1);
1695 *q=(Quantum) v;
1696 p++;
1697 q++;
1698 r++;
1699 }
1700 else
cristybb503372010-05-27 20:51:26 +00001701 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001702 {
1703 v=(MagickRealType) (*p);
1704 if ((MagickRealType) *r <= (v-(MagickRealType) ScaleCharToQuantum(2)))
cristybb503372010-05-27 20:51:26 +00001705 v-=(ssize_t) ScaleCharToQuantum(1);
cristy3ed852e2009-09-05 21:47:34 +00001706 *q=(Quantum) v;
1707 p++;
1708 q++;
1709 r++;
1710 }
1711 p++;
1712 q++;
1713 r++;
1714 }
1715 p=f+(columns+2);
1716 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001717 r=q+(y_offset*((ssize_t) columns+2)+x_offset);
1718 s=q-(y_offset*((ssize_t) columns+2)+x_offset);
1719 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001720 {
1721 p++;
1722 q++;
1723 r++;
1724 s++;
1725 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001726 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001727 {
1728 v=(MagickRealType) (*q);
1729 if (((MagickRealType) *s >=
1730 (v+(MagickRealType) ScaleCharToQuantum(2))) &&
1731 ((MagickRealType) *r > v))
1732 v+=ScaleCharToQuantum(1);
1733 *p=(Quantum) v;
1734 p++;
1735 q++;
1736 r++;
1737 s++;
1738 }
1739 else
cristybb503372010-05-27 20:51:26 +00001740 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001741 {
1742 v=(MagickRealType) (*q);
1743 if (((MagickRealType) *s <=
1744 (v-(MagickRealType) ScaleCharToQuantum(2))) &&
1745 ((MagickRealType) *r < v))
1746 v-=(MagickRealType) ScaleCharToQuantum(1);
1747 *p=(Quantum) v;
1748 p++;
1749 q++;
1750 r++;
1751 s++;
1752 }
1753 p++;
1754 q++;
1755 r++;
1756 s++;
1757 }
1758}
1759
1760MagickExport Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1761{
1762#define DespeckleImageTag "Despeckle/Image"
1763
cristy2407fc22009-09-11 00:55:25 +00001764 CacheView
1765 *despeckle_view,
1766 *image_view;
1767
cristy3ed852e2009-09-05 21:47:34 +00001768 Image
1769 *despeckle_image;
1770
cristy3ed852e2009-09-05 21:47:34 +00001771 MagickBooleanType
1772 status;
1773
cristya58c3172011-02-19 19:23:11 +00001774 register ssize_t
1775 i;
1776
cristy3ed852e2009-09-05 21:47:34 +00001777 Quantum
cristy65b9f392011-02-22 14:22:54 +00001778 *restrict buffers,
1779 *restrict pixels;
cristy3ed852e2009-09-05 21:47:34 +00001780
1781 size_t
cristya58c3172011-02-19 19:23:11 +00001782 length,
1783 number_channels;
cristy117ff172010-08-15 21:35:32 +00001784
cristybb503372010-05-27 20:51:26 +00001785 static const ssize_t
cristy691a29e2009-09-11 00:44:10 +00001786 X[4] = {0, 1, 1,-1},
1787 Y[4] = {1, 0, 1, 1};
cristy3ed852e2009-09-05 21:47:34 +00001788
cristy3ed852e2009-09-05 21:47:34 +00001789 /*
1790 Allocate despeckled image.
1791 */
1792 assert(image != (const Image *) NULL);
1793 assert(image->signature == MagickSignature);
1794 if (image->debug != MagickFalse)
1795 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1796 assert(exception != (ExceptionInfo *) NULL);
1797 assert(exception->signature == MagickSignature);
1798 despeckle_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1799 exception);
1800 if (despeckle_image == (Image *) NULL)
1801 return((Image *) NULL);
1802 if (SetImageStorageClass(despeckle_image,DirectClass) == MagickFalse)
1803 {
1804 InheritException(exception,&despeckle_image->exception);
1805 despeckle_image=DestroyImage(despeckle_image);
1806 return((Image *) NULL);
1807 }
1808 /*
1809 Allocate image buffers.
1810 */
1811 length=(size_t) ((image->columns+2)*(image->rows+2));
cristy65b9f392011-02-22 14:22:54 +00001812 pixels=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1813 buffers=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1814 if ((pixels == (Quantum *) NULL) || (buffers == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001815 {
cristy65b9f392011-02-22 14:22:54 +00001816 if (buffers != (Quantum *) NULL)
1817 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1818 if (pixels != (Quantum *) NULL)
1819 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001820 despeckle_image=DestroyImage(despeckle_image);
1821 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1822 }
1823 /*
1824 Reduce speckle in the image.
1825 */
1826 status=MagickTrue;
cristy109695a2011-02-19 19:38:14 +00001827 number_channels=(size_t) (image->colorspace == CMYKColorspace ? 5 : 4);
cristy3ed852e2009-09-05 21:47:34 +00001828 image_view=AcquireCacheView(image);
1829 despeckle_view=AcquireCacheView(despeckle_image);
cristy8df3d002011-02-19 19:40:59 +00001830 for (i=0; i < (ssize_t) number_channels; i++)
cristy3ed852e2009-09-05 21:47:34 +00001831 {
cristy3ed852e2009-09-05 21:47:34 +00001832 register Quantum
1833 *buffer,
1834 *pixel;
1835
cristyc1488b52011-02-19 18:54:15 +00001836 register ssize_t
cristya58c3172011-02-19 19:23:11 +00001837 k,
cristyc1488b52011-02-19 18:54:15 +00001838 x;
1839
cristy117ff172010-08-15 21:35:32 +00001840 ssize_t
1841 j,
1842 y;
1843
cristy3ed852e2009-09-05 21:47:34 +00001844 if (status == MagickFalse)
1845 continue;
cristy65b9f392011-02-22 14:22:54 +00001846 pixel=pixels;
cristy3ed852e2009-09-05 21:47:34 +00001847 (void) ResetMagickMemory(pixel,0,length*sizeof(*pixel));
cristy65b9f392011-02-22 14:22:54 +00001848 buffer=buffers;
cristybb503372010-05-27 20:51:26 +00001849 j=(ssize_t) image->columns+2;
1850 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001851 {
cristya58c3172011-02-19 19:23:11 +00001852 register const IndexPacket
1853 *restrict indexes;
1854
cristy3ed852e2009-09-05 21:47:34 +00001855 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001856 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001857
1858 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1859 if (p == (const PixelPacket *) NULL)
1860 break;
cristya58c3172011-02-19 19:23:11 +00001861 indexes=GetCacheViewVirtualIndexQueue(image_view);
cristy3ed852e2009-09-05 21:47:34 +00001862 j++;
cristybb503372010-05-27 20:51:26 +00001863 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001864 {
cristya58c3172011-02-19 19:23:11 +00001865 switch (i)
cristy3ed852e2009-09-05 21:47:34 +00001866 {
cristyce70c172010-01-07 17:15:30 +00001867 case 0: pixel[j]=GetRedPixelComponent(p); break;
1868 case 1: pixel[j]=GetGreenPixelComponent(p); break;
1869 case 2: pixel[j]=GetBluePixelComponent(p); break;
1870 case 3: pixel[j]=GetOpacityPixelComponent(p); break;
cristya58c3172011-02-19 19:23:11 +00001871 case 4: pixel[j]=GetBlackPixelComponent(indexes,x); break;
cristy3ed852e2009-09-05 21:47:34 +00001872 default: break;
1873 }
1874 p++;
1875 j++;
1876 }
1877 j++;
1878 }
cristy3ed852e2009-09-05 21:47:34 +00001879 (void) ResetMagickMemory(buffer,0,length*sizeof(*buffer));
cristya58c3172011-02-19 19:23:11 +00001880 for (k=0; k < 4; k++)
cristy3ed852e2009-09-05 21:47:34 +00001881 {
cristya58c3172011-02-19 19:23:11 +00001882 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,1);
1883 Hull(-X[k],-Y[k],image->columns,image->rows,pixel,buffer,1);
1884 Hull(-X[k],-Y[k],image->columns,image->rows,pixel,buffer,-1);
1885 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,-1);
cristy3ed852e2009-09-05 21:47:34 +00001886 }
cristybb503372010-05-27 20:51:26 +00001887 j=(ssize_t) image->columns+2;
1888 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001889 {
1890 MagickBooleanType
1891 sync;
1892
cristya58c3172011-02-19 19:23:11 +00001893 register IndexPacket
1894 *restrict indexes;
1895
cristy3ed852e2009-09-05 21:47:34 +00001896 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001897 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001898
1899 q=GetCacheViewAuthenticPixels(despeckle_view,0,y,despeckle_image->columns,
1900 1,exception);
1901 if (q == (PixelPacket *) NULL)
1902 break;
cristya58c3172011-02-19 19:23:11 +00001903 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristy3ed852e2009-09-05 21:47:34 +00001904 j++;
cristybb503372010-05-27 20:51:26 +00001905 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001906 {
cristya58c3172011-02-19 19:23:11 +00001907 switch (i)
cristy3ed852e2009-09-05 21:47:34 +00001908 {
1909 case 0: q->red=pixel[j]; break;
1910 case 1: q->green=pixel[j]; break;
1911 case 2: q->blue=pixel[j]; break;
1912 case 3: q->opacity=pixel[j]; break;
cristya58c3172011-02-19 19:23:11 +00001913 case 4: indexes[x]=pixel[j]; break;
cristy3ed852e2009-09-05 21:47:34 +00001914 default: break;
1915 }
1916 q++;
1917 j++;
1918 }
1919 sync=SyncCacheViewAuthenticPixels(despeckle_view,exception);
1920 if (sync == MagickFalse)
1921 {
1922 status=MagickFalse;
1923 break;
1924 }
1925 j++;
1926 }
1927 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1928 {
1929 MagickBooleanType
1930 proceed;
1931
cristya58c3172011-02-19 19:23:11 +00001932 proceed=SetImageProgress(image,DespeckleImageTag,(MagickOffsetType) i,
1933 number_channels);
cristy3ed852e2009-09-05 21:47:34 +00001934 if (proceed == MagickFalse)
1935 status=MagickFalse;
1936 }
1937 }
1938 despeckle_view=DestroyCacheView(despeckle_view);
1939 image_view=DestroyCacheView(image_view);
cristy65b9f392011-02-22 14:22:54 +00001940 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1941 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001942 despeckle_image->type=image->type;
1943 if (status == MagickFalse)
1944 despeckle_image=DestroyImage(despeckle_image);
1945 return(despeckle_image);
1946}
1947
1948/*
1949%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1950% %
1951% %
1952% %
1953% E d g e I m a g e %
1954% %
1955% %
1956% %
1957%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1958%
1959% EdgeImage() finds edges in an image. Radius defines the radius of the
1960% convolution filter. Use a radius of 0 and EdgeImage() selects a suitable
1961% radius for you.
1962%
1963% The format of the EdgeImage method is:
1964%
1965% Image *EdgeImage(const Image *image,const double radius,
1966% ExceptionInfo *exception)
1967%
1968% A description of each parameter follows:
1969%
1970% o image: the image.
1971%
1972% o radius: the radius of the pixel neighborhood.
1973%
1974% o exception: return any errors or warnings in this structure.
1975%
1976*/
1977MagickExport Image *EdgeImage(const Image *image,const double radius,
1978 ExceptionInfo *exception)
1979{
1980 Image
1981 *edge_image;
1982
1983 double
1984 *kernel;
1985
cristybb503372010-05-27 20:51:26 +00001986 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001987 i;
1988
cristybb503372010-05-27 20:51:26 +00001989 size_t
cristy3ed852e2009-09-05 21:47:34 +00001990 width;
1991
1992 assert(image != (const Image *) NULL);
1993 assert(image->signature == MagickSignature);
1994 if (image->debug != MagickFalse)
1995 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1996 assert(exception != (ExceptionInfo *) NULL);
1997 assert(exception->signature == MagickSignature);
1998 width=GetOptimalKernelWidth1D(radius,0.5);
1999 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2000 if (kernel == (double *) NULL)
2001 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00002002 for (i=0; i < (ssize_t) (width*width); i++)
cristy3ed852e2009-09-05 21:47:34 +00002003 kernel[i]=(-1.0);
2004 kernel[i/2]=(double) (width*width-1.0);
2005 edge_image=ConvolveImage(image,width,kernel,exception);
2006 kernel=(double *) RelinquishMagickMemory(kernel);
2007 return(edge_image);
2008}
2009
2010/*
2011%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2012% %
2013% %
2014% %
2015% E m b o s s I m a g e %
2016% %
2017% %
2018% %
2019%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2020%
2021% EmbossImage() returns a grayscale image with a three-dimensional effect.
2022% We convolve the image with a Gaussian operator of the given radius and
2023% standard deviation (sigma). For reasonable results, radius should be
2024% larger than sigma. Use a radius of 0 and Emboss() selects a suitable
2025% radius for you.
2026%
2027% The format of the EmbossImage method is:
2028%
2029% Image *EmbossImage(const Image *image,const double radius,
2030% const double sigma,ExceptionInfo *exception)
2031%
2032% A description of each parameter follows:
2033%
2034% o image: the image.
2035%
2036% o radius: the radius of the pixel neighborhood.
2037%
2038% o sigma: the standard deviation of the Gaussian, in pixels.
2039%
2040% o exception: return any errors or warnings in this structure.
2041%
2042*/
2043MagickExport Image *EmbossImage(const Image *image,const double radius,
2044 const double sigma,ExceptionInfo *exception)
2045{
2046 double
2047 *kernel;
2048
2049 Image
2050 *emboss_image;
2051
cristybb503372010-05-27 20:51:26 +00002052 register ssize_t
cristy47e00502009-12-17 19:19:57 +00002053 i;
2054
cristybb503372010-05-27 20:51:26 +00002055 size_t
cristy3ed852e2009-09-05 21:47:34 +00002056 width;
2057
cristy117ff172010-08-15 21:35:32 +00002058 ssize_t
2059 j,
2060 k,
2061 u,
2062 v;
2063
cristy3ed852e2009-09-05 21:47:34 +00002064 assert(image != (Image *) NULL);
2065 assert(image->signature == MagickSignature);
2066 if (image->debug != MagickFalse)
2067 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2068 assert(exception != (ExceptionInfo *) NULL);
2069 assert(exception->signature == MagickSignature);
2070 width=GetOptimalKernelWidth2D(radius,sigma);
2071 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2072 if (kernel == (double *) NULL)
2073 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00002074 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +00002075 k=j;
2076 i=0;
2077 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002078 {
cristy47e00502009-12-17 19:19:57 +00002079 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00002080 {
cristy4205a3c2010-09-12 20:19:59 +00002081 kernel[i]=(double) (((u < 0) || (v < 0) ? -8.0 : 8.0)*
cristy47e00502009-12-17 19:19:57 +00002082 exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
cristy4205a3c2010-09-12 20:19:59 +00002083 (2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +00002084 if (u != k)
cristy3ed852e2009-09-05 21:47:34 +00002085 kernel[i]=0.0;
2086 i++;
2087 }
cristy47e00502009-12-17 19:19:57 +00002088 k--;
cristy3ed852e2009-09-05 21:47:34 +00002089 }
2090 emboss_image=ConvolveImage(image,width,kernel,exception);
2091 if (emboss_image != (Image *) NULL)
2092 (void) EqualizeImage(emboss_image);
2093 kernel=(double *) RelinquishMagickMemory(kernel);
2094 return(emboss_image);
2095}
2096
2097/*
2098%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2099% %
2100% %
2101% %
cristy56a9e512010-01-06 18:18:55 +00002102% F i l t e r I m a g e %
2103% %
2104% %
2105% %
2106%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2107%
2108% FilterImage() applies a custom convolution kernel to the image.
2109%
2110% The format of the FilterImage method is:
2111%
cristy2be15382010-01-21 02:38:03 +00002112% Image *FilterImage(const Image *image,const KernelInfo *kernel,
cristy56a9e512010-01-06 18:18:55 +00002113% ExceptionInfo *exception)
2114% Image *FilterImageChannel(const Image *image,const ChannelType channel,
cristy2be15382010-01-21 02:38:03 +00002115% const KernelInfo *kernel,ExceptionInfo *exception)
cristy56a9e512010-01-06 18:18:55 +00002116%
2117% A description of each parameter follows:
2118%
2119% o image: the image.
2120%
2121% o channel: the channel type.
2122%
2123% o kernel: the filtering kernel.
2124%
2125% o exception: return any errors or warnings in this structure.
2126%
2127*/
2128
cristy2be15382010-01-21 02:38:03 +00002129MagickExport Image *FilterImage(const Image *image,const KernelInfo *kernel,
cristy56a9e512010-01-06 18:18:55 +00002130 ExceptionInfo *exception)
2131{
2132 Image
2133 *filter_image;
2134
2135 filter_image=FilterImageChannel(image,DefaultChannels,kernel,exception);
2136 return(filter_image);
2137}
2138
2139MagickExport Image *FilterImageChannel(const Image *image,
cristy2be15382010-01-21 02:38:03 +00002140 const ChannelType channel,const KernelInfo *kernel,ExceptionInfo *exception)
cristy56a9e512010-01-06 18:18:55 +00002141{
2142#define FilterImageTag "Filter/Image"
2143
2144 CacheView
2145 *filter_view,
2146 *image_view;
2147
cristy56a9e512010-01-06 18:18:55 +00002148 Image
2149 *filter_image;
2150
cristy56a9e512010-01-06 18:18:55 +00002151 MagickBooleanType
2152 status;
2153
cristybb503372010-05-27 20:51:26 +00002154 MagickOffsetType
2155 progress;
2156
cristy56a9e512010-01-06 18:18:55 +00002157 MagickPixelPacket
2158 bias;
2159
cristybb503372010-05-27 20:51:26 +00002160 ssize_t
2161 y;
2162
cristy56a9e512010-01-06 18:18:55 +00002163 /*
2164 Initialize filter image attributes.
2165 */
2166 assert(image != (Image *) NULL);
2167 assert(image->signature == MagickSignature);
2168 if (image->debug != MagickFalse)
2169 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2170 assert(exception != (ExceptionInfo *) NULL);
2171 assert(exception->signature == MagickSignature);
2172 if ((kernel->width % 2) == 0)
2173 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
2174 filter_image=CloneImage(image,0,0,MagickTrue,exception);
2175 if (filter_image == (Image *) NULL)
2176 return((Image *) NULL);
2177 if (SetImageStorageClass(filter_image,DirectClass) == MagickFalse)
2178 {
2179 InheritException(exception,&filter_image->exception);
2180 filter_image=DestroyImage(filter_image);
2181 return((Image *) NULL);
2182 }
2183 if (image->debug != MagickFalse)
2184 {
2185 char
2186 format[MaxTextExtent],
2187 *message;
2188
cristy117ff172010-08-15 21:35:32 +00002189 register const double
2190 *k;
2191
cristybb503372010-05-27 20:51:26 +00002192 ssize_t
cristy56a9e512010-01-06 18:18:55 +00002193 u,
2194 v;
2195
cristy56a9e512010-01-06 18:18:55 +00002196 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00002197 " FilterImage with %.20gx%.20g kernel:",(double) kernel->width,(double)
2198 kernel->height);
cristy56a9e512010-01-06 18:18:55 +00002199 message=AcquireString("");
2200 k=kernel->values;
cristybb503372010-05-27 20:51:26 +00002201 for (v=0; v < (ssize_t) kernel->height; v++)
cristy56a9e512010-01-06 18:18:55 +00002202 {
2203 *message='\0';
cristye8c25f92010-06-03 00:53:06 +00002204 (void) FormatMagickString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy56a9e512010-01-06 18:18:55 +00002205 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00002206 for (u=0; u < (ssize_t) kernel->width; u++)
cristy56a9e512010-01-06 18:18:55 +00002207 {
cristye7f51092010-01-17 00:39:37 +00002208 (void) FormatMagickString(format,MaxTextExtent,"%g ",*k++);
cristy56a9e512010-01-06 18:18:55 +00002209 (void) ConcatenateString(&message,format);
2210 }
2211 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
2212 }
2213 message=DestroyString(message);
2214 }
cristy36826ab2010-03-06 01:29:30 +00002215 status=AccelerateConvolveImage(image,kernel,filter_image,exception);
cristyd43a46b2010-01-21 02:13:41 +00002216 if (status == MagickTrue)
2217 return(filter_image);
cristy56a9e512010-01-06 18:18:55 +00002218 /*
2219 Filter image.
2220 */
2221 status=MagickTrue;
2222 progress=0;
2223 GetMagickPixelPacket(image,&bias);
2224 SetMagickPixelPacketBias(image,&bias);
2225 image_view=AcquireCacheView(image);
2226 filter_view=AcquireCacheView(filter_image);
2227#if defined(MAGICKCORE_OPENMP_SUPPORT)
2228 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2229#endif
cristybb503372010-05-27 20:51:26 +00002230 for (y=0; y < (ssize_t) image->rows; y++)
cristy56a9e512010-01-06 18:18:55 +00002231 {
2232 MagickBooleanType
2233 sync;
2234
2235 register const IndexPacket
2236 *restrict indexes;
2237
2238 register const PixelPacket
2239 *restrict p;
2240
2241 register IndexPacket
2242 *restrict filter_indexes;
2243
cristy56a9e512010-01-06 18:18:55 +00002244 register PixelPacket
2245 *restrict q;
2246
cristy117ff172010-08-15 21:35:32 +00002247 register ssize_t
2248 x;
2249
cristy56a9e512010-01-06 18:18:55 +00002250 if (status == MagickFalse)
2251 continue;
cristybb503372010-05-27 20:51:26 +00002252 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) kernel->width/2L),
cristy117ff172010-08-15 21:35:32 +00002253 y-(ssize_t) (kernel->height/2L),image->columns+kernel->width,
2254 kernel->height,exception);
cristy56a9e512010-01-06 18:18:55 +00002255 q=GetCacheViewAuthenticPixels(filter_view,0,y,filter_image->columns,1,
2256 exception);
2257 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2258 {
2259 status=MagickFalse;
2260 continue;
2261 }
2262 indexes=GetCacheViewVirtualIndexQueue(image_view);
2263 filter_indexes=GetCacheViewAuthenticIndexQueue(filter_view);
cristybb503372010-05-27 20:51:26 +00002264 for (x=0; x < (ssize_t) image->columns; x++)
cristy56a9e512010-01-06 18:18:55 +00002265 {
cristy56a9e512010-01-06 18:18:55 +00002266 MagickPixelPacket
2267 pixel;
2268
2269 register const double
2270 *restrict k;
2271
2272 register const PixelPacket
2273 *restrict kernel_pixels;
2274
cristybb503372010-05-27 20:51:26 +00002275 register ssize_t
cristy56a9e512010-01-06 18:18:55 +00002276 u;
2277
cristy117ff172010-08-15 21:35:32 +00002278 ssize_t
2279 v;
2280
cristy56a9e512010-01-06 18:18:55 +00002281 pixel=bias;
cristy36826ab2010-03-06 01:29:30 +00002282 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002283 kernel_pixels=p;
2284 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
2285 {
cristybb503372010-05-27 20:51:26 +00002286 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002287 {
cristybb503372010-05-27 20:51:26 +00002288 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002289 {
2290 pixel.red+=(*k)*kernel_pixels[u].red;
2291 pixel.green+=(*k)*kernel_pixels[u].green;
2292 pixel.blue+=(*k)*kernel_pixels[u].blue;
2293 k++;
2294 }
cristy36826ab2010-03-06 01:29:30 +00002295 kernel_pixels+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002296 }
2297 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002298 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002299 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002300 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002301 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002302 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002303 if ((channel & OpacityChannel) != 0)
2304 {
cristy36826ab2010-03-06 01:29:30 +00002305 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002306 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00002307 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002308 {
cristybb503372010-05-27 20:51:26 +00002309 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002310 {
2311 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
2312 k++;
2313 }
cristy36826ab2010-03-06 01:29:30 +00002314 kernel_pixels+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002315 }
cristyce70c172010-01-07 17:15:30 +00002316 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002317 }
2318 if (((channel & IndexChannel) != 0) &&
2319 (image->colorspace == CMYKColorspace))
2320 {
2321 register const IndexPacket
2322 *restrict kernel_indexes;
2323
cristy36826ab2010-03-06 01:29:30 +00002324 k=kernel->values;
cristy9d314ff2011-03-09 01:30:28 +00002325 kernel_indexes=indexes;
cristybb503372010-05-27 20:51:26 +00002326 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002327 {
cristybb503372010-05-27 20:51:26 +00002328 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002329 {
2330 pixel.index+=(*k)*kernel_indexes[u];
2331 k++;
2332 }
cristy36826ab2010-03-06 01:29:30 +00002333 kernel_indexes+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002334 }
cristyce70c172010-01-07 17:15:30 +00002335 filter_indexes[x]=ClampToQuantum(pixel.index);
cristy56a9e512010-01-06 18:18:55 +00002336 }
2337 }
2338 else
2339 {
2340 MagickRealType
2341 alpha,
2342 gamma;
2343
2344 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00002345 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002346 {
cristybb503372010-05-27 20:51:26 +00002347 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002348 {
2349 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
2350 kernel_pixels[u].opacity));
2351 pixel.red+=(*k)*alpha*kernel_pixels[u].red;
2352 pixel.green+=(*k)*alpha*kernel_pixels[u].green;
2353 pixel.blue+=(*k)*alpha*kernel_pixels[u].blue;
2354 gamma+=(*k)*alpha;
2355 k++;
2356 }
cristy36826ab2010-03-06 01:29:30 +00002357 kernel_pixels+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002358 }
2359 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
2360 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002361 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002362 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002363 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002364 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002365 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002366 if ((channel & OpacityChannel) != 0)
2367 {
cristy36826ab2010-03-06 01:29:30 +00002368 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002369 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00002370 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002371 {
cristybb503372010-05-27 20:51:26 +00002372 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002373 {
2374 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
2375 k++;
2376 }
cristy36826ab2010-03-06 01:29:30 +00002377 kernel_pixels+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002378 }
cristyce70c172010-01-07 17:15:30 +00002379 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002380 }
2381 if (((channel & IndexChannel) != 0) &&
2382 (image->colorspace == CMYKColorspace))
2383 {
2384 register const IndexPacket
2385 *restrict kernel_indexes;
2386
cristy36826ab2010-03-06 01:29:30 +00002387 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002388 kernel_pixels=p;
cristy9d314ff2011-03-09 01:30:28 +00002389 kernel_indexes=indexes;
cristybb503372010-05-27 20:51:26 +00002390 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002391 {
cristybb503372010-05-27 20:51:26 +00002392 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002393 {
2394 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
2395 kernel_pixels[u].opacity));
2396 pixel.index+=(*k)*alpha*kernel_indexes[u];
2397 k++;
2398 }
cristy36826ab2010-03-06 01:29:30 +00002399 kernel_pixels+=image->columns+kernel->width;
2400 kernel_indexes+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002401 }
cristy2115aea2010-01-09 23:16:08 +00002402 filter_indexes[x]=ClampToQuantum(gamma*
2403 GetIndexPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002404 }
2405 }
cristy9d314ff2011-03-09 01:30:28 +00002406 indexes++;
cristy56a9e512010-01-06 18:18:55 +00002407 p++;
2408 q++;
2409 }
2410 sync=SyncCacheViewAuthenticPixels(filter_view,exception);
2411 if (sync == MagickFalse)
2412 status=MagickFalse;
2413 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2414 {
2415 MagickBooleanType
2416 proceed;
2417
2418#if defined(MAGICKCORE_OPENMP_SUPPORT)
2419 #pragma omp critical (MagickCore_FilterImageChannel)
2420#endif
2421 proceed=SetImageProgress(image,FilterImageTag,progress++,image->rows);
2422 if (proceed == MagickFalse)
2423 status=MagickFalse;
2424 }
2425 }
2426 filter_image->type=image->type;
2427 filter_view=DestroyCacheView(filter_view);
2428 image_view=DestroyCacheView(image_view);
cristy56a9e512010-01-06 18:18:55 +00002429 if (status == MagickFalse)
2430 filter_image=DestroyImage(filter_image);
2431 return(filter_image);
2432}
2433
2434/*
2435%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2436% %
2437% %
2438% %
cristy3ed852e2009-09-05 21:47:34 +00002439% G a u s s i a n B l u r I m a g e %
2440% %
2441% %
2442% %
2443%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2444%
2445% GaussianBlurImage() blurs an image. We convolve the image with a
2446% Gaussian operator of the given radius and standard deviation (sigma).
2447% For reasonable results, the radius should be larger than sigma. Use a
2448% radius of 0 and GaussianBlurImage() selects a suitable radius for you
2449%
2450% The format of the GaussianBlurImage method is:
2451%
2452% Image *GaussianBlurImage(const Image *image,onst double radius,
2453% const double sigma,ExceptionInfo *exception)
2454% Image *GaussianBlurImageChannel(const Image *image,
2455% const ChannelType channel,const double radius,const double sigma,
2456% ExceptionInfo *exception)
2457%
2458% A description of each parameter follows:
2459%
2460% o image: the image.
2461%
2462% o channel: the channel type.
2463%
2464% o radius: the radius of the Gaussian, in pixels, not counting the center
2465% pixel.
2466%
2467% o sigma: the standard deviation of the Gaussian, in pixels.
2468%
2469% o exception: return any errors or warnings in this structure.
2470%
2471*/
2472
2473MagickExport Image *GaussianBlurImage(const Image *image,const double radius,
2474 const double sigma,ExceptionInfo *exception)
2475{
2476 Image
2477 *blur_image;
2478
2479 blur_image=GaussianBlurImageChannel(image,DefaultChannels,radius,sigma,
2480 exception);
2481 return(blur_image);
2482}
2483
2484MagickExport Image *GaussianBlurImageChannel(const Image *image,
2485 const ChannelType channel,const double radius,const double sigma,
2486 ExceptionInfo *exception)
2487{
2488 double
2489 *kernel;
2490
2491 Image
2492 *blur_image;
2493
cristybb503372010-05-27 20:51:26 +00002494 register ssize_t
cristy47e00502009-12-17 19:19:57 +00002495 i;
2496
cristybb503372010-05-27 20:51:26 +00002497 size_t
cristy3ed852e2009-09-05 21:47:34 +00002498 width;
2499
cristy117ff172010-08-15 21:35:32 +00002500 ssize_t
2501 j,
2502 u,
2503 v;
2504
cristy3ed852e2009-09-05 21:47:34 +00002505 assert(image != (const Image *) NULL);
2506 assert(image->signature == MagickSignature);
2507 if (image->debug != MagickFalse)
2508 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2509 assert(exception != (ExceptionInfo *) NULL);
2510 assert(exception->signature == MagickSignature);
2511 width=GetOptimalKernelWidth2D(radius,sigma);
2512 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2513 if (kernel == (double *) NULL)
2514 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00002515 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00002516 i=0;
cristy47e00502009-12-17 19:19:57 +00002517 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002518 {
cristy47e00502009-12-17 19:19:57 +00002519 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00002520 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
2521 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00002522 }
2523 blur_image=ConvolveImageChannel(image,channel,width,kernel,exception);
2524 kernel=(double *) RelinquishMagickMemory(kernel);
2525 return(blur_image);
2526}
2527
2528/*
2529%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2530% %
2531% %
2532% %
cristy3ed852e2009-09-05 21:47:34 +00002533% M o t i o n B l u r I m a g e %
2534% %
2535% %
2536% %
2537%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2538%
2539% MotionBlurImage() simulates motion blur. We convolve the image with a
2540% Gaussian operator of the given radius and standard deviation (sigma).
2541% For reasonable results, radius should be larger than sigma. Use a
2542% radius of 0 and MotionBlurImage() selects a suitable radius for you.
2543% Angle gives the angle of the blurring motion.
2544%
2545% Andrew Protano contributed this effect.
2546%
2547% The format of the MotionBlurImage method is:
2548%
2549% Image *MotionBlurImage(const Image *image,const double radius,
2550% const double sigma,const double angle,ExceptionInfo *exception)
2551% Image *MotionBlurImageChannel(const Image *image,const ChannelType channel,
2552% const double radius,const double sigma,const double angle,
2553% ExceptionInfo *exception)
2554%
2555% A description of each parameter follows:
2556%
2557% o image: the image.
2558%
2559% o channel: the channel type.
2560%
2561% o radius: the radius of the Gaussian, in pixels, not counting the center
2562% o radius: the radius of the Gaussian, in pixels, not counting
2563% the center pixel.
2564%
2565% o sigma: the standard deviation of the Gaussian, in pixels.
2566%
cristycee97112010-05-28 00:44:52 +00002567% o angle: Apply the effect along this angle.
cristy3ed852e2009-09-05 21:47:34 +00002568%
2569% o exception: return any errors or warnings in this structure.
2570%
2571*/
2572
cristybb503372010-05-27 20:51:26 +00002573static double *GetMotionBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +00002574{
cristy3ed852e2009-09-05 21:47:34 +00002575 double
cristy47e00502009-12-17 19:19:57 +00002576 *kernel,
cristy3ed852e2009-09-05 21:47:34 +00002577 normalize;
2578
cristybb503372010-05-27 20:51:26 +00002579 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002580 i;
2581
2582 /*
cristy47e00502009-12-17 19:19:57 +00002583 Generate a 1-D convolution kernel.
cristy3ed852e2009-09-05 21:47:34 +00002584 */
2585 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
2586 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
2587 if (kernel == (double *) NULL)
2588 return(kernel);
cristy3ed852e2009-09-05 21:47:34 +00002589 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00002590 for (i=0; i < (ssize_t) width; i++)
cristy47e00502009-12-17 19:19:57 +00002591 {
cristy4205a3c2010-09-12 20:19:59 +00002592 kernel[i]=(double) (exp((-((double) i*i)/(double) (2.0*MagickSigma*
2593 MagickSigma)))/(MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00002594 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +00002595 }
cristybb503372010-05-27 20:51:26 +00002596 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002597 kernel[i]/=normalize;
2598 return(kernel);
2599}
2600
2601MagickExport Image *MotionBlurImage(const Image *image,const double radius,
2602 const double sigma,const double angle,ExceptionInfo *exception)
2603{
2604 Image
2605 *motion_blur;
2606
2607 motion_blur=MotionBlurImageChannel(image,DefaultChannels,radius,sigma,angle,
2608 exception);
2609 return(motion_blur);
2610}
2611
2612MagickExport Image *MotionBlurImageChannel(const Image *image,
2613 const ChannelType channel,const double radius,const double sigma,
2614 const double angle,ExceptionInfo *exception)
2615{
cristyc4c8d132010-01-07 01:58:38 +00002616 CacheView
2617 *blur_view,
2618 *image_view;
2619
cristy3ed852e2009-09-05 21:47:34 +00002620 double
2621 *kernel;
2622
2623 Image
2624 *blur_image;
2625
cristy3ed852e2009-09-05 21:47:34 +00002626 MagickBooleanType
2627 status;
2628
cristybb503372010-05-27 20:51:26 +00002629 MagickOffsetType
2630 progress;
2631
cristy3ed852e2009-09-05 21:47:34 +00002632 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00002633 bias;
cristy3ed852e2009-09-05 21:47:34 +00002634
2635 OffsetInfo
2636 *offset;
2637
2638 PointInfo
2639 point;
2640
cristybb503372010-05-27 20:51:26 +00002641 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002642 i;
2643
cristybb503372010-05-27 20:51:26 +00002644 size_t
cristy3ed852e2009-09-05 21:47:34 +00002645 width;
2646
cristybb503372010-05-27 20:51:26 +00002647 ssize_t
2648 y;
2649
cristy3ed852e2009-09-05 21:47:34 +00002650 assert(image != (Image *) NULL);
2651 assert(image->signature == MagickSignature);
2652 if (image->debug != MagickFalse)
2653 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2654 assert(exception != (ExceptionInfo *) NULL);
2655 width=GetOptimalKernelWidth1D(radius,sigma);
2656 kernel=GetMotionBlurKernel(width,sigma);
2657 if (kernel == (double *) NULL)
2658 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2659 offset=(OffsetInfo *) AcquireQuantumMemory(width,sizeof(*offset));
2660 if (offset == (OffsetInfo *) NULL)
2661 {
2662 kernel=(double *) RelinquishMagickMemory(kernel);
2663 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2664 }
2665 blur_image=CloneImage(image,0,0,MagickTrue,exception);
2666 if (blur_image == (Image *) NULL)
2667 {
2668 kernel=(double *) RelinquishMagickMemory(kernel);
2669 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2670 return((Image *) NULL);
2671 }
2672 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
2673 {
2674 kernel=(double *) RelinquishMagickMemory(kernel);
2675 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2676 InheritException(exception,&blur_image->exception);
2677 blur_image=DestroyImage(blur_image);
2678 return((Image *) NULL);
2679 }
2680 point.x=(double) width*sin(DegreesToRadians(angle));
2681 point.y=(double) width*cos(DegreesToRadians(angle));
cristybb503372010-05-27 20:51:26 +00002682 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002683 {
cristybb503372010-05-27 20:51:26 +00002684 offset[i].x=(ssize_t) ceil((double) (i*point.y)/hypot(point.x,point.y)-0.5);
2685 offset[i].y=(ssize_t) ceil((double) (i*point.x)/hypot(point.x,point.y)-0.5);
cristy3ed852e2009-09-05 21:47:34 +00002686 }
2687 /*
2688 Motion blur image.
2689 */
2690 status=MagickTrue;
2691 progress=0;
cristyddd82202009-11-03 20:14:50 +00002692 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00002693 image_view=AcquireCacheView(image);
2694 blur_view=AcquireCacheView(blur_image);
cristyb557a152011-02-22 12:14:30 +00002695#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00002696 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00002697#endif
cristybb503372010-05-27 20:51:26 +00002698 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002699 {
2700 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002701 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00002702
cristy3ed852e2009-09-05 21:47:34 +00002703 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002704 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002705
cristy117ff172010-08-15 21:35:32 +00002706 register ssize_t
2707 x;
2708
cristy3ed852e2009-09-05 21:47:34 +00002709 if (status == MagickFalse)
2710 continue;
2711 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
2712 exception);
2713 if (q == (PixelPacket *) NULL)
2714 {
2715 status=MagickFalse;
2716 continue;
2717 }
2718 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
cristybb503372010-05-27 20:51:26 +00002719 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002720 {
2721 MagickPixelPacket
2722 qixel;
2723
2724 PixelPacket
2725 pixel;
2726
cristy117ff172010-08-15 21:35:32 +00002727 register const IndexPacket
2728 *restrict indexes;
2729
cristy3ed852e2009-09-05 21:47:34 +00002730 register double
cristyc47d1f82009-11-26 01:44:43 +00002731 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00002732
cristybb503372010-05-27 20:51:26 +00002733 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002734 i;
2735
cristy3ed852e2009-09-05 21:47:34 +00002736 k=kernel;
cristyddd82202009-11-03 20:14:50 +00002737 qixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00002738 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
2739 {
cristybb503372010-05-27 20:51:26 +00002740 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002741 {
2742 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
2743 offset[i].y,&pixel,exception);
2744 qixel.red+=(*k)*pixel.red;
2745 qixel.green+=(*k)*pixel.green;
2746 qixel.blue+=(*k)*pixel.blue;
2747 qixel.opacity+=(*k)*pixel.opacity;
2748 if (image->colorspace == CMYKColorspace)
2749 {
2750 indexes=GetCacheViewVirtualIndexQueue(image_view);
2751 qixel.index+=(*k)*(*indexes);
2752 }
2753 k++;
2754 }
2755 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002756 q->red=ClampToQuantum(qixel.red);
cristy3ed852e2009-09-05 21:47:34 +00002757 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002758 q->green=ClampToQuantum(qixel.green);
cristy3ed852e2009-09-05 21:47:34 +00002759 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002760 q->blue=ClampToQuantum(qixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00002761 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002762 q->opacity=ClampToQuantum(qixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00002763 if (((channel & IndexChannel) != 0) &&
2764 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00002765 blur_indexes[x]=(IndexPacket) ClampToQuantum(qixel.index);
cristy3ed852e2009-09-05 21:47:34 +00002766 }
2767 else
2768 {
2769 MagickRealType
2770 alpha,
2771 gamma;
2772
2773 alpha=0.0;
2774 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00002775 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002776 {
2777 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
2778 offset[i].y,&pixel,exception);
cristy8a7ea362010-03-10 20:31:43 +00002779 alpha=(MagickRealType) (QuantumScale*
2780 GetAlphaPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002781 qixel.red+=(*k)*alpha*pixel.red;
2782 qixel.green+=(*k)*alpha*pixel.green;
2783 qixel.blue+=(*k)*alpha*pixel.blue;
2784 qixel.opacity+=(*k)*pixel.opacity;
2785 if (image->colorspace == CMYKColorspace)
2786 {
2787 indexes=GetCacheViewVirtualIndexQueue(image_view);
2788 qixel.index+=(*k)*alpha*(*indexes);
2789 }
2790 gamma+=(*k)*alpha;
2791 k++;
2792 }
2793 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
2794 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002795 q->red=ClampToQuantum(gamma*qixel.red);
cristy3ed852e2009-09-05 21:47:34 +00002796 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002797 q->green=ClampToQuantum(gamma*qixel.green);
cristy3ed852e2009-09-05 21:47:34 +00002798 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002799 q->blue=ClampToQuantum(gamma*qixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00002800 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002801 q->opacity=ClampToQuantum(qixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00002802 if (((channel & IndexChannel) != 0) &&
2803 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00002804 blur_indexes[x]=(IndexPacket) ClampToQuantum(gamma*qixel.index);
cristy3ed852e2009-09-05 21:47:34 +00002805 }
2806 q++;
2807 }
2808 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
2809 status=MagickFalse;
2810 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2811 {
2812 MagickBooleanType
2813 proceed;
2814
cristyb557a152011-02-22 12:14:30 +00002815#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002816 #pragma omp critical (MagickCore_MotionBlurImageChannel)
2817#endif
2818 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
2819 if (proceed == MagickFalse)
2820 status=MagickFalse;
2821 }
2822 }
2823 blur_view=DestroyCacheView(blur_view);
2824 image_view=DestroyCacheView(image_view);
2825 kernel=(double *) RelinquishMagickMemory(kernel);
2826 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2827 if (status == MagickFalse)
2828 blur_image=DestroyImage(blur_image);
2829 return(blur_image);
2830}
2831
2832/*
2833%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2834% %
2835% %
2836% %
2837% P r e v i e w I m a g e %
2838% %
2839% %
2840% %
2841%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2842%
2843% PreviewImage() tiles 9 thumbnails of the specified image with an image
2844% processing operation applied with varying parameters. This may be helpful
2845% pin-pointing an appropriate parameter for a particular image processing
2846% operation.
2847%
2848% The format of the PreviewImages method is:
2849%
2850% Image *PreviewImages(const Image *image,const PreviewType preview,
2851% ExceptionInfo *exception)
2852%
2853% A description of each parameter follows:
2854%
2855% o image: the image.
2856%
2857% o preview: the image processing operation.
2858%
2859% o exception: return any errors or warnings in this structure.
2860%
2861*/
2862MagickExport Image *PreviewImage(const Image *image,const PreviewType preview,
2863 ExceptionInfo *exception)
2864{
2865#define NumberTiles 9
2866#define PreviewImageTag "Preview/Image"
2867#define DefaultPreviewGeometry "204x204+10+10"
2868
2869 char
2870 factor[MaxTextExtent],
2871 label[MaxTextExtent];
2872
2873 double
2874 degrees,
2875 gamma,
2876 percentage,
2877 radius,
2878 sigma,
2879 threshold;
2880
2881 Image
2882 *images,
2883 *montage_image,
2884 *preview_image,
2885 *thumbnail;
2886
2887 ImageInfo
2888 *preview_info;
2889
cristy3ed852e2009-09-05 21:47:34 +00002890 MagickBooleanType
2891 proceed;
2892
2893 MontageInfo
2894 *montage_info;
2895
2896 QuantizeInfo
2897 quantize_info;
2898
2899 RectangleInfo
2900 geometry;
2901
cristybb503372010-05-27 20:51:26 +00002902 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002903 i,
2904 x;
2905
cristybb503372010-05-27 20:51:26 +00002906 size_t
cristy3ed852e2009-09-05 21:47:34 +00002907 colors;
2908
cristy117ff172010-08-15 21:35:32 +00002909 ssize_t
2910 y;
2911
cristy3ed852e2009-09-05 21:47:34 +00002912 /*
2913 Open output image file.
2914 */
2915 assert(image != (Image *) NULL);
2916 assert(image->signature == MagickSignature);
2917 if (image->debug != MagickFalse)
2918 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2919 colors=2;
2920 degrees=0.0;
2921 gamma=(-0.2f);
2922 preview_info=AcquireImageInfo();
2923 SetGeometry(image,&geometry);
2924 (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y,
2925 &geometry.width,&geometry.height);
2926 images=NewImageList();
2927 percentage=12.5;
2928 GetQuantizeInfo(&quantize_info);
2929 radius=0.0;
2930 sigma=1.0;
2931 threshold=0.0;
2932 x=0;
2933 y=0;
2934 for (i=0; i < NumberTiles; i++)
2935 {
2936 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
2937 if (thumbnail == (Image *) NULL)
2938 break;
2939 (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL,
2940 (void *) NULL);
2941 (void) SetImageProperty(thumbnail,"label",DefaultTileLabel);
2942 if (i == (NumberTiles/2))
2943 {
2944 (void) QueryColorDatabase("#dfdfdf",&thumbnail->matte_color,exception);
2945 AppendImageToList(&images,thumbnail);
2946 continue;
2947 }
2948 switch (preview)
2949 {
2950 case RotatePreview:
2951 {
2952 degrees+=45.0;
2953 preview_image=RotateImage(thumbnail,degrees,exception);
cristye7f51092010-01-17 00:39:37 +00002954 (void) FormatMagickString(label,MaxTextExtent,"rotate %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002955 break;
2956 }
2957 case ShearPreview:
2958 {
2959 degrees+=5.0;
2960 preview_image=ShearImage(thumbnail,degrees,degrees,exception);
cristye7f51092010-01-17 00:39:37 +00002961 (void) FormatMagickString(label,MaxTextExtent,"shear %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002962 degrees,2.0*degrees);
2963 break;
2964 }
2965 case RollPreview:
2966 {
cristybb503372010-05-27 20:51:26 +00002967 x=(ssize_t) ((i+1)*thumbnail->columns)/NumberTiles;
2968 y=(ssize_t) ((i+1)*thumbnail->rows)/NumberTiles;
cristy3ed852e2009-09-05 21:47:34 +00002969 preview_image=RollImage(thumbnail,x,y,exception);
cristye8c25f92010-06-03 00:53:06 +00002970 (void) FormatMagickString(label,MaxTextExtent,"roll %+.20gx%+.20g",
2971 (double) x,(double) y);
cristy3ed852e2009-09-05 21:47:34 +00002972 break;
2973 }
2974 case HuePreview:
2975 {
2976 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2977 if (preview_image == (Image *) NULL)
2978 break;
cristye7f51092010-01-17 00:39:37 +00002979 (void) FormatMagickString(factor,MaxTextExtent,"100,100,%g",
cristy3ed852e2009-09-05 21:47:34 +00002980 2.0*percentage);
2981 (void) ModulateImage(preview_image,factor);
2982 (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor);
2983 break;
2984 }
2985 case SaturationPreview:
2986 {
2987 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2988 if (preview_image == (Image *) NULL)
2989 break;
cristye7f51092010-01-17 00:39:37 +00002990 (void) FormatMagickString(factor,MaxTextExtent,"100,%g",
cristy8cd5b312010-01-07 01:10:24 +00002991 2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00002992 (void) ModulateImage(preview_image,factor);
2993 (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor);
2994 break;
2995 }
2996 case BrightnessPreview:
2997 {
2998 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2999 if (preview_image == (Image *) NULL)
3000 break;
cristye7f51092010-01-17 00:39:37 +00003001 (void) FormatMagickString(factor,MaxTextExtent,"%g",2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00003002 (void) ModulateImage(preview_image,factor);
3003 (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor);
3004 break;
3005 }
3006 case GammaPreview:
3007 default:
3008 {
3009 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3010 if (preview_image == (Image *) NULL)
3011 break;
3012 gamma+=0.4f;
3013 (void) GammaImageChannel(preview_image,DefaultChannels,gamma);
cristye7f51092010-01-17 00:39:37 +00003014 (void) FormatMagickString(label,MaxTextExtent,"gamma %g",gamma);
cristy3ed852e2009-09-05 21:47:34 +00003015 break;
3016 }
3017 case SpiffPreview:
3018 {
3019 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3020 if (preview_image != (Image *) NULL)
3021 for (x=0; x < i; x++)
3022 (void) ContrastImage(preview_image,MagickTrue);
cristye8c25f92010-06-03 00:53:06 +00003023 (void) FormatMagickString(label,MaxTextExtent,"contrast (%.20g)",
3024 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00003025 break;
3026 }
3027 case DullPreview:
3028 {
3029 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3030 if (preview_image == (Image *) NULL)
3031 break;
3032 for (x=0; x < i; x++)
3033 (void) ContrastImage(preview_image,MagickFalse);
cristye8c25f92010-06-03 00:53:06 +00003034 (void) FormatMagickString(label,MaxTextExtent,"+contrast (%.20g)",
3035 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00003036 break;
3037 }
3038 case GrayscalePreview:
3039 {
3040 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3041 if (preview_image == (Image *) NULL)
3042 break;
3043 colors<<=1;
3044 quantize_info.number_colors=colors;
3045 quantize_info.colorspace=GRAYColorspace;
3046 (void) QuantizeImage(&quantize_info,preview_image);
3047 (void) FormatMagickString(label,MaxTextExtent,
cristye8c25f92010-06-03 00:53:06 +00003048 "-colorspace gray -colors %.20g",(double) colors);
cristy3ed852e2009-09-05 21:47:34 +00003049 break;
3050 }
3051 case QuantizePreview:
3052 {
3053 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3054 if (preview_image == (Image *) NULL)
3055 break;
3056 colors<<=1;
3057 quantize_info.number_colors=colors;
3058 (void) QuantizeImage(&quantize_info,preview_image);
cristye8c25f92010-06-03 00:53:06 +00003059 (void) FormatMagickString(label,MaxTextExtent,"colors %.20g",(double)
3060 colors);
cristy3ed852e2009-09-05 21:47:34 +00003061 break;
3062 }
3063 case DespecklePreview:
3064 {
3065 for (x=0; x < (i-1); x++)
3066 {
3067 preview_image=DespeckleImage(thumbnail,exception);
3068 if (preview_image == (Image *) NULL)
3069 break;
3070 thumbnail=DestroyImage(thumbnail);
3071 thumbnail=preview_image;
3072 }
3073 preview_image=DespeckleImage(thumbnail,exception);
3074 if (preview_image == (Image *) NULL)
3075 break;
cristye8c25f92010-06-03 00:53:06 +00003076 (void) FormatMagickString(label,MaxTextExtent,"despeckle (%.20g)",
3077 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00003078 break;
3079 }
3080 case ReduceNoisePreview:
3081 {
cristy95c38342011-03-18 22:39:51 +00003082 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) radius,
3083 (size_t) radius,exception);
cristye7f51092010-01-17 00:39:37 +00003084 (void) FormatMagickString(label,MaxTextExtent,"noise %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003085 break;
3086 }
3087 case AddNoisePreview:
3088 {
3089 switch ((int) i)
3090 {
3091 case 0:
3092 {
3093 (void) CopyMagickString(factor,"uniform",MaxTextExtent);
3094 break;
3095 }
3096 case 1:
3097 {
3098 (void) CopyMagickString(factor,"gaussian",MaxTextExtent);
3099 break;
3100 }
3101 case 2:
3102 {
3103 (void) CopyMagickString(factor,"multiplicative",MaxTextExtent);
3104 break;
3105 }
3106 case 3:
3107 {
3108 (void) CopyMagickString(factor,"impulse",MaxTextExtent);
3109 break;
3110 }
3111 case 4:
3112 {
3113 (void) CopyMagickString(factor,"laplacian",MaxTextExtent);
3114 break;
3115 }
3116 case 5:
3117 {
3118 (void) CopyMagickString(factor,"Poisson",MaxTextExtent);
3119 break;
3120 }
3121 default:
3122 {
3123 (void) CopyMagickString(thumbnail->magick,"NULL",MaxTextExtent);
3124 break;
3125 }
3126 }
cristy95c38342011-03-18 22:39:51 +00003127 preview_image=StatisticImage(thumbnail,NonpeakStatistic,i,i,
cristy733678d2011-03-18 21:29:28 +00003128 exception);
cristy3ed852e2009-09-05 21:47:34 +00003129 (void) FormatMagickString(label,MaxTextExtent,"+noise %s",factor);
3130 break;
3131 }
3132 case SharpenPreview:
3133 {
3134 preview_image=SharpenImage(thumbnail,radius,sigma,exception);
cristye7f51092010-01-17 00:39:37 +00003135 (void) FormatMagickString(label,MaxTextExtent,"sharpen %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003136 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00003137 break;
3138 }
3139 case BlurPreview:
3140 {
3141 preview_image=BlurImage(thumbnail,radius,sigma,exception);
cristye7f51092010-01-17 00:39:37 +00003142 (void) FormatMagickString(label,MaxTextExtent,"blur %gx%g",radius,
cristy3ed852e2009-09-05 21:47:34 +00003143 sigma);
3144 break;
3145 }
3146 case ThresholdPreview:
3147 {
3148 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3149 if (preview_image == (Image *) NULL)
3150 break;
3151 (void) BilevelImage(thumbnail,
3152 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
cristye7f51092010-01-17 00:39:37 +00003153 (void) FormatMagickString(label,MaxTextExtent,"threshold %g",
cristy3ed852e2009-09-05 21:47:34 +00003154 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
3155 break;
3156 }
3157 case EdgeDetectPreview:
3158 {
3159 preview_image=EdgeImage(thumbnail,radius,exception);
cristye7f51092010-01-17 00:39:37 +00003160 (void) FormatMagickString(label,MaxTextExtent,"edge %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003161 break;
3162 }
3163 case SpreadPreview:
3164 {
3165 preview_image=SpreadImage(thumbnail,radius,exception);
cristye7f51092010-01-17 00:39:37 +00003166 (void) FormatMagickString(label,MaxTextExtent,"spread %g",
cristy8cd5b312010-01-07 01:10:24 +00003167 radius+0.5);
cristy3ed852e2009-09-05 21:47:34 +00003168 break;
3169 }
3170 case SolarizePreview:
3171 {
3172 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3173 if (preview_image == (Image *) NULL)
3174 break;
3175 (void) SolarizeImage(preview_image,(double) QuantumRange*
3176 percentage/100.0);
cristye7f51092010-01-17 00:39:37 +00003177 (void) FormatMagickString(label,MaxTextExtent,"solarize %g",
cristy3ed852e2009-09-05 21:47:34 +00003178 (QuantumRange*percentage)/100.0);
3179 break;
3180 }
3181 case ShadePreview:
3182 {
3183 degrees+=10.0;
3184 preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees,
3185 exception);
cristye7f51092010-01-17 00:39:37 +00003186 (void) FormatMagickString(label,MaxTextExtent,"shade %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003187 degrees,degrees);
cristy3ed852e2009-09-05 21:47:34 +00003188 break;
3189 }
3190 case RaisePreview:
3191 {
3192 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3193 if (preview_image == (Image *) NULL)
3194 break;
cristybb503372010-05-27 20:51:26 +00003195 geometry.width=(size_t) (2*i+2);
3196 geometry.height=(size_t) (2*i+2);
cristy3ed852e2009-09-05 21:47:34 +00003197 geometry.x=i/2;
3198 geometry.y=i/2;
3199 (void) RaiseImage(preview_image,&geometry,MagickTrue);
cristye8c25f92010-06-03 00:53:06 +00003200 (void) FormatMagickString(label,MaxTextExtent,
cristy6d8abba2010-06-03 01:10:47 +00003201 "raise %.20gx%.20g%+.20g%+.20g",(double) geometry.width,(double)
cristye8c25f92010-06-03 00:53:06 +00003202 geometry.height,(double) geometry.x,(double) geometry.y);
cristy3ed852e2009-09-05 21:47:34 +00003203 break;
3204 }
3205 case SegmentPreview:
3206 {
3207 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3208 if (preview_image == (Image *) NULL)
3209 break;
3210 threshold+=0.4f;
3211 (void) SegmentImage(preview_image,RGBColorspace,MagickFalse,threshold,
3212 threshold);
cristye7f51092010-01-17 00:39:37 +00003213 (void) FormatMagickString(label,MaxTextExtent,"segment %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00003214 threshold,threshold);
3215 break;
3216 }
3217 case SwirlPreview:
3218 {
3219 preview_image=SwirlImage(thumbnail,degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003220 (void) FormatMagickString(label,MaxTextExtent,"swirl %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003221 degrees+=45.0;
3222 break;
3223 }
3224 case ImplodePreview:
3225 {
3226 degrees+=0.1f;
3227 preview_image=ImplodeImage(thumbnail,degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003228 (void) FormatMagickString(label,MaxTextExtent,"implode %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003229 break;
3230 }
3231 case WavePreview:
3232 {
3233 degrees+=5.0f;
3234 preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003235 (void) FormatMagickString(label,MaxTextExtent,"wave %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003236 0.5*degrees,2.0*degrees);
cristy3ed852e2009-09-05 21:47:34 +00003237 break;
3238 }
3239 case OilPaintPreview:
3240 {
3241 preview_image=OilPaintImage(thumbnail,(double) radius,exception);
cristye7f51092010-01-17 00:39:37 +00003242 (void) FormatMagickString(label,MaxTextExtent,"paint %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003243 break;
3244 }
3245 case CharcoalDrawingPreview:
3246 {
3247 preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma,
3248 exception);
cristye7f51092010-01-17 00:39:37 +00003249 (void) FormatMagickString(label,MaxTextExtent,"charcoal %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003250 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00003251 break;
3252 }
3253 case JPEGPreview:
3254 {
3255 char
3256 filename[MaxTextExtent];
3257
3258 int
3259 file;
3260
3261 MagickBooleanType
3262 status;
3263
3264 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3265 if (preview_image == (Image *) NULL)
3266 break;
cristybb503372010-05-27 20:51:26 +00003267 preview_info->quality=(size_t) percentage;
cristye8c25f92010-06-03 00:53:06 +00003268 (void) FormatMagickString(factor,MaxTextExtent,"%.20g",(double)
3269 preview_info->quality);
cristy3ed852e2009-09-05 21:47:34 +00003270 file=AcquireUniqueFileResource(filename);
3271 if (file != -1)
3272 file=close(file)-1;
3273 (void) FormatMagickString(preview_image->filename,MaxTextExtent,
3274 "jpeg:%s",filename);
3275 status=WriteImage(preview_info,preview_image);
3276 if (status != MagickFalse)
3277 {
3278 Image
3279 *quality_image;
3280
3281 (void) CopyMagickString(preview_info->filename,
3282 preview_image->filename,MaxTextExtent);
3283 quality_image=ReadImage(preview_info,exception);
3284 if (quality_image != (Image *) NULL)
3285 {
3286 preview_image=DestroyImage(preview_image);
3287 preview_image=quality_image;
3288 }
3289 }
3290 (void) RelinquishUniqueFileResource(preview_image->filename);
3291 if ((GetBlobSize(preview_image)/1024) >= 1024)
cristye7f51092010-01-17 00:39:37 +00003292 (void) FormatMagickString(label,MaxTextExtent,"quality %s\n%gmb ",
cristy3ed852e2009-09-05 21:47:34 +00003293 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
3294 1024.0/1024.0);
3295 else
3296 if (GetBlobSize(preview_image) >= 1024)
cristy8cd5b312010-01-07 01:10:24 +00003297 (void) FormatMagickString(label,MaxTextExtent,
cristye7f51092010-01-17 00:39:37 +00003298 "quality %s\n%gkb ",factor,(double) ((MagickOffsetType)
cristy8cd5b312010-01-07 01:10:24 +00003299 GetBlobSize(preview_image))/1024.0);
cristy3ed852e2009-09-05 21:47:34 +00003300 else
cristye8c25f92010-06-03 00:53:06 +00003301 (void) FormatMagickString(label,MaxTextExtent,"quality %s\n%.20gb ",
3302 factor,(double) GetBlobSize(thumbnail));
cristy3ed852e2009-09-05 21:47:34 +00003303 break;
3304 }
3305 }
3306 thumbnail=DestroyImage(thumbnail);
3307 percentage+=12.5;
3308 radius+=0.5;
3309 sigma+=0.25;
3310 if (preview_image == (Image *) NULL)
3311 break;
3312 (void) DeleteImageProperty(preview_image,"label");
3313 (void) SetImageProperty(preview_image,"label",label);
3314 AppendImageToList(&images,preview_image);
cristybb503372010-05-27 20:51:26 +00003315 proceed=SetImageProgress(image,PreviewImageTag,(MagickOffsetType) i,
3316 NumberTiles);
cristy3ed852e2009-09-05 21:47:34 +00003317 if (proceed == MagickFalse)
3318 break;
3319 }
3320 if (images == (Image *) NULL)
3321 {
3322 preview_info=DestroyImageInfo(preview_info);
3323 return((Image *) NULL);
3324 }
3325 /*
3326 Create the montage.
3327 */
3328 montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL);
3329 (void) CopyMagickString(montage_info->filename,image->filename,MaxTextExtent);
3330 montage_info->shadow=MagickTrue;
3331 (void) CloneString(&montage_info->tile,"3x3");
3332 (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry);
3333 (void) CloneString(&montage_info->frame,DefaultTileFrame);
3334 montage_image=MontageImages(images,montage_info,exception);
3335 montage_info=DestroyMontageInfo(montage_info);
3336 images=DestroyImageList(images);
3337 if (montage_image == (Image *) NULL)
3338 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3339 if (montage_image->montage != (char *) NULL)
3340 {
3341 /*
3342 Free image directory.
3343 */
3344 montage_image->montage=(char *) RelinquishMagickMemory(
3345 montage_image->montage);
3346 if (image->directory != (char *) NULL)
3347 montage_image->directory=(char *) RelinquishMagickMemory(
3348 montage_image->directory);
3349 }
3350 preview_info=DestroyImageInfo(preview_info);
3351 return(montage_image);
3352}
3353
3354/*
3355%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3356% %
3357% %
3358% %
3359% R a d i a l B l u r I m a g e %
3360% %
3361% %
3362% %
3363%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3364%
3365% RadialBlurImage() applies a radial blur to the image.
3366%
3367% Andrew Protano contributed this effect.
3368%
3369% The format of the RadialBlurImage method is:
3370%
3371% Image *RadialBlurImage(const Image *image,const double angle,
3372% ExceptionInfo *exception)
3373% Image *RadialBlurImageChannel(const Image *image,const ChannelType channel,
3374% const double angle,ExceptionInfo *exception)
3375%
3376% A description of each parameter follows:
3377%
3378% o image: the image.
3379%
3380% o channel: the channel type.
3381%
3382% o angle: the angle of the radial blur.
3383%
3384% o exception: return any errors or warnings in this structure.
3385%
3386*/
3387
3388MagickExport Image *RadialBlurImage(const Image *image,const double angle,
3389 ExceptionInfo *exception)
3390{
3391 Image
3392 *blur_image;
3393
3394 blur_image=RadialBlurImageChannel(image,DefaultChannels,angle,exception);
3395 return(blur_image);
3396}
3397
3398MagickExport Image *RadialBlurImageChannel(const Image *image,
3399 const ChannelType channel,const double angle,ExceptionInfo *exception)
3400{
cristyc4c8d132010-01-07 01:58:38 +00003401 CacheView
3402 *blur_view,
3403 *image_view;
3404
cristy3ed852e2009-09-05 21:47:34 +00003405 Image
3406 *blur_image;
3407
cristy3ed852e2009-09-05 21:47:34 +00003408 MagickBooleanType
3409 status;
3410
cristybb503372010-05-27 20:51:26 +00003411 MagickOffsetType
3412 progress;
3413
cristy3ed852e2009-09-05 21:47:34 +00003414 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00003415 bias;
cristy3ed852e2009-09-05 21:47:34 +00003416
3417 MagickRealType
3418 blur_radius,
3419 *cos_theta,
3420 offset,
3421 *sin_theta,
3422 theta;
3423
3424 PointInfo
3425 blur_center;
3426
cristybb503372010-05-27 20:51:26 +00003427 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003428 i;
3429
cristybb503372010-05-27 20:51:26 +00003430 size_t
cristy3ed852e2009-09-05 21:47:34 +00003431 n;
3432
cristybb503372010-05-27 20:51:26 +00003433 ssize_t
3434 y;
3435
cristy3ed852e2009-09-05 21:47:34 +00003436 /*
3437 Allocate blur image.
3438 */
3439 assert(image != (Image *) NULL);
3440 assert(image->signature == MagickSignature);
3441 if (image->debug != MagickFalse)
3442 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3443 assert(exception != (ExceptionInfo *) NULL);
3444 assert(exception->signature == MagickSignature);
3445 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3446 if (blur_image == (Image *) NULL)
3447 return((Image *) NULL);
3448 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3449 {
3450 InheritException(exception,&blur_image->exception);
3451 blur_image=DestroyImage(blur_image);
3452 return((Image *) NULL);
3453 }
3454 blur_center.x=(double) image->columns/2.0;
3455 blur_center.y=(double) image->rows/2.0;
3456 blur_radius=hypot(blur_center.x,blur_center.y);
cristy117ff172010-08-15 21:35:32 +00003457 n=(size_t) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+2UL);
cristy3ed852e2009-09-05 21:47:34 +00003458 theta=DegreesToRadians(angle)/(MagickRealType) (n-1);
3459 cos_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3460 sizeof(*cos_theta));
3461 sin_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3462 sizeof(*sin_theta));
3463 if ((cos_theta == (MagickRealType *) NULL) ||
3464 (sin_theta == (MagickRealType *) NULL))
3465 {
3466 blur_image=DestroyImage(blur_image);
3467 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3468 }
3469 offset=theta*(MagickRealType) (n-1)/2.0;
cristybb503372010-05-27 20:51:26 +00003470 for (i=0; i < (ssize_t) n; i++)
cristy3ed852e2009-09-05 21:47:34 +00003471 {
3472 cos_theta[i]=cos((double) (theta*i-offset));
3473 sin_theta[i]=sin((double) (theta*i-offset));
3474 }
3475 /*
3476 Radial blur image.
3477 */
3478 status=MagickTrue;
3479 progress=0;
cristyddd82202009-11-03 20:14:50 +00003480 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003481 image_view=AcquireCacheView(image);
3482 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003483#if defined(MAGICKCORE_OPENMP_SUPPORT)
3484 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003485#endif
cristybb503372010-05-27 20:51:26 +00003486 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003487 {
3488 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003489 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003490
3491 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003492 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00003493
cristy3ed852e2009-09-05 21:47:34 +00003494 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003495 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003496
cristy117ff172010-08-15 21:35:32 +00003497 register ssize_t
3498 x;
3499
cristy3ed852e2009-09-05 21:47:34 +00003500 if (status == MagickFalse)
3501 continue;
3502 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3503 exception);
3504 if (q == (PixelPacket *) NULL)
3505 {
3506 status=MagickFalse;
3507 continue;
3508 }
3509 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
cristybb503372010-05-27 20:51:26 +00003510 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003511 {
3512 MagickPixelPacket
3513 qixel;
3514
3515 MagickRealType
3516 normalize,
3517 radius;
3518
3519 PixelPacket
3520 pixel;
3521
3522 PointInfo
3523 center;
3524
cristybb503372010-05-27 20:51:26 +00003525 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003526 i;
3527
cristybb503372010-05-27 20:51:26 +00003528 size_t
cristy3ed852e2009-09-05 21:47:34 +00003529 step;
3530
3531 center.x=(double) x-blur_center.x;
3532 center.y=(double) y-blur_center.y;
3533 radius=hypot((double) center.x,center.y);
3534 if (radius == 0)
3535 step=1;
3536 else
3537 {
cristybb503372010-05-27 20:51:26 +00003538 step=(size_t) (blur_radius/radius);
cristy3ed852e2009-09-05 21:47:34 +00003539 if (step == 0)
3540 step=1;
3541 else
3542 if (step >= n)
3543 step=n-1;
3544 }
3545 normalize=0.0;
cristyddd82202009-11-03 20:14:50 +00003546 qixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00003547 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
3548 {
cristyeaedf062010-05-29 22:36:02 +00003549 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00003550 {
cristyeaedf062010-05-29 22:36:02 +00003551 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
3552 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
3553 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
3554 cos_theta[i]+0.5),&pixel,exception);
cristy3ed852e2009-09-05 21:47:34 +00003555 qixel.red+=pixel.red;
3556 qixel.green+=pixel.green;
3557 qixel.blue+=pixel.blue;
3558 qixel.opacity+=pixel.opacity;
3559 if (image->colorspace == CMYKColorspace)
3560 {
3561 indexes=GetCacheViewVirtualIndexQueue(image_view);
3562 qixel.index+=(*indexes);
3563 }
3564 normalize+=1.0;
3565 }
3566 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
3567 normalize);
3568 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003569 q->red=ClampToQuantum(normalize*qixel.red);
cristy3ed852e2009-09-05 21:47:34 +00003570 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003571 q->green=ClampToQuantum(normalize*qixel.green);
cristy3ed852e2009-09-05 21:47:34 +00003572 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003573 q->blue=ClampToQuantum(normalize*qixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00003574 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003575 q->opacity=ClampToQuantum(normalize*qixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00003576 if (((channel & IndexChannel) != 0) &&
3577 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00003578 blur_indexes[x]=(IndexPacket) ClampToQuantum(normalize*qixel.index);
cristy3ed852e2009-09-05 21:47:34 +00003579 }
3580 else
3581 {
3582 MagickRealType
3583 alpha,
3584 gamma;
3585
3586 alpha=1.0;
3587 gamma=0.0;
cristyeaedf062010-05-29 22:36:02 +00003588 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00003589 {
cristyeaedf062010-05-29 22:36:02 +00003590 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
3591 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
3592 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
3593 cos_theta[i]+0.5),&pixel,exception);
cristy46f08202010-01-10 04:04:21 +00003594 alpha=(MagickRealType) (QuantumScale*
3595 GetAlphaPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00003596 qixel.red+=alpha*pixel.red;
3597 qixel.green+=alpha*pixel.green;
3598 qixel.blue+=alpha*pixel.blue;
3599 qixel.opacity+=pixel.opacity;
3600 if (image->colorspace == CMYKColorspace)
3601 {
3602 indexes=GetCacheViewVirtualIndexQueue(image_view);
3603 qixel.index+=alpha*(*indexes);
3604 }
3605 gamma+=alpha;
3606 normalize+=1.0;
3607 }
3608 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3609 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
3610 normalize);
3611 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003612 q->red=ClampToQuantum(gamma*qixel.red);
cristy3ed852e2009-09-05 21:47:34 +00003613 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003614 q->green=ClampToQuantum(gamma*qixel.green);
cristy3ed852e2009-09-05 21:47:34 +00003615 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003616 q->blue=ClampToQuantum(gamma*qixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00003617 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003618 q->opacity=ClampToQuantum(normalize*qixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00003619 if (((channel & IndexChannel) != 0) &&
3620 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00003621 blur_indexes[x]=(IndexPacket) ClampToQuantum(gamma*qixel.index);
cristy3ed852e2009-09-05 21:47:34 +00003622 }
3623 q++;
3624 }
3625 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
3626 status=MagickFalse;
3627 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3628 {
3629 MagickBooleanType
3630 proceed;
3631
cristyb5d5f722009-11-04 03:03:49 +00003632#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003633 #pragma omp critical (MagickCore_RadialBlurImageChannel)
3634#endif
3635 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
3636 if (proceed == MagickFalse)
3637 status=MagickFalse;
3638 }
3639 }
3640 blur_view=DestroyCacheView(blur_view);
3641 image_view=DestroyCacheView(image_view);
3642 cos_theta=(MagickRealType *) RelinquishMagickMemory(cos_theta);
3643 sin_theta=(MagickRealType *) RelinquishMagickMemory(sin_theta);
3644 if (status == MagickFalse)
3645 blur_image=DestroyImage(blur_image);
3646 return(blur_image);
3647}
3648
3649/*
3650%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3651% %
3652% %
3653% %
cristy3ed852e2009-09-05 21:47:34 +00003654% S e l e c t i v e B l u r I m a g e %
3655% %
3656% %
3657% %
3658%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3659%
3660% SelectiveBlurImage() selectively blur pixels within a contrast threshold.
3661% It is similar to the unsharpen mask that sharpens everything with contrast
3662% above a certain threshold.
3663%
3664% The format of the SelectiveBlurImage method is:
3665%
3666% Image *SelectiveBlurImage(const Image *image,const double radius,
3667% const double sigma,const double threshold,ExceptionInfo *exception)
3668% Image *SelectiveBlurImageChannel(const Image *image,
3669% const ChannelType channel,const double radius,const double sigma,
3670% const double threshold,ExceptionInfo *exception)
3671%
3672% A description of each parameter follows:
3673%
3674% o image: the image.
3675%
3676% o channel: the channel type.
3677%
3678% o radius: the radius of the Gaussian, in pixels, not counting the center
3679% pixel.
3680%
3681% o sigma: the standard deviation of the Gaussian, in pixels.
3682%
3683% o threshold: only pixels within this contrast threshold are included
3684% in the blur operation.
3685%
3686% o exception: return any errors or warnings in this structure.
3687%
3688*/
3689
3690static inline MagickBooleanType SelectiveContrast(const PixelPacket *p,
3691 const PixelPacket *q,const double threshold)
3692{
3693 if (fabs(PixelIntensity(p)-PixelIntensity(q)) < threshold)
3694 return(MagickTrue);
3695 return(MagickFalse);
3696}
3697
3698MagickExport Image *SelectiveBlurImage(const Image *image,const double radius,
3699 const double sigma,const double threshold,ExceptionInfo *exception)
3700{
3701 Image
3702 *blur_image;
3703
3704 blur_image=SelectiveBlurImageChannel(image,DefaultChannels,radius,sigma,
3705 threshold,exception);
3706 return(blur_image);
3707}
3708
3709MagickExport Image *SelectiveBlurImageChannel(const Image *image,
3710 const ChannelType channel,const double radius,const double sigma,
3711 const double threshold,ExceptionInfo *exception)
3712{
3713#define SelectiveBlurImageTag "SelectiveBlur/Image"
3714
cristy47e00502009-12-17 19:19:57 +00003715 CacheView
3716 *blur_view,
3717 *image_view;
3718
cristy3ed852e2009-09-05 21:47:34 +00003719 double
cristy3ed852e2009-09-05 21:47:34 +00003720 *kernel;
3721
3722 Image
3723 *blur_image;
3724
cristy3ed852e2009-09-05 21:47:34 +00003725 MagickBooleanType
3726 status;
3727
cristybb503372010-05-27 20:51:26 +00003728 MagickOffsetType
3729 progress;
3730
cristy3ed852e2009-09-05 21:47:34 +00003731 MagickPixelPacket
cristy3ed852e2009-09-05 21:47:34 +00003732 bias;
3733
cristybb503372010-05-27 20:51:26 +00003734 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003735 i;
cristy3ed852e2009-09-05 21:47:34 +00003736
cristybb503372010-05-27 20:51:26 +00003737 size_t
cristy3ed852e2009-09-05 21:47:34 +00003738 width;
3739
cristybb503372010-05-27 20:51:26 +00003740 ssize_t
3741 j,
3742 u,
3743 v,
3744 y;
3745
cristy3ed852e2009-09-05 21:47:34 +00003746 /*
3747 Initialize blur image attributes.
3748 */
3749 assert(image != (Image *) NULL);
3750 assert(image->signature == MagickSignature);
3751 if (image->debug != MagickFalse)
3752 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3753 assert(exception != (ExceptionInfo *) NULL);
3754 assert(exception->signature == MagickSignature);
3755 width=GetOptimalKernelWidth1D(radius,sigma);
3756 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
3757 if (kernel == (double *) NULL)
3758 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00003759 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00003760 i=0;
cristy47e00502009-12-17 19:19:57 +00003761 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00003762 {
cristy47e00502009-12-17 19:19:57 +00003763 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00003764 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
3765 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00003766 }
3767 if (image->debug != MagickFalse)
3768 {
3769 char
3770 format[MaxTextExtent],
3771 *message;
3772
cristy117ff172010-08-15 21:35:32 +00003773 register const double
3774 *k;
3775
cristybb503372010-05-27 20:51:26 +00003776 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003777 u,
3778 v;
3779
cristy3ed852e2009-09-05 21:47:34 +00003780 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00003781 " SelectiveBlurImage with %.20gx%.20g kernel:",(double) width,(double)
3782 width);
cristy3ed852e2009-09-05 21:47:34 +00003783 message=AcquireString("");
3784 k=kernel;
cristybb503372010-05-27 20:51:26 +00003785 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003786 {
3787 *message='\0';
cristye8c25f92010-06-03 00:53:06 +00003788 (void) FormatMagickString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy3ed852e2009-09-05 21:47:34 +00003789 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00003790 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003791 {
3792 (void) FormatMagickString(format,MaxTextExtent,"%+f ",*k++);
3793 (void) ConcatenateString(&message,format);
3794 }
3795 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
3796 }
3797 message=DestroyString(message);
3798 }
3799 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3800 if (blur_image == (Image *) NULL)
3801 return((Image *) NULL);
3802 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3803 {
3804 InheritException(exception,&blur_image->exception);
3805 blur_image=DestroyImage(blur_image);
3806 return((Image *) NULL);
3807 }
3808 /*
3809 Threshold blur image.
3810 */
3811 status=MagickTrue;
3812 progress=0;
cristyddd82202009-11-03 20:14:50 +00003813 GetMagickPixelPacket(image,&bias);
3814 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003815 image_view=AcquireCacheView(image);
3816 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003817#if defined(MAGICKCORE_OPENMP_SUPPORT)
3818 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003819#endif
cristybb503372010-05-27 20:51:26 +00003820 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003821 {
3822 MagickBooleanType
3823 sync;
3824
3825 MagickRealType
3826 gamma;
3827
3828 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003829 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003830
3831 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003832 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003833
3834 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003835 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00003836
cristy3ed852e2009-09-05 21:47:34 +00003837 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003838 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003839
cristy117ff172010-08-15 21:35:32 +00003840 register ssize_t
3841 x;
3842
cristy3ed852e2009-09-05 21:47:34 +00003843 if (status == MagickFalse)
3844 continue;
cristy117ff172010-08-15 21:35:32 +00003845 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
3846 (width/2L),image->columns+width,width,exception);
cristy3ed852e2009-09-05 21:47:34 +00003847 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3848 exception);
3849 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
3850 {
3851 status=MagickFalse;
3852 continue;
3853 }
3854 indexes=GetCacheViewVirtualIndexQueue(image_view);
3855 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
cristybb503372010-05-27 20:51:26 +00003856 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003857 {
cristy3ed852e2009-09-05 21:47:34 +00003858 MagickPixelPacket
3859 pixel;
3860
3861 register const double
cristyc47d1f82009-11-26 01:44:43 +00003862 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00003863
cristybb503372010-05-27 20:51:26 +00003864 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003865 u;
3866
cristy117ff172010-08-15 21:35:32 +00003867 ssize_t
3868 j,
3869 v;
3870
cristyddd82202009-11-03 20:14:50 +00003871 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00003872 k=kernel;
3873 gamma=0.0;
3874 j=0;
3875 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
3876 {
cristybb503372010-05-27 20:51:26 +00003877 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003878 {
cristybb503372010-05-27 20:51:26 +00003879 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003880 {
3881 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
3882 {
3883 pixel.red+=(*k)*(p+u+j)->red;
3884 pixel.green+=(*k)*(p+u+j)->green;
3885 pixel.blue+=(*k)*(p+u+j)->blue;
3886 gamma+=(*k);
3887 k++;
3888 }
3889 }
cristyd99b0962010-05-29 23:14:26 +00003890 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003891 }
3892 if (gamma != 0.0)
3893 {
3894 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3895 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003896 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00003897 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003898 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00003899 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003900 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00003901 }
3902 if ((channel & OpacityChannel) != 0)
3903 {
3904 gamma=0.0;
3905 j=0;
cristybb503372010-05-27 20:51:26 +00003906 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003907 {
cristybb503372010-05-27 20:51:26 +00003908 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003909 {
3910 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
3911 {
3912 pixel.opacity+=(*k)*(p+u+j)->opacity;
3913 gamma+=(*k);
3914 k++;
3915 }
3916 }
cristyeaedf062010-05-29 22:36:02 +00003917 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003918 }
3919 if (gamma != 0.0)
3920 {
3921 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
3922 gamma);
cristyce70c172010-01-07 17:15:30 +00003923 SetOpacityPixelComponent(q,ClampToQuantum(gamma*
3924 GetOpacityPixelComponent(&pixel)));
cristy3ed852e2009-09-05 21:47:34 +00003925 }
3926 }
3927 if (((channel & IndexChannel) != 0) &&
3928 (image->colorspace == CMYKColorspace))
3929 {
3930 gamma=0.0;
3931 j=0;
cristybb503372010-05-27 20:51:26 +00003932 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003933 {
cristybb503372010-05-27 20:51:26 +00003934 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003935 {
3936 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
3937 {
3938 pixel.index+=(*k)*indexes[x+u+j];
3939 gamma+=(*k);
3940 k++;
3941 }
3942 }
cristyeaedf062010-05-29 22:36:02 +00003943 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003944 }
3945 if (gamma != 0.0)
3946 {
3947 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
3948 gamma);
cristy6db48122010-01-11 00:18:07 +00003949 blur_indexes[x]=ClampToQuantum(gamma*
3950 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00003951 }
3952 }
3953 }
3954 else
3955 {
3956 MagickRealType
3957 alpha;
3958
cristybb503372010-05-27 20:51:26 +00003959 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003960 {
cristybb503372010-05-27 20:51:26 +00003961 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003962 {
3963 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
3964 {
cristy46f08202010-01-10 04:04:21 +00003965 alpha=(MagickRealType) (QuantumScale*
3966 GetAlphaPixelComponent(p+u+j));
cristy3ed852e2009-09-05 21:47:34 +00003967 pixel.red+=(*k)*alpha*(p+u+j)->red;
3968 pixel.green+=(*k)*alpha*(p+u+j)->green;
3969 pixel.blue+=(*k)*alpha*(p+u+j)->blue;
3970 pixel.opacity+=(*k)*(p+u+j)->opacity;
3971 gamma+=(*k)*alpha;
3972 k++;
3973 }
3974 }
cristyeaedf062010-05-29 22:36:02 +00003975 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003976 }
3977 if (gamma != 0.0)
3978 {
3979 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3980 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003981 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00003982 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003983 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00003984 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003985 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00003986 }
3987 if ((channel & OpacityChannel) != 0)
3988 {
3989 gamma=0.0;
3990 j=0;
cristybb503372010-05-27 20:51:26 +00003991 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003992 {
cristybb503372010-05-27 20:51:26 +00003993 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003994 {
3995 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
3996 {
3997 pixel.opacity+=(*k)*(p+u+j)->opacity;
3998 gamma+=(*k);
3999 k++;
4000 }
4001 }
cristyeaedf062010-05-29 22:36:02 +00004002 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00004003 }
4004 if (gamma != 0.0)
4005 {
4006 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4007 gamma);
cristy6db48122010-01-11 00:18:07 +00004008 SetOpacityPixelComponent(q,
4009 ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004010 }
4011 }
4012 if (((channel & IndexChannel) != 0) &&
4013 (image->colorspace == CMYKColorspace))
4014 {
4015 gamma=0.0;
4016 j=0;
cristybb503372010-05-27 20:51:26 +00004017 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00004018 {
cristybb503372010-05-27 20:51:26 +00004019 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00004020 {
4021 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4022 {
cristy46f08202010-01-10 04:04:21 +00004023 alpha=(MagickRealType) (QuantumScale*
4024 GetAlphaPixelComponent(p+u+j));
cristy3ed852e2009-09-05 21:47:34 +00004025 pixel.index+=(*k)*alpha*indexes[x+u+j];
4026 gamma+=(*k);
4027 k++;
4028 }
4029 }
cristyeaedf062010-05-29 22:36:02 +00004030 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00004031 }
4032 if (gamma != 0.0)
4033 {
4034 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4035 gamma);
cristy6db48122010-01-11 00:18:07 +00004036 blur_indexes[x]=ClampToQuantum(gamma*
4037 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004038 }
4039 }
4040 }
4041 p++;
4042 q++;
4043 }
4044 sync=SyncCacheViewAuthenticPixels(blur_view,exception);
4045 if (sync == MagickFalse)
4046 status=MagickFalse;
4047 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4048 {
4049 MagickBooleanType
4050 proceed;
4051
cristyb5d5f722009-11-04 03:03:49 +00004052#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004053 #pragma omp critical (MagickCore_SelectiveBlurImageChannel)
4054#endif
4055 proceed=SetImageProgress(image,SelectiveBlurImageTag,progress++,
4056 image->rows);
4057 if (proceed == MagickFalse)
4058 status=MagickFalse;
4059 }
4060 }
4061 blur_image->type=image->type;
4062 blur_view=DestroyCacheView(blur_view);
4063 image_view=DestroyCacheView(image_view);
4064 kernel=(double *) RelinquishMagickMemory(kernel);
4065 if (status == MagickFalse)
4066 blur_image=DestroyImage(blur_image);
4067 return(blur_image);
4068}
4069
4070/*
4071%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4072% %
4073% %
4074% %
4075% S h a d e I m a g e %
4076% %
4077% %
4078% %
4079%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4080%
4081% ShadeImage() shines a distant light on an image to create a
4082% three-dimensional effect. You control the positioning of the light with
4083% azimuth and elevation; azimuth is measured in degrees off the x axis
4084% and elevation is measured in pixels above the Z axis.
4085%
4086% The format of the ShadeImage method is:
4087%
4088% Image *ShadeImage(const Image *image,const MagickBooleanType gray,
4089% const double azimuth,const double elevation,ExceptionInfo *exception)
4090%
4091% A description of each parameter follows:
4092%
4093% o image: the image.
4094%
4095% o gray: A value other than zero shades the intensity of each pixel.
4096%
4097% o azimuth, elevation: Define the light source direction.
4098%
4099% o exception: return any errors or warnings in this structure.
4100%
4101*/
4102MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray,
4103 const double azimuth,const double elevation,ExceptionInfo *exception)
4104{
4105#define ShadeImageTag "Shade/Image"
4106
cristyc4c8d132010-01-07 01:58:38 +00004107 CacheView
4108 *image_view,
4109 *shade_view;
4110
cristy3ed852e2009-09-05 21:47:34 +00004111 Image
4112 *shade_image;
4113
cristy3ed852e2009-09-05 21:47:34 +00004114 MagickBooleanType
4115 status;
4116
cristybb503372010-05-27 20:51:26 +00004117 MagickOffsetType
4118 progress;
4119
cristy3ed852e2009-09-05 21:47:34 +00004120 PrimaryInfo
4121 light;
4122
cristybb503372010-05-27 20:51:26 +00004123 ssize_t
4124 y;
4125
cristy3ed852e2009-09-05 21:47:34 +00004126 /*
4127 Initialize shaded image attributes.
4128 */
4129 assert(image != (const Image *) NULL);
4130 assert(image->signature == MagickSignature);
4131 if (image->debug != MagickFalse)
4132 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4133 assert(exception != (ExceptionInfo *) NULL);
4134 assert(exception->signature == MagickSignature);
4135 shade_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
4136 if (shade_image == (Image *) NULL)
4137 return((Image *) NULL);
4138 if (SetImageStorageClass(shade_image,DirectClass) == MagickFalse)
4139 {
4140 InheritException(exception,&shade_image->exception);
4141 shade_image=DestroyImage(shade_image);
4142 return((Image *) NULL);
4143 }
4144 /*
4145 Compute the light vector.
4146 */
4147 light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))*
4148 cos(DegreesToRadians(elevation));
4149 light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))*
4150 cos(DegreesToRadians(elevation));
4151 light.z=(double) QuantumRange*sin(DegreesToRadians(elevation));
4152 /*
4153 Shade image.
4154 */
4155 status=MagickTrue;
4156 progress=0;
4157 image_view=AcquireCacheView(image);
4158 shade_view=AcquireCacheView(shade_image);
cristyb5d5f722009-11-04 03:03:49 +00004159#if defined(MAGICKCORE_OPENMP_SUPPORT)
4160 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004161#endif
cristybb503372010-05-27 20:51:26 +00004162 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00004163 {
4164 MagickRealType
4165 distance,
4166 normal_distance,
4167 shade;
4168
4169 PrimaryInfo
4170 normal;
4171
4172 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004173 *restrict p,
4174 *restrict s0,
4175 *restrict s1,
4176 *restrict s2;
cristy3ed852e2009-09-05 21:47:34 +00004177
cristy3ed852e2009-09-05 21:47:34 +00004178 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004179 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004180
cristy117ff172010-08-15 21:35:32 +00004181 register ssize_t
4182 x;
4183
cristy3ed852e2009-09-05 21:47:34 +00004184 if (status == MagickFalse)
4185 continue;
4186 p=GetCacheViewVirtualPixels(image_view,-1,y-1,image->columns+2,3,exception);
4187 q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1,
4188 exception);
4189 if ((p == (PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
4190 {
4191 status=MagickFalse;
4192 continue;
4193 }
4194 /*
4195 Shade this row of pixels.
4196 */
4197 normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */
4198 s0=p+1;
4199 s1=s0+image->columns+2;
4200 s2=s1+image->columns+2;
cristybb503372010-05-27 20:51:26 +00004201 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004202 {
4203 /*
4204 Determine the surface normal and compute shading.
4205 */
4206 normal.x=(double) (PixelIntensity(s0-1)+PixelIntensity(s1-1)+
4207 PixelIntensity(s2-1)-PixelIntensity(s0+1)-PixelIntensity(s1+1)-
4208 PixelIntensity(s2+1));
4209 normal.y=(double) (PixelIntensity(s2-1)+PixelIntensity(s2)+
4210 PixelIntensity(s2+1)-PixelIntensity(s0-1)-PixelIntensity(s0)-
4211 PixelIntensity(s0+1));
4212 if ((normal.x == 0.0) && (normal.y == 0.0))
4213 shade=light.z;
4214 else
4215 {
4216 shade=0.0;
4217 distance=normal.x*light.x+normal.y*light.y+normal.z*light.z;
4218 if (distance > MagickEpsilon)
4219 {
4220 normal_distance=
4221 normal.x*normal.x+normal.y*normal.y+normal.z*normal.z;
4222 if (normal_distance > (MagickEpsilon*MagickEpsilon))
4223 shade=distance/sqrt((double) normal_distance);
4224 }
4225 }
4226 if (gray != MagickFalse)
4227 {
4228 q->red=(Quantum) shade;
4229 q->green=(Quantum) shade;
4230 q->blue=(Quantum) shade;
4231 }
4232 else
4233 {
cristyce70c172010-01-07 17:15:30 +00004234 q->red=ClampToQuantum(QuantumScale*shade*s1->red);
4235 q->green=ClampToQuantum(QuantumScale*shade*s1->green);
4236 q->blue=ClampToQuantum(QuantumScale*shade*s1->blue);
cristy3ed852e2009-09-05 21:47:34 +00004237 }
4238 q->opacity=s1->opacity;
4239 s0++;
4240 s1++;
4241 s2++;
4242 q++;
4243 }
4244 if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
4245 status=MagickFalse;
4246 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4247 {
4248 MagickBooleanType
4249 proceed;
4250
cristyb5d5f722009-11-04 03:03:49 +00004251#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004252 #pragma omp critical (MagickCore_ShadeImage)
4253#endif
4254 proceed=SetImageProgress(image,ShadeImageTag,progress++,image->rows);
4255 if (proceed == MagickFalse)
4256 status=MagickFalse;
4257 }
4258 }
4259 shade_view=DestroyCacheView(shade_view);
4260 image_view=DestroyCacheView(image_view);
4261 if (status == MagickFalse)
4262 shade_image=DestroyImage(shade_image);
4263 return(shade_image);
4264}
4265
4266/*
4267%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4268% %
4269% %
4270% %
4271% S h a r p e n I m a g e %
4272% %
4273% %
4274% %
4275%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4276%
4277% SharpenImage() sharpens the image. We convolve the image with a Gaussian
4278% operator of the given radius and standard deviation (sigma). For
4279% reasonable results, radius should be larger than sigma. Use a radius of 0
4280% and SharpenImage() selects a suitable radius for you.
4281%
4282% Using a separable kernel would be faster, but the negative weights cancel
4283% out on the corners of the kernel producing often undesirable ringing in the
4284% filtered result; this can be avoided by using a 2D gaussian shaped image
4285% sharpening kernel instead.
4286%
4287% The format of the SharpenImage method is:
4288%
4289% Image *SharpenImage(const Image *image,const double radius,
4290% const double sigma,ExceptionInfo *exception)
4291% Image *SharpenImageChannel(const Image *image,const ChannelType channel,
4292% const double radius,const double sigma,ExceptionInfo *exception)
4293%
4294% A description of each parameter follows:
4295%
4296% o image: the image.
4297%
4298% o channel: the channel type.
4299%
4300% o radius: the radius of the Gaussian, in pixels, not counting the center
4301% pixel.
4302%
4303% o sigma: the standard deviation of the Laplacian, in pixels.
4304%
4305% o exception: return any errors or warnings in this structure.
4306%
4307*/
4308
4309MagickExport Image *SharpenImage(const Image *image,const double radius,
4310 const double sigma,ExceptionInfo *exception)
4311{
4312 Image
4313 *sharp_image;
4314
4315 sharp_image=SharpenImageChannel(image,DefaultChannels,radius,sigma,exception);
4316 return(sharp_image);
4317}
4318
4319MagickExport Image *SharpenImageChannel(const Image *image,
4320 const ChannelType channel,const double radius,const double sigma,
4321 ExceptionInfo *exception)
4322{
4323 double
cristy47e00502009-12-17 19:19:57 +00004324 *kernel,
4325 normalize;
cristy3ed852e2009-09-05 21:47:34 +00004326
4327 Image
4328 *sharp_image;
4329
cristybb503372010-05-27 20:51:26 +00004330 register ssize_t
cristy47e00502009-12-17 19:19:57 +00004331 i;
4332
cristybb503372010-05-27 20:51:26 +00004333 size_t
cristy3ed852e2009-09-05 21:47:34 +00004334 width;
4335
cristy117ff172010-08-15 21:35:32 +00004336 ssize_t
4337 j,
4338 u,
4339 v;
4340
cristy3ed852e2009-09-05 21:47:34 +00004341 assert(image != (const Image *) NULL);
4342 assert(image->signature == MagickSignature);
4343 if (image->debug != MagickFalse)
4344 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4345 assert(exception != (ExceptionInfo *) NULL);
4346 assert(exception->signature == MagickSignature);
4347 width=GetOptimalKernelWidth2D(radius,sigma);
4348 kernel=(double *) AcquireQuantumMemory((size_t) width*width,sizeof(*kernel));
4349 if (kernel == (double *) NULL)
4350 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy3ed852e2009-09-05 21:47:34 +00004351 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00004352 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +00004353 i=0;
4354 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00004355 {
cristy47e00502009-12-17 19:19:57 +00004356 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00004357 {
cristy4205a3c2010-09-12 20:19:59 +00004358 kernel[i]=(double) (-exp(-((double) u*u+v*v)/(2.0*MagickSigma*
4359 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00004360 normalize+=kernel[i];
4361 i++;
4362 }
4363 }
4364 kernel[i/2]=(double) ((-2.0)*normalize);
4365 sharp_image=ConvolveImageChannel(image,channel,width,kernel,exception);
4366 kernel=(double *) RelinquishMagickMemory(kernel);
4367 return(sharp_image);
4368}
4369
4370/*
4371%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4372% %
4373% %
4374% %
4375% S p r e a d I m a g e %
4376% %
4377% %
4378% %
4379%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4380%
4381% SpreadImage() is a special effects method that randomly displaces each
4382% pixel in a block defined by the radius parameter.
4383%
4384% The format of the SpreadImage method is:
4385%
4386% Image *SpreadImage(const Image *image,const double radius,
4387% ExceptionInfo *exception)
4388%
4389% A description of each parameter follows:
4390%
4391% o image: the image.
4392%
4393% o radius: Choose a random pixel in a neighborhood of this extent.
4394%
4395% o exception: return any errors or warnings in this structure.
4396%
4397*/
4398MagickExport Image *SpreadImage(const Image *image,const double radius,
4399 ExceptionInfo *exception)
4400{
4401#define SpreadImageTag "Spread/Image"
4402
cristyfa112112010-01-04 17:48:07 +00004403 CacheView
4404 *image_view;
4405
cristy3ed852e2009-09-05 21:47:34 +00004406 Image
4407 *spread_image;
4408
cristy3ed852e2009-09-05 21:47:34 +00004409 MagickBooleanType
4410 status;
4411
cristybb503372010-05-27 20:51:26 +00004412 MagickOffsetType
4413 progress;
4414
cristy3ed852e2009-09-05 21:47:34 +00004415 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00004416 bias;
cristy3ed852e2009-09-05 21:47:34 +00004417
4418 RandomInfo
cristyfa112112010-01-04 17:48:07 +00004419 **restrict random_info;
cristy3ed852e2009-09-05 21:47:34 +00004420
4421 ResampleFilter
cristyfa112112010-01-04 17:48:07 +00004422 **restrict resample_filter;
cristy3ed852e2009-09-05 21:47:34 +00004423
cristybb503372010-05-27 20:51:26 +00004424 size_t
cristy3ed852e2009-09-05 21:47:34 +00004425 width;
4426
cristybb503372010-05-27 20:51:26 +00004427 ssize_t
4428 y;
4429
cristy3ed852e2009-09-05 21:47:34 +00004430 /*
4431 Initialize spread image attributes.
4432 */
4433 assert(image != (Image *) NULL);
4434 assert(image->signature == MagickSignature);
4435 if (image->debug != MagickFalse)
4436 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4437 assert(exception != (ExceptionInfo *) NULL);
4438 assert(exception->signature == MagickSignature);
4439 spread_image=CloneImage(image,image->columns,image->rows,MagickTrue,
4440 exception);
4441 if (spread_image == (Image *) NULL)
4442 return((Image *) NULL);
4443 if (SetImageStorageClass(spread_image,DirectClass) == MagickFalse)
4444 {
4445 InheritException(exception,&spread_image->exception);
4446 spread_image=DestroyImage(spread_image);
4447 return((Image *) NULL);
4448 }
4449 /*
4450 Spread image.
4451 */
4452 status=MagickTrue;
4453 progress=0;
cristyddd82202009-11-03 20:14:50 +00004454 GetMagickPixelPacket(spread_image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00004455 width=GetOptimalKernelWidth1D(radius,0.5);
cristyb2a11ae2010-02-22 00:53:36 +00004456 resample_filter=AcquireResampleFilterThreadSet(image,
4457 UndefinedVirtualPixelMethod,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00004458 random_info=AcquireRandomInfoThreadSet();
4459 image_view=AcquireCacheView(spread_image);
cristyb557a152011-02-22 12:14:30 +00004460#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00004461 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00004462#endif
cristybb503372010-05-27 20:51:26 +00004463 for (y=0; y < (ssize_t) spread_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00004464 {
cristy5c9e6f22010-09-17 17:31:01 +00004465 const int
4466 id = GetOpenMPThreadId();
cristy6ebe97c2010-07-03 01:17:28 +00004467
cristy3ed852e2009-09-05 21:47:34 +00004468 MagickPixelPacket
4469 pixel;
4470
4471 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004472 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00004473
cristy3ed852e2009-09-05 21:47:34 +00004474 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004475 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004476
cristy117ff172010-08-15 21:35:32 +00004477 register ssize_t
4478 x;
4479
cristy3ed852e2009-09-05 21:47:34 +00004480 if (status == MagickFalse)
4481 continue;
4482 q=QueueCacheViewAuthenticPixels(image_view,0,y,spread_image->columns,1,
4483 exception);
4484 if (q == (PixelPacket *) NULL)
4485 {
4486 status=MagickFalse;
4487 continue;
4488 }
4489 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristyddd82202009-11-03 20:14:50 +00004490 pixel=bias;
cristybb503372010-05-27 20:51:26 +00004491 for (x=0; x < (ssize_t) spread_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004492 {
4493 (void) ResamplePixelColor(resample_filter[id],(double) x+width*
4494 (GetPseudoRandomValue(random_info[id])-0.5),(double) y+width*
4495 (GetPseudoRandomValue(random_info[id])-0.5),&pixel);
4496 SetPixelPacket(spread_image,&pixel,q,indexes+x);
4497 q++;
4498 }
4499 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
4500 status=MagickFalse;
4501 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4502 {
4503 MagickBooleanType
4504 proceed;
4505
cristyb557a152011-02-22 12:14:30 +00004506#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004507 #pragma omp critical (MagickCore_SpreadImage)
4508#endif
4509 proceed=SetImageProgress(image,SpreadImageTag,progress++,image->rows);
4510 if (proceed == MagickFalse)
4511 status=MagickFalse;
4512 }
4513 }
4514 image_view=DestroyCacheView(image_view);
4515 random_info=DestroyRandomInfoThreadSet(random_info);
4516 resample_filter=DestroyResampleFilterThreadSet(resample_filter);
4517 return(spread_image);
4518}
4519
4520/*
4521%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4522% %
4523% %
4524% %
cristy0834d642011-03-18 18:26:08 +00004525% S t a t i s t i c I m a g e %
4526% %
4527% %
4528% %
4529%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4530%
4531% StatisticImage() makes each pixel the min / max / median / mode / etc. of
cristy8d752042011-03-19 01:00:36 +00004532% the neighborhood of the specified width and height.
cristy0834d642011-03-18 18:26:08 +00004533%
4534% The format of the StatisticImage method is:
4535%
4536% Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00004537% const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00004538% Image *StatisticImageChannel(const Image *image,
4539% const ChannelType channel,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00004540% const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00004541%
4542% A description of each parameter follows:
4543%
4544% o image: the image.
4545%
4546% o channel: the image channel.
4547%
4548% o type: the statistic type (median, mode, etc.).
4549%
cristy95c38342011-03-18 22:39:51 +00004550% o width: the width of the pixel neighborhood.
4551%
4552% o height: the height of the pixel neighborhood.
cristy0834d642011-03-18 18:26:08 +00004553%
4554% o exception: return any errors or warnings in this structure.
4555%
4556*/
4557
cristy733678d2011-03-18 21:29:28 +00004558#define ListChannels 5
4559
4560typedef struct _ListNode
4561{
4562 size_t
4563 next[9],
4564 count,
4565 signature;
4566} ListNode;
4567
4568typedef struct _SkipList
4569{
4570 ssize_t
4571 level;
4572
4573 ListNode
4574 *nodes;
4575} SkipList;
4576
4577typedef struct _PixelList
4578{
4579 size_t
cristy6fc86bb2011-03-18 23:45:16 +00004580 length,
cristy733678d2011-03-18 21:29:28 +00004581 seed,
4582 signature;
4583
4584 SkipList
4585 lists[ListChannels];
4586} PixelList;
4587
4588static PixelList *DestroyPixelList(PixelList *pixel_list)
4589{
4590 register ssize_t
4591 i;
4592
4593 if (pixel_list == (PixelList *) NULL)
4594 return((PixelList *) NULL);
4595 for (i=0; i < ListChannels; i++)
4596 if (pixel_list->lists[i].nodes != (ListNode *) NULL)
4597 pixel_list->lists[i].nodes=(ListNode *) RelinquishMagickMemory(
4598 pixel_list->lists[i].nodes);
4599 pixel_list=(PixelList *) RelinquishMagickMemory(pixel_list);
4600 return(pixel_list);
4601}
4602
4603static PixelList **DestroyPixelListThreadSet(PixelList **pixel_list)
4604{
4605 register ssize_t
4606 i;
4607
4608 assert(pixel_list != (PixelList **) NULL);
4609 for (i=0; i < (ssize_t) GetOpenMPMaximumThreads(); i++)
4610 if (pixel_list[i] != (PixelList *) NULL)
4611 pixel_list[i]=DestroyPixelList(pixel_list[i]);
4612 pixel_list=(PixelList **) RelinquishMagickMemory(pixel_list);
4613 return(pixel_list);
4614}
4615
cristy6fc86bb2011-03-18 23:45:16 +00004616static PixelList *AcquirePixelList(const size_t width,const size_t height)
cristy733678d2011-03-18 21:29:28 +00004617{
4618 PixelList
4619 *pixel_list;
4620
4621 register ssize_t
4622 i;
4623
4624 pixel_list=(PixelList *) AcquireMagickMemory(sizeof(*pixel_list));
4625 if (pixel_list == (PixelList *) NULL)
4626 return(pixel_list);
4627 (void) ResetMagickMemory((void *) pixel_list,0,sizeof(*pixel_list));
cristy6fc86bb2011-03-18 23:45:16 +00004628 pixel_list->length=width*height;
cristy733678d2011-03-18 21:29:28 +00004629 for (i=0; i < ListChannels; i++)
4630 {
4631 pixel_list->lists[i].nodes=(ListNode *) AcquireQuantumMemory(65537UL,
4632 sizeof(*pixel_list->lists[i].nodes));
4633 if (pixel_list->lists[i].nodes == (ListNode *) NULL)
4634 return(DestroyPixelList(pixel_list));
4635 (void) ResetMagickMemory(pixel_list->lists[i].nodes,0,65537UL*
4636 sizeof(*pixel_list->lists[i].nodes));
4637 }
4638 pixel_list->signature=MagickSignature;
4639 return(pixel_list);
4640}
4641
cristy6fc86bb2011-03-18 23:45:16 +00004642static PixelList **AcquirePixelListThreadSet(const size_t width,
4643 const size_t height)
cristy733678d2011-03-18 21:29:28 +00004644{
4645 PixelList
4646 **pixel_list;
4647
4648 register ssize_t
4649 i;
4650
4651 size_t
4652 number_threads;
4653
4654 number_threads=GetOpenMPMaximumThreads();
4655 pixel_list=(PixelList **) AcquireQuantumMemory(number_threads,
4656 sizeof(*pixel_list));
4657 if (pixel_list == (PixelList **) NULL)
4658 return((PixelList **) NULL);
4659 (void) ResetMagickMemory(pixel_list,0,number_threads*sizeof(*pixel_list));
4660 for (i=0; i < (ssize_t) number_threads; i++)
4661 {
cristy6fc86bb2011-03-18 23:45:16 +00004662 pixel_list[i]=AcquirePixelList(width,height);
cristy733678d2011-03-18 21:29:28 +00004663 if (pixel_list[i] == (PixelList *) NULL)
4664 return(DestroyPixelListThreadSet(pixel_list));
4665 }
4666 return(pixel_list);
4667}
4668
4669static void AddNodePixelList(PixelList *pixel_list,const ssize_t channel,
4670 const size_t color)
4671{
4672 register SkipList
4673 *list;
4674
4675 register ssize_t
4676 level;
4677
4678 size_t
4679 search,
4680 update[9];
4681
4682 /*
4683 Initialize the node.
4684 */
4685 list=pixel_list->lists+channel;
4686 list->nodes[color].signature=pixel_list->signature;
4687 list->nodes[color].count=1;
4688 /*
4689 Determine where it belongs in the list.
4690 */
4691 search=65536UL;
4692 for (level=list->level; level >= 0; level--)
4693 {
4694 while (list->nodes[search].next[level] < color)
4695 search=list->nodes[search].next[level];
4696 update[level]=search;
4697 }
4698 /*
4699 Generate a pseudo-random level for this node.
4700 */
4701 for (level=0; ; level++)
4702 {
4703 pixel_list->seed=(pixel_list->seed*42893621L)+1L;
4704 if ((pixel_list->seed & 0x300) != 0x300)
4705 break;
4706 }
4707 if (level > 8)
4708 level=8;
4709 if (level > (list->level+2))
4710 level=list->level+2;
4711 /*
4712 If we're raising the list's level, link back to the root node.
4713 */
4714 while (level > list->level)
4715 {
4716 list->level++;
4717 update[list->level]=65536UL;
4718 }
4719 /*
4720 Link the node into the skip-list.
4721 */
4722 do
4723 {
4724 list->nodes[color].next[level]=list->nodes[update[level]].next[level];
4725 list->nodes[update[level]].next[level]=color;
cristy3cba8ca2011-03-19 01:29:12 +00004726 } while (level-- > 0);
cristy733678d2011-03-18 21:29:28 +00004727}
4728
cristy6fc86bb2011-03-18 23:45:16 +00004729static MagickPixelPacket GetMaximumPixelList(PixelList *pixel_list)
4730{
4731 MagickPixelPacket
4732 pixel;
4733
4734 register SkipList
4735 *list;
4736
4737 register ssize_t
4738 channel;
4739
4740 size_t
4741 color,
4742 count;
4743
4744 unsigned short
4745 channels[ListChannels];
4746
4747 /*
4748 Find the maximum value for each of the color.
4749 */
4750 for (channel=0; channel < 5; channel++)
4751 {
4752 list=pixel_list->lists+channel;
4753 color=65536UL;
4754 count=0;
4755 do
4756 {
4757 color=list->nodes[color].next[0];
4758 count+=list->nodes[color].count;
4759 } while (count <= pixel_list->length);
4760 channels[channel]=(unsigned short) color;
4761 }
4762 GetMagickPixelPacket((const Image *) NULL,&pixel);
4763 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4764 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4765 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
4766 pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
4767 pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]);
4768 return(pixel);
4769}
4770
cristy733678d2011-03-18 21:29:28 +00004771static MagickPixelPacket GetMedianPixelList(PixelList *pixel_list)
4772{
4773 MagickPixelPacket
4774 pixel;
4775
4776 register SkipList
4777 *list;
4778
4779 register ssize_t
4780 channel;
4781
4782 size_t
cristy733678d2011-03-18 21:29:28 +00004783 color,
4784 count;
4785
4786 unsigned short
4787 channels[ListChannels];
4788
4789 /*
4790 Find the median value for each of the color.
4791 */
cristy733678d2011-03-18 21:29:28 +00004792 for (channel=0; channel < 5; channel++)
4793 {
4794 list=pixel_list->lists+channel;
4795 color=65536UL;
4796 count=0;
4797 do
4798 {
4799 color=list->nodes[color].next[0];
4800 count+=list->nodes[color].count;
cristy6fc86bb2011-03-18 23:45:16 +00004801 } while (count <= (pixel_list->length >> 1));
4802 channels[channel]=(unsigned short) color;
4803 }
4804 GetMagickPixelPacket((const Image *) NULL,&pixel);
4805 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4806 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4807 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
4808 pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
4809 pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]);
4810 return(pixel);
4811}
4812
4813static MagickPixelPacket GetMinimumPixelList(PixelList *pixel_list)
4814{
4815 MagickPixelPacket
4816 pixel;
4817
4818 register SkipList
4819 *list;
4820
4821 register ssize_t
4822 channel;
4823
4824 size_t
4825 color;
4826
4827 unsigned short
4828 channels[ListChannels];
4829
4830 /*
4831 Find the minimum value for each of the color.
4832 */
4833 for (channel=0; channel < 5; channel++)
4834 {
4835 list=pixel_list->lists+channel;
4836 color=65536UL;
4837 color=list->nodes[color].next[0];
cristy733678d2011-03-18 21:29:28 +00004838 channels[channel]=(unsigned short) color;
4839 }
4840 GetMagickPixelPacket((const Image *) NULL,&pixel);
4841 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4842 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4843 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
4844 pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
4845 pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]);
4846 return(pixel);
4847}
4848
4849static MagickPixelPacket GetModePixelList(PixelList *pixel_list)
4850{
4851 MagickPixelPacket
4852 pixel;
4853
4854 register SkipList
4855 *list;
4856
4857 register ssize_t
4858 channel;
4859
4860 size_t
4861 color,
4862 count,
4863 max_count,
cristy6fc86bb2011-03-18 23:45:16 +00004864 mode;
cristy733678d2011-03-18 21:29:28 +00004865
4866 unsigned short
4867 channels[5];
4868
4869 /*
4870 Make each pixel the 'predominate color' of the specified neighborhood.
4871 */
cristy733678d2011-03-18 21:29:28 +00004872 for (channel=0; channel < 5; channel++)
4873 {
4874 list=pixel_list->lists+channel;
4875 color=65536UL;
4876 mode=color;
4877 max_count=list->nodes[mode].count;
4878 count=0;
4879 do
4880 {
4881 color=list->nodes[color].next[0];
4882 if (list->nodes[color].count > max_count)
4883 {
4884 mode=color;
4885 max_count=list->nodes[mode].count;
4886 }
4887 count+=list->nodes[color].count;
cristy6fc86bb2011-03-18 23:45:16 +00004888 } while (count <= pixel_list->length);
cristy733678d2011-03-18 21:29:28 +00004889 channels[channel]=(unsigned short) mode;
4890 }
4891 GetMagickPixelPacket((const Image *) NULL,&pixel);
4892 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4893 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4894 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
4895 pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
4896 pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]);
4897 return(pixel);
4898}
4899
4900static MagickPixelPacket GetNonpeakPixelList(PixelList *pixel_list)
4901{
4902 MagickPixelPacket
4903 pixel;
4904
4905 register SkipList
4906 *list;
4907
4908 register ssize_t
4909 channel;
4910
4911 size_t
cristy733678d2011-03-18 21:29:28 +00004912 color,
4913 count,
4914 next,
4915 previous;
4916
4917 unsigned short
4918 channels[5];
4919
4920 /*
cristy6fc86bb2011-03-18 23:45:16 +00004921 Finds the non peak value for each of the color.
cristy733678d2011-03-18 21:29:28 +00004922 */
cristy733678d2011-03-18 21:29:28 +00004923 for (channel=0; channel < 5; channel++)
4924 {
4925 list=pixel_list->lists+channel;
4926 color=65536UL;
4927 next=list->nodes[color].next[0];
4928 count=0;
4929 do
4930 {
4931 previous=color;
4932 color=next;
4933 next=list->nodes[color].next[0];
4934 count+=list->nodes[color].count;
cristy6fc86bb2011-03-18 23:45:16 +00004935 } while (count <= (pixel_list->length >> 1));
cristy733678d2011-03-18 21:29:28 +00004936 if ((previous == 65536UL) && (next != 65536UL))
4937 color=next;
4938 else
4939 if ((previous != 65536UL) && (next == 65536UL))
4940 color=previous;
4941 channels[channel]=(unsigned short) color;
4942 }
4943 GetMagickPixelPacket((const Image *) NULL,&pixel);
4944 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4945 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4946 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
4947 pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
4948 pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]);
4949 return(pixel);
4950}
4951
4952static inline void InsertPixelList(const Image *image,const PixelPacket *pixel,
4953 const IndexPacket *indexes,PixelList *pixel_list)
4954{
4955 size_t
4956 signature;
4957
4958 unsigned short
4959 index;
4960
4961 index=ScaleQuantumToShort(pixel->red);
4962 signature=pixel_list->lists[0].nodes[index].signature;
4963 if (signature == pixel_list->signature)
4964 pixel_list->lists[0].nodes[index].count++;
4965 else
4966 AddNodePixelList(pixel_list,0,index);
4967 index=ScaleQuantumToShort(pixel->green);
4968 signature=pixel_list->lists[1].nodes[index].signature;
4969 if (signature == pixel_list->signature)
4970 pixel_list->lists[1].nodes[index].count++;
4971 else
4972 AddNodePixelList(pixel_list,1,index);
4973 index=ScaleQuantumToShort(pixel->blue);
4974 signature=pixel_list->lists[2].nodes[index].signature;
4975 if (signature == pixel_list->signature)
4976 pixel_list->lists[2].nodes[index].count++;
4977 else
4978 AddNodePixelList(pixel_list,2,index);
4979 index=ScaleQuantumToShort(pixel->opacity);
4980 signature=pixel_list->lists[3].nodes[index].signature;
4981 if (signature == pixel_list->signature)
4982 pixel_list->lists[3].nodes[index].count++;
4983 else
4984 AddNodePixelList(pixel_list,3,index);
4985 if (image->colorspace == CMYKColorspace)
4986 index=ScaleQuantumToShort(*indexes);
4987 signature=pixel_list->lists[4].nodes[index].signature;
4988 if (signature == pixel_list->signature)
4989 pixel_list->lists[4].nodes[index].count++;
4990 else
4991 AddNodePixelList(pixel_list,4,index);
4992}
4993
4994static void ResetPixelList(PixelList *pixel_list)
4995{
4996 int
4997 level;
4998
4999 register ListNode
5000 *root;
5001
5002 register SkipList
5003 *list;
5004
5005 register ssize_t
5006 channel;
5007
5008 /*
5009 Reset the skip-list.
5010 */
5011 for (channel=0; channel < 5; channel++)
5012 {
5013 list=pixel_list->lists+channel;
5014 root=list->nodes+65536UL;
5015 list->level=0;
5016 for (level=0; level < 9; level++)
5017 root->next[level]=65536UL;
5018 }
5019 pixel_list->seed=pixel_list->signature++;
5020}
5021
cristy0834d642011-03-18 18:26:08 +00005022MagickExport Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00005023 const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00005024{
cristy95c38342011-03-18 22:39:51 +00005025 Image
5026 *statistic_image;
5027
5028 statistic_image=StatisticImageChannel(image,DefaultChannels,type,width,
5029 height,exception);
5030 return(statistic_image);
cristy0834d642011-03-18 18:26:08 +00005031}
5032
5033MagickExport Image *StatisticImageChannel(const Image *image,
cristy95c38342011-03-18 22:39:51 +00005034 const ChannelType channel,const StatisticType type,const size_t width,
5035 const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00005036{
cristy3cba8ca2011-03-19 01:29:12 +00005037#define StatisticWidth \
5038 (width == 0 ? GetOptimalKernelWidth2D(width,0.5) : width)
5039#define StatisticHeight \
5040 (height == 0 ? GetOptimalKernelWidth2D(height,0.5) : height)
cristy0834d642011-03-18 18:26:08 +00005041#define StatisticImageTag "Statistic/Image"
5042
5043 CacheView
5044 *image_view,
5045 *statistic_view;
5046
5047 Image
5048 *statistic_image;
5049
5050 MagickBooleanType
5051 status;
5052
5053 MagickOffsetType
5054 progress;
5055
5056 PixelList
5057 **restrict pixel_list;
5058
cristy0834d642011-03-18 18:26:08 +00005059 ssize_t
5060 y;
5061
5062 /*
5063 Initialize statistics image attributes.
5064 */
5065 assert(image != (Image *) NULL);
5066 assert(image->signature == MagickSignature);
5067 if (image->debug != MagickFalse)
5068 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5069 assert(exception != (ExceptionInfo *) NULL);
5070 assert(exception->signature == MagickSignature);
cristy0834d642011-03-18 18:26:08 +00005071 statistic_image=CloneImage(image,image->columns,image->rows,MagickTrue,
5072 exception);
5073 if (statistic_image == (Image *) NULL)
5074 return((Image *) NULL);
5075 if (SetImageStorageClass(statistic_image,DirectClass) == MagickFalse)
5076 {
5077 InheritException(exception,&statistic_image->exception);
5078 statistic_image=DestroyImage(statistic_image);
5079 return((Image *) NULL);
5080 }
cristy6fc86bb2011-03-18 23:45:16 +00005081 pixel_list=AcquirePixelListThreadSet(StatisticWidth,StatisticHeight);
cristy0834d642011-03-18 18:26:08 +00005082 if (pixel_list == (PixelList **) NULL)
5083 {
5084 statistic_image=DestroyImage(statistic_image);
5085 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
5086 }
5087 /*
cristy8d752042011-03-19 01:00:36 +00005088 Make each pixel the min / max / median / mode / etc. of the neighborhood.
cristy0834d642011-03-18 18:26:08 +00005089 */
5090 status=MagickTrue;
5091 progress=0;
5092 image_view=AcquireCacheView(image);
5093 statistic_view=AcquireCacheView(statistic_image);
5094#if defined(MAGICKCORE_OPENMP_SUPPORT)
5095 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
5096#endif
5097 for (y=0; y < (ssize_t) statistic_image->rows; y++)
5098 {
5099 const int
5100 id = GetOpenMPThreadId();
5101
5102 register const IndexPacket
5103 *restrict indexes;
5104
5105 register const PixelPacket
5106 *restrict p;
5107
5108 register IndexPacket
5109 *restrict statistic_indexes;
5110
5111 register PixelPacket
5112 *restrict q;
5113
5114 register ssize_t
5115 x;
5116
5117 if (status == MagickFalse)
5118 continue;
cristy6fc86bb2011-03-18 23:45:16 +00005119 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) StatisticWidth/2L),y-
5120 (ssize_t) (StatisticHeight/2L),image->columns+StatisticWidth,
5121 StatisticHeight,exception);
cristy3cba8ca2011-03-19 01:29:12 +00005122 q=QueueCacheViewAuthenticPixels(statistic_view,0,y,statistic_image->columns, 1,exception);
cristy0834d642011-03-18 18:26:08 +00005123 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
5124 {
5125 status=MagickFalse;
5126 continue;
5127 }
5128 indexes=GetCacheViewVirtualIndexQueue(image_view);
5129 statistic_indexes=GetCacheViewAuthenticIndexQueue(statistic_view);
5130 for (x=0; x < (ssize_t) statistic_image->columns; x++)
5131 {
5132 MagickPixelPacket
5133 pixel;
5134
cristy0834d642011-03-18 18:26:08 +00005135 register const IndexPacket
5136 *restrict s;
5137
cristy6e3026a2011-03-19 00:54:38 +00005138 register const PixelPacket
5139 *restrict r;
5140
cristy0834d642011-03-18 18:26:08 +00005141 register ssize_t
5142 u,
5143 v;
5144
5145 r=p;
5146 s=indexes+x;
5147 ResetPixelList(pixel_list[id]);
cristy6e4c3292011-03-19 00:53:55 +00005148 for (v=0; v < (ssize_t) StatisticHeight; v++)
cristy0834d642011-03-18 18:26:08 +00005149 {
cristy6e4c3292011-03-19 00:53:55 +00005150 for (u=0; u < (ssize_t) StatisticWidth; u++)
cristy0834d642011-03-18 18:26:08 +00005151 InsertPixelList(image,r+u,s+u,pixel_list[id]);
cristy6fc86bb2011-03-18 23:45:16 +00005152 r+=image->columns+StatisticWidth;
cristy6e4c3292011-03-19 00:53:55 +00005153 s+=image->columns+StatisticWidth;
cristy0834d642011-03-18 18:26:08 +00005154 }
5155 switch (type)
5156 {
cristy6fc86bb2011-03-18 23:45:16 +00005157 case MaximumStatistic:
5158 {
5159 pixel=GetMaximumPixelList(pixel_list[id]);
5160 break;
5161 }
cristyf2ad14a2011-03-18 18:57:25 +00005162 case MedianStatistic:
cristy6fc86bb2011-03-18 23:45:16 +00005163 default:
cristyf2ad14a2011-03-18 18:57:25 +00005164 {
5165 pixel=GetMedianPixelList(pixel_list[id]);
5166 break;
5167 }
cristy6fc86bb2011-03-18 23:45:16 +00005168 case MinimumStatistic:
5169 {
5170 pixel=GetMinimumPixelList(pixel_list[id]);
5171 break;
5172 }
cristyf2ad14a2011-03-18 18:57:25 +00005173 case ModeStatistic:
5174 {
5175 pixel=GetModePixelList(pixel_list[id]);
5176 break;
5177 }
5178 case NonpeakStatistic:
5179 {
5180 pixel=GetNonpeakPixelList(pixel_list[id]);
5181 break;
5182 }
cristy0834d642011-03-18 18:26:08 +00005183 }
5184 if ((channel & RedChannel) != 0)
5185 q->red=ClampToQuantum(pixel.red);
5186 if ((channel & GreenChannel) != 0)
5187 q->green=ClampToQuantum(pixel.green);
5188 if ((channel & BlueChannel) != 0)
5189 q->blue=ClampToQuantum(pixel.blue);
cristy5b273dc2011-03-19 00:58:44 +00005190 if (((channel & OpacityChannel) != 0) &&
5191 (image->matte != MagickFalse))
cristy0834d642011-03-18 18:26:08 +00005192 q->opacity=ClampToQuantum(pixel.opacity);
5193 if (((channel & IndexChannel) != 0) &&
5194 (image->colorspace == CMYKColorspace))
5195 statistic_indexes[x]=(IndexPacket) ClampToQuantum(pixel.index);
5196 p++;
5197 q++;
5198 }
5199 if (SyncCacheViewAuthenticPixels(statistic_view,exception) == MagickFalse)
5200 status=MagickFalse;
5201 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5202 {
5203 MagickBooleanType
5204 proceed;
5205
5206#if defined(MAGICKCORE_OPENMP_SUPPORT)
5207 #pragma omp critical (MagickCore_StatisticImage)
5208#endif
5209 proceed=SetImageProgress(image,StatisticImageTag,progress++,
5210 image->rows);
5211 if (proceed == MagickFalse)
5212 status=MagickFalse;
5213 }
5214 }
5215 statistic_view=DestroyCacheView(statistic_view);
5216 image_view=DestroyCacheView(image_view);
5217 pixel_list=DestroyPixelListThreadSet(pixel_list);
5218 return(statistic_image);
5219}
5220
5221/*
5222%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5223% %
5224% %
5225% %
cristy3ed852e2009-09-05 21:47:34 +00005226% U n s h a r p M a s k I m a g e %
5227% %
5228% %
5229% %
5230%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5231%
5232% UnsharpMaskImage() sharpens one or more image channels. We convolve the
5233% image with a Gaussian operator of the given radius and standard deviation
5234% (sigma). For reasonable results, radius should be larger than sigma. Use a
5235% radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
5236%
5237% The format of the UnsharpMaskImage method is:
5238%
5239% Image *UnsharpMaskImage(const Image *image,const double radius,
5240% const double sigma,const double amount,const double threshold,
5241% ExceptionInfo *exception)
5242% Image *UnsharpMaskImageChannel(const Image *image,
5243% const ChannelType channel,const double radius,const double sigma,
5244% const double amount,const double threshold,ExceptionInfo *exception)
5245%
5246% A description of each parameter follows:
5247%
5248% o image: the image.
5249%
5250% o channel: the channel type.
5251%
5252% o radius: the radius of the Gaussian, in pixels, not counting the center
5253% pixel.
5254%
5255% o sigma: the standard deviation of the Gaussian, in pixels.
5256%
5257% o amount: the percentage of the difference between the original and the
5258% blur image that is added back into the original.
5259%
5260% o threshold: the threshold in pixels needed to apply the diffence amount.
5261%
5262% o exception: return any errors or warnings in this structure.
5263%
5264*/
5265
5266MagickExport Image *UnsharpMaskImage(const Image *image,const double radius,
5267 const double sigma,const double amount,const double threshold,
5268 ExceptionInfo *exception)
5269{
5270 Image
5271 *sharp_image;
5272
5273 sharp_image=UnsharpMaskImageChannel(image,DefaultChannels,radius,sigma,amount,
5274 threshold,exception);
5275 return(sharp_image);
5276}
5277
5278MagickExport Image *UnsharpMaskImageChannel(const Image *image,
5279 const ChannelType channel,const double radius,const double sigma,
5280 const double amount,const double threshold,ExceptionInfo *exception)
5281{
5282#define SharpenImageTag "Sharpen/Image"
5283
cristyc4c8d132010-01-07 01:58:38 +00005284 CacheView
5285 *image_view,
5286 *unsharp_view;
5287
cristy3ed852e2009-09-05 21:47:34 +00005288 Image
5289 *unsharp_image;
5290
cristy3ed852e2009-09-05 21:47:34 +00005291 MagickBooleanType
5292 status;
5293
cristybb503372010-05-27 20:51:26 +00005294 MagickOffsetType
5295 progress;
5296
cristy3ed852e2009-09-05 21:47:34 +00005297 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00005298 bias;
cristy3ed852e2009-09-05 21:47:34 +00005299
5300 MagickRealType
5301 quantum_threshold;
5302
cristybb503372010-05-27 20:51:26 +00005303 ssize_t
5304 y;
5305
cristy3ed852e2009-09-05 21:47:34 +00005306 assert(image != (const Image *) NULL);
5307 assert(image->signature == MagickSignature);
5308 if (image->debug != MagickFalse)
5309 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5310 assert(exception != (ExceptionInfo *) NULL);
5311 unsharp_image=BlurImageChannel(image,channel,radius,sigma,exception);
5312 if (unsharp_image == (Image *) NULL)
5313 return((Image *) NULL);
5314 quantum_threshold=(MagickRealType) QuantumRange*threshold;
5315 /*
5316 Unsharp-mask image.
5317 */
5318 status=MagickTrue;
5319 progress=0;
cristyddd82202009-11-03 20:14:50 +00005320 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00005321 image_view=AcquireCacheView(image);
5322 unsharp_view=AcquireCacheView(unsharp_image);
cristyb5d5f722009-11-04 03:03:49 +00005323#if defined(MAGICKCORE_OPENMP_SUPPORT)
5324 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00005325#endif
cristybb503372010-05-27 20:51:26 +00005326 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00005327 {
5328 MagickPixelPacket
5329 pixel;
5330
5331 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00005332 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00005333
5334 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00005335 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00005336
5337 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00005338 *restrict unsharp_indexes;
cristy3ed852e2009-09-05 21:47:34 +00005339
cristy3ed852e2009-09-05 21:47:34 +00005340 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00005341 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00005342
cristy117ff172010-08-15 21:35:32 +00005343 register ssize_t
5344 x;
5345
cristy3ed852e2009-09-05 21:47:34 +00005346 if (status == MagickFalse)
5347 continue;
5348 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
5349 q=GetCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
5350 exception);
5351 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
5352 {
5353 status=MagickFalse;
5354 continue;
5355 }
5356 indexes=GetCacheViewVirtualIndexQueue(image_view);
5357 unsharp_indexes=GetCacheViewAuthenticIndexQueue(unsharp_view);
cristyddd82202009-11-03 20:14:50 +00005358 pixel=bias;
cristybb503372010-05-27 20:51:26 +00005359 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00005360 {
5361 if ((channel & RedChannel) != 0)
5362 {
5363 pixel.red=p->red-(MagickRealType) q->red;
5364 if (fabs(2.0*pixel.red) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005365 pixel.red=(MagickRealType) GetRedPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005366 else
5367 pixel.red=(MagickRealType) p->red+(pixel.red*amount);
cristyce70c172010-01-07 17:15:30 +00005368 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00005369 }
5370 if ((channel & GreenChannel) != 0)
5371 {
5372 pixel.green=p->green-(MagickRealType) q->green;
5373 if (fabs(2.0*pixel.green) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005374 pixel.green=(MagickRealType) GetGreenPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005375 else
5376 pixel.green=(MagickRealType) p->green+(pixel.green*amount);
cristyce70c172010-01-07 17:15:30 +00005377 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00005378 }
5379 if ((channel & BlueChannel) != 0)
5380 {
5381 pixel.blue=p->blue-(MagickRealType) q->blue;
5382 if (fabs(2.0*pixel.blue) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005383 pixel.blue=(MagickRealType) GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005384 else
5385 pixel.blue=(MagickRealType) p->blue+(pixel.blue*amount);
cristyce70c172010-01-07 17:15:30 +00005386 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00005387 }
5388 if ((channel & OpacityChannel) != 0)
5389 {
5390 pixel.opacity=p->opacity-(MagickRealType) q->opacity;
5391 if (fabs(2.0*pixel.opacity) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005392 pixel.opacity=(MagickRealType) GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005393 else
5394 pixel.opacity=p->opacity+(pixel.opacity*amount);
cristyce70c172010-01-07 17:15:30 +00005395 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00005396 }
5397 if (((channel & IndexChannel) != 0) &&
5398 (image->colorspace == CMYKColorspace))
5399 {
cristyf01182f2011-03-01 14:56:40 +00005400 pixel.index=indexes[x]-(MagickRealType) unsharp_indexes[x];
cristy3ed852e2009-09-05 21:47:34 +00005401 if (fabs(2.0*pixel.index) < quantum_threshold)
cristyb557a152011-02-22 12:14:30 +00005402 pixel.index=(MagickRealType) indexes[x];
cristy3ed852e2009-09-05 21:47:34 +00005403 else
cristyb557a152011-02-22 12:14:30 +00005404 pixel.index=(MagickRealType) indexes[x]+(pixel.index*amount);
cristyce70c172010-01-07 17:15:30 +00005405 unsharp_indexes[x]=ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00005406 }
5407 p++;
5408 q++;
5409 }
5410 if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
5411 status=MagickFalse;
5412 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5413 {
5414 MagickBooleanType
5415 proceed;
5416
cristyb5d5f722009-11-04 03:03:49 +00005417#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00005418 #pragma omp critical (MagickCore_UnsharpMaskImageChannel)
5419#endif
5420 proceed=SetImageProgress(image,SharpenImageTag,progress++,image->rows);
5421 if (proceed == MagickFalse)
5422 status=MagickFalse;
5423 }
5424 }
5425 unsharp_image->type=image->type;
5426 unsharp_view=DestroyCacheView(unsharp_view);
5427 image_view=DestroyCacheView(image_view);
5428 if (status == MagickFalse)
5429 unsharp_image=DestroyImage(unsharp_image);
5430 return(unsharp_image);
5431}