blob: 321efaa2bd3927c0584722f5a4f4d9b49845e9b3 [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)
cristyc5071682011-04-22 02:06:27 +0000362 SetRedPixelComponent(q,ClampToQuantum(gamma*
363 GetRedPixelComponent(&pixel)));
cristy3ed852e2009-09-05 21:47:34 +0000364 if ((channel & GreenChannel) != 0)
cristyc5071682011-04-22 02:06:27 +0000365 SetGreenPixelComponent(q,ClampToQuantum(gamma*
366 GetGreenPixelComponent(&pixel)));
cristy3ed852e2009-09-05 21:47:34 +0000367 if ((channel & BlueChannel) != 0)
cristyc5071682011-04-22 02:06:27 +0000368 SetBluePixelComponent(q,ClampToQuantum(gamma*
369 GetBluePixelComponent(&pixel)));
cristy3ed852e2009-09-05 21:47:34 +0000370 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000371 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000372 if (((channel & IndexChannel) != 0) &&
373 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +0000374 blur_indexes[x]=ClampToQuantum(gamma*GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000375 q++;
376 r++;
377 }
378 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
379 status=MagickFalse;
380 if (image->progress_monitor != (MagickProgressMonitor) NULL)
381 {
382 MagickBooleanType
383 proceed;
384
cristyb5d5f722009-11-04 03:03:49 +0000385#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000386 #pragma omp critical (MagickCore_AdaptiveBlurImageChannel)
387#endif
388 proceed=SetImageProgress(image,AdaptiveBlurImageTag,progress++,
389 image->rows);
390 if (proceed == MagickFalse)
391 status=MagickFalse;
392 }
393 }
394 blur_image->type=image->type;
395 blur_view=DestroyCacheView(blur_view);
396 edge_view=DestroyCacheView(edge_view);
397 image_view=DestroyCacheView(image_view);
398 edge_image=DestroyImage(edge_image);
cristybb503372010-05-27 20:51:26 +0000399 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000400 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
401 kernel=(double **) RelinquishMagickMemory(kernel);
402 if (status == MagickFalse)
403 blur_image=DestroyImage(blur_image);
404 return(blur_image);
405}
406
407/*
408%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
409% %
410% %
411% %
412% A d a p t i v e S h a r p e n I m a g e %
413% %
414% %
415% %
416%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
417%
418% AdaptiveSharpenImage() adaptively sharpens the image by sharpening more
419% intensely near image edges and less intensely far from edges. We sharpen the
420% image with a Gaussian operator of the given radius and standard deviation
421% (sigma). For reasonable results, radius should be larger than sigma. Use a
422% radius of 0 and AdaptiveSharpenImage() selects a suitable radius for you.
423%
424% The format of the AdaptiveSharpenImage method is:
425%
426% Image *AdaptiveSharpenImage(const Image *image,const double radius,
427% const double sigma,ExceptionInfo *exception)
428% Image *AdaptiveSharpenImageChannel(const Image *image,
429% const ChannelType channel,double radius,const double sigma,
430% ExceptionInfo *exception)
431%
432% A description of each parameter follows:
433%
434% o image: the image.
435%
436% o channel: the channel type.
437%
438% o radius: the radius of the Gaussian, in pixels, not counting the center
439% pixel.
440%
441% o sigma: the standard deviation of the Laplacian, in pixels.
442%
443% o exception: return any errors or warnings in this structure.
444%
445*/
446
447MagickExport Image *AdaptiveSharpenImage(const Image *image,const double radius,
448 const double sigma,ExceptionInfo *exception)
449{
450 Image
451 *sharp_image;
452
453 sharp_image=AdaptiveSharpenImageChannel(image,DefaultChannels,radius,sigma,
454 exception);
455 return(sharp_image);
456}
457
458MagickExport Image *AdaptiveSharpenImageChannel(const Image *image,
459 const ChannelType channel,const double radius,const double sigma,
460 ExceptionInfo *exception)
461{
462#define AdaptiveSharpenImageTag "Convolve/Image"
463#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
464
cristyc4c8d132010-01-07 01:58:38 +0000465 CacheView
466 *sharp_view,
467 *edge_view,
468 *image_view;
469
cristy3ed852e2009-09-05 21:47:34 +0000470 double
cristy47e00502009-12-17 19:19:57 +0000471 **kernel,
472 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000473
474 Image
475 *sharp_image,
476 *edge_image,
477 *gaussian_image;
478
cristy3ed852e2009-09-05 21:47:34 +0000479 MagickBooleanType
480 status;
481
cristybb503372010-05-27 20:51:26 +0000482 MagickOffsetType
483 progress;
484
cristy3ed852e2009-09-05 21:47:34 +0000485 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +0000486 bias;
cristy3ed852e2009-09-05 21:47:34 +0000487
cristybb503372010-05-27 20:51:26 +0000488 register ssize_t
cristy47e00502009-12-17 19:19:57 +0000489 i;
cristy3ed852e2009-09-05 21:47:34 +0000490
cristybb503372010-05-27 20:51:26 +0000491 size_t
cristy3ed852e2009-09-05 21:47:34 +0000492 width;
493
cristybb503372010-05-27 20:51:26 +0000494 ssize_t
495 j,
496 k,
497 u,
498 v,
499 y;
500
cristy3ed852e2009-09-05 21:47:34 +0000501 assert(image != (const Image *) NULL);
502 assert(image->signature == MagickSignature);
503 if (image->debug != MagickFalse)
504 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
505 assert(exception != (ExceptionInfo *) NULL);
506 assert(exception->signature == MagickSignature);
507 sharp_image=CloneImage(image,0,0,MagickTrue,exception);
508 if (sharp_image == (Image *) NULL)
509 return((Image *) NULL);
510 if (fabs(sigma) <= MagickEpsilon)
511 return(sharp_image);
512 if (SetImageStorageClass(sharp_image,DirectClass) == MagickFalse)
513 {
514 InheritException(exception,&sharp_image->exception);
515 sharp_image=DestroyImage(sharp_image);
516 return((Image *) NULL);
517 }
518 /*
519 Edge detect the image brighness channel, level, sharp, and level again.
520 */
521 edge_image=EdgeImage(image,radius,exception);
522 if (edge_image == (Image *) NULL)
523 {
524 sharp_image=DestroyImage(sharp_image);
525 return((Image *) NULL);
526 }
527 (void) LevelImage(edge_image,"20%,95%");
528 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,exception);
529 if (gaussian_image != (Image *) NULL)
530 {
531 edge_image=DestroyImage(edge_image);
532 edge_image=gaussian_image;
533 }
534 (void) LevelImage(edge_image,"10%,95%");
535 /*
536 Create a set of kernels from maximum (radius,sigma) to minimum.
537 */
538 width=GetOptimalKernelWidth2D(radius,sigma);
539 kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
540 if (kernel == (double **) NULL)
541 {
542 edge_image=DestroyImage(edge_image);
543 sharp_image=DestroyImage(sharp_image);
544 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
545 }
546 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
cristybb503372010-05-27 20:51:26 +0000547 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000548 {
549 kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)*
550 sizeof(**kernel));
551 if (kernel[i] == (double *) NULL)
552 break;
cristy47e00502009-12-17 19:19:57 +0000553 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000554 j=(ssize_t) (width-i)/2;
cristy47e00502009-12-17 19:19:57 +0000555 k=0;
556 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000557 {
cristy47e00502009-12-17 19:19:57 +0000558 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000559 {
cristy4205a3c2010-09-12 20:19:59 +0000560 kernel[i][k]=(double) (-exp(-((double) u*u+v*v)/(2.0*MagickSigma*
561 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +0000562 normalize+=kernel[i][k];
563 k++;
cristy3ed852e2009-09-05 21:47:34 +0000564 }
565 }
cristy3ed852e2009-09-05 21:47:34 +0000566 if (fabs(normalize) <= MagickEpsilon)
567 normalize=1.0;
568 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000569 for (k=0; k < (j*j); k++)
570 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000571 }
cristybb503372010-05-27 20:51:26 +0000572 if (i < (ssize_t) width)
cristy3ed852e2009-09-05 21:47:34 +0000573 {
574 for (i-=2; i >= 0; i-=2)
575 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
576 kernel=(double **) RelinquishMagickMemory(kernel);
577 edge_image=DestroyImage(edge_image);
578 sharp_image=DestroyImage(sharp_image);
579 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
580 }
581 /*
582 Adaptively sharpen image.
583 */
584 status=MagickTrue;
585 progress=0;
cristyddd82202009-11-03 20:14:50 +0000586 GetMagickPixelPacket(image,&bias);
587 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000588 image_view=AcquireCacheView(image);
589 edge_view=AcquireCacheView(edge_image);
590 sharp_view=AcquireCacheView(sharp_image);
cristyb5d5f722009-11-04 03:03:49 +0000591#if defined(MAGICKCORE_OPENMP_SUPPORT)
592 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000593#endif
cristybb503372010-05-27 20:51:26 +0000594 for (y=0; y < (ssize_t) sharp_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000595 {
596 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000597 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000598
599 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000600 *restrict p,
601 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000602
603 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000604 *restrict sharp_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000605
cristy3ed852e2009-09-05 21:47:34 +0000606 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000607 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000608
cristy117ff172010-08-15 21:35:32 +0000609 register ssize_t
610 x;
611
cristy3ed852e2009-09-05 21:47:34 +0000612 if (status == MagickFalse)
613 continue;
614 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
615 q=QueueCacheViewAuthenticPixels(sharp_view,0,y,sharp_image->columns,1,
616 exception);
617 if ((r == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
618 {
619 status=MagickFalse;
620 continue;
621 }
622 sharp_indexes=GetCacheViewAuthenticIndexQueue(sharp_view);
cristybb503372010-05-27 20:51:26 +0000623 for (x=0; x < (ssize_t) sharp_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000624 {
625 MagickPixelPacket
626 pixel;
627
628 MagickRealType
629 alpha,
630 gamma;
631
632 register const double
cristyc47d1f82009-11-26 01:44:43 +0000633 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000634
cristybb503372010-05-27 20:51:26 +0000635 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000636 i,
637 u,
638 v;
639
640 gamma=0.0;
cristybb503372010-05-27 20:51:26 +0000641 i=(ssize_t) ceil((double) width*(QuantumRange-QuantumScale*
cristy1f9ce9f2010-04-28 11:55:12 +0000642 PixelIntensity(r))-0.5);
cristy3ed852e2009-09-05 21:47:34 +0000643 if (i < 0)
644 i=0;
645 else
cristybb503372010-05-27 20:51:26 +0000646 if (i > (ssize_t) width)
647 i=(ssize_t) width;
cristy3ed852e2009-09-05 21:47:34 +0000648 if ((i & 0x01) != 0)
649 i--;
cristy117ff172010-08-15 21:35:32 +0000650 p=GetCacheViewVirtualPixels(image_view,x-((ssize_t) (width-i)/2L),y-
651 (ssize_t) ((width-i)/2L),width-i,width-i,exception);
cristy3ed852e2009-09-05 21:47:34 +0000652 if (p == (const PixelPacket *) NULL)
653 break;
654 indexes=GetCacheViewVirtualIndexQueue(image_view);
655 k=kernel[i];
cristyddd82202009-11-03 20:14:50 +0000656 pixel=bias;
cristybb503372010-05-27 20:51:26 +0000657 for (v=0; v < (ssize_t) (width-i); v++)
cristy3ed852e2009-09-05 21:47:34 +0000658 {
cristybb503372010-05-27 20:51:26 +0000659 for (u=0; u < (ssize_t) (width-i); u++)
cristy3ed852e2009-09-05 21:47:34 +0000660 {
661 alpha=1.0;
662 if (((channel & OpacityChannel) != 0) &&
663 (image->matte != MagickFalse))
cristy46f08202010-01-10 04:04:21 +0000664 alpha=(MagickRealType) (QuantumScale*GetAlphaPixelComponent(p));
cristy3ed852e2009-09-05 21:47:34 +0000665 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000666 pixel.red+=(*k)*alpha*GetRedPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000667 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000668 pixel.green+=(*k)*alpha*GetGreenPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000669 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000670 pixel.blue+=(*k)*alpha*GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000671 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000672 pixel.opacity+=(*k)*GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000673 if (((channel & IndexChannel) != 0) &&
674 (image->colorspace == CMYKColorspace))
675 pixel.index+=(*k)*alpha*indexes[x+(width-i)*v+u];
676 gamma+=(*k)*alpha;
677 k++;
678 p++;
679 }
680 }
681 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
682 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000683 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000684 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000685 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000686 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000687 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000688 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000689 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000690 if (((channel & IndexChannel) != 0) &&
691 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +0000692 sharp_indexes[x]=ClampToQuantum(gamma*GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000693 q++;
694 r++;
695 }
696 if (SyncCacheViewAuthenticPixels(sharp_view,exception) == MagickFalse)
697 status=MagickFalse;
698 if (image->progress_monitor != (MagickProgressMonitor) NULL)
699 {
700 MagickBooleanType
701 proceed;
702
cristyb5d5f722009-11-04 03:03:49 +0000703#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000704 #pragma omp critical (MagickCore_AdaptiveSharpenImageChannel)
705#endif
706 proceed=SetImageProgress(image,AdaptiveSharpenImageTag,progress++,
707 image->rows);
708 if (proceed == MagickFalse)
709 status=MagickFalse;
710 }
711 }
712 sharp_image->type=image->type;
713 sharp_view=DestroyCacheView(sharp_view);
714 edge_view=DestroyCacheView(edge_view);
715 image_view=DestroyCacheView(image_view);
716 edge_image=DestroyImage(edge_image);
cristybb503372010-05-27 20:51:26 +0000717 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000718 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
719 kernel=(double **) RelinquishMagickMemory(kernel);
720 if (status == MagickFalse)
721 sharp_image=DestroyImage(sharp_image);
722 return(sharp_image);
723}
724
725/*
726%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
727% %
728% %
729% %
730% B l u r I m a g e %
731% %
732% %
733% %
734%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
735%
736% BlurImage() blurs an image. We convolve the image with a Gaussian operator
737% of the given radius and standard deviation (sigma). For reasonable results,
738% the radius should be larger than sigma. Use a radius of 0 and BlurImage()
739% selects a suitable radius for you.
740%
741% BlurImage() differs from GaussianBlurImage() in that it uses a separable
742% kernel which is faster but mathematically equivalent to the non-separable
743% kernel.
744%
745% The format of the BlurImage method is:
746%
747% Image *BlurImage(const Image *image,const double radius,
748% const double sigma,ExceptionInfo *exception)
749% Image *BlurImageChannel(const Image *image,const ChannelType channel,
750% const double radius,const double sigma,ExceptionInfo *exception)
751%
752% A description of each parameter follows:
753%
754% o image: the image.
755%
756% o channel: the channel type.
757%
758% o radius: the radius of the Gaussian, in pixels, not counting the center
759% pixel.
760%
761% o sigma: the standard deviation of the Gaussian, in pixels.
762%
763% o exception: return any errors or warnings in this structure.
764%
765*/
766
767MagickExport Image *BlurImage(const Image *image,const double radius,
768 const double sigma,ExceptionInfo *exception)
769{
770 Image
771 *blur_image;
772
773 blur_image=BlurImageChannel(image,DefaultChannels,radius,sigma,exception);
774 return(blur_image);
775}
776
cristybb503372010-05-27 20:51:26 +0000777static double *GetBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +0000778{
cristy3ed852e2009-09-05 21:47:34 +0000779 double
cristy47e00502009-12-17 19:19:57 +0000780 *kernel,
781 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000782
cristy117ff172010-08-15 21:35:32 +0000783 register ssize_t
784 i;
785
cristybb503372010-05-27 20:51:26 +0000786 ssize_t
cristy47e00502009-12-17 19:19:57 +0000787 j,
788 k;
cristy3ed852e2009-09-05 21:47:34 +0000789
cristy3ed852e2009-09-05 21:47:34 +0000790 /*
791 Generate a 1-D convolution kernel.
792 */
793 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
794 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
795 if (kernel == (double *) NULL)
796 return(0);
cristy3ed852e2009-09-05 21:47:34 +0000797 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000798 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +0000799 i=0;
800 for (k=(-j); k <= j; k++)
801 {
cristy4205a3c2010-09-12 20:19:59 +0000802 kernel[i]=(double) (exp(-((double) k*k)/(2.0*MagickSigma*MagickSigma))/
803 (MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +0000804 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +0000805 i++;
806 }
cristybb503372010-05-27 20:51:26 +0000807 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000808 kernel[i]/=normalize;
809 return(kernel);
810}
811
812MagickExport Image *BlurImageChannel(const Image *image,
813 const ChannelType channel,const double radius,const double sigma,
814 ExceptionInfo *exception)
815{
816#define BlurImageTag "Blur/Image"
817
cristyc4c8d132010-01-07 01:58:38 +0000818 CacheView
819 *blur_view,
820 *image_view;
821
cristy3ed852e2009-09-05 21:47:34 +0000822 double
823 *kernel;
824
825 Image
826 *blur_image;
827
cristy3ed852e2009-09-05 21:47:34 +0000828 MagickBooleanType
829 status;
830
cristybb503372010-05-27 20:51:26 +0000831 MagickOffsetType
832 progress;
833
cristy3ed852e2009-09-05 21:47:34 +0000834 MagickPixelPacket
cristy3ed852e2009-09-05 21:47:34 +0000835 bias;
836
cristybb503372010-05-27 20:51:26 +0000837 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000838 i;
839
cristybb503372010-05-27 20:51:26 +0000840 size_t
cristy3ed852e2009-09-05 21:47:34 +0000841 width;
842
cristybb503372010-05-27 20:51:26 +0000843 ssize_t
844 x,
845 y;
846
cristy3ed852e2009-09-05 21:47:34 +0000847 /*
848 Initialize blur image attributes.
849 */
850 assert(image != (Image *) NULL);
851 assert(image->signature == MagickSignature);
852 if (image->debug != MagickFalse)
853 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
854 assert(exception != (ExceptionInfo *) NULL);
855 assert(exception->signature == MagickSignature);
856 blur_image=CloneImage(image,0,0,MagickTrue,exception);
857 if (blur_image == (Image *) NULL)
858 return((Image *) NULL);
859 if (fabs(sigma) <= MagickEpsilon)
860 return(blur_image);
861 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
862 {
863 InheritException(exception,&blur_image->exception);
864 blur_image=DestroyImage(blur_image);
865 return((Image *) NULL);
866 }
867 width=GetOptimalKernelWidth1D(radius,sigma);
868 kernel=GetBlurKernel(width,sigma);
869 if (kernel == (double *) NULL)
870 {
871 blur_image=DestroyImage(blur_image);
872 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
873 }
874 if (image->debug != MagickFalse)
875 {
876 char
877 format[MaxTextExtent],
878 *message;
879
880 register const double
881 *k;
882
883 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +0000884 " BlurImage with %.20g kernel:",(double) width);
cristy3ed852e2009-09-05 21:47:34 +0000885 message=AcquireString("");
886 k=kernel;
cristybb503372010-05-27 20:51:26 +0000887 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000888 {
889 *message='\0';
cristye8c25f92010-06-03 00:53:06 +0000890 (void) FormatMagickString(format,MaxTextExtent,"%.20g: ",(double) i);
cristy3ed852e2009-09-05 21:47:34 +0000891 (void) ConcatenateString(&message,format);
cristye7f51092010-01-17 00:39:37 +0000892 (void) FormatMagickString(format,MaxTextExtent,"%g ",*k++);
cristy3ed852e2009-09-05 21:47:34 +0000893 (void) ConcatenateString(&message,format);
894 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
895 }
896 message=DestroyString(message);
897 }
898 /*
899 Blur rows.
900 */
901 status=MagickTrue;
902 progress=0;
cristyddd82202009-11-03 20:14:50 +0000903 GetMagickPixelPacket(image,&bias);
904 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000905 image_view=AcquireCacheView(image);
906 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000907#if defined(MAGICKCORE_OPENMP_SUPPORT)
908 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000909#endif
cristybb503372010-05-27 20:51:26 +0000910 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000911 {
912 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000913 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000914
915 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000916 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000917
918 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000919 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000920
cristy3ed852e2009-09-05 21:47:34 +0000921 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000922 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000923
cristy117ff172010-08-15 21:35:32 +0000924 register ssize_t
925 x;
926
cristy3ed852e2009-09-05 21:47:34 +0000927 if (status == MagickFalse)
928 continue;
cristy117ff172010-08-15 21:35:32 +0000929 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y,
930 image->columns+width,1,exception);
cristy3ed852e2009-09-05 21:47:34 +0000931 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
932 exception);
933 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
934 {
935 status=MagickFalse;
936 continue;
937 }
938 indexes=GetCacheViewVirtualIndexQueue(image_view);
939 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
cristybb503372010-05-27 20:51:26 +0000940 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000941 {
942 MagickPixelPacket
943 pixel;
944
945 register const double
cristyc47d1f82009-11-26 01:44:43 +0000946 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000947
948 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000949 *restrict kernel_pixels;
cristy3ed852e2009-09-05 21:47:34 +0000950
cristybb503372010-05-27 20:51:26 +0000951 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000952 i;
953
cristyddd82202009-11-03 20:14:50 +0000954 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +0000955 k=kernel;
956 kernel_pixels=p;
957 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
958 {
cristybb503372010-05-27 20:51:26 +0000959 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000960 {
961 pixel.red+=(*k)*kernel_pixels->red;
962 pixel.green+=(*k)*kernel_pixels->green;
963 pixel.blue+=(*k)*kernel_pixels->blue;
964 k++;
965 kernel_pixels++;
966 }
967 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000968 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000969 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000970 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000971 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000972 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000973 if ((channel & OpacityChannel) != 0)
974 {
975 k=kernel;
976 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +0000977 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000978 {
979 pixel.opacity+=(*k)*kernel_pixels->opacity;
980 k++;
981 kernel_pixels++;
982 }
cristyce70c172010-01-07 17:15:30 +0000983 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000984 }
985 if (((channel & IndexChannel) != 0) &&
986 (image->colorspace == CMYKColorspace))
987 {
988 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000989 *restrict kernel_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000990
991 k=kernel;
cristy9d314ff2011-03-09 01:30:28 +0000992 kernel_indexes=indexes;
cristybb503372010-05-27 20:51:26 +0000993 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000994 {
995 pixel.index+=(*k)*(*kernel_indexes);
996 k++;
997 kernel_indexes++;
998 }
cristyce70c172010-01-07 17:15:30 +0000999 blur_indexes[x]=ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00001000 }
1001 }
1002 else
1003 {
1004 MagickRealType
1005 alpha,
1006 gamma;
1007
1008 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00001009 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001010 {
cristy46f08202010-01-10 04:04:21 +00001011 alpha=(MagickRealType) (QuantumScale*
1012 GetAlphaPixelComponent(kernel_pixels));
cristy3ed852e2009-09-05 21:47:34 +00001013 pixel.red+=(*k)*alpha*kernel_pixels->red;
1014 pixel.green+=(*k)*alpha*kernel_pixels->green;
1015 pixel.blue+=(*k)*alpha*kernel_pixels->blue;
1016 gamma+=(*k)*alpha;
1017 k++;
1018 kernel_pixels++;
1019 }
1020 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1021 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001022 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001023 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001024 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001025 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001026 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001027 if ((channel & OpacityChannel) != 0)
1028 {
1029 k=kernel;
1030 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001031 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001032 {
1033 pixel.opacity+=(*k)*kernel_pixels->opacity;
1034 k++;
1035 kernel_pixels++;
1036 }
cristyce70c172010-01-07 17:15:30 +00001037 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001038 }
1039 if (((channel & IndexChannel) != 0) &&
1040 (image->colorspace == CMYKColorspace))
1041 {
1042 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001043 *restrict kernel_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001044
1045 k=kernel;
1046 kernel_pixels=p;
cristy9d314ff2011-03-09 01:30:28 +00001047 kernel_indexes=indexes;
cristybb503372010-05-27 20:51:26 +00001048 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001049 {
cristy46f08202010-01-10 04:04:21 +00001050 alpha=(MagickRealType) (QuantumScale*
1051 GetAlphaPixelComponent(kernel_pixels));
cristy3ed852e2009-09-05 21:47:34 +00001052 pixel.index+=(*k)*alpha*(*kernel_indexes);
1053 k++;
1054 kernel_pixels++;
1055 kernel_indexes++;
1056 }
cristy46f08202010-01-10 04:04:21 +00001057 blur_indexes[x]=ClampToQuantum(gamma*
1058 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001059 }
1060 }
cristy9d314ff2011-03-09 01:30:28 +00001061 indexes++;
cristy3ed852e2009-09-05 21:47:34 +00001062 p++;
1063 q++;
1064 }
1065 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1066 status=MagickFalse;
1067 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1068 {
1069 MagickBooleanType
1070 proceed;
1071
cristyb5d5f722009-11-04 03:03:49 +00001072#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001073 #pragma omp critical (MagickCore_BlurImageChannel)
1074#endif
1075 proceed=SetImageProgress(image,BlurImageTag,progress++,blur_image->rows+
1076 blur_image->columns);
1077 if (proceed == MagickFalse)
1078 status=MagickFalse;
1079 }
1080 }
1081 blur_view=DestroyCacheView(blur_view);
1082 image_view=DestroyCacheView(image_view);
1083 /*
1084 Blur columns.
1085 */
1086 image_view=AcquireCacheView(blur_image);
1087 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00001088#if defined(MAGICKCORE_OPENMP_SUPPORT)
1089 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001090#endif
cristybb503372010-05-27 20:51:26 +00001091 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001092 {
1093 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001094 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001095
1096 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001097 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001098
1099 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001100 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001101
cristy3ed852e2009-09-05 21:47:34 +00001102 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001103 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001104
cristy117ff172010-08-15 21:35:32 +00001105 register ssize_t
1106 y;
1107
cristy3ed852e2009-09-05 21:47:34 +00001108 if (status == MagickFalse)
1109 continue;
cristy117ff172010-08-15 21:35:32 +00001110 p=GetCacheViewVirtualPixels(image_view,x,-((ssize_t) width/2L),1,
1111 image->rows+width,exception);
cristy3ed852e2009-09-05 21:47:34 +00001112 q=GetCacheViewAuthenticPixels(blur_view,x,0,1,blur_image->rows,exception);
1113 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1114 {
1115 status=MagickFalse;
1116 continue;
1117 }
1118 indexes=GetCacheViewVirtualIndexQueue(image_view);
1119 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
cristybb503372010-05-27 20:51:26 +00001120 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001121 {
1122 MagickPixelPacket
1123 pixel;
1124
1125 register const double
cristyc47d1f82009-11-26 01:44:43 +00001126 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00001127
1128 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001129 *restrict kernel_pixels;
cristy3ed852e2009-09-05 21:47:34 +00001130
cristybb503372010-05-27 20:51:26 +00001131 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001132 i;
1133
cristyddd82202009-11-03 20:14:50 +00001134 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00001135 k=kernel;
1136 kernel_pixels=p;
1137 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
1138 {
cristybb503372010-05-27 20:51:26 +00001139 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001140 {
1141 pixel.red+=(*k)*kernel_pixels->red;
1142 pixel.green+=(*k)*kernel_pixels->green;
1143 pixel.blue+=(*k)*kernel_pixels->blue;
1144 k++;
1145 kernel_pixels++;
1146 }
1147 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001148 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001149 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001150 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001151 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001152 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001153 if ((channel & OpacityChannel) != 0)
1154 {
1155 k=kernel;
1156 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001157 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001158 {
1159 pixel.opacity+=(*k)*kernel_pixels->opacity;
1160 k++;
1161 kernel_pixels++;
1162 }
cristyce70c172010-01-07 17:15:30 +00001163 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001164 }
1165 if (((channel & IndexChannel) != 0) &&
1166 (image->colorspace == CMYKColorspace))
1167 {
1168 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001169 *restrict kernel_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001170
1171 k=kernel;
cristy9d314ff2011-03-09 01:30:28 +00001172 kernel_indexes=indexes;
cristybb503372010-05-27 20:51:26 +00001173 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001174 {
1175 pixel.index+=(*k)*(*kernel_indexes);
1176 k++;
1177 kernel_indexes++;
1178 }
cristyce70c172010-01-07 17:15:30 +00001179 blur_indexes[y]=ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00001180 }
1181 }
1182 else
1183 {
1184 MagickRealType
1185 alpha,
1186 gamma;
1187
1188 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00001189 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001190 {
cristy46f08202010-01-10 04:04:21 +00001191 alpha=(MagickRealType) (QuantumScale*
1192 GetAlphaPixelComponent(kernel_pixels));
cristy3ed852e2009-09-05 21:47:34 +00001193 pixel.red+=(*k)*alpha*kernel_pixels->red;
1194 pixel.green+=(*k)*alpha*kernel_pixels->green;
1195 pixel.blue+=(*k)*alpha*kernel_pixels->blue;
1196 gamma+=(*k)*alpha;
1197 k++;
1198 kernel_pixels++;
1199 }
1200 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1201 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001202 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001203 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001204 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001205 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001206 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001207 if ((channel & OpacityChannel) != 0)
1208 {
1209 k=kernel;
1210 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001211 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001212 {
1213 pixel.opacity+=(*k)*kernel_pixels->opacity;
1214 k++;
1215 kernel_pixels++;
1216 }
cristyce70c172010-01-07 17:15:30 +00001217 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001218 }
1219 if (((channel & IndexChannel) != 0) &&
1220 (image->colorspace == CMYKColorspace))
1221 {
1222 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001223 *restrict kernel_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001224
1225 k=kernel;
1226 kernel_pixels=p;
cristy9d314ff2011-03-09 01:30:28 +00001227 kernel_indexes=indexes;
cristybb503372010-05-27 20:51:26 +00001228 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001229 {
cristy46f08202010-01-10 04:04:21 +00001230 alpha=(MagickRealType) (QuantumScale*
1231 GetAlphaPixelComponent(kernel_pixels));
cristy3ed852e2009-09-05 21:47:34 +00001232 pixel.index+=(*k)*alpha*(*kernel_indexes);
1233 k++;
1234 kernel_pixels++;
1235 kernel_indexes++;
1236 }
cristy46f08202010-01-10 04:04:21 +00001237 blur_indexes[y]=ClampToQuantum(gamma*
1238 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001239 }
1240 }
cristy9d314ff2011-03-09 01:30:28 +00001241 indexes++;
cristy3ed852e2009-09-05 21:47:34 +00001242 p++;
1243 q++;
1244 }
1245 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1246 status=MagickFalse;
1247 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1248 {
1249 MagickBooleanType
1250 proceed;
1251
cristyb5d5f722009-11-04 03:03:49 +00001252#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001253 #pragma omp critical (MagickCore_BlurImageChannel)
1254#endif
1255 proceed=SetImageProgress(image,BlurImageTag,progress++,blur_image->rows+
1256 blur_image->columns);
1257 if (proceed == MagickFalse)
1258 status=MagickFalse;
1259 }
1260 }
1261 blur_view=DestroyCacheView(blur_view);
1262 image_view=DestroyCacheView(image_view);
1263 kernel=(double *) RelinquishMagickMemory(kernel);
1264 if (status == MagickFalse)
1265 blur_image=DestroyImage(blur_image);
1266 blur_image->type=image->type;
1267 return(blur_image);
1268}
1269
1270/*
1271%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1272% %
1273% %
1274% %
cristyfccdab92009-11-30 16:43:57 +00001275% C o n v o l v e I m a g e %
1276% %
1277% %
1278% %
1279%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1280%
1281% ConvolveImage() applies a custom convolution kernel to the image.
1282%
1283% The format of the ConvolveImage method is:
1284%
cristybb503372010-05-27 20:51:26 +00001285% Image *ConvolveImage(const Image *image,const size_t order,
cristyfccdab92009-11-30 16:43:57 +00001286% const double *kernel,ExceptionInfo *exception)
1287% Image *ConvolveImageChannel(const Image *image,const ChannelType channel,
cristy117ff172010-08-15 21:35:32 +00001288% const size_t order,const double *kernel,ExceptionInfo *exception)
cristyfccdab92009-11-30 16:43:57 +00001289%
1290% A description of each parameter follows:
1291%
1292% o image: the image.
1293%
1294% o channel: the channel type.
1295%
1296% o order: the number of columns and rows in the filter kernel.
1297%
1298% o kernel: An array of double representing the convolution kernel.
1299%
1300% o exception: return any errors or warnings in this structure.
1301%
1302*/
1303
cristybb503372010-05-27 20:51:26 +00001304MagickExport Image *ConvolveImage(const Image *image,const size_t order,
cristyfccdab92009-11-30 16:43:57 +00001305 const double *kernel,ExceptionInfo *exception)
1306{
1307 Image
1308 *convolve_image;
1309
1310 convolve_image=ConvolveImageChannel(image,DefaultChannels,order,kernel,
1311 exception);
1312 return(convolve_image);
1313}
1314
1315MagickExport Image *ConvolveImageChannel(const Image *image,
cristybb503372010-05-27 20:51:26 +00001316 const ChannelType channel,const size_t order,const double *kernel,
cristyfccdab92009-11-30 16:43:57 +00001317 ExceptionInfo *exception)
1318{
1319#define ConvolveImageTag "Convolve/Image"
1320
cristyc4c8d132010-01-07 01:58:38 +00001321 CacheView
1322 *convolve_view,
1323 *image_view;
1324
cristyfccdab92009-11-30 16:43:57 +00001325 double
1326 *normal_kernel;
1327
1328 Image
1329 *convolve_image;
1330
cristyfccdab92009-11-30 16:43:57 +00001331 MagickBooleanType
1332 status;
1333
cristybb503372010-05-27 20:51:26 +00001334 MagickOffsetType
1335 progress;
1336
cristyfccdab92009-11-30 16:43:57 +00001337 MagickPixelPacket
1338 bias;
1339
1340 MagickRealType
1341 gamma;
1342
cristybb503372010-05-27 20:51:26 +00001343 register ssize_t
cristyfccdab92009-11-30 16:43:57 +00001344 i;
1345
cristybb503372010-05-27 20:51:26 +00001346 size_t
cristyfccdab92009-11-30 16:43:57 +00001347 width;
1348
cristybb503372010-05-27 20:51:26 +00001349 ssize_t
1350 y;
1351
cristyfccdab92009-11-30 16:43:57 +00001352 /*
1353 Initialize convolve image attributes.
1354 */
1355 assert(image != (Image *) NULL);
1356 assert(image->signature == MagickSignature);
1357 if (image->debug != MagickFalse)
1358 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1359 assert(exception != (ExceptionInfo *) NULL);
1360 assert(exception->signature == MagickSignature);
1361 width=order;
1362 if ((width % 2) == 0)
1363 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
1364 convolve_image=CloneImage(image,0,0,MagickTrue,exception);
1365 if (convolve_image == (Image *) NULL)
1366 return((Image *) NULL);
1367 if (SetImageStorageClass(convolve_image,DirectClass) == MagickFalse)
1368 {
1369 InheritException(exception,&convolve_image->exception);
1370 convolve_image=DestroyImage(convolve_image);
1371 return((Image *) NULL);
1372 }
1373 if (image->debug != MagickFalse)
1374 {
1375 char
1376 format[MaxTextExtent],
1377 *message;
1378
cristy117ff172010-08-15 21:35:32 +00001379 register const double
1380 *k;
1381
cristybb503372010-05-27 20:51:26 +00001382 ssize_t
cristyfccdab92009-11-30 16:43:57 +00001383 u,
1384 v;
1385
cristyfccdab92009-11-30 16:43:57 +00001386 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00001387 " ConvolveImage with %.20gx%.20g kernel:",(double) width,(double)
1388 width);
cristyfccdab92009-11-30 16:43:57 +00001389 message=AcquireString("");
1390 k=kernel;
cristybb503372010-05-27 20:51:26 +00001391 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001392 {
1393 *message='\0';
cristye8c25f92010-06-03 00:53:06 +00001394 (void) FormatMagickString(format,MaxTextExtent,"%.20g: ",(double) v);
cristyfccdab92009-11-30 16:43:57 +00001395 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00001396 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001397 {
cristye7f51092010-01-17 00:39:37 +00001398 (void) FormatMagickString(format,MaxTextExtent,"%g ",*k++);
cristyfccdab92009-11-30 16:43:57 +00001399 (void) ConcatenateString(&message,format);
1400 }
1401 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
1402 }
1403 message=DestroyString(message);
1404 }
1405 /*
1406 Normalize kernel.
1407 */
1408 normal_kernel=(double *) AcquireQuantumMemory(width*width,
1409 sizeof(*normal_kernel));
1410 if (normal_kernel == (double *) NULL)
1411 {
1412 convolve_image=DestroyImage(convolve_image);
1413 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1414 }
1415 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00001416 for (i=0; i < (ssize_t) (width*width); i++)
cristyfccdab92009-11-30 16:43:57 +00001417 gamma+=kernel[i];
1418 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristybb503372010-05-27 20:51:26 +00001419 for (i=0; i < (ssize_t) (width*width); i++)
cristyfccdab92009-11-30 16:43:57 +00001420 normal_kernel[i]=gamma*kernel[i];
1421 /*
1422 Convolve image.
1423 */
1424 status=MagickTrue;
1425 progress=0;
1426 GetMagickPixelPacket(image,&bias);
1427 SetMagickPixelPacketBias(image,&bias);
1428 image_view=AcquireCacheView(image);
1429 convolve_view=AcquireCacheView(convolve_image);
1430#if defined(MAGICKCORE_OPENMP_SUPPORT)
1431 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1432#endif
cristybb503372010-05-27 20:51:26 +00001433 for (y=0; y < (ssize_t) image->rows; y++)
cristyfccdab92009-11-30 16:43:57 +00001434 {
1435 MagickBooleanType
1436 sync;
1437
1438 register const IndexPacket
1439 *restrict indexes;
1440
1441 register const PixelPacket
1442 *restrict p;
1443
1444 register IndexPacket
1445 *restrict convolve_indexes;
1446
cristyfccdab92009-11-30 16:43:57 +00001447 register PixelPacket
1448 *restrict q;
1449
cristy117ff172010-08-15 21:35:32 +00001450 register ssize_t
1451 x;
1452
cristyfccdab92009-11-30 16:43:57 +00001453 if (status == MagickFalse)
1454 continue;
cristyce889302010-06-30 19:16:36 +00001455 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
1456 (width/2L),image->columns+width,width,exception);
cristyfccdab92009-11-30 16:43:57 +00001457 q=GetCacheViewAuthenticPixels(convolve_view,0,y,convolve_image->columns,1,
1458 exception);
1459 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1460 {
1461 status=MagickFalse;
1462 continue;
1463 }
1464 indexes=GetCacheViewVirtualIndexQueue(image_view);
1465 convolve_indexes=GetCacheViewAuthenticIndexQueue(convolve_view);
cristybb503372010-05-27 20:51:26 +00001466 for (x=0; x < (ssize_t) image->columns; x++)
cristyfccdab92009-11-30 16:43:57 +00001467 {
cristyfccdab92009-11-30 16:43:57 +00001468 MagickPixelPacket
1469 pixel;
1470
1471 register const double
1472 *restrict k;
1473
1474 register const PixelPacket
1475 *restrict kernel_pixels;
1476
cristybb503372010-05-27 20:51:26 +00001477 register ssize_t
cristyfccdab92009-11-30 16:43:57 +00001478 u;
1479
cristy117ff172010-08-15 21:35:32 +00001480 ssize_t
1481 v;
1482
cristyfccdab92009-11-30 16:43:57 +00001483 pixel=bias;
1484 k=normal_kernel;
1485 kernel_pixels=p;
1486 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
1487 {
cristybb503372010-05-27 20:51:26 +00001488 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001489 {
cristybb503372010-05-27 20:51:26 +00001490 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001491 {
1492 pixel.red+=(*k)*kernel_pixels[u].red;
1493 pixel.green+=(*k)*kernel_pixels[u].green;
1494 pixel.blue+=(*k)*kernel_pixels[u].blue;
1495 k++;
1496 }
1497 kernel_pixels+=image->columns+width;
1498 }
1499 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001500 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001501 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001502 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001503 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001504 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001505 if ((channel & OpacityChannel) != 0)
1506 {
1507 k=normal_kernel;
1508 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001509 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001510 {
cristybb503372010-05-27 20:51:26 +00001511 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001512 {
1513 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
1514 k++;
1515 }
1516 kernel_pixels+=image->columns+width;
1517 }
cristyce70c172010-01-07 17:15:30 +00001518 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001519 }
1520 if (((channel & IndexChannel) != 0) &&
1521 (image->colorspace == CMYKColorspace))
1522 {
1523 register const IndexPacket
1524 *restrict kernel_indexes;
1525
1526 k=normal_kernel;
cristy9d314ff2011-03-09 01:30:28 +00001527 kernel_indexes=indexes;
cristybb503372010-05-27 20:51:26 +00001528 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001529 {
cristybb503372010-05-27 20:51:26 +00001530 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001531 {
1532 pixel.index+=(*k)*kernel_indexes[u];
1533 k++;
1534 }
1535 kernel_indexes+=image->columns+width;
1536 }
cristyce70c172010-01-07 17:15:30 +00001537 convolve_indexes[x]=ClampToQuantum(pixel.index);
cristyfccdab92009-11-30 16:43:57 +00001538 }
1539 }
1540 else
1541 {
1542 MagickRealType
1543 alpha,
1544 gamma;
1545
1546 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00001547 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001548 {
cristybb503372010-05-27 20:51:26 +00001549 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001550 {
1551 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
1552 kernel_pixels[u].opacity));
1553 pixel.red+=(*k)*alpha*kernel_pixels[u].red;
1554 pixel.green+=(*k)*alpha*kernel_pixels[u].green;
1555 pixel.blue+=(*k)*alpha*kernel_pixels[u].blue;
cristyfccdab92009-11-30 16:43:57 +00001556 gamma+=(*k)*alpha;
1557 k++;
1558 }
1559 kernel_pixels+=image->columns+width;
1560 }
1561 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1562 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001563 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001564 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001565 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001566 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001567 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001568 if ((channel & OpacityChannel) != 0)
1569 {
1570 k=normal_kernel;
1571 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001572 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001573 {
cristybb503372010-05-27 20:51:26 +00001574 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001575 {
1576 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
1577 k++;
1578 }
1579 kernel_pixels+=image->columns+width;
1580 }
cristyce70c172010-01-07 17:15:30 +00001581 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001582 }
1583 if (((channel & IndexChannel) != 0) &&
1584 (image->colorspace == CMYKColorspace))
1585 {
1586 register const IndexPacket
1587 *restrict kernel_indexes;
1588
1589 k=normal_kernel;
1590 kernel_pixels=p;
cristy9d314ff2011-03-09 01:30:28 +00001591 kernel_indexes=indexes;
cristybb503372010-05-27 20:51:26 +00001592 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001593 {
cristybb503372010-05-27 20:51:26 +00001594 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001595 {
1596 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
1597 kernel_pixels[u].opacity));
1598 pixel.index+=(*k)*alpha*kernel_indexes[u];
1599 k++;
1600 }
1601 kernel_pixels+=image->columns+width;
1602 kernel_indexes+=image->columns+width;
1603 }
cristy24b06da2010-01-09 23:05:56 +00001604 convolve_indexes[x]=ClampToQuantum(gamma*
1605 GetIndexPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001606 }
1607 }
cristy9d314ff2011-03-09 01:30:28 +00001608 indexes++;
cristyfccdab92009-11-30 16:43:57 +00001609 p++;
1610 q++;
1611 }
1612 sync=SyncCacheViewAuthenticPixels(convolve_view,exception);
1613 if (sync == MagickFalse)
1614 status=MagickFalse;
1615 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1616 {
1617 MagickBooleanType
1618 proceed;
1619
1620#if defined(MAGICKCORE_OPENMP_SUPPORT)
1621 #pragma omp critical (MagickCore_ConvolveImageChannel)
1622#endif
1623 proceed=SetImageProgress(image,ConvolveImageTag,progress++,image->rows);
1624 if (proceed == MagickFalse)
1625 status=MagickFalse;
1626 }
1627 }
1628 convolve_image->type=image->type;
1629 convolve_view=DestroyCacheView(convolve_view);
1630 image_view=DestroyCacheView(image_view);
1631 normal_kernel=(double *) RelinquishMagickMemory(normal_kernel);
1632 if (status == MagickFalse)
1633 convolve_image=DestroyImage(convolve_image);
1634 return(convolve_image);
1635}
1636
1637/*
1638%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1639% %
1640% %
1641% %
cristy3ed852e2009-09-05 21:47:34 +00001642% D e s p e c k l e I m a g e %
1643% %
1644% %
1645% %
1646%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1647%
1648% DespeckleImage() reduces the speckle noise in an image while perserving the
1649% edges of the original image.
1650%
1651% The format of the DespeckleImage method is:
1652%
1653% Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1654%
1655% A description of each parameter follows:
1656%
1657% o image: the image.
1658%
1659% o exception: return any errors or warnings in this structure.
1660%
1661*/
1662
cristybb503372010-05-27 20:51:26 +00001663static void Hull(const ssize_t x_offset,const ssize_t y_offset,
1664 const size_t columns,const size_t rows,Quantum *f,Quantum *g,
cristy3ed852e2009-09-05 21:47:34 +00001665 const int polarity)
1666{
cristy3ed852e2009-09-05 21:47:34 +00001667 MagickRealType
1668 v;
1669
cristy3ed852e2009-09-05 21:47:34 +00001670 register Quantum
1671 *p,
1672 *q,
1673 *r,
1674 *s;
1675
cristy117ff172010-08-15 21:35:32 +00001676 register ssize_t
1677 x;
1678
1679 ssize_t
1680 y;
1681
cristy3ed852e2009-09-05 21:47:34 +00001682 assert(f != (Quantum *) NULL);
1683 assert(g != (Quantum *) NULL);
1684 p=f+(columns+2);
1685 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001686 r=p+(y_offset*((ssize_t) columns+2)+x_offset);
1687 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001688 {
1689 p++;
1690 q++;
1691 r++;
1692 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001693 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001694 {
1695 v=(MagickRealType) (*p);
1696 if ((MagickRealType) *r >= (v+(MagickRealType) ScaleCharToQuantum(2)))
1697 v+=ScaleCharToQuantum(1);
1698 *q=(Quantum) v;
1699 p++;
1700 q++;
1701 r++;
1702 }
1703 else
cristybb503372010-05-27 20:51:26 +00001704 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001705 {
1706 v=(MagickRealType) (*p);
1707 if ((MagickRealType) *r <= (v-(MagickRealType) ScaleCharToQuantum(2)))
cristybb503372010-05-27 20:51:26 +00001708 v-=(ssize_t) ScaleCharToQuantum(1);
cristy3ed852e2009-09-05 21:47:34 +00001709 *q=(Quantum) v;
1710 p++;
1711 q++;
1712 r++;
1713 }
1714 p++;
1715 q++;
1716 r++;
1717 }
1718 p=f+(columns+2);
1719 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001720 r=q+(y_offset*((ssize_t) columns+2)+x_offset);
1721 s=q-(y_offset*((ssize_t) columns+2)+x_offset);
1722 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001723 {
1724 p++;
1725 q++;
1726 r++;
1727 s++;
1728 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001729 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001730 {
1731 v=(MagickRealType) (*q);
1732 if (((MagickRealType) *s >=
1733 (v+(MagickRealType) ScaleCharToQuantum(2))) &&
1734 ((MagickRealType) *r > v))
1735 v+=ScaleCharToQuantum(1);
1736 *p=(Quantum) v;
1737 p++;
1738 q++;
1739 r++;
1740 s++;
1741 }
1742 else
cristybb503372010-05-27 20:51:26 +00001743 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001744 {
1745 v=(MagickRealType) (*q);
1746 if (((MagickRealType) *s <=
1747 (v-(MagickRealType) ScaleCharToQuantum(2))) &&
1748 ((MagickRealType) *r < v))
1749 v-=(MagickRealType) ScaleCharToQuantum(1);
1750 *p=(Quantum) v;
1751 p++;
1752 q++;
1753 r++;
1754 s++;
1755 }
1756 p++;
1757 q++;
1758 r++;
1759 s++;
1760 }
1761}
1762
1763MagickExport Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1764{
1765#define DespeckleImageTag "Despeckle/Image"
1766
cristy2407fc22009-09-11 00:55:25 +00001767 CacheView
1768 *despeckle_view,
1769 *image_view;
1770
cristy3ed852e2009-09-05 21:47:34 +00001771 Image
1772 *despeckle_image;
1773
cristy3ed852e2009-09-05 21:47:34 +00001774 MagickBooleanType
1775 status;
1776
cristya58c3172011-02-19 19:23:11 +00001777 register ssize_t
1778 i;
1779
cristy3ed852e2009-09-05 21:47:34 +00001780 Quantum
cristy65b9f392011-02-22 14:22:54 +00001781 *restrict buffers,
1782 *restrict pixels;
cristy3ed852e2009-09-05 21:47:34 +00001783
1784 size_t
cristya58c3172011-02-19 19:23:11 +00001785 length,
1786 number_channels;
cristy117ff172010-08-15 21:35:32 +00001787
cristybb503372010-05-27 20:51:26 +00001788 static const ssize_t
cristy691a29e2009-09-11 00:44:10 +00001789 X[4] = {0, 1, 1,-1},
1790 Y[4] = {1, 0, 1, 1};
cristy3ed852e2009-09-05 21:47:34 +00001791
cristy3ed852e2009-09-05 21:47:34 +00001792 /*
1793 Allocate despeckled image.
1794 */
1795 assert(image != (const Image *) NULL);
1796 assert(image->signature == MagickSignature);
1797 if (image->debug != MagickFalse)
1798 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1799 assert(exception != (ExceptionInfo *) NULL);
1800 assert(exception->signature == MagickSignature);
1801 despeckle_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1802 exception);
1803 if (despeckle_image == (Image *) NULL)
1804 return((Image *) NULL);
1805 if (SetImageStorageClass(despeckle_image,DirectClass) == MagickFalse)
1806 {
1807 InheritException(exception,&despeckle_image->exception);
1808 despeckle_image=DestroyImage(despeckle_image);
1809 return((Image *) NULL);
1810 }
1811 /*
1812 Allocate image buffers.
1813 */
1814 length=(size_t) ((image->columns+2)*(image->rows+2));
cristy65b9f392011-02-22 14:22:54 +00001815 pixels=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1816 buffers=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1817 if ((pixels == (Quantum *) NULL) || (buffers == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001818 {
cristy65b9f392011-02-22 14:22:54 +00001819 if (buffers != (Quantum *) NULL)
1820 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1821 if (pixels != (Quantum *) NULL)
1822 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001823 despeckle_image=DestroyImage(despeckle_image);
1824 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1825 }
1826 /*
1827 Reduce speckle in the image.
1828 */
1829 status=MagickTrue;
cristy109695a2011-02-19 19:38:14 +00001830 number_channels=(size_t) (image->colorspace == CMYKColorspace ? 5 : 4);
cristy3ed852e2009-09-05 21:47:34 +00001831 image_view=AcquireCacheView(image);
1832 despeckle_view=AcquireCacheView(despeckle_image);
cristy8df3d002011-02-19 19:40:59 +00001833 for (i=0; i < (ssize_t) number_channels; i++)
cristy3ed852e2009-09-05 21:47:34 +00001834 {
cristy3ed852e2009-09-05 21:47:34 +00001835 register Quantum
1836 *buffer,
1837 *pixel;
1838
cristyc1488b52011-02-19 18:54:15 +00001839 register ssize_t
cristya58c3172011-02-19 19:23:11 +00001840 k,
cristyc1488b52011-02-19 18:54:15 +00001841 x;
1842
cristy117ff172010-08-15 21:35:32 +00001843 ssize_t
1844 j,
1845 y;
1846
cristy3ed852e2009-09-05 21:47:34 +00001847 if (status == MagickFalse)
1848 continue;
cristy65b9f392011-02-22 14:22:54 +00001849 pixel=pixels;
cristy3ed852e2009-09-05 21:47:34 +00001850 (void) ResetMagickMemory(pixel,0,length*sizeof(*pixel));
cristy65b9f392011-02-22 14:22:54 +00001851 buffer=buffers;
cristybb503372010-05-27 20:51:26 +00001852 j=(ssize_t) image->columns+2;
1853 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001854 {
cristya58c3172011-02-19 19:23:11 +00001855 register const IndexPacket
1856 *restrict indexes;
1857
cristy3ed852e2009-09-05 21:47:34 +00001858 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001859 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001860
1861 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1862 if (p == (const PixelPacket *) NULL)
1863 break;
cristya58c3172011-02-19 19:23:11 +00001864 indexes=GetCacheViewVirtualIndexQueue(image_view);
cristy3ed852e2009-09-05 21:47:34 +00001865 j++;
cristybb503372010-05-27 20:51:26 +00001866 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001867 {
cristya58c3172011-02-19 19:23:11 +00001868 switch (i)
cristy3ed852e2009-09-05 21:47:34 +00001869 {
cristyce70c172010-01-07 17:15:30 +00001870 case 0: pixel[j]=GetRedPixelComponent(p); break;
1871 case 1: pixel[j]=GetGreenPixelComponent(p); break;
1872 case 2: pixel[j]=GetBluePixelComponent(p); break;
1873 case 3: pixel[j]=GetOpacityPixelComponent(p); break;
cristy8a5d50e2011-04-17 23:43:51 +00001874 case 4: pixel[j]=GetBlackPixelComponent(indexes+x); break;
cristy3ed852e2009-09-05 21:47:34 +00001875 default: break;
1876 }
1877 p++;
1878 j++;
1879 }
1880 j++;
1881 }
cristy3ed852e2009-09-05 21:47:34 +00001882 (void) ResetMagickMemory(buffer,0,length*sizeof(*buffer));
cristya58c3172011-02-19 19:23:11 +00001883 for (k=0; k < 4; k++)
cristy3ed852e2009-09-05 21:47:34 +00001884 {
cristya58c3172011-02-19 19:23:11 +00001885 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,1);
1886 Hull(-X[k],-Y[k],image->columns,image->rows,pixel,buffer,1);
1887 Hull(-X[k],-Y[k],image->columns,image->rows,pixel,buffer,-1);
1888 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,-1);
cristy3ed852e2009-09-05 21:47:34 +00001889 }
cristybb503372010-05-27 20:51:26 +00001890 j=(ssize_t) image->columns+2;
1891 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001892 {
1893 MagickBooleanType
1894 sync;
1895
cristya58c3172011-02-19 19:23:11 +00001896 register IndexPacket
1897 *restrict indexes;
1898
cristy3ed852e2009-09-05 21:47:34 +00001899 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001900 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001901
1902 q=GetCacheViewAuthenticPixels(despeckle_view,0,y,despeckle_image->columns,
1903 1,exception);
1904 if (q == (PixelPacket *) NULL)
1905 break;
cristya58c3172011-02-19 19:23:11 +00001906 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristy3ed852e2009-09-05 21:47:34 +00001907 j++;
cristybb503372010-05-27 20:51:26 +00001908 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001909 {
cristya58c3172011-02-19 19:23:11 +00001910 switch (i)
cristy3ed852e2009-09-05 21:47:34 +00001911 {
1912 case 0: q->red=pixel[j]; break;
1913 case 1: q->green=pixel[j]; break;
1914 case 2: q->blue=pixel[j]; break;
1915 case 3: q->opacity=pixel[j]; break;
cristya58c3172011-02-19 19:23:11 +00001916 case 4: indexes[x]=pixel[j]; break;
cristy3ed852e2009-09-05 21:47:34 +00001917 default: break;
1918 }
1919 q++;
1920 j++;
1921 }
1922 sync=SyncCacheViewAuthenticPixels(despeckle_view,exception);
1923 if (sync == MagickFalse)
1924 {
1925 status=MagickFalse;
1926 break;
1927 }
1928 j++;
1929 }
1930 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1931 {
1932 MagickBooleanType
1933 proceed;
1934
cristya58c3172011-02-19 19:23:11 +00001935 proceed=SetImageProgress(image,DespeckleImageTag,(MagickOffsetType) i,
1936 number_channels);
cristy3ed852e2009-09-05 21:47:34 +00001937 if (proceed == MagickFalse)
1938 status=MagickFalse;
1939 }
1940 }
1941 despeckle_view=DestroyCacheView(despeckle_view);
1942 image_view=DestroyCacheView(image_view);
cristy65b9f392011-02-22 14:22:54 +00001943 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1944 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001945 despeckle_image->type=image->type;
1946 if (status == MagickFalse)
1947 despeckle_image=DestroyImage(despeckle_image);
1948 return(despeckle_image);
1949}
1950
1951/*
1952%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1953% %
1954% %
1955% %
1956% E d g e I m a g e %
1957% %
1958% %
1959% %
1960%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1961%
1962% EdgeImage() finds edges in an image. Radius defines the radius of the
1963% convolution filter. Use a radius of 0 and EdgeImage() selects a suitable
1964% radius for you.
1965%
1966% The format of the EdgeImage method is:
1967%
1968% Image *EdgeImage(const Image *image,const double radius,
1969% ExceptionInfo *exception)
1970%
1971% A description of each parameter follows:
1972%
1973% o image: the image.
1974%
1975% o radius: the radius of the pixel neighborhood.
1976%
1977% o exception: return any errors or warnings in this structure.
1978%
1979*/
1980MagickExport Image *EdgeImage(const Image *image,const double radius,
1981 ExceptionInfo *exception)
1982{
1983 Image
1984 *edge_image;
1985
1986 double
1987 *kernel;
1988
cristybb503372010-05-27 20:51:26 +00001989 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001990 i;
1991
cristybb503372010-05-27 20:51:26 +00001992 size_t
cristy3ed852e2009-09-05 21:47:34 +00001993 width;
1994
1995 assert(image != (const Image *) NULL);
1996 assert(image->signature == MagickSignature);
1997 if (image->debug != MagickFalse)
1998 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1999 assert(exception != (ExceptionInfo *) NULL);
2000 assert(exception->signature == MagickSignature);
2001 width=GetOptimalKernelWidth1D(radius,0.5);
2002 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2003 if (kernel == (double *) NULL)
2004 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00002005 for (i=0; i < (ssize_t) (width*width); i++)
cristy3ed852e2009-09-05 21:47:34 +00002006 kernel[i]=(-1.0);
2007 kernel[i/2]=(double) (width*width-1.0);
2008 edge_image=ConvolveImage(image,width,kernel,exception);
2009 kernel=(double *) RelinquishMagickMemory(kernel);
2010 return(edge_image);
2011}
2012
2013/*
2014%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2015% %
2016% %
2017% %
2018% E m b o s s I m a g e %
2019% %
2020% %
2021% %
2022%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2023%
2024% EmbossImage() returns a grayscale image with a three-dimensional effect.
2025% We convolve the image with a Gaussian operator of the given radius and
2026% standard deviation (sigma). For reasonable results, radius should be
2027% larger than sigma. Use a radius of 0 and Emboss() selects a suitable
2028% radius for you.
2029%
2030% The format of the EmbossImage method is:
2031%
2032% Image *EmbossImage(const Image *image,const double radius,
2033% const double sigma,ExceptionInfo *exception)
2034%
2035% A description of each parameter follows:
2036%
2037% o image: the image.
2038%
2039% o radius: the radius of the pixel neighborhood.
2040%
2041% o sigma: the standard deviation of the Gaussian, in pixels.
2042%
2043% o exception: return any errors or warnings in this structure.
2044%
2045*/
2046MagickExport Image *EmbossImage(const Image *image,const double radius,
2047 const double sigma,ExceptionInfo *exception)
2048{
2049 double
2050 *kernel;
2051
2052 Image
2053 *emboss_image;
2054
cristybb503372010-05-27 20:51:26 +00002055 register ssize_t
cristy47e00502009-12-17 19:19:57 +00002056 i;
2057
cristybb503372010-05-27 20:51:26 +00002058 size_t
cristy3ed852e2009-09-05 21:47:34 +00002059 width;
2060
cristy117ff172010-08-15 21:35:32 +00002061 ssize_t
2062 j,
2063 k,
2064 u,
2065 v;
2066
cristy3ed852e2009-09-05 21:47:34 +00002067 assert(image != (Image *) NULL);
2068 assert(image->signature == MagickSignature);
2069 if (image->debug != MagickFalse)
2070 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2071 assert(exception != (ExceptionInfo *) NULL);
2072 assert(exception->signature == MagickSignature);
2073 width=GetOptimalKernelWidth2D(radius,sigma);
2074 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2075 if (kernel == (double *) NULL)
2076 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00002077 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +00002078 k=j;
2079 i=0;
2080 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002081 {
cristy47e00502009-12-17 19:19:57 +00002082 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00002083 {
cristy4205a3c2010-09-12 20:19:59 +00002084 kernel[i]=(double) (((u < 0) || (v < 0) ? -8.0 : 8.0)*
cristy47e00502009-12-17 19:19:57 +00002085 exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
cristy4205a3c2010-09-12 20:19:59 +00002086 (2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +00002087 if (u != k)
cristy3ed852e2009-09-05 21:47:34 +00002088 kernel[i]=0.0;
2089 i++;
2090 }
cristy47e00502009-12-17 19:19:57 +00002091 k--;
cristy3ed852e2009-09-05 21:47:34 +00002092 }
2093 emboss_image=ConvolveImage(image,width,kernel,exception);
2094 if (emboss_image != (Image *) NULL)
2095 (void) EqualizeImage(emboss_image);
2096 kernel=(double *) RelinquishMagickMemory(kernel);
2097 return(emboss_image);
2098}
2099
2100/*
2101%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2102% %
2103% %
2104% %
cristy56a9e512010-01-06 18:18:55 +00002105% F i l t e r I m a g e %
2106% %
2107% %
2108% %
2109%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2110%
2111% FilterImage() applies a custom convolution kernel to the image.
2112%
2113% The format of the FilterImage method is:
2114%
cristy2be15382010-01-21 02:38:03 +00002115% Image *FilterImage(const Image *image,const KernelInfo *kernel,
cristy56a9e512010-01-06 18:18:55 +00002116% ExceptionInfo *exception)
2117% Image *FilterImageChannel(const Image *image,const ChannelType channel,
cristy2be15382010-01-21 02:38:03 +00002118% const KernelInfo *kernel,ExceptionInfo *exception)
cristy56a9e512010-01-06 18:18:55 +00002119%
2120% A description of each parameter follows:
2121%
2122% o image: the image.
2123%
2124% o channel: the channel type.
2125%
2126% o kernel: the filtering kernel.
2127%
2128% o exception: return any errors or warnings in this structure.
2129%
2130*/
2131
cristy2be15382010-01-21 02:38:03 +00002132MagickExport Image *FilterImage(const Image *image,const KernelInfo *kernel,
cristy56a9e512010-01-06 18:18:55 +00002133 ExceptionInfo *exception)
2134{
2135 Image
2136 *filter_image;
2137
2138 filter_image=FilterImageChannel(image,DefaultChannels,kernel,exception);
2139 return(filter_image);
2140}
2141
2142MagickExport Image *FilterImageChannel(const Image *image,
cristy2be15382010-01-21 02:38:03 +00002143 const ChannelType channel,const KernelInfo *kernel,ExceptionInfo *exception)
cristy56a9e512010-01-06 18:18:55 +00002144{
2145#define FilterImageTag "Filter/Image"
2146
2147 CacheView
2148 *filter_view,
2149 *image_view;
2150
cristy56a9e512010-01-06 18:18:55 +00002151 Image
2152 *filter_image;
2153
cristy56a9e512010-01-06 18:18:55 +00002154 MagickBooleanType
2155 status;
2156
cristybb503372010-05-27 20:51:26 +00002157 MagickOffsetType
2158 progress;
2159
cristy56a9e512010-01-06 18:18:55 +00002160 MagickPixelPacket
2161 bias;
2162
cristybb503372010-05-27 20:51:26 +00002163 ssize_t
2164 y;
2165
cristy56a9e512010-01-06 18:18:55 +00002166 /*
2167 Initialize filter image attributes.
2168 */
2169 assert(image != (Image *) NULL);
2170 assert(image->signature == MagickSignature);
2171 if (image->debug != MagickFalse)
2172 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2173 assert(exception != (ExceptionInfo *) NULL);
2174 assert(exception->signature == MagickSignature);
2175 if ((kernel->width % 2) == 0)
2176 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
2177 filter_image=CloneImage(image,0,0,MagickTrue,exception);
2178 if (filter_image == (Image *) NULL)
2179 return((Image *) NULL);
2180 if (SetImageStorageClass(filter_image,DirectClass) == MagickFalse)
2181 {
2182 InheritException(exception,&filter_image->exception);
2183 filter_image=DestroyImage(filter_image);
2184 return((Image *) NULL);
2185 }
2186 if (image->debug != MagickFalse)
2187 {
2188 char
2189 format[MaxTextExtent],
2190 *message;
2191
cristy117ff172010-08-15 21:35:32 +00002192 register const double
2193 *k;
2194
cristybb503372010-05-27 20:51:26 +00002195 ssize_t
cristy56a9e512010-01-06 18:18:55 +00002196 u,
2197 v;
2198
cristy56a9e512010-01-06 18:18:55 +00002199 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00002200 " FilterImage with %.20gx%.20g kernel:",(double) kernel->width,(double)
2201 kernel->height);
cristy56a9e512010-01-06 18:18:55 +00002202 message=AcquireString("");
2203 k=kernel->values;
cristybb503372010-05-27 20:51:26 +00002204 for (v=0; v < (ssize_t) kernel->height; v++)
cristy56a9e512010-01-06 18:18:55 +00002205 {
2206 *message='\0';
cristye8c25f92010-06-03 00:53:06 +00002207 (void) FormatMagickString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy56a9e512010-01-06 18:18:55 +00002208 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00002209 for (u=0; u < (ssize_t) kernel->width; u++)
cristy56a9e512010-01-06 18:18:55 +00002210 {
cristye7f51092010-01-17 00:39:37 +00002211 (void) FormatMagickString(format,MaxTextExtent,"%g ",*k++);
cristy56a9e512010-01-06 18:18:55 +00002212 (void) ConcatenateString(&message,format);
2213 }
2214 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
2215 }
2216 message=DestroyString(message);
2217 }
cristy36826ab2010-03-06 01:29:30 +00002218 status=AccelerateConvolveImage(image,kernel,filter_image,exception);
cristyd43a46b2010-01-21 02:13:41 +00002219 if (status == MagickTrue)
2220 return(filter_image);
cristy56a9e512010-01-06 18:18:55 +00002221 /*
2222 Filter image.
2223 */
2224 status=MagickTrue;
2225 progress=0;
2226 GetMagickPixelPacket(image,&bias);
2227 SetMagickPixelPacketBias(image,&bias);
2228 image_view=AcquireCacheView(image);
2229 filter_view=AcquireCacheView(filter_image);
2230#if defined(MAGICKCORE_OPENMP_SUPPORT)
2231 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2232#endif
cristybb503372010-05-27 20:51:26 +00002233 for (y=0; y < (ssize_t) image->rows; y++)
cristy56a9e512010-01-06 18:18:55 +00002234 {
2235 MagickBooleanType
2236 sync;
2237
2238 register const IndexPacket
2239 *restrict indexes;
2240
2241 register const PixelPacket
2242 *restrict p;
2243
2244 register IndexPacket
2245 *restrict filter_indexes;
2246
cristy56a9e512010-01-06 18:18:55 +00002247 register PixelPacket
2248 *restrict q;
2249
cristy117ff172010-08-15 21:35:32 +00002250 register ssize_t
2251 x;
2252
cristy56a9e512010-01-06 18:18:55 +00002253 if (status == MagickFalse)
2254 continue;
cristybb503372010-05-27 20:51:26 +00002255 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) kernel->width/2L),
cristy117ff172010-08-15 21:35:32 +00002256 y-(ssize_t) (kernel->height/2L),image->columns+kernel->width,
2257 kernel->height,exception);
cristy56a9e512010-01-06 18:18:55 +00002258 q=GetCacheViewAuthenticPixels(filter_view,0,y,filter_image->columns,1,
2259 exception);
2260 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2261 {
2262 status=MagickFalse;
2263 continue;
2264 }
2265 indexes=GetCacheViewVirtualIndexQueue(image_view);
2266 filter_indexes=GetCacheViewAuthenticIndexQueue(filter_view);
cristybb503372010-05-27 20:51:26 +00002267 for (x=0; x < (ssize_t) image->columns; x++)
cristy56a9e512010-01-06 18:18:55 +00002268 {
cristy56a9e512010-01-06 18:18:55 +00002269 MagickPixelPacket
2270 pixel;
2271
2272 register const double
2273 *restrict k;
2274
2275 register const PixelPacket
2276 *restrict kernel_pixels;
2277
cristybb503372010-05-27 20:51:26 +00002278 register ssize_t
cristy56a9e512010-01-06 18:18:55 +00002279 u;
2280
cristy117ff172010-08-15 21:35:32 +00002281 ssize_t
2282 v;
2283
cristy56a9e512010-01-06 18:18:55 +00002284 pixel=bias;
cristy36826ab2010-03-06 01:29:30 +00002285 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002286 kernel_pixels=p;
2287 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
2288 {
cristybb503372010-05-27 20:51:26 +00002289 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002290 {
cristybb503372010-05-27 20:51:26 +00002291 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002292 {
2293 pixel.red+=(*k)*kernel_pixels[u].red;
2294 pixel.green+=(*k)*kernel_pixels[u].green;
2295 pixel.blue+=(*k)*kernel_pixels[u].blue;
2296 k++;
2297 }
cristy36826ab2010-03-06 01:29:30 +00002298 kernel_pixels+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002299 }
2300 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002301 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002302 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002303 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002304 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002305 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002306 if ((channel & OpacityChannel) != 0)
2307 {
cristy36826ab2010-03-06 01:29:30 +00002308 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002309 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00002310 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002311 {
cristybb503372010-05-27 20:51:26 +00002312 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002313 {
2314 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
2315 k++;
2316 }
cristy36826ab2010-03-06 01:29:30 +00002317 kernel_pixels+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002318 }
cristyce70c172010-01-07 17:15:30 +00002319 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002320 }
2321 if (((channel & IndexChannel) != 0) &&
2322 (image->colorspace == CMYKColorspace))
2323 {
2324 register const IndexPacket
2325 *restrict kernel_indexes;
2326
cristy36826ab2010-03-06 01:29:30 +00002327 k=kernel->values;
cristy9d314ff2011-03-09 01:30:28 +00002328 kernel_indexes=indexes;
cristybb503372010-05-27 20:51:26 +00002329 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002330 {
cristybb503372010-05-27 20:51:26 +00002331 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002332 {
2333 pixel.index+=(*k)*kernel_indexes[u];
2334 k++;
2335 }
cristy36826ab2010-03-06 01:29:30 +00002336 kernel_indexes+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002337 }
cristyce70c172010-01-07 17:15:30 +00002338 filter_indexes[x]=ClampToQuantum(pixel.index);
cristy56a9e512010-01-06 18:18:55 +00002339 }
2340 }
2341 else
2342 {
2343 MagickRealType
2344 alpha,
2345 gamma;
2346
2347 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00002348 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002349 {
cristybb503372010-05-27 20:51:26 +00002350 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002351 {
2352 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
2353 kernel_pixels[u].opacity));
2354 pixel.red+=(*k)*alpha*kernel_pixels[u].red;
2355 pixel.green+=(*k)*alpha*kernel_pixels[u].green;
2356 pixel.blue+=(*k)*alpha*kernel_pixels[u].blue;
2357 gamma+=(*k)*alpha;
2358 k++;
2359 }
cristy36826ab2010-03-06 01:29:30 +00002360 kernel_pixels+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002361 }
2362 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
2363 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002364 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002365 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002366 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002367 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002368 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002369 if ((channel & OpacityChannel) != 0)
2370 {
cristy36826ab2010-03-06 01:29:30 +00002371 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002372 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00002373 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002374 {
cristybb503372010-05-27 20:51:26 +00002375 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002376 {
2377 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
2378 k++;
2379 }
cristy36826ab2010-03-06 01:29:30 +00002380 kernel_pixels+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002381 }
cristyce70c172010-01-07 17:15:30 +00002382 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002383 }
2384 if (((channel & IndexChannel) != 0) &&
2385 (image->colorspace == CMYKColorspace))
2386 {
2387 register const IndexPacket
2388 *restrict kernel_indexes;
2389
cristy36826ab2010-03-06 01:29:30 +00002390 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002391 kernel_pixels=p;
cristy9d314ff2011-03-09 01:30:28 +00002392 kernel_indexes=indexes;
cristybb503372010-05-27 20:51:26 +00002393 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002394 {
cristybb503372010-05-27 20:51:26 +00002395 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002396 {
2397 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
2398 kernel_pixels[u].opacity));
2399 pixel.index+=(*k)*alpha*kernel_indexes[u];
2400 k++;
2401 }
cristy36826ab2010-03-06 01:29:30 +00002402 kernel_pixels+=image->columns+kernel->width;
2403 kernel_indexes+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002404 }
cristy2115aea2010-01-09 23:16:08 +00002405 filter_indexes[x]=ClampToQuantum(gamma*
2406 GetIndexPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002407 }
2408 }
cristy9d314ff2011-03-09 01:30:28 +00002409 indexes++;
cristy56a9e512010-01-06 18:18:55 +00002410 p++;
2411 q++;
2412 }
2413 sync=SyncCacheViewAuthenticPixels(filter_view,exception);
2414 if (sync == MagickFalse)
2415 status=MagickFalse;
2416 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2417 {
2418 MagickBooleanType
2419 proceed;
2420
2421#if defined(MAGICKCORE_OPENMP_SUPPORT)
2422 #pragma omp critical (MagickCore_FilterImageChannel)
2423#endif
2424 proceed=SetImageProgress(image,FilterImageTag,progress++,image->rows);
2425 if (proceed == MagickFalse)
2426 status=MagickFalse;
2427 }
2428 }
2429 filter_image->type=image->type;
2430 filter_view=DestroyCacheView(filter_view);
2431 image_view=DestroyCacheView(image_view);
cristy56a9e512010-01-06 18:18:55 +00002432 if (status == MagickFalse)
2433 filter_image=DestroyImage(filter_image);
2434 return(filter_image);
2435}
2436
2437/*
2438%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2439% %
2440% %
2441% %
cristy3ed852e2009-09-05 21:47:34 +00002442% G a u s s i a n B l u r I m a g e %
2443% %
2444% %
2445% %
2446%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2447%
2448% GaussianBlurImage() blurs an image. We convolve the image with a
2449% Gaussian operator of the given radius and standard deviation (sigma).
2450% For reasonable results, the radius should be larger than sigma. Use a
2451% radius of 0 and GaussianBlurImage() selects a suitable radius for you
2452%
2453% The format of the GaussianBlurImage method is:
2454%
2455% Image *GaussianBlurImage(const Image *image,onst double radius,
2456% const double sigma,ExceptionInfo *exception)
2457% Image *GaussianBlurImageChannel(const Image *image,
2458% const ChannelType channel,const double radius,const double sigma,
2459% ExceptionInfo *exception)
2460%
2461% A description of each parameter follows:
2462%
2463% o image: the image.
2464%
2465% o channel: the channel type.
2466%
2467% o radius: the radius of the Gaussian, in pixels, not counting the center
2468% pixel.
2469%
2470% o sigma: the standard deviation of the Gaussian, in pixels.
2471%
2472% o exception: return any errors or warnings in this structure.
2473%
2474*/
2475
2476MagickExport Image *GaussianBlurImage(const Image *image,const double radius,
2477 const double sigma,ExceptionInfo *exception)
2478{
2479 Image
2480 *blur_image;
2481
2482 blur_image=GaussianBlurImageChannel(image,DefaultChannels,radius,sigma,
2483 exception);
2484 return(blur_image);
2485}
2486
2487MagickExport Image *GaussianBlurImageChannel(const Image *image,
2488 const ChannelType channel,const double radius,const double sigma,
2489 ExceptionInfo *exception)
2490{
2491 double
2492 *kernel;
2493
2494 Image
2495 *blur_image;
2496
cristybb503372010-05-27 20:51:26 +00002497 register ssize_t
cristy47e00502009-12-17 19:19:57 +00002498 i;
2499
cristybb503372010-05-27 20:51:26 +00002500 size_t
cristy3ed852e2009-09-05 21:47:34 +00002501 width;
2502
cristy117ff172010-08-15 21:35:32 +00002503 ssize_t
2504 j,
2505 u,
2506 v;
2507
cristy3ed852e2009-09-05 21:47:34 +00002508 assert(image != (const Image *) NULL);
2509 assert(image->signature == MagickSignature);
2510 if (image->debug != MagickFalse)
2511 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2512 assert(exception != (ExceptionInfo *) NULL);
2513 assert(exception->signature == MagickSignature);
2514 width=GetOptimalKernelWidth2D(radius,sigma);
2515 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2516 if (kernel == (double *) NULL)
2517 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00002518 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00002519 i=0;
cristy47e00502009-12-17 19:19:57 +00002520 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002521 {
cristy47e00502009-12-17 19:19:57 +00002522 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00002523 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
2524 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00002525 }
2526 blur_image=ConvolveImageChannel(image,channel,width,kernel,exception);
2527 kernel=(double *) RelinquishMagickMemory(kernel);
2528 return(blur_image);
2529}
2530
2531/*
2532%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2533% %
2534% %
2535% %
cristy3ed852e2009-09-05 21:47:34 +00002536% M o t i o n B l u r I m a g e %
2537% %
2538% %
2539% %
2540%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2541%
2542% MotionBlurImage() simulates motion blur. We convolve the image with a
2543% Gaussian operator of the given radius and standard deviation (sigma).
2544% For reasonable results, radius should be larger than sigma. Use a
2545% radius of 0 and MotionBlurImage() selects a suitable radius for you.
2546% Angle gives the angle of the blurring motion.
2547%
2548% Andrew Protano contributed this effect.
2549%
2550% The format of the MotionBlurImage method is:
2551%
2552% Image *MotionBlurImage(const Image *image,const double radius,
2553% const double sigma,const double angle,ExceptionInfo *exception)
2554% Image *MotionBlurImageChannel(const Image *image,const ChannelType channel,
2555% const double radius,const double sigma,const double angle,
2556% ExceptionInfo *exception)
2557%
2558% A description of each parameter follows:
2559%
2560% o image: the image.
2561%
2562% o channel: the channel type.
2563%
2564% o radius: the radius of the Gaussian, in pixels, not counting the center
2565% o radius: the radius of the Gaussian, in pixels, not counting
2566% the center pixel.
2567%
2568% o sigma: the standard deviation of the Gaussian, in pixels.
2569%
cristycee97112010-05-28 00:44:52 +00002570% o angle: Apply the effect along this angle.
cristy3ed852e2009-09-05 21:47:34 +00002571%
2572% o exception: return any errors or warnings in this structure.
2573%
2574*/
2575
cristybb503372010-05-27 20:51:26 +00002576static double *GetMotionBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +00002577{
cristy3ed852e2009-09-05 21:47:34 +00002578 double
cristy47e00502009-12-17 19:19:57 +00002579 *kernel,
cristy3ed852e2009-09-05 21:47:34 +00002580 normalize;
2581
cristybb503372010-05-27 20:51:26 +00002582 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002583 i;
2584
2585 /*
cristy47e00502009-12-17 19:19:57 +00002586 Generate a 1-D convolution kernel.
cristy3ed852e2009-09-05 21:47:34 +00002587 */
2588 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
2589 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
2590 if (kernel == (double *) NULL)
2591 return(kernel);
cristy3ed852e2009-09-05 21:47:34 +00002592 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00002593 for (i=0; i < (ssize_t) width; i++)
cristy47e00502009-12-17 19:19:57 +00002594 {
cristy4205a3c2010-09-12 20:19:59 +00002595 kernel[i]=(double) (exp((-((double) i*i)/(double) (2.0*MagickSigma*
2596 MagickSigma)))/(MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00002597 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +00002598 }
cristybb503372010-05-27 20:51:26 +00002599 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002600 kernel[i]/=normalize;
2601 return(kernel);
2602}
2603
2604MagickExport Image *MotionBlurImage(const Image *image,const double radius,
2605 const double sigma,const double angle,ExceptionInfo *exception)
2606{
2607 Image
2608 *motion_blur;
2609
2610 motion_blur=MotionBlurImageChannel(image,DefaultChannels,radius,sigma,angle,
2611 exception);
2612 return(motion_blur);
2613}
2614
2615MagickExport Image *MotionBlurImageChannel(const Image *image,
2616 const ChannelType channel,const double radius,const double sigma,
2617 const double angle,ExceptionInfo *exception)
2618{
cristyc4c8d132010-01-07 01:58:38 +00002619 CacheView
2620 *blur_view,
2621 *image_view;
2622
cristy3ed852e2009-09-05 21:47:34 +00002623 double
2624 *kernel;
2625
2626 Image
2627 *blur_image;
2628
cristy3ed852e2009-09-05 21:47:34 +00002629 MagickBooleanType
2630 status;
2631
cristybb503372010-05-27 20:51:26 +00002632 MagickOffsetType
2633 progress;
2634
cristy3ed852e2009-09-05 21:47:34 +00002635 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00002636 bias;
cristy3ed852e2009-09-05 21:47:34 +00002637
2638 OffsetInfo
2639 *offset;
2640
2641 PointInfo
2642 point;
2643
cristybb503372010-05-27 20:51:26 +00002644 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002645 i;
2646
cristybb503372010-05-27 20:51:26 +00002647 size_t
cristy3ed852e2009-09-05 21:47:34 +00002648 width;
2649
cristybb503372010-05-27 20:51:26 +00002650 ssize_t
2651 y;
2652
cristy3ed852e2009-09-05 21:47:34 +00002653 assert(image != (Image *) NULL);
2654 assert(image->signature == MagickSignature);
2655 if (image->debug != MagickFalse)
2656 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2657 assert(exception != (ExceptionInfo *) NULL);
2658 width=GetOptimalKernelWidth1D(radius,sigma);
2659 kernel=GetMotionBlurKernel(width,sigma);
2660 if (kernel == (double *) NULL)
2661 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2662 offset=(OffsetInfo *) AcquireQuantumMemory(width,sizeof(*offset));
2663 if (offset == (OffsetInfo *) NULL)
2664 {
2665 kernel=(double *) RelinquishMagickMemory(kernel);
2666 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2667 }
2668 blur_image=CloneImage(image,0,0,MagickTrue,exception);
2669 if (blur_image == (Image *) NULL)
2670 {
2671 kernel=(double *) RelinquishMagickMemory(kernel);
2672 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2673 return((Image *) NULL);
2674 }
2675 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
2676 {
2677 kernel=(double *) RelinquishMagickMemory(kernel);
2678 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2679 InheritException(exception,&blur_image->exception);
2680 blur_image=DestroyImage(blur_image);
2681 return((Image *) NULL);
2682 }
2683 point.x=(double) width*sin(DegreesToRadians(angle));
2684 point.y=(double) width*cos(DegreesToRadians(angle));
cristybb503372010-05-27 20:51:26 +00002685 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002686 {
cristybb503372010-05-27 20:51:26 +00002687 offset[i].x=(ssize_t) ceil((double) (i*point.y)/hypot(point.x,point.y)-0.5);
2688 offset[i].y=(ssize_t) ceil((double) (i*point.x)/hypot(point.x,point.y)-0.5);
cristy3ed852e2009-09-05 21:47:34 +00002689 }
2690 /*
2691 Motion blur image.
2692 */
2693 status=MagickTrue;
2694 progress=0;
cristyddd82202009-11-03 20:14:50 +00002695 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00002696 image_view=AcquireCacheView(image);
2697 blur_view=AcquireCacheView(blur_image);
cristyb557a152011-02-22 12:14:30 +00002698#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00002699 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00002700#endif
cristybb503372010-05-27 20:51:26 +00002701 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002702 {
2703 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002704 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00002705
cristy3ed852e2009-09-05 21:47:34 +00002706 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002707 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002708
cristy117ff172010-08-15 21:35:32 +00002709 register ssize_t
2710 x;
2711
cristy3ed852e2009-09-05 21:47:34 +00002712 if (status == MagickFalse)
2713 continue;
2714 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
2715 exception);
2716 if (q == (PixelPacket *) NULL)
2717 {
2718 status=MagickFalse;
2719 continue;
2720 }
2721 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
cristybb503372010-05-27 20:51:26 +00002722 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002723 {
2724 MagickPixelPacket
2725 qixel;
2726
2727 PixelPacket
2728 pixel;
2729
cristy117ff172010-08-15 21:35:32 +00002730 register const IndexPacket
2731 *restrict indexes;
2732
cristy3ed852e2009-09-05 21:47:34 +00002733 register double
cristyc47d1f82009-11-26 01:44:43 +00002734 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00002735
cristybb503372010-05-27 20:51:26 +00002736 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002737 i;
2738
cristy3ed852e2009-09-05 21:47:34 +00002739 k=kernel;
cristyddd82202009-11-03 20:14:50 +00002740 qixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00002741 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
2742 {
cristybb503372010-05-27 20:51:26 +00002743 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002744 {
2745 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
2746 offset[i].y,&pixel,exception);
2747 qixel.red+=(*k)*pixel.red;
2748 qixel.green+=(*k)*pixel.green;
2749 qixel.blue+=(*k)*pixel.blue;
2750 qixel.opacity+=(*k)*pixel.opacity;
2751 if (image->colorspace == CMYKColorspace)
2752 {
2753 indexes=GetCacheViewVirtualIndexQueue(image_view);
2754 qixel.index+=(*k)*(*indexes);
2755 }
2756 k++;
2757 }
2758 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002759 q->red=ClampToQuantum(qixel.red);
cristy3ed852e2009-09-05 21:47:34 +00002760 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002761 q->green=ClampToQuantum(qixel.green);
cristy3ed852e2009-09-05 21:47:34 +00002762 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002763 q->blue=ClampToQuantum(qixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00002764 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002765 q->opacity=ClampToQuantum(qixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00002766 if (((channel & IndexChannel) != 0) &&
2767 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00002768 blur_indexes[x]=(IndexPacket) ClampToQuantum(qixel.index);
cristy3ed852e2009-09-05 21:47:34 +00002769 }
2770 else
2771 {
2772 MagickRealType
2773 alpha,
2774 gamma;
2775
2776 alpha=0.0;
2777 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00002778 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002779 {
2780 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
2781 offset[i].y,&pixel,exception);
cristy8a7ea362010-03-10 20:31:43 +00002782 alpha=(MagickRealType) (QuantumScale*
2783 GetAlphaPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002784 qixel.red+=(*k)*alpha*pixel.red;
2785 qixel.green+=(*k)*alpha*pixel.green;
2786 qixel.blue+=(*k)*alpha*pixel.blue;
2787 qixel.opacity+=(*k)*pixel.opacity;
2788 if (image->colorspace == CMYKColorspace)
2789 {
2790 indexes=GetCacheViewVirtualIndexQueue(image_view);
2791 qixel.index+=(*k)*alpha*(*indexes);
2792 }
2793 gamma+=(*k)*alpha;
2794 k++;
2795 }
2796 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
2797 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002798 q->red=ClampToQuantum(gamma*qixel.red);
cristy3ed852e2009-09-05 21:47:34 +00002799 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002800 q->green=ClampToQuantum(gamma*qixel.green);
cristy3ed852e2009-09-05 21:47:34 +00002801 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002802 q->blue=ClampToQuantum(gamma*qixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00002803 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002804 q->opacity=ClampToQuantum(qixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00002805 if (((channel & IndexChannel) != 0) &&
2806 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00002807 blur_indexes[x]=(IndexPacket) ClampToQuantum(gamma*qixel.index);
cristy3ed852e2009-09-05 21:47:34 +00002808 }
2809 q++;
2810 }
2811 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
2812 status=MagickFalse;
2813 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2814 {
2815 MagickBooleanType
2816 proceed;
2817
cristyb557a152011-02-22 12:14:30 +00002818#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002819 #pragma omp critical (MagickCore_MotionBlurImageChannel)
2820#endif
2821 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
2822 if (proceed == MagickFalse)
2823 status=MagickFalse;
2824 }
2825 }
2826 blur_view=DestroyCacheView(blur_view);
2827 image_view=DestroyCacheView(image_view);
2828 kernel=(double *) RelinquishMagickMemory(kernel);
2829 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2830 if (status == MagickFalse)
2831 blur_image=DestroyImage(blur_image);
2832 return(blur_image);
2833}
2834
2835/*
2836%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2837% %
2838% %
2839% %
2840% P r e v i e w I m a g e %
2841% %
2842% %
2843% %
2844%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2845%
2846% PreviewImage() tiles 9 thumbnails of the specified image with an image
2847% processing operation applied with varying parameters. This may be helpful
2848% pin-pointing an appropriate parameter for a particular image processing
2849% operation.
2850%
2851% The format of the PreviewImages method is:
2852%
2853% Image *PreviewImages(const Image *image,const PreviewType preview,
2854% ExceptionInfo *exception)
2855%
2856% A description of each parameter follows:
2857%
2858% o image: the image.
2859%
2860% o preview: the image processing operation.
2861%
2862% o exception: return any errors or warnings in this structure.
2863%
2864*/
2865MagickExport Image *PreviewImage(const Image *image,const PreviewType preview,
2866 ExceptionInfo *exception)
2867{
2868#define NumberTiles 9
2869#define PreviewImageTag "Preview/Image"
2870#define DefaultPreviewGeometry "204x204+10+10"
2871
2872 char
2873 factor[MaxTextExtent],
2874 label[MaxTextExtent];
2875
2876 double
2877 degrees,
2878 gamma,
2879 percentage,
2880 radius,
2881 sigma,
2882 threshold;
2883
2884 Image
2885 *images,
2886 *montage_image,
2887 *preview_image,
2888 *thumbnail;
2889
2890 ImageInfo
2891 *preview_info;
2892
cristy3ed852e2009-09-05 21:47:34 +00002893 MagickBooleanType
2894 proceed;
2895
2896 MontageInfo
2897 *montage_info;
2898
2899 QuantizeInfo
2900 quantize_info;
2901
2902 RectangleInfo
2903 geometry;
2904
cristybb503372010-05-27 20:51:26 +00002905 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002906 i,
2907 x;
2908
cristybb503372010-05-27 20:51:26 +00002909 size_t
cristy3ed852e2009-09-05 21:47:34 +00002910 colors;
2911
cristy117ff172010-08-15 21:35:32 +00002912 ssize_t
2913 y;
2914
cristy3ed852e2009-09-05 21:47:34 +00002915 /*
2916 Open output image file.
2917 */
2918 assert(image != (Image *) NULL);
2919 assert(image->signature == MagickSignature);
2920 if (image->debug != MagickFalse)
2921 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2922 colors=2;
2923 degrees=0.0;
2924 gamma=(-0.2f);
2925 preview_info=AcquireImageInfo();
2926 SetGeometry(image,&geometry);
2927 (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y,
2928 &geometry.width,&geometry.height);
2929 images=NewImageList();
2930 percentage=12.5;
2931 GetQuantizeInfo(&quantize_info);
2932 radius=0.0;
2933 sigma=1.0;
2934 threshold=0.0;
2935 x=0;
2936 y=0;
2937 for (i=0; i < NumberTiles; i++)
2938 {
2939 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
2940 if (thumbnail == (Image *) NULL)
2941 break;
2942 (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL,
2943 (void *) NULL);
2944 (void) SetImageProperty(thumbnail,"label",DefaultTileLabel);
2945 if (i == (NumberTiles/2))
2946 {
2947 (void) QueryColorDatabase("#dfdfdf",&thumbnail->matte_color,exception);
2948 AppendImageToList(&images,thumbnail);
2949 continue;
2950 }
2951 switch (preview)
2952 {
2953 case RotatePreview:
2954 {
2955 degrees+=45.0;
2956 preview_image=RotateImage(thumbnail,degrees,exception);
cristye7f51092010-01-17 00:39:37 +00002957 (void) FormatMagickString(label,MaxTextExtent,"rotate %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002958 break;
2959 }
2960 case ShearPreview:
2961 {
2962 degrees+=5.0;
2963 preview_image=ShearImage(thumbnail,degrees,degrees,exception);
cristye7f51092010-01-17 00:39:37 +00002964 (void) FormatMagickString(label,MaxTextExtent,"shear %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002965 degrees,2.0*degrees);
2966 break;
2967 }
2968 case RollPreview:
2969 {
cristybb503372010-05-27 20:51:26 +00002970 x=(ssize_t) ((i+1)*thumbnail->columns)/NumberTiles;
2971 y=(ssize_t) ((i+1)*thumbnail->rows)/NumberTiles;
cristy3ed852e2009-09-05 21:47:34 +00002972 preview_image=RollImage(thumbnail,x,y,exception);
cristye8c25f92010-06-03 00:53:06 +00002973 (void) FormatMagickString(label,MaxTextExtent,"roll %+.20gx%+.20g",
2974 (double) x,(double) y);
cristy3ed852e2009-09-05 21:47:34 +00002975 break;
2976 }
2977 case HuePreview:
2978 {
2979 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2980 if (preview_image == (Image *) NULL)
2981 break;
cristye7f51092010-01-17 00:39:37 +00002982 (void) FormatMagickString(factor,MaxTextExtent,"100,100,%g",
cristy3ed852e2009-09-05 21:47:34 +00002983 2.0*percentage);
2984 (void) ModulateImage(preview_image,factor);
2985 (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor);
2986 break;
2987 }
2988 case SaturationPreview:
2989 {
2990 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2991 if (preview_image == (Image *) NULL)
2992 break;
cristye7f51092010-01-17 00:39:37 +00002993 (void) FormatMagickString(factor,MaxTextExtent,"100,%g",
cristy8cd5b312010-01-07 01:10:24 +00002994 2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00002995 (void) ModulateImage(preview_image,factor);
2996 (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor);
2997 break;
2998 }
2999 case BrightnessPreview:
3000 {
3001 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3002 if (preview_image == (Image *) NULL)
3003 break;
cristye7f51092010-01-17 00:39:37 +00003004 (void) FormatMagickString(factor,MaxTextExtent,"%g",2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00003005 (void) ModulateImage(preview_image,factor);
3006 (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor);
3007 break;
3008 }
3009 case GammaPreview:
3010 default:
3011 {
3012 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3013 if (preview_image == (Image *) NULL)
3014 break;
3015 gamma+=0.4f;
3016 (void) GammaImageChannel(preview_image,DefaultChannels,gamma);
cristye7f51092010-01-17 00:39:37 +00003017 (void) FormatMagickString(label,MaxTextExtent,"gamma %g",gamma);
cristy3ed852e2009-09-05 21:47:34 +00003018 break;
3019 }
3020 case SpiffPreview:
3021 {
3022 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3023 if (preview_image != (Image *) NULL)
3024 for (x=0; x < i; x++)
3025 (void) ContrastImage(preview_image,MagickTrue);
cristye8c25f92010-06-03 00:53:06 +00003026 (void) FormatMagickString(label,MaxTextExtent,"contrast (%.20g)",
3027 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00003028 break;
3029 }
3030 case DullPreview:
3031 {
3032 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3033 if (preview_image == (Image *) NULL)
3034 break;
3035 for (x=0; x < i; x++)
3036 (void) ContrastImage(preview_image,MagickFalse);
cristye8c25f92010-06-03 00:53:06 +00003037 (void) FormatMagickString(label,MaxTextExtent,"+contrast (%.20g)",
3038 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00003039 break;
3040 }
3041 case GrayscalePreview:
3042 {
3043 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3044 if (preview_image == (Image *) NULL)
3045 break;
3046 colors<<=1;
3047 quantize_info.number_colors=colors;
3048 quantize_info.colorspace=GRAYColorspace;
3049 (void) QuantizeImage(&quantize_info,preview_image);
3050 (void) FormatMagickString(label,MaxTextExtent,
cristye8c25f92010-06-03 00:53:06 +00003051 "-colorspace gray -colors %.20g",(double) colors);
cristy3ed852e2009-09-05 21:47:34 +00003052 break;
3053 }
3054 case QuantizePreview:
3055 {
3056 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3057 if (preview_image == (Image *) NULL)
3058 break;
3059 colors<<=1;
3060 quantize_info.number_colors=colors;
3061 (void) QuantizeImage(&quantize_info,preview_image);
cristye8c25f92010-06-03 00:53:06 +00003062 (void) FormatMagickString(label,MaxTextExtent,"colors %.20g",(double)
3063 colors);
cristy3ed852e2009-09-05 21:47:34 +00003064 break;
3065 }
3066 case DespecklePreview:
3067 {
3068 for (x=0; x < (i-1); x++)
3069 {
3070 preview_image=DespeckleImage(thumbnail,exception);
3071 if (preview_image == (Image *) NULL)
3072 break;
3073 thumbnail=DestroyImage(thumbnail);
3074 thumbnail=preview_image;
3075 }
3076 preview_image=DespeckleImage(thumbnail,exception);
3077 if (preview_image == (Image *) NULL)
3078 break;
cristye8c25f92010-06-03 00:53:06 +00003079 (void) FormatMagickString(label,MaxTextExtent,"despeckle (%.20g)",
3080 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00003081 break;
3082 }
3083 case ReduceNoisePreview:
3084 {
cristy95c38342011-03-18 22:39:51 +00003085 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) radius,
3086 (size_t) radius,exception);
cristye7f51092010-01-17 00:39:37 +00003087 (void) FormatMagickString(label,MaxTextExtent,"noise %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003088 break;
3089 }
3090 case AddNoisePreview:
3091 {
3092 switch ((int) i)
3093 {
3094 case 0:
3095 {
3096 (void) CopyMagickString(factor,"uniform",MaxTextExtent);
3097 break;
3098 }
3099 case 1:
3100 {
3101 (void) CopyMagickString(factor,"gaussian",MaxTextExtent);
3102 break;
3103 }
3104 case 2:
3105 {
3106 (void) CopyMagickString(factor,"multiplicative",MaxTextExtent);
3107 break;
3108 }
3109 case 3:
3110 {
3111 (void) CopyMagickString(factor,"impulse",MaxTextExtent);
3112 break;
3113 }
3114 case 4:
3115 {
3116 (void) CopyMagickString(factor,"laplacian",MaxTextExtent);
3117 break;
3118 }
3119 case 5:
3120 {
3121 (void) CopyMagickString(factor,"Poisson",MaxTextExtent);
3122 break;
3123 }
3124 default:
3125 {
3126 (void) CopyMagickString(thumbnail->magick,"NULL",MaxTextExtent);
3127 break;
3128 }
3129 }
cristyd76c51e2011-03-26 00:21:26 +00003130 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) i,
3131 (size_t) i,exception);
cristy3ed852e2009-09-05 21:47:34 +00003132 (void) FormatMagickString(label,MaxTextExtent,"+noise %s",factor);
3133 break;
3134 }
3135 case SharpenPreview:
3136 {
3137 preview_image=SharpenImage(thumbnail,radius,sigma,exception);
cristye7f51092010-01-17 00:39:37 +00003138 (void) FormatMagickString(label,MaxTextExtent,"sharpen %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003139 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00003140 break;
3141 }
3142 case BlurPreview:
3143 {
3144 preview_image=BlurImage(thumbnail,radius,sigma,exception);
cristye7f51092010-01-17 00:39:37 +00003145 (void) FormatMagickString(label,MaxTextExtent,"blur %gx%g",radius,
cristy3ed852e2009-09-05 21:47:34 +00003146 sigma);
3147 break;
3148 }
3149 case ThresholdPreview:
3150 {
3151 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3152 if (preview_image == (Image *) NULL)
3153 break;
3154 (void) BilevelImage(thumbnail,
3155 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
cristye7f51092010-01-17 00:39:37 +00003156 (void) FormatMagickString(label,MaxTextExtent,"threshold %g",
cristy3ed852e2009-09-05 21:47:34 +00003157 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
3158 break;
3159 }
3160 case EdgeDetectPreview:
3161 {
3162 preview_image=EdgeImage(thumbnail,radius,exception);
cristye7f51092010-01-17 00:39:37 +00003163 (void) FormatMagickString(label,MaxTextExtent,"edge %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003164 break;
3165 }
3166 case SpreadPreview:
3167 {
3168 preview_image=SpreadImage(thumbnail,radius,exception);
cristye7f51092010-01-17 00:39:37 +00003169 (void) FormatMagickString(label,MaxTextExtent,"spread %g",
cristy8cd5b312010-01-07 01:10:24 +00003170 radius+0.5);
cristy3ed852e2009-09-05 21:47:34 +00003171 break;
3172 }
3173 case SolarizePreview:
3174 {
3175 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3176 if (preview_image == (Image *) NULL)
3177 break;
3178 (void) SolarizeImage(preview_image,(double) QuantumRange*
3179 percentage/100.0);
cristye7f51092010-01-17 00:39:37 +00003180 (void) FormatMagickString(label,MaxTextExtent,"solarize %g",
cristy3ed852e2009-09-05 21:47:34 +00003181 (QuantumRange*percentage)/100.0);
3182 break;
3183 }
3184 case ShadePreview:
3185 {
3186 degrees+=10.0;
3187 preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees,
3188 exception);
cristye7f51092010-01-17 00:39:37 +00003189 (void) FormatMagickString(label,MaxTextExtent,"shade %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003190 degrees,degrees);
cristy3ed852e2009-09-05 21:47:34 +00003191 break;
3192 }
3193 case RaisePreview:
3194 {
3195 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3196 if (preview_image == (Image *) NULL)
3197 break;
cristybb503372010-05-27 20:51:26 +00003198 geometry.width=(size_t) (2*i+2);
3199 geometry.height=(size_t) (2*i+2);
cristy3ed852e2009-09-05 21:47:34 +00003200 geometry.x=i/2;
3201 geometry.y=i/2;
3202 (void) RaiseImage(preview_image,&geometry,MagickTrue);
cristye8c25f92010-06-03 00:53:06 +00003203 (void) FormatMagickString(label,MaxTextExtent,
cristy6d8abba2010-06-03 01:10:47 +00003204 "raise %.20gx%.20g%+.20g%+.20g",(double) geometry.width,(double)
cristye8c25f92010-06-03 00:53:06 +00003205 geometry.height,(double) geometry.x,(double) geometry.y);
cristy3ed852e2009-09-05 21:47:34 +00003206 break;
3207 }
3208 case SegmentPreview:
3209 {
3210 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3211 if (preview_image == (Image *) NULL)
3212 break;
3213 threshold+=0.4f;
3214 (void) SegmentImage(preview_image,RGBColorspace,MagickFalse,threshold,
3215 threshold);
cristye7f51092010-01-17 00:39:37 +00003216 (void) FormatMagickString(label,MaxTextExtent,"segment %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00003217 threshold,threshold);
3218 break;
3219 }
3220 case SwirlPreview:
3221 {
3222 preview_image=SwirlImage(thumbnail,degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003223 (void) FormatMagickString(label,MaxTextExtent,"swirl %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003224 degrees+=45.0;
3225 break;
3226 }
3227 case ImplodePreview:
3228 {
3229 degrees+=0.1f;
3230 preview_image=ImplodeImage(thumbnail,degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003231 (void) FormatMagickString(label,MaxTextExtent,"implode %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003232 break;
3233 }
3234 case WavePreview:
3235 {
3236 degrees+=5.0f;
3237 preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003238 (void) FormatMagickString(label,MaxTextExtent,"wave %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003239 0.5*degrees,2.0*degrees);
cristy3ed852e2009-09-05 21:47:34 +00003240 break;
3241 }
3242 case OilPaintPreview:
3243 {
3244 preview_image=OilPaintImage(thumbnail,(double) radius,exception);
cristye7f51092010-01-17 00:39:37 +00003245 (void) FormatMagickString(label,MaxTextExtent,"paint %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003246 break;
3247 }
3248 case CharcoalDrawingPreview:
3249 {
3250 preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma,
3251 exception);
cristye7f51092010-01-17 00:39:37 +00003252 (void) FormatMagickString(label,MaxTextExtent,"charcoal %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003253 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00003254 break;
3255 }
3256 case JPEGPreview:
3257 {
3258 char
3259 filename[MaxTextExtent];
3260
3261 int
3262 file;
3263
3264 MagickBooleanType
3265 status;
3266
3267 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3268 if (preview_image == (Image *) NULL)
3269 break;
cristybb503372010-05-27 20:51:26 +00003270 preview_info->quality=(size_t) percentage;
cristye8c25f92010-06-03 00:53:06 +00003271 (void) FormatMagickString(factor,MaxTextExtent,"%.20g",(double)
3272 preview_info->quality);
cristy3ed852e2009-09-05 21:47:34 +00003273 file=AcquireUniqueFileResource(filename);
3274 if (file != -1)
3275 file=close(file)-1;
3276 (void) FormatMagickString(preview_image->filename,MaxTextExtent,
3277 "jpeg:%s",filename);
3278 status=WriteImage(preview_info,preview_image);
3279 if (status != MagickFalse)
3280 {
3281 Image
3282 *quality_image;
3283
3284 (void) CopyMagickString(preview_info->filename,
3285 preview_image->filename,MaxTextExtent);
3286 quality_image=ReadImage(preview_info,exception);
3287 if (quality_image != (Image *) NULL)
3288 {
3289 preview_image=DestroyImage(preview_image);
3290 preview_image=quality_image;
3291 }
3292 }
3293 (void) RelinquishUniqueFileResource(preview_image->filename);
3294 if ((GetBlobSize(preview_image)/1024) >= 1024)
cristye7f51092010-01-17 00:39:37 +00003295 (void) FormatMagickString(label,MaxTextExtent,"quality %s\n%gmb ",
cristy3ed852e2009-09-05 21:47:34 +00003296 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
3297 1024.0/1024.0);
3298 else
3299 if (GetBlobSize(preview_image) >= 1024)
cristy8cd5b312010-01-07 01:10:24 +00003300 (void) FormatMagickString(label,MaxTextExtent,
cristye7f51092010-01-17 00:39:37 +00003301 "quality %s\n%gkb ",factor,(double) ((MagickOffsetType)
cristy8cd5b312010-01-07 01:10:24 +00003302 GetBlobSize(preview_image))/1024.0);
cristy3ed852e2009-09-05 21:47:34 +00003303 else
cristye8c25f92010-06-03 00:53:06 +00003304 (void) FormatMagickString(label,MaxTextExtent,"quality %s\n%.20gb ",
3305 factor,(double) GetBlobSize(thumbnail));
cristy3ed852e2009-09-05 21:47:34 +00003306 break;
3307 }
3308 }
3309 thumbnail=DestroyImage(thumbnail);
3310 percentage+=12.5;
3311 radius+=0.5;
3312 sigma+=0.25;
3313 if (preview_image == (Image *) NULL)
3314 break;
3315 (void) DeleteImageProperty(preview_image,"label");
3316 (void) SetImageProperty(preview_image,"label",label);
3317 AppendImageToList(&images,preview_image);
cristybb503372010-05-27 20:51:26 +00003318 proceed=SetImageProgress(image,PreviewImageTag,(MagickOffsetType) i,
3319 NumberTiles);
cristy3ed852e2009-09-05 21:47:34 +00003320 if (proceed == MagickFalse)
3321 break;
3322 }
3323 if (images == (Image *) NULL)
3324 {
3325 preview_info=DestroyImageInfo(preview_info);
3326 return((Image *) NULL);
3327 }
3328 /*
3329 Create the montage.
3330 */
3331 montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL);
3332 (void) CopyMagickString(montage_info->filename,image->filename,MaxTextExtent);
3333 montage_info->shadow=MagickTrue;
3334 (void) CloneString(&montage_info->tile,"3x3");
3335 (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry);
3336 (void) CloneString(&montage_info->frame,DefaultTileFrame);
3337 montage_image=MontageImages(images,montage_info,exception);
3338 montage_info=DestroyMontageInfo(montage_info);
3339 images=DestroyImageList(images);
3340 if (montage_image == (Image *) NULL)
3341 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3342 if (montage_image->montage != (char *) NULL)
3343 {
3344 /*
3345 Free image directory.
3346 */
3347 montage_image->montage=(char *) RelinquishMagickMemory(
3348 montage_image->montage);
3349 if (image->directory != (char *) NULL)
3350 montage_image->directory=(char *) RelinquishMagickMemory(
3351 montage_image->directory);
3352 }
3353 preview_info=DestroyImageInfo(preview_info);
3354 return(montage_image);
3355}
3356
3357/*
3358%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3359% %
3360% %
3361% %
3362% R a d i a l B l u r I m a g e %
3363% %
3364% %
3365% %
3366%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3367%
3368% RadialBlurImage() applies a radial blur to the image.
3369%
3370% Andrew Protano contributed this effect.
3371%
3372% The format of the RadialBlurImage method is:
3373%
3374% Image *RadialBlurImage(const Image *image,const double angle,
3375% ExceptionInfo *exception)
3376% Image *RadialBlurImageChannel(const Image *image,const ChannelType channel,
3377% const double angle,ExceptionInfo *exception)
3378%
3379% A description of each parameter follows:
3380%
3381% o image: the image.
3382%
3383% o channel: the channel type.
3384%
3385% o angle: the angle of the radial blur.
3386%
3387% o exception: return any errors or warnings in this structure.
3388%
3389*/
3390
3391MagickExport Image *RadialBlurImage(const Image *image,const double angle,
3392 ExceptionInfo *exception)
3393{
3394 Image
3395 *blur_image;
3396
3397 blur_image=RadialBlurImageChannel(image,DefaultChannels,angle,exception);
3398 return(blur_image);
3399}
3400
3401MagickExport Image *RadialBlurImageChannel(const Image *image,
3402 const ChannelType channel,const double angle,ExceptionInfo *exception)
3403{
cristyc4c8d132010-01-07 01:58:38 +00003404 CacheView
3405 *blur_view,
3406 *image_view;
3407
cristy3ed852e2009-09-05 21:47:34 +00003408 Image
3409 *blur_image;
3410
cristy3ed852e2009-09-05 21:47:34 +00003411 MagickBooleanType
3412 status;
3413
cristybb503372010-05-27 20:51:26 +00003414 MagickOffsetType
3415 progress;
3416
cristy3ed852e2009-09-05 21:47:34 +00003417 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00003418 bias;
cristy3ed852e2009-09-05 21:47:34 +00003419
3420 MagickRealType
3421 blur_radius,
3422 *cos_theta,
3423 offset,
3424 *sin_theta,
3425 theta;
3426
3427 PointInfo
3428 blur_center;
3429
cristybb503372010-05-27 20:51:26 +00003430 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003431 i;
3432
cristybb503372010-05-27 20:51:26 +00003433 size_t
cristy3ed852e2009-09-05 21:47:34 +00003434 n;
3435
cristybb503372010-05-27 20:51:26 +00003436 ssize_t
3437 y;
3438
cristy3ed852e2009-09-05 21:47:34 +00003439 /*
3440 Allocate blur image.
3441 */
3442 assert(image != (Image *) NULL);
3443 assert(image->signature == MagickSignature);
3444 if (image->debug != MagickFalse)
3445 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3446 assert(exception != (ExceptionInfo *) NULL);
3447 assert(exception->signature == MagickSignature);
3448 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3449 if (blur_image == (Image *) NULL)
3450 return((Image *) NULL);
3451 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3452 {
3453 InheritException(exception,&blur_image->exception);
3454 blur_image=DestroyImage(blur_image);
3455 return((Image *) NULL);
3456 }
3457 blur_center.x=(double) image->columns/2.0;
3458 blur_center.y=(double) image->rows/2.0;
3459 blur_radius=hypot(blur_center.x,blur_center.y);
cristy117ff172010-08-15 21:35:32 +00003460 n=(size_t) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+2UL);
cristy3ed852e2009-09-05 21:47:34 +00003461 theta=DegreesToRadians(angle)/(MagickRealType) (n-1);
3462 cos_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3463 sizeof(*cos_theta));
3464 sin_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3465 sizeof(*sin_theta));
3466 if ((cos_theta == (MagickRealType *) NULL) ||
3467 (sin_theta == (MagickRealType *) NULL))
3468 {
3469 blur_image=DestroyImage(blur_image);
3470 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3471 }
3472 offset=theta*(MagickRealType) (n-1)/2.0;
cristybb503372010-05-27 20:51:26 +00003473 for (i=0; i < (ssize_t) n; i++)
cristy3ed852e2009-09-05 21:47:34 +00003474 {
3475 cos_theta[i]=cos((double) (theta*i-offset));
3476 sin_theta[i]=sin((double) (theta*i-offset));
3477 }
3478 /*
3479 Radial blur image.
3480 */
3481 status=MagickTrue;
3482 progress=0;
cristyddd82202009-11-03 20:14:50 +00003483 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003484 image_view=AcquireCacheView(image);
3485 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003486#if defined(MAGICKCORE_OPENMP_SUPPORT)
3487 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003488#endif
cristybb503372010-05-27 20:51:26 +00003489 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003490 {
3491 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003492 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003493
3494 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003495 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00003496
cristy3ed852e2009-09-05 21:47:34 +00003497 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003498 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003499
cristy117ff172010-08-15 21:35:32 +00003500 register ssize_t
3501 x;
3502
cristy3ed852e2009-09-05 21:47:34 +00003503 if (status == MagickFalse)
3504 continue;
3505 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3506 exception);
3507 if (q == (PixelPacket *) NULL)
3508 {
3509 status=MagickFalse;
3510 continue;
3511 }
3512 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
cristybb503372010-05-27 20:51:26 +00003513 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003514 {
3515 MagickPixelPacket
3516 qixel;
3517
3518 MagickRealType
3519 normalize,
3520 radius;
3521
3522 PixelPacket
3523 pixel;
3524
3525 PointInfo
3526 center;
3527
cristybb503372010-05-27 20:51:26 +00003528 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003529 i;
3530
cristybb503372010-05-27 20:51:26 +00003531 size_t
cristy3ed852e2009-09-05 21:47:34 +00003532 step;
3533
3534 center.x=(double) x-blur_center.x;
3535 center.y=(double) y-blur_center.y;
3536 radius=hypot((double) center.x,center.y);
3537 if (radius == 0)
3538 step=1;
3539 else
3540 {
cristybb503372010-05-27 20:51:26 +00003541 step=(size_t) (blur_radius/radius);
cristy3ed852e2009-09-05 21:47:34 +00003542 if (step == 0)
3543 step=1;
3544 else
3545 if (step >= n)
3546 step=n-1;
3547 }
3548 normalize=0.0;
cristyddd82202009-11-03 20:14:50 +00003549 qixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00003550 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
3551 {
cristyeaedf062010-05-29 22:36:02 +00003552 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00003553 {
cristyeaedf062010-05-29 22:36:02 +00003554 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
3555 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
3556 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
3557 cos_theta[i]+0.5),&pixel,exception);
cristy3ed852e2009-09-05 21:47:34 +00003558 qixel.red+=pixel.red;
3559 qixel.green+=pixel.green;
3560 qixel.blue+=pixel.blue;
3561 qixel.opacity+=pixel.opacity;
3562 if (image->colorspace == CMYKColorspace)
3563 {
3564 indexes=GetCacheViewVirtualIndexQueue(image_view);
3565 qixel.index+=(*indexes);
3566 }
3567 normalize+=1.0;
3568 }
3569 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
3570 normalize);
3571 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003572 q->red=ClampToQuantum(normalize*qixel.red);
cristy3ed852e2009-09-05 21:47:34 +00003573 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003574 q->green=ClampToQuantum(normalize*qixel.green);
cristy3ed852e2009-09-05 21:47:34 +00003575 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003576 q->blue=ClampToQuantum(normalize*qixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00003577 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003578 q->opacity=ClampToQuantum(normalize*qixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00003579 if (((channel & IndexChannel) != 0) &&
3580 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00003581 blur_indexes[x]=(IndexPacket) ClampToQuantum(normalize*qixel.index);
cristy3ed852e2009-09-05 21:47:34 +00003582 }
3583 else
3584 {
3585 MagickRealType
3586 alpha,
3587 gamma;
3588
3589 alpha=1.0;
3590 gamma=0.0;
cristyeaedf062010-05-29 22:36:02 +00003591 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00003592 {
cristyeaedf062010-05-29 22:36:02 +00003593 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
3594 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
3595 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
3596 cos_theta[i]+0.5),&pixel,exception);
cristy46f08202010-01-10 04:04:21 +00003597 alpha=(MagickRealType) (QuantumScale*
3598 GetAlphaPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00003599 qixel.red+=alpha*pixel.red;
3600 qixel.green+=alpha*pixel.green;
3601 qixel.blue+=alpha*pixel.blue;
3602 qixel.opacity+=pixel.opacity;
3603 if (image->colorspace == CMYKColorspace)
3604 {
3605 indexes=GetCacheViewVirtualIndexQueue(image_view);
3606 qixel.index+=alpha*(*indexes);
3607 }
3608 gamma+=alpha;
3609 normalize+=1.0;
3610 }
3611 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3612 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
3613 normalize);
3614 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003615 q->red=ClampToQuantum(gamma*qixel.red);
cristy3ed852e2009-09-05 21:47:34 +00003616 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003617 q->green=ClampToQuantum(gamma*qixel.green);
cristy3ed852e2009-09-05 21:47:34 +00003618 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003619 q->blue=ClampToQuantum(gamma*qixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00003620 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003621 q->opacity=ClampToQuantum(normalize*qixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00003622 if (((channel & IndexChannel) != 0) &&
3623 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00003624 blur_indexes[x]=(IndexPacket) ClampToQuantum(gamma*qixel.index);
cristy3ed852e2009-09-05 21:47:34 +00003625 }
3626 q++;
3627 }
3628 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
3629 status=MagickFalse;
3630 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3631 {
3632 MagickBooleanType
3633 proceed;
3634
cristyb5d5f722009-11-04 03:03:49 +00003635#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003636 #pragma omp critical (MagickCore_RadialBlurImageChannel)
3637#endif
3638 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
3639 if (proceed == MagickFalse)
3640 status=MagickFalse;
3641 }
3642 }
3643 blur_view=DestroyCacheView(blur_view);
3644 image_view=DestroyCacheView(image_view);
3645 cos_theta=(MagickRealType *) RelinquishMagickMemory(cos_theta);
3646 sin_theta=(MagickRealType *) RelinquishMagickMemory(sin_theta);
3647 if (status == MagickFalse)
3648 blur_image=DestroyImage(blur_image);
3649 return(blur_image);
3650}
3651
3652/*
3653%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3654% %
3655% %
3656% %
cristy3ed852e2009-09-05 21:47:34 +00003657% S e l e c t i v e B l u r I m a g e %
3658% %
3659% %
3660% %
3661%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3662%
3663% SelectiveBlurImage() selectively blur pixels within a contrast threshold.
3664% It is similar to the unsharpen mask that sharpens everything with contrast
3665% above a certain threshold.
3666%
3667% The format of the SelectiveBlurImage method is:
3668%
3669% Image *SelectiveBlurImage(const Image *image,const double radius,
3670% const double sigma,const double threshold,ExceptionInfo *exception)
3671% Image *SelectiveBlurImageChannel(const Image *image,
3672% const ChannelType channel,const double radius,const double sigma,
3673% const double threshold,ExceptionInfo *exception)
3674%
3675% A description of each parameter follows:
3676%
3677% o image: the image.
3678%
3679% o channel: the channel type.
3680%
3681% o radius: the radius of the Gaussian, in pixels, not counting the center
3682% pixel.
3683%
3684% o sigma: the standard deviation of the Gaussian, in pixels.
3685%
3686% o threshold: only pixels within this contrast threshold are included
3687% in the blur operation.
3688%
3689% o exception: return any errors or warnings in this structure.
3690%
3691*/
3692
3693static inline MagickBooleanType SelectiveContrast(const PixelPacket *p,
3694 const PixelPacket *q,const double threshold)
3695{
3696 if (fabs(PixelIntensity(p)-PixelIntensity(q)) < threshold)
3697 return(MagickTrue);
3698 return(MagickFalse);
3699}
3700
3701MagickExport Image *SelectiveBlurImage(const Image *image,const double radius,
3702 const double sigma,const double threshold,ExceptionInfo *exception)
3703{
3704 Image
3705 *blur_image;
3706
3707 blur_image=SelectiveBlurImageChannel(image,DefaultChannels,radius,sigma,
3708 threshold,exception);
3709 return(blur_image);
3710}
3711
3712MagickExport Image *SelectiveBlurImageChannel(const Image *image,
3713 const ChannelType channel,const double radius,const double sigma,
3714 const double threshold,ExceptionInfo *exception)
3715{
3716#define SelectiveBlurImageTag "SelectiveBlur/Image"
3717
cristy47e00502009-12-17 19:19:57 +00003718 CacheView
3719 *blur_view,
3720 *image_view;
3721
cristy3ed852e2009-09-05 21:47:34 +00003722 double
cristy3ed852e2009-09-05 21:47:34 +00003723 *kernel;
3724
3725 Image
3726 *blur_image;
3727
cristy3ed852e2009-09-05 21:47:34 +00003728 MagickBooleanType
3729 status;
3730
cristybb503372010-05-27 20:51:26 +00003731 MagickOffsetType
3732 progress;
3733
cristy3ed852e2009-09-05 21:47:34 +00003734 MagickPixelPacket
cristy3ed852e2009-09-05 21:47:34 +00003735 bias;
3736
cristybb503372010-05-27 20:51:26 +00003737 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003738 i;
cristy3ed852e2009-09-05 21:47:34 +00003739
cristybb503372010-05-27 20:51:26 +00003740 size_t
cristy3ed852e2009-09-05 21:47:34 +00003741 width;
3742
cristybb503372010-05-27 20:51:26 +00003743 ssize_t
3744 j,
3745 u,
3746 v,
3747 y;
3748
cristy3ed852e2009-09-05 21:47:34 +00003749 /*
3750 Initialize blur image attributes.
3751 */
3752 assert(image != (Image *) NULL);
3753 assert(image->signature == MagickSignature);
3754 if (image->debug != MagickFalse)
3755 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3756 assert(exception != (ExceptionInfo *) NULL);
3757 assert(exception->signature == MagickSignature);
3758 width=GetOptimalKernelWidth1D(radius,sigma);
3759 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
3760 if (kernel == (double *) NULL)
3761 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00003762 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00003763 i=0;
cristy47e00502009-12-17 19:19:57 +00003764 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00003765 {
cristy47e00502009-12-17 19:19:57 +00003766 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00003767 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
3768 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00003769 }
3770 if (image->debug != MagickFalse)
3771 {
3772 char
3773 format[MaxTextExtent],
3774 *message;
3775
cristy117ff172010-08-15 21:35:32 +00003776 register const double
3777 *k;
3778
cristybb503372010-05-27 20:51:26 +00003779 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003780 u,
3781 v;
3782
cristy3ed852e2009-09-05 21:47:34 +00003783 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00003784 " SelectiveBlurImage with %.20gx%.20g kernel:",(double) width,(double)
3785 width);
cristy3ed852e2009-09-05 21:47:34 +00003786 message=AcquireString("");
3787 k=kernel;
cristybb503372010-05-27 20:51:26 +00003788 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003789 {
3790 *message='\0';
cristye8c25f92010-06-03 00:53:06 +00003791 (void) FormatMagickString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy3ed852e2009-09-05 21:47:34 +00003792 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00003793 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003794 {
3795 (void) FormatMagickString(format,MaxTextExtent,"%+f ",*k++);
3796 (void) ConcatenateString(&message,format);
3797 }
3798 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
3799 }
3800 message=DestroyString(message);
3801 }
3802 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3803 if (blur_image == (Image *) NULL)
3804 return((Image *) NULL);
3805 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3806 {
3807 InheritException(exception,&blur_image->exception);
3808 blur_image=DestroyImage(blur_image);
3809 return((Image *) NULL);
3810 }
3811 /*
3812 Threshold blur image.
3813 */
3814 status=MagickTrue;
3815 progress=0;
cristyddd82202009-11-03 20:14:50 +00003816 GetMagickPixelPacket(image,&bias);
3817 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003818 image_view=AcquireCacheView(image);
3819 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003820#if defined(MAGICKCORE_OPENMP_SUPPORT)
3821 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003822#endif
cristybb503372010-05-27 20:51:26 +00003823 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003824 {
3825 MagickBooleanType
3826 sync;
3827
3828 MagickRealType
3829 gamma;
3830
3831 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003832 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003833
3834 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003835 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003836
3837 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003838 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00003839
cristy3ed852e2009-09-05 21:47:34 +00003840 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003841 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003842
cristy117ff172010-08-15 21:35:32 +00003843 register ssize_t
3844 x;
3845
cristy3ed852e2009-09-05 21:47:34 +00003846 if (status == MagickFalse)
3847 continue;
cristy117ff172010-08-15 21:35:32 +00003848 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
3849 (width/2L),image->columns+width,width,exception);
cristy3ed852e2009-09-05 21:47:34 +00003850 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3851 exception);
3852 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
3853 {
3854 status=MagickFalse;
3855 continue;
3856 }
3857 indexes=GetCacheViewVirtualIndexQueue(image_view);
3858 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
cristybb503372010-05-27 20:51:26 +00003859 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003860 {
cristy3ed852e2009-09-05 21:47:34 +00003861 MagickPixelPacket
3862 pixel;
3863
3864 register const double
cristyc47d1f82009-11-26 01:44:43 +00003865 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00003866
cristybb503372010-05-27 20:51:26 +00003867 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003868 u;
3869
cristy117ff172010-08-15 21:35:32 +00003870 ssize_t
3871 j,
3872 v;
3873
cristyddd82202009-11-03 20:14:50 +00003874 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00003875 k=kernel;
3876 gamma=0.0;
3877 j=0;
3878 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
3879 {
cristybb503372010-05-27 20:51:26 +00003880 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003881 {
cristybb503372010-05-27 20:51:26 +00003882 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003883 {
3884 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
3885 {
3886 pixel.red+=(*k)*(p+u+j)->red;
3887 pixel.green+=(*k)*(p+u+j)->green;
3888 pixel.blue+=(*k)*(p+u+j)->blue;
3889 gamma+=(*k);
3890 k++;
3891 }
3892 }
cristyd99b0962010-05-29 23:14:26 +00003893 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003894 }
3895 if (gamma != 0.0)
3896 {
3897 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3898 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003899 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00003900 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003901 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00003902 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003903 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00003904 }
3905 if ((channel & OpacityChannel) != 0)
3906 {
3907 gamma=0.0;
3908 j=0;
cristybb503372010-05-27 20:51:26 +00003909 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003910 {
cristybb503372010-05-27 20:51:26 +00003911 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003912 {
3913 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
3914 {
3915 pixel.opacity+=(*k)*(p+u+j)->opacity;
3916 gamma+=(*k);
3917 k++;
3918 }
3919 }
cristyeaedf062010-05-29 22:36:02 +00003920 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003921 }
3922 if (gamma != 0.0)
3923 {
3924 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
3925 gamma);
cristyce70c172010-01-07 17:15:30 +00003926 SetOpacityPixelComponent(q,ClampToQuantum(gamma*
3927 GetOpacityPixelComponent(&pixel)));
cristy3ed852e2009-09-05 21:47:34 +00003928 }
3929 }
3930 if (((channel & IndexChannel) != 0) &&
3931 (image->colorspace == CMYKColorspace))
3932 {
3933 gamma=0.0;
3934 j=0;
cristybb503372010-05-27 20:51:26 +00003935 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003936 {
cristybb503372010-05-27 20:51:26 +00003937 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003938 {
3939 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
3940 {
3941 pixel.index+=(*k)*indexes[x+u+j];
3942 gamma+=(*k);
3943 k++;
3944 }
3945 }
cristyeaedf062010-05-29 22:36:02 +00003946 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003947 }
3948 if (gamma != 0.0)
3949 {
3950 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
3951 gamma);
cristy6db48122010-01-11 00:18:07 +00003952 blur_indexes[x]=ClampToQuantum(gamma*
3953 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00003954 }
3955 }
3956 }
3957 else
3958 {
3959 MagickRealType
3960 alpha;
3961
cristybb503372010-05-27 20:51:26 +00003962 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003963 {
cristybb503372010-05-27 20:51:26 +00003964 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003965 {
3966 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
3967 {
cristy46f08202010-01-10 04:04:21 +00003968 alpha=(MagickRealType) (QuantumScale*
3969 GetAlphaPixelComponent(p+u+j));
cristy3ed852e2009-09-05 21:47:34 +00003970 pixel.red+=(*k)*alpha*(p+u+j)->red;
3971 pixel.green+=(*k)*alpha*(p+u+j)->green;
3972 pixel.blue+=(*k)*alpha*(p+u+j)->blue;
3973 pixel.opacity+=(*k)*(p+u+j)->opacity;
3974 gamma+=(*k)*alpha;
3975 k++;
3976 }
3977 }
cristyeaedf062010-05-29 22:36:02 +00003978 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003979 }
3980 if (gamma != 0.0)
3981 {
3982 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3983 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003984 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00003985 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003986 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00003987 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003988 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00003989 }
3990 if ((channel & OpacityChannel) != 0)
3991 {
3992 gamma=0.0;
3993 j=0;
cristybb503372010-05-27 20:51:26 +00003994 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003995 {
cristybb503372010-05-27 20:51:26 +00003996 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003997 {
3998 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
3999 {
4000 pixel.opacity+=(*k)*(p+u+j)->opacity;
4001 gamma+=(*k);
4002 k++;
4003 }
4004 }
cristyeaedf062010-05-29 22:36:02 +00004005 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00004006 }
4007 if (gamma != 0.0)
4008 {
4009 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4010 gamma);
cristy6db48122010-01-11 00:18:07 +00004011 SetOpacityPixelComponent(q,
4012 ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004013 }
4014 }
4015 if (((channel & IndexChannel) != 0) &&
4016 (image->colorspace == CMYKColorspace))
4017 {
4018 gamma=0.0;
4019 j=0;
cristybb503372010-05-27 20:51:26 +00004020 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00004021 {
cristybb503372010-05-27 20:51:26 +00004022 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00004023 {
4024 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4025 {
cristy46f08202010-01-10 04:04:21 +00004026 alpha=(MagickRealType) (QuantumScale*
4027 GetAlphaPixelComponent(p+u+j));
cristy3ed852e2009-09-05 21:47:34 +00004028 pixel.index+=(*k)*alpha*indexes[x+u+j];
4029 gamma+=(*k);
4030 k++;
4031 }
4032 }
cristyeaedf062010-05-29 22:36:02 +00004033 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00004034 }
4035 if (gamma != 0.0)
4036 {
4037 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4038 gamma);
cristy6db48122010-01-11 00:18:07 +00004039 blur_indexes[x]=ClampToQuantum(gamma*
4040 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004041 }
4042 }
4043 }
4044 p++;
4045 q++;
4046 }
4047 sync=SyncCacheViewAuthenticPixels(blur_view,exception);
4048 if (sync == MagickFalse)
4049 status=MagickFalse;
4050 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4051 {
4052 MagickBooleanType
4053 proceed;
4054
cristyb5d5f722009-11-04 03:03:49 +00004055#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004056 #pragma omp critical (MagickCore_SelectiveBlurImageChannel)
4057#endif
4058 proceed=SetImageProgress(image,SelectiveBlurImageTag,progress++,
4059 image->rows);
4060 if (proceed == MagickFalse)
4061 status=MagickFalse;
4062 }
4063 }
4064 blur_image->type=image->type;
4065 blur_view=DestroyCacheView(blur_view);
4066 image_view=DestroyCacheView(image_view);
4067 kernel=(double *) RelinquishMagickMemory(kernel);
4068 if (status == MagickFalse)
4069 blur_image=DestroyImage(blur_image);
4070 return(blur_image);
4071}
4072
4073/*
4074%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4075% %
4076% %
4077% %
4078% S h a d e I m a g e %
4079% %
4080% %
4081% %
4082%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4083%
4084% ShadeImage() shines a distant light on an image to create a
4085% three-dimensional effect. You control the positioning of the light with
4086% azimuth and elevation; azimuth is measured in degrees off the x axis
4087% and elevation is measured in pixels above the Z axis.
4088%
4089% The format of the ShadeImage method is:
4090%
4091% Image *ShadeImage(const Image *image,const MagickBooleanType gray,
4092% const double azimuth,const double elevation,ExceptionInfo *exception)
4093%
4094% A description of each parameter follows:
4095%
4096% o image: the image.
4097%
4098% o gray: A value other than zero shades the intensity of each pixel.
4099%
4100% o azimuth, elevation: Define the light source direction.
4101%
4102% o exception: return any errors or warnings in this structure.
4103%
4104*/
4105MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray,
4106 const double azimuth,const double elevation,ExceptionInfo *exception)
4107{
4108#define ShadeImageTag "Shade/Image"
4109
cristyc4c8d132010-01-07 01:58:38 +00004110 CacheView
4111 *image_view,
4112 *shade_view;
4113
cristy3ed852e2009-09-05 21:47:34 +00004114 Image
4115 *shade_image;
4116
cristy3ed852e2009-09-05 21:47:34 +00004117 MagickBooleanType
4118 status;
4119
cristybb503372010-05-27 20:51:26 +00004120 MagickOffsetType
4121 progress;
4122
cristy3ed852e2009-09-05 21:47:34 +00004123 PrimaryInfo
4124 light;
4125
cristybb503372010-05-27 20:51:26 +00004126 ssize_t
4127 y;
4128
cristy3ed852e2009-09-05 21:47:34 +00004129 /*
4130 Initialize shaded image attributes.
4131 */
4132 assert(image != (const 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 shade_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
4139 if (shade_image == (Image *) NULL)
4140 return((Image *) NULL);
4141 if (SetImageStorageClass(shade_image,DirectClass) == MagickFalse)
4142 {
4143 InheritException(exception,&shade_image->exception);
4144 shade_image=DestroyImage(shade_image);
4145 return((Image *) NULL);
4146 }
4147 /*
4148 Compute the light vector.
4149 */
4150 light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))*
4151 cos(DegreesToRadians(elevation));
4152 light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))*
4153 cos(DegreesToRadians(elevation));
4154 light.z=(double) QuantumRange*sin(DegreesToRadians(elevation));
4155 /*
4156 Shade image.
4157 */
4158 status=MagickTrue;
4159 progress=0;
4160 image_view=AcquireCacheView(image);
4161 shade_view=AcquireCacheView(shade_image);
cristyb5d5f722009-11-04 03:03:49 +00004162#if defined(MAGICKCORE_OPENMP_SUPPORT)
4163 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004164#endif
cristybb503372010-05-27 20:51:26 +00004165 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00004166 {
4167 MagickRealType
4168 distance,
4169 normal_distance,
4170 shade;
4171
4172 PrimaryInfo
4173 normal;
4174
4175 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004176 *restrict p,
4177 *restrict s0,
4178 *restrict s1,
4179 *restrict s2;
cristy3ed852e2009-09-05 21:47:34 +00004180
cristy3ed852e2009-09-05 21:47:34 +00004181 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004182 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004183
cristy117ff172010-08-15 21:35:32 +00004184 register ssize_t
4185 x;
4186
cristy3ed852e2009-09-05 21:47:34 +00004187 if (status == MagickFalse)
4188 continue;
4189 p=GetCacheViewVirtualPixels(image_view,-1,y-1,image->columns+2,3,exception);
4190 q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1,
4191 exception);
4192 if ((p == (PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
4193 {
4194 status=MagickFalse;
4195 continue;
4196 }
4197 /*
4198 Shade this row of pixels.
4199 */
4200 normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */
4201 s0=p+1;
4202 s1=s0+image->columns+2;
4203 s2=s1+image->columns+2;
cristybb503372010-05-27 20:51:26 +00004204 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004205 {
4206 /*
4207 Determine the surface normal and compute shading.
4208 */
4209 normal.x=(double) (PixelIntensity(s0-1)+PixelIntensity(s1-1)+
4210 PixelIntensity(s2-1)-PixelIntensity(s0+1)-PixelIntensity(s1+1)-
4211 PixelIntensity(s2+1));
4212 normal.y=(double) (PixelIntensity(s2-1)+PixelIntensity(s2)+
4213 PixelIntensity(s2+1)-PixelIntensity(s0-1)-PixelIntensity(s0)-
4214 PixelIntensity(s0+1));
4215 if ((normal.x == 0.0) && (normal.y == 0.0))
4216 shade=light.z;
4217 else
4218 {
4219 shade=0.0;
4220 distance=normal.x*light.x+normal.y*light.y+normal.z*light.z;
4221 if (distance > MagickEpsilon)
4222 {
4223 normal_distance=
4224 normal.x*normal.x+normal.y*normal.y+normal.z*normal.z;
4225 if (normal_distance > (MagickEpsilon*MagickEpsilon))
4226 shade=distance/sqrt((double) normal_distance);
4227 }
4228 }
4229 if (gray != MagickFalse)
4230 {
4231 q->red=(Quantum) shade;
4232 q->green=(Quantum) shade;
4233 q->blue=(Quantum) shade;
4234 }
4235 else
4236 {
cristyce70c172010-01-07 17:15:30 +00004237 q->red=ClampToQuantum(QuantumScale*shade*s1->red);
4238 q->green=ClampToQuantum(QuantumScale*shade*s1->green);
4239 q->blue=ClampToQuantum(QuantumScale*shade*s1->blue);
cristy3ed852e2009-09-05 21:47:34 +00004240 }
4241 q->opacity=s1->opacity;
4242 s0++;
4243 s1++;
4244 s2++;
4245 q++;
4246 }
4247 if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
4248 status=MagickFalse;
4249 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4250 {
4251 MagickBooleanType
4252 proceed;
4253
cristyb5d5f722009-11-04 03:03:49 +00004254#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004255 #pragma omp critical (MagickCore_ShadeImage)
4256#endif
4257 proceed=SetImageProgress(image,ShadeImageTag,progress++,image->rows);
4258 if (proceed == MagickFalse)
4259 status=MagickFalse;
4260 }
4261 }
4262 shade_view=DestroyCacheView(shade_view);
4263 image_view=DestroyCacheView(image_view);
4264 if (status == MagickFalse)
4265 shade_image=DestroyImage(shade_image);
4266 return(shade_image);
4267}
4268
4269/*
4270%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4271% %
4272% %
4273% %
4274% S h a r p e n I m a g e %
4275% %
4276% %
4277% %
4278%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4279%
4280% SharpenImage() sharpens the image. We convolve the image with a Gaussian
4281% operator of the given radius and standard deviation (sigma). For
4282% reasonable results, radius should be larger than sigma. Use a radius of 0
4283% and SharpenImage() selects a suitable radius for you.
4284%
4285% Using a separable kernel would be faster, but the negative weights cancel
4286% out on the corners of the kernel producing often undesirable ringing in the
4287% filtered result; this can be avoided by using a 2D gaussian shaped image
4288% sharpening kernel instead.
4289%
4290% The format of the SharpenImage method is:
4291%
4292% Image *SharpenImage(const Image *image,const double radius,
4293% const double sigma,ExceptionInfo *exception)
4294% Image *SharpenImageChannel(const Image *image,const ChannelType channel,
4295% const double radius,const double sigma,ExceptionInfo *exception)
4296%
4297% A description of each parameter follows:
4298%
4299% o image: the image.
4300%
4301% o channel: the channel type.
4302%
4303% o radius: the radius of the Gaussian, in pixels, not counting the center
4304% pixel.
4305%
4306% o sigma: the standard deviation of the Laplacian, in pixels.
4307%
4308% o exception: return any errors or warnings in this structure.
4309%
4310*/
4311
4312MagickExport Image *SharpenImage(const Image *image,const double radius,
4313 const double sigma,ExceptionInfo *exception)
4314{
4315 Image
4316 *sharp_image;
4317
4318 sharp_image=SharpenImageChannel(image,DefaultChannels,radius,sigma,exception);
4319 return(sharp_image);
4320}
4321
4322MagickExport Image *SharpenImageChannel(const Image *image,
4323 const ChannelType channel,const double radius,const double sigma,
4324 ExceptionInfo *exception)
4325{
4326 double
cristy47e00502009-12-17 19:19:57 +00004327 *kernel,
4328 normalize;
cristy3ed852e2009-09-05 21:47:34 +00004329
4330 Image
4331 *sharp_image;
4332
cristybb503372010-05-27 20:51:26 +00004333 register ssize_t
cristy47e00502009-12-17 19:19:57 +00004334 i;
4335
cristybb503372010-05-27 20:51:26 +00004336 size_t
cristy3ed852e2009-09-05 21:47:34 +00004337 width;
4338
cristy117ff172010-08-15 21:35:32 +00004339 ssize_t
4340 j,
4341 u,
4342 v;
4343
cristy3ed852e2009-09-05 21:47:34 +00004344 assert(image != (const Image *) NULL);
4345 assert(image->signature == MagickSignature);
4346 if (image->debug != MagickFalse)
4347 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4348 assert(exception != (ExceptionInfo *) NULL);
4349 assert(exception->signature == MagickSignature);
4350 width=GetOptimalKernelWidth2D(radius,sigma);
4351 kernel=(double *) AcquireQuantumMemory((size_t) width*width,sizeof(*kernel));
4352 if (kernel == (double *) NULL)
4353 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy3ed852e2009-09-05 21:47:34 +00004354 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00004355 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +00004356 i=0;
4357 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00004358 {
cristy47e00502009-12-17 19:19:57 +00004359 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00004360 {
cristy4205a3c2010-09-12 20:19:59 +00004361 kernel[i]=(double) (-exp(-((double) u*u+v*v)/(2.0*MagickSigma*
4362 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00004363 normalize+=kernel[i];
4364 i++;
4365 }
4366 }
4367 kernel[i/2]=(double) ((-2.0)*normalize);
4368 sharp_image=ConvolveImageChannel(image,channel,width,kernel,exception);
4369 kernel=(double *) RelinquishMagickMemory(kernel);
4370 return(sharp_image);
4371}
4372
4373/*
4374%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4375% %
4376% %
4377% %
4378% S p r e a d I m a g e %
4379% %
4380% %
4381% %
4382%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4383%
4384% SpreadImage() is a special effects method that randomly displaces each
4385% pixel in a block defined by the radius parameter.
4386%
4387% The format of the SpreadImage method is:
4388%
4389% Image *SpreadImage(const Image *image,const double radius,
4390% ExceptionInfo *exception)
4391%
4392% A description of each parameter follows:
4393%
4394% o image: the image.
4395%
4396% o radius: Choose a random pixel in a neighborhood of this extent.
4397%
4398% o exception: return any errors or warnings in this structure.
4399%
4400*/
4401MagickExport Image *SpreadImage(const Image *image,const double radius,
4402 ExceptionInfo *exception)
4403{
4404#define SpreadImageTag "Spread/Image"
4405
cristyfa112112010-01-04 17:48:07 +00004406 CacheView
cristy9f7e7cb2011-03-26 00:49:57 +00004407 *image_view,
4408 *spread_view;
cristyfa112112010-01-04 17:48:07 +00004409
cristy3ed852e2009-09-05 21:47:34 +00004410 Image
4411 *spread_image;
4412
cristy3ed852e2009-09-05 21:47:34 +00004413 MagickBooleanType
4414 status;
4415
cristybb503372010-05-27 20:51:26 +00004416 MagickOffsetType
4417 progress;
4418
cristy3ed852e2009-09-05 21:47:34 +00004419 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00004420 bias;
cristy3ed852e2009-09-05 21:47:34 +00004421
4422 RandomInfo
cristyfa112112010-01-04 17:48:07 +00004423 **restrict random_info;
cristy3ed852e2009-09-05 21:47:34 +00004424
cristybb503372010-05-27 20:51:26 +00004425 size_t
cristy3ed852e2009-09-05 21:47:34 +00004426 width;
4427
cristybb503372010-05-27 20:51:26 +00004428 ssize_t
4429 y;
4430
cristy3ed852e2009-09-05 21:47:34 +00004431 /*
4432 Initialize spread image attributes.
4433 */
4434 assert(image != (Image *) NULL);
4435 assert(image->signature == MagickSignature);
4436 if (image->debug != MagickFalse)
4437 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4438 assert(exception != (ExceptionInfo *) NULL);
4439 assert(exception->signature == MagickSignature);
4440 spread_image=CloneImage(image,image->columns,image->rows,MagickTrue,
4441 exception);
4442 if (spread_image == (Image *) NULL)
4443 return((Image *) NULL);
4444 if (SetImageStorageClass(spread_image,DirectClass) == MagickFalse)
4445 {
4446 InheritException(exception,&spread_image->exception);
4447 spread_image=DestroyImage(spread_image);
4448 return((Image *) NULL);
4449 }
4450 /*
4451 Spread image.
4452 */
4453 status=MagickTrue;
4454 progress=0;
cristyddd82202009-11-03 20:14:50 +00004455 GetMagickPixelPacket(spread_image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00004456 width=GetOptimalKernelWidth1D(radius,0.5);
cristy3ed852e2009-09-05 21:47:34 +00004457 random_info=AcquireRandomInfoThreadSet();
cristy9f7e7cb2011-03-26 00:49:57 +00004458 image_view=AcquireCacheView(image);
4459 spread_view=AcquireCacheView(spread_image);
cristyb557a152011-02-22 12:14:30 +00004460#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00004461 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00004462#endif
cristybb503372010-05-27 20:51:26 +00004463 for (y=0; y < (ssize_t) spread_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00004464 {
cristy5c9e6f22010-09-17 17:31:01 +00004465 const int
4466 id = GetOpenMPThreadId();
cristy6ebe97c2010-07-03 01:17:28 +00004467
cristy3ed852e2009-09-05 21:47:34 +00004468 MagickPixelPacket
4469 pixel;
4470
4471 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004472 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00004473
cristy3ed852e2009-09-05 21:47:34 +00004474 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004475 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004476
cristy117ff172010-08-15 21:35:32 +00004477 register ssize_t
4478 x;
4479
cristy3ed852e2009-09-05 21:47:34 +00004480 if (status == MagickFalse)
4481 continue;
cristy9f7e7cb2011-03-26 00:49:57 +00004482 q=QueueCacheViewAuthenticPixels(spread_view,0,y,spread_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +00004483 exception);
4484 if (q == (PixelPacket *) NULL)
4485 {
4486 status=MagickFalse;
4487 continue;
4488 }
cristy9f7e7cb2011-03-26 00:49:57 +00004489 indexes=GetCacheViewAuthenticIndexQueue(spread_view);
cristyddd82202009-11-03 20:14:50 +00004490 pixel=bias;
cristybb503372010-05-27 20:51:26 +00004491 for (x=0; x < (ssize_t) spread_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004492 {
cristy8a7c3e82011-03-26 02:10:53 +00004493 (void) InterpolateMagickPixelPacket(image,image_view,
4494 UndefinedInterpolatePixel,(double) x+width*(GetPseudoRandomValue(
4495 random_info[id])-0.5),(double) y+width*(GetPseudoRandomValue(
4496 random_info[id])-0.5),&pixel,exception);
cristy3ed852e2009-09-05 21:47:34 +00004497 SetPixelPacket(spread_image,&pixel,q,indexes+x);
4498 q++;
4499 }
cristy9f7e7cb2011-03-26 00:49:57 +00004500 if (SyncCacheViewAuthenticPixels(spread_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00004501 status=MagickFalse;
4502 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4503 {
4504 MagickBooleanType
4505 proceed;
4506
cristyb557a152011-02-22 12:14:30 +00004507#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004508 #pragma omp critical (MagickCore_SpreadImage)
4509#endif
4510 proceed=SetImageProgress(image,SpreadImageTag,progress++,image->rows);
4511 if (proceed == MagickFalse)
4512 status=MagickFalse;
4513 }
4514 }
cristy9f7e7cb2011-03-26 00:49:57 +00004515 spread_view=DestroyCacheView(spread_view);
cristy3ed852e2009-09-05 21:47:34 +00004516 image_view=DestroyCacheView(image_view);
4517 random_info=DestroyRandomInfoThreadSet(random_info);
cristy3ed852e2009-09-05 21:47:34 +00004518 return(spread_image);
4519}
4520
4521/*
4522%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4523% %
4524% %
4525% %
cristy0834d642011-03-18 18:26:08 +00004526% S t a t i s t i c I m a g e %
4527% %
4528% %
4529% %
4530%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4531%
4532% StatisticImage() makes each pixel the min / max / median / mode / etc. of
cristy8d752042011-03-19 01:00:36 +00004533% the neighborhood of the specified width and height.
cristy0834d642011-03-18 18:26:08 +00004534%
4535% The format of the StatisticImage method is:
4536%
4537% Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00004538% const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00004539% Image *StatisticImageChannel(const Image *image,
4540% const ChannelType channel,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00004541% const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00004542%
4543% A description of each parameter follows:
4544%
4545% o image: the image.
4546%
4547% o channel: the image channel.
4548%
4549% o type: the statistic type (median, mode, etc.).
4550%
cristy95c38342011-03-18 22:39:51 +00004551% o width: the width of the pixel neighborhood.
4552%
4553% o height: the height of the pixel neighborhood.
cristy0834d642011-03-18 18:26:08 +00004554%
4555% o exception: return any errors or warnings in this structure.
4556%
4557*/
4558
cristy733678d2011-03-18 21:29:28 +00004559#define ListChannels 5
4560
4561typedef struct _ListNode
4562{
4563 size_t
4564 next[9],
4565 count,
4566 signature;
4567} ListNode;
4568
4569typedef struct _SkipList
4570{
4571 ssize_t
4572 level;
4573
4574 ListNode
4575 *nodes;
4576} SkipList;
4577
4578typedef struct _PixelList
4579{
4580 size_t
cristy6fc86bb2011-03-18 23:45:16 +00004581 length,
cristy733678d2011-03-18 21:29:28 +00004582 seed,
4583 signature;
4584
4585 SkipList
4586 lists[ListChannels];
4587} PixelList;
4588
4589static PixelList *DestroyPixelList(PixelList *pixel_list)
4590{
4591 register ssize_t
4592 i;
4593
4594 if (pixel_list == (PixelList *) NULL)
4595 return((PixelList *) NULL);
4596 for (i=0; i < ListChannels; i++)
4597 if (pixel_list->lists[i].nodes != (ListNode *) NULL)
4598 pixel_list->lists[i].nodes=(ListNode *) RelinquishMagickMemory(
4599 pixel_list->lists[i].nodes);
4600 pixel_list=(PixelList *) RelinquishMagickMemory(pixel_list);
4601 return(pixel_list);
4602}
4603
4604static PixelList **DestroyPixelListThreadSet(PixelList **pixel_list)
4605{
4606 register ssize_t
4607 i;
4608
4609 assert(pixel_list != (PixelList **) NULL);
4610 for (i=0; i < (ssize_t) GetOpenMPMaximumThreads(); i++)
4611 if (pixel_list[i] != (PixelList *) NULL)
4612 pixel_list[i]=DestroyPixelList(pixel_list[i]);
4613 pixel_list=(PixelList **) RelinquishMagickMemory(pixel_list);
4614 return(pixel_list);
4615}
4616
cristy6fc86bb2011-03-18 23:45:16 +00004617static PixelList *AcquirePixelList(const size_t width,const size_t height)
cristy733678d2011-03-18 21:29:28 +00004618{
4619 PixelList
4620 *pixel_list;
4621
4622 register ssize_t
4623 i;
4624
4625 pixel_list=(PixelList *) AcquireMagickMemory(sizeof(*pixel_list));
4626 if (pixel_list == (PixelList *) NULL)
4627 return(pixel_list);
4628 (void) ResetMagickMemory((void *) pixel_list,0,sizeof(*pixel_list));
cristy6fc86bb2011-03-18 23:45:16 +00004629 pixel_list->length=width*height;
cristy733678d2011-03-18 21:29:28 +00004630 for (i=0; i < ListChannels; i++)
4631 {
4632 pixel_list->lists[i].nodes=(ListNode *) AcquireQuantumMemory(65537UL,
4633 sizeof(*pixel_list->lists[i].nodes));
4634 if (pixel_list->lists[i].nodes == (ListNode *) NULL)
4635 return(DestroyPixelList(pixel_list));
4636 (void) ResetMagickMemory(pixel_list->lists[i].nodes,0,65537UL*
4637 sizeof(*pixel_list->lists[i].nodes));
4638 }
4639 pixel_list->signature=MagickSignature;
4640 return(pixel_list);
4641}
4642
cristy6fc86bb2011-03-18 23:45:16 +00004643static PixelList **AcquirePixelListThreadSet(const size_t width,
4644 const size_t height)
cristy733678d2011-03-18 21:29:28 +00004645{
4646 PixelList
4647 **pixel_list;
4648
4649 register ssize_t
4650 i;
4651
4652 size_t
4653 number_threads;
4654
4655 number_threads=GetOpenMPMaximumThreads();
4656 pixel_list=(PixelList **) AcquireQuantumMemory(number_threads,
4657 sizeof(*pixel_list));
4658 if (pixel_list == (PixelList **) NULL)
4659 return((PixelList **) NULL);
4660 (void) ResetMagickMemory(pixel_list,0,number_threads*sizeof(*pixel_list));
4661 for (i=0; i < (ssize_t) number_threads; i++)
4662 {
cristy6fc86bb2011-03-18 23:45:16 +00004663 pixel_list[i]=AcquirePixelList(width,height);
cristy733678d2011-03-18 21:29:28 +00004664 if (pixel_list[i] == (PixelList *) NULL)
4665 return(DestroyPixelListThreadSet(pixel_list));
4666 }
4667 return(pixel_list);
4668}
4669
4670static void AddNodePixelList(PixelList *pixel_list,const ssize_t channel,
4671 const size_t color)
4672{
4673 register SkipList
4674 *list;
4675
4676 register ssize_t
4677 level;
4678
4679 size_t
4680 search,
4681 update[9];
4682
4683 /*
4684 Initialize the node.
4685 */
4686 list=pixel_list->lists+channel;
4687 list->nodes[color].signature=pixel_list->signature;
4688 list->nodes[color].count=1;
4689 /*
4690 Determine where it belongs in the list.
4691 */
4692 search=65536UL;
4693 for (level=list->level; level >= 0; level--)
4694 {
4695 while (list->nodes[search].next[level] < color)
4696 search=list->nodes[search].next[level];
4697 update[level]=search;
4698 }
4699 /*
4700 Generate a pseudo-random level for this node.
4701 */
4702 for (level=0; ; level++)
4703 {
4704 pixel_list->seed=(pixel_list->seed*42893621L)+1L;
4705 if ((pixel_list->seed & 0x300) != 0x300)
4706 break;
4707 }
4708 if (level > 8)
4709 level=8;
4710 if (level > (list->level+2))
4711 level=list->level+2;
4712 /*
4713 If we're raising the list's level, link back to the root node.
4714 */
4715 while (level > list->level)
4716 {
4717 list->level++;
4718 update[list->level]=65536UL;
4719 }
4720 /*
4721 Link the node into the skip-list.
4722 */
4723 do
4724 {
4725 list->nodes[color].next[level]=list->nodes[update[level]].next[level];
4726 list->nodes[update[level]].next[level]=color;
cristy3cba8ca2011-03-19 01:29:12 +00004727 } while (level-- > 0);
cristy733678d2011-03-18 21:29:28 +00004728}
4729
cristy6fc86bb2011-03-18 23:45:16 +00004730static MagickPixelPacket GetMaximumPixelList(PixelList *pixel_list)
4731{
4732 MagickPixelPacket
4733 pixel;
4734
4735 register SkipList
4736 *list;
4737
4738 register ssize_t
4739 channel;
4740
4741 size_t
cristyd76c51e2011-03-26 00:21:26 +00004742 color,
4743 maximum;
cristy49f37242011-03-22 18:18:23 +00004744
4745 ssize_t
cristy6fc86bb2011-03-18 23:45:16 +00004746 count;
4747
4748 unsigned short
cristyd76c51e2011-03-26 00:21:26 +00004749 channels[ListChannels];
cristy6fc86bb2011-03-18 23:45:16 +00004750
4751 /*
4752 Find the maximum value for each of the color.
4753 */
4754 for (channel=0; channel < 5; channel++)
4755 {
4756 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004757 color=65536L;
cristy6fc86bb2011-03-18 23:45:16 +00004758 count=0;
cristy49f37242011-03-22 18:18:23 +00004759 maximum=list->nodes[color].next[0];
cristy6fc86bb2011-03-18 23:45:16 +00004760 do
4761 {
4762 color=list->nodes[color].next[0];
cristy49f37242011-03-22 18:18:23 +00004763 if (color > maximum)
4764 maximum=color;
cristy6fc86bb2011-03-18 23:45:16 +00004765 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004766 } while (count < (ssize_t) pixel_list->length);
cristy49f37242011-03-22 18:18:23 +00004767 channels[channel]=(unsigned short) maximum;
4768 }
4769 GetMagickPixelPacket((const Image *) NULL,&pixel);
4770 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4771 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4772 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
4773 pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
4774 pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]);
4775 return(pixel);
4776}
4777
4778static MagickPixelPacket GetMeanPixelList(PixelList *pixel_list)
4779{
4780 MagickPixelPacket
4781 pixel;
4782
cristy80a99a32011-03-30 01:30:23 +00004783 MagickRealType
4784 sum;
4785
cristy49f37242011-03-22 18:18:23 +00004786 register SkipList
4787 *list;
4788
4789 register ssize_t
4790 channel;
4791
4792 size_t
cristy80a99a32011-03-30 01:30:23 +00004793 color;
cristy49f37242011-03-22 18:18:23 +00004794
4795 ssize_t
4796 count;
4797
4798 unsigned short
4799 channels[ListChannels];
4800
4801 /*
4802 Find the mean value for each of the color.
4803 */
4804 for (channel=0; channel < 5; channel++)
4805 {
4806 list=pixel_list->lists+channel;
4807 color=65536L;
4808 count=0;
cristy80a99a32011-03-30 01:30:23 +00004809 sum=0.0;
cristy49f37242011-03-22 18:18:23 +00004810 do
4811 {
4812 color=list->nodes[color].next[0];
cristy80a99a32011-03-30 01:30:23 +00004813 sum+=(MagickRealType) list->nodes[color].count*color;
cristy49f37242011-03-22 18:18:23 +00004814 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004815 } while (count < (ssize_t) pixel_list->length);
cristy80a99a32011-03-30 01:30:23 +00004816 sum/=pixel_list->length;
4817 channels[channel]=(unsigned short) sum;
cristy6fc86bb2011-03-18 23:45:16 +00004818 }
4819 GetMagickPixelPacket((const Image *) NULL,&pixel);
4820 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4821 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4822 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
4823 pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
4824 pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]);
4825 return(pixel);
4826}
4827
cristy733678d2011-03-18 21:29:28 +00004828static MagickPixelPacket GetMedianPixelList(PixelList *pixel_list)
4829{
4830 MagickPixelPacket
4831 pixel;
4832
4833 register SkipList
4834 *list;
4835
4836 register ssize_t
4837 channel;
4838
4839 size_t
cristy49f37242011-03-22 18:18:23 +00004840 color;
4841
4842 ssize_t
cristy733678d2011-03-18 21:29:28 +00004843 count;
4844
4845 unsigned short
4846 channels[ListChannels];
4847
4848 /*
4849 Find the median value for each of the color.
4850 */
cristy733678d2011-03-18 21:29:28 +00004851 for (channel=0; channel < 5; channel++)
4852 {
4853 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004854 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004855 count=0;
4856 do
4857 {
4858 color=list->nodes[color].next[0];
4859 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004860 } while (count <= (ssize_t) (pixel_list->length >> 1));
cristy6fc86bb2011-03-18 23:45:16 +00004861 channels[channel]=(unsigned short) color;
4862 }
4863 GetMagickPixelPacket((const Image *) NULL,&pixel);
4864 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4865 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4866 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
4867 pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
4868 pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]);
4869 return(pixel);
4870}
4871
4872static MagickPixelPacket GetMinimumPixelList(PixelList *pixel_list)
4873{
4874 MagickPixelPacket
4875 pixel;
4876
4877 register SkipList
4878 *list;
4879
4880 register ssize_t
4881 channel;
4882
4883 size_t
cristyd76c51e2011-03-26 00:21:26 +00004884 color,
4885 minimum;
cristy6fc86bb2011-03-18 23:45:16 +00004886
cristy49f37242011-03-22 18:18:23 +00004887 ssize_t
4888 count;
4889
cristy6fc86bb2011-03-18 23:45:16 +00004890 unsigned short
cristyd76c51e2011-03-26 00:21:26 +00004891 channels[ListChannels];
cristy6fc86bb2011-03-18 23:45:16 +00004892
4893 /*
4894 Find the minimum value for each of the color.
4895 */
4896 for (channel=0; channel < 5; channel++)
4897 {
4898 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004899 count=0;
cristy6fc86bb2011-03-18 23:45:16 +00004900 color=65536UL;
cristy49f37242011-03-22 18:18:23 +00004901 minimum=list->nodes[color].next[0];
4902 do
4903 {
4904 color=list->nodes[color].next[0];
4905 if (color < minimum)
4906 minimum=color;
4907 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004908 } while (count < (ssize_t) pixel_list->length);
cristy49f37242011-03-22 18:18:23 +00004909 channels[channel]=(unsigned short) minimum;
cristy733678d2011-03-18 21:29:28 +00004910 }
4911 GetMagickPixelPacket((const Image *) NULL,&pixel);
4912 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4913 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4914 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
4915 pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
4916 pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]);
4917 return(pixel);
4918}
4919
4920static MagickPixelPacket GetModePixelList(PixelList *pixel_list)
4921{
4922 MagickPixelPacket
4923 pixel;
4924
4925 register SkipList
4926 *list;
4927
4928 register ssize_t
4929 channel;
4930
4931 size_t
4932 color,
cristy733678d2011-03-18 21:29:28 +00004933 max_count,
cristy6fc86bb2011-03-18 23:45:16 +00004934 mode;
cristy733678d2011-03-18 21:29:28 +00004935
cristy49f37242011-03-22 18:18:23 +00004936 ssize_t
4937 count;
4938
cristy733678d2011-03-18 21:29:28 +00004939 unsigned short
4940 channels[5];
4941
4942 /*
4943 Make each pixel the 'predominate color' of the specified neighborhood.
4944 */
cristy733678d2011-03-18 21:29:28 +00004945 for (channel=0; channel < 5; channel++)
4946 {
4947 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004948 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004949 mode=color;
4950 max_count=list->nodes[mode].count;
4951 count=0;
4952 do
4953 {
4954 color=list->nodes[color].next[0];
4955 if (list->nodes[color].count > max_count)
4956 {
4957 mode=color;
4958 max_count=list->nodes[mode].count;
4959 }
4960 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004961 } while (count < (ssize_t) pixel_list->length);
cristy733678d2011-03-18 21:29:28 +00004962 channels[channel]=(unsigned short) mode;
4963 }
4964 GetMagickPixelPacket((const Image *) NULL,&pixel);
4965 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4966 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4967 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
4968 pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
4969 pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]);
4970 return(pixel);
4971}
4972
4973static MagickPixelPacket GetNonpeakPixelList(PixelList *pixel_list)
4974{
4975 MagickPixelPacket
4976 pixel;
4977
4978 register SkipList
4979 *list;
4980
4981 register ssize_t
4982 channel;
4983
4984 size_t
cristy733678d2011-03-18 21:29:28 +00004985 color,
cristy733678d2011-03-18 21:29:28 +00004986 next,
4987 previous;
4988
cristy49f37242011-03-22 18:18:23 +00004989 ssize_t
4990 count;
4991
cristy733678d2011-03-18 21:29:28 +00004992 unsigned short
4993 channels[5];
4994
4995 /*
cristy49f37242011-03-22 18:18:23 +00004996 Finds the non peak value for each of the colors.
cristy733678d2011-03-18 21:29:28 +00004997 */
cristy733678d2011-03-18 21:29:28 +00004998 for (channel=0; channel < 5; channel++)
4999 {
5000 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00005001 color=65536L;
cristy733678d2011-03-18 21:29:28 +00005002 next=list->nodes[color].next[0];
5003 count=0;
5004 do
5005 {
5006 previous=color;
5007 color=next;
5008 next=list->nodes[color].next[0];
5009 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00005010 } while (count <= (ssize_t) (pixel_list->length >> 1));
cristy733678d2011-03-18 21:29:28 +00005011 if ((previous == 65536UL) && (next != 65536UL))
5012 color=next;
5013 else
5014 if ((previous != 65536UL) && (next == 65536UL))
5015 color=previous;
5016 channels[channel]=(unsigned short) color;
5017 }
5018 GetMagickPixelPacket((const Image *) NULL,&pixel);
5019 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
5020 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
5021 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
5022 pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
5023 pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]);
5024 return(pixel);
5025}
5026
cristy9a68cbb2011-03-29 00:51:23 +00005027static MagickPixelPacket GetStandardDeviationPixelList(PixelList *pixel_list)
5028{
5029 MagickPixelPacket
5030 pixel;
5031
cristy80a99a32011-03-30 01:30:23 +00005032 MagickRealType
5033 sum,
5034 sum_squared;
5035
cristy9a68cbb2011-03-29 00:51:23 +00005036 register SkipList
5037 *list;
5038
5039 register ssize_t
5040 channel;
5041
5042 size_t
cristy80a99a32011-03-30 01:30:23 +00005043 color;
cristy9a68cbb2011-03-29 00:51:23 +00005044
5045 ssize_t
5046 count;
5047
5048 unsigned short
5049 channels[ListChannels];
5050
5051 /*
cristy80a99a32011-03-30 01:30:23 +00005052 Find the standard-deviation value for each of the color.
cristy9a68cbb2011-03-29 00:51:23 +00005053 */
5054 for (channel=0; channel < 5; channel++)
5055 {
5056 list=pixel_list->lists+channel;
5057 color=65536L;
5058 count=0;
cristy80a99a32011-03-30 01:30:23 +00005059 sum=0.0;
5060 sum_squared=0.0;
cristy9a68cbb2011-03-29 00:51:23 +00005061 do
5062 {
cristy80a99a32011-03-30 01:30:23 +00005063 register ssize_t
5064 i;
5065
cristy9a68cbb2011-03-29 00:51:23 +00005066 color=list->nodes[color].next[0];
cristy80a99a32011-03-30 01:30:23 +00005067 sum+=(MagickRealType) list->nodes[color].count*color;
5068 for (i=0; i < (ssize_t) list->nodes[color].count; i++)
5069 sum_squared+=((MagickRealType) color)*((MagickRealType) color);
cristy9a68cbb2011-03-29 00:51:23 +00005070 count+=list->nodes[color].count;
5071 } while (count < (ssize_t) pixel_list->length);
cristy80a99a32011-03-30 01:30:23 +00005072 sum/=pixel_list->length;
5073 sum_squared/=pixel_list->length;
5074 channels[channel]=(unsigned short) sqrt(sum_squared-(sum*sum));
cristy9a68cbb2011-03-29 00:51:23 +00005075 }
5076 GetMagickPixelPacket((const Image *) NULL,&pixel);
5077 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
5078 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
5079 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
5080 pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
5081 pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]);
5082 return(pixel);
5083}
5084
cristy733678d2011-03-18 21:29:28 +00005085static inline void InsertPixelList(const Image *image,const PixelPacket *pixel,
5086 const IndexPacket *indexes,PixelList *pixel_list)
5087{
5088 size_t
5089 signature;
5090
5091 unsigned short
5092 index;
5093
5094 index=ScaleQuantumToShort(pixel->red);
5095 signature=pixel_list->lists[0].nodes[index].signature;
5096 if (signature == pixel_list->signature)
5097 pixel_list->lists[0].nodes[index].count++;
5098 else
5099 AddNodePixelList(pixel_list,0,index);
5100 index=ScaleQuantumToShort(pixel->green);
5101 signature=pixel_list->lists[1].nodes[index].signature;
5102 if (signature == pixel_list->signature)
5103 pixel_list->lists[1].nodes[index].count++;
5104 else
5105 AddNodePixelList(pixel_list,1,index);
5106 index=ScaleQuantumToShort(pixel->blue);
5107 signature=pixel_list->lists[2].nodes[index].signature;
5108 if (signature == pixel_list->signature)
5109 pixel_list->lists[2].nodes[index].count++;
5110 else
5111 AddNodePixelList(pixel_list,2,index);
5112 index=ScaleQuantumToShort(pixel->opacity);
5113 signature=pixel_list->lists[3].nodes[index].signature;
5114 if (signature == pixel_list->signature)
5115 pixel_list->lists[3].nodes[index].count++;
5116 else
5117 AddNodePixelList(pixel_list,3,index);
5118 if (image->colorspace == CMYKColorspace)
5119 index=ScaleQuantumToShort(*indexes);
5120 signature=pixel_list->lists[4].nodes[index].signature;
5121 if (signature == pixel_list->signature)
5122 pixel_list->lists[4].nodes[index].count++;
5123 else
5124 AddNodePixelList(pixel_list,4,index);
5125}
5126
cristy80c99742011-04-04 14:46:39 +00005127static inline MagickRealType MagickAbsoluteValue(const MagickRealType x)
5128{
5129 if (x < 0)
5130 return(-x);
5131 return(x);
5132}
5133
cristy733678d2011-03-18 21:29:28 +00005134static void ResetPixelList(PixelList *pixel_list)
5135{
5136 int
5137 level;
5138
5139 register ListNode
5140 *root;
5141
5142 register SkipList
5143 *list;
5144
5145 register ssize_t
5146 channel;
5147
5148 /*
5149 Reset the skip-list.
5150 */
5151 for (channel=0; channel < 5; channel++)
5152 {
5153 list=pixel_list->lists+channel;
5154 root=list->nodes+65536UL;
5155 list->level=0;
5156 for (level=0; level < 9; level++)
5157 root->next[level]=65536UL;
5158 }
5159 pixel_list->seed=pixel_list->signature++;
5160}
5161
cristy0834d642011-03-18 18:26:08 +00005162MagickExport Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00005163 const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00005164{
cristy95c38342011-03-18 22:39:51 +00005165 Image
5166 *statistic_image;
5167
5168 statistic_image=StatisticImageChannel(image,DefaultChannels,type,width,
5169 height,exception);
5170 return(statistic_image);
cristy0834d642011-03-18 18:26:08 +00005171}
5172
5173MagickExport Image *StatisticImageChannel(const Image *image,
cristy95c38342011-03-18 22:39:51 +00005174 const ChannelType channel,const StatisticType type,const size_t width,
5175 const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00005176{
cristy3cba8ca2011-03-19 01:29:12 +00005177#define StatisticWidth \
cristyd76c51e2011-03-26 00:21:26 +00005178 (width == 0 ? GetOptimalKernelWidth2D((double) width,0.5) : width)
cristy3cba8ca2011-03-19 01:29:12 +00005179#define StatisticHeight \
cristyd76c51e2011-03-26 00:21:26 +00005180 (height == 0 ? GetOptimalKernelWidth2D((double) height,0.5) : height)
cristy0834d642011-03-18 18:26:08 +00005181#define StatisticImageTag "Statistic/Image"
5182
5183 CacheView
5184 *image_view,
5185 *statistic_view;
5186
5187 Image
5188 *statistic_image;
5189
5190 MagickBooleanType
5191 status;
5192
5193 MagickOffsetType
5194 progress;
5195
5196 PixelList
5197 **restrict pixel_list;
5198
cristy0834d642011-03-18 18:26:08 +00005199 ssize_t
5200 y;
5201
5202 /*
5203 Initialize statistics image attributes.
5204 */
5205 assert(image != (Image *) NULL);
5206 assert(image->signature == MagickSignature);
5207 if (image->debug != MagickFalse)
5208 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5209 assert(exception != (ExceptionInfo *) NULL);
5210 assert(exception->signature == MagickSignature);
cristy0834d642011-03-18 18:26:08 +00005211 statistic_image=CloneImage(image,image->columns,image->rows,MagickTrue,
5212 exception);
5213 if (statistic_image == (Image *) NULL)
5214 return((Image *) NULL);
5215 if (SetImageStorageClass(statistic_image,DirectClass) == MagickFalse)
5216 {
5217 InheritException(exception,&statistic_image->exception);
5218 statistic_image=DestroyImage(statistic_image);
5219 return((Image *) NULL);
5220 }
cristy6fc86bb2011-03-18 23:45:16 +00005221 pixel_list=AcquirePixelListThreadSet(StatisticWidth,StatisticHeight);
cristy0834d642011-03-18 18:26:08 +00005222 if (pixel_list == (PixelList **) NULL)
5223 {
5224 statistic_image=DestroyImage(statistic_image);
5225 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
5226 }
5227 /*
cristy8d752042011-03-19 01:00:36 +00005228 Make each pixel the min / max / median / mode / etc. of the neighborhood.
cristy0834d642011-03-18 18:26:08 +00005229 */
5230 status=MagickTrue;
5231 progress=0;
5232 image_view=AcquireCacheView(image);
5233 statistic_view=AcquireCacheView(statistic_image);
5234#if defined(MAGICKCORE_OPENMP_SUPPORT)
5235 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
5236#endif
5237 for (y=0; y < (ssize_t) statistic_image->rows; y++)
5238 {
5239 const int
5240 id = GetOpenMPThreadId();
5241
5242 register const IndexPacket
5243 *restrict indexes;
5244
5245 register const PixelPacket
5246 *restrict p;
5247
5248 register IndexPacket
5249 *restrict statistic_indexes;
5250
5251 register PixelPacket
5252 *restrict q;
5253
5254 register ssize_t
5255 x;
5256
5257 if (status == MagickFalse)
5258 continue;
cristy6fc86bb2011-03-18 23:45:16 +00005259 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) StatisticWidth/2L),y-
5260 (ssize_t) (StatisticHeight/2L),image->columns+StatisticWidth,
5261 StatisticHeight,exception);
cristy3cba8ca2011-03-19 01:29:12 +00005262 q=QueueCacheViewAuthenticPixels(statistic_view,0,y,statistic_image->columns, 1,exception);
cristy0834d642011-03-18 18:26:08 +00005263 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
5264 {
5265 status=MagickFalse;
5266 continue;
5267 }
5268 indexes=GetCacheViewVirtualIndexQueue(image_view);
5269 statistic_indexes=GetCacheViewAuthenticIndexQueue(statistic_view);
5270 for (x=0; x < (ssize_t) statistic_image->columns; x++)
5271 {
5272 MagickPixelPacket
5273 pixel;
5274
cristy0834d642011-03-18 18:26:08 +00005275 register const IndexPacket
5276 *restrict s;
5277
cristy6e3026a2011-03-19 00:54:38 +00005278 register const PixelPacket
5279 *restrict r;
5280
cristy0834d642011-03-18 18:26:08 +00005281 register ssize_t
5282 u,
5283 v;
5284
5285 r=p;
5286 s=indexes+x;
5287 ResetPixelList(pixel_list[id]);
cristy6e4c3292011-03-19 00:53:55 +00005288 for (v=0; v < (ssize_t) StatisticHeight; v++)
cristy0834d642011-03-18 18:26:08 +00005289 {
cristy6e4c3292011-03-19 00:53:55 +00005290 for (u=0; u < (ssize_t) StatisticWidth; u++)
cristy0834d642011-03-18 18:26:08 +00005291 InsertPixelList(image,r+u,s+u,pixel_list[id]);
cristy6fc86bb2011-03-18 23:45:16 +00005292 r+=image->columns+StatisticWidth;
cristy6e4c3292011-03-19 00:53:55 +00005293 s+=image->columns+StatisticWidth;
cristy0834d642011-03-18 18:26:08 +00005294 }
cristy80c99742011-04-04 14:46:39 +00005295 GetMagickPixelPacket(image,&pixel);
5296 SetMagickPixelPacket(image,p+StatisticWidth*StatisticHeight/2,indexes+
5297 StatisticWidth*StatisticHeight/2+x,&pixel);
cristy0834d642011-03-18 18:26:08 +00005298 switch (type)
5299 {
cristy80c99742011-04-04 14:46:39 +00005300 case GradientStatistic:
5301 {
5302 MagickPixelPacket
5303 maximum,
5304 minimum;
5305
5306 minimum=GetMinimumPixelList(pixel_list[id]);
5307 maximum=GetMaximumPixelList(pixel_list[id]);
5308 pixel.red=MagickAbsoluteValue(maximum.red-minimum.red);
5309 pixel.green=MagickAbsoluteValue(maximum.green-minimum.green);
5310 pixel.blue=MagickAbsoluteValue(maximum.blue-minimum.blue);
5311 pixel.opacity=MagickAbsoluteValue(maximum.opacity-minimum.opacity);
5312 if (image->colorspace == CMYKColorspace)
5313 pixel.index=MagickAbsoluteValue(maximum.index-minimum.index);
5314 break;
5315 }
cristy6fc86bb2011-03-18 23:45:16 +00005316 case MaximumStatistic:
5317 {
5318 pixel=GetMaximumPixelList(pixel_list[id]);
5319 break;
5320 }
cristy49f37242011-03-22 18:18:23 +00005321 case MeanStatistic:
5322 {
5323 pixel=GetMeanPixelList(pixel_list[id]);
5324 break;
5325 }
cristyf2ad14a2011-03-18 18:57:25 +00005326 case MedianStatistic:
cristy6fc86bb2011-03-18 23:45:16 +00005327 default:
cristyf2ad14a2011-03-18 18:57:25 +00005328 {
5329 pixel=GetMedianPixelList(pixel_list[id]);
5330 break;
5331 }
cristy6fc86bb2011-03-18 23:45:16 +00005332 case MinimumStatistic:
5333 {
5334 pixel=GetMinimumPixelList(pixel_list[id]);
5335 break;
5336 }
cristyf2ad14a2011-03-18 18:57:25 +00005337 case ModeStatistic:
5338 {
5339 pixel=GetModePixelList(pixel_list[id]);
5340 break;
5341 }
5342 case NonpeakStatistic:
5343 {
5344 pixel=GetNonpeakPixelList(pixel_list[id]);
5345 break;
5346 }
cristy9a68cbb2011-03-29 00:51:23 +00005347 case StandardDeviationStatistic:
5348 {
5349 pixel=GetStandardDeviationPixelList(pixel_list[id]);
5350 break;
5351 }
cristy0834d642011-03-18 18:26:08 +00005352 }
5353 if ((channel & RedChannel) != 0)
5354 q->red=ClampToQuantum(pixel.red);
5355 if ((channel & GreenChannel) != 0)
5356 q->green=ClampToQuantum(pixel.green);
5357 if ((channel & BlueChannel) != 0)
5358 q->blue=ClampToQuantum(pixel.blue);
cristy5b273dc2011-03-19 00:58:44 +00005359 if (((channel & OpacityChannel) != 0) &&
5360 (image->matte != MagickFalse))
cristy0834d642011-03-18 18:26:08 +00005361 q->opacity=ClampToQuantum(pixel.opacity);
5362 if (((channel & IndexChannel) != 0) &&
5363 (image->colorspace == CMYKColorspace))
5364 statistic_indexes[x]=(IndexPacket) ClampToQuantum(pixel.index);
5365 p++;
5366 q++;
5367 }
5368 if (SyncCacheViewAuthenticPixels(statistic_view,exception) == MagickFalse)
5369 status=MagickFalse;
5370 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5371 {
5372 MagickBooleanType
5373 proceed;
5374
5375#if defined(MAGICKCORE_OPENMP_SUPPORT)
5376 #pragma omp critical (MagickCore_StatisticImage)
5377#endif
5378 proceed=SetImageProgress(image,StatisticImageTag,progress++,
5379 image->rows);
5380 if (proceed == MagickFalse)
5381 status=MagickFalse;
5382 }
5383 }
5384 statistic_view=DestroyCacheView(statistic_view);
5385 image_view=DestroyCacheView(image_view);
5386 pixel_list=DestroyPixelListThreadSet(pixel_list);
5387 return(statistic_image);
5388}
5389
5390/*
5391%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5392% %
5393% %
5394% %
cristy3ed852e2009-09-05 21:47:34 +00005395% U n s h a r p M a s k I m a g e %
5396% %
5397% %
5398% %
5399%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5400%
5401% UnsharpMaskImage() sharpens one or more image channels. We convolve the
5402% image with a Gaussian operator of the given radius and standard deviation
5403% (sigma). For reasonable results, radius should be larger than sigma. Use a
5404% radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
5405%
5406% The format of the UnsharpMaskImage method is:
5407%
5408% Image *UnsharpMaskImage(const Image *image,const double radius,
5409% const double sigma,const double amount,const double threshold,
5410% ExceptionInfo *exception)
5411% Image *UnsharpMaskImageChannel(const Image *image,
5412% const ChannelType channel,const double radius,const double sigma,
5413% const double amount,const double threshold,ExceptionInfo *exception)
5414%
5415% A description of each parameter follows:
5416%
5417% o image: the image.
5418%
5419% o channel: the channel type.
5420%
5421% o radius: the radius of the Gaussian, in pixels, not counting the center
5422% pixel.
5423%
5424% o sigma: the standard deviation of the Gaussian, in pixels.
5425%
5426% o amount: the percentage of the difference between the original and the
5427% blur image that is added back into the original.
5428%
5429% o threshold: the threshold in pixels needed to apply the diffence amount.
5430%
5431% o exception: return any errors or warnings in this structure.
5432%
5433*/
5434
5435MagickExport Image *UnsharpMaskImage(const Image *image,const double radius,
5436 const double sigma,const double amount,const double threshold,
5437 ExceptionInfo *exception)
5438{
5439 Image
5440 *sharp_image;
5441
5442 sharp_image=UnsharpMaskImageChannel(image,DefaultChannels,radius,sigma,amount,
5443 threshold,exception);
5444 return(sharp_image);
5445}
5446
5447MagickExport Image *UnsharpMaskImageChannel(const Image *image,
5448 const ChannelType channel,const double radius,const double sigma,
5449 const double amount,const double threshold,ExceptionInfo *exception)
5450{
5451#define SharpenImageTag "Sharpen/Image"
5452
cristyc4c8d132010-01-07 01:58:38 +00005453 CacheView
5454 *image_view,
5455 *unsharp_view;
5456
cristy3ed852e2009-09-05 21:47:34 +00005457 Image
5458 *unsharp_image;
5459
cristy3ed852e2009-09-05 21:47:34 +00005460 MagickBooleanType
5461 status;
5462
cristybb503372010-05-27 20:51:26 +00005463 MagickOffsetType
5464 progress;
5465
cristy3ed852e2009-09-05 21:47:34 +00005466 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00005467 bias;
cristy3ed852e2009-09-05 21:47:34 +00005468
5469 MagickRealType
5470 quantum_threshold;
5471
cristybb503372010-05-27 20:51:26 +00005472 ssize_t
5473 y;
5474
cristy3ed852e2009-09-05 21:47:34 +00005475 assert(image != (const Image *) NULL);
5476 assert(image->signature == MagickSignature);
5477 if (image->debug != MagickFalse)
5478 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5479 assert(exception != (ExceptionInfo *) NULL);
5480 unsharp_image=BlurImageChannel(image,channel,radius,sigma,exception);
5481 if (unsharp_image == (Image *) NULL)
5482 return((Image *) NULL);
5483 quantum_threshold=(MagickRealType) QuantumRange*threshold;
5484 /*
5485 Unsharp-mask image.
5486 */
5487 status=MagickTrue;
5488 progress=0;
cristyddd82202009-11-03 20:14:50 +00005489 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00005490 image_view=AcquireCacheView(image);
5491 unsharp_view=AcquireCacheView(unsharp_image);
cristyb5d5f722009-11-04 03:03:49 +00005492#if defined(MAGICKCORE_OPENMP_SUPPORT)
5493 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00005494#endif
cristybb503372010-05-27 20:51:26 +00005495 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00005496 {
5497 MagickPixelPacket
5498 pixel;
5499
5500 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00005501 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00005502
5503 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00005504 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00005505
5506 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00005507 *restrict unsharp_indexes;
cristy3ed852e2009-09-05 21:47:34 +00005508
cristy3ed852e2009-09-05 21:47:34 +00005509 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00005510 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00005511
cristy117ff172010-08-15 21:35:32 +00005512 register ssize_t
5513 x;
5514
cristy3ed852e2009-09-05 21:47:34 +00005515 if (status == MagickFalse)
5516 continue;
5517 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
5518 q=GetCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
5519 exception);
5520 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
5521 {
5522 status=MagickFalse;
5523 continue;
5524 }
5525 indexes=GetCacheViewVirtualIndexQueue(image_view);
5526 unsharp_indexes=GetCacheViewAuthenticIndexQueue(unsharp_view);
cristyddd82202009-11-03 20:14:50 +00005527 pixel=bias;
cristybb503372010-05-27 20:51:26 +00005528 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00005529 {
5530 if ((channel & RedChannel) != 0)
5531 {
5532 pixel.red=p->red-(MagickRealType) q->red;
5533 if (fabs(2.0*pixel.red) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005534 pixel.red=(MagickRealType) GetRedPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005535 else
5536 pixel.red=(MagickRealType) p->red+(pixel.red*amount);
cristyce70c172010-01-07 17:15:30 +00005537 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00005538 }
5539 if ((channel & GreenChannel) != 0)
5540 {
5541 pixel.green=p->green-(MagickRealType) q->green;
5542 if (fabs(2.0*pixel.green) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005543 pixel.green=(MagickRealType) GetGreenPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005544 else
5545 pixel.green=(MagickRealType) p->green+(pixel.green*amount);
cristyce70c172010-01-07 17:15:30 +00005546 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00005547 }
5548 if ((channel & BlueChannel) != 0)
5549 {
5550 pixel.blue=p->blue-(MagickRealType) q->blue;
5551 if (fabs(2.0*pixel.blue) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005552 pixel.blue=(MagickRealType) GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005553 else
5554 pixel.blue=(MagickRealType) p->blue+(pixel.blue*amount);
cristyce70c172010-01-07 17:15:30 +00005555 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00005556 }
5557 if ((channel & OpacityChannel) != 0)
5558 {
5559 pixel.opacity=p->opacity-(MagickRealType) q->opacity;
5560 if (fabs(2.0*pixel.opacity) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005561 pixel.opacity=(MagickRealType) GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005562 else
5563 pixel.opacity=p->opacity+(pixel.opacity*amount);
cristyce70c172010-01-07 17:15:30 +00005564 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00005565 }
5566 if (((channel & IndexChannel) != 0) &&
5567 (image->colorspace == CMYKColorspace))
5568 {
cristyf01182f2011-03-01 14:56:40 +00005569 pixel.index=indexes[x]-(MagickRealType) unsharp_indexes[x];
cristy3ed852e2009-09-05 21:47:34 +00005570 if (fabs(2.0*pixel.index) < quantum_threshold)
cristyb557a152011-02-22 12:14:30 +00005571 pixel.index=(MagickRealType) indexes[x];
cristy3ed852e2009-09-05 21:47:34 +00005572 else
cristyb557a152011-02-22 12:14:30 +00005573 pixel.index=(MagickRealType) indexes[x]+(pixel.index*amount);
cristyce70c172010-01-07 17:15:30 +00005574 unsharp_indexes[x]=ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00005575 }
5576 p++;
5577 q++;
5578 }
5579 if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
5580 status=MagickFalse;
5581 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5582 {
5583 MagickBooleanType
5584 proceed;
5585
cristyb5d5f722009-11-04 03:03:49 +00005586#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00005587 #pragma omp critical (MagickCore_UnsharpMaskImageChannel)
5588#endif
5589 proceed=SetImageProgress(image,SharpenImageTag,progress++,image->rows);
5590 if (proceed == MagickFalse)
5591 status=MagickFalse;
5592 }
5593 }
5594 unsharp_image->type=image->type;
5595 unsharp_view=DestroyCacheView(unsharp_view);
5596 image_view=DestroyCacheView(image_view);
5597 if (status == MagickFalse)
5598 unsharp_image=DestroyImage(unsharp_image);
5599 return(unsharp_image);
5600}