blob: 59de1640947323716a2d4273d9bb9d8a190284f3 [file] [log] [blame]
cristy3ed852e2009-09-05 21:47:34 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% EEEEE FFFFF FFFFF EEEEE CCCC TTTTT %
7% E F F E C T %
8% EEE FFF FFF EEE C T %
9% E F F E C T %
10% EEEEE F F EEEEE CCCC T %
11% %
12% %
13% MagickCore Image Effects Methods %
14% %
15% Software Design %
16% John Cristy %
17% October 1996 %
18% %
19% %
cristy16af1cb2009-12-11 21:38:29 +000020% Copyright 1999-2010 ImageMagick Studio LLC, a non-profit organization %
cristy3ed852e2009-09-05 21:47:34 +000021% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
26% http://www.imagemagick.org/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
36%
37%
38*/
39
40/*
41 Include declarations.
42*/
43#include "magick/studio.h"
cristyd43a46b2010-01-21 02:13:41 +000044#include "magick/accelerate.h"
cristy3ed852e2009-09-05 21:47:34 +000045#include "magick/blob.h"
46#include "magick/cache-view.h"
47#include "magick/color.h"
48#include "magick/color-private.h"
49#include "magick/colorspace.h"
50#include "magick/constitute.h"
51#include "magick/decorate.h"
52#include "magick/draw.h"
53#include "magick/enhance.h"
54#include "magick/exception.h"
55#include "magick/exception-private.h"
56#include "magick/effect.h"
57#include "magick/fx.h"
58#include "magick/gem.h"
59#include "magick/geometry.h"
60#include "magick/image-private.h"
61#include "magick/list.h"
62#include "magick/log.h"
63#include "magick/memory_.h"
64#include "magick/monitor.h"
65#include "magick/monitor-private.h"
66#include "magick/montage.h"
cristy6771f1e2010-03-05 19:43:39 +000067#include "magick/morphology.h"
cristy3ed852e2009-09-05 21:47:34 +000068#include "magick/paint.h"
69#include "magick/pixel-private.h"
70#include "magick/property.h"
71#include "magick/quantize.h"
72#include "magick/quantum.h"
73#include "magick/random_.h"
74#include "magick/random-private.h"
75#include "magick/resample.h"
76#include "magick/resample-private.h"
77#include "magick/resize.h"
78#include "magick/resource_.h"
79#include "magick/segment.h"
80#include "magick/shear.h"
81#include "magick/signature-private.h"
82#include "magick/string_.h"
83#include "magick/thread-private.h"
84#include "magick/transform.h"
85#include "magick/threshold.h"
86
87/*
88%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
89% %
90% %
91% %
92% A d a p t i v e B l u r I m a g e %
93% %
94% %
95% %
96%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
97%
98% AdaptiveBlurImage() adaptively blurs the image by blurring less
99% intensely near image edges and more intensely far from edges. We blur the
100% image with a Gaussian operator of the given radius and standard deviation
101% (sigma). For reasonable results, radius should be larger than sigma. Use a
102% radius of 0 and AdaptiveBlurImage() selects a suitable radius for you.
103%
104% The format of the AdaptiveBlurImage method is:
105%
106% Image *AdaptiveBlurImage(const Image *image,const double radius,
107% const double sigma,ExceptionInfo *exception)
108% Image *AdaptiveBlurImageChannel(const Image *image,
109% const ChannelType channel,double radius,const double sigma,
110% ExceptionInfo *exception)
111%
112% A description of each parameter follows:
113%
114% o image: the image.
115%
116% o channel: the channel type.
117%
118% o radius: the radius of the Gaussian, in pixels, not counting the center
119% pixel.
120%
121% o sigma: the standard deviation of the Laplacian, in pixels.
122%
123% o exception: return any errors or warnings in this structure.
124%
125*/
126
127MagickExport Image *AdaptiveBlurImage(const Image *image,const double radius,
128 const double sigma,ExceptionInfo *exception)
129{
130 Image
131 *blur_image;
132
133 blur_image=AdaptiveBlurImageChannel(image,DefaultChannels,radius,sigma,
134 exception);
135 return(blur_image);
136}
137
138MagickExport Image *AdaptiveBlurImageChannel(const Image *image,
139 const ChannelType channel,const double radius,const double sigma,
140 ExceptionInfo *exception)
141{
142#define AdaptiveBlurImageTag "Convolve/Image"
143#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
144
cristyc4c8d132010-01-07 01:58:38 +0000145 CacheView
146 *blur_view,
147 *edge_view,
148 *image_view;
149
cristy3ed852e2009-09-05 21:47:34 +0000150 double
cristy47e00502009-12-17 19:19:57 +0000151 **kernel,
152 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000153
154 Image
155 *blur_image,
156 *edge_image,
157 *gaussian_image;
158
cristy3ed852e2009-09-05 21:47:34 +0000159 MagickBooleanType
160 status;
161
cristybb503372010-05-27 20:51:26 +0000162 MagickOffsetType
163 progress;
164
cristy3ed852e2009-09-05 21:47:34 +0000165 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +0000166 bias;
cristy3ed852e2009-09-05 21:47:34 +0000167
cristybb503372010-05-27 20:51:26 +0000168 register ssize_t
cristy47e00502009-12-17 19:19:57 +0000169 i;
cristy3ed852e2009-09-05 21:47:34 +0000170
cristybb503372010-05-27 20:51:26 +0000171 size_t
cristy3ed852e2009-09-05 21:47:34 +0000172 width;
173
cristybb503372010-05-27 20:51:26 +0000174 ssize_t
175 j,
176 k,
177 u,
178 v,
179 y;
180
cristy3ed852e2009-09-05 21:47:34 +0000181 assert(image != (const Image *) NULL);
182 assert(image->signature == MagickSignature);
183 if (image->debug != MagickFalse)
184 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
185 assert(exception != (ExceptionInfo *) NULL);
186 assert(exception->signature == MagickSignature);
187 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
188 if (blur_image == (Image *) NULL)
189 return((Image *) NULL);
190 if (fabs(sigma) <= MagickEpsilon)
191 return(blur_image);
192 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
193 {
194 InheritException(exception,&blur_image->exception);
195 blur_image=DestroyImage(blur_image);
196 return((Image *) NULL);
197 }
198 /*
199 Edge detect the image brighness channel, level, blur, and level again.
200 */
201 edge_image=EdgeImage(image,radius,exception);
202 if (edge_image == (Image *) NULL)
203 {
204 blur_image=DestroyImage(blur_image);
205 return((Image *) NULL);
206 }
207 (void) LevelImage(edge_image,"20%,95%");
208 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,exception);
209 if (gaussian_image != (Image *) NULL)
210 {
211 edge_image=DestroyImage(edge_image);
212 edge_image=gaussian_image;
213 }
214 (void) LevelImage(edge_image,"10%,95%");
215 /*
216 Create a set of kernels from maximum (radius,sigma) to minimum.
217 */
218 width=GetOptimalKernelWidth2D(radius,sigma);
219 kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
220 if (kernel == (double **) NULL)
221 {
222 edge_image=DestroyImage(edge_image);
223 blur_image=DestroyImage(blur_image);
224 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
225 }
226 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
cristybb503372010-05-27 20:51:26 +0000227 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000228 {
229 kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)*
230 sizeof(**kernel));
231 if (kernel[i] == (double *) NULL)
232 break;
cristy47e00502009-12-17 19:19:57 +0000233 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000234 j=(ssize_t) (width-i)/2;
cristy47e00502009-12-17 19:19:57 +0000235 k=0;
236 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000237 {
cristy47e00502009-12-17 19:19:57 +0000238 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000239 {
cristy47e00502009-12-17 19:19:57 +0000240 kernel[i][k]=exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
241 (2.0*MagickPI*MagickSigma*MagickSigma);
242 normalize+=kernel[i][k];
243 k++;
cristy3ed852e2009-09-05 21:47:34 +0000244 }
245 }
cristy3ed852e2009-09-05 21:47:34 +0000246 if (fabs(normalize) <= MagickEpsilon)
247 normalize=1.0;
248 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000249 for (k=0; k < (j*j); k++)
250 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000251 }
cristybb503372010-05-27 20:51:26 +0000252 if (i < (ssize_t) width)
cristy3ed852e2009-09-05 21:47:34 +0000253 {
254 for (i-=2; i >= 0; i-=2)
255 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
256 kernel=(double **) RelinquishMagickMemory(kernel);
257 edge_image=DestroyImage(edge_image);
258 blur_image=DestroyImage(blur_image);
259 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
260 }
261 /*
262 Adaptively blur image.
263 */
264 status=MagickTrue;
265 progress=0;
cristyddd82202009-11-03 20:14:50 +0000266 GetMagickPixelPacket(image,&bias);
267 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000268 image_view=AcquireCacheView(image);
269 edge_view=AcquireCacheView(edge_image);
270 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000271#if defined(MAGICKCORE_OPENMP_SUPPORT)
272 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000273#endif
cristybb503372010-05-27 20:51:26 +0000274 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000275 {
276 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000277 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000278
279 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000280 *restrict p,
281 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000282
283 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000284 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000285
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 {
cristy47e00502009-12-17 19:19:57 +0000557 kernel[i][k]=(-exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
558 (2.0*MagickPI*MagickSigma*MagickSigma));
559 normalize+=kernel[i][k];
560 k++;
cristy3ed852e2009-09-05 21:47:34 +0000561 }
562 }
cristy3ed852e2009-09-05 21:47:34 +0000563 if (fabs(normalize) <= MagickEpsilon)
564 normalize=1.0;
565 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000566 for (k=0; k < (j*j); k++)
567 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000568 }
cristybb503372010-05-27 20:51:26 +0000569 if (i < (ssize_t) width)
cristy3ed852e2009-09-05 21:47:34 +0000570 {
571 for (i-=2; i >= 0; i-=2)
572 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
573 kernel=(double **) RelinquishMagickMemory(kernel);
574 edge_image=DestroyImage(edge_image);
575 sharp_image=DestroyImage(sharp_image);
576 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
577 }
578 /*
579 Adaptively sharpen image.
580 */
581 status=MagickTrue;
582 progress=0;
cristyddd82202009-11-03 20:14:50 +0000583 GetMagickPixelPacket(image,&bias);
584 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000585 image_view=AcquireCacheView(image);
586 edge_view=AcquireCacheView(edge_image);
587 sharp_view=AcquireCacheView(sharp_image);
cristyb5d5f722009-11-04 03:03:49 +0000588#if defined(MAGICKCORE_OPENMP_SUPPORT)
589 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000590#endif
cristybb503372010-05-27 20:51:26 +0000591 for (y=0; y < (ssize_t) sharp_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000592 {
593 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000594 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000595
596 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000597 *restrict p,
598 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000599
600 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000601 *restrict sharp_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000602
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 {
cristyf267c722009-12-18 00:07:22 +0000799 kernel[i]=exp(-((double) k*k)/(2.0*MagickSigma*MagickSigma))/
cristy47e00502009-12-17 19:19:57 +0000800 (MagickSQ2PI*MagickSigma);
cristy3ed852e2009-09-05 21:47:34 +0000801 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +0000802 i++;
803 }
cristybb503372010-05-27 20:51:26 +0000804 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000805 kernel[i]/=normalize;
806 return(kernel);
807}
808
809MagickExport Image *BlurImageChannel(const Image *image,
810 const ChannelType channel,const double radius,const double sigma,
811 ExceptionInfo *exception)
812{
813#define BlurImageTag "Blur/Image"
814
cristyc4c8d132010-01-07 01:58:38 +0000815 CacheView
816 *blur_view,
817 *image_view;
818
cristy3ed852e2009-09-05 21:47:34 +0000819 double
820 *kernel;
821
822 Image
823 *blur_image;
824
cristy3ed852e2009-09-05 21:47:34 +0000825 MagickBooleanType
826 status;
827
cristybb503372010-05-27 20:51:26 +0000828 MagickOffsetType
829 progress;
830
cristy3ed852e2009-09-05 21:47:34 +0000831 MagickPixelPacket
cristy3ed852e2009-09-05 21:47:34 +0000832 bias;
833
cristybb503372010-05-27 20:51:26 +0000834 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000835 i;
836
cristybb503372010-05-27 20:51:26 +0000837 size_t
cristy3ed852e2009-09-05 21:47:34 +0000838 width;
839
cristybb503372010-05-27 20:51:26 +0000840 ssize_t
841 x,
842 y;
843
cristy3ed852e2009-09-05 21:47:34 +0000844 /*
845 Initialize blur image attributes.
846 */
847 assert(image != (Image *) NULL);
848 assert(image->signature == MagickSignature);
849 if (image->debug != MagickFalse)
850 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
851 assert(exception != (ExceptionInfo *) NULL);
852 assert(exception->signature == MagickSignature);
853 blur_image=CloneImage(image,0,0,MagickTrue,exception);
854 if (blur_image == (Image *) NULL)
855 return((Image *) NULL);
856 if (fabs(sigma) <= MagickEpsilon)
857 return(blur_image);
858 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
859 {
860 InheritException(exception,&blur_image->exception);
861 blur_image=DestroyImage(blur_image);
862 return((Image *) NULL);
863 }
864 width=GetOptimalKernelWidth1D(radius,sigma);
865 kernel=GetBlurKernel(width,sigma);
866 if (kernel == (double *) NULL)
867 {
868 blur_image=DestroyImage(blur_image);
869 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
870 }
871 if (image->debug != MagickFalse)
872 {
873 char
874 format[MaxTextExtent],
875 *message;
876
877 register const double
878 *k;
879
880 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +0000881 " BlurImage with %.20g kernel:",(double) width);
cristy3ed852e2009-09-05 21:47:34 +0000882 message=AcquireString("");
883 k=kernel;
cristybb503372010-05-27 20:51:26 +0000884 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000885 {
886 *message='\0';
cristye8c25f92010-06-03 00:53:06 +0000887 (void) FormatMagickString(format,MaxTextExtent,"%.20g: ",(double) i);
cristy3ed852e2009-09-05 21:47:34 +0000888 (void) ConcatenateString(&message,format);
cristye7f51092010-01-17 00:39:37 +0000889 (void) FormatMagickString(format,MaxTextExtent,"%g ",*k++);
cristy3ed852e2009-09-05 21:47:34 +0000890 (void) ConcatenateString(&message,format);
891 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
892 }
893 message=DestroyString(message);
894 }
895 /*
896 Blur rows.
897 */
898 status=MagickTrue;
899 progress=0;
cristyddd82202009-11-03 20:14:50 +0000900 GetMagickPixelPacket(image,&bias);
901 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000902 image_view=AcquireCacheView(image);
903 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000904#if defined(MAGICKCORE_OPENMP_SUPPORT)
905 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000906#endif
cristybb503372010-05-27 20:51:26 +0000907 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000908 {
909 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000910 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000911
912 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000913 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000914
915 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000916 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000917
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]);
1666 pixels=(Quantum **) RelinquishAlignedMemory(pixels);
1667 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();
1682 pixels=(Quantum **) AcquireAlignedMemory(number_threads,sizeof(*pixels));
1683 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
1809 Quantum
cristyfa112112010-01-04 17:48:07 +00001810 **restrict buffers,
1811 **restrict pixels;
cristy3ed852e2009-09-05 21:47:34 +00001812
1813 size_t
1814 length;
1815
cristy117ff172010-08-15 21:35:32 +00001816 ssize_t
1817 channel;
1818
cristybb503372010-05-27 20:51:26 +00001819 static const ssize_t
cristy691a29e2009-09-11 00:44:10 +00001820 X[4] = {0, 1, 1,-1},
1821 Y[4] = {1, 0, 1, 1};
cristy3ed852e2009-09-05 21:47:34 +00001822
cristy3ed852e2009-09-05 21:47:34 +00001823 /*
1824 Allocate despeckled image.
1825 */
1826 assert(image != (const Image *) NULL);
1827 assert(image->signature == MagickSignature);
1828 if (image->debug != MagickFalse)
1829 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1830 assert(exception != (ExceptionInfo *) NULL);
1831 assert(exception->signature == MagickSignature);
1832 despeckle_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1833 exception);
1834 if (despeckle_image == (Image *) NULL)
1835 return((Image *) NULL);
1836 if (SetImageStorageClass(despeckle_image,DirectClass) == MagickFalse)
1837 {
1838 InheritException(exception,&despeckle_image->exception);
1839 despeckle_image=DestroyImage(despeckle_image);
1840 return((Image *) NULL);
1841 }
1842 /*
1843 Allocate image buffers.
1844 */
1845 length=(size_t) ((image->columns+2)*(image->rows+2));
1846 pixels=AcquirePixelThreadSet(length);
1847 buffers=AcquirePixelThreadSet(length);
1848 if ((pixels == (Quantum **) NULL) || (buffers == (Quantum **) NULL))
1849 {
1850 if (buffers != (Quantum **) NULL)
1851 buffers=DestroyPixelThreadSet(buffers);
1852 if (pixels != (Quantum **) NULL)
1853 pixels=DestroyPixelThreadSet(pixels);
1854 despeckle_image=DestroyImage(despeckle_image);
1855 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1856 }
1857 /*
1858 Reduce speckle in the image.
1859 */
1860 status=MagickTrue;
1861 image_view=AcquireCacheView(image);
1862 despeckle_view=AcquireCacheView(despeckle_image);
cristyb5d5f722009-11-04 03:03:49 +00001863#if defined(MAGICKCORE_OPENMP_SUPPORT)
1864 #pragma omp parallel for schedule(dynamic,4) shared(status)
cristy3ed852e2009-09-05 21:47:34 +00001865#endif
1866 for (channel=0; channel <= 3; channel++)
1867 {
cristy6ebe97c2010-07-03 01:17:28 +00001868 int
1869 id;
1870
cristybb503372010-05-27 20:51:26 +00001871 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001872 i,
1873 x;
1874
1875 register Quantum
1876 *buffer,
1877 *pixel;
1878
cristy117ff172010-08-15 21:35:32 +00001879 ssize_t
1880 j,
1881 y;
1882
cristy3ed852e2009-09-05 21:47:34 +00001883 if (status == MagickFalse)
1884 continue;
cristy691a29e2009-09-11 00:44:10 +00001885 id=GetOpenMPThreadId();
1886 pixel=pixels[id];
cristy3ed852e2009-09-05 21:47:34 +00001887 (void) ResetMagickMemory(pixel,0,length*sizeof(*pixel));
cristy691a29e2009-09-11 00:44:10 +00001888 buffer=buffers[id];
cristybb503372010-05-27 20:51:26 +00001889 j=(ssize_t) image->columns+2;
1890 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001891 {
1892 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001893 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001894
1895 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1896 if (p == (const PixelPacket *) NULL)
1897 break;
1898 j++;
cristybb503372010-05-27 20:51:26 +00001899 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001900 {
1901 switch (channel)
1902 {
cristyce70c172010-01-07 17:15:30 +00001903 case 0: pixel[j]=GetRedPixelComponent(p); break;
1904 case 1: pixel[j]=GetGreenPixelComponent(p); break;
1905 case 2: pixel[j]=GetBluePixelComponent(p); break;
1906 case 3: pixel[j]=GetOpacityPixelComponent(p); break;
cristy3ed852e2009-09-05 21:47:34 +00001907 default: break;
1908 }
1909 p++;
1910 j++;
1911 }
1912 j++;
1913 }
cristy3ed852e2009-09-05 21:47:34 +00001914 (void) ResetMagickMemory(buffer,0,length*sizeof(*buffer));
1915 for (i=0; i < 4; i++)
1916 {
1917 Hull(X[i],Y[i],image->columns,image->rows,pixel,buffer,1);
1918 Hull(-X[i],-Y[i],image->columns,image->rows,pixel,buffer,1);
1919 Hull(-X[i],-Y[i],image->columns,image->rows,pixel,buffer,-1);
1920 Hull(X[i],Y[i],image->columns,image->rows,pixel,buffer,-1);
1921 }
cristybb503372010-05-27 20:51:26 +00001922 j=(ssize_t) image->columns+2;
1923 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001924 {
1925 MagickBooleanType
1926 sync;
1927
1928 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001929 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001930
1931 q=GetCacheViewAuthenticPixels(despeckle_view,0,y,despeckle_image->columns,
1932 1,exception);
1933 if (q == (PixelPacket *) NULL)
1934 break;
1935 j++;
cristybb503372010-05-27 20:51:26 +00001936 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001937 {
1938 switch (channel)
1939 {
1940 case 0: q->red=pixel[j]; break;
1941 case 1: q->green=pixel[j]; break;
1942 case 2: q->blue=pixel[j]; break;
1943 case 3: q->opacity=pixel[j]; break;
1944 default: break;
1945 }
1946 q++;
1947 j++;
1948 }
1949 sync=SyncCacheViewAuthenticPixels(despeckle_view,exception);
1950 if (sync == MagickFalse)
1951 {
1952 status=MagickFalse;
1953 break;
1954 }
1955 j++;
1956 }
1957 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1958 {
1959 MagickBooleanType
1960 proceed;
1961
cristyb5d5f722009-11-04 03:03:49 +00001962#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001963 #pragma omp critical (MagickCore_DespeckleImage)
1964#endif
cristybb503372010-05-27 20:51:26 +00001965 proceed=SetImageProgress(image,DespeckleImageTag,(MagickOffsetType)
1966 channel,3);
cristy3ed852e2009-09-05 21:47:34 +00001967 if (proceed == MagickFalse)
1968 status=MagickFalse;
1969 }
1970 }
1971 despeckle_view=DestroyCacheView(despeckle_view);
1972 image_view=DestroyCacheView(image_view);
1973 buffers=DestroyPixelThreadSet(buffers);
1974 pixels=DestroyPixelThreadSet(pixels);
1975 despeckle_image->type=image->type;
1976 if (status == MagickFalse)
1977 despeckle_image=DestroyImage(despeckle_image);
1978 return(despeckle_image);
1979}
1980
1981/*
1982%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1983% %
1984% %
1985% %
1986% E d g e I m a g e %
1987% %
1988% %
1989% %
1990%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1991%
1992% EdgeImage() finds edges in an image. Radius defines the radius of the
1993% convolution filter. Use a radius of 0 and EdgeImage() selects a suitable
1994% radius for you.
1995%
1996% The format of the EdgeImage method is:
1997%
1998% Image *EdgeImage(const Image *image,const double radius,
1999% ExceptionInfo *exception)
2000%
2001% A description of each parameter follows:
2002%
2003% o image: the image.
2004%
2005% o radius: the radius of the pixel neighborhood.
2006%
2007% o exception: return any errors or warnings in this structure.
2008%
2009*/
2010MagickExport Image *EdgeImage(const Image *image,const double radius,
2011 ExceptionInfo *exception)
2012{
2013 Image
2014 *edge_image;
2015
2016 double
2017 *kernel;
2018
cristybb503372010-05-27 20:51:26 +00002019 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002020 i;
2021
cristybb503372010-05-27 20:51:26 +00002022 size_t
cristy3ed852e2009-09-05 21:47:34 +00002023 width;
2024
2025 assert(image != (const Image *) NULL);
2026 assert(image->signature == MagickSignature);
2027 if (image->debug != MagickFalse)
2028 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2029 assert(exception != (ExceptionInfo *) NULL);
2030 assert(exception->signature == MagickSignature);
2031 width=GetOptimalKernelWidth1D(radius,0.5);
2032 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2033 if (kernel == (double *) NULL)
2034 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00002035 for (i=0; i < (ssize_t) (width*width); i++)
cristy3ed852e2009-09-05 21:47:34 +00002036 kernel[i]=(-1.0);
2037 kernel[i/2]=(double) (width*width-1.0);
2038 edge_image=ConvolveImage(image,width,kernel,exception);
2039 kernel=(double *) RelinquishMagickMemory(kernel);
2040 return(edge_image);
2041}
2042
2043/*
2044%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2045% %
2046% %
2047% %
2048% E m b o s s I m a g e %
2049% %
2050% %
2051% %
2052%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2053%
2054% EmbossImage() returns a grayscale image with a three-dimensional effect.
2055% We convolve the image with a Gaussian operator of the given radius and
2056% standard deviation (sigma). For reasonable results, radius should be
2057% larger than sigma. Use a radius of 0 and Emboss() selects a suitable
2058% radius for you.
2059%
2060% The format of the EmbossImage method is:
2061%
2062% Image *EmbossImage(const Image *image,const double radius,
2063% const double sigma,ExceptionInfo *exception)
2064%
2065% A description of each parameter follows:
2066%
2067% o image: the image.
2068%
2069% o radius: the radius of the pixel neighborhood.
2070%
2071% o sigma: the standard deviation of the Gaussian, in pixels.
2072%
2073% o exception: return any errors or warnings in this structure.
2074%
2075*/
2076MagickExport Image *EmbossImage(const Image *image,const double radius,
2077 const double sigma,ExceptionInfo *exception)
2078{
2079 double
2080 *kernel;
2081
2082 Image
2083 *emboss_image;
2084
cristybb503372010-05-27 20:51:26 +00002085 register ssize_t
cristy47e00502009-12-17 19:19:57 +00002086 i;
2087
cristybb503372010-05-27 20:51:26 +00002088 size_t
cristy3ed852e2009-09-05 21:47:34 +00002089 width;
2090
cristy117ff172010-08-15 21:35:32 +00002091 ssize_t
2092 j,
2093 k,
2094 u,
2095 v;
2096
cristy3ed852e2009-09-05 21:47:34 +00002097 assert(image != (Image *) NULL);
2098 assert(image->signature == MagickSignature);
2099 if (image->debug != MagickFalse)
2100 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2101 assert(exception != (ExceptionInfo *) NULL);
2102 assert(exception->signature == MagickSignature);
2103 width=GetOptimalKernelWidth2D(radius,sigma);
2104 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2105 if (kernel == (double *) NULL)
2106 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00002107 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +00002108 k=j;
2109 i=0;
2110 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002111 {
cristy47e00502009-12-17 19:19:57 +00002112 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00002113 {
cristy47e00502009-12-17 19:19:57 +00002114 kernel[i]=((u < 0) || (v < 0) ? -8.0 : 8.0)*
2115 exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
2116 (2.0*MagickPI*MagickSigma*MagickSigma);
2117 if (u != k)
cristy3ed852e2009-09-05 21:47:34 +00002118 kernel[i]=0.0;
2119 i++;
2120 }
cristy47e00502009-12-17 19:19:57 +00002121 k--;
cristy3ed852e2009-09-05 21:47:34 +00002122 }
2123 emboss_image=ConvolveImage(image,width,kernel,exception);
2124 if (emboss_image != (Image *) NULL)
2125 (void) EqualizeImage(emboss_image);
2126 kernel=(double *) RelinquishMagickMemory(kernel);
2127 return(emboss_image);
2128}
2129
2130/*
2131%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2132% %
2133% %
2134% %
cristy56a9e512010-01-06 18:18:55 +00002135% F i l t e r I m a g e %
2136% %
2137% %
2138% %
2139%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2140%
2141% FilterImage() applies a custom convolution kernel to the image.
2142%
2143% The format of the FilterImage method is:
2144%
cristy2be15382010-01-21 02:38:03 +00002145% Image *FilterImage(const Image *image,const KernelInfo *kernel,
cristy56a9e512010-01-06 18:18:55 +00002146% ExceptionInfo *exception)
2147% Image *FilterImageChannel(const Image *image,const ChannelType channel,
cristy2be15382010-01-21 02:38:03 +00002148% const KernelInfo *kernel,ExceptionInfo *exception)
cristy56a9e512010-01-06 18:18:55 +00002149%
2150% A description of each parameter follows:
2151%
2152% o image: the image.
2153%
2154% o channel: the channel type.
2155%
2156% o kernel: the filtering kernel.
2157%
2158% o exception: return any errors or warnings in this structure.
2159%
2160*/
2161
cristy2be15382010-01-21 02:38:03 +00002162MagickExport Image *FilterImage(const Image *image,const KernelInfo *kernel,
cristy56a9e512010-01-06 18:18:55 +00002163 ExceptionInfo *exception)
2164{
2165 Image
2166 *filter_image;
2167
2168 filter_image=FilterImageChannel(image,DefaultChannels,kernel,exception);
2169 return(filter_image);
2170}
2171
2172MagickExport Image *FilterImageChannel(const Image *image,
cristy2be15382010-01-21 02:38:03 +00002173 const ChannelType channel,const KernelInfo *kernel,ExceptionInfo *exception)
cristy56a9e512010-01-06 18:18:55 +00002174{
2175#define FilterImageTag "Filter/Image"
2176
2177 CacheView
2178 *filter_view,
2179 *image_view;
2180
cristy56a9e512010-01-06 18:18:55 +00002181 Image
2182 *filter_image;
2183
cristy56a9e512010-01-06 18:18:55 +00002184 MagickBooleanType
2185 status;
2186
cristybb503372010-05-27 20:51:26 +00002187 MagickOffsetType
2188 progress;
2189
cristy56a9e512010-01-06 18:18:55 +00002190 MagickPixelPacket
2191 bias;
2192
cristybb503372010-05-27 20:51:26 +00002193 ssize_t
2194 y;
2195
cristy56a9e512010-01-06 18:18:55 +00002196 /*
2197 Initialize filter image attributes.
2198 */
2199 assert(image != (Image *) NULL);
2200 assert(image->signature == MagickSignature);
2201 if (image->debug != MagickFalse)
2202 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2203 assert(exception != (ExceptionInfo *) NULL);
2204 assert(exception->signature == MagickSignature);
2205 if ((kernel->width % 2) == 0)
2206 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
2207 filter_image=CloneImage(image,0,0,MagickTrue,exception);
2208 if (filter_image == (Image *) NULL)
2209 return((Image *) NULL);
2210 if (SetImageStorageClass(filter_image,DirectClass) == MagickFalse)
2211 {
2212 InheritException(exception,&filter_image->exception);
2213 filter_image=DestroyImage(filter_image);
2214 return((Image *) NULL);
2215 }
2216 if (image->debug != MagickFalse)
2217 {
2218 char
2219 format[MaxTextExtent],
2220 *message;
2221
cristy117ff172010-08-15 21:35:32 +00002222 register const double
2223 *k;
2224
cristybb503372010-05-27 20:51:26 +00002225 ssize_t
cristy56a9e512010-01-06 18:18:55 +00002226 u,
2227 v;
2228
cristy56a9e512010-01-06 18:18:55 +00002229 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00002230 " FilterImage with %.20gx%.20g kernel:",(double) kernel->width,(double)
2231 kernel->height);
cristy56a9e512010-01-06 18:18:55 +00002232 message=AcquireString("");
2233 k=kernel->values;
cristybb503372010-05-27 20:51:26 +00002234 for (v=0; v < (ssize_t) kernel->height; v++)
cristy56a9e512010-01-06 18:18:55 +00002235 {
2236 *message='\0';
cristye8c25f92010-06-03 00:53:06 +00002237 (void) FormatMagickString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy56a9e512010-01-06 18:18:55 +00002238 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00002239 for (u=0; u < (ssize_t) kernel->width; u++)
cristy56a9e512010-01-06 18:18:55 +00002240 {
cristye7f51092010-01-17 00:39:37 +00002241 (void) FormatMagickString(format,MaxTextExtent,"%g ",*k++);
cristy56a9e512010-01-06 18:18:55 +00002242 (void) ConcatenateString(&message,format);
2243 }
2244 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
2245 }
2246 message=DestroyString(message);
2247 }
cristy36826ab2010-03-06 01:29:30 +00002248 status=AccelerateConvolveImage(image,kernel,filter_image,exception);
cristyd43a46b2010-01-21 02:13:41 +00002249 if (status == MagickTrue)
2250 return(filter_image);
cristy56a9e512010-01-06 18:18:55 +00002251 /*
2252 Filter image.
2253 */
2254 status=MagickTrue;
2255 progress=0;
2256 GetMagickPixelPacket(image,&bias);
2257 SetMagickPixelPacketBias(image,&bias);
2258 image_view=AcquireCacheView(image);
2259 filter_view=AcquireCacheView(filter_image);
2260#if defined(MAGICKCORE_OPENMP_SUPPORT)
2261 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2262#endif
cristybb503372010-05-27 20:51:26 +00002263 for (y=0; y < (ssize_t) image->rows; y++)
cristy56a9e512010-01-06 18:18:55 +00002264 {
2265 MagickBooleanType
2266 sync;
2267
2268 register const IndexPacket
2269 *restrict indexes;
2270
2271 register const PixelPacket
2272 *restrict p;
2273
2274 register IndexPacket
2275 *restrict filter_indexes;
2276
cristy56a9e512010-01-06 18:18:55 +00002277 register PixelPacket
2278 *restrict q;
2279
cristy117ff172010-08-15 21:35:32 +00002280 register ssize_t
2281 x;
2282
cristy56a9e512010-01-06 18:18:55 +00002283 if (status == MagickFalse)
2284 continue;
cristybb503372010-05-27 20:51:26 +00002285 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) kernel->width/2L),
cristy117ff172010-08-15 21:35:32 +00002286 y-(ssize_t) (kernel->height/2L),image->columns+kernel->width,
2287 kernel->height,exception);
cristy56a9e512010-01-06 18:18:55 +00002288 q=GetCacheViewAuthenticPixels(filter_view,0,y,filter_image->columns,1,
2289 exception);
2290 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2291 {
2292 status=MagickFalse;
2293 continue;
2294 }
2295 indexes=GetCacheViewVirtualIndexQueue(image_view);
2296 filter_indexes=GetCacheViewAuthenticIndexQueue(filter_view);
cristybb503372010-05-27 20:51:26 +00002297 for (x=0; x < (ssize_t) image->columns; x++)
cristy56a9e512010-01-06 18:18:55 +00002298 {
cristy56a9e512010-01-06 18:18:55 +00002299 MagickPixelPacket
2300 pixel;
2301
2302 register const double
2303 *restrict k;
2304
2305 register const PixelPacket
2306 *restrict kernel_pixels;
2307
cristybb503372010-05-27 20:51:26 +00002308 register ssize_t
cristy56a9e512010-01-06 18:18:55 +00002309 u;
2310
cristy117ff172010-08-15 21:35:32 +00002311 ssize_t
2312 v;
2313
cristy56a9e512010-01-06 18:18:55 +00002314 pixel=bias;
cristy36826ab2010-03-06 01:29:30 +00002315 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002316 kernel_pixels=p;
2317 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
2318 {
cristybb503372010-05-27 20:51:26 +00002319 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002320 {
cristybb503372010-05-27 20:51:26 +00002321 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002322 {
2323 pixel.red+=(*k)*kernel_pixels[u].red;
2324 pixel.green+=(*k)*kernel_pixels[u].green;
2325 pixel.blue+=(*k)*kernel_pixels[u].blue;
2326 k++;
2327 }
cristy36826ab2010-03-06 01:29:30 +00002328 kernel_pixels+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002329 }
2330 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002331 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002332 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002333 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002334 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002335 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002336 if ((channel & OpacityChannel) != 0)
2337 {
cristy36826ab2010-03-06 01:29:30 +00002338 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002339 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00002340 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002341 {
cristybb503372010-05-27 20:51:26 +00002342 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002343 {
2344 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
2345 k++;
2346 }
cristy36826ab2010-03-06 01:29:30 +00002347 kernel_pixels+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002348 }
cristyce70c172010-01-07 17:15:30 +00002349 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002350 }
2351 if (((channel & IndexChannel) != 0) &&
2352 (image->colorspace == CMYKColorspace))
2353 {
2354 register const IndexPacket
2355 *restrict kernel_indexes;
2356
cristy36826ab2010-03-06 01:29:30 +00002357 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002358 kernel_indexes=indexes;
cristybb503372010-05-27 20:51:26 +00002359 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002360 {
cristybb503372010-05-27 20:51:26 +00002361 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002362 {
2363 pixel.index+=(*k)*kernel_indexes[u];
2364 k++;
2365 }
cristy36826ab2010-03-06 01:29:30 +00002366 kernel_indexes+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002367 }
cristyce70c172010-01-07 17:15:30 +00002368 filter_indexes[x]=ClampToQuantum(pixel.index);
cristy56a9e512010-01-06 18:18:55 +00002369 }
2370 }
2371 else
2372 {
2373 MagickRealType
2374 alpha,
2375 gamma;
2376
2377 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00002378 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002379 {
cristybb503372010-05-27 20:51:26 +00002380 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002381 {
2382 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
2383 kernel_pixels[u].opacity));
2384 pixel.red+=(*k)*alpha*kernel_pixels[u].red;
2385 pixel.green+=(*k)*alpha*kernel_pixels[u].green;
2386 pixel.blue+=(*k)*alpha*kernel_pixels[u].blue;
2387 gamma+=(*k)*alpha;
2388 k++;
2389 }
cristy36826ab2010-03-06 01:29:30 +00002390 kernel_pixels+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002391 }
2392 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
2393 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002394 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002395 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002396 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002397 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002398 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002399 if ((channel & OpacityChannel) != 0)
2400 {
cristy36826ab2010-03-06 01:29:30 +00002401 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002402 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00002403 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002404 {
cristybb503372010-05-27 20:51:26 +00002405 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002406 {
2407 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
2408 k++;
2409 }
cristy36826ab2010-03-06 01:29:30 +00002410 kernel_pixels+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002411 }
cristyce70c172010-01-07 17:15:30 +00002412 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002413 }
2414 if (((channel & IndexChannel) != 0) &&
2415 (image->colorspace == CMYKColorspace))
2416 {
2417 register const IndexPacket
2418 *restrict kernel_indexes;
2419
cristy36826ab2010-03-06 01:29:30 +00002420 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002421 kernel_pixels=p;
2422 kernel_indexes=indexes;
cristybb503372010-05-27 20:51:26 +00002423 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002424 {
cristybb503372010-05-27 20:51:26 +00002425 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002426 {
2427 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
2428 kernel_pixels[u].opacity));
2429 pixel.index+=(*k)*alpha*kernel_indexes[u];
2430 k++;
2431 }
cristy36826ab2010-03-06 01:29:30 +00002432 kernel_pixels+=image->columns+kernel->width;
2433 kernel_indexes+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002434 }
cristy2115aea2010-01-09 23:16:08 +00002435 filter_indexes[x]=ClampToQuantum(gamma*
2436 GetIndexPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002437 }
2438 }
2439 p++;
2440 q++;
2441 }
2442 sync=SyncCacheViewAuthenticPixels(filter_view,exception);
2443 if (sync == MagickFalse)
2444 status=MagickFalse;
2445 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2446 {
2447 MagickBooleanType
2448 proceed;
2449
2450#if defined(MAGICKCORE_OPENMP_SUPPORT)
2451 #pragma omp critical (MagickCore_FilterImageChannel)
2452#endif
2453 proceed=SetImageProgress(image,FilterImageTag,progress++,image->rows);
2454 if (proceed == MagickFalse)
2455 status=MagickFalse;
2456 }
2457 }
2458 filter_image->type=image->type;
2459 filter_view=DestroyCacheView(filter_view);
2460 image_view=DestroyCacheView(image_view);
cristy56a9e512010-01-06 18:18:55 +00002461 if (status == MagickFalse)
2462 filter_image=DestroyImage(filter_image);
2463 return(filter_image);
2464}
2465
2466/*
2467%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2468% %
2469% %
2470% %
cristy3ed852e2009-09-05 21:47:34 +00002471% G a u s s i a n B l u r I m a g e %
2472% %
2473% %
2474% %
2475%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2476%
2477% GaussianBlurImage() blurs an image. We convolve the image with a
2478% Gaussian operator of the given radius and standard deviation (sigma).
2479% For reasonable results, the radius should be larger than sigma. Use a
2480% radius of 0 and GaussianBlurImage() selects a suitable radius for you
2481%
2482% The format of the GaussianBlurImage method is:
2483%
2484% Image *GaussianBlurImage(const Image *image,onst double radius,
2485% const double sigma,ExceptionInfo *exception)
2486% Image *GaussianBlurImageChannel(const Image *image,
2487% const ChannelType channel,const double radius,const double sigma,
2488% ExceptionInfo *exception)
2489%
2490% A description of each parameter follows:
2491%
2492% o image: the image.
2493%
2494% o channel: the channel type.
2495%
2496% o radius: the radius of the Gaussian, in pixels, not counting the center
2497% pixel.
2498%
2499% o sigma: the standard deviation of the Gaussian, in pixels.
2500%
2501% o exception: return any errors or warnings in this structure.
2502%
2503*/
2504
2505MagickExport Image *GaussianBlurImage(const Image *image,const double radius,
2506 const double sigma,ExceptionInfo *exception)
2507{
2508 Image
2509 *blur_image;
2510
2511 blur_image=GaussianBlurImageChannel(image,DefaultChannels,radius,sigma,
2512 exception);
2513 return(blur_image);
2514}
2515
2516MagickExport Image *GaussianBlurImageChannel(const Image *image,
2517 const ChannelType channel,const double radius,const double sigma,
2518 ExceptionInfo *exception)
2519{
2520 double
2521 *kernel;
2522
2523 Image
2524 *blur_image;
2525
cristybb503372010-05-27 20:51:26 +00002526 register ssize_t
cristy47e00502009-12-17 19:19:57 +00002527 i;
2528
cristybb503372010-05-27 20:51:26 +00002529 size_t
cristy3ed852e2009-09-05 21:47:34 +00002530 width;
2531
cristy117ff172010-08-15 21:35:32 +00002532 ssize_t
2533 j,
2534 u,
2535 v;
2536
cristy3ed852e2009-09-05 21:47:34 +00002537 assert(image != (const Image *) NULL);
2538 assert(image->signature == MagickSignature);
2539 if (image->debug != MagickFalse)
2540 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2541 assert(exception != (ExceptionInfo *) NULL);
2542 assert(exception->signature == MagickSignature);
2543 width=GetOptimalKernelWidth2D(radius,sigma);
2544 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2545 if (kernel == (double *) NULL)
2546 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00002547 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00002548 i=0;
cristy47e00502009-12-17 19:19:57 +00002549 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002550 {
cristy47e00502009-12-17 19:19:57 +00002551 for (u=(-j); u <= j; u++)
2552 kernel[i++]=exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
2553 (2.0*MagickPI*MagickSigma*MagickSigma);
cristy3ed852e2009-09-05 21:47:34 +00002554 }
2555 blur_image=ConvolveImageChannel(image,channel,width,kernel,exception);
2556 kernel=(double *) RelinquishMagickMemory(kernel);
2557 return(blur_image);
2558}
2559
2560/*
2561%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2562% %
2563% %
2564% %
2565% M e d i a n F i l t e r I m a g e %
2566% %
2567% %
2568% %
2569%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2570%
2571% MedianFilterImage() applies a digital filter that improves the quality
2572% of a noisy image. Each pixel is replaced by the median in a set of
2573% neighboring pixels as defined by radius.
2574%
2575% The algorithm was contributed by Mike Edmonds and implements an insertion
2576% sort for selecting median color-channel values. For more on this algorithm
2577% see "Skip Lists: A probabilistic Alternative to Balanced Trees" by William
2578% Pugh in the June 1990 of Communications of the ACM.
2579%
2580% The format of the MedianFilterImage method is:
2581%
2582% Image *MedianFilterImage(const Image *image,const double radius,
2583% ExceptionInfo *exception)
2584%
2585% A description of each parameter follows:
2586%
2587% o image: the image.
2588%
2589% o radius: the radius of the pixel neighborhood.
2590%
2591% o exception: return any errors or warnings in this structure.
2592%
2593*/
2594
2595#define MedianListChannels 5
2596
2597typedef struct _MedianListNode
2598{
cristybb503372010-05-27 20:51:26 +00002599 size_t
cristy3ed852e2009-09-05 21:47:34 +00002600 next[9],
2601 count,
2602 signature;
2603} MedianListNode;
2604
2605typedef struct _MedianSkipList
2606{
cristybb503372010-05-27 20:51:26 +00002607 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002608 level;
2609
2610 MedianListNode
2611 *nodes;
2612} MedianSkipList;
2613
2614typedef struct _MedianPixelList
2615{
cristybb503372010-05-27 20:51:26 +00002616 size_t
cristy3ed852e2009-09-05 21:47:34 +00002617 center,
2618 seed,
2619 signature;
2620
2621 MedianSkipList
2622 lists[MedianListChannels];
2623} MedianPixelList;
2624
2625static MedianPixelList *DestroyMedianPixelList(MedianPixelList *pixel_list)
2626{
cristybb503372010-05-27 20:51:26 +00002627 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002628 i;
2629
2630 if (pixel_list == (MedianPixelList *) NULL)
2631 return((MedianPixelList *) NULL);
2632 for (i=0; i < MedianListChannels; i++)
2633 if (pixel_list->lists[i].nodes != (MedianListNode *) NULL)
2634 pixel_list->lists[i].nodes=(MedianListNode *) RelinquishMagickMemory(
2635 pixel_list->lists[i].nodes);
2636 pixel_list=(MedianPixelList *) RelinquishAlignedMemory(pixel_list);
2637 return(pixel_list);
2638}
2639
2640static MedianPixelList **DestroyMedianPixelListThreadSet(
2641 MedianPixelList **pixel_list)
2642{
cristybb503372010-05-27 20:51:26 +00002643 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002644 i;
2645
2646 assert(pixel_list != (MedianPixelList **) NULL);
cristybb503372010-05-27 20:51:26 +00002647 for (i=0; i < (ssize_t) GetOpenMPMaximumThreads(); i++)
cristy3ed852e2009-09-05 21:47:34 +00002648 if (pixel_list[i] != (MedianPixelList *) NULL)
2649 pixel_list[i]=DestroyMedianPixelList(pixel_list[i]);
2650 pixel_list=(MedianPixelList **) RelinquishAlignedMemory(pixel_list);
2651 return(pixel_list);
2652}
2653
cristybb503372010-05-27 20:51:26 +00002654static MedianPixelList *AcquireMedianPixelList(const size_t width)
cristy3ed852e2009-09-05 21:47:34 +00002655{
2656 MedianPixelList
2657 *pixel_list;
2658
cristybb503372010-05-27 20:51:26 +00002659 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002660 i;
2661
2662 pixel_list=(MedianPixelList *) AcquireAlignedMemory(1,sizeof(*pixel_list));
2663 if (pixel_list == (MedianPixelList *) NULL)
2664 return(pixel_list);
2665 (void) ResetMagickMemory((void *) pixel_list,0,sizeof(*pixel_list));
2666 pixel_list->center=width*width/2;
2667 for (i=0; i < MedianListChannels; i++)
2668 {
2669 pixel_list->lists[i].nodes=(MedianListNode *) AcquireQuantumMemory(65537UL,
2670 sizeof(*pixel_list->lists[i].nodes));
2671 if (pixel_list->lists[i].nodes == (MedianListNode *) NULL)
2672 return(DestroyMedianPixelList(pixel_list));
2673 (void) ResetMagickMemory(pixel_list->lists[i].nodes,0,65537UL*
2674 sizeof(*pixel_list->lists[i].nodes));
2675 }
2676 pixel_list->signature=MagickSignature;
2677 return(pixel_list);
2678}
2679
cristy117ff172010-08-15 21:35:32 +00002680static MedianPixelList **AcquireMedianPixelListThreadSet(const size_t width)
cristy3ed852e2009-09-05 21:47:34 +00002681{
cristy3ed852e2009-09-05 21:47:34 +00002682 MedianPixelList
2683 **pixel_list;
2684
cristy117ff172010-08-15 21:35:32 +00002685 register ssize_t
2686 i;
2687
cristybb503372010-05-27 20:51:26 +00002688 size_t
cristy3ed852e2009-09-05 21:47:34 +00002689 number_threads;
2690
2691 number_threads=GetOpenMPMaximumThreads();
2692 pixel_list=(MedianPixelList **) AcquireAlignedMemory(number_threads,
2693 sizeof(*pixel_list));
2694 if (pixel_list == (MedianPixelList **) NULL)
2695 return((MedianPixelList **) NULL);
2696 (void) ResetMagickMemory(pixel_list,0,number_threads*sizeof(*pixel_list));
cristybb503372010-05-27 20:51:26 +00002697 for (i=0; i < (ssize_t) number_threads; i++)
cristy3ed852e2009-09-05 21:47:34 +00002698 {
2699 pixel_list[i]=AcquireMedianPixelList(width);
2700 if (pixel_list[i] == (MedianPixelList *) NULL)
2701 return(DestroyMedianPixelListThreadSet(pixel_list));
2702 }
2703 return(pixel_list);
2704}
2705
2706static void AddNodeMedianPixelList(MedianPixelList *pixel_list,
cristybb503372010-05-27 20:51:26 +00002707 const ssize_t channel,const size_t color)
cristy3ed852e2009-09-05 21:47:34 +00002708{
cristy3ed852e2009-09-05 21:47:34 +00002709 register MedianSkipList
2710 *list;
2711
cristy117ff172010-08-15 21:35:32 +00002712 register ssize_t
2713 level;
2714
cristybb503372010-05-27 20:51:26 +00002715 size_t
cristy3ed852e2009-09-05 21:47:34 +00002716 search,
2717 update[9];
2718
2719 /*
2720 Initialize the node.
2721 */
2722 list=pixel_list->lists+channel;
2723 list->nodes[color].signature=pixel_list->signature;
2724 list->nodes[color].count=1;
2725 /*
cristy33c53022010-06-25 12:17:27 +00002726 Determine where it belongs in the list.
cristy3ed852e2009-09-05 21:47:34 +00002727 */
2728 search=65536UL;
2729 for (level=list->level; level >= 0; level--)
2730 {
2731 while (list->nodes[search].next[level] < color)
2732 search=list->nodes[search].next[level];
2733 update[level]=search;
2734 }
2735 /*
2736 Generate a pseudo-random level for this node.
2737 */
2738 for (level=0; ; level++)
2739 {
2740 pixel_list->seed=(pixel_list->seed*42893621L)+1L;
2741 if ((pixel_list->seed & 0x300) != 0x300)
2742 break;
2743 }
2744 if (level > 8)
2745 level=8;
2746 if (level > (list->level+2))
2747 level=list->level+2;
2748 /*
2749 If we're raising the list's level, link back to the root node.
2750 */
2751 while (level > list->level)
2752 {
2753 list->level++;
2754 update[list->level]=65536UL;
2755 }
2756 /*
2757 Link the node into the skip-list.
2758 */
2759 do
2760 {
2761 list->nodes[color].next[level]=list->nodes[update[level]].next[level];
2762 list->nodes[update[level]].next[level]=color;
2763 }
2764 while (level-- > 0);
2765}
2766
2767static MagickPixelPacket GetMedianPixelList(MedianPixelList *pixel_list)
2768{
2769 MagickPixelPacket
2770 pixel;
2771
cristy3ed852e2009-09-05 21:47:34 +00002772 register MedianSkipList
2773 *list;
2774
cristy117ff172010-08-15 21:35:32 +00002775 register ssize_t
2776 channel;
2777
cristybb503372010-05-27 20:51:26 +00002778 size_t
cristy3ed852e2009-09-05 21:47:34 +00002779 center,
2780 color,
2781 count;
2782
2783 unsigned short
2784 channels[MedianListChannels];
2785
2786 /*
2787 Find the median value for each of the color.
2788 */
2789 center=pixel_list->center;
2790 for (channel=0; channel < 5; channel++)
2791 {
2792 list=pixel_list->lists+channel;
2793 color=65536UL;
2794 count=0;
2795 do
2796 {
2797 color=list->nodes[color].next[0];
2798 count+=list->nodes[color].count;
2799 }
2800 while (count <= center);
2801 channels[channel]=(unsigned short) color;
2802 }
2803 GetMagickPixelPacket((const Image *) NULL,&pixel);
2804 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
2805 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
2806 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
2807 pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
2808 pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]);
2809 return(pixel);
2810}
2811
2812static inline void InsertMedianPixelList(const Image *image,
2813 const PixelPacket *pixel,const IndexPacket *indexes,
2814 MedianPixelList *pixel_list)
2815{
cristybb503372010-05-27 20:51:26 +00002816 size_t
cristy3ed852e2009-09-05 21:47:34 +00002817 signature;
2818
2819 unsigned short
2820 index;
2821
2822 index=ScaleQuantumToShort(pixel->red);
2823 signature=pixel_list->lists[0].nodes[index].signature;
2824 if (signature == pixel_list->signature)
2825 pixel_list->lists[0].nodes[index].count++;
2826 else
2827 AddNodeMedianPixelList(pixel_list,0,index);
2828 index=ScaleQuantumToShort(pixel->green);
2829 signature=pixel_list->lists[1].nodes[index].signature;
2830 if (signature == pixel_list->signature)
2831 pixel_list->lists[1].nodes[index].count++;
2832 else
2833 AddNodeMedianPixelList(pixel_list,1,index);
2834 index=ScaleQuantumToShort(pixel->blue);
2835 signature=pixel_list->lists[2].nodes[index].signature;
2836 if (signature == pixel_list->signature)
2837 pixel_list->lists[2].nodes[index].count++;
2838 else
2839 AddNodeMedianPixelList(pixel_list,2,index);
2840 index=ScaleQuantumToShort(pixel->opacity);
2841 signature=pixel_list->lists[3].nodes[index].signature;
2842 if (signature == pixel_list->signature)
2843 pixel_list->lists[3].nodes[index].count++;
2844 else
2845 AddNodeMedianPixelList(pixel_list,3,index);
2846 if (image->colorspace == CMYKColorspace)
2847 index=ScaleQuantumToShort(*indexes);
2848 signature=pixel_list->lists[4].nodes[index].signature;
2849 if (signature == pixel_list->signature)
2850 pixel_list->lists[4].nodes[index].count++;
2851 else
2852 AddNodeMedianPixelList(pixel_list,4,index);
2853}
2854
2855static void ResetMedianPixelList(MedianPixelList *pixel_list)
2856{
2857 int
2858 level;
2859
cristy3ed852e2009-09-05 21:47:34 +00002860 register MedianListNode
2861 *root;
2862
2863 register MedianSkipList
2864 *list;
2865
cristy117ff172010-08-15 21:35:32 +00002866 register ssize_t
2867 channel;
2868
cristy3ed852e2009-09-05 21:47:34 +00002869 /*
2870 Reset the skip-list.
2871 */
2872 for (channel=0; channel < 5; channel++)
2873 {
2874 list=pixel_list->lists+channel;
2875 root=list->nodes+65536UL;
2876 list->level=0;
2877 for (level=0; level < 9; level++)
2878 root->next[level]=65536UL;
2879 }
2880 pixel_list->seed=pixel_list->signature++;
2881}
2882
2883MagickExport Image *MedianFilterImage(const Image *image,const double radius,
2884 ExceptionInfo *exception)
2885{
2886#define MedianFilterImageTag "MedianFilter/Image"
2887
cristyc4c8d132010-01-07 01:58:38 +00002888 CacheView
2889 *image_view,
2890 *median_view;
2891
cristy3ed852e2009-09-05 21:47:34 +00002892 Image
2893 *median_image;
2894
cristy3ed852e2009-09-05 21:47:34 +00002895 MagickBooleanType
2896 status;
2897
cristybb503372010-05-27 20:51:26 +00002898 MagickOffsetType
2899 progress;
2900
cristy3ed852e2009-09-05 21:47:34 +00002901 MedianPixelList
cristyfa112112010-01-04 17:48:07 +00002902 **restrict pixel_list;
cristy3ed852e2009-09-05 21:47:34 +00002903
cristybb503372010-05-27 20:51:26 +00002904 size_t
cristy3ed852e2009-09-05 21:47:34 +00002905 width;
2906
cristybb503372010-05-27 20:51:26 +00002907 ssize_t
2908 y;
2909
cristy3ed852e2009-09-05 21:47:34 +00002910 /*
2911 Initialize median image attributes.
2912 */
2913 assert(image != (Image *) NULL);
2914 assert(image->signature == MagickSignature);
2915 if (image->debug != MagickFalse)
2916 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2917 assert(exception != (ExceptionInfo *) NULL);
2918 assert(exception->signature == MagickSignature);
2919 width=GetOptimalKernelWidth2D(radius,0.5);
2920 if ((image->columns < width) || (image->rows < width))
2921 ThrowImageException(OptionError,"ImageSmallerThanKernelRadius");
2922 median_image=CloneImage(image,image->columns,image->rows,MagickTrue,
2923 exception);
2924 if (median_image == (Image *) NULL)
2925 return((Image *) NULL);
2926 if (SetImageStorageClass(median_image,DirectClass) == MagickFalse)
2927 {
2928 InheritException(exception,&median_image->exception);
2929 median_image=DestroyImage(median_image);
2930 return((Image *) NULL);
2931 }
2932 pixel_list=AcquireMedianPixelListThreadSet(width);
2933 if (pixel_list == (MedianPixelList **) NULL)
2934 {
2935 median_image=DestroyImage(median_image);
2936 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2937 }
2938 /*
2939 Median filter each image row.
2940 */
2941 status=MagickTrue;
2942 progress=0;
2943 image_view=AcquireCacheView(image);
2944 median_view=AcquireCacheView(median_image);
cristyb5d5f722009-11-04 03:03:49 +00002945#if defined(MAGICKCORE_OPENMP_SUPPORT)
2946 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002947#endif
cristybb503372010-05-27 20:51:26 +00002948 for (y=0; y < (ssize_t) median_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002949 {
cristy6ebe97c2010-07-03 01:17:28 +00002950 int
2951 id;
2952
cristy3ed852e2009-09-05 21:47:34 +00002953 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002954 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002955
2956 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002957 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002958
2959 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002960 *restrict median_indexes;
cristy3ed852e2009-09-05 21:47:34 +00002961
cristy3ed852e2009-09-05 21:47:34 +00002962 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002963 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002964
cristy117ff172010-08-15 21:35:32 +00002965 register ssize_t
2966 x;
2967
cristy3ed852e2009-09-05 21:47:34 +00002968 if (status == MagickFalse)
2969 continue;
cristy6ebe97c2010-07-03 01:17:28 +00002970 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
2971 (width/2L),image->columns+width,width,exception);
cristy3ed852e2009-09-05 21:47:34 +00002972 q=QueueCacheViewAuthenticPixels(median_view,0,y,median_image->columns,1,
2973 exception);
2974 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2975 {
2976 status=MagickFalse;
2977 continue;
2978 }
2979 indexes=GetCacheViewVirtualIndexQueue(image_view);
2980 median_indexes=GetCacheViewAuthenticIndexQueue(median_view);
2981 id=GetOpenMPThreadId();
cristybb503372010-05-27 20:51:26 +00002982 for (x=0; x < (ssize_t) median_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002983 {
2984 MagickPixelPacket
2985 pixel;
2986
cristy3ed852e2009-09-05 21:47:34 +00002987 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002988 *restrict s;
cristy3ed852e2009-09-05 21:47:34 +00002989
cristy117ff172010-08-15 21:35:32 +00002990 register const PixelPacket
2991 *restrict r;
2992
cristybb503372010-05-27 20:51:26 +00002993 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002994 u,
2995 v;
2996
2997 r=p;
2998 s=indexes+x;
2999 ResetMedianPixelList(pixel_list[id]);
cristybb503372010-05-27 20:51:26 +00003000 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003001 {
cristybb503372010-05-27 20:51:26 +00003002 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003003 InsertMedianPixelList(image,r+u,s+u,pixel_list[id]);
3004 r+=image->columns+width;
3005 s+=image->columns+width;
3006 }
3007 pixel=GetMedianPixelList(pixel_list[id]);
3008 SetPixelPacket(median_image,&pixel,q,median_indexes+x);
3009 p++;
3010 q++;
3011 }
3012 if (SyncCacheViewAuthenticPixels(median_view,exception) == MagickFalse)
3013 status=MagickFalse;
3014 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3015 {
3016 MagickBooleanType
3017 proceed;
3018
cristyb5d5f722009-11-04 03:03:49 +00003019#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003020 #pragma omp critical (MagickCore_MedianFilterImage)
3021#endif
3022 proceed=SetImageProgress(image,MedianFilterImageTag,progress++,
3023 image->rows);
3024 if (proceed == MagickFalse)
3025 status=MagickFalse;
3026 }
3027 }
3028 median_view=DestroyCacheView(median_view);
3029 image_view=DestroyCacheView(image_view);
3030 pixel_list=DestroyMedianPixelListThreadSet(pixel_list);
3031 return(median_image);
3032}
3033
3034/*
3035%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3036% %
3037% %
3038% %
3039% M o t i o n B l u r I m a g e %
3040% %
3041% %
3042% %
3043%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3044%
3045% MotionBlurImage() simulates motion blur. We convolve the image with a
3046% Gaussian operator of the given radius and standard deviation (sigma).
3047% For reasonable results, radius should be larger than sigma. Use a
3048% radius of 0 and MotionBlurImage() selects a suitable radius for you.
3049% Angle gives the angle of the blurring motion.
3050%
3051% Andrew Protano contributed this effect.
3052%
3053% The format of the MotionBlurImage method is:
3054%
3055% Image *MotionBlurImage(const Image *image,const double radius,
3056% const double sigma,const double angle,ExceptionInfo *exception)
3057% Image *MotionBlurImageChannel(const Image *image,const ChannelType channel,
3058% const double radius,const double sigma,const double angle,
3059% ExceptionInfo *exception)
3060%
3061% A description of each parameter follows:
3062%
3063% o image: the image.
3064%
3065% o channel: the channel type.
3066%
3067% o radius: the radius of the Gaussian, in pixels, not counting the center
3068% o radius: the radius of the Gaussian, in pixels, not counting
3069% the center pixel.
3070%
3071% o sigma: the standard deviation of the Gaussian, in pixels.
3072%
cristycee97112010-05-28 00:44:52 +00003073% o angle: Apply the effect along this angle.
cristy3ed852e2009-09-05 21:47:34 +00003074%
3075% o exception: return any errors or warnings in this structure.
3076%
3077*/
3078
cristybb503372010-05-27 20:51:26 +00003079static double *GetMotionBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +00003080{
cristy3ed852e2009-09-05 21:47:34 +00003081 double
cristy47e00502009-12-17 19:19:57 +00003082 *kernel,
cristy3ed852e2009-09-05 21:47:34 +00003083 normalize;
3084
cristybb503372010-05-27 20:51:26 +00003085 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003086 i;
3087
3088 /*
cristy47e00502009-12-17 19:19:57 +00003089 Generate a 1-D convolution kernel.
cristy3ed852e2009-09-05 21:47:34 +00003090 */
3091 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
3092 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
3093 if (kernel == (double *) NULL)
3094 return(kernel);
cristy3ed852e2009-09-05 21:47:34 +00003095 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00003096 for (i=0; i < (ssize_t) width; i++)
cristy47e00502009-12-17 19:19:57 +00003097 {
3098 kernel[i]=exp((-((double) i*i)/(double) (2.0*MagickSigma*MagickSigma)))/
3099 (MagickSQ2PI*MagickSigma);
cristy3ed852e2009-09-05 21:47:34 +00003100 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +00003101 }
cristybb503372010-05-27 20:51:26 +00003102 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00003103 kernel[i]/=normalize;
3104 return(kernel);
3105}
3106
3107MagickExport Image *MotionBlurImage(const Image *image,const double radius,
3108 const double sigma,const double angle,ExceptionInfo *exception)
3109{
3110 Image
3111 *motion_blur;
3112
3113 motion_blur=MotionBlurImageChannel(image,DefaultChannels,radius,sigma,angle,
3114 exception);
3115 return(motion_blur);
3116}
3117
3118MagickExport Image *MotionBlurImageChannel(const Image *image,
3119 const ChannelType channel,const double radius,const double sigma,
3120 const double angle,ExceptionInfo *exception)
3121{
cristyc4c8d132010-01-07 01:58:38 +00003122 CacheView
3123 *blur_view,
3124 *image_view;
3125
cristy3ed852e2009-09-05 21:47:34 +00003126 double
3127 *kernel;
3128
3129 Image
3130 *blur_image;
3131
cristy3ed852e2009-09-05 21:47:34 +00003132 MagickBooleanType
3133 status;
3134
cristybb503372010-05-27 20:51:26 +00003135 MagickOffsetType
3136 progress;
3137
cristy3ed852e2009-09-05 21:47:34 +00003138 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00003139 bias;
cristy3ed852e2009-09-05 21:47:34 +00003140
3141 OffsetInfo
3142 *offset;
3143
3144 PointInfo
3145 point;
3146
cristybb503372010-05-27 20:51:26 +00003147 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003148 i;
3149
cristybb503372010-05-27 20:51:26 +00003150 size_t
cristy3ed852e2009-09-05 21:47:34 +00003151 width;
3152
cristybb503372010-05-27 20:51:26 +00003153 ssize_t
3154 y;
3155
cristy3ed852e2009-09-05 21:47:34 +00003156 assert(image != (Image *) NULL);
3157 assert(image->signature == MagickSignature);
3158 if (image->debug != MagickFalse)
3159 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3160 assert(exception != (ExceptionInfo *) NULL);
3161 width=GetOptimalKernelWidth1D(radius,sigma);
3162 kernel=GetMotionBlurKernel(width,sigma);
3163 if (kernel == (double *) NULL)
3164 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3165 offset=(OffsetInfo *) AcquireQuantumMemory(width,sizeof(*offset));
3166 if (offset == (OffsetInfo *) NULL)
3167 {
3168 kernel=(double *) RelinquishMagickMemory(kernel);
3169 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3170 }
3171 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3172 if (blur_image == (Image *) NULL)
3173 {
3174 kernel=(double *) RelinquishMagickMemory(kernel);
3175 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
3176 return((Image *) NULL);
3177 }
3178 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3179 {
3180 kernel=(double *) RelinquishMagickMemory(kernel);
3181 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
3182 InheritException(exception,&blur_image->exception);
3183 blur_image=DestroyImage(blur_image);
3184 return((Image *) NULL);
3185 }
3186 point.x=(double) width*sin(DegreesToRadians(angle));
3187 point.y=(double) width*cos(DegreesToRadians(angle));
cristybb503372010-05-27 20:51:26 +00003188 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00003189 {
cristybb503372010-05-27 20:51:26 +00003190 offset[i].x=(ssize_t) ceil((double) (i*point.y)/hypot(point.x,point.y)-0.5);
3191 offset[i].y=(ssize_t) ceil((double) (i*point.x)/hypot(point.x,point.y)-0.5);
cristy3ed852e2009-09-05 21:47:34 +00003192 }
3193 /*
3194 Motion blur image.
3195 */
3196 status=MagickTrue;
3197 progress=0;
cristyddd82202009-11-03 20:14:50 +00003198 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003199 image_view=AcquireCacheView(image);
3200 blur_view=AcquireCacheView(blur_image);
cristy59e0d3b2010-06-07 13:12:38 +00003201#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy8a7ea362010-03-10 20:31:43 +00003202 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003203#endif
cristybb503372010-05-27 20:51:26 +00003204 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003205 {
3206 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003207 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00003208
cristy3ed852e2009-09-05 21:47:34 +00003209 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003210 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003211
cristy117ff172010-08-15 21:35:32 +00003212 register ssize_t
3213 x;
3214
cristy3ed852e2009-09-05 21:47:34 +00003215 if (status == MagickFalse)
3216 continue;
3217 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3218 exception);
3219 if (q == (PixelPacket *) NULL)
3220 {
3221 status=MagickFalse;
3222 continue;
3223 }
3224 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
cristybb503372010-05-27 20:51:26 +00003225 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003226 {
3227 MagickPixelPacket
3228 qixel;
3229
3230 PixelPacket
3231 pixel;
3232
cristy117ff172010-08-15 21:35:32 +00003233 register const IndexPacket
3234 *restrict indexes;
3235
cristy3ed852e2009-09-05 21:47:34 +00003236 register double
cristyc47d1f82009-11-26 01:44:43 +00003237 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00003238
cristybb503372010-05-27 20:51:26 +00003239 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003240 i;
3241
cristy3ed852e2009-09-05 21:47:34 +00003242 k=kernel;
cristyddd82202009-11-03 20:14:50 +00003243 qixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00003244 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
3245 {
cristybb503372010-05-27 20:51:26 +00003246 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00003247 {
3248 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
3249 offset[i].y,&pixel,exception);
3250 qixel.red+=(*k)*pixel.red;
3251 qixel.green+=(*k)*pixel.green;
3252 qixel.blue+=(*k)*pixel.blue;
3253 qixel.opacity+=(*k)*pixel.opacity;
3254 if (image->colorspace == CMYKColorspace)
3255 {
3256 indexes=GetCacheViewVirtualIndexQueue(image_view);
3257 qixel.index+=(*k)*(*indexes);
3258 }
3259 k++;
3260 }
3261 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003262 q->red=ClampToQuantum(qixel.red);
cristy3ed852e2009-09-05 21:47:34 +00003263 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003264 q->green=ClampToQuantum(qixel.green);
cristy3ed852e2009-09-05 21:47:34 +00003265 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003266 q->blue=ClampToQuantum(qixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00003267 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003268 q->opacity=ClampToQuantum(qixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00003269 if (((channel & IndexChannel) != 0) &&
3270 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00003271 blur_indexes[x]=(IndexPacket) ClampToQuantum(qixel.index);
cristy3ed852e2009-09-05 21:47:34 +00003272 }
3273 else
3274 {
3275 MagickRealType
3276 alpha,
3277 gamma;
3278
3279 alpha=0.0;
3280 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00003281 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00003282 {
3283 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
3284 offset[i].y,&pixel,exception);
cristy8a7ea362010-03-10 20:31:43 +00003285 alpha=(MagickRealType) (QuantumScale*
3286 GetAlphaPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00003287 qixel.red+=(*k)*alpha*pixel.red;
3288 qixel.green+=(*k)*alpha*pixel.green;
3289 qixel.blue+=(*k)*alpha*pixel.blue;
3290 qixel.opacity+=(*k)*pixel.opacity;
3291 if (image->colorspace == CMYKColorspace)
3292 {
3293 indexes=GetCacheViewVirtualIndexQueue(image_view);
3294 qixel.index+=(*k)*alpha*(*indexes);
3295 }
3296 gamma+=(*k)*alpha;
3297 k++;
3298 }
3299 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3300 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003301 q->red=ClampToQuantum(gamma*qixel.red);
cristy3ed852e2009-09-05 21:47:34 +00003302 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003303 q->green=ClampToQuantum(gamma*qixel.green);
cristy3ed852e2009-09-05 21:47:34 +00003304 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003305 q->blue=ClampToQuantum(gamma*qixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00003306 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003307 q->opacity=ClampToQuantum(qixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00003308 if (((channel & IndexChannel) != 0) &&
3309 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00003310 blur_indexes[x]=(IndexPacket) ClampToQuantum(gamma*qixel.index);
cristy3ed852e2009-09-05 21:47:34 +00003311 }
3312 q++;
3313 }
3314 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
3315 status=MagickFalse;
3316 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3317 {
3318 MagickBooleanType
3319 proceed;
3320
cristy59e0d3b2010-06-07 13:12:38 +00003321#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003322 #pragma omp critical (MagickCore_MotionBlurImageChannel)
3323#endif
3324 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
3325 if (proceed == MagickFalse)
3326 status=MagickFalse;
3327 }
3328 }
3329 blur_view=DestroyCacheView(blur_view);
3330 image_view=DestroyCacheView(image_view);
3331 kernel=(double *) RelinquishMagickMemory(kernel);
3332 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
3333 if (status == MagickFalse)
3334 blur_image=DestroyImage(blur_image);
3335 return(blur_image);
3336}
3337
3338/*
3339%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3340% %
3341% %
3342% %
3343% P r e v i e w I m a g e %
3344% %
3345% %
3346% %
3347%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3348%
3349% PreviewImage() tiles 9 thumbnails of the specified image with an image
3350% processing operation applied with varying parameters. This may be helpful
3351% pin-pointing an appropriate parameter for a particular image processing
3352% operation.
3353%
3354% The format of the PreviewImages method is:
3355%
3356% Image *PreviewImages(const Image *image,const PreviewType preview,
3357% ExceptionInfo *exception)
3358%
3359% A description of each parameter follows:
3360%
3361% o image: the image.
3362%
3363% o preview: the image processing operation.
3364%
3365% o exception: return any errors or warnings in this structure.
3366%
3367*/
3368MagickExport Image *PreviewImage(const Image *image,const PreviewType preview,
3369 ExceptionInfo *exception)
3370{
3371#define NumberTiles 9
3372#define PreviewImageTag "Preview/Image"
3373#define DefaultPreviewGeometry "204x204+10+10"
3374
3375 char
3376 factor[MaxTextExtent],
3377 label[MaxTextExtent];
3378
3379 double
3380 degrees,
3381 gamma,
3382 percentage,
3383 radius,
3384 sigma,
3385 threshold;
3386
3387 Image
3388 *images,
3389 *montage_image,
3390 *preview_image,
3391 *thumbnail;
3392
3393 ImageInfo
3394 *preview_info;
3395
cristy3ed852e2009-09-05 21:47:34 +00003396 MagickBooleanType
3397 proceed;
3398
3399 MontageInfo
3400 *montage_info;
3401
3402 QuantizeInfo
3403 quantize_info;
3404
3405 RectangleInfo
3406 geometry;
3407
cristybb503372010-05-27 20:51:26 +00003408 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003409 i,
3410 x;
3411
cristybb503372010-05-27 20:51:26 +00003412 size_t
cristy3ed852e2009-09-05 21:47:34 +00003413 colors;
3414
cristy117ff172010-08-15 21:35:32 +00003415 ssize_t
3416 y;
3417
cristy3ed852e2009-09-05 21:47:34 +00003418 /*
3419 Open output image file.
3420 */
3421 assert(image != (Image *) NULL);
3422 assert(image->signature == MagickSignature);
3423 if (image->debug != MagickFalse)
3424 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3425 colors=2;
3426 degrees=0.0;
3427 gamma=(-0.2f);
3428 preview_info=AcquireImageInfo();
3429 SetGeometry(image,&geometry);
3430 (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y,
3431 &geometry.width,&geometry.height);
3432 images=NewImageList();
3433 percentage=12.5;
3434 GetQuantizeInfo(&quantize_info);
3435 radius=0.0;
3436 sigma=1.0;
3437 threshold=0.0;
3438 x=0;
3439 y=0;
3440 for (i=0; i < NumberTiles; i++)
3441 {
3442 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
3443 if (thumbnail == (Image *) NULL)
3444 break;
3445 (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL,
3446 (void *) NULL);
3447 (void) SetImageProperty(thumbnail,"label",DefaultTileLabel);
3448 if (i == (NumberTiles/2))
3449 {
3450 (void) QueryColorDatabase("#dfdfdf",&thumbnail->matte_color,exception);
3451 AppendImageToList(&images,thumbnail);
3452 continue;
3453 }
3454 switch (preview)
3455 {
3456 case RotatePreview:
3457 {
3458 degrees+=45.0;
3459 preview_image=RotateImage(thumbnail,degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003460 (void) FormatMagickString(label,MaxTextExtent,"rotate %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003461 break;
3462 }
3463 case ShearPreview:
3464 {
3465 degrees+=5.0;
3466 preview_image=ShearImage(thumbnail,degrees,degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003467 (void) FormatMagickString(label,MaxTextExtent,"shear %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00003468 degrees,2.0*degrees);
3469 break;
3470 }
3471 case RollPreview:
3472 {
cristybb503372010-05-27 20:51:26 +00003473 x=(ssize_t) ((i+1)*thumbnail->columns)/NumberTiles;
3474 y=(ssize_t) ((i+1)*thumbnail->rows)/NumberTiles;
cristy3ed852e2009-09-05 21:47:34 +00003475 preview_image=RollImage(thumbnail,x,y,exception);
cristye8c25f92010-06-03 00:53:06 +00003476 (void) FormatMagickString(label,MaxTextExtent,"roll %+.20gx%+.20g",
3477 (double) x,(double) y);
cristy3ed852e2009-09-05 21:47:34 +00003478 break;
3479 }
3480 case HuePreview:
3481 {
3482 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3483 if (preview_image == (Image *) NULL)
3484 break;
cristye7f51092010-01-17 00:39:37 +00003485 (void) FormatMagickString(factor,MaxTextExtent,"100,100,%g",
cristy3ed852e2009-09-05 21:47:34 +00003486 2.0*percentage);
3487 (void) ModulateImage(preview_image,factor);
3488 (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor);
3489 break;
3490 }
3491 case SaturationPreview:
3492 {
3493 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3494 if (preview_image == (Image *) NULL)
3495 break;
cristye7f51092010-01-17 00:39:37 +00003496 (void) FormatMagickString(factor,MaxTextExtent,"100,%g",
cristy8cd5b312010-01-07 01:10:24 +00003497 2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00003498 (void) ModulateImage(preview_image,factor);
3499 (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor);
3500 break;
3501 }
3502 case BrightnessPreview:
3503 {
3504 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3505 if (preview_image == (Image *) NULL)
3506 break;
cristye7f51092010-01-17 00:39:37 +00003507 (void) FormatMagickString(factor,MaxTextExtent,"%g",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 GammaPreview:
3513 default:
3514 {
3515 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3516 if (preview_image == (Image *) NULL)
3517 break;
3518 gamma+=0.4f;
3519 (void) GammaImageChannel(preview_image,DefaultChannels,gamma);
cristye7f51092010-01-17 00:39:37 +00003520 (void) FormatMagickString(label,MaxTextExtent,"gamma %g",gamma);
cristy3ed852e2009-09-05 21:47:34 +00003521 break;
3522 }
3523 case SpiffPreview:
3524 {
3525 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3526 if (preview_image != (Image *) NULL)
3527 for (x=0; x < i; x++)
3528 (void) ContrastImage(preview_image,MagickTrue);
cristye8c25f92010-06-03 00:53:06 +00003529 (void) FormatMagickString(label,MaxTextExtent,"contrast (%.20g)",
3530 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00003531 break;
3532 }
3533 case DullPreview:
3534 {
3535 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3536 if (preview_image == (Image *) NULL)
3537 break;
3538 for (x=0; x < i; x++)
3539 (void) ContrastImage(preview_image,MagickFalse);
cristye8c25f92010-06-03 00:53:06 +00003540 (void) FormatMagickString(label,MaxTextExtent,"+contrast (%.20g)",
3541 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00003542 break;
3543 }
3544 case GrayscalePreview:
3545 {
3546 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3547 if (preview_image == (Image *) NULL)
3548 break;
3549 colors<<=1;
3550 quantize_info.number_colors=colors;
3551 quantize_info.colorspace=GRAYColorspace;
3552 (void) QuantizeImage(&quantize_info,preview_image);
3553 (void) FormatMagickString(label,MaxTextExtent,
cristye8c25f92010-06-03 00:53:06 +00003554 "-colorspace gray -colors %.20g",(double) colors);
cristy3ed852e2009-09-05 21:47:34 +00003555 break;
3556 }
3557 case QuantizePreview:
3558 {
3559 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3560 if (preview_image == (Image *) NULL)
3561 break;
3562 colors<<=1;
3563 quantize_info.number_colors=colors;
3564 (void) QuantizeImage(&quantize_info,preview_image);
cristye8c25f92010-06-03 00:53:06 +00003565 (void) FormatMagickString(label,MaxTextExtent,"colors %.20g",(double)
3566 colors);
cristy3ed852e2009-09-05 21:47:34 +00003567 break;
3568 }
3569 case DespecklePreview:
3570 {
3571 for (x=0; x < (i-1); x++)
3572 {
3573 preview_image=DespeckleImage(thumbnail,exception);
3574 if (preview_image == (Image *) NULL)
3575 break;
3576 thumbnail=DestroyImage(thumbnail);
3577 thumbnail=preview_image;
3578 }
3579 preview_image=DespeckleImage(thumbnail,exception);
3580 if (preview_image == (Image *) NULL)
3581 break;
cristye8c25f92010-06-03 00:53:06 +00003582 (void) FormatMagickString(label,MaxTextExtent,"despeckle (%.20g)",
3583 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00003584 break;
3585 }
3586 case ReduceNoisePreview:
3587 {
3588 preview_image=ReduceNoiseImage(thumbnail,radius,exception);
cristye7f51092010-01-17 00:39:37 +00003589 (void) FormatMagickString(label,MaxTextExtent,"noise %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003590 break;
3591 }
3592 case AddNoisePreview:
3593 {
3594 switch ((int) i)
3595 {
3596 case 0:
3597 {
3598 (void) CopyMagickString(factor,"uniform",MaxTextExtent);
3599 break;
3600 }
3601 case 1:
3602 {
3603 (void) CopyMagickString(factor,"gaussian",MaxTextExtent);
3604 break;
3605 }
3606 case 2:
3607 {
3608 (void) CopyMagickString(factor,"multiplicative",MaxTextExtent);
3609 break;
3610 }
3611 case 3:
3612 {
3613 (void) CopyMagickString(factor,"impulse",MaxTextExtent);
3614 break;
3615 }
3616 case 4:
3617 {
3618 (void) CopyMagickString(factor,"laplacian",MaxTextExtent);
3619 break;
3620 }
3621 case 5:
3622 {
3623 (void) CopyMagickString(factor,"Poisson",MaxTextExtent);
3624 break;
3625 }
3626 default:
3627 {
3628 (void) CopyMagickString(thumbnail->magick,"NULL",MaxTextExtent);
3629 break;
3630 }
3631 }
3632 preview_image=ReduceNoiseImage(thumbnail,(double) i,exception);
3633 (void) FormatMagickString(label,MaxTextExtent,"+noise %s",factor);
3634 break;
3635 }
3636 case SharpenPreview:
3637 {
3638 preview_image=SharpenImage(thumbnail,radius,sigma,exception);
cristye7f51092010-01-17 00:39:37 +00003639 (void) FormatMagickString(label,MaxTextExtent,"sharpen %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003640 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00003641 break;
3642 }
3643 case BlurPreview:
3644 {
3645 preview_image=BlurImage(thumbnail,radius,sigma,exception);
cristye7f51092010-01-17 00:39:37 +00003646 (void) FormatMagickString(label,MaxTextExtent,"blur %gx%g",radius,
cristy3ed852e2009-09-05 21:47:34 +00003647 sigma);
3648 break;
3649 }
3650 case ThresholdPreview:
3651 {
3652 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3653 if (preview_image == (Image *) NULL)
3654 break;
3655 (void) BilevelImage(thumbnail,
3656 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
cristye7f51092010-01-17 00:39:37 +00003657 (void) FormatMagickString(label,MaxTextExtent,"threshold %g",
cristy3ed852e2009-09-05 21:47:34 +00003658 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
3659 break;
3660 }
3661 case EdgeDetectPreview:
3662 {
3663 preview_image=EdgeImage(thumbnail,radius,exception);
cristye7f51092010-01-17 00:39:37 +00003664 (void) FormatMagickString(label,MaxTextExtent,"edge %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003665 break;
3666 }
3667 case SpreadPreview:
3668 {
3669 preview_image=SpreadImage(thumbnail,radius,exception);
cristye7f51092010-01-17 00:39:37 +00003670 (void) FormatMagickString(label,MaxTextExtent,"spread %g",
cristy8cd5b312010-01-07 01:10:24 +00003671 radius+0.5);
cristy3ed852e2009-09-05 21:47:34 +00003672 break;
3673 }
3674 case SolarizePreview:
3675 {
3676 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3677 if (preview_image == (Image *) NULL)
3678 break;
3679 (void) SolarizeImage(preview_image,(double) QuantumRange*
3680 percentage/100.0);
cristye7f51092010-01-17 00:39:37 +00003681 (void) FormatMagickString(label,MaxTextExtent,"solarize %g",
cristy3ed852e2009-09-05 21:47:34 +00003682 (QuantumRange*percentage)/100.0);
3683 break;
3684 }
3685 case ShadePreview:
3686 {
3687 degrees+=10.0;
3688 preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees,
3689 exception);
cristye7f51092010-01-17 00:39:37 +00003690 (void) FormatMagickString(label,MaxTextExtent,"shade %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003691 degrees,degrees);
cristy3ed852e2009-09-05 21:47:34 +00003692 break;
3693 }
3694 case RaisePreview:
3695 {
3696 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3697 if (preview_image == (Image *) NULL)
3698 break;
cristybb503372010-05-27 20:51:26 +00003699 geometry.width=(size_t) (2*i+2);
3700 geometry.height=(size_t) (2*i+2);
cristy3ed852e2009-09-05 21:47:34 +00003701 geometry.x=i/2;
3702 geometry.y=i/2;
3703 (void) RaiseImage(preview_image,&geometry,MagickTrue);
cristye8c25f92010-06-03 00:53:06 +00003704 (void) FormatMagickString(label,MaxTextExtent,
cristy6d8abba2010-06-03 01:10:47 +00003705 "raise %.20gx%.20g%+.20g%+.20g",(double) geometry.width,(double)
cristye8c25f92010-06-03 00:53:06 +00003706 geometry.height,(double) geometry.x,(double) geometry.y);
cristy3ed852e2009-09-05 21:47:34 +00003707 break;
3708 }
3709 case SegmentPreview:
3710 {
3711 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3712 if (preview_image == (Image *) NULL)
3713 break;
3714 threshold+=0.4f;
3715 (void) SegmentImage(preview_image,RGBColorspace,MagickFalse,threshold,
3716 threshold);
cristye7f51092010-01-17 00:39:37 +00003717 (void) FormatMagickString(label,MaxTextExtent,"segment %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00003718 threshold,threshold);
3719 break;
3720 }
3721 case SwirlPreview:
3722 {
3723 preview_image=SwirlImage(thumbnail,degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003724 (void) FormatMagickString(label,MaxTextExtent,"swirl %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003725 degrees+=45.0;
3726 break;
3727 }
3728 case ImplodePreview:
3729 {
3730 degrees+=0.1f;
3731 preview_image=ImplodeImage(thumbnail,degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003732 (void) FormatMagickString(label,MaxTextExtent,"implode %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003733 break;
3734 }
3735 case WavePreview:
3736 {
3737 degrees+=5.0f;
3738 preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003739 (void) FormatMagickString(label,MaxTextExtent,"wave %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003740 0.5*degrees,2.0*degrees);
cristy3ed852e2009-09-05 21:47:34 +00003741 break;
3742 }
3743 case OilPaintPreview:
3744 {
3745 preview_image=OilPaintImage(thumbnail,(double) radius,exception);
cristye7f51092010-01-17 00:39:37 +00003746 (void) FormatMagickString(label,MaxTextExtent,"paint %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003747 break;
3748 }
3749 case CharcoalDrawingPreview:
3750 {
3751 preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma,
3752 exception);
cristye7f51092010-01-17 00:39:37 +00003753 (void) FormatMagickString(label,MaxTextExtent,"charcoal %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003754 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00003755 break;
3756 }
3757 case JPEGPreview:
3758 {
3759 char
3760 filename[MaxTextExtent];
3761
3762 int
3763 file;
3764
3765 MagickBooleanType
3766 status;
3767
3768 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3769 if (preview_image == (Image *) NULL)
3770 break;
cristybb503372010-05-27 20:51:26 +00003771 preview_info->quality=(size_t) percentage;
cristye8c25f92010-06-03 00:53:06 +00003772 (void) FormatMagickString(factor,MaxTextExtent,"%.20g",(double)
3773 preview_info->quality);
cristy3ed852e2009-09-05 21:47:34 +00003774 file=AcquireUniqueFileResource(filename);
3775 if (file != -1)
3776 file=close(file)-1;
3777 (void) FormatMagickString(preview_image->filename,MaxTextExtent,
3778 "jpeg:%s",filename);
3779 status=WriteImage(preview_info,preview_image);
3780 if (status != MagickFalse)
3781 {
3782 Image
3783 *quality_image;
3784
3785 (void) CopyMagickString(preview_info->filename,
3786 preview_image->filename,MaxTextExtent);
3787 quality_image=ReadImage(preview_info,exception);
3788 if (quality_image != (Image *) NULL)
3789 {
3790 preview_image=DestroyImage(preview_image);
3791 preview_image=quality_image;
3792 }
3793 }
3794 (void) RelinquishUniqueFileResource(preview_image->filename);
3795 if ((GetBlobSize(preview_image)/1024) >= 1024)
cristye7f51092010-01-17 00:39:37 +00003796 (void) FormatMagickString(label,MaxTextExtent,"quality %s\n%gmb ",
cristy3ed852e2009-09-05 21:47:34 +00003797 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
3798 1024.0/1024.0);
3799 else
3800 if (GetBlobSize(preview_image) >= 1024)
cristy8cd5b312010-01-07 01:10:24 +00003801 (void) FormatMagickString(label,MaxTextExtent,
cristye7f51092010-01-17 00:39:37 +00003802 "quality %s\n%gkb ",factor,(double) ((MagickOffsetType)
cristy8cd5b312010-01-07 01:10:24 +00003803 GetBlobSize(preview_image))/1024.0);
cristy3ed852e2009-09-05 21:47:34 +00003804 else
cristye8c25f92010-06-03 00:53:06 +00003805 (void) FormatMagickString(label,MaxTextExtent,"quality %s\n%.20gb ",
3806 factor,(double) GetBlobSize(thumbnail));
cristy3ed852e2009-09-05 21:47:34 +00003807 break;
3808 }
3809 }
3810 thumbnail=DestroyImage(thumbnail);
3811 percentage+=12.5;
3812 radius+=0.5;
3813 sigma+=0.25;
3814 if (preview_image == (Image *) NULL)
3815 break;
3816 (void) DeleteImageProperty(preview_image,"label");
3817 (void) SetImageProperty(preview_image,"label",label);
3818 AppendImageToList(&images,preview_image);
cristybb503372010-05-27 20:51:26 +00003819 proceed=SetImageProgress(image,PreviewImageTag,(MagickOffsetType) i,
3820 NumberTiles);
cristy3ed852e2009-09-05 21:47:34 +00003821 if (proceed == MagickFalse)
3822 break;
3823 }
3824 if (images == (Image *) NULL)
3825 {
3826 preview_info=DestroyImageInfo(preview_info);
3827 return((Image *) NULL);
3828 }
3829 /*
3830 Create the montage.
3831 */
3832 montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL);
3833 (void) CopyMagickString(montage_info->filename,image->filename,MaxTextExtent);
3834 montage_info->shadow=MagickTrue;
3835 (void) CloneString(&montage_info->tile,"3x3");
3836 (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry);
3837 (void) CloneString(&montage_info->frame,DefaultTileFrame);
3838 montage_image=MontageImages(images,montage_info,exception);
3839 montage_info=DestroyMontageInfo(montage_info);
3840 images=DestroyImageList(images);
3841 if (montage_image == (Image *) NULL)
3842 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3843 if (montage_image->montage != (char *) NULL)
3844 {
3845 /*
3846 Free image directory.
3847 */
3848 montage_image->montage=(char *) RelinquishMagickMemory(
3849 montage_image->montage);
3850 if (image->directory != (char *) NULL)
3851 montage_image->directory=(char *) RelinquishMagickMemory(
3852 montage_image->directory);
3853 }
3854 preview_info=DestroyImageInfo(preview_info);
3855 return(montage_image);
3856}
3857
3858/*
3859%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3860% %
3861% %
3862% %
3863% R a d i a l B l u r I m a g e %
3864% %
3865% %
3866% %
3867%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3868%
3869% RadialBlurImage() applies a radial blur to the image.
3870%
3871% Andrew Protano contributed this effect.
3872%
3873% The format of the RadialBlurImage method is:
3874%
3875% Image *RadialBlurImage(const Image *image,const double angle,
3876% ExceptionInfo *exception)
3877% Image *RadialBlurImageChannel(const Image *image,const ChannelType channel,
3878% const double angle,ExceptionInfo *exception)
3879%
3880% A description of each parameter follows:
3881%
3882% o image: the image.
3883%
3884% o channel: the channel type.
3885%
3886% o angle: the angle of the radial blur.
3887%
3888% o exception: return any errors or warnings in this structure.
3889%
3890*/
3891
3892MagickExport Image *RadialBlurImage(const Image *image,const double angle,
3893 ExceptionInfo *exception)
3894{
3895 Image
3896 *blur_image;
3897
3898 blur_image=RadialBlurImageChannel(image,DefaultChannels,angle,exception);
3899 return(blur_image);
3900}
3901
3902MagickExport Image *RadialBlurImageChannel(const Image *image,
3903 const ChannelType channel,const double angle,ExceptionInfo *exception)
3904{
cristyc4c8d132010-01-07 01:58:38 +00003905 CacheView
3906 *blur_view,
3907 *image_view;
3908
cristy3ed852e2009-09-05 21:47:34 +00003909 Image
3910 *blur_image;
3911
cristy3ed852e2009-09-05 21:47:34 +00003912 MagickBooleanType
3913 status;
3914
cristybb503372010-05-27 20:51:26 +00003915 MagickOffsetType
3916 progress;
3917
cristy3ed852e2009-09-05 21:47:34 +00003918 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00003919 bias;
cristy3ed852e2009-09-05 21:47:34 +00003920
3921 MagickRealType
3922 blur_radius,
3923 *cos_theta,
3924 offset,
3925 *sin_theta,
3926 theta;
3927
3928 PointInfo
3929 blur_center;
3930
cristybb503372010-05-27 20:51:26 +00003931 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003932 i;
3933
cristybb503372010-05-27 20:51:26 +00003934 size_t
cristy3ed852e2009-09-05 21:47:34 +00003935 n;
3936
cristybb503372010-05-27 20:51:26 +00003937 ssize_t
3938 y;
3939
cristy3ed852e2009-09-05 21:47:34 +00003940 /*
3941 Allocate blur image.
3942 */
3943 assert(image != (Image *) NULL);
3944 assert(image->signature == MagickSignature);
3945 if (image->debug != MagickFalse)
3946 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3947 assert(exception != (ExceptionInfo *) NULL);
3948 assert(exception->signature == MagickSignature);
3949 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3950 if (blur_image == (Image *) NULL)
3951 return((Image *) NULL);
3952 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3953 {
3954 InheritException(exception,&blur_image->exception);
3955 blur_image=DestroyImage(blur_image);
3956 return((Image *) NULL);
3957 }
3958 blur_center.x=(double) image->columns/2.0;
3959 blur_center.y=(double) image->rows/2.0;
3960 blur_radius=hypot(blur_center.x,blur_center.y);
cristy117ff172010-08-15 21:35:32 +00003961 n=(size_t) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+2UL);
cristy3ed852e2009-09-05 21:47:34 +00003962 theta=DegreesToRadians(angle)/(MagickRealType) (n-1);
3963 cos_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3964 sizeof(*cos_theta));
3965 sin_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3966 sizeof(*sin_theta));
3967 if ((cos_theta == (MagickRealType *) NULL) ||
3968 (sin_theta == (MagickRealType *) NULL))
3969 {
3970 blur_image=DestroyImage(blur_image);
3971 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3972 }
3973 offset=theta*(MagickRealType) (n-1)/2.0;
cristybb503372010-05-27 20:51:26 +00003974 for (i=0; i < (ssize_t) n; i++)
cristy3ed852e2009-09-05 21:47:34 +00003975 {
3976 cos_theta[i]=cos((double) (theta*i-offset));
3977 sin_theta[i]=sin((double) (theta*i-offset));
3978 }
3979 /*
3980 Radial blur image.
3981 */
3982 status=MagickTrue;
3983 progress=0;
cristyddd82202009-11-03 20:14:50 +00003984 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003985 image_view=AcquireCacheView(image);
3986 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003987#if defined(MAGICKCORE_OPENMP_SUPPORT)
3988 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003989#endif
cristybb503372010-05-27 20:51:26 +00003990 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003991 {
3992 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003993 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003994
3995 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003996 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00003997
cristy3ed852e2009-09-05 21:47:34 +00003998 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003999 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004000
cristy117ff172010-08-15 21:35:32 +00004001 register ssize_t
4002 x;
4003
cristy3ed852e2009-09-05 21:47:34 +00004004 if (status == MagickFalse)
4005 continue;
4006 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
4007 exception);
4008 if (q == (PixelPacket *) NULL)
4009 {
4010 status=MagickFalse;
4011 continue;
4012 }
4013 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
cristybb503372010-05-27 20:51:26 +00004014 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004015 {
4016 MagickPixelPacket
4017 qixel;
4018
4019 MagickRealType
4020 normalize,
4021 radius;
4022
4023 PixelPacket
4024 pixel;
4025
4026 PointInfo
4027 center;
4028
cristybb503372010-05-27 20:51:26 +00004029 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00004030 i;
4031
cristybb503372010-05-27 20:51:26 +00004032 size_t
cristy3ed852e2009-09-05 21:47:34 +00004033 step;
4034
4035 center.x=(double) x-blur_center.x;
4036 center.y=(double) y-blur_center.y;
4037 radius=hypot((double) center.x,center.y);
4038 if (radius == 0)
4039 step=1;
4040 else
4041 {
cristybb503372010-05-27 20:51:26 +00004042 step=(size_t) (blur_radius/radius);
cristy3ed852e2009-09-05 21:47:34 +00004043 if (step == 0)
4044 step=1;
4045 else
4046 if (step >= n)
4047 step=n-1;
4048 }
4049 normalize=0.0;
cristyddd82202009-11-03 20:14:50 +00004050 qixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00004051 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
4052 {
cristyeaedf062010-05-29 22:36:02 +00004053 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00004054 {
cristyeaedf062010-05-29 22:36:02 +00004055 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
4056 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
4057 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
4058 cos_theta[i]+0.5),&pixel,exception);
cristy3ed852e2009-09-05 21:47:34 +00004059 qixel.red+=pixel.red;
4060 qixel.green+=pixel.green;
4061 qixel.blue+=pixel.blue;
4062 qixel.opacity+=pixel.opacity;
4063 if (image->colorspace == CMYKColorspace)
4064 {
4065 indexes=GetCacheViewVirtualIndexQueue(image_view);
4066 qixel.index+=(*indexes);
4067 }
4068 normalize+=1.0;
4069 }
4070 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
4071 normalize);
4072 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004073 q->red=ClampToQuantum(normalize*qixel.red);
cristy3ed852e2009-09-05 21:47:34 +00004074 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004075 q->green=ClampToQuantum(normalize*qixel.green);
cristy3ed852e2009-09-05 21:47:34 +00004076 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004077 q->blue=ClampToQuantum(normalize*qixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00004078 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004079 q->opacity=ClampToQuantum(normalize*qixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00004080 if (((channel & IndexChannel) != 0) &&
4081 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00004082 blur_indexes[x]=(IndexPacket) ClampToQuantum(normalize*qixel.index);
cristy3ed852e2009-09-05 21:47:34 +00004083 }
4084 else
4085 {
4086 MagickRealType
4087 alpha,
4088 gamma;
4089
4090 alpha=1.0;
4091 gamma=0.0;
cristyeaedf062010-05-29 22:36:02 +00004092 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00004093 {
cristyeaedf062010-05-29 22:36:02 +00004094 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
4095 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
4096 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
4097 cos_theta[i]+0.5),&pixel,exception);
cristy46f08202010-01-10 04:04:21 +00004098 alpha=(MagickRealType) (QuantumScale*
4099 GetAlphaPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004100 qixel.red+=alpha*pixel.red;
4101 qixel.green+=alpha*pixel.green;
4102 qixel.blue+=alpha*pixel.blue;
4103 qixel.opacity+=pixel.opacity;
4104 if (image->colorspace == CMYKColorspace)
4105 {
4106 indexes=GetCacheViewVirtualIndexQueue(image_view);
4107 qixel.index+=alpha*(*indexes);
4108 }
4109 gamma+=alpha;
4110 normalize+=1.0;
4111 }
4112 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
4113 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
4114 normalize);
4115 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004116 q->red=ClampToQuantum(gamma*qixel.red);
cristy3ed852e2009-09-05 21:47:34 +00004117 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004118 q->green=ClampToQuantum(gamma*qixel.green);
cristy3ed852e2009-09-05 21:47:34 +00004119 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004120 q->blue=ClampToQuantum(gamma*qixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00004121 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004122 q->opacity=ClampToQuantum(normalize*qixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00004123 if (((channel & IndexChannel) != 0) &&
4124 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00004125 blur_indexes[x]=(IndexPacket) ClampToQuantum(gamma*qixel.index);
cristy3ed852e2009-09-05 21:47:34 +00004126 }
4127 q++;
4128 }
4129 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
4130 status=MagickFalse;
4131 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4132 {
4133 MagickBooleanType
4134 proceed;
4135
cristyb5d5f722009-11-04 03:03:49 +00004136#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004137 #pragma omp critical (MagickCore_RadialBlurImageChannel)
4138#endif
4139 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
4140 if (proceed == MagickFalse)
4141 status=MagickFalse;
4142 }
4143 }
4144 blur_view=DestroyCacheView(blur_view);
4145 image_view=DestroyCacheView(image_view);
4146 cos_theta=(MagickRealType *) RelinquishMagickMemory(cos_theta);
4147 sin_theta=(MagickRealType *) RelinquishMagickMemory(sin_theta);
4148 if (status == MagickFalse)
4149 blur_image=DestroyImage(blur_image);
4150 return(blur_image);
4151}
4152
4153/*
4154%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4155% %
4156% %
4157% %
4158% R e d u c e N o i s e I m a g e %
4159% %
4160% %
4161% %
4162%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4163%
4164% ReduceNoiseImage() smooths the contours of an image while still preserving
4165% edge information. The algorithm works by replacing each pixel with its
4166% neighbor closest in value. A neighbor is defined by radius. Use a radius
4167% of 0 and ReduceNoise() selects a suitable radius for you.
4168%
4169% The format of the ReduceNoiseImage method is:
4170%
4171% Image *ReduceNoiseImage(const Image *image,const double radius,
4172% ExceptionInfo *exception)
4173%
4174% A description of each parameter follows:
4175%
4176% o image: the image.
4177%
4178% o radius: the radius of the pixel neighborhood.
4179%
4180% o exception: return any errors or warnings in this structure.
4181%
4182*/
4183
4184static MagickPixelPacket GetNonpeakMedianPixelList(MedianPixelList *pixel_list)
4185{
4186 MagickPixelPacket
4187 pixel;
4188
cristy3ed852e2009-09-05 21:47:34 +00004189 register MedianSkipList
4190 *list;
4191
cristy117ff172010-08-15 21:35:32 +00004192 register ssize_t
4193 channel;
4194
cristybb503372010-05-27 20:51:26 +00004195 size_t
cristy3ed852e2009-09-05 21:47:34 +00004196 center,
4197 color,
4198 count,
4199 previous,
4200 next;
4201
4202 unsigned short
4203 channels[5];
4204
4205 /*
4206 Finds the median value for each of the color.
4207 */
4208 center=pixel_list->center;
4209 for (channel=0; channel < 5; channel++)
4210 {
4211 list=pixel_list->lists+channel;
4212 color=65536UL;
4213 next=list->nodes[color].next[0];
4214 count=0;
4215 do
4216 {
4217 previous=color;
4218 color=next;
4219 next=list->nodes[color].next[0];
4220 count+=list->nodes[color].count;
4221 }
4222 while (count <= center);
4223 if ((previous == 65536UL) && (next != 65536UL))
4224 color=next;
4225 else
4226 if ((previous != 65536UL) && (next == 65536UL))
4227 color=previous;
4228 channels[channel]=(unsigned short) color;
4229 }
4230 GetMagickPixelPacket((const Image *) NULL,&pixel);
4231 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4232 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4233 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
4234 pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
4235 pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]);
4236 return(pixel);
4237}
4238
4239MagickExport Image *ReduceNoiseImage(const Image *image,const double radius,
4240 ExceptionInfo *exception)
4241{
4242#define ReduceNoiseImageTag "ReduceNoise/Image"
4243
cristyfa112112010-01-04 17:48:07 +00004244 CacheView
4245 *image_view,
4246 *noise_view;
4247
cristy3ed852e2009-09-05 21:47:34 +00004248 Image
4249 *noise_image;
4250
cristy3ed852e2009-09-05 21:47:34 +00004251 MagickBooleanType
4252 status;
4253
cristybb503372010-05-27 20:51:26 +00004254 MagickOffsetType
4255 progress;
4256
cristy3ed852e2009-09-05 21:47:34 +00004257 MedianPixelList
cristyfa112112010-01-04 17:48:07 +00004258 **restrict pixel_list;
cristy3ed852e2009-09-05 21:47:34 +00004259
cristybb503372010-05-27 20:51:26 +00004260 size_t
cristy3ed852e2009-09-05 21:47:34 +00004261 width;
4262
cristybb503372010-05-27 20:51:26 +00004263 ssize_t
4264 y;
4265
cristy3ed852e2009-09-05 21:47:34 +00004266 /*
4267 Initialize noise image attributes.
4268 */
4269 assert(image != (Image *) NULL);
4270 assert(image->signature == MagickSignature);
4271 if (image->debug != MagickFalse)
4272 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4273 assert(exception != (ExceptionInfo *) NULL);
4274 assert(exception->signature == MagickSignature);
4275 width=GetOptimalKernelWidth2D(radius,0.5);
4276 if ((image->columns < width) || (image->rows < width))
4277 ThrowImageException(OptionError,"ImageSmallerThanKernelRadius");
4278 noise_image=CloneImage(image,image->columns,image->rows,MagickTrue,
4279 exception);
4280 if (noise_image == (Image *) NULL)
4281 return((Image *) NULL);
4282 if (SetImageStorageClass(noise_image,DirectClass) == MagickFalse)
4283 {
4284 InheritException(exception,&noise_image->exception);
4285 noise_image=DestroyImage(noise_image);
4286 return((Image *) NULL);
4287 }
4288 pixel_list=AcquireMedianPixelListThreadSet(width);
4289 if (pixel_list == (MedianPixelList **) NULL)
4290 {
4291 noise_image=DestroyImage(noise_image);
4292 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
4293 }
4294 /*
4295 Reduce noise image.
4296 */
4297 status=MagickTrue;
4298 progress=0;
4299 image_view=AcquireCacheView(image);
4300 noise_view=AcquireCacheView(noise_image);
cristyb5d5f722009-11-04 03:03:49 +00004301#if defined(MAGICKCORE_OPENMP_SUPPORT)
4302 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004303#endif
cristybb503372010-05-27 20:51:26 +00004304 for (y=0; y < (ssize_t) noise_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00004305 {
cristy6ebe97c2010-07-03 01:17:28 +00004306 int
4307 id;
4308
cristy3ed852e2009-09-05 21:47:34 +00004309 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004310 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00004311
4312 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004313 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00004314
4315 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004316 *restrict noise_indexes;
cristy3ed852e2009-09-05 21:47:34 +00004317
cristy3ed852e2009-09-05 21:47:34 +00004318 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004319 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004320
cristy117ff172010-08-15 21:35:32 +00004321 register ssize_t
4322 x;
4323
cristy3ed852e2009-09-05 21:47:34 +00004324 if (status == MagickFalse)
4325 continue;
cristy6ebe97c2010-07-03 01:17:28 +00004326 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
4327 (width/2L),image->columns+width,width,exception);
cristy3ed852e2009-09-05 21:47:34 +00004328 q=QueueCacheViewAuthenticPixels(noise_view,0,y,noise_image->columns,1,
4329 exception);
4330 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
4331 {
4332 status=MagickFalse;
4333 continue;
4334 }
4335 indexes=GetCacheViewVirtualIndexQueue(image_view);
4336 noise_indexes=GetCacheViewAuthenticIndexQueue(noise_view);
4337 id=GetOpenMPThreadId();
cristybb503372010-05-27 20:51:26 +00004338 for (x=0; x < (ssize_t) noise_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004339 {
4340 MagickPixelPacket
4341 pixel;
4342
4343 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004344 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +00004345
4346 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004347 *restrict s;
cristy3ed852e2009-09-05 21:47:34 +00004348
cristybb503372010-05-27 20:51:26 +00004349 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00004350 u,
4351 v;
4352
4353 r=p;
4354 s=indexes+x;
4355 ResetMedianPixelList(pixel_list[id]);
cristybb503372010-05-27 20:51:26 +00004356 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00004357 {
cristybb503372010-05-27 20:51:26 +00004358 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00004359 InsertMedianPixelList(image,r+u,s+u,pixel_list[id]);
4360 r+=image->columns+width;
4361 s+=image->columns+width;
4362 }
4363 pixel=GetNonpeakMedianPixelList(pixel_list[id]);
4364 SetPixelPacket(noise_image,&pixel,q,noise_indexes+x);
4365 p++;
4366 q++;
4367 }
4368 if (SyncCacheViewAuthenticPixels(noise_view,exception) == MagickFalse)
4369 status=MagickFalse;
4370 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4371 {
4372 MagickBooleanType
4373 proceed;
4374
cristyb5d5f722009-11-04 03:03:49 +00004375#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004376 #pragma omp critical (MagickCore_ReduceNoiseImage)
4377#endif
4378 proceed=SetImageProgress(image,ReduceNoiseImageTag,progress++,
4379 image->rows);
4380 if (proceed == MagickFalse)
4381 status=MagickFalse;
4382 }
4383 }
4384 noise_view=DestroyCacheView(noise_view);
4385 image_view=DestroyCacheView(image_view);
4386 pixel_list=DestroyMedianPixelListThreadSet(pixel_list);
4387 return(noise_image);
4388}
4389
4390/*
4391%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4392% %
4393% %
4394% %
4395% S e l e c t i v e B l u r I m a g e %
4396% %
4397% %
4398% %
4399%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4400%
4401% SelectiveBlurImage() selectively blur pixels within a contrast threshold.
4402% It is similar to the unsharpen mask that sharpens everything with contrast
4403% above a certain threshold.
4404%
4405% The format of the SelectiveBlurImage method is:
4406%
4407% Image *SelectiveBlurImage(const Image *image,const double radius,
4408% const double sigma,const double threshold,ExceptionInfo *exception)
4409% Image *SelectiveBlurImageChannel(const Image *image,
4410% const ChannelType channel,const double radius,const double sigma,
4411% const double threshold,ExceptionInfo *exception)
4412%
4413% A description of each parameter follows:
4414%
4415% o image: the image.
4416%
4417% o channel: the channel type.
4418%
4419% o radius: the radius of the Gaussian, in pixels, not counting the center
4420% pixel.
4421%
4422% o sigma: the standard deviation of the Gaussian, in pixels.
4423%
4424% o threshold: only pixels within this contrast threshold are included
4425% in the blur operation.
4426%
4427% o exception: return any errors or warnings in this structure.
4428%
4429*/
4430
4431static inline MagickBooleanType SelectiveContrast(const PixelPacket *p,
4432 const PixelPacket *q,const double threshold)
4433{
4434 if (fabs(PixelIntensity(p)-PixelIntensity(q)) < threshold)
4435 return(MagickTrue);
4436 return(MagickFalse);
4437}
4438
4439MagickExport Image *SelectiveBlurImage(const Image *image,const double radius,
4440 const double sigma,const double threshold,ExceptionInfo *exception)
4441{
4442 Image
4443 *blur_image;
4444
4445 blur_image=SelectiveBlurImageChannel(image,DefaultChannels,radius,sigma,
4446 threshold,exception);
4447 return(blur_image);
4448}
4449
4450MagickExport Image *SelectiveBlurImageChannel(const Image *image,
4451 const ChannelType channel,const double radius,const double sigma,
4452 const double threshold,ExceptionInfo *exception)
4453{
4454#define SelectiveBlurImageTag "SelectiveBlur/Image"
4455
cristy47e00502009-12-17 19:19:57 +00004456 CacheView
4457 *blur_view,
4458 *image_view;
4459
cristy3ed852e2009-09-05 21:47:34 +00004460 double
cristy3ed852e2009-09-05 21:47:34 +00004461 *kernel;
4462
4463 Image
4464 *blur_image;
4465
cristy3ed852e2009-09-05 21:47:34 +00004466 MagickBooleanType
4467 status;
4468
cristybb503372010-05-27 20:51:26 +00004469 MagickOffsetType
4470 progress;
4471
cristy3ed852e2009-09-05 21:47:34 +00004472 MagickPixelPacket
cristy3ed852e2009-09-05 21:47:34 +00004473 bias;
4474
cristybb503372010-05-27 20:51:26 +00004475 register ssize_t
cristy47e00502009-12-17 19:19:57 +00004476 i;
cristy3ed852e2009-09-05 21:47:34 +00004477
cristybb503372010-05-27 20:51:26 +00004478 size_t
cristy3ed852e2009-09-05 21:47:34 +00004479 width;
4480
cristybb503372010-05-27 20:51:26 +00004481 ssize_t
4482 j,
4483 u,
4484 v,
4485 y;
4486
cristy3ed852e2009-09-05 21:47:34 +00004487 /*
4488 Initialize blur image attributes.
4489 */
4490 assert(image != (Image *) NULL);
4491 assert(image->signature == MagickSignature);
4492 if (image->debug != MagickFalse)
4493 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4494 assert(exception != (ExceptionInfo *) NULL);
4495 assert(exception->signature == MagickSignature);
4496 width=GetOptimalKernelWidth1D(radius,sigma);
4497 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
4498 if (kernel == (double *) NULL)
4499 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00004500 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00004501 i=0;
cristy47e00502009-12-17 19:19:57 +00004502 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00004503 {
cristy47e00502009-12-17 19:19:57 +00004504 for (u=(-j); u <= j; u++)
4505 kernel[i++]=exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
4506 (2.0*MagickPI*MagickSigma*MagickSigma);
cristy3ed852e2009-09-05 21:47:34 +00004507 }
4508 if (image->debug != MagickFalse)
4509 {
4510 char
4511 format[MaxTextExtent],
4512 *message;
4513
cristy117ff172010-08-15 21:35:32 +00004514 register const double
4515 *k;
4516
cristybb503372010-05-27 20:51:26 +00004517 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00004518 u,
4519 v;
4520
cristy3ed852e2009-09-05 21:47:34 +00004521 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00004522 " SelectiveBlurImage with %.20gx%.20g kernel:",(double) width,(double)
4523 width);
cristy3ed852e2009-09-05 21:47:34 +00004524 message=AcquireString("");
4525 k=kernel;
cristybb503372010-05-27 20:51:26 +00004526 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00004527 {
4528 *message='\0';
cristye8c25f92010-06-03 00:53:06 +00004529 (void) FormatMagickString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy3ed852e2009-09-05 21:47:34 +00004530 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00004531 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00004532 {
4533 (void) FormatMagickString(format,MaxTextExtent,"%+f ",*k++);
4534 (void) ConcatenateString(&message,format);
4535 }
4536 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
4537 }
4538 message=DestroyString(message);
4539 }
4540 blur_image=CloneImage(image,0,0,MagickTrue,exception);
4541 if (blur_image == (Image *) NULL)
4542 return((Image *) NULL);
4543 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
4544 {
4545 InheritException(exception,&blur_image->exception);
4546 blur_image=DestroyImage(blur_image);
4547 return((Image *) NULL);
4548 }
4549 /*
4550 Threshold blur image.
4551 */
4552 status=MagickTrue;
4553 progress=0;
cristyddd82202009-11-03 20:14:50 +00004554 GetMagickPixelPacket(image,&bias);
4555 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00004556 image_view=AcquireCacheView(image);
4557 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00004558#if defined(MAGICKCORE_OPENMP_SUPPORT)
4559 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004560#endif
cristybb503372010-05-27 20:51:26 +00004561 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00004562 {
4563 MagickBooleanType
4564 sync;
4565
4566 MagickRealType
4567 gamma;
4568
4569 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004570 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00004571
4572 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004573 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00004574
4575 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004576 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00004577
cristy3ed852e2009-09-05 21:47:34 +00004578 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004579 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004580
cristy117ff172010-08-15 21:35:32 +00004581 register ssize_t
4582 x;
4583
cristy3ed852e2009-09-05 21:47:34 +00004584 if (status == MagickFalse)
4585 continue;
cristy117ff172010-08-15 21:35:32 +00004586 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
4587 (width/2L),image->columns+width,width,exception);
cristy3ed852e2009-09-05 21:47:34 +00004588 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
4589 exception);
4590 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
4591 {
4592 status=MagickFalse;
4593 continue;
4594 }
4595 indexes=GetCacheViewVirtualIndexQueue(image_view);
4596 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
cristybb503372010-05-27 20:51:26 +00004597 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004598 {
cristy3ed852e2009-09-05 21:47:34 +00004599 MagickPixelPacket
4600 pixel;
4601
4602 register const double
cristyc47d1f82009-11-26 01:44:43 +00004603 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00004604
cristybb503372010-05-27 20:51:26 +00004605 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00004606 u;
4607
cristy117ff172010-08-15 21:35:32 +00004608 ssize_t
4609 j,
4610 v;
4611
cristyddd82202009-11-03 20:14:50 +00004612 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00004613 k=kernel;
4614 gamma=0.0;
4615 j=0;
4616 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
4617 {
cristybb503372010-05-27 20:51:26 +00004618 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00004619 {
cristybb503372010-05-27 20:51:26 +00004620 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00004621 {
4622 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4623 {
4624 pixel.red+=(*k)*(p+u+j)->red;
4625 pixel.green+=(*k)*(p+u+j)->green;
4626 pixel.blue+=(*k)*(p+u+j)->blue;
4627 gamma+=(*k);
4628 k++;
4629 }
4630 }
cristyd99b0962010-05-29 23:14:26 +00004631 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00004632 }
4633 if (gamma != 0.0)
4634 {
4635 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
4636 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004637 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004638 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004639 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004640 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004641 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004642 }
4643 if ((channel & OpacityChannel) != 0)
4644 {
4645 gamma=0.0;
4646 j=0;
cristybb503372010-05-27 20:51:26 +00004647 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00004648 {
cristybb503372010-05-27 20:51:26 +00004649 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00004650 {
4651 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4652 {
4653 pixel.opacity+=(*k)*(p+u+j)->opacity;
4654 gamma+=(*k);
4655 k++;
4656 }
4657 }
cristyeaedf062010-05-29 22:36:02 +00004658 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00004659 }
4660 if (gamma != 0.0)
4661 {
4662 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4663 gamma);
cristyce70c172010-01-07 17:15:30 +00004664 SetOpacityPixelComponent(q,ClampToQuantum(gamma*
4665 GetOpacityPixelComponent(&pixel)));
cristy3ed852e2009-09-05 21:47:34 +00004666 }
4667 }
4668 if (((channel & IndexChannel) != 0) &&
4669 (image->colorspace == CMYKColorspace))
4670 {
4671 gamma=0.0;
4672 j=0;
cristybb503372010-05-27 20:51:26 +00004673 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00004674 {
cristybb503372010-05-27 20:51:26 +00004675 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00004676 {
4677 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4678 {
4679 pixel.index+=(*k)*indexes[x+u+j];
4680 gamma+=(*k);
4681 k++;
4682 }
4683 }
cristyeaedf062010-05-29 22:36:02 +00004684 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00004685 }
4686 if (gamma != 0.0)
4687 {
4688 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4689 gamma);
cristy6db48122010-01-11 00:18:07 +00004690 blur_indexes[x]=ClampToQuantum(gamma*
4691 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004692 }
4693 }
4694 }
4695 else
4696 {
4697 MagickRealType
4698 alpha;
4699
cristybb503372010-05-27 20:51:26 +00004700 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00004701 {
cristybb503372010-05-27 20:51:26 +00004702 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00004703 {
4704 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4705 {
cristy46f08202010-01-10 04:04:21 +00004706 alpha=(MagickRealType) (QuantumScale*
4707 GetAlphaPixelComponent(p+u+j));
cristy3ed852e2009-09-05 21:47:34 +00004708 pixel.red+=(*k)*alpha*(p+u+j)->red;
4709 pixel.green+=(*k)*alpha*(p+u+j)->green;
4710 pixel.blue+=(*k)*alpha*(p+u+j)->blue;
4711 pixel.opacity+=(*k)*(p+u+j)->opacity;
4712 gamma+=(*k)*alpha;
4713 k++;
4714 }
4715 }
cristyeaedf062010-05-29 22:36:02 +00004716 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00004717 }
4718 if (gamma != 0.0)
4719 {
4720 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
4721 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004722 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004723 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004724 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004725 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004726 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004727 }
4728 if ((channel & OpacityChannel) != 0)
4729 {
4730 gamma=0.0;
4731 j=0;
cristybb503372010-05-27 20:51:26 +00004732 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00004733 {
cristybb503372010-05-27 20:51:26 +00004734 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00004735 {
4736 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4737 {
4738 pixel.opacity+=(*k)*(p+u+j)->opacity;
4739 gamma+=(*k);
4740 k++;
4741 }
4742 }
cristyeaedf062010-05-29 22:36:02 +00004743 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00004744 }
4745 if (gamma != 0.0)
4746 {
4747 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4748 gamma);
cristy6db48122010-01-11 00:18:07 +00004749 SetOpacityPixelComponent(q,
4750 ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004751 }
4752 }
4753 if (((channel & IndexChannel) != 0) &&
4754 (image->colorspace == CMYKColorspace))
4755 {
4756 gamma=0.0;
4757 j=0;
cristybb503372010-05-27 20:51:26 +00004758 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00004759 {
cristybb503372010-05-27 20:51:26 +00004760 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00004761 {
4762 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4763 {
cristy46f08202010-01-10 04:04:21 +00004764 alpha=(MagickRealType) (QuantumScale*
4765 GetAlphaPixelComponent(p+u+j));
cristy3ed852e2009-09-05 21:47:34 +00004766 pixel.index+=(*k)*alpha*indexes[x+u+j];
4767 gamma+=(*k);
4768 k++;
4769 }
4770 }
cristyeaedf062010-05-29 22:36:02 +00004771 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00004772 }
4773 if (gamma != 0.0)
4774 {
4775 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4776 gamma);
cristy6db48122010-01-11 00:18:07 +00004777 blur_indexes[x]=ClampToQuantum(gamma*
4778 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004779 }
4780 }
4781 }
4782 p++;
4783 q++;
4784 }
4785 sync=SyncCacheViewAuthenticPixels(blur_view,exception);
4786 if (sync == MagickFalse)
4787 status=MagickFalse;
4788 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4789 {
4790 MagickBooleanType
4791 proceed;
4792
cristyb5d5f722009-11-04 03:03:49 +00004793#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004794 #pragma omp critical (MagickCore_SelectiveBlurImageChannel)
4795#endif
4796 proceed=SetImageProgress(image,SelectiveBlurImageTag,progress++,
4797 image->rows);
4798 if (proceed == MagickFalse)
4799 status=MagickFalse;
4800 }
4801 }
4802 blur_image->type=image->type;
4803 blur_view=DestroyCacheView(blur_view);
4804 image_view=DestroyCacheView(image_view);
4805 kernel=(double *) RelinquishMagickMemory(kernel);
4806 if (status == MagickFalse)
4807 blur_image=DestroyImage(blur_image);
4808 return(blur_image);
4809}
4810
4811/*
4812%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4813% %
4814% %
4815% %
4816% S h a d e I m a g e %
4817% %
4818% %
4819% %
4820%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4821%
4822% ShadeImage() shines a distant light on an image to create a
4823% three-dimensional effect. You control the positioning of the light with
4824% azimuth and elevation; azimuth is measured in degrees off the x axis
4825% and elevation is measured in pixels above the Z axis.
4826%
4827% The format of the ShadeImage method is:
4828%
4829% Image *ShadeImage(const Image *image,const MagickBooleanType gray,
4830% const double azimuth,const double elevation,ExceptionInfo *exception)
4831%
4832% A description of each parameter follows:
4833%
4834% o image: the image.
4835%
4836% o gray: A value other than zero shades the intensity of each pixel.
4837%
4838% o azimuth, elevation: Define the light source direction.
4839%
4840% o exception: return any errors or warnings in this structure.
4841%
4842*/
4843MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray,
4844 const double azimuth,const double elevation,ExceptionInfo *exception)
4845{
4846#define ShadeImageTag "Shade/Image"
4847
cristyc4c8d132010-01-07 01:58:38 +00004848 CacheView
4849 *image_view,
4850 *shade_view;
4851
cristy3ed852e2009-09-05 21:47:34 +00004852 Image
4853 *shade_image;
4854
cristy3ed852e2009-09-05 21:47:34 +00004855 MagickBooleanType
4856 status;
4857
cristybb503372010-05-27 20:51:26 +00004858 MagickOffsetType
4859 progress;
4860
cristy3ed852e2009-09-05 21:47:34 +00004861 PrimaryInfo
4862 light;
4863
cristybb503372010-05-27 20:51:26 +00004864 ssize_t
4865 y;
4866
cristy3ed852e2009-09-05 21:47:34 +00004867 /*
4868 Initialize shaded image attributes.
4869 */
4870 assert(image != (const Image *) NULL);
4871 assert(image->signature == MagickSignature);
4872 if (image->debug != MagickFalse)
4873 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4874 assert(exception != (ExceptionInfo *) NULL);
4875 assert(exception->signature == MagickSignature);
4876 shade_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
4877 if (shade_image == (Image *) NULL)
4878 return((Image *) NULL);
4879 if (SetImageStorageClass(shade_image,DirectClass) == MagickFalse)
4880 {
4881 InheritException(exception,&shade_image->exception);
4882 shade_image=DestroyImage(shade_image);
4883 return((Image *) NULL);
4884 }
4885 /*
4886 Compute the light vector.
4887 */
4888 light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))*
4889 cos(DegreesToRadians(elevation));
4890 light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))*
4891 cos(DegreesToRadians(elevation));
4892 light.z=(double) QuantumRange*sin(DegreesToRadians(elevation));
4893 /*
4894 Shade image.
4895 */
4896 status=MagickTrue;
4897 progress=0;
4898 image_view=AcquireCacheView(image);
4899 shade_view=AcquireCacheView(shade_image);
cristyb5d5f722009-11-04 03:03:49 +00004900#if defined(MAGICKCORE_OPENMP_SUPPORT)
4901 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004902#endif
cristybb503372010-05-27 20:51:26 +00004903 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00004904 {
4905 MagickRealType
4906 distance,
4907 normal_distance,
4908 shade;
4909
4910 PrimaryInfo
4911 normal;
4912
4913 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004914 *restrict p,
4915 *restrict s0,
4916 *restrict s1,
4917 *restrict s2;
cristy3ed852e2009-09-05 21:47:34 +00004918
cristy3ed852e2009-09-05 21:47:34 +00004919 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004920 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004921
cristy117ff172010-08-15 21:35:32 +00004922 register ssize_t
4923 x;
4924
cristy3ed852e2009-09-05 21:47:34 +00004925 if (status == MagickFalse)
4926 continue;
4927 p=GetCacheViewVirtualPixels(image_view,-1,y-1,image->columns+2,3,exception);
4928 q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1,
4929 exception);
4930 if ((p == (PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
4931 {
4932 status=MagickFalse;
4933 continue;
4934 }
4935 /*
4936 Shade this row of pixels.
4937 */
4938 normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */
4939 s0=p+1;
4940 s1=s0+image->columns+2;
4941 s2=s1+image->columns+2;
cristybb503372010-05-27 20:51:26 +00004942 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004943 {
4944 /*
4945 Determine the surface normal and compute shading.
4946 */
4947 normal.x=(double) (PixelIntensity(s0-1)+PixelIntensity(s1-1)+
4948 PixelIntensity(s2-1)-PixelIntensity(s0+1)-PixelIntensity(s1+1)-
4949 PixelIntensity(s2+1));
4950 normal.y=(double) (PixelIntensity(s2-1)+PixelIntensity(s2)+
4951 PixelIntensity(s2+1)-PixelIntensity(s0-1)-PixelIntensity(s0)-
4952 PixelIntensity(s0+1));
4953 if ((normal.x == 0.0) && (normal.y == 0.0))
4954 shade=light.z;
4955 else
4956 {
4957 shade=0.0;
4958 distance=normal.x*light.x+normal.y*light.y+normal.z*light.z;
4959 if (distance > MagickEpsilon)
4960 {
4961 normal_distance=
4962 normal.x*normal.x+normal.y*normal.y+normal.z*normal.z;
4963 if (normal_distance > (MagickEpsilon*MagickEpsilon))
4964 shade=distance/sqrt((double) normal_distance);
4965 }
4966 }
4967 if (gray != MagickFalse)
4968 {
4969 q->red=(Quantum) shade;
4970 q->green=(Quantum) shade;
4971 q->blue=(Quantum) shade;
4972 }
4973 else
4974 {
cristyce70c172010-01-07 17:15:30 +00004975 q->red=ClampToQuantum(QuantumScale*shade*s1->red);
4976 q->green=ClampToQuantum(QuantumScale*shade*s1->green);
4977 q->blue=ClampToQuantum(QuantumScale*shade*s1->blue);
cristy3ed852e2009-09-05 21:47:34 +00004978 }
4979 q->opacity=s1->opacity;
4980 s0++;
4981 s1++;
4982 s2++;
4983 q++;
4984 }
4985 if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
4986 status=MagickFalse;
4987 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4988 {
4989 MagickBooleanType
4990 proceed;
4991
cristyb5d5f722009-11-04 03:03:49 +00004992#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004993 #pragma omp critical (MagickCore_ShadeImage)
4994#endif
4995 proceed=SetImageProgress(image,ShadeImageTag,progress++,image->rows);
4996 if (proceed == MagickFalse)
4997 status=MagickFalse;
4998 }
4999 }
5000 shade_view=DestroyCacheView(shade_view);
5001 image_view=DestroyCacheView(image_view);
5002 if (status == MagickFalse)
5003 shade_image=DestroyImage(shade_image);
5004 return(shade_image);
5005}
5006
5007/*
5008%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5009% %
5010% %
5011% %
5012% S h a r p e n I m a g e %
5013% %
5014% %
5015% %
5016%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5017%
5018% SharpenImage() sharpens the image. We convolve the image with a Gaussian
5019% operator of the given radius and standard deviation (sigma). For
5020% reasonable results, radius should be larger than sigma. Use a radius of 0
5021% and SharpenImage() selects a suitable radius for you.
5022%
5023% Using a separable kernel would be faster, but the negative weights cancel
5024% out on the corners of the kernel producing often undesirable ringing in the
5025% filtered result; this can be avoided by using a 2D gaussian shaped image
5026% sharpening kernel instead.
5027%
5028% The format of the SharpenImage method is:
5029%
5030% Image *SharpenImage(const Image *image,const double radius,
5031% const double sigma,ExceptionInfo *exception)
5032% Image *SharpenImageChannel(const Image *image,const ChannelType channel,
5033% const double radius,const double sigma,ExceptionInfo *exception)
5034%
5035% A description of each parameter follows:
5036%
5037% o image: the image.
5038%
5039% o channel: the channel type.
5040%
5041% o radius: the radius of the Gaussian, in pixels, not counting the center
5042% pixel.
5043%
5044% o sigma: the standard deviation of the Laplacian, in pixels.
5045%
5046% o exception: return any errors or warnings in this structure.
5047%
5048*/
5049
5050MagickExport Image *SharpenImage(const Image *image,const double radius,
5051 const double sigma,ExceptionInfo *exception)
5052{
5053 Image
5054 *sharp_image;
5055
5056 sharp_image=SharpenImageChannel(image,DefaultChannels,radius,sigma,exception);
5057 return(sharp_image);
5058}
5059
5060MagickExport Image *SharpenImageChannel(const Image *image,
5061 const ChannelType channel,const double radius,const double sigma,
5062 ExceptionInfo *exception)
5063{
5064 double
cristy47e00502009-12-17 19:19:57 +00005065 *kernel,
5066 normalize;
cristy3ed852e2009-09-05 21:47:34 +00005067
5068 Image
5069 *sharp_image;
5070
cristybb503372010-05-27 20:51:26 +00005071 register ssize_t
cristy47e00502009-12-17 19:19:57 +00005072 i;
5073
cristybb503372010-05-27 20:51:26 +00005074 size_t
cristy3ed852e2009-09-05 21:47:34 +00005075 width;
5076
cristy117ff172010-08-15 21:35:32 +00005077 ssize_t
5078 j,
5079 u,
5080 v;
5081
cristy3ed852e2009-09-05 21:47:34 +00005082 assert(image != (const Image *) NULL);
5083 assert(image->signature == MagickSignature);
5084 if (image->debug != MagickFalse)
5085 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5086 assert(exception != (ExceptionInfo *) NULL);
5087 assert(exception->signature == MagickSignature);
5088 width=GetOptimalKernelWidth2D(radius,sigma);
5089 kernel=(double *) AcquireQuantumMemory((size_t) width*width,sizeof(*kernel));
5090 if (kernel == (double *) NULL)
5091 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy3ed852e2009-09-05 21:47:34 +00005092 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00005093 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +00005094 i=0;
5095 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00005096 {
cristy47e00502009-12-17 19:19:57 +00005097 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00005098 {
cristy47e00502009-12-17 19:19:57 +00005099 kernel[i]=(-exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
5100 (2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00005101 normalize+=kernel[i];
5102 i++;
5103 }
5104 }
5105 kernel[i/2]=(double) ((-2.0)*normalize);
5106 sharp_image=ConvolveImageChannel(image,channel,width,kernel,exception);
5107 kernel=(double *) RelinquishMagickMemory(kernel);
5108 return(sharp_image);
5109}
5110
5111/*
5112%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5113% %
5114% %
5115% %
5116% S p r e a d I m a g e %
5117% %
5118% %
5119% %
5120%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5121%
5122% SpreadImage() is a special effects method that randomly displaces each
5123% pixel in a block defined by the radius parameter.
5124%
5125% The format of the SpreadImage method is:
5126%
5127% Image *SpreadImage(const Image *image,const double radius,
5128% ExceptionInfo *exception)
5129%
5130% A description of each parameter follows:
5131%
5132% o image: the image.
5133%
5134% o radius: Choose a random pixel in a neighborhood of this extent.
5135%
5136% o exception: return any errors or warnings in this structure.
5137%
5138*/
5139MagickExport Image *SpreadImage(const Image *image,const double radius,
5140 ExceptionInfo *exception)
5141{
5142#define SpreadImageTag "Spread/Image"
5143
cristyfa112112010-01-04 17:48:07 +00005144 CacheView
5145 *image_view;
5146
cristy3ed852e2009-09-05 21:47:34 +00005147 Image
5148 *spread_image;
5149
cristy3ed852e2009-09-05 21:47:34 +00005150 MagickBooleanType
5151 status;
5152
cristybb503372010-05-27 20:51:26 +00005153 MagickOffsetType
5154 progress;
5155
cristy3ed852e2009-09-05 21:47:34 +00005156 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00005157 bias;
cristy3ed852e2009-09-05 21:47:34 +00005158
5159 RandomInfo
cristyfa112112010-01-04 17:48:07 +00005160 **restrict random_info;
cristy3ed852e2009-09-05 21:47:34 +00005161
5162 ResampleFilter
cristyfa112112010-01-04 17:48:07 +00005163 **restrict resample_filter;
cristy3ed852e2009-09-05 21:47:34 +00005164
cristybb503372010-05-27 20:51:26 +00005165 size_t
cristy3ed852e2009-09-05 21:47:34 +00005166 width;
5167
cristybb503372010-05-27 20:51:26 +00005168 ssize_t
5169 y;
5170
cristy3ed852e2009-09-05 21:47:34 +00005171 /*
5172 Initialize spread image attributes.
5173 */
5174 assert(image != (Image *) NULL);
5175 assert(image->signature == MagickSignature);
5176 if (image->debug != MagickFalse)
5177 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5178 assert(exception != (ExceptionInfo *) NULL);
5179 assert(exception->signature == MagickSignature);
5180 spread_image=CloneImage(image,image->columns,image->rows,MagickTrue,
5181 exception);
5182 if (spread_image == (Image *) NULL)
5183 return((Image *) NULL);
5184 if (SetImageStorageClass(spread_image,DirectClass) == MagickFalse)
5185 {
5186 InheritException(exception,&spread_image->exception);
5187 spread_image=DestroyImage(spread_image);
5188 return((Image *) NULL);
5189 }
5190 /*
5191 Spread image.
5192 */
5193 status=MagickTrue;
5194 progress=0;
cristyddd82202009-11-03 20:14:50 +00005195 GetMagickPixelPacket(spread_image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00005196 width=GetOptimalKernelWidth1D(radius,0.5);
cristyb2a11ae2010-02-22 00:53:36 +00005197 resample_filter=AcquireResampleFilterThreadSet(image,
5198 UndefinedVirtualPixelMethod,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00005199 random_info=AcquireRandomInfoThreadSet();
5200 image_view=AcquireCacheView(spread_image);
cristy59e0d3b2010-06-07 13:12:38 +00005201#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy8a7ea362010-03-10 20:31:43 +00005202 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00005203#endif
cristybb503372010-05-27 20:51:26 +00005204 for (y=0; y < (ssize_t) spread_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00005205 {
cristy6ebe97c2010-07-03 01:17:28 +00005206 int
5207 id;
5208
cristy3ed852e2009-09-05 21:47:34 +00005209 MagickPixelPacket
5210 pixel;
5211
5212 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00005213 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00005214
cristy3ed852e2009-09-05 21:47:34 +00005215 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00005216 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00005217
cristy117ff172010-08-15 21:35:32 +00005218 register ssize_t
5219 x;
5220
cristy3ed852e2009-09-05 21:47:34 +00005221 if (status == MagickFalse)
5222 continue;
5223 q=QueueCacheViewAuthenticPixels(image_view,0,y,spread_image->columns,1,
5224 exception);
5225 if (q == (PixelPacket *) NULL)
5226 {
5227 status=MagickFalse;
5228 continue;
5229 }
5230 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristyddd82202009-11-03 20:14:50 +00005231 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00005232 id=GetOpenMPThreadId();
cristybb503372010-05-27 20:51:26 +00005233 for (x=0; x < (ssize_t) spread_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00005234 {
5235 (void) ResamplePixelColor(resample_filter[id],(double) x+width*
5236 (GetPseudoRandomValue(random_info[id])-0.5),(double) y+width*
5237 (GetPseudoRandomValue(random_info[id])-0.5),&pixel);
5238 SetPixelPacket(spread_image,&pixel,q,indexes+x);
5239 q++;
5240 }
5241 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
5242 status=MagickFalse;
5243 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5244 {
5245 MagickBooleanType
5246 proceed;
5247
cristy59e0d3b2010-06-07 13:12:38 +00005248#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00005249 #pragma omp critical (MagickCore_SpreadImage)
5250#endif
5251 proceed=SetImageProgress(image,SpreadImageTag,progress++,image->rows);
5252 if (proceed == MagickFalse)
5253 status=MagickFalse;
5254 }
5255 }
5256 image_view=DestroyCacheView(image_view);
5257 random_info=DestroyRandomInfoThreadSet(random_info);
5258 resample_filter=DestroyResampleFilterThreadSet(resample_filter);
5259 return(spread_image);
5260}
5261
5262/*
5263%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5264% %
5265% %
5266% %
5267% U n s h a r p M a s k I m a g e %
5268% %
5269% %
5270% %
5271%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5272%
5273% UnsharpMaskImage() sharpens one or more image channels. We convolve the
5274% image with a Gaussian operator of the given radius and standard deviation
5275% (sigma). For reasonable results, radius should be larger than sigma. Use a
5276% radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
5277%
5278% The format of the UnsharpMaskImage method is:
5279%
5280% Image *UnsharpMaskImage(const Image *image,const double radius,
5281% const double sigma,const double amount,const double threshold,
5282% ExceptionInfo *exception)
5283% Image *UnsharpMaskImageChannel(const Image *image,
5284% const ChannelType channel,const double radius,const double sigma,
5285% const double amount,const double threshold,ExceptionInfo *exception)
5286%
5287% A description of each parameter follows:
5288%
5289% o image: the image.
5290%
5291% o channel: the channel type.
5292%
5293% o radius: the radius of the Gaussian, in pixels, not counting the center
5294% pixel.
5295%
5296% o sigma: the standard deviation of the Gaussian, in pixels.
5297%
5298% o amount: the percentage of the difference between the original and the
5299% blur image that is added back into the original.
5300%
5301% o threshold: the threshold in pixels needed to apply the diffence amount.
5302%
5303% o exception: return any errors or warnings in this structure.
5304%
5305*/
5306
5307MagickExport Image *UnsharpMaskImage(const Image *image,const double radius,
5308 const double sigma,const double amount,const double threshold,
5309 ExceptionInfo *exception)
5310{
5311 Image
5312 *sharp_image;
5313
5314 sharp_image=UnsharpMaskImageChannel(image,DefaultChannels,radius,sigma,amount,
5315 threshold,exception);
5316 return(sharp_image);
5317}
5318
5319MagickExport Image *UnsharpMaskImageChannel(const Image *image,
5320 const ChannelType channel,const double radius,const double sigma,
5321 const double amount,const double threshold,ExceptionInfo *exception)
5322{
5323#define SharpenImageTag "Sharpen/Image"
5324
cristyc4c8d132010-01-07 01:58:38 +00005325 CacheView
5326 *image_view,
5327 *unsharp_view;
5328
cristy3ed852e2009-09-05 21:47:34 +00005329 Image
5330 *unsharp_image;
5331
cristy3ed852e2009-09-05 21:47:34 +00005332 MagickBooleanType
5333 status;
5334
cristybb503372010-05-27 20:51:26 +00005335 MagickOffsetType
5336 progress;
5337
cristy3ed852e2009-09-05 21:47:34 +00005338 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00005339 bias;
cristy3ed852e2009-09-05 21:47:34 +00005340
5341 MagickRealType
5342 quantum_threshold;
5343
cristybb503372010-05-27 20:51:26 +00005344 ssize_t
5345 y;
5346
cristy3ed852e2009-09-05 21:47:34 +00005347 assert(image != (const Image *) NULL);
5348 assert(image->signature == MagickSignature);
5349 if (image->debug != MagickFalse)
5350 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5351 assert(exception != (ExceptionInfo *) NULL);
5352 unsharp_image=BlurImageChannel(image,channel,radius,sigma,exception);
5353 if (unsharp_image == (Image *) NULL)
5354 return((Image *) NULL);
5355 quantum_threshold=(MagickRealType) QuantumRange*threshold;
5356 /*
5357 Unsharp-mask image.
5358 */
5359 status=MagickTrue;
5360 progress=0;
cristyddd82202009-11-03 20:14:50 +00005361 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00005362 image_view=AcquireCacheView(image);
5363 unsharp_view=AcquireCacheView(unsharp_image);
cristyb5d5f722009-11-04 03:03:49 +00005364#if defined(MAGICKCORE_OPENMP_SUPPORT)
5365 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00005366#endif
cristybb503372010-05-27 20:51:26 +00005367 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00005368 {
5369 MagickPixelPacket
5370 pixel;
5371
5372 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00005373 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00005374
5375 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00005376 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00005377
5378 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00005379 *restrict unsharp_indexes;
cristy3ed852e2009-09-05 21:47:34 +00005380
cristy3ed852e2009-09-05 21:47:34 +00005381 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00005382 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00005383
cristy117ff172010-08-15 21:35:32 +00005384 register ssize_t
5385 x;
5386
cristy3ed852e2009-09-05 21:47:34 +00005387 if (status == MagickFalse)
5388 continue;
5389 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
5390 q=GetCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
5391 exception);
5392 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
5393 {
5394 status=MagickFalse;
5395 continue;
5396 }
5397 indexes=GetCacheViewVirtualIndexQueue(image_view);
5398 unsharp_indexes=GetCacheViewAuthenticIndexQueue(unsharp_view);
cristyddd82202009-11-03 20:14:50 +00005399 pixel=bias;
cristybb503372010-05-27 20:51:26 +00005400 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00005401 {
5402 if ((channel & RedChannel) != 0)
5403 {
5404 pixel.red=p->red-(MagickRealType) q->red;
5405 if (fabs(2.0*pixel.red) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005406 pixel.red=(MagickRealType) GetRedPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005407 else
5408 pixel.red=(MagickRealType) p->red+(pixel.red*amount);
cristyce70c172010-01-07 17:15:30 +00005409 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00005410 }
5411 if ((channel & GreenChannel) != 0)
5412 {
5413 pixel.green=p->green-(MagickRealType) q->green;
5414 if (fabs(2.0*pixel.green) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005415 pixel.green=(MagickRealType) GetGreenPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005416 else
5417 pixel.green=(MagickRealType) p->green+(pixel.green*amount);
cristyce70c172010-01-07 17:15:30 +00005418 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00005419 }
5420 if ((channel & BlueChannel) != 0)
5421 {
5422 pixel.blue=p->blue-(MagickRealType) q->blue;
5423 if (fabs(2.0*pixel.blue) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005424 pixel.blue=(MagickRealType) GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005425 else
5426 pixel.blue=(MagickRealType) p->blue+(pixel.blue*amount);
cristyce70c172010-01-07 17:15:30 +00005427 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00005428 }
5429 if ((channel & OpacityChannel) != 0)
5430 {
5431 pixel.opacity=p->opacity-(MagickRealType) q->opacity;
5432 if (fabs(2.0*pixel.opacity) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005433 pixel.opacity=(MagickRealType) GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005434 else
5435 pixel.opacity=p->opacity+(pixel.opacity*amount);
cristyce70c172010-01-07 17:15:30 +00005436 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00005437 }
5438 if (((channel & IndexChannel) != 0) &&
5439 (image->colorspace == CMYKColorspace))
5440 {
5441 pixel.index=unsharp_indexes[x]-(MagickRealType) indexes[x];
5442 if (fabs(2.0*pixel.index) < quantum_threshold)
5443 pixel.index=(MagickRealType) unsharp_indexes[x];
5444 else
5445 pixel.index=(MagickRealType) unsharp_indexes[x]+(pixel.index*
5446 amount);
cristyce70c172010-01-07 17:15:30 +00005447 unsharp_indexes[x]=ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00005448 }
5449 p++;
5450 q++;
5451 }
5452 if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
5453 status=MagickFalse;
5454 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5455 {
5456 MagickBooleanType
5457 proceed;
5458
cristyb5d5f722009-11-04 03:03:49 +00005459#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00005460 #pragma omp critical (MagickCore_UnsharpMaskImageChannel)
5461#endif
5462 proceed=SetImageProgress(image,SharpenImageTag,progress++,image->rows);
5463 if (proceed == MagickFalse)
5464 status=MagickFalse;
5465 }
5466 }
5467 unsharp_image->type=image->type;
5468 unsharp_view=DestroyCacheView(unsharp_view);
5469 image_view=DestroyCacheView(image_view);
5470 if (status == MagickFalse)
5471 unsharp_image=DestroyImage(unsharp_image);
5472 return(unsharp_image);
5473}