blob: dd72e6ea5f9d970c9d1615463d744eb0c78ebd5a [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% %
cristy16af1cb2009-12-11 21:38:29 +000020% Copyright 1999-2010 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 {
cristy47e00502009-12-17 19:19:57 +0000240 kernel[i][k]=exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
241 (2.0*MagickPI*MagickSigma*MagickSigma);
242 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
cristybb503372010-05-27 20:51:26 +0000286 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000287 x;
288
289 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000290 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000291
292 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 {
cristy47e00502009-12-17 19:19:57 +0000557 kernel[i][k]=(-exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
558 (2.0*MagickPI*MagickSigma*MagickSigma));
559 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
cristybb503372010-05-27 20:51:26 +0000603 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000604 x;
605
606 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000607 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000608
609 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--;
cristybb503372010-05-27 20:51:26 +0000647 p=GetCacheViewVirtualPixels(image_view,x-((ssize_t) (width-i)/2L),y-(ssize_t)
cristy3ed852e2009-09-05 21:47:34 +0000648 ((width-i)/2L),width-i,width-i,exception);
649 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
cristybb503372010-05-27 20:51:26 +0000780 ssize_t
cristy47e00502009-12-17 19:19:57 +0000781 j,
782 k;
cristy3ed852e2009-09-05 21:47:34 +0000783
cristybb503372010-05-27 20:51:26 +0000784 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000785 i;
786
787 /*
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 {
cristyf267c722009-12-18 00:07:22 +0000799 kernel[i]=exp(-((double) k*k)/(2.0*MagickSigma*MagickSigma))/
cristy47e00502009-12-17 19:19:57 +0000800 (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
cristybb503372010-05-27 20:51:26 +0000918 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000919 x;
920
921 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000922 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000923
924 if (status == MagickFalse)
925 continue;
cristybb503372010-05-27 20:51:26 +0000926 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y,image->columns+
cristy3ed852e2009-09-05 21:47:34 +0000927 width,1,exception);
928 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;
989 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;
1044 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 }
1058 p++;
1059 q++;
1060 }
1061 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1062 status=MagickFalse;
1063 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1064 {
1065 MagickBooleanType
1066 proceed;
1067
cristyb5d5f722009-11-04 03:03:49 +00001068#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001069 #pragma omp critical (MagickCore_BlurImageChannel)
1070#endif
1071 proceed=SetImageProgress(image,BlurImageTag,progress++,blur_image->rows+
1072 blur_image->columns);
1073 if (proceed == MagickFalse)
1074 status=MagickFalse;
1075 }
1076 }
1077 blur_view=DestroyCacheView(blur_view);
1078 image_view=DestroyCacheView(image_view);
1079 /*
1080 Blur columns.
1081 */
1082 image_view=AcquireCacheView(blur_image);
1083 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00001084#if defined(MAGICKCORE_OPENMP_SUPPORT)
1085 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001086#endif
cristybb503372010-05-27 20:51:26 +00001087 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001088 {
1089 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001090 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001091
1092 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001093 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001094
1095 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001096 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001097
cristybb503372010-05-27 20:51:26 +00001098 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001099 y;
1100
1101 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001102 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001103
1104 if (status == MagickFalse)
1105 continue;
cristybb503372010-05-27 20:51:26 +00001106 p=GetCacheViewVirtualPixels(image_view,x,-((ssize_t) width/2L),1,image->rows+
cristy3ed852e2009-09-05 21:47:34 +00001107 width,exception);
1108 q=GetCacheViewAuthenticPixels(blur_view,x,0,1,blur_image->rows,exception);
1109 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1110 {
1111 status=MagickFalse;
1112 continue;
1113 }
1114 indexes=GetCacheViewVirtualIndexQueue(image_view);
1115 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
cristybb503372010-05-27 20:51:26 +00001116 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001117 {
1118 MagickPixelPacket
1119 pixel;
1120
1121 register const double
cristyc47d1f82009-11-26 01:44:43 +00001122 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00001123
1124 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001125 *restrict kernel_pixels;
cristy3ed852e2009-09-05 21:47:34 +00001126
cristybb503372010-05-27 20:51:26 +00001127 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001128 i;
1129
cristyddd82202009-11-03 20:14:50 +00001130 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00001131 k=kernel;
1132 kernel_pixels=p;
1133 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
1134 {
cristybb503372010-05-27 20:51:26 +00001135 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001136 {
1137 pixel.red+=(*k)*kernel_pixels->red;
1138 pixel.green+=(*k)*kernel_pixels->green;
1139 pixel.blue+=(*k)*kernel_pixels->blue;
1140 k++;
1141 kernel_pixels++;
1142 }
1143 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001144 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001145 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001146 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001147 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001148 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001149 if ((channel & OpacityChannel) != 0)
1150 {
1151 k=kernel;
1152 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001153 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001154 {
1155 pixel.opacity+=(*k)*kernel_pixels->opacity;
1156 k++;
1157 kernel_pixels++;
1158 }
cristyce70c172010-01-07 17:15:30 +00001159 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001160 }
1161 if (((channel & IndexChannel) != 0) &&
1162 (image->colorspace == CMYKColorspace))
1163 {
1164 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001165 *restrict kernel_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001166
1167 k=kernel;
1168 kernel_indexes=indexes;
cristybb503372010-05-27 20:51:26 +00001169 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001170 {
1171 pixel.index+=(*k)*(*kernel_indexes);
1172 k++;
1173 kernel_indexes++;
1174 }
cristyce70c172010-01-07 17:15:30 +00001175 blur_indexes[y]=ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00001176 }
1177 }
1178 else
1179 {
1180 MagickRealType
1181 alpha,
1182 gamma;
1183
1184 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00001185 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001186 {
cristy46f08202010-01-10 04:04:21 +00001187 alpha=(MagickRealType) (QuantumScale*
1188 GetAlphaPixelComponent(kernel_pixels));
cristy3ed852e2009-09-05 21:47:34 +00001189 pixel.red+=(*k)*alpha*kernel_pixels->red;
1190 pixel.green+=(*k)*alpha*kernel_pixels->green;
1191 pixel.blue+=(*k)*alpha*kernel_pixels->blue;
1192 gamma+=(*k)*alpha;
1193 k++;
1194 kernel_pixels++;
1195 }
1196 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1197 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001198 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001199 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001200 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001201 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001202 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001203 if ((channel & OpacityChannel) != 0)
1204 {
1205 k=kernel;
1206 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001207 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001208 {
1209 pixel.opacity+=(*k)*kernel_pixels->opacity;
1210 k++;
1211 kernel_pixels++;
1212 }
cristyce70c172010-01-07 17:15:30 +00001213 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001214 }
1215 if (((channel & IndexChannel) != 0) &&
1216 (image->colorspace == CMYKColorspace))
1217 {
1218 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001219 *restrict kernel_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001220
1221 k=kernel;
1222 kernel_pixels=p;
1223 kernel_indexes=indexes;
cristybb503372010-05-27 20:51:26 +00001224 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001225 {
cristy46f08202010-01-10 04:04:21 +00001226 alpha=(MagickRealType) (QuantumScale*
1227 GetAlphaPixelComponent(kernel_pixels));
cristy3ed852e2009-09-05 21:47:34 +00001228 pixel.index+=(*k)*alpha*(*kernel_indexes);
1229 k++;
1230 kernel_pixels++;
1231 kernel_indexes++;
1232 }
cristy46f08202010-01-10 04:04:21 +00001233 blur_indexes[y]=ClampToQuantum(gamma*
1234 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001235 }
1236 }
1237 p++;
1238 q++;
1239 }
1240 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1241 status=MagickFalse;
1242 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1243 {
1244 MagickBooleanType
1245 proceed;
1246
cristyb5d5f722009-11-04 03:03:49 +00001247#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001248 #pragma omp critical (MagickCore_BlurImageChannel)
1249#endif
1250 proceed=SetImageProgress(image,BlurImageTag,progress++,blur_image->rows+
1251 blur_image->columns);
1252 if (proceed == MagickFalse)
1253 status=MagickFalse;
1254 }
1255 }
1256 blur_view=DestroyCacheView(blur_view);
1257 image_view=DestroyCacheView(image_view);
1258 kernel=(double *) RelinquishMagickMemory(kernel);
1259 if (status == MagickFalse)
1260 blur_image=DestroyImage(blur_image);
1261 blur_image->type=image->type;
1262 return(blur_image);
1263}
1264
1265/*
1266%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1267% %
1268% %
1269% %
cristyfccdab92009-11-30 16:43:57 +00001270% C o n v o l v e I m a g e %
1271% %
1272% %
1273% %
1274%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1275%
1276% ConvolveImage() applies a custom convolution kernel to the image.
1277%
1278% The format of the ConvolveImage method is:
1279%
cristybb503372010-05-27 20:51:26 +00001280% Image *ConvolveImage(const Image *image,const size_t order,
cristyfccdab92009-11-30 16:43:57 +00001281% const double *kernel,ExceptionInfo *exception)
1282% Image *ConvolveImageChannel(const Image *image,const ChannelType channel,
cristybb503372010-05-27 20:51:26 +00001283% const size_t order,const double *kernel,
cristyfccdab92009-11-30 16:43:57 +00001284% ExceptionInfo *exception)
1285%
1286% A description of each parameter follows:
1287%
1288% o image: the image.
1289%
1290% o channel: the channel type.
1291%
1292% o order: the number of columns and rows in the filter kernel.
1293%
1294% o kernel: An array of double representing the convolution kernel.
1295%
1296% o exception: return any errors or warnings in this structure.
1297%
1298*/
1299
cristybb503372010-05-27 20:51:26 +00001300MagickExport Image *ConvolveImage(const Image *image,const size_t order,
cristyfccdab92009-11-30 16:43:57 +00001301 const double *kernel,ExceptionInfo *exception)
1302{
1303 Image
1304 *convolve_image;
1305
1306 convolve_image=ConvolveImageChannel(image,DefaultChannels,order,kernel,
1307 exception);
1308 return(convolve_image);
1309}
1310
1311MagickExport Image *ConvolveImageChannel(const Image *image,
cristybb503372010-05-27 20:51:26 +00001312 const ChannelType channel,const size_t order,const double *kernel,
cristyfccdab92009-11-30 16:43:57 +00001313 ExceptionInfo *exception)
1314{
1315#define ConvolveImageTag "Convolve/Image"
1316
cristyc4c8d132010-01-07 01:58:38 +00001317 CacheView
1318 *convolve_view,
1319 *image_view;
1320
cristyfccdab92009-11-30 16:43:57 +00001321 double
1322 *normal_kernel;
1323
1324 Image
1325 *convolve_image;
1326
cristyfccdab92009-11-30 16:43:57 +00001327 MagickBooleanType
1328 status;
1329
cristybb503372010-05-27 20:51:26 +00001330 MagickOffsetType
1331 progress;
1332
cristyfccdab92009-11-30 16:43:57 +00001333 MagickPixelPacket
1334 bias;
1335
1336 MagickRealType
1337 gamma;
1338
cristybb503372010-05-27 20:51:26 +00001339 register ssize_t
cristyfccdab92009-11-30 16:43:57 +00001340 i;
1341
cristybb503372010-05-27 20:51:26 +00001342 size_t
cristyfccdab92009-11-30 16:43:57 +00001343 width;
1344
cristybb503372010-05-27 20:51:26 +00001345 ssize_t
1346 y;
1347
cristyfccdab92009-11-30 16:43:57 +00001348 /*
1349 Initialize convolve image attributes.
1350 */
1351 assert(image != (Image *) NULL);
1352 assert(image->signature == MagickSignature);
1353 if (image->debug != MagickFalse)
1354 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1355 assert(exception != (ExceptionInfo *) NULL);
1356 assert(exception->signature == MagickSignature);
1357 width=order;
1358 if ((width % 2) == 0)
1359 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
1360 convolve_image=CloneImage(image,0,0,MagickTrue,exception);
1361 if (convolve_image == (Image *) NULL)
1362 return((Image *) NULL);
1363 if (SetImageStorageClass(convolve_image,DirectClass) == MagickFalse)
1364 {
1365 InheritException(exception,&convolve_image->exception);
1366 convolve_image=DestroyImage(convolve_image);
1367 return((Image *) NULL);
1368 }
1369 if (image->debug != MagickFalse)
1370 {
1371 char
1372 format[MaxTextExtent],
1373 *message;
1374
cristybb503372010-05-27 20:51:26 +00001375 ssize_t
cristyfccdab92009-11-30 16:43:57 +00001376 u,
1377 v;
1378
1379 register const double
1380 *k;
1381
1382 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00001383 " ConvolveImage with %.20gx%.20g kernel:",(double) width,(double)
1384 width);
cristyfccdab92009-11-30 16:43:57 +00001385 message=AcquireString("");
1386 k=kernel;
cristybb503372010-05-27 20:51:26 +00001387 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001388 {
1389 *message='\0';
cristye8c25f92010-06-03 00:53:06 +00001390 (void) FormatMagickString(format,MaxTextExtent,"%.20g: ",(double) v);
cristyfccdab92009-11-30 16:43:57 +00001391 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00001392 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001393 {
cristye7f51092010-01-17 00:39:37 +00001394 (void) FormatMagickString(format,MaxTextExtent,"%g ",*k++);
cristyfccdab92009-11-30 16:43:57 +00001395 (void) ConcatenateString(&message,format);
1396 }
1397 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
1398 }
1399 message=DestroyString(message);
1400 }
1401 /*
1402 Normalize kernel.
1403 */
1404 normal_kernel=(double *) AcquireQuantumMemory(width*width,
1405 sizeof(*normal_kernel));
1406 if (normal_kernel == (double *) NULL)
1407 {
1408 convolve_image=DestroyImage(convolve_image);
1409 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1410 }
1411 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00001412 for (i=0; i < (ssize_t) (width*width); i++)
cristyfccdab92009-11-30 16:43:57 +00001413 gamma+=kernel[i];
1414 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristybb503372010-05-27 20:51:26 +00001415 for (i=0; i < (ssize_t) (width*width); i++)
cristyfccdab92009-11-30 16:43:57 +00001416 normal_kernel[i]=gamma*kernel[i];
1417 /*
1418 Convolve image.
1419 */
1420 status=MagickTrue;
1421 progress=0;
1422 GetMagickPixelPacket(image,&bias);
1423 SetMagickPixelPacketBias(image,&bias);
1424 image_view=AcquireCacheView(image);
1425 convolve_view=AcquireCacheView(convolve_image);
1426#if defined(MAGICKCORE_OPENMP_SUPPORT)
1427 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1428#endif
cristybb503372010-05-27 20:51:26 +00001429 for (y=0; y < (ssize_t) image->rows; y++)
cristyfccdab92009-11-30 16:43:57 +00001430 {
1431 MagickBooleanType
1432 sync;
1433
1434 register const IndexPacket
1435 *restrict indexes;
1436
1437 register const PixelPacket
1438 *restrict p;
1439
1440 register IndexPacket
1441 *restrict convolve_indexes;
1442
cristybb503372010-05-27 20:51:26 +00001443 register ssize_t
cristyfccdab92009-11-30 16:43:57 +00001444 x;
1445
1446 register PixelPacket
1447 *restrict q;
1448
1449 if (status == MagickFalse)
1450 continue;
cristyce889302010-06-30 19:16:36 +00001451 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
1452 (width/2L),image->columns+width,width,exception);
cristyfccdab92009-11-30 16:43:57 +00001453 q=GetCacheViewAuthenticPixels(convolve_view,0,y,convolve_image->columns,1,
1454 exception);
1455 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1456 {
1457 status=MagickFalse;
1458 continue;
1459 }
1460 indexes=GetCacheViewVirtualIndexQueue(image_view);
1461 convolve_indexes=GetCacheViewAuthenticIndexQueue(convolve_view);
cristybb503372010-05-27 20:51:26 +00001462 for (x=0; x < (ssize_t) image->columns; x++)
cristyfccdab92009-11-30 16:43:57 +00001463 {
cristybb503372010-05-27 20:51:26 +00001464 ssize_t
cristyfccdab92009-11-30 16:43:57 +00001465 v;
1466
1467 MagickPixelPacket
1468 pixel;
1469
1470 register const double
1471 *restrict k;
1472
1473 register const PixelPacket
1474 *restrict kernel_pixels;
1475
cristybb503372010-05-27 20:51:26 +00001476 register ssize_t
cristyfccdab92009-11-30 16:43:57 +00001477 u;
1478
1479 pixel=bias;
1480 k=normal_kernel;
1481 kernel_pixels=p;
1482 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
1483 {
cristybb503372010-05-27 20:51:26 +00001484 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001485 {
cristybb503372010-05-27 20:51:26 +00001486 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001487 {
1488 pixel.red+=(*k)*kernel_pixels[u].red;
1489 pixel.green+=(*k)*kernel_pixels[u].green;
1490 pixel.blue+=(*k)*kernel_pixels[u].blue;
1491 k++;
1492 }
1493 kernel_pixels+=image->columns+width;
1494 }
1495 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001496 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001497 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001498 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001499 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001500 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001501 if ((channel & OpacityChannel) != 0)
1502 {
1503 k=normal_kernel;
1504 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001505 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001506 {
cristybb503372010-05-27 20:51:26 +00001507 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001508 {
1509 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
1510 k++;
1511 }
1512 kernel_pixels+=image->columns+width;
1513 }
cristyce70c172010-01-07 17:15:30 +00001514 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001515 }
1516 if (((channel & IndexChannel) != 0) &&
1517 (image->colorspace == CMYKColorspace))
1518 {
1519 register const IndexPacket
1520 *restrict kernel_indexes;
1521
1522 k=normal_kernel;
1523 kernel_indexes=indexes;
cristybb503372010-05-27 20:51:26 +00001524 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001525 {
cristybb503372010-05-27 20:51:26 +00001526 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001527 {
1528 pixel.index+=(*k)*kernel_indexes[u];
1529 k++;
1530 }
1531 kernel_indexes+=image->columns+width;
1532 }
cristyce70c172010-01-07 17:15:30 +00001533 convolve_indexes[x]=ClampToQuantum(pixel.index);
cristyfccdab92009-11-30 16:43:57 +00001534 }
1535 }
1536 else
1537 {
1538 MagickRealType
1539 alpha,
1540 gamma;
1541
1542 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00001543 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001544 {
cristybb503372010-05-27 20:51:26 +00001545 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001546 {
1547 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
1548 kernel_pixels[u].opacity));
1549 pixel.red+=(*k)*alpha*kernel_pixels[u].red;
1550 pixel.green+=(*k)*alpha*kernel_pixels[u].green;
1551 pixel.blue+=(*k)*alpha*kernel_pixels[u].blue;
cristyfccdab92009-11-30 16:43:57 +00001552 gamma+=(*k)*alpha;
1553 k++;
1554 }
1555 kernel_pixels+=image->columns+width;
1556 }
1557 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1558 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001559 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001560 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001561 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001562 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001563 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001564 if ((channel & OpacityChannel) != 0)
1565 {
1566 k=normal_kernel;
1567 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001568 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001569 {
cristybb503372010-05-27 20:51:26 +00001570 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001571 {
1572 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
1573 k++;
1574 }
1575 kernel_pixels+=image->columns+width;
1576 }
cristyce70c172010-01-07 17:15:30 +00001577 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001578 }
1579 if (((channel & IndexChannel) != 0) &&
1580 (image->colorspace == CMYKColorspace))
1581 {
1582 register const IndexPacket
1583 *restrict kernel_indexes;
1584
1585 k=normal_kernel;
1586 kernel_pixels=p;
1587 kernel_indexes=indexes;
cristybb503372010-05-27 20:51:26 +00001588 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001589 {
cristybb503372010-05-27 20:51:26 +00001590 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001591 {
1592 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
1593 kernel_pixels[u].opacity));
1594 pixel.index+=(*k)*alpha*kernel_indexes[u];
1595 k++;
1596 }
1597 kernel_pixels+=image->columns+width;
1598 kernel_indexes+=image->columns+width;
1599 }
cristy24b06da2010-01-09 23:05:56 +00001600 convolve_indexes[x]=ClampToQuantum(gamma*
1601 GetIndexPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001602 }
1603 }
1604 p++;
1605 q++;
1606 }
1607 sync=SyncCacheViewAuthenticPixels(convolve_view,exception);
1608 if (sync == MagickFalse)
1609 status=MagickFalse;
1610 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1611 {
1612 MagickBooleanType
1613 proceed;
1614
1615#if defined(MAGICKCORE_OPENMP_SUPPORT)
1616 #pragma omp critical (MagickCore_ConvolveImageChannel)
1617#endif
1618 proceed=SetImageProgress(image,ConvolveImageTag,progress++,image->rows);
1619 if (proceed == MagickFalse)
1620 status=MagickFalse;
1621 }
1622 }
1623 convolve_image->type=image->type;
1624 convolve_view=DestroyCacheView(convolve_view);
1625 image_view=DestroyCacheView(image_view);
1626 normal_kernel=(double *) RelinquishMagickMemory(normal_kernel);
1627 if (status == MagickFalse)
1628 convolve_image=DestroyImage(convolve_image);
1629 return(convolve_image);
1630}
1631
1632/*
1633%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1634% %
1635% %
1636% %
cristy3ed852e2009-09-05 21:47:34 +00001637% D e s p e c k l e I m a g e %
1638% %
1639% %
1640% %
1641%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1642%
1643% DespeckleImage() reduces the speckle noise in an image while perserving the
1644% edges of the original image.
1645%
1646% The format of the DespeckleImage method is:
1647%
1648% Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1649%
1650% A description of each parameter follows:
1651%
1652% o image: the image.
1653%
1654% o exception: return any errors or warnings in this structure.
1655%
1656*/
1657
1658static Quantum **DestroyPixelThreadSet(Quantum **pixels)
1659{
cristybb503372010-05-27 20:51:26 +00001660 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001661 i;
1662
1663 assert(pixels != (Quantum **) NULL);
cristybb503372010-05-27 20:51:26 +00001664 for (i=0; i < (ssize_t) GetOpenMPMaximumThreads(); i++)
cristy3ed852e2009-09-05 21:47:34 +00001665 if (pixels[i] != (Quantum *) NULL)
1666 pixels[i]=(Quantum *) RelinquishMagickMemory(pixels[i]);
1667 pixels=(Quantum **) RelinquishAlignedMemory(pixels);
1668 return(pixels);
1669}
1670
1671static Quantum **AcquirePixelThreadSet(const size_t count)
1672{
cristybb503372010-05-27 20:51:26 +00001673 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001674 i;
1675
1676 Quantum
1677 **pixels;
1678
cristybb503372010-05-27 20:51:26 +00001679 size_t
cristy3ed852e2009-09-05 21:47:34 +00001680 number_threads;
1681
1682 number_threads=GetOpenMPMaximumThreads();
1683 pixels=(Quantum **) AcquireAlignedMemory(number_threads,sizeof(*pixels));
1684 if (pixels == (Quantum **) NULL)
1685 return((Quantum **) NULL);
1686 (void) ResetMagickMemory(pixels,0,number_threads*sizeof(*pixels));
cristybb503372010-05-27 20:51:26 +00001687 for (i=0; i < (ssize_t) number_threads; i++)
cristy3ed852e2009-09-05 21:47:34 +00001688 {
1689 pixels[i]=(Quantum *) AcquireQuantumMemory(count,sizeof(**pixels));
1690 if (pixels[i] == (Quantum *) NULL)
1691 return(DestroyPixelThreadSet(pixels));
1692 }
1693 return(pixels);
1694}
1695
cristybb503372010-05-27 20:51:26 +00001696static void Hull(const ssize_t x_offset,const ssize_t y_offset,
1697 const size_t columns,const size_t rows,Quantum *f,Quantum *g,
cristy3ed852e2009-09-05 21:47:34 +00001698 const int polarity)
1699{
cristybb503372010-05-27 20:51:26 +00001700 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001701 y;
1702
1703 MagickRealType
1704 v;
1705
cristybb503372010-05-27 20:51:26 +00001706 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001707 x;
1708
1709 register Quantum
1710 *p,
1711 *q,
1712 *r,
1713 *s;
1714
1715 assert(f != (Quantum *) NULL);
1716 assert(g != (Quantum *) NULL);
1717 p=f+(columns+2);
1718 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001719 r=p+(y_offset*((ssize_t) columns+2)+x_offset);
1720 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001721 {
1722 p++;
1723 q++;
1724 r++;
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) (*p);
1729 if ((MagickRealType) *r >= (v+(MagickRealType) ScaleCharToQuantum(2)))
1730 v+=ScaleCharToQuantum(1);
1731 *q=(Quantum) v;
1732 p++;
1733 q++;
1734 r++;
1735 }
1736 else
cristybb503372010-05-27 20:51:26 +00001737 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001738 {
1739 v=(MagickRealType) (*p);
1740 if ((MagickRealType) *r <= (v-(MagickRealType) ScaleCharToQuantum(2)))
cristybb503372010-05-27 20:51:26 +00001741 v-=(ssize_t) ScaleCharToQuantum(1);
cristy3ed852e2009-09-05 21:47:34 +00001742 *q=(Quantum) v;
1743 p++;
1744 q++;
1745 r++;
1746 }
1747 p++;
1748 q++;
1749 r++;
1750 }
1751 p=f+(columns+2);
1752 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001753 r=q+(y_offset*((ssize_t) columns+2)+x_offset);
1754 s=q-(y_offset*((ssize_t) columns+2)+x_offset);
1755 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001756 {
1757 p++;
1758 q++;
1759 r++;
1760 s++;
1761 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001762 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001763 {
1764 v=(MagickRealType) (*q);
1765 if (((MagickRealType) *s >=
1766 (v+(MagickRealType) ScaleCharToQuantum(2))) &&
1767 ((MagickRealType) *r > v))
1768 v+=ScaleCharToQuantum(1);
1769 *p=(Quantum) v;
1770 p++;
1771 q++;
1772 r++;
1773 s++;
1774 }
1775 else
cristybb503372010-05-27 20:51:26 +00001776 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001777 {
1778 v=(MagickRealType) (*q);
1779 if (((MagickRealType) *s <=
1780 (v-(MagickRealType) ScaleCharToQuantum(2))) &&
1781 ((MagickRealType) *r < v))
1782 v-=(MagickRealType) ScaleCharToQuantum(1);
1783 *p=(Quantum) v;
1784 p++;
1785 q++;
1786 r++;
1787 s++;
1788 }
1789 p++;
1790 q++;
1791 r++;
1792 s++;
1793 }
1794}
1795
1796MagickExport Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1797{
1798#define DespeckleImageTag "Despeckle/Image"
1799
cristy2407fc22009-09-11 00:55:25 +00001800 CacheView
1801 *despeckle_view,
1802 *image_view;
1803
cristy3ed852e2009-09-05 21:47:34 +00001804 Image
1805 *despeckle_image;
1806
cristybb503372010-05-27 20:51:26 +00001807 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001808 channel;
1809
1810 MagickBooleanType
1811 status;
1812
1813 Quantum
cristyfa112112010-01-04 17:48:07 +00001814 **restrict buffers,
1815 **restrict pixels;
cristy3ed852e2009-09-05 21:47:34 +00001816
1817 size_t
1818 length;
1819
cristybb503372010-05-27 20:51:26 +00001820 static const ssize_t
cristy691a29e2009-09-11 00:44:10 +00001821 X[4] = {0, 1, 1,-1},
1822 Y[4] = {1, 0, 1, 1};
cristy3ed852e2009-09-05 21:47:34 +00001823
cristy3ed852e2009-09-05 21:47:34 +00001824 /*
1825 Allocate despeckled image.
1826 */
1827 assert(image != (const Image *) NULL);
1828 assert(image->signature == MagickSignature);
1829 if (image->debug != MagickFalse)
1830 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1831 assert(exception != (ExceptionInfo *) NULL);
1832 assert(exception->signature == MagickSignature);
1833 despeckle_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1834 exception);
1835 if (despeckle_image == (Image *) NULL)
1836 return((Image *) NULL);
1837 if (SetImageStorageClass(despeckle_image,DirectClass) == MagickFalse)
1838 {
1839 InheritException(exception,&despeckle_image->exception);
1840 despeckle_image=DestroyImage(despeckle_image);
1841 return((Image *) NULL);
1842 }
1843 /*
1844 Allocate image buffers.
1845 */
1846 length=(size_t) ((image->columns+2)*(image->rows+2));
1847 pixels=AcquirePixelThreadSet(length);
1848 buffers=AcquirePixelThreadSet(length);
1849 if ((pixels == (Quantum **) NULL) || (buffers == (Quantum **) NULL))
1850 {
1851 if (buffers != (Quantum **) NULL)
1852 buffers=DestroyPixelThreadSet(buffers);
1853 if (pixels != (Quantum **) NULL)
1854 pixels=DestroyPixelThreadSet(pixels);
1855 despeckle_image=DestroyImage(despeckle_image);
1856 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1857 }
1858 /*
1859 Reduce speckle in the image.
1860 */
1861 status=MagickTrue;
1862 image_view=AcquireCacheView(image);
1863 despeckle_view=AcquireCacheView(despeckle_image);
cristyb5d5f722009-11-04 03:03:49 +00001864#if defined(MAGICKCORE_OPENMP_SUPPORT)
1865 #pragma omp parallel for schedule(dynamic,4) shared(status)
cristy3ed852e2009-09-05 21:47:34 +00001866#endif
1867 for (channel=0; channel <= 3; channel++)
1868 {
cristy6ebe97c2010-07-03 01:17:28 +00001869 int
1870 id;
1871
cristybb503372010-05-27 20:51:26 +00001872 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001873 j,
1874 y;
1875
cristybb503372010-05-27 20:51:26 +00001876 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001877 i,
1878 x;
1879
1880 register Quantum
1881 *buffer,
1882 *pixel;
1883
1884 if (status == MagickFalse)
1885 continue;
cristy691a29e2009-09-11 00:44:10 +00001886 id=GetOpenMPThreadId();
1887 pixel=pixels[id];
cristy3ed852e2009-09-05 21:47:34 +00001888 (void) ResetMagickMemory(pixel,0,length*sizeof(*pixel));
cristy691a29e2009-09-11 00:44:10 +00001889 buffer=buffers[id];
cristybb503372010-05-27 20:51:26 +00001890 j=(ssize_t) image->columns+2;
1891 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001892 {
1893 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001894 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001895
1896 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1897 if (p == (const PixelPacket *) NULL)
1898 break;
1899 j++;
cristybb503372010-05-27 20:51:26 +00001900 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001901 {
1902 switch (channel)
1903 {
cristyce70c172010-01-07 17:15:30 +00001904 case 0: pixel[j]=GetRedPixelComponent(p); break;
1905 case 1: pixel[j]=GetGreenPixelComponent(p); break;
1906 case 2: pixel[j]=GetBluePixelComponent(p); break;
1907 case 3: pixel[j]=GetOpacityPixelComponent(p); break;
cristy3ed852e2009-09-05 21:47:34 +00001908 default: break;
1909 }
1910 p++;
1911 j++;
1912 }
1913 j++;
1914 }
cristy3ed852e2009-09-05 21:47:34 +00001915 (void) ResetMagickMemory(buffer,0,length*sizeof(*buffer));
1916 for (i=0; i < 4; i++)
1917 {
1918 Hull(X[i],Y[i],image->columns,image->rows,pixel,buffer,1);
1919 Hull(-X[i],-Y[i],image->columns,image->rows,pixel,buffer,1);
1920 Hull(-X[i],-Y[i],image->columns,image->rows,pixel,buffer,-1);
1921 Hull(X[i],Y[i],image->columns,image->rows,pixel,buffer,-1);
1922 }
cristybb503372010-05-27 20:51:26 +00001923 j=(ssize_t) image->columns+2;
1924 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001925 {
1926 MagickBooleanType
1927 sync;
1928
1929 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001930 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001931
1932 q=GetCacheViewAuthenticPixels(despeckle_view,0,y,despeckle_image->columns,
1933 1,exception);
1934 if (q == (PixelPacket *) NULL)
1935 break;
1936 j++;
cristybb503372010-05-27 20:51:26 +00001937 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001938 {
1939 switch (channel)
1940 {
1941 case 0: q->red=pixel[j]; break;
1942 case 1: q->green=pixel[j]; break;
1943 case 2: q->blue=pixel[j]; break;
1944 case 3: q->opacity=pixel[j]; break;
1945 default: break;
1946 }
1947 q++;
1948 j++;
1949 }
1950 sync=SyncCacheViewAuthenticPixels(despeckle_view,exception);
1951 if (sync == MagickFalse)
1952 {
1953 status=MagickFalse;
1954 break;
1955 }
1956 j++;
1957 }
1958 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1959 {
1960 MagickBooleanType
1961 proceed;
1962
cristyb5d5f722009-11-04 03:03:49 +00001963#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001964 #pragma omp critical (MagickCore_DespeckleImage)
1965#endif
cristybb503372010-05-27 20:51:26 +00001966 proceed=SetImageProgress(image,DespeckleImageTag,(MagickOffsetType)
1967 channel,3);
cristy3ed852e2009-09-05 21:47:34 +00001968 if (proceed == MagickFalse)
1969 status=MagickFalse;
1970 }
1971 }
1972 despeckle_view=DestroyCacheView(despeckle_view);
1973 image_view=DestroyCacheView(image_view);
1974 buffers=DestroyPixelThreadSet(buffers);
1975 pixels=DestroyPixelThreadSet(pixels);
1976 despeckle_image->type=image->type;
1977 if (status == MagickFalse)
1978 despeckle_image=DestroyImage(despeckle_image);
1979 return(despeckle_image);
1980}
1981
1982/*
1983%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1984% %
1985% %
1986% %
1987% E d g e I m a g e %
1988% %
1989% %
1990% %
1991%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1992%
1993% EdgeImage() finds edges in an image. Radius defines the radius of the
1994% convolution filter. Use a radius of 0 and EdgeImage() selects a suitable
1995% radius for you.
1996%
1997% The format of the EdgeImage method is:
1998%
1999% Image *EdgeImage(const Image *image,const double radius,
2000% ExceptionInfo *exception)
2001%
2002% A description of each parameter follows:
2003%
2004% o image: the image.
2005%
2006% o radius: the radius of the pixel neighborhood.
2007%
2008% o exception: return any errors or warnings in this structure.
2009%
2010*/
2011MagickExport Image *EdgeImage(const Image *image,const double radius,
2012 ExceptionInfo *exception)
2013{
2014 Image
2015 *edge_image;
2016
2017 double
2018 *kernel;
2019
cristybb503372010-05-27 20:51:26 +00002020 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002021 i;
2022
cristybb503372010-05-27 20:51:26 +00002023 size_t
cristy3ed852e2009-09-05 21:47:34 +00002024 width;
2025
2026 assert(image != (const Image *) NULL);
2027 assert(image->signature == MagickSignature);
2028 if (image->debug != MagickFalse)
2029 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2030 assert(exception != (ExceptionInfo *) NULL);
2031 assert(exception->signature == MagickSignature);
2032 width=GetOptimalKernelWidth1D(radius,0.5);
2033 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2034 if (kernel == (double *) NULL)
2035 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00002036 for (i=0; i < (ssize_t) (width*width); i++)
cristy3ed852e2009-09-05 21:47:34 +00002037 kernel[i]=(-1.0);
2038 kernel[i/2]=(double) (width*width-1.0);
2039 edge_image=ConvolveImage(image,width,kernel,exception);
2040 kernel=(double *) RelinquishMagickMemory(kernel);
2041 return(edge_image);
2042}
2043
2044/*
2045%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2046% %
2047% %
2048% %
2049% E m b o s s I m a g e %
2050% %
2051% %
2052% %
2053%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2054%
2055% EmbossImage() returns a grayscale image with a three-dimensional effect.
2056% We convolve the image with a Gaussian operator of the given radius and
2057% standard deviation (sigma). For reasonable results, radius should be
2058% larger than sigma. Use a radius of 0 and Emboss() selects a suitable
2059% radius for you.
2060%
2061% The format of the EmbossImage method is:
2062%
2063% Image *EmbossImage(const Image *image,const double radius,
2064% const double sigma,ExceptionInfo *exception)
2065%
2066% A description of each parameter follows:
2067%
2068% o image: the image.
2069%
2070% o radius: the radius of the pixel neighborhood.
2071%
2072% o sigma: the standard deviation of the Gaussian, in pixels.
2073%
2074% o exception: return any errors or warnings in this structure.
2075%
2076*/
2077MagickExport Image *EmbossImage(const Image *image,const double radius,
2078 const double sigma,ExceptionInfo *exception)
2079{
2080 double
2081 *kernel;
2082
2083 Image
2084 *emboss_image;
2085
cristybb503372010-05-27 20:51:26 +00002086 ssize_t
cristy47e00502009-12-17 19:19:57 +00002087 j,
2088 k,
cristy3ed852e2009-09-05 21:47:34 +00002089 u,
2090 v;
2091
cristybb503372010-05-27 20:51:26 +00002092 register ssize_t
cristy47e00502009-12-17 19:19:57 +00002093 i;
2094
cristybb503372010-05-27 20:51:26 +00002095 size_t
cristy3ed852e2009-09-05 21:47:34 +00002096 width;
2097
2098 assert(image != (Image *) NULL);
2099 assert(image->signature == MagickSignature);
2100 if (image->debug != MagickFalse)
2101 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2102 assert(exception != (ExceptionInfo *) NULL);
2103 assert(exception->signature == MagickSignature);
2104 width=GetOptimalKernelWidth2D(radius,sigma);
2105 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2106 if (kernel == (double *) NULL)
2107 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00002108 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +00002109 k=j;
2110 i=0;
2111 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002112 {
cristy47e00502009-12-17 19:19:57 +00002113 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00002114 {
cristy47e00502009-12-17 19:19:57 +00002115 kernel[i]=((u < 0) || (v < 0) ? -8.0 : 8.0)*
2116 exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
2117 (2.0*MagickPI*MagickSigma*MagickSigma);
2118 if (u != k)
cristy3ed852e2009-09-05 21:47:34 +00002119 kernel[i]=0.0;
2120 i++;
2121 }
cristy47e00502009-12-17 19:19:57 +00002122 k--;
cristy3ed852e2009-09-05 21:47:34 +00002123 }
2124 emboss_image=ConvolveImage(image,width,kernel,exception);
2125 if (emboss_image != (Image *) NULL)
2126 (void) EqualizeImage(emboss_image);
2127 kernel=(double *) RelinquishMagickMemory(kernel);
2128 return(emboss_image);
2129}
2130
2131/*
2132%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2133% %
2134% %
2135% %
cristy56a9e512010-01-06 18:18:55 +00002136% F i l t e r I m a g e %
2137% %
2138% %
2139% %
2140%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2141%
2142% FilterImage() applies a custom convolution kernel to the image.
2143%
2144% The format of the FilterImage method is:
2145%
cristy2be15382010-01-21 02:38:03 +00002146% Image *FilterImage(const Image *image,const KernelInfo *kernel,
cristy56a9e512010-01-06 18:18:55 +00002147% ExceptionInfo *exception)
2148% Image *FilterImageChannel(const Image *image,const ChannelType channel,
cristy2be15382010-01-21 02:38:03 +00002149% const KernelInfo *kernel,ExceptionInfo *exception)
cristy56a9e512010-01-06 18:18:55 +00002150%
2151% A description of each parameter follows:
2152%
2153% o image: the image.
2154%
2155% o channel: the channel type.
2156%
2157% o kernel: the filtering kernel.
2158%
2159% o exception: return any errors or warnings in this structure.
2160%
2161*/
2162
cristy2be15382010-01-21 02:38:03 +00002163MagickExport Image *FilterImage(const Image *image,const KernelInfo *kernel,
cristy56a9e512010-01-06 18:18:55 +00002164 ExceptionInfo *exception)
2165{
2166 Image
2167 *filter_image;
2168
2169 filter_image=FilterImageChannel(image,DefaultChannels,kernel,exception);
2170 return(filter_image);
2171}
2172
2173MagickExport Image *FilterImageChannel(const Image *image,
cristy2be15382010-01-21 02:38:03 +00002174 const ChannelType channel,const KernelInfo *kernel,ExceptionInfo *exception)
cristy56a9e512010-01-06 18:18:55 +00002175{
2176#define FilterImageTag "Filter/Image"
2177
2178 CacheView
2179 *filter_view,
2180 *image_view;
2181
cristy56a9e512010-01-06 18:18:55 +00002182 Image
2183 *filter_image;
2184
cristy56a9e512010-01-06 18:18:55 +00002185 MagickBooleanType
2186 status;
2187
cristybb503372010-05-27 20:51:26 +00002188 MagickOffsetType
2189 progress;
2190
cristy56a9e512010-01-06 18:18:55 +00002191 MagickPixelPacket
2192 bias;
2193
cristybb503372010-05-27 20:51:26 +00002194 ssize_t
2195 y;
2196
cristy56a9e512010-01-06 18:18:55 +00002197 /*
2198 Initialize filter image attributes.
2199 */
2200 assert(image != (Image *) NULL);
2201 assert(image->signature == MagickSignature);
2202 if (image->debug != MagickFalse)
2203 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2204 assert(exception != (ExceptionInfo *) NULL);
2205 assert(exception->signature == MagickSignature);
2206 if ((kernel->width % 2) == 0)
2207 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
2208 filter_image=CloneImage(image,0,0,MagickTrue,exception);
2209 if (filter_image == (Image *) NULL)
2210 return((Image *) NULL);
2211 if (SetImageStorageClass(filter_image,DirectClass) == MagickFalse)
2212 {
2213 InheritException(exception,&filter_image->exception);
2214 filter_image=DestroyImage(filter_image);
2215 return((Image *) NULL);
2216 }
2217 if (image->debug != MagickFalse)
2218 {
2219 char
2220 format[MaxTextExtent],
2221 *message;
2222
cristybb503372010-05-27 20:51:26 +00002223 ssize_t
cristy56a9e512010-01-06 18:18:55 +00002224 u,
2225 v;
2226
2227 register const double
2228 *k;
2229
2230 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00002231 " FilterImage with %.20gx%.20g kernel:",(double) kernel->width,(double)
2232 kernel->height);
cristy56a9e512010-01-06 18:18:55 +00002233 message=AcquireString("");
2234 k=kernel->values;
cristybb503372010-05-27 20:51:26 +00002235 for (v=0; v < (ssize_t) kernel->height; v++)
cristy56a9e512010-01-06 18:18:55 +00002236 {
2237 *message='\0';
cristye8c25f92010-06-03 00:53:06 +00002238 (void) FormatMagickString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy56a9e512010-01-06 18:18:55 +00002239 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00002240 for (u=0; u < (ssize_t) kernel->width; u++)
cristy56a9e512010-01-06 18:18:55 +00002241 {
cristye7f51092010-01-17 00:39:37 +00002242 (void) FormatMagickString(format,MaxTextExtent,"%g ",*k++);
cristy56a9e512010-01-06 18:18:55 +00002243 (void) ConcatenateString(&message,format);
2244 }
2245 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
2246 }
2247 message=DestroyString(message);
2248 }
cristy36826ab2010-03-06 01:29:30 +00002249 status=AccelerateConvolveImage(image,kernel,filter_image,exception);
cristyd43a46b2010-01-21 02:13:41 +00002250 if (status == MagickTrue)
2251 return(filter_image);
cristy56a9e512010-01-06 18:18:55 +00002252 /*
2253 Filter image.
2254 */
2255 status=MagickTrue;
2256 progress=0;
2257 GetMagickPixelPacket(image,&bias);
2258 SetMagickPixelPacketBias(image,&bias);
2259 image_view=AcquireCacheView(image);
2260 filter_view=AcquireCacheView(filter_image);
2261#if defined(MAGICKCORE_OPENMP_SUPPORT)
2262 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2263#endif
cristybb503372010-05-27 20:51:26 +00002264 for (y=0; y < (ssize_t) image->rows; y++)
cristy56a9e512010-01-06 18:18:55 +00002265 {
2266 MagickBooleanType
2267 sync;
2268
2269 register const IndexPacket
2270 *restrict indexes;
2271
2272 register const PixelPacket
2273 *restrict p;
2274
2275 register IndexPacket
2276 *restrict filter_indexes;
2277
cristybb503372010-05-27 20:51:26 +00002278 register ssize_t
cristy56a9e512010-01-06 18:18:55 +00002279 x;
2280
2281 register PixelPacket
2282 *restrict q;
2283
2284 if (status == MagickFalse)
2285 continue;
cristybb503372010-05-27 20:51:26 +00002286 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) kernel->width/2L),
2287 y-(ssize_t) (kernel->height/2L),image->columns+kernel->width,kernel->height,
cristy36826ab2010-03-06 01:29:30 +00002288 exception);
cristy56a9e512010-01-06 18:18:55 +00002289 q=GetCacheViewAuthenticPixels(filter_view,0,y,filter_image->columns,1,
2290 exception);
2291 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2292 {
2293 status=MagickFalse;
2294 continue;
2295 }
2296 indexes=GetCacheViewVirtualIndexQueue(image_view);
2297 filter_indexes=GetCacheViewAuthenticIndexQueue(filter_view);
cristybb503372010-05-27 20:51:26 +00002298 for (x=0; x < (ssize_t) image->columns; x++)
cristy56a9e512010-01-06 18:18:55 +00002299 {
cristybb503372010-05-27 20:51:26 +00002300 ssize_t
cristy56a9e512010-01-06 18:18:55 +00002301 v;
2302
2303 MagickPixelPacket
2304 pixel;
2305
2306 register const double
2307 *restrict k;
2308
2309 register const PixelPacket
2310 *restrict kernel_pixels;
2311
cristybb503372010-05-27 20:51:26 +00002312 register ssize_t
cristy56a9e512010-01-06 18:18:55 +00002313 u;
2314
2315 pixel=bias;
cristy36826ab2010-03-06 01:29:30 +00002316 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002317 kernel_pixels=p;
2318 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
2319 {
cristybb503372010-05-27 20:51:26 +00002320 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002321 {
cristybb503372010-05-27 20:51:26 +00002322 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002323 {
2324 pixel.red+=(*k)*kernel_pixels[u].red;
2325 pixel.green+=(*k)*kernel_pixels[u].green;
2326 pixel.blue+=(*k)*kernel_pixels[u].blue;
2327 k++;
2328 }
cristy36826ab2010-03-06 01:29:30 +00002329 kernel_pixels+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002330 }
2331 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002332 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002333 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002334 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002335 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002336 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002337 if ((channel & OpacityChannel) != 0)
2338 {
cristy36826ab2010-03-06 01:29:30 +00002339 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002340 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00002341 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002342 {
cristybb503372010-05-27 20:51:26 +00002343 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002344 {
2345 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
2346 k++;
2347 }
cristy36826ab2010-03-06 01:29:30 +00002348 kernel_pixels+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002349 }
cristyce70c172010-01-07 17:15:30 +00002350 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002351 }
2352 if (((channel & IndexChannel) != 0) &&
2353 (image->colorspace == CMYKColorspace))
2354 {
2355 register const IndexPacket
2356 *restrict kernel_indexes;
2357
cristy36826ab2010-03-06 01:29:30 +00002358 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002359 kernel_indexes=indexes;
cristybb503372010-05-27 20:51:26 +00002360 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002361 {
cristybb503372010-05-27 20:51:26 +00002362 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002363 {
2364 pixel.index+=(*k)*kernel_indexes[u];
2365 k++;
2366 }
cristy36826ab2010-03-06 01:29:30 +00002367 kernel_indexes+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002368 }
cristyce70c172010-01-07 17:15:30 +00002369 filter_indexes[x]=ClampToQuantum(pixel.index);
cristy56a9e512010-01-06 18:18:55 +00002370 }
2371 }
2372 else
2373 {
2374 MagickRealType
2375 alpha,
2376 gamma;
2377
2378 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00002379 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002380 {
cristybb503372010-05-27 20:51:26 +00002381 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002382 {
2383 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
2384 kernel_pixels[u].opacity));
2385 pixel.red+=(*k)*alpha*kernel_pixels[u].red;
2386 pixel.green+=(*k)*alpha*kernel_pixels[u].green;
2387 pixel.blue+=(*k)*alpha*kernel_pixels[u].blue;
2388 gamma+=(*k)*alpha;
2389 k++;
2390 }
cristy36826ab2010-03-06 01:29:30 +00002391 kernel_pixels+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002392 }
2393 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
2394 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002395 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002396 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002397 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002398 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002399 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002400 if ((channel & OpacityChannel) != 0)
2401 {
cristy36826ab2010-03-06 01:29:30 +00002402 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002403 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00002404 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002405 {
cristybb503372010-05-27 20:51:26 +00002406 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002407 {
2408 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
2409 k++;
2410 }
cristy36826ab2010-03-06 01:29:30 +00002411 kernel_pixels+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002412 }
cristyce70c172010-01-07 17:15:30 +00002413 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002414 }
2415 if (((channel & IndexChannel) != 0) &&
2416 (image->colorspace == CMYKColorspace))
2417 {
2418 register const IndexPacket
2419 *restrict kernel_indexes;
2420
cristy36826ab2010-03-06 01:29:30 +00002421 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002422 kernel_pixels=p;
2423 kernel_indexes=indexes;
cristybb503372010-05-27 20:51:26 +00002424 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002425 {
cristybb503372010-05-27 20:51:26 +00002426 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002427 {
2428 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
2429 kernel_pixels[u].opacity));
2430 pixel.index+=(*k)*alpha*kernel_indexes[u];
2431 k++;
2432 }
cristy36826ab2010-03-06 01:29:30 +00002433 kernel_pixels+=image->columns+kernel->width;
2434 kernel_indexes+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002435 }
cristy2115aea2010-01-09 23:16:08 +00002436 filter_indexes[x]=ClampToQuantum(gamma*
2437 GetIndexPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002438 }
2439 }
2440 p++;
2441 q++;
2442 }
2443 sync=SyncCacheViewAuthenticPixels(filter_view,exception);
2444 if (sync == MagickFalse)
2445 status=MagickFalse;
2446 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2447 {
2448 MagickBooleanType
2449 proceed;
2450
2451#if defined(MAGICKCORE_OPENMP_SUPPORT)
2452 #pragma omp critical (MagickCore_FilterImageChannel)
2453#endif
2454 proceed=SetImageProgress(image,FilterImageTag,progress++,image->rows);
2455 if (proceed == MagickFalse)
2456 status=MagickFalse;
2457 }
2458 }
2459 filter_image->type=image->type;
2460 filter_view=DestroyCacheView(filter_view);
2461 image_view=DestroyCacheView(image_view);
cristy56a9e512010-01-06 18:18:55 +00002462 if (status == MagickFalse)
2463 filter_image=DestroyImage(filter_image);
2464 return(filter_image);
2465}
2466
2467/*
2468%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2469% %
2470% %
2471% %
cristy3ed852e2009-09-05 21:47:34 +00002472% G a u s s i a n B l u r I m a g e %
2473% %
2474% %
2475% %
2476%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2477%
2478% GaussianBlurImage() blurs an image. We convolve the image with a
2479% Gaussian operator of the given radius and standard deviation (sigma).
2480% For reasonable results, the radius should be larger than sigma. Use a
2481% radius of 0 and GaussianBlurImage() selects a suitable radius for you
2482%
2483% The format of the GaussianBlurImage method is:
2484%
2485% Image *GaussianBlurImage(const Image *image,onst double radius,
2486% const double sigma,ExceptionInfo *exception)
2487% Image *GaussianBlurImageChannel(const Image *image,
2488% const ChannelType channel,const double radius,const double sigma,
2489% ExceptionInfo *exception)
2490%
2491% A description of each parameter follows:
2492%
2493% o image: the image.
2494%
2495% o channel: the channel type.
2496%
2497% o radius: the radius of the Gaussian, in pixels, not counting the center
2498% pixel.
2499%
2500% o sigma: the standard deviation of the Gaussian, in pixels.
2501%
2502% o exception: return any errors or warnings in this structure.
2503%
2504*/
2505
2506MagickExport Image *GaussianBlurImage(const Image *image,const double radius,
2507 const double sigma,ExceptionInfo *exception)
2508{
2509 Image
2510 *blur_image;
2511
2512 blur_image=GaussianBlurImageChannel(image,DefaultChannels,radius,sigma,
2513 exception);
2514 return(blur_image);
2515}
2516
2517MagickExport Image *GaussianBlurImageChannel(const Image *image,
2518 const ChannelType channel,const double radius,const double sigma,
2519 ExceptionInfo *exception)
2520{
2521 double
2522 *kernel;
2523
2524 Image
2525 *blur_image;
2526
cristybb503372010-05-27 20:51:26 +00002527 ssize_t
cristy47e00502009-12-17 19:19:57 +00002528 j,
cristy3ed852e2009-09-05 21:47:34 +00002529 u,
2530 v;
2531
cristybb503372010-05-27 20:51:26 +00002532 register ssize_t
cristy47e00502009-12-17 19:19:57 +00002533 i;
2534
cristybb503372010-05-27 20:51:26 +00002535 size_t
cristy3ed852e2009-09-05 21:47:34 +00002536 width;
2537
2538 assert(image != (const Image *) NULL);
2539 assert(image->signature == MagickSignature);
2540 if (image->debug != MagickFalse)
2541 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2542 assert(exception != (ExceptionInfo *) NULL);
2543 assert(exception->signature == MagickSignature);
2544 width=GetOptimalKernelWidth2D(radius,sigma);
2545 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2546 if (kernel == (double *) NULL)
2547 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00002548 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00002549 i=0;
cristy47e00502009-12-17 19:19:57 +00002550 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002551 {
cristy47e00502009-12-17 19:19:57 +00002552 for (u=(-j); u <= j; u++)
2553 kernel[i++]=exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
2554 (2.0*MagickPI*MagickSigma*MagickSigma);
cristy3ed852e2009-09-05 21:47:34 +00002555 }
2556 blur_image=ConvolveImageChannel(image,channel,width,kernel,exception);
2557 kernel=(double *) RelinquishMagickMemory(kernel);
2558 return(blur_image);
2559}
2560
2561/*
2562%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2563% %
2564% %
2565% %
2566% M e d i a n F i l t e r I m a g e %
2567% %
2568% %
2569% %
2570%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2571%
2572% MedianFilterImage() applies a digital filter that improves the quality
2573% of a noisy image. Each pixel is replaced by the median in a set of
2574% neighboring pixels as defined by radius.
2575%
2576% The algorithm was contributed by Mike Edmonds and implements an insertion
2577% sort for selecting median color-channel values. For more on this algorithm
2578% see "Skip Lists: A probabilistic Alternative to Balanced Trees" by William
2579% Pugh in the June 1990 of Communications of the ACM.
2580%
2581% The format of the MedianFilterImage method is:
2582%
2583% Image *MedianFilterImage(const Image *image,const double radius,
2584% ExceptionInfo *exception)
2585%
2586% A description of each parameter follows:
2587%
2588% o image: the image.
2589%
2590% o radius: the radius of the pixel neighborhood.
2591%
2592% o exception: return any errors or warnings in this structure.
2593%
2594*/
2595
2596#define MedianListChannels 5
2597
2598typedef struct _MedianListNode
2599{
cristybb503372010-05-27 20:51:26 +00002600 size_t
cristy3ed852e2009-09-05 21:47:34 +00002601 next[9],
2602 count,
2603 signature;
2604} MedianListNode;
2605
2606typedef struct _MedianSkipList
2607{
cristybb503372010-05-27 20:51:26 +00002608 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002609 level;
2610
2611 MedianListNode
2612 *nodes;
2613} MedianSkipList;
2614
2615typedef struct _MedianPixelList
2616{
cristybb503372010-05-27 20:51:26 +00002617 size_t
cristy3ed852e2009-09-05 21:47:34 +00002618 center,
2619 seed,
2620 signature;
2621
2622 MedianSkipList
2623 lists[MedianListChannels];
2624} MedianPixelList;
2625
2626static MedianPixelList *DestroyMedianPixelList(MedianPixelList *pixel_list)
2627{
cristybb503372010-05-27 20:51:26 +00002628 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002629 i;
2630
2631 if (pixel_list == (MedianPixelList *) NULL)
2632 return((MedianPixelList *) NULL);
2633 for (i=0; i < MedianListChannels; i++)
2634 if (pixel_list->lists[i].nodes != (MedianListNode *) NULL)
2635 pixel_list->lists[i].nodes=(MedianListNode *) RelinquishMagickMemory(
2636 pixel_list->lists[i].nodes);
2637 pixel_list=(MedianPixelList *) RelinquishAlignedMemory(pixel_list);
2638 return(pixel_list);
2639}
2640
2641static MedianPixelList **DestroyMedianPixelListThreadSet(
2642 MedianPixelList **pixel_list)
2643{
cristybb503372010-05-27 20:51:26 +00002644 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002645 i;
2646
2647 assert(pixel_list != (MedianPixelList **) NULL);
cristybb503372010-05-27 20:51:26 +00002648 for (i=0; i < (ssize_t) GetOpenMPMaximumThreads(); i++)
cristy3ed852e2009-09-05 21:47:34 +00002649 if (pixel_list[i] != (MedianPixelList *) NULL)
2650 pixel_list[i]=DestroyMedianPixelList(pixel_list[i]);
2651 pixel_list=(MedianPixelList **) RelinquishAlignedMemory(pixel_list);
2652 return(pixel_list);
2653}
2654
cristybb503372010-05-27 20:51:26 +00002655static MedianPixelList *AcquireMedianPixelList(const size_t width)
cristy3ed852e2009-09-05 21:47:34 +00002656{
2657 MedianPixelList
2658 *pixel_list;
2659
cristybb503372010-05-27 20:51:26 +00002660 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002661 i;
2662
2663 pixel_list=(MedianPixelList *) AcquireAlignedMemory(1,sizeof(*pixel_list));
2664 if (pixel_list == (MedianPixelList *) NULL)
2665 return(pixel_list);
2666 (void) ResetMagickMemory((void *) pixel_list,0,sizeof(*pixel_list));
2667 pixel_list->center=width*width/2;
2668 for (i=0; i < MedianListChannels; i++)
2669 {
2670 pixel_list->lists[i].nodes=(MedianListNode *) AcquireQuantumMemory(65537UL,
2671 sizeof(*pixel_list->lists[i].nodes));
2672 if (pixel_list->lists[i].nodes == (MedianListNode *) NULL)
2673 return(DestroyMedianPixelList(pixel_list));
2674 (void) ResetMagickMemory(pixel_list->lists[i].nodes,0,65537UL*
2675 sizeof(*pixel_list->lists[i].nodes));
2676 }
2677 pixel_list->signature=MagickSignature;
2678 return(pixel_list);
2679}
2680
2681static MedianPixelList **AcquireMedianPixelListThreadSet(
cristybb503372010-05-27 20:51:26 +00002682 const size_t width)
cristy3ed852e2009-09-05 21:47:34 +00002683{
cristybb503372010-05-27 20:51:26 +00002684 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002685 i;
2686
2687 MedianPixelList
2688 **pixel_list;
2689
cristybb503372010-05-27 20:51:26 +00002690 size_t
cristy3ed852e2009-09-05 21:47:34 +00002691 number_threads;
2692
2693 number_threads=GetOpenMPMaximumThreads();
2694 pixel_list=(MedianPixelList **) AcquireAlignedMemory(number_threads,
2695 sizeof(*pixel_list));
2696 if (pixel_list == (MedianPixelList **) NULL)
2697 return((MedianPixelList **) NULL);
2698 (void) ResetMagickMemory(pixel_list,0,number_threads*sizeof(*pixel_list));
cristybb503372010-05-27 20:51:26 +00002699 for (i=0; i < (ssize_t) number_threads; i++)
cristy3ed852e2009-09-05 21:47:34 +00002700 {
2701 pixel_list[i]=AcquireMedianPixelList(width);
2702 if (pixel_list[i] == (MedianPixelList *) NULL)
2703 return(DestroyMedianPixelListThreadSet(pixel_list));
2704 }
2705 return(pixel_list);
2706}
2707
2708static void AddNodeMedianPixelList(MedianPixelList *pixel_list,
cristybb503372010-05-27 20:51:26 +00002709 const ssize_t channel,const size_t color)
cristy3ed852e2009-09-05 21:47:34 +00002710{
cristybb503372010-05-27 20:51:26 +00002711 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002712 level;
2713
2714 register MedianSkipList
2715 *list;
2716
cristybb503372010-05-27 20:51:26 +00002717 size_t
cristy3ed852e2009-09-05 21:47:34 +00002718 search,
2719 update[9];
2720
2721 /*
2722 Initialize the node.
2723 */
2724 list=pixel_list->lists+channel;
2725 list->nodes[color].signature=pixel_list->signature;
2726 list->nodes[color].count=1;
2727 /*
cristy33c53022010-06-25 12:17:27 +00002728 Determine where it belongs in the list.
cristy3ed852e2009-09-05 21:47:34 +00002729 */
2730 search=65536UL;
2731 for (level=list->level; level >= 0; level--)
2732 {
2733 while (list->nodes[search].next[level] < color)
2734 search=list->nodes[search].next[level];
2735 update[level]=search;
2736 }
2737 /*
2738 Generate a pseudo-random level for this node.
2739 */
2740 for (level=0; ; level++)
2741 {
2742 pixel_list->seed=(pixel_list->seed*42893621L)+1L;
2743 if ((pixel_list->seed & 0x300) != 0x300)
2744 break;
2745 }
2746 if (level > 8)
2747 level=8;
2748 if (level > (list->level+2))
2749 level=list->level+2;
2750 /*
2751 If we're raising the list's level, link back to the root node.
2752 */
2753 while (level > list->level)
2754 {
2755 list->level++;
2756 update[list->level]=65536UL;
2757 }
2758 /*
2759 Link the node into the skip-list.
2760 */
2761 do
2762 {
2763 list->nodes[color].next[level]=list->nodes[update[level]].next[level];
2764 list->nodes[update[level]].next[level]=color;
2765 }
2766 while (level-- > 0);
2767}
2768
2769static MagickPixelPacket GetMedianPixelList(MedianPixelList *pixel_list)
2770{
2771 MagickPixelPacket
2772 pixel;
2773
cristybb503372010-05-27 20:51:26 +00002774 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002775 channel;
2776
2777 register MedianSkipList
2778 *list;
2779
cristybb503372010-05-27 20:51:26 +00002780 size_t
cristy3ed852e2009-09-05 21:47:34 +00002781 center,
2782 color,
2783 count;
2784
2785 unsigned short
2786 channels[MedianListChannels];
2787
2788 /*
2789 Find the median value for each of the color.
2790 */
2791 center=pixel_list->center;
2792 for (channel=0; channel < 5; channel++)
2793 {
2794 list=pixel_list->lists+channel;
2795 color=65536UL;
2796 count=0;
2797 do
2798 {
2799 color=list->nodes[color].next[0];
2800 count+=list->nodes[color].count;
2801 }
2802 while (count <= center);
2803 channels[channel]=(unsigned short) color;
2804 }
2805 GetMagickPixelPacket((const Image *) NULL,&pixel);
2806 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
2807 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
2808 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
2809 pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
2810 pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]);
2811 return(pixel);
2812}
2813
2814static inline void InsertMedianPixelList(const Image *image,
2815 const PixelPacket *pixel,const IndexPacket *indexes,
2816 MedianPixelList *pixel_list)
2817{
cristybb503372010-05-27 20:51:26 +00002818 size_t
cristy3ed852e2009-09-05 21:47:34 +00002819 signature;
2820
2821 unsigned short
2822 index;
2823
2824 index=ScaleQuantumToShort(pixel->red);
2825 signature=pixel_list->lists[0].nodes[index].signature;
2826 if (signature == pixel_list->signature)
2827 pixel_list->lists[0].nodes[index].count++;
2828 else
2829 AddNodeMedianPixelList(pixel_list,0,index);
2830 index=ScaleQuantumToShort(pixel->green);
2831 signature=pixel_list->lists[1].nodes[index].signature;
2832 if (signature == pixel_list->signature)
2833 pixel_list->lists[1].nodes[index].count++;
2834 else
2835 AddNodeMedianPixelList(pixel_list,1,index);
2836 index=ScaleQuantumToShort(pixel->blue);
2837 signature=pixel_list->lists[2].nodes[index].signature;
2838 if (signature == pixel_list->signature)
2839 pixel_list->lists[2].nodes[index].count++;
2840 else
2841 AddNodeMedianPixelList(pixel_list,2,index);
2842 index=ScaleQuantumToShort(pixel->opacity);
2843 signature=pixel_list->lists[3].nodes[index].signature;
2844 if (signature == pixel_list->signature)
2845 pixel_list->lists[3].nodes[index].count++;
2846 else
2847 AddNodeMedianPixelList(pixel_list,3,index);
2848 if (image->colorspace == CMYKColorspace)
2849 index=ScaleQuantumToShort(*indexes);
2850 signature=pixel_list->lists[4].nodes[index].signature;
2851 if (signature == pixel_list->signature)
2852 pixel_list->lists[4].nodes[index].count++;
2853 else
2854 AddNodeMedianPixelList(pixel_list,4,index);
2855}
2856
2857static void ResetMedianPixelList(MedianPixelList *pixel_list)
2858{
2859 int
2860 level;
2861
cristybb503372010-05-27 20:51:26 +00002862 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002863 channel;
2864
2865 register MedianListNode
2866 *root;
2867
2868 register MedianSkipList
2869 *list;
2870
2871 /*
2872 Reset the skip-list.
2873 */
2874 for (channel=0; channel < 5; channel++)
2875 {
2876 list=pixel_list->lists+channel;
2877 root=list->nodes+65536UL;
2878 list->level=0;
2879 for (level=0; level < 9; level++)
2880 root->next[level]=65536UL;
2881 }
2882 pixel_list->seed=pixel_list->signature++;
2883}
2884
2885MagickExport Image *MedianFilterImage(const Image *image,const double radius,
2886 ExceptionInfo *exception)
2887{
2888#define MedianFilterImageTag "MedianFilter/Image"
2889
cristyc4c8d132010-01-07 01:58:38 +00002890 CacheView
2891 *image_view,
2892 *median_view;
2893
cristy3ed852e2009-09-05 21:47:34 +00002894 Image
2895 *median_image;
2896
cristy3ed852e2009-09-05 21:47:34 +00002897 MagickBooleanType
2898 status;
2899
cristybb503372010-05-27 20:51:26 +00002900 MagickOffsetType
2901 progress;
2902
cristy3ed852e2009-09-05 21:47:34 +00002903 MedianPixelList
cristyfa112112010-01-04 17:48:07 +00002904 **restrict pixel_list;
cristy3ed852e2009-09-05 21:47:34 +00002905
cristybb503372010-05-27 20:51:26 +00002906 size_t
cristy3ed852e2009-09-05 21:47:34 +00002907 width;
2908
cristybb503372010-05-27 20:51:26 +00002909 ssize_t
2910 y;
2911
cristy3ed852e2009-09-05 21:47:34 +00002912 /*
2913 Initialize median image attributes.
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 assert(exception != (ExceptionInfo *) NULL);
2920 assert(exception->signature == MagickSignature);
2921 width=GetOptimalKernelWidth2D(radius,0.5);
2922 if ((image->columns < width) || (image->rows < width))
2923 ThrowImageException(OptionError,"ImageSmallerThanKernelRadius");
2924 median_image=CloneImage(image,image->columns,image->rows,MagickTrue,
2925 exception);
2926 if (median_image == (Image *) NULL)
2927 return((Image *) NULL);
2928 if (SetImageStorageClass(median_image,DirectClass) == MagickFalse)
2929 {
2930 InheritException(exception,&median_image->exception);
2931 median_image=DestroyImage(median_image);
2932 return((Image *) NULL);
2933 }
2934 pixel_list=AcquireMedianPixelListThreadSet(width);
2935 if (pixel_list == (MedianPixelList **) NULL)
2936 {
2937 median_image=DestroyImage(median_image);
2938 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2939 }
2940 /*
2941 Median filter each image row.
2942 */
2943 status=MagickTrue;
2944 progress=0;
2945 image_view=AcquireCacheView(image);
2946 median_view=AcquireCacheView(median_image);
cristyb5d5f722009-11-04 03:03:49 +00002947#if defined(MAGICKCORE_OPENMP_SUPPORT)
2948 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002949#endif
cristybb503372010-05-27 20:51:26 +00002950 for (y=0; y < (ssize_t) median_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002951 {
cristy6ebe97c2010-07-03 01:17:28 +00002952 int
2953 id;
2954
cristy3ed852e2009-09-05 21:47:34 +00002955 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002956 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002957
2958 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002959 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002960
2961 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002962 *restrict median_indexes;
cristy3ed852e2009-09-05 21:47:34 +00002963
cristybb503372010-05-27 20:51:26 +00002964 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002965 x;
2966
2967 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002968 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002969
2970 if (status == MagickFalse)
2971 continue;
cristy6ebe97c2010-07-03 01:17:28 +00002972 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
2973 (width/2L),image->columns+width,width,exception);
cristy3ed852e2009-09-05 21:47:34 +00002974 q=QueueCacheViewAuthenticPixels(median_view,0,y,median_image->columns,1,
2975 exception);
2976 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2977 {
2978 status=MagickFalse;
2979 continue;
2980 }
2981 indexes=GetCacheViewVirtualIndexQueue(image_view);
2982 median_indexes=GetCacheViewAuthenticIndexQueue(median_view);
2983 id=GetOpenMPThreadId();
cristybb503372010-05-27 20:51:26 +00002984 for (x=0; x < (ssize_t) median_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002985 {
2986 MagickPixelPacket
2987 pixel;
2988
2989 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002990 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +00002991
2992 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002993 *restrict s;
cristy3ed852e2009-09-05 21:47:34 +00002994
cristybb503372010-05-27 20:51:26 +00002995 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002996 u,
2997 v;
2998
2999 r=p;
3000 s=indexes+x;
3001 ResetMedianPixelList(pixel_list[id]);
cristybb503372010-05-27 20:51:26 +00003002 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003003 {
cristybb503372010-05-27 20:51:26 +00003004 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003005 InsertMedianPixelList(image,r+u,s+u,pixel_list[id]);
3006 r+=image->columns+width;
3007 s+=image->columns+width;
3008 }
3009 pixel=GetMedianPixelList(pixel_list[id]);
3010 SetPixelPacket(median_image,&pixel,q,median_indexes+x);
3011 p++;
3012 q++;
3013 }
3014 if (SyncCacheViewAuthenticPixels(median_view,exception) == MagickFalse)
3015 status=MagickFalse;
3016 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3017 {
3018 MagickBooleanType
3019 proceed;
3020
cristyb5d5f722009-11-04 03:03:49 +00003021#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003022 #pragma omp critical (MagickCore_MedianFilterImage)
3023#endif
3024 proceed=SetImageProgress(image,MedianFilterImageTag,progress++,
3025 image->rows);
3026 if (proceed == MagickFalse)
3027 status=MagickFalse;
3028 }
3029 }
3030 median_view=DestroyCacheView(median_view);
3031 image_view=DestroyCacheView(image_view);
3032 pixel_list=DestroyMedianPixelListThreadSet(pixel_list);
3033 return(median_image);
3034}
3035
3036/*
3037%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3038% %
3039% %
3040% %
3041% M o t i o n B l u r I m a g e %
3042% %
3043% %
3044% %
3045%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3046%
3047% MotionBlurImage() simulates motion blur. We convolve the image with a
3048% Gaussian operator of the given radius and standard deviation (sigma).
3049% For reasonable results, radius should be larger than sigma. Use a
3050% radius of 0 and MotionBlurImage() selects a suitable radius for you.
3051% Angle gives the angle of the blurring motion.
3052%
3053% Andrew Protano contributed this effect.
3054%
3055% The format of the MotionBlurImage method is:
3056%
3057% Image *MotionBlurImage(const Image *image,const double radius,
3058% const double sigma,const double angle,ExceptionInfo *exception)
3059% Image *MotionBlurImageChannel(const Image *image,const ChannelType channel,
3060% const double radius,const double sigma,const double angle,
3061% ExceptionInfo *exception)
3062%
3063% A description of each parameter follows:
3064%
3065% o image: the image.
3066%
3067% o channel: the channel type.
3068%
3069% o radius: the radius of the Gaussian, in pixels, not counting the center
3070% o radius: the radius of the Gaussian, in pixels, not counting
3071% the center pixel.
3072%
3073% o sigma: the standard deviation of the Gaussian, in pixels.
3074%
cristycee97112010-05-28 00:44:52 +00003075% o angle: Apply the effect along this angle.
cristy3ed852e2009-09-05 21:47:34 +00003076%
3077% o exception: return any errors or warnings in this structure.
3078%
3079*/
3080
cristybb503372010-05-27 20:51:26 +00003081static double *GetMotionBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +00003082{
cristy3ed852e2009-09-05 21:47:34 +00003083 double
cristy47e00502009-12-17 19:19:57 +00003084 *kernel,
cristy3ed852e2009-09-05 21:47:34 +00003085 normalize;
3086
cristybb503372010-05-27 20:51:26 +00003087 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003088 i;
3089
3090 /*
cristy47e00502009-12-17 19:19:57 +00003091 Generate a 1-D convolution kernel.
cristy3ed852e2009-09-05 21:47:34 +00003092 */
3093 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
3094 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
3095 if (kernel == (double *) NULL)
3096 return(kernel);
cristy3ed852e2009-09-05 21:47:34 +00003097 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00003098 for (i=0; i < (ssize_t) width; i++)
cristy47e00502009-12-17 19:19:57 +00003099 {
3100 kernel[i]=exp((-((double) i*i)/(double) (2.0*MagickSigma*MagickSigma)))/
3101 (MagickSQ2PI*MagickSigma);
cristy3ed852e2009-09-05 21:47:34 +00003102 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +00003103 }
cristybb503372010-05-27 20:51:26 +00003104 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00003105 kernel[i]/=normalize;
3106 return(kernel);
3107}
3108
3109MagickExport Image *MotionBlurImage(const Image *image,const double radius,
3110 const double sigma,const double angle,ExceptionInfo *exception)
3111{
3112 Image
3113 *motion_blur;
3114
3115 motion_blur=MotionBlurImageChannel(image,DefaultChannels,radius,sigma,angle,
3116 exception);
3117 return(motion_blur);
3118}
3119
3120MagickExport Image *MotionBlurImageChannel(const Image *image,
3121 const ChannelType channel,const double radius,const double sigma,
3122 const double angle,ExceptionInfo *exception)
3123{
cristyc4c8d132010-01-07 01:58:38 +00003124 CacheView
3125 *blur_view,
3126 *image_view;
3127
cristy3ed852e2009-09-05 21:47:34 +00003128 double
3129 *kernel;
3130
3131 Image
3132 *blur_image;
3133
cristy3ed852e2009-09-05 21:47:34 +00003134 MagickBooleanType
3135 status;
3136
cristybb503372010-05-27 20:51:26 +00003137 MagickOffsetType
3138 progress;
3139
cristy3ed852e2009-09-05 21:47:34 +00003140 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00003141 bias;
cristy3ed852e2009-09-05 21:47:34 +00003142
3143 OffsetInfo
3144 *offset;
3145
3146 PointInfo
3147 point;
3148
cristybb503372010-05-27 20:51:26 +00003149 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003150 i;
3151
cristybb503372010-05-27 20:51:26 +00003152 size_t
cristy3ed852e2009-09-05 21:47:34 +00003153 width;
3154
cristybb503372010-05-27 20:51:26 +00003155 ssize_t
3156 y;
3157
cristy3ed852e2009-09-05 21:47:34 +00003158 assert(image != (Image *) NULL);
3159 assert(image->signature == MagickSignature);
3160 if (image->debug != MagickFalse)
3161 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3162 assert(exception != (ExceptionInfo *) NULL);
3163 width=GetOptimalKernelWidth1D(radius,sigma);
3164 kernel=GetMotionBlurKernel(width,sigma);
3165 if (kernel == (double *) NULL)
3166 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3167 offset=(OffsetInfo *) AcquireQuantumMemory(width,sizeof(*offset));
3168 if (offset == (OffsetInfo *) NULL)
3169 {
3170 kernel=(double *) RelinquishMagickMemory(kernel);
3171 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3172 }
3173 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3174 if (blur_image == (Image *) NULL)
3175 {
3176 kernel=(double *) RelinquishMagickMemory(kernel);
3177 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
3178 return((Image *) NULL);
3179 }
3180 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3181 {
3182 kernel=(double *) RelinquishMagickMemory(kernel);
3183 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
3184 InheritException(exception,&blur_image->exception);
3185 blur_image=DestroyImage(blur_image);
3186 return((Image *) NULL);
3187 }
3188 point.x=(double) width*sin(DegreesToRadians(angle));
3189 point.y=(double) width*cos(DegreesToRadians(angle));
cristybb503372010-05-27 20:51:26 +00003190 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00003191 {
cristybb503372010-05-27 20:51:26 +00003192 offset[i].x=(ssize_t) ceil((double) (i*point.y)/hypot(point.x,point.y)-0.5);
3193 offset[i].y=(ssize_t) ceil((double) (i*point.x)/hypot(point.x,point.y)-0.5);
cristy3ed852e2009-09-05 21:47:34 +00003194 }
3195 /*
3196 Motion blur image.
3197 */
3198 status=MagickTrue;
3199 progress=0;
cristyddd82202009-11-03 20:14:50 +00003200 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003201 image_view=AcquireCacheView(image);
3202 blur_view=AcquireCacheView(blur_image);
cristy59e0d3b2010-06-07 13:12:38 +00003203#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy8a7ea362010-03-10 20:31:43 +00003204 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003205#endif
cristybb503372010-05-27 20:51:26 +00003206 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003207 {
3208 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003209 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00003210
cristybb503372010-05-27 20:51:26 +00003211 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003212 x;
3213
3214 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003215 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003216
3217 if (status == MagickFalse)
3218 continue;
3219 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3220 exception);
3221 if (q == (PixelPacket *) NULL)
3222 {
3223 status=MagickFalse;
3224 continue;
3225 }
3226 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
cristybb503372010-05-27 20:51:26 +00003227 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003228 {
3229 MagickPixelPacket
3230 qixel;
3231
3232 PixelPacket
3233 pixel;
3234
3235 register double
cristyc47d1f82009-11-26 01:44:43 +00003236 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00003237
cristybb503372010-05-27 20:51:26 +00003238 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003239 i;
3240
3241 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003242 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003243
3244 k=kernel;
cristyddd82202009-11-03 20:14:50 +00003245 qixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00003246 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
3247 {
cristybb503372010-05-27 20:51:26 +00003248 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00003249 {
3250 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
3251 offset[i].y,&pixel,exception);
3252 qixel.red+=(*k)*pixel.red;
3253 qixel.green+=(*k)*pixel.green;
3254 qixel.blue+=(*k)*pixel.blue;
3255 qixel.opacity+=(*k)*pixel.opacity;
3256 if (image->colorspace == CMYKColorspace)
3257 {
3258 indexes=GetCacheViewVirtualIndexQueue(image_view);
3259 qixel.index+=(*k)*(*indexes);
3260 }
3261 k++;
3262 }
3263 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003264 q->red=ClampToQuantum(qixel.red);
cristy3ed852e2009-09-05 21:47:34 +00003265 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003266 q->green=ClampToQuantum(qixel.green);
cristy3ed852e2009-09-05 21:47:34 +00003267 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003268 q->blue=ClampToQuantum(qixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00003269 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003270 q->opacity=ClampToQuantum(qixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00003271 if (((channel & IndexChannel) != 0) &&
3272 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00003273 blur_indexes[x]=(IndexPacket) ClampToQuantum(qixel.index);
cristy3ed852e2009-09-05 21:47:34 +00003274 }
3275 else
3276 {
3277 MagickRealType
3278 alpha,
3279 gamma;
3280
3281 alpha=0.0;
3282 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00003283 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00003284 {
3285 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
3286 offset[i].y,&pixel,exception);
cristy8a7ea362010-03-10 20:31:43 +00003287 alpha=(MagickRealType) (QuantumScale*
3288 GetAlphaPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00003289 qixel.red+=(*k)*alpha*pixel.red;
3290 qixel.green+=(*k)*alpha*pixel.green;
3291 qixel.blue+=(*k)*alpha*pixel.blue;
3292 qixel.opacity+=(*k)*pixel.opacity;
3293 if (image->colorspace == CMYKColorspace)
3294 {
3295 indexes=GetCacheViewVirtualIndexQueue(image_view);
3296 qixel.index+=(*k)*alpha*(*indexes);
3297 }
3298 gamma+=(*k)*alpha;
3299 k++;
3300 }
3301 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3302 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003303 q->red=ClampToQuantum(gamma*qixel.red);
cristy3ed852e2009-09-05 21:47:34 +00003304 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003305 q->green=ClampToQuantum(gamma*qixel.green);
cristy3ed852e2009-09-05 21:47:34 +00003306 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003307 q->blue=ClampToQuantum(gamma*qixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00003308 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003309 q->opacity=ClampToQuantum(qixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00003310 if (((channel & IndexChannel) != 0) &&
3311 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00003312 blur_indexes[x]=(IndexPacket) ClampToQuantum(gamma*qixel.index);
cristy3ed852e2009-09-05 21:47:34 +00003313 }
3314 q++;
3315 }
3316 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
3317 status=MagickFalse;
3318 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3319 {
3320 MagickBooleanType
3321 proceed;
3322
cristy59e0d3b2010-06-07 13:12:38 +00003323#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003324 #pragma omp critical (MagickCore_MotionBlurImageChannel)
3325#endif
3326 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
3327 if (proceed == MagickFalse)
3328 status=MagickFalse;
3329 }
3330 }
3331 blur_view=DestroyCacheView(blur_view);
3332 image_view=DestroyCacheView(image_view);
3333 kernel=(double *) RelinquishMagickMemory(kernel);
3334 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
3335 if (status == MagickFalse)
3336 blur_image=DestroyImage(blur_image);
3337 return(blur_image);
3338}
3339
3340/*
3341%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3342% %
3343% %
3344% %
3345% P r e v i e w I m a g e %
3346% %
3347% %
3348% %
3349%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3350%
3351% PreviewImage() tiles 9 thumbnails of the specified image with an image
3352% processing operation applied with varying parameters. This may be helpful
3353% pin-pointing an appropriate parameter for a particular image processing
3354% operation.
3355%
3356% The format of the PreviewImages method is:
3357%
3358% Image *PreviewImages(const Image *image,const PreviewType preview,
3359% ExceptionInfo *exception)
3360%
3361% A description of each parameter follows:
3362%
3363% o image: the image.
3364%
3365% o preview: the image processing operation.
3366%
3367% o exception: return any errors or warnings in this structure.
3368%
3369*/
3370MagickExport Image *PreviewImage(const Image *image,const PreviewType preview,
3371 ExceptionInfo *exception)
3372{
3373#define NumberTiles 9
3374#define PreviewImageTag "Preview/Image"
3375#define DefaultPreviewGeometry "204x204+10+10"
3376
3377 char
3378 factor[MaxTextExtent],
3379 label[MaxTextExtent];
3380
3381 double
3382 degrees,
3383 gamma,
3384 percentage,
3385 radius,
3386 sigma,
3387 threshold;
3388
3389 Image
3390 *images,
3391 *montage_image,
3392 *preview_image,
3393 *thumbnail;
3394
3395 ImageInfo
3396 *preview_info;
3397
cristybb503372010-05-27 20:51:26 +00003398 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003399 y;
3400
3401 MagickBooleanType
3402 proceed;
3403
3404 MontageInfo
3405 *montage_info;
3406
3407 QuantizeInfo
3408 quantize_info;
3409
3410 RectangleInfo
3411 geometry;
3412
cristybb503372010-05-27 20:51:26 +00003413 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003414 i,
3415 x;
3416
cristybb503372010-05-27 20:51:26 +00003417 size_t
cristy3ed852e2009-09-05 21:47:34 +00003418 colors;
3419
3420 /*
3421 Open output image file.
3422 */
3423 assert(image != (Image *) NULL);
3424 assert(image->signature == MagickSignature);
3425 if (image->debug != MagickFalse)
3426 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3427 colors=2;
3428 degrees=0.0;
3429 gamma=(-0.2f);
3430 preview_info=AcquireImageInfo();
3431 SetGeometry(image,&geometry);
3432 (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y,
3433 &geometry.width,&geometry.height);
3434 images=NewImageList();
3435 percentage=12.5;
3436 GetQuantizeInfo(&quantize_info);
3437 radius=0.0;
3438 sigma=1.0;
3439 threshold=0.0;
3440 x=0;
3441 y=0;
3442 for (i=0; i < NumberTiles; i++)
3443 {
3444 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
3445 if (thumbnail == (Image *) NULL)
3446 break;
3447 (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL,
3448 (void *) NULL);
3449 (void) SetImageProperty(thumbnail,"label",DefaultTileLabel);
3450 if (i == (NumberTiles/2))
3451 {
3452 (void) QueryColorDatabase("#dfdfdf",&thumbnail->matte_color,exception);
3453 AppendImageToList(&images,thumbnail);
3454 continue;
3455 }
3456 switch (preview)
3457 {
3458 case RotatePreview:
3459 {
3460 degrees+=45.0;
3461 preview_image=RotateImage(thumbnail,degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003462 (void) FormatMagickString(label,MaxTextExtent,"rotate %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003463 break;
3464 }
3465 case ShearPreview:
3466 {
3467 degrees+=5.0;
3468 preview_image=ShearImage(thumbnail,degrees,degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003469 (void) FormatMagickString(label,MaxTextExtent,"shear %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00003470 degrees,2.0*degrees);
3471 break;
3472 }
3473 case RollPreview:
3474 {
cristybb503372010-05-27 20:51:26 +00003475 x=(ssize_t) ((i+1)*thumbnail->columns)/NumberTiles;
3476 y=(ssize_t) ((i+1)*thumbnail->rows)/NumberTiles;
cristy3ed852e2009-09-05 21:47:34 +00003477 preview_image=RollImage(thumbnail,x,y,exception);
cristye8c25f92010-06-03 00:53:06 +00003478 (void) FormatMagickString(label,MaxTextExtent,"roll %+.20gx%+.20g",
3479 (double) x,(double) y);
cristy3ed852e2009-09-05 21:47:34 +00003480 break;
3481 }
3482 case HuePreview:
3483 {
3484 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3485 if (preview_image == (Image *) NULL)
3486 break;
cristye7f51092010-01-17 00:39:37 +00003487 (void) FormatMagickString(factor,MaxTextExtent,"100,100,%g",
cristy3ed852e2009-09-05 21:47:34 +00003488 2.0*percentage);
3489 (void) ModulateImage(preview_image,factor);
3490 (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor);
3491 break;
3492 }
3493 case SaturationPreview:
3494 {
3495 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3496 if (preview_image == (Image *) NULL)
3497 break;
cristye7f51092010-01-17 00:39:37 +00003498 (void) FormatMagickString(factor,MaxTextExtent,"100,%g",
cristy8cd5b312010-01-07 01:10:24 +00003499 2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00003500 (void) ModulateImage(preview_image,factor);
3501 (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor);
3502 break;
3503 }
3504 case BrightnessPreview:
3505 {
3506 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3507 if (preview_image == (Image *) NULL)
3508 break;
cristye7f51092010-01-17 00:39:37 +00003509 (void) FormatMagickString(factor,MaxTextExtent,"%g",2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00003510 (void) ModulateImage(preview_image,factor);
3511 (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor);
3512 break;
3513 }
3514 case GammaPreview:
3515 default:
3516 {
3517 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3518 if (preview_image == (Image *) NULL)
3519 break;
3520 gamma+=0.4f;
3521 (void) GammaImageChannel(preview_image,DefaultChannels,gamma);
cristye7f51092010-01-17 00:39:37 +00003522 (void) FormatMagickString(label,MaxTextExtent,"gamma %g",gamma);
cristy3ed852e2009-09-05 21:47:34 +00003523 break;
3524 }
3525 case SpiffPreview:
3526 {
3527 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3528 if (preview_image != (Image *) NULL)
3529 for (x=0; x < i; x++)
3530 (void) ContrastImage(preview_image,MagickTrue);
cristye8c25f92010-06-03 00:53:06 +00003531 (void) FormatMagickString(label,MaxTextExtent,"contrast (%.20g)",
3532 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00003533 break;
3534 }
3535 case DullPreview:
3536 {
3537 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3538 if (preview_image == (Image *) NULL)
3539 break;
3540 for (x=0; x < i; x++)
3541 (void) ContrastImage(preview_image,MagickFalse);
cristye8c25f92010-06-03 00:53:06 +00003542 (void) FormatMagickString(label,MaxTextExtent,"+contrast (%.20g)",
3543 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00003544 break;
3545 }
3546 case GrayscalePreview:
3547 {
3548 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3549 if (preview_image == (Image *) NULL)
3550 break;
3551 colors<<=1;
3552 quantize_info.number_colors=colors;
3553 quantize_info.colorspace=GRAYColorspace;
3554 (void) QuantizeImage(&quantize_info,preview_image);
3555 (void) FormatMagickString(label,MaxTextExtent,
cristye8c25f92010-06-03 00:53:06 +00003556 "-colorspace gray -colors %.20g",(double) colors);
cristy3ed852e2009-09-05 21:47:34 +00003557 break;
3558 }
3559 case QuantizePreview:
3560 {
3561 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3562 if (preview_image == (Image *) NULL)
3563 break;
3564 colors<<=1;
3565 quantize_info.number_colors=colors;
3566 (void) QuantizeImage(&quantize_info,preview_image);
cristye8c25f92010-06-03 00:53:06 +00003567 (void) FormatMagickString(label,MaxTextExtent,"colors %.20g",(double)
3568 colors);
cristy3ed852e2009-09-05 21:47:34 +00003569 break;
3570 }
3571 case DespecklePreview:
3572 {
3573 for (x=0; x < (i-1); x++)
3574 {
3575 preview_image=DespeckleImage(thumbnail,exception);
3576 if (preview_image == (Image *) NULL)
3577 break;
3578 thumbnail=DestroyImage(thumbnail);
3579 thumbnail=preview_image;
3580 }
3581 preview_image=DespeckleImage(thumbnail,exception);
3582 if (preview_image == (Image *) NULL)
3583 break;
cristye8c25f92010-06-03 00:53:06 +00003584 (void) FormatMagickString(label,MaxTextExtent,"despeckle (%.20g)",
3585 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00003586 break;
3587 }
3588 case ReduceNoisePreview:
3589 {
3590 preview_image=ReduceNoiseImage(thumbnail,radius,exception);
cristye7f51092010-01-17 00:39:37 +00003591 (void) FormatMagickString(label,MaxTextExtent,"noise %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003592 break;
3593 }
3594 case AddNoisePreview:
3595 {
3596 switch ((int) i)
3597 {
3598 case 0:
3599 {
3600 (void) CopyMagickString(factor,"uniform",MaxTextExtent);
3601 break;
3602 }
3603 case 1:
3604 {
3605 (void) CopyMagickString(factor,"gaussian",MaxTextExtent);
3606 break;
3607 }
3608 case 2:
3609 {
3610 (void) CopyMagickString(factor,"multiplicative",MaxTextExtent);
3611 break;
3612 }
3613 case 3:
3614 {
3615 (void) CopyMagickString(factor,"impulse",MaxTextExtent);
3616 break;
3617 }
3618 case 4:
3619 {
3620 (void) CopyMagickString(factor,"laplacian",MaxTextExtent);
3621 break;
3622 }
3623 case 5:
3624 {
3625 (void) CopyMagickString(factor,"Poisson",MaxTextExtent);
3626 break;
3627 }
3628 default:
3629 {
3630 (void) CopyMagickString(thumbnail->magick,"NULL",MaxTextExtent);
3631 break;
3632 }
3633 }
3634 preview_image=ReduceNoiseImage(thumbnail,(double) i,exception);
3635 (void) FormatMagickString(label,MaxTextExtent,"+noise %s",factor);
3636 break;
3637 }
3638 case SharpenPreview:
3639 {
3640 preview_image=SharpenImage(thumbnail,radius,sigma,exception);
cristye7f51092010-01-17 00:39:37 +00003641 (void) FormatMagickString(label,MaxTextExtent,"sharpen %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003642 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00003643 break;
3644 }
3645 case BlurPreview:
3646 {
3647 preview_image=BlurImage(thumbnail,radius,sigma,exception);
cristye7f51092010-01-17 00:39:37 +00003648 (void) FormatMagickString(label,MaxTextExtent,"blur %gx%g",radius,
cristy3ed852e2009-09-05 21:47:34 +00003649 sigma);
3650 break;
3651 }
3652 case ThresholdPreview:
3653 {
3654 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3655 if (preview_image == (Image *) NULL)
3656 break;
3657 (void) BilevelImage(thumbnail,
3658 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
cristye7f51092010-01-17 00:39:37 +00003659 (void) FormatMagickString(label,MaxTextExtent,"threshold %g",
cristy3ed852e2009-09-05 21:47:34 +00003660 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
3661 break;
3662 }
3663 case EdgeDetectPreview:
3664 {
3665 preview_image=EdgeImage(thumbnail,radius,exception);
cristye7f51092010-01-17 00:39:37 +00003666 (void) FormatMagickString(label,MaxTextExtent,"edge %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003667 break;
3668 }
3669 case SpreadPreview:
3670 {
3671 preview_image=SpreadImage(thumbnail,radius,exception);
cristye7f51092010-01-17 00:39:37 +00003672 (void) FormatMagickString(label,MaxTextExtent,"spread %g",
cristy8cd5b312010-01-07 01:10:24 +00003673 radius+0.5);
cristy3ed852e2009-09-05 21:47:34 +00003674 break;
3675 }
3676 case SolarizePreview:
3677 {
3678 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3679 if (preview_image == (Image *) NULL)
3680 break;
3681 (void) SolarizeImage(preview_image,(double) QuantumRange*
3682 percentage/100.0);
cristye7f51092010-01-17 00:39:37 +00003683 (void) FormatMagickString(label,MaxTextExtent,"solarize %g",
cristy3ed852e2009-09-05 21:47:34 +00003684 (QuantumRange*percentage)/100.0);
3685 break;
3686 }
3687 case ShadePreview:
3688 {
3689 degrees+=10.0;
3690 preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees,
3691 exception);
cristye7f51092010-01-17 00:39:37 +00003692 (void) FormatMagickString(label,MaxTextExtent,"shade %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003693 degrees,degrees);
cristy3ed852e2009-09-05 21:47:34 +00003694 break;
3695 }
3696 case RaisePreview:
3697 {
3698 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3699 if (preview_image == (Image *) NULL)
3700 break;
cristybb503372010-05-27 20:51:26 +00003701 geometry.width=(size_t) (2*i+2);
3702 geometry.height=(size_t) (2*i+2);
cristy3ed852e2009-09-05 21:47:34 +00003703 geometry.x=i/2;
3704 geometry.y=i/2;
3705 (void) RaiseImage(preview_image,&geometry,MagickTrue);
cristye8c25f92010-06-03 00:53:06 +00003706 (void) FormatMagickString(label,MaxTextExtent,
cristy6d8abba2010-06-03 01:10:47 +00003707 "raise %.20gx%.20g%+.20g%+.20g",(double) geometry.width,(double)
cristye8c25f92010-06-03 00:53:06 +00003708 geometry.height,(double) geometry.x,(double) geometry.y);
cristy3ed852e2009-09-05 21:47:34 +00003709 break;
3710 }
3711 case SegmentPreview:
3712 {
3713 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3714 if (preview_image == (Image *) NULL)
3715 break;
3716 threshold+=0.4f;
3717 (void) SegmentImage(preview_image,RGBColorspace,MagickFalse,threshold,
3718 threshold);
cristye7f51092010-01-17 00:39:37 +00003719 (void) FormatMagickString(label,MaxTextExtent,"segment %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00003720 threshold,threshold);
3721 break;
3722 }
3723 case SwirlPreview:
3724 {
3725 preview_image=SwirlImage(thumbnail,degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003726 (void) FormatMagickString(label,MaxTextExtent,"swirl %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003727 degrees+=45.0;
3728 break;
3729 }
3730 case ImplodePreview:
3731 {
3732 degrees+=0.1f;
3733 preview_image=ImplodeImage(thumbnail,degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003734 (void) FormatMagickString(label,MaxTextExtent,"implode %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003735 break;
3736 }
3737 case WavePreview:
3738 {
3739 degrees+=5.0f;
3740 preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003741 (void) FormatMagickString(label,MaxTextExtent,"wave %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003742 0.5*degrees,2.0*degrees);
cristy3ed852e2009-09-05 21:47:34 +00003743 break;
3744 }
3745 case OilPaintPreview:
3746 {
3747 preview_image=OilPaintImage(thumbnail,(double) radius,exception);
cristye7f51092010-01-17 00:39:37 +00003748 (void) FormatMagickString(label,MaxTextExtent,"paint %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003749 break;
3750 }
3751 case CharcoalDrawingPreview:
3752 {
3753 preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma,
3754 exception);
cristye7f51092010-01-17 00:39:37 +00003755 (void) FormatMagickString(label,MaxTextExtent,"charcoal %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003756 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00003757 break;
3758 }
3759 case JPEGPreview:
3760 {
3761 char
3762 filename[MaxTextExtent];
3763
3764 int
3765 file;
3766
3767 MagickBooleanType
3768 status;
3769
3770 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3771 if (preview_image == (Image *) NULL)
3772 break;
cristybb503372010-05-27 20:51:26 +00003773 preview_info->quality=(size_t) percentage;
cristye8c25f92010-06-03 00:53:06 +00003774 (void) FormatMagickString(factor,MaxTextExtent,"%.20g",(double)
3775 preview_info->quality);
cristy3ed852e2009-09-05 21:47:34 +00003776 file=AcquireUniqueFileResource(filename);
3777 if (file != -1)
3778 file=close(file)-1;
3779 (void) FormatMagickString(preview_image->filename,MaxTextExtent,
3780 "jpeg:%s",filename);
3781 status=WriteImage(preview_info,preview_image);
3782 if (status != MagickFalse)
3783 {
3784 Image
3785 *quality_image;
3786
3787 (void) CopyMagickString(preview_info->filename,
3788 preview_image->filename,MaxTextExtent);
3789 quality_image=ReadImage(preview_info,exception);
3790 if (quality_image != (Image *) NULL)
3791 {
3792 preview_image=DestroyImage(preview_image);
3793 preview_image=quality_image;
3794 }
3795 }
3796 (void) RelinquishUniqueFileResource(preview_image->filename);
3797 if ((GetBlobSize(preview_image)/1024) >= 1024)
cristye7f51092010-01-17 00:39:37 +00003798 (void) FormatMagickString(label,MaxTextExtent,"quality %s\n%gmb ",
cristy3ed852e2009-09-05 21:47:34 +00003799 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
3800 1024.0/1024.0);
3801 else
3802 if (GetBlobSize(preview_image) >= 1024)
cristy8cd5b312010-01-07 01:10:24 +00003803 (void) FormatMagickString(label,MaxTextExtent,
cristye7f51092010-01-17 00:39:37 +00003804 "quality %s\n%gkb ",factor,(double) ((MagickOffsetType)
cristy8cd5b312010-01-07 01:10:24 +00003805 GetBlobSize(preview_image))/1024.0);
cristy3ed852e2009-09-05 21:47:34 +00003806 else
cristye8c25f92010-06-03 00:53:06 +00003807 (void) FormatMagickString(label,MaxTextExtent,"quality %s\n%.20gb ",
3808 factor,(double) GetBlobSize(thumbnail));
cristy3ed852e2009-09-05 21:47:34 +00003809 break;
3810 }
3811 }
3812 thumbnail=DestroyImage(thumbnail);
3813 percentage+=12.5;
3814 radius+=0.5;
3815 sigma+=0.25;
3816 if (preview_image == (Image *) NULL)
3817 break;
3818 (void) DeleteImageProperty(preview_image,"label");
3819 (void) SetImageProperty(preview_image,"label",label);
3820 AppendImageToList(&images,preview_image);
cristybb503372010-05-27 20:51:26 +00003821 proceed=SetImageProgress(image,PreviewImageTag,(MagickOffsetType) i,
3822 NumberTiles);
cristy3ed852e2009-09-05 21:47:34 +00003823 if (proceed == MagickFalse)
3824 break;
3825 }
3826 if (images == (Image *) NULL)
3827 {
3828 preview_info=DestroyImageInfo(preview_info);
3829 return((Image *) NULL);
3830 }
3831 /*
3832 Create the montage.
3833 */
3834 montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL);
3835 (void) CopyMagickString(montage_info->filename,image->filename,MaxTextExtent);
3836 montage_info->shadow=MagickTrue;
3837 (void) CloneString(&montage_info->tile,"3x3");
3838 (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry);
3839 (void) CloneString(&montage_info->frame,DefaultTileFrame);
3840 montage_image=MontageImages(images,montage_info,exception);
3841 montage_info=DestroyMontageInfo(montage_info);
3842 images=DestroyImageList(images);
3843 if (montage_image == (Image *) NULL)
3844 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3845 if (montage_image->montage != (char *) NULL)
3846 {
3847 /*
3848 Free image directory.
3849 */
3850 montage_image->montage=(char *) RelinquishMagickMemory(
3851 montage_image->montage);
3852 if (image->directory != (char *) NULL)
3853 montage_image->directory=(char *) RelinquishMagickMemory(
3854 montage_image->directory);
3855 }
3856 preview_info=DestroyImageInfo(preview_info);
3857 return(montage_image);
3858}
3859
3860/*
3861%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3862% %
3863% %
3864% %
3865% R a d i a l B l u r I m a g e %
3866% %
3867% %
3868% %
3869%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3870%
3871% RadialBlurImage() applies a radial blur to the image.
3872%
3873% Andrew Protano contributed this effect.
3874%
3875% The format of the RadialBlurImage method is:
3876%
3877% Image *RadialBlurImage(const Image *image,const double angle,
3878% ExceptionInfo *exception)
3879% Image *RadialBlurImageChannel(const Image *image,const ChannelType channel,
3880% const double angle,ExceptionInfo *exception)
3881%
3882% A description of each parameter follows:
3883%
3884% o image: the image.
3885%
3886% o channel: the channel type.
3887%
3888% o angle: the angle of the radial blur.
3889%
3890% o exception: return any errors or warnings in this structure.
3891%
3892*/
3893
3894MagickExport Image *RadialBlurImage(const Image *image,const double angle,
3895 ExceptionInfo *exception)
3896{
3897 Image
3898 *blur_image;
3899
3900 blur_image=RadialBlurImageChannel(image,DefaultChannels,angle,exception);
3901 return(blur_image);
3902}
3903
3904MagickExport Image *RadialBlurImageChannel(const Image *image,
3905 const ChannelType channel,const double angle,ExceptionInfo *exception)
3906{
cristyc4c8d132010-01-07 01:58:38 +00003907 CacheView
3908 *blur_view,
3909 *image_view;
3910
cristy3ed852e2009-09-05 21:47:34 +00003911 Image
3912 *blur_image;
3913
cristy3ed852e2009-09-05 21:47:34 +00003914 MagickBooleanType
3915 status;
3916
cristybb503372010-05-27 20:51:26 +00003917 MagickOffsetType
3918 progress;
3919
cristy3ed852e2009-09-05 21:47:34 +00003920 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00003921 bias;
cristy3ed852e2009-09-05 21:47:34 +00003922
3923 MagickRealType
3924 blur_radius,
3925 *cos_theta,
3926 offset,
3927 *sin_theta,
3928 theta;
3929
3930 PointInfo
3931 blur_center;
3932
cristybb503372010-05-27 20:51:26 +00003933 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003934 i;
3935
cristybb503372010-05-27 20:51:26 +00003936 size_t
cristy3ed852e2009-09-05 21:47:34 +00003937 n;
3938
cristybb503372010-05-27 20:51:26 +00003939 ssize_t
3940 y;
3941
cristy3ed852e2009-09-05 21:47:34 +00003942 /*
3943 Allocate blur image.
3944 */
3945 assert(image != (Image *) NULL);
3946 assert(image->signature == MagickSignature);
3947 if (image->debug != MagickFalse)
3948 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3949 assert(exception != (ExceptionInfo *) NULL);
3950 assert(exception->signature == MagickSignature);
3951 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3952 if (blur_image == (Image *) NULL)
3953 return((Image *) NULL);
3954 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3955 {
3956 InheritException(exception,&blur_image->exception);
3957 blur_image=DestroyImage(blur_image);
3958 return((Image *) NULL);
3959 }
3960 blur_center.x=(double) image->columns/2.0;
3961 blur_center.y=(double) image->rows/2.0;
3962 blur_radius=hypot(blur_center.x,blur_center.y);
cristybb503372010-05-27 20:51:26 +00003963 n=(size_t) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+
cristy3ed852e2009-09-05 21:47:34 +00003964 2UL);
3965 theta=DegreesToRadians(angle)/(MagickRealType) (n-1);
3966 cos_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3967 sizeof(*cos_theta));
3968 sin_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3969 sizeof(*sin_theta));
3970 if ((cos_theta == (MagickRealType *) NULL) ||
3971 (sin_theta == (MagickRealType *) NULL))
3972 {
3973 blur_image=DestroyImage(blur_image);
3974 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3975 }
3976 offset=theta*(MagickRealType) (n-1)/2.0;
cristybb503372010-05-27 20:51:26 +00003977 for (i=0; i < (ssize_t) n; i++)
cristy3ed852e2009-09-05 21:47:34 +00003978 {
3979 cos_theta[i]=cos((double) (theta*i-offset));
3980 sin_theta[i]=sin((double) (theta*i-offset));
3981 }
3982 /*
3983 Radial blur image.
3984 */
3985 status=MagickTrue;
3986 progress=0;
cristyddd82202009-11-03 20:14:50 +00003987 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003988 image_view=AcquireCacheView(image);
3989 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003990#if defined(MAGICKCORE_OPENMP_SUPPORT)
3991 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003992#endif
cristybb503372010-05-27 20:51:26 +00003993 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003994 {
3995 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003996 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003997
3998 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003999 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00004000
cristybb503372010-05-27 20:51:26 +00004001 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00004002 x;
4003
4004 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004005 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004006
4007 if (status == MagickFalse)
4008 continue;
4009 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
4010 exception);
4011 if (q == (PixelPacket *) NULL)
4012 {
4013 status=MagickFalse;
4014 continue;
4015 }
4016 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
cristybb503372010-05-27 20:51:26 +00004017 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004018 {
4019 MagickPixelPacket
4020 qixel;
4021
4022 MagickRealType
4023 normalize,
4024 radius;
4025
4026 PixelPacket
4027 pixel;
4028
4029 PointInfo
4030 center;
4031
cristybb503372010-05-27 20:51:26 +00004032 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00004033 i;
4034
cristybb503372010-05-27 20:51:26 +00004035 size_t
cristy3ed852e2009-09-05 21:47:34 +00004036 step;
4037
4038 center.x=(double) x-blur_center.x;
4039 center.y=(double) y-blur_center.y;
4040 radius=hypot((double) center.x,center.y);
4041 if (radius == 0)
4042 step=1;
4043 else
4044 {
cristybb503372010-05-27 20:51:26 +00004045 step=(size_t) (blur_radius/radius);
cristy3ed852e2009-09-05 21:47:34 +00004046 if (step == 0)
4047 step=1;
4048 else
4049 if (step >= n)
4050 step=n-1;
4051 }
4052 normalize=0.0;
cristyddd82202009-11-03 20:14:50 +00004053 qixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00004054 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
4055 {
cristyeaedf062010-05-29 22:36:02 +00004056 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00004057 {
cristyeaedf062010-05-29 22:36:02 +00004058 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
4059 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
4060 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
4061 cos_theta[i]+0.5),&pixel,exception);
cristy3ed852e2009-09-05 21:47:34 +00004062 qixel.red+=pixel.red;
4063 qixel.green+=pixel.green;
4064 qixel.blue+=pixel.blue;
4065 qixel.opacity+=pixel.opacity;
4066 if (image->colorspace == CMYKColorspace)
4067 {
4068 indexes=GetCacheViewVirtualIndexQueue(image_view);
4069 qixel.index+=(*indexes);
4070 }
4071 normalize+=1.0;
4072 }
4073 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
4074 normalize);
4075 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004076 q->red=ClampToQuantum(normalize*qixel.red);
cristy3ed852e2009-09-05 21:47:34 +00004077 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004078 q->green=ClampToQuantum(normalize*qixel.green);
cristy3ed852e2009-09-05 21:47:34 +00004079 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004080 q->blue=ClampToQuantum(normalize*qixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00004081 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004082 q->opacity=ClampToQuantum(normalize*qixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00004083 if (((channel & IndexChannel) != 0) &&
4084 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00004085 blur_indexes[x]=(IndexPacket) ClampToQuantum(normalize*qixel.index);
cristy3ed852e2009-09-05 21:47:34 +00004086 }
4087 else
4088 {
4089 MagickRealType
4090 alpha,
4091 gamma;
4092
4093 alpha=1.0;
4094 gamma=0.0;
cristyeaedf062010-05-29 22:36:02 +00004095 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00004096 {
cristyeaedf062010-05-29 22:36:02 +00004097 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
4098 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
4099 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
4100 cos_theta[i]+0.5),&pixel,exception);
cristy46f08202010-01-10 04:04:21 +00004101 alpha=(MagickRealType) (QuantumScale*
4102 GetAlphaPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004103 qixel.red+=alpha*pixel.red;
4104 qixel.green+=alpha*pixel.green;
4105 qixel.blue+=alpha*pixel.blue;
4106 qixel.opacity+=pixel.opacity;
4107 if (image->colorspace == CMYKColorspace)
4108 {
4109 indexes=GetCacheViewVirtualIndexQueue(image_view);
4110 qixel.index+=alpha*(*indexes);
4111 }
4112 gamma+=alpha;
4113 normalize+=1.0;
4114 }
4115 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
4116 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
4117 normalize);
4118 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004119 q->red=ClampToQuantum(gamma*qixel.red);
cristy3ed852e2009-09-05 21:47:34 +00004120 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004121 q->green=ClampToQuantum(gamma*qixel.green);
cristy3ed852e2009-09-05 21:47:34 +00004122 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004123 q->blue=ClampToQuantum(gamma*qixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00004124 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004125 q->opacity=ClampToQuantum(normalize*qixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00004126 if (((channel & IndexChannel) != 0) &&
4127 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00004128 blur_indexes[x]=(IndexPacket) ClampToQuantum(gamma*qixel.index);
cristy3ed852e2009-09-05 21:47:34 +00004129 }
4130 q++;
4131 }
4132 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
4133 status=MagickFalse;
4134 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4135 {
4136 MagickBooleanType
4137 proceed;
4138
cristyb5d5f722009-11-04 03:03:49 +00004139#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004140 #pragma omp critical (MagickCore_RadialBlurImageChannel)
4141#endif
4142 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
4143 if (proceed == MagickFalse)
4144 status=MagickFalse;
4145 }
4146 }
4147 blur_view=DestroyCacheView(blur_view);
4148 image_view=DestroyCacheView(image_view);
4149 cos_theta=(MagickRealType *) RelinquishMagickMemory(cos_theta);
4150 sin_theta=(MagickRealType *) RelinquishMagickMemory(sin_theta);
4151 if (status == MagickFalse)
4152 blur_image=DestroyImage(blur_image);
4153 return(blur_image);
4154}
4155
4156/*
4157%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4158% %
4159% %
4160% %
4161% R e d u c e N o i s e I m a g e %
4162% %
4163% %
4164% %
4165%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4166%
4167% ReduceNoiseImage() smooths the contours of an image while still preserving
4168% edge information. The algorithm works by replacing each pixel with its
4169% neighbor closest in value. A neighbor is defined by radius. Use a radius
4170% of 0 and ReduceNoise() selects a suitable radius for you.
4171%
4172% The format of the ReduceNoiseImage method is:
4173%
4174% Image *ReduceNoiseImage(const Image *image,const double radius,
4175% ExceptionInfo *exception)
4176%
4177% A description of each parameter follows:
4178%
4179% o image: the image.
4180%
4181% o radius: the radius of the pixel neighborhood.
4182%
4183% o exception: return any errors or warnings in this structure.
4184%
4185*/
4186
4187static MagickPixelPacket GetNonpeakMedianPixelList(MedianPixelList *pixel_list)
4188{
4189 MagickPixelPacket
4190 pixel;
4191
cristybb503372010-05-27 20:51:26 +00004192 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00004193 channel;
4194
4195 register MedianSkipList
4196 *list;
4197
cristybb503372010-05-27 20:51:26 +00004198 size_t
cristy3ed852e2009-09-05 21:47:34 +00004199 center,
4200 color,
4201 count,
4202 previous,
4203 next;
4204
4205 unsigned short
4206 channels[5];
4207
4208 /*
4209 Finds the median value for each of the color.
4210 */
4211 center=pixel_list->center;
4212 for (channel=0; channel < 5; channel++)
4213 {
4214 list=pixel_list->lists+channel;
4215 color=65536UL;
4216 next=list->nodes[color].next[0];
4217 count=0;
4218 do
4219 {
4220 previous=color;
4221 color=next;
4222 next=list->nodes[color].next[0];
4223 count+=list->nodes[color].count;
4224 }
4225 while (count <= center);
4226 if ((previous == 65536UL) && (next != 65536UL))
4227 color=next;
4228 else
4229 if ((previous != 65536UL) && (next == 65536UL))
4230 color=previous;
4231 channels[channel]=(unsigned short) color;
4232 }
4233 GetMagickPixelPacket((const Image *) NULL,&pixel);
4234 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4235 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4236 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
4237 pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
4238 pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]);
4239 return(pixel);
4240}
4241
4242MagickExport Image *ReduceNoiseImage(const Image *image,const double radius,
4243 ExceptionInfo *exception)
4244{
4245#define ReduceNoiseImageTag "ReduceNoise/Image"
4246
cristyfa112112010-01-04 17:48:07 +00004247 CacheView
4248 *image_view,
4249 *noise_view;
4250
cristy3ed852e2009-09-05 21:47:34 +00004251 Image
4252 *noise_image;
4253
cristy3ed852e2009-09-05 21:47:34 +00004254 MagickBooleanType
4255 status;
4256
cristybb503372010-05-27 20:51:26 +00004257 MagickOffsetType
4258 progress;
4259
cristy3ed852e2009-09-05 21:47:34 +00004260 MedianPixelList
cristyfa112112010-01-04 17:48:07 +00004261 **restrict pixel_list;
cristy3ed852e2009-09-05 21:47:34 +00004262
cristybb503372010-05-27 20:51:26 +00004263 size_t
cristy3ed852e2009-09-05 21:47:34 +00004264 width;
4265
cristybb503372010-05-27 20:51:26 +00004266 ssize_t
4267 y;
4268
cristy3ed852e2009-09-05 21:47:34 +00004269 /*
4270 Initialize noise image attributes.
4271 */
4272 assert(image != (Image *) NULL);
4273 assert(image->signature == MagickSignature);
4274 if (image->debug != MagickFalse)
4275 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4276 assert(exception != (ExceptionInfo *) NULL);
4277 assert(exception->signature == MagickSignature);
4278 width=GetOptimalKernelWidth2D(radius,0.5);
4279 if ((image->columns < width) || (image->rows < width))
4280 ThrowImageException(OptionError,"ImageSmallerThanKernelRadius");
4281 noise_image=CloneImage(image,image->columns,image->rows,MagickTrue,
4282 exception);
4283 if (noise_image == (Image *) NULL)
4284 return((Image *) NULL);
4285 if (SetImageStorageClass(noise_image,DirectClass) == MagickFalse)
4286 {
4287 InheritException(exception,&noise_image->exception);
4288 noise_image=DestroyImage(noise_image);
4289 return((Image *) NULL);
4290 }
4291 pixel_list=AcquireMedianPixelListThreadSet(width);
4292 if (pixel_list == (MedianPixelList **) NULL)
4293 {
4294 noise_image=DestroyImage(noise_image);
4295 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
4296 }
4297 /*
4298 Reduce noise image.
4299 */
4300 status=MagickTrue;
4301 progress=0;
4302 image_view=AcquireCacheView(image);
4303 noise_view=AcquireCacheView(noise_image);
cristyb5d5f722009-11-04 03:03:49 +00004304#if defined(MAGICKCORE_OPENMP_SUPPORT)
4305 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004306#endif
cristybb503372010-05-27 20:51:26 +00004307 for (y=0; y < (ssize_t) noise_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00004308 {
cristy6ebe97c2010-07-03 01:17:28 +00004309 int
4310 id;
4311
cristy3ed852e2009-09-05 21:47:34 +00004312 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004313 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00004314
4315 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004316 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00004317
4318 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004319 *restrict noise_indexes;
cristy3ed852e2009-09-05 21:47:34 +00004320
cristybb503372010-05-27 20:51:26 +00004321 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00004322 x;
4323
4324 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004325 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004326
4327 if (status == MagickFalse)
4328 continue;
cristy6ebe97c2010-07-03 01:17:28 +00004329 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
4330 (width/2L),image->columns+width,width,exception);
cristy3ed852e2009-09-05 21:47:34 +00004331 q=QueueCacheViewAuthenticPixels(noise_view,0,y,noise_image->columns,1,
4332 exception);
4333 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
4334 {
4335 status=MagickFalse;
4336 continue;
4337 }
4338 indexes=GetCacheViewVirtualIndexQueue(image_view);
4339 noise_indexes=GetCacheViewAuthenticIndexQueue(noise_view);
4340 id=GetOpenMPThreadId();
cristybb503372010-05-27 20:51:26 +00004341 for (x=0; x < (ssize_t) noise_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004342 {
4343 MagickPixelPacket
4344 pixel;
4345
4346 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004347 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +00004348
4349 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004350 *restrict s;
cristy3ed852e2009-09-05 21:47:34 +00004351
cristybb503372010-05-27 20:51:26 +00004352 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00004353 u,
4354 v;
4355
4356 r=p;
4357 s=indexes+x;
4358 ResetMedianPixelList(pixel_list[id]);
cristybb503372010-05-27 20:51:26 +00004359 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00004360 {
cristybb503372010-05-27 20:51:26 +00004361 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00004362 InsertMedianPixelList(image,r+u,s+u,pixel_list[id]);
4363 r+=image->columns+width;
4364 s+=image->columns+width;
4365 }
4366 pixel=GetNonpeakMedianPixelList(pixel_list[id]);
4367 SetPixelPacket(noise_image,&pixel,q,noise_indexes+x);
4368 p++;
4369 q++;
4370 }
4371 if (SyncCacheViewAuthenticPixels(noise_view,exception) == MagickFalse)
4372 status=MagickFalse;
4373 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4374 {
4375 MagickBooleanType
4376 proceed;
4377
cristyb5d5f722009-11-04 03:03:49 +00004378#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004379 #pragma omp critical (MagickCore_ReduceNoiseImage)
4380#endif
4381 proceed=SetImageProgress(image,ReduceNoiseImageTag,progress++,
4382 image->rows);
4383 if (proceed == MagickFalse)
4384 status=MagickFalse;
4385 }
4386 }
4387 noise_view=DestroyCacheView(noise_view);
4388 image_view=DestroyCacheView(image_view);
4389 pixel_list=DestroyMedianPixelListThreadSet(pixel_list);
4390 return(noise_image);
4391}
4392
4393/*
4394%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4395% %
4396% %
4397% %
4398% S e l e c t i v e B l u r I m a g e %
4399% %
4400% %
4401% %
4402%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4403%
4404% SelectiveBlurImage() selectively blur pixels within a contrast threshold.
4405% It is similar to the unsharpen mask that sharpens everything with contrast
4406% above a certain threshold.
4407%
4408% The format of the SelectiveBlurImage method is:
4409%
4410% Image *SelectiveBlurImage(const Image *image,const double radius,
4411% const double sigma,const double threshold,ExceptionInfo *exception)
4412% Image *SelectiveBlurImageChannel(const Image *image,
4413% const ChannelType channel,const double radius,const double sigma,
4414% const double threshold,ExceptionInfo *exception)
4415%
4416% A description of each parameter follows:
4417%
4418% o image: the image.
4419%
4420% o channel: the channel type.
4421%
4422% o radius: the radius of the Gaussian, in pixels, not counting the center
4423% pixel.
4424%
4425% o sigma: the standard deviation of the Gaussian, in pixels.
4426%
4427% o threshold: only pixels within this contrast threshold are included
4428% in the blur operation.
4429%
4430% o exception: return any errors or warnings in this structure.
4431%
4432*/
4433
4434static inline MagickBooleanType SelectiveContrast(const PixelPacket *p,
4435 const PixelPacket *q,const double threshold)
4436{
4437 if (fabs(PixelIntensity(p)-PixelIntensity(q)) < threshold)
4438 return(MagickTrue);
4439 return(MagickFalse);
4440}
4441
4442MagickExport Image *SelectiveBlurImage(const Image *image,const double radius,
4443 const double sigma,const double threshold,ExceptionInfo *exception)
4444{
4445 Image
4446 *blur_image;
4447
4448 blur_image=SelectiveBlurImageChannel(image,DefaultChannels,radius,sigma,
4449 threshold,exception);
4450 return(blur_image);
4451}
4452
4453MagickExport Image *SelectiveBlurImageChannel(const Image *image,
4454 const ChannelType channel,const double radius,const double sigma,
4455 const double threshold,ExceptionInfo *exception)
4456{
4457#define SelectiveBlurImageTag "SelectiveBlur/Image"
4458
cristy47e00502009-12-17 19:19:57 +00004459 CacheView
4460 *blur_view,
4461 *image_view;
4462
cristy3ed852e2009-09-05 21:47:34 +00004463 double
cristy3ed852e2009-09-05 21:47:34 +00004464 *kernel;
4465
4466 Image
4467 *blur_image;
4468
cristy3ed852e2009-09-05 21:47:34 +00004469 MagickBooleanType
4470 status;
4471
cristybb503372010-05-27 20:51:26 +00004472 MagickOffsetType
4473 progress;
4474
cristy3ed852e2009-09-05 21:47:34 +00004475 MagickPixelPacket
cristy3ed852e2009-09-05 21:47:34 +00004476 bias;
4477
cristybb503372010-05-27 20:51:26 +00004478 register ssize_t
cristy47e00502009-12-17 19:19:57 +00004479 i;
cristy3ed852e2009-09-05 21:47:34 +00004480
cristybb503372010-05-27 20:51:26 +00004481 size_t
cristy3ed852e2009-09-05 21:47:34 +00004482 width;
4483
cristybb503372010-05-27 20:51:26 +00004484 ssize_t
4485 j,
4486 u,
4487 v,
4488 y;
4489
cristy3ed852e2009-09-05 21:47:34 +00004490 /*
4491 Initialize blur image attributes.
4492 */
4493 assert(image != (Image *) NULL);
4494 assert(image->signature == MagickSignature);
4495 if (image->debug != MagickFalse)
4496 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4497 assert(exception != (ExceptionInfo *) NULL);
4498 assert(exception->signature == MagickSignature);
4499 width=GetOptimalKernelWidth1D(radius,sigma);
4500 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
4501 if (kernel == (double *) NULL)
4502 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00004503 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00004504 i=0;
cristy47e00502009-12-17 19:19:57 +00004505 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00004506 {
cristy47e00502009-12-17 19:19:57 +00004507 for (u=(-j); u <= j; u++)
4508 kernel[i++]=exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
4509 (2.0*MagickPI*MagickSigma*MagickSigma);
cristy3ed852e2009-09-05 21:47:34 +00004510 }
4511 if (image->debug != MagickFalse)
4512 {
4513 char
4514 format[MaxTextExtent],
4515 *message;
4516
cristybb503372010-05-27 20:51:26 +00004517 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00004518 u,
4519 v;
4520
4521 register const double
4522 *k;
4523
4524 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00004525 " SelectiveBlurImage with %.20gx%.20g kernel:",(double) width,(double)
4526 width);
cristy3ed852e2009-09-05 21:47:34 +00004527 message=AcquireString("");
4528 k=kernel;
cristybb503372010-05-27 20:51:26 +00004529 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00004530 {
4531 *message='\0';
cristye8c25f92010-06-03 00:53:06 +00004532 (void) FormatMagickString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy3ed852e2009-09-05 21:47:34 +00004533 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00004534 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00004535 {
4536 (void) FormatMagickString(format,MaxTextExtent,"%+f ",*k++);
4537 (void) ConcatenateString(&message,format);
4538 }
4539 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
4540 }
4541 message=DestroyString(message);
4542 }
4543 blur_image=CloneImage(image,0,0,MagickTrue,exception);
4544 if (blur_image == (Image *) NULL)
4545 return((Image *) NULL);
4546 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
4547 {
4548 InheritException(exception,&blur_image->exception);
4549 blur_image=DestroyImage(blur_image);
4550 return((Image *) NULL);
4551 }
4552 /*
4553 Threshold blur image.
4554 */
4555 status=MagickTrue;
4556 progress=0;
cristyddd82202009-11-03 20:14:50 +00004557 GetMagickPixelPacket(image,&bias);
4558 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00004559 image_view=AcquireCacheView(image);
4560 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00004561#if defined(MAGICKCORE_OPENMP_SUPPORT)
4562 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004563#endif
cristybb503372010-05-27 20:51:26 +00004564 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00004565 {
4566 MagickBooleanType
4567 sync;
4568
4569 MagickRealType
4570 gamma;
4571
4572 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004573 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00004574
4575 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004576 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00004577
4578 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004579 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00004580
cristybb503372010-05-27 20:51:26 +00004581 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00004582 x;
4583
4584 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004585 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004586
4587 if (status == MagickFalse)
4588 continue;
cristybb503372010-05-27 20:51:26 +00004589 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t) (width/
cristy3ed852e2009-09-05 21:47:34 +00004590 2L),image->columns+width,width,exception);
4591 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
4592 exception);
4593 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
4594 {
4595 status=MagickFalse;
4596 continue;
4597 }
4598 indexes=GetCacheViewVirtualIndexQueue(image_view);
4599 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
cristybb503372010-05-27 20:51:26 +00004600 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004601 {
cristybb503372010-05-27 20:51:26 +00004602 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00004603 j,
4604 v;
4605
4606 MagickPixelPacket
4607 pixel;
4608
4609 register const double
cristyc47d1f82009-11-26 01:44:43 +00004610 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00004611
cristybb503372010-05-27 20:51:26 +00004612 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00004613 u;
4614
cristyddd82202009-11-03 20:14:50 +00004615 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00004616 k=kernel;
4617 gamma=0.0;
4618 j=0;
4619 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
4620 {
cristybb503372010-05-27 20:51:26 +00004621 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00004622 {
cristybb503372010-05-27 20:51:26 +00004623 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00004624 {
4625 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4626 {
4627 pixel.red+=(*k)*(p+u+j)->red;
4628 pixel.green+=(*k)*(p+u+j)->green;
4629 pixel.blue+=(*k)*(p+u+j)->blue;
4630 gamma+=(*k);
4631 k++;
4632 }
4633 }
cristyd99b0962010-05-29 23:14:26 +00004634 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00004635 }
4636 if (gamma != 0.0)
4637 {
4638 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
4639 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004640 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004641 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004642 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004643 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004644 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004645 }
4646 if ((channel & OpacityChannel) != 0)
4647 {
4648 gamma=0.0;
4649 j=0;
cristybb503372010-05-27 20:51:26 +00004650 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00004651 {
cristybb503372010-05-27 20:51:26 +00004652 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00004653 {
4654 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4655 {
4656 pixel.opacity+=(*k)*(p+u+j)->opacity;
4657 gamma+=(*k);
4658 k++;
4659 }
4660 }
cristyeaedf062010-05-29 22:36:02 +00004661 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00004662 }
4663 if (gamma != 0.0)
4664 {
4665 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4666 gamma);
cristyce70c172010-01-07 17:15:30 +00004667 SetOpacityPixelComponent(q,ClampToQuantum(gamma*
4668 GetOpacityPixelComponent(&pixel)));
cristy3ed852e2009-09-05 21:47:34 +00004669 }
4670 }
4671 if (((channel & IndexChannel) != 0) &&
4672 (image->colorspace == CMYKColorspace))
4673 {
4674 gamma=0.0;
4675 j=0;
cristybb503372010-05-27 20:51:26 +00004676 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00004677 {
cristybb503372010-05-27 20:51:26 +00004678 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00004679 {
4680 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4681 {
4682 pixel.index+=(*k)*indexes[x+u+j];
4683 gamma+=(*k);
4684 k++;
4685 }
4686 }
cristyeaedf062010-05-29 22:36:02 +00004687 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00004688 }
4689 if (gamma != 0.0)
4690 {
4691 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4692 gamma);
cristy6db48122010-01-11 00:18:07 +00004693 blur_indexes[x]=ClampToQuantum(gamma*
4694 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004695 }
4696 }
4697 }
4698 else
4699 {
4700 MagickRealType
4701 alpha;
4702
cristybb503372010-05-27 20:51:26 +00004703 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00004704 {
cristybb503372010-05-27 20:51:26 +00004705 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00004706 {
4707 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4708 {
cristy46f08202010-01-10 04:04:21 +00004709 alpha=(MagickRealType) (QuantumScale*
4710 GetAlphaPixelComponent(p+u+j));
cristy3ed852e2009-09-05 21:47:34 +00004711 pixel.red+=(*k)*alpha*(p+u+j)->red;
4712 pixel.green+=(*k)*alpha*(p+u+j)->green;
4713 pixel.blue+=(*k)*alpha*(p+u+j)->blue;
4714 pixel.opacity+=(*k)*(p+u+j)->opacity;
4715 gamma+=(*k)*alpha;
4716 k++;
4717 }
4718 }
cristyeaedf062010-05-29 22:36:02 +00004719 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00004720 }
4721 if (gamma != 0.0)
4722 {
4723 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
4724 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004725 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004726 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004727 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004728 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004729 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004730 }
4731 if ((channel & OpacityChannel) != 0)
4732 {
4733 gamma=0.0;
4734 j=0;
cristybb503372010-05-27 20:51:26 +00004735 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00004736 {
cristybb503372010-05-27 20:51:26 +00004737 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00004738 {
4739 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4740 {
4741 pixel.opacity+=(*k)*(p+u+j)->opacity;
4742 gamma+=(*k);
4743 k++;
4744 }
4745 }
cristyeaedf062010-05-29 22:36:02 +00004746 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00004747 }
4748 if (gamma != 0.0)
4749 {
4750 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4751 gamma);
cristy6db48122010-01-11 00:18:07 +00004752 SetOpacityPixelComponent(q,
4753 ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004754 }
4755 }
4756 if (((channel & IndexChannel) != 0) &&
4757 (image->colorspace == CMYKColorspace))
4758 {
4759 gamma=0.0;
4760 j=0;
cristybb503372010-05-27 20:51:26 +00004761 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00004762 {
cristybb503372010-05-27 20:51:26 +00004763 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00004764 {
4765 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4766 {
cristy46f08202010-01-10 04:04:21 +00004767 alpha=(MagickRealType) (QuantumScale*
4768 GetAlphaPixelComponent(p+u+j));
cristy3ed852e2009-09-05 21:47:34 +00004769 pixel.index+=(*k)*alpha*indexes[x+u+j];
4770 gamma+=(*k);
4771 k++;
4772 }
4773 }
cristyeaedf062010-05-29 22:36:02 +00004774 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00004775 }
4776 if (gamma != 0.0)
4777 {
4778 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4779 gamma);
cristy6db48122010-01-11 00:18:07 +00004780 blur_indexes[x]=ClampToQuantum(gamma*
4781 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004782 }
4783 }
4784 }
4785 p++;
4786 q++;
4787 }
4788 sync=SyncCacheViewAuthenticPixels(blur_view,exception);
4789 if (sync == MagickFalse)
4790 status=MagickFalse;
4791 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4792 {
4793 MagickBooleanType
4794 proceed;
4795
cristyb5d5f722009-11-04 03:03:49 +00004796#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004797 #pragma omp critical (MagickCore_SelectiveBlurImageChannel)
4798#endif
4799 proceed=SetImageProgress(image,SelectiveBlurImageTag,progress++,
4800 image->rows);
4801 if (proceed == MagickFalse)
4802 status=MagickFalse;
4803 }
4804 }
4805 blur_image->type=image->type;
4806 blur_view=DestroyCacheView(blur_view);
4807 image_view=DestroyCacheView(image_view);
4808 kernel=(double *) RelinquishMagickMemory(kernel);
4809 if (status == MagickFalse)
4810 blur_image=DestroyImage(blur_image);
4811 return(blur_image);
4812}
4813
4814/*
4815%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4816% %
4817% %
4818% %
4819% S h a d e I m a g e %
4820% %
4821% %
4822% %
4823%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4824%
4825% ShadeImage() shines a distant light on an image to create a
4826% three-dimensional effect. You control the positioning of the light with
4827% azimuth and elevation; azimuth is measured in degrees off the x axis
4828% and elevation is measured in pixels above the Z axis.
4829%
4830% The format of the ShadeImage method is:
4831%
4832% Image *ShadeImage(const Image *image,const MagickBooleanType gray,
4833% const double azimuth,const double elevation,ExceptionInfo *exception)
4834%
4835% A description of each parameter follows:
4836%
4837% o image: the image.
4838%
4839% o gray: A value other than zero shades the intensity of each pixel.
4840%
4841% o azimuth, elevation: Define the light source direction.
4842%
4843% o exception: return any errors or warnings in this structure.
4844%
4845*/
4846MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray,
4847 const double azimuth,const double elevation,ExceptionInfo *exception)
4848{
4849#define ShadeImageTag "Shade/Image"
4850
cristyc4c8d132010-01-07 01:58:38 +00004851 CacheView
4852 *image_view,
4853 *shade_view;
4854
cristy3ed852e2009-09-05 21:47:34 +00004855 Image
4856 *shade_image;
4857
cristy3ed852e2009-09-05 21:47:34 +00004858 MagickBooleanType
4859 status;
4860
cristybb503372010-05-27 20:51:26 +00004861 MagickOffsetType
4862 progress;
4863
cristy3ed852e2009-09-05 21:47:34 +00004864 PrimaryInfo
4865 light;
4866
cristybb503372010-05-27 20:51:26 +00004867 ssize_t
4868 y;
4869
cristy3ed852e2009-09-05 21:47:34 +00004870 /*
4871 Initialize shaded image attributes.
4872 */
4873 assert(image != (const Image *) NULL);
4874 assert(image->signature == MagickSignature);
4875 if (image->debug != MagickFalse)
4876 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4877 assert(exception != (ExceptionInfo *) NULL);
4878 assert(exception->signature == MagickSignature);
4879 shade_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
4880 if (shade_image == (Image *) NULL)
4881 return((Image *) NULL);
4882 if (SetImageStorageClass(shade_image,DirectClass) == MagickFalse)
4883 {
4884 InheritException(exception,&shade_image->exception);
4885 shade_image=DestroyImage(shade_image);
4886 return((Image *) NULL);
4887 }
4888 /*
4889 Compute the light vector.
4890 */
4891 light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))*
4892 cos(DegreesToRadians(elevation));
4893 light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))*
4894 cos(DegreesToRadians(elevation));
4895 light.z=(double) QuantumRange*sin(DegreesToRadians(elevation));
4896 /*
4897 Shade image.
4898 */
4899 status=MagickTrue;
4900 progress=0;
4901 image_view=AcquireCacheView(image);
4902 shade_view=AcquireCacheView(shade_image);
cristyb5d5f722009-11-04 03:03:49 +00004903#if defined(MAGICKCORE_OPENMP_SUPPORT)
4904 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004905#endif
cristybb503372010-05-27 20:51:26 +00004906 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00004907 {
4908 MagickRealType
4909 distance,
4910 normal_distance,
4911 shade;
4912
4913 PrimaryInfo
4914 normal;
4915
4916 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004917 *restrict p,
4918 *restrict s0,
4919 *restrict s1,
4920 *restrict s2;
cristy3ed852e2009-09-05 21:47:34 +00004921
cristybb503372010-05-27 20:51:26 +00004922 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00004923 x;
4924
4925 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004926 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004927
4928 if (status == MagickFalse)
4929 continue;
4930 p=GetCacheViewVirtualPixels(image_view,-1,y-1,image->columns+2,3,exception);
4931 q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1,
4932 exception);
4933 if ((p == (PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
4934 {
4935 status=MagickFalse;
4936 continue;
4937 }
4938 /*
4939 Shade this row of pixels.
4940 */
4941 normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */
4942 s0=p+1;
4943 s1=s0+image->columns+2;
4944 s2=s1+image->columns+2;
cristybb503372010-05-27 20:51:26 +00004945 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004946 {
4947 /*
4948 Determine the surface normal and compute shading.
4949 */
4950 normal.x=(double) (PixelIntensity(s0-1)+PixelIntensity(s1-1)+
4951 PixelIntensity(s2-1)-PixelIntensity(s0+1)-PixelIntensity(s1+1)-
4952 PixelIntensity(s2+1));
4953 normal.y=(double) (PixelIntensity(s2-1)+PixelIntensity(s2)+
4954 PixelIntensity(s2+1)-PixelIntensity(s0-1)-PixelIntensity(s0)-
4955 PixelIntensity(s0+1));
4956 if ((normal.x == 0.0) && (normal.y == 0.0))
4957 shade=light.z;
4958 else
4959 {
4960 shade=0.0;
4961 distance=normal.x*light.x+normal.y*light.y+normal.z*light.z;
4962 if (distance > MagickEpsilon)
4963 {
4964 normal_distance=
4965 normal.x*normal.x+normal.y*normal.y+normal.z*normal.z;
4966 if (normal_distance > (MagickEpsilon*MagickEpsilon))
4967 shade=distance/sqrt((double) normal_distance);
4968 }
4969 }
4970 if (gray != MagickFalse)
4971 {
4972 q->red=(Quantum) shade;
4973 q->green=(Quantum) shade;
4974 q->blue=(Quantum) shade;
4975 }
4976 else
4977 {
cristyce70c172010-01-07 17:15:30 +00004978 q->red=ClampToQuantum(QuantumScale*shade*s1->red);
4979 q->green=ClampToQuantum(QuantumScale*shade*s1->green);
4980 q->blue=ClampToQuantum(QuantumScale*shade*s1->blue);
cristy3ed852e2009-09-05 21:47:34 +00004981 }
4982 q->opacity=s1->opacity;
4983 s0++;
4984 s1++;
4985 s2++;
4986 q++;
4987 }
4988 if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
4989 status=MagickFalse;
4990 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4991 {
4992 MagickBooleanType
4993 proceed;
4994
cristyb5d5f722009-11-04 03:03:49 +00004995#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004996 #pragma omp critical (MagickCore_ShadeImage)
4997#endif
4998 proceed=SetImageProgress(image,ShadeImageTag,progress++,image->rows);
4999 if (proceed == MagickFalse)
5000 status=MagickFalse;
5001 }
5002 }
5003 shade_view=DestroyCacheView(shade_view);
5004 image_view=DestroyCacheView(image_view);
5005 if (status == MagickFalse)
5006 shade_image=DestroyImage(shade_image);
5007 return(shade_image);
5008}
5009
5010/*
5011%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5012% %
5013% %
5014% %
5015% S h a r p e n I m a g e %
5016% %
5017% %
5018% %
5019%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5020%
5021% SharpenImage() sharpens the image. We convolve the image with a Gaussian
5022% operator of the given radius and standard deviation (sigma). For
5023% reasonable results, radius should be larger than sigma. Use a radius of 0
5024% and SharpenImage() selects a suitable radius for you.
5025%
5026% Using a separable kernel would be faster, but the negative weights cancel
5027% out on the corners of the kernel producing often undesirable ringing in the
5028% filtered result; this can be avoided by using a 2D gaussian shaped image
5029% sharpening kernel instead.
5030%
5031% The format of the SharpenImage method is:
5032%
5033% Image *SharpenImage(const Image *image,const double radius,
5034% const double sigma,ExceptionInfo *exception)
5035% Image *SharpenImageChannel(const Image *image,const ChannelType channel,
5036% const double radius,const double sigma,ExceptionInfo *exception)
5037%
5038% A description of each parameter follows:
5039%
5040% o image: the image.
5041%
5042% o channel: the channel type.
5043%
5044% o radius: the radius of the Gaussian, in pixels, not counting the center
5045% pixel.
5046%
5047% o sigma: the standard deviation of the Laplacian, in pixels.
5048%
5049% o exception: return any errors or warnings in this structure.
5050%
5051*/
5052
5053MagickExport Image *SharpenImage(const Image *image,const double radius,
5054 const double sigma,ExceptionInfo *exception)
5055{
5056 Image
5057 *sharp_image;
5058
5059 sharp_image=SharpenImageChannel(image,DefaultChannels,radius,sigma,exception);
5060 return(sharp_image);
5061}
5062
5063MagickExport Image *SharpenImageChannel(const Image *image,
5064 const ChannelType channel,const double radius,const double sigma,
5065 ExceptionInfo *exception)
5066{
5067 double
cristy47e00502009-12-17 19:19:57 +00005068 *kernel,
5069 normalize;
cristy3ed852e2009-09-05 21:47:34 +00005070
5071 Image
5072 *sharp_image;
5073
cristybb503372010-05-27 20:51:26 +00005074 ssize_t
cristy47e00502009-12-17 19:19:57 +00005075 j,
cristy3ed852e2009-09-05 21:47:34 +00005076 u,
5077 v;
5078
cristybb503372010-05-27 20:51:26 +00005079 register ssize_t
cristy47e00502009-12-17 19:19:57 +00005080 i;
5081
cristybb503372010-05-27 20:51:26 +00005082 size_t
cristy3ed852e2009-09-05 21:47:34 +00005083 width;
5084
5085 assert(image != (const Image *) NULL);
5086 assert(image->signature == MagickSignature);
5087 if (image->debug != MagickFalse)
5088 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5089 assert(exception != (ExceptionInfo *) NULL);
5090 assert(exception->signature == MagickSignature);
5091 width=GetOptimalKernelWidth2D(radius,sigma);
5092 kernel=(double *) AcquireQuantumMemory((size_t) width*width,sizeof(*kernel));
5093 if (kernel == (double *) NULL)
5094 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy3ed852e2009-09-05 21:47:34 +00005095 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00005096 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +00005097 i=0;
5098 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00005099 {
cristy47e00502009-12-17 19:19:57 +00005100 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00005101 {
cristy47e00502009-12-17 19:19:57 +00005102 kernel[i]=(-exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
5103 (2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00005104 normalize+=kernel[i];
5105 i++;
5106 }
5107 }
5108 kernel[i/2]=(double) ((-2.0)*normalize);
5109 sharp_image=ConvolveImageChannel(image,channel,width,kernel,exception);
5110 kernel=(double *) RelinquishMagickMemory(kernel);
5111 return(sharp_image);
5112}
5113
5114/*
5115%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5116% %
5117% %
5118% %
5119% S p r e a d I m a g e %
5120% %
5121% %
5122% %
5123%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5124%
5125% SpreadImage() is a special effects method that randomly displaces each
5126% pixel in a block defined by the radius parameter.
5127%
5128% The format of the SpreadImage method is:
5129%
5130% Image *SpreadImage(const Image *image,const double radius,
5131% ExceptionInfo *exception)
5132%
5133% A description of each parameter follows:
5134%
5135% o image: the image.
5136%
5137% o radius: Choose a random pixel in a neighborhood of this extent.
5138%
5139% o exception: return any errors or warnings in this structure.
5140%
5141*/
5142MagickExport Image *SpreadImage(const Image *image,const double radius,
5143 ExceptionInfo *exception)
5144{
5145#define SpreadImageTag "Spread/Image"
5146
cristyfa112112010-01-04 17:48:07 +00005147 CacheView
5148 *image_view;
5149
cristy3ed852e2009-09-05 21:47:34 +00005150 Image
5151 *spread_image;
5152
cristy3ed852e2009-09-05 21:47:34 +00005153 MagickBooleanType
5154 status;
5155
cristybb503372010-05-27 20:51:26 +00005156 MagickOffsetType
5157 progress;
5158
cristy3ed852e2009-09-05 21:47:34 +00005159 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00005160 bias;
cristy3ed852e2009-09-05 21:47:34 +00005161
5162 RandomInfo
cristyfa112112010-01-04 17:48:07 +00005163 **restrict random_info;
cristy3ed852e2009-09-05 21:47:34 +00005164
5165 ResampleFilter
cristyfa112112010-01-04 17:48:07 +00005166 **restrict resample_filter;
cristy3ed852e2009-09-05 21:47:34 +00005167
cristybb503372010-05-27 20:51:26 +00005168 size_t
cristy3ed852e2009-09-05 21:47:34 +00005169 width;
5170
cristybb503372010-05-27 20:51:26 +00005171 ssize_t
5172 y;
5173
cristy3ed852e2009-09-05 21:47:34 +00005174 /*
5175 Initialize spread image attributes.
5176 */
5177 assert(image != (Image *) NULL);
5178 assert(image->signature == MagickSignature);
5179 if (image->debug != MagickFalse)
5180 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5181 assert(exception != (ExceptionInfo *) NULL);
5182 assert(exception->signature == MagickSignature);
5183 spread_image=CloneImage(image,image->columns,image->rows,MagickTrue,
5184 exception);
5185 if (spread_image == (Image *) NULL)
5186 return((Image *) NULL);
5187 if (SetImageStorageClass(spread_image,DirectClass) == MagickFalse)
5188 {
5189 InheritException(exception,&spread_image->exception);
5190 spread_image=DestroyImage(spread_image);
5191 return((Image *) NULL);
5192 }
5193 /*
5194 Spread image.
5195 */
5196 status=MagickTrue;
5197 progress=0;
cristyddd82202009-11-03 20:14:50 +00005198 GetMagickPixelPacket(spread_image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00005199 width=GetOptimalKernelWidth1D(radius,0.5);
cristyb2a11ae2010-02-22 00:53:36 +00005200 resample_filter=AcquireResampleFilterThreadSet(image,
5201 UndefinedVirtualPixelMethod,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00005202 random_info=AcquireRandomInfoThreadSet();
5203 image_view=AcquireCacheView(spread_image);
cristy59e0d3b2010-06-07 13:12:38 +00005204#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy8a7ea362010-03-10 20:31:43 +00005205 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00005206#endif
cristybb503372010-05-27 20:51:26 +00005207 for (y=0; y < (ssize_t) spread_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00005208 {
cristy6ebe97c2010-07-03 01:17:28 +00005209 int
5210 id;
5211
cristy3ed852e2009-09-05 21:47:34 +00005212 MagickPixelPacket
5213 pixel;
5214
5215 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00005216 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00005217
cristybb503372010-05-27 20:51:26 +00005218 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00005219 x;
5220
5221 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00005222 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00005223
5224 if (status == MagickFalse)
5225 continue;
5226 q=QueueCacheViewAuthenticPixels(image_view,0,y,spread_image->columns,1,
5227 exception);
5228 if (q == (PixelPacket *) NULL)
5229 {
5230 status=MagickFalse;
5231 continue;
5232 }
5233 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristyddd82202009-11-03 20:14:50 +00005234 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00005235 id=GetOpenMPThreadId();
cristybb503372010-05-27 20:51:26 +00005236 for (x=0; x < (ssize_t) spread_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00005237 {
5238 (void) ResamplePixelColor(resample_filter[id],(double) x+width*
5239 (GetPseudoRandomValue(random_info[id])-0.5),(double) y+width*
5240 (GetPseudoRandomValue(random_info[id])-0.5),&pixel);
5241 SetPixelPacket(spread_image,&pixel,q,indexes+x);
5242 q++;
5243 }
5244 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
5245 status=MagickFalse;
5246 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5247 {
5248 MagickBooleanType
5249 proceed;
5250
cristy59e0d3b2010-06-07 13:12:38 +00005251#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00005252 #pragma omp critical (MagickCore_SpreadImage)
5253#endif
5254 proceed=SetImageProgress(image,SpreadImageTag,progress++,image->rows);
5255 if (proceed == MagickFalse)
5256 status=MagickFalse;
5257 }
5258 }
5259 image_view=DestroyCacheView(image_view);
5260 random_info=DestroyRandomInfoThreadSet(random_info);
5261 resample_filter=DestroyResampleFilterThreadSet(resample_filter);
5262 return(spread_image);
5263}
5264
5265/*
5266%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5267% %
5268% %
5269% %
5270% U n s h a r p M a s k I m a g e %
5271% %
5272% %
5273% %
5274%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5275%
5276% UnsharpMaskImage() sharpens one or more image channels. We convolve the
5277% image with a Gaussian operator of the given radius and standard deviation
5278% (sigma). For reasonable results, radius should be larger than sigma. Use a
5279% radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
5280%
5281% The format of the UnsharpMaskImage method is:
5282%
5283% Image *UnsharpMaskImage(const Image *image,const double radius,
5284% const double sigma,const double amount,const double threshold,
5285% ExceptionInfo *exception)
5286% Image *UnsharpMaskImageChannel(const Image *image,
5287% const ChannelType channel,const double radius,const double sigma,
5288% const double amount,const double threshold,ExceptionInfo *exception)
5289%
5290% A description of each parameter follows:
5291%
5292% o image: the image.
5293%
5294% o channel: the channel type.
5295%
5296% o radius: the radius of the Gaussian, in pixels, not counting the center
5297% pixel.
5298%
5299% o sigma: the standard deviation of the Gaussian, in pixels.
5300%
5301% o amount: the percentage of the difference between the original and the
5302% blur image that is added back into the original.
5303%
5304% o threshold: the threshold in pixels needed to apply the diffence amount.
5305%
5306% o exception: return any errors or warnings in this structure.
5307%
5308*/
5309
5310MagickExport Image *UnsharpMaskImage(const Image *image,const double radius,
5311 const double sigma,const double amount,const double threshold,
5312 ExceptionInfo *exception)
5313{
5314 Image
5315 *sharp_image;
5316
5317 sharp_image=UnsharpMaskImageChannel(image,DefaultChannels,radius,sigma,amount,
5318 threshold,exception);
5319 return(sharp_image);
5320}
5321
5322MagickExport Image *UnsharpMaskImageChannel(const Image *image,
5323 const ChannelType channel,const double radius,const double sigma,
5324 const double amount,const double threshold,ExceptionInfo *exception)
5325{
5326#define SharpenImageTag "Sharpen/Image"
5327
cristyc4c8d132010-01-07 01:58:38 +00005328 CacheView
5329 *image_view,
5330 *unsharp_view;
5331
cristy3ed852e2009-09-05 21:47:34 +00005332 Image
5333 *unsharp_image;
5334
cristy3ed852e2009-09-05 21:47:34 +00005335 MagickBooleanType
5336 status;
5337
cristybb503372010-05-27 20:51:26 +00005338 MagickOffsetType
5339 progress;
5340
cristy3ed852e2009-09-05 21:47:34 +00005341 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00005342 bias;
cristy3ed852e2009-09-05 21:47:34 +00005343
5344 MagickRealType
5345 quantum_threshold;
5346
cristybb503372010-05-27 20:51:26 +00005347 ssize_t
5348 y;
5349
cristy3ed852e2009-09-05 21:47:34 +00005350 assert(image != (const Image *) NULL);
5351 assert(image->signature == MagickSignature);
5352 if (image->debug != MagickFalse)
5353 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5354 assert(exception != (ExceptionInfo *) NULL);
5355 unsharp_image=BlurImageChannel(image,channel,radius,sigma,exception);
5356 if (unsharp_image == (Image *) NULL)
5357 return((Image *) NULL);
5358 quantum_threshold=(MagickRealType) QuantumRange*threshold;
5359 /*
5360 Unsharp-mask image.
5361 */
5362 status=MagickTrue;
5363 progress=0;
cristyddd82202009-11-03 20:14:50 +00005364 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00005365 image_view=AcquireCacheView(image);
5366 unsharp_view=AcquireCacheView(unsharp_image);
cristyb5d5f722009-11-04 03:03:49 +00005367#if defined(MAGICKCORE_OPENMP_SUPPORT)
5368 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00005369#endif
cristybb503372010-05-27 20:51:26 +00005370 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00005371 {
5372 MagickPixelPacket
5373 pixel;
5374
5375 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00005376 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00005377
5378 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00005379 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00005380
5381 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00005382 *restrict unsharp_indexes;
cristy3ed852e2009-09-05 21:47:34 +00005383
cristybb503372010-05-27 20:51:26 +00005384 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00005385 x;
5386
5387 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00005388 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00005389
5390 if (status == MagickFalse)
5391 continue;
5392 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
5393 q=GetCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
5394 exception);
5395 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
5396 {
5397 status=MagickFalse;
5398 continue;
5399 }
5400 indexes=GetCacheViewVirtualIndexQueue(image_view);
5401 unsharp_indexes=GetCacheViewAuthenticIndexQueue(unsharp_view);
cristyddd82202009-11-03 20:14:50 +00005402 pixel=bias;
cristybb503372010-05-27 20:51:26 +00005403 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00005404 {
5405 if ((channel & RedChannel) != 0)
5406 {
5407 pixel.red=p->red-(MagickRealType) q->red;
5408 if (fabs(2.0*pixel.red) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005409 pixel.red=(MagickRealType) GetRedPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005410 else
5411 pixel.red=(MagickRealType) p->red+(pixel.red*amount);
cristyce70c172010-01-07 17:15:30 +00005412 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00005413 }
5414 if ((channel & GreenChannel) != 0)
5415 {
5416 pixel.green=p->green-(MagickRealType) q->green;
5417 if (fabs(2.0*pixel.green) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005418 pixel.green=(MagickRealType) GetGreenPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005419 else
5420 pixel.green=(MagickRealType) p->green+(pixel.green*amount);
cristyce70c172010-01-07 17:15:30 +00005421 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00005422 }
5423 if ((channel & BlueChannel) != 0)
5424 {
5425 pixel.blue=p->blue-(MagickRealType) q->blue;
5426 if (fabs(2.0*pixel.blue) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005427 pixel.blue=(MagickRealType) GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005428 else
5429 pixel.blue=(MagickRealType) p->blue+(pixel.blue*amount);
cristyce70c172010-01-07 17:15:30 +00005430 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00005431 }
5432 if ((channel & OpacityChannel) != 0)
5433 {
5434 pixel.opacity=p->opacity-(MagickRealType) q->opacity;
5435 if (fabs(2.0*pixel.opacity) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005436 pixel.opacity=(MagickRealType) GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005437 else
5438 pixel.opacity=p->opacity+(pixel.opacity*amount);
cristyce70c172010-01-07 17:15:30 +00005439 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00005440 }
5441 if (((channel & IndexChannel) != 0) &&
5442 (image->colorspace == CMYKColorspace))
5443 {
5444 pixel.index=unsharp_indexes[x]-(MagickRealType) indexes[x];
5445 if (fabs(2.0*pixel.index) < quantum_threshold)
5446 pixel.index=(MagickRealType) unsharp_indexes[x];
5447 else
5448 pixel.index=(MagickRealType) unsharp_indexes[x]+(pixel.index*
5449 amount);
cristyce70c172010-01-07 17:15:30 +00005450 unsharp_indexes[x]=ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00005451 }
5452 p++;
5453 q++;
5454 }
5455 if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
5456 status=MagickFalse;
5457 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5458 {
5459 MagickBooleanType
5460 proceed;
5461
cristyb5d5f722009-11-04 03:03:49 +00005462#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00005463 #pragma omp critical (MagickCore_UnsharpMaskImageChannel)
5464#endif
5465 proceed=SetImageProgress(image,SharpenImageTag,progress++,image->rows);
5466 if (proceed == MagickFalse)
5467 status=MagickFalse;
5468 }
5469 }
5470 unsharp_image->type=image->type;
5471 unsharp_view=DestroyCacheView(unsharp_view);
5472 image_view=DestroyCacheView(image_view);
5473 if (status == MagickFalse)
5474 unsharp_image=DestroyImage(unsharp_image);
5475 return(unsharp_image);
5476}