blob: e80a3eac323ed57d61da43c8484c9e380d6ea490 [file] [log] [blame]
cristy3ed852e2009-09-05 21:47:34 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% EEEEE FFFFF FFFFF EEEEE CCCC TTTTT %
7% E F F E C T %
8% EEE FFF FFF EEE C T %
9% E F F E C T %
10% EEEEE F F EEEEE CCCC T %
11% %
12% %
13% MagickCore Image Effects Methods %
14% %
15% Software Design %
16% John Cristy %
17% October 1996 %
18% %
19% %
cristy7e41fe82010-12-04 23:12:08 +000020% Copyright 1999-2011 ImageMagick Studio LLC, a non-profit organization %
cristy3ed852e2009-09-05 21:47:34 +000021% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
26% http://www.imagemagick.org/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
36%
37%
38*/
39
40/*
41 Include declarations.
42*/
43#include "magick/studio.h"
cristyd43a46b2010-01-21 02:13:41 +000044#include "magick/accelerate.h"
cristy3ed852e2009-09-05 21:47:34 +000045#include "magick/blob.h"
46#include "magick/cache-view.h"
47#include "magick/color.h"
48#include "magick/color-private.h"
49#include "magick/colorspace.h"
50#include "magick/constitute.h"
51#include "magick/decorate.h"
52#include "magick/draw.h"
53#include "magick/enhance.h"
54#include "magick/exception.h"
55#include "magick/exception-private.h"
56#include "magick/effect.h"
57#include "magick/fx.h"
58#include "magick/gem.h"
59#include "magick/geometry.h"
60#include "magick/image-private.h"
61#include "magick/list.h"
62#include "magick/log.h"
63#include "magick/memory_.h"
64#include "magick/monitor.h"
65#include "magick/monitor-private.h"
66#include "magick/montage.h"
cristy6771f1e2010-03-05 19:43:39 +000067#include "magick/morphology.h"
cristy3ed852e2009-09-05 21:47:34 +000068#include "magick/paint.h"
69#include "magick/pixel-private.h"
70#include "magick/property.h"
71#include "magick/quantize.h"
72#include "magick/quantum.h"
73#include "magick/random_.h"
74#include "magick/random-private.h"
75#include "magick/resample.h"
76#include "magick/resample-private.h"
77#include "magick/resize.h"
78#include "magick/resource_.h"
79#include "magick/segment.h"
80#include "magick/shear.h"
81#include "magick/signature-private.h"
82#include "magick/string_.h"
83#include "magick/thread-private.h"
84#include "magick/transform.h"
85#include "magick/threshold.h"
86
87/*
88%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
89% %
90% %
91% %
92% A d a p t i v e B l u r I m a g e %
93% %
94% %
95% %
96%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
97%
98% AdaptiveBlurImage() adaptively blurs the image by blurring less
99% intensely near image edges and more intensely far from edges. We blur the
100% image with a Gaussian operator of the given radius and standard deviation
101% (sigma). For reasonable results, radius should be larger than sigma. Use a
102% radius of 0 and AdaptiveBlurImage() selects a suitable radius for you.
103%
104% The format of the AdaptiveBlurImage method is:
105%
106% Image *AdaptiveBlurImage(const Image *image,const double radius,
107% const double sigma,ExceptionInfo *exception)
108% Image *AdaptiveBlurImageChannel(const Image *image,
109% const ChannelType channel,double radius,const double sigma,
110% ExceptionInfo *exception)
111%
112% A description of each parameter follows:
113%
114% o image: the image.
115%
116% o channel: the channel type.
117%
118% o radius: the radius of the Gaussian, in pixels, not counting the center
119% pixel.
120%
121% o sigma: the standard deviation of the Laplacian, in pixels.
122%
123% o exception: return any errors or warnings in this structure.
124%
125*/
126
127MagickExport Image *AdaptiveBlurImage(const Image *image,const double radius,
128 const double sigma,ExceptionInfo *exception)
129{
130 Image
131 *blur_image;
132
133 blur_image=AdaptiveBlurImageChannel(image,DefaultChannels,radius,sigma,
134 exception);
135 return(blur_image);
136}
137
138MagickExport Image *AdaptiveBlurImageChannel(const Image *image,
139 const ChannelType channel,const double radius,const double sigma,
140 ExceptionInfo *exception)
141{
142#define AdaptiveBlurImageTag "Convolve/Image"
143#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
144
cristyc4c8d132010-01-07 01:58:38 +0000145 CacheView
146 *blur_view,
147 *edge_view,
148 *image_view;
149
cristy3ed852e2009-09-05 21:47:34 +0000150 double
cristy47e00502009-12-17 19:19:57 +0000151 **kernel,
152 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000153
154 Image
155 *blur_image,
156 *edge_image,
157 *gaussian_image;
158
cristy3ed852e2009-09-05 21:47:34 +0000159 MagickBooleanType
160 status;
161
cristybb503372010-05-27 20:51:26 +0000162 MagickOffsetType
163 progress;
164
cristy3ed852e2009-09-05 21:47:34 +0000165 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +0000166 bias;
cristy3ed852e2009-09-05 21:47:34 +0000167
cristybb503372010-05-27 20:51:26 +0000168 register ssize_t
cristy47e00502009-12-17 19:19:57 +0000169 i;
cristy3ed852e2009-09-05 21:47:34 +0000170
cristybb503372010-05-27 20:51:26 +0000171 size_t
cristy3ed852e2009-09-05 21:47:34 +0000172 width;
173
cristybb503372010-05-27 20:51:26 +0000174 ssize_t
175 j,
176 k,
177 u,
178 v,
179 y;
180
cristy3ed852e2009-09-05 21:47:34 +0000181 assert(image != (const Image *) NULL);
182 assert(image->signature == MagickSignature);
183 if (image->debug != MagickFalse)
184 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
185 assert(exception != (ExceptionInfo *) NULL);
186 assert(exception->signature == MagickSignature);
187 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
188 if (blur_image == (Image *) NULL)
189 return((Image *) NULL);
190 if (fabs(sigma) <= MagickEpsilon)
191 return(blur_image);
192 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
193 {
194 InheritException(exception,&blur_image->exception);
195 blur_image=DestroyImage(blur_image);
196 return((Image *) NULL);
197 }
198 /*
199 Edge detect the image brighness channel, level, blur, and level again.
200 */
201 edge_image=EdgeImage(image,radius,exception);
202 if (edge_image == (Image *) NULL)
203 {
204 blur_image=DestroyImage(blur_image);
205 return((Image *) NULL);
206 }
207 (void) LevelImage(edge_image,"20%,95%");
208 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,exception);
209 if (gaussian_image != (Image *) NULL)
210 {
211 edge_image=DestroyImage(edge_image);
212 edge_image=gaussian_image;
213 }
214 (void) LevelImage(edge_image,"10%,95%");
215 /*
216 Create a set of kernels from maximum (radius,sigma) to minimum.
217 */
218 width=GetOptimalKernelWidth2D(radius,sigma);
219 kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
220 if (kernel == (double **) NULL)
221 {
222 edge_image=DestroyImage(edge_image);
223 blur_image=DestroyImage(blur_image);
224 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
225 }
226 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
cristybb503372010-05-27 20:51:26 +0000227 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000228 {
229 kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)*
230 sizeof(**kernel));
231 if (kernel[i] == (double *) NULL)
232 break;
cristy47e00502009-12-17 19:19:57 +0000233 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000234 j=(ssize_t) (width-i)/2;
cristy47e00502009-12-17 19:19:57 +0000235 k=0;
236 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000237 {
cristy47e00502009-12-17 19:19:57 +0000238 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000239 {
cristy4205a3c2010-09-12 20:19:59 +0000240 kernel[i][k]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
241 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +0000242 normalize+=kernel[i][k];
243 k++;
cristy3ed852e2009-09-05 21:47:34 +0000244 }
245 }
cristy3ed852e2009-09-05 21:47:34 +0000246 if (fabs(normalize) <= MagickEpsilon)
247 normalize=1.0;
248 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000249 for (k=0; k < (j*j); k++)
250 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000251 }
cristybb503372010-05-27 20:51:26 +0000252 if (i < (ssize_t) width)
cristy3ed852e2009-09-05 21:47:34 +0000253 {
254 for (i-=2; i >= 0; i-=2)
255 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
256 kernel=(double **) RelinquishMagickMemory(kernel);
257 edge_image=DestroyImage(edge_image);
258 blur_image=DestroyImage(blur_image);
259 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
260 }
261 /*
262 Adaptively blur image.
263 */
264 status=MagickTrue;
265 progress=0;
cristyddd82202009-11-03 20:14:50 +0000266 GetMagickPixelPacket(image,&bias);
267 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000268 image_view=AcquireCacheView(image);
269 edge_view=AcquireCacheView(edge_image);
270 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000271#if defined(MAGICKCORE_OPENMP_SUPPORT)
272 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000273#endif
cristybb503372010-05-27 20:51:26 +0000274 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000275 {
276 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000277 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000278
279 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000280 *restrict p,
281 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000282
283 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000284 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000285
cristy3ed852e2009-09-05 21:47:34 +0000286 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000287 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000288
cristy117ff172010-08-15 21:35:32 +0000289 register ssize_t
290 x;
291
cristy3ed852e2009-09-05 21:47:34 +0000292 if (status == MagickFalse)
293 continue;
294 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
295 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
296 exception);
297 if ((r == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
298 {
299 status=MagickFalse;
300 continue;
301 }
302 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
cristybb503372010-05-27 20:51:26 +0000303 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000304 {
305 MagickPixelPacket
306 pixel;
307
308 MagickRealType
309 alpha,
310 gamma;
311
312 register const double
cristyc47d1f82009-11-26 01:44:43 +0000313 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000314
cristybb503372010-05-27 20:51:26 +0000315 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000316 i,
317 u,
318 v;
319
320 gamma=0.0;
cristybb503372010-05-27 20:51:26 +0000321 i=(ssize_t) ceil((double) width*QuantumScale*PixelIntensity(r)-0.5);
cristy3ed852e2009-09-05 21:47:34 +0000322 if (i < 0)
323 i=0;
324 else
cristybb503372010-05-27 20:51:26 +0000325 if (i > (ssize_t) width)
326 i=(ssize_t) width;
cristy3ed852e2009-09-05 21:47:34 +0000327 if ((i & 0x01) != 0)
328 i--;
cristya21afde2010-07-02 00:45:40 +0000329 p=GetCacheViewVirtualPixels(image_view,x-((ssize_t) (width-i)/2L),y-
330 (ssize_t) ((width-i)/2L),width-i,width-i,exception);
cristy3ed852e2009-09-05 21:47:34 +0000331 if (p == (const PixelPacket *) NULL)
332 break;
333 indexes=GetCacheViewVirtualIndexQueue(image_view);
cristyddd82202009-11-03 20:14:50 +0000334 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +0000335 k=kernel[i];
cristybb503372010-05-27 20:51:26 +0000336 for (v=0; v < (ssize_t) (width-i); v++)
cristy3ed852e2009-09-05 21:47:34 +0000337 {
cristybb503372010-05-27 20:51:26 +0000338 for (u=0; u < (ssize_t) (width-i); u++)
cristy3ed852e2009-09-05 21:47:34 +0000339 {
340 alpha=1.0;
341 if (((channel & OpacityChannel) != 0) &&
342 (image->matte != MagickFalse))
cristy46f08202010-01-10 04:04:21 +0000343 alpha=(MagickRealType) (QuantumScale*GetAlphaPixelComponent(p));
cristy3ed852e2009-09-05 21:47:34 +0000344 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000345 pixel.red+=(*k)*alpha*GetRedPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000346 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000347 pixel.green+=(*k)*alpha*GetGreenPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000348 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000349 pixel.blue+=(*k)*alpha*GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000350 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000351 pixel.opacity+=(*k)*GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000352 if (((channel & IndexChannel) != 0) &&
353 (image->colorspace == CMYKColorspace))
354 pixel.index+=(*k)*alpha*indexes[x+(width-i)*v+u];
355 gamma+=(*k)*alpha;
356 k++;
357 p++;
358 }
359 }
360 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
361 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000362 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000363 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000364 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000365 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000366 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000367 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000368 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000369 if (((channel & IndexChannel) != 0) &&
370 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +0000371 blur_indexes[x]=ClampToQuantum(gamma*GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000372 q++;
373 r++;
374 }
375 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
376 status=MagickFalse;
377 if (image->progress_monitor != (MagickProgressMonitor) NULL)
378 {
379 MagickBooleanType
380 proceed;
381
cristyb5d5f722009-11-04 03:03:49 +0000382#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000383 #pragma omp critical (MagickCore_AdaptiveBlurImageChannel)
384#endif
385 proceed=SetImageProgress(image,AdaptiveBlurImageTag,progress++,
386 image->rows);
387 if (proceed == MagickFalse)
388 status=MagickFalse;
389 }
390 }
391 blur_image->type=image->type;
392 blur_view=DestroyCacheView(blur_view);
393 edge_view=DestroyCacheView(edge_view);
394 image_view=DestroyCacheView(image_view);
395 edge_image=DestroyImage(edge_image);
cristybb503372010-05-27 20:51:26 +0000396 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000397 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
398 kernel=(double **) RelinquishMagickMemory(kernel);
399 if (status == MagickFalse)
400 blur_image=DestroyImage(blur_image);
401 return(blur_image);
402}
403
404/*
405%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
406% %
407% %
408% %
409% A d a p t i v e S h a r p e n I m a g e %
410% %
411% %
412% %
413%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
414%
415% AdaptiveSharpenImage() adaptively sharpens the image by sharpening more
416% intensely near image edges and less intensely far from edges. We sharpen the
417% image with a Gaussian operator of the given radius and standard deviation
418% (sigma). For reasonable results, radius should be larger than sigma. Use a
419% radius of 0 and AdaptiveSharpenImage() selects a suitable radius for you.
420%
421% The format of the AdaptiveSharpenImage method is:
422%
423% Image *AdaptiveSharpenImage(const Image *image,const double radius,
424% const double sigma,ExceptionInfo *exception)
425% Image *AdaptiveSharpenImageChannel(const Image *image,
426% const ChannelType channel,double radius,const double sigma,
427% ExceptionInfo *exception)
428%
429% A description of each parameter follows:
430%
431% o image: the image.
432%
433% o channel: the channel type.
434%
435% o radius: the radius of the Gaussian, in pixels, not counting the center
436% pixel.
437%
438% o sigma: the standard deviation of the Laplacian, in pixels.
439%
440% o exception: return any errors or warnings in this structure.
441%
442*/
443
444MagickExport Image *AdaptiveSharpenImage(const Image *image,const double radius,
445 const double sigma,ExceptionInfo *exception)
446{
447 Image
448 *sharp_image;
449
450 sharp_image=AdaptiveSharpenImageChannel(image,DefaultChannels,radius,sigma,
451 exception);
452 return(sharp_image);
453}
454
455MagickExport Image *AdaptiveSharpenImageChannel(const Image *image,
456 const ChannelType channel,const double radius,const double sigma,
457 ExceptionInfo *exception)
458{
459#define AdaptiveSharpenImageTag "Convolve/Image"
460#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
461
cristyc4c8d132010-01-07 01:58:38 +0000462 CacheView
463 *sharp_view,
464 *edge_view,
465 *image_view;
466
cristy3ed852e2009-09-05 21:47:34 +0000467 double
cristy47e00502009-12-17 19:19:57 +0000468 **kernel,
469 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000470
471 Image
472 *sharp_image,
473 *edge_image,
474 *gaussian_image;
475
cristy3ed852e2009-09-05 21:47:34 +0000476 MagickBooleanType
477 status;
478
cristybb503372010-05-27 20:51:26 +0000479 MagickOffsetType
480 progress;
481
cristy3ed852e2009-09-05 21:47:34 +0000482 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +0000483 bias;
cristy3ed852e2009-09-05 21:47:34 +0000484
cristybb503372010-05-27 20:51:26 +0000485 register ssize_t
cristy47e00502009-12-17 19:19:57 +0000486 i;
cristy3ed852e2009-09-05 21:47:34 +0000487
cristybb503372010-05-27 20:51:26 +0000488 size_t
cristy3ed852e2009-09-05 21:47:34 +0000489 width;
490
cristybb503372010-05-27 20:51:26 +0000491 ssize_t
492 j,
493 k,
494 u,
495 v,
496 y;
497
cristy3ed852e2009-09-05 21:47:34 +0000498 assert(image != (const Image *) NULL);
499 assert(image->signature == MagickSignature);
500 if (image->debug != MagickFalse)
501 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
502 assert(exception != (ExceptionInfo *) NULL);
503 assert(exception->signature == MagickSignature);
504 sharp_image=CloneImage(image,0,0,MagickTrue,exception);
505 if (sharp_image == (Image *) NULL)
506 return((Image *) NULL);
507 if (fabs(sigma) <= MagickEpsilon)
508 return(sharp_image);
509 if (SetImageStorageClass(sharp_image,DirectClass) == MagickFalse)
510 {
511 InheritException(exception,&sharp_image->exception);
512 sharp_image=DestroyImage(sharp_image);
513 return((Image *) NULL);
514 }
515 /*
516 Edge detect the image brighness channel, level, sharp, and level again.
517 */
518 edge_image=EdgeImage(image,radius,exception);
519 if (edge_image == (Image *) NULL)
520 {
521 sharp_image=DestroyImage(sharp_image);
522 return((Image *) NULL);
523 }
524 (void) LevelImage(edge_image,"20%,95%");
525 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,exception);
526 if (gaussian_image != (Image *) NULL)
527 {
528 edge_image=DestroyImage(edge_image);
529 edge_image=gaussian_image;
530 }
531 (void) LevelImage(edge_image,"10%,95%");
532 /*
533 Create a set of kernels from maximum (radius,sigma) to minimum.
534 */
535 width=GetOptimalKernelWidth2D(radius,sigma);
536 kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
537 if (kernel == (double **) NULL)
538 {
539 edge_image=DestroyImage(edge_image);
540 sharp_image=DestroyImage(sharp_image);
541 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
542 }
543 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
cristybb503372010-05-27 20:51:26 +0000544 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000545 {
546 kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)*
547 sizeof(**kernel));
548 if (kernel[i] == (double *) NULL)
549 break;
cristy47e00502009-12-17 19:19:57 +0000550 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000551 j=(ssize_t) (width-i)/2;
cristy47e00502009-12-17 19:19:57 +0000552 k=0;
553 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000554 {
cristy47e00502009-12-17 19:19:57 +0000555 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000556 {
cristy4205a3c2010-09-12 20:19:59 +0000557 kernel[i][k]=(double) (-exp(-((double) u*u+v*v)/(2.0*MagickSigma*
558 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +0000559 normalize+=kernel[i][k];
560 k++;
cristy3ed852e2009-09-05 21:47:34 +0000561 }
562 }
cristy3ed852e2009-09-05 21:47:34 +0000563 if (fabs(normalize) <= MagickEpsilon)
564 normalize=1.0;
565 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000566 for (k=0; k < (j*j); k++)
567 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000568 }
cristybb503372010-05-27 20:51:26 +0000569 if (i < (ssize_t) width)
cristy3ed852e2009-09-05 21:47:34 +0000570 {
571 for (i-=2; i >= 0; i-=2)
572 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
573 kernel=(double **) RelinquishMagickMemory(kernel);
574 edge_image=DestroyImage(edge_image);
575 sharp_image=DestroyImage(sharp_image);
576 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
577 }
578 /*
579 Adaptively sharpen image.
580 */
581 status=MagickTrue;
582 progress=0;
cristyddd82202009-11-03 20:14:50 +0000583 GetMagickPixelPacket(image,&bias);
584 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000585 image_view=AcquireCacheView(image);
586 edge_view=AcquireCacheView(edge_image);
587 sharp_view=AcquireCacheView(sharp_image);
cristyb5d5f722009-11-04 03:03:49 +0000588#if defined(MAGICKCORE_OPENMP_SUPPORT)
589 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000590#endif
cristybb503372010-05-27 20:51:26 +0000591 for (y=0; y < (ssize_t) sharp_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000592 {
593 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000594 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000595
596 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000597 *restrict p,
598 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000599
600 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000601 *restrict sharp_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000602
cristy3ed852e2009-09-05 21:47:34 +0000603 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000604 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000605
cristy117ff172010-08-15 21:35:32 +0000606 register ssize_t
607 x;
608
cristy3ed852e2009-09-05 21:47:34 +0000609 if (status == MagickFalse)
610 continue;
611 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
612 q=QueueCacheViewAuthenticPixels(sharp_view,0,y,sharp_image->columns,1,
613 exception);
614 if ((r == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
615 {
616 status=MagickFalse;
617 continue;
618 }
619 sharp_indexes=GetCacheViewAuthenticIndexQueue(sharp_view);
cristybb503372010-05-27 20:51:26 +0000620 for (x=0; x < (ssize_t) sharp_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000621 {
622 MagickPixelPacket
623 pixel;
624
625 MagickRealType
626 alpha,
627 gamma;
628
629 register const double
cristyc47d1f82009-11-26 01:44:43 +0000630 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000631
cristybb503372010-05-27 20:51:26 +0000632 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000633 i,
634 u,
635 v;
636
637 gamma=0.0;
cristybb503372010-05-27 20:51:26 +0000638 i=(ssize_t) ceil((double) width*(QuantumRange-QuantumScale*
cristy1f9ce9f2010-04-28 11:55:12 +0000639 PixelIntensity(r))-0.5);
cristy3ed852e2009-09-05 21:47:34 +0000640 if (i < 0)
641 i=0;
642 else
cristybb503372010-05-27 20:51:26 +0000643 if (i > (ssize_t) width)
644 i=(ssize_t) width;
cristy3ed852e2009-09-05 21:47:34 +0000645 if ((i & 0x01) != 0)
646 i--;
cristy117ff172010-08-15 21:35:32 +0000647 p=GetCacheViewVirtualPixels(image_view,x-((ssize_t) (width-i)/2L),y-
648 (ssize_t) ((width-i)/2L),width-i,width-i,exception);
cristy3ed852e2009-09-05 21:47:34 +0000649 if (p == (const PixelPacket *) NULL)
650 break;
651 indexes=GetCacheViewVirtualIndexQueue(image_view);
652 k=kernel[i];
cristyddd82202009-11-03 20:14:50 +0000653 pixel=bias;
cristybb503372010-05-27 20:51:26 +0000654 for (v=0; v < (ssize_t) (width-i); v++)
cristy3ed852e2009-09-05 21:47:34 +0000655 {
cristybb503372010-05-27 20:51:26 +0000656 for (u=0; u < (ssize_t) (width-i); u++)
cristy3ed852e2009-09-05 21:47:34 +0000657 {
658 alpha=1.0;
659 if (((channel & OpacityChannel) != 0) &&
660 (image->matte != MagickFalse))
cristy46f08202010-01-10 04:04:21 +0000661 alpha=(MagickRealType) (QuantumScale*GetAlphaPixelComponent(p));
cristy3ed852e2009-09-05 21:47:34 +0000662 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000663 pixel.red+=(*k)*alpha*GetRedPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000664 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000665 pixel.green+=(*k)*alpha*GetGreenPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000666 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000667 pixel.blue+=(*k)*alpha*GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000668 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000669 pixel.opacity+=(*k)*GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000670 if (((channel & IndexChannel) != 0) &&
671 (image->colorspace == CMYKColorspace))
672 pixel.index+=(*k)*alpha*indexes[x+(width-i)*v+u];
673 gamma+=(*k)*alpha;
674 k++;
675 p++;
676 }
677 }
678 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
679 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000680 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000681 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000682 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000683 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000684 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000685 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000686 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000687 if (((channel & IndexChannel) != 0) &&
688 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +0000689 sharp_indexes[x]=ClampToQuantum(gamma*GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000690 q++;
691 r++;
692 }
693 if (SyncCacheViewAuthenticPixels(sharp_view,exception) == MagickFalse)
694 status=MagickFalse;
695 if (image->progress_monitor != (MagickProgressMonitor) NULL)
696 {
697 MagickBooleanType
698 proceed;
699
cristyb5d5f722009-11-04 03:03:49 +0000700#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000701 #pragma omp critical (MagickCore_AdaptiveSharpenImageChannel)
702#endif
703 proceed=SetImageProgress(image,AdaptiveSharpenImageTag,progress++,
704 image->rows);
705 if (proceed == MagickFalse)
706 status=MagickFalse;
707 }
708 }
709 sharp_image->type=image->type;
710 sharp_view=DestroyCacheView(sharp_view);
711 edge_view=DestroyCacheView(edge_view);
712 image_view=DestroyCacheView(image_view);
713 edge_image=DestroyImage(edge_image);
cristybb503372010-05-27 20:51:26 +0000714 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000715 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
716 kernel=(double **) RelinquishMagickMemory(kernel);
717 if (status == MagickFalse)
718 sharp_image=DestroyImage(sharp_image);
719 return(sharp_image);
720}
721
722/*
723%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
724% %
725% %
726% %
727% B l u r I m a g e %
728% %
729% %
730% %
731%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
732%
733% BlurImage() blurs an image. We convolve the image with a Gaussian operator
734% of the given radius and standard deviation (sigma). For reasonable results,
735% the radius should be larger than sigma. Use a radius of 0 and BlurImage()
736% selects a suitable radius for you.
737%
738% BlurImage() differs from GaussianBlurImage() in that it uses a separable
739% kernel which is faster but mathematically equivalent to the non-separable
740% kernel.
741%
742% The format of the BlurImage method is:
743%
744% Image *BlurImage(const Image *image,const double radius,
745% const double sigma,ExceptionInfo *exception)
746% Image *BlurImageChannel(const Image *image,const ChannelType channel,
747% const double radius,const double sigma,ExceptionInfo *exception)
748%
749% A description of each parameter follows:
750%
751% o image: the image.
752%
753% o channel: the channel type.
754%
755% o radius: the radius of the Gaussian, in pixels, not counting the center
756% pixel.
757%
758% o sigma: the standard deviation of the Gaussian, in pixels.
759%
760% o exception: return any errors or warnings in this structure.
761%
762*/
763
764MagickExport Image *BlurImage(const Image *image,const double radius,
765 const double sigma,ExceptionInfo *exception)
766{
767 Image
768 *blur_image;
769
770 blur_image=BlurImageChannel(image,DefaultChannels,radius,sigma,exception);
771 return(blur_image);
772}
773
cristybb503372010-05-27 20:51:26 +0000774static double *GetBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +0000775{
cristy3ed852e2009-09-05 21:47:34 +0000776 double
cristy47e00502009-12-17 19:19:57 +0000777 *kernel,
778 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000779
cristy117ff172010-08-15 21:35:32 +0000780 register ssize_t
781 i;
782
cristybb503372010-05-27 20:51:26 +0000783 ssize_t
cristy47e00502009-12-17 19:19:57 +0000784 j,
785 k;
cristy3ed852e2009-09-05 21:47:34 +0000786
cristy3ed852e2009-09-05 21:47:34 +0000787 /*
788 Generate a 1-D convolution kernel.
789 */
790 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
791 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
792 if (kernel == (double *) NULL)
793 return(0);
cristy3ed852e2009-09-05 21:47:34 +0000794 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000795 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +0000796 i=0;
797 for (k=(-j); k <= j; k++)
798 {
cristy4205a3c2010-09-12 20:19:59 +0000799 kernel[i]=(double) (exp(-((double) k*k)/(2.0*MagickSigma*MagickSigma))/
800 (MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +0000801 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +0000802 i++;
803 }
cristybb503372010-05-27 20:51:26 +0000804 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000805 kernel[i]/=normalize;
806 return(kernel);
807}
808
809MagickExport Image *BlurImageChannel(const Image *image,
810 const ChannelType channel,const double radius,const double sigma,
811 ExceptionInfo *exception)
812{
813#define BlurImageTag "Blur/Image"
814
cristyc4c8d132010-01-07 01:58:38 +0000815 CacheView
816 *blur_view,
817 *image_view;
818
cristy3ed852e2009-09-05 21:47:34 +0000819 double
820 *kernel;
821
822 Image
823 *blur_image;
824
cristy3ed852e2009-09-05 21:47:34 +0000825 MagickBooleanType
826 status;
827
cristybb503372010-05-27 20:51:26 +0000828 MagickOffsetType
829 progress;
830
cristy3ed852e2009-09-05 21:47:34 +0000831 MagickPixelPacket
cristy3ed852e2009-09-05 21:47:34 +0000832 bias;
833
cristybb503372010-05-27 20:51:26 +0000834 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000835 i;
836
cristybb503372010-05-27 20:51:26 +0000837 size_t
cristy3ed852e2009-09-05 21:47:34 +0000838 width;
839
cristybb503372010-05-27 20:51:26 +0000840 ssize_t
841 x,
842 y;
843
cristy3ed852e2009-09-05 21:47:34 +0000844 /*
845 Initialize blur image attributes.
846 */
847 assert(image != (Image *) NULL);
848 assert(image->signature == MagickSignature);
849 if (image->debug != MagickFalse)
850 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
851 assert(exception != (ExceptionInfo *) NULL);
852 assert(exception->signature == MagickSignature);
853 blur_image=CloneImage(image,0,0,MagickTrue,exception);
854 if (blur_image == (Image *) NULL)
855 return((Image *) NULL);
856 if (fabs(sigma) <= MagickEpsilon)
857 return(blur_image);
858 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
859 {
860 InheritException(exception,&blur_image->exception);
861 blur_image=DestroyImage(blur_image);
862 return((Image *) NULL);
863 }
864 width=GetOptimalKernelWidth1D(radius,sigma);
865 kernel=GetBlurKernel(width,sigma);
866 if (kernel == (double *) NULL)
867 {
868 blur_image=DestroyImage(blur_image);
869 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
870 }
871 if (image->debug != MagickFalse)
872 {
873 char
874 format[MaxTextExtent],
875 *message;
876
877 register const double
878 *k;
879
880 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +0000881 " BlurImage with %.20g kernel:",(double) width);
cristy3ed852e2009-09-05 21:47:34 +0000882 message=AcquireString("");
883 k=kernel;
cristybb503372010-05-27 20:51:26 +0000884 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000885 {
886 *message='\0';
cristye8c25f92010-06-03 00:53:06 +0000887 (void) FormatMagickString(format,MaxTextExtent,"%.20g: ",(double) i);
cristy3ed852e2009-09-05 21:47:34 +0000888 (void) ConcatenateString(&message,format);
cristye7f51092010-01-17 00:39:37 +0000889 (void) FormatMagickString(format,MaxTextExtent,"%g ",*k++);
cristy3ed852e2009-09-05 21:47:34 +0000890 (void) ConcatenateString(&message,format);
891 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
892 }
893 message=DestroyString(message);
894 }
895 /*
896 Blur rows.
897 */
898 status=MagickTrue;
899 progress=0;
cristyddd82202009-11-03 20:14:50 +0000900 GetMagickPixelPacket(image,&bias);
901 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000902 image_view=AcquireCacheView(image);
903 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000904#if defined(MAGICKCORE_OPENMP_SUPPORT)
905 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000906#endif
cristybb503372010-05-27 20:51:26 +0000907 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000908 {
909 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000910 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000911
912 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000913 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000914
915 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000916 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000917
cristy3ed852e2009-09-05 21:47:34 +0000918 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000919 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000920
cristy117ff172010-08-15 21:35:32 +0000921 register ssize_t
922 x;
923
cristy3ed852e2009-09-05 21:47:34 +0000924 if (status == MagickFalse)
925 continue;
cristy117ff172010-08-15 21:35:32 +0000926 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y,
927 image->columns+width,1,exception);
cristy3ed852e2009-09-05 21:47:34 +0000928 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
929 exception);
930 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
931 {
932 status=MagickFalse;
933 continue;
934 }
935 indexes=GetCacheViewVirtualIndexQueue(image_view);
936 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
cristybb503372010-05-27 20:51:26 +0000937 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000938 {
939 MagickPixelPacket
940 pixel;
941
942 register const double
cristyc47d1f82009-11-26 01:44:43 +0000943 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000944
945 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000946 *restrict kernel_pixels;
cristy3ed852e2009-09-05 21:47:34 +0000947
cristybb503372010-05-27 20:51:26 +0000948 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000949 i;
950
cristyddd82202009-11-03 20:14:50 +0000951 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +0000952 k=kernel;
953 kernel_pixels=p;
954 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
955 {
cristybb503372010-05-27 20:51:26 +0000956 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000957 {
958 pixel.red+=(*k)*kernel_pixels->red;
959 pixel.green+=(*k)*kernel_pixels->green;
960 pixel.blue+=(*k)*kernel_pixels->blue;
961 k++;
962 kernel_pixels++;
963 }
964 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000965 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000966 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000967 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000968 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000969 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000970 if ((channel & OpacityChannel) != 0)
971 {
972 k=kernel;
973 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +0000974 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000975 {
976 pixel.opacity+=(*k)*kernel_pixels->opacity;
977 k++;
978 kernel_pixels++;
979 }
cristyce70c172010-01-07 17:15:30 +0000980 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000981 }
982 if (((channel & IndexChannel) != 0) &&
983 (image->colorspace == CMYKColorspace))
984 {
985 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000986 *restrict kernel_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000987
988 k=kernel;
989 kernel_indexes=indexes;
cristybb503372010-05-27 20:51:26 +0000990 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000991 {
992 pixel.index+=(*k)*(*kernel_indexes);
993 k++;
994 kernel_indexes++;
995 }
cristyce70c172010-01-07 17:15:30 +0000996 blur_indexes[x]=ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +0000997 }
998 }
999 else
1000 {
1001 MagickRealType
1002 alpha,
1003 gamma;
1004
1005 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00001006 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001007 {
cristy46f08202010-01-10 04:04:21 +00001008 alpha=(MagickRealType) (QuantumScale*
1009 GetAlphaPixelComponent(kernel_pixels));
cristy3ed852e2009-09-05 21:47:34 +00001010 pixel.red+=(*k)*alpha*kernel_pixels->red;
1011 pixel.green+=(*k)*alpha*kernel_pixels->green;
1012 pixel.blue+=(*k)*alpha*kernel_pixels->blue;
1013 gamma+=(*k)*alpha;
1014 k++;
1015 kernel_pixels++;
1016 }
1017 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1018 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001019 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001020 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001021 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001022 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001023 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001024 if ((channel & OpacityChannel) != 0)
1025 {
1026 k=kernel;
1027 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001028 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001029 {
1030 pixel.opacity+=(*k)*kernel_pixels->opacity;
1031 k++;
1032 kernel_pixels++;
1033 }
cristyce70c172010-01-07 17:15:30 +00001034 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001035 }
1036 if (((channel & IndexChannel) != 0) &&
1037 (image->colorspace == CMYKColorspace))
1038 {
1039 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001040 *restrict kernel_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001041
1042 k=kernel;
1043 kernel_pixels=p;
1044 kernel_indexes=indexes;
cristybb503372010-05-27 20:51:26 +00001045 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001046 {
cristy46f08202010-01-10 04:04:21 +00001047 alpha=(MagickRealType) (QuantumScale*
1048 GetAlphaPixelComponent(kernel_pixels));
cristy3ed852e2009-09-05 21:47:34 +00001049 pixel.index+=(*k)*alpha*(*kernel_indexes);
1050 k++;
1051 kernel_pixels++;
1052 kernel_indexes++;
1053 }
cristy46f08202010-01-10 04:04:21 +00001054 blur_indexes[x]=ClampToQuantum(gamma*
1055 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001056 }
1057 }
1058 p++;
1059 q++;
1060 }
1061 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1062 status=MagickFalse;
1063 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1064 {
1065 MagickBooleanType
1066 proceed;
1067
cristyb5d5f722009-11-04 03:03:49 +00001068#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001069 #pragma omp critical (MagickCore_BlurImageChannel)
1070#endif
1071 proceed=SetImageProgress(image,BlurImageTag,progress++,blur_image->rows+
1072 blur_image->columns);
1073 if (proceed == MagickFalse)
1074 status=MagickFalse;
1075 }
1076 }
1077 blur_view=DestroyCacheView(blur_view);
1078 image_view=DestroyCacheView(image_view);
1079 /*
1080 Blur columns.
1081 */
1082 image_view=AcquireCacheView(blur_image);
1083 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00001084#if defined(MAGICKCORE_OPENMP_SUPPORT)
1085 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001086#endif
cristybb503372010-05-27 20:51:26 +00001087 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001088 {
1089 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001090 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001091
1092 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001093 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001094
1095 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001096 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001097
cristy3ed852e2009-09-05 21:47:34 +00001098 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001099 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001100
cristy117ff172010-08-15 21:35:32 +00001101 register ssize_t
1102 y;
1103
cristy3ed852e2009-09-05 21:47:34 +00001104 if (status == MagickFalse)
1105 continue;
cristy117ff172010-08-15 21:35:32 +00001106 p=GetCacheViewVirtualPixels(image_view,x,-((ssize_t) width/2L),1,
1107 image->rows+width,exception);
cristy3ed852e2009-09-05 21:47:34 +00001108 q=GetCacheViewAuthenticPixels(blur_view,x,0,1,blur_image->rows,exception);
1109 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1110 {
1111 status=MagickFalse;
1112 continue;
1113 }
1114 indexes=GetCacheViewVirtualIndexQueue(image_view);
1115 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
cristybb503372010-05-27 20:51:26 +00001116 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001117 {
1118 MagickPixelPacket
1119 pixel;
1120
1121 register const double
cristyc47d1f82009-11-26 01:44:43 +00001122 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00001123
1124 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001125 *restrict kernel_pixels;
cristy3ed852e2009-09-05 21:47:34 +00001126
cristybb503372010-05-27 20:51:26 +00001127 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001128 i;
1129
cristyddd82202009-11-03 20:14:50 +00001130 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00001131 k=kernel;
1132 kernel_pixels=p;
1133 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
1134 {
cristybb503372010-05-27 20:51:26 +00001135 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001136 {
1137 pixel.red+=(*k)*kernel_pixels->red;
1138 pixel.green+=(*k)*kernel_pixels->green;
1139 pixel.blue+=(*k)*kernel_pixels->blue;
1140 k++;
1141 kernel_pixels++;
1142 }
1143 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001144 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001145 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001146 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001147 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001148 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001149 if ((channel & OpacityChannel) != 0)
1150 {
1151 k=kernel;
1152 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001153 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001154 {
1155 pixel.opacity+=(*k)*kernel_pixels->opacity;
1156 k++;
1157 kernel_pixels++;
1158 }
cristyce70c172010-01-07 17:15:30 +00001159 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001160 }
1161 if (((channel & IndexChannel) != 0) &&
1162 (image->colorspace == CMYKColorspace))
1163 {
1164 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001165 *restrict kernel_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001166
1167 k=kernel;
1168 kernel_indexes=indexes;
cristybb503372010-05-27 20:51:26 +00001169 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001170 {
1171 pixel.index+=(*k)*(*kernel_indexes);
1172 k++;
1173 kernel_indexes++;
1174 }
cristyce70c172010-01-07 17:15:30 +00001175 blur_indexes[y]=ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00001176 }
1177 }
1178 else
1179 {
1180 MagickRealType
1181 alpha,
1182 gamma;
1183
1184 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00001185 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001186 {
cristy46f08202010-01-10 04:04:21 +00001187 alpha=(MagickRealType) (QuantumScale*
1188 GetAlphaPixelComponent(kernel_pixels));
cristy3ed852e2009-09-05 21:47:34 +00001189 pixel.red+=(*k)*alpha*kernel_pixels->red;
1190 pixel.green+=(*k)*alpha*kernel_pixels->green;
1191 pixel.blue+=(*k)*alpha*kernel_pixels->blue;
1192 gamma+=(*k)*alpha;
1193 k++;
1194 kernel_pixels++;
1195 }
1196 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1197 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001198 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001199 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001200 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001201 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001202 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001203 if ((channel & OpacityChannel) != 0)
1204 {
1205 k=kernel;
1206 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001207 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001208 {
1209 pixel.opacity+=(*k)*kernel_pixels->opacity;
1210 k++;
1211 kernel_pixels++;
1212 }
cristyce70c172010-01-07 17:15:30 +00001213 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001214 }
1215 if (((channel & IndexChannel) != 0) &&
1216 (image->colorspace == CMYKColorspace))
1217 {
1218 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001219 *restrict kernel_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001220
1221 k=kernel;
1222 kernel_pixels=p;
1223 kernel_indexes=indexes;
cristybb503372010-05-27 20:51:26 +00001224 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001225 {
cristy46f08202010-01-10 04:04:21 +00001226 alpha=(MagickRealType) (QuantumScale*
1227 GetAlphaPixelComponent(kernel_pixels));
cristy3ed852e2009-09-05 21:47:34 +00001228 pixel.index+=(*k)*alpha*(*kernel_indexes);
1229 k++;
1230 kernel_pixels++;
1231 kernel_indexes++;
1232 }
cristy46f08202010-01-10 04:04:21 +00001233 blur_indexes[y]=ClampToQuantum(gamma*
1234 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001235 }
1236 }
1237 p++;
1238 q++;
1239 }
1240 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1241 status=MagickFalse;
1242 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1243 {
1244 MagickBooleanType
1245 proceed;
1246
cristyb5d5f722009-11-04 03:03:49 +00001247#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001248 #pragma omp critical (MagickCore_BlurImageChannel)
1249#endif
1250 proceed=SetImageProgress(image,BlurImageTag,progress++,blur_image->rows+
1251 blur_image->columns);
1252 if (proceed == MagickFalse)
1253 status=MagickFalse;
1254 }
1255 }
1256 blur_view=DestroyCacheView(blur_view);
1257 image_view=DestroyCacheView(image_view);
1258 kernel=(double *) RelinquishMagickMemory(kernel);
1259 if (status == MagickFalse)
1260 blur_image=DestroyImage(blur_image);
1261 blur_image->type=image->type;
1262 return(blur_image);
1263}
1264
1265/*
1266%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1267% %
1268% %
1269% %
cristyfccdab92009-11-30 16:43:57 +00001270% C o n v o l v e I m a g e %
1271% %
1272% %
1273% %
1274%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1275%
1276% ConvolveImage() applies a custom convolution kernel to the image.
1277%
1278% The format of the ConvolveImage method is:
1279%
cristybb503372010-05-27 20:51:26 +00001280% Image *ConvolveImage(const Image *image,const size_t order,
cristyfccdab92009-11-30 16:43:57 +00001281% const double *kernel,ExceptionInfo *exception)
1282% Image *ConvolveImageChannel(const Image *image,const ChannelType channel,
cristy117ff172010-08-15 21:35:32 +00001283% const size_t order,const double *kernel,ExceptionInfo *exception)
cristyfccdab92009-11-30 16:43:57 +00001284%
1285% A description of each parameter follows:
1286%
1287% o image: the image.
1288%
1289% o channel: the channel type.
1290%
1291% o order: the number of columns and rows in the filter kernel.
1292%
1293% o kernel: An array of double representing the convolution kernel.
1294%
1295% o exception: return any errors or warnings in this structure.
1296%
1297*/
1298
cristybb503372010-05-27 20:51:26 +00001299MagickExport Image *ConvolveImage(const Image *image,const size_t order,
cristyfccdab92009-11-30 16:43:57 +00001300 const double *kernel,ExceptionInfo *exception)
1301{
1302 Image
1303 *convolve_image;
1304
1305 convolve_image=ConvolveImageChannel(image,DefaultChannels,order,kernel,
1306 exception);
1307 return(convolve_image);
1308}
1309
1310MagickExport Image *ConvolveImageChannel(const Image *image,
cristybb503372010-05-27 20:51:26 +00001311 const ChannelType channel,const size_t order,const double *kernel,
cristyfccdab92009-11-30 16:43:57 +00001312 ExceptionInfo *exception)
1313{
1314#define ConvolveImageTag "Convolve/Image"
1315
cristyc4c8d132010-01-07 01:58:38 +00001316 CacheView
1317 *convolve_view,
1318 *image_view;
1319
cristyfccdab92009-11-30 16:43:57 +00001320 double
1321 *normal_kernel;
1322
1323 Image
1324 *convolve_image;
1325
cristyfccdab92009-11-30 16:43:57 +00001326 MagickBooleanType
1327 status;
1328
cristybb503372010-05-27 20:51:26 +00001329 MagickOffsetType
1330 progress;
1331
cristyfccdab92009-11-30 16:43:57 +00001332 MagickPixelPacket
1333 bias;
1334
1335 MagickRealType
1336 gamma;
1337
cristybb503372010-05-27 20:51:26 +00001338 register ssize_t
cristyfccdab92009-11-30 16:43:57 +00001339 i;
1340
cristybb503372010-05-27 20:51:26 +00001341 size_t
cristyfccdab92009-11-30 16:43:57 +00001342 width;
1343
cristybb503372010-05-27 20:51:26 +00001344 ssize_t
1345 y;
1346
cristyfccdab92009-11-30 16:43:57 +00001347 /*
1348 Initialize convolve image attributes.
1349 */
1350 assert(image != (Image *) NULL);
1351 assert(image->signature == MagickSignature);
1352 if (image->debug != MagickFalse)
1353 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1354 assert(exception != (ExceptionInfo *) NULL);
1355 assert(exception->signature == MagickSignature);
1356 width=order;
1357 if ((width % 2) == 0)
1358 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
1359 convolve_image=CloneImage(image,0,0,MagickTrue,exception);
1360 if (convolve_image == (Image *) NULL)
1361 return((Image *) NULL);
1362 if (SetImageStorageClass(convolve_image,DirectClass) == MagickFalse)
1363 {
1364 InheritException(exception,&convolve_image->exception);
1365 convolve_image=DestroyImage(convolve_image);
1366 return((Image *) NULL);
1367 }
1368 if (image->debug != MagickFalse)
1369 {
1370 char
1371 format[MaxTextExtent],
1372 *message;
1373
cristy117ff172010-08-15 21:35:32 +00001374 register const double
1375 *k;
1376
cristybb503372010-05-27 20:51:26 +00001377 ssize_t
cristyfccdab92009-11-30 16:43:57 +00001378 u,
1379 v;
1380
cristyfccdab92009-11-30 16:43:57 +00001381 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00001382 " ConvolveImage with %.20gx%.20g kernel:",(double) width,(double)
1383 width);
cristyfccdab92009-11-30 16:43:57 +00001384 message=AcquireString("");
1385 k=kernel;
cristybb503372010-05-27 20:51:26 +00001386 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001387 {
1388 *message='\0';
cristye8c25f92010-06-03 00:53:06 +00001389 (void) FormatMagickString(format,MaxTextExtent,"%.20g: ",(double) v);
cristyfccdab92009-11-30 16:43:57 +00001390 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00001391 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001392 {
cristye7f51092010-01-17 00:39:37 +00001393 (void) FormatMagickString(format,MaxTextExtent,"%g ",*k++);
cristyfccdab92009-11-30 16:43:57 +00001394 (void) ConcatenateString(&message,format);
1395 }
1396 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
1397 }
1398 message=DestroyString(message);
1399 }
1400 /*
1401 Normalize kernel.
1402 */
1403 normal_kernel=(double *) AcquireQuantumMemory(width*width,
1404 sizeof(*normal_kernel));
1405 if (normal_kernel == (double *) NULL)
1406 {
1407 convolve_image=DestroyImage(convolve_image);
1408 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1409 }
1410 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00001411 for (i=0; i < (ssize_t) (width*width); i++)
cristyfccdab92009-11-30 16:43:57 +00001412 gamma+=kernel[i];
1413 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristybb503372010-05-27 20:51:26 +00001414 for (i=0; i < (ssize_t) (width*width); i++)
cristyfccdab92009-11-30 16:43:57 +00001415 normal_kernel[i]=gamma*kernel[i];
1416 /*
1417 Convolve image.
1418 */
1419 status=MagickTrue;
1420 progress=0;
1421 GetMagickPixelPacket(image,&bias);
1422 SetMagickPixelPacketBias(image,&bias);
1423 image_view=AcquireCacheView(image);
1424 convolve_view=AcquireCacheView(convolve_image);
1425#if defined(MAGICKCORE_OPENMP_SUPPORT)
1426 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1427#endif
cristybb503372010-05-27 20:51:26 +00001428 for (y=0; y < (ssize_t) image->rows; y++)
cristyfccdab92009-11-30 16:43:57 +00001429 {
1430 MagickBooleanType
1431 sync;
1432
1433 register const IndexPacket
1434 *restrict indexes;
1435
1436 register const PixelPacket
1437 *restrict p;
1438
1439 register IndexPacket
1440 *restrict convolve_indexes;
1441
cristyfccdab92009-11-30 16:43:57 +00001442 register PixelPacket
1443 *restrict q;
1444
cristy117ff172010-08-15 21:35:32 +00001445 register ssize_t
1446 x;
1447
cristyfccdab92009-11-30 16:43:57 +00001448 if (status == MagickFalse)
1449 continue;
cristyce889302010-06-30 19:16:36 +00001450 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
1451 (width/2L),image->columns+width,width,exception);
cristyfccdab92009-11-30 16:43:57 +00001452 q=GetCacheViewAuthenticPixels(convolve_view,0,y,convolve_image->columns,1,
1453 exception);
1454 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1455 {
1456 status=MagickFalse;
1457 continue;
1458 }
1459 indexes=GetCacheViewVirtualIndexQueue(image_view);
1460 convolve_indexes=GetCacheViewAuthenticIndexQueue(convolve_view);
cristybb503372010-05-27 20:51:26 +00001461 for (x=0; x < (ssize_t) image->columns; x++)
cristyfccdab92009-11-30 16:43:57 +00001462 {
cristyfccdab92009-11-30 16:43:57 +00001463 MagickPixelPacket
1464 pixel;
1465
1466 register const double
1467 *restrict k;
1468
1469 register const PixelPacket
1470 *restrict kernel_pixels;
1471
cristybb503372010-05-27 20:51:26 +00001472 register ssize_t
cristyfccdab92009-11-30 16:43:57 +00001473 u;
1474
cristy117ff172010-08-15 21:35:32 +00001475 ssize_t
1476 v;
1477
cristyfccdab92009-11-30 16:43:57 +00001478 pixel=bias;
1479 k=normal_kernel;
1480 kernel_pixels=p;
1481 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
1482 {
cristybb503372010-05-27 20:51:26 +00001483 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001484 {
cristybb503372010-05-27 20:51:26 +00001485 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001486 {
1487 pixel.red+=(*k)*kernel_pixels[u].red;
1488 pixel.green+=(*k)*kernel_pixels[u].green;
1489 pixel.blue+=(*k)*kernel_pixels[u].blue;
1490 k++;
1491 }
1492 kernel_pixels+=image->columns+width;
1493 }
1494 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001495 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001496 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001497 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001498 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001499 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001500 if ((channel & OpacityChannel) != 0)
1501 {
1502 k=normal_kernel;
1503 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001504 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001505 {
cristybb503372010-05-27 20:51:26 +00001506 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001507 {
1508 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
1509 k++;
1510 }
1511 kernel_pixels+=image->columns+width;
1512 }
cristyce70c172010-01-07 17:15:30 +00001513 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001514 }
1515 if (((channel & IndexChannel) != 0) &&
1516 (image->colorspace == CMYKColorspace))
1517 {
1518 register const IndexPacket
1519 *restrict kernel_indexes;
1520
1521 k=normal_kernel;
1522 kernel_indexes=indexes;
cristybb503372010-05-27 20:51:26 +00001523 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001524 {
cristybb503372010-05-27 20:51:26 +00001525 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001526 {
1527 pixel.index+=(*k)*kernel_indexes[u];
1528 k++;
1529 }
1530 kernel_indexes+=image->columns+width;
1531 }
cristyce70c172010-01-07 17:15:30 +00001532 convolve_indexes[x]=ClampToQuantum(pixel.index);
cristyfccdab92009-11-30 16:43:57 +00001533 }
1534 }
1535 else
1536 {
1537 MagickRealType
1538 alpha,
1539 gamma;
1540
1541 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00001542 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001543 {
cristybb503372010-05-27 20:51:26 +00001544 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001545 {
1546 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
1547 kernel_pixels[u].opacity));
1548 pixel.red+=(*k)*alpha*kernel_pixels[u].red;
1549 pixel.green+=(*k)*alpha*kernel_pixels[u].green;
1550 pixel.blue+=(*k)*alpha*kernel_pixels[u].blue;
cristyfccdab92009-11-30 16:43:57 +00001551 gamma+=(*k)*alpha;
1552 k++;
1553 }
1554 kernel_pixels+=image->columns+width;
1555 }
1556 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1557 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001558 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001559 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001560 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001561 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001562 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001563 if ((channel & OpacityChannel) != 0)
1564 {
1565 k=normal_kernel;
1566 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001567 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001568 {
cristybb503372010-05-27 20:51:26 +00001569 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001570 {
1571 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
1572 k++;
1573 }
1574 kernel_pixels+=image->columns+width;
1575 }
cristyce70c172010-01-07 17:15:30 +00001576 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001577 }
1578 if (((channel & IndexChannel) != 0) &&
1579 (image->colorspace == CMYKColorspace))
1580 {
1581 register const IndexPacket
1582 *restrict kernel_indexes;
1583
1584 k=normal_kernel;
1585 kernel_pixels=p;
1586 kernel_indexes=indexes;
cristybb503372010-05-27 20:51:26 +00001587 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001588 {
cristybb503372010-05-27 20:51:26 +00001589 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001590 {
1591 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
1592 kernel_pixels[u].opacity));
1593 pixel.index+=(*k)*alpha*kernel_indexes[u];
1594 k++;
1595 }
1596 kernel_pixels+=image->columns+width;
1597 kernel_indexes+=image->columns+width;
1598 }
cristy24b06da2010-01-09 23:05:56 +00001599 convolve_indexes[x]=ClampToQuantum(gamma*
1600 GetIndexPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001601 }
1602 }
1603 p++;
1604 q++;
1605 }
1606 sync=SyncCacheViewAuthenticPixels(convolve_view,exception);
1607 if (sync == MagickFalse)
1608 status=MagickFalse;
1609 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1610 {
1611 MagickBooleanType
1612 proceed;
1613
1614#if defined(MAGICKCORE_OPENMP_SUPPORT)
1615 #pragma omp critical (MagickCore_ConvolveImageChannel)
1616#endif
1617 proceed=SetImageProgress(image,ConvolveImageTag,progress++,image->rows);
1618 if (proceed == MagickFalse)
1619 status=MagickFalse;
1620 }
1621 }
1622 convolve_image->type=image->type;
1623 convolve_view=DestroyCacheView(convolve_view);
1624 image_view=DestroyCacheView(image_view);
1625 normal_kernel=(double *) RelinquishMagickMemory(normal_kernel);
1626 if (status == MagickFalse)
1627 convolve_image=DestroyImage(convolve_image);
1628 return(convolve_image);
1629}
1630
1631/*
1632%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1633% %
1634% %
1635% %
cristy3ed852e2009-09-05 21:47:34 +00001636% D e s p e c k l e I m a g e %
1637% %
1638% %
1639% %
1640%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1641%
1642% DespeckleImage() reduces the speckle noise in an image while perserving the
1643% edges of the original image.
1644%
1645% The format of the DespeckleImage method is:
1646%
1647% Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1648%
1649% A description of each parameter follows:
1650%
1651% o image: the image.
1652%
1653% o exception: return any errors or warnings in this structure.
1654%
1655*/
1656
cristybb503372010-05-27 20:51:26 +00001657static void Hull(const ssize_t x_offset,const ssize_t y_offset,
1658 const size_t columns,const size_t rows,Quantum *f,Quantum *g,
cristy3ed852e2009-09-05 21:47:34 +00001659 const int polarity)
1660{
cristy3ed852e2009-09-05 21:47:34 +00001661 MagickRealType
1662 v;
1663
cristy3ed852e2009-09-05 21:47:34 +00001664 register Quantum
1665 *p,
1666 *q,
1667 *r,
1668 *s;
1669
cristy117ff172010-08-15 21:35:32 +00001670 register ssize_t
1671 x;
1672
1673 ssize_t
1674 y;
1675
cristy3ed852e2009-09-05 21:47:34 +00001676 assert(f != (Quantum *) NULL);
1677 assert(g != (Quantum *) NULL);
1678 p=f+(columns+2);
1679 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001680 r=p+(y_offset*((ssize_t) columns+2)+x_offset);
1681 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001682 {
1683 p++;
1684 q++;
1685 r++;
1686 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001687 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001688 {
1689 v=(MagickRealType) (*p);
1690 if ((MagickRealType) *r >= (v+(MagickRealType) ScaleCharToQuantum(2)))
1691 v+=ScaleCharToQuantum(1);
1692 *q=(Quantum) v;
1693 p++;
1694 q++;
1695 r++;
1696 }
1697 else
cristybb503372010-05-27 20:51:26 +00001698 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001699 {
1700 v=(MagickRealType) (*p);
1701 if ((MagickRealType) *r <= (v-(MagickRealType) ScaleCharToQuantum(2)))
cristybb503372010-05-27 20:51:26 +00001702 v-=(ssize_t) ScaleCharToQuantum(1);
cristy3ed852e2009-09-05 21:47:34 +00001703 *q=(Quantum) v;
1704 p++;
1705 q++;
1706 r++;
1707 }
1708 p++;
1709 q++;
1710 r++;
1711 }
1712 p=f+(columns+2);
1713 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001714 r=q+(y_offset*((ssize_t) columns+2)+x_offset);
1715 s=q-(y_offset*((ssize_t) columns+2)+x_offset);
1716 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001717 {
1718 p++;
1719 q++;
1720 r++;
1721 s++;
1722 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001723 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001724 {
1725 v=(MagickRealType) (*q);
1726 if (((MagickRealType) *s >=
1727 (v+(MagickRealType) ScaleCharToQuantum(2))) &&
1728 ((MagickRealType) *r > v))
1729 v+=ScaleCharToQuantum(1);
1730 *p=(Quantum) v;
1731 p++;
1732 q++;
1733 r++;
1734 s++;
1735 }
1736 else
cristybb503372010-05-27 20:51:26 +00001737 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001738 {
1739 v=(MagickRealType) (*q);
1740 if (((MagickRealType) *s <=
1741 (v-(MagickRealType) ScaleCharToQuantum(2))) &&
1742 ((MagickRealType) *r < v))
1743 v-=(MagickRealType) ScaleCharToQuantum(1);
1744 *p=(Quantum) v;
1745 p++;
1746 q++;
1747 r++;
1748 s++;
1749 }
1750 p++;
1751 q++;
1752 r++;
1753 s++;
1754 }
1755}
1756
1757MagickExport Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1758{
1759#define DespeckleImageTag "Despeckle/Image"
1760
cristy2407fc22009-09-11 00:55:25 +00001761 CacheView
1762 *despeckle_view,
1763 *image_view;
1764
cristy3ed852e2009-09-05 21:47:34 +00001765 Image
1766 *despeckle_image;
1767
cristy3ed852e2009-09-05 21:47:34 +00001768 MagickBooleanType
1769 status;
1770
cristya58c3172011-02-19 19:23:11 +00001771 register ssize_t
1772 i;
1773
cristy3ed852e2009-09-05 21:47:34 +00001774 Quantum
cristy65b9f392011-02-22 14:22:54 +00001775 *restrict buffers,
1776 *restrict pixels;
cristy3ed852e2009-09-05 21:47:34 +00001777
1778 size_t
cristya58c3172011-02-19 19:23:11 +00001779 length,
1780 number_channels;
cristy117ff172010-08-15 21:35:32 +00001781
cristybb503372010-05-27 20:51:26 +00001782 static const ssize_t
cristy691a29e2009-09-11 00:44:10 +00001783 X[4] = {0, 1, 1,-1},
1784 Y[4] = {1, 0, 1, 1};
cristy3ed852e2009-09-05 21:47:34 +00001785
cristy3ed852e2009-09-05 21:47:34 +00001786 /*
1787 Allocate despeckled image.
1788 */
1789 assert(image != (const Image *) NULL);
1790 assert(image->signature == MagickSignature);
1791 if (image->debug != MagickFalse)
1792 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1793 assert(exception != (ExceptionInfo *) NULL);
1794 assert(exception->signature == MagickSignature);
1795 despeckle_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1796 exception);
1797 if (despeckle_image == (Image *) NULL)
1798 return((Image *) NULL);
1799 if (SetImageStorageClass(despeckle_image,DirectClass) == MagickFalse)
1800 {
1801 InheritException(exception,&despeckle_image->exception);
1802 despeckle_image=DestroyImage(despeckle_image);
1803 return((Image *) NULL);
1804 }
1805 /*
1806 Allocate image buffers.
1807 */
1808 length=(size_t) ((image->columns+2)*(image->rows+2));
cristy65b9f392011-02-22 14:22:54 +00001809 pixels=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1810 buffers=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1811 if ((pixels == (Quantum *) NULL) || (buffers == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001812 {
cristy65b9f392011-02-22 14:22:54 +00001813 if (buffers != (Quantum *) NULL)
1814 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1815 if (pixels != (Quantum *) NULL)
1816 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001817 despeckle_image=DestroyImage(despeckle_image);
1818 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1819 }
1820 /*
1821 Reduce speckle in the image.
1822 */
1823 status=MagickTrue;
cristy109695a2011-02-19 19:38:14 +00001824 number_channels=(size_t) (image->colorspace == CMYKColorspace ? 5 : 4);
cristy3ed852e2009-09-05 21:47:34 +00001825 image_view=AcquireCacheView(image);
1826 despeckle_view=AcquireCacheView(despeckle_image);
cristy8df3d002011-02-19 19:40:59 +00001827 for (i=0; i < (ssize_t) number_channels; i++)
cristy3ed852e2009-09-05 21:47:34 +00001828 {
cristy3ed852e2009-09-05 21:47:34 +00001829 register Quantum
1830 *buffer,
1831 *pixel;
1832
cristyc1488b52011-02-19 18:54:15 +00001833 register ssize_t
cristya58c3172011-02-19 19:23:11 +00001834 k,
cristyc1488b52011-02-19 18:54:15 +00001835 x;
1836
cristy117ff172010-08-15 21:35:32 +00001837 ssize_t
1838 j,
1839 y;
1840
cristy3ed852e2009-09-05 21:47:34 +00001841 if (status == MagickFalse)
1842 continue;
cristy65b9f392011-02-22 14:22:54 +00001843 pixel=pixels;
cristy3ed852e2009-09-05 21:47:34 +00001844 (void) ResetMagickMemory(pixel,0,length*sizeof(*pixel));
cristy65b9f392011-02-22 14:22:54 +00001845 buffer=buffers;
cristybb503372010-05-27 20:51:26 +00001846 j=(ssize_t) image->columns+2;
1847 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001848 {
cristya58c3172011-02-19 19:23:11 +00001849 register const IndexPacket
1850 *restrict indexes;
1851
cristy3ed852e2009-09-05 21:47:34 +00001852 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001853 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001854
1855 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1856 if (p == (const PixelPacket *) NULL)
1857 break;
cristya58c3172011-02-19 19:23:11 +00001858 indexes=GetCacheViewVirtualIndexQueue(image_view);
cristy3ed852e2009-09-05 21:47:34 +00001859 j++;
cristybb503372010-05-27 20:51:26 +00001860 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001861 {
cristya58c3172011-02-19 19:23:11 +00001862 switch (i)
cristy3ed852e2009-09-05 21:47:34 +00001863 {
cristyce70c172010-01-07 17:15:30 +00001864 case 0: pixel[j]=GetRedPixelComponent(p); break;
1865 case 1: pixel[j]=GetGreenPixelComponent(p); break;
1866 case 2: pixel[j]=GetBluePixelComponent(p); break;
1867 case 3: pixel[j]=GetOpacityPixelComponent(p); break;
cristya58c3172011-02-19 19:23:11 +00001868 case 4: pixel[j]=GetBlackPixelComponent(indexes,x); break;
cristy3ed852e2009-09-05 21:47:34 +00001869 default: break;
1870 }
1871 p++;
1872 j++;
1873 }
1874 j++;
1875 }
cristy3ed852e2009-09-05 21:47:34 +00001876 (void) ResetMagickMemory(buffer,0,length*sizeof(*buffer));
cristya58c3172011-02-19 19:23:11 +00001877 for (k=0; k < 4; k++)
cristy3ed852e2009-09-05 21:47:34 +00001878 {
cristya58c3172011-02-19 19:23:11 +00001879 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,1);
1880 Hull(-X[k],-Y[k],image->columns,image->rows,pixel,buffer,1);
1881 Hull(-X[k],-Y[k],image->columns,image->rows,pixel,buffer,-1);
1882 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,-1);
cristy3ed852e2009-09-05 21:47:34 +00001883 }
cristybb503372010-05-27 20:51:26 +00001884 j=(ssize_t) image->columns+2;
1885 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001886 {
1887 MagickBooleanType
1888 sync;
1889
cristya58c3172011-02-19 19:23:11 +00001890 register IndexPacket
1891 *restrict indexes;
1892
cristy3ed852e2009-09-05 21:47:34 +00001893 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001894 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001895
1896 q=GetCacheViewAuthenticPixels(despeckle_view,0,y,despeckle_image->columns,
1897 1,exception);
1898 if (q == (PixelPacket *) NULL)
1899 break;
cristya58c3172011-02-19 19:23:11 +00001900 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristy3ed852e2009-09-05 21:47:34 +00001901 j++;
cristybb503372010-05-27 20:51:26 +00001902 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001903 {
cristya58c3172011-02-19 19:23:11 +00001904 switch (i)
cristy3ed852e2009-09-05 21:47:34 +00001905 {
1906 case 0: q->red=pixel[j]; break;
1907 case 1: q->green=pixel[j]; break;
1908 case 2: q->blue=pixel[j]; break;
1909 case 3: q->opacity=pixel[j]; break;
cristya58c3172011-02-19 19:23:11 +00001910 case 4: indexes[x]=pixel[j]; break;
cristy3ed852e2009-09-05 21:47:34 +00001911 default: break;
1912 }
1913 q++;
1914 j++;
1915 }
1916 sync=SyncCacheViewAuthenticPixels(despeckle_view,exception);
1917 if (sync == MagickFalse)
1918 {
1919 status=MagickFalse;
1920 break;
1921 }
1922 j++;
1923 }
1924 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1925 {
1926 MagickBooleanType
1927 proceed;
1928
cristya58c3172011-02-19 19:23:11 +00001929 proceed=SetImageProgress(image,DespeckleImageTag,(MagickOffsetType) i,
1930 number_channels);
cristy3ed852e2009-09-05 21:47:34 +00001931 if (proceed == MagickFalse)
1932 status=MagickFalse;
1933 }
1934 }
1935 despeckle_view=DestroyCacheView(despeckle_view);
1936 image_view=DestroyCacheView(image_view);
cristy65b9f392011-02-22 14:22:54 +00001937 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1938 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001939 despeckle_image->type=image->type;
1940 if (status == MagickFalse)
1941 despeckle_image=DestroyImage(despeckle_image);
1942 return(despeckle_image);
1943}
1944
1945/*
1946%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1947% %
1948% %
1949% %
1950% E d g e I m a g e %
1951% %
1952% %
1953% %
1954%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1955%
1956% EdgeImage() finds edges in an image. Radius defines the radius of the
1957% convolution filter. Use a radius of 0 and EdgeImage() selects a suitable
1958% radius for you.
1959%
1960% The format of the EdgeImage method is:
1961%
1962% Image *EdgeImage(const Image *image,const double radius,
1963% ExceptionInfo *exception)
1964%
1965% A description of each parameter follows:
1966%
1967% o image: the image.
1968%
1969% o radius: the radius of the pixel neighborhood.
1970%
1971% o exception: return any errors or warnings in this structure.
1972%
1973*/
1974MagickExport Image *EdgeImage(const Image *image,const double radius,
1975 ExceptionInfo *exception)
1976{
1977 Image
1978 *edge_image;
1979
1980 double
1981 *kernel;
1982
cristybb503372010-05-27 20:51:26 +00001983 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001984 i;
1985
cristybb503372010-05-27 20:51:26 +00001986 size_t
cristy3ed852e2009-09-05 21:47:34 +00001987 width;
1988
1989 assert(image != (const Image *) NULL);
1990 assert(image->signature == MagickSignature);
1991 if (image->debug != MagickFalse)
1992 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1993 assert(exception != (ExceptionInfo *) NULL);
1994 assert(exception->signature == MagickSignature);
1995 width=GetOptimalKernelWidth1D(radius,0.5);
1996 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
1997 if (kernel == (double *) NULL)
1998 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00001999 for (i=0; i < (ssize_t) (width*width); i++)
cristy3ed852e2009-09-05 21:47:34 +00002000 kernel[i]=(-1.0);
2001 kernel[i/2]=(double) (width*width-1.0);
2002 edge_image=ConvolveImage(image,width,kernel,exception);
2003 kernel=(double *) RelinquishMagickMemory(kernel);
2004 return(edge_image);
2005}
2006
2007/*
2008%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2009% %
2010% %
2011% %
2012% E m b o s s I m a g e %
2013% %
2014% %
2015% %
2016%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2017%
2018% EmbossImage() returns a grayscale image with a three-dimensional effect.
2019% We convolve the image with a Gaussian operator of the given radius and
2020% standard deviation (sigma). For reasonable results, radius should be
2021% larger than sigma. Use a radius of 0 and Emboss() selects a suitable
2022% radius for you.
2023%
2024% The format of the EmbossImage method is:
2025%
2026% Image *EmbossImage(const Image *image,const double radius,
2027% const double sigma,ExceptionInfo *exception)
2028%
2029% A description of each parameter follows:
2030%
2031% o image: the image.
2032%
2033% o radius: the radius of the pixel neighborhood.
2034%
2035% o sigma: the standard deviation of the Gaussian, in pixels.
2036%
2037% o exception: return any errors or warnings in this structure.
2038%
2039*/
2040MagickExport Image *EmbossImage(const Image *image,const double radius,
2041 const double sigma,ExceptionInfo *exception)
2042{
2043 double
2044 *kernel;
2045
2046 Image
2047 *emboss_image;
2048
cristybb503372010-05-27 20:51:26 +00002049 register ssize_t
cristy47e00502009-12-17 19:19:57 +00002050 i;
2051
cristybb503372010-05-27 20:51:26 +00002052 size_t
cristy3ed852e2009-09-05 21:47:34 +00002053 width;
2054
cristy117ff172010-08-15 21:35:32 +00002055 ssize_t
2056 j,
2057 k,
2058 u,
2059 v;
2060
cristy3ed852e2009-09-05 21:47:34 +00002061 assert(image != (Image *) NULL);
2062 assert(image->signature == MagickSignature);
2063 if (image->debug != MagickFalse)
2064 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2065 assert(exception != (ExceptionInfo *) NULL);
2066 assert(exception->signature == MagickSignature);
2067 width=GetOptimalKernelWidth2D(radius,sigma);
2068 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2069 if (kernel == (double *) NULL)
2070 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00002071 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +00002072 k=j;
2073 i=0;
2074 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002075 {
cristy47e00502009-12-17 19:19:57 +00002076 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00002077 {
cristy4205a3c2010-09-12 20:19:59 +00002078 kernel[i]=(double) (((u < 0) || (v < 0) ? -8.0 : 8.0)*
cristy47e00502009-12-17 19:19:57 +00002079 exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
cristy4205a3c2010-09-12 20:19:59 +00002080 (2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +00002081 if (u != k)
cristy3ed852e2009-09-05 21:47:34 +00002082 kernel[i]=0.0;
2083 i++;
2084 }
cristy47e00502009-12-17 19:19:57 +00002085 k--;
cristy3ed852e2009-09-05 21:47:34 +00002086 }
2087 emboss_image=ConvolveImage(image,width,kernel,exception);
2088 if (emboss_image != (Image *) NULL)
2089 (void) EqualizeImage(emboss_image);
2090 kernel=(double *) RelinquishMagickMemory(kernel);
2091 return(emboss_image);
2092}
2093
2094/*
2095%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2096% %
2097% %
2098% %
cristy56a9e512010-01-06 18:18:55 +00002099% F i l t e r I m a g e %
2100% %
2101% %
2102% %
2103%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2104%
2105% FilterImage() applies a custom convolution kernel to the image.
2106%
2107% The format of the FilterImage method is:
2108%
cristy2be15382010-01-21 02:38:03 +00002109% Image *FilterImage(const Image *image,const KernelInfo *kernel,
cristy56a9e512010-01-06 18:18:55 +00002110% ExceptionInfo *exception)
2111% Image *FilterImageChannel(const Image *image,const ChannelType channel,
cristy2be15382010-01-21 02:38:03 +00002112% const KernelInfo *kernel,ExceptionInfo *exception)
cristy56a9e512010-01-06 18:18:55 +00002113%
2114% A description of each parameter follows:
2115%
2116% o image: the image.
2117%
2118% o channel: the channel type.
2119%
2120% o kernel: the filtering kernel.
2121%
2122% o exception: return any errors or warnings in this structure.
2123%
2124*/
2125
cristy2be15382010-01-21 02:38:03 +00002126MagickExport Image *FilterImage(const Image *image,const KernelInfo *kernel,
cristy56a9e512010-01-06 18:18:55 +00002127 ExceptionInfo *exception)
2128{
2129 Image
2130 *filter_image;
2131
2132 filter_image=FilterImageChannel(image,DefaultChannels,kernel,exception);
2133 return(filter_image);
2134}
2135
2136MagickExport Image *FilterImageChannel(const Image *image,
cristy2be15382010-01-21 02:38:03 +00002137 const ChannelType channel,const KernelInfo *kernel,ExceptionInfo *exception)
cristy56a9e512010-01-06 18:18:55 +00002138{
2139#define FilterImageTag "Filter/Image"
2140
2141 CacheView
2142 *filter_view,
2143 *image_view;
2144
cristy56a9e512010-01-06 18:18:55 +00002145 Image
2146 *filter_image;
2147
cristy56a9e512010-01-06 18:18:55 +00002148 MagickBooleanType
2149 status;
2150
cristybb503372010-05-27 20:51:26 +00002151 MagickOffsetType
2152 progress;
2153
cristy56a9e512010-01-06 18:18:55 +00002154 MagickPixelPacket
2155 bias;
2156
cristybb503372010-05-27 20:51:26 +00002157 ssize_t
2158 y;
2159
cristy56a9e512010-01-06 18:18:55 +00002160 /*
2161 Initialize filter image attributes.
2162 */
2163 assert(image != (Image *) NULL);
2164 assert(image->signature == MagickSignature);
2165 if (image->debug != MagickFalse)
2166 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2167 assert(exception != (ExceptionInfo *) NULL);
2168 assert(exception->signature == MagickSignature);
2169 if ((kernel->width % 2) == 0)
2170 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
2171 filter_image=CloneImage(image,0,0,MagickTrue,exception);
2172 if (filter_image == (Image *) NULL)
2173 return((Image *) NULL);
2174 if (SetImageStorageClass(filter_image,DirectClass) == MagickFalse)
2175 {
2176 InheritException(exception,&filter_image->exception);
2177 filter_image=DestroyImage(filter_image);
2178 return((Image *) NULL);
2179 }
2180 if (image->debug != MagickFalse)
2181 {
2182 char
2183 format[MaxTextExtent],
2184 *message;
2185
cristy117ff172010-08-15 21:35:32 +00002186 register const double
2187 *k;
2188
cristybb503372010-05-27 20:51:26 +00002189 ssize_t
cristy56a9e512010-01-06 18:18:55 +00002190 u,
2191 v;
2192
cristy56a9e512010-01-06 18:18:55 +00002193 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00002194 " FilterImage with %.20gx%.20g kernel:",(double) kernel->width,(double)
2195 kernel->height);
cristy56a9e512010-01-06 18:18:55 +00002196 message=AcquireString("");
2197 k=kernel->values;
cristybb503372010-05-27 20:51:26 +00002198 for (v=0; v < (ssize_t) kernel->height; v++)
cristy56a9e512010-01-06 18:18:55 +00002199 {
2200 *message='\0';
cristye8c25f92010-06-03 00:53:06 +00002201 (void) FormatMagickString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy56a9e512010-01-06 18:18:55 +00002202 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00002203 for (u=0; u < (ssize_t) kernel->width; u++)
cristy56a9e512010-01-06 18:18:55 +00002204 {
cristye7f51092010-01-17 00:39:37 +00002205 (void) FormatMagickString(format,MaxTextExtent,"%g ",*k++);
cristy56a9e512010-01-06 18:18:55 +00002206 (void) ConcatenateString(&message,format);
2207 }
2208 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
2209 }
2210 message=DestroyString(message);
2211 }
cristy36826ab2010-03-06 01:29:30 +00002212 status=AccelerateConvolveImage(image,kernel,filter_image,exception);
cristyd43a46b2010-01-21 02:13:41 +00002213 if (status == MagickTrue)
2214 return(filter_image);
cristy56a9e512010-01-06 18:18:55 +00002215 /*
2216 Filter image.
2217 */
2218 status=MagickTrue;
2219 progress=0;
2220 GetMagickPixelPacket(image,&bias);
2221 SetMagickPixelPacketBias(image,&bias);
2222 image_view=AcquireCacheView(image);
2223 filter_view=AcquireCacheView(filter_image);
2224#if defined(MAGICKCORE_OPENMP_SUPPORT)
2225 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2226#endif
cristybb503372010-05-27 20:51:26 +00002227 for (y=0; y < (ssize_t) image->rows; y++)
cristy56a9e512010-01-06 18:18:55 +00002228 {
2229 MagickBooleanType
2230 sync;
2231
2232 register const IndexPacket
2233 *restrict indexes;
2234
2235 register const PixelPacket
2236 *restrict p;
2237
2238 register IndexPacket
2239 *restrict filter_indexes;
2240
cristy56a9e512010-01-06 18:18:55 +00002241 register PixelPacket
2242 *restrict q;
2243
cristy117ff172010-08-15 21:35:32 +00002244 register ssize_t
2245 x;
2246
cristy56a9e512010-01-06 18:18:55 +00002247 if (status == MagickFalse)
2248 continue;
cristybb503372010-05-27 20:51:26 +00002249 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) kernel->width/2L),
cristy117ff172010-08-15 21:35:32 +00002250 y-(ssize_t) (kernel->height/2L),image->columns+kernel->width,
2251 kernel->height,exception);
cristy56a9e512010-01-06 18:18:55 +00002252 q=GetCacheViewAuthenticPixels(filter_view,0,y,filter_image->columns,1,
2253 exception);
2254 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2255 {
2256 status=MagickFalse;
2257 continue;
2258 }
2259 indexes=GetCacheViewVirtualIndexQueue(image_view);
2260 filter_indexes=GetCacheViewAuthenticIndexQueue(filter_view);
cristybb503372010-05-27 20:51:26 +00002261 for (x=0; x < (ssize_t) image->columns; x++)
cristy56a9e512010-01-06 18:18:55 +00002262 {
cristy56a9e512010-01-06 18:18:55 +00002263 MagickPixelPacket
2264 pixel;
2265
2266 register const double
2267 *restrict k;
2268
2269 register const PixelPacket
2270 *restrict kernel_pixels;
2271
cristybb503372010-05-27 20:51:26 +00002272 register ssize_t
cristy56a9e512010-01-06 18:18:55 +00002273 u;
2274
cristy117ff172010-08-15 21:35:32 +00002275 ssize_t
2276 v;
2277
cristy56a9e512010-01-06 18:18:55 +00002278 pixel=bias;
cristy36826ab2010-03-06 01:29:30 +00002279 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002280 kernel_pixels=p;
2281 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
2282 {
cristybb503372010-05-27 20:51:26 +00002283 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002284 {
cristybb503372010-05-27 20:51:26 +00002285 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002286 {
2287 pixel.red+=(*k)*kernel_pixels[u].red;
2288 pixel.green+=(*k)*kernel_pixels[u].green;
2289 pixel.blue+=(*k)*kernel_pixels[u].blue;
2290 k++;
2291 }
cristy36826ab2010-03-06 01:29:30 +00002292 kernel_pixels+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002293 }
2294 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002295 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002296 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002297 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002298 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002299 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002300 if ((channel & OpacityChannel) != 0)
2301 {
cristy36826ab2010-03-06 01:29:30 +00002302 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002303 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00002304 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002305 {
cristybb503372010-05-27 20:51:26 +00002306 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002307 {
2308 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
2309 k++;
2310 }
cristy36826ab2010-03-06 01:29:30 +00002311 kernel_pixels+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002312 }
cristyce70c172010-01-07 17:15:30 +00002313 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002314 }
2315 if (((channel & IndexChannel) != 0) &&
2316 (image->colorspace == CMYKColorspace))
2317 {
2318 register const IndexPacket
2319 *restrict kernel_indexes;
2320
cristy36826ab2010-03-06 01:29:30 +00002321 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002322 kernel_indexes=indexes;
cristybb503372010-05-27 20:51:26 +00002323 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002324 {
cristybb503372010-05-27 20:51:26 +00002325 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002326 {
2327 pixel.index+=(*k)*kernel_indexes[u];
2328 k++;
2329 }
cristy36826ab2010-03-06 01:29:30 +00002330 kernel_indexes+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002331 }
cristyce70c172010-01-07 17:15:30 +00002332 filter_indexes[x]=ClampToQuantum(pixel.index);
cristy56a9e512010-01-06 18:18:55 +00002333 }
2334 }
2335 else
2336 {
2337 MagickRealType
2338 alpha,
2339 gamma;
2340
2341 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00002342 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002343 {
cristybb503372010-05-27 20:51:26 +00002344 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002345 {
2346 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
2347 kernel_pixels[u].opacity));
2348 pixel.red+=(*k)*alpha*kernel_pixels[u].red;
2349 pixel.green+=(*k)*alpha*kernel_pixels[u].green;
2350 pixel.blue+=(*k)*alpha*kernel_pixels[u].blue;
2351 gamma+=(*k)*alpha;
2352 k++;
2353 }
cristy36826ab2010-03-06 01:29:30 +00002354 kernel_pixels+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002355 }
2356 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
2357 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002358 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002359 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002360 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002361 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002362 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002363 if ((channel & OpacityChannel) != 0)
2364 {
cristy36826ab2010-03-06 01:29:30 +00002365 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002366 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00002367 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002368 {
cristybb503372010-05-27 20:51:26 +00002369 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002370 {
2371 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
2372 k++;
2373 }
cristy36826ab2010-03-06 01:29:30 +00002374 kernel_pixels+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002375 }
cristyce70c172010-01-07 17:15:30 +00002376 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002377 }
2378 if (((channel & IndexChannel) != 0) &&
2379 (image->colorspace == CMYKColorspace))
2380 {
2381 register const IndexPacket
2382 *restrict kernel_indexes;
2383
cristy36826ab2010-03-06 01:29:30 +00002384 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002385 kernel_pixels=p;
2386 kernel_indexes=indexes;
cristybb503372010-05-27 20:51:26 +00002387 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002388 {
cristybb503372010-05-27 20:51:26 +00002389 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002390 {
2391 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
2392 kernel_pixels[u].opacity));
2393 pixel.index+=(*k)*alpha*kernel_indexes[u];
2394 k++;
2395 }
cristy36826ab2010-03-06 01:29:30 +00002396 kernel_pixels+=image->columns+kernel->width;
2397 kernel_indexes+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002398 }
cristy2115aea2010-01-09 23:16:08 +00002399 filter_indexes[x]=ClampToQuantum(gamma*
2400 GetIndexPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002401 }
2402 }
2403 p++;
2404 q++;
2405 }
2406 sync=SyncCacheViewAuthenticPixels(filter_view,exception);
2407 if (sync == MagickFalse)
2408 status=MagickFalse;
2409 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2410 {
2411 MagickBooleanType
2412 proceed;
2413
2414#if defined(MAGICKCORE_OPENMP_SUPPORT)
2415 #pragma omp critical (MagickCore_FilterImageChannel)
2416#endif
2417 proceed=SetImageProgress(image,FilterImageTag,progress++,image->rows);
2418 if (proceed == MagickFalse)
2419 status=MagickFalse;
2420 }
2421 }
2422 filter_image->type=image->type;
2423 filter_view=DestroyCacheView(filter_view);
2424 image_view=DestroyCacheView(image_view);
cristy56a9e512010-01-06 18:18:55 +00002425 if (status == MagickFalse)
2426 filter_image=DestroyImage(filter_image);
2427 return(filter_image);
2428}
2429
2430/*
2431%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2432% %
2433% %
2434% %
cristy3ed852e2009-09-05 21:47:34 +00002435% G a u s s i a n B l u r I m a g e %
2436% %
2437% %
2438% %
2439%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2440%
2441% GaussianBlurImage() blurs an image. We convolve the image with a
2442% Gaussian operator of the given radius and standard deviation (sigma).
2443% For reasonable results, the radius should be larger than sigma. Use a
2444% radius of 0 and GaussianBlurImage() selects a suitable radius for you
2445%
2446% The format of the GaussianBlurImage method is:
2447%
2448% Image *GaussianBlurImage(const Image *image,onst double radius,
2449% const double sigma,ExceptionInfo *exception)
2450% Image *GaussianBlurImageChannel(const Image *image,
2451% const ChannelType channel,const double radius,const double sigma,
2452% ExceptionInfo *exception)
2453%
2454% A description of each parameter follows:
2455%
2456% o image: the image.
2457%
2458% o channel: the channel type.
2459%
2460% o radius: the radius of the Gaussian, in pixels, not counting the center
2461% pixel.
2462%
2463% o sigma: the standard deviation of the Gaussian, in pixels.
2464%
2465% o exception: return any errors or warnings in this structure.
2466%
2467*/
2468
2469MagickExport Image *GaussianBlurImage(const Image *image,const double radius,
2470 const double sigma,ExceptionInfo *exception)
2471{
2472 Image
2473 *blur_image;
2474
2475 blur_image=GaussianBlurImageChannel(image,DefaultChannels,radius,sigma,
2476 exception);
2477 return(blur_image);
2478}
2479
2480MagickExport Image *GaussianBlurImageChannel(const Image *image,
2481 const ChannelType channel,const double radius,const double sigma,
2482 ExceptionInfo *exception)
2483{
2484 double
2485 *kernel;
2486
2487 Image
2488 *blur_image;
2489
cristybb503372010-05-27 20:51:26 +00002490 register ssize_t
cristy47e00502009-12-17 19:19:57 +00002491 i;
2492
cristybb503372010-05-27 20:51:26 +00002493 size_t
cristy3ed852e2009-09-05 21:47:34 +00002494 width;
2495
cristy117ff172010-08-15 21:35:32 +00002496 ssize_t
2497 j,
2498 u,
2499 v;
2500
cristy3ed852e2009-09-05 21:47:34 +00002501 assert(image != (const Image *) NULL);
2502 assert(image->signature == MagickSignature);
2503 if (image->debug != MagickFalse)
2504 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2505 assert(exception != (ExceptionInfo *) NULL);
2506 assert(exception->signature == MagickSignature);
2507 width=GetOptimalKernelWidth2D(radius,sigma);
2508 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2509 if (kernel == (double *) NULL)
2510 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00002511 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00002512 i=0;
cristy47e00502009-12-17 19:19:57 +00002513 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002514 {
cristy47e00502009-12-17 19:19:57 +00002515 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00002516 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
2517 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00002518 }
2519 blur_image=ConvolveImageChannel(image,channel,width,kernel,exception);
2520 kernel=(double *) RelinquishMagickMemory(kernel);
2521 return(blur_image);
2522}
2523
2524/*
2525%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2526% %
2527% %
2528% %
2529% M e d i a n F i l t e r I m a g e %
2530% %
2531% %
2532% %
2533%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2534%
2535% MedianFilterImage() applies a digital filter that improves the quality
2536% of a noisy image. Each pixel is replaced by the median in a set of
2537% neighboring pixels as defined by radius.
2538%
2539% The algorithm was contributed by Mike Edmonds and implements an insertion
2540% sort for selecting median color-channel values. For more on this algorithm
2541% see "Skip Lists: A probabilistic Alternative to Balanced Trees" by William
2542% Pugh in the June 1990 of Communications of the ACM.
2543%
2544% The format of the MedianFilterImage method is:
2545%
2546% Image *MedianFilterImage(const Image *image,const double radius,
2547% ExceptionInfo *exception)
2548%
2549% A description of each parameter follows:
2550%
2551% o image: the image.
2552%
2553% o radius: the radius of the pixel neighborhood.
2554%
2555% o exception: return any errors or warnings in this structure.
2556%
2557*/
2558
cristy69ec32d2011-02-27 23:57:09 +00002559#define ListChannels 5
cristy3ed852e2009-09-05 21:47:34 +00002560
cristy69ec32d2011-02-27 23:57:09 +00002561typedef struct _ListNode
cristy3ed852e2009-09-05 21:47:34 +00002562{
cristybb503372010-05-27 20:51:26 +00002563 size_t
cristy3ed852e2009-09-05 21:47:34 +00002564 next[9],
2565 count,
2566 signature;
cristy69ec32d2011-02-27 23:57:09 +00002567} ListNode;
cristy3ed852e2009-09-05 21:47:34 +00002568
cristy69ec32d2011-02-27 23:57:09 +00002569typedef struct _SkipList
cristy3ed852e2009-09-05 21:47:34 +00002570{
cristybb503372010-05-27 20:51:26 +00002571 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002572 level;
2573
cristy69ec32d2011-02-27 23:57:09 +00002574 ListNode
cristy3ed852e2009-09-05 21:47:34 +00002575 *nodes;
cristy69ec32d2011-02-27 23:57:09 +00002576} SkipList;
cristy3ed852e2009-09-05 21:47:34 +00002577
cristy69ec32d2011-02-27 23:57:09 +00002578typedef struct _PixelList
cristy3ed852e2009-09-05 21:47:34 +00002579{
cristybb503372010-05-27 20:51:26 +00002580 size_t
cristy3ed852e2009-09-05 21:47:34 +00002581 center,
2582 seed,
2583 signature;
2584
cristy69ec32d2011-02-27 23:57:09 +00002585 SkipList
2586 lists[ListChannels];
2587} PixelList;
cristy3ed852e2009-09-05 21:47:34 +00002588
cristy69ec32d2011-02-27 23:57:09 +00002589static PixelList *DestroyPixelList(PixelList *pixel_list)
cristy3ed852e2009-09-05 21:47:34 +00002590{
cristybb503372010-05-27 20:51:26 +00002591 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002592 i;
2593
cristy69ec32d2011-02-27 23:57:09 +00002594 if (pixel_list == (PixelList *) NULL)
2595 return((PixelList *) NULL);
2596 for (i=0; i < ListChannels; i++)
2597 if (pixel_list->lists[i].nodes != (ListNode *) NULL)
2598 pixel_list->lists[i].nodes=(ListNode *) RelinquishMagickMemory(
cristy3ed852e2009-09-05 21:47:34 +00002599 pixel_list->lists[i].nodes);
cristy69ec32d2011-02-27 23:57:09 +00002600 pixel_list=(PixelList *) RelinquishMagickMemory(pixel_list);
cristy3ed852e2009-09-05 21:47:34 +00002601 return(pixel_list);
2602}
2603
cristy69ec32d2011-02-27 23:57:09 +00002604static PixelList **DestroyPixelListThreadSet(PixelList **pixel_list)
cristy3ed852e2009-09-05 21:47:34 +00002605{
cristybb503372010-05-27 20:51:26 +00002606 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002607 i;
2608
cristy69ec32d2011-02-27 23:57:09 +00002609 assert(pixel_list != (PixelList **) NULL);
cristybb503372010-05-27 20:51:26 +00002610 for (i=0; i < (ssize_t) GetOpenMPMaximumThreads(); i++)
cristy69ec32d2011-02-27 23:57:09 +00002611 if (pixel_list[i] != (PixelList *) NULL)
2612 pixel_list[i]=DestroyPixelList(pixel_list[i]);
2613 pixel_list=(PixelList **) RelinquishMagickMemory(pixel_list);
cristy3ed852e2009-09-05 21:47:34 +00002614 return(pixel_list);
2615}
2616
cristy69ec32d2011-02-27 23:57:09 +00002617static PixelList *AcquirePixelList(const size_t width)
cristy3ed852e2009-09-05 21:47:34 +00002618{
cristy69ec32d2011-02-27 23:57:09 +00002619 PixelList
cristy3ed852e2009-09-05 21:47:34 +00002620 *pixel_list;
2621
cristybb503372010-05-27 20:51:26 +00002622 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002623 i;
2624
cristy69ec32d2011-02-27 23:57:09 +00002625 pixel_list=(PixelList *) AcquireMagickMemory(sizeof(*pixel_list));
2626 if (pixel_list == (PixelList *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002627 return(pixel_list);
2628 (void) ResetMagickMemory((void *) pixel_list,0,sizeof(*pixel_list));
2629 pixel_list->center=width*width/2;
cristy69ec32d2011-02-27 23:57:09 +00002630 for (i=0; i < ListChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +00002631 {
cristy69ec32d2011-02-27 23:57:09 +00002632 pixel_list->lists[i].nodes=(ListNode *) AcquireQuantumMemory(65537UL,
cristy3ed852e2009-09-05 21:47:34 +00002633 sizeof(*pixel_list->lists[i].nodes));
cristy69ec32d2011-02-27 23:57:09 +00002634 if (pixel_list->lists[i].nodes == (ListNode *) NULL)
2635 return(DestroyPixelList(pixel_list));
cristy3ed852e2009-09-05 21:47:34 +00002636 (void) ResetMagickMemory(pixel_list->lists[i].nodes,0,65537UL*
2637 sizeof(*pixel_list->lists[i].nodes));
2638 }
2639 pixel_list->signature=MagickSignature;
2640 return(pixel_list);
2641}
2642
cristy69ec32d2011-02-27 23:57:09 +00002643static PixelList **AcquirePixelListThreadSet(const size_t width)
cristy3ed852e2009-09-05 21:47:34 +00002644{
cristy69ec32d2011-02-27 23:57:09 +00002645 PixelList
cristy3ed852e2009-09-05 21:47:34 +00002646 **pixel_list;
2647
cristy117ff172010-08-15 21:35:32 +00002648 register ssize_t
2649 i;
2650
cristybb503372010-05-27 20:51:26 +00002651 size_t
cristy3ed852e2009-09-05 21:47:34 +00002652 number_threads;
2653
2654 number_threads=GetOpenMPMaximumThreads();
cristy69ec32d2011-02-27 23:57:09 +00002655 pixel_list=(PixelList **) AcquireQuantumMemory(number_threads,
cristy3ed852e2009-09-05 21:47:34 +00002656 sizeof(*pixel_list));
cristy69ec32d2011-02-27 23:57:09 +00002657 if (pixel_list == (PixelList **) NULL)
2658 return((PixelList **) NULL);
cristy3ed852e2009-09-05 21:47:34 +00002659 (void) ResetMagickMemory(pixel_list,0,number_threads*sizeof(*pixel_list));
cristybb503372010-05-27 20:51:26 +00002660 for (i=0; i < (ssize_t) number_threads; i++)
cristy3ed852e2009-09-05 21:47:34 +00002661 {
cristy69ec32d2011-02-27 23:57:09 +00002662 pixel_list[i]=AcquirePixelList(width);
2663 if (pixel_list[i] == (PixelList *) NULL)
2664 return(DestroyPixelListThreadSet(pixel_list));
cristy3ed852e2009-09-05 21:47:34 +00002665 }
2666 return(pixel_list);
2667}
2668
cristy69ec32d2011-02-27 23:57:09 +00002669static void AddNodePixelList(PixelList *pixel_list,const ssize_t channel,
2670 const size_t color)
cristy3ed852e2009-09-05 21:47:34 +00002671{
cristy69ec32d2011-02-27 23:57:09 +00002672 register SkipList
cristy3ed852e2009-09-05 21:47:34 +00002673 *list;
2674
cristy117ff172010-08-15 21:35:32 +00002675 register ssize_t
2676 level;
2677
cristybb503372010-05-27 20:51:26 +00002678 size_t
cristy3ed852e2009-09-05 21:47:34 +00002679 search,
2680 update[9];
2681
2682 /*
2683 Initialize the node.
2684 */
2685 list=pixel_list->lists+channel;
2686 list->nodes[color].signature=pixel_list->signature;
2687 list->nodes[color].count=1;
2688 /*
cristy33c53022010-06-25 12:17:27 +00002689 Determine where it belongs in the list.
cristy3ed852e2009-09-05 21:47:34 +00002690 */
2691 search=65536UL;
2692 for (level=list->level; level >= 0; level--)
2693 {
2694 while (list->nodes[search].next[level] < color)
2695 search=list->nodes[search].next[level];
2696 update[level]=search;
2697 }
2698 /*
2699 Generate a pseudo-random level for this node.
2700 */
2701 for (level=0; ; level++)
2702 {
2703 pixel_list->seed=(pixel_list->seed*42893621L)+1L;
2704 if ((pixel_list->seed & 0x300) != 0x300)
2705 break;
2706 }
2707 if (level > 8)
2708 level=8;
2709 if (level > (list->level+2))
2710 level=list->level+2;
2711 /*
2712 If we're raising the list's level, link back to the root node.
2713 */
2714 while (level > list->level)
2715 {
2716 list->level++;
2717 update[list->level]=65536UL;
2718 }
2719 /*
2720 Link the node into the skip-list.
2721 */
2722 do
2723 {
2724 list->nodes[color].next[level]=list->nodes[update[level]].next[level];
2725 list->nodes[update[level]].next[level]=color;
2726 }
2727 while (level-- > 0);
2728}
2729
cristy69ec32d2011-02-27 23:57:09 +00002730static MagickPixelPacket GetPixelList(PixelList *pixel_list)
cristy3ed852e2009-09-05 21:47:34 +00002731{
2732 MagickPixelPacket
2733 pixel;
2734
cristy69ec32d2011-02-27 23:57:09 +00002735 register SkipList
cristy3ed852e2009-09-05 21:47:34 +00002736 *list;
2737
cristy117ff172010-08-15 21:35:32 +00002738 register ssize_t
2739 channel;
2740
cristybb503372010-05-27 20:51:26 +00002741 size_t
cristy3ed852e2009-09-05 21:47:34 +00002742 center,
2743 color,
2744 count;
2745
2746 unsigned short
cristy69ec32d2011-02-27 23:57:09 +00002747 channels[ListChannels];
cristy3ed852e2009-09-05 21:47:34 +00002748
2749 /*
2750 Find the median value for each of the color.
2751 */
2752 center=pixel_list->center;
2753 for (channel=0; channel < 5; channel++)
2754 {
2755 list=pixel_list->lists+channel;
2756 color=65536UL;
2757 count=0;
2758 do
2759 {
2760 color=list->nodes[color].next[0];
2761 count+=list->nodes[color].count;
2762 }
2763 while (count <= center);
2764 channels[channel]=(unsigned short) color;
2765 }
2766 GetMagickPixelPacket((const Image *) NULL,&pixel);
2767 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
2768 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
2769 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
2770 pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
2771 pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]);
2772 return(pixel);
2773}
2774
cristy69ec32d2011-02-27 23:57:09 +00002775static inline void InsertPixelList(const Image *image,const PixelPacket *pixel,
2776 const IndexPacket *indexes,PixelList *pixel_list)
cristy3ed852e2009-09-05 21:47:34 +00002777{
cristybb503372010-05-27 20:51:26 +00002778 size_t
cristy3ed852e2009-09-05 21:47:34 +00002779 signature;
2780
2781 unsigned short
2782 index;
2783
2784 index=ScaleQuantumToShort(pixel->red);
2785 signature=pixel_list->lists[0].nodes[index].signature;
2786 if (signature == pixel_list->signature)
2787 pixel_list->lists[0].nodes[index].count++;
2788 else
cristy69ec32d2011-02-27 23:57:09 +00002789 AddNodePixelList(pixel_list,0,index);
cristy3ed852e2009-09-05 21:47:34 +00002790 index=ScaleQuantumToShort(pixel->green);
2791 signature=pixel_list->lists[1].nodes[index].signature;
2792 if (signature == pixel_list->signature)
2793 pixel_list->lists[1].nodes[index].count++;
2794 else
cristy69ec32d2011-02-27 23:57:09 +00002795 AddNodePixelList(pixel_list,1,index);
cristy3ed852e2009-09-05 21:47:34 +00002796 index=ScaleQuantumToShort(pixel->blue);
2797 signature=pixel_list->lists[2].nodes[index].signature;
2798 if (signature == pixel_list->signature)
2799 pixel_list->lists[2].nodes[index].count++;
2800 else
cristy69ec32d2011-02-27 23:57:09 +00002801 AddNodePixelList(pixel_list,2,index);
cristy3ed852e2009-09-05 21:47:34 +00002802 index=ScaleQuantumToShort(pixel->opacity);
2803 signature=pixel_list->lists[3].nodes[index].signature;
2804 if (signature == pixel_list->signature)
2805 pixel_list->lists[3].nodes[index].count++;
2806 else
cristy69ec32d2011-02-27 23:57:09 +00002807 AddNodePixelList(pixel_list,3,index);
cristy3ed852e2009-09-05 21:47:34 +00002808 if (image->colorspace == CMYKColorspace)
2809 index=ScaleQuantumToShort(*indexes);
2810 signature=pixel_list->lists[4].nodes[index].signature;
2811 if (signature == pixel_list->signature)
2812 pixel_list->lists[4].nodes[index].count++;
2813 else
cristy69ec32d2011-02-27 23:57:09 +00002814 AddNodePixelList(pixel_list,4,index);
cristy3ed852e2009-09-05 21:47:34 +00002815}
2816
cristy69ec32d2011-02-27 23:57:09 +00002817static void ResetPixelList(PixelList *pixel_list)
cristy3ed852e2009-09-05 21:47:34 +00002818{
2819 int
2820 level;
2821
cristy69ec32d2011-02-27 23:57:09 +00002822 register ListNode
cristy3ed852e2009-09-05 21:47:34 +00002823 *root;
2824
cristy69ec32d2011-02-27 23:57:09 +00002825 register SkipList
cristy3ed852e2009-09-05 21:47:34 +00002826 *list;
2827
cristy117ff172010-08-15 21:35:32 +00002828 register ssize_t
2829 channel;
2830
cristy3ed852e2009-09-05 21:47:34 +00002831 /*
2832 Reset the skip-list.
2833 */
2834 for (channel=0; channel < 5; channel++)
2835 {
2836 list=pixel_list->lists+channel;
2837 root=list->nodes+65536UL;
2838 list->level=0;
2839 for (level=0; level < 9; level++)
2840 root->next[level]=65536UL;
2841 }
2842 pixel_list->seed=pixel_list->signature++;
2843}
2844
2845MagickExport Image *MedianFilterImage(const Image *image,const double radius,
2846 ExceptionInfo *exception)
2847{
2848#define MedianFilterImageTag "MedianFilter/Image"
2849
cristyc4c8d132010-01-07 01:58:38 +00002850 CacheView
2851 *image_view,
2852 *median_view;
2853
cristy3ed852e2009-09-05 21:47:34 +00002854 Image
2855 *median_image;
2856
cristy3ed852e2009-09-05 21:47:34 +00002857 MagickBooleanType
2858 status;
2859
cristybb503372010-05-27 20:51:26 +00002860 MagickOffsetType
2861 progress;
2862
cristy69ec32d2011-02-27 23:57:09 +00002863 PixelList
cristyfa112112010-01-04 17:48:07 +00002864 **restrict pixel_list;
cristy3ed852e2009-09-05 21:47:34 +00002865
cristybb503372010-05-27 20:51:26 +00002866 size_t
cristy3ed852e2009-09-05 21:47:34 +00002867 width;
2868
cristybb503372010-05-27 20:51:26 +00002869 ssize_t
2870 y;
2871
cristy3ed852e2009-09-05 21:47:34 +00002872 /*
2873 Initialize median image attributes.
2874 */
2875 assert(image != (Image *) NULL);
2876 assert(image->signature == MagickSignature);
2877 if (image->debug != MagickFalse)
2878 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2879 assert(exception != (ExceptionInfo *) NULL);
2880 assert(exception->signature == MagickSignature);
2881 width=GetOptimalKernelWidth2D(radius,0.5);
cristy3ed852e2009-09-05 21:47:34 +00002882 median_image=CloneImage(image,image->columns,image->rows,MagickTrue,
2883 exception);
2884 if (median_image == (Image *) NULL)
2885 return((Image *) NULL);
2886 if (SetImageStorageClass(median_image,DirectClass) == MagickFalse)
2887 {
2888 InheritException(exception,&median_image->exception);
2889 median_image=DestroyImage(median_image);
2890 return((Image *) NULL);
2891 }
cristy69ec32d2011-02-27 23:57:09 +00002892 pixel_list=AcquirePixelListThreadSet(width);
2893 if (pixel_list == (PixelList **) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002894 {
2895 median_image=DestroyImage(median_image);
2896 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2897 }
2898 /*
2899 Median filter each image row.
2900 */
2901 status=MagickTrue;
2902 progress=0;
2903 image_view=AcquireCacheView(image);
2904 median_view=AcquireCacheView(median_image);
cristyb5d5f722009-11-04 03:03:49 +00002905#if defined(MAGICKCORE_OPENMP_SUPPORT)
2906 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002907#endif
cristybb503372010-05-27 20:51:26 +00002908 for (y=0; y < (ssize_t) median_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002909 {
cristy5c9e6f22010-09-17 17:31:01 +00002910 const int
2911 id = GetOpenMPThreadId();
cristy6ebe97c2010-07-03 01:17:28 +00002912
cristy3ed852e2009-09-05 21:47:34 +00002913 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002914 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002915
2916 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002917 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002918
2919 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002920 *restrict median_indexes;
cristy3ed852e2009-09-05 21:47:34 +00002921
cristy3ed852e2009-09-05 21:47:34 +00002922 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002923 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002924
cristy117ff172010-08-15 21:35:32 +00002925 register ssize_t
2926 x;
2927
cristy3ed852e2009-09-05 21:47:34 +00002928 if (status == MagickFalse)
2929 continue;
cristy6ebe97c2010-07-03 01:17:28 +00002930 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
2931 (width/2L),image->columns+width,width,exception);
cristy3ed852e2009-09-05 21:47:34 +00002932 q=QueueCacheViewAuthenticPixels(median_view,0,y,median_image->columns,1,
2933 exception);
2934 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2935 {
2936 status=MagickFalse;
2937 continue;
2938 }
2939 indexes=GetCacheViewVirtualIndexQueue(image_view);
2940 median_indexes=GetCacheViewAuthenticIndexQueue(median_view);
cristybb503372010-05-27 20:51:26 +00002941 for (x=0; x < (ssize_t) median_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002942 {
2943 MagickPixelPacket
2944 pixel;
2945
cristy3ed852e2009-09-05 21:47:34 +00002946 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002947 *restrict s;
cristy3ed852e2009-09-05 21:47:34 +00002948
cristy117ff172010-08-15 21:35:32 +00002949 register const PixelPacket
2950 *restrict r;
2951
cristybb503372010-05-27 20:51:26 +00002952 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002953 u,
2954 v;
2955
2956 r=p;
2957 s=indexes+x;
cristy69ec32d2011-02-27 23:57:09 +00002958 ResetPixelList(pixel_list[id]);
cristybb503372010-05-27 20:51:26 +00002959 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00002960 {
cristybb503372010-05-27 20:51:26 +00002961 for (u=0; u < (ssize_t) width; u++)
cristy69ec32d2011-02-27 23:57:09 +00002962 InsertPixelList(image,r+u,s+u,pixel_list[id]);
cristy3ed852e2009-09-05 21:47:34 +00002963 r+=image->columns+width;
2964 s+=image->columns+width;
2965 }
cristy69ec32d2011-02-27 23:57:09 +00002966 pixel=GetPixelList(pixel_list[id]);
cristy3ed852e2009-09-05 21:47:34 +00002967 SetPixelPacket(median_image,&pixel,q,median_indexes+x);
2968 p++;
2969 q++;
2970 }
2971 if (SyncCacheViewAuthenticPixels(median_view,exception) == MagickFalse)
2972 status=MagickFalse;
2973 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2974 {
2975 MagickBooleanType
2976 proceed;
2977
cristyb5d5f722009-11-04 03:03:49 +00002978#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002979 #pragma omp critical (MagickCore_MedianFilterImage)
2980#endif
2981 proceed=SetImageProgress(image,MedianFilterImageTag,progress++,
2982 image->rows);
2983 if (proceed == MagickFalse)
2984 status=MagickFalse;
2985 }
2986 }
2987 median_view=DestroyCacheView(median_view);
2988 image_view=DestroyCacheView(image_view);
cristy69ec32d2011-02-27 23:57:09 +00002989 pixel_list=DestroyPixelListThreadSet(pixel_list);
cristy3ed852e2009-09-05 21:47:34 +00002990 return(median_image);
2991}
2992
2993/*
2994%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2995% %
2996% %
2997% %
cristy69ec32d2011-02-27 23:57:09 +00002998% M o d e I m a g e %
2999% %
3000% %
3001% %
3002%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3003%
3004% ModeImage() makes each pixel the 'predominate color' of the neighborhood
cristyfe4ba002011-02-28 14:54:12 +00003005% of the specified radius.
cristy69ec32d2011-02-27 23:57:09 +00003006%
3007% The format of the ModeImage method is:
3008%
3009% Image *ModeImage(const Image *image,const double radius,
3010% ExceptionInfo *exception)
3011%
3012% A description of each parameter follows:
3013%
3014% o image: the image.
3015%
3016% o radius: the radius of the pixel neighborhood.
3017%
3018% o exception: return any errors or warnings in this structure.
3019%
3020*/
3021
3022static MagickPixelPacket GetModePixelList(PixelList *pixel_list)
3023{
3024 MagickPixelPacket
3025 pixel;
3026
3027 register SkipList
3028 *list;
3029
3030 register ssize_t
3031 channel;
3032
3033 size_t
cristy69ec32d2011-02-27 23:57:09 +00003034 color,
3035 count,
cristya6020132011-02-28 01:06:03 +00003036 max_count,
3037 mode,
3038 width;
cristy69ec32d2011-02-27 23:57:09 +00003039
3040 unsigned short
3041 channels[5];
3042
3043 /*
cristya6020132011-02-28 01:06:03 +00003044 Make each pixel the 'predominate color' of the specified neighborhood.
cristy69ec32d2011-02-27 23:57:09 +00003045 */
cristya6020132011-02-28 01:06:03 +00003046 width=pixel_list->center << 1;
cristy69ec32d2011-02-27 23:57:09 +00003047 for (channel=0; channel < 5; channel++)
3048 {
3049 list=pixel_list->lists+channel;
3050 color=65536UL;
cristya6020132011-02-28 01:06:03 +00003051 mode=color;
3052 max_count=list->nodes[mode].count;
cristy69ec32d2011-02-27 23:57:09 +00003053 count=0;
3054 do
3055 {
cristya6020132011-02-28 01:06:03 +00003056 color=list->nodes[color].next[0];
3057 if (list->nodes[color].count > max_count)
3058 {
3059 mode=color;
3060 max_count=list->nodes[mode].count;
3061 }
cristy69ec32d2011-02-27 23:57:09 +00003062 count+=list->nodes[color].count;
3063 }
cristya6020132011-02-28 01:06:03 +00003064 while (count <= width);
3065 channels[channel]=(unsigned short) mode;
cristy69ec32d2011-02-27 23:57:09 +00003066 }
3067 GetMagickPixelPacket((const Image *) NULL,&pixel);
3068 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
3069 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
3070 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
3071 pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
3072 pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]);
3073 return(pixel);
3074}
3075
3076MagickExport Image *ModeImage(const Image *image,const double radius,
3077 ExceptionInfo *exception)
3078{
3079#define ModeImageTag "Mode/Image"
3080
3081 CacheView
3082 *image_view,
3083 *mode_view;
3084
3085 Image
3086 *mode_image;
3087
3088 MagickBooleanType
3089 status;
3090
3091 MagickOffsetType
3092 progress;
3093
3094 PixelList
3095 **restrict pixel_list;
3096
3097 size_t
3098 width;
3099
3100 ssize_t
3101 y;
3102
3103 /*
3104 Initialize mode image attributes.
3105 */
3106 assert(image != (Image *) NULL);
3107 assert(image->signature == MagickSignature);
3108 if (image->debug != MagickFalse)
3109 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3110 assert(exception != (ExceptionInfo *) NULL);
3111 assert(exception->signature == MagickSignature);
3112 width=GetOptimalKernelWidth2D(radius,0.5);
cristy69ec32d2011-02-27 23:57:09 +00003113 mode_image=CloneImage(image,image->columns,image->rows,MagickTrue,
3114 exception);
3115 if (mode_image == (Image *) NULL)
3116 return((Image *) NULL);
3117 if (SetImageStorageClass(mode_image,DirectClass) == MagickFalse)
3118 {
3119 InheritException(exception,&mode_image->exception);
3120 mode_image=DestroyImage(mode_image);
3121 return((Image *) NULL);
3122 }
3123 pixel_list=AcquirePixelListThreadSet(width);
3124 if (pixel_list == (PixelList **) NULL)
3125 {
3126 mode_image=DestroyImage(mode_image);
3127 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3128 }
3129 /*
3130 Reduce mode image.
3131 */
3132 status=MagickTrue;
3133 progress=0;
3134 image_view=AcquireCacheView(image);
3135 mode_view=AcquireCacheView(mode_image);
3136#if defined(MAGICKCORE_OPENMP_SUPPORT)
3137 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3138#endif
3139 for (y=0; y < (ssize_t) mode_image->rows; y++)
3140 {
3141 const int
3142 id = GetOpenMPThreadId();
3143
3144 register const IndexPacket
3145 *restrict indexes;
3146
3147 register const PixelPacket
3148 *restrict p;
3149
3150 register IndexPacket
3151 *restrict mode_indexes;
3152
3153 register PixelPacket
3154 *restrict q;
3155
3156 register ssize_t
3157 x;
3158
3159 if (status == MagickFalse)
3160 continue;
3161 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
3162 (width/2L),image->columns+width,width,exception);
3163 q=QueueCacheViewAuthenticPixels(mode_view,0,y,mode_image->columns,1,
3164 exception);
3165 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
3166 {
3167 status=MagickFalse;
3168 continue;
3169 }
3170 indexes=GetCacheViewVirtualIndexQueue(image_view);
3171 mode_indexes=GetCacheViewAuthenticIndexQueue(mode_view);
3172 for (x=0; x < (ssize_t) mode_image->columns; x++)
3173 {
3174 MagickPixelPacket
3175 pixel;
3176
3177 register const PixelPacket
3178 *restrict r;
3179
3180 register const IndexPacket
3181 *restrict s;
3182
3183 register ssize_t
3184 u,
3185 v;
3186
3187 r=p;
3188 s=indexes+x;
3189 ResetPixelList(pixel_list[id]);
3190 for (v=0; v < (ssize_t) width; v++)
3191 {
3192 for (u=0; u < (ssize_t) width; u++)
3193 InsertPixelList(image,r+u,s+u,pixel_list[id]);
3194 r+=image->columns+width;
3195 s+=image->columns+width;
3196 }
3197 pixel=GetModePixelList(pixel_list[id]);
3198 SetPixelPacket(mode_image,&pixel,q,mode_indexes+x);
3199 p++;
3200 q++;
3201 }
3202 if (SyncCacheViewAuthenticPixels(mode_view,exception) == MagickFalse)
3203 status=MagickFalse;
3204 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3205 {
3206 MagickBooleanType
3207 proceed;
3208
3209#if defined(MAGICKCORE_OPENMP_SUPPORT)
3210 #pragma omp critical (MagickCore_ModeImage)
3211#endif
3212 proceed=SetImageProgress(image,ModeImageTag,progress++,image->rows);
3213 if (proceed == MagickFalse)
3214 status=MagickFalse;
3215 }
3216 }
3217 mode_view=DestroyCacheView(mode_view);
3218 image_view=DestroyCacheView(image_view);
3219 pixel_list=DestroyPixelListThreadSet(pixel_list);
3220 return(mode_image);
3221}
3222
3223/*
3224%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3225% %
3226% %
3227% %
cristy3ed852e2009-09-05 21:47:34 +00003228% M o t i o n B l u r I m a g e %
3229% %
3230% %
3231% %
3232%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3233%
3234% MotionBlurImage() simulates motion blur. We convolve the image with a
3235% Gaussian operator of the given radius and standard deviation (sigma).
3236% For reasonable results, radius should be larger than sigma. Use a
3237% radius of 0 and MotionBlurImage() selects a suitable radius for you.
3238% Angle gives the angle of the blurring motion.
3239%
3240% Andrew Protano contributed this effect.
3241%
3242% The format of the MotionBlurImage method is:
3243%
3244% Image *MotionBlurImage(const Image *image,const double radius,
3245% const double sigma,const double angle,ExceptionInfo *exception)
3246% Image *MotionBlurImageChannel(const Image *image,const ChannelType channel,
3247% const double radius,const double sigma,const double angle,
3248% ExceptionInfo *exception)
3249%
3250% A description of each parameter follows:
3251%
3252% o image: the image.
3253%
3254% o channel: the channel type.
3255%
3256% o radius: the radius of the Gaussian, in pixels, not counting the center
3257% o radius: the radius of the Gaussian, in pixels, not counting
3258% the center pixel.
3259%
3260% o sigma: the standard deviation of the Gaussian, in pixels.
3261%
cristycee97112010-05-28 00:44:52 +00003262% o angle: Apply the effect along this angle.
cristy3ed852e2009-09-05 21:47:34 +00003263%
3264% o exception: return any errors or warnings in this structure.
3265%
3266*/
3267
cristybb503372010-05-27 20:51:26 +00003268static double *GetMotionBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +00003269{
cristy3ed852e2009-09-05 21:47:34 +00003270 double
cristy47e00502009-12-17 19:19:57 +00003271 *kernel,
cristy3ed852e2009-09-05 21:47:34 +00003272 normalize;
3273
cristybb503372010-05-27 20:51:26 +00003274 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003275 i;
3276
3277 /*
cristy47e00502009-12-17 19:19:57 +00003278 Generate a 1-D convolution kernel.
cristy3ed852e2009-09-05 21:47:34 +00003279 */
3280 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
3281 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
3282 if (kernel == (double *) NULL)
3283 return(kernel);
cristy3ed852e2009-09-05 21:47:34 +00003284 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00003285 for (i=0; i < (ssize_t) width; i++)
cristy47e00502009-12-17 19:19:57 +00003286 {
cristy4205a3c2010-09-12 20:19:59 +00003287 kernel[i]=(double) (exp((-((double) i*i)/(double) (2.0*MagickSigma*
3288 MagickSigma)))/(MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00003289 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +00003290 }
cristybb503372010-05-27 20:51:26 +00003291 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00003292 kernel[i]/=normalize;
3293 return(kernel);
3294}
3295
3296MagickExport Image *MotionBlurImage(const Image *image,const double radius,
3297 const double sigma,const double angle,ExceptionInfo *exception)
3298{
3299 Image
3300 *motion_blur;
3301
3302 motion_blur=MotionBlurImageChannel(image,DefaultChannels,radius,sigma,angle,
3303 exception);
3304 return(motion_blur);
3305}
3306
3307MagickExport Image *MotionBlurImageChannel(const Image *image,
3308 const ChannelType channel,const double radius,const double sigma,
3309 const double angle,ExceptionInfo *exception)
3310{
cristyc4c8d132010-01-07 01:58:38 +00003311 CacheView
3312 *blur_view,
3313 *image_view;
3314
cristy3ed852e2009-09-05 21:47:34 +00003315 double
3316 *kernel;
3317
3318 Image
3319 *blur_image;
3320
cristy3ed852e2009-09-05 21:47:34 +00003321 MagickBooleanType
3322 status;
3323
cristybb503372010-05-27 20:51:26 +00003324 MagickOffsetType
3325 progress;
3326
cristy3ed852e2009-09-05 21:47:34 +00003327 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00003328 bias;
cristy3ed852e2009-09-05 21:47:34 +00003329
3330 OffsetInfo
3331 *offset;
3332
3333 PointInfo
3334 point;
3335
cristybb503372010-05-27 20:51:26 +00003336 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003337 i;
3338
cristybb503372010-05-27 20:51:26 +00003339 size_t
cristy3ed852e2009-09-05 21:47:34 +00003340 width;
3341
cristybb503372010-05-27 20:51:26 +00003342 ssize_t
3343 y;
3344
cristy3ed852e2009-09-05 21:47:34 +00003345 assert(image != (Image *) NULL);
3346 assert(image->signature == MagickSignature);
3347 if (image->debug != MagickFalse)
3348 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3349 assert(exception != (ExceptionInfo *) NULL);
3350 width=GetOptimalKernelWidth1D(radius,sigma);
3351 kernel=GetMotionBlurKernel(width,sigma);
3352 if (kernel == (double *) NULL)
3353 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3354 offset=(OffsetInfo *) AcquireQuantumMemory(width,sizeof(*offset));
3355 if (offset == (OffsetInfo *) NULL)
3356 {
3357 kernel=(double *) RelinquishMagickMemory(kernel);
3358 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3359 }
3360 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3361 if (blur_image == (Image *) NULL)
3362 {
3363 kernel=(double *) RelinquishMagickMemory(kernel);
3364 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
3365 return((Image *) NULL);
3366 }
3367 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3368 {
3369 kernel=(double *) RelinquishMagickMemory(kernel);
3370 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
3371 InheritException(exception,&blur_image->exception);
3372 blur_image=DestroyImage(blur_image);
3373 return((Image *) NULL);
3374 }
3375 point.x=(double) width*sin(DegreesToRadians(angle));
3376 point.y=(double) width*cos(DegreesToRadians(angle));
cristybb503372010-05-27 20:51:26 +00003377 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00003378 {
cristybb503372010-05-27 20:51:26 +00003379 offset[i].x=(ssize_t) ceil((double) (i*point.y)/hypot(point.x,point.y)-0.5);
3380 offset[i].y=(ssize_t) ceil((double) (i*point.x)/hypot(point.x,point.y)-0.5);
cristy3ed852e2009-09-05 21:47:34 +00003381 }
3382 /*
3383 Motion blur image.
3384 */
3385 status=MagickTrue;
3386 progress=0;
cristyddd82202009-11-03 20:14:50 +00003387 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003388 image_view=AcquireCacheView(image);
3389 blur_view=AcquireCacheView(blur_image);
cristyb557a152011-02-22 12:14:30 +00003390#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00003391 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00003392#endif
cristybb503372010-05-27 20:51:26 +00003393 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003394 {
3395 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003396 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00003397
cristy3ed852e2009-09-05 21:47:34 +00003398 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003399 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003400
cristy117ff172010-08-15 21:35:32 +00003401 register ssize_t
3402 x;
3403
cristy3ed852e2009-09-05 21:47:34 +00003404 if (status == MagickFalse)
3405 continue;
3406 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3407 exception);
3408 if (q == (PixelPacket *) NULL)
3409 {
3410 status=MagickFalse;
3411 continue;
3412 }
3413 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
cristybb503372010-05-27 20:51:26 +00003414 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003415 {
3416 MagickPixelPacket
3417 qixel;
3418
3419 PixelPacket
3420 pixel;
3421
cristy117ff172010-08-15 21:35:32 +00003422 register const IndexPacket
3423 *restrict indexes;
3424
cristy3ed852e2009-09-05 21:47:34 +00003425 register double
cristyc47d1f82009-11-26 01:44:43 +00003426 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00003427
cristybb503372010-05-27 20:51:26 +00003428 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003429 i;
3430
cristy3ed852e2009-09-05 21:47:34 +00003431 k=kernel;
cristyddd82202009-11-03 20:14:50 +00003432 qixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00003433 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
3434 {
cristybb503372010-05-27 20:51:26 +00003435 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00003436 {
3437 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
3438 offset[i].y,&pixel,exception);
3439 qixel.red+=(*k)*pixel.red;
3440 qixel.green+=(*k)*pixel.green;
3441 qixel.blue+=(*k)*pixel.blue;
3442 qixel.opacity+=(*k)*pixel.opacity;
3443 if (image->colorspace == CMYKColorspace)
3444 {
3445 indexes=GetCacheViewVirtualIndexQueue(image_view);
3446 qixel.index+=(*k)*(*indexes);
3447 }
3448 k++;
3449 }
3450 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003451 q->red=ClampToQuantum(qixel.red);
cristy3ed852e2009-09-05 21:47:34 +00003452 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003453 q->green=ClampToQuantum(qixel.green);
cristy3ed852e2009-09-05 21:47:34 +00003454 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003455 q->blue=ClampToQuantum(qixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00003456 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003457 q->opacity=ClampToQuantum(qixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00003458 if (((channel & IndexChannel) != 0) &&
3459 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00003460 blur_indexes[x]=(IndexPacket) ClampToQuantum(qixel.index);
cristy3ed852e2009-09-05 21:47:34 +00003461 }
3462 else
3463 {
3464 MagickRealType
3465 alpha,
3466 gamma;
3467
3468 alpha=0.0;
3469 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00003470 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00003471 {
3472 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
3473 offset[i].y,&pixel,exception);
cristy8a7ea362010-03-10 20:31:43 +00003474 alpha=(MagickRealType) (QuantumScale*
3475 GetAlphaPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00003476 qixel.red+=(*k)*alpha*pixel.red;
3477 qixel.green+=(*k)*alpha*pixel.green;
3478 qixel.blue+=(*k)*alpha*pixel.blue;
3479 qixel.opacity+=(*k)*pixel.opacity;
3480 if (image->colorspace == CMYKColorspace)
3481 {
3482 indexes=GetCacheViewVirtualIndexQueue(image_view);
3483 qixel.index+=(*k)*alpha*(*indexes);
3484 }
3485 gamma+=(*k)*alpha;
3486 k++;
3487 }
3488 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3489 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003490 q->red=ClampToQuantum(gamma*qixel.red);
cristy3ed852e2009-09-05 21:47:34 +00003491 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003492 q->green=ClampToQuantum(gamma*qixel.green);
cristy3ed852e2009-09-05 21:47:34 +00003493 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003494 q->blue=ClampToQuantum(gamma*qixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00003495 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003496 q->opacity=ClampToQuantum(qixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00003497 if (((channel & IndexChannel) != 0) &&
3498 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00003499 blur_indexes[x]=(IndexPacket) ClampToQuantum(gamma*qixel.index);
cristy3ed852e2009-09-05 21:47:34 +00003500 }
3501 q++;
3502 }
3503 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
3504 status=MagickFalse;
3505 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3506 {
3507 MagickBooleanType
3508 proceed;
3509
cristyb557a152011-02-22 12:14:30 +00003510#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003511 #pragma omp critical (MagickCore_MotionBlurImageChannel)
3512#endif
3513 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
3514 if (proceed == MagickFalse)
3515 status=MagickFalse;
3516 }
3517 }
3518 blur_view=DestroyCacheView(blur_view);
3519 image_view=DestroyCacheView(image_view);
3520 kernel=(double *) RelinquishMagickMemory(kernel);
3521 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
3522 if (status == MagickFalse)
3523 blur_image=DestroyImage(blur_image);
3524 return(blur_image);
3525}
3526
3527/*
3528%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3529% %
3530% %
3531% %
3532% P r e v i e w I m a g e %
3533% %
3534% %
3535% %
3536%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3537%
3538% PreviewImage() tiles 9 thumbnails of the specified image with an image
3539% processing operation applied with varying parameters. This may be helpful
3540% pin-pointing an appropriate parameter for a particular image processing
3541% operation.
3542%
3543% The format of the PreviewImages method is:
3544%
3545% Image *PreviewImages(const Image *image,const PreviewType preview,
3546% ExceptionInfo *exception)
3547%
3548% A description of each parameter follows:
3549%
3550% o image: the image.
3551%
3552% o preview: the image processing operation.
3553%
3554% o exception: return any errors or warnings in this structure.
3555%
3556*/
3557MagickExport Image *PreviewImage(const Image *image,const PreviewType preview,
3558 ExceptionInfo *exception)
3559{
3560#define NumberTiles 9
3561#define PreviewImageTag "Preview/Image"
3562#define DefaultPreviewGeometry "204x204+10+10"
3563
3564 char
3565 factor[MaxTextExtent],
3566 label[MaxTextExtent];
3567
3568 double
3569 degrees,
3570 gamma,
3571 percentage,
3572 radius,
3573 sigma,
3574 threshold;
3575
3576 Image
3577 *images,
3578 *montage_image,
3579 *preview_image,
3580 *thumbnail;
3581
3582 ImageInfo
3583 *preview_info;
3584
cristy3ed852e2009-09-05 21:47:34 +00003585 MagickBooleanType
3586 proceed;
3587
3588 MontageInfo
3589 *montage_info;
3590
3591 QuantizeInfo
3592 quantize_info;
3593
3594 RectangleInfo
3595 geometry;
3596
cristybb503372010-05-27 20:51:26 +00003597 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003598 i,
3599 x;
3600
cristybb503372010-05-27 20:51:26 +00003601 size_t
cristy3ed852e2009-09-05 21:47:34 +00003602 colors;
3603
cristy117ff172010-08-15 21:35:32 +00003604 ssize_t
3605 y;
3606
cristy3ed852e2009-09-05 21:47:34 +00003607 /*
3608 Open output image file.
3609 */
3610 assert(image != (Image *) NULL);
3611 assert(image->signature == MagickSignature);
3612 if (image->debug != MagickFalse)
3613 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3614 colors=2;
3615 degrees=0.0;
3616 gamma=(-0.2f);
3617 preview_info=AcquireImageInfo();
3618 SetGeometry(image,&geometry);
3619 (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y,
3620 &geometry.width,&geometry.height);
3621 images=NewImageList();
3622 percentage=12.5;
3623 GetQuantizeInfo(&quantize_info);
3624 radius=0.0;
3625 sigma=1.0;
3626 threshold=0.0;
3627 x=0;
3628 y=0;
3629 for (i=0; i < NumberTiles; i++)
3630 {
3631 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
3632 if (thumbnail == (Image *) NULL)
3633 break;
3634 (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL,
3635 (void *) NULL);
3636 (void) SetImageProperty(thumbnail,"label",DefaultTileLabel);
3637 if (i == (NumberTiles/2))
3638 {
3639 (void) QueryColorDatabase("#dfdfdf",&thumbnail->matte_color,exception);
3640 AppendImageToList(&images,thumbnail);
3641 continue;
3642 }
3643 switch (preview)
3644 {
3645 case RotatePreview:
3646 {
3647 degrees+=45.0;
3648 preview_image=RotateImage(thumbnail,degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003649 (void) FormatMagickString(label,MaxTextExtent,"rotate %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003650 break;
3651 }
3652 case ShearPreview:
3653 {
3654 degrees+=5.0;
3655 preview_image=ShearImage(thumbnail,degrees,degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003656 (void) FormatMagickString(label,MaxTextExtent,"shear %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00003657 degrees,2.0*degrees);
3658 break;
3659 }
3660 case RollPreview:
3661 {
cristybb503372010-05-27 20:51:26 +00003662 x=(ssize_t) ((i+1)*thumbnail->columns)/NumberTiles;
3663 y=(ssize_t) ((i+1)*thumbnail->rows)/NumberTiles;
cristy3ed852e2009-09-05 21:47:34 +00003664 preview_image=RollImage(thumbnail,x,y,exception);
cristye8c25f92010-06-03 00:53:06 +00003665 (void) FormatMagickString(label,MaxTextExtent,"roll %+.20gx%+.20g",
3666 (double) x,(double) y);
cristy3ed852e2009-09-05 21:47:34 +00003667 break;
3668 }
3669 case HuePreview:
3670 {
3671 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3672 if (preview_image == (Image *) NULL)
3673 break;
cristye7f51092010-01-17 00:39:37 +00003674 (void) FormatMagickString(factor,MaxTextExtent,"100,100,%g",
cristy3ed852e2009-09-05 21:47:34 +00003675 2.0*percentage);
3676 (void) ModulateImage(preview_image,factor);
3677 (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor);
3678 break;
3679 }
3680 case SaturationPreview:
3681 {
3682 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3683 if (preview_image == (Image *) NULL)
3684 break;
cristye7f51092010-01-17 00:39:37 +00003685 (void) FormatMagickString(factor,MaxTextExtent,"100,%g",
cristy8cd5b312010-01-07 01:10:24 +00003686 2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00003687 (void) ModulateImage(preview_image,factor);
3688 (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor);
3689 break;
3690 }
3691 case BrightnessPreview:
3692 {
3693 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3694 if (preview_image == (Image *) NULL)
3695 break;
cristye7f51092010-01-17 00:39:37 +00003696 (void) FormatMagickString(factor,MaxTextExtent,"%g",2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00003697 (void) ModulateImage(preview_image,factor);
3698 (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor);
3699 break;
3700 }
3701 case GammaPreview:
3702 default:
3703 {
3704 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3705 if (preview_image == (Image *) NULL)
3706 break;
3707 gamma+=0.4f;
3708 (void) GammaImageChannel(preview_image,DefaultChannels,gamma);
cristye7f51092010-01-17 00:39:37 +00003709 (void) FormatMagickString(label,MaxTextExtent,"gamma %g",gamma);
cristy3ed852e2009-09-05 21:47:34 +00003710 break;
3711 }
3712 case SpiffPreview:
3713 {
3714 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3715 if (preview_image != (Image *) NULL)
3716 for (x=0; x < i; x++)
3717 (void) ContrastImage(preview_image,MagickTrue);
cristye8c25f92010-06-03 00:53:06 +00003718 (void) FormatMagickString(label,MaxTextExtent,"contrast (%.20g)",
3719 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00003720 break;
3721 }
3722 case DullPreview:
3723 {
3724 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3725 if (preview_image == (Image *) NULL)
3726 break;
3727 for (x=0; x < i; x++)
3728 (void) ContrastImage(preview_image,MagickFalse);
cristye8c25f92010-06-03 00:53:06 +00003729 (void) FormatMagickString(label,MaxTextExtent,"+contrast (%.20g)",
3730 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00003731 break;
3732 }
3733 case GrayscalePreview:
3734 {
3735 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3736 if (preview_image == (Image *) NULL)
3737 break;
3738 colors<<=1;
3739 quantize_info.number_colors=colors;
3740 quantize_info.colorspace=GRAYColorspace;
3741 (void) QuantizeImage(&quantize_info,preview_image);
3742 (void) FormatMagickString(label,MaxTextExtent,
cristye8c25f92010-06-03 00:53:06 +00003743 "-colorspace gray -colors %.20g",(double) colors);
cristy3ed852e2009-09-05 21:47:34 +00003744 break;
3745 }
3746 case QuantizePreview:
3747 {
3748 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3749 if (preview_image == (Image *) NULL)
3750 break;
3751 colors<<=1;
3752 quantize_info.number_colors=colors;
3753 (void) QuantizeImage(&quantize_info,preview_image);
cristye8c25f92010-06-03 00:53:06 +00003754 (void) FormatMagickString(label,MaxTextExtent,"colors %.20g",(double)
3755 colors);
cristy3ed852e2009-09-05 21:47:34 +00003756 break;
3757 }
3758 case DespecklePreview:
3759 {
3760 for (x=0; x < (i-1); x++)
3761 {
3762 preview_image=DespeckleImage(thumbnail,exception);
3763 if (preview_image == (Image *) NULL)
3764 break;
3765 thumbnail=DestroyImage(thumbnail);
3766 thumbnail=preview_image;
3767 }
3768 preview_image=DespeckleImage(thumbnail,exception);
3769 if (preview_image == (Image *) NULL)
3770 break;
cristye8c25f92010-06-03 00:53:06 +00003771 (void) FormatMagickString(label,MaxTextExtent,"despeckle (%.20g)",
3772 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00003773 break;
3774 }
3775 case ReduceNoisePreview:
3776 {
3777 preview_image=ReduceNoiseImage(thumbnail,radius,exception);
cristye7f51092010-01-17 00:39:37 +00003778 (void) FormatMagickString(label,MaxTextExtent,"noise %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003779 break;
3780 }
3781 case AddNoisePreview:
3782 {
3783 switch ((int) i)
3784 {
3785 case 0:
3786 {
3787 (void) CopyMagickString(factor,"uniform",MaxTextExtent);
3788 break;
3789 }
3790 case 1:
3791 {
3792 (void) CopyMagickString(factor,"gaussian",MaxTextExtent);
3793 break;
3794 }
3795 case 2:
3796 {
3797 (void) CopyMagickString(factor,"multiplicative",MaxTextExtent);
3798 break;
3799 }
3800 case 3:
3801 {
3802 (void) CopyMagickString(factor,"impulse",MaxTextExtent);
3803 break;
3804 }
3805 case 4:
3806 {
3807 (void) CopyMagickString(factor,"laplacian",MaxTextExtent);
3808 break;
3809 }
3810 case 5:
3811 {
3812 (void) CopyMagickString(factor,"Poisson",MaxTextExtent);
3813 break;
3814 }
3815 default:
3816 {
3817 (void) CopyMagickString(thumbnail->magick,"NULL",MaxTextExtent);
3818 break;
3819 }
3820 }
3821 preview_image=ReduceNoiseImage(thumbnail,(double) i,exception);
3822 (void) FormatMagickString(label,MaxTextExtent,"+noise %s",factor);
3823 break;
3824 }
3825 case SharpenPreview:
3826 {
3827 preview_image=SharpenImage(thumbnail,radius,sigma,exception);
cristye7f51092010-01-17 00:39:37 +00003828 (void) FormatMagickString(label,MaxTextExtent,"sharpen %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003829 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00003830 break;
3831 }
3832 case BlurPreview:
3833 {
3834 preview_image=BlurImage(thumbnail,radius,sigma,exception);
cristye7f51092010-01-17 00:39:37 +00003835 (void) FormatMagickString(label,MaxTextExtent,"blur %gx%g",radius,
cristy3ed852e2009-09-05 21:47:34 +00003836 sigma);
3837 break;
3838 }
3839 case ThresholdPreview:
3840 {
3841 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3842 if (preview_image == (Image *) NULL)
3843 break;
3844 (void) BilevelImage(thumbnail,
3845 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
cristye7f51092010-01-17 00:39:37 +00003846 (void) FormatMagickString(label,MaxTextExtent,"threshold %g",
cristy3ed852e2009-09-05 21:47:34 +00003847 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
3848 break;
3849 }
3850 case EdgeDetectPreview:
3851 {
3852 preview_image=EdgeImage(thumbnail,radius,exception);
cristye7f51092010-01-17 00:39:37 +00003853 (void) FormatMagickString(label,MaxTextExtent,"edge %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003854 break;
3855 }
3856 case SpreadPreview:
3857 {
3858 preview_image=SpreadImage(thumbnail,radius,exception);
cristye7f51092010-01-17 00:39:37 +00003859 (void) FormatMagickString(label,MaxTextExtent,"spread %g",
cristy8cd5b312010-01-07 01:10:24 +00003860 radius+0.5);
cristy3ed852e2009-09-05 21:47:34 +00003861 break;
3862 }
3863 case SolarizePreview:
3864 {
3865 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3866 if (preview_image == (Image *) NULL)
3867 break;
3868 (void) SolarizeImage(preview_image,(double) QuantumRange*
3869 percentage/100.0);
cristye7f51092010-01-17 00:39:37 +00003870 (void) FormatMagickString(label,MaxTextExtent,"solarize %g",
cristy3ed852e2009-09-05 21:47:34 +00003871 (QuantumRange*percentage)/100.0);
3872 break;
3873 }
3874 case ShadePreview:
3875 {
3876 degrees+=10.0;
3877 preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees,
3878 exception);
cristye7f51092010-01-17 00:39:37 +00003879 (void) FormatMagickString(label,MaxTextExtent,"shade %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003880 degrees,degrees);
cristy3ed852e2009-09-05 21:47:34 +00003881 break;
3882 }
3883 case RaisePreview:
3884 {
3885 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3886 if (preview_image == (Image *) NULL)
3887 break;
cristybb503372010-05-27 20:51:26 +00003888 geometry.width=(size_t) (2*i+2);
3889 geometry.height=(size_t) (2*i+2);
cristy3ed852e2009-09-05 21:47:34 +00003890 geometry.x=i/2;
3891 geometry.y=i/2;
3892 (void) RaiseImage(preview_image,&geometry,MagickTrue);
cristye8c25f92010-06-03 00:53:06 +00003893 (void) FormatMagickString(label,MaxTextExtent,
cristy6d8abba2010-06-03 01:10:47 +00003894 "raise %.20gx%.20g%+.20g%+.20g",(double) geometry.width,(double)
cristye8c25f92010-06-03 00:53:06 +00003895 geometry.height,(double) geometry.x,(double) geometry.y);
cristy3ed852e2009-09-05 21:47:34 +00003896 break;
3897 }
3898 case SegmentPreview:
3899 {
3900 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3901 if (preview_image == (Image *) NULL)
3902 break;
3903 threshold+=0.4f;
3904 (void) SegmentImage(preview_image,RGBColorspace,MagickFalse,threshold,
3905 threshold);
cristye7f51092010-01-17 00:39:37 +00003906 (void) FormatMagickString(label,MaxTextExtent,"segment %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00003907 threshold,threshold);
3908 break;
3909 }
3910 case SwirlPreview:
3911 {
3912 preview_image=SwirlImage(thumbnail,degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003913 (void) FormatMagickString(label,MaxTextExtent,"swirl %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003914 degrees+=45.0;
3915 break;
3916 }
3917 case ImplodePreview:
3918 {
3919 degrees+=0.1f;
3920 preview_image=ImplodeImage(thumbnail,degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003921 (void) FormatMagickString(label,MaxTextExtent,"implode %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003922 break;
3923 }
3924 case WavePreview:
3925 {
3926 degrees+=5.0f;
3927 preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003928 (void) FormatMagickString(label,MaxTextExtent,"wave %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003929 0.5*degrees,2.0*degrees);
cristy3ed852e2009-09-05 21:47:34 +00003930 break;
3931 }
3932 case OilPaintPreview:
3933 {
3934 preview_image=OilPaintImage(thumbnail,(double) radius,exception);
cristye7f51092010-01-17 00:39:37 +00003935 (void) FormatMagickString(label,MaxTextExtent,"paint %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003936 break;
3937 }
3938 case CharcoalDrawingPreview:
3939 {
3940 preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma,
3941 exception);
cristye7f51092010-01-17 00:39:37 +00003942 (void) FormatMagickString(label,MaxTextExtent,"charcoal %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003943 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00003944 break;
3945 }
3946 case JPEGPreview:
3947 {
3948 char
3949 filename[MaxTextExtent];
3950
3951 int
3952 file;
3953
3954 MagickBooleanType
3955 status;
3956
3957 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3958 if (preview_image == (Image *) NULL)
3959 break;
cristybb503372010-05-27 20:51:26 +00003960 preview_info->quality=(size_t) percentage;
cristye8c25f92010-06-03 00:53:06 +00003961 (void) FormatMagickString(factor,MaxTextExtent,"%.20g",(double)
3962 preview_info->quality);
cristy3ed852e2009-09-05 21:47:34 +00003963 file=AcquireUniqueFileResource(filename);
3964 if (file != -1)
3965 file=close(file)-1;
3966 (void) FormatMagickString(preview_image->filename,MaxTextExtent,
3967 "jpeg:%s",filename);
3968 status=WriteImage(preview_info,preview_image);
3969 if (status != MagickFalse)
3970 {
3971 Image
3972 *quality_image;
3973
3974 (void) CopyMagickString(preview_info->filename,
3975 preview_image->filename,MaxTextExtent);
3976 quality_image=ReadImage(preview_info,exception);
3977 if (quality_image != (Image *) NULL)
3978 {
3979 preview_image=DestroyImage(preview_image);
3980 preview_image=quality_image;
3981 }
3982 }
3983 (void) RelinquishUniqueFileResource(preview_image->filename);
3984 if ((GetBlobSize(preview_image)/1024) >= 1024)
cristye7f51092010-01-17 00:39:37 +00003985 (void) FormatMagickString(label,MaxTextExtent,"quality %s\n%gmb ",
cristy3ed852e2009-09-05 21:47:34 +00003986 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
3987 1024.0/1024.0);
3988 else
3989 if (GetBlobSize(preview_image) >= 1024)
cristy8cd5b312010-01-07 01:10:24 +00003990 (void) FormatMagickString(label,MaxTextExtent,
cristye7f51092010-01-17 00:39:37 +00003991 "quality %s\n%gkb ",factor,(double) ((MagickOffsetType)
cristy8cd5b312010-01-07 01:10:24 +00003992 GetBlobSize(preview_image))/1024.0);
cristy3ed852e2009-09-05 21:47:34 +00003993 else
cristye8c25f92010-06-03 00:53:06 +00003994 (void) FormatMagickString(label,MaxTextExtent,"quality %s\n%.20gb ",
3995 factor,(double) GetBlobSize(thumbnail));
cristy3ed852e2009-09-05 21:47:34 +00003996 break;
3997 }
3998 }
3999 thumbnail=DestroyImage(thumbnail);
4000 percentage+=12.5;
4001 radius+=0.5;
4002 sigma+=0.25;
4003 if (preview_image == (Image *) NULL)
4004 break;
4005 (void) DeleteImageProperty(preview_image,"label");
4006 (void) SetImageProperty(preview_image,"label",label);
4007 AppendImageToList(&images,preview_image);
cristybb503372010-05-27 20:51:26 +00004008 proceed=SetImageProgress(image,PreviewImageTag,(MagickOffsetType) i,
4009 NumberTiles);
cristy3ed852e2009-09-05 21:47:34 +00004010 if (proceed == MagickFalse)
4011 break;
4012 }
4013 if (images == (Image *) NULL)
4014 {
4015 preview_info=DestroyImageInfo(preview_info);
4016 return((Image *) NULL);
4017 }
4018 /*
4019 Create the montage.
4020 */
4021 montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL);
4022 (void) CopyMagickString(montage_info->filename,image->filename,MaxTextExtent);
4023 montage_info->shadow=MagickTrue;
4024 (void) CloneString(&montage_info->tile,"3x3");
4025 (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry);
4026 (void) CloneString(&montage_info->frame,DefaultTileFrame);
4027 montage_image=MontageImages(images,montage_info,exception);
4028 montage_info=DestroyMontageInfo(montage_info);
4029 images=DestroyImageList(images);
4030 if (montage_image == (Image *) NULL)
4031 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
4032 if (montage_image->montage != (char *) NULL)
4033 {
4034 /*
4035 Free image directory.
4036 */
4037 montage_image->montage=(char *) RelinquishMagickMemory(
4038 montage_image->montage);
4039 if (image->directory != (char *) NULL)
4040 montage_image->directory=(char *) RelinquishMagickMemory(
4041 montage_image->directory);
4042 }
4043 preview_info=DestroyImageInfo(preview_info);
4044 return(montage_image);
4045}
4046
4047/*
4048%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4049% %
4050% %
4051% %
4052% R a d i a l B l u r I m a g e %
4053% %
4054% %
4055% %
4056%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4057%
4058% RadialBlurImage() applies a radial blur to the image.
4059%
4060% Andrew Protano contributed this effect.
4061%
4062% The format of the RadialBlurImage method is:
4063%
4064% Image *RadialBlurImage(const Image *image,const double angle,
4065% ExceptionInfo *exception)
4066% Image *RadialBlurImageChannel(const Image *image,const ChannelType channel,
4067% const double angle,ExceptionInfo *exception)
4068%
4069% A description of each parameter follows:
4070%
4071% o image: the image.
4072%
4073% o channel: the channel type.
4074%
4075% o angle: the angle of the radial blur.
4076%
4077% o exception: return any errors or warnings in this structure.
4078%
4079*/
4080
4081MagickExport Image *RadialBlurImage(const Image *image,const double angle,
4082 ExceptionInfo *exception)
4083{
4084 Image
4085 *blur_image;
4086
4087 blur_image=RadialBlurImageChannel(image,DefaultChannels,angle,exception);
4088 return(blur_image);
4089}
4090
4091MagickExport Image *RadialBlurImageChannel(const Image *image,
4092 const ChannelType channel,const double angle,ExceptionInfo *exception)
4093{
cristyc4c8d132010-01-07 01:58:38 +00004094 CacheView
4095 *blur_view,
4096 *image_view;
4097
cristy3ed852e2009-09-05 21:47:34 +00004098 Image
4099 *blur_image;
4100
cristy3ed852e2009-09-05 21:47:34 +00004101 MagickBooleanType
4102 status;
4103
cristybb503372010-05-27 20:51:26 +00004104 MagickOffsetType
4105 progress;
4106
cristy3ed852e2009-09-05 21:47:34 +00004107 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00004108 bias;
cristy3ed852e2009-09-05 21:47:34 +00004109
4110 MagickRealType
4111 blur_radius,
4112 *cos_theta,
4113 offset,
4114 *sin_theta,
4115 theta;
4116
4117 PointInfo
4118 blur_center;
4119
cristybb503372010-05-27 20:51:26 +00004120 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00004121 i;
4122
cristybb503372010-05-27 20:51:26 +00004123 size_t
cristy3ed852e2009-09-05 21:47:34 +00004124 n;
4125
cristybb503372010-05-27 20:51:26 +00004126 ssize_t
4127 y;
4128
cristy3ed852e2009-09-05 21:47:34 +00004129 /*
4130 Allocate blur image.
4131 */
4132 assert(image != (Image *) NULL);
4133 assert(image->signature == MagickSignature);
4134 if (image->debug != MagickFalse)
4135 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4136 assert(exception != (ExceptionInfo *) NULL);
4137 assert(exception->signature == MagickSignature);
4138 blur_image=CloneImage(image,0,0,MagickTrue,exception);
4139 if (blur_image == (Image *) NULL)
4140 return((Image *) NULL);
4141 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
4142 {
4143 InheritException(exception,&blur_image->exception);
4144 blur_image=DestroyImage(blur_image);
4145 return((Image *) NULL);
4146 }
4147 blur_center.x=(double) image->columns/2.0;
4148 blur_center.y=(double) image->rows/2.0;
4149 blur_radius=hypot(blur_center.x,blur_center.y);
cristy117ff172010-08-15 21:35:32 +00004150 n=(size_t) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+2UL);
cristy3ed852e2009-09-05 21:47:34 +00004151 theta=DegreesToRadians(angle)/(MagickRealType) (n-1);
4152 cos_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
4153 sizeof(*cos_theta));
4154 sin_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
4155 sizeof(*sin_theta));
4156 if ((cos_theta == (MagickRealType *) NULL) ||
4157 (sin_theta == (MagickRealType *) NULL))
4158 {
4159 blur_image=DestroyImage(blur_image);
4160 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
4161 }
4162 offset=theta*(MagickRealType) (n-1)/2.0;
cristybb503372010-05-27 20:51:26 +00004163 for (i=0; i < (ssize_t) n; i++)
cristy3ed852e2009-09-05 21:47:34 +00004164 {
4165 cos_theta[i]=cos((double) (theta*i-offset));
4166 sin_theta[i]=sin((double) (theta*i-offset));
4167 }
4168 /*
4169 Radial blur image.
4170 */
4171 status=MagickTrue;
4172 progress=0;
cristyddd82202009-11-03 20:14:50 +00004173 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00004174 image_view=AcquireCacheView(image);
4175 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00004176#if defined(MAGICKCORE_OPENMP_SUPPORT)
4177 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004178#endif
cristybb503372010-05-27 20:51:26 +00004179 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00004180 {
4181 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004182 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00004183
4184 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004185 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00004186
cristy3ed852e2009-09-05 21:47:34 +00004187 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004188 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004189
cristy117ff172010-08-15 21:35:32 +00004190 register ssize_t
4191 x;
4192
cristy3ed852e2009-09-05 21:47:34 +00004193 if (status == MagickFalse)
4194 continue;
4195 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
4196 exception);
4197 if (q == (PixelPacket *) NULL)
4198 {
4199 status=MagickFalse;
4200 continue;
4201 }
4202 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
cristybb503372010-05-27 20:51:26 +00004203 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004204 {
4205 MagickPixelPacket
4206 qixel;
4207
4208 MagickRealType
4209 normalize,
4210 radius;
4211
4212 PixelPacket
4213 pixel;
4214
4215 PointInfo
4216 center;
4217
cristybb503372010-05-27 20:51:26 +00004218 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00004219 i;
4220
cristybb503372010-05-27 20:51:26 +00004221 size_t
cristy3ed852e2009-09-05 21:47:34 +00004222 step;
4223
4224 center.x=(double) x-blur_center.x;
4225 center.y=(double) y-blur_center.y;
4226 radius=hypot((double) center.x,center.y);
4227 if (radius == 0)
4228 step=1;
4229 else
4230 {
cristybb503372010-05-27 20:51:26 +00004231 step=(size_t) (blur_radius/radius);
cristy3ed852e2009-09-05 21:47:34 +00004232 if (step == 0)
4233 step=1;
4234 else
4235 if (step >= n)
4236 step=n-1;
4237 }
4238 normalize=0.0;
cristyddd82202009-11-03 20:14:50 +00004239 qixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00004240 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
4241 {
cristyeaedf062010-05-29 22:36:02 +00004242 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00004243 {
cristyeaedf062010-05-29 22:36:02 +00004244 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
4245 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
4246 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
4247 cos_theta[i]+0.5),&pixel,exception);
cristy3ed852e2009-09-05 21:47:34 +00004248 qixel.red+=pixel.red;
4249 qixel.green+=pixel.green;
4250 qixel.blue+=pixel.blue;
4251 qixel.opacity+=pixel.opacity;
4252 if (image->colorspace == CMYKColorspace)
4253 {
4254 indexes=GetCacheViewVirtualIndexQueue(image_view);
4255 qixel.index+=(*indexes);
4256 }
4257 normalize+=1.0;
4258 }
4259 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
4260 normalize);
4261 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004262 q->red=ClampToQuantum(normalize*qixel.red);
cristy3ed852e2009-09-05 21:47:34 +00004263 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004264 q->green=ClampToQuantum(normalize*qixel.green);
cristy3ed852e2009-09-05 21:47:34 +00004265 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004266 q->blue=ClampToQuantum(normalize*qixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00004267 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004268 q->opacity=ClampToQuantum(normalize*qixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00004269 if (((channel & IndexChannel) != 0) &&
4270 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00004271 blur_indexes[x]=(IndexPacket) ClampToQuantum(normalize*qixel.index);
cristy3ed852e2009-09-05 21:47:34 +00004272 }
4273 else
4274 {
4275 MagickRealType
4276 alpha,
4277 gamma;
4278
4279 alpha=1.0;
4280 gamma=0.0;
cristyeaedf062010-05-29 22:36:02 +00004281 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00004282 {
cristyeaedf062010-05-29 22:36:02 +00004283 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
4284 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
4285 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
4286 cos_theta[i]+0.5),&pixel,exception);
cristy46f08202010-01-10 04:04:21 +00004287 alpha=(MagickRealType) (QuantumScale*
4288 GetAlphaPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004289 qixel.red+=alpha*pixel.red;
4290 qixel.green+=alpha*pixel.green;
4291 qixel.blue+=alpha*pixel.blue;
4292 qixel.opacity+=pixel.opacity;
4293 if (image->colorspace == CMYKColorspace)
4294 {
4295 indexes=GetCacheViewVirtualIndexQueue(image_view);
4296 qixel.index+=alpha*(*indexes);
4297 }
4298 gamma+=alpha;
4299 normalize+=1.0;
4300 }
4301 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
4302 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
4303 normalize);
4304 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004305 q->red=ClampToQuantum(gamma*qixel.red);
cristy3ed852e2009-09-05 21:47:34 +00004306 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004307 q->green=ClampToQuantum(gamma*qixel.green);
cristy3ed852e2009-09-05 21:47:34 +00004308 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004309 q->blue=ClampToQuantum(gamma*qixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00004310 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004311 q->opacity=ClampToQuantum(normalize*qixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00004312 if (((channel & IndexChannel) != 0) &&
4313 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00004314 blur_indexes[x]=(IndexPacket) ClampToQuantum(gamma*qixel.index);
cristy3ed852e2009-09-05 21:47:34 +00004315 }
4316 q++;
4317 }
4318 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
4319 status=MagickFalse;
4320 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4321 {
4322 MagickBooleanType
4323 proceed;
4324
cristyb5d5f722009-11-04 03:03:49 +00004325#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004326 #pragma omp critical (MagickCore_RadialBlurImageChannel)
4327#endif
4328 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
4329 if (proceed == MagickFalse)
4330 status=MagickFalse;
4331 }
4332 }
4333 blur_view=DestroyCacheView(blur_view);
4334 image_view=DestroyCacheView(image_view);
4335 cos_theta=(MagickRealType *) RelinquishMagickMemory(cos_theta);
4336 sin_theta=(MagickRealType *) RelinquishMagickMemory(sin_theta);
4337 if (status == MagickFalse)
4338 blur_image=DestroyImage(blur_image);
4339 return(blur_image);
4340}
4341
4342/*
4343%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4344% %
4345% %
4346% %
4347% R e d u c e N o i s e I m a g e %
4348% %
4349% %
4350% %
4351%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4352%
4353% ReduceNoiseImage() smooths the contours of an image while still preserving
4354% edge information. The algorithm works by replacing each pixel with its
4355% neighbor closest in value. A neighbor is defined by radius. Use a radius
4356% of 0 and ReduceNoise() selects a suitable radius for you.
4357%
4358% The format of the ReduceNoiseImage method is:
4359%
4360% Image *ReduceNoiseImage(const Image *image,const double radius,
4361% ExceptionInfo *exception)
4362%
4363% A description of each parameter follows:
4364%
4365% o image: the image.
4366%
4367% o radius: the radius of the pixel neighborhood.
4368%
4369% o exception: return any errors or warnings in this structure.
4370%
4371*/
4372
cristy69ec32d2011-02-27 23:57:09 +00004373static MagickPixelPacket GetNonpeakPixelList(PixelList *pixel_list)
cristy3ed852e2009-09-05 21:47:34 +00004374{
4375 MagickPixelPacket
4376 pixel;
4377
cristy69ec32d2011-02-27 23:57:09 +00004378 register SkipList
cristy3ed852e2009-09-05 21:47:34 +00004379 *list;
4380
cristy117ff172010-08-15 21:35:32 +00004381 register ssize_t
4382 channel;
4383
cristybb503372010-05-27 20:51:26 +00004384 size_t
cristy3ed852e2009-09-05 21:47:34 +00004385 center,
4386 color,
4387 count,
cristya6020132011-02-28 01:06:03 +00004388 next,
4389 previous;
cristy3ed852e2009-09-05 21:47:34 +00004390
4391 unsigned short
4392 channels[5];
4393
4394 /*
4395 Finds the median value for each of the color.
4396 */
4397 center=pixel_list->center;
4398 for (channel=0; channel < 5; channel++)
4399 {
4400 list=pixel_list->lists+channel;
4401 color=65536UL;
4402 next=list->nodes[color].next[0];
4403 count=0;
4404 do
4405 {
4406 previous=color;
4407 color=next;
4408 next=list->nodes[color].next[0];
4409 count+=list->nodes[color].count;
4410 }
4411 while (count <= center);
4412 if ((previous == 65536UL) && (next != 65536UL))
4413 color=next;
4414 else
4415 if ((previous != 65536UL) && (next == 65536UL))
4416 color=previous;
4417 channels[channel]=(unsigned short) color;
4418 }
4419 GetMagickPixelPacket((const Image *) NULL,&pixel);
4420 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4421 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4422 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
4423 pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
4424 pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]);
4425 return(pixel);
4426}
4427
4428MagickExport Image *ReduceNoiseImage(const Image *image,const double radius,
4429 ExceptionInfo *exception)
4430{
4431#define ReduceNoiseImageTag "ReduceNoise/Image"
4432
cristyfa112112010-01-04 17:48:07 +00004433 CacheView
4434 *image_view,
4435 *noise_view;
4436
cristy3ed852e2009-09-05 21:47:34 +00004437 Image
4438 *noise_image;
4439
cristy3ed852e2009-09-05 21:47:34 +00004440 MagickBooleanType
4441 status;
4442
cristybb503372010-05-27 20:51:26 +00004443 MagickOffsetType
4444 progress;
4445
cristy69ec32d2011-02-27 23:57:09 +00004446 PixelList
cristyfa112112010-01-04 17:48:07 +00004447 **restrict pixel_list;
cristy3ed852e2009-09-05 21:47:34 +00004448
cristybb503372010-05-27 20:51:26 +00004449 size_t
cristy3ed852e2009-09-05 21:47:34 +00004450 width;
4451
cristybb503372010-05-27 20:51:26 +00004452 ssize_t
4453 y;
4454
cristy3ed852e2009-09-05 21:47:34 +00004455 /*
4456 Initialize noise image attributes.
4457 */
4458 assert(image != (Image *) NULL);
4459 assert(image->signature == MagickSignature);
4460 if (image->debug != MagickFalse)
4461 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4462 assert(exception != (ExceptionInfo *) NULL);
4463 assert(exception->signature == MagickSignature);
4464 width=GetOptimalKernelWidth2D(radius,0.5);
cristy3ed852e2009-09-05 21:47:34 +00004465 noise_image=CloneImage(image,image->columns,image->rows,MagickTrue,
4466 exception);
4467 if (noise_image == (Image *) NULL)
4468 return((Image *) NULL);
4469 if (SetImageStorageClass(noise_image,DirectClass) == MagickFalse)
4470 {
4471 InheritException(exception,&noise_image->exception);
4472 noise_image=DestroyImage(noise_image);
4473 return((Image *) NULL);
4474 }
cristy69ec32d2011-02-27 23:57:09 +00004475 pixel_list=AcquirePixelListThreadSet(width);
4476 if (pixel_list == (PixelList **) NULL)
cristy3ed852e2009-09-05 21:47:34 +00004477 {
4478 noise_image=DestroyImage(noise_image);
4479 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
4480 }
4481 /*
4482 Reduce noise image.
4483 */
4484 status=MagickTrue;
4485 progress=0;
4486 image_view=AcquireCacheView(image);
4487 noise_view=AcquireCacheView(noise_image);
cristyb5d5f722009-11-04 03:03:49 +00004488#if defined(MAGICKCORE_OPENMP_SUPPORT)
4489 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004490#endif
cristybb503372010-05-27 20:51:26 +00004491 for (y=0; y < (ssize_t) noise_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00004492 {
cristy5c9e6f22010-09-17 17:31:01 +00004493 const int
4494 id = GetOpenMPThreadId();
cristy6ebe97c2010-07-03 01:17:28 +00004495
cristy3ed852e2009-09-05 21:47:34 +00004496 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004497 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00004498
4499 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004500 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00004501
4502 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004503 *restrict noise_indexes;
cristy3ed852e2009-09-05 21:47:34 +00004504
cristy3ed852e2009-09-05 21:47:34 +00004505 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004506 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004507
cristy117ff172010-08-15 21:35:32 +00004508 register ssize_t
4509 x;
4510
cristy3ed852e2009-09-05 21:47:34 +00004511 if (status == MagickFalse)
4512 continue;
cristy6ebe97c2010-07-03 01:17:28 +00004513 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
4514 (width/2L),image->columns+width,width,exception);
cristy3ed852e2009-09-05 21:47:34 +00004515 q=QueueCacheViewAuthenticPixels(noise_view,0,y,noise_image->columns,1,
4516 exception);
4517 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
4518 {
4519 status=MagickFalse;
4520 continue;
4521 }
4522 indexes=GetCacheViewVirtualIndexQueue(image_view);
4523 noise_indexes=GetCacheViewAuthenticIndexQueue(noise_view);
cristybb503372010-05-27 20:51:26 +00004524 for (x=0; x < (ssize_t) noise_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004525 {
4526 MagickPixelPacket
4527 pixel;
4528
4529 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004530 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +00004531
4532 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004533 *restrict s;
cristy3ed852e2009-09-05 21:47:34 +00004534
cristybb503372010-05-27 20:51:26 +00004535 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00004536 u,
4537 v;
4538
4539 r=p;
4540 s=indexes+x;
cristy69ec32d2011-02-27 23:57:09 +00004541 ResetPixelList(pixel_list[id]);
cristybb503372010-05-27 20:51:26 +00004542 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00004543 {
cristybb503372010-05-27 20:51:26 +00004544 for (u=0; u < (ssize_t) width; u++)
cristy69ec32d2011-02-27 23:57:09 +00004545 InsertPixelList(image,r+u,s+u,pixel_list[id]);
cristy3ed852e2009-09-05 21:47:34 +00004546 r+=image->columns+width;
4547 s+=image->columns+width;
4548 }
cristy69ec32d2011-02-27 23:57:09 +00004549 pixel=GetNonpeakPixelList(pixel_list[id]);
cristy3ed852e2009-09-05 21:47:34 +00004550 SetPixelPacket(noise_image,&pixel,q,noise_indexes+x);
4551 p++;
4552 q++;
4553 }
4554 if (SyncCacheViewAuthenticPixels(noise_view,exception) == MagickFalse)
4555 status=MagickFalse;
4556 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4557 {
4558 MagickBooleanType
4559 proceed;
4560
cristyb5d5f722009-11-04 03:03:49 +00004561#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004562 #pragma omp critical (MagickCore_ReduceNoiseImage)
4563#endif
4564 proceed=SetImageProgress(image,ReduceNoiseImageTag,progress++,
4565 image->rows);
4566 if (proceed == MagickFalse)
4567 status=MagickFalse;
4568 }
4569 }
4570 noise_view=DestroyCacheView(noise_view);
4571 image_view=DestroyCacheView(image_view);
cristy69ec32d2011-02-27 23:57:09 +00004572 pixel_list=DestroyPixelListThreadSet(pixel_list);
cristy3ed852e2009-09-05 21:47:34 +00004573 return(noise_image);
4574}
4575
4576/*
4577%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4578% %
4579% %
4580% %
4581% S e l e c t i v e B l u r I m a g e %
4582% %
4583% %
4584% %
4585%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4586%
4587% SelectiveBlurImage() selectively blur pixels within a contrast threshold.
4588% It is similar to the unsharpen mask that sharpens everything with contrast
4589% above a certain threshold.
4590%
4591% The format of the SelectiveBlurImage method is:
4592%
4593% Image *SelectiveBlurImage(const Image *image,const double radius,
4594% const double sigma,const double threshold,ExceptionInfo *exception)
4595% Image *SelectiveBlurImageChannel(const Image *image,
4596% const ChannelType channel,const double radius,const double sigma,
4597% const double threshold,ExceptionInfo *exception)
4598%
4599% A description of each parameter follows:
4600%
4601% o image: the image.
4602%
4603% o channel: the channel type.
4604%
4605% o radius: the radius of the Gaussian, in pixels, not counting the center
4606% pixel.
4607%
4608% o sigma: the standard deviation of the Gaussian, in pixels.
4609%
4610% o threshold: only pixels within this contrast threshold are included
4611% in the blur operation.
4612%
4613% o exception: return any errors or warnings in this structure.
4614%
4615*/
4616
4617static inline MagickBooleanType SelectiveContrast(const PixelPacket *p,
4618 const PixelPacket *q,const double threshold)
4619{
4620 if (fabs(PixelIntensity(p)-PixelIntensity(q)) < threshold)
4621 return(MagickTrue);
4622 return(MagickFalse);
4623}
4624
4625MagickExport Image *SelectiveBlurImage(const Image *image,const double radius,
4626 const double sigma,const double threshold,ExceptionInfo *exception)
4627{
4628 Image
4629 *blur_image;
4630
4631 blur_image=SelectiveBlurImageChannel(image,DefaultChannels,radius,sigma,
4632 threshold,exception);
4633 return(blur_image);
4634}
4635
4636MagickExport Image *SelectiveBlurImageChannel(const Image *image,
4637 const ChannelType channel,const double radius,const double sigma,
4638 const double threshold,ExceptionInfo *exception)
4639{
4640#define SelectiveBlurImageTag "SelectiveBlur/Image"
4641
cristy47e00502009-12-17 19:19:57 +00004642 CacheView
4643 *blur_view,
4644 *image_view;
4645
cristy3ed852e2009-09-05 21:47:34 +00004646 double
cristy3ed852e2009-09-05 21:47:34 +00004647 *kernel;
4648
4649 Image
4650 *blur_image;
4651
cristy3ed852e2009-09-05 21:47:34 +00004652 MagickBooleanType
4653 status;
4654
cristybb503372010-05-27 20:51:26 +00004655 MagickOffsetType
4656 progress;
4657
cristy3ed852e2009-09-05 21:47:34 +00004658 MagickPixelPacket
cristy3ed852e2009-09-05 21:47:34 +00004659 bias;
4660
cristybb503372010-05-27 20:51:26 +00004661 register ssize_t
cristy47e00502009-12-17 19:19:57 +00004662 i;
cristy3ed852e2009-09-05 21:47:34 +00004663
cristybb503372010-05-27 20:51:26 +00004664 size_t
cristy3ed852e2009-09-05 21:47:34 +00004665 width;
4666
cristybb503372010-05-27 20:51:26 +00004667 ssize_t
4668 j,
4669 u,
4670 v,
4671 y;
4672
cristy3ed852e2009-09-05 21:47:34 +00004673 /*
4674 Initialize blur image attributes.
4675 */
4676 assert(image != (Image *) NULL);
4677 assert(image->signature == MagickSignature);
4678 if (image->debug != MagickFalse)
4679 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4680 assert(exception != (ExceptionInfo *) NULL);
4681 assert(exception->signature == MagickSignature);
4682 width=GetOptimalKernelWidth1D(radius,sigma);
4683 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
4684 if (kernel == (double *) NULL)
4685 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00004686 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00004687 i=0;
cristy47e00502009-12-17 19:19:57 +00004688 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00004689 {
cristy47e00502009-12-17 19:19:57 +00004690 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00004691 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
4692 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00004693 }
4694 if (image->debug != MagickFalse)
4695 {
4696 char
4697 format[MaxTextExtent],
4698 *message;
4699
cristy117ff172010-08-15 21:35:32 +00004700 register const double
4701 *k;
4702
cristybb503372010-05-27 20:51:26 +00004703 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00004704 u,
4705 v;
4706
cristy3ed852e2009-09-05 21:47:34 +00004707 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00004708 " SelectiveBlurImage with %.20gx%.20g kernel:",(double) width,(double)
4709 width);
cristy3ed852e2009-09-05 21:47:34 +00004710 message=AcquireString("");
4711 k=kernel;
cristybb503372010-05-27 20:51:26 +00004712 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00004713 {
4714 *message='\0';
cristye8c25f92010-06-03 00:53:06 +00004715 (void) FormatMagickString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy3ed852e2009-09-05 21:47:34 +00004716 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00004717 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00004718 {
4719 (void) FormatMagickString(format,MaxTextExtent,"%+f ",*k++);
4720 (void) ConcatenateString(&message,format);
4721 }
4722 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
4723 }
4724 message=DestroyString(message);
4725 }
4726 blur_image=CloneImage(image,0,0,MagickTrue,exception);
4727 if (blur_image == (Image *) NULL)
4728 return((Image *) NULL);
4729 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
4730 {
4731 InheritException(exception,&blur_image->exception);
4732 blur_image=DestroyImage(blur_image);
4733 return((Image *) NULL);
4734 }
4735 /*
4736 Threshold blur image.
4737 */
4738 status=MagickTrue;
4739 progress=0;
cristyddd82202009-11-03 20:14:50 +00004740 GetMagickPixelPacket(image,&bias);
4741 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00004742 image_view=AcquireCacheView(image);
4743 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00004744#if defined(MAGICKCORE_OPENMP_SUPPORT)
4745 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004746#endif
cristybb503372010-05-27 20:51:26 +00004747 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00004748 {
4749 MagickBooleanType
4750 sync;
4751
4752 MagickRealType
4753 gamma;
4754
4755 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004756 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00004757
4758 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004759 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00004760
4761 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004762 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00004763
cristy3ed852e2009-09-05 21:47:34 +00004764 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004765 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004766
cristy117ff172010-08-15 21:35:32 +00004767 register ssize_t
4768 x;
4769
cristy3ed852e2009-09-05 21:47:34 +00004770 if (status == MagickFalse)
4771 continue;
cristy117ff172010-08-15 21:35:32 +00004772 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
4773 (width/2L),image->columns+width,width,exception);
cristy3ed852e2009-09-05 21:47:34 +00004774 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
4775 exception);
4776 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
4777 {
4778 status=MagickFalse;
4779 continue;
4780 }
4781 indexes=GetCacheViewVirtualIndexQueue(image_view);
4782 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
cristybb503372010-05-27 20:51:26 +00004783 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004784 {
cristy3ed852e2009-09-05 21:47:34 +00004785 MagickPixelPacket
4786 pixel;
4787
4788 register const double
cristyc47d1f82009-11-26 01:44:43 +00004789 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00004790
cristybb503372010-05-27 20:51:26 +00004791 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00004792 u;
4793
cristy117ff172010-08-15 21:35:32 +00004794 ssize_t
4795 j,
4796 v;
4797
cristyddd82202009-11-03 20:14:50 +00004798 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00004799 k=kernel;
4800 gamma=0.0;
4801 j=0;
4802 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
4803 {
cristybb503372010-05-27 20:51:26 +00004804 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00004805 {
cristybb503372010-05-27 20:51:26 +00004806 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00004807 {
4808 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4809 {
4810 pixel.red+=(*k)*(p+u+j)->red;
4811 pixel.green+=(*k)*(p+u+j)->green;
4812 pixel.blue+=(*k)*(p+u+j)->blue;
4813 gamma+=(*k);
4814 k++;
4815 }
4816 }
cristyd99b0962010-05-29 23:14:26 +00004817 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00004818 }
4819 if (gamma != 0.0)
4820 {
4821 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
4822 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004823 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004824 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004825 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004826 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004827 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004828 }
4829 if ((channel & OpacityChannel) != 0)
4830 {
4831 gamma=0.0;
4832 j=0;
cristybb503372010-05-27 20:51:26 +00004833 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00004834 {
cristybb503372010-05-27 20:51:26 +00004835 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00004836 {
4837 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4838 {
4839 pixel.opacity+=(*k)*(p+u+j)->opacity;
4840 gamma+=(*k);
4841 k++;
4842 }
4843 }
cristyeaedf062010-05-29 22:36:02 +00004844 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00004845 }
4846 if (gamma != 0.0)
4847 {
4848 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4849 gamma);
cristyce70c172010-01-07 17:15:30 +00004850 SetOpacityPixelComponent(q,ClampToQuantum(gamma*
4851 GetOpacityPixelComponent(&pixel)));
cristy3ed852e2009-09-05 21:47:34 +00004852 }
4853 }
4854 if (((channel & IndexChannel) != 0) &&
4855 (image->colorspace == CMYKColorspace))
4856 {
4857 gamma=0.0;
4858 j=0;
cristybb503372010-05-27 20:51:26 +00004859 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00004860 {
cristybb503372010-05-27 20:51:26 +00004861 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00004862 {
4863 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4864 {
4865 pixel.index+=(*k)*indexes[x+u+j];
4866 gamma+=(*k);
4867 k++;
4868 }
4869 }
cristyeaedf062010-05-29 22:36:02 +00004870 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00004871 }
4872 if (gamma != 0.0)
4873 {
4874 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4875 gamma);
cristy6db48122010-01-11 00:18:07 +00004876 blur_indexes[x]=ClampToQuantum(gamma*
4877 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004878 }
4879 }
4880 }
4881 else
4882 {
4883 MagickRealType
4884 alpha;
4885
cristybb503372010-05-27 20:51:26 +00004886 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00004887 {
cristybb503372010-05-27 20:51:26 +00004888 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00004889 {
4890 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4891 {
cristy46f08202010-01-10 04:04:21 +00004892 alpha=(MagickRealType) (QuantumScale*
4893 GetAlphaPixelComponent(p+u+j));
cristy3ed852e2009-09-05 21:47:34 +00004894 pixel.red+=(*k)*alpha*(p+u+j)->red;
4895 pixel.green+=(*k)*alpha*(p+u+j)->green;
4896 pixel.blue+=(*k)*alpha*(p+u+j)->blue;
4897 pixel.opacity+=(*k)*(p+u+j)->opacity;
4898 gamma+=(*k)*alpha;
4899 k++;
4900 }
4901 }
cristyeaedf062010-05-29 22:36:02 +00004902 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00004903 }
4904 if (gamma != 0.0)
4905 {
4906 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
4907 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004908 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004909 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004910 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004911 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004912 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004913 }
4914 if ((channel & OpacityChannel) != 0)
4915 {
4916 gamma=0.0;
4917 j=0;
cristybb503372010-05-27 20:51:26 +00004918 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00004919 {
cristybb503372010-05-27 20:51:26 +00004920 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00004921 {
4922 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4923 {
4924 pixel.opacity+=(*k)*(p+u+j)->opacity;
4925 gamma+=(*k);
4926 k++;
4927 }
4928 }
cristyeaedf062010-05-29 22:36:02 +00004929 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00004930 }
4931 if (gamma != 0.0)
4932 {
4933 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4934 gamma);
cristy6db48122010-01-11 00:18:07 +00004935 SetOpacityPixelComponent(q,
4936 ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004937 }
4938 }
4939 if (((channel & IndexChannel) != 0) &&
4940 (image->colorspace == CMYKColorspace))
4941 {
4942 gamma=0.0;
4943 j=0;
cristybb503372010-05-27 20:51:26 +00004944 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00004945 {
cristybb503372010-05-27 20:51:26 +00004946 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00004947 {
4948 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4949 {
cristy46f08202010-01-10 04:04:21 +00004950 alpha=(MagickRealType) (QuantumScale*
4951 GetAlphaPixelComponent(p+u+j));
cristy3ed852e2009-09-05 21:47:34 +00004952 pixel.index+=(*k)*alpha*indexes[x+u+j];
4953 gamma+=(*k);
4954 k++;
4955 }
4956 }
cristyeaedf062010-05-29 22:36:02 +00004957 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00004958 }
4959 if (gamma != 0.0)
4960 {
4961 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4962 gamma);
cristy6db48122010-01-11 00:18:07 +00004963 blur_indexes[x]=ClampToQuantum(gamma*
4964 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004965 }
4966 }
4967 }
4968 p++;
4969 q++;
4970 }
4971 sync=SyncCacheViewAuthenticPixels(blur_view,exception);
4972 if (sync == MagickFalse)
4973 status=MagickFalse;
4974 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4975 {
4976 MagickBooleanType
4977 proceed;
4978
cristyb5d5f722009-11-04 03:03:49 +00004979#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004980 #pragma omp critical (MagickCore_SelectiveBlurImageChannel)
4981#endif
4982 proceed=SetImageProgress(image,SelectiveBlurImageTag,progress++,
4983 image->rows);
4984 if (proceed == MagickFalse)
4985 status=MagickFalse;
4986 }
4987 }
4988 blur_image->type=image->type;
4989 blur_view=DestroyCacheView(blur_view);
4990 image_view=DestroyCacheView(image_view);
4991 kernel=(double *) RelinquishMagickMemory(kernel);
4992 if (status == MagickFalse)
4993 blur_image=DestroyImage(blur_image);
4994 return(blur_image);
4995}
4996
4997/*
4998%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4999% %
5000% %
5001% %
5002% S h a d e I m a g e %
5003% %
5004% %
5005% %
5006%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5007%
5008% ShadeImage() shines a distant light on an image to create a
5009% three-dimensional effect. You control the positioning of the light with
5010% azimuth and elevation; azimuth is measured in degrees off the x axis
5011% and elevation is measured in pixels above the Z axis.
5012%
5013% The format of the ShadeImage method is:
5014%
5015% Image *ShadeImage(const Image *image,const MagickBooleanType gray,
5016% const double azimuth,const double elevation,ExceptionInfo *exception)
5017%
5018% A description of each parameter follows:
5019%
5020% o image: the image.
5021%
5022% o gray: A value other than zero shades the intensity of each pixel.
5023%
5024% o azimuth, elevation: Define the light source direction.
5025%
5026% o exception: return any errors or warnings in this structure.
5027%
5028*/
5029MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray,
5030 const double azimuth,const double elevation,ExceptionInfo *exception)
5031{
5032#define ShadeImageTag "Shade/Image"
5033
cristyc4c8d132010-01-07 01:58:38 +00005034 CacheView
5035 *image_view,
5036 *shade_view;
5037
cristy3ed852e2009-09-05 21:47:34 +00005038 Image
5039 *shade_image;
5040
cristy3ed852e2009-09-05 21:47:34 +00005041 MagickBooleanType
5042 status;
5043
cristybb503372010-05-27 20:51:26 +00005044 MagickOffsetType
5045 progress;
5046
cristy3ed852e2009-09-05 21:47:34 +00005047 PrimaryInfo
5048 light;
5049
cristybb503372010-05-27 20:51:26 +00005050 ssize_t
5051 y;
5052
cristy3ed852e2009-09-05 21:47:34 +00005053 /*
5054 Initialize shaded image attributes.
5055 */
5056 assert(image != (const Image *) NULL);
5057 assert(image->signature == MagickSignature);
5058 if (image->debug != MagickFalse)
5059 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5060 assert(exception != (ExceptionInfo *) NULL);
5061 assert(exception->signature == MagickSignature);
5062 shade_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
5063 if (shade_image == (Image *) NULL)
5064 return((Image *) NULL);
5065 if (SetImageStorageClass(shade_image,DirectClass) == MagickFalse)
5066 {
5067 InheritException(exception,&shade_image->exception);
5068 shade_image=DestroyImage(shade_image);
5069 return((Image *) NULL);
5070 }
5071 /*
5072 Compute the light vector.
5073 */
5074 light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))*
5075 cos(DegreesToRadians(elevation));
5076 light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))*
5077 cos(DegreesToRadians(elevation));
5078 light.z=(double) QuantumRange*sin(DegreesToRadians(elevation));
5079 /*
5080 Shade image.
5081 */
5082 status=MagickTrue;
5083 progress=0;
5084 image_view=AcquireCacheView(image);
5085 shade_view=AcquireCacheView(shade_image);
cristyb5d5f722009-11-04 03:03:49 +00005086#if defined(MAGICKCORE_OPENMP_SUPPORT)
5087 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00005088#endif
cristybb503372010-05-27 20:51:26 +00005089 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00005090 {
5091 MagickRealType
5092 distance,
5093 normal_distance,
5094 shade;
5095
5096 PrimaryInfo
5097 normal;
5098
5099 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00005100 *restrict p,
5101 *restrict s0,
5102 *restrict s1,
5103 *restrict s2;
cristy3ed852e2009-09-05 21:47:34 +00005104
cristy3ed852e2009-09-05 21:47:34 +00005105 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00005106 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00005107
cristy117ff172010-08-15 21:35:32 +00005108 register ssize_t
5109 x;
5110
cristy3ed852e2009-09-05 21:47:34 +00005111 if (status == MagickFalse)
5112 continue;
5113 p=GetCacheViewVirtualPixels(image_view,-1,y-1,image->columns+2,3,exception);
5114 q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1,
5115 exception);
5116 if ((p == (PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
5117 {
5118 status=MagickFalse;
5119 continue;
5120 }
5121 /*
5122 Shade this row of pixels.
5123 */
5124 normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */
5125 s0=p+1;
5126 s1=s0+image->columns+2;
5127 s2=s1+image->columns+2;
cristybb503372010-05-27 20:51:26 +00005128 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00005129 {
5130 /*
5131 Determine the surface normal and compute shading.
5132 */
5133 normal.x=(double) (PixelIntensity(s0-1)+PixelIntensity(s1-1)+
5134 PixelIntensity(s2-1)-PixelIntensity(s0+1)-PixelIntensity(s1+1)-
5135 PixelIntensity(s2+1));
5136 normal.y=(double) (PixelIntensity(s2-1)+PixelIntensity(s2)+
5137 PixelIntensity(s2+1)-PixelIntensity(s0-1)-PixelIntensity(s0)-
5138 PixelIntensity(s0+1));
5139 if ((normal.x == 0.0) && (normal.y == 0.0))
5140 shade=light.z;
5141 else
5142 {
5143 shade=0.0;
5144 distance=normal.x*light.x+normal.y*light.y+normal.z*light.z;
5145 if (distance > MagickEpsilon)
5146 {
5147 normal_distance=
5148 normal.x*normal.x+normal.y*normal.y+normal.z*normal.z;
5149 if (normal_distance > (MagickEpsilon*MagickEpsilon))
5150 shade=distance/sqrt((double) normal_distance);
5151 }
5152 }
5153 if (gray != MagickFalse)
5154 {
5155 q->red=(Quantum) shade;
5156 q->green=(Quantum) shade;
5157 q->blue=(Quantum) shade;
5158 }
5159 else
5160 {
cristyce70c172010-01-07 17:15:30 +00005161 q->red=ClampToQuantum(QuantumScale*shade*s1->red);
5162 q->green=ClampToQuantum(QuantumScale*shade*s1->green);
5163 q->blue=ClampToQuantum(QuantumScale*shade*s1->blue);
cristy3ed852e2009-09-05 21:47:34 +00005164 }
5165 q->opacity=s1->opacity;
5166 s0++;
5167 s1++;
5168 s2++;
5169 q++;
5170 }
5171 if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
5172 status=MagickFalse;
5173 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5174 {
5175 MagickBooleanType
5176 proceed;
5177
cristyb5d5f722009-11-04 03:03:49 +00005178#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00005179 #pragma omp critical (MagickCore_ShadeImage)
5180#endif
5181 proceed=SetImageProgress(image,ShadeImageTag,progress++,image->rows);
5182 if (proceed == MagickFalse)
5183 status=MagickFalse;
5184 }
5185 }
5186 shade_view=DestroyCacheView(shade_view);
5187 image_view=DestroyCacheView(image_view);
5188 if (status == MagickFalse)
5189 shade_image=DestroyImage(shade_image);
5190 return(shade_image);
5191}
5192
5193/*
5194%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5195% %
5196% %
5197% %
5198% S h a r p e n I m a g e %
5199% %
5200% %
5201% %
5202%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5203%
5204% SharpenImage() sharpens the image. We convolve the image with a Gaussian
5205% operator of the given radius and standard deviation (sigma). For
5206% reasonable results, radius should be larger than sigma. Use a radius of 0
5207% and SharpenImage() selects a suitable radius for you.
5208%
5209% Using a separable kernel would be faster, but the negative weights cancel
5210% out on the corners of the kernel producing often undesirable ringing in the
5211% filtered result; this can be avoided by using a 2D gaussian shaped image
5212% sharpening kernel instead.
5213%
5214% The format of the SharpenImage method is:
5215%
5216% Image *SharpenImage(const Image *image,const double radius,
5217% const double sigma,ExceptionInfo *exception)
5218% Image *SharpenImageChannel(const Image *image,const ChannelType channel,
5219% const double radius,const double sigma,ExceptionInfo *exception)
5220%
5221% A description of each parameter follows:
5222%
5223% o image: the image.
5224%
5225% o channel: the channel type.
5226%
5227% o radius: the radius of the Gaussian, in pixels, not counting the center
5228% pixel.
5229%
5230% o sigma: the standard deviation of the Laplacian, in pixels.
5231%
5232% o exception: return any errors or warnings in this structure.
5233%
5234*/
5235
5236MagickExport Image *SharpenImage(const Image *image,const double radius,
5237 const double sigma,ExceptionInfo *exception)
5238{
5239 Image
5240 *sharp_image;
5241
5242 sharp_image=SharpenImageChannel(image,DefaultChannels,radius,sigma,exception);
5243 return(sharp_image);
5244}
5245
5246MagickExport Image *SharpenImageChannel(const Image *image,
5247 const ChannelType channel,const double radius,const double sigma,
5248 ExceptionInfo *exception)
5249{
5250 double
cristy47e00502009-12-17 19:19:57 +00005251 *kernel,
5252 normalize;
cristy3ed852e2009-09-05 21:47:34 +00005253
5254 Image
5255 *sharp_image;
5256
cristybb503372010-05-27 20:51:26 +00005257 register ssize_t
cristy47e00502009-12-17 19:19:57 +00005258 i;
5259
cristybb503372010-05-27 20:51:26 +00005260 size_t
cristy3ed852e2009-09-05 21:47:34 +00005261 width;
5262
cristy117ff172010-08-15 21:35:32 +00005263 ssize_t
5264 j,
5265 u,
5266 v;
5267
cristy3ed852e2009-09-05 21:47:34 +00005268 assert(image != (const Image *) NULL);
5269 assert(image->signature == MagickSignature);
5270 if (image->debug != MagickFalse)
5271 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5272 assert(exception != (ExceptionInfo *) NULL);
5273 assert(exception->signature == MagickSignature);
5274 width=GetOptimalKernelWidth2D(radius,sigma);
5275 kernel=(double *) AcquireQuantumMemory((size_t) width*width,sizeof(*kernel));
5276 if (kernel == (double *) NULL)
5277 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy3ed852e2009-09-05 21:47:34 +00005278 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00005279 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +00005280 i=0;
5281 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00005282 {
cristy47e00502009-12-17 19:19:57 +00005283 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00005284 {
cristy4205a3c2010-09-12 20:19:59 +00005285 kernel[i]=(double) (-exp(-((double) u*u+v*v)/(2.0*MagickSigma*
5286 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00005287 normalize+=kernel[i];
5288 i++;
5289 }
5290 }
5291 kernel[i/2]=(double) ((-2.0)*normalize);
5292 sharp_image=ConvolveImageChannel(image,channel,width,kernel,exception);
5293 kernel=(double *) RelinquishMagickMemory(kernel);
5294 return(sharp_image);
5295}
5296
5297/*
5298%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5299% %
5300% %
5301% %
5302% S p r e a d I m a g e %
5303% %
5304% %
5305% %
5306%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5307%
5308% SpreadImage() is a special effects method that randomly displaces each
5309% pixel in a block defined by the radius parameter.
5310%
5311% The format of the SpreadImage method is:
5312%
5313% Image *SpreadImage(const Image *image,const double radius,
5314% ExceptionInfo *exception)
5315%
5316% A description of each parameter follows:
5317%
5318% o image: the image.
5319%
5320% o radius: Choose a random pixel in a neighborhood of this extent.
5321%
5322% o exception: return any errors or warnings in this structure.
5323%
5324*/
5325MagickExport Image *SpreadImage(const Image *image,const double radius,
5326 ExceptionInfo *exception)
5327{
5328#define SpreadImageTag "Spread/Image"
5329
cristyfa112112010-01-04 17:48:07 +00005330 CacheView
5331 *image_view;
5332
cristy3ed852e2009-09-05 21:47:34 +00005333 Image
5334 *spread_image;
5335
cristy3ed852e2009-09-05 21:47:34 +00005336 MagickBooleanType
5337 status;
5338
cristybb503372010-05-27 20:51:26 +00005339 MagickOffsetType
5340 progress;
5341
cristy3ed852e2009-09-05 21:47:34 +00005342 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00005343 bias;
cristy3ed852e2009-09-05 21:47:34 +00005344
5345 RandomInfo
cristyfa112112010-01-04 17:48:07 +00005346 **restrict random_info;
cristy3ed852e2009-09-05 21:47:34 +00005347
5348 ResampleFilter
cristyfa112112010-01-04 17:48:07 +00005349 **restrict resample_filter;
cristy3ed852e2009-09-05 21:47:34 +00005350
cristybb503372010-05-27 20:51:26 +00005351 size_t
cristy3ed852e2009-09-05 21:47:34 +00005352 width;
5353
cristybb503372010-05-27 20:51:26 +00005354 ssize_t
5355 y;
5356
cristy3ed852e2009-09-05 21:47:34 +00005357 /*
5358 Initialize spread image attributes.
5359 */
5360 assert(image != (Image *) NULL);
5361 assert(image->signature == MagickSignature);
5362 if (image->debug != MagickFalse)
5363 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5364 assert(exception != (ExceptionInfo *) NULL);
5365 assert(exception->signature == MagickSignature);
5366 spread_image=CloneImage(image,image->columns,image->rows,MagickTrue,
5367 exception);
5368 if (spread_image == (Image *) NULL)
5369 return((Image *) NULL);
5370 if (SetImageStorageClass(spread_image,DirectClass) == MagickFalse)
5371 {
5372 InheritException(exception,&spread_image->exception);
5373 spread_image=DestroyImage(spread_image);
5374 return((Image *) NULL);
5375 }
5376 /*
5377 Spread image.
5378 */
5379 status=MagickTrue;
5380 progress=0;
cristyddd82202009-11-03 20:14:50 +00005381 GetMagickPixelPacket(spread_image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00005382 width=GetOptimalKernelWidth1D(radius,0.5);
cristyb2a11ae2010-02-22 00:53:36 +00005383 resample_filter=AcquireResampleFilterThreadSet(image,
5384 UndefinedVirtualPixelMethod,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00005385 random_info=AcquireRandomInfoThreadSet();
5386 image_view=AcquireCacheView(spread_image);
cristyb557a152011-02-22 12:14:30 +00005387#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00005388 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00005389#endif
cristybb503372010-05-27 20:51:26 +00005390 for (y=0; y < (ssize_t) spread_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00005391 {
cristy5c9e6f22010-09-17 17:31:01 +00005392 const int
5393 id = GetOpenMPThreadId();
cristy6ebe97c2010-07-03 01:17:28 +00005394
cristy3ed852e2009-09-05 21:47:34 +00005395 MagickPixelPacket
5396 pixel;
5397
5398 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00005399 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00005400
cristy3ed852e2009-09-05 21:47:34 +00005401 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00005402 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00005403
cristy117ff172010-08-15 21:35:32 +00005404 register ssize_t
5405 x;
5406
cristy3ed852e2009-09-05 21:47:34 +00005407 if (status == MagickFalse)
5408 continue;
5409 q=QueueCacheViewAuthenticPixels(image_view,0,y,spread_image->columns,1,
5410 exception);
5411 if (q == (PixelPacket *) NULL)
5412 {
5413 status=MagickFalse;
5414 continue;
5415 }
5416 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristyddd82202009-11-03 20:14:50 +00005417 pixel=bias;
cristybb503372010-05-27 20:51:26 +00005418 for (x=0; x < (ssize_t) spread_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00005419 {
5420 (void) ResamplePixelColor(resample_filter[id],(double) x+width*
5421 (GetPseudoRandomValue(random_info[id])-0.5),(double) y+width*
5422 (GetPseudoRandomValue(random_info[id])-0.5),&pixel);
5423 SetPixelPacket(spread_image,&pixel,q,indexes+x);
5424 q++;
5425 }
5426 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
5427 status=MagickFalse;
5428 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5429 {
5430 MagickBooleanType
5431 proceed;
5432
cristyb557a152011-02-22 12:14:30 +00005433#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00005434 #pragma omp critical (MagickCore_SpreadImage)
5435#endif
5436 proceed=SetImageProgress(image,SpreadImageTag,progress++,image->rows);
5437 if (proceed == MagickFalse)
5438 status=MagickFalse;
5439 }
5440 }
5441 image_view=DestroyCacheView(image_view);
5442 random_info=DestroyRandomInfoThreadSet(random_info);
5443 resample_filter=DestroyResampleFilterThreadSet(resample_filter);
5444 return(spread_image);
5445}
5446
5447/*
5448%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5449% %
5450% %
5451% %
5452% U n s h a r p M a s k I m a g e %
5453% %
5454% %
5455% %
5456%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5457%
5458% UnsharpMaskImage() sharpens one or more image channels. We convolve the
5459% image with a Gaussian operator of the given radius and standard deviation
5460% (sigma). For reasonable results, radius should be larger than sigma. Use a
5461% radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
5462%
5463% The format of the UnsharpMaskImage method is:
5464%
5465% Image *UnsharpMaskImage(const Image *image,const double radius,
5466% const double sigma,const double amount,const double threshold,
5467% ExceptionInfo *exception)
5468% Image *UnsharpMaskImageChannel(const Image *image,
5469% const ChannelType channel,const double radius,const double sigma,
5470% const double amount,const double threshold,ExceptionInfo *exception)
5471%
5472% A description of each parameter follows:
5473%
5474% o image: the image.
5475%
5476% o channel: the channel type.
5477%
5478% o radius: the radius of the Gaussian, in pixels, not counting the center
5479% pixel.
5480%
5481% o sigma: the standard deviation of the Gaussian, in pixels.
5482%
5483% o amount: the percentage of the difference between the original and the
5484% blur image that is added back into the original.
5485%
5486% o threshold: the threshold in pixels needed to apply the diffence amount.
5487%
5488% o exception: return any errors or warnings in this structure.
5489%
5490*/
5491
5492MagickExport Image *UnsharpMaskImage(const Image *image,const double radius,
5493 const double sigma,const double amount,const double threshold,
5494 ExceptionInfo *exception)
5495{
5496 Image
5497 *sharp_image;
5498
5499 sharp_image=UnsharpMaskImageChannel(image,DefaultChannels,radius,sigma,amount,
5500 threshold,exception);
5501 return(sharp_image);
5502}
5503
5504MagickExport Image *UnsharpMaskImageChannel(const Image *image,
5505 const ChannelType channel,const double radius,const double sigma,
5506 const double amount,const double threshold,ExceptionInfo *exception)
5507{
5508#define SharpenImageTag "Sharpen/Image"
5509
cristyc4c8d132010-01-07 01:58:38 +00005510 CacheView
5511 *image_view,
5512 *unsharp_view;
5513
cristy3ed852e2009-09-05 21:47:34 +00005514 Image
5515 *unsharp_image;
5516
cristy3ed852e2009-09-05 21:47:34 +00005517 MagickBooleanType
5518 status;
5519
cristybb503372010-05-27 20:51:26 +00005520 MagickOffsetType
5521 progress;
5522
cristy3ed852e2009-09-05 21:47:34 +00005523 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00005524 bias;
cristy3ed852e2009-09-05 21:47:34 +00005525
5526 MagickRealType
5527 quantum_threshold;
5528
cristybb503372010-05-27 20:51:26 +00005529 ssize_t
5530 y;
5531
cristy3ed852e2009-09-05 21:47:34 +00005532 assert(image != (const Image *) NULL);
5533 assert(image->signature == MagickSignature);
5534 if (image->debug != MagickFalse)
5535 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5536 assert(exception != (ExceptionInfo *) NULL);
5537 unsharp_image=BlurImageChannel(image,channel,radius,sigma,exception);
5538 if (unsharp_image == (Image *) NULL)
5539 return((Image *) NULL);
5540 quantum_threshold=(MagickRealType) QuantumRange*threshold;
5541 /*
5542 Unsharp-mask image.
5543 */
5544 status=MagickTrue;
5545 progress=0;
cristyddd82202009-11-03 20:14:50 +00005546 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00005547 image_view=AcquireCacheView(image);
5548 unsharp_view=AcquireCacheView(unsharp_image);
cristyb5d5f722009-11-04 03:03:49 +00005549#if defined(MAGICKCORE_OPENMP_SUPPORT)
5550 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00005551#endif
cristybb503372010-05-27 20:51:26 +00005552 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00005553 {
5554 MagickPixelPacket
5555 pixel;
5556
5557 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00005558 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00005559
5560 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00005561 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00005562
5563 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00005564 *restrict unsharp_indexes;
cristy3ed852e2009-09-05 21:47:34 +00005565
cristy3ed852e2009-09-05 21:47:34 +00005566 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00005567 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00005568
cristy117ff172010-08-15 21:35:32 +00005569 register ssize_t
5570 x;
5571
cristy3ed852e2009-09-05 21:47:34 +00005572 if (status == MagickFalse)
5573 continue;
5574 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
5575 q=GetCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
5576 exception);
5577 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
5578 {
5579 status=MagickFalse;
5580 continue;
5581 }
5582 indexes=GetCacheViewVirtualIndexQueue(image_view);
5583 unsharp_indexes=GetCacheViewAuthenticIndexQueue(unsharp_view);
cristyddd82202009-11-03 20:14:50 +00005584 pixel=bias;
cristybb503372010-05-27 20:51:26 +00005585 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00005586 {
5587 if ((channel & RedChannel) != 0)
5588 {
5589 pixel.red=p->red-(MagickRealType) q->red;
5590 if (fabs(2.0*pixel.red) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005591 pixel.red=(MagickRealType) GetRedPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005592 else
5593 pixel.red=(MagickRealType) p->red+(pixel.red*amount);
cristyce70c172010-01-07 17:15:30 +00005594 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00005595 }
5596 if ((channel & GreenChannel) != 0)
5597 {
5598 pixel.green=p->green-(MagickRealType) q->green;
5599 if (fabs(2.0*pixel.green) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005600 pixel.green=(MagickRealType) GetGreenPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005601 else
5602 pixel.green=(MagickRealType) p->green+(pixel.green*amount);
cristyce70c172010-01-07 17:15:30 +00005603 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00005604 }
5605 if ((channel & BlueChannel) != 0)
5606 {
5607 pixel.blue=p->blue-(MagickRealType) q->blue;
5608 if (fabs(2.0*pixel.blue) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005609 pixel.blue=(MagickRealType) GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005610 else
5611 pixel.blue=(MagickRealType) p->blue+(pixel.blue*amount);
cristyce70c172010-01-07 17:15:30 +00005612 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00005613 }
5614 if ((channel & OpacityChannel) != 0)
5615 {
5616 pixel.opacity=p->opacity-(MagickRealType) q->opacity;
5617 if (fabs(2.0*pixel.opacity) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005618 pixel.opacity=(MagickRealType) GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005619 else
5620 pixel.opacity=p->opacity+(pixel.opacity*amount);
cristyce70c172010-01-07 17:15:30 +00005621 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00005622 }
5623 if (((channel & IndexChannel) != 0) &&
5624 (image->colorspace == CMYKColorspace))
5625 {
cristyf01182f2011-03-01 14:56:40 +00005626 pixel.index=indexes[x]-(MagickRealType) unsharp_indexes[x];
cristy3ed852e2009-09-05 21:47:34 +00005627 if (fabs(2.0*pixel.index) < quantum_threshold)
cristyb557a152011-02-22 12:14:30 +00005628 pixel.index=(MagickRealType) indexes[x];
cristy3ed852e2009-09-05 21:47:34 +00005629 else
cristyb557a152011-02-22 12:14:30 +00005630 pixel.index=(MagickRealType) indexes[x]+(pixel.index*amount);
cristyce70c172010-01-07 17:15:30 +00005631 unsharp_indexes[x]=ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00005632 }
5633 p++;
5634 q++;
5635 }
5636 if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
5637 status=MagickFalse;
5638 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5639 {
5640 MagickBooleanType
5641 proceed;
5642
cristyb5d5f722009-11-04 03:03:49 +00005643#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00005644 #pragma omp critical (MagickCore_UnsharpMaskImageChannel)
5645#endif
5646 proceed=SetImageProgress(image,SharpenImageTag,progress++,image->rows);
5647 if (proceed == MagickFalse)
5648 status=MagickFalse;
5649 }
5650 }
5651 unsharp_image->type=image->type;
5652 unsharp_view=DestroyCacheView(unsharp_view);
5653 image_view=DestroyCacheView(image_view);
5654 if (status == MagickFalse)
5655 unsharp_image=DestroyImage(unsharp_image);
5656 return(unsharp_image);
5657}