blob: a028c9e238225344a32945fafb508b6745ab1551 [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))
cristyc8d25bc2011-04-29 02:19:30 +0000354 pixel.index+=(*k)*alpha*GetIndexPixelComponent(indexes+x+(width-i)*
355 v+u);
cristy3ed852e2009-09-05 21:47:34 +0000356 gamma+=(*k)*alpha;
357 k++;
358 p++;
359 }
360 }
361 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
362 if ((channel & RedChannel) != 0)
cristya2d08742011-04-22 19:59:52 +0000363 SetRedPixelComponent(q,ClampToQuantum(gamma*pixel.red));
cristy3ed852e2009-09-05 21:47:34 +0000364 if ((channel & GreenChannel) != 0)
cristya2d08742011-04-22 19:59:52 +0000365 SetGreenPixelComponent(q,ClampToQuantum(gamma*pixel.green));
cristy3ed852e2009-09-05 21:47:34 +0000366 if ((channel & BlueChannel) != 0)
cristya2d08742011-04-22 19:59:52 +0000367 SetBluePixelComponent(q,ClampToQuantum(gamma*pixel.blue));
cristy3ed852e2009-09-05 21:47:34 +0000368 if ((channel & OpacityChannel) != 0)
cristya2d08742011-04-22 19:59:52 +0000369 SetOpacityPixelComponent(q,ClampToQuantum(pixel.opacity));
cristy3ed852e2009-09-05 21:47:34 +0000370 if (((channel & IndexChannel) != 0) &&
371 (image->colorspace == CMYKColorspace))
cristyc8d25bc2011-04-29 02:19:30 +0000372 SetIndexPixelComponent(blur_indexes+x,ClampToQuantum(gamma*
373 pixel.index));
cristy3ed852e2009-09-05 21:47:34 +0000374 q++;
375 r++;
376 }
377 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
378 status=MagickFalse;
379 if (image->progress_monitor != (MagickProgressMonitor) NULL)
380 {
381 MagickBooleanType
382 proceed;
383
cristyb5d5f722009-11-04 03:03:49 +0000384#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000385 #pragma omp critical (MagickCore_AdaptiveBlurImageChannel)
386#endif
387 proceed=SetImageProgress(image,AdaptiveBlurImageTag,progress++,
388 image->rows);
389 if (proceed == MagickFalse)
390 status=MagickFalse;
391 }
392 }
393 blur_image->type=image->type;
394 blur_view=DestroyCacheView(blur_view);
395 edge_view=DestroyCacheView(edge_view);
396 image_view=DestroyCacheView(image_view);
397 edge_image=DestroyImage(edge_image);
cristybb503372010-05-27 20:51:26 +0000398 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000399 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
400 kernel=(double **) RelinquishMagickMemory(kernel);
401 if (status == MagickFalse)
402 blur_image=DestroyImage(blur_image);
403 return(blur_image);
404}
405
406/*
407%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
408% %
409% %
410% %
411% A d a p t i v e S h a r p e n I m a g e %
412% %
413% %
414% %
415%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
416%
417% AdaptiveSharpenImage() adaptively sharpens the image by sharpening more
418% intensely near image edges and less intensely far from edges. We sharpen the
419% image with a Gaussian operator of the given radius and standard deviation
420% (sigma). For reasonable results, radius should be larger than sigma. Use a
421% radius of 0 and AdaptiveSharpenImage() selects a suitable radius for you.
422%
423% The format of the AdaptiveSharpenImage method is:
424%
425% Image *AdaptiveSharpenImage(const Image *image,const double radius,
426% const double sigma,ExceptionInfo *exception)
427% Image *AdaptiveSharpenImageChannel(const Image *image,
428% const ChannelType channel,double radius,const double sigma,
429% ExceptionInfo *exception)
430%
431% A description of each parameter follows:
432%
433% o image: the image.
434%
435% o channel: the channel type.
436%
437% o radius: the radius of the Gaussian, in pixels, not counting the center
438% pixel.
439%
440% o sigma: the standard deviation of the Laplacian, in pixels.
441%
442% o exception: return any errors or warnings in this structure.
443%
444*/
445
446MagickExport Image *AdaptiveSharpenImage(const Image *image,const double radius,
447 const double sigma,ExceptionInfo *exception)
448{
449 Image
450 *sharp_image;
451
452 sharp_image=AdaptiveSharpenImageChannel(image,DefaultChannels,radius,sigma,
453 exception);
454 return(sharp_image);
455}
456
457MagickExport Image *AdaptiveSharpenImageChannel(const Image *image,
458 const ChannelType channel,const double radius,const double sigma,
459 ExceptionInfo *exception)
460{
461#define AdaptiveSharpenImageTag "Convolve/Image"
462#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
463
cristyc4c8d132010-01-07 01:58:38 +0000464 CacheView
465 *sharp_view,
466 *edge_view,
467 *image_view;
468
cristy3ed852e2009-09-05 21:47:34 +0000469 double
cristy47e00502009-12-17 19:19:57 +0000470 **kernel,
471 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000472
473 Image
474 *sharp_image,
475 *edge_image,
476 *gaussian_image;
477
cristy3ed852e2009-09-05 21:47:34 +0000478 MagickBooleanType
479 status;
480
cristybb503372010-05-27 20:51:26 +0000481 MagickOffsetType
482 progress;
483
cristy3ed852e2009-09-05 21:47:34 +0000484 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +0000485 bias;
cristy3ed852e2009-09-05 21:47:34 +0000486
cristybb503372010-05-27 20:51:26 +0000487 register ssize_t
cristy47e00502009-12-17 19:19:57 +0000488 i;
cristy3ed852e2009-09-05 21:47:34 +0000489
cristybb503372010-05-27 20:51:26 +0000490 size_t
cristy3ed852e2009-09-05 21:47:34 +0000491 width;
492
cristybb503372010-05-27 20:51:26 +0000493 ssize_t
494 j,
495 k,
496 u,
497 v,
498 y;
499
cristy3ed852e2009-09-05 21:47:34 +0000500 assert(image != (const Image *) NULL);
501 assert(image->signature == MagickSignature);
502 if (image->debug != MagickFalse)
503 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
504 assert(exception != (ExceptionInfo *) NULL);
505 assert(exception->signature == MagickSignature);
506 sharp_image=CloneImage(image,0,0,MagickTrue,exception);
507 if (sharp_image == (Image *) NULL)
508 return((Image *) NULL);
509 if (fabs(sigma) <= MagickEpsilon)
510 return(sharp_image);
511 if (SetImageStorageClass(sharp_image,DirectClass) == MagickFalse)
512 {
513 InheritException(exception,&sharp_image->exception);
514 sharp_image=DestroyImage(sharp_image);
515 return((Image *) NULL);
516 }
517 /*
518 Edge detect the image brighness channel, level, sharp, and level again.
519 */
520 edge_image=EdgeImage(image,radius,exception);
521 if (edge_image == (Image *) NULL)
522 {
523 sharp_image=DestroyImage(sharp_image);
524 return((Image *) NULL);
525 }
526 (void) LevelImage(edge_image,"20%,95%");
527 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,exception);
528 if (gaussian_image != (Image *) NULL)
529 {
530 edge_image=DestroyImage(edge_image);
531 edge_image=gaussian_image;
532 }
533 (void) LevelImage(edge_image,"10%,95%");
534 /*
535 Create a set of kernels from maximum (radius,sigma) to minimum.
536 */
537 width=GetOptimalKernelWidth2D(radius,sigma);
538 kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
539 if (kernel == (double **) NULL)
540 {
541 edge_image=DestroyImage(edge_image);
542 sharp_image=DestroyImage(sharp_image);
543 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
544 }
545 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
cristybb503372010-05-27 20:51:26 +0000546 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000547 {
548 kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)*
549 sizeof(**kernel));
550 if (kernel[i] == (double *) NULL)
551 break;
cristy47e00502009-12-17 19:19:57 +0000552 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000553 j=(ssize_t) (width-i)/2;
cristy47e00502009-12-17 19:19:57 +0000554 k=0;
555 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000556 {
cristy47e00502009-12-17 19:19:57 +0000557 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000558 {
cristy4205a3c2010-09-12 20:19:59 +0000559 kernel[i][k]=(double) (-exp(-((double) u*u+v*v)/(2.0*MagickSigma*
560 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +0000561 normalize+=kernel[i][k];
562 k++;
cristy3ed852e2009-09-05 21:47:34 +0000563 }
564 }
cristy3ed852e2009-09-05 21:47:34 +0000565 if (fabs(normalize) <= MagickEpsilon)
566 normalize=1.0;
567 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000568 for (k=0; k < (j*j); k++)
569 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000570 }
cristybb503372010-05-27 20:51:26 +0000571 if (i < (ssize_t) width)
cristy3ed852e2009-09-05 21:47:34 +0000572 {
573 for (i-=2; i >= 0; i-=2)
574 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
575 kernel=(double **) RelinquishMagickMemory(kernel);
576 edge_image=DestroyImage(edge_image);
577 sharp_image=DestroyImage(sharp_image);
578 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
579 }
580 /*
581 Adaptively sharpen image.
582 */
583 status=MagickTrue;
584 progress=0;
cristyddd82202009-11-03 20:14:50 +0000585 GetMagickPixelPacket(image,&bias);
586 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000587 image_view=AcquireCacheView(image);
588 edge_view=AcquireCacheView(edge_image);
589 sharp_view=AcquireCacheView(sharp_image);
cristyb5d5f722009-11-04 03:03:49 +0000590#if defined(MAGICKCORE_OPENMP_SUPPORT)
591 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000592#endif
cristybb503372010-05-27 20:51:26 +0000593 for (y=0; y < (ssize_t) sharp_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000594 {
595 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000596 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000597
598 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000599 *restrict p,
600 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000601
602 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000603 *restrict sharp_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000604
cristy3ed852e2009-09-05 21:47:34 +0000605 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000606 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000607
cristy117ff172010-08-15 21:35:32 +0000608 register ssize_t
609 x;
610
cristy3ed852e2009-09-05 21:47:34 +0000611 if (status == MagickFalse)
612 continue;
613 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
614 q=QueueCacheViewAuthenticPixels(sharp_view,0,y,sharp_image->columns,1,
615 exception);
616 if ((r == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
617 {
618 status=MagickFalse;
619 continue;
620 }
621 sharp_indexes=GetCacheViewAuthenticIndexQueue(sharp_view);
cristybb503372010-05-27 20:51:26 +0000622 for (x=0; x < (ssize_t) sharp_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000623 {
624 MagickPixelPacket
625 pixel;
626
627 MagickRealType
628 alpha,
629 gamma;
630
631 register const double
cristyc47d1f82009-11-26 01:44:43 +0000632 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000633
cristybb503372010-05-27 20:51:26 +0000634 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000635 i,
636 u,
637 v;
638
639 gamma=0.0;
cristybb503372010-05-27 20:51:26 +0000640 i=(ssize_t) ceil((double) width*(QuantumRange-QuantumScale*
cristy1f9ce9f2010-04-28 11:55:12 +0000641 PixelIntensity(r))-0.5);
cristy3ed852e2009-09-05 21:47:34 +0000642 if (i < 0)
643 i=0;
644 else
cristybb503372010-05-27 20:51:26 +0000645 if (i > (ssize_t) width)
646 i=(ssize_t) width;
cristy3ed852e2009-09-05 21:47:34 +0000647 if ((i & 0x01) != 0)
648 i--;
cristy117ff172010-08-15 21:35:32 +0000649 p=GetCacheViewVirtualPixels(image_view,x-((ssize_t) (width-i)/2L),y-
650 (ssize_t) ((width-i)/2L),width-i,width-i,exception);
cristy3ed852e2009-09-05 21:47:34 +0000651 if (p == (const PixelPacket *) NULL)
652 break;
653 indexes=GetCacheViewVirtualIndexQueue(image_view);
654 k=kernel[i];
cristyddd82202009-11-03 20:14:50 +0000655 pixel=bias;
cristybb503372010-05-27 20:51:26 +0000656 for (v=0; v < (ssize_t) (width-i); v++)
cristy3ed852e2009-09-05 21:47:34 +0000657 {
cristybb503372010-05-27 20:51:26 +0000658 for (u=0; u < (ssize_t) (width-i); u++)
cristy3ed852e2009-09-05 21:47:34 +0000659 {
660 alpha=1.0;
661 if (((channel & OpacityChannel) != 0) &&
662 (image->matte != MagickFalse))
cristy46f08202010-01-10 04:04:21 +0000663 alpha=(MagickRealType) (QuantumScale*GetAlphaPixelComponent(p));
cristy3ed852e2009-09-05 21:47:34 +0000664 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000665 pixel.red+=(*k)*alpha*GetRedPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000666 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000667 pixel.green+=(*k)*alpha*GetGreenPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000668 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000669 pixel.blue+=(*k)*alpha*GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000670 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000671 pixel.opacity+=(*k)*GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000672 if (((channel & IndexChannel) != 0) &&
673 (image->colorspace == CMYKColorspace))
cristyc8d25bc2011-04-29 02:19:30 +0000674 pixel.index+=(*k)*alpha*GetIndexPixelComponent(indexes+x+(width-i)*
675 v+u);
cristy3ed852e2009-09-05 21:47:34 +0000676 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)
cristyc8d25bc2011-04-29 02:19:30 +0000683 SetRedPixelComponent(q,ClampToQuantum(gamma*pixel.red));
cristy3ed852e2009-09-05 21:47:34 +0000684 if ((channel & GreenChannel) != 0)
cristyc8d25bc2011-04-29 02:19:30 +0000685 SetGreenPixelComponent(q,ClampToQuantum(gamma*pixel.green));
cristy3ed852e2009-09-05 21:47:34 +0000686 if ((channel & BlueChannel) != 0)
cristyc8d25bc2011-04-29 02:19:30 +0000687 SetBluePixelComponent(q,ClampToQuantum(gamma*pixel.blue));
cristy3ed852e2009-09-05 21:47:34 +0000688 if ((channel & OpacityChannel) != 0)
cristya2d08742011-04-22 19:59:52 +0000689 SetOpacityPixelComponent(q,ClampToQuantum(pixel.opacity));
cristy3ed852e2009-09-05 21:47:34 +0000690 if (((channel & IndexChannel) != 0) &&
691 (image->colorspace == CMYKColorspace))
cristyc8d25bc2011-04-29 02:19:30 +0000692 SetIndexPixelComponent(sharp_indexes+x,ClampToQuantum(gamma*
693 pixel.index));
cristy3ed852e2009-09-05 21:47:34 +0000694 q++;
695 r++;
696 }
697 if (SyncCacheViewAuthenticPixels(sharp_view,exception) == MagickFalse)
698 status=MagickFalse;
699 if (image->progress_monitor != (MagickProgressMonitor) NULL)
700 {
701 MagickBooleanType
702 proceed;
703
cristyb5d5f722009-11-04 03:03:49 +0000704#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000705 #pragma omp critical (MagickCore_AdaptiveSharpenImageChannel)
706#endif
707 proceed=SetImageProgress(image,AdaptiveSharpenImageTag,progress++,
708 image->rows);
709 if (proceed == MagickFalse)
710 status=MagickFalse;
711 }
712 }
713 sharp_image->type=image->type;
714 sharp_view=DestroyCacheView(sharp_view);
715 edge_view=DestroyCacheView(edge_view);
716 image_view=DestroyCacheView(image_view);
717 edge_image=DestroyImage(edge_image);
cristybb503372010-05-27 20:51:26 +0000718 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000719 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
720 kernel=(double **) RelinquishMagickMemory(kernel);
721 if (status == MagickFalse)
722 sharp_image=DestroyImage(sharp_image);
723 return(sharp_image);
724}
725
726/*
727%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
728% %
729% %
730% %
731% B l u r I m a g e %
732% %
733% %
734% %
735%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
736%
737% BlurImage() blurs an image. We convolve the image with a Gaussian operator
738% of the given radius and standard deviation (sigma). For reasonable results,
739% the radius should be larger than sigma. Use a radius of 0 and BlurImage()
740% selects a suitable radius for you.
741%
742% BlurImage() differs from GaussianBlurImage() in that it uses a separable
743% kernel which is faster but mathematically equivalent to the non-separable
744% kernel.
745%
746% The format of the BlurImage method is:
747%
748% Image *BlurImage(const Image *image,const double radius,
749% const double sigma,ExceptionInfo *exception)
750% Image *BlurImageChannel(const Image *image,const ChannelType channel,
751% const double radius,const double sigma,ExceptionInfo *exception)
752%
753% A description of each parameter follows:
754%
755% o image: the image.
756%
757% o channel: the channel type.
758%
759% o radius: the radius of the Gaussian, in pixels, not counting the center
760% pixel.
761%
762% o sigma: the standard deviation of the Gaussian, in pixels.
763%
764% o exception: return any errors or warnings in this structure.
765%
766*/
767
768MagickExport Image *BlurImage(const Image *image,const double radius,
769 const double sigma,ExceptionInfo *exception)
770{
771 Image
772 *blur_image;
773
774 blur_image=BlurImageChannel(image,DefaultChannels,radius,sigma,exception);
775 return(blur_image);
776}
777
cristybb503372010-05-27 20:51:26 +0000778static double *GetBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +0000779{
cristy3ed852e2009-09-05 21:47:34 +0000780 double
cristy47e00502009-12-17 19:19:57 +0000781 *kernel,
782 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000783
cristy117ff172010-08-15 21:35:32 +0000784 register ssize_t
785 i;
786
cristybb503372010-05-27 20:51:26 +0000787 ssize_t
cristy47e00502009-12-17 19:19:57 +0000788 j,
789 k;
cristy3ed852e2009-09-05 21:47:34 +0000790
cristy3ed852e2009-09-05 21:47:34 +0000791 /*
792 Generate a 1-D convolution kernel.
793 */
794 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
795 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
796 if (kernel == (double *) NULL)
797 return(0);
cristy3ed852e2009-09-05 21:47:34 +0000798 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000799 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +0000800 i=0;
801 for (k=(-j); k <= j; k++)
802 {
cristy4205a3c2010-09-12 20:19:59 +0000803 kernel[i]=(double) (exp(-((double) k*k)/(2.0*MagickSigma*MagickSigma))/
804 (MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +0000805 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +0000806 i++;
807 }
cristybb503372010-05-27 20:51:26 +0000808 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000809 kernel[i]/=normalize;
810 return(kernel);
811}
812
813MagickExport Image *BlurImageChannel(const Image *image,
814 const ChannelType channel,const double radius,const double sigma,
815 ExceptionInfo *exception)
816{
817#define BlurImageTag "Blur/Image"
818
cristyc4c8d132010-01-07 01:58:38 +0000819 CacheView
820 *blur_view,
821 *image_view;
822
cristy3ed852e2009-09-05 21:47:34 +0000823 double
824 *kernel;
825
826 Image
827 *blur_image;
828
cristy3ed852e2009-09-05 21:47:34 +0000829 MagickBooleanType
830 status;
831
cristybb503372010-05-27 20:51:26 +0000832 MagickOffsetType
833 progress;
834
cristy3ed852e2009-09-05 21:47:34 +0000835 MagickPixelPacket
cristy3ed852e2009-09-05 21:47:34 +0000836 bias;
837
cristybb503372010-05-27 20:51:26 +0000838 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000839 i;
840
cristybb503372010-05-27 20:51:26 +0000841 size_t
cristy3ed852e2009-09-05 21:47:34 +0000842 width;
843
cristybb503372010-05-27 20:51:26 +0000844 ssize_t
845 x,
846 y;
847
cristy3ed852e2009-09-05 21:47:34 +0000848 /*
849 Initialize blur image attributes.
850 */
851 assert(image != (Image *) NULL);
852 assert(image->signature == MagickSignature);
853 if (image->debug != MagickFalse)
854 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
855 assert(exception != (ExceptionInfo *) NULL);
856 assert(exception->signature == MagickSignature);
857 blur_image=CloneImage(image,0,0,MagickTrue,exception);
858 if (blur_image == (Image *) NULL)
859 return((Image *) NULL);
860 if (fabs(sigma) <= MagickEpsilon)
861 return(blur_image);
862 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
863 {
864 InheritException(exception,&blur_image->exception);
865 blur_image=DestroyImage(blur_image);
866 return((Image *) NULL);
867 }
868 width=GetOptimalKernelWidth1D(radius,sigma);
869 kernel=GetBlurKernel(width,sigma);
870 if (kernel == (double *) NULL)
871 {
872 blur_image=DestroyImage(blur_image);
873 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
874 }
875 if (image->debug != MagickFalse)
876 {
877 char
878 format[MaxTextExtent],
879 *message;
880
881 register const double
882 *k;
883
884 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +0000885 " BlurImage with %.20g kernel:",(double) width);
cristy3ed852e2009-09-05 21:47:34 +0000886 message=AcquireString("");
887 k=kernel;
cristybb503372010-05-27 20:51:26 +0000888 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000889 {
890 *message='\0';
cristye8c25f92010-06-03 00:53:06 +0000891 (void) FormatMagickString(format,MaxTextExtent,"%.20g: ",(double) i);
cristy3ed852e2009-09-05 21:47:34 +0000892 (void) ConcatenateString(&message,format);
cristye7f51092010-01-17 00:39:37 +0000893 (void) FormatMagickString(format,MaxTextExtent,"%g ",*k++);
cristy3ed852e2009-09-05 21:47:34 +0000894 (void) ConcatenateString(&message,format);
895 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
896 }
897 message=DestroyString(message);
898 }
899 /*
900 Blur rows.
901 */
902 status=MagickTrue;
903 progress=0;
cristyddd82202009-11-03 20:14:50 +0000904 GetMagickPixelPacket(image,&bias);
905 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000906 image_view=AcquireCacheView(image);
907 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000908#if defined(MAGICKCORE_OPENMP_SUPPORT)
909 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000910#endif
cristybb503372010-05-27 20:51:26 +0000911 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000912 {
913 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000914 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000915
916 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000917 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000918
919 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000920 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000921
cristy3ed852e2009-09-05 21:47:34 +0000922 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000923 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000924
cristy117ff172010-08-15 21:35:32 +0000925 register ssize_t
926 x;
927
cristy3ed852e2009-09-05 21:47:34 +0000928 if (status == MagickFalse)
929 continue;
cristy117ff172010-08-15 21:35:32 +0000930 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y,
931 image->columns+width,1,exception);
cristy3ed852e2009-09-05 21:47:34 +0000932 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
933 exception);
934 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
935 {
936 status=MagickFalse;
937 continue;
938 }
939 indexes=GetCacheViewVirtualIndexQueue(image_view);
940 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
cristybb503372010-05-27 20:51:26 +0000941 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000942 {
943 MagickPixelPacket
944 pixel;
945
946 register const double
cristyc47d1f82009-11-26 01:44:43 +0000947 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000948
949 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000950 *restrict kernel_pixels;
cristy3ed852e2009-09-05 21:47:34 +0000951
cristybb503372010-05-27 20:51:26 +0000952 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000953 i;
954
cristyddd82202009-11-03 20:14:50 +0000955 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +0000956 k=kernel;
957 kernel_pixels=p;
958 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
959 {
cristybb503372010-05-27 20:51:26 +0000960 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000961 {
cristyc8d25bc2011-04-29 02:19:30 +0000962 pixel.red+=(*k)*GetRedPixelComponent(kernel_pixels);
963 pixel.green+=(*k)*GetGreenPixelComponent(kernel_pixels);
964 pixel.blue+=(*k)*GetBluePixelComponent(kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +0000965 k++;
966 kernel_pixels++;
967 }
968 if ((channel & RedChannel) != 0)
cristya2d08742011-04-22 19:59:52 +0000969 SetRedPixelComponent(q,ClampToQuantum(pixel.red));
cristy3ed852e2009-09-05 21:47:34 +0000970 if ((channel & GreenChannel) != 0)
cristya2d08742011-04-22 19:59:52 +0000971 SetGreenPixelComponent(q,ClampToQuantum(pixel.green));
cristy3ed852e2009-09-05 21:47:34 +0000972 if ((channel & BlueChannel) != 0)
cristya2d08742011-04-22 19:59:52 +0000973 SetBluePixelComponent(q,ClampToQuantum(pixel.blue));
cristy3ed852e2009-09-05 21:47:34 +0000974 if ((channel & OpacityChannel) != 0)
975 {
976 k=kernel;
977 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +0000978 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000979 {
cristyc8d25bc2011-04-29 02:19:30 +0000980 pixel.opacity+=(*k)*GetOpacityPixelComponent(kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +0000981 k++;
982 kernel_pixels++;
983 }
cristya2d08742011-04-22 19:59:52 +0000984 SetOpacityPixelComponent(q,ClampToQuantum(pixel.opacity));
cristy3ed852e2009-09-05 21:47:34 +0000985 }
986 if (((channel & IndexChannel) != 0) &&
987 (image->colorspace == CMYKColorspace))
988 {
989 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000990 *restrict kernel_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000991
992 k=kernel;
cristy9d314ff2011-03-09 01:30:28 +0000993 kernel_indexes=indexes;
cristybb503372010-05-27 20:51:26 +0000994 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000995 {
cristyc8d25bc2011-04-29 02:19:30 +0000996 pixel.index+=(*k)*GetIndexPixelComponent(kernel_indexes);
cristy3ed852e2009-09-05 21:47:34 +0000997 k++;
998 kernel_indexes++;
999 }
cristyc8d25bc2011-04-29 02:19:30 +00001000 SetIndexPixelComponent(blur_indexes+x,ClampToQuantum(
1001 pixel.index));
cristy3ed852e2009-09-05 21:47:34 +00001002 }
1003 }
1004 else
1005 {
1006 MagickRealType
1007 alpha,
1008 gamma;
1009
1010 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00001011 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001012 {
cristy46f08202010-01-10 04:04:21 +00001013 alpha=(MagickRealType) (QuantumScale*
1014 GetAlphaPixelComponent(kernel_pixels));
cristyc8d25bc2011-04-29 02:19:30 +00001015 pixel.red+=(*k)*alpha*GetRedPixelComponent(kernel_pixels);
1016 pixel.green+=(*k)*alpha*GetGreenPixelComponent(kernel_pixels);
1017 pixel.blue+=(*k)*alpha*GetBluePixelComponent(kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001018 gamma+=(*k)*alpha;
1019 k++;
1020 kernel_pixels++;
1021 }
1022 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1023 if ((channel & RedChannel) != 0)
cristyc8d25bc2011-04-29 02:19:30 +00001024 SetRedPixelComponent(q,ClampToQuantum(gamma*pixel.red));
cristy3ed852e2009-09-05 21:47:34 +00001025 if ((channel & GreenChannel) != 0)
cristyc8d25bc2011-04-29 02:19:30 +00001026 SetGreenPixelComponent(q,ClampToQuantum(gamma*pixel.green));
cristy3ed852e2009-09-05 21:47:34 +00001027 if ((channel & BlueChannel) != 0)
cristyc8d25bc2011-04-29 02:19:30 +00001028 SetBluePixelComponent(q,ClampToQuantum(gamma*pixel.blue));
cristy3ed852e2009-09-05 21:47:34 +00001029 if ((channel & OpacityChannel) != 0)
1030 {
1031 k=kernel;
1032 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001033 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001034 {
cristyc8d25bc2011-04-29 02:19:30 +00001035 pixel.opacity+=(*k)*GetOpacityPixelComponent(kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001036 k++;
1037 kernel_pixels++;
1038 }
cristya2d08742011-04-22 19:59:52 +00001039 SetOpacityPixelComponent(q,ClampToQuantum(pixel.opacity));
cristy3ed852e2009-09-05 21:47:34 +00001040 }
1041 if (((channel & IndexChannel) != 0) &&
1042 (image->colorspace == CMYKColorspace))
1043 {
1044 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001045 *restrict kernel_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001046
1047 k=kernel;
1048 kernel_pixels=p;
cristy9d314ff2011-03-09 01:30:28 +00001049 kernel_indexes=indexes;
cristybb503372010-05-27 20:51:26 +00001050 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001051 {
cristy46f08202010-01-10 04:04:21 +00001052 alpha=(MagickRealType) (QuantumScale*
1053 GetAlphaPixelComponent(kernel_pixels));
cristy3ed852e2009-09-05 21:47:34 +00001054 pixel.index+=(*k)*alpha*(*kernel_indexes);
1055 k++;
1056 kernel_pixels++;
1057 kernel_indexes++;
1058 }
cristyc8d25bc2011-04-29 02:19:30 +00001059 SetIndexPixelComponent(blur_indexes+x,ClampToQuantum(gamma*
1060 pixel.index));
cristy3ed852e2009-09-05 21:47:34 +00001061 }
1062 }
cristy9d314ff2011-03-09 01:30:28 +00001063 indexes++;
cristy3ed852e2009-09-05 21:47:34 +00001064 p++;
1065 q++;
1066 }
1067 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1068 status=MagickFalse;
1069 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1070 {
1071 MagickBooleanType
1072 proceed;
1073
cristyb5d5f722009-11-04 03:03:49 +00001074#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001075 #pragma omp critical (MagickCore_BlurImageChannel)
1076#endif
1077 proceed=SetImageProgress(image,BlurImageTag,progress++,blur_image->rows+
1078 blur_image->columns);
1079 if (proceed == MagickFalse)
1080 status=MagickFalse;
1081 }
1082 }
1083 blur_view=DestroyCacheView(blur_view);
1084 image_view=DestroyCacheView(image_view);
1085 /*
1086 Blur columns.
1087 */
1088 image_view=AcquireCacheView(blur_image);
1089 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00001090#if defined(MAGICKCORE_OPENMP_SUPPORT)
1091 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001092#endif
cristybb503372010-05-27 20:51:26 +00001093 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001094 {
1095 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001096 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001097
1098 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001099 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001100
1101 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001102 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001103
cristy3ed852e2009-09-05 21:47:34 +00001104 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001105 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001106
cristy117ff172010-08-15 21:35:32 +00001107 register ssize_t
1108 y;
1109
cristy3ed852e2009-09-05 21:47:34 +00001110 if (status == MagickFalse)
1111 continue;
cristy117ff172010-08-15 21:35:32 +00001112 p=GetCacheViewVirtualPixels(image_view,x,-((ssize_t) width/2L),1,
1113 image->rows+width,exception);
cristy3ed852e2009-09-05 21:47:34 +00001114 q=GetCacheViewAuthenticPixels(blur_view,x,0,1,blur_image->rows,exception);
1115 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1116 {
1117 status=MagickFalse;
1118 continue;
1119 }
1120 indexes=GetCacheViewVirtualIndexQueue(image_view);
1121 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
cristybb503372010-05-27 20:51:26 +00001122 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001123 {
1124 MagickPixelPacket
1125 pixel;
1126
1127 register const double
cristyc47d1f82009-11-26 01:44:43 +00001128 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00001129
1130 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001131 *restrict kernel_pixels;
cristy3ed852e2009-09-05 21:47:34 +00001132
cristybb503372010-05-27 20:51:26 +00001133 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001134 i;
1135
cristyddd82202009-11-03 20:14:50 +00001136 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00001137 k=kernel;
1138 kernel_pixels=p;
1139 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
1140 {
cristybb503372010-05-27 20:51:26 +00001141 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001142 {
cristyc8d25bc2011-04-29 02:19:30 +00001143 pixel.red+=(*k)*GetRedPixelComponent(kernel_pixels);
1144 pixel.green+=(*k)*GetGreenPixelComponent(kernel_pixels);
1145 pixel.blue+=(*k)*GetBluePixelComponent(kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001146 k++;
1147 kernel_pixels++;
1148 }
1149 if ((channel & RedChannel) != 0)
cristya2d08742011-04-22 19:59:52 +00001150 SetRedPixelComponent(q,ClampToQuantum(pixel.red));
cristy3ed852e2009-09-05 21:47:34 +00001151 if ((channel & GreenChannel) != 0)
cristya2d08742011-04-22 19:59:52 +00001152 SetGreenPixelComponent(q,ClampToQuantum(pixel.green));
cristy3ed852e2009-09-05 21:47:34 +00001153 if ((channel & BlueChannel) != 0)
cristya2d08742011-04-22 19:59:52 +00001154 SetBluePixelComponent(q,ClampToQuantum(pixel.blue));
cristy3ed852e2009-09-05 21:47:34 +00001155 if ((channel & OpacityChannel) != 0)
1156 {
1157 k=kernel;
1158 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001159 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001160 {
cristyc8d25bc2011-04-29 02:19:30 +00001161 pixel.opacity+=(*k)*GetOpacityPixelComponent(kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001162 k++;
1163 kernel_pixels++;
1164 }
cristya2d08742011-04-22 19:59:52 +00001165 SetOpacityPixelComponent(q,ClampToQuantum(pixel.opacity));
cristy3ed852e2009-09-05 21:47:34 +00001166 }
1167 if (((channel & IndexChannel) != 0) &&
1168 (image->colorspace == CMYKColorspace))
1169 {
1170 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001171 *restrict kernel_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001172
1173 k=kernel;
cristy9d314ff2011-03-09 01:30:28 +00001174 kernel_indexes=indexes;
cristybb503372010-05-27 20:51:26 +00001175 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001176 {
cristyc8d25bc2011-04-29 02:19:30 +00001177 pixel.index+=(*k)*GetIndexPixelComponent(kernel_indexes);
cristy3ed852e2009-09-05 21:47:34 +00001178 k++;
1179 kernel_indexes++;
1180 }
cristyc8d25bc2011-04-29 02:19:30 +00001181 SetIndexPixelComponent(blur_indexes+y,ClampToQuantum(
1182 pixel.index));
cristy3ed852e2009-09-05 21:47:34 +00001183 }
1184 }
1185 else
1186 {
1187 MagickRealType
1188 alpha,
1189 gamma;
1190
1191 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00001192 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001193 {
cristy46f08202010-01-10 04:04:21 +00001194 alpha=(MagickRealType) (QuantumScale*
1195 GetAlphaPixelComponent(kernel_pixels));
cristyc8d25bc2011-04-29 02:19:30 +00001196 pixel.red+=(*k)*alpha*GetRedPixelComponent(kernel_pixels);
1197 pixel.green+=(*k)*alpha*GetGreenPixelComponent(kernel_pixels);
1198 pixel.blue+=(*k)*alpha*GetBluePixelComponent(kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001199 gamma+=(*k)*alpha;
1200 k++;
1201 kernel_pixels++;
1202 }
1203 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1204 if ((channel & RedChannel) != 0)
cristyc8d25bc2011-04-29 02:19:30 +00001205 SetRedPixelComponent(q,ClampToQuantum(gamma*pixel.red));
cristy3ed852e2009-09-05 21:47:34 +00001206 if ((channel & GreenChannel) != 0)
cristyc8d25bc2011-04-29 02:19:30 +00001207 SetGreenPixelComponent(q,ClampToQuantum(gamma*pixel.green));
cristy3ed852e2009-09-05 21:47:34 +00001208 if ((channel & BlueChannel) != 0)
cristyc8d25bc2011-04-29 02:19:30 +00001209 SetBluePixelComponent(q,ClampToQuantum(gamma*pixel.blue));
cristy3ed852e2009-09-05 21:47:34 +00001210 if ((channel & OpacityChannel) != 0)
1211 {
1212 k=kernel;
1213 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001214 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001215 {
cristyc8d25bc2011-04-29 02:19:30 +00001216 pixel.opacity+=(*k)*GetOpacityPixelComponent(kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001217 k++;
1218 kernel_pixels++;
1219 }
cristya2d08742011-04-22 19:59:52 +00001220 SetOpacityPixelComponent(q,ClampToQuantum(pixel.opacity));
cristy3ed852e2009-09-05 21:47:34 +00001221 }
1222 if (((channel & IndexChannel) != 0) &&
1223 (image->colorspace == CMYKColorspace))
1224 {
1225 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001226 *restrict kernel_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001227
1228 k=kernel;
1229 kernel_pixels=p;
cristy9d314ff2011-03-09 01:30:28 +00001230 kernel_indexes=indexes;
cristybb503372010-05-27 20:51:26 +00001231 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001232 {
cristy46f08202010-01-10 04:04:21 +00001233 alpha=(MagickRealType) (QuantumScale*
1234 GetAlphaPixelComponent(kernel_pixels));
cristy3ed852e2009-09-05 21:47:34 +00001235 pixel.index+=(*k)*alpha*(*kernel_indexes);
1236 k++;
1237 kernel_pixels++;
1238 kernel_indexes++;
1239 }
cristyc8d25bc2011-04-29 02:19:30 +00001240 SetIndexPixelComponent(blur_indexes+y,ClampToQuantum(gamma*
1241 pixel.index));
cristy3ed852e2009-09-05 21:47:34 +00001242 }
1243 }
cristy9d314ff2011-03-09 01:30:28 +00001244 indexes++;
cristy3ed852e2009-09-05 21:47:34 +00001245 p++;
1246 q++;
1247 }
1248 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1249 status=MagickFalse;
1250 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1251 {
1252 MagickBooleanType
1253 proceed;
1254
cristyb5d5f722009-11-04 03:03:49 +00001255#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001256 #pragma omp critical (MagickCore_BlurImageChannel)
1257#endif
1258 proceed=SetImageProgress(image,BlurImageTag,progress++,blur_image->rows+
1259 blur_image->columns);
1260 if (proceed == MagickFalse)
1261 status=MagickFalse;
1262 }
1263 }
1264 blur_view=DestroyCacheView(blur_view);
1265 image_view=DestroyCacheView(image_view);
1266 kernel=(double *) RelinquishMagickMemory(kernel);
1267 if (status == MagickFalse)
1268 blur_image=DestroyImage(blur_image);
1269 blur_image->type=image->type;
1270 return(blur_image);
1271}
1272
1273/*
1274%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1275% %
1276% %
1277% %
cristyfccdab92009-11-30 16:43:57 +00001278% C o n v o l v e I m a g e %
1279% %
1280% %
1281% %
1282%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1283%
1284% ConvolveImage() applies a custom convolution kernel to the image.
1285%
1286% The format of the ConvolveImage method is:
1287%
cristybb503372010-05-27 20:51:26 +00001288% Image *ConvolveImage(const Image *image,const size_t order,
cristyfccdab92009-11-30 16:43:57 +00001289% const double *kernel,ExceptionInfo *exception)
1290% Image *ConvolveImageChannel(const Image *image,const ChannelType channel,
cristy117ff172010-08-15 21:35:32 +00001291% const size_t order,const double *kernel,ExceptionInfo *exception)
cristyfccdab92009-11-30 16:43:57 +00001292%
1293% A description of each parameter follows:
1294%
1295% o image: the image.
1296%
1297% o channel: the channel type.
1298%
1299% o order: the number of columns and rows in the filter kernel.
1300%
1301% o kernel: An array of double representing the convolution kernel.
1302%
1303% o exception: return any errors or warnings in this structure.
1304%
1305*/
1306
cristybb503372010-05-27 20:51:26 +00001307MagickExport Image *ConvolveImage(const Image *image,const size_t order,
cristyfccdab92009-11-30 16:43:57 +00001308 const double *kernel,ExceptionInfo *exception)
1309{
1310 Image
1311 *convolve_image;
1312
1313 convolve_image=ConvolveImageChannel(image,DefaultChannels,order,kernel,
1314 exception);
1315 return(convolve_image);
1316}
1317
1318MagickExport Image *ConvolveImageChannel(const Image *image,
cristybb503372010-05-27 20:51:26 +00001319 const ChannelType channel,const size_t order,const double *kernel,
cristyfccdab92009-11-30 16:43:57 +00001320 ExceptionInfo *exception)
1321{
1322#define ConvolveImageTag "Convolve/Image"
1323
cristyc4c8d132010-01-07 01:58:38 +00001324 CacheView
1325 *convolve_view,
1326 *image_view;
1327
cristyfccdab92009-11-30 16:43:57 +00001328 double
1329 *normal_kernel;
1330
1331 Image
1332 *convolve_image;
1333
cristyfccdab92009-11-30 16:43:57 +00001334 MagickBooleanType
1335 status;
1336
cristybb503372010-05-27 20:51:26 +00001337 MagickOffsetType
1338 progress;
1339
cristyfccdab92009-11-30 16:43:57 +00001340 MagickPixelPacket
1341 bias;
1342
1343 MagickRealType
1344 gamma;
1345
cristybb503372010-05-27 20:51:26 +00001346 register ssize_t
cristyfccdab92009-11-30 16:43:57 +00001347 i;
1348
cristybb503372010-05-27 20:51:26 +00001349 size_t
cristyfccdab92009-11-30 16:43:57 +00001350 width;
1351
cristybb503372010-05-27 20:51:26 +00001352 ssize_t
1353 y;
1354
cristyfccdab92009-11-30 16:43:57 +00001355 /*
1356 Initialize convolve image attributes.
1357 */
1358 assert(image != (Image *) NULL);
1359 assert(image->signature == MagickSignature);
1360 if (image->debug != MagickFalse)
1361 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1362 assert(exception != (ExceptionInfo *) NULL);
1363 assert(exception->signature == MagickSignature);
1364 width=order;
1365 if ((width % 2) == 0)
1366 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
1367 convolve_image=CloneImage(image,0,0,MagickTrue,exception);
1368 if (convolve_image == (Image *) NULL)
1369 return((Image *) NULL);
1370 if (SetImageStorageClass(convolve_image,DirectClass) == MagickFalse)
1371 {
1372 InheritException(exception,&convolve_image->exception);
1373 convolve_image=DestroyImage(convolve_image);
1374 return((Image *) NULL);
1375 }
1376 if (image->debug != MagickFalse)
1377 {
1378 char
1379 format[MaxTextExtent],
1380 *message;
1381
cristy117ff172010-08-15 21:35:32 +00001382 register const double
1383 *k;
1384
cristybb503372010-05-27 20:51:26 +00001385 ssize_t
cristyfccdab92009-11-30 16:43:57 +00001386 u,
1387 v;
1388
cristyfccdab92009-11-30 16:43:57 +00001389 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00001390 " ConvolveImage with %.20gx%.20g kernel:",(double) width,(double)
1391 width);
cristyfccdab92009-11-30 16:43:57 +00001392 message=AcquireString("");
1393 k=kernel;
cristybb503372010-05-27 20:51:26 +00001394 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001395 {
1396 *message='\0';
cristye8c25f92010-06-03 00:53:06 +00001397 (void) FormatMagickString(format,MaxTextExtent,"%.20g: ",(double) v);
cristyfccdab92009-11-30 16:43:57 +00001398 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00001399 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001400 {
cristye7f51092010-01-17 00:39:37 +00001401 (void) FormatMagickString(format,MaxTextExtent,"%g ",*k++);
cristyfccdab92009-11-30 16:43:57 +00001402 (void) ConcatenateString(&message,format);
1403 }
1404 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
1405 }
1406 message=DestroyString(message);
1407 }
1408 /*
1409 Normalize kernel.
1410 */
1411 normal_kernel=(double *) AcquireQuantumMemory(width*width,
1412 sizeof(*normal_kernel));
1413 if (normal_kernel == (double *) NULL)
1414 {
1415 convolve_image=DestroyImage(convolve_image);
1416 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1417 }
1418 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00001419 for (i=0; i < (ssize_t) (width*width); i++)
cristyfccdab92009-11-30 16:43:57 +00001420 gamma+=kernel[i];
1421 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristybb503372010-05-27 20:51:26 +00001422 for (i=0; i < (ssize_t) (width*width); i++)
cristyfccdab92009-11-30 16:43:57 +00001423 normal_kernel[i]=gamma*kernel[i];
1424 /*
1425 Convolve image.
1426 */
1427 status=MagickTrue;
1428 progress=0;
1429 GetMagickPixelPacket(image,&bias);
1430 SetMagickPixelPacketBias(image,&bias);
1431 image_view=AcquireCacheView(image);
1432 convolve_view=AcquireCacheView(convolve_image);
1433#if defined(MAGICKCORE_OPENMP_SUPPORT)
1434 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1435#endif
cristybb503372010-05-27 20:51:26 +00001436 for (y=0; y < (ssize_t) image->rows; y++)
cristyfccdab92009-11-30 16:43:57 +00001437 {
1438 MagickBooleanType
1439 sync;
1440
1441 register const IndexPacket
1442 *restrict indexes;
1443
1444 register const PixelPacket
1445 *restrict p;
1446
1447 register IndexPacket
1448 *restrict convolve_indexes;
1449
cristyfccdab92009-11-30 16:43:57 +00001450 register PixelPacket
1451 *restrict q;
1452
cristy117ff172010-08-15 21:35:32 +00001453 register ssize_t
1454 x;
1455
cristyfccdab92009-11-30 16:43:57 +00001456 if (status == MagickFalse)
1457 continue;
cristyce889302010-06-30 19:16:36 +00001458 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
1459 (width/2L),image->columns+width,width,exception);
cristyfccdab92009-11-30 16:43:57 +00001460 q=GetCacheViewAuthenticPixels(convolve_view,0,y,convolve_image->columns,1,
1461 exception);
1462 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1463 {
1464 status=MagickFalse;
1465 continue;
1466 }
1467 indexes=GetCacheViewVirtualIndexQueue(image_view);
1468 convolve_indexes=GetCacheViewAuthenticIndexQueue(convolve_view);
cristybb503372010-05-27 20:51:26 +00001469 for (x=0; x < (ssize_t) image->columns; x++)
cristyfccdab92009-11-30 16:43:57 +00001470 {
cristyfccdab92009-11-30 16:43:57 +00001471 MagickPixelPacket
1472 pixel;
1473
1474 register const double
1475 *restrict k;
1476
1477 register const PixelPacket
1478 *restrict kernel_pixels;
1479
cristybb503372010-05-27 20:51:26 +00001480 register ssize_t
cristyfccdab92009-11-30 16:43:57 +00001481 u;
1482
cristy117ff172010-08-15 21:35:32 +00001483 ssize_t
1484 v;
1485
cristyfccdab92009-11-30 16:43:57 +00001486 pixel=bias;
1487 k=normal_kernel;
1488 kernel_pixels=p;
1489 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
1490 {
cristybb503372010-05-27 20:51:26 +00001491 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001492 {
cristybb503372010-05-27 20:51:26 +00001493 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001494 {
1495 pixel.red+=(*k)*kernel_pixels[u].red;
1496 pixel.green+=(*k)*kernel_pixels[u].green;
1497 pixel.blue+=(*k)*kernel_pixels[u].blue;
1498 k++;
1499 }
1500 kernel_pixels+=image->columns+width;
1501 }
1502 if ((channel & RedChannel) != 0)
cristya2d08742011-04-22 19:59:52 +00001503 SetRedPixelComponent(q,ClampToQuantum(pixel.red));
cristyfccdab92009-11-30 16:43:57 +00001504 if ((channel & GreenChannel) != 0)
cristya2d08742011-04-22 19:59:52 +00001505 SetGreenPixelComponent(q,ClampToQuantum(pixel.green));
cristyfccdab92009-11-30 16:43:57 +00001506 if ((channel & BlueChannel) != 0)
cristya2d08742011-04-22 19:59:52 +00001507 SetBluePixelComponent(q,ClampToQuantum(pixel.blue));
cristyfccdab92009-11-30 16:43:57 +00001508 if ((channel & OpacityChannel) != 0)
1509 {
1510 k=normal_kernel;
1511 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001512 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001513 {
cristybb503372010-05-27 20:51:26 +00001514 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001515 {
1516 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
1517 k++;
1518 }
1519 kernel_pixels+=image->columns+width;
1520 }
cristya2d08742011-04-22 19:59:52 +00001521 SetOpacityPixelComponent(q,ClampToQuantum(pixel.opacity));
cristyfccdab92009-11-30 16:43:57 +00001522 }
1523 if (((channel & IndexChannel) != 0) &&
1524 (image->colorspace == CMYKColorspace))
1525 {
1526 register const IndexPacket
1527 *restrict kernel_indexes;
1528
1529 k=normal_kernel;
cristy9d314ff2011-03-09 01:30:28 +00001530 kernel_indexes=indexes;
cristybb503372010-05-27 20:51:26 +00001531 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001532 {
cristybb503372010-05-27 20:51:26 +00001533 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001534 {
cristyc8d25bc2011-04-29 02:19:30 +00001535 pixel.index+=(*k)*GetIndexPixelComponent(kernel_indexes+u);
cristyfccdab92009-11-30 16:43:57 +00001536 k++;
1537 }
1538 kernel_indexes+=image->columns+width;
1539 }
cristyc8d25bc2011-04-29 02:19:30 +00001540 SetIndexPixelComponent(convolve_indexes+x,ClampToQuantum(
1541 pixel.index));
cristyfccdab92009-11-30 16:43:57 +00001542 }
1543 }
1544 else
1545 {
1546 MagickRealType
1547 alpha,
1548 gamma;
1549
1550 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00001551 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001552 {
cristybb503372010-05-27 20:51:26 +00001553 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001554 {
1555 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
1556 kernel_pixels[u].opacity));
1557 pixel.red+=(*k)*alpha*kernel_pixels[u].red;
1558 pixel.green+=(*k)*alpha*kernel_pixels[u].green;
1559 pixel.blue+=(*k)*alpha*kernel_pixels[u].blue;
cristyfccdab92009-11-30 16:43:57 +00001560 gamma+=(*k)*alpha;
1561 k++;
1562 }
1563 kernel_pixels+=image->columns+width;
1564 }
1565 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1566 if ((channel & RedChannel) != 0)
cristyc8d25bc2011-04-29 02:19:30 +00001567 SetRedPixelComponent(q,ClampToQuantum(gamma*pixel.red));
cristyfccdab92009-11-30 16:43:57 +00001568 if ((channel & GreenChannel) != 0)
cristyc8d25bc2011-04-29 02:19:30 +00001569 SetGreenPixelComponent(q,ClampToQuantum(gamma*pixel.green));
cristyfccdab92009-11-30 16:43:57 +00001570 if ((channel & BlueChannel) != 0)
cristyc8d25bc2011-04-29 02:19:30 +00001571 SetBluePixelComponent(q,ClampToQuantum(gamma*pixel.blue));
cristyfccdab92009-11-30 16:43:57 +00001572 if ((channel & OpacityChannel) != 0)
1573 {
1574 k=normal_kernel;
1575 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001576 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001577 {
cristybb503372010-05-27 20:51:26 +00001578 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001579 {
cristyc8d25bc2011-04-29 02:19:30 +00001580 pixel.opacity+=(*k)*GetOpacityPixelComponent(kernel_pixels+u);
cristyfccdab92009-11-30 16:43:57 +00001581 k++;
1582 }
1583 kernel_pixels+=image->columns+width;
1584 }
cristya2d08742011-04-22 19:59:52 +00001585 SetOpacityPixelComponent(q,ClampToQuantum(pixel.opacity));
cristyfccdab92009-11-30 16:43:57 +00001586 }
1587 if (((channel & IndexChannel) != 0) &&
1588 (image->colorspace == CMYKColorspace))
1589 {
1590 register const IndexPacket
1591 *restrict kernel_indexes;
1592
1593 k=normal_kernel;
1594 kernel_pixels=p;
cristy9d314ff2011-03-09 01:30:28 +00001595 kernel_indexes=indexes;
cristybb503372010-05-27 20:51:26 +00001596 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001597 {
cristybb503372010-05-27 20:51:26 +00001598 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001599 {
1600 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
1601 kernel_pixels[u].opacity));
cristyc8d25bc2011-04-29 02:19:30 +00001602 pixel.index+=(*k)*alpha*GetIndexPixelComponent(
1603 kernel_indexes+u);
cristyfccdab92009-11-30 16:43:57 +00001604 k++;
1605 }
1606 kernel_pixels+=image->columns+width;
1607 kernel_indexes+=image->columns+width;
1608 }
cristyc8d25bc2011-04-29 02:19:30 +00001609 SetIndexPixelComponent(convolve_indexes+x,ClampToQuantum(gamma*
1610 pixel.index));
cristyfccdab92009-11-30 16:43:57 +00001611 }
1612 }
cristy9d314ff2011-03-09 01:30:28 +00001613 indexes++;
cristyfccdab92009-11-30 16:43:57 +00001614 p++;
1615 q++;
1616 }
1617 sync=SyncCacheViewAuthenticPixels(convolve_view,exception);
1618 if (sync == MagickFalse)
1619 status=MagickFalse;
1620 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1621 {
1622 MagickBooleanType
1623 proceed;
1624
1625#if defined(MAGICKCORE_OPENMP_SUPPORT)
1626 #pragma omp critical (MagickCore_ConvolveImageChannel)
1627#endif
1628 proceed=SetImageProgress(image,ConvolveImageTag,progress++,image->rows);
1629 if (proceed == MagickFalse)
1630 status=MagickFalse;
1631 }
1632 }
1633 convolve_image->type=image->type;
1634 convolve_view=DestroyCacheView(convolve_view);
1635 image_view=DestroyCacheView(image_view);
1636 normal_kernel=(double *) RelinquishMagickMemory(normal_kernel);
1637 if (status == MagickFalse)
1638 convolve_image=DestroyImage(convolve_image);
1639 return(convolve_image);
1640}
1641
1642/*
1643%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1644% %
1645% %
1646% %
cristy3ed852e2009-09-05 21:47:34 +00001647% D e s p e c k l e I m a g e %
1648% %
1649% %
1650% %
1651%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1652%
1653% DespeckleImage() reduces the speckle noise in an image while perserving the
1654% edges of the original image.
1655%
1656% The format of the DespeckleImage method is:
1657%
1658% Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1659%
1660% A description of each parameter follows:
1661%
1662% o image: the image.
1663%
1664% o exception: return any errors or warnings in this structure.
1665%
1666*/
1667
cristybb503372010-05-27 20:51:26 +00001668static void Hull(const ssize_t x_offset,const ssize_t y_offset,
1669 const size_t columns,const size_t rows,Quantum *f,Quantum *g,
cristy3ed852e2009-09-05 21:47:34 +00001670 const int polarity)
1671{
cristy3ed852e2009-09-05 21:47:34 +00001672 MagickRealType
1673 v;
1674
cristy3ed852e2009-09-05 21:47:34 +00001675 register Quantum
1676 *p,
1677 *q,
1678 *r,
1679 *s;
1680
cristy117ff172010-08-15 21:35:32 +00001681 register ssize_t
1682 x;
1683
1684 ssize_t
1685 y;
1686
cristy3ed852e2009-09-05 21:47:34 +00001687 assert(f != (Quantum *) NULL);
1688 assert(g != (Quantum *) NULL);
1689 p=f+(columns+2);
1690 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001691 r=p+(y_offset*((ssize_t) columns+2)+x_offset);
1692 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001693 {
1694 p++;
1695 q++;
1696 r++;
1697 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001698 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001699 {
1700 v=(MagickRealType) (*p);
1701 if ((MagickRealType) *r >= (v+(MagickRealType) ScaleCharToQuantum(2)))
1702 v+=ScaleCharToQuantum(1);
1703 *q=(Quantum) v;
1704 p++;
1705 q++;
1706 r++;
1707 }
1708 else
cristybb503372010-05-27 20:51:26 +00001709 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001710 {
1711 v=(MagickRealType) (*p);
1712 if ((MagickRealType) *r <= (v-(MagickRealType) ScaleCharToQuantum(2)))
cristybb503372010-05-27 20:51:26 +00001713 v-=(ssize_t) ScaleCharToQuantum(1);
cristy3ed852e2009-09-05 21:47:34 +00001714 *q=(Quantum) v;
1715 p++;
1716 q++;
1717 r++;
1718 }
1719 p++;
1720 q++;
1721 r++;
1722 }
1723 p=f+(columns+2);
1724 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001725 r=q+(y_offset*((ssize_t) columns+2)+x_offset);
1726 s=q-(y_offset*((ssize_t) columns+2)+x_offset);
1727 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001728 {
1729 p++;
1730 q++;
1731 r++;
1732 s++;
1733 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001734 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001735 {
1736 v=(MagickRealType) (*q);
1737 if (((MagickRealType) *s >=
1738 (v+(MagickRealType) ScaleCharToQuantum(2))) &&
1739 ((MagickRealType) *r > v))
1740 v+=ScaleCharToQuantum(1);
1741 *p=(Quantum) v;
1742 p++;
1743 q++;
1744 r++;
1745 s++;
1746 }
1747 else
cristybb503372010-05-27 20:51:26 +00001748 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001749 {
1750 v=(MagickRealType) (*q);
1751 if (((MagickRealType) *s <=
1752 (v-(MagickRealType) ScaleCharToQuantum(2))) &&
1753 ((MagickRealType) *r < v))
1754 v-=(MagickRealType) ScaleCharToQuantum(1);
1755 *p=(Quantum) v;
1756 p++;
1757 q++;
1758 r++;
1759 s++;
1760 }
1761 p++;
1762 q++;
1763 r++;
1764 s++;
1765 }
1766}
1767
1768MagickExport Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1769{
1770#define DespeckleImageTag "Despeckle/Image"
1771
cristy2407fc22009-09-11 00:55:25 +00001772 CacheView
1773 *despeckle_view,
1774 *image_view;
1775
cristy3ed852e2009-09-05 21:47:34 +00001776 Image
1777 *despeckle_image;
1778
cristy3ed852e2009-09-05 21:47:34 +00001779 MagickBooleanType
1780 status;
1781
cristya58c3172011-02-19 19:23:11 +00001782 register ssize_t
1783 i;
1784
cristy3ed852e2009-09-05 21:47:34 +00001785 Quantum
cristy65b9f392011-02-22 14:22:54 +00001786 *restrict buffers,
1787 *restrict pixels;
cristy3ed852e2009-09-05 21:47:34 +00001788
1789 size_t
cristya58c3172011-02-19 19:23:11 +00001790 length,
1791 number_channels;
cristy117ff172010-08-15 21:35:32 +00001792
cristybb503372010-05-27 20:51:26 +00001793 static const ssize_t
cristy691a29e2009-09-11 00:44:10 +00001794 X[4] = {0, 1, 1,-1},
1795 Y[4] = {1, 0, 1, 1};
cristy3ed852e2009-09-05 21:47:34 +00001796
cristy3ed852e2009-09-05 21:47:34 +00001797 /*
1798 Allocate despeckled image.
1799 */
1800 assert(image != (const Image *) NULL);
1801 assert(image->signature == MagickSignature);
1802 if (image->debug != MagickFalse)
1803 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1804 assert(exception != (ExceptionInfo *) NULL);
1805 assert(exception->signature == MagickSignature);
1806 despeckle_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1807 exception);
1808 if (despeckle_image == (Image *) NULL)
1809 return((Image *) NULL);
1810 if (SetImageStorageClass(despeckle_image,DirectClass) == MagickFalse)
1811 {
1812 InheritException(exception,&despeckle_image->exception);
1813 despeckle_image=DestroyImage(despeckle_image);
1814 return((Image *) NULL);
1815 }
1816 /*
1817 Allocate image buffers.
1818 */
1819 length=(size_t) ((image->columns+2)*(image->rows+2));
cristy65b9f392011-02-22 14:22:54 +00001820 pixels=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1821 buffers=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1822 if ((pixels == (Quantum *) NULL) || (buffers == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001823 {
cristy65b9f392011-02-22 14:22:54 +00001824 if (buffers != (Quantum *) NULL)
1825 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1826 if (pixels != (Quantum *) NULL)
1827 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001828 despeckle_image=DestroyImage(despeckle_image);
1829 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1830 }
1831 /*
1832 Reduce speckle in the image.
1833 */
1834 status=MagickTrue;
cristy109695a2011-02-19 19:38:14 +00001835 number_channels=(size_t) (image->colorspace == CMYKColorspace ? 5 : 4);
cristy3ed852e2009-09-05 21:47:34 +00001836 image_view=AcquireCacheView(image);
1837 despeckle_view=AcquireCacheView(despeckle_image);
cristy8df3d002011-02-19 19:40:59 +00001838 for (i=0; i < (ssize_t) number_channels; i++)
cristy3ed852e2009-09-05 21:47:34 +00001839 {
cristy3ed852e2009-09-05 21:47:34 +00001840 register Quantum
1841 *buffer,
1842 *pixel;
1843
cristyc1488b52011-02-19 18:54:15 +00001844 register ssize_t
cristya58c3172011-02-19 19:23:11 +00001845 k,
cristyc1488b52011-02-19 18:54:15 +00001846 x;
1847
cristy117ff172010-08-15 21:35:32 +00001848 ssize_t
1849 j,
1850 y;
1851
cristy3ed852e2009-09-05 21:47:34 +00001852 if (status == MagickFalse)
1853 continue;
cristy65b9f392011-02-22 14:22:54 +00001854 pixel=pixels;
cristy3ed852e2009-09-05 21:47:34 +00001855 (void) ResetMagickMemory(pixel,0,length*sizeof(*pixel));
cristy65b9f392011-02-22 14:22:54 +00001856 buffer=buffers;
cristybb503372010-05-27 20:51:26 +00001857 j=(ssize_t) image->columns+2;
1858 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001859 {
cristya58c3172011-02-19 19:23:11 +00001860 register const IndexPacket
1861 *restrict indexes;
1862
cristy3ed852e2009-09-05 21:47:34 +00001863 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001864 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001865
1866 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1867 if (p == (const PixelPacket *) NULL)
1868 break;
cristya58c3172011-02-19 19:23:11 +00001869 indexes=GetCacheViewVirtualIndexQueue(image_view);
cristy3ed852e2009-09-05 21:47:34 +00001870 j++;
cristybb503372010-05-27 20:51:26 +00001871 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001872 {
cristya58c3172011-02-19 19:23:11 +00001873 switch (i)
cristy3ed852e2009-09-05 21:47:34 +00001874 {
cristyce70c172010-01-07 17:15:30 +00001875 case 0: pixel[j]=GetRedPixelComponent(p); break;
1876 case 1: pixel[j]=GetGreenPixelComponent(p); break;
1877 case 2: pixel[j]=GetBluePixelComponent(p); break;
1878 case 3: pixel[j]=GetOpacityPixelComponent(p); break;
cristy8a5d50e2011-04-17 23:43:51 +00001879 case 4: pixel[j]=GetBlackPixelComponent(indexes+x); break;
cristy3ed852e2009-09-05 21:47:34 +00001880 default: break;
1881 }
1882 p++;
1883 j++;
1884 }
1885 j++;
1886 }
cristy3ed852e2009-09-05 21:47:34 +00001887 (void) ResetMagickMemory(buffer,0,length*sizeof(*buffer));
cristya58c3172011-02-19 19:23:11 +00001888 for (k=0; k < 4; k++)
cristy3ed852e2009-09-05 21:47:34 +00001889 {
cristya58c3172011-02-19 19:23:11 +00001890 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,1);
1891 Hull(-X[k],-Y[k],image->columns,image->rows,pixel,buffer,1);
1892 Hull(-X[k],-Y[k],image->columns,image->rows,pixel,buffer,-1);
1893 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,-1);
cristy3ed852e2009-09-05 21:47:34 +00001894 }
cristybb503372010-05-27 20:51:26 +00001895 j=(ssize_t) image->columns+2;
1896 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001897 {
1898 MagickBooleanType
1899 sync;
1900
cristya58c3172011-02-19 19:23:11 +00001901 register IndexPacket
1902 *restrict indexes;
1903
cristy3ed852e2009-09-05 21:47:34 +00001904 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001905 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001906
1907 q=GetCacheViewAuthenticPixels(despeckle_view,0,y,despeckle_image->columns,
1908 1,exception);
1909 if (q == (PixelPacket *) NULL)
1910 break;
cristya58c3172011-02-19 19:23:11 +00001911 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristy3ed852e2009-09-05 21:47:34 +00001912 j++;
cristybb503372010-05-27 20:51:26 +00001913 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001914 {
cristya58c3172011-02-19 19:23:11 +00001915 switch (i)
cristy3ed852e2009-09-05 21:47:34 +00001916 {
cristyc8d25bc2011-04-29 02:19:30 +00001917 case 0: SetRedPixelComponent(q,pixel[j]); break;
1918 case 1: SetGreenPixelComponent(q,pixel[j]); break;
1919 case 2: SetBluePixelComponent(q,pixel[j]); break;
1920 case 3: SetOpacityPixelComponent(q,pixel[j]); break;
1921 case 4: SetIndexPixelComponent(indexes+x,pixel[j]); break;
cristy3ed852e2009-09-05 21:47:34 +00001922 default: break;
1923 }
1924 q++;
1925 j++;
1926 }
1927 sync=SyncCacheViewAuthenticPixels(despeckle_view,exception);
1928 if (sync == MagickFalse)
1929 {
1930 status=MagickFalse;
1931 break;
1932 }
1933 j++;
1934 }
1935 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1936 {
1937 MagickBooleanType
1938 proceed;
1939
cristya58c3172011-02-19 19:23:11 +00001940 proceed=SetImageProgress(image,DespeckleImageTag,(MagickOffsetType) i,
1941 number_channels);
cristy3ed852e2009-09-05 21:47:34 +00001942 if (proceed == MagickFalse)
1943 status=MagickFalse;
1944 }
1945 }
1946 despeckle_view=DestroyCacheView(despeckle_view);
1947 image_view=DestroyCacheView(image_view);
cristy65b9f392011-02-22 14:22:54 +00001948 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1949 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001950 despeckle_image->type=image->type;
1951 if (status == MagickFalse)
1952 despeckle_image=DestroyImage(despeckle_image);
1953 return(despeckle_image);
1954}
1955
1956/*
1957%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1958% %
1959% %
1960% %
1961% E d g e I m a g e %
1962% %
1963% %
1964% %
1965%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1966%
1967% EdgeImage() finds edges in an image. Radius defines the radius of the
1968% convolution filter. Use a radius of 0 and EdgeImage() selects a suitable
1969% radius for you.
1970%
1971% The format of the EdgeImage method is:
1972%
1973% Image *EdgeImage(const Image *image,const double radius,
1974% ExceptionInfo *exception)
1975%
1976% A description of each parameter follows:
1977%
1978% o image: the image.
1979%
1980% o radius: the radius of the pixel neighborhood.
1981%
1982% o exception: return any errors or warnings in this structure.
1983%
1984*/
1985MagickExport Image *EdgeImage(const Image *image,const double radius,
1986 ExceptionInfo *exception)
1987{
1988 Image
1989 *edge_image;
1990
1991 double
1992 *kernel;
1993
cristybb503372010-05-27 20:51:26 +00001994 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001995 i;
1996
cristybb503372010-05-27 20:51:26 +00001997 size_t
cristy3ed852e2009-09-05 21:47:34 +00001998 width;
1999
2000 assert(image != (const Image *) NULL);
2001 assert(image->signature == MagickSignature);
2002 if (image->debug != MagickFalse)
2003 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2004 assert(exception != (ExceptionInfo *) NULL);
2005 assert(exception->signature == MagickSignature);
2006 width=GetOptimalKernelWidth1D(radius,0.5);
2007 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2008 if (kernel == (double *) NULL)
2009 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00002010 for (i=0; i < (ssize_t) (width*width); i++)
cristy3ed852e2009-09-05 21:47:34 +00002011 kernel[i]=(-1.0);
2012 kernel[i/2]=(double) (width*width-1.0);
2013 edge_image=ConvolveImage(image,width,kernel,exception);
2014 kernel=(double *) RelinquishMagickMemory(kernel);
2015 return(edge_image);
2016}
2017
2018/*
2019%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2020% %
2021% %
2022% %
2023% E m b o s s I m a g e %
2024% %
2025% %
2026% %
2027%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2028%
2029% EmbossImage() returns a grayscale image with a three-dimensional effect.
2030% We convolve the image with a Gaussian operator of the given radius and
2031% standard deviation (sigma). For reasonable results, radius should be
2032% larger than sigma. Use a radius of 0 and Emboss() selects a suitable
2033% radius for you.
2034%
2035% The format of the EmbossImage method is:
2036%
2037% Image *EmbossImage(const Image *image,const double radius,
2038% const double sigma,ExceptionInfo *exception)
2039%
2040% A description of each parameter follows:
2041%
2042% o image: the image.
2043%
2044% o radius: the radius of the pixel neighborhood.
2045%
2046% o sigma: the standard deviation of the Gaussian, in pixels.
2047%
2048% o exception: return any errors or warnings in this structure.
2049%
2050*/
2051MagickExport Image *EmbossImage(const Image *image,const double radius,
2052 const double sigma,ExceptionInfo *exception)
2053{
2054 double
2055 *kernel;
2056
2057 Image
2058 *emboss_image;
2059
cristybb503372010-05-27 20:51:26 +00002060 register ssize_t
cristy47e00502009-12-17 19:19:57 +00002061 i;
2062
cristybb503372010-05-27 20:51:26 +00002063 size_t
cristy3ed852e2009-09-05 21:47:34 +00002064 width;
2065
cristy117ff172010-08-15 21:35:32 +00002066 ssize_t
2067 j,
2068 k,
2069 u,
2070 v;
2071
cristy3ed852e2009-09-05 21:47:34 +00002072 assert(image != (Image *) NULL);
2073 assert(image->signature == MagickSignature);
2074 if (image->debug != MagickFalse)
2075 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2076 assert(exception != (ExceptionInfo *) NULL);
2077 assert(exception->signature == MagickSignature);
2078 width=GetOptimalKernelWidth2D(radius,sigma);
2079 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2080 if (kernel == (double *) NULL)
2081 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00002082 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +00002083 k=j;
2084 i=0;
2085 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002086 {
cristy47e00502009-12-17 19:19:57 +00002087 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00002088 {
cristy4205a3c2010-09-12 20:19:59 +00002089 kernel[i]=(double) (((u < 0) || (v < 0) ? -8.0 : 8.0)*
cristy47e00502009-12-17 19:19:57 +00002090 exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
cristy4205a3c2010-09-12 20:19:59 +00002091 (2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +00002092 if (u != k)
cristy3ed852e2009-09-05 21:47:34 +00002093 kernel[i]=0.0;
2094 i++;
2095 }
cristy47e00502009-12-17 19:19:57 +00002096 k--;
cristy3ed852e2009-09-05 21:47:34 +00002097 }
2098 emboss_image=ConvolveImage(image,width,kernel,exception);
2099 if (emboss_image != (Image *) NULL)
2100 (void) EqualizeImage(emboss_image);
2101 kernel=(double *) RelinquishMagickMemory(kernel);
2102 return(emboss_image);
2103}
2104
2105/*
2106%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2107% %
2108% %
2109% %
cristy56a9e512010-01-06 18:18:55 +00002110% F i l t e r I m a g e %
2111% %
2112% %
2113% %
2114%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2115%
2116% FilterImage() applies a custom convolution kernel to the image.
2117%
2118% The format of the FilterImage method is:
2119%
cristy2be15382010-01-21 02:38:03 +00002120% Image *FilterImage(const Image *image,const KernelInfo *kernel,
cristy56a9e512010-01-06 18:18:55 +00002121% ExceptionInfo *exception)
2122% Image *FilterImageChannel(const Image *image,const ChannelType channel,
cristy2be15382010-01-21 02:38:03 +00002123% const KernelInfo *kernel,ExceptionInfo *exception)
cristy56a9e512010-01-06 18:18:55 +00002124%
2125% A description of each parameter follows:
2126%
2127% o image: the image.
2128%
2129% o channel: the channel type.
2130%
2131% o kernel: the filtering kernel.
2132%
2133% o exception: return any errors or warnings in this structure.
2134%
2135*/
2136
cristy2be15382010-01-21 02:38:03 +00002137MagickExport Image *FilterImage(const Image *image,const KernelInfo *kernel,
cristy56a9e512010-01-06 18:18:55 +00002138 ExceptionInfo *exception)
2139{
2140 Image
2141 *filter_image;
2142
2143 filter_image=FilterImageChannel(image,DefaultChannels,kernel,exception);
2144 return(filter_image);
2145}
2146
2147MagickExport Image *FilterImageChannel(const Image *image,
cristy2be15382010-01-21 02:38:03 +00002148 const ChannelType channel,const KernelInfo *kernel,ExceptionInfo *exception)
cristy56a9e512010-01-06 18:18:55 +00002149{
2150#define FilterImageTag "Filter/Image"
2151
2152 CacheView
2153 *filter_view,
2154 *image_view;
2155
cristy56a9e512010-01-06 18:18:55 +00002156 Image
2157 *filter_image;
2158
cristy56a9e512010-01-06 18:18:55 +00002159 MagickBooleanType
2160 status;
2161
cristybb503372010-05-27 20:51:26 +00002162 MagickOffsetType
2163 progress;
2164
cristy56a9e512010-01-06 18:18:55 +00002165 MagickPixelPacket
2166 bias;
2167
cristybb503372010-05-27 20:51:26 +00002168 ssize_t
2169 y;
2170
cristy56a9e512010-01-06 18:18:55 +00002171 /*
2172 Initialize filter image attributes.
2173 */
2174 assert(image != (Image *) NULL);
2175 assert(image->signature == MagickSignature);
2176 if (image->debug != MagickFalse)
2177 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2178 assert(exception != (ExceptionInfo *) NULL);
2179 assert(exception->signature == MagickSignature);
2180 if ((kernel->width % 2) == 0)
2181 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
2182 filter_image=CloneImage(image,0,0,MagickTrue,exception);
2183 if (filter_image == (Image *) NULL)
2184 return((Image *) NULL);
2185 if (SetImageStorageClass(filter_image,DirectClass) == MagickFalse)
2186 {
2187 InheritException(exception,&filter_image->exception);
2188 filter_image=DestroyImage(filter_image);
2189 return((Image *) NULL);
2190 }
2191 if (image->debug != MagickFalse)
2192 {
2193 char
2194 format[MaxTextExtent],
2195 *message;
2196
cristy117ff172010-08-15 21:35:32 +00002197 register const double
2198 *k;
2199
cristybb503372010-05-27 20:51:26 +00002200 ssize_t
cristy56a9e512010-01-06 18:18:55 +00002201 u,
2202 v;
2203
cristy56a9e512010-01-06 18:18:55 +00002204 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00002205 " FilterImage with %.20gx%.20g kernel:",(double) kernel->width,(double)
2206 kernel->height);
cristy56a9e512010-01-06 18:18:55 +00002207 message=AcquireString("");
2208 k=kernel->values;
cristybb503372010-05-27 20:51:26 +00002209 for (v=0; v < (ssize_t) kernel->height; v++)
cristy56a9e512010-01-06 18:18:55 +00002210 {
2211 *message='\0';
cristye8c25f92010-06-03 00:53:06 +00002212 (void) FormatMagickString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy56a9e512010-01-06 18:18:55 +00002213 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00002214 for (u=0; u < (ssize_t) kernel->width; u++)
cristy56a9e512010-01-06 18:18:55 +00002215 {
cristye7f51092010-01-17 00:39:37 +00002216 (void) FormatMagickString(format,MaxTextExtent,"%g ",*k++);
cristy56a9e512010-01-06 18:18:55 +00002217 (void) ConcatenateString(&message,format);
2218 }
2219 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
2220 }
2221 message=DestroyString(message);
2222 }
cristy36826ab2010-03-06 01:29:30 +00002223 status=AccelerateConvolveImage(image,kernel,filter_image,exception);
cristyd43a46b2010-01-21 02:13:41 +00002224 if (status == MagickTrue)
2225 return(filter_image);
cristy56a9e512010-01-06 18:18:55 +00002226 /*
2227 Filter image.
2228 */
2229 status=MagickTrue;
2230 progress=0;
2231 GetMagickPixelPacket(image,&bias);
2232 SetMagickPixelPacketBias(image,&bias);
2233 image_view=AcquireCacheView(image);
2234 filter_view=AcquireCacheView(filter_image);
2235#if defined(MAGICKCORE_OPENMP_SUPPORT)
2236 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2237#endif
cristybb503372010-05-27 20:51:26 +00002238 for (y=0; y < (ssize_t) image->rows; y++)
cristy56a9e512010-01-06 18:18:55 +00002239 {
2240 MagickBooleanType
2241 sync;
2242
2243 register const IndexPacket
2244 *restrict indexes;
2245
2246 register const PixelPacket
2247 *restrict p;
2248
2249 register IndexPacket
2250 *restrict filter_indexes;
2251
cristy56a9e512010-01-06 18:18:55 +00002252 register PixelPacket
2253 *restrict q;
2254
cristy117ff172010-08-15 21:35:32 +00002255 register ssize_t
2256 x;
2257
cristy56a9e512010-01-06 18:18:55 +00002258 if (status == MagickFalse)
2259 continue;
cristybb503372010-05-27 20:51:26 +00002260 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) kernel->width/2L),
cristy117ff172010-08-15 21:35:32 +00002261 y-(ssize_t) (kernel->height/2L),image->columns+kernel->width,
2262 kernel->height,exception);
cristy56a9e512010-01-06 18:18:55 +00002263 q=GetCacheViewAuthenticPixels(filter_view,0,y,filter_image->columns,1,
2264 exception);
2265 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2266 {
2267 status=MagickFalse;
2268 continue;
2269 }
2270 indexes=GetCacheViewVirtualIndexQueue(image_view);
2271 filter_indexes=GetCacheViewAuthenticIndexQueue(filter_view);
cristybb503372010-05-27 20:51:26 +00002272 for (x=0; x < (ssize_t) image->columns; x++)
cristy56a9e512010-01-06 18:18:55 +00002273 {
cristy56a9e512010-01-06 18:18:55 +00002274 MagickPixelPacket
2275 pixel;
2276
2277 register const double
2278 *restrict k;
2279
2280 register const PixelPacket
2281 *restrict kernel_pixels;
2282
cristybb503372010-05-27 20:51:26 +00002283 register ssize_t
cristy56a9e512010-01-06 18:18:55 +00002284 u;
2285
cristy117ff172010-08-15 21:35:32 +00002286 ssize_t
2287 v;
2288
cristy56a9e512010-01-06 18:18:55 +00002289 pixel=bias;
cristy36826ab2010-03-06 01:29:30 +00002290 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002291 kernel_pixels=p;
2292 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
2293 {
cristybb503372010-05-27 20:51:26 +00002294 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002295 {
cristybb503372010-05-27 20:51:26 +00002296 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002297 {
2298 pixel.red+=(*k)*kernel_pixels[u].red;
2299 pixel.green+=(*k)*kernel_pixels[u].green;
2300 pixel.blue+=(*k)*kernel_pixels[u].blue;
2301 k++;
2302 }
cristy36826ab2010-03-06 01:29:30 +00002303 kernel_pixels+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002304 }
2305 if ((channel & RedChannel) != 0)
cristya2d08742011-04-22 19:59:52 +00002306 SetRedPixelComponent(q,ClampToQuantum(pixel.red));
cristy56a9e512010-01-06 18:18:55 +00002307 if ((channel & GreenChannel) != 0)
cristya2d08742011-04-22 19:59:52 +00002308 SetGreenPixelComponent(q,ClampToQuantum(pixel.green));
cristy56a9e512010-01-06 18:18:55 +00002309 if ((channel & BlueChannel) != 0)
cristya2d08742011-04-22 19:59:52 +00002310 SetBluePixelComponent(q,ClampToQuantum(pixel.blue));
cristy56a9e512010-01-06 18:18:55 +00002311 if ((channel & OpacityChannel) != 0)
2312 {
cristy36826ab2010-03-06 01:29:30 +00002313 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002314 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00002315 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002316 {
cristybb503372010-05-27 20:51:26 +00002317 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002318 {
2319 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
2320 k++;
2321 }
cristy36826ab2010-03-06 01:29:30 +00002322 kernel_pixels+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002323 }
cristya2d08742011-04-22 19:59:52 +00002324 SetOpacityPixelComponent(q,ClampToQuantum(pixel.opacity));
cristy56a9e512010-01-06 18:18:55 +00002325 }
2326 if (((channel & IndexChannel) != 0) &&
2327 (image->colorspace == CMYKColorspace))
2328 {
2329 register const IndexPacket
2330 *restrict kernel_indexes;
2331
cristy36826ab2010-03-06 01:29:30 +00002332 k=kernel->values;
cristy9d314ff2011-03-09 01:30:28 +00002333 kernel_indexes=indexes;
cristybb503372010-05-27 20:51:26 +00002334 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002335 {
cristybb503372010-05-27 20:51:26 +00002336 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002337 {
cristyc8d25bc2011-04-29 02:19:30 +00002338 pixel.index+=(*k)*GetIndexPixelComponent(kernel_indexes+u);
cristy56a9e512010-01-06 18:18:55 +00002339 k++;
2340 }
cristy36826ab2010-03-06 01:29:30 +00002341 kernel_indexes+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002342 }
cristyc8d25bc2011-04-29 02:19:30 +00002343 SetIndexPixelComponent(filter_indexes+x,ClampToQuantum(
2344 pixel.index));
cristy56a9e512010-01-06 18:18:55 +00002345 }
2346 }
2347 else
2348 {
2349 MagickRealType
2350 alpha,
2351 gamma;
2352
2353 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00002354 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002355 {
cristybb503372010-05-27 20:51:26 +00002356 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002357 {
2358 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
cristyc8d25bc2011-04-29 02:19:30 +00002359 GetOpacityPixelComponent(kernel_pixels+u)));
2360 pixel.red+=(*k)*alpha*GetRedPixelComponent(kernel_pixels+u);
2361 pixel.green+=(*k)*alpha*GetGreenPixelComponent(kernel_pixels+u);
2362 pixel.blue+=(*k)*alpha*GetBluePixelComponent(kernel_pixels+u);
cristy56a9e512010-01-06 18:18:55 +00002363 gamma+=(*k)*alpha;
2364 k++;
2365 }
cristy36826ab2010-03-06 01:29:30 +00002366 kernel_pixels+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002367 }
2368 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
2369 if ((channel & RedChannel) != 0)
cristyc8d25bc2011-04-29 02:19:30 +00002370 SetRedPixelComponent(q,ClampToQuantum(gamma*pixel.red));
cristy56a9e512010-01-06 18:18:55 +00002371 if ((channel & GreenChannel) != 0)
cristyc8d25bc2011-04-29 02:19:30 +00002372 SetGreenPixelComponent(q,ClampToQuantum(gamma*pixel.green));
cristy56a9e512010-01-06 18:18:55 +00002373 if ((channel & BlueChannel) != 0)
cristyc8d25bc2011-04-29 02:19:30 +00002374 SetBluePixelComponent(q,ClampToQuantum(gamma*pixel.blue));
cristy56a9e512010-01-06 18:18:55 +00002375 if ((channel & OpacityChannel) != 0)
2376 {
cristy36826ab2010-03-06 01:29:30 +00002377 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002378 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00002379 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002380 {
cristybb503372010-05-27 20:51:26 +00002381 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002382 {
cristyc8d25bc2011-04-29 02:19:30 +00002383 pixel.opacity+=(*k)*GetOpacityPixelComponent(kernel_pixels+u);
cristy56a9e512010-01-06 18:18:55 +00002384 k++;
2385 }
cristy36826ab2010-03-06 01:29:30 +00002386 kernel_pixels+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002387 }
cristya2d08742011-04-22 19:59:52 +00002388 SetOpacityPixelComponent(q,ClampToQuantum(pixel.opacity));
cristy56a9e512010-01-06 18:18:55 +00002389 }
2390 if (((channel & IndexChannel) != 0) &&
2391 (image->colorspace == CMYKColorspace))
2392 {
2393 register const IndexPacket
2394 *restrict kernel_indexes;
2395
cristy36826ab2010-03-06 01:29:30 +00002396 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002397 kernel_pixels=p;
cristy9d314ff2011-03-09 01:30:28 +00002398 kernel_indexes=indexes;
cristybb503372010-05-27 20:51:26 +00002399 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002400 {
cristybb503372010-05-27 20:51:26 +00002401 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002402 {
2403 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
2404 kernel_pixels[u].opacity));
cristyc8d25bc2011-04-29 02:19:30 +00002405 pixel.index+=(*k)*alpha*GetIndexPixelComponent(
2406 kernel_indexes+u);
cristy56a9e512010-01-06 18:18:55 +00002407 k++;
2408 }
cristy36826ab2010-03-06 01:29:30 +00002409 kernel_pixels+=image->columns+kernel->width;
2410 kernel_indexes+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002411 }
cristyc8d25bc2011-04-29 02:19:30 +00002412 SetIndexPixelComponent(filter_indexes+x,ClampToQuantum(gamma*
2413 pixel.index));
cristy56a9e512010-01-06 18:18:55 +00002414 }
2415 }
cristy9d314ff2011-03-09 01:30:28 +00002416 indexes++;
cristy56a9e512010-01-06 18:18:55 +00002417 p++;
2418 q++;
2419 }
2420 sync=SyncCacheViewAuthenticPixels(filter_view,exception);
2421 if (sync == MagickFalse)
2422 status=MagickFalse;
2423 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2424 {
2425 MagickBooleanType
2426 proceed;
2427
2428#if defined(MAGICKCORE_OPENMP_SUPPORT)
2429 #pragma omp critical (MagickCore_FilterImageChannel)
2430#endif
2431 proceed=SetImageProgress(image,FilterImageTag,progress++,image->rows);
2432 if (proceed == MagickFalse)
2433 status=MagickFalse;
2434 }
2435 }
2436 filter_image->type=image->type;
2437 filter_view=DestroyCacheView(filter_view);
2438 image_view=DestroyCacheView(image_view);
cristy56a9e512010-01-06 18:18:55 +00002439 if (status == MagickFalse)
2440 filter_image=DestroyImage(filter_image);
2441 return(filter_image);
2442}
2443
2444/*
2445%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2446% %
2447% %
2448% %
cristy3ed852e2009-09-05 21:47:34 +00002449% G a u s s i a n B l u r I m a g e %
2450% %
2451% %
2452% %
2453%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2454%
2455% GaussianBlurImage() blurs an image. We convolve the image with a
2456% Gaussian operator of the given radius and standard deviation (sigma).
2457% For reasonable results, the radius should be larger than sigma. Use a
2458% radius of 0 and GaussianBlurImage() selects a suitable radius for you
2459%
2460% The format of the GaussianBlurImage method is:
2461%
2462% Image *GaussianBlurImage(const Image *image,onst double radius,
2463% const double sigma,ExceptionInfo *exception)
2464% Image *GaussianBlurImageChannel(const Image *image,
2465% const ChannelType channel,const double radius,const double sigma,
2466% ExceptionInfo *exception)
2467%
2468% A description of each parameter follows:
2469%
2470% o image: the image.
2471%
2472% o channel: the channel type.
2473%
2474% o radius: the radius of the Gaussian, in pixels, not counting the center
2475% pixel.
2476%
2477% o sigma: the standard deviation of the Gaussian, in pixels.
2478%
2479% o exception: return any errors or warnings in this structure.
2480%
2481*/
2482
2483MagickExport Image *GaussianBlurImage(const Image *image,const double radius,
2484 const double sigma,ExceptionInfo *exception)
2485{
2486 Image
2487 *blur_image;
2488
2489 blur_image=GaussianBlurImageChannel(image,DefaultChannels,radius,sigma,
2490 exception);
2491 return(blur_image);
2492}
2493
2494MagickExport Image *GaussianBlurImageChannel(const Image *image,
2495 const ChannelType channel,const double radius,const double sigma,
2496 ExceptionInfo *exception)
2497{
2498 double
2499 *kernel;
2500
2501 Image
2502 *blur_image;
2503
cristybb503372010-05-27 20:51:26 +00002504 register ssize_t
cristy47e00502009-12-17 19:19:57 +00002505 i;
2506
cristybb503372010-05-27 20:51:26 +00002507 size_t
cristy3ed852e2009-09-05 21:47:34 +00002508 width;
2509
cristy117ff172010-08-15 21:35:32 +00002510 ssize_t
2511 j,
2512 u,
2513 v;
2514
cristy3ed852e2009-09-05 21:47:34 +00002515 assert(image != (const Image *) NULL);
2516 assert(image->signature == MagickSignature);
2517 if (image->debug != MagickFalse)
2518 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2519 assert(exception != (ExceptionInfo *) NULL);
2520 assert(exception->signature == MagickSignature);
2521 width=GetOptimalKernelWidth2D(radius,sigma);
2522 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2523 if (kernel == (double *) NULL)
2524 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00002525 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00002526 i=0;
cristy47e00502009-12-17 19:19:57 +00002527 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002528 {
cristy47e00502009-12-17 19:19:57 +00002529 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00002530 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
2531 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00002532 }
2533 blur_image=ConvolveImageChannel(image,channel,width,kernel,exception);
2534 kernel=(double *) RelinquishMagickMemory(kernel);
2535 return(blur_image);
2536}
2537
2538/*
2539%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2540% %
2541% %
2542% %
cristy3ed852e2009-09-05 21:47:34 +00002543% M o t i o n B l u r I m a g e %
2544% %
2545% %
2546% %
2547%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2548%
2549% MotionBlurImage() simulates motion blur. We convolve the image with a
2550% Gaussian operator of the given radius and standard deviation (sigma).
2551% For reasonable results, radius should be larger than sigma. Use a
2552% radius of 0 and MotionBlurImage() selects a suitable radius for you.
2553% Angle gives the angle of the blurring motion.
2554%
2555% Andrew Protano contributed this effect.
2556%
2557% The format of the MotionBlurImage method is:
2558%
2559% Image *MotionBlurImage(const Image *image,const double radius,
2560% const double sigma,const double angle,ExceptionInfo *exception)
2561% Image *MotionBlurImageChannel(const Image *image,const ChannelType channel,
2562% const double radius,const double sigma,const double angle,
2563% ExceptionInfo *exception)
2564%
2565% A description of each parameter follows:
2566%
2567% o image: the image.
2568%
2569% o channel: the channel type.
2570%
2571% o radius: the radius of the Gaussian, in pixels, not counting the center
2572% o radius: the radius of the Gaussian, in pixels, not counting
2573% the center pixel.
2574%
2575% o sigma: the standard deviation of the Gaussian, in pixels.
2576%
cristycee97112010-05-28 00:44:52 +00002577% o angle: Apply the effect along this angle.
cristy3ed852e2009-09-05 21:47:34 +00002578%
2579% o exception: return any errors or warnings in this structure.
2580%
2581*/
2582
cristybb503372010-05-27 20:51:26 +00002583static double *GetMotionBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +00002584{
cristy3ed852e2009-09-05 21:47:34 +00002585 double
cristy47e00502009-12-17 19:19:57 +00002586 *kernel,
cristy3ed852e2009-09-05 21:47:34 +00002587 normalize;
2588
cristybb503372010-05-27 20:51:26 +00002589 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002590 i;
2591
2592 /*
cristy47e00502009-12-17 19:19:57 +00002593 Generate a 1-D convolution kernel.
cristy3ed852e2009-09-05 21:47:34 +00002594 */
2595 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
2596 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
2597 if (kernel == (double *) NULL)
2598 return(kernel);
cristy3ed852e2009-09-05 21:47:34 +00002599 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00002600 for (i=0; i < (ssize_t) width; i++)
cristy47e00502009-12-17 19:19:57 +00002601 {
cristy4205a3c2010-09-12 20:19:59 +00002602 kernel[i]=(double) (exp((-((double) i*i)/(double) (2.0*MagickSigma*
2603 MagickSigma)))/(MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00002604 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +00002605 }
cristybb503372010-05-27 20:51:26 +00002606 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002607 kernel[i]/=normalize;
2608 return(kernel);
2609}
2610
2611MagickExport Image *MotionBlurImage(const Image *image,const double radius,
2612 const double sigma,const double angle,ExceptionInfo *exception)
2613{
2614 Image
2615 *motion_blur;
2616
2617 motion_blur=MotionBlurImageChannel(image,DefaultChannels,radius,sigma,angle,
2618 exception);
2619 return(motion_blur);
2620}
2621
2622MagickExport Image *MotionBlurImageChannel(const Image *image,
2623 const ChannelType channel,const double radius,const double sigma,
2624 const double angle,ExceptionInfo *exception)
2625{
cristyc4c8d132010-01-07 01:58:38 +00002626 CacheView
2627 *blur_view,
2628 *image_view;
2629
cristy3ed852e2009-09-05 21:47:34 +00002630 double
2631 *kernel;
2632
2633 Image
2634 *blur_image;
2635
cristy3ed852e2009-09-05 21:47:34 +00002636 MagickBooleanType
2637 status;
2638
cristybb503372010-05-27 20:51:26 +00002639 MagickOffsetType
2640 progress;
2641
cristy3ed852e2009-09-05 21:47:34 +00002642 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00002643 bias;
cristy3ed852e2009-09-05 21:47:34 +00002644
2645 OffsetInfo
2646 *offset;
2647
2648 PointInfo
2649 point;
2650
cristybb503372010-05-27 20:51:26 +00002651 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002652 i;
2653
cristybb503372010-05-27 20:51:26 +00002654 size_t
cristy3ed852e2009-09-05 21:47:34 +00002655 width;
2656
cristybb503372010-05-27 20:51:26 +00002657 ssize_t
2658 y;
2659
cristy3ed852e2009-09-05 21:47:34 +00002660 assert(image != (Image *) NULL);
2661 assert(image->signature == MagickSignature);
2662 if (image->debug != MagickFalse)
2663 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2664 assert(exception != (ExceptionInfo *) NULL);
2665 width=GetOptimalKernelWidth1D(radius,sigma);
2666 kernel=GetMotionBlurKernel(width,sigma);
2667 if (kernel == (double *) NULL)
2668 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2669 offset=(OffsetInfo *) AcquireQuantumMemory(width,sizeof(*offset));
2670 if (offset == (OffsetInfo *) NULL)
2671 {
2672 kernel=(double *) RelinquishMagickMemory(kernel);
2673 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2674 }
2675 blur_image=CloneImage(image,0,0,MagickTrue,exception);
2676 if (blur_image == (Image *) NULL)
2677 {
2678 kernel=(double *) RelinquishMagickMemory(kernel);
2679 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2680 return((Image *) NULL);
2681 }
2682 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
2683 {
2684 kernel=(double *) RelinquishMagickMemory(kernel);
2685 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2686 InheritException(exception,&blur_image->exception);
2687 blur_image=DestroyImage(blur_image);
2688 return((Image *) NULL);
2689 }
2690 point.x=(double) width*sin(DegreesToRadians(angle));
2691 point.y=(double) width*cos(DegreesToRadians(angle));
cristybb503372010-05-27 20:51:26 +00002692 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002693 {
cristybb503372010-05-27 20:51:26 +00002694 offset[i].x=(ssize_t) ceil((double) (i*point.y)/hypot(point.x,point.y)-0.5);
2695 offset[i].y=(ssize_t) ceil((double) (i*point.x)/hypot(point.x,point.y)-0.5);
cristy3ed852e2009-09-05 21:47:34 +00002696 }
2697 /*
2698 Motion blur image.
2699 */
2700 status=MagickTrue;
2701 progress=0;
cristyddd82202009-11-03 20:14:50 +00002702 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00002703 image_view=AcquireCacheView(image);
2704 blur_view=AcquireCacheView(blur_image);
cristyb557a152011-02-22 12:14:30 +00002705#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00002706 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00002707#endif
cristybb503372010-05-27 20:51:26 +00002708 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002709 {
2710 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002711 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00002712
cristy3ed852e2009-09-05 21:47:34 +00002713 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002714 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002715
cristy117ff172010-08-15 21:35:32 +00002716 register ssize_t
2717 x;
2718
cristy3ed852e2009-09-05 21:47:34 +00002719 if (status == MagickFalse)
2720 continue;
2721 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
2722 exception);
2723 if (q == (PixelPacket *) NULL)
2724 {
2725 status=MagickFalse;
2726 continue;
2727 }
2728 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
cristybb503372010-05-27 20:51:26 +00002729 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002730 {
2731 MagickPixelPacket
2732 qixel;
2733
2734 PixelPacket
2735 pixel;
2736
cristy117ff172010-08-15 21:35:32 +00002737 register const IndexPacket
2738 *restrict indexes;
2739
cristy3ed852e2009-09-05 21:47:34 +00002740 register double
cristyc47d1f82009-11-26 01:44:43 +00002741 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00002742
cristybb503372010-05-27 20:51:26 +00002743 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002744 i;
2745
cristy3ed852e2009-09-05 21:47:34 +00002746 k=kernel;
cristyddd82202009-11-03 20:14:50 +00002747 qixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00002748 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
2749 {
cristybb503372010-05-27 20:51:26 +00002750 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002751 {
2752 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
2753 offset[i].y,&pixel,exception);
2754 qixel.red+=(*k)*pixel.red;
2755 qixel.green+=(*k)*pixel.green;
2756 qixel.blue+=(*k)*pixel.blue;
2757 qixel.opacity+=(*k)*pixel.opacity;
2758 if (image->colorspace == CMYKColorspace)
2759 {
2760 indexes=GetCacheViewVirtualIndexQueue(image_view);
2761 qixel.index+=(*k)*(*indexes);
2762 }
2763 k++;
2764 }
2765 if ((channel & RedChannel) != 0)
cristyc8d25bc2011-04-29 02:19:30 +00002766 SetRedPixelComponent(q,ClampToQuantum(qixel.red));
cristy3ed852e2009-09-05 21:47:34 +00002767 if ((channel & GreenChannel) != 0)
cristyc8d25bc2011-04-29 02:19:30 +00002768 SetGreenPixelComponent(q,ClampToQuantum(qixel.green));
cristy3ed852e2009-09-05 21:47:34 +00002769 if ((channel & BlueChannel) != 0)
cristyc8d25bc2011-04-29 02:19:30 +00002770 SetBluePixelComponent(q,ClampToQuantum(qixel.blue));
cristy3ed852e2009-09-05 21:47:34 +00002771 if ((channel & OpacityChannel) != 0)
cristyc8d25bc2011-04-29 02:19:30 +00002772 SetOpacityPixelComponent(q,ClampToQuantum(qixel.opacity));
cristy3ed852e2009-09-05 21:47:34 +00002773 if (((channel & IndexChannel) != 0) &&
2774 (image->colorspace == CMYKColorspace))
cristyc8d25bc2011-04-29 02:19:30 +00002775 SetIndexPixelComponent(blur_indexes+x,ClampToQuantum(qixel.index));
cristy3ed852e2009-09-05 21:47:34 +00002776 }
2777 else
2778 {
2779 MagickRealType
2780 alpha,
2781 gamma;
2782
2783 alpha=0.0;
2784 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00002785 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002786 {
2787 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
2788 offset[i].y,&pixel,exception);
cristy8a7ea362010-03-10 20:31:43 +00002789 alpha=(MagickRealType) (QuantumScale*
2790 GetAlphaPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002791 qixel.red+=(*k)*alpha*pixel.red;
2792 qixel.green+=(*k)*alpha*pixel.green;
2793 qixel.blue+=(*k)*alpha*pixel.blue;
2794 qixel.opacity+=(*k)*pixel.opacity;
2795 if (image->colorspace == CMYKColorspace)
2796 {
2797 indexes=GetCacheViewVirtualIndexQueue(image_view);
cristyc8d25bc2011-04-29 02:19:30 +00002798 qixel.index+=(*k)*alpha*GetIndexPixelComponent(indexes);
cristy3ed852e2009-09-05 21:47:34 +00002799 }
2800 gamma+=(*k)*alpha;
2801 k++;
2802 }
2803 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
2804 if ((channel & RedChannel) != 0)
cristyc8d25bc2011-04-29 02:19:30 +00002805 SetRedPixelComponent(q,ClampToQuantum(gamma*qixel.red));
cristy3ed852e2009-09-05 21:47:34 +00002806 if ((channel & GreenChannel) != 0)
cristyc8d25bc2011-04-29 02:19:30 +00002807 SetGreenPixelComponent(q,ClampToQuantum(gamma*qixel.green));
cristy3ed852e2009-09-05 21:47:34 +00002808 if ((channel & BlueChannel) != 0)
cristyc8d25bc2011-04-29 02:19:30 +00002809 SetBluePixelComponent(q,ClampToQuantum(gamma*qixel.blue));
cristy3ed852e2009-09-05 21:47:34 +00002810 if ((channel & OpacityChannel) != 0)
cristyc8d25bc2011-04-29 02:19:30 +00002811 SetOpacityPixelComponent(q,ClampToQuantum(qixel.opacity));
cristy3ed852e2009-09-05 21:47:34 +00002812 if (((channel & IndexChannel) != 0) &&
2813 (image->colorspace == CMYKColorspace))
cristyc8d25bc2011-04-29 02:19:30 +00002814 SetIndexPixelComponent(blur_indexes+x,ClampToQuantum(gamma*
2815 qixel.index));
cristy3ed852e2009-09-05 21:47:34 +00002816 }
2817 q++;
2818 }
2819 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
2820 status=MagickFalse;
2821 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2822 {
2823 MagickBooleanType
2824 proceed;
2825
cristyb557a152011-02-22 12:14:30 +00002826#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002827 #pragma omp critical (MagickCore_MotionBlurImageChannel)
2828#endif
2829 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
2830 if (proceed == MagickFalse)
2831 status=MagickFalse;
2832 }
2833 }
2834 blur_view=DestroyCacheView(blur_view);
2835 image_view=DestroyCacheView(image_view);
2836 kernel=(double *) RelinquishMagickMemory(kernel);
2837 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2838 if (status == MagickFalse)
2839 blur_image=DestroyImage(blur_image);
2840 return(blur_image);
2841}
2842
2843/*
2844%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2845% %
2846% %
2847% %
2848% P r e v i e w I m a g e %
2849% %
2850% %
2851% %
2852%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2853%
2854% PreviewImage() tiles 9 thumbnails of the specified image with an image
2855% processing operation applied with varying parameters. This may be helpful
2856% pin-pointing an appropriate parameter for a particular image processing
2857% operation.
2858%
2859% The format of the PreviewImages method is:
2860%
2861% Image *PreviewImages(const Image *image,const PreviewType preview,
2862% ExceptionInfo *exception)
2863%
2864% A description of each parameter follows:
2865%
2866% o image: the image.
2867%
2868% o preview: the image processing operation.
2869%
2870% o exception: return any errors or warnings in this structure.
2871%
2872*/
2873MagickExport Image *PreviewImage(const Image *image,const PreviewType preview,
2874 ExceptionInfo *exception)
2875{
2876#define NumberTiles 9
2877#define PreviewImageTag "Preview/Image"
2878#define DefaultPreviewGeometry "204x204+10+10"
2879
2880 char
2881 factor[MaxTextExtent],
2882 label[MaxTextExtent];
2883
2884 double
2885 degrees,
2886 gamma,
2887 percentage,
2888 radius,
2889 sigma,
2890 threshold;
2891
2892 Image
2893 *images,
2894 *montage_image,
2895 *preview_image,
2896 *thumbnail;
2897
2898 ImageInfo
2899 *preview_info;
2900
cristy3ed852e2009-09-05 21:47:34 +00002901 MagickBooleanType
2902 proceed;
2903
2904 MontageInfo
2905 *montage_info;
2906
2907 QuantizeInfo
2908 quantize_info;
2909
2910 RectangleInfo
2911 geometry;
2912
cristybb503372010-05-27 20:51:26 +00002913 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002914 i,
2915 x;
2916
cristybb503372010-05-27 20:51:26 +00002917 size_t
cristy3ed852e2009-09-05 21:47:34 +00002918 colors;
2919
cristy117ff172010-08-15 21:35:32 +00002920 ssize_t
2921 y;
2922
cristy3ed852e2009-09-05 21:47:34 +00002923 /*
2924 Open output image file.
2925 */
2926 assert(image != (Image *) NULL);
2927 assert(image->signature == MagickSignature);
2928 if (image->debug != MagickFalse)
2929 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2930 colors=2;
2931 degrees=0.0;
2932 gamma=(-0.2f);
2933 preview_info=AcquireImageInfo();
2934 SetGeometry(image,&geometry);
2935 (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y,
2936 &geometry.width,&geometry.height);
2937 images=NewImageList();
2938 percentage=12.5;
2939 GetQuantizeInfo(&quantize_info);
2940 radius=0.0;
2941 sigma=1.0;
2942 threshold=0.0;
2943 x=0;
2944 y=0;
2945 for (i=0; i < NumberTiles; i++)
2946 {
2947 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
2948 if (thumbnail == (Image *) NULL)
2949 break;
2950 (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL,
2951 (void *) NULL);
2952 (void) SetImageProperty(thumbnail,"label",DefaultTileLabel);
2953 if (i == (NumberTiles/2))
2954 {
2955 (void) QueryColorDatabase("#dfdfdf",&thumbnail->matte_color,exception);
2956 AppendImageToList(&images,thumbnail);
2957 continue;
2958 }
2959 switch (preview)
2960 {
2961 case RotatePreview:
2962 {
2963 degrees+=45.0;
2964 preview_image=RotateImage(thumbnail,degrees,exception);
cristye7f51092010-01-17 00:39:37 +00002965 (void) FormatMagickString(label,MaxTextExtent,"rotate %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002966 break;
2967 }
2968 case ShearPreview:
2969 {
2970 degrees+=5.0;
2971 preview_image=ShearImage(thumbnail,degrees,degrees,exception);
cristye7f51092010-01-17 00:39:37 +00002972 (void) FormatMagickString(label,MaxTextExtent,"shear %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002973 degrees,2.0*degrees);
2974 break;
2975 }
2976 case RollPreview:
2977 {
cristybb503372010-05-27 20:51:26 +00002978 x=(ssize_t) ((i+1)*thumbnail->columns)/NumberTiles;
2979 y=(ssize_t) ((i+1)*thumbnail->rows)/NumberTiles;
cristy3ed852e2009-09-05 21:47:34 +00002980 preview_image=RollImage(thumbnail,x,y,exception);
cristye8c25f92010-06-03 00:53:06 +00002981 (void) FormatMagickString(label,MaxTextExtent,"roll %+.20gx%+.20g",
2982 (double) x,(double) y);
cristy3ed852e2009-09-05 21:47:34 +00002983 break;
2984 }
2985 case HuePreview:
2986 {
2987 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2988 if (preview_image == (Image *) NULL)
2989 break;
cristye7f51092010-01-17 00:39:37 +00002990 (void) FormatMagickString(factor,MaxTextExtent,"100,100,%g",
cristy3ed852e2009-09-05 21:47:34 +00002991 2.0*percentage);
2992 (void) ModulateImage(preview_image,factor);
2993 (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor);
2994 break;
2995 }
2996 case SaturationPreview:
2997 {
2998 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2999 if (preview_image == (Image *) NULL)
3000 break;
cristye7f51092010-01-17 00:39:37 +00003001 (void) FormatMagickString(factor,MaxTextExtent,"100,%g",
cristy8cd5b312010-01-07 01:10:24 +00003002 2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00003003 (void) ModulateImage(preview_image,factor);
3004 (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor);
3005 break;
3006 }
3007 case BrightnessPreview:
3008 {
3009 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3010 if (preview_image == (Image *) NULL)
3011 break;
cristye7f51092010-01-17 00:39:37 +00003012 (void) FormatMagickString(factor,MaxTextExtent,"%g",2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00003013 (void) ModulateImage(preview_image,factor);
3014 (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor);
3015 break;
3016 }
3017 case GammaPreview:
3018 default:
3019 {
3020 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3021 if (preview_image == (Image *) NULL)
3022 break;
3023 gamma+=0.4f;
3024 (void) GammaImageChannel(preview_image,DefaultChannels,gamma);
cristye7f51092010-01-17 00:39:37 +00003025 (void) FormatMagickString(label,MaxTextExtent,"gamma %g",gamma);
cristy3ed852e2009-09-05 21:47:34 +00003026 break;
3027 }
3028 case SpiffPreview:
3029 {
3030 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3031 if (preview_image != (Image *) NULL)
3032 for (x=0; x < i; x++)
3033 (void) ContrastImage(preview_image,MagickTrue);
cristye8c25f92010-06-03 00:53:06 +00003034 (void) FormatMagickString(label,MaxTextExtent,"contrast (%.20g)",
3035 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00003036 break;
3037 }
3038 case DullPreview:
3039 {
3040 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3041 if (preview_image == (Image *) NULL)
3042 break;
3043 for (x=0; x < i; x++)
3044 (void) ContrastImage(preview_image,MagickFalse);
cristye8c25f92010-06-03 00:53:06 +00003045 (void) FormatMagickString(label,MaxTextExtent,"+contrast (%.20g)",
3046 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00003047 break;
3048 }
3049 case GrayscalePreview:
3050 {
3051 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3052 if (preview_image == (Image *) NULL)
3053 break;
3054 colors<<=1;
3055 quantize_info.number_colors=colors;
3056 quantize_info.colorspace=GRAYColorspace;
3057 (void) QuantizeImage(&quantize_info,preview_image);
3058 (void) FormatMagickString(label,MaxTextExtent,
cristye8c25f92010-06-03 00:53:06 +00003059 "-colorspace gray -colors %.20g",(double) colors);
cristy3ed852e2009-09-05 21:47:34 +00003060 break;
3061 }
3062 case QuantizePreview:
3063 {
3064 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3065 if (preview_image == (Image *) NULL)
3066 break;
3067 colors<<=1;
3068 quantize_info.number_colors=colors;
3069 (void) QuantizeImage(&quantize_info,preview_image);
cristye8c25f92010-06-03 00:53:06 +00003070 (void) FormatMagickString(label,MaxTextExtent,"colors %.20g",(double)
3071 colors);
cristy3ed852e2009-09-05 21:47:34 +00003072 break;
3073 }
3074 case DespecklePreview:
3075 {
3076 for (x=0; x < (i-1); x++)
3077 {
3078 preview_image=DespeckleImage(thumbnail,exception);
3079 if (preview_image == (Image *) NULL)
3080 break;
3081 thumbnail=DestroyImage(thumbnail);
3082 thumbnail=preview_image;
3083 }
3084 preview_image=DespeckleImage(thumbnail,exception);
3085 if (preview_image == (Image *) NULL)
3086 break;
cristye8c25f92010-06-03 00:53:06 +00003087 (void) FormatMagickString(label,MaxTextExtent,"despeckle (%.20g)",
3088 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00003089 break;
3090 }
3091 case ReduceNoisePreview:
3092 {
cristy95c38342011-03-18 22:39:51 +00003093 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) radius,
3094 (size_t) radius,exception);
cristye7f51092010-01-17 00:39:37 +00003095 (void) FormatMagickString(label,MaxTextExtent,"noise %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003096 break;
3097 }
3098 case AddNoisePreview:
3099 {
3100 switch ((int) i)
3101 {
3102 case 0:
3103 {
3104 (void) CopyMagickString(factor,"uniform",MaxTextExtent);
3105 break;
3106 }
3107 case 1:
3108 {
3109 (void) CopyMagickString(factor,"gaussian",MaxTextExtent);
3110 break;
3111 }
3112 case 2:
3113 {
3114 (void) CopyMagickString(factor,"multiplicative",MaxTextExtent);
3115 break;
3116 }
3117 case 3:
3118 {
3119 (void) CopyMagickString(factor,"impulse",MaxTextExtent);
3120 break;
3121 }
3122 case 4:
3123 {
3124 (void) CopyMagickString(factor,"laplacian",MaxTextExtent);
3125 break;
3126 }
3127 case 5:
3128 {
3129 (void) CopyMagickString(factor,"Poisson",MaxTextExtent);
3130 break;
3131 }
3132 default:
3133 {
3134 (void) CopyMagickString(thumbnail->magick,"NULL",MaxTextExtent);
3135 break;
3136 }
3137 }
cristyd76c51e2011-03-26 00:21:26 +00003138 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) i,
3139 (size_t) i,exception);
cristy3ed852e2009-09-05 21:47:34 +00003140 (void) FormatMagickString(label,MaxTextExtent,"+noise %s",factor);
3141 break;
3142 }
3143 case SharpenPreview:
3144 {
3145 preview_image=SharpenImage(thumbnail,radius,sigma,exception);
cristye7f51092010-01-17 00:39:37 +00003146 (void) FormatMagickString(label,MaxTextExtent,"sharpen %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003147 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00003148 break;
3149 }
3150 case BlurPreview:
3151 {
3152 preview_image=BlurImage(thumbnail,radius,sigma,exception);
cristye7f51092010-01-17 00:39:37 +00003153 (void) FormatMagickString(label,MaxTextExtent,"blur %gx%g",radius,
cristy3ed852e2009-09-05 21:47:34 +00003154 sigma);
3155 break;
3156 }
3157 case ThresholdPreview:
3158 {
3159 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3160 if (preview_image == (Image *) NULL)
3161 break;
3162 (void) BilevelImage(thumbnail,
3163 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
cristye7f51092010-01-17 00:39:37 +00003164 (void) FormatMagickString(label,MaxTextExtent,"threshold %g",
cristy3ed852e2009-09-05 21:47:34 +00003165 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
3166 break;
3167 }
3168 case EdgeDetectPreview:
3169 {
3170 preview_image=EdgeImage(thumbnail,radius,exception);
cristye7f51092010-01-17 00:39:37 +00003171 (void) FormatMagickString(label,MaxTextExtent,"edge %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003172 break;
3173 }
3174 case SpreadPreview:
3175 {
3176 preview_image=SpreadImage(thumbnail,radius,exception);
cristye7f51092010-01-17 00:39:37 +00003177 (void) FormatMagickString(label,MaxTextExtent,"spread %g",
cristy8cd5b312010-01-07 01:10:24 +00003178 radius+0.5);
cristy3ed852e2009-09-05 21:47:34 +00003179 break;
3180 }
3181 case SolarizePreview:
3182 {
3183 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3184 if (preview_image == (Image *) NULL)
3185 break;
3186 (void) SolarizeImage(preview_image,(double) QuantumRange*
3187 percentage/100.0);
cristye7f51092010-01-17 00:39:37 +00003188 (void) FormatMagickString(label,MaxTextExtent,"solarize %g",
cristy3ed852e2009-09-05 21:47:34 +00003189 (QuantumRange*percentage)/100.0);
3190 break;
3191 }
3192 case ShadePreview:
3193 {
3194 degrees+=10.0;
3195 preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees,
3196 exception);
cristye7f51092010-01-17 00:39:37 +00003197 (void) FormatMagickString(label,MaxTextExtent,"shade %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003198 degrees,degrees);
cristy3ed852e2009-09-05 21:47:34 +00003199 break;
3200 }
3201 case RaisePreview:
3202 {
3203 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3204 if (preview_image == (Image *) NULL)
3205 break;
cristybb503372010-05-27 20:51:26 +00003206 geometry.width=(size_t) (2*i+2);
3207 geometry.height=(size_t) (2*i+2);
cristy3ed852e2009-09-05 21:47:34 +00003208 geometry.x=i/2;
3209 geometry.y=i/2;
3210 (void) RaiseImage(preview_image,&geometry,MagickTrue);
cristye8c25f92010-06-03 00:53:06 +00003211 (void) FormatMagickString(label,MaxTextExtent,
cristy6d8abba2010-06-03 01:10:47 +00003212 "raise %.20gx%.20g%+.20g%+.20g",(double) geometry.width,(double)
cristye8c25f92010-06-03 00:53:06 +00003213 geometry.height,(double) geometry.x,(double) geometry.y);
cristy3ed852e2009-09-05 21:47:34 +00003214 break;
3215 }
3216 case SegmentPreview:
3217 {
3218 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3219 if (preview_image == (Image *) NULL)
3220 break;
3221 threshold+=0.4f;
3222 (void) SegmentImage(preview_image,RGBColorspace,MagickFalse,threshold,
3223 threshold);
cristye7f51092010-01-17 00:39:37 +00003224 (void) FormatMagickString(label,MaxTextExtent,"segment %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00003225 threshold,threshold);
3226 break;
3227 }
3228 case SwirlPreview:
3229 {
3230 preview_image=SwirlImage(thumbnail,degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003231 (void) FormatMagickString(label,MaxTextExtent,"swirl %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003232 degrees+=45.0;
3233 break;
3234 }
3235 case ImplodePreview:
3236 {
3237 degrees+=0.1f;
3238 preview_image=ImplodeImage(thumbnail,degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003239 (void) FormatMagickString(label,MaxTextExtent,"implode %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003240 break;
3241 }
3242 case WavePreview:
3243 {
3244 degrees+=5.0f;
3245 preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003246 (void) FormatMagickString(label,MaxTextExtent,"wave %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003247 0.5*degrees,2.0*degrees);
cristy3ed852e2009-09-05 21:47:34 +00003248 break;
3249 }
3250 case OilPaintPreview:
3251 {
3252 preview_image=OilPaintImage(thumbnail,(double) radius,exception);
cristye7f51092010-01-17 00:39:37 +00003253 (void) FormatMagickString(label,MaxTextExtent,"paint %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003254 break;
3255 }
3256 case CharcoalDrawingPreview:
3257 {
3258 preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma,
3259 exception);
cristye7f51092010-01-17 00:39:37 +00003260 (void) FormatMagickString(label,MaxTextExtent,"charcoal %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003261 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00003262 break;
3263 }
3264 case JPEGPreview:
3265 {
3266 char
3267 filename[MaxTextExtent];
3268
3269 int
3270 file;
3271
3272 MagickBooleanType
3273 status;
3274
3275 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3276 if (preview_image == (Image *) NULL)
3277 break;
cristybb503372010-05-27 20:51:26 +00003278 preview_info->quality=(size_t) percentage;
cristye8c25f92010-06-03 00:53:06 +00003279 (void) FormatMagickString(factor,MaxTextExtent,"%.20g",(double)
3280 preview_info->quality);
cristy3ed852e2009-09-05 21:47:34 +00003281 file=AcquireUniqueFileResource(filename);
3282 if (file != -1)
3283 file=close(file)-1;
3284 (void) FormatMagickString(preview_image->filename,MaxTextExtent,
3285 "jpeg:%s",filename);
3286 status=WriteImage(preview_info,preview_image);
3287 if (status != MagickFalse)
3288 {
3289 Image
3290 *quality_image;
3291
3292 (void) CopyMagickString(preview_info->filename,
3293 preview_image->filename,MaxTextExtent);
3294 quality_image=ReadImage(preview_info,exception);
3295 if (quality_image != (Image *) NULL)
3296 {
3297 preview_image=DestroyImage(preview_image);
3298 preview_image=quality_image;
3299 }
3300 }
3301 (void) RelinquishUniqueFileResource(preview_image->filename);
3302 if ((GetBlobSize(preview_image)/1024) >= 1024)
cristye7f51092010-01-17 00:39:37 +00003303 (void) FormatMagickString(label,MaxTextExtent,"quality %s\n%gmb ",
cristy3ed852e2009-09-05 21:47:34 +00003304 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
3305 1024.0/1024.0);
3306 else
3307 if (GetBlobSize(preview_image) >= 1024)
cristy8cd5b312010-01-07 01:10:24 +00003308 (void) FormatMagickString(label,MaxTextExtent,
cristye7f51092010-01-17 00:39:37 +00003309 "quality %s\n%gkb ",factor,(double) ((MagickOffsetType)
cristy8cd5b312010-01-07 01:10:24 +00003310 GetBlobSize(preview_image))/1024.0);
cristy3ed852e2009-09-05 21:47:34 +00003311 else
cristye8c25f92010-06-03 00:53:06 +00003312 (void) FormatMagickString(label,MaxTextExtent,"quality %s\n%.20gb ",
3313 factor,(double) GetBlobSize(thumbnail));
cristy3ed852e2009-09-05 21:47:34 +00003314 break;
3315 }
3316 }
3317 thumbnail=DestroyImage(thumbnail);
3318 percentage+=12.5;
3319 radius+=0.5;
3320 sigma+=0.25;
3321 if (preview_image == (Image *) NULL)
3322 break;
3323 (void) DeleteImageProperty(preview_image,"label");
3324 (void) SetImageProperty(preview_image,"label",label);
3325 AppendImageToList(&images,preview_image);
cristybb503372010-05-27 20:51:26 +00003326 proceed=SetImageProgress(image,PreviewImageTag,(MagickOffsetType) i,
3327 NumberTiles);
cristy3ed852e2009-09-05 21:47:34 +00003328 if (proceed == MagickFalse)
3329 break;
3330 }
3331 if (images == (Image *) NULL)
3332 {
3333 preview_info=DestroyImageInfo(preview_info);
3334 return((Image *) NULL);
3335 }
3336 /*
3337 Create the montage.
3338 */
3339 montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL);
3340 (void) CopyMagickString(montage_info->filename,image->filename,MaxTextExtent);
3341 montage_info->shadow=MagickTrue;
3342 (void) CloneString(&montage_info->tile,"3x3");
3343 (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry);
3344 (void) CloneString(&montage_info->frame,DefaultTileFrame);
3345 montage_image=MontageImages(images,montage_info,exception);
3346 montage_info=DestroyMontageInfo(montage_info);
3347 images=DestroyImageList(images);
3348 if (montage_image == (Image *) NULL)
3349 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3350 if (montage_image->montage != (char *) NULL)
3351 {
3352 /*
3353 Free image directory.
3354 */
3355 montage_image->montage=(char *) RelinquishMagickMemory(
3356 montage_image->montage);
3357 if (image->directory != (char *) NULL)
3358 montage_image->directory=(char *) RelinquishMagickMemory(
3359 montage_image->directory);
3360 }
3361 preview_info=DestroyImageInfo(preview_info);
3362 return(montage_image);
3363}
3364
3365/*
3366%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3367% %
3368% %
3369% %
3370% R a d i a l B l u r I m a g e %
3371% %
3372% %
3373% %
3374%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3375%
3376% RadialBlurImage() applies a radial blur to the image.
3377%
3378% Andrew Protano contributed this effect.
3379%
3380% The format of the RadialBlurImage method is:
3381%
3382% Image *RadialBlurImage(const Image *image,const double angle,
3383% ExceptionInfo *exception)
3384% Image *RadialBlurImageChannel(const Image *image,const ChannelType channel,
3385% const double angle,ExceptionInfo *exception)
3386%
3387% A description of each parameter follows:
3388%
3389% o image: the image.
3390%
3391% o channel: the channel type.
3392%
3393% o angle: the angle of the radial blur.
3394%
3395% o exception: return any errors or warnings in this structure.
3396%
3397*/
3398
3399MagickExport Image *RadialBlurImage(const Image *image,const double angle,
3400 ExceptionInfo *exception)
3401{
3402 Image
3403 *blur_image;
3404
3405 blur_image=RadialBlurImageChannel(image,DefaultChannels,angle,exception);
3406 return(blur_image);
3407}
3408
3409MagickExport Image *RadialBlurImageChannel(const Image *image,
3410 const ChannelType channel,const double angle,ExceptionInfo *exception)
3411{
cristyc4c8d132010-01-07 01:58:38 +00003412 CacheView
3413 *blur_view,
3414 *image_view;
3415
cristy3ed852e2009-09-05 21:47:34 +00003416 Image
3417 *blur_image;
3418
cristy3ed852e2009-09-05 21:47:34 +00003419 MagickBooleanType
3420 status;
3421
cristybb503372010-05-27 20:51:26 +00003422 MagickOffsetType
3423 progress;
3424
cristy3ed852e2009-09-05 21:47:34 +00003425 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00003426 bias;
cristy3ed852e2009-09-05 21:47:34 +00003427
3428 MagickRealType
3429 blur_radius,
3430 *cos_theta,
3431 offset,
3432 *sin_theta,
3433 theta;
3434
3435 PointInfo
3436 blur_center;
3437
cristybb503372010-05-27 20:51:26 +00003438 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003439 i;
3440
cristybb503372010-05-27 20:51:26 +00003441 size_t
cristy3ed852e2009-09-05 21:47:34 +00003442 n;
3443
cristybb503372010-05-27 20:51:26 +00003444 ssize_t
3445 y;
3446
cristy3ed852e2009-09-05 21:47:34 +00003447 /*
3448 Allocate blur image.
3449 */
3450 assert(image != (Image *) NULL);
3451 assert(image->signature == MagickSignature);
3452 if (image->debug != MagickFalse)
3453 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3454 assert(exception != (ExceptionInfo *) NULL);
3455 assert(exception->signature == MagickSignature);
3456 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3457 if (blur_image == (Image *) NULL)
3458 return((Image *) NULL);
3459 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3460 {
3461 InheritException(exception,&blur_image->exception);
3462 blur_image=DestroyImage(blur_image);
3463 return((Image *) NULL);
3464 }
3465 blur_center.x=(double) image->columns/2.0;
3466 blur_center.y=(double) image->rows/2.0;
3467 blur_radius=hypot(blur_center.x,blur_center.y);
cristy117ff172010-08-15 21:35:32 +00003468 n=(size_t) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+2UL);
cristy3ed852e2009-09-05 21:47:34 +00003469 theta=DegreesToRadians(angle)/(MagickRealType) (n-1);
3470 cos_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3471 sizeof(*cos_theta));
3472 sin_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3473 sizeof(*sin_theta));
3474 if ((cos_theta == (MagickRealType *) NULL) ||
3475 (sin_theta == (MagickRealType *) NULL))
3476 {
3477 blur_image=DestroyImage(blur_image);
3478 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3479 }
3480 offset=theta*(MagickRealType) (n-1)/2.0;
cristybb503372010-05-27 20:51:26 +00003481 for (i=0; i < (ssize_t) n; i++)
cristy3ed852e2009-09-05 21:47:34 +00003482 {
3483 cos_theta[i]=cos((double) (theta*i-offset));
3484 sin_theta[i]=sin((double) (theta*i-offset));
3485 }
3486 /*
3487 Radial blur image.
3488 */
3489 status=MagickTrue;
3490 progress=0;
cristyddd82202009-11-03 20:14:50 +00003491 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003492 image_view=AcquireCacheView(image);
3493 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003494#if defined(MAGICKCORE_OPENMP_SUPPORT)
3495 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003496#endif
cristybb503372010-05-27 20:51:26 +00003497 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003498 {
3499 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003500 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003501
3502 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003503 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00003504
cristy3ed852e2009-09-05 21:47:34 +00003505 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003506 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003507
cristy117ff172010-08-15 21:35:32 +00003508 register ssize_t
3509 x;
3510
cristy3ed852e2009-09-05 21:47:34 +00003511 if (status == MagickFalse)
3512 continue;
3513 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3514 exception);
3515 if (q == (PixelPacket *) NULL)
3516 {
3517 status=MagickFalse;
3518 continue;
3519 }
3520 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
cristybb503372010-05-27 20:51:26 +00003521 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003522 {
3523 MagickPixelPacket
3524 qixel;
3525
3526 MagickRealType
3527 normalize,
3528 radius;
3529
3530 PixelPacket
3531 pixel;
3532
3533 PointInfo
3534 center;
3535
cristybb503372010-05-27 20:51:26 +00003536 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003537 i;
3538
cristybb503372010-05-27 20:51:26 +00003539 size_t
cristy3ed852e2009-09-05 21:47:34 +00003540 step;
3541
3542 center.x=(double) x-blur_center.x;
3543 center.y=(double) y-blur_center.y;
3544 radius=hypot((double) center.x,center.y);
3545 if (radius == 0)
3546 step=1;
3547 else
3548 {
cristybb503372010-05-27 20:51:26 +00003549 step=(size_t) (blur_radius/radius);
cristy3ed852e2009-09-05 21:47:34 +00003550 if (step == 0)
3551 step=1;
3552 else
3553 if (step >= n)
3554 step=n-1;
3555 }
3556 normalize=0.0;
cristyddd82202009-11-03 20:14:50 +00003557 qixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00003558 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
3559 {
cristyeaedf062010-05-29 22:36:02 +00003560 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00003561 {
cristyeaedf062010-05-29 22:36:02 +00003562 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
3563 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
3564 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
3565 cos_theta[i]+0.5),&pixel,exception);
cristy3ed852e2009-09-05 21:47:34 +00003566 qixel.red+=pixel.red;
3567 qixel.green+=pixel.green;
3568 qixel.blue+=pixel.blue;
3569 qixel.opacity+=pixel.opacity;
3570 if (image->colorspace == CMYKColorspace)
3571 {
3572 indexes=GetCacheViewVirtualIndexQueue(image_view);
3573 qixel.index+=(*indexes);
3574 }
3575 normalize+=1.0;
3576 }
3577 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
3578 normalize);
3579 if ((channel & RedChannel) != 0)
cristyc8d25bc2011-04-29 02:19:30 +00003580 SetRedPixelComponent(q,ClampToQuantum(normalize*qixel.red));
cristy3ed852e2009-09-05 21:47:34 +00003581 if ((channel & GreenChannel) != 0)
cristyc8d25bc2011-04-29 02:19:30 +00003582 SetGreenPixelComponent(q,ClampToQuantum(normalize*qixel.green));
cristy3ed852e2009-09-05 21:47:34 +00003583 if ((channel & BlueChannel) != 0)
cristyc8d25bc2011-04-29 02:19:30 +00003584 SetBluePixelComponent(q,ClampToQuantum(normalize*qixel.blue));
cristy3ed852e2009-09-05 21:47:34 +00003585 if ((channel & OpacityChannel) != 0)
cristyc8d25bc2011-04-29 02:19:30 +00003586 SetOpacityPixelComponent(q,ClampToQuantum(normalize*qixel.opacity));
cristy3ed852e2009-09-05 21:47:34 +00003587 if (((channel & IndexChannel) != 0) &&
3588 (image->colorspace == CMYKColorspace))
cristyc8d25bc2011-04-29 02:19:30 +00003589 SetIndexPixelComponent(blur_indexes+x,ClampToQuantum(normalize*
3590 qixel.index));
cristy3ed852e2009-09-05 21:47:34 +00003591 }
3592 else
3593 {
3594 MagickRealType
3595 alpha,
3596 gamma;
3597
3598 alpha=1.0;
3599 gamma=0.0;
cristyeaedf062010-05-29 22:36:02 +00003600 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00003601 {
cristyeaedf062010-05-29 22:36:02 +00003602 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
3603 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
3604 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
3605 cos_theta[i]+0.5),&pixel,exception);
cristy46f08202010-01-10 04:04:21 +00003606 alpha=(MagickRealType) (QuantumScale*
3607 GetAlphaPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00003608 qixel.red+=alpha*pixel.red;
3609 qixel.green+=alpha*pixel.green;
3610 qixel.blue+=alpha*pixel.blue;
3611 qixel.opacity+=pixel.opacity;
3612 if (image->colorspace == CMYKColorspace)
3613 {
3614 indexes=GetCacheViewVirtualIndexQueue(image_view);
3615 qixel.index+=alpha*(*indexes);
3616 }
3617 gamma+=alpha;
3618 normalize+=1.0;
3619 }
3620 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3621 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
3622 normalize);
3623 if ((channel & RedChannel) != 0)
cristyc8d25bc2011-04-29 02:19:30 +00003624 SetRedPixelComponent(q,ClampToQuantum(gamma*qixel.red));
cristy3ed852e2009-09-05 21:47:34 +00003625 if ((channel & GreenChannel) != 0)
cristyc8d25bc2011-04-29 02:19:30 +00003626 SetGreenPixelComponent(q,ClampToQuantum(gamma*qixel.green));
cristy3ed852e2009-09-05 21:47:34 +00003627 if ((channel & BlueChannel) != 0)
cristyc8d25bc2011-04-29 02:19:30 +00003628 SetBluePixelComponent(q,ClampToQuantum(gamma*qixel.blue));
cristy3ed852e2009-09-05 21:47:34 +00003629 if ((channel & OpacityChannel) != 0)
cristyc8d25bc2011-04-29 02:19:30 +00003630 SetOpacityPixelComponent(q,ClampToQuantum(normalize*qixel.opacity));
cristy3ed852e2009-09-05 21:47:34 +00003631 if (((channel & IndexChannel) != 0) &&
3632 (image->colorspace == CMYKColorspace))
cristyc8d25bc2011-04-29 02:19:30 +00003633 SetIndexPixelComponent(blur_indexes+x,ClampToQuantum(gamma*
3634 qixel.index));
cristy3ed852e2009-09-05 21:47:34 +00003635 }
3636 q++;
3637 }
3638 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
3639 status=MagickFalse;
3640 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3641 {
3642 MagickBooleanType
3643 proceed;
3644
cristyb5d5f722009-11-04 03:03:49 +00003645#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003646 #pragma omp critical (MagickCore_RadialBlurImageChannel)
3647#endif
3648 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
3649 if (proceed == MagickFalse)
3650 status=MagickFalse;
3651 }
3652 }
3653 blur_view=DestroyCacheView(blur_view);
3654 image_view=DestroyCacheView(image_view);
3655 cos_theta=(MagickRealType *) RelinquishMagickMemory(cos_theta);
3656 sin_theta=(MagickRealType *) RelinquishMagickMemory(sin_theta);
3657 if (status == MagickFalse)
3658 blur_image=DestroyImage(blur_image);
3659 return(blur_image);
3660}
3661
3662/*
3663%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3664% %
3665% %
3666% %
cristy3ed852e2009-09-05 21:47:34 +00003667% S e l e c t i v e B l u r I m a g e %
3668% %
3669% %
3670% %
3671%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3672%
3673% SelectiveBlurImage() selectively blur pixels within a contrast threshold.
3674% It is similar to the unsharpen mask that sharpens everything with contrast
3675% above a certain threshold.
3676%
3677% The format of the SelectiveBlurImage method is:
3678%
3679% Image *SelectiveBlurImage(const Image *image,const double radius,
3680% const double sigma,const double threshold,ExceptionInfo *exception)
3681% Image *SelectiveBlurImageChannel(const Image *image,
3682% const ChannelType channel,const double radius,const double sigma,
3683% const double threshold,ExceptionInfo *exception)
3684%
3685% A description of each parameter follows:
3686%
3687% o image: the image.
3688%
3689% o channel: the channel type.
3690%
3691% o radius: the radius of the Gaussian, in pixels, not counting the center
3692% pixel.
3693%
3694% o sigma: the standard deviation of the Gaussian, in pixels.
3695%
3696% o threshold: only pixels within this contrast threshold are included
3697% in the blur operation.
3698%
3699% o exception: return any errors or warnings in this structure.
3700%
3701*/
3702
3703static inline MagickBooleanType SelectiveContrast(const PixelPacket *p,
3704 const PixelPacket *q,const double threshold)
3705{
3706 if (fabs(PixelIntensity(p)-PixelIntensity(q)) < threshold)
3707 return(MagickTrue);
3708 return(MagickFalse);
3709}
3710
3711MagickExport Image *SelectiveBlurImage(const Image *image,const double radius,
3712 const double sigma,const double threshold,ExceptionInfo *exception)
3713{
3714 Image
3715 *blur_image;
3716
3717 blur_image=SelectiveBlurImageChannel(image,DefaultChannels,radius,sigma,
3718 threshold,exception);
3719 return(blur_image);
3720}
3721
3722MagickExport Image *SelectiveBlurImageChannel(const Image *image,
3723 const ChannelType channel,const double radius,const double sigma,
3724 const double threshold,ExceptionInfo *exception)
3725{
3726#define SelectiveBlurImageTag "SelectiveBlur/Image"
3727
cristy47e00502009-12-17 19:19:57 +00003728 CacheView
3729 *blur_view,
3730 *image_view;
3731
cristy3ed852e2009-09-05 21:47:34 +00003732 double
cristy3ed852e2009-09-05 21:47:34 +00003733 *kernel;
3734
3735 Image
3736 *blur_image;
3737
cristy3ed852e2009-09-05 21:47:34 +00003738 MagickBooleanType
3739 status;
3740
cristybb503372010-05-27 20:51:26 +00003741 MagickOffsetType
3742 progress;
3743
cristy3ed852e2009-09-05 21:47:34 +00003744 MagickPixelPacket
cristy3ed852e2009-09-05 21:47:34 +00003745 bias;
3746
cristybb503372010-05-27 20:51:26 +00003747 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003748 i;
cristy3ed852e2009-09-05 21:47:34 +00003749
cristybb503372010-05-27 20:51:26 +00003750 size_t
cristy3ed852e2009-09-05 21:47:34 +00003751 width;
3752
cristybb503372010-05-27 20:51:26 +00003753 ssize_t
3754 j,
3755 u,
3756 v,
3757 y;
3758
cristy3ed852e2009-09-05 21:47:34 +00003759 /*
3760 Initialize blur image attributes.
3761 */
3762 assert(image != (Image *) NULL);
3763 assert(image->signature == MagickSignature);
3764 if (image->debug != MagickFalse)
3765 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3766 assert(exception != (ExceptionInfo *) NULL);
3767 assert(exception->signature == MagickSignature);
3768 width=GetOptimalKernelWidth1D(radius,sigma);
3769 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
3770 if (kernel == (double *) NULL)
3771 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00003772 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00003773 i=0;
cristy47e00502009-12-17 19:19:57 +00003774 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00003775 {
cristy47e00502009-12-17 19:19:57 +00003776 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00003777 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
3778 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00003779 }
3780 if (image->debug != MagickFalse)
3781 {
3782 char
3783 format[MaxTextExtent],
3784 *message;
3785
cristy117ff172010-08-15 21:35:32 +00003786 register const double
3787 *k;
3788
cristybb503372010-05-27 20:51:26 +00003789 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003790 u,
3791 v;
3792
cristy3ed852e2009-09-05 21:47:34 +00003793 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00003794 " SelectiveBlurImage with %.20gx%.20g kernel:",(double) width,(double)
3795 width);
cristy3ed852e2009-09-05 21:47:34 +00003796 message=AcquireString("");
3797 k=kernel;
cristybb503372010-05-27 20:51:26 +00003798 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003799 {
3800 *message='\0';
cristye8c25f92010-06-03 00:53:06 +00003801 (void) FormatMagickString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy3ed852e2009-09-05 21:47:34 +00003802 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00003803 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003804 {
3805 (void) FormatMagickString(format,MaxTextExtent,"%+f ",*k++);
3806 (void) ConcatenateString(&message,format);
3807 }
3808 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
3809 }
3810 message=DestroyString(message);
3811 }
3812 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3813 if (blur_image == (Image *) NULL)
3814 return((Image *) NULL);
3815 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3816 {
3817 InheritException(exception,&blur_image->exception);
3818 blur_image=DestroyImage(blur_image);
3819 return((Image *) NULL);
3820 }
3821 /*
3822 Threshold blur image.
3823 */
3824 status=MagickTrue;
3825 progress=0;
cristyddd82202009-11-03 20:14:50 +00003826 GetMagickPixelPacket(image,&bias);
3827 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003828 image_view=AcquireCacheView(image);
3829 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003830#if defined(MAGICKCORE_OPENMP_SUPPORT)
3831 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003832#endif
cristybb503372010-05-27 20:51:26 +00003833 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003834 {
3835 MagickBooleanType
3836 sync;
3837
3838 MagickRealType
3839 gamma;
3840
3841 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003842 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003843
3844 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003845 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003846
3847 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003848 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00003849
cristy3ed852e2009-09-05 21:47:34 +00003850 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003851 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003852
cristy117ff172010-08-15 21:35:32 +00003853 register ssize_t
3854 x;
3855
cristy3ed852e2009-09-05 21:47:34 +00003856 if (status == MagickFalse)
3857 continue;
cristy117ff172010-08-15 21:35:32 +00003858 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
3859 (width/2L),image->columns+width,width,exception);
cristy3ed852e2009-09-05 21:47:34 +00003860 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3861 exception);
3862 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
3863 {
3864 status=MagickFalse;
3865 continue;
3866 }
3867 indexes=GetCacheViewVirtualIndexQueue(image_view);
3868 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
cristybb503372010-05-27 20:51:26 +00003869 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003870 {
cristy3ed852e2009-09-05 21:47:34 +00003871 MagickPixelPacket
3872 pixel;
3873
3874 register const double
cristyc47d1f82009-11-26 01:44:43 +00003875 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00003876
cristybb503372010-05-27 20:51:26 +00003877 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003878 u;
3879
cristy117ff172010-08-15 21:35:32 +00003880 ssize_t
3881 j,
3882 v;
3883
cristyddd82202009-11-03 20:14:50 +00003884 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00003885 k=kernel;
3886 gamma=0.0;
3887 j=0;
3888 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
3889 {
cristybb503372010-05-27 20:51:26 +00003890 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003891 {
cristybb503372010-05-27 20:51:26 +00003892 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003893 {
3894 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
3895 {
cristyc8d25bc2011-04-29 02:19:30 +00003896 pixel.red+=(*k)*GetRedPixelComponent(p+u+j);
3897 pixel.green+=(*k)*GetGreenPixelComponent(p+u+j);
3898 pixel.blue+=(*k)*GetBluePixelComponent(p+u+j);
cristy3ed852e2009-09-05 21:47:34 +00003899 gamma+=(*k);
3900 k++;
3901 }
3902 }
cristyd99b0962010-05-29 23:14:26 +00003903 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003904 }
3905 if (gamma != 0.0)
3906 {
3907 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3908 if ((channel & RedChannel) != 0)
cristyc8d25bc2011-04-29 02:19:30 +00003909 SetRedPixelComponent(q,ClampToQuantum(gamma*pixel.red));
cristy3ed852e2009-09-05 21:47:34 +00003910 if ((channel & GreenChannel) != 0)
cristyc8d25bc2011-04-29 02:19:30 +00003911 SetGreenPixelComponent(q,ClampToQuantum(gamma*pixel.green));
cristy3ed852e2009-09-05 21:47:34 +00003912 if ((channel & BlueChannel) != 0)
cristyc8d25bc2011-04-29 02:19:30 +00003913 SetBluePixelComponent(q,ClampToQuantum(gamma*pixel.blue));
cristy3ed852e2009-09-05 21:47:34 +00003914 }
3915 if ((channel & OpacityChannel) != 0)
3916 {
3917 gamma=0.0;
3918 j=0;
cristybb503372010-05-27 20:51:26 +00003919 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003920 {
cristybb503372010-05-27 20:51:26 +00003921 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003922 {
3923 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
3924 {
3925 pixel.opacity+=(*k)*(p+u+j)->opacity;
3926 gamma+=(*k);
3927 k++;
3928 }
3929 }
cristyeaedf062010-05-29 22:36:02 +00003930 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003931 }
3932 if (gamma != 0.0)
3933 {
3934 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
3935 gamma);
cristyce70c172010-01-07 17:15:30 +00003936 SetOpacityPixelComponent(q,ClampToQuantum(gamma*
cristya2d08742011-04-22 19:59:52 +00003937 pixel.opacity));
cristy3ed852e2009-09-05 21:47:34 +00003938 }
3939 }
3940 if (((channel & IndexChannel) != 0) &&
3941 (image->colorspace == CMYKColorspace))
3942 {
3943 gamma=0.0;
3944 j=0;
cristybb503372010-05-27 20:51:26 +00003945 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003946 {
cristybb503372010-05-27 20:51:26 +00003947 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003948 {
3949 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
3950 {
cristyc8d25bc2011-04-29 02:19:30 +00003951 pixel.index+=(*k)*GetIndexPixelComponent(indexes+x+u+j);
cristy3ed852e2009-09-05 21:47:34 +00003952 gamma+=(*k);
3953 k++;
3954 }
3955 }
cristyeaedf062010-05-29 22:36:02 +00003956 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003957 }
3958 if (gamma != 0.0)
3959 {
3960 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
3961 gamma);
cristyc8d25bc2011-04-29 02:19:30 +00003962 SetIndexPixelComponent(blur_indexes+x,ClampToQuantum(gamma*
3963 pixel.index));
cristy3ed852e2009-09-05 21:47:34 +00003964 }
3965 }
3966 }
3967 else
3968 {
3969 MagickRealType
3970 alpha;
3971
cristybb503372010-05-27 20:51:26 +00003972 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003973 {
cristybb503372010-05-27 20:51:26 +00003974 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003975 {
3976 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
3977 {
cristy46f08202010-01-10 04:04:21 +00003978 alpha=(MagickRealType) (QuantumScale*
3979 GetAlphaPixelComponent(p+u+j));
cristyc8d25bc2011-04-29 02:19:30 +00003980 pixel.red+=(*k)*alpha*GetRedPixelComponent(p+u+j);
3981 pixel.green+=(*k)*alpha*GetGreenPixelComponent(p+u+j);
3982 pixel.blue+=(*k)*alpha*GetBluePixelComponent(p+u+j);
3983 pixel.opacity+=(*k)*GetOpacityPixelComponent(p+u+j);
cristy3ed852e2009-09-05 21:47:34 +00003984 gamma+=(*k)*alpha;
3985 k++;
3986 }
3987 }
cristyeaedf062010-05-29 22:36:02 +00003988 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003989 }
3990 if (gamma != 0.0)
3991 {
3992 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3993 if ((channel & RedChannel) != 0)
cristyc8d25bc2011-04-29 02:19:30 +00003994 SetRedPixelComponent(q,ClampToQuantum(gamma*pixel.red));
cristy3ed852e2009-09-05 21:47:34 +00003995 if ((channel & GreenChannel) != 0)
cristyc8d25bc2011-04-29 02:19:30 +00003996 SetGreenPixelComponent(q,ClampToQuantum(gamma*pixel.green));
cristy3ed852e2009-09-05 21:47:34 +00003997 if ((channel & BlueChannel) != 0)
cristyc8d25bc2011-04-29 02:19:30 +00003998 SetBluePixelComponent(q,ClampToQuantum(gamma*pixel.blue));
cristy3ed852e2009-09-05 21:47:34 +00003999 }
4000 if ((channel & OpacityChannel) != 0)
4001 {
4002 gamma=0.0;
4003 j=0;
cristybb503372010-05-27 20:51:26 +00004004 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00004005 {
cristybb503372010-05-27 20:51:26 +00004006 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00004007 {
4008 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4009 {
cristyc8d25bc2011-04-29 02:19:30 +00004010 pixel.opacity+=(*k)*GetOpacityPixelComponent(p+u+j);
cristy3ed852e2009-09-05 21:47:34 +00004011 gamma+=(*k);
4012 k++;
4013 }
4014 }
cristyeaedf062010-05-29 22:36:02 +00004015 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00004016 }
4017 if (gamma != 0.0)
4018 {
4019 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4020 gamma);
cristyc8d25bc2011-04-29 02:19:30 +00004021 SetOpacityPixelComponent(q,ClampToQuantum(pixel.opacity));
cristy3ed852e2009-09-05 21:47:34 +00004022 }
4023 }
4024 if (((channel & IndexChannel) != 0) &&
4025 (image->colorspace == CMYKColorspace))
4026 {
4027 gamma=0.0;
4028 j=0;
cristybb503372010-05-27 20:51:26 +00004029 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00004030 {
cristybb503372010-05-27 20:51:26 +00004031 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00004032 {
4033 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4034 {
cristy46f08202010-01-10 04:04:21 +00004035 alpha=(MagickRealType) (QuantumScale*
4036 GetAlphaPixelComponent(p+u+j));
cristyc8d25bc2011-04-29 02:19:30 +00004037 pixel.index+=(*k)*alpha*GetIndexPixelComponent(indexes+x+
4038 u+j);
cristy3ed852e2009-09-05 21:47:34 +00004039 gamma+=(*k);
4040 k++;
4041 }
4042 }
cristyeaedf062010-05-29 22:36:02 +00004043 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00004044 }
4045 if (gamma != 0.0)
4046 {
4047 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4048 gamma);
cristyc8d25bc2011-04-29 02:19:30 +00004049 SetIndexPixelComponent(blur_indexes+x,ClampToQuantum(gamma*
4050 pixel.index));
cristy3ed852e2009-09-05 21:47:34 +00004051 }
4052 }
4053 }
4054 p++;
4055 q++;
4056 }
4057 sync=SyncCacheViewAuthenticPixels(blur_view,exception);
4058 if (sync == MagickFalse)
4059 status=MagickFalse;
4060 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4061 {
4062 MagickBooleanType
4063 proceed;
4064
cristyb5d5f722009-11-04 03:03:49 +00004065#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004066 #pragma omp critical (MagickCore_SelectiveBlurImageChannel)
4067#endif
4068 proceed=SetImageProgress(image,SelectiveBlurImageTag,progress++,
4069 image->rows);
4070 if (proceed == MagickFalse)
4071 status=MagickFalse;
4072 }
4073 }
4074 blur_image->type=image->type;
4075 blur_view=DestroyCacheView(blur_view);
4076 image_view=DestroyCacheView(image_view);
4077 kernel=(double *) RelinquishMagickMemory(kernel);
4078 if (status == MagickFalse)
4079 blur_image=DestroyImage(blur_image);
4080 return(blur_image);
4081}
4082
4083/*
4084%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4085% %
4086% %
4087% %
4088% S h a d e I m a g e %
4089% %
4090% %
4091% %
4092%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4093%
4094% ShadeImage() shines a distant light on an image to create a
4095% three-dimensional effect. You control the positioning of the light with
4096% azimuth and elevation; azimuth is measured in degrees off the x axis
4097% and elevation is measured in pixels above the Z axis.
4098%
4099% The format of the ShadeImage method is:
4100%
4101% Image *ShadeImage(const Image *image,const MagickBooleanType gray,
4102% const double azimuth,const double elevation,ExceptionInfo *exception)
4103%
4104% A description of each parameter follows:
4105%
4106% o image: the image.
4107%
4108% o gray: A value other than zero shades the intensity of each pixel.
4109%
4110% o azimuth, elevation: Define the light source direction.
4111%
4112% o exception: return any errors or warnings in this structure.
4113%
4114*/
4115MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray,
4116 const double azimuth,const double elevation,ExceptionInfo *exception)
4117{
4118#define ShadeImageTag "Shade/Image"
4119
cristyc4c8d132010-01-07 01:58:38 +00004120 CacheView
4121 *image_view,
4122 *shade_view;
4123
cristy3ed852e2009-09-05 21:47:34 +00004124 Image
4125 *shade_image;
4126
cristy3ed852e2009-09-05 21:47:34 +00004127 MagickBooleanType
4128 status;
4129
cristybb503372010-05-27 20:51:26 +00004130 MagickOffsetType
4131 progress;
4132
cristy3ed852e2009-09-05 21:47:34 +00004133 PrimaryInfo
4134 light;
4135
cristybb503372010-05-27 20:51:26 +00004136 ssize_t
4137 y;
4138
cristy3ed852e2009-09-05 21:47:34 +00004139 /*
4140 Initialize shaded image attributes.
4141 */
4142 assert(image != (const Image *) NULL);
4143 assert(image->signature == MagickSignature);
4144 if (image->debug != MagickFalse)
4145 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4146 assert(exception != (ExceptionInfo *) NULL);
4147 assert(exception->signature == MagickSignature);
4148 shade_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
4149 if (shade_image == (Image *) NULL)
4150 return((Image *) NULL);
4151 if (SetImageStorageClass(shade_image,DirectClass) == MagickFalse)
4152 {
4153 InheritException(exception,&shade_image->exception);
4154 shade_image=DestroyImage(shade_image);
4155 return((Image *) NULL);
4156 }
4157 /*
4158 Compute the light vector.
4159 */
4160 light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))*
4161 cos(DegreesToRadians(elevation));
4162 light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))*
4163 cos(DegreesToRadians(elevation));
4164 light.z=(double) QuantumRange*sin(DegreesToRadians(elevation));
4165 /*
4166 Shade image.
4167 */
4168 status=MagickTrue;
4169 progress=0;
4170 image_view=AcquireCacheView(image);
4171 shade_view=AcquireCacheView(shade_image);
cristyb5d5f722009-11-04 03:03:49 +00004172#if defined(MAGICKCORE_OPENMP_SUPPORT)
4173 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004174#endif
cristybb503372010-05-27 20:51:26 +00004175 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00004176 {
4177 MagickRealType
4178 distance,
4179 normal_distance,
4180 shade;
4181
4182 PrimaryInfo
4183 normal;
4184
4185 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004186 *restrict p,
4187 *restrict s0,
4188 *restrict s1,
4189 *restrict s2;
cristy3ed852e2009-09-05 21:47:34 +00004190
cristy3ed852e2009-09-05 21:47:34 +00004191 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004192 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004193
cristy117ff172010-08-15 21:35:32 +00004194 register ssize_t
4195 x;
4196
cristy3ed852e2009-09-05 21:47:34 +00004197 if (status == MagickFalse)
4198 continue;
4199 p=GetCacheViewVirtualPixels(image_view,-1,y-1,image->columns+2,3,exception);
4200 q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1,
4201 exception);
4202 if ((p == (PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
4203 {
4204 status=MagickFalse;
4205 continue;
4206 }
4207 /*
4208 Shade this row of pixels.
4209 */
4210 normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */
4211 s0=p+1;
4212 s1=s0+image->columns+2;
4213 s2=s1+image->columns+2;
cristybb503372010-05-27 20:51:26 +00004214 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004215 {
4216 /*
4217 Determine the surface normal and compute shading.
4218 */
4219 normal.x=(double) (PixelIntensity(s0-1)+PixelIntensity(s1-1)+
4220 PixelIntensity(s2-1)-PixelIntensity(s0+1)-PixelIntensity(s1+1)-
4221 PixelIntensity(s2+1));
4222 normal.y=(double) (PixelIntensity(s2-1)+PixelIntensity(s2)+
4223 PixelIntensity(s2+1)-PixelIntensity(s0-1)-PixelIntensity(s0)-
4224 PixelIntensity(s0+1));
4225 if ((normal.x == 0.0) && (normal.y == 0.0))
4226 shade=light.z;
4227 else
4228 {
4229 shade=0.0;
4230 distance=normal.x*light.x+normal.y*light.y+normal.z*light.z;
4231 if (distance > MagickEpsilon)
4232 {
4233 normal_distance=
4234 normal.x*normal.x+normal.y*normal.y+normal.z*normal.z;
4235 if (normal_distance > (MagickEpsilon*MagickEpsilon))
4236 shade=distance/sqrt((double) normal_distance);
4237 }
4238 }
4239 if (gray != MagickFalse)
4240 {
cristyc8d25bc2011-04-29 02:19:30 +00004241 SetRedPixelComponent(q,shade);
4242 SetGreenPixelComponent(q,shade);
4243 SetBluePixelComponent(q,shade);
cristy3ed852e2009-09-05 21:47:34 +00004244 }
4245 else
4246 {
cristyc8d25bc2011-04-29 02:19:30 +00004247 SetRedPixelComponent(q,ClampToQuantum(QuantumScale*shade*
4248 GetRedPixelComponent(s1)));
4249 SetGreenPixelComponent(q,ClampToQuantum(QuantumScale*shade*
4250 GetGreenPixelComponent(s1)));
4251 SetBluePixelComponent(q,ClampToQuantum(QuantumScale*shade*
4252 GetBluePixelComponent(s1)));
cristy3ed852e2009-09-05 21:47:34 +00004253 }
4254 q->opacity=s1->opacity;
4255 s0++;
4256 s1++;
4257 s2++;
4258 q++;
4259 }
4260 if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
4261 status=MagickFalse;
4262 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4263 {
4264 MagickBooleanType
4265 proceed;
4266
cristyb5d5f722009-11-04 03:03:49 +00004267#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004268 #pragma omp critical (MagickCore_ShadeImage)
4269#endif
4270 proceed=SetImageProgress(image,ShadeImageTag,progress++,image->rows);
4271 if (proceed == MagickFalse)
4272 status=MagickFalse;
4273 }
4274 }
4275 shade_view=DestroyCacheView(shade_view);
4276 image_view=DestroyCacheView(image_view);
4277 if (status == MagickFalse)
4278 shade_image=DestroyImage(shade_image);
4279 return(shade_image);
4280}
4281
4282/*
4283%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4284% %
4285% %
4286% %
4287% S h a r p e n I m a g e %
4288% %
4289% %
4290% %
4291%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4292%
4293% SharpenImage() sharpens the image. We convolve the image with a Gaussian
4294% operator of the given radius and standard deviation (sigma). For
4295% reasonable results, radius should be larger than sigma. Use a radius of 0
4296% and SharpenImage() selects a suitable radius for you.
4297%
4298% Using a separable kernel would be faster, but the negative weights cancel
4299% out on the corners of the kernel producing often undesirable ringing in the
4300% filtered result; this can be avoided by using a 2D gaussian shaped image
4301% sharpening kernel instead.
4302%
4303% The format of the SharpenImage method is:
4304%
4305% Image *SharpenImage(const Image *image,const double radius,
4306% const double sigma,ExceptionInfo *exception)
4307% Image *SharpenImageChannel(const Image *image,const ChannelType channel,
4308% const double radius,const double sigma,ExceptionInfo *exception)
4309%
4310% A description of each parameter follows:
4311%
4312% o image: the image.
4313%
4314% o channel: the channel type.
4315%
4316% o radius: the radius of the Gaussian, in pixels, not counting the center
4317% pixel.
4318%
4319% o sigma: the standard deviation of the Laplacian, in pixels.
4320%
4321% o exception: return any errors or warnings in this structure.
4322%
4323*/
4324
4325MagickExport Image *SharpenImage(const Image *image,const double radius,
4326 const double sigma,ExceptionInfo *exception)
4327{
4328 Image
4329 *sharp_image;
4330
4331 sharp_image=SharpenImageChannel(image,DefaultChannels,radius,sigma,exception);
4332 return(sharp_image);
4333}
4334
4335MagickExport Image *SharpenImageChannel(const Image *image,
4336 const ChannelType channel,const double radius,const double sigma,
4337 ExceptionInfo *exception)
4338{
4339 double
cristy47e00502009-12-17 19:19:57 +00004340 *kernel,
4341 normalize;
cristy3ed852e2009-09-05 21:47:34 +00004342
4343 Image
4344 *sharp_image;
4345
cristybb503372010-05-27 20:51:26 +00004346 register ssize_t
cristy47e00502009-12-17 19:19:57 +00004347 i;
4348
cristybb503372010-05-27 20:51:26 +00004349 size_t
cristy3ed852e2009-09-05 21:47:34 +00004350 width;
4351
cristy117ff172010-08-15 21:35:32 +00004352 ssize_t
4353 j,
4354 u,
4355 v;
4356
cristy3ed852e2009-09-05 21:47:34 +00004357 assert(image != (const Image *) NULL);
4358 assert(image->signature == MagickSignature);
4359 if (image->debug != MagickFalse)
4360 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4361 assert(exception != (ExceptionInfo *) NULL);
4362 assert(exception->signature == MagickSignature);
4363 width=GetOptimalKernelWidth2D(radius,sigma);
4364 kernel=(double *) AcquireQuantumMemory((size_t) width*width,sizeof(*kernel));
4365 if (kernel == (double *) NULL)
4366 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy3ed852e2009-09-05 21:47:34 +00004367 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00004368 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +00004369 i=0;
4370 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00004371 {
cristy47e00502009-12-17 19:19:57 +00004372 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00004373 {
cristy4205a3c2010-09-12 20:19:59 +00004374 kernel[i]=(double) (-exp(-((double) u*u+v*v)/(2.0*MagickSigma*
4375 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00004376 normalize+=kernel[i];
4377 i++;
4378 }
4379 }
4380 kernel[i/2]=(double) ((-2.0)*normalize);
4381 sharp_image=ConvolveImageChannel(image,channel,width,kernel,exception);
4382 kernel=(double *) RelinquishMagickMemory(kernel);
4383 return(sharp_image);
4384}
4385
4386/*
4387%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4388% %
4389% %
4390% %
4391% S p r e a d I m a g e %
4392% %
4393% %
4394% %
4395%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4396%
4397% SpreadImage() is a special effects method that randomly displaces each
4398% pixel in a block defined by the radius parameter.
4399%
4400% The format of the SpreadImage method is:
4401%
4402% Image *SpreadImage(const Image *image,const double radius,
4403% ExceptionInfo *exception)
4404%
4405% A description of each parameter follows:
4406%
4407% o image: the image.
4408%
4409% o radius: Choose a random pixel in a neighborhood of this extent.
4410%
4411% o exception: return any errors or warnings in this structure.
4412%
4413*/
4414MagickExport Image *SpreadImage(const Image *image,const double radius,
4415 ExceptionInfo *exception)
4416{
4417#define SpreadImageTag "Spread/Image"
4418
cristyfa112112010-01-04 17:48:07 +00004419 CacheView
cristy9f7e7cb2011-03-26 00:49:57 +00004420 *image_view,
4421 *spread_view;
cristyfa112112010-01-04 17:48:07 +00004422
cristy3ed852e2009-09-05 21:47:34 +00004423 Image
4424 *spread_image;
4425
cristy3ed852e2009-09-05 21:47:34 +00004426 MagickBooleanType
4427 status;
4428
cristybb503372010-05-27 20:51:26 +00004429 MagickOffsetType
4430 progress;
4431
cristy3ed852e2009-09-05 21:47:34 +00004432 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00004433 bias;
cristy3ed852e2009-09-05 21:47:34 +00004434
4435 RandomInfo
cristyfa112112010-01-04 17:48:07 +00004436 **restrict random_info;
cristy3ed852e2009-09-05 21:47:34 +00004437
cristybb503372010-05-27 20:51:26 +00004438 size_t
cristy3ed852e2009-09-05 21:47:34 +00004439 width;
4440
cristybb503372010-05-27 20:51:26 +00004441 ssize_t
4442 y;
4443
cristy3ed852e2009-09-05 21:47:34 +00004444 /*
4445 Initialize spread image attributes.
4446 */
4447 assert(image != (Image *) NULL);
4448 assert(image->signature == MagickSignature);
4449 if (image->debug != MagickFalse)
4450 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4451 assert(exception != (ExceptionInfo *) NULL);
4452 assert(exception->signature == MagickSignature);
4453 spread_image=CloneImage(image,image->columns,image->rows,MagickTrue,
4454 exception);
4455 if (spread_image == (Image *) NULL)
4456 return((Image *) NULL);
4457 if (SetImageStorageClass(spread_image,DirectClass) == MagickFalse)
4458 {
4459 InheritException(exception,&spread_image->exception);
4460 spread_image=DestroyImage(spread_image);
4461 return((Image *) NULL);
4462 }
4463 /*
4464 Spread image.
4465 */
4466 status=MagickTrue;
4467 progress=0;
cristyddd82202009-11-03 20:14:50 +00004468 GetMagickPixelPacket(spread_image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00004469 width=GetOptimalKernelWidth1D(radius,0.5);
cristy3ed852e2009-09-05 21:47:34 +00004470 random_info=AcquireRandomInfoThreadSet();
cristy9f7e7cb2011-03-26 00:49:57 +00004471 image_view=AcquireCacheView(image);
4472 spread_view=AcquireCacheView(spread_image);
cristyb557a152011-02-22 12:14:30 +00004473#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00004474 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00004475#endif
cristybb503372010-05-27 20:51:26 +00004476 for (y=0; y < (ssize_t) spread_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00004477 {
cristy5c9e6f22010-09-17 17:31:01 +00004478 const int
4479 id = GetOpenMPThreadId();
cristy6ebe97c2010-07-03 01:17:28 +00004480
cristy3ed852e2009-09-05 21:47:34 +00004481 MagickPixelPacket
4482 pixel;
4483
4484 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004485 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00004486
cristy3ed852e2009-09-05 21:47:34 +00004487 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004488 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004489
cristy117ff172010-08-15 21:35:32 +00004490 register ssize_t
4491 x;
4492
cristy3ed852e2009-09-05 21:47:34 +00004493 if (status == MagickFalse)
4494 continue;
cristy9f7e7cb2011-03-26 00:49:57 +00004495 q=QueueCacheViewAuthenticPixels(spread_view,0,y,spread_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +00004496 exception);
4497 if (q == (PixelPacket *) NULL)
4498 {
4499 status=MagickFalse;
4500 continue;
4501 }
cristy9f7e7cb2011-03-26 00:49:57 +00004502 indexes=GetCacheViewAuthenticIndexQueue(spread_view);
cristyddd82202009-11-03 20:14:50 +00004503 pixel=bias;
cristybb503372010-05-27 20:51:26 +00004504 for (x=0; x < (ssize_t) spread_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004505 {
cristy8a7c3e82011-03-26 02:10:53 +00004506 (void) InterpolateMagickPixelPacket(image,image_view,
4507 UndefinedInterpolatePixel,(double) x+width*(GetPseudoRandomValue(
4508 random_info[id])-0.5),(double) y+width*(GetPseudoRandomValue(
4509 random_info[id])-0.5),&pixel,exception);
cristy3ed852e2009-09-05 21:47:34 +00004510 SetPixelPacket(spread_image,&pixel,q,indexes+x);
4511 q++;
4512 }
cristy9f7e7cb2011-03-26 00:49:57 +00004513 if (SyncCacheViewAuthenticPixels(spread_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00004514 status=MagickFalse;
4515 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4516 {
4517 MagickBooleanType
4518 proceed;
4519
cristyb557a152011-02-22 12:14:30 +00004520#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004521 #pragma omp critical (MagickCore_SpreadImage)
4522#endif
4523 proceed=SetImageProgress(image,SpreadImageTag,progress++,image->rows);
4524 if (proceed == MagickFalse)
4525 status=MagickFalse;
4526 }
4527 }
cristy9f7e7cb2011-03-26 00:49:57 +00004528 spread_view=DestroyCacheView(spread_view);
cristy3ed852e2009-09-05 21:47:34 +00004529 image_view=DestroyCacheView(image_view);
4530 random_info=DestroyRandomInfoThreadSet(random_info);
cristy3ed852e2009-09-05 21:47:34 +00004531 return(spread_image);
4532}
4533
4534/*
4535%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4536% %
4537% %
4538% %
cristy0834d642011-03-18 18:26:08 +00004539% S t a t i s t i c I m a g e %
4540% %
4541% %
4542% %
4543%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4544%
4545% StatisticImage() makes each pixel the min / max / median / mode / etc. of
cristy8d752042011-03-19 01:00:36 +00004546% the neighborhood of the specified width and height.
cristy0834d642011-03-18 18:26:08 +00004547%
4548% The format of the StatisticImage method is:
4549%
4550% Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00004551% const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00004552% Image *StatisticImageChannel(const Image *image,
4553% const ChannelType channel,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00004554% const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00004555%
4556% A description of each parameter follows:
4557%
4558% o image: the image.
4559%
4560% o channel: the image channel.
4561%
4562% o type: the statistic type (median, mode, etc.).
4563%
cristy95c38342011-03-18 22:39:51 +00004564% o width: the width of the pixel neighborhood.
4565%
4566% o height: the height of the pixel neighborhood.
cristy0834d642011-03-18 18:26:08 +00004567%
4568% o exception: return any errors or warnings in this structure.
4569%
4570*/
4571
cristy733678d2011-03-18 21:29:28 +00004572#define ListChannels 5
4573
4574typedef struct _ListNode
4575{
4576 size_t
4577 next[9],
4578 count,
4579 signature;
4580} ListNode;
4581
4582typedef struct _SkipList
4583{
4584 ssize_t
4585 level;
4586
4587 ListNode
4588 *nodes;
4589} SkipList;
4590
4591typedef struct _PixelList
4592{
4593 size_t
cristy6fc86bb2011-03-18 23:45:16 +00004594 length,
cristy733678d2011-03-18 21:29:28 +00004595 seed,
4596 signature;
4597
4598 SkipList
4599 lists[ListChannels];
4600} PixelList;
4601
4602static PixelList *DestroyPixelList(PixelList *pixel_list)
4603{
4604 register ssize_t
4605 i;
4606
4607 if (pixel_list == (PixelList *) NULL)
4608 return((PixelList *) NULL);
4609 for (i=0; i < ListChannels; i++)
4610 if (pixel_list->lists[i].nodes != (ListNode *) NULL)
4611 pixel_list->lists[i].nodes=(ListNode *) RelinquishMagickMemory(
4612 pixel_list->lists[i].nodes);
4613 pixel_list=(PixelList *) RelinquishMagickMemory(pixel_list);
4614 return(pixel_list);
4615}
4616
4617static PixelList **DestroyPixelListThreadSet(PixelList **pixel_list)
4618{
4619 register ssize_t
4620 i;
4621
4622 assert(pixel_list != (PixelList **) NULL);
4623 for (i=0; i < (ssize_t) GetOpenMPMaximumThreads(); i++)
4624 if (pixel_list[i] != (PixelList *) NULL)
4625 pixel_list[i]=DestroyPixelList(pixel_list[i]);
4626 pixel_list=(PixelList **) RelinquishMagickMemory(pixel_list);
4627 return(pixel_list);
4628}
4629
cristy6fc86bb2011-03-18 23:45:16 +00004630static PixelList *AcquirePixelList(const size_t width,const size_t height)
cristy733678d2011-03-18 21:29:28 +00004631{
4632 PixelList
4633 *pixel_list;
4634
4635 register ssize_t
4636 i;
4637
4638 pixel_list=(PixelList *) AcquireMagickMemory(sizeof(*pixel_list));
4639 if (pixel_list == (PixelList *) NULL)
4640 return(pixel_list);
4641 (void) ResetMagickMemory((void *) pixel_list,0,sizeof(*pixel_list));
cristy6fc86bb2011-03-18 23:45:16 +00004642 pixel_list->length=width*height;
cristy733678d2011-03-18 21:29:28 +00004643 for (i=0; i < ListChannels; i++)
4644 {
4645 pixel_list->lists[i].nodes=(ListNode *) AcquireQuantumMemory(65537UL,
4646 sizeof(*pixel_list->lists[i].nodes));
4647 if (pixel_list->lists[i].nodes == (ListNode *) NULL)
4648 return(DestroyPixelList(pixel_list));
4649 (void) ResetMagickMemory(pixel_list->lists[i].nodes,0,65537UL*
4650 sizeof(*pixel_list->lists[i].nodes));
4651 }
4652 pixel_list->signature=MagickSignature;
4653 return(pixel_list);
4654}
4655
cristy6fc86bb2011-03-18 23:45:16 +00004656static PixelList **AcquirePixelListThreadSet(const size_t width,
4657 const size_t height)
cristy733678d2011-03-18 21:29:28 +00004658{
4659 PixelList
4660 **pixel_list;
4661
4662 register ssize_t
4663 i;
4664
4665 size_t
4666 number_threads;
4667
4668 number_threads=GetOpenMPMaximumThreads();
4669 pixel_list=(PixelList **) AcquireQuantumMemory(number_threads,
4670 sizeof(*pixel_list));
4671 if (pixel_list == (PixelList **) NULL)
4672 return((PixelList **) NULL);
4673 (void) ResetMagickMemory(pixel_list,0,number_threads*sizeof(*pixel_list));
4674 for (i=0; i < (ssize_t) number_threads; i++)
4675 {
cristy6fc86bb2011-03-18 23:45:16 +00004676 pixel_list[i]=AcquirePixelList(width,height);
cristy733678d2011-03-18 21:29:28 +00004677 if (pixel_list[i] == (PixelList *) NULL)
4678 return(DestroyPixelListThreadSet(pixel_list));
4679 }
4680 return(pixel_list);
4681}
4682
4683static void AddNodePixelList(PixelList *pixel_list,const ssize_t channel,
4684 const size_t color)
4685{
4686 register SkipList
4687 *list;
4688
4689 register ssize_t
4690 level;
4691
4692 size_t
4693 search,
4694 update[9];
4695
4696 /*
4697 Initialize the node.
4698 */
4699 list=pixel_list->lists+channel;
4700 list->nodes[color].signature=pixel_list->signature;
4701 list->nodes[color].count=1;
4702 /*
4703 Determine where it belongs in the list.
4704 */
4705 search=65536UL;
4706 for (level=list->level; level >= 0; level--)
4707 {
4708 while (list->nodes[search].next[level] < color)
4709 search=list->nodes[search].next[level];
4710 update[level]=search;
4711 }
4712 /*
4713 Generate a pseudo-random level for this node.
4714 */
4715 for (level=0; ; level++)
4716 {
4717 pixel_list->seed=(pixel_list->seed*42893621L)+1L;
4718 if ((pixel_list->seed & 0x300) != 0x300)
4719 break;
4720 }
4721 if (level > 8)
4722 level=8;
4723 if (level > (list->level+2))
4724 level=list->level+2;
4725 /*
4726 If we're raising the list's level, link back to the root node.
4727 */
4728 while (level > list->level)
4729 {
4730 list->level++;
4731 update[list->level]=65536UL;
4732 }
4733 /*
4734 Link the node into the skip-list.
4735 */
4736 do
4737 {
4738 list->nodes[color].next[level]=list->nodes[update[level]].next[level];
4739 list->nodes[update[level]].next[level]=color;
cristy3cba8ca2011-03-19 01:29:12 +00004740 } while (level-- > 0);
cristy733678d2011-03-18 21:29:28 +00004741}
4742
cristy6fc86bb2011-03-18 23:45:16 +00004743static MagickPixelPacket GetMaximumPixelList(PixelList *pixel_list)
4744{
4745 MagickPixelPacket
4746 pixel;
4747
4748 register SkipList
4749 *list;
4750
4751 register ssize_t
4752 channel;
4753
4754 size_t
cristyd76c51e2011-03-26 00:21:26 +00004755 color,
4756 maximum;
cristy49f37242011-03-22 18:18:23 +00004757
4758 ssize_t
cristy6fc86bb2011-03-18 23:45:16 +00004759 count;
4760
4761 unsigned short
cristyd76c51e2011-03-26 00:21:26 +00004762 channels[ListChannels];
cristy6fc86bb2011-03-18 23:45:16 +00004763
4764 /*
4765 Find the maximum value for each of the color.
4766 */
4767 for (channel=0; channel < 5; channel++)
4768 {
4769 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004770 color=65536L;
cristy6fc86bb2011-03-18 23:45:16 +00004771 count=0;
cristy49f37242011-03-22 18:18:23 +00004772 maximum=list->nodes[color].next[0];
cristy6fc86bb2011-03-18 23:45:16 +00004773 do
4774 {
4775 color=list->nodes[color].next[0];
cristy49f37242011-03-22 18:18:23 +00004776 if (color > maximum)
4777 maximum=color;
cristy6fc86bb2011-03-18 23:45:16 +00004778 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004779 } while (count < (ssize_t) pixel_list->length);
cristy49f37242011-03-22 18:18:23 +00004780 channels[channel]=(unsigned short) maximum;
4781 }
4782 GetMagickPixelPacket((const Image *) NULL,&pixel);
4783 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4784 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4785 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
4786 pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
4787 pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]);
4788 return(pixel);
4789}
4790
4791static MagickPixelPacket GetMeanPixelList(PixelList *pixel_list)
4792{
4793 MagickPixelPacket
4794 pixel;
4795
cristy80a99a32011-03-30 01:30:23 +00004796 MagickRealType
4797 sum;
4798
cristy49f37242011-03-22 18:18:23 +00004799 register SkipList
4800 *list;
4801
4802 register ssize_t
4803 channel;
4804
4805 size_t
cristy80a99a32011-03-30 01:30:23 +00004806 color;
cristy49f37242011-03-22 18:18:23 +00004807
4808 ssize_t
4809 count;
4810
4811 unsigned short
4812 channels[ListChannels];
4813
4814 /*
4815 Find the mean value for each of the color.
4816 */
4817 for (channel=0; channel < 5; channel++)
4818 {
4819 list=pixel_list->lists+channel;
4820 color=65536L;
4821 count=0;
cristy80a99a32011-03-30 01:30:23 +00004822 sum=0.0;
cristy49f37242011-03-22 18:18:23 +00004823 do
4824 {
4825 color=list->nodes[color].next[0];
cristy80a99a32011-03-30 01:30:23 +00004826 sum+=(MagickRealType) list->nodes[color].count*color;
cristy49f37242011-03-22 18:18:23 +00004827 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004828 } while (count < (ssize_t) pixel_list->length);
cristy80a99a32011-03-30 01:30:23 +00004829 sum/=pixel_list->length;
4830 channels[channel]=(unsigned short) sum;
cristy6fc86bb2011-03-18 23:45:16 +00004831 }
4832 GetMagickPixelPacket((const Image *) NULL,&pixel);
4833 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4834 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4835 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
4836 pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
4837 pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]);
4838 return(pixel);
4839}
4840
cristy733678d2011-03-18 21:29:28 +00004841static MagickPixelPacket GetMedianPixelList(PixelList *pixel_list)
4842{
4843 MagickPixelPacket
4844 pixel;
4845
4846 register SkipList
4847 *list;
4848
4849 register ssize_t
4850 channel;
4851
4852 size_t
cristy49f37242011-03-22 18:18:23 +00004853 color;
4854
4855 ssize_t
cristy733678d2011-03-18 21:29:28 +00004856 count;
4857
4858 unsigned short
4859 channels[ListChannels];
4860
4861 /*
4862 Find the median value for each of the color.
4863 */
cristy733678d2011-03-18 21:29:28 +00004864 for (channel=0; channel < 5; channel++)
4865 {
4866 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004867 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004868 count=0;
4869 do
4870 {
4871 color=list->nodes[color].next[0];
4872 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004873 } while (count <= (ssize_t) (pixel_list->length >> 1));
cristy6fc86bb2011-03-18 23:45:16 +00004874 channels[channel]=(unsigned short) color;
4875 }
4876 GetMagickPixelPacket((const Image *) NULL,&pixel);
4877 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4878 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4879 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
4880 pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
4881 pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]);
4882 return(pixel);
4883}
4884
4885static MagickPixelPacket GetMinimumPixelList(PixelList *pixel_list)
4886{
4887 MagickPixelPacket
4888 pixel;
4889
4890 register SkipList
4891 *list;
4892
4893 register ssize_t
4894 channel;
4895
4896 size_t
cristyd76c51e2011-03-26 00:21:26 +00004897 color,
4898 minimum;
cristy6fc86bb2011-03-18 23:45:16 +00004899
cristy49f37242011-03-22 18:18:23 +00004900 ssize_t
4901 count;
4902
cristy6fc86bb2011-03-18 23:45:16 +00004903 unsigned short
cristyd76c51e2011-03-26 00:21:26 +00004904 channels[ListChannels];
cristy6fc86bb2011-03-18 23:45:16 +00004905
4906 /*
4907 Find the minimum value for each of the color.
4908 */
4909 for (channel=0; channel < 5; channel++)
4910 {
4911 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004912 count=0;
cristy6fc86bb2011-03-18 23:45:16 +00004913 color=65536UL;
cristy49f37242011-03-22 18:18:23 +00004914 minimum=list->nodes[color].next[0];
4915 do
4916 {
4917 color=list->nodes[color].next[0];
4918 if (color < minimum)
4919 minimum=color;
4920 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004921 } while (count < (ssize_t) pixel_list->length);
cristy49f37242011-03-22 18:18:23 +00004922 channels[channel]=(unsigned short) minimum;
cristy733678d2011-03-18 21:29:28 +00004923 }
4924 GetMagickPixelPacket((const Image *) NULL,&pixel);
4925 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4926 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4927 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
4928 pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
4929 pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]);
4930 return(pixel);
4931}
4932
4933static MagickPixelPacket GetModePixelList(PixelList *pixel_list)
4934{
4935 MagickPixelPacket
4936 pixel;
4937
4938 register SkipList
4939 *list;
4940
4941 register ssize_t
4942 channel;
4943
4944 size_t
4945 color,
cristy733678d2011-03-18 21:29:28 +00004946 max_count,
cristy6fc86bb2011-03-18 23:45:16 +00004947 mode;
cristy733678d2011-03-18 21:29:28 +00004948
cristy49f37242011-03-22 18:18:23 +00004949 ssize_t
4950 count;
4951
cristy733678d2011-03-18 21:29:28 +00004952 unsigned short
4953 channels[5];
4954
4955 /*
4956 Make each pixel the 'predominate color' of the specified neighborhood.
4957 */
cristy733678d2011-03-18 21:29:28 +00004958 for (channel=0; channel < 5; channel++)
4959 {
4960 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004961 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004962 mode=color;
4963 max_count=list->nodes[mode].count;
4964 count=0;
4965 do
4966 {
4967 color=list->nodes[color].next[0];
4968 if (list->nodes[color].count > max_count)
4969 {
4970 mode=color;
4971 max_count=list->nodes[mode].count;
4972 }
4973 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004974 } while (count < (ssize_t) pixel_list->length);
cristy733678d2011-03-18 21:29:28 +00004975 channels[channel]=(unsigned short) mode;
4976 }
4977 GetMagickPixelPacket((const Image *) NULL,&pixel);
4978 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4979 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4980 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
4981 pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
4982 pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]);
4983 return(pixel);
4984}
4985
4986static MagickPixelPacket GetNonpeakPixelList(PixelList *pixel_list)
4987{
4988 MagickPixelPacket
4989 pixel;
4990
4991 register SkipList
4992 *list;
4993
4994 register ssize_t
4995 channel;
4996
4997 size_t
cristy733678d2011-03-18 21:29:28 +00004998 color,
cristy733678d2011-03-18 21:29:28 +00004999 next,
5000 previous;
5001
cristy49f37242011-03-22 18:18:23 +00005002 ssize_t
5003 count;
5004
cristy733678d2011-03-18 21:29:28 +00005005 unsigned short
5006 channels[5];
5007
5008 /*
cristy49f37242011-03-22 18:18:23 +00005009 Finds the non peak value for each of the colors.
cristy733678d2011-03-18 21:29:28 +00005010 */
cristy733678d2011-03-18 21:29:28 +00005011 for (channel=0; channel < 5; channel++)
5012 {
5013 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00005014 color=65536L;
cristy733678d2011-03-18 21:29:28 +00005015 next=list->nodes[color].next[0];
5016 count=0;
5017 do
5018 {
5019 previous=color;
5020 color=next;
5021 next=list->nodes[color].next[0];
5022 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00005023 } while (count <= (ssize_t) (pixel_list->length >> 1));
cristy733678d2011-03-18 21:29:28 +00005024 if ((previous == 65536UL) && (next != 65536UL))
5025 color=next;
5026 else
5027 if ((previous != 65536UL) && (next == 65536UL))
5028 color=previous;
5029 channels[channel]=(unsigned short) color;
5030 }
5031 GetMagickPixelPacket((const Image *) NULL,&pixel);
5032 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
5033 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
5034 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
5035 pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
5036 pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]);
5037 return(pixel);
5038}
5039
cristy9a68cbb2011-03-29 00:51:23 +00005040static MagickPixelPacket GetStandardDeviationPixelList(PixelList *pixel_list)
5041{
5042 MagickPixelPacket
5043 pixel;
5044
cristy80a99a32011-03-30 01:30:23 +00005045 MagickRealType
5046 sum,
5047 sum_squared;
5048
cristy9a68cbb2011-03-29 00:51:23 +00005049 register SkipList
5050 *list;
5051
5052 register ssize_t
5053 channel;
5054
5055 size_t
cristy80a99a32011-03-30 01:30:23 +00005056 color;
cristy9a68cbb2011-03-29 00:51:23 +00005057
5058 ssize_t
5059 count;
5060
5061 unsigned short
5062 channels[ListChannels];
5063
5064 /*
cristy80a99a32011-03-30 01:30:23 +00005065 Find the standard-deviation value for each of the color.
cristy9a68cbb2011-03-29 00:51:23 +00005066 */
5067 for (channel=0; channel < 5; channel++)
5068 {
5069 list=pixel_list->lists+channel;
5070 color=65536L;
5071 count=0;
cristy80a99a32011-03-30 01:30:23 +00005072 sum=0.0;
5073 sum_squared=0.0;
cristy9a68cbb2011-03-29 00:51:23 +00005074 do
5075 {
cristy80a99a32011-03-30 01:30:23 +00005076 register ssize_t
5077 i;
5078
cristy9a68cbb2011-03-29 00:51:23 +00005079 color=list->nodes[color].next[0];
cristy80a99a32011-03-30 01:30:23 +00005080 sum+=(MagickRealType) list->nodes[color].count*color;
5081 for (i=0; i < (ssize_t) list->nodes[color].count; i++)
5082 sum_squared+=((MagickRealType) color)*((MagickRealType) color);
cristy9a68cbb2011-03-29 00:51:23 +00005083 count+=list->nodes[color].count;
5084 } while (count < (ssize_t) pixel_list->length);
cristy80a99a32011-03-30 01:30:23 +00005085 sum/=pixel_list->length;
5086 sum_squared/=pixel_list->length;
5087 channels[channel]=(unsigned short) sqrt(sum_squared-(sum*sum));
cristy9a68cbb2011-03-29 00:51:23 +00005088 }
5089 GetMagickPixelPacket((const Image *) NULL,&pixel);
5090 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
5091 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
5092 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
5093 pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
5094 pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]);
5095 return(pixel);
5096}
5097
cristy733678d2011-03-18 21:29:28 +00005098static inline void InsertPixelList(const Image *image,const PixelPacket *pixel,
5099 const IndexPacket *indexes,PixelList *pixel_list)
5100{
5101 size_t
5102 signature;
5103
5104 unsigned short
5105 index;
5106
cristyc8d25bc2011-04-29 02:19:30 +00005107 index=ScaleQuantumToShort(GetRedPixelComponent(pixel));
cristy733678d2011-03-18 21:29:28 +00005108 signature=pixel_list->lists[0].nodes[index].signature;
5109 if (signature == pixel_list->signature)
5110 pixel_list->lists[0].nodes[index].count++;
5111 else
5112 AddNodePixelList(pixel_list,0,index);
cristyc8d25bc2011-04-29 02:19:30 +00005113 index=ScaleQuantumToShort(GetGreenPixelComponent(pixel));
cristy733678d2011-03-18 21:29:28 +00005114 signature=pixel_list->lists[1].nodes[index].signature;
5115 if (signature == pixel_list->signature)
5116 pixel_list->lists[1].nodes[index].count++;
5117 else
5118 AddNodePixelList(pixel_list,1,index);
cristyc8d25bc2011-04-29 02:19:30 +00005119 index=ScaleQuantumToShort(GetBluePixelComponent(pixel));
cristy733678d2011-03-18 21:29:28 +00005120 signature=pixel_list->lists[2].nodes[index].signature;
5121 if (signature == pixel_list->signature)
5122 pixel_list->lists[2].nodes[index].count++;
5123 else
5124 AddNodePixelList(pixel_list,2,index);
cristyc8d25bc2011-04-29 02:19:30 +00005125 index=ScaleQuantumToShort(GetOpacityPixelComponent(pixel));
cristy733678d2011-03-18 21:29:28 +00005126 signature=pixel_list->lists[3].nodes[index].signature;
5127 if (signature == pixel_list->signature)
5128 pixel_list->lists[3].nodes[index].count++;
5129 else
5130 AddNodePixelList(pixel_list,3,index);
5131 if (image->colorspace == CMYKColorspace)
cristyc8d25bc2011-04-29 02:19:30 +00005132 index=ScaleQuantumToShort(GetIndexPixelComponent(indexes));
cristy733678d2011-03-18 21:29:28 +00005133 signature=pixel_list->lists[4].nodes[index].signature;
5134 if (signature == pixel_list->signature)
5135 pixel_list->lists[4].nodes[index].count++;
5136 else
5137 AddNodePixelList(pixel_list,4,index);
5138}
5139
cristy80c99742011-04-04 14:46:39 +00005140static inline MagickRealType MagickAbsoluteValue(const MagickRealType x)
5141{
5142 if (x < 0)
5143 return(-x);
5144 return(x);
5145}
5146
cristy733678d2011-03-18 21:29:28 +00005147static void ResetPixelList(PixelList *pixel_list)
5148{
5149 int
5150 level;
5151
5152 register ListNode
5153 *root;
5154
5155 register SkipList
5156 *list;
5157
5158 register ssize_t
5159 channel;
5160
5161 /*
5162 Reset the skip-list.
5163 */
5164 for (channel=0; channel < 5; channel++)
5165 {
5166 list=pixel_list->lists+channel;
5167 root=list->nodes+65536UL;
5168 list->level=0;
5169 for (level=0; level < 9; level++)
5170 root->next[level]=65536UL;
5171 }
5172 pixel_list->seed=pixel_list->signature++;
5173}
5174
cristy0834d642011-03-18 18:26:08 +00005175MagickExport Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00005176 const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00005177{
cristy95c38342011-03-18 22:39:51 +00005178 Image
5179 *statistic_image;
5180
5181 statistic_image=StatisticImageChannel(image,DefaultChannels,type,width,
5182 height,exception);
5183 return(statistic_image);
cristy0834d642011-03-18 18:26:08 +00005184}
5185
5186MagickExport Image *StatisticImageChannel(const Image *image,
cristy95c38342011-03-18 22:39:51 +00005187 const ChannelType channel,const StatisticType type,const size_t width,
5188 const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00005189{
cristy3cba8ca2011-03-19 01:29:12 +00005190#define StatisticWidth \
cristyd76c51e2011-03-26 00:21:26 +00005191 (width == 0 ? GetOptimalKernelWidth2D((double) width,0.5) : width)
cristy3cba8ca2011-03-19 01:29:12 +00005192#define StatisticHeight \
cristyd76c51e2011-03-26 00:21:26 +00005193 (height == 0 ? GetOptimalKernelWidth2D((double) height,0.5) : height)
cristy0834d642011-03-18 18:26:08 +00005194#define StatisticImageTag "Statistic/Image"
5195
5196 CacheView
5197 *image_view,
5198 *statistic_view;
5199
5200 Image
5201 *statistic_image;
5202
5203 MagickBooleanType
5204 status;
5205
5206 MagickOffsetType
5207 progress;
5208
5209 PixelList
5210 **restrict pixel_list;
5211
cristy0834d642011-03-18 18:26:08 +00005212 ssize_t
5213 y;
5214
5215 /*
5216 Initialize statistics image attributes.
5217 */
5218 assert(image != (Image *) NULL);
5219 assert(image->signature == MagickSignature);
5220 if (image->debug != MagickFalse)
5221 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5222 assert(exception != (ExceptionInfo *) NULL);
5223 assert(exception->signature == MagickSignature);
cristy0834d642011-03-18 18:26:08 +00005224 statistic_image=CloneImage(image,image->columns,image->rows,MagickTrue,
5225 exception);
5226 if (statistic_image == (Image *) NULL)
5227 return((Image *) NULL);
5228 if (SetImageStorageClass(statistic_image,DirectClass) == MagickFalse)
5229 {
5230 InheritException(exception,&statistic_image->exception);
5231 statistic_image=DestroyImage(statistic_image);
5232 return((Image *) NULL);
5233 }
cristy6fc86bb2011-03-18 23:45:16 +00005234 pixel_list=AcquirePixelListThreadSet(StatisticWidth,StatisticHeight);
cristy0834d642011-03-18 18:26:08 +00005235 if (pixel_list == (PixelList **) NULL)
5236 {
5237 statistic_image=DestroyImage(statistic_image);
5238 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
5239 }
5240 /*
cristy8d752042011-03-19 01:00:36 +00005241 Make each pixel the min / max / median / mode / etc. of the neighborhood.
cristy0834d642011-03-18 18:26:08 +00005242 */
5243 status=MagickTrue;
5244 progress=0;
5245 image_view=AcquireCacheView(image);
5246 statistic_view=AcquireCacheView(statistic_image);
5247#if defined(MAGICKCORE_OPENMP_SUPPORT)
5248 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
5249#endif
5250 for (y=0; y < (ssize_t) statistic_image->rows; y++)
5251 {
5252 const int
5253 id = GetOpenMPThreadId();
5254
5255 register const IndexPacket
5256 *restrict indexes;
5257
5258 register const PixelPacket
5259 *restrict p;
5260
5261 register IndexPacket
5262 *restrict statistic_indexes;
5263
5264 register PixelPacket
5265 *restrict q;
5266
5267 register ssize_t
5268 x;
5269
5270 if (status == MagickFalse)
5271 continue;
cristy6fc86bb2011-03-18 23:45:16 +00005272 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) StatisticWidth/2L),y-
5273 (ssize_t) (StatisticHeight/2L),image->columns+StatisticWidth,
5274 StatisticHeight,exception);
cristy3cba8ca2011-03-19 01:29:12 +00005275 q=QueueCacheViewAuthenticPixels(statistic_view,0,y,statistic_image->columns, 1,exception);
cristy0834d642011-03-18 18:26:08 +00005276 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
5277 {
5278 status=MagickFalse;
5279 continue;
5280 }
5281 indexes=GetCacheViewVirtualIndexQueue(image_view);
5282 statistic_indexes=GetCacheViewAuthenticIndexQueue(statistic_view);
5283 for (x=0; x < (ssize_t) statistic_image->columns; x++)
5284 {
5285 MagickPixelPacket
5286 pixel;
5287
cristy0834d642011-03-18 18:26:08 +00005288 register const IndexPacket
5289 *restrict s;
5290
cristy6e3026a2011-03-19 00:54:38 +00005291 register const PixelPacket
5292 *restrict r;
5293
cristy0834d642011-03-18 18:26:08 +00005294 register ssize_t
5295 u,
5296 v;
5297
5298 r=p;
5299 s=indexes+x;
5300 ResetPixelList(pixel_list[id]);
cristy6e4c3292011-03-19 00:53:55 +00005301 for (v=0; v < (ssize_t) StatisticHeight; v++)
cristy0834d642011-03-18 18:26:08 +00005302 {
cristy6e4c3292011-03-19 00:53:55 +00005303 for (u=0; u < (ssize_t) StatisticWidth; u++)
cristy0834d642011-03-18 18:26:08 +00005304 InsertPixelList(image,r+u,s+u,pixel_list[id]);
cristy6fc86bb2011-03-18 23:45:16 +00005305 r+=image->columns+StatisticWidth;
cristy6e4c3292011-03-19 00:53:55 +00005306 s+=image->columns+StatisticWidth;
cristy0834d642011-03-18 18:26:08 +00005307 }
cristy80c99742011-04-04 14:46:39 +00005308 GetMagickPixelPacket(image,&pixel);
5309 SetMagickPixelPacket(image,p+StatisticWidth*StatisticHeight/2,indexes+
5310 StatisticWidth*StatisticHeight/2+x,&pixel);
cristy0834d642011-03-18 18:26:08 +00005311 switch (type)
5312 {
cristy80c99742011-04-04 14:46:39 +00005313 case GradientStatistic:
5314 {
5315 MagickPixelPacket
5316 maximum,
5317 minimum;
5318
5319 minimum=GetMinimumPixelList(pixel_list[id]);
5320 maximum=GetMaximumPixelList(pixel_list[id]);
5321 pixel.red=MagickAbsoluteValue(maximum.red-minimum.red);
5322 pixel.green=MagickAbsoluteValue(maximum.green-minimum.green);
5323 pixel.blue=MagickAbsoluteValue(maximum.blue-minimum.blue);
5324 pixel.opacity=MagickAbsoluteValue(maximum.opacity-minimum.opacity);
5325 if (image->colorspace == CMYKColorspace)
5326 pixel.index=MagickAbsoluteValue(maximum.index-minimum.index);
5327 break;
5328 }
cristy6fc86bb2011-03-18 23:45:16 +00005329 case MaximumStatistic:
5330 {
5331 pixel=GetMaximumPixelList(pixel_list[id]);
5332 break;
5333 }
cristy49f37242011-03-22 18:18:23 +00005334 case MeanStatistic:
5335 {
5336 pixel=GetMeanPixelList(pixel_list[id]);
5337 break;
5338 }
cristyf2ad14a2011-03-18 18:57:25 +00005339 case MedianStatistic:
cristy6fc86bb2011-03-18 23:45:16 +00005340 default:
cristyf2ad14a2011-03-18 18:57:25 +00005341 {
5342 pixel=GetMedianPixelList(pixel_list[id]);
5343 break;
5344 }
cristy6fc86bb2011-03-18 23:45:16 +00005345 case MinimumStatistic:
5346 {
5347 pixel=GetMinimumPixelList(pixel_list[id]);
5348 break;
5349 }
cristyf2ad14a2011-03-18 18:57:25 +00005350 case ModeStatistic:
5351 {
5352 pixel=GetModePixelList(pixel_list[id]);
5353 break;
5354 }
5355 case NonpeakStatistic:
5356 {
5357 pixel=GetNonpeakPixelList(pixel_list[id]);
5358 break;
5359 }
cristy9a68cbb2011-03-29 00:51:23 +00005360 case StandardDeviationStatistic:
5361 {
5362 pixel=GetStandardDeviationPixelList(pixel_list[id]);
5363 break;
5364 }
cristy0834d642011-03-18 18:26:08 +00005365 }
5366 if ((channel & RedChannel) != 0)
cristyd05ecd12011-04-22 20:44:42 +00005367 SetRedPixelComponent(q,ClampToQuantum(pixel.red));
cristy0834d642011-03-18 18:26:08 +00005368 if ((channel & GreenChannel) != 0)
cristyd05ecd12011-04-22 20:44:42 +00005369 SetGreenPixelComponent(q,ClampToQuantum(pixel.green));
cristy0834d642011-03-18 18:26:08 +00005370 if ((channel & BlueChannel) != 0)
cristyd05ecd12011-04-22 20:44:42 +00005371 SetBluePixelComponent(q,ClampToQuantum(pixel.blue));
cristy5b273dc2011-03-19 00:58:44 +00005372 if (((channel & OpacityChannel) != 0) &&
5373 (image->matte != MagickFalse))
cristyd05ecd12011-04-22 20:44:42 +00005374 SetOpacityPixelComponent(q,ClampToQuantum(pixel.opacity));
cristy0834d642011-03-18 18:26:08 +00005375 if (((channel & IndexChannel) != 0) &&
5376 (image->colorspace == CMYKColorspace))
cristyc8d25bc2011-04-29 02:19:30 +00005377 SetIndexPixelComponent(statistic_indexes+x,ClampToQuantum(pixel.index));
cristy0834d642011-03-18 18:26:08 +00005378 p++;
5379 q++;
5380 }
5381 if (SyncCacheViewAuthenticPixels(statistic_view,exception) == MagickFalse)
5382 status=MagickFalse;
5383 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5384 {
5385 MagickBooleanType
5386 proceed;
5387
5388#if defined(MAGICKCORE_OPENMP_SUPPORT)
5389 #pragma omp critical (MagickCore_StatisticImage)
5390#endif
5391 proceed=SetImageProgress(image,StatisticImageTag,progress++,
5392 image->rows);
5393 if (proceed == MagickFalse)
5394 status=MagickFalse;
5395 }
5396 }
5397 statistic_view=DestroyCacheView(statistic_view);
5398 image_view=DestroyCacheView(image_view);
5399 pixel_list=DestroyPixelListThreadSet(pixel_list);
5400 return(statistic_image);
5401}
5402
5403/*
5404%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5405% %
5406% %
5407% %
cristy3ed852e2009-09-05 21:47:34 +00005408% U n s h a r p M a s k I m a g e %
5409% %
5410% %
5411% %
5412%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5413%
5414% UnsharpMaskImage() sharpens one or more image channels. We convolve the
5415% image with a Gaussian operator of the given radius and standard deviation
5416% (sigma). For reasonable results, radius should be larger than sigma. Use a
5417% radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
5418%
5419% The format of the UnsharpMaskImage method is:
5420%
5421% Image *UnsharpMaskImage(const Image *image,const double radius,
5422% const double sigma,const double amount,const double threshold,
5423% ExceptionInfo *exception)
5424% Image *UnsharpMaskImageChannel(const Image *image,
5425% const ChannelType channel,const double radius,const double sigma,
5426% const double amount,const double threshold,ExceptionInfo *exception)
5427%
5428% A description of each parameter follows:
5429%
5430% o image: the image.
5431%
5432% o channel: the channel type.
5433%
5434% o radius: the radius of the Gaussian, in pixels, not counting the center
5435% pixel.
5436%
5437% o sigma: the standard deviation of the Gaussian, in pixels.
5438%
5439% o amount: the percentage of the difference between the original and the
5440% blur image that is added back into the original.
5441%
5442% o threshold: the threshold in pixels needed to apply the diffence amount.
5443%
5444% o exception: return any errors or warnings in this structure.
5445%
5446*/
5447
5448MagickExport Image *UnsharpMaskImage(const Image *image,const double radius,
5449 const double sigma,const double amount,const double threshold,
5450 ExceptionInfo *exception)
5451{
5452 Image
5453 *sharp_image;
5454
5455 sharp_image=UnsharpMaskImageChannel(image,DefaultChannels,radius,sigma,amount,
5456 threshold,exception);
5457 return(sharp_image);
5458}
5459
5460MagickExport Image *UnsharpMaskImageChannel(const Image *image,
5461 const ChannelType channel,const double radius,const double sigma,
5462 const double amount,const double threshold,ExceptionInfo *exception)
5463{
5464#define SharpenImageTag "Sharpen/Image"
5465
cristyc4c8d132010-01-07 01:58:38 +00005466 CacheView
5467 *image_view,
5468 *unsharp_view;
5469
cristy3ed852e2009-09-05 21:47:34 +00005470 Image
5471 *unsharp_image;
5472
cristy3ed852e2009-09-05 21:47:34 +00005473 MagickBooleanType
5474 status;
5475
cristybb503372010-05-27 20:51:26 +00005476 MagickOffsetType
5477 progress;
5478
cristy3ed852e2009-09-05 21:47:34 +00005479 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00005480 bias;
cristy3ed852e2009-09-05 21:47:34 +00005481
5482 MagickRealType
5483 quantum_threshold;
5484
cristybb503372010-05-27 20:51:26 +00005485 ssize_t
5486 y;
5487
cristy3ed852e2009-09-05 21:47:34 +00005488 assert(image != (const Image *) NULL);
5489 assert(image->signature == MagickSignature);
5490 if (image->debug != MagickFalse)
5491 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5492 assert(exception != (ExceptionInfo *) NULL);
5493 unsharp_image=BlurImageChannel(image,channel,radius,sigma,exception);
5494 if (unsharp_image == (Image *) NULL)
5495 return((Image *) NULL);
5496 quantum_threshold=(MagickRealType) QuantumRange*threshold;
5497 /*
5498 Unsharp-mask image.
5499 */
5500 status=MagickTrue;
5501 progress=0;
cristyddd82202009-11-03 20:14:50 +00005502 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00005503 image_view=AcquireCacheView(image);
5504 unsharp_view=AcquireCacheView(unsharp_image);
cristyb5d5f722009-11-04 03:03:49 +00005505#if defined(MAGICKCORE_OPENMP_SUPPORT)
5506 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00005507#endif
cristybb503372010-05-27 20:51:26 +00005508 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00005509 {
5510 MagickPixelPacket
5511 pixel;
5512
5513 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00005514 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00005515
5516 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00005517 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00005518
5519 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00005520 *restrict unsharp_indexes;
cristy3ed852e2009-09-05 21:47:34 +00005521
cristy3ed852e2009-09-05 21:47:34 +00005522 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00005523 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00005524
cristy117ff172010-08-15 21:35:32 +00005525 register ssize_t
5526 x;
5527
cristy3ed852e2009-09-05 21:47:34 +00005528 if (status == MagickFalse)
5529 continue;
5530 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
5531 q=GetCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
5532 exception);
5533 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
5534 {
5535 status=MagickFalse;
5536 continue;
5537 }
5538 indexes=GetCacheViewVirtualIndexQueue(image_view);
5539 unsharp_indexes=GetCacheViewAuthenticIndexQueue(unsharp_view);
cristyddd82202009-11-03 20:14:50 +00005540 pixel=bias;
cristybb503372010-05-27 20:51:26 +00005541 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00005542 {
5543 if ((channel & RedChannel) != 0)
5544 {
cristyc8d25bc2011-04-29 02:19:30 +00005545 pixel.red=GetRedPixelComponent(p)-(MagickRealType)
5546 GetRedPixelComponent(q);
cristy3ed852e2009-09-05 21:47:34 +00005547 if (fabs(2.0*pixel.red) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005548 pixel.red=(MagickRealType) GetRedPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005549 else
cristyc8d25bc2011-04-29 02:19:30 +00005550 pixel.red=(MagickRealType) GetRedPixelComponent(p)+
5551 (pixel.red*amount);
cristya2d08742011-04-22 19:59:52 +00005552 SetRedPixelComponent(q,ClampToQuantum(pixel.red));
cristy3ed852e2009-09-05 21:47:34 +00005553 }
5554 if ((channel & GreenChannel) != 0)
5555 {
cristyd05ecd12011-04-22 20:44:42 +00005556 pixel.green=GetGreenPixelComponent(p)-(MagickRealType) q->green;
cristy3ed852e2009-09-05 21:47:34 +00005557 if (fabs(2.0*pixel.green) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005558 pixel.green=(MagickRealType) GetGreenPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005559 else
cristyd05ecd12011-04-22 20:44:42 +00005560 pixel.green=(MagickRealType) GetGreenPixelComponent(p)+(pixel.green*amount);
cristya2d08742011-04-22 19:59:52 +00005561 SetGreenPixelComponent(q,ClampToQuantum(pixel.green));
cristy3ed852e2009-09-05 21:47:34 +00005562 }
5563 if ((channel & BlueChannel) != 0)
5564 {
cristyd05ecd12011-04-22 20:44:42 +00005565 pixel.blue=GetBluePixelComponent(p)-(MagickRealType) q->blue;
cristy3ed852e2009-09-05 21:47:34 +00005566 if (fabs(2.0*pixel.blue) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005567 pixel.blue=(MagickRealType) GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005568 else
cristyd05ecd12011-04-22 20:44:42 +00005569 pixel.blue=(MagickRealType) GetBluePixelComponent(p)+(pixel.blue*amount);
cristya2d08742011-04-22 19:59:52 +00005570 SetBluePixelComponent(q,ClampToQuantum(pixel.blue));
cristy3ed852e2009-09-05 21:47:34 +00005571 }
5572 if ((channel & OpacityChannel) != 0)
5573 {
cristyd05ecd12011-04-22 20:44:42 +00005574 pixel.opacity=GetOpacityPixelComponent(p)-(MagickRealType) q->opacity;
cristy3ed852e2009-09-05 21:47:34 +00005575 if (fabs(2.0*pixel.opacity) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005576 pixel.opacity=(MagickRealType) GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005577 else
cristyd05ecd12011-04-22 20:44:42 +00005578 pixel.opacity=GetOpacityPixelComponent(p)+(pixel.opacity*amount);
cristya2d08742011-04-22 19:59:52 +00005579 SetOpacityPixelComponent(q,ClampToQuantum(pixel.opacity));
cristy3ed852e2009-09-05 21:47:34 +00005580 }
5581 if (((channel & IndexChannel) != 0) &&
5582 (image->colorspace == CMYKColorspace))
5583 {
cristyc8d25bc2011-04-29 02:19:30 +00005584 pixel.index=GetIndexPixelComponent(indexes+x)-(MagickRealType)
5585 GetIndexPixelComponent(unsharp_indexes+x);
cristy3ed852e2009-09-05 21:47:34 +00005586 if (fabs(2.0*pixel.index) < quantum_threshold)
cristyc8d25bc2011-04-29 02:19:30 +00005587 pixel.index=(MagickRealType) GetIndexPixelComponent(indexes+x);
cristy3ed852e2009-09-05 21:47:34 +00005588 else
cristyc8d25bc2011-04-29 02:19:30 +00005589 pixel.index=(MagickRealType) GetIndexPixelComponent(indexes+x)+
5590 (pixel.index*amount);
5591 SetIndexPixelComponent(unsharp_indexes+x,ClampToQuantum(pixel.index));
cristy3ed852e2009-09-05 21:47:34 +00005592 }
5593 p++;
5594 q++;
5595 }
5596 if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
5597 status=MagickFalse;
5598 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5599 {
5600 MagickBooleanType
5601 proceed;
5602
cristyb5d5f722009-11-04 03:03:49 +00005603#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00005604 #pragma omp critical (MagickCore_UnsharpMaskImageChannel)
5605#endif
5606 proceed=SetImageProgress(image,SharpenImageTag,progress++,image->rows);
5607 if (proceed == MagickFalse)
5608 status=MagickFalse;
5609 }
5610 }
5611 unsharp_image->type=image->type;
5612 unsharp_view=DestroyCacheView(unsharp_view);
5613 image_view=DestroyCacheView(image_view);
5614 if (status == MagickFalse)
5615 unsharp_image=DestroyImage(unsharp_image);
5616 return(unsharp_image);
5617}