blob: 69b9d3d9e495832d0b0f9893fb88ed246912bf96 [file] [log] [blame]
cristy3ed852e2009-09-05 21:47:34 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% EEEEE FFFFF FFFFF EEEEE CCCC TTTTT %
7% E F F E C T %
8% EEE FFF FFF EEE C T %
9% E F F E C T %
10% EEEEE F F EEEEE CCCC T %
11% %
12% %
13% MagickCore Image Effects Methods %
14% %
15% Software Design %
16% John Cristy %
17% October 1996 %
18% %
19% %
cristy7e41fe82010-12-04 23:12:08 +000020% Copyright 1999-2011 ImageMagick Studio LLC, a non-profit organization %
cristy3ed852e2009-09-05 21:47:34 +000021% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
26% http://www.imagemagick.org/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
36%
37%
38*/
39
40/*
41 Include declarations.
42*/
43#include "magick/studio.h"
cristyd43a46b2010-01-21 02:13:41 +000044#include "magick/accelerate.h"
cristy3ed852e2009-09-05 21:47:34 +000045#include "magick/blob.h"
46#include "magick/cache-view.h"
47#include "magick/color.h"
48#include "magick/color-private.h"
49#include "magick/colorspace.h"
50#include "magick/constitute.h"
51#include "magick/decorate.h"
52#include "magick/draw.h"
53#include "magick/enhance.h"
54#include "magick/exception.h"
55#include "magick/exception-private.h"
56#include "magick/effect.h"
57#include "magick/fx.h"
58#include "magick/gem.h"
59#include "magick/geometry.h"
60#include "magick/image-private.h"
61#include "magick/list.h"
62#include "magick/log.h"
63#include "magick/memory_.h"
64#include "magick/monitor.h"
65#include "magick/monitor-private.h"
66#include "magick/montage.h"
cristy6771f1e2010-03-05 19:43:39 +000067#include "magick/morphology.h"
cristy3ed852e2009-09-05 21:47:34 +000068#include "magick/paint.h"
69#include "magick/pixel-private.h"
70#include "magick/property.h"
71#include "magick/quantize.h"
72#include "magick/quantum.h"
73#include "magick/random_.h"
74#include "magick/random-private.h"
75#include "magick/resample.h"
76#include "magick/resample-private.h"
77#include "magick/resize.h"
78#include "magick/resource_.h"
79#include "magick/segment.h"
80#include "magick/shear.h"
81#include "magick/signature-private.h"
82#include "magick/string_.h"
83#include "magick/thread-private.h"
84#include "magick/transform.h"
85#include "magick/threshold.h"
86
87/*
88%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
89% %
90% %
91% %
92% A d a p t i v e B l u r I m a g e %
93% %
94% %
95% %
96%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
97%
98% AdaptiveBlurImage() adaptively blurs the image by blurring less
99% intensely near image edges and more intensely far from edges. We blur the
100% image with a Gaussian operator of the given radius and standard deviation
101% (sigma). For reasonable results, radius should be larger than sigma. Use a
102% radius of 0 and AdaptiveBlurImage() selects a suitable radius for you.
103%
104% The format of the AdaptiveBlurImage method is:
105%
106% Image *AdaptiveBlurImage(const Image *image,const double radius,
107% const double sigma,ExceptionInfo *exception)
108% Image *AdaptiveBlurImageChannel(const Image *image,
109% const ChannelType channel,double radius,const double sigma,
110% ExceptionInfo *exception)
111%
112% A description of each parameter follows:
113%
114% o image: the image.
115%
116% o channel: the channel type.
117%
118% o radius: the radius of the Gaussian, in pixels, not counting the center
119% pixel.
120%
121% o sigma: the standard deviation of the Laplacian, in pixels.
122%
123% o exception: return any errors or warnings in this structure.
124%
125*/
126
127MagickExport Image *AdaptiveBlurImage(const Image *image,const double radius,
128 const double sigma,ExceptionInfo *exception)
129{
130 Image
131 *blur_image;
132
133 blur_image=AdaptiveBlurImageChannel(image,DefaultChannels,radius,sigma,
134 exception);
135 return(blur_image);
136}
137
138MagickExport Image *AdaptiveBlurImageChannel(const Image *image,
139 const ChannelType channel,const double radius,const double sigma,
140 ExceptionInfo *exception)
141{
142#define AdaptiveBlurImageTag "Convolve/Image"
143#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
144
cristyc4c8d132010-01-07 01:58:38 +0000145 CacheView
146 *blur_view,
147 *edge_view,
148 *image_view;
149
cristy3ed852e2009-09-05 21:47:34 +0000150 double
cristy47e00502009-12-17 19:19:57 +0000151 **kernel,
152 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000153
154 Image
155 *blur_image,
156 *edge_image,
157 *gaussian_image;
158
cristy3ed852e2009-09-05 21:47:34 +0000159 MagickBooleanType
160 status;
161
cristybb503372010-05-27 20:51:26 +0000162 MagickOffsetType
163 progress;
164
cristy3ed852e2009-09-05 21:47:34 +0000165 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +0000166 bias;
cristy3ed852e2009-09-05 21:47:34 +0000167
cristybb503372010-05-27 20:51:26 +0000168 register ssize_t
cristy47e00502009-12-17 19:19:57 +0000169 i;
cristy3ed852e2009-09-05 21:47:34 +0000170
cristybb503372010-05-27 20:51:26 +0000171 size_t
cristy3ed852e2009-09-05 21:47:34 +0000172 width;
173
cristybb503372010-05-27 20:51:26 +0000174 ssize_t
175 j,
176 k,
177 u,
178 v,
179 y;
180
cristy3ed852e2009-09-05 21:47:34 +0000181 assert(image != (const Image *) NULL);
182 assert(image->signature == MagickSignature);
183 if (image->debug != MagickFalse)
184 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
185 assert(exception != (ExceptionInfo *) NULL);
186 assert(exception->signature == MagickSignature);
187 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
188 if (blur_image == (Image *) NULL)
189 return((Image *) NULL);
190 if (fabs(sigma) <= MagickEpsilon)
191 return(blur_image);
192 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
193 {
194 InheritException(exception,&blur_image->exception);
195 blur_image=DestroyImage(blur_image);
196 return((Image *) NULL);
197 }
198 /*
199 Edge detect the image brighness channel, level, blur, and level again.
200 */
201 edge_image=EdgeImage(image,radius,exception);
202 if (edge_image == (Image *) NULL)
203 {
204 blur_image=DestroyImage(blur_image);
205 return((Image *) NULL);
206 }
207 (void) LevelImage(edge_image,"20%,95%");
208 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,exception);
209 if (gaussian_image != (Image *) NULL)
210 {
211 edge_image=DestroyImage(edge_image);
212 edge_image=gaussian_image;
213 }
214 (void) LevelImage(edge_image,"10%,95%");
215 /*
216 Create a set of kernels from maximum (radius,sigma) to minimum.
217 */
218 width=GetOptimalKernelWidth2D(radius,sigma);
219 kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
220 if (kernel == (double **) NULL)
221 {
222 edge_image=DestroyImage(edge_image);
223 blur_image=DestroyImage(blur_image);
224 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
225 }
226 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
cristybb503372010-05-27 20:51:26 +0000227 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000228 {
229 kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)*
230 sizeof(**kernel));
231 if (kernel[i] == (double *) NULL)
232 break;
cristy47e00502009-12-17 19:19:57 +0000233 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000234 j=(ssize_t) (width-i)/2;
cristy47e00502009-12-17 19:19:57 +0000235 k=0;
236 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000237 {
cristy47e00502009-12-17 19:19:57 +0000238 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000239 {
cristy4205a3c2010-09-12 20:19:59 +0000240 kernel[i][k]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
241 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +0000242 normalize+=kernel[i][k];
243 k++;
cristy3ed852e2009-09-05 21:47:34 +0000244 }
245 }
cristy3ed852e2009-09-05 21:47:34 +0000246 if (fabs(normalize) <= MagickEpsilon)
247 normalize=1.0;
248 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000249 for (k=0; k < (j*j); k++)
250 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000251 }
cristybb503372010-05-27 20:51:26 +0000252 if (i < (ssize_t) width)
cristy3ed852e2009-09-05 21:47:34 +0000253 {
254 for (i-=2; i >= 0; i-=2)
255 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
256 kernel=(double **) RelinquishMagickMemory(kernel);
257 edge_image=DestroyImage(edge_image);
258 blur_image=DestroyImage(blur_image);
259 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
260 }
261 /*
262 Adaptively blur image.
263 */
264 status=MagickTrue;
265 progress=0;
cristyddd82202009-11-03 20:14:50 +0000266 GetMagickPixelPacket(image,&bias);
267 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000268 image_view=AcquireCacheView(image);
269 edge_view=AcquireCacheView(edge_image);
270 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000271#if defined(MAGICKCORE_OPENMP_SUPPORT)
272 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000273#endif
cristybb503372010-05-27 20:51:26 +0000274 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000275 {
276 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000277 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000278
279 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000280 *restrict p,
281 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000282
283 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000284 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000285
cristy3ed852e2009-09-05 21:47:34 +0000286 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000287 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000288
cristy117ff172010-08-15 21:35:32 +0000289 register ssize_t
290 x;
291
cristy3ed852e2009-09-05 21:47:34 +0000292 if (status == MagickFalse)
293 continue;
294 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
295 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
296 exception);
297 if ((r == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
298 {
299 status=MagickFalse;
300 continue;
301 }
302 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
cristybb503372010-05-27 20:51:26 +0000303 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000304 {
305 MagickPixelPacket
306 pixel;
307
308 MagickRealType
309 alpha,
310 gamma;
311
312 register const double
cristyc47d1f82009-11-26 01:44:43 +0000313 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000314
cristybb503372010-05-27 20:51:26 +0000315 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000316 i,
317 u,
318 v;
319
320 gamma=0.0;
cristybb503372010-05-27 20:51:26 +0000321 i=(ssize_t) ceil((double) width*QuantumScale*PixelIntensity(r)-0.5);
cristy3ed852e2009-09-05 21:47:34 +0000322 if (i < 0)
323 i=0;
324 else
cristybb503372010-05-27 20:51:26 +0000325 if (i > (ssize_t) width)
326 i=(ssize_t) width;
cristy3ed852e2009-09-05 21:47:34 +0000327 if ((i & 0x01) != 0)
328 i--;
cristya21afde2010-07-02 00:45:40 +0000329 p=GetCacheViewVirtualPixels(image_view,x-((ssize_t) (width-i)/2L),y-
330 (ssize_t) ((width-i)/2L),width-i,width-i,exception);
cristy3ed852e2009-09-05 21:47:34 +0000331 if (p == (const PixelPacket *) NULL)
332 break;
333 indexes=GetCacheViewVirtualIndexQueue(image_view);
cristyddd82202009-11-03 20:14:50 +0000334 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +0000335 k=kernel[i];
cristybb503372010-05-27 20:51:26 +0000336 for (v=0; v < (ssize_t) (width-i); v++)
cristy3ed852e2009-09-05 21:47:34 +0000337 {
cristybb503372010-05-27 20:51:26 +0000338 for (u=0; u < (ssize_t) (width-i); u++)
cristy3ed852e2009-09-05 21:47:34 +0000339 {
340 alpha=1.0;
341 if (((channel & OpacityChannel) != 0) &&
342 (image->matte != MagickFalse))
cristy46f08202010-01-10 04:04:21 +0000343 alpha=(MagickRealType) (QuantumScale*GetAlphaPixelComponent(p));
cristy3ed852e2009-09-05 21:47:34 +0000344 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000345 pixel.red+=(*k)*alpha*GetRedPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000346 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000347 pixel.green+=(*k)*alpha*GetGreenPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000348 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000349 pixel.blue+=(*k)*alpha*GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000350 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000351 pixel.opacity+=(*k)*GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000352 if (((channel & IndexChannel) != 0) &&
353 (image->colorspace == CMYKColorspace))
354 pixel.index+=(*k)*alpha*indexes[x+(width-i)*v+u];
355 gamma+=(*k)*alpha;
356 k++;
357 p++;
358 }
359 }
360 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
361 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000362 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000363 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000364 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000365 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000366 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000367 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000368 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000369 if (((channel & IndexChannel) != 0) &&
370 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +0000371 blur_indexes[x]=ClampToQuantum(gamma*GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000372 q++;
373 r++;
374 }
375 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
376 status=MagickFalse;
377 if (image->progress_monitor != (MagickProgressMonitor) NULL)
378 {
379 MagickBooleanType
380 proceed;
381
cristyb5d5f722009-11-04 03:03:49 +0000382#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000383 #pragma omp critical (MagickCore_AdaptiveBlurImageChannel)
384#endif
385 proceed=SetImageProgress(image,AdaptiveBlurImageTag,progress++,
386 image->rows);
387 if (proceed == MagickFalse)
388 status=MagickFalse;
389 }
390 }
391 blur_image->type=image->type;
392 blur_view=DestroyCacheView(blur_view);
393 edge_view=DestroyCacheView(edge_view);
394 image_view=DestroyCacheView(image_view);
395 edge_image=DestroyImage(edge_image);
cristybb503372010-05-27 20:51:26 +0000396 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000397 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
398 kernel=(double **) RelinquishMagickMemory(kernel);
399 if (status == MagickFalse)
400 blur_image=DestroyImage(blur_image);
401 return(blur_image);
402}
403
404/*
405%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
406% %
407% %
408% %
409% A d a p t i v e S h a r p e n I m a g e %
410% %
411% %
412% %
413%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
414%
415% AdaptiveSharpenImage() adaptively sharpens the image by sharpening more
416% intensely near image edges and less intensely far from edges. We sharpen the
417% image with a Gaussian operator of the given radius and standard deviation
418% (sigma). For reasonable results, radius should be larger than sigma. Use a
419% radius of 0 and AdaptiveSharpenImage() selects a suitable radius for you.
420%
421% The format of the AdaptiveSharpenImage method is:
422%
423% Image *AdaptiveSharpenImage(const Image *image,const double radius,
424% const double sigma,ExceptionInfo *exception)
425% Image *AdaptiveSharpenImageChannel(const Image *image,
426% const ChannelType channel,double radius,const double sigma,
427% ExceptionInfo *exception)
428%
429% A description of each parameter follows:
430%
431% o image: the image.
432%
433% o channel: the channel type.
434%
435% o radius: the radius of the Gaussian, in pixels, not counting the center
436% pixel.
437%
438% o sigma: the standard deviation of the Laplacian, in pixels.
439%
440% o exception: return any errors or warnings in this structure.
441%
442*/
443
444MagickExport Image *AdaptiveSharpenImage(const Image *image,const double radius,
445 const double sigma,ExceptionInfo *exception)
446{
447 Image
448 *sharp_image;
449
450 sharp_image=AdaptiveSharpenImageChannel(image,DefaultChannels,radius,sigma,
451 exception);
452 return(sharp_image);
453}
454
455MagickExport Image *AdaptiveSharpenImageChannel(const Image *image,
456 const ChannelType channel,const double radius,const double sigma,
457 ExceptionInfo *exception)
458{
459#define AdaptiveSharpenImageTag "Convolve/Image"
460#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
461
cristyc4c8d132010-01-07 01:58:38 +0000462 CacheView
463 *sharp_view,
464 *edge_view,
465 *image_view;
466
cristy3ed852e2009-09-05 21:47:34 +0000467 double
cristy47e00502009-12-17 19:19:57 +0000468 **kernel,
469 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000470
471 Image
472 *sharp_image,
473 *edge_image,
474 *gaussian_image;
475
cristy3ed852e2009-09-05 21:47:34 +0000476 MagickBooleanType
477 status;
478
cristybb503372010-05-27 20:51:26 +0000479 MagickOffsetType
480 progress;
481
cristy3ed852e2009-09-05 21:47:34 +0000482 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +0000483 bias;
cristy3ed852e2009-09-05 21:47:34 +0000484
cristybb503372010-05-27 20:51:26 +0000485 register ssize_t
cristy47e00502009-12-17 19:19:57 +0000486 i;
cristy3ed852e2009-09-05 21:47:34 +0000487
cristybb503372010-05-27 20:51:26 +0000488 size_t
cristy3ed852e2009-09-05 21:47:34 +0000489 width;
490
cristybb503372010-05-27 20:51:26 +0000491 ssize_t
492 j,
493 k,
494 u,
495 v,
496 y;
497
cristy3ed852e2009-09-05 21:47:34 +0000498 assert(image != (const Image *) NULL);
499 assert(image->signature == MagickSignature);
500 if (image->debug != MagickFalse)
501 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
502 assert(exception != (ExceptionInfo *) NULL);
503 assert(exception->signature == MagickSignature);
504 sharp_image=CloneImage(image,0,0,MagickTrue,exception);
505 if (sharp_image == (Image *) NULL)
506 return((Image *) NULL);
507 if (fabs(sigma) <= MagickEpsilon)
508 return(sharp_image);
509 if (SetImageStorageClass(sharp_image,DirectClass) == MagickFalse)
510 {
511 InheritException(exception,&sharp_image->exception);
512 sharp_image=DestroyImage(sharp_image);
513 return((Image *) NULL);
514 }
515 /*
516 Edge detect the image brighness channel, level, sharp, and level again.
517 */
518 edge_image=EdgeImage(image,radius,exception);
519 if (edge_image == (Image *) NULL)
520 {
521 sharp_image=DestroyImage(sharp_image);
522 return((Image *) NULL);
523 }
524 (void) LevelImage(edge_image,"20%,95%");
525 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,exception);
526 if (gaussian_image != (Image *) NULL)
527 {
528 edge_image=DestroyImage(edge_image);
529 edge_image=gaussian_image;
530 }
531 (void) LevelImage(edge_image,"10%,95%");
532 /*
533 Create a set of kernels from maximum (radius,sigma) to minimum.
534 */
535 width=GetOptimalKernelWidth2D(radius,sigma);
536 kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
537 if (kernel == (double **) NULL)
538 {
539 edge_image=DestroyImage(edge_image);
540 sharp_image=DestroyImage(sharp_image);
541 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
542 }
543 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
cristybb503372010-05-27 20:51:26 +0000544 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000545 {
546 kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)*
547 sizeof(**kernel));
548 if (kernel[i] == (double *) NULL)
549 break;
cristy47e00502009-12-17 19:19:57 +0000550 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000551 j=(ssize_t) (width-i)/2;
cristy47e00502009-12-17 19:19:57 +0000552 k=0;
553 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000554 {
cristy47e00502009-12-17 19:19:57 +0000555 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000556 {
cristy4205a3c2010-09-12 20:19:59 +0000557 kernel[i][k]=(double) (-exp(-((double) u*u+v*v)/(2.0*MagickSigma*
558 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +0000559 normalize+=kernel[i][k];
560 k++;
cristy3ed852e2009-09-05 21:47:34 +0000561 }
562 }
cristy3ed852e2009-09-05 21:47:34 +0000563 if (fabs(normalize) <= MagickEpsilon)
564 normalize=1.0;
565 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000566 for (k=0; k < (j*j); k++)
567 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000568 }
cristybb503372010-05-27 20:51:26 +0000569 if (i < (ssize_t) width)
cristy3ed852e2009-09-05 21:47:34 +0000570 {
571 for (i-=2; i >= 0; i-=2)
572 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
573 kernel=(double **) RelinquishMagickMemory(kernel);
574 edge_image=DestroyImage(edge_image);
575 sharp_image=DestroyImage(sharp_image);
576 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
577 }
578 /*
579 Adaptively sharpen image.
580 */
581 status=MagickTrue;
582 progress=0;
cristyddd82202009-11-03 20:14:50 +0000583 GetMagickPixelPacket(image,&bias);
584 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000585 image_view=AcquireCacheView(image);
586 edge_view=AcquireCacheView(edge_image);
587 sharp_view=AcquireCacheView(sharp_image);
cristyb5d5f722009-11-04 03:03:49 +0000588#if defined(MAGICKCORE_OPENMP_SUPPORT)
589 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000590#endif
cristybb503372010-05-27 20:51:26 +0000591 for (y=0; y < (ssize_t) sharp_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000592 {
593 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000594 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000595
596 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000597 *restrict p,
598 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000599
600 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000601 *restrict sharp_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000602
cristy3ed852e2009-09-05 21:47:34 +0000603 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000604 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000605
cristy117ff172010-08-15 21:35:32 +0000606 register ssize_t
607 x;
608
cristy3ed852e2009-09-05 21:47:34 +0000609 if (status == MagickFalse)
610 continue;
611 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
612 q=QueueCacheViewAuthenticPixels(sharp_view,0,y,sharp_image->columns,1,
613 exception);
614 if ((r == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
615 {
616 status=MagickFalse;
617 continue;
618 }
619 sharp_indexes=GetCacheViewAuthenticIndexQueue(sharp_view);
cristybb503372010-05-27 20:51:26 +0000620 for (x=0; x < (ssize_t) sharp_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000621 {
622 MagickPixelPacket
623 pixel;
624
625 MagickRealType
626 alpha,
627 gamma;
628
629 register const double
cristyc47d1f82009-11-26 01:44:43 +0000630 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000631
cristybb503372010-05-27 20:51:26 +0000632 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000633 i,
634 u,
635 v;
636
637 gamma=0.0;
cristybb503372010-05-27 20:51:26 +0000638 i=(ssize_t) ceil((double) width*(QuantumRange-QuantumScale*
cristy1f9ce9f2010-04-28 11:55:12 +0000639 PixelIntensity(r))-0.5);
cristy3ed852e2009-09-05 21:47:34 +0000640 if (i < 0)
641 i=0;
642 else
cristybb503372010-05-27 20:51:26 +0000643 if (i > (ssize_t) width)
644 i=(ssize_t) width;
cristy3ed852e2009-09-05 21:47:34 +0000645 if ((i & 0x01) != 0)
646 i--;
cristy117ff172010-08-15 21:35:32 +0000647 p=GetCacheViewVirtualPixels(image_view,x-((ssize_t) (width-i)/2L),y-
648 (ssize_t) ((width-i)/2L),width-i,width-i,exception);
cristy3ed852e2009-09-05 21:47:34 +0000649 if (p == (const PixelPacket *) NULL)
650 break;
651 indexes=GetCacheViewVirtualIndexQueue(image_view);
652 k=kernel[i];
cristyddd82202009-11-03 20:14:50 +0000653 pixel=bias;
cristybb503372010-05-27 20:51:26 +0000654 for (v=0; v < (ssize_t) (width-i); v++)
cristy3ed852e2009-09-05 21:47:34 +0000655 {
cristybb503372010-05-27 20:51:26 +0000656 for (u=0; u < (ssize_t) (width-i); u++)
cristy3ed852e2009-09-05 21:47:34 +0000657 {
658 alpha=1.0;
659 if (((channel & OpacityChannel) != 0) &&
660 (image->matte != MagickFalse))
cristy46f08202010-01-10 04:04:21 +0000661 alpha=(MagickRealType) (QuantumScale*GetAlphaPixelComponent(p));
cristy3ed852e2009-09-05 21:47:34 +0000662 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000663 pixel.red+=(*k)*alpha*GetRedPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000664 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000665 pixel.green+=(*k)*alpha*GetGreenPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000666 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000667 pixel.blue+=(*k)*alpha*GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000668 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000669 pixel.opacity+=(*k)*GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000670 if (((channel & IndexChannel) != 0) &&
671 (image->colorspace == CMYKColorspace))
672 pixel.index+=(*k)*alpha*indexes[x+(width-i)*v+u];
673 gamma+=(*k)*alpha;
674 k++;
675 p++;
676 }
677 }
678 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
679 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000680 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000681 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000682 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000683 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000684 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000685 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000686 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000687 if (((channel & IndexChannel) != 0) &&
688 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +0000689 sharp_indexes[x]=ClampToQuantum(gamma*GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000690 q++;
691 r++;
692 }
693 if (SyncCacheViewAuthenticPixels(sharp_view,exception) == MagickFalse)
694 status=MagickFalse;
695 if (image->progress_monitor != (MagickProgressMonitor) NULL)
696 {
697 MagickBooleanType
698 proceed;
699
cristyb5d5f722009-11-04 03:03:49 +0000700#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000701 #pragma omp critical (MagickCore_AdaptiveSharpenImageChannel)
702#endif
703 proceed=SetImageProgress(image,AdaptiveSharpenImageTag,progress++,
704 image->rows);
705 if (proceed == MagickFalse)
706 status=MagickFalse;
707 }
708 }
709 sharp_image->type=image->type;
710 sharp_view=DestroyCacheView(sharp_view);
711 edge_view=DestroyCacheView(edge_view);
712 image_view=DestroyCacheView(image_view);
713 edge_image=DestroyImage(edge_image);
cristybb503372010-05-27 20:51:26 +0000714 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000715 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
716 kernel=(double **) RelinquishMagickMemory(kernel);
717 if (status == MagickFalse)
718 sharp_image=DestroyImage(sharp_image);
719 return(sharp_image);
720}
721
722/*
723%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
724% %
725% %
726% %
727% B l u r I m a g e %
728% %
729% %
730% %
731%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
732%
733% BlurImage() blurs an image. We convolve the image with a Gaussian operator
734% of the given radius and standard deviation (sigma). For reasonable results,
735% the radius should be larger than sigma. Use a radius of 0 and BlurImage()
736% selects a suitable radius for you.
737%
738% BlurImage() differs from GaussianBlurImage() in that it uses a separable
739% kernel which is faster but mathematically equivalent to the non-separable
740% kernel.
741%
742% The format of the BlurImage method is:
743%
744% Image *BlurImage(const Image *image,const double radius,
745% const double sigma,ExceptionInfo *exception)
746% Image *BlurImageChannel(const Image *image,const ChannelType channel,
747% const double radius,const double sigma,ExceptionInfo *exception)
748%
749% A description of each parameter follows:
750%
751% o image: the image.
752%
753% o channel: the channel type.
754%
755% o radius: the radius of the Gaussian, in pixels, not counting the center
756% pixel.
757%
758% o sigma: the standard deviation of the Gaussian, in pixels.
759%
760% o exception: return any errors or warnings in this structure.
761%
762*/
763
764MagickExport Image *BlurImage(const Image *image,const double radius,
765 const double sigma,ExceptionInfo *exception)
766{
767 Image
768 *blur_image;
769
770 blur_image=BlurImageChannel(image,DefaultChannels,radius,sigma,exception);
771 return(blur_image);
772}
773
cristybb503372010-05-27 20:51:26 +0000774static double *GetBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +0000775{
cristy3ed852e2009-09-05 21:47:34 +0000776 double
cristy47e00502009-12-17 19:19:57 +0000777 *kernel,
778 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000779
cristy117ff172010-08-15 21:35:32 +0000780 register ssize_t
781 i;
782
cristybb503372010-05-27 20:51:26 +0000783 ssize_t
cristy47e00502009-12-17 19:19:57 +0000784 j,
785 k;
cristy3ed852e2009-09-05 21:47:34 +0000786
cristy3ed852e2009-09-05 21:47:34 +0000787 /*
788 Generate a 1-D convolution kernel.
789 */
790 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
791 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
792 if (kernel == (double *) NULL)
793 return(0);
cristy3ed852e2009-09-05 21:47:34 +0000794 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000795 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +0000796 i=0;
797 for (k=(-j); k <= j; k++)
798 {
cristy4205a3c2010-09-12 20:19:59 +0000799 kernel[i]=(double) (exp(-((double) k*k)/(2.0*MagickSigma*MagickSigma))/
800 (MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +0000801 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +0000802 i++;
803 }
cristybb503372010-05-27 20:51:26 +0000804 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000805 kernel[i]/=normalize;
806 return(kernel);
807}
808
809MagickExport Image *BlurImageChannel(const Image *image,
810 const ChannelType channel,const double radius,const double sigma,
811 ExceptionInfo *exception)
812{
813#define BlurImageTag "Blur/Image"
814
cristyc4c8d132010-01-07 01:58:38 +0000815 CacheView
816 *blur_view,
817 *image_view;
818
cristy3ed852e2009-09-05 21:47:34 +0000819 double
820 *kernel;
821
822 Image
823 *blur_image;
824
cristy3ed852e2009-09-05 21:47:34 +0000825 MagickBooleanType
826 status;
827
cristybb503372010-05-27 20:51:26 +0000828 MagickOffsetType
829 progress;
830
cristy3ed852e2009-09-05 21:47:34 +0000831 MagickPixelPacket
cristy3ed852e2009-09-05 21:47:34 +0000832 bias;
833
cristybb503372010-05-27 20:51:26 +0000834 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000835 i;
836
cristybb503372010-05-27 20:51:26 +0000837 size_t
cristy3ed852e2009-09-05 21:47:34 +0000838 width;
839
cristybb503372010-05-27 20:51:26 +0000840 ssize_t
841 x,
842 y;
843
cristy3ed852e2009-09-05 21:47:34 +0000844 /*
845 Initialize blur image attributes.
846 */
847 assert(image != (Image *) NULL);
848 assert(image->signature == MagickSignature);
849 if (image->debug != MagickFalse)
850 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
851 assert(exception != (ExceptionInfo *) NULL);
852 assert(exception->signature == MagickSignature);
853 blur_image=CloneImage(image,0,0,MagickTrue,exception);
854 if (blur_image == (Image *) NULL)
855 return((Image *) NULL);
856 if (fabs(sigma) <= MagickEpsilon)
857 return(blur_image);
858 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
859 {
860 InheritException(exception,&blur_image->exception);
861 blur_image=DestroyImage(blur_image);
862 return((Image *) NULL);
863 }
864 width=GetOptimalKernelWidth1D(radius,sigma);
865 kernel=GetBlurKernel(width,sigma);
866 if (kernel == (double *) NULL)
867 {
868 blur_image=DestroyImage(blur_image);
869 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
870 }
871 if (image->debug != MagickFalse)
872 {
873 char
874 format[MaxTextExtent],
875 *message;
876
877 register const double
878 *k;
879
880 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +0000881 " BlurImage with %.20g kernel:",(double) width);
cristy3ed852e2009-09-05 21:47:34 +0000882 message=AcquireString("");
883 k=kernel;
cristybb503372010-05-27 20:51:26 +0000884 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000885 {
886 *message='\0';
cristye8c25f92010-06-03 00:53:06 +0000887 (void) FormatMagickString(format,MaxTextExtent,"%.20g: ",(double) i);
cristy3ed852e2009-09-05 21:47:34 +0000888 (void) ConcatenateString(&message,format);
cristye7f51092010-01-17 00:39:37 +0000889 (void) FormatMagickString(format,MaxTextExtent,"%g ",*k++);
cristy3ed852e2009-09-05 21:47:34 +0000890 (void) ConcatenateString(&message,format);
891 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
892 }
893 message=DestroyString(message);
894 }
895 /*
896 Blur rows.
897 */
898 status=MagickTrue;
899 progress=0;
cristyddd82202009-11-03 20:14:50 +0000900 GetMagickPixelPacket(image,&bias);
901 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000902 image_view=AcquireCacheView(image);
903 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000904#if defined(MAGICKCORE_OPENMP_SUPPORT)
905 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000906#endif
cristybb503372010-05-27 20:51:26 +0000907 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000908 {
909 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000910 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000911
912 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000913 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000914
915 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000916 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000917
cristy3ed852e2009-09-05 21:47:34 +0000918 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000919 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000920
cristy117ff172010-08-15 21:35:32 +0000921 register ssize_t
922 x;
923
cristy3ed852e2009-09-05 21:47:34 +0000924 if (status == MagickFalse)
925 continue;
cristy117ff172010-08-15 21:35:32 +0000926 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y,
927 image->columns+width,1,exception);
cristy3ed852e2009-09-05 21:47:34 +0000928 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
929 exception);
930 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
931 {
932 status=MagickFalse;
933 continue;
934 }
935 indexes=GetCacheViewVirtualIndexQueue(image_view);
936 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
cristybb503372010-05-27 20:51:26 +0000937 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000938 {
939 MagickPixelPacket
940 pixel;
941
942 register const double
cristyc47d1f82009-11-26 01:44:43 +0000943 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000944
945 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000946 *restrict kernel_pixels;
cristy3ed852e2009-09-05 21:47:34 +0000947
cristybb503372010-05-27 20:51:26 +0000948 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000949 i;
950
cristyddd82202009-11-03 20:14:50 +0000951 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +0000952 k=kernel;
953 kernel_pixels=p;
954 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
955 {
cristybb503372010-05-27 20:51:26 +0000956 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000957 {
958 pixel.red+=(*k)*kernel_pixels->red;
959 pixel.green+=(*k)*kernel_pixels->green;
960 pixel.blue+=(*k)*kernel_pixels->blue;
961 k++;
962 kernel_pixels++;
963 }
964 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000965 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000966 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000967 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000968 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000969 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000970 if ((channel & OpacityChannel) != 0)
971 {
972 k=kernel;
973 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +0000974 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000975 {
976 pixel.opacity+=(*k)*kernel_pixels->opacity;
977 k++;
978 kernel_pixels++;
979 }
cristyce70c172010-01-07 17:15:30 +0000980 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000981 }
982 if (((channel & IndexChannel) != 0) &&
983 (image->colorspace == CMYKColorspace))
984 {
985 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000986 *restrict kernel_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000987
988 k=kernel;
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
cristy3ed852e2009-09-05 21:47:34 +00001098 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001099 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001100
cristy117ff172010-08-15 21:35:32 +00001101 register ssize_t
1102 y;
1103
cristy3ed852e2009-09-05 21:47:34 +00001104 if (status == MagickFalse)
1105 continue;
cristy117ff172010-08-15 21:35:32 +00001106 p=GetCacheViewVirtualPixels(image_view,x,-((ssize_t) width/2L),1,
1107 image->rows+width,exception);
cristy3ed852e2009-09-05 21:47:34 +00001108 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,
cristy117ff172010-08-15 21:35:32 +00001283% const size_t order,const double *kernel,ExceptionInfo *exception)
cristyfccdab92009-11-30 16:43:57 +00001284%
1285% A description of each parameter follows:
1286%
1287% o image: the image.
1288%
1289% o channel: the channel type.
1290%
1291% o order: the number of columns and rows in the filter kernel.
1292%
1293% o kernel: An array of double representing the convolution kernel.
1294%
1295% o exception: return any errors or warnings in this structure.
1296%
1297*/
1298
cristybb503372010-05-27 20:51:26 +00001299MagickExport Image *ConvolveImage(const Image *image,const size_t order,
cristyfccdab92009-11-30 16:43:57 +00001300 const double *kernel,ExceptionInfo *exception)
1301{
1302 Image
1303 *convolve_image;
1304
1305 convolve_image=ConvolveImageChannel(image,DefaultChannels,order,kernel,
1306 exception);
1307 return(convolve_image);
1308}
1309
1310MagickExport Image *ConvolveImageChannel(const Image *image,
cristybb503372010-05-27 20:51:26 +00001311 const ChannelType channel,const size_t order,const double *kernel,
cristyfccdab92009-11-30 16:43:57 +00001312 ExceptionInfo *exception)
1313{
1314#define ConvolveImageTag "Convolve/Image"
1315
cristyc4c8d132010-01-07 01:58:38 +00001316 CacheView
1317 *convolve_view,
1318 *image_view;
1319
cristyfccdab92009-11-30 16:43:57 +00001320 double
1321 *normal_kernel;
1322
1323 Image
1324 *convolve_image;
1325
cristyfccdab92009-11-30 16:43:57 +00001326 MagickBooleanType
1327 status;
1328
cristybb503372010-05-27 20:51:26 +00001329 MagickOffsetType
1330 progress;
1331
cristyfccdab92009-11-30 16:43:57 +00001332 MagickPixelPacket
1333 bias;
1334
1335 MagickRealType
1336 gamma;
1337
cristybb503372010-05-27 20:51:26 +00001338 register ssize_t
cristyfccdab92009-11-30 16:43:57 +00001339 i;
1340
cristybb503372010-05-27 20:51:26 +00001341 size_t
cristyfccdab92009-11-30 16:43:57 +00001342 width;
1343
cristybb503372010-05-27 20:51:26 +00001344 ssize_t
1345 y;
1346
cristyfccdab92009-11-30 16:43:57 +00001347 /*
1348 Initialize convolve image attributes.
1349 */
1350 assert(image != (Image *) NULL);
1351 assert(image->signature == MagickSignature);
1352 if (image->debug != MagickFalse)
1353 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1354 assert(exception != (ExceptionInfo *) NULL);
1355 assert(exception->signature == MagickSignature);
1356 width=order;
1357 if ((width % 2) == 0)
1358 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
1359 convolve_image=CloneImage(image,0,0,MagickTrue,exception);
1360 if (convolve_image == (Image *) NULL)
1361 return((Image *) NULL);
1362 if (SetImageStorageClass(convolve_image,DirectClass) == MagickFalse)
1363 {
1364 InheritException(exception,&convolve_image->exception);
1365 convolve_image=DestroyImage(convolve_image);
1366 return((Image *) NULL);
1367 }
1368 if (image->debug != MagickFalse)
1369 {
1370 char
1371 format[MaxTextExtent],
1372 *message;
1373
cristy117ff172010-08-15 21:35:32 +00001374 register const double
1375 *k;
1376
cristybb503372010-05-27 20:51:26 +00001377 ssize_t
cristyfccdab92009-11-30 16:43:57 +00001378 u,
1379 v;
1380
cristyfccdab92009-11-30 16:43:57 +00001381 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00001382 " ConvolveImage with %.20gx%.20g kernel:",(double) width,(double)
1383 width);
cristyfccdab92009-11-30 16:43:57 +00001384 message=AcquireString("");
1385 k=kernel;
cristybb503372010-05-27 20:51:26 +00001386 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001387 {
1388 *message='\0';
cristye8c25f92010-06-03 00:53:06 +00001389 (void) FormatMagickString(format,MaxTextExtent,"%.20g: ",(double) v);
cristyfccdab92009-11-30 16:43:57 +00001390 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00001391 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001392 {
cristye7f51092010-01-17 00:39:37 +00001393 (void) FormatMagickString(format,MaxTextExtent,"%g ",*k++);
cristyfccdab92009-11-30 16:43:57 +00001394 (void) ConcatenateString(&message,format);
1395 }
1396 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
1397 }
1398 message=DestroyString(message);
1399 }
1400 /*
1401 Normalize kernel.
1402 */
1403 normal_kernel=(double *) AcquireQuantumMemory(width*width,
1404 sizeof(*normal_kernel));
1405 if (normal_kernel == (double *) NULL)
1406 {
1407 convolve_image=DestroyImage(convolve_image);
1408 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1409 }
1410 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00001411 for (i=0; i < (ssize_t) (width*width); i++)
cristyfccdab92009-11-30 16:43:57 +00001412 gamma+=kernel[i];
1413 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristybb503372010-05-27 20:51:26 +00001414 for (i=0; i < (ssize_t) (width*width); i++)
cristyfccdab92009-11-30 16:43:57 +00001415 normal_kernel[i]=gamma*kernel[i];
1416 /*
1417 Convolve image.
1418 */
1419 status=MagickTrue;
1420 progress=0;
1421 GetMagickPixelPacket(image,&bias);
1422 SetMagickPixelPacketBias(image,&bias);
1423 image_view=AcquireCacheView(image);
1424 convolve_view=AcquireCacheView(convolve_image);
1425#if defined(MAGICKCORE_OPENMP_SUPPORT)
1426 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1427#endif
cristybb503372010-05-27 20:51:26 +00001428 for (y=0; y < (ssize_t) image->rows; y++)
cristyfccdab92009-11-30 16:43:57 +00001429 {
1430 MagickBooleanType
1431 sync;
1432
1433 register const IndexPacket
1434 *restrict indexes;
1435
1436 register const PixelPacket
1437 *restrict p;
1438
1439 register IndexPacket
1440 *restrict convolve_indexes;
1441
cristyfccdab92009-11-30 16:43:57 +00001442 register PixelPacket
1443 *restrict q;
1444
cristy117ff172010-08-15 21:35:32 +00001445 register ssize_t
1446 x;
1447
cristyfccdab92009-11-30 16:43:57 +00001448 if (status == MagickFalse)
1449 continue;
cristyce889302010-06-30 19:16:36 +00001450 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
1451 (width/2L),image->columns+width,width,exception);
cristyfccdab92009-11-30 16:43:57 +00001452 q=GetCacheViewAuthenticPixels(convolve_view,0,y,convolve_image->columns,1,
1453 exception);
1454 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1455 {
1456 status=MagickFalse;
1457 continue;
1458 }
1459 indexes=GetCacheViewVirtualIndexQueue(image_view);
1460 convolve_indexes=GetCacheViewAuthenticIndexQueue(convolve_view);
cristybb503372010-05-27 20:51:26 +00001461 for (x=0; x < (ssize_t) image->columns; x++)
cristyfccdab92009-11-30 16:43:57 +00001462 {
cristyfccdab92009-11-30 16:43:57 +00001463 MagickPixelPacket
1464 pixel;
1465
1466 register const double
1467 *restrict k;
1468
1469 register const PixelPacket
1470 *restrict kernel_pixels;
1471
cristybb503372010-05-27 20:51:26 +00001472 register ssize_t
cristyfccdab92009-11-30 16:43:57 +00001473 u;
1474
cristy117ff172010-08-15 21:35:32 +00001475 ssize_t
1476 v;
1477
cristyfccdab92009-11-30 16:43:57 +00001478 pixel=bias;
1479 k=normal_kernel;
1480 kernel_pixels=p;
1481 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
1482 {
cristybb503372010-05-27 20:51:26 +00001483 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001484 {
cristybb503372010-05-27 20:51:26 +00001485 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001486 {
1487 pixel.red+=(*k)*kernel_pixels[u].red;
1488 pixel.green+=(*k)*kernel_pixels[u].green;
1489 pixel.blue+=(*k)*kernel_pixels[u].blue;
1490 k++;
1491 }
1492 kernel_pixels+=image->columns+width;
1493 }
1494 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001495 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001496 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001497 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001498 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001499 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001500 if ((channel & OpacityChannel) != 0)
1501 {
1502 k=normal_kernel;
1503 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001504 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001505 {
cristybb503372010-05-27 20:51:26 +00001506 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001507 {
1508 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
1509 k++;
1510 }
1511 kernel_pixels+=image->columns+width;
1512 }
cristyce70c172010-01-07 17:15:30 +00001513 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001514 }
1515 if (((channel & IndexChannel) != 0) &&
1516 (image->colorspace == CMYKColorspace))
1517 {
1518 register const IndexPacket
1519 *restrict kernel_indexes;
1520
1521 k=normal_kernel;
1522 kernel_indexes=indexes;
cristybb503372010-05-27 20:51:26 +00001523 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001524 {
cristybb503372010-05-27 20:51:26 +00001525 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001526 {
1527 pixel.index+=(*k)*kernel_indexes[u];
1528 k++;
1529 }
1530 kernel_indexes+=image->columns+width;
1531 }
cristyce70c172010-01-07 17:15:30 +00001532 convolve_indexes[x]=ClampToQuantum(pixel.index);
cristyfccdab92009-11-30 16:43:57 +00001533 }
1534 }
1535 else
1536 {
1537 MagickRealType
1538 alpha,
1539 gamma;
1540
1541 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00001542 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001543 {
cristybb503372010-05-27 20:51:26 +00001544 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001545 {
1546 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
1547 kernel_pixels[u].opacity));
1548 pixel.red+=(*k)*alpha*kernel_pixels[u].red;
1549 pixel.green+=(*k)*alpha*kernel_pixels[u].green;
1550 pixel.blue+=(*k)*alpha*kernel_pixels[u].blue;
cristyfccdab92009-11-30 16:43:57 +00001551 gamma+=(*k)*alpha;
1552 k++;
1553 }
1554 kernel_pixels+=image->columns+width;
1555 }
1556 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1557 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001558 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001559 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001560 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001561 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001562 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001563 if ((channel & OpacityChannel) != 0)
1564 {
1565 k=normal_kernel;
1566 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001567 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001568 {
cristybb503372010-05-27 20:51:26 +00001569 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001570 {
1571 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
1572 k++;
1573 }
1574 kernel_pixels+=image->columns+width;
1575 }
cristyce70c172010-01-07 17:15:30 +00001576 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001577 }
1578 if (((channel & IndexChannel) != 0) &&
1579 (image->colorspace == CMYKColorspace))
1580 {
1581 register const IndexPacket
1582 *restrict kernel_indexes;
1583
1584 k=normal_kernel;
1585 kernel_pixels=p;
1586 kernel_indexes=indexes;
cristybb503372010-05-27 20:51:26 +00001587 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001588 {
cristybb503372010-05-27 20:51:26 +00001589 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001590 {
1591 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
1592 kernel_pixels[u].opacity));
1593 pixel.index+=(*k)*alpha*kernel_indexes[u];
1594 k++;
1595 }
1596 kernel_pixels+=image->columns+width;
1597 kernel_indexes+=image->columns+width;
1598 }
cristy24b06da2010-01-09 23:05:56 +00001599 convolve_indexes[x]=ClampToQuantum(gamma*
1600 GetIndexPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001601 }
1602 }
1603 p++;
1604 q++;
1605 }
1606 sync=SyncCacheViewAuthenticPixels(convolve_view,exception);
1607 if (sync == MagickFalse)
1608 status=MagickFalse;
1609 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1610 {
1611 MagickBooleanType
1612 proceed;
1613
1614#if defined(MAGICKCORE_OPENMP_SUPPORT)
1615 #pragma omp critical (MagickCore_ConvolveImageChannel)
1616#endif
1617 proceed=SetImageProgress(image,ConvolveImageTag,progress++,image->rows);
1618 if (proceed == MagickFalse)
1619 status=MagickFalse;
1620 }
1621 }
1622 convolve_image->type=image->type;
1623 convolve_view=DestroyCacheView(convolve_view);
1624 image_view=DestroyCacheView(image_view);
1625 normal_kernel=(double *) RelinquishMagickMemory(normal_kernel);
1626 if (status == MagickFalse)
1627 convolve_image=DestroyImage(convolve_image);
1628 return(convolve_image);
1629}
1630
1631/*
1632%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1633% %
1634% %
1635% %
cristy3ed852e2009-09-05 21:47:34 +00001636% D e s p e c k l e I m a g e %
1637% %
1638% %
1639% %
1640%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1641%
1642% DespeckleImage() reduces the speckle noise in an image while perserving the
1643% edges of the original image.
1644%
1645% The format of the DespeckleImage method is:
1646%
1647% Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1648%
1649% A description of each parameter follows:
1650%
1651% o image: the image.
1652%
1653% o exception: return any errors or warnings in this structure.
1654%
1655*/
1656
1657static Quantum **DestroyPixelThreadSet(Quantum **pixels)
1658{
cristybb503372010-05-27 20:51:26 +00001659 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001660 i;
1661
1662 assert(pixels != (Quantum **) NULL);
cristybb503372010-05-27 20:51:26 +00001663 for (i=0; i < (ssize_t) GetOpenMPMaximumThreads(); i++)
cristy3ed852e2009-09-05 21:47:34 +00001664 if (pixels[i] != (Quantum *) NULL)
1665 pixels[i]=(Quantum *) RelinquishMagickMemory(pixels[i]);
cristyb41ee102010-10-04 16:46:15 +00001666 pixels=(Quantum **) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001667 return(pixels);
1668}
1669
1670static Quantum **AcquirePixelThreadSet(const size_t count)
1671{
cristy3ed852e2009-09-05 21:47:34 +00001672 Quantum
1673 **pixels;
1674
cristy117ff172010-08-15 21:35:32 +00001675 register ssize_t
1676 i;
1677
cristybb503372010-05-27 20:51:26 +00001678 size_t
cristy3ed852e2009-09-05 21:47:34 +00001679 number_threads;
1680
1681 number_threads=GetOpenMPMaximumThreads();
cristyb41ee102010-10-04 16:46:15 +00001682 pixels=(Quantum **) AcquireQuantumMemory(number_threads,sizeof(*pixels));
cristy3ed852e2009-09-05 21:47:34 +00001683 if (pixels == (Quantum **) NULL)
1684 return((Quantum **) NULL);
1685 (void) ResetMagickMemory(pixels,0,number_threads*sizeof(*pixels));
cristybb503372010-05-27 20:51:26 +00001686 for (i=0; i < (ssize_t) number_threads; i++)
cristy3ed852e2009-09-05 21:47:34 +00001687 {
1688 pixels[i]=(Quantum *) AcquireQuantumMemory(count,sizeof(**pixels));
1689 if (pixels[i] == (Quantum *) NULL)
1690 return(DestroyPixelThreadSet(pixels));
1691 }
1692 return(pixels);
1693}
1694
cristybb503372010-05-27 20:51:26 +00001695static void Hull(const ssize_t x_offset,const ssize_t y_offset,
1696 const size_t columns,const size_t rows,Quantum *f,Quantum *g,
cristy3ed852e2009-09-05 21:47:34 +00001697 const int polarity)
1698{
cristy3ed852e2009-09-05 21:47:34 +00001699 MagickRealType
1700 v;
1701
cristy3ed852e2009-09-05 21:47:34 +00001702 register Quantum
1703 *p,
1704 *q,
1705 *r,
1706 *s;
1707
cristy117ff172010-08-15 21:35:32 +00001708 register ssize_t
1709 x;
1710
1711 ssize_t
1712 y;
1713
cristy3ed852e2009-09-05 21:47:34 +00001714 assert(f != (Quantum *) NULL);
1715 assert(g != (Quantum *) NULL);
1716 p=f+(columns+2);
1717 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001718 r=p+(y_offset*((ssize_t) columns+2)+x_offset);
1719 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001720 {
1721 p++;
1722 q++;
1723 r++;
1724 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001725 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001726 {
1727 v=(MagickRealType) (*p);
1728 if ((MagickRealType) *r >= (v+(MagickRealType) ScaleCharToQuantum(2)))
1729 v+=ScaleCharToQuantum(1);
1730 *q=(Quantum) v;
1731 p++;
1732 q++;
1733 r++;
1734 }
1735 else
cristybb503372010-05-27 20:51:26 +00001736 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001737 {
1738 v=(MagickRealType) (*p);
1739 if ((MagickRealType) *r <= (v-(MagickRealType) ScaleCharToQuantum(2)))
cristybb503372010-05-27 20:51:26 +00001740 v-=(ssize_t) ScaleCharToQuantum(1);
cristy3ed852e2009-09-05 21:47:34 +00001741 *q=(Quantum) v;
1742 p++;
1743 q++;
1744 r++;
1745 }
1746 p++;
1747 q++;
1748 r++;
1749 }
1750 p=f+(columns+2);
1751 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001752 r=q+(y_offset*((ssize_t) columns+2)+x_offset);
1753 s=q-(y_offset*((ssize_t) columns+2)+x_offset);
1754 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001755 {
1756 p++;
1757 q++;
1758 r++;
1759 s++;
1760 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001761 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001762 {
1763 v=(MagickRealType) (*q);
1764 if (((MagickRealType) *s >=
1765 (v+(MagickRealType) ScaleCharToQuantum(2))) &&
1766 ((MagickRealType) *r > v))
1767 v+=ScaleCharToQuantum(1);
1768 *p=(Quantum) v;
1769 p++;
1770 q++;
1771 r++;
1772 s++;
1773 }
1774 else
cristybb503372010-05-27 20:51:26 +00001775 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001776 {
1777 v=(MagickRealType) (*q);
1778 if (((MagickRealType) *s <=
1779 (v-(MagickRealType) ScaleCharToQuantum(2))) &&
1780 ((MagickRealType) *r < v))
1781 v-=(MagickRealType) ScaleCharToQuantum(1);
1782 *p=(Quantum) v;
1783 p++;
1784 q++;
1785 r++;
1786 s++;
1787 }
1788 p++;
1789 q++;
1790 r++;
1791 s++;
1792 }
1793}
1794
1795MagickExport Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1796{
1797#define DespeckleImageTag "Despeckle/Image"
1798
cristy2407fc22009-09-11 00:55:25 +00001799 CacheView
1800 *despeckle_view,
1801 *image_view;
1802
cristy3ed852e2009-09-05 21:47:34 +00001803 Image
1804 *despeckle_image;
1805
cristy3ed852e2009-09-05 21:47:34 +00001806 MagickBooleanType
1807 status;
1808
cristya58c3172011-02-19 19:23:11 +00001809 register ssize_t
1810 i;
1811
cristy3ed852e2009-09-05 21:47:34 +00001812 Quantum
cristyfa112112010-01-04 17:48:07 +00001813 **restrict buffers,
1814 **restrict pixels;
cristy3ed852e2009-09-05 21:47:34 +00001815
1816 size_t
cristya58c3172011-02-19 19:23:11 +00001817 length,
1818 number_channels;
cristy117ff172010-08-15 21:35:32 +00001819
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;
cristy109695a2011-02-19 19:38:14 +00001862 number_channels=(size_t) (image->colorspace == CMYKColorspace ? 5 : 4);
cristy3ed852e2009-09-05 21:47:34 +00001863 image_view=AcquireCacheView(image);
1864 despeckle_view=AcquireCacheView(despeckle_image);
cristyb5d5f722009-11-04 03:03:49 +00001865#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyc26cb732010-10-07 00:55:11 +00001866 #pragma omp parallel for schedule(dynamic,1) shared(status)
cristy3ed852e2009-09-05 21:47:34 +00001867#endif
cristya58c3172011-02-19 19:23:11 +00001868 for (i=0; i <= (ssize_t) number_channels; i++)
cristy3ed852e2009-09-05 21:47:34 +00001869 {
cristy5c9e6f22010-09-17 17:31:01 +00001870 const int
1871 id = GetOpenMPThreadId();
cristy6ebe97c2010-07-03 01:17:28 +00001872
cristy3ed852e2009-09-05 21:47:34 +00001873 register Quantum
1874 *buffer,
1875 *pixel;
1876
cristyc1488b52011-02-19 18:54:15 +00001877 register ssize_t
cristya58c3172011-02-19 19:23:11 +00001878 k,
cristyc1488b52011-02-19 18:54:15 +00001879 x;
1880
cristy117ff172010-08-15 21:35:32 +00001881 ssize_t
1882 j,
1883 y;
1884
cristy3ed852e2009-09-05 21:47:34 +00001885 if (status == MagickFalse)
1886 continue;
cristy691a29e2009-09-11 00:44:10 +00001887 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 {
cristya58c3172011-02-19 19:23:11 +00001893 register const IndexPacket
1894 *restrict indexes;
1895
cristy3ed852e2009-09-05 21:47:34 +00001896 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001897 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001898
1899 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1900 if (p == (const PixelPacket *) NULL)
1901 break;
cristya58c3172011-02-19 19:23:11 +00001902 indexes=GetCacheViewVirtualIndexQueue(image_view);
cristy3ed852e2009-09-05 21:47:34 +00001903 j++;
cristybb503372010-05-27 20:51:26 +00001904 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001905 {
cristya58c3172011-02-19 19:23:11 +00001906 switch (i)
cristy3ed852e2009-09-05 21:47:34 +00001907 {
cristyce70c172010-01-07 17:15:30 +00001908 case 0: pixel[j]=GetRedPixelComponent(p); break;
1909 case 1: pixel[j]=GetGreenPixelComponent(p); break;
1910 case 2: pixel[j]=GetBluePixelComponent(p); break;
1911 case 3: pixel[j]=GetOpacityPixelComponent(p); break;
cristya58c3172011-02-19 19:23:11 +00001912 case 4: pixel[j]=GetBlackPixelComponent(indexes,x); break;
cristy3ed852e2009-09-05 21:47:34 +00001913 default: break;
1914 }
1915 p++;
1916 j++;
1917 }
1918 j++;
1919 }
cristy3ed852e2009-09-05 21:47:34 +00001920 (void) ResetMagickMemory(buffer,0,length*sizeof(*buffer));
cristya58c3172011-02-19 19:23:11 +00001921 for (k=0; k < 4; k++)
cristy3ed852e2009-09-05 21:47:34 +00001922 {
cristya58c3172011-02-19 19:23:11 +00001923 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,1);
1924 Hull(-X[k],-Y[k],image->columns,image->rows,pixel,buffer,1);
1925 Hull(-X[k],-Y[k],image->columns,image->rows,pixel,buffer,-1);
1926 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,-1);
cristy3ed852e2009-09-05 21:47:34 +00001927 }
cristybb503372010-05-27 20:51:26 +00001928 j=(ssize_t) image->columns+2;
1929 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001930 {
1931 MagickBooleanType
1932 sync;
1933
cristya58c3172011-02-19 19:23:11 +00001934 register IndexPacket
1935 *restrict indexes;
1936
cristy3ed852e2009-09-05 21:47:34 +00001937 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001938 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001939
1940 q=GetCacheViewAuthenticPixels(despeckle_view,0,y,despeckle_image->columns,
1941 1,exception);
1942 if (q == (PixelPacket *) NULL)
1943 break;
cristya58c3172011-02-19 19:23:11 +00001944 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristy3ed852e2009-09-05 21:47:34 +00001945 j++;
cristybb503372010-05-27 20:51:26 +00001946 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001947 {
cristya58c3172011-02-19 19:23:11 +00001948 switch (i)
cristy3ed852e2009-09-05 21:47:34 +00001949 {
1950 case 0: q->red=pixel[j]; break;
1951 case 1: q->green=pixel[j]; break;
1952 case 2: q->blue=pixel[j]; break;
1953 case 3: q->opacity=pixel[j]; break;
cristya58c3172011-02-19 19:23:11 +00001954 case 4: indexes[x]=pixel[j]; break;
cristy3ed852e2009-09-05 21:47:34 +00001955 default: break;
1956 }
1957 q++;
1958 j++;
1959 }
1960 sync=SyncCacheViewAuthenticPixels(despeckle_view,exception);
1961 if (sync == MagickFalse)
1962 {
1963 status=MagickFalse;
1964 break;
1965 }
1966 j++;
1967 }
1968 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1969 {
1970 MagickBooleanType
1971 proceed;
1972
cristyb5d5f722009-11-04 03:03:49 +00001973#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001974 #pragma omp critical (MagickCore_DespeckleImage)
1975#endif
cristya58c3172011-02-19 19:23:11 +00001976 proceed=SetImageProgress(image,DespeckleImageTag,(MagickOffsetType) i,
1977 number_channels);
cristy3ed852e2009-09-05 21:47:34 +00001978 if (proceed == MagickFalse)
1979 status=MagickFalse;
1980 }
1981 }
1982 despeckle_view=DestroyCacheView(despeckle_view);
1983 image_view=DestroyCacheView(image_view);
1984 buffers=DestroyPixelThreadSet(buffers);
1985 pixels=DestroyPixelThreadSet(pixels);
1986 despeckle_image->type=image->type;
1987 if (status == MagickFalse)
1988 despeckle_image=DestroyImage(despeckle_image);
1989 return(despeckle_image);
1990}
1991
1992/*
1993%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1994% %
1995% %
1996% %
1997% E d g e I m a g e %
1998% %
1999% %
2000% %
2001%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2002%
2003% EdgeImage() finds edges in an image. Radius defines the radius of the
2004% convolution filter. Use a radius of 0 and EdgeImage() selects a suitable
2005% radius for you.
2006%
2007% The format of the EdgeImage method is:
2008%
2009% Image *EdgeImage(const Image *image,const double radius,
2010% ExceptionInfo *exception)
2011%
2012% A description of each parameter follows:
2013%
2014% o image: the image.
2015%
2016% o radius: the radius of the pixel neighborhood.
2017%
2018% o exception: return any errors or warnings in this structure.
2019%
2020*/
2021MagickExport Image *EdgeImage(const Image *image,const double radius,
2022 ExceptionInfo *exception)
2023{
2024 Image
2025 *edge_image;
2026
2027 double
2028 *kernel;
2029
cristybb503372010-05-27 20:51:26 +00002030 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002031 i;
2032
cristybb503372010-05-27 20:51:26 +00002033 size_t
cristy3ed852e2009-09-05 21:47:34 +00002034 width;
2035
2036 assert(image != (const Image *) NULL);
2037 assert(image->signature == MagickSignature);
2038 if (image->debug != MagickFalse)
2039 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2040 assert(exception != (ExceptionInfo *) NULL);
2041 assert(exception->signature == MagickSignature);
2042 width=GetOptimalKernelWidth1D(radius,0.5);
2043 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2044 if (kernel == (double *) NULL)
2045 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00002046 for (i=0; i < (ssize_t) (width*width); i++)
cristy3ed852e2009-09-05 21:47:34 +00002047 kernel[i]=(-1.0);
2048 kernel[i/2]=(double) (width*width-1.0);
2049 edge_image=ConvolveImage(image,width,kernel,exception);
2050 kernel=(double *) RelinquishMagickMemory(kernel);
2051 return(edge_image);
2052}
2053
2054/*
2055%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2056% %
2057% %
2058% %
2059% E m b o s s I m a g e %
2060% %
2061% %
2062% %
2063%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2064%
2065% EmbossImage() returns a grayscale image with a three-dimensional effect.
2066% We convolve the image with a Gaussian operator of the given radius and
2067% standard deviation (sigma). For reasonable results, radius should be
2068% larger than sigma. Use a radius of 0 and Emboss() selects a suitable
2069% radius for you.
2070%
2071% The format of the EmbossImage method is:
2072%
2073% Image *EmbossImage(const Image *image,const double radius,
2074% const double sigma,ExceptionInfo *exception)
2075%
2076% A description of each parameter follows:
2077%
2078% o image: the image.
2079%
2080% o radius: the radius of the pixel neighborhood.
2081%
2082% o sigma: the standard deviation of the Gaussian, in pixels.
2083%
2084% o exception: return any errors or warnings in this structure.
2085%
2086*/
2087MagickExport Image *EmbossImage(const Image *image,const double radius,
2088 const double sigma,ExceptionInfo *exception)
2089{
2090 double
2091 *kernel;
2092
2093 Image
2094 *emboss_image;
2095
cristybb503372010-05-27 20:51:26 +00002096 register ssize_t
cristy47e00502009-12-17 19:19:57 +00002097 i;
2098
cristybb503372010-05-27 20:51:26 +00002099 size_t
cristy3ed852e2009-09-05 21:47:34 +00002100 width;
2101
cristy117ff172010-08-15 21:35:32 +00002102 ssize_t
2103 j,
2104 k,
2105 u,
2106 v;
2107
cristy3ed852e2009-09-05 21:47:34 +00002108 assert(image != (Image *) NULL);
2109 assert(image->signature == MagickSignature);
2110 if (image->debug != MagickFalse)
2111 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2112 assert(exception != (ExceptionInfo *) NULL);
2113 assert(exception->signature == MagickSignature);
2114 width=GetOptimalKernelWidth2D(radius,sigma);
2115 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2116 if (kernel == (double *) NULL)
2117 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00002118 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +00002119 k=j;
2120 i=0;
2121 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002122 {
cristy47e00502009-12-17 19:19:57 +00002123 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00002124 {
cristy4205a3c2010-09-12 20:19:59 +00002125 kernel[i]=(double) (((u < 0) || (v < 0) ? -8.0 : 8.0)*
cristy47e00502009-12-17 19:19:57 +00002126 exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
cristy4205a3c2010-09-12 20:19:59 +00002127 (2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +00002128 if (u != k)
cristy3ed852e2009-09-05 21:47:34 +00002129 kernel[i]=0.0;
2130 i++;
2131 }
cristy47e00502009-12-17 19:19:57 +00002132 k--;
cristy3ed852e2009-09-05 21:47:34 +00002133 }
2134 emboss_image=ConvolveImage(image,width,kernel,exception);
2135 if (emboss_image != (Image *) NULL)
2136 (void) EqualizeImage(emboss_image);
2137 kernel=(double *) RelinquishMagickMemory(kernel);
2138 return(emboss_image);
2139}
2140
2141/*
2142%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2143% %
2144% %
2145% %
cristy56a9e512010-01-06 18:18:55 +00002146% F i l t e r I m a g e %
2147% %
2148% %
2149% %
2150%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2151%
2152% FilterImage() applies a custom convolution kernel to the image.
2153%
2154% The format of the FilterImage method is:
2155%
cristy2be15382010-01-21 02:38:03 +00002156% Image *FilterImage(const Image *image,const KernelInfo *kernel,
cristy56a9e512010-01-06 18:18:55 +00002157% ExceptionInfo *exception)
2158% Image *FilterImageChannel(const Image *image,const ChannelType channel,
cristy2be15382010-01-21 02:38:03 +00002159% const KernelInfo *kernel,ExceptionInfo *exception)
cristy56a9e512010-01-06 18:18:55 +00002160%
2161% A description of each parameter follows:
2162%
2163% o image: the image.
2164%
2165% o channel: the channel type.
2166%
2167% o kernel: the filtering kernel.
2168%
2169% o exception: return any errors or warnings in this structure.
2170%
2171*/
2172
cristy2be15382010-01-21 02:38:03 +00002173MagickExport Image *FilterImage(const Image *image,const KernelInfo *kernel,
cristy56a9e512010-01-06 18:18:55 +00002174 ExceptionInfo *exception)
2175{
2176 Image
2177 *filter_image;
2178
2179 filter_image=FilterImageChannel(image,DefaultChannels,kernel,exception);
2180 return(filter_image);
2181}
2182
2183MagickExport Image *FilterImageChannel(const Image *image,
cristy2be15382010-01-21 02:38:03 +00002184 const ChannelType channel,const KernelInfo *kernel,ExceptionInfo *exception)
cristy56a9e512010-01-06 18:18:55 +00002185{
2186#define FilterImageTag "Filter/Image"
2187
2188 CacheView
2189 *filter_view,
2190 *image_view;
2191
cristy56a9e512010-01-06 18:18:55 +00002192 Image
2193 *filter_image;
2194
cristy56a9e512010-01-06 18:18:55 +00002195 MagickBooleanType
2196 status;
2197
cristybb503372010-05-27 20:51:26 +00002198 MagickOffsetType
2199 progress;
2200
cristy56a9e512010-01-06 18:18:55 +00002201 MagickPixelPacket
2202 bias;
2203
cristybb503372010-05-27 20:51:26 +00002204 ssize_t
2205 y;
2206
cristy56a9e512010-01-06 18:18:55 +00002207 /*
2208 Initialize filter image attributes.
2209 */
2210 assert(image != (Image *) NULL);
2211 assert(image->signature == MagickSignature);
2212 if (image->debug != MagickFalse)
2213 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2214 assert(exception != (ExceptionInfo *) NULL);
2215 assert(exception->signature == MagickSignature);
2216 if ((kernel->width % 2) == 0)
2217 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
2218 filter_image=CloneImage(image,0,0,MagickTrue,exception);
2219 if (filter_image == (Image *) NULL)
2220 return((Image *) NULL);
2221 if (SetImageStorageClass(filter_image,DirectClass) == MagickFalse)
2222 {
2223 InheritException(exception,&filter_image->exception);
2224 filter_image=DestroyImage(filter_image);
2225 return((Image *) NULL);
2226 }
2227 if (image->debug != MagickFalse)
2228 {
2229 char
2230 format[MaxTextExtent],
2231 *message;
2232
cristy117ff172010-08-15 21:35:32 +00002233 register const double
2234 *k;
2235
cristybb503372010-05-27 20:51:26 +00002236 ssize_t
cristy56a9e512010-01-06 18:18:55 +00002237 u,
2238 v;
2239
cristy56a9e512010-01-06 18:18:55 +00002240 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00002241 " FilterImage with %.20gx%.20g kernel:",(double) kernel->width,(double)
2242 kernel->height);
cristy56a9e512010-01-06 18:18:55 +00002243 message=AcquireString("");
2244 k=kernel->values;
cristybb503372010-05-27 20:51:26 +00002245 for (v=0; v < (ssize_t) kernel->height; v++)
cristy56a9e512010-01-06 18:18:55 +00002246 {
2247 *message='\0';
cristye8c25f92010-06-03 00:53:06 +00002248 (void) FormatMagickString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy56a9e512010-01-06 18:18:55 +00002249 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00002250 for (u=0; u < (ssize_t) kernel->width; u++)
cristy56a9e512010-01-06 18:18:55 +00002251 {
cristye7f51092010-01-17 00:39:37 +00002252 (void) FormatMagickString(format,MaxTextExtent,"%g ",*k++);
cristy56a9e512010-01-06 18:18:55 +00002253 (void) ConcatenateString(&message,format);
2254 }
2255 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
2256 }
2257 message=DestroyString(message);
2258 }
cristy36826ab2010-03-06 01:29:30 +00002259 status=AccelerateConvolveImage(image,kernel,filter_image,exception);
cristyd43a46b2010-01-21 02:13:41 +00002260 if (status == MagickTrue)
2261 return(filter_image);
cristy56a9e512010-01-06 18:18:55 +00002262 /*
2263 Filter image.
2264 */
2265 status=MagickTrue;
2266 progress=0;
2267 GetMagickPixelPacket(image,&bias);
2268 SetMagickPixelPacketBias(image,&bias);
2269 image_view=AcquireCacheView(image);
2270 filter_view=AcquireCacheView(filter_image);
2271#if defined(MAGICKCORE_OPENMP_SUPPORT)
2272 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2273#endif
cristybb503372010-05-27 20:51:26 +00002274 for (y=0; y < (ssize_t) image->rows; y++)
cristy56a9e512010-01-06 18:18:55 +00002275 {
2276 MagickBooleanType
2277 sync;
2278
2279 register const IndexPacket
2280 *restrict indexes;
2281
2282 register const PixelPacket
2283 *restrict p;
2284
2285 register IndexPacket
2286 *restrict filter_indexes;
2287
cristy56a9e512010-01-06 18:18:55 +00002288 register PixelPacket
2289 *restrict q;
2290
cristy117ff172010-08-15 21:35:32 +00002291 register ssize_t
2292 x;
2293
cristy56a9e512010-01-06 18:18:55 +00002294 if (status == MagickFalse)
2295 continue;
cristybb503372010-05-27 20:51:26 +00002296 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) kernel->width/2L),
cristy117ff172010-08-15 21:35:32 +00002297 y-(ssize_t) (kernel->height/2L),image->columns+kernel->width,
2298 kernel->height,exception);
cristy56a9e512010-01-06 18:18:55 +00002299 q=GetCacheViewAuthenticPixels(filter_view,0,y,filter_image->columns,1,
2300 exception);
2301 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2302 {
2303 status=MagickFalse;
2304 continue;
2305 }
2306 indexes=GetCacheViewVirtualIndexQueue(image_view);
2307 filter_indexes=GetCacheViewAuthenticIndexQueue(filter_view);
cristybb503372010-05-27 20:51:26 +00002308 for (x=0; x < (ssize_t) image->columns; x++)
cristy56a9e512010-01-06 18:18:55 +00002309 {
cristy56a9e512010-01-06 18:18:55 +00002310 MagickPixelPacket
2311 pixel;
2312
2313 register const double
2314 *restrict k;
2315
2316 register const PixelPacket
2317 *restrict kernel_pixels;
2318
cristybb503372010-05-27 20:51:26 +00002319 register ssize_t
cristy56a9e512010-01-06 18:18:55 +00002320 u;
2321
cristy117ff172010-08-15 21:35:32 +00002322 ssize_t
2323 v;
2324
cristy56a9e512010-01-06 18:18:55 +00002325 pixel=bias;
cristy36826ab2010-03-06 01:29:30 +00002326 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002327 kernel_pixels=p;
2328 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
2329 {
cristybb503372010-05-27 20:51:26 +00002330 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002331 {
cristybb503372010-05-27 20:51:26 +00002332 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002333 {
2334 pixel.red+=(*k)*kernel_pixels[u].red;
2335 pixel.green+=(*k)*kernel_pixels[u].green;
2336 pixel.blue+=(*k)*kernel_pixels[u].blue;
2337 k++;
2338 }
cristy36826ab2010-03-06 01:29:30 +00002339 kernel_pixels+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002340 }
2341 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002342 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002343 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002344 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002345 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002346 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002347 if ((channel & OpacityChannel) != 0)
2348 {
cristy36826ab2010-03-06 01:29:30 +00002349 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002350 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00002351 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002352 {
cristybb503372010-05-27 20:51:26 +00002353 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002354 {
2355 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
2356 k++;
2357 }
cristy36826ab2010-03-06 01:29:30 +00002358 kernel_pixels+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002359 }
cristyce70c172010-01-07 17:15:30 +00002360 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002361 }
2362 if (((channel & IndexChannel) != 0) &&
2363 (image->colorspace == CMYKColorspace))
2364 {
2365 register const IndexPacket
2366 *restrict kernel_indexes;
2367
cristy36826ab2010-03-06 01:29:30 +00002368 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002369 kernel_indexes=indexes;
cristybb503372010-05-27 20:51:26 +00002370 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002371 {
cristybb503372010-05-27 20:51:26 +00002372 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002373 {
2374 pixel.index+=(*k)*kernel_indexes[u];
2375 k++;
2376 }
cristy36826ab2010-03-06 01:29:30 +00002377 kernel_indexes+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002378 }
cristyce70c172010-01-07 17:15:30 +00002379 filter_indexes[x]=ClampToQuantum(pixel.index);
cristy56a9e512010-01-06 18:18:55 +00002380 }
2381 }
2382 else
2383 {
2384 MagickRealType
2385 alpha,
2386 gamma;
2387
2388 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00002389 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002390 {
cristybb503372010-05-27 20:51:26 +00002391 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002392 {
2393 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
2394 kernel_pixels[u].opacity));
2395 pixel.red+=(*k)*alpha*kernel_pixels[u].red;
2396 pixel.green+=(*k)*alpha*kernel_pixels[u].green;
2397 pixel.blue+=(*k)*alpha*kernel_pixels[u].blue;
2398 gamma+=(*k)*alpha;
2399 k++;
2400 }
cristy36826ab2010-03-06 01:29:30 +00002401 kernel_pixels+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002402 }
2403 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
2404 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002405 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002406 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002407 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002408 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002409 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002410 if ((channel & OpacityChannel) != 0)
2411 {
cristy36826ab2010-03-06 01:29:30 +00002412 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002413 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00002414 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002415 {
cristybb503372010-05-27 20:51:26 +00002416 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002417 {
2418 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
2419 k++;
2420 }
cristy36826ab2010-03-06 01:29:30 +00002421 kernel_pixels+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002422 }
cristyce70c172010-01-07 17:15:30 +00002423 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002424 }
2425 if (((channel & IndexChannel) != 0) &&
2426 (image->colorspace == CMYKColorspace))
2427 {
2428 register const IndexPacket
2429 *restrict kernel_indexes;
2430
cristy36826ab2010-03-06 01:29:30 +00002431 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002432 kernel_pixels=p;
2433 kernel_indexes=indexes;
cristybb503372010-05-27 20:51:26 +00002434 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002435 {
cristybb503372010-05-27 20:51:26 +00002436 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002437 {
2438 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
2439 kernel_pixels[u].opacity));
2440 pixel.index+=(*k)*alpha*kernel_indexes[u];
2441 k++;
2442 }
cristy36826ab2010-03-06 01:29:30 +00002443 kernel_pixels+=image->columns+kernel->width;
2444 kernel_indexes+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002445 }
cristy2115aea2010-01-09 23:16:08 +00002446 filter_indexes[x]=ClampToQuantum(gamma*
2447 GetIndexPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002448 }
2449 }
2450 p++;
2451 q++;
2452 }
2453 sync=SyncCacheViewAuthenticPixels(filter_view,exception);
2454 if (sync == MagickFalse)
2455 status=MagickFalse;
2456 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2457 {
2458 MagickBooleanType
2459 proceed;
2460
2461#if defined(MAGICKCORE_OPENMP_SUPPORT)
2462 #pragma omp critical (MagickCore_FilterImageChannel)
2463#endif
2464 proceed=SetImageProgress(image,FilterImageTag,progress++,image->rows);
2465 if (proceed == MagickFalse)
2466 status=MagickFalse;
2467 }
2468 }
2469 filter_image->type=image->type;
2470 filter_view=DestroyCacheView(filter_view);
2471 image_view=DestroyCacheView(image_view);
cristy56a9e512010-01-06 18:18:55 +00002472 if (status == MagickFalse)
2473 filter_image=DestroyImage(filter_image);
2474 return(filter_image);
2475}
2476
2477/*
2478%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2479% %
2480% %
2481% %
cristy3ed852e2009-09-05 21:47:34 +00002482% G a u s s i a n B l u r I m a g e %
2483% %
2484% %
2485% %
2486%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2487%
2488% GaussianBlurImage() blurs an image. We convolve the image with a
2489% Gaussian operator of the given radius and standard deviation (sigma).
2490% For reasonable results, the radius should be larger than sigma. Use a
2491% radius of 0 and GaussianBlurImage() selects a suitable radius for you
2492%
2493% The format of the GaussianBlurImage method is:
2494%
2495% Image *GaussianBlurImage(const Image *image,onst double radius,
2496% const double sigma,ExceptionInfo *exception)
2497% Image *GaussianBlurImageChannel(const Image *image,
2498% const ChannelType channel,const double radius,const double sigma,
2499% ExceptionInfo *exception)
2500%
2501% A description of each parameter follows:
2502%
2503% o image: the image.
2504%
2505% o channel: the channel type.
2506%
2507% o radius: the radius of the Gaussian, in pixels, not counting the center
2508% pixel.
2509%
2510% o sigma: the standard deviation of the Gaussian, in pixels.
2511%
2512% o exception: return any errors or warnings in this structure.
2513%
2514*/
2515
2516MagickExport Image *GaussianBlurImage(const Image *image,const double radius,
2517 const double sigma,ExceptionInfo *exception)
2518{
2519 Image
2520 *blur_image;
2521
2522 blur_image=GaussianBlurImageChannel(image,DefaultChannels,radius,sigma,
2523 exception);
2524 return(blur_image);
2525}
2526
2527MagickExport Image *GaussianBlurImageChannel(const Image *image,
2528 const ChannelType channel,const double radius,const double sigma,
2529 ExceptionInfo *exception)
2530{
2531 double
2532 *kernel;
2533
2534 Image
2535 *blur_image;
2536
cristybb503372010-05-27 20:51:26 +00002537 register ssize_t
cristy47e00502009-12-17 19:19:57 +00002538 i;
2539
cristybb503372010-05-27 20:51:26 +00002540 size_t
cristy3ed852e2009-09-05 21:47:34 +00002541 width;
2542
cristy117ff172010-08-15 21:35:32 +00002543 ssize_t
2544 j,
2545 u,
2546 v;
2547
cristy3ed852e2009-09-05 21:47:34 +00002548 assert(image != (const Image *) NULL);
2549 assert(image->signature == MagickSignature);
2550 if (image->debug != MagickFalse)
2551 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2552 assert(exception != (ExceptionInfo *) NULL);
2553 assert(exception->signature == MagickSignature);
2554 width=GetOptimalKernelWidth2D(radius,sigma);
2555 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2556 if (kernel == (double *) NULL)
2557 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00002558 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00002559 i=0;
cristy47e00502009-12-17 19:19:57 +00002560 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002561 {
cristy47e00502009-12-17 19:19:57 +00002562 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00002563 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
2564 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00002565 }
2566 blur_image=ConvolveImageChannel(image,channel,width,kernel,exception);
2567 kernel=(double *) RelinquishMagickMemory(kernel);
2568 return(blur_image);
2569}
2570
2571/*
2572%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2573% %
2574% %
2575% %
2576% M e d i a n F i l t e r I m a g e %
2577% %
2578% %
2579% %
2580%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2581%
2582% MedianFilterImage() applies a digital filter that improves the quality
2583% of a noisy image. Each pixel is replaced by the median in a set of
2584% neighboring pixels as defined by radius.
2585%
2586% The algorithm was contributed by Mike Edmonds and implements an insertion
2587% sort for selecting median color-channel values. For more on this algorithm
2588% see "Skip Lists: A probabilistic Alternative to Balanced Trees" by William
2589% Pugh in the June 1990 of Communications of the ACM.
2590%
2591% The format of the MedianFilterImage method is:
2592%
2593% Image *MedianFilterImage(const Image *image,const double radius,
2594% ExceptionInfo *exception)
2595%
2596% A description of each parameter follows:
2597%
2598% o image: the image.
2599%
2600% o radius: the radius of the pixel neighborhood.
2601%
2602% o exception: return any errors or warnings in this structure.
2603%
2604*/
2605
2606#define MedianListChannels 5
2607
2608typedef struct _MedianListNode
2609{
cristybb503372010-05-27 20:51:26 +00002610 size_t
cristy3ed852e2009-09-05 21:47:34 +00002611 next[9],
2612 count,
2613 signature;
2614} MedianListNode;
2615
2616typedef struct _MedianSkipList
2617{
cristybb503372010-05-27 20:51:26 +00002618 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002619 level;
2620
2621 MedianListNode
2622 *nodes;
2623} MedianSkipList;
2624
2625typedef struct _MedianPixelList
2626{
cristybb503372010-05-27 20:51:26 +00002627 size_t
cristy3ed852e2009-09-05 21:47:34 +00002628 center,
2629 seed,
2630 signature;
2631
2632 MedianSkipList
2633 lists[MedianListChannels];
2634} MedianPixelList;
2635
2636static MedianPixelList *DestroyMedianPixelList(MedianPixelList *pixel_list)
2637{
cristybb503372010-05-27 20:51:26 +00002638 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002639 i;
2640
2641 if (pixel_list == (MedianPixelList *) NULL)
2642 return((MedianPixelList *) NULL);
2643 for (i=0; i < MedianListChannels; i++)
2644 if (pixel_list->lists[i].nodes != (MedianListNode *) NULL)
2645 pixel_list->lists[i].nodes=(MedianListNode *) RelinquishMagickMemory(
2646 pixel_list->lists[i].nodes);
cristyb41ee102010-10-04 16:46:15 +00002647 pixel_list=(MedianPixelList *) RelinquishMagickMemory(pixel_list);
cristy3ed852e2009-09-05 21:47:34 +00002648 return(pixel_list);
2649}
2650
2651static MedianPixelList **DestroyMedianPixelListThreadSet(
2652 MedianPixelList **pixel_list)
2653{
cristybb503372010-05-27 20:51:26 +00002654 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002655 i;
2656
2657 assert(pixel_list != (MedianPixelList **) NULL);
cristybb503372010-05-27 20:51:26 +00002658 for (i=0; i < (ssize_t) GetOpenMPMaximumThreads(); i++)
cristy3ed852e2009-09-05 21:47:34 +00002659 if (pixel_list[i] != (MedianPixelList *) NULL)
2660 pixel_list[i]=DestroyMedianPixelList(pixel_list[i]);
cristyb41ee102010-10-04 16:46:15 +00002661 pixel_list=(MedianPixelList **) RelinquishMagickMemory(pixel_list);
cristy3ed852e2009-09-05 21:47:34 +00002662 return(pixel_list);
2663}
2664
cristybb503372010-05-27 20:51:26 +00002665static MedianPixelList *AcquireMedianPixelList(const size_t width)
cristy3ed852e2009-09-05 21:47:34 +00002666{
2667 MedianPixelList
2668 *pixel_list;
2669
cristybb503372010-05-27 20:51:26 +00002670 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002671 i;
2672
cristy73bd4a52010-10-05 11:24:23 +00002673 pixel_list=(MedianPixelList *) AcquireMagickMemory(sizeof(*pixel_list));
cristy3ed852e2009-09-05 21:47:34 +00002674 if (pixel_list == (MedianPixelList *) NULL)
2675 return(pixel_list);
2676 (void) ResetMagickMemory((void *) pixel_list,0,sizeof(*pixel_list));
2677 pixel_list->center=width*width/2;
2678 for (i=0; i < MedianListChannels; i++)
2679 {
2680 pixel_list->lists[i].nodes=(MedianListNode *) AcquireQuantumMemory(65537UL,
2681 sizeof(*pixel_list->lists[i].nodes));
2682 if (pixel_list->lists[i].nodes == (MedianListNode *) NULL)
2683 return(DestroyMedianPixelList(pixel_list));
2684 (void) ResetMagickMemory(pixel_list->lists[i].nodes,0,65537UL*
2685 sizeof(*pixel_list->lists[i].nodes));
2686 }
2687 pixel_list->signature=MagickSignature;
2688 return(pixel_list);
2689}
2690
cristy117ff172010-08-15 21:35:32 +00002691static MedianPixelList **AcquireMedianPixelListThreadSet(const size_t width)
cristy3ed852e2009-09-05 21:47:34 +00002692{
cristy3ed852e2009-09-05 21:47:34 +00002693 MedianPixelList
2694 **pixel_list;
2695
cristy117ff172010-08-15 21:35:32 +00002696 register ssize_t
2697 i;
2698
cristybb503372010-05-27 20:51:26 +00002699 size_t
cristy3ed852e2009-09-05 21:47:34 +00002700 number_threads;
2701
2702 number_threads=GetOpenMPMaximumThreads();
cristyb41ee102010-10-04 16:46:15 +00002703 pixel_list=(MedianPixelList **) AcquireQuantumMemory(number_threads,
cristy3ed852e2009-09-05 21:47:34 +00002704 sizeof(*pixel_list));
2705 if (pixel_list == (MedianPixelList **) NULL)
2706 return((MedianPixelList **) NULL);
2707 (void) ResetMagickMemory(pixel_list,0,number_threads*sizeof(*pixel_list));
cristybb503372010-05-27 20:51:26 +00002708 for (i=0; i < (ssize_t) number_threads; i++)
cristy3ed852e2009-09-05 21:47:34 +00002709 {
2710 pixel_list[i]=AcquireMedianPixelList(width);
2711 if (pixel_list[i] == (MedianPixelList *) NULL)
2712 return(DestroyMedianPixelListThreadSet(pixel_list));
2713 }
2714 return(pixel_list);
2715}
2716
2717static void AddNodeMedianPixelList(MedianPixelList *pixel_list,
cristybb503372010-05-27 20:51:26 +00002718 const ssize_t channel,const size_t color)
cristy3ed852e2009-09-05 21:47:34 +00002719{
cristy3ed852e2009-09-05 21:47:34 +00002720 register MedianSkipList
2721 *list;
2722
cristy117ff172010-08-15 21:35:32 +00002723 register ssize_t
2724 level;
2725
cristybb503372010-05-27 20:51:26 +00002726 size_t
cristy3ed852e2009-09-05 21:47:34 +00002727 search,
2728 update[9];
2729
2730 /*
2731 Initialize the node.
2732 */
2733 list=pixel_list->lists+channel;
2734 list->nodes[color].signature=pixel_list->signature;
2735 list->nodes[color].count=1;
2736 /*
cristy33c53022010-06-25 12:17:27 +00002737 Determine where it belongs in the list.
cristy3ed852e2009-09-05 21:47:34 +00002738 */
2739 search=65536UL;
2740 for (level=list->level; level >= 0; level--)
2741 {
2742 while (list->nodes[search].next[level] < color)
2743 search=list->nodes[search].next[level];
2744 update[level]=search;
2745 }
2746 /*
2747 Generate a pseudo-random level for this node.
2748 */
2749 for (level=0; ; level++)
2750 {
2751 pixel_list->seed=(pixel_list->seed*42893621L)+1L;
2752 if ((pixel_list->seed & 0x300) != 0x300)
2753 break;
2754 }
2755 if (level > 8)
2756 level=8;
2757 if (level > (list->level+2))
2758 level=list->level+2;
2759 /*
2760 If we're raising the list's level, link back to the root node.
2761 */
2762 while (level > list->level)
2763 {
2764 list->level++;
2765 update[list->level]=65536UL;
2766 }
2767 /*
2768 Link the node into the skip-list.
2769 */
2770 do
2771 {
2772 list->nodes[color].next[level]=list->nodes[update[level]].next[level];
2773 list->nodes[update[level]].next[level]=color;
2774 }
2775 while (level-- > 0);
2776}
2777
2778static MagickPixelPacket GetMedianPixelList(MedianPixelList *pixel_list)
2779{
2780 MagickPixelPacket
2781 pixel;
2782
cristy3ed852e2009-09-05 21:47:34 +00002783 register MedianSkipList
2784 *list;
2785
cristy117ff172010-08-15 21:35:32 +00002786 register ssize_t
2787 channel;
2788
cristybb503372010-05-27 20:51:26 +00002789 size_t
cristy3ed852e2009-09-05 21:47:34 +00002790 center,
2791 color,
2792 count;
2793
2794 unsigned short
2795 channels[MedianListChannels];
2796
2797 /*
2798 Find the median value for each of the color.
2799 */
2800 center=pixel_list->center;
2801 for (channel=0; channel < 5; channel++)
2802 {
2803 list=pixel_list->lists+channel;
2804 color=65536UL;
2805 count=0;
2806 do
2807 {
2808 color=list->nodes[color].next[0];
2809 count+=list->nodes[color].count;
2810 }
2811 while (count <= center);
2812 channels[channel]=(unsigned short) color;
2813 }
2814 GetMagickPixelPacket((const Image *) NULL,&pixel);
2815 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
2816 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
2817 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
2818 pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
2819 pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]);
2820 return(pixel);
2821}
2822
2823static inline void InsertMedianPixelList(const Image *image,
2824 const PixelPacket *pixel,const IndexPacket *indexes,
2825 MedianPixelList *pixel_list)
2826{
cristybb503372010-05-27 20:51:26 +00002827 size_t
cristy3ed852e2009-09-05 21:47:34 +00002828 signature;
2829
2830 unsigned short
2831 index;
2832
2833 index=ScaleQuantumToShort(pixel->red);
2834 signature=pixel_list->lists[0].nodes[index].signature;
2835 if (signature == pixel_list->signature)
2836 pixel_list->lists[0].nodes[index].count++;
2837 else
2838 AddNodeMedianPixelList(pixel_list,0,index);
2839 index=ScaleQuantumToShort(pixel->green);
2840 signature=pixel_list->lists[1].nodes[index].signature;
2841 if (signature == pixel_list->signature)
2842 pixel_list->lists[1].nodes[index].count++;
2843 else
2844 AddNodeMedianPixelList(pixel_list,1,index);
2845 index=ScaleQuantumToShort(pixel->blue);
2846 signature=pixel_list->lists[2].nodes[index].signature;
2847 if (signature == pixel_list->signature)
2848 pixel_list->lists[2].nodes[index].count++;
2849 else
2850 AddNodeMedianPixelList(pixel_list,2,index);
2851 index=ScaleQuantumToShort(pixel->opacity);
2852 signature=pixel_list->lists[3].nodes[index].signature;
2853 if (signature == pixel_list->signature)
2854 pixel_list->lists[3].nodes[index].count++;
2855 else
2856 AddNodeMedianPixelList(pixel_list,3,index);
2857 if (image->colorspace == CMYKColorspace)
2858 index=ScaleQuantumToShort(*indexes);
2859 signature=pixel_list->lists[4].nodes[index].signature;
2860 if (signature == pixel_list->signature)
2861 pixel_list->lists[4].nodes[index].count++;
2862 else
2863 AddNodeMedianPixelList(pixel_list,4,index);
2864}
2865
2866static void ResetMedianPixelList(MedianPixelList *pixel_list)
2867{
2868 int
2869 level;
2870
cristy3ed852e2009-09-05 21:47:34 +00002871 register MedianListNode
2872 *root;
2873
2874 register MedianSkipList
2875 *list;
2876
cristy117ff172010-08-15 21:35:32 +00002877 register ssize_t
2878 channel;
2879
cristy3ed852e2009-09-05 21:47:34 +00002880 /*
2881 Reset the skip-list.
2882 */
2883 for (channel=0; channel < 5; channel++)
2884 {
2885 list=pixel_list->lists+channel;
2886 root=list->nodes+65536UL;
2887 list->level=0;
2888 for (level=0; level < 9; level++)
2889 root->next[level]=65536UL;
2890 }
2891 pixel_list->seed=pixel_list->signature++;
2892}
2893
2894MagickExport Image *MedianFilterImage(const Image *image,const double radius,
2895 ExceptionInfo *exception)
2896{
2897#define MedianFilterImageTag "MedianFilter/Image"
2898
cristyc4c8d132010-01-07 01:58:38 +00002899 CacheView
2900 *image_view,
2901 *median_view;
2902
cristy3ed852e2009-09-05 21:47:34 +00002903 Image
2904 *median_image;
2905
cristy3ed852e2009-09-05 21:47:34 +00002906 MagickBooleanType
2907 status;
2908
cristybb503372010-05-27 20:51:26 +00002909 MagickOffsetType
2910 progress;
2911
cristy3ed852e2009-09-05 21:47:34 +00002912 MedianPixelList
cristyfa112112010-01-04 17:48:07 +00002913 **restrict pixel_list;
cristy3ed852e2009-09-05 21:47:34 +00002914
cristybb503372010-05-27 20:51:26 +00002915 size_t
cristy3ed852e2009-09-05 21:47:34 +00002916 width;
2917
cristybb503372010-05-27 20:51:26 +00002918 ssize_t
2919 y;
2920
cristy3ed852e2009-09-05 21:47:34 +00002921 /*
2922 Initialize median image attributes.
2923 */
2924 assert(image != (Image *) NULL);
2925 assert(image->signature == MagickSignature);
2926 if (image->debug != MagickFalse)
2927 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2928 assert(exception != (ExceptionInfo *) NULL);
2929 assert(exception->signature == MagickSignature);
2930 width=GetOptimalKernelWidth2D(radius,0.5);
2931 if ((image->columns < width) || (image->rows < width))
2932 ThrowImageException(OptionError,"ImageSmallerThanKernelRadius");
2933 median_image=CloneImage(image,image->columns,image->rows,MagickTrue,
2934 exception);
2935 if (median_image == (Image *) NULL)
2936 return((Image *) NULL);
2937 if (SetImageStorageClass(median_image,DirectClass) == MagickFalse)
2938 {
2939 InheritException(exception,&median_image->exception);
2940 median_image=DestroyImage(median_image);
2941 return((Image *) NULL);
2942 }
2943 pixel_list=AcquireMedianPixelListThreadSet(width);
2944 if (pixel_list == (MedianPixelList **) NULL)
2945 {
2946 median_image=DestroyImage(median_image);
2947 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2948 }
2949 /*
2950 Median filter each image row.
2951 */
2952 status=MagickTrue;
2953 progress=0;
2954 image_view=AcquireCacheView(image);
2955 median_view=AcquireCacheView(median_image);
cristyb5d5f722009-11-04 03:03:49 +00002956#if defined(MAGICKCORE_OPENMP_SUPPORT)
2957 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002958#endif
cristybb503372010-05-27 20:51:26 +00002959 for (y=0; y < (ssize_t) median_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002960 {
cristy5c9e6f22010-09-17 17:31:01 +00002961 const int
2962 id = GetOpenMPThreadId();
cristy6ebe97c2010-07-03 01:17:28 +00002963
cristy3ed852e2009-09-05 21:47:34 +00002964 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002965 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002966
2967 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002968 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002969
2970 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002971 *restrict median_indexes;
cristy3ed852e2009-09-05 21:47:34 +00002972
cristy3ed852e2009-09-05 21:47:34 +00002973 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002974 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002975
cristy117ff172010-08-15 21:35:32 +00002976 register ssize_t
2977 x;
2978
cristy3ed852e2009-09-05 21:47:34 +00002979 if (status == MagickFalse)
2980 continue;
cristy6ebe97c2010-07-03 01:17:28 +00002981 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
2982 (width/2L),image->columns+width,width,exception);
cristy3ed852e2009-09-05 21:47:34 +00002983 q=QueueCacheViewAuthenticPixels(median_view,0,y,median_image->columns,1,
2984 exception);
2985 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2986 {
2987 status=MagickFalse;
2988 continue;
2989 }
2990 indexes=GetCacheViewVirtualIndexQueue(image_view);
2991 median_indexes=GetCacheViewAuthenticIndexQueue(median_view);
cristybb503372010-05-27 20:51:26 +00002992 for (x=0; x < (ssize_t) median_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002993 {
2994 MagickPixelPacket
2995 pixel;
2996
cristy3ed852e2009-09-05 21:47:34 +00002997 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002998 *restrict s;
cristy3ed852e2009-09-05 21:47:34 +00002999
cristy117ff172010-08-15 21:35:32 +00003000 register const PixelPacket
3001 *restrict r;
3002
cristybb503372010-05-27 20:51:26 +00003003 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003004 u,
3005 v;
3006
3007 r=p;
3008 s=indexes+x;
3009 ResetMedianPixelList(pixel_list[id]);
cristybb503372010-05-27 20:51:26 +00003010 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003011 {
cristybb503372010-05-27 20:51:26 +00003012 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003013 InsertMedianPixelList(image,r+u,s+u,pixel_list[id]);
3014 r+=image->columns+width;
3015 s+=image->columns+width;
3016 }
3017 pixel=GetMedianPixelList(pixel_list[id]);
3018 SetPixelPacket(median_image,&pixel,q,median_indexes+x);
3019 p++;
3020 q++;
3021 }
3022 if (SyncCacheViewAuthenticPixels(median_view,exception) == MagickFalse)
3023 status=MagickFalse;
3024 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3025 {
3026 MagickBooleanType
3027 proceed;
3028
cristyb5d5f722009-11-04 03:03:49 +00003029#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003030 #pragma omp critical (MagickCore_MedianFilterImage)
3031#endif
3032 proceed=SetImageProgress(image,MedianFilterImageTag,progress++,
3033 image->rows);
3034 if (proceed == MagickFalse)
3035 status=MagickFalse;
3036 }
3037 }
3038 median_view=DestroyCacheView(median_view);
3039 image_view=DestroyCacheView(image_view);
3040 pixel_list=DestroyMedianPixelListThreadSet(pixel_list);
3041 return(median_image);
3042}
3043
3044/*
3045%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3046% %
3047% %
3048% %
3049% M o t i o n B l u r I m a g e %
3050% %
3051% %
3052% %
3053%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3054%
3055% MotionBlurImage() simulates motion blur. We convolve the image with a
3056% Gaussian operator of the given radius and standard deviation (sigma).
3057% For reasonable results, radius should be larger than sigma. Use a
3058% radius of 0 and MotionBlurImage() selects a suitable radius for you.
3059% Angle gives the angle of the blurring motion.
3060%
3061% Andrew Protano contributed this effect.
3062%
3063% The format of the MotionBlurImage method is:
3064%
3065% Image *MotionBlurImage(const Image *image,const double radius,
3066% const double sigma,const double angle,ExceptionInfo *exception)
3067% Image *MotionBlurImageChannel(const Image *image,const ChannelType channel,
3068% const double radius,const double sigma,const double angle,
3069% ExceptionInfo *exception)
3070%
3071% A description of each parameter follows:
3072%
3073% o image: the image.
3074%
3075% o channel: the channel type.
3076%
3077% o radius: the radius of the Gaussian, in pixels, not counting the center
3078% o radius: the radius of the Gaussian, in pixels, not counting
3079% the center pixel.
3080%
3081% o sigma: the standard deviation of the Gaussian, in pixels.
3082%
cristycee97112010-05-28 00:44:52 +00003083% o angle: Apply the effect along this angle.
cristy3ed852e2009-09-05 21:47:34 +00003084%
3085% o exception: return any errors or warnings in this structure.
3086%
3087*/
3088
cristybb503372010-05-27 20:51:26 +00003089static double *GetMotionBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +00003090{
cristy3ed852e2009-09-05 21:47:34 +00003091 double
cristy47e00502009-12-17 19:19:57 +00003092 *kernel,
cristy3ed852e2009-09-05 21:47:34 +00003093 normalize;
3094
cristybb503372010-05-27 20:51:26 +00003095 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003096 i;
3097
3098 /*
cristy47e00502009-12-17 19:19:57 +00003099 Generate a 1-D convolution kernel.
cristy3ed852e2009-09-05 21:47:34 +00003100 */
3101 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
3102 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
3103 if (kernel == (double *) NULL)
3104 return(kernel);
cristy3ed852e2009-09-05 21:47:34 +00003105 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00003106 for (i=0; i < (ssize_t) width; i++)
cristy47e00502009-12-17 19:19:57 +00003107 {
cristy4205a3c2010-09-12 20:19:59 +00003108 kernel[i]=(double) (exp((-((double) i*i)/(double) (2.0*MagickSigma*
3109 MagickSigma)))/(MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00003110 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +00003111 }
cristybb503372010-05-27 20:51:26 +00003112 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00003113 kernel[i]/=normalize;
3114 return(kernel);
3115}
3116
3117MagickExport Image *MotionBlurImage(const Image *image,const double radius,
3118 const double sigma,const double angle,ExceptionInfo *exception)
3119{
3120 Image
3121 *motion_blur;
3122
3123 motion_blur=MotionBlurImageChannel(image,DefaultChannels,radius,sigma,angle,
3124 exception);
3125 return(motion_blur);
3126}
3127
3128MagickExport Image *MotionBlurImageChannel(const Image *image,
3129 const ChannelType channel,const double radius,const double sigma,
3130 const double angle,ExceptionInfo *exception)
3131{
cristyc4c8d132010-01-07 01:58:38 +00003132 CacheView
3133 *blur_view,
3134 *image_view;
3135
cristy3ed852e2009-09-05 21:47:34 +00003136 double
3137 *kernel;
3138
3139 Image
3140 *blur_image;
3141
cristy3ed852e2009-09-05 21:47:34 +00003142 MagickBooleanType
3143 status;
3144
cristybb503372010-05-27 20:51:26 +00003145 MagickOffsetType
3146 progress;
3147
cristy3ed852e2009-09-05 21:47:34 +00003148 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00003149 bias;
cristy3ed852e2009-09-05 21:47:34 +00003150
3151 OffsetInfo
3152 *offset;
3153
3154 PointInfo
3155 point;
3156
cristybb503372010-05-27 20:51:26 +00003157 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003158 i;
3159
cristybb503372010-05-27 20:51:26 +00003160 size_t
cristy3ed852e2009-09-05 21:47:34 +00003161 width;
3162
cristybb503372010-05-27 20:51:26 +00003163 ssize_t
3164 y;
3165
cristy3ed852e2009-09-05 21:47:34 +00003166 assert(image != (Image *) NULL);
3167 assert(image->signature == MagickSignature);
3168 if (image->debug != MagickFalse)
3169 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3170 assert(exception != (ExceptionInfo *) NULL);
3171 width=GetOptimalKernelWidth1D(radius,sigma);
3172 kernel=GetMotionBlurKernel(width,sigma);
3173 if (kernel == (double *) NULL)
3174 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3175 offset=(OffsetInfo *) AcquireQuantumMemory(width,sizeof(*offset));
3176 if (offset == (OffsetInfo *) NULL)
3177 {
3178 kernel=(double *) RelinquishMagickMemory(kernel);
3179 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3180 }
3181 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3182 if (blur_image == (Image *) NULL)
3183 {
3184 kernel=(double *) RelinquishMagickMemory(kernel);
3185 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
3186 return((Image *) NULL);
3187 }
3188 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3189 {
3190 kernel=(double *) RelinquishMagickMemory(kernel);
3191 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
3192 InheritException(exception,&blur_image->exception);
3193 blur_image=DestroyImage(blur_image);
3194 return((Image *) NULL);
3195 }
3196 point.x=(double) width*sin(DegreesToRadians(angle));
3197 point.y=(double) width*cos(DegreesToRadians(angle));
cristybb503372010-05-27 20:51:26 +00003198 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00003199 {
cristybb503372010-05-27 20:51:26 +00003200 offset[i].x=(ssize_t) ceil((double) (i*point.y)/hypot(point.x,point.y)-0.5);
3201 offset[i].y=(ssize_t) ceil((double) (i*point.x)/hypot(point.x,point.y)-0.5);
cristy3ed852e2009-09-05 21:47:34 +00003202 }
3203 /*
3204 Motion blur image.
3205 */
3206 status=MagickTrue;
3207 progress=0;
cristyddd82202009-11-03 20:14:50 +00003208 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003209 image_view=AcquireCacheView(image);
3210 blur_view=AcquireCacheView(blur_image);
cristy59e0d3b2010-06-07 13:12:38 +00003211#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00003212 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00003213#endif
cristybb503372010-05-27 20:51:26 +00003214 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003215 {
3216 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003217 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00003218
cristy3ed852e2009-09-05 21:47:34 +00003219 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003220 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003221
cristy117ff172010-08-15 21:35:32 +00003222 register ssize_t
3223 x;
3224
cristy3ed852e2009-09-05 21:47:34 +00003225 if (status == MagickFalse)
3226 continue;
3227 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3228 exception);
3229 if (q == (PixelPacket *) NULL)
3230 {
3231 status=MagickFalse;
3232 continue;
3233 }
3234 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
cristybb503372010-05-27 20:51:26 +00003235 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003236 {
3237 MagickPixelPacket
3238 qixel;
3239
3240 PixelPacket
3241 pixel;
3242
cristy117ff172010-08-15 21:35:32 +00003243 register const IndexPacket
3244 *restrict indexes;
3245
cristy3ed852e2009-09-05 21:47:34 +00003246 register double
cristyc47d1f82009-11-26 01:44:43 +00003247 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00003248
cristybb503372010-05-27 20:51:26 +00003249 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003250 i;
3251
cristy3ed852e2009-09-05 21:47:34 +00003252 k=kernel;
cristyddd82202009-11-03 20:14:50 +00003253 qixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00003254 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
3255 {
cristybb503372010-05-27 20:51:26 +00003256 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00003257 {
3258 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
3259 offset[i].y,&pixel,exception);
3260 qixel.red+=(*k)*pixel.red;
3261 qixel.green+=(*k)*pixel.green;
3262 qixel.blue+=(*k)*pixel.blue;
3263 qixel.opacity+=(*k)*pixel.opacity;
3264 if (image->colorspace == CMYKColorspace)
3265 {
3266 indexes=GetCacheViewVirtualIndexQueue(image_view);
3267 qixel.index+=(*k)*(*indexes);
3268 }
3269 k++;
3270 }
3271 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003272 q->red=ClampToQuantum(qixel.red);
cristy3ed852e2009-09-05 21:47:34 +00003273 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003274 q->green=ClampToQuantum(qixel.green);
cristy3ed852e2009-09-05 21:47:34 +00003275 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003276 q->blue=ClampToQuantum(qixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00003277 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003278 q->opacity=ClampToQuantum(qixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00003279 if (((channel & IndexChannel) != 0) &&
3280 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00003281 blur_indexes[x]=(IndexPacket) ClampToQuantum(qixel.index);
cristy3ed852e2009-09-05 21:47:34 +00003282 }
3283 else
3284 {
3285 MagickRealType
3286 alpha,
3287 gamma;
3288
3289 alpha=0.0;
3290 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00003291 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00003292 {
3293 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
3294 offset[i].y,&pixel,exception);
cristy8a7ea362010-03-10 20:31:43 +00003295 alpha=(MagickRealType) (QuantumScale*
3296 GetAlphaPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00003297 qixel.red+=(*k)*alpha*pixel.red;
3298 qixel.green+=(*k)*alpha*pixel.green;
3299 qixel.blue+=(*k)*alpha*pixel.blue;
3300 qixel.opacity+=(*k)*pixel.opacity;
3301 if (image->colorspace == CMYKColorspace)
3302 {
3303 indexes=GetCacheViewVirtualIndexQueue(image_view);
3304 qixel.index+=(*k)*alpha*(*indexes);
3305 }
3306 gamma+=(*k)*alpha;
3307 k++;
3308 }
3309 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3310 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003311 q->red=ClampToQuantum(gamma*qixel.red);
cristy3ed852e2009-09-05 21:47:34 +00003312 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003313 q->green=ClampToQuantum(gamma*qixel.green);
cristy3ed852e2009-09-05 21:47:34 +00003314 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003315 q->blue=ClampToQuantum(gamma*qixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00003316 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003317 q->opacity=ClampToQuantum(qixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00003318 if (((channel & IndexChannel) != 0) &&
3319 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00003320 blur_indexes[x]=(IndexPacket) ClampToQuantum(gamma*qixel.index);
cristy3ed852e2009-09-05 21:47:34 +00003321 }
3322 q++;
3323 }
3324 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
3325 status=MagickFalse;
3326 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3327 {
3328 MagickBooleanType
3329 proceed;
3330
cristy59e0d3b2010-06-07 13:12:38 +00003331#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003332 #pragma omp critical (MagickCore_MotionBlurImageChannel)
3333#endif
3334 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
3335 if (proceed == MagickFalse)
3336 status=MagickFalse;
3337 }
3338 }
3339 blur_view=DestroyCacheView(blur_view);
3340 image_view=DestroyCacheView(image_view);
3341 kernel=(double *) RelinquishMagickMemory(kernel);
3342 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
3343 if (status == MagickFalse)
3344 blur_image=DestroyImage(blur_image);
3345 return(blur_image);
3346}
3347
3348/*
3349%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3350% %
3351% %
3352% %
3353% P r e v i e w I m a g e %
3354% %
3355% %
3356% %
3357%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3358%
3359% PreviewImage() tiles 9 thumbnails of the specified image with an image
3360% processing operation applied with varying parameters. This may be helpful
3361% pin-pointing an appropriate parameter for a particular image processing
3362% operation.
3363%
3364% The format of the PreviewImages method is:
3365%
3366% Image *PreviewImages(const Image *image,const PreviewType preview,
3367% ExceptionInfo *exception)
3368%
3369% A description of each parameter follows:
3370%
3371% o image: the image.
3372%
3373% o preview: the image processing operation.
3374%
3375% o exception: return any errors or warnings in this structure.
3376%
3377*/
3378MagickExport Image *PreviewImage(const Image *image,const PreviewType preview,
3379 ExceptionInfo *exception)
3380{
3381#define NumberTiles 9
3382#define PreviewImageTag "Preview/Image"
3383#define DefaultPreviewGeometry "204x204+10+10"
3384
3385 char
3386 factor[MaxTextExtent],
3387 label[MaxTextExtent];
3388
3389 double
3390 degrees,
3391 gamma,
3392 percentage,
3393 radius,
3394 sigma,
3395 threshold;
3396
3397 Image
3398 *images,
3399 *montage_image,
3400 *preview_image,
3401 *thumbnail;
3402
3403 ImageInfo
3404 *preview_info;
3405
cristy3ed852e2009-09-05 21:47:34 +00003406 MagickBooleanType
3407 proceed;
3408
3409 MontageInfo
3410 *montage_info;
3411
3412 QuantizeInfo
3413 quantize_info;
3414
3415 RectangleInfo
3416 geometry;
3417
cristybb503372010-05-27 20:51:26 +00003418 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003419 i,
3420 x;
3421
cristybb503372010-05-27 20:51:26 +00003422 size_t
cristy3ed852e2009-09-05 21:47:34 +00003423 colors;
3424
cristy117ff172010-08-15 21:35:32 +00003425 ssize_t
3426 y;
3427
cristy3ed852e2009-09-05 21:47:34 +00003428 /*
3429 Open output image file.
3430 */
3431 assert(image != (Image *) NULL);
3432 assert(image->signature == MagickSignature);
3433 if (image->debug != MagickFalse)
3434 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3435 colors=2;
3436 degrees=0.0;
3437 gamma=(-0.2f);
3438 preview_info=AcquireImageInfo();
3439 SetGeometry(image,&geometry);
3440 (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y,
3441 &geometry.width,&geometry.height);
3442 images=NewImageList();
3443 percentage=12.5;
3444 GetQuantizeInfo(&quantize_info);
3445 radius=0.0;
3446 sigma=1.0;
3447 threshold=0.0;
3448 x=0;
3449 y=0;
3450 for (i=0; i < NumberTiles; i++)
3451 {
3452 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
3453 if (thumbnail == (Image *) NULL)
3454 break;
3455 (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL,
3456 (void *) NULL);
3457 (void) SetImageProperty(thumbnail,"label",DefaultTileLabel);
3458 if (i == (NumberTiles/2))
3459 {
3460 (void) QueryColorDatabase("#dfdfdf",&thumbnail->matte_color,exception);
3461 AppendImageToList(&images,thumbnail);
3462 continue;
3463 }
3464 switch (preview)
3465 {
3466 case RotatePreview:
3467 {
3468 degrees+=45.0;
3469 preview_image=RotateImage(thumbnail,degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003470 (void) FormatMagickString(label,MaxTextExtent,"rotate %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003471 break;
3472 }
3473 case ShearPreview:
3474 {
3475 degrees+=5.0;
3476 preview_image=ShearImage(thumbnail,degrees,degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003477 (void) FormatMagickString(label,MaxTextExtent,"shear %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00003478 degrees,2.0*degrees);
3479 break;
3480 }
3481 case RollPreview:
3482 {
cristybb503372010-05-27 20:51:26 +00003483 x=(ssize_t) ((i+1)*thumbnail->columns)/NumberTiles;
3484 y=(ssize_t) ((i+1)*thumbnail->rows)/NumberTiles;
cristy3ed852e2009-09-05 21:47:34 +00003485 preview_image=RollImage(thumbnail,x,y,exception);
cristye8c25f92010-06-03 00:53:06 +00003486 (void) FormatMagickString(label,MaxTextExtent,"roll %+.20gx%+.20g",
3487 (double) x,(double) y);
cristy3ed852e2009-09-05 21:47:34 +00003488 break;
3489 }
3490 case HuePreview:
3491 {
3492 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3493 if (preview_image == (Image *) NULL)
3494 break;
cristye7f51092010-01-17 00:39:37 +00003495 (void) FormatMagickString(factor,MaxTextExtent,"100,100,%g",
cristy3ed852e2009-09-05 21:47:34 +00003496 2.0*percentage);
3497 (void) ModulateImage(preview_image,factor);
3498 (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor);
3499 break;
3500 }
3501 case SaturationPreview:
3502 {
3503 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3504 if (preview_image == (Image *) NULL)
3505 break;
cristye7f51092010-01-17 00:39:37 +00003506 (void) FormatMagickString(factor,MaxTextExtent,"100,%g",
cristy8cd5b312010-01-07 01:10:24 +00003507 2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00003508 (void) ModulateImage(preview_image,factor);
3509 (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor);
3510 break;
3511 }
3512 case BrightnessPreview:
3513 {
3514 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3515 if (preview_image == (Image *) NULL)
3516 break;
cristye7f51092010-01-17 00:39:37 +00003517 (void) FormatMagickString(factor,MaxTextExtent,"%g",2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00003518 (void) ModulateImage(preview_image,factor);
3519 (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor);
3520 break;
3521 }
3522 case GammaPreview:
3523 default:
3524 {
3525 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3526 if (preview_image == (Image *) NULL)
3527 break;
3528 gamma+=0.4f;
3529 (void) GammaImageChannel(preview_image,DefaultChannels,gamma);
cristye7f51092010-01-17 00:39:37 +00003530 (void) FormatMagickString(label,MaxTextExtent,"gamma %g",gamma);
cristy3ed852e2009-09-05 21:47:34 +00003531 break;
3532 }
3533 case SpiffPreview:
3534 {
3535 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3536 if (preview_image != (Image *) NULL)
3537 for (x=0; x < i; x++)
3538 (void) ContrastImage(preview_image,MagickTrue);
cristye8c25f92010-06-03 00:53:06 +00003539 (void) FormatMagickString(label,MaxTextExtent,"contrast (%.20g)",
3540 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00003541 break;
3542 }
3543 case DullPreview:
3544 {
3545 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3546 if (preview_image == (Image *) NULL)
3547 break;
3548 for (x=0; x < i; x++)
3549 (void) ContrastImage(preview_image,MagickFalse);
cristye8c25f92010-06-03 00:53:06 +00003550 (void) FormatMagickString(label,MaxTextExtent,"+contrast (%.20g)",
3551 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00003552 break;
3553 }
3554 case GrayscalePreview:
3555 {
3556 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3557 if (preview_image == (Image *) NULL)
3558 break;
3559 colors<<=1;
3560 quantize_info.number_colors=colors;
3561 quantize_info.colorspace=GRAYColorspace;
3562 (void) QuantizeImage(&quantize_info,preview_image);
3563 (void) FormatMagickString(label,MaxTextExtent,
cristye8c25f92010-06-03 00:53:06 +00003564 "-colorspace gray -colors %.20g",(double) colors);
cristy3ed852e2009-09-05 21:47:34 +00003565 break;
3566 }
3567 case QuantizePreview:
3568 {
3569 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3570 if (preview_image == (Image *) NULL)
3571 break;
3572 colors<<=1;
3573 quantize_info.number_colors=colors;
3574 (void) QuantizeImage(&quantize_info,preview_image);
cristye8c25f92010-06-03 00:53:06 +00003575 (void) FormatMagickString(label,MaxTextExtent,"colors %.20g",(double)
3576 colors);
cristy3ed852e2009-09-05 21:47:34 +00003577 break;
3578 }
3579 case DespecklePreview:
3580 {
3581 for (x=0; x < (i-1); x++)
3582 {
3583 preview_image=DespeckleImage(thumbnail,exception);
3584 if (preview_image == (Image *) NULL)
3585 break;
3586 thumbnail=DestroyImage(thumbnail);
3587 thumbnail=preview_image;
3588 }
3589 preview_image=DespeckleImage(thumbnail,exception);
3590 if (preview_image == (Image *) NULL)
3591 break;
cristye8c25f92010-06-03 00:53:06 +00003592 (void) FormatMagickString(label,MaxTextExtent,"despeckle (%.20g)",
3593 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00003594 break;
3595 }
3596 case ReduceNoisePreview:
3597 {
3598 preview_image=ReduceNoiseImage(thumbnail,radius,exception);
cristye7f51092010-01-17 00:39:37 +00003599 (void) FormatMagickString(label,MaxTextExtent,"noise %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003600 break;
3601 }
3602 case AddNoisePreview:
3603 {
3604 switch ((int) i)
3605 {
3606 case 0:
3607 {
3608 (void) CopyMagickString(factor,"uniform",MaxTextExtent);
3609 break;
3610 }
3611 case 1:
3612 {
3613 (void) CopyMagickString(factor,"gaussian",MaxTextExtent);
3614 break;
3615 }
3616 case 2:
3617 {
3618 (void) CopyMagickString(factor,"multiplicative",MaxTextExtent);
3619 break;
3620 }
3621 case 3:
3622 {
3623 (void) CopyMagickString(factor,"impulse",MaxTextExtent);
3624 break;
3625 }
3626 case 4:
3627 {
3628 (void) CopyMagickString(factor,"laplacian",MaxTextExtent);
3629 break;
3630 }
3631 case 5:
3632 {
3633 (void) CopyMagickString(factor,"Poisson",MaxTextExtent);
3634 break;
3635 }
3636 default:
3637 {
3638 (void) CopyMagickString(thumbnail->magick,"NULL",MaxTextExtent);
3639 break;
3640 }
3641 }
3642 preview_image=ReduceNoiseImage(thumbnail,(double) i,exception);
3643 (void) FormatMagickString(label,MaxTextExtent,"+noise %s",factor);
3644 break;
3645 }
3646 case SharpenPreview:
3647 {
3648 preview_image=SharpenImage(thumbnail,radius,sigma,exception);
cristye7f51092010-01-17 00:39:37 +00003649 (void) FormatMagickString(label,MaxTextExtent,"sharpen %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003650 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00003651 break;
3652 }
3653 case BlurPreview:
3654 {
3655 preview_image=BlurImage(thumbnail,radius,sigma,exception);
cristye7f51092010-01-17 00:39:37 +00003656 (void) FormatMagickString(label,MaxTextExtent,"blur %gx%g",radius,
cristy3ed852e2009-09-05 21:47:34 +00003657 sigma);
3658 break;
3659 }
3660 case ThresholdPreview:
3661 {
3662 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3663 if (preview_image == (Image *) NULL)
3664 break;
3665 (void) BilevelImage(thumbnail,
3666 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
cristye7f51092010-01-17 00:39:37 +00003667 (void) FormatMagickString(label,MaxTextExtent,"threshold %g",
cristy3ed852e2009-09-05 21:47:34 +00003668 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
3669 break;
3670 }
3671 case EdgeDetectPreview:
3672 {
3673 preview_image=EdgeImage(thumbnail,radius,exception);
cristye7f51092010-01-17 00:39:37 +00003674 (void) FormatMagickString(label,MaxTextExtent,"edge %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003675 break;
3676 }
3677 case SpreadPreview:
3678 {
3679 preview_image=SpreadImage(thumbnail,radius,exception);
cristye7f51092010-01-17 00:39:37 +00003680 (void) FormatMagickString(label,MaxTextExtent,"spread %g",
cristy8cd5b312010-01-07 01:10:24 +00003681 radius+0.5);
cristy3ed852e2009-09-05 21:47:34 +00003682 break;
3683 }
3684 case SolarizePreview:
3685 {
3686 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3687 if (preview_image == (Image *) NULL)
3688 break;
3689 (void) SolarizeImage(preview_image,(double) QuantumRange*
3690 percentage/100.0);
cristye7f51092010-01-17 00:39:37 +00003691 (void) FormatMagickString(label,MaxTextExtent,"solarize %g",
cristy3ed852e2009-09-05 21:47:34 +00003692 (QuantumRange*percentage)/100.0);
3693 break;
3694 }
3695 case ShadePreview:
3696 {
3697 degrees+=10.0;
3698 preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees,
3699 exception);
cristye7f51092010-01-17 00:39:37 +00003700 (void) FormatMagickString(label,MaxTextExtent,"shade %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003701 degrees,degrees);
cristy3ed852e2009-09-05 21:47:34 +00003702 break;
3703 }
3704 case RaisePreview:
3705 {
3706 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3707 if (preview_image == (Image *) NULL)
3708 break;
cristybb503372010-05-27 20:51:26 +00003709 geometry.width=(size_t) (2*i+2);
3710 geometry.height=(size_t) (2*i+2);
cristy3ed852e2009-09-05 21:47:34 +00003711 geometry.x=i/2;
3712 geometry.y=i/2;
3713 (void) RaiseImage(preview_image,&geometry,MagickTrue);
cristye8c25f92010-06-03 00:53:06 +00003714 (void) FormatMagickString(label,MaxTextExtent,
cristy6d8abba2010-06-03 01:10:47 +00003715 "raise %.20gx%.20g%+.20g%+.20g",(double) geometry.width,(double)
cristye8c25f92010-06-03 00:53:06 +00003716 geometry.height,(double) geometry.x,(double) geometry.y);
cristy3ed852e2009-09-05 21:47:34 +00003717 break;
3718 }
3719 case SegmentPreview:
3720 {
3721 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3722 if (preview_image == (Image *) NULL)
3723 break;
3724 threshold+=0.4f;
3725 (void) SegmentImage(preview_image,RGBColorspace,MagickFalse,threshold,
3726 threshold);
cristye7f51092010-01-17 00:39:37 +00003727 (void) FormatMagickString(label,MaxTextExtent,"segment %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00003728 threshold,threshold);
3729 break;
3730 }
3731 case SwirlPreview:
3732 {
3733 preview_image=SwirlImage(thumbnail,degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003734 (void) FormatMagickString(label,MaxTextExtent,"swirl %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003735 degrees+=45.0;
3736 break;
3737 }
3738 case ImplodePreview:
3739 {
3740 degrees+=0.1f;
3741 preview_image=ImplodeImage(thumbnail,degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003742 (void) FormatMagickString(label,MaxTextExtent,"implode %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003743 break;
3744 }
3745 case WavePreview:
3746 {
3747 degrees+=5.0f;
3748 preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003749 (void) FormatMagickString(label,MaxTextExtent,"wave %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003750 0.5*degrees,2.0*degrees);
cristy3ed852e2009-09-05 21:47:34 +00003751 break;
3752 }
3753 case OilPaintPreview:
3754 {
3755 preview_image=OilPaintImage(thumbnail,(double) radius,exception);
cristye7f51092010-01-17 00:39:37 +00003756 (void) FormatMagickString(label,MaxTextExtent,"paint %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003757 break;
3758 }
3759 case CharcoalDrawingPreview:
3760 {
3761 preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma,
3762 exception);
cristye7f51092010-01-17 00:39:37 +00003763 (void) FormatMagickString(label,MaxTextExtent,"charcoal %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003764 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00003765 break;
3766 }
3767 case JPEGPreview:
3768 {
3769 char
3770 filename[MaxTextExtent];
3771
3772 int
3773 file;
3774
3775 MagickBooleanType
3776 status;
3777
3778 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3779 if (preview_image == (Image *) NULL)
3780 break;
cristybb503372010-05-27 20:51:26 +00003781 preview_info->quality=(size_t) percentage;
cristye8c25f92010-06-03 00:53:06 +00003782 (void) FormatMagickString(factor,MaxTextExtent,"%.20g",(double)
3783 preview_info->quality);
cristy3ed852e2009-09-05 21:47:34 +00003784 file=AcquireUniqueFileResource(filename);
3785 if (file != -1)
3786 file=close(file)-1;
3787 (void) FormatMagickString(preview_image->filename,MaxTextExtent,
3788 "jpeg:%s",filename);
3789 status=WriteImage(preview_info,preview_image);
3790 if (status != MagickFalse)
3791 {
3792 Image
3793 *quality_image;
3794
3795 (void) CopyMagickString(preview_info->filename,
3796 preview_image->filename,MaxTextExtent);
3797 quality_image=ReadImage(preview_info,exception);
3798 if (quality_image != (Image *) NULL)
3799 {
3800 preview_image=DestroyImage(preview_image);
3801 preview_image=quality_image;
3802 }
3803 }
3804 (void) RelinquishUniqueFileResource(preview_image->filename);
3805 if ((GetBlobSize(preview_image)/1024) >= 1024)
cristye7f51092010-01-17 00:39:37 +00003806 (void) FormatMagickString(label,MaxTextExtent,"quality %s\n%gmb ",
cristy3ed852e2009-09-05 21:47:34 +00003807 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
3808 1024.0/1024.0);
3809 else
3810 if (GetBlobSize(preview_image) >= 1024)
cristy8cd5b312010-01-07 01:10:24 +00003811 (void) FormatMagickString(label,MaxTextExtent,
cristye7f51092010-01-17 00:39:37 +00003812 "quality %s\n%gkb ",factor,(double) ((MagickOffsetType)
cristy8cd5b312010-01-07 01:10:24 +00003813 GetBlobSize(preview_image))/1024.0);
cristy3ed852e2009-09-05 21:47:34 +00003814 else
cristye8c25f92010-06-03 00:53:06 +00003815 (void) FormatMagickString(label,MaxTextExtent,"quality %s\n%.20gb ",
3816 factor,(double) GetBlobSize(thumbnail));
cristy3ed852e2009-09-05 21:47:34 +00003817 break;
3818 }
3819 }
3820 thumbnail=DestroyImage(thumbnail);
3821 percentage+=12.5;
3822 radius+=0.5;
3823 sigma+=0.25;
3824 if (preview_image == (Image *) NULL)
3825 break;
3826 (void) DeleteImageProperty(preview_image,"label");
3827 (void) SetImageProperty(preview_image,"label",label);
3828 AppendImageToList(&images,preview_image);
cristybb503372010-05-27 20:51:26 +00003829 proceed=SetImageProgress(image,PreviewImageTag,(MagickOffsetType) i,
3830 NumberTiles);
cristy3ed852e2009-09-05 21:47:34 +00003831 if (proceed == MagickFalse)
3832 break;
3833 }
3834 if (images == (Image *) NULL)
3835 {
3836 preview_info=DestroyImageInfo(preview_info);
3837 return((Image *) NULL);
3838 }
3839 /*
3840 Create the montage.
3841 */
3842 montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL);
3843 (void) CopyMagickString(montage_info->filename,image->filename,MaxTextExtent);
3844 montage_info->shadow=MagickTrue;
3845 (void) CloneString(&montage_info->tile,"3x3");
3846 (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry);
3847 (void) CloneString(&montage_info->frame,DefaultTileFrame);
3848 montage_image=MontageImages(images,montage_info,exception);
3849 montage_info=DestroyMontageInfo(montage_info);
3850 images=DestroyImageList(images);
3851 if (montage_image == (Image *) NULL)
3852 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3853 if (montage_image->montage != (char *) NULL)
3854 {
3855 /*
3856 Free image directory.
3857 */
3858 montage_image->montage=(char *) RelinquishMagickMemory(
3859 montage_image->montage);
3860 if (image->directory != (char *) NULL)
3861 montage_image->directory=(char *) RelinquishMagickMemory(
3862 montage_image->directory);
3863 }
3864 preview_info=DestroyImageInfo(preview_info);
3865 return(montage_image);
3866}
3867
3868/*
3869%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3870% %
3871% %
3872% %
3873% R a d i a l B l u r I m a g e %
3874% %
3875% %
3876% %
3877%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3878%
3879% RadialBlurImage() applies a radial blur to the image.
3880%
3881% Andrew Protano contributed this effect.
3882%
3883% The format of the RadialBlurImage method is:
3884%
3885% Image *RadialBlurImage(const Image *image,const double angle,
3886% ExceptionInfo *exception)
3887% Image *RadialBlurImageChannel(const Image *image,const ChannelType channel,
3888% const double angle,ExceptionInfo *exception)
3889%
3890% A description of each parameter follows:
3891%
3892% o image: the image.
3893%
3894% o channel: the channel type.
3895%
3896% o angle: the angle of the radial blur.
3897%
3898% o exception: return any errors or warnings in this structure.
3899%
3900*/
3901
3902MagickExport Image *RadialBlurImage(const Image *image,const double angle,
3903 ExceptionInfo *exception)
3904{
3905 Image
3906 *blur_image;
3907
3908 blur_image=RadialBlurImageChannel(image,DefaultChannels,angle,exception);
3909 return(blur_image);
3910}
3911
3912MagickExport Image *RadialBlurImageChannel(const Image *image,
3913 const ChannelType channel,const double angle,ExceptionInfo *exception)
3914{
cristyc4c8d132010-01-07 01:58:38 +00003915 CacheView
3916 *blur_view,
3917 *image_view;
3918
cristy3ed852e2009-09-05 21:47:34 +00003919 Image
3920 *blur_image;
3921
cristy3ed852e2009-09-05 21:47:34 +00003922 MagickBooleanType
3923 status;
3924
cristybb503372010-05-27 20:51:26 +00003925 MagickOffsetType
3926 progress;
3927
cristy3ed852e2009-09-05 21:47:34 +00003928 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00003929 bias;
cristy3ed852e2009-09-05 21:47:34 +00003930
3931 MagickRealType
3932 blur_radius,
3933 *cos_theta,
3934 offset,
3935 *sin_theta,
3936 theta;
3937
3938 PointInfo
3939 blur_center;
3940
cristybb503372010-05-27 20:51:26 +00003941 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003942 i;
3943
cristybb503372010-05-27 20:51:26 +00003944 size_t
cristy3ed852e2009-09-05 21:47:34 +00003945 n;
3946
cristybb503372010-05-27 20:51:26 +00003947 ssize_t
3948 y;
3949
cristy3ed852e2009-09-05 21:47:34 +00003950 /*
3951 Allocate blur image.
3952 */
3953 assert(image != (Image *) NULL);
3954 assert(image->signature == MagickSignature);
3955 if (image->debug != MagickFalse)
3956 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3957 assert(exception != (ExceptionInfo *) NULL);
3958 assert(exception->signature == MagickSignature);
3959 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3960 if (blur_image == (Image *) NULL)
3961 return((Image *) NULL);
3962 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3963 {
3964 InheritException(exception,&blur_image->exception);
3965 blur_image=DestroyImage(blur_image);
3966 return((Image *) NULL);
3967 }
3968 blur_center.x=(double) image->columns/2.0;
3969 blur_center.y=(double) image->rows/2.0;
3970 blur_radius=hypot(blur_center.x,blur_center.y);
cristy117ff172010-08-15 21:35:32 +00003971 n=(size_t) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+2UL);
cristy3ed852e2009-09-05 21:47:34 +00003972 theta=DegreesToRadians(angle)/(MagickRealType) (n-1);
3973 cos_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3974 sizeof(*cos_theta));
3975 sin_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3976 sizeof(*sin_theta));
3977 if ((cos_theta == (MagickRealType *) NULL) ||
3978 (sin_theta == (MagickRealType *) NULL))
3979 {
3980 blur_image=DestroyImage(blur_image);
3981 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3982 }
3983 offset=theta*(MagickRealType) (n-1)/2.0;
cristybb503372010-05-27 20:51:26 +00003984 for (i=0; i < (ssize_t) n; i++)
cristy3ed852e2009-09-05 21:47:34 +00003985 {
3986 cos_theta[i]=cos((double) (theta*i-offset));
3987 sin_theta[i]=sin((double) (theta*i-offset));
3988 }
3989 /*
3990 Radial blur image.
3991 */
3992 status=MagickTrue;
3993 progress=0;
cristyddd82202009-11-03 20:14:50 +00003994 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003995 image_view=AcquireCacheView(image);
3996 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003997#if defined(MAGICKCORE_OPENMP_SUPPORT)
3998 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003999#endif
cristybb503372010-05-27 20:51:26 +00004000 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00004001 {
4002 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004003 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00004004
4005 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004006 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00004007
cristy3ed852e2009-09-05 21:47:34 +00004008 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004009 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004010
cristy117ff172010-08-15 21:35:32 +00004011 register ssize_t
4012 x;
4013
cristy3ed852e2009-09-05 21:47:34 +00004014 if (status == MagickFalse)
4015 continue;
4016 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
4017 exception);
4018 if (q == (PixelPacket *) NULL)
4019 {
4020 status=MagickFalse;
4021 continue;
4022 }
4023 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
cristybb503372010-05-27 20:51:26 +00004024 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004025 {
4026 MagickPixelPacket
4027 qixel;
4028
4029 MagickRealType
4030 normalize,
4031 radius;
4032
4033 PixelPacket
4034 pixel;
4035
4036 PointInfo
4037 center;
4038
cristybb503372010-05-27 20:51:26 +00004039 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00004040 i;
4041
cristybb503372010-05-27 20:51:26 +00004042 size_t
cristy3ed852e2009-09-05 21:47:34 +00004043 step;
4044
4045 center.x=(double) x-blur_center.x;
4046 center.y=(double) y-blur_center.y;
4047 radius=hypot((double) center.x,center.y);
4048 if (radius == 0)
4049 step=1;
4050 else
4051 {
cristybb503372010-05-27 20:51:26 +00004052 step=(size_t) (blur_radius/radius);
cristy3ed852e2009-09-05 21:47:34 +00004053 if (step == 0)
4054 step=1;
4055 else
4056 if (step >= n)
4057 step=n-1;
4058 }
4059 normalize=0.0;
cristyddd82202009-11-03 20:14:50 +00004060 qixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00004061 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
4062 {
cristyeaedf062010-05-29 22:36:02 +00004063 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00004064 {
cristyeaedf062010-05-29 22:36:02 +00004065 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
4066 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
4067 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
4068 cos_theta[i]+0.5),&pixel,exception);
cristy3ed852e2009-09-05 21:47:34 +00004069 qixel.red+=pixel.red;
4070 qixel.green+=pixel.green;
4071 qixel.blue+=pixel.blue;
4072 qixel.opacity+=pixel.opacity;
4073 if (image->colorspace == CMYKColorspace)
4074 {
4075 indexes=GetCacheViewVirtualIndexQueue(image_view);
4076 qixel.index+=(*indexes);
4077 }
4078 normalize+=1.0;
4079 }
4080 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
4081 normalize);
4082 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004083 q->red=ClampToQuantum(normalize*qixel.red);
cristy3ed852e2009-09-05 21:47:34 +00004084 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004085 q->green=ClampToQuantum(normalize*qixel.green);
cristy3ed852e2009-09-05 21:47:34 +00004086 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004087 q->blue=ClampToQuantum(normalize*qixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00004088 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004089 q->opacity=ClampToQuantum(normalize*qixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00004090 if (((channel & IndexChannel) != 0) &&
4091 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00004092 blur_indexes[x]=(IndexPacket) ClampToQuantum(normalize*qixel.index);
cristy3ed852e2009-09-05 21:47:34 +00004093 }
4094 else
4095 {
4096 MagickRealType
4097 alpha,
4098 gamma;
4099
4100 alpha=1.0;
4101 gamma=0.0;
cristyeaedf062010-05-29 22:36:02 +00004102 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00004103 {
cristyeaedf062010-05-29 22:36:02 +00004104 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
4105 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
4106 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
4107 cos_theta[i]+0.5),&pixel,exception);
cristy46f08202010-01-10 04:04:21 +00004108 alpha=(MagickRealType) (QuantumScale*
4109 GetAlphaPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004110 qixel.red+=alpha*pixel.red;
4111 qixel.green+=alpha*pixel.green;
4112 qixel.blue+=alpha*pixel.blue;
4113 qixel.opacity+=pixel.opacity;
4114 if (image->colorspace == CMYKColorspace)
4115 {
4116 indexes=GetCacheViewVirtualIndexQueue(image_view);
4117 qixel.index+=alpha*(*indexes);
4118 }
4119 gamma+=alpha;
4120 normalize+=1.0;
4121 }
4122 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
4123 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
4124 normalize);
4125 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004126 q->red=ClampToQuantum(gamma*qixel.red);
cristy3ed852e2009-09-05 21:47:34 +00004127 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004128 q->green=ClampToQuantum(gamma*qixel.green);
cristy3ed852e2009-09-05 21:47:34 +00004129 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004130 q->blue=ClampToQuantum(gamma*qixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00004131 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004132 q->opacity=ClampToQuantum(normalize*qixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00004133 if (((channel & IndexChannel) != 0) &&
4134 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00004135 blur_indexes[x]=(IndexPacket) ClampToQuantum(gamma*qixel.index);
cristy3ed852e2009-09-05 21:47:34 +00004136 }
4137 q++;
4138 }
4139 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
4140 status=MagickFalse;
4141 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4142 {
4143 MagickBooleanType
4144 proceed;
4145
cristyb5d5f722009-11-04 03:03:49 +00004146#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004147 #pragma omp critical (MagickCore_RadialBlurImageChannel)
4148#endif
4149 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
4150 if (proceed == MagickFalse)
4151 status=MagickFalse;
4152 }
4153 }
4154 blur_view=DestroyCacheView(blur_view);
4155 image_view=DestroyCacheView(image_view);
4156 cos_theta=(MagickRealType *) RelinquishMagickMemory(cos_theta);
4157 sin_theta=(MagickRealType *) RelinquishMagickMemory(sin_theta);
4158 if (status == MagickFalse)
4159 blur_image=DestroyImage(blur_image);
4160 return(blur_image);
4161}
4162
4163/*
4164%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4165% %
4166% %
4167% %
4168% R e d u c e N o i s e I m a g e %
4169% %
4170% %
4171% %
4172%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4173%
4174% ReduceNoiseImage() smooths the contours of an image while still preserving
4175% edge information. The algorithm works by replacing each pixel with its
4176% neighbor closest in value. A neighbor is defined by radius. Use a radius
4177% of 0 and ReduceNoise() selects a suitable radius for you.
4178%
4179% The format of the ReduceNoiseImage method is:
4180%
4181% Image *ReduceNoiseImage(const Image *image,const double radius,
4182% ExceptionInfo *exception)
4183%
4184% A description of each parameter follows:
4185%
4186% o image: the image.
4187%
4188% o radius: the radius of the pixel neighborhood.
4189%
4190% o exception: return any errors or warnings in this structure.
4191%
4192*/
4193
4194static MagickPixelPacket GetNonpeakMedianPixelList(MedianPixelList *pixel_list)
4195{
4196 MagickPixelPacket
4197 pixel;
4198
cristy3ed852e2009-09-05 21:47:34 +00004199 register MedianSkipList
4200 *list;
4201
cristy117ff172010-08-15 21:35:32 +00004202 register ssize_t
4203 channel;
4204
cristybb503372010-05-27 20:51:26 +00004205 size_t
cristy3ed852e2009-09-05 21:47:34 +00004206 center,
4207 color,
4208 count,
4209 previous,
4210 next;
4211
4212 unsigned short
4213 channels[5];
4214
4215 /*
4216 Finds the median value for each of the color.
4217 */
4218 center=pixel_list->center;
4219 for (channel=0; channel < 5; channel++)
4220 {
4221 list=pixel_list->lists+channel;
4222 color=65536UL;
4223 next=list->nodes[color].next[0];
4224 count=0;
4225 do
4226 {
4227 previous=color;
4228 color=next;
4229 next=list->nodes[color].next[0];
4230 count+=list->nodes[color].count;
4231 }
4232 while (count <= center);
4233 if ((previous == 65536UL) && (next != 65536UL))
4234 color=next;
4235 else
4236 if ((previous != 65536UL) && (next == 65536UL))
4237 color=previous;
4238 channels[channel]=(unsigned short) color;
4239 }
4240 GetMagickPixelPacket((const Image *) NULL,&pixel);
4241 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4242 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4243 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
4244 pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
4245 pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]);
4246 return(pixel);
4247}
4248
4249MagickExport Image *ReduceNoiseImage(const Image *image,const double radius,
4250 ExceptionInfo *exception)
4251{
4252#define ReduceNoiseImageTag "ReduceNoise/Image"
4253
cristyfa112112010-01-04 17:48:07 +00004254 CacheView
4255 *image_view,
4256 *noise_view;
4257
cristy3ed852e2009-09-05 21:47:34 +00004258 Image
4259 *noise_image;
4260
cristy3ed852e2009-09-05 21:47:34 +00004261 MagickBooleanType
4262 status;
4263
cristybb503372010-05-27 20:51:26 +00004264 MagickOffsetType
4265 progress;
4266
cristy3ed852e2009-09-05 21:47:34 +00004267 MedianPixelList
cristyfa112112010-01-04 17:48:07 +00004268 **restrict pixel_list;
cristy3ed852e2009-09-05 21:47:34 +00004269
cristybb503372010-05-27 20:51:26 +00004270 size_t
cristy3ed852e2009-09-05 21:47:34 +00004271 width;
4272
cristybb503372010-05-27 20:51:26 +00004273 ssize_t
4274 y;
4275
cristy3ed852e2009-09-05 21:47:34 +00004276 /*
4277 Initialize noise image attributes.
4278 */
4279 assert(image != (Image *) NULL);
4280 assert(image->signature == MagickSignature);
4281 if (image->debug != MagickFalse)
4282 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4283 assert(exception != (ExceptionInfo *) NULL);
4284 assert(exception->signature == MagickSignature);
4285 width=GetOptimalKernelWidth2D(radius,0.5);
4286 if ((image->columns < width) || (image->rows < width))
4287 ThrowImageException(OptionError,"ImageSmallerThanKernelRadius");
4288 noise_image=CloneImage(image,image->columns,image->rows,MagickTrue,
4289 exception);
4290 if (noise_image == (Image *) NULL)
4291 return((Image *) NULL);
4292 if (SetImageStorageClass(noise_image,DirectClass) == MagickFalse)
4293 {
4294 InheritException(exception,&noise_image->exception);
4295 noise_image=DestroyImage(noise_image);
4296 return((Image *) NULL);
4297 }
4298 pixel_list=AcquireMedianPixelListThreadSet(width);
4299 if (pixel_list == (MedianPixelList **) NULL)
4300 {
4301 noise_image=DestroyImage(noise_image);
4302 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
4303 }
4304 /*
4305 Reduce noise image.
4306 */
4307 status=MagickTrue;
4308 progress=0;
4309 image_view=AcquireCacheView(image);
4310 noise_view=AcquireCacheView(noise_image);
cristyb5d5f722009-11-04 03:03:49 +00004311#if defined(MAGICKCORE_OPENMP_SUPPORT)
4312 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004313#endif
cristybb503372010-05-27 20:51:26 +00004314 for (y=0; y < (ssize_t) noise_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00004315 {
cristy5c9e6f22010-09-17 17:31:01 +00004316 const int
4317 id = GetOpenMPThreadId();
cristy6ebe97c2010-07-03 01:17:28 +00004318
cristy3ed852e2009-09-05 21:47:34 +00004319 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004320 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00004321
4322 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004323 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00004324
4325 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004326 *restrict noise_indexes;
cristy3ed852e2009-09-05 21:47:34 +00004327
cristy3ed852e2009-09-05 21:47:34 +00004328 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004329 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004330
cristy117ff172010-08-15 21:35:32 +00004331 register ssize_t
4332 x;
4333
cristy3ed852e2009-09-05 21:47:34 +00004334 if (status == MagickFalse)
4335 continue;
cristy6ebe97c2010-07-03 01:17:28 +00004336 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
4337 (width/2L),image->columns+width,width,exception);
cristy3ed852e2009-09-05 21:47:34 +00004338 q=QueueCacheViewAuthenticPixels(noise_view,0,y,noise_image->columns,1,
4339 exception);
4340 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
4341 {
4342 status=MagickFalse;
4343 continue;
4344 }
4345 indexes=GetCacheViewVirtualIndexQueue(image_view);
4346 noise_indexes=GetCacheViewAuthenticIndexQueue(noise_view);
cristybb503372010-05-27 20:51:26 +00004347 for (x=0; x < (ssize_t) noise_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004348 {
4349 MagickPixelPacket
4350 pixel;
4351
4352 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004353 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +00004354
4355 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004356 *restrict s;
cristy3ed852e2009-09-05 21:47:34 +00004357
cristybb503372010-05-27 20:51:26 +00004358 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00004359 u,
4360 v;
4361
4362 r=p;
4363 s=indexes+x;
4364 ResetMedianPixelList(pixel_list[id]);
cristybb503372010-05-27 20:51:26 +00004365 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00004366 {
cristybb503372010-05-27 20:51:26 +00004367 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00004368 InsertMedianPixelList(image,r+u,s+u,pixel_list[id]);
4369 r+=image->columns+width;
4370 s+=image->columns+width;
4371 }
4372 pixel=GetNonpeakMedianPixelList(pixel_list[id]);
4373 SetPixelPacket(noise_image,&pixel,q,noise_indexes+x);
4374 p++;
4375 q++;
4376 }
4377 if (SyncCacheViewAuthenticPixels(noise_view,exception) == MagickFalse)
4378 status=MagickFalse;
4379 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4380 {
4381 MagickBooleanType
4382 proceed;
4383
cristyb5d5f722009-11-04 03:03:49 +00004384#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004385 #pragma omp critical (MagickCore_ReduceNoiseImage)
4386#endif
4387 proceed=SetImageProgress(image,ReduceNoiseImageTag,progress++,
4388 image->rows);
4389 if (proceed == MagickFalse)
4390 status=MagickFalse;
4391 }
4392 }
4393 noise_view=DestroyCacheView(noise_view);
4394 image_view=DestroyCacheView(image_view);
4395 pixel_list=DestroyMedianPixelListThreadSet(pixel_list);
4396 return(noise_image);
4397}
4398
4399/*
4400%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4401% %
4402% %
4403% %
4404% S e l e c t i v e B l u r I m a g e %
4405% %
4406% %
4407% %
4408%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4409%
4410% SelectiveBlurImage() selectively blur pixels within a contrast threshold.
4411% It is similar to the unsharpen mask that sharpens everything with contrast
4412% above a certain threshold.
4413%
4414% The format of the SelectiveBlurImage method is:
4415%
4416% Image *SelectiveBlurImage(const Image *image,const double radius,
4417% const double sigma,const double threshold,ExceptionInfo *exception)
4418% Image *SelectiveBlurImageChannel(const Image *image,
4419% const ChannelType channel,const double radius,const double sigma,
4420% const double threshold,ExceptionInfo *exception)
4421%
4422% A description of each parameter follows:
4423%
4424% o image: the image.
4425%
4426% o channel: the channel type.
4427%
4428% o radius: the radius of the Gaussian, in pixels, not counting the center
4429% pixel.
4430%
4431% o sigma: the standard deviation of the Gaussian, in pixels.
4432%
4433% o threshold: only pixels within this contrast threshold are included
4434% in the blur operation.
4435%
4436% o exception: return any errors or warnings in this structure.
4437%
4438*/
4439
4440static inline MagickBooleanType SelectiveContrast(const PixelPacket *p,
4441 const PixelPacket *q,const double threshold)
4442{
4443 if (fabs(PixelIntensity(p)-PixelIntensity(q)) < threshold)
4444 return(MagickTrue);
4445 return(MagickFalse);
4446}
4447
4448MagickExport Image *SelectiveBlurImage(const Image *image,const double radius,
4449 const double sigma,const double threshold,ExceptionInfo *exception)
4450{
4451 Image
4452 *blur_image;
4453
4454 blur_image=SelectiveBlurImageChannel(image,DefaultChannels,radius,sigma,
4455 threshold,exception);
4456 return(blur_image);
4457}
4458
4459MagickExport Image *SelectiveBlurImageChannel(const Image *image,
4460 const ChannelType channel,const double radius,const double sigma,
4461 const double threshold,ExceptionInfo *exception)
4462{
4463#define SelectiveBlurImageTag "SelectiveBlur/Image"
4464
cristy47e00502009-12-17 19:19:57 +00004465 CacheView
4466 *blur_view,
4467 *image_view;
4468
cristy3ed852e2009-09-05 21:47:34 +00004469 double
cristy3ed852e2009-09-05 21:47:34 +00004470 *kernel;
4471
4472 Image
4473 *blur_image;
4474
cristy3ed852e2009-09-05 21:47:34 +00004475 MagickBooleanType
4476 status;
4477
cristybb503372010-05-27 20:51:26 +00004478 MagickOffsetType
4479 progress;
4480
cristy3ed852e2009-09-05 21:47:34 +00004481 MagickPixelPacket
cristy3ed852e2009-09-05 21:47:34 +00004482 bias;
4483
cristybb503372010-05-27 20:51:26 +00004484 register ssize_t
cristy47e00502009-12-17 19:19:57 +00004485 i;
cristy3ed852e2009-09-05 21:47:34 +00004486
cristybb503372010-05-27 20:51:26 +00004487 size_t
cristy3ed852e2009-09-05 21:47:34 +00004488 width;
4489
cristybb503372010-05-27 20:51:26 +00004490 ssize_t
4491 j,
4492 u,
4493 v,
4494 y;
4495
cristy3ed852e2009-09-05 21:47:34 +00004496 /*
4497 Initialize blur image attributes.
4498 */
4499 assert(image != (Image *) NULL);
4500 assert(image->signature == MagickSignature);
4501 if (image->debug != MagickFalse)
4502 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4503 assert(exception != (ExceptionInfo *) NULL);
4504 assert(exception->signature == MagickSignature);
4505 width=GetOptimalKernelWidth1D(radius,sigma);
4506 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
4507 if (kernel == (double *) NULL)
4508 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00004509 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00004510 i=0;
cristy47e00502009-12-17 19:19:57 +00004511 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00004512 {
cristy47e00502009-12-17 19:19:57 +00004513 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00004514 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
4515 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00004516 }
4517 if (image->debug != MagickFalse)
4518 {
4519 char
4520 format[MaxTextExtent],
4521 *message;
4522
cristy117ff172010-08-15 21:35:32 +00004523 register const double
4524 *k;
4525
cristybb503372010-05-27 20:51:26 +00004526 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00004527 u,
4528 v;
4529
cristy3ed852e2009-09-05 21:47:34 +00004530 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00004531 " SelectiveBlurImage with %.20gx%.20g kernel:",(double) width,(double)
4532 width);
cristy3ed852e2009-09-05 21:47:34 +00004533 message=AcquireString("");
4534 k=kernel;
cristybb503372010-05-27 20:51:26 +00004535 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00004536 {
4537 *message='\0';
cristye8c25f92010-06-03 00:53:06 +00004538 (void) FormatMagickString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy3ed852e2009-09-05 21:47:34 +00004539 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00004540 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00004541 {
4542 (void) FormatMagickString(format,MaxTextExtent,"%+f ",*k++);
4543 (void) ConcatenateString(&message,format);
4544 }
4545 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
4546 }
4547 message=DestroyString(message);
4548 }
4549 blur_image=CloneImage(image,0,0,MagickTrue,exception);
4550 if (blur_image == (Image *) NULL)
4551 return((Image *) NULL);
4552 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
4553 {
4554 InheritException(exception,&blur_image->exception);
4555 blur_image=DestroyImage(blur_image);
4556 return((Image *) NULL);
4557 }
4558 /*
4559 Threshold blur image.
4560 */
4561 status=MagickTrue;
4562 progress=0;
cristyddd82202009-11-03 20:14:50 +00004563 GetMagickPixelPacket(image,&bias);
4564 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00004565 image_view=AcquireCacheView(image);
4566 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00004567#if defined(MAGICKCORE_OPENMP_SUPPORT)
4568 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004569#endif
cristybb503372010-05-27 20:51:26 +00004570 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00004571 {
4572 MagickBooleanType
4573 sync;
4574
4575 MagickRealType
4576 gamma;
4577
4578 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004579 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00004580
4581 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004582 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00004583
4584 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004585 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00004586
cristy3ed852e2009-09-05 21:47:34 +00004587 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004588 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004589
cristy117ff172010-08-15 21:35:32 +00004590 register ssize_t
4591 x;
4592
cristy3ed852e2009-09-05 21:47:34 +00004593 if (status == MagickFalse)
4594 continue;
cristy117ff172010-08-15 21:35:32 +00004595 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
4596 (width/2L),image->columns+width,width,exception);
cristy3ed852e2009-09-05 21:47:34 +00004597 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
4598 exception);
4599 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
4600 {
4601 status=MagickFalse;
4602 continue;
4603 }
4604 indexes=GetCacheViewVirtualIndexQueue(image_view);
4605 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
cristybb503372010-05-27 20:51:26 +00004606 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004607 {
cristy3ed852e2009-09-05 21:47:34 +00004608 MagickPixelPacket
4609 pixel;
4610
4611 register const double
cristyc47d1f82009-11-26 01:44:43 +00004612 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00004613
cristybb503372010-05-27 20:51:26 +00004614 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00004615 u;
4616
cristy117ff172010-08-15 21:35:32 +00004617 ssize_t
4618 j,
4619 v;
4620
cristyddd82202009-11-03 20:14:50 +00004621 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00004622 k=kernel;
4623 gamma=0.0;
4624 j=0;
4625 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
4626 {
cristybb503372010-05-27 20:51:26 +00004627 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00004628 {
cristybb503372010-05-27 20:51:26 +00004629 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00004630 {
4631 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4632 {
4633 pixel.red+=(*k)*(p+u+j)->red;
4634 pixel.green+=(*k)*(p+u+j)->green;
4635 pixel.blue+=(*k)*(p+u+j)->blue;
4636 gamma+=(*k);
4637 k++;
4638 }
4639 }
cristyd99b0962010-05-29 23:14:26 +00004640 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00004641 }
4642 if (gamma != 0.0)
4643 {
4644 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
4645 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004646 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004647 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004648 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004649 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004650 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004651 }
4652 if ((channel & OpacityChannel) != 0)
4653 {
4654 gamma=0.0;
4655 j=0;
cristybb503372010-05-27 20:51:26 +00004656 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00004657 {
cristybb503372010-05-27 20:51:26 +00004658 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00004659 {
4660 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4661 {
4662 pixel.opacity+=(*k)*(p+u+j)->opacity;
4663 gamma+=(*k);
4664 k++;
4665 }
4666 }
cristyeaedf062010-05-29 22:36:02 +00004667 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00004668 }
4669 if (gamma != 0.0)
4670 {
4671 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4672 gamma);
cristyce70c172010-01-07 17:15:30 +00004673 SetOpacityPixelComponent(q,ClampToQuantum(gamma*
4674 GetOpacityPixelComponent(&pixel)));
cristy3ed852e2009-09-05 21:47:34 +00004675 }
4676 }
4677 if (((channel & IndexChannel) != 0) &&
4678 (image->colorspace == CMYKColorspace))
4679 {
4680 gamma=0.0;
4681 j=0;
cristybb503372010-05-27 20:51:26 +00004682 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00004683 {
cristybb503372010-05-27 20:51:26 +00004684 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00004685 {
4686 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4687 {
4688 pixel.index+=(*k)*indexes[x+u+j];
4689 gamma+=(*k);
4690 k++;
4691 }
4692 }
cristyeaedf062010-05-29 22:36:02 +00004693 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00004694 }
4695 if (gamma != 0.0)
4696 {
4697 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4698 gamma);
cristy6db48122010-01-11 00:18:07 +00004699 blur_indexes[x]=ClampToQuantum(gamma*
4700 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004701 }
4702 }
4703 }
4704 else
4705 {
4706 MagickRealType
4707 alpha;
4708
cristybb503372010-05-27 20:51:26 +00004709 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00004710 {
cristybb503372010-05-27 20:51:26 +00004711 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00004712 {
4713 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4714 {
cristy46f08202010-01-10 04:04:21 +00004715 alpha=(MagickRealType) (QuantumScale*
4716 GetAlphaPixelComponent(p+u+j));
cristy3ed852e2009-09-05 21:47:34 +00004717 pixel.red+=(*k)*alpha*(p+u+j)->red;
4718 pixel.green+=(*k)*alpha*(p+u+j)->green;
4719 pixel.blue+=(*k)*alpha*(p+u+j)->blue;
4720 pixel.opacity+=(*k)*(p+u+j)->opacity;
4721 gamma+=(*k)*alpha;
4722 k++;
4723 }
4724 }
cristyeaedf062010-05-29 22:36:02 +00004725 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00004726 }
4727 if (gamma != 0.0)
4728 {
4729 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
4730 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004731 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004732 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004733 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004734 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004735 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004736 }
4737 if ((channel & OpacityChannel) != 0)
4738 {
4739 gamma=0.0;
4740 j=0;
cristybb503372010-05-27 20:51:26 +00004741 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00004742 {
cristybb503372010-05-27 20:51:26 +00004743 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00004744 {
4745 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4746 {
4747 pixel.opacity+=(*k)*(p+u+j)->opacity;
4748 gamma+=(*k);
4749 k++;
4750 }
4751 }
cristyeaedf062010-05-29 22:36:02 +00004752 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00004753 }
4754 if (gamma != 0.0)
4755 {
4756 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4757 gamma);
cristy6db48122010-01-11 00:18:07 +00004758 SetOpacityPixelComponent(q,
4759 ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004760 }
4761 }
4762 if (((channel & IndexChannel) != 0) &&
4763 (image->colorspace == CMYKColorspace))
4764 {
4765 gamma=0.0;
4766 j=0;
cristybb503372010-05-27 20:51:26 +00004767 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00004768 {
cristybb503372010-05-27 20:51:26 +00004769 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00004770 {
4771 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4772 {
cristy46f08202010-01-10 04:04:21 +00004773 alpha=(MagickRealType) (QuantumScale*
4774 GetAlphaPixelComponent(p+u+j));
cristy3ed852e2009-09-05 21:47:34 +00004775 pixel.index+=(*k)*alpha*indexes[x+u+j];
4776 gamma+=(*k);
4777 k++;
4778 }
4779 }
cristyeaedf062010-05-29 22:36:02 +00004780 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00004781 }
4782 if (gamma != 0.0)
4783 {
4784 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4785 gamma);
cristy6db48122010-01-11 00:18:07 +00004786 blur_indexes[x]=ClampToQuantum(gamma*
4787 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004788 }
4789 }
4790 }
4791 p++;
4792 q++;
4793 }
4794 sync=SyncCacheViewAuthenticPixels(blur_view,exception);
4795 if (sync == MagickFalse)
4796 status=MagickFalse;
4797 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4798 {
4799 MagickBooleanType
4800 proceed;
4801
cristyb5d5f722009-11-04 03:03:49 +00004802#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004803 #pragma omp critical (MagickCore_SelectiveBlurImageChannel)
4804#endif
4805 proceed=SetImageProgress(image,SelectiveBlurImageTag,progress++,
4806 image->rows);
4807 if (proceed == MagickFalse)
4808 status=MagickFalse;
4809 }
4810 }
4811 blur_image->type=image->type;
4812 blur_view=DestroyCacheView(blur_view);
4813 image_view=DestroyCacheView(image_view);
4814 kernel=(double *) RelinquishMagickMemory(kernel);
4815 if (status == MagickFalse)
4816 blur_image=DestroyImage(blur_image);
4817 return(blur_image);
4818}
4819
4820/*
4821%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4822% %
4823% %
4824% %
4825% S h a d e I m a g e %
4826% %
4827% %
4828% %
4829%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4830%
4831% ShadeImage() shines a distant light on an image to create a
4832% three-dimensional effect. You control the positioning of the light with
4833% azimuth and elevation; azimuth is measured in degrees off the x axis
4834% and elevation is measured in pixels above the Z axis.
4835%
4836% The format of the ShadeImage method is:
4837%
4838% Image *ShadeImage(const Image *image,const MagickBooleanType gray,
4839% const double azimuth,const double elevation,ExceptionInfo *exception)
4840%
4841% A description of each parameter follows:
4842%
4843% o image: the image.
4844%
4845% o gray: A value other than zero shades the intensity of each pixel.
4846%
4847% o azimuth, elevation: Define the light source direction.
4848%
4849% o exception: return any errors or warnings in this structure.
4850%
4851*/
4852MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray,
4853 const double azimuth,const double elevation,ExceptionInfo *exception)
4854{
4855#define ShadeImageTag "Shade/Image"
4856
cristyc4c8d132010-01-07 01:58:38 +00004857 CacheView
4858 *image_view,
4859 *shade_view;
4860
cristy3ed852e2009-09-05 21:47:34 +00004861 Image
4862 *shade_image;
4863
cristy3ed852e2009-09-05 21:47:34 +00004864 MagickBooleanType
4865 status;
4866
cristybb503372010-05-27 20:51:26 +00004867 MagickOffsetType
4868 progress;
4869
cristy3ed852e2009-09-05 21:47:34 +00004870 PrimaryInfo
4871 light;
4872
cristybb503372010-05-27 20:51:26 +00004873 ssize_t
4874 y;
4875
cristy3ed852e2009-09-05 21:47:34 +00004876 /*
4877 Initialize shaded image attributes.
4878 */
4879 assert(image != (const Image *) NULL);
4880 assert(image->signature == MagickSignature);
4881 if (image->debug != MagickFalse)
4882 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4883 assert(exception != (ExceptionInfo *) NULL);
4884 assert(exception->signature == MagickSignature);
4885 shade_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
4886 if (shade_image == (Image *) NULL)
4887 return((Image *) NULL);
4888 if (SetImageStorageClass(shade_image,DirectClass) == MagickFalse)
4889 {
4890 InheritException(exception,&shade_image->exception);
4891 shade_image=DestroyImage(shade_image);
4892 return((Image *) NULL);
4893 }
4894 /*
4895 Compute the light vector.
4896 */
4897 light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))*
4898 cos(DegreesToRadians(elevation));
4899 light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))*
4900 cos(DegreesToRadians(elevation));
4901 light.z=(double) QuantumRange*sin(DegreesToRadians(elevation));
4902 /*
4903 Shade image.
4904 */
4905 status=MagickTrue;
4906 progress=0;
4907 image_view=AcquireCacheView(image);
4908 shade_view=AcquireCacheView(shade_image);
cristyb5d5f722009-11-04 03:03:49 +00004909#if defined(MAGICKCORE_OPENMP_SUPPORT)
4910 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004911#endif
cristybb503372010-05-27 20:51:26 +00004912 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00004913 {
4914 MagickRealType
4915 distance,
4916 normal_distance,
4917 shade;
4918
4919 PrimaryInfo
4920 normal;
4921
4922 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004923 *restrict p,
4924 *restrict s0,
4925 *restrict s1,
4926 *restrict s2;
cristy3ed852e2009-09-05 21:47:34 +00004927
cristy3ed852e2009-09-05 21:47:34 +00004928 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004929 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004930
cristy117ff172010-08-15 21:35:32 +00004931 register ssize_t
4932 x;
4933
cristy3ed852e2009-09-05 21:47:34 +00004934 if (status == MagickFalse)
4935 continue;
4936 p=GetCacheViewVirtualPixels(image_view,-1,y-1,image->columns+2,3,exception);
4937 q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1,
4938 exception);
4939 if ((p == (PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
4940 {
4941 status=MagickFalse;
4942 continue;
4943 }
4944 /*
4945 Shade this row of pixels.
4946 */
4947 normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */
4948 s0=p+1;
4949 s1=s0+image->columns+2;
4950 s2=s1+image->columns+2;
cristybb503372010-05-27 20:51:26 +00004951 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004952 {
4953 /*
4954 Determine the surface normal and compute shading.
4955 */
4956 normal.x=(double) (PixelIntensity(s0-1)+PixelIntensity(s1-1)+
4957 PixelIntensity(s2-1)-PixelIntensity(s0+1)-PixelIntensity(s1+1)-
4958 PixelIntensity(s2+1));
4959 normal.y=(double) (PixelIntensity(s2-1)+PixelIntensity(s2)+
4960 PixelIntensity(s2+1)-PixelIntensity(s0-1)-PixelIntensity(s0)-
4961 PixelIntensity(s0+1));
4962 if ((normal.x == 0.0) && (normal.y == 0.0))
4963 shade=light.z;
4964 else
4965 {
4966 shade=0.0;
4967 distance=normal.x*light.x+normal.y*light.y+normal.z*light.z;
4968 if (distance > MagickEpsilon)
4969 {
4970 normal_distance=
4971 normal.x*normal.x+normal.y*normal.y+normal.z*normal.z;
4972 if (normal_distance > (MagickEpsilon*MagickEpsilon))
4973 shade=distance/sqrt((double) normal_distance);
4974 }
4975 }
4976 if (gray != MagickFalse)
4977 {
4978 q->red=(Quantum) shade;
4979 q->green=(Quantum) shade;
4980 q->blue=(Quantum) shade;
4981 }
4982 else
4983 {
cristyce70c172010-01-07 17:15:30 +00004984 q->red=ClampToQuantum(QuantumScale*shade*s1->red);
4985 q->green=ClampToQuantum(QuantumScale*shade*s1->green);
4986 q->blue=ClampToQuantum(QuantumScale*shade*s1->blue);
cristy3ed852e2009-09-05 21:47:34 +00004987 }
4988 q->opacity=s1->opacity;
4989 s0++;
4990 s1++;
4991 s2++;
4992 q++;
4993 }
4994 if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
4995 status=MagickFalse;
4996 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4997 {
4998 MagickBooleanType
4999 proceed;
5000
cristyb5d5f722009-11-04 03:03:49 +00005001#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00005002 #pragma omp critical (MagickCore_ShadeImage)
5003#endif
5004 proceed=SetImageProgress(image,ShadeImageTag,progress++,image->rows);
5005 if (proceed == MagickFalse)
5006 status=MagickFalse;
5007 }
5008 }
5009 shade_view=DestroyCacheView(shade_view);
5010 image_view=DestroyCacheView(image_view);
5011 if (status == MagickFalse)
5012 shade_image=DestroyImage(shade_image);
5013 return(shade_image);
5014}
5015
5016/*
5017%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5018% %
5019% %
5020% %
5021% S h a r p e n I m a g e %
5022% %
5023% %
5024% %
5025%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5026%
5027% SharpenImage() sharpens the image. We convolve the image with a Gaussian
5028% operator of the given radius and standard deviation (sigma). For
5029% reasonable results, radius should be larger than sigma. Use a radius of 0
5030% and SharpenImage() selects a suitable radius for you.
5031%
5032% Using a separable kernel would be faster, but the negative weights cancel
5033% out on the corners of the kernel producing often undesirable ringing in the
5034% filtered result; this can be avoided by using a 2D gaussian shaped image
5035% sharpening kernel instead.
5036%
5037% The format of the SharpenImage method is:
5038%
5039% Image *SharpenImage(const Image *image,const double radius,
5040% const double sigma,ExceptionInfo *exception)
5041% Image *SharpenImageChannel(const Image *image,const ChannelType channel,
5042% const double radius,const double sigma,ExceptionInfo *exception)
5043%
5044% A description of each parameter follows:
5045%
5046% o image: the image.
5047%
5048% o channel: the channel type.
5049%
5050% o radius: the radius of the Gaussian, in pixels, not counting the center
5051% pixel.
5052%
5053% o sigma: the standard deviation of the Laplacian, in pixels.
5054%
5055% o exception: return any errors or warnings in this structure.
5056%
5057*/
5058
5059MagickExport Image *SharpenImage(const Image *image,const double radius,
5060 const double sigma,ExceptionInfo *exception)
5061{
5062 Image
5063 *sharp_image;
5064
5065 sharp_image=SharpenImageChannel(image,DefaultChannels,radius,sigma,exception);
5066 return(sharp_image);
5067}
5068
5069MagickExport Image *SharpenImageChannel(const Image *image,
5070 const ChannelType channel,const double radius,const double sigma,
5071 ExceptionInfo *exception)
5072{
5073 double
cristy47e00502009-12-17 19:19:57 +00005074 *kernel,
5075 normalize;
cristy3ed852e2009-09-05 21:47:34 +00005076
5077 Image
5078 *sharp_image;
5079
cristybb503372010-05-27 20:51:26 +00005080 register ssize_t
cristy47e00502009-12-17 19:19:57 +00005081 i;
5082
cristybb503372010-05-27 20:51:26 +00005083 size_t
cristy3ed852e2009-09-05 21:47:34 +00005084 width;
5085
cristy117ff172010-08-15 21:35:32 +00005086 ssize_t
5087 j,
5088 u,
5089 v;
5090
cristy3ed852e2009-09-05 21:47:34 +00005091 assert(image != (const Image *) NULL);
5092 assert(image->signature == MagickSignature);
5093 if (image->debug != MagickFalse)
5094 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5095 assert(exception != (ExceptionInfo *) NULL);
5096 assert(exception->signature == MagickSignature);
5097 width=GetOptimalKernelWidth2D(radius,sigma);
5098 kernel=(double *) AcquireQuantumMemory((size_t) width*width,sizeof(*kernel));
5099 if (kernel == (double *) NULL)
5100 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy3ed852e2009-09-05 21:47:34 +00005101 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00005102 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +00005103 i=0;
5104 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00005105 {
cristy47e00502009-12-17 19:19:57 +00005106 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00005107 {
cristy4205a3c2010-09-12 20:19:59 +00005108 kernel[i]=(double) (-exp(-((double) u*u+v*v)/(2.0*MagickSigma*
5109 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00005110 normalize+=kernel[i];
5111 i++;
5112 }
5113 }
5114 kernel[i/2]=(double) ((-2.0)*normalize);
5115 sharp_image=ConvolveImageChannel(image,channel,width,kernel,exception);
5116 kernel=(double *) RelinquishMagickMemory(kernel);
5117 return(sharp_image);
5118}
5119
5120/*
5121%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5122% %
5123% %
5124% %
5125% S p r e a d I m a g e %
5126% %
5127% %
5128% %
5129%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5130%
5131% SpreadImage() is a special effects method that randomly displaces each
5132% pixel in a block defined by the radius parameter.
5133%
5134% The format of the SpreadImage method is:
5135%
5136% Image *SpreadImage(const Image *image,const double radius,
5137% ExceptionInfo *exception)
5138%
5139% A description of each parameter follows:
5140%
5141% o image: the image.
5142%
5143% o radius: Choose a random pixel in a neighborhood of this extent.
5144%
5145% o exception: return any errors or warnings in this structure.
5146%
5147*/
5148MagickExport Image *SpreadImage(const Image *image,const double radius,
5149 ExceptionInfo *exception)
5150{
5151#define SpreadImageTag "Spread/Image"
5152
cristyfa112112010-01-04 17:48:07 +00005153 CacheView
5154 *image_view;
5155
cristy3ed852e2009-09-05 21:47:34 +00005156 Image
5157 *spread_image;
5158
cristy3ed852e2009-09-05 21:47:34 +00005159 MagickBooleanType
5160 status;
5161
cristybb503372010-05-27 20:51:26 +00005162 MagickOffsetType
5163 progress;
5164
cristy3ed852e2009-09-05 21:47:34 +00005165 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00005166 bias;
cristy3ed852e2009-09-05 21:47:34 +00005167
5168 RandomInfo
cristyfa112112010-01-04 17:48:07 +00005169 **restrict random_info;
cristy3ed852e2009-09-05 21:47:34 +00005170
5171 ResampleFilter
cristyfa112112010-01-04 17:48:07 +00005172 **restrict resample_filter;
cristy3ed852e2009-09-05 21:47:34 +00005173
cristybb503372010-05-27 20:51:26 +00005174 size_t
cristy3ed852e2009-09-05 21:47:34 +00005175 width;
5176
cristybb503372010-05-27 20:51:26 +00005177 ssize_t
5178 y;
5179
cristy3ed852e2009-09-05 21:47:34 +00005180 /*
5181 Initialize spread image attributes.
5182 */
5183 assert(image != (Image *) NULL);
5184 assert(image->signature == MagickSignature);
5185 if (image->debug != MagickFalse)
5186 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5187 assert(exception != (ExceptionInfo *) NULL);
5188 assert(exception->signature == MagickSignature);
5189 spread_image=CloneImage(image,image->columns,image->rows,MagickTrue,
5190 exception);
5191 if (spread_image == (Image *) NULL)
5192 return((Image *) NULL);
5193 if (SetImageStorageClass(spread_image,DirectClass) == MagickFalse)
5194 {
5195 InheritException(exception,&spread_image->exception);
5196 spread_image=DestroyImage(spread_image);
5197 return((Image *) NULL);
5198 }
5199 /*
5200 Spread image.
5201 */
5202 status=MagickTrue;
5203 progress=0;
cristyddd82202009-11-03 20:14:50 +00005204 GetMagickPixelPacket(spread_image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00005205 width=GetOptimalKernelWidth1D(radius,0.5);
cristyb2a11ae2010-02-22 00:53:36 +00005206 resample_filter=AcquireResampleFilterThreadSet(image,
5207 UndefinedVirtualPixelMethod,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00005208 random_info=AcquireRandomInfoThreadSet();
5209 image_view=AcquireCacheView(spread_image);
cristy59e0d3b2010-06-07 13:12:38 +00005210#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00005211 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00005212#endif
cristybb503372010-05-27 20:51:26 +00005213 for (y=0; y < (ssize_t) spread_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00005214 {
cristy5c9e6f22010-09-17 17:31:01 +00005215 const int
5216 id = GetOpenMPThreadId();
cristy6ebe97c2010-07-03 01:17:28 +00005217
cristy3ed852e2009-09-05 21:47:34 +00005218 MagickPixelPacket
5219 pixel;
5220
5221 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00005222 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00005223
cristy3ed852e2009-09-05 21:47:34 +00005224 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00005225 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00005226
cristy117ff172010-08-15 21:35:32 +00005227 register ssize_t
5228 x;
5229
cristy3ed852e2009-09-05 21:47:34 +00005230 if (status == MagickFalse)
5231 continue;
5232 q=QueueCacheViewAuthenticPixels(image_view,0,y,spread_image->columns,1,
5233 exception);
5234 if (q == (PixelPacket *) NULL)
5235 {
5236 status=MagickFalse;
5237 continue;
5238 }
5239 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristyddd82202009-11-03 20:14:50 +00005240 pixel=bias;
cristybb503372010-05-27 20:51:26 +00005241 for (x=0; x < (ssize_t) spread_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00005242 {
5243 (void) ResamplePixelColor(resample_filter[id],(double) x+width*
5244 (GetPseudoRandomValue(random_info[id])-0.5),(double) y+width*
5245 (GetPseudoRandomValue(random_info[id])-0.5),&pixel);
5246 SetPixelPacket(spread_image,&pixel,q,indexes+x);
5247 q++;
5248 }
5249 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
5250 status=MagickFalse;
5251 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5252 {
5253 MagickBooleanType
5254 proceed;
5255
cristy59e0d3b2010-06-07 13:12:38 +00005256#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00005257 #pragma omp critical (MagickCore_SpreadImage)
5258#endif
5259 proceed=SetImageProgress(image,SpreadImageTag,progress++,image->rows);
5260 if (proceed == MagickFalse)
5261 status=MagickFalse;
5262 }
5263 }
5264 image_view=DestroyCacheView(image_view);
5265 random_info=DestroyRandomInfoThreadSet(random_info);
5266 resample_filter=DestroyResampleFilterThreadSet(resample_filter);
5267 return(spread_image);
5268}
5269
5270/*
5271%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5272% %
5273% %
5274% %
5275% U n s h a r p M a s k I m a g e %
5276% %
5277% %
5278% %
5279%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5280%
5281% UnsharpMaskImage() sharpens one or more image channels. We convolve the
5282% image with a Gaussian operator of the given radius and standard deviation
5283% (sigma). For reasonable results, radius should be larger than sigma. Use a
5284% radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
5285%
5286% The format of the UnsharpMaskImage method is:
5287%
5288% Image *UnsharpMaskImage(const Image *image,const double radius,
5289% const double sigma,const double amount,const double threshold,
5290% ExceptionInfo *exception)
5291% Image *UnsharpMaskImageChannel(const Image *image,
5292% const ChannelType channel,const double radius,const double sigma,
5293% const double amount,const double threshold,ExceptionInfo *exception)
5294%
5295% A description of each parameter follows:
5296%
5297% o image: the image.
5298%
5299% o channel: the channel type.
5300%
5301% o radius: the radius of the Gaussian, in pixels, not counting the center
5302% pixel.
5303%
5304% o sigma: the standard deviation of the Gaussian, in pixels.
5305%
5306% o amount: the percentage of the difference between the original and the
5307% blur image that is added back into the original.
5308%
5309% o threshold: the threshold in pixels needed to apply the diffence amount.
5310%
5311% o exception: return any errors or warnings in this structure.
5312%
5313*/
5314
5315MagickExport Image *UnsharpMaskImage(const Image *image,const double radius,
5316 const double sigma,const double amount,const double threshold,
5317 ExceptionInfo *exception)
5318{
5319 Image
5320 *sharp_image;
5321
5322 sharp_image=UnsharpMaskImageChannel(image,DefaultChannels,radius,sigma,amount,
5323 threshold,exception);
5324 return(sharp_image);
5325}
5326
5327MagickExport Image *UnsharpMaskImageChannel(const Image *image,
5328 const ChannelType channel,const double radius,const double sigma,
5329 const double amount,const double threshold,ExceptionInfo *exception)
5330{
5331#define SharpenImageTag "Sharpen/Image"
5332
cristyc4c8d132010-01-07 01:58:38 +00005333 CacheView
5334 *image_view,
5335 *unsharp_view;
5336
cristy3ed852e2009-09-05 21:47:34 +00005337 Image
5338 *unsharp_image;
5339
cristy3ed852e2009-09-05 21:47:34 +00005340 MagickBooleanType
5341 status;
5342
cristybb503372010-05-27 20:51:26 +00005343 MagickOffsetType
5344 progress;
5345
cristy3ed852e2009-09-05 21:47:34 +00005346 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00005347 bias;
cristy3ed852e2009-09-05 21:47:34 +00005348
5349 MagickRealType
5350 quantum_threshold;
5351
cristybb503372010-05-27 20:51:26 +00005352 ssize_t
5353 y;
5354
cristy3ed852e2009-09-05 21:47:34 +00005355 assert(image != (const Image *) NULL);
5356 assert(image->signature == MagickSignature);
5357 if (image->debug != MagickFalse)
5358 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5359 assert(exception != (ExceptionInfo *) NULL);
5360 unsharp_image=BlurImageChannel(image,channel,radius,sigma,exception);
5361 if (unsharp_image == (Image *) NULL)
5362 return((Image *) NULL);
5363 quantum_threshold=(MagickRealType) QuantumRange*threshold;
5364 /*
5365 Unsharp-mask image.
5366 */
5367 status=MagickTrue;
5368 progress=0;
cristyddd82202009-11-03 20:14:50 +00005369 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00005370 image_view=AcquireCacheView(image);
5371 unsharp_view=AcquireCacheView(unsharp_image);
cristyb5d5f722009-11-04 03:03:49 +00005372#if defined(MAGICKCORE_OPENMP_SUPPORT)
5373 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00005374#endif
cristybb503372010-05-27 20:51:26 +00005375 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00005376 {
5377 MagickPixelPacket
5378 pixel;
5379
5380 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00005381 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00005382
5383 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00005384 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00005385
5386 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00005387 *restrict unsharp_indexes;
cristy3ed852e2009-09-05 21:47:34 +00005388
cristy3ed852e2009-09-05 21:47:34 +00005389 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00005390 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00005391
cristy117ff172010-08-15 21:35:32 +00005392 register ssize_t
5393 x;
5394
cristy3ed852e2009-09-05 21:47:34 +00005395 if (status == MagickFalse)
5396 continue;
5397 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
5398 q=GetCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
5399 exception);
5400 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
5401 {
5402 status=MagickFalse;
5403 continue;
5404 }
5405 indexes=GetCacheViewVirtualIndexQueue(image_view);
5406 unsharp_indexes=GetCacheViewAuthenticIndexQueue(unsharp_view);
cristyddd82202009-11-03 20:14:50 +00005407 pixel=bias;
cristybb503372010-05-27 20:51:26 +00005408 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00005409 {
5410 if ((channel & RedChannel) != 0)
5411 {
5412 pixel.red=p->red-(MagickRealType) q->red;
5413 if (fabs(2.0*pixel.red) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005414 pixel.red=(MagickRealType) GetRedPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005415 else
5416 pixel.red=(MagickRealType) p->red+(pixel.red*amount);
cristyce70c172010-01-07 17:15:30 +00005417 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00005418 }
5419 if ((channel & GreenChannel) != 0)
5420 {
5421 pixel.green=p->green-(MagickRealType) q->green;
5422 if (fabs(2.0*pixel.green) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005423 pixel.green=(MagickRealType) GetGreenPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005424 else
5425 pixel.green=(MagickRealType) p->green+(pixel.green*amount);
cristyce70c172010-01-07 17:15:30 +00005426 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00005427 }
5428 if ((channel & BlueChannel) != 0)
5429 {
5430 pixel.blue=p->blue-(MagickRealType) q->blue;
5431 if (fabs(2.0*pixel.blue) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005432 pixel.blue=(MagickRealType) GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005433 else
5434 pixel.blue=(MagickRealType) p->blue+(pixel.blue*amount);
cristyce70c172010-01-07 17:15:30 +00005435 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00005436 }
5437 if ((channel & OpacityChannel) != 0)
5438 {
5439 pixel.opacity=p->opacity-(MagickRealType) q->opacity;
5440 if (fabs(2.0*pixel.opacity) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005441 pixel.opacity=(MagickRealType) GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005442 else
5443 pixel.opacity=p->opacity+(pixel.opacity*amount);
cristyce70c172010-01-07 17:15:30 +00005444 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00005445 }
5446 if (((channel & IndexChannel) != 0) &&
5447 (image->colorspace == CMYKColorspace))
5448 {
5449 pixel.index=unsharp_indexes[x]-(MagickRealType) indexes[x];
5450 if (fabs(2.0*pixel.index) < quantum_threshold)
5451 pixel.index=(MagickRealType) unsharp_indexes[x];
5452 else
5453 pixel.index=(MagickRealType) unsharp_indexes[x]+(pixel.index*
5454 amount);
cristyce70c172010-01-07 17:15:30 +00005455 unsharp_indexes[x]=ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00005456 }
5457 p++;
5458 q++;
5459 }
5460 if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
5461 status=MagickFalse;
5462 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5463 {
5464 MagickBooleanType
5465 proceed;
5466
cristyb5d5f722009-11-04 03:03:49 +00005467#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00005468 #pragma omp critical (MagickCore_UnsharpMaskImageChannel)
5469#endif
5470 proceed=SetImageProgress(image,SharpenImageTag,progress++,image->rows);
5471 if (proceed == MagickFalse)
5472 status=MagickFalse;
5473 }
5474 }
5475 unsharp_image->type=image->type;
5476 unsharp_view=DestroyCacheView(unsharp_view);
5477 image_view=DestroyCacheView(image_view);
5478 if (status == MagickFalse)
5479 unsharp_image=DestroyImage(unsharp_image);
5480 return(unsharp_image);
5481}