blob: 6bd0f57be35433d083cacf1010b48bca6eb762e3 [file] [log] [blame]
cristy3ed852e2009-09-05 21:47:34 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% EEEEE FFFFF FFFFF EEEEE CCCC TTTTT %
7% E F F E C T %
8% EEE FFF FFF EEE C T %
9% E F F E C T %
10% EEEEE F F EEEEE CCCC T %
11% %
12% %
13% MagickCore Image Effects Methods %
14% %
15% Software Design %
16% John Cristy %
17% October 1996 %
18% %
19% %
cristy16af1cb2009-12-11 21:38:29 +000020% Copyright 1999-2010 ImageMagick Studio LLC, a non-profit organization %
cristy3ed852e2009-09-05 21:47:34 +000021% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
26% http://www.imagemagick.org/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
36%
37%
38*/
39
40/*
41 Include declarations.
42*/
43#include "magick/studio.h"
cristyd43a46b2010-01-21 02:13:41 +000044#include "magick/accelerate.h"
cristy3ed852e2009-09-05 21:47:34 +000045#include "magick/blob.h"
46#include "magick/cache-view.h"
47#include "magick/color.h"
48#include "magick/color-private.h"
49#include "magick/colorspace.h"
50#include "magick/constitute.h"
51#include "magick/decorate.h"
52#include "magick/draw.h"
53#include "magick/enhance.h"
54#include "magick/exception.h"
55#include "magick/exception-private.h"
56#include "magick/effect.h"
57#include "magick/fx.h"
58#include "magick/gem.h"
59#include "magick/geometry.h"
60#include "magick/image-private.h"
61#include "magick/list.h"
62#include "magick/log.h"
63#include "magick/memory_.h"
64#include "magick/monitor.h"
65#include "magick/monitor-private.h"
66#include "magick/montage.h"
cristy6771f1e2010-03-05 19:43:39 +000067#include "magick/morphology.h"
cristy3ed852e2009-09-05 21:47:34 +000068#include "magick/paint.h"
69#include "magick/pixel-private.h"
70#include "magick/property.h"
71#include "magick/quantize.h"
72#include "magick/quantum.h"
73#include "magick/random_.h"
74#include "magick/random-private.h"
75#include "magick/resample.h"
76#include "magick/resample-private.h"
77#include "magick/resize.h"
78#include "magick/resource_.h"
79#include "magick/segment.h"
80#include "magick/shear.h"
81#include "magick/signature-private.h"
82#include "magick/string_.h"
83#include "magick/thread-private.h"
84#include "magick/transform.h"
85#include "magick/threshold.h"
86
87/*
88%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
89% %
90% %
91% %
92% A d a p t i v e B l u r I m a g e %
93% %
94% %
95% %
96%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
97%
98% AdaptiveBlurImage() adaptively blurs the image by blurring less
99% intensely near image edges and more intensely far from edges. We blur the
100% image with a Gaussian operator of the given radius and standard deviation
101% (sigma). For reasonable results, radius should be larger than sigma. Use a
102% radius of 0 and AdaptiveBlurImage() selects a suitable radius for you.
103%
104% The format of the AdaptiveBlurImage method is:
105%
106% Image *AdaptiveBlurImage(const Image *image,const double radius,
107% const double sigma,ExceptionInfo *exception)
108% Image *AdaptiveBlurImageChannel(const Image *image,
109% const ChannelType channel,double radius,const double sigma,
110% ExceptionInfo *exception)
111%
112% A description of each parameter follows:
113%
114% o image: the image.
115%
116% o channel: the channel type.
117%
118% o radius: the radius of the Gaussian, in pixels, not counting the center
119% pixel.
120%
121% o sigma: the standard deviation of the Laplacian, in pixels.
122%
123% o exception: return any errors or warnings in this structure.
124%
125*/
126
127MagickExport Image *AdaptiveBlurImage(const Image *image,const double radius,
128 const double sigma,ExceptionInfo *exception)
129{
130 Image
131 *blur_image;
132
133 blur_image=AdaptiveBlurImageChannel(image,DefaultChannels,radius,sigma,
134 exception);
135 return(blur_image);
136}
137
138MagickExport Image *AdaptiveBlurImageChannel(const Image *image,
139 const ChannelType channel,const double radius,const double sigma,
140 ExceptionInfo *exception)
141{
142#define AdaptiveBlurImageTag "Convolve/Image"
143#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
144
cristyc4c8d132010-01-07 01:58:38 +0000145 CacheView
146 *blur_view,
147 *edge_view,
148 *image_view;
149
cristy3ed852e2009-09-05 21:47:34 +0000150 double
cristy47e00502009-12-17 19:19:57 +0000151 **kernel,
152 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000153
154 Image
155 *blur_image,
156 *edge_image,
157 *gaussian_image;
158
159 long
160 j,
cristy47e00502009-12-17 19:19:57 +0000161 k,
cristy3ed852e2009-09-05 21:47:34 +0000162 progress,
cristy47e00502009-12-17 19:19:57 +0000163 u,
164 v,
cristy3ed852e2009-09-05 21:47:34 +0000165 y;
166
167 MagickBooleanType
168 status;
169
170 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +0000171 bias;
cristy3ed852e2009-09-05 21:47:34 +0000172
cristy3ed852e2009-09-05 21:47:34 +0000173 register long
cristy47e00502009-12-17 19:19:57 +0000174 i;
cristy3ed852e2009-09-05 21:47:34 +0000175
176 unsigned long
177 width;
178
cristy3ed852e2009-09-05 21:47:34 +0000179 assert(image != (const Image *) NULL);
180 assert(image->signature == MagickSignature);
181 if (image->debug != MagickFalse)
182 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
183 assert(exception != (ExceptionInfo *) NULL);
184 assert(exception->signature == MagickSignature);
185 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
186 if (blur_image == (Image *) NULL)
187 return((Image *) NULL);
188 if (fabs(sigma) <= MagickEpsilon)
189 return(blur_image);
190 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
191 {
192 InheritException(exception,&blur_image->exception);
193 blur_image=DestroyImage(blur_image);
194 return((Image *) NULL);
195 }
196 /*
197 Edge detect the image brighness channel, level, blur, and level again.
198 */
199 edge_image=EdgeImage(image,radius,exception);
200 if (edge_image == (Image *) NULL)
201 {
202 blur_image=DestroyImage(blur_image);
203 return((Image *) NULL);
204 }
205 (void) LevelImage(edge_image,"20%,95%");
206 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,exception);
207 if (gaussian_image != (Image *) NULL)
208 {
209 edge_image=DestroyImage(edge_image);
210 edge_image=gaussian_image;
211 }
212 (void) LevelImage(edge_image,"10%,95%");
213 /*
214 Create a set of kernels from maximum (radius,sigma) to minimum.
215 */
216 width=GetOptimalKernelWidth2D(radius,sigma);
217 kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
218 if (kernel == (double **) NULL)
219 {
220 edge_image=DestroyImage(edge_image);
221 blur_image=DestroyImage(blur_image);
222 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
223 }
224 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
225 for (i=0; i < (long) width; i+=2)
226 {
227 kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)*
228 sizeof(**kernel));
229 if (kernel[i] == (double *) NULL)
230 break;
cristy47e00502009-12-17 19:19:57 +0000231 normalize=0.0;
232 j=(long) (width-i)/2;
233 k=0;
234 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000235 {
cristy47e00502009-12-17 19:19:57 +0000236 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000237 {
cristy47e00502009-12-17 19:19:57 +0000238 kernel[i][k]=exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
239 (2.0*MagickPI*MagickSigma*MagickSigma);
240 normalize+=kernel[i][k];
241 k++;
cristy3ed852e2009-09-05 21:47:34 +0000242 }
243 }
cristy3ed852e2009-09-05 21:47:34 +0000244 if (fabs(normalize) <= MagickEpsilon)
245 normalize=1.0;
246 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000247 for (k=0; k < (j*j); k++)
248 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000249 }
250 if (i < (long) width)
251 {
252 for (i-=2; i >= 0; i-=2)
253 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
254 kernel=(double **) RelinquishMagickMemory(kernel);
255 edge_image=DestroyImage(edge_image);
256 blur_image=DestroyImage(blur_image);
257 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
258 }
259 /*
260 Adaptively blur image.
261 */
262 status=MagickTrue;
263 progress=0;
cristyddd82202009-11-03 20:14:50 +0000264 GetMagickPixelPacket(image,&bias);
265 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000266 image_view=AcquireCacheView(image);
267 edge_view=AcquireCacheView(edge_image);
268 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000269#if defined(MAGICKCORE_OPENMP_SUPPORT)
270 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000271#endif
272 for (y=0; y < (long) blur_image->rows; y++)
273 {
274 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000275 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000276
277 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000278 *restrict p,
279 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000280
281 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000282 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000283
284 register long
285 x;
286
287 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000288 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000289
290 if (status == MagickFalse)
291 continue;
292 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
293 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
294 exception);
295 if ((r == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
296 {
297 status=MagickFalse;
298 continue;
299 }
300 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
301 for (x=0; x < (long) blur_image->columns; x++)
302 {
303 MagickPixelPacket
304 pixel;
305
306 MagickRealType
307 alpha,
308 gamma;
309
310 register const double
cristyc47d1f82009-11-26 01:44:43 +0000311 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000312
313 register long
314 i,
315 u,
316 v;
317
318 gamma=0.0;
319 i=(long) (width*QuantumScale*PixelIntensity(r)+0.5);
320 if (i < 0)
321 i=0;
322 else
323 if (i > (long) width)
324 i=(long) width;
325 if ((i & 0x01) != 0)
326 i--;
327 p=GetCacheViewVirtualPixels(image_view,x-((long) (width-i)/2L),y-(long)
328 ((width-i)/2L),width-i,width-i,exception);
329 if (p == (const PixelPacket *) NULL)
330 break;
331 indexes=GetCacheViewVirtualIndexQueue(image_view);
cristyddd82202009-11-03 20:14:50 +0000332 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +0000333 k=kernel[i];
334 for (v=0; v < (long) (width-i); v++)
335 {
336 for (u=0; u < (long) (width-i); u++)
337 {
338 alpha=1.0;
339 if (((channel & OpacityChannel) != 0) &&
340 (image->matte != MagickFalse))
cristy46f08202010-01-10 04:04:21 +0000341 alpha=(MagickRealType) (QuantumScale*GetAlphaPixelComponent(p));
cristy3ed852e2009-09-05 21:47:34 +0000342 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000343 pixel.red+=(*k)*alpha*GetRedPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000344 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000345 pixel.green+=(*k)*alpha*GetGreenPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000346 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000347 pixel.blue+=(*k)*alpha*GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000348 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000349 pixel.opacity+=(*k)*GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000350 if (((channel & IndexChannel) != 0) &&
351 (image->colorspace == CMYKColorspace))
352 pixel.index+=(*k)*alpha*indexes[x+(width-i)*v+u];
353 gamma+=(*k)*alpha;
354 k++;
355 p++;
356 }
357 }
358 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
359 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000360 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000361 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000362 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000363 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000364 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000365 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000366 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000367 if (((channel & IndexChannel) != 0) &&
368 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +0000369 blur_indexes[x]=ClampToQuantum(gamma*GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000370 q++;
371 r++;
372 }
373 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
374 status=MagickFalse;
375 if (image->progress_monitor != (MagickProgressMonitor) NULL)
376 {
377 MagickBooleanType
378 proceed;
379
cristyb5d5f722009-11-04 03:03:49 +0000380#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000381 #pragma omp critical (MagickCore_AdaptiveBlurImageChannel)
382#endif
383 proceed=SetImageProgress(image,AdaptiveBlurImageTag,progress++,
384 image->rows);
385 if (proceed == MagickFalse)
386 status=MagickFalse;
387 }
388 }
389 blur_image->type=image->type;
390 blur_view=DestroyCacheView(blur_view);
391 edge_view=DestroyCacheView(edge_view);
392 image_view=DestroyCacheView(image_view);
393 edge_image=DestroyImage(edge_image);
394 for (i=0; i < (long) width; i+=2)
395 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
396 kernel=(double **) RelinquishMagickMemory(kernel);
397 if (status == MagickFalse)
398 blur_image=DestroyImage(blur_image);
399 return(blur_image);
400}
401
402/*
403%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
404% %
405% %
406% %
407% A d a p t i v e S h a r p e n I m a g e %
408% %
409% %
410% %
411%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
412%
413% AdaptiveSharpenImage() adaptively sharpens the image by sharpening more
414% intensely near image edges and less intensely far from edges. We sharpen the
415% image with a Gaussian operator of the given radius and standard deviation
416% (sigma). For reasonable results, radius should be larger than sigma. Use a
417% radius of 0 and AdaptiveSharpenImage() selects a suitable radius for you.
418%
419% The format of the AdaptiveSharpenImage method is:
420%
421% Image *AdaptiveSharpenImage(const Image *image,const double radius,
422% const double sigma,ExceptionInfo *exception)
423% Image *AdaptiveSharpenImageChannel(const Image *image,
424% const ChannelType channel,double radius,const double sigma,
425% ExceptionInfo *exception)
426%
427% A description of each parameter follows:
428%
429% o image: the image.
430%
431% o channel: the channel type.
432%
433% o radius: the radius of the Gaussian, in pixels, not counting the center
434% pixel.
435%
436% o sigma: the standard deviation of the Laplacian, in pixels.
437%
438% o exception: return any errors or warnings in this structure.
439%
440*/
441
442MagickExport Image *AdaptiveSharpenImage(const Image *image,const double radius,
443 const double sigma,ExceptionInfo *exception)
444{
445 Image
446 *sharp_image;
447
448 sharp_image=AdaptiveSharpenImageChannel(image,DefaultChannels,radius,sigma,
449 exception);
450 return(sharp_image);
451}
452
453MagickExport Image *AdaptiveSharpenImageChannel(const Image *image,
454 const ChannelType channel,const double radius,const double sigma,
455 ExceptionInfo *exception)
456{
457#define AdaptiveSharpenImageTag "Convolve/Image"
458#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
459
cristyc4c8d132010-01-07 01:58:38 +0000460 CacheView
461 *sharp_view,
462 *edge_view,
463 *image_view;
464
cristy3ed852e2009-09-05 21:47:34 +0000465 double
cristy47e00502009-12-17 19:19:57 +0000466 **kernel,
467 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000468
469 Image
470 *sharp_image,
471 *edge_image,
472 *gaussian_image;
473
474 long
475 j,
cristy47e00502009-12-17 19:19:57 +0000476 k,
cristy3ed852e2009-09-05 21:47:34 +0000477 progress,
cristy47e00502009-12-17 19:19:57 +0000478 u,
479 v,
cristy3ed852e2009-09-05 21:47:34 +0000480 y;
481
482 MagickBooleanType
483 status;
484
485 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +0000486 bias;
cristy3ed852e2009-09-05 21:47:34 +0000487
cristy3ed852e2009-09-05 21:47:34 +0000488 register long
cristy47e00502009-12-17 19:19:57 +0000489 i;
cristy3ed852e2009-09-05 21:47:34 +0000490
491 unsigned long
492 width;
493
cristy3ed852e2009-09-05 21:47:34 +0000494 assert(image != (const Image *) NULL);
495 assert(image->signature == MagickSignature);
496 if (image->debug != MagickFalse)
497 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
498 assert(exception != (ExceptionInfo *) NULL);
499 assert(exception->signature == MagickSignature);
500 sharp_image=CloneImage(image,0,0,MagickTrue,exception);
501 if (sharp_image == (Image *) NULL)
502 return((Image *) NULL);
503 if (fabs(sigma) <= MagickEpsilon)
504 return(sharp_image);
505 if (SetImageStorageClass(sharp_image,DirectClass) == MagickFalse)
506 {
507 InheritException(exception,&sharp_image->exception);
508 sharp_image=DestroyImage(sharp_image);
509 return((Image *) NULL);
510 }
511 /*
512 Edge detect the image brighness channel, level, sharp, and level again.
513 */
514 edge_image=EdgeImage(image,radius,exception);
515 if (edge_image == (Image *) NULL)
516 {
517 sharp_image=DestroyImage(sharp_image);
518 return((Image *) NULL);
519 }
520 (void) LevelImage(edge_image,"20%,95%");
521 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,exception);
522 if (gaussian_image != (Image *) NULL)
523 {
524 edge_image=DestroyImage(edge_image);
525 edge_image=gaussian_image;
526 }
527 (void) LevelImage(edge_image,"10%,95%");
528 /*
529 Create a set of kernels from maximum (radius,sigma) to minimum.
530 */
531 width=GetOptimalKernelWidth2D(radius,sigma);
532 kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
533 if (kernel == (double **) NULL)
534 {
535 edge_image=DestroyImage(edge_image);
536 sharp_image=DestroyImage(sharp_image);
537 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
538 }
539 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
540 for (i=0; i < (long) width; i+=2)
541 {
542 kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)*
543 sizeof(**kernel));
544 if (kernel[i] == (double *) NULL)
545 break;
cristy47e00502009-12-17 19:19:57 +0000546 normalize=0.0;
547 j=(long) (width-i)/2;
548 k=0;
549 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000550 {
cristy47e00502009-12-17 19:19:57 +0000551 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000552 {
cristy47e00502009-12-17 19:19:57 +0000553 kernel[i][k]=(-exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
554 (2.0*MagickPI*MagickSigma*MagickSigma));
555 normalize+=kernel[i][k];
556 k++;
cristy3ed852e2009-09-05 21:47:34 +0000557 }
558 }
cristy3ed852e2009-09-05 21:47:34 +0000559 if (fabs(normalize) <= MagickEpsilon)
560 normalize=1.0;
561 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000562 for (k=0; k < (j*j); k++)
563 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000564 }
565 if (i < (long) width)
566 {
567 for (i-=2; i >= 0; i-=2)
568 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
569 kernel=(double **) RelinquishMagickMemory(kernel);
570 edge_image=DestroyImage(edge_image);
571 sharp_image=DestroyImage(sharp_image);
572 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
573 }
574 /*
575 Adaptively sharpen image.
576 */
577 status=MagickTrue;
578 progress=0;
cristyddd82202009-11-03 20:14:50 +0000579 GetMagickPixelPacket(image,&bias);
580 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000581 image_view=AcquireCacheView(image);
582 edge_view=AcquireCacheView(edge_image);
583 sharp_view=AcquireCacheView(sharp_image);
cristyb5d5f722009-11-04 03:03:49 +0000584#if defined(MAGICKCORE_OPENMP_SUPPORT)
585 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000586#endif
587 for (y=0; y < (long) sharp_image->rows; y++)
588 {
589 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000590 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000591
592 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000593 *restrict p,
594 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000595
596 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000597 *restrict sharp_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000598
599 register long
600 x;
601
602 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000603 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000604
605 if (status == MagickFalse)
606 continue;
607 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
608 q=QueueCacheViewAuthenticPixels(sharp_view,0,y,sharp_image->columns,1,
609 exception);
610 if ((r == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
611 {
612 status=MagickFalse;
613 continue;
614 }
615 sharp_indexes=GetCacheViewAuthenticIndexQueue(sharp_view);
616 for (x=0; x < (long) sharp_image->columns; x++)
617 {
618 MagickPixelPacket
619 pixel;
620
621 MagickRealType
622 alpha,
623 gamma;
624
625 register const double
cristyc47d1f82009-11-26 01:44:43 +0000626 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000627
628 register long
629 i,
630 u,
631 v;
632
633 gamma=0.0;
634 i=(long) (width*(QuantumRange-QuantumScale*PixelIntensity(r))+0.5);
635 if (i < 0)
636 i=0;
637 else
638 if (i > (long) width)
639 i=(long) width;
640 if ((i & 0x01) != 0)
641 i--;
642 p=GetCacheViewVirtualPixels(image_view,x-((long) (width-i)/2L),y-(long)
643 ((width-i)/2L),width-i,width-i,exception);
644 if (p == (const PixelPacket *) NULL)
645 break;
646 indexes=GetCacheViewVirtualIndexQueue(image_view);
647 k=kernel[i];
cristyddd82202009-11-03 20:14:50 +0000648 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +0000649 for (v=0; v < (long) (width-i); v++)
650 {
651 for (u=0; u < (long) (width-i); u++)
652 {
653 alpha=1.0;
654 if (((channel & OpacityChannel) != 0) &&
655 (image->matte != MagickFalse))
cristy46f08202010-01-10 04:04:21 +0000656 alpha=(MagickRealType) (QuantumScale*GetAlphaPixelComponent(p));
cristy3ed852e2009-09-05 21:47:34 +0000657 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000658 pixel.red+=(*k)*alpha*GetRedPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000659 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000660 pixel.green+=(*k)*alpha*GetGreenPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000661 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000662 pixel.blue+=(*k)*alpha*GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000663 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000664 pixel.opacity+=(*k)*GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000665 if (((channel & IndexChannel) != 0) &&
666 (image->colorspace == CMYKColorspace))
667 pixel.index+=(*k)*alpha*indexes[x+(width-i)*v+u];
668 gamma+=(*k)*alpha;
669 k++;
670 p++;
671 }
672 }
673 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
674 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000675 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000676 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000677 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000678 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000679 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000680 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000681 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000682 if (((channel & IndexChannel) != 0) &&
683 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +0000684 sharp_indexes[x]=ClampToQuantum(gamma*GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000685 q++;
686 r++;
687 }
688 if (SyncCacheViewAuthenticPixels(sharp_view,exception) == MagickFalse)
689 status=MagickFalse;
690 if (image->progress_monitor != (MagickProgressMonitor) NULL)
691 {
692 MagickBooleanType
693 proceed;
694
cristyb5d5f722009-11-04 03:03:49 +0000695#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000696 #pragma omp critical (MagickCore_AdaptiveSharpenImageChannel)
697#endif
698 proceed=SetImageProgress(image,AdaptiveSharpenImageTag,progress++,
699 image->rows);
700 if (proceed == MagickFalse)
701 status=MagickFalse;
702 }
703 }
704 sharp_image->type=image->type;
705 sharp_view=DestroyCacheView(sharp_view);
706 edge_view=DestroyCacheView(edge_view);
707 image_view=DestroyCacheView(image_view);
708 edge_image=DestroyImage(edge_image);
709 for (i=0; i < (long) width; i+=2)
710 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
711 kernel=(double **) RelinquishMagickMemory(kernel);
712 if (status == MagickFalse)
713 sharp_image=DestroyImage(sharp_image);
714 return(sharp_image);
715}
716
717/*
718%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
719% %
720% %
721% %
722% B l u r I m a g e %
723% %
724% %
725% %
726%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
727%
728% BlurImage() blurs an image. We convolve the image with a Gaussian operator
729% of the given radius and standard deviation (sigma). For reasonable results,
730% the radius should be larger than sigma. Use a radius of 0 and BlurImage()
731% selects a suitable radius for you.
732%
733% BlurImage() differs from GaussianBlurImage() in that it uses a separable
734% kernel which is faster but mathematically equivalent to the non-separable
735% kernel.
736%
737% The format of the BlurImage method is:
738%
739% Image *BlurImage(const Image *image,const double radius,
740% const double sigma,ExceptionInfo *exception)
741% Image *BlurImageChannel(const Image *image,const ChannelType channel,
742% const double radius,const double sigma,ExceptionInfo *exception)
743%
744% A description of each parameter follows:
745%
746% o image: the image.
747%
748% o channel: the channel type.
749%
750% o radius: the radius of the Gaussian, in pixels, not counting the center
751% pixel.
752%
753% o sigma: the standard deviation of the Gaussian, in pixels.
754%
755% o exception: return any errors or warnings in this structure.
756%
757*/
758
759MagickExport Image *BlurImage(const Image *image,const double radius,
760 const double sigma,ExceptionInfo *exception)
761{
762 Image
763 *blur_image;
764
765 blur_image=BlurImageChannel(image,DefaultChannels,radius,sigma,exception);
766 return(blur_image);
767}
768
cristy47e00502009-12-17 19:19:57 +0000769static double *GetBlurKernel(const unsigned long width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +0000770{
cristy3ed852e2009-09-05 21:47:34 +0000771 double
cristy47e00502009-12-17 19:19:57 +0000772 *kernel,
773 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000774
775 long
cristy47e00502009-12-17 19:19:57 +0000776 j,
777 k;
cristy3ed852e2009-09-05 21:47:34 +0000778
779 register long
780 i;
781
782 /*
783 Generate a 1-D convolution kernel.
784 */
785 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
786 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
787 if (kernel == (double *) NULL)
788 return(0);
cristy3ed852e2009-09-05 21:47:34 +0000789 normalize=0.0;
cristy47e00502009-12-17 19:19:57 +0000790 j=(long) width/2;
791 i=0;
792 for (k=(-j); k <= j; k++)
793 {
cristyf267c722009-12-18 00:07:22 +0000794 kernel[i]=exp(-((double) k*k)/(2.0*MagickSigma*MagickSigma))/
cristy47e00502009-12-17 19:19:57 +0000795 (MagickSQ2PI*MagickSigma);
cristy3ed852e2009-09-05 21:47:34 +0000796 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +0000797 i++;
798 }
cristy3ed852e2009-09-05 21:47:34 +0000799 for (i=0; i < (long) width; i++)
800 kernel[i]/=normalize;
801 return(kernel);
802}
803
804MagickExport Image *BlurImageChannel(const Image *image,
805 const ChannelType channel,const double radius,const double sigma,
806 ExceptionInfo *exception)
807{
808#define BlurImageTag "Blur/Image"
809
cristyc4c8d132010-01-07 01:58:38 +0000810 CacheView
811 *blur_view,
812 *image_view;
813
cristy3ed852e2009-09-05 21:47:34 +0000814 double
815 *kernel;
816
817 Image
818 *blur_image;
819
820 long
821 progress,
822 x,
823 y;
824
825 MagickBooleanType
826 status;
827
828 MagickPixelPacket
cristy3ed852e2009-09-05 21:47:34 +0000829 bias;
830
831 register long
832 i;
833
834 unsigned long
835 width;
836
cristy3ed852e2009-09-05 21:47:34 +0000837 /*
838 Initialize blur image attributes.
839 */
840 assert(image != (Image *) NULL);
841 assert(image->signature == MagickSignature);
842 if (image->debug != MagickFalse)
843 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
844 assert(exception != (ExceptionInfo *) NULL);
845 assert(exception->signature == MagickSignature);
846 blur_image=CloneImage(image,0,0,MagickTrue,exception);
847 if (blur_image == (Image *) NULL)
848 return((Image *) NULL);
849 if (fabs(sigma) <= MagickEpsilon)
850 return(blur_image);
851 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
852 {
853 InheritException(exception,&blur_image->exception);
854 blur_image=DestroyImage(blur_image);
855 return((Image *) NULL);
856 }
857 width=GetOptimalKernelWidth1D(radius,sigma);
858 kernel=GetBlurKernel(width,sigma);
859 if (kernel == (double *) NULL)
860 {
861 blur_image=DestroyImage(blur_image);
862 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
863 }
864 if (image->debug != MagickFalse)
865 {
866 char
867 format[MaxTextExtent],
868 *message;
869
870 register const double
871 *k;
872
873 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
874 " BlurImage with %ld kernel:",width);
875 message=AcquireString("");
876 k=kernel;
877 for (i=0; i < (long) width; i++)
878 {
879 *message='\0';
880 (void) FormatMagickString(format,MaxTextExtent,"%ld: ",i);
881 (void) ConcatenateString(&message,format);
cristye7f51092010-01-17 00:39:37 +0000882 (void) FormatMagickString(format,MaxTextExtent,"%g ",*k++);
cristy3ed852e2009-09-05 21:47:34 +0000883 (void) ConcatenateString(&message,format);
884 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
885 }
886 message=DestroyString(message);
887 }
888 /*
889 Blur rows.
890 */
891 status=MagickTrue;
892 progress=0;
cristyddd82202009-11-03 20:14:50 +0000893 GetMagickPixelPacket(image,&bias);
894 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000895 image_view=AcquireCacheView(image);
896 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000897#if defined(MAGICKCORE_OPENMP_SUPPORT)
898 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000899#endif
900 for (y=0; y < (long) blur_image->rows; y++)
901 {
902 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000903 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000904
905 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000906 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000907
908 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000909 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000910
911 register long
912 x;
913
914 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000915 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000916
917 if (status == MagickFalse)
918 continue;
919 p=GetCacheViewVirtualPixels(image_view,-((long) width/2L),y,image->columns+
920 width,1,exception);
921 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
922 exception);
923 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
924 {
925 status=MagickFalse;
926 continue;
927 }
928 indexes=GetCacheViewVirtualIndexQueue(image_view);
929 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
930 for (x=0; x < (long) blur_image->columns; x++)
931 {
932 MagickPixelPacket
933 pixel;
934
935 register const double
cristyc47d1f82009-11-26 01:44:43 +0000936 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000937
938 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000939 *restrict kernel_pixels;
cristy3ed852e2009-09-05 21:47:34 +0000940
941 register long
942 i;
943
cristyddd82202009-11-03 20:14:50 +0000944 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +0000945 k=kernel;
946 kernel_pixels=p;
947 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
948 {
949 for (i=0; i < (long) width; i++)
950 {
951 pixel.red+=(*k)*kernel_pixels->red;
952 pixel.green+=(*k)*kernel_pixels->green;
953 pixel.blue+=(*k)*kernel_pixels->blue;
954 k++;
955 kernel_pixels++;
956 }
957 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000958 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000959 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000960 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000961 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000962 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000963 if ((channel & OpacityChannel) != 0)
964 {
965 k=kernel;
966 kernel_pixels=p;
967 for (i=0; i < (long) width; i++)
968 {
969 pixel.opacity+=(*k)*kernel_pixels->opacity;
970 k++;
971 kernel_pixels++;
972 }
cristyce70c172010-01-07 17:15:30 +0000973 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000974 }
975 if (((channel & IndexChannel) != 0) &&
976 (image->colorspace == CMYKColorspace))
977 {
978 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000979 *restrict kernel_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000980
981 k=kernel;
982 kernel_indexes=indexes;
983 for (i=0; i < (long) width; i++)
984 {
985 pixel.index+=(*k)*(*kernel_indexes);
986 k++;
987 kernel_indexes++;
988 }
cristyce70c172010-01-07 17:15:30 +0000989 blur_indexes[x]=ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +0000990 }
991 }
992 else
993 {
994 MagickRealType
995 alpha,
996 gamma;
997
998 gamma=0.0;
999 for (i=0; i < (long) width; i++)
1000 {
cristy46f08202010-01-10 04:04:21 +00001001 alpha=(MagickRealType) (QuantumScale*
1002 GetAlphaPixelComponent(kernel_pixels));
cristy3ed852e2009-09-05 21:47:34 +00001003 pixel.red+=(*k)*alpha*kernel_pixels->red;
1004 pixel.green+=(*k)*alpha*kernel_pixels->green;
1005 pixel.blue+=(*k)*alpha*kernel_pixels->blue;
1006 gamma+=(*k)*alpha;
1007 k++;
1008 kernel_pixels++;
1009 }
1010 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1011 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001012 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001013 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001014 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001015 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001016 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001017 if ((channel & OpacityChannel) != 0)
1018 {
1019 k=kernel;
1020 kernel_pixels=p;
1021 for (i=0; i < (long) width; i++)
1022 {
1023 pixel.opacity+=(*k)*kernel_pixels->opacity;
1024 k++;
1025 kernel_pixels++;
1026 }
cristyce70c172010-01-07 17:15:30 +00001027 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001028 }
1029 if (((channel & IndexChannel) != 0) &&
1030 (image->colorspace == CMYKColorspace))
1031 {
1032 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001033 *restrict kernel_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001034
1035 k=kernel;
1036 kernel_pixels=p;
1037 kernel_indexes=indexes;
1038 for (i=0; i < (long) width; i++)
1039 {
cristy46f08202010-01-10 04:04:21 +00001040 alpha=(MagickRealType) (QuantumScale*
1041 GetAlphaPixelComponent(kernel_pixels));
cristy3ed852e2009-09-05 21:47:34 +00001042 pixel.index+=(*k)*alpha*(*kernel_indexes);
1043 k++;
1044 kernel_pixels++;
1045 kernel_indexes++;
1046 }
cristy46f08202010-01-10 04:04:21 +00001047 blur_indexes[x]=ClampToQuantum(gamma*
1048 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001049 }
1050 }
1051 p++;
1052 q++;
1053 }
1054 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1055 status=MagickFalse;
1056 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1057 {
1058 MagickBooleanType
1059 proceed;
1060
cristyb5d5f722009-11-04 03:03:49 +00001061#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001062 #pragma omp critical (MagickCore_BlurImageChannel)
1063#endif
1064 proceed=SetImageProgress(image,BlurImageTag,progress++,blur_image->rows+
1065 blur_image->columns);
1066 if (proceed == MagickFalse)
1067 status=MagickFalse;
1068 }
1069 }
1070 blur_view=DestroyCacheView(blur_view);
1071 image_view=DestroyCacheView(image_view);
1072 /*
1073 Blur columns.
1074 */
1075 image_view=AcquireCacheView(blur_image);
1076 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00001077#if defined(MAGICKCORE_OPENMP_SUPPORT)
1078 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001079#endif
1080 for (x=0; x < (long) blur_image->columns; x++)
1081 {
1082 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001083 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001084
1085 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001086 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001087
1088 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001089 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001090
1091 register long
1092 y;
1093
1094 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001095 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001096
1097 if (status == MagickFalse)
1098 continue;
1099 p=GetCacheViewVirtualPixels(image_view,x,-((long) width/2L),1,image->rows+
1100 width,exception);
1101 q=GetCacheViewAuthenticPixels(blur_view,x,0,1,blur_image->rows,exception);
1102 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1103 {
1104 status=MagickFalse;
1105 continue;
1106 }
1107 indexes=GetCacheViewVirtualIndexQueue(image_view);
1108 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
1109 for (y=0; y < (long) blur_image->rows; y++)
1110 {
1111 MagickPixelPacket
1112 pixel;
1113
1114 register const double
cristyc47d1f82009-11-26 01:44:43 +00001115 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00001116
1117 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001118 *restrict kernel_pixels;
cristy3ed852e2009-09-05 21:47:34 +00001119
1120 register long
1121 i;
1122
cristyddd82202009-11-03 20:14:50 +00001123 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00001124 k=kernel;
1125 kernel_pixels=p;
1126 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
1127 {
1128 for (i=0; i < (long) width; i++)
1129 {
1130 pixel.red+=(*k)*kernel_pixels->red;
1131 pixel.green+=(*k)*kernel_pixels->green;
1132 pixel.blue+=(*k)*kernel_pixels->blue;
1133 k++;
1134 kernel_pixels++;
1135 }
1136 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001137 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001138 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001139 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001140 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001141 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001142 if ((channel & OpacityChannel) != 0)
1143 {
1144 k=kernel;
1145 kernel_pixels=p;
1146 for (i=0; i < (long) width; i++)
1147 {
1148 pixel.opacity+=(*k)*kernel_pixels->opacity;
1149 k++;
1150 kernel_pixels++;
1151 }
cristyce70c172010-01-07 17:15:30 +00001152 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001153 }
1154 if (((channel & IndexChannel) != 0) &&
1155 (image->colorspace == CMYKColorspace))
1156 {
1157 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001158 *restrict kernel_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001159
1160 k=kernel;
1161 kernel_indexes=indexes;
1162 for (i=0; i < (long) width; i++)
1163 {
1164 pixel.index+=(*k)*(*kernel_indexes);
1165 k++;
1166 kernel_indexes++;
1167 }
cristyce70c172010-01-07 17:15:30 +00001168 blur_indexes[y]=ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00001169 }
1170 }
1171 else
1172 {
1173 MagickRealType
1174 alpha,
1175 gamma;
1176
1177 gamma=0.0;
1178 for (i=0; i < (long) width; i++)
1179 {
cristy46f08202010-01-10 04:04:21 +00001180 alpha=(MagickRealType) (QuantumScale*
1181 GetAlphaPixelComponent(kernel_pixels));
cristy3ed852e2009-09-05 21:47:34 +00001182 pixel.red+=(*k)*alpha*kernel_pixels->red;
1183 pixel.green+=(*k)*alpha*kernel_pixels->green;
1184 pixel.blue+=(*k)*alpha*kernel_pixels->blue;
1185 gamma+=(*k)*alpha;
1186 k++;
1187 kernel_pixels++;
1188 }
1189 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1190 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001191 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001192 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001193 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001194 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001195 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001196 if ((channel & OpacityChannel) != 0)
1197 {
1198 k=kernel;
1199 kernel_pixels=p;
1200 for (i=0; i < (long) width; i++)
1201 {
1202 pixel.opacity+=(*k)*kernel_pixels->opacity;
1203 k++;
1204 kernel_pixels++;
1205 }
cristyce70c172010-01-07 17:15:30 +00001206 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001207 }
1208 if (((channel & IndexChannel) != 0) &&
1209 (image->colorspace == CMYKColorspace))
1210 {
1211 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001212 *restrict kernel_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001213
1214 k=kernel;
1215 kernel_pixels=p;
1216 kernel_indexes=indexes;
1217 for (i=0; i < (long) width; i++)
1218 {
cristy46f08202010-01-10 04:04:21 +00001219 alpha=(MagickRealType) (QuantumScale*
1220 GetAlphaPixelComponent(kernel_pixels));
cristy3ed852e2009-09-05 21:47:34 +00001221 pixel.index+=(*k)*alpha*(*kernel_indexes);
1222 k++;
1223 kernel_pixels++;
1224 kernel_indexes++;
1225 }
cristy46f08202010-01-10 04:04:21 +00001226 blur_indexes[y]=ClampToQuantum(gamma*
1227 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001228 }
1229 }
1230 p++;
1231 q++;
1232 }
1233 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1234 status=MagickFalse;
1235 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1236 {
1237 MagickBooleanType
1238 proceed;
1239
cristyb5d5f722009-11-04 03:03:49 +00001240#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001241 #pragma omp critical (MagickCore_BlurImageChannel)
1242#endif
1243 proceed=SetImageProgress(image,BlurImageTag,progress++,blur_image->rows+
1244 blur_image->columns);
1245 if (proceed == MagickFalse)
1246 status=MagickFalse;
1247 }
1248 }
1249 blur_view=DestroyCacheView(blur_view);
1250 image_view=DestroyCacheView(image_view);
1251 kernel=(double *) RelinquishMagickMemory(kernel);
1252 if (status == MagickFalse)
1253 blur_image=DestroyImage(blur_image);
1254 blur_image->type=image->type;
1255 return(blur_image);
1256}
1257
1258/*
1259%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1260% %
1261% %
1262% %
cristyfccdab92009-11-30 16:43:57 +00001263% C o n v o l v e I m a g e %
1264% %
1265% %
1266% %
1267%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1268%
1269% ConvolveImage() applies a custom convolution kernel to the image.
1270%
1271% The format of the ConvolveImage method is:
1272%
1273% Image *ConvolveImage(const Image *image,const unsigned long order,
1274% const double *kernel,ExceptionInfo *exception)
1275% Image *ConvolveImageChannel(const Image *image,const ChannelType channel,
1276% const unsigned long order,const double *kernel,
1277% ExceptionInfo *exception)
1278%
1279% A description of each parameter follows:
1280%
1281% o image: the image.
1282%
1283% o channel: the channel type.
1284%
1285% o order: the number of columns and rows in the filter kernel.
1286%
1287% o kernel: An array of double representing the convolution kernel.
1288%
1289% o exception: return any errors or warnings in this structure.
1290%
1291*/
1292
1293MagickExport Image *ConvolveImage(const Image *image,const unsigned long order,
1294 const double *kernel,ExceptionInfo *exception)
1295{
1296 Image
1297 *convolve_image;
1298
1299 convolve_image=ConvolveImageChannel(image,DefaultChannels,order,kernel,
1300 exception);
1301 return(convolve_image);
1302}
1303
1304MagickExport Image *ConvolveImageChannel(const Image *image,
1305 const ChannelType channel,const unsigned long order,const double *kernel,
1306 ExceptionInfo *exception)
1307{
1308#define ConvolveImageTag "Convolve/Image"
1309
cristyc4c8d132010-01-07 01:58:38 +00001310 CacheView
1311 *convolve_view,
1312 *image_view;
1313
cristyfccdab92009-11-30 16:43:57 +00001314 double
1315 *normal_kernel;
1316
1317 Image
1318 *convolve_image;
1319
1320 long
1321 progress,
1322 y;
1323
1324 MagickBooleanType
1325 status;
1326
1327 MagickPixelPacket
1328 bias;
1329
1330 MagickRealType
1331 gamma;
1332
1333 register long
1334 i;
1335
1336 unsigned long
1337 width;
1338
cristyfccdab92009-11-30 16:43:57 +00001339 /*
1340 Initialize convolve image attributes.
1341 */
1342 assert(image != (Image *) NULL);
1343 assert(image->signature == MagickSignature);
1344 if (image->debug != MagickFalse)
1345 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1346 assert(exception != (ExceptionInfo *) NULL);
1347 assert(exception->signature == MagickSignature);
1348 width=order;
1349 if ((width % 2) == 0)
1350 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
1351 convolve_image=CloneImage(image,0,0,MagickTrue,exception);
1352 if (convolve_image == (Image *) NULL)
1353 return((Image *) NULL);
1354 if (SetImageStorageClass(convolve_image,DirectClass) == MagickFalse)
1355 {
1356 InheritException(exception,&convolve_image->exception);
1357 convolve_image=DestroyImage(convolve_image);
1358 return((Image *) NULL);
1359 }
1360 if (image->debug != MagickFalse)
1361 {
1362 char
1363 format[MaxTextExtent],
1364 *message;
1365
1366 long
1367 u,
1368 v;
1369
1370 register const double
1371 *k;
1372
1373 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
1374 " ConvolveImage with %ldx%ld kernel:",width,width);
1375 message=AcquireString("");
1376 k=kernel;
1377 for (v=0; v < (long) width; v++)
1378 {
1379 *message='\0';
1380 (void) FormatMagickString(format,MaxTextExtent,"%ld: ",v);
1381 (void) ConcatenateString(&message,format);
1382 for (u=0; u < (long) width; u++)
1383 {
cristye7f51092010-01-17 00:39:37 +00001384 (void) FormatMagickString(format,MaxTextExtent,"%g ",*k++);
cristyfccdab92009-11-30 16:43:57 +00001385 (void) ConcatenateString(&message,format);
1386 }
1387 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
1388 }
1389 message=DestroyString(message);
1390 }
1391 /*
1392 Normalize kernel.
1393 */
1394 normal_kernel=(double *) AcquireQuantumMemory(width*width,
1395 sizeof(*normal_kernel));
1396 if (normal_kernel == (double *) NULL)
1397 {
1398 convolve_image=DestroyImage(convolve_image);
1399 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1400 }
1401 gamma=0.0;
1402 for (i=0; i < (long) (width*width); i++)
1403 gamma+=kernel[i];
1404 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1405 for (i=0; i < (long) (width*width); i++)
1406 normal_kernel[i]=gamma*kernel[i];
1407 /*
1408 Convolve image.
1409 */
1410 status=MagickTrue;
1411 progress=0;
1412 GetMagickPixelPacket(image,&bias);
1413 SetMagickPixelPacketBias(image,&bias);
1414 image_view=AcquireCacheView(image);
1415 convolve_view=AcquireCacheView(convolve_image);
1416#if defined(MAGICKCORE_OPENMP_SUPPORT)
1417 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1418#endif
1419 for (y=0; y < (long) image->rows; y++)
1420 {
1421 MagickBooleanType
1422 sync;
1423
1424 register const IndexPacket
1425 *restrict indexes;
1426
1427 register const PixelPacket
1428 *restrict p;
1429
1430 register IndexPacket
1431 *restrict convolve_indexes;
1432
1433 register long
1434 x;
1435
1436 register PixelPacket
1437 *restrict q;
1438
1439 if (status == MagickFalse)
1440 continue;
1441 p=GetCacheViewVirtualPixels(image_view,-((long) width/2L),y-(long) (width/
1442 2L),image->columns+width,width,exception);
1443 q=GetCacheViewAuthenticPixels(convolve_view,0,y,convolve_image->columns,1,
1444 exception);
1445 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1446 {
1447 status=MagickFalse;
1448 continue;
1449 }
1450 indexes=GetCacheViewVirtualIndexQueue(image_view);
1451 convolve_indexes=GetCacheViewAuthenticIndexQueue(convolve_view);
1452 for (x=0; x < (long) image->columns; x++)
1453 {
1454 long
1455 v;
1456
1457 MagickPixelPacket
1458 pixel;
1459
1460 register const double
1461 *restrict k;
1462
1463 register const PixelPacket
1464 *restrict kernel_pixels;
1465
1466 register long
1467 u;
1468
1469 pixel=bias;
1470 k=normal_kernel;
1471 kernel_pixels=p;
1472 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
1473 {
1474 for (v=0; v < (long) width; v++)
1475 {
1476 for (u=0; u < (long) width; u++)
1477 {
1478 pixel.red+=(*k)*kernel_pixels[u].red;
1479 pixel.green+=(*k)*kernel_pixels[u].green;
1480 pixel.blue+=(*k)*kernel_pixels[u].blue;
1481 k++;
1482 }
1483 kernel_pixels+=image->columns+width;
1484 }
1485 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001486 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001487 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001488 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001489 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001490 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001491 if ((channel & OpacityChannel) != 0)
1492 {
1493 k=normal_kernel;
1494 kernel_pixels=p;
1495 for (v=0; v < (long) width; v++)
1496 {
1497 for (u=0; u < (long) width; u++)
1498 {
1499 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
1500 k++;
1501 }
1502 kernel_pixels+=image->columns+width;
1503 }
cristyce70c172010-01-07 17:15:30 +00001504 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001505 }
1506 if (((channel & IndexChannel) != 0) &&
1507 (image->colorspace == CMYKColorspace))
1508 {
1509 register const IndexPacket
1510 *restrict kernel_indexes;
1511
1512 k=normal_kernel;
1513 kernel_indexes=indexes;
1514 for (v=0; v < (long) width; v++)
1515 {
1516 for (u=0; u < (long) width; u++)
1517 {
1518 pixel.index+=(*k)*kernel_indexes[u];
1519 k++;
1520 }
1521 kernel_indexes+=image->columns+width;
1522 }
cristyce70c172010-01-07 17:15:30 +00001523 convolve_indexes[x]=ClampToQuantum(pixel.index);
cristyfccdab92009-11-30 16:43:57 +00001524 }
1525 }
1526 else
1527 {
1528 MagickRealType
1529 alpha,
1530 gamma;
1531
1532 gamma=0.0;
1533 for (v=0; v < (long) width; v++)
1534 {
1535 for (u=0; u < (long) width; u++)
1536 {
1537 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
1538 kernel_pixels[u].opacity));
1539 pixel.red+=(*k)*alpha*kernel_pixels[u].red;
1540 pixel.green+=(*k)*alpha*kernel_pixels[u].green;
1541 pixel.blue+=(*k)*alpha*kernel_pixels[u].blue;
cristyfccdab92009-11-30 16:43:57 +00001542 gamma+=(*k)*alpha;
1543 k++;
1544 }
1545 kernel_pixels+=image->columns+width;
1546 }
1547 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1548 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001549 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001550 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001551 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001552 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001553 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001554 if ((channel & OpacityChannel) != 0)
1555 {
1556 k=normal_kernel;
1557 kernel_pixels=p;
1558 for (v=0; v < (long) width; v++)
1559 {
1560 for (u=0; u < (long) width; u++)
1561 {
1562 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
1563 k++;
1564 }
1565 kernel_pixels+=image->columns+width;
1566 }
cristyce70c172010-01-07 17:15:30 +00001567 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001568 }
1569 if (((channel & IndexChannel) != 0) &&
1570 (image->colorspace == CMYKColorspace))
1571 {
1572 register const IndexPacket
1573 *restrict kernel_indexes;
1574
1575 k=normal_kernel;
1576 kernel_pixels=p;
1577 kernel_indexes=indexes;
1578 for (v=0; v < (long) width; v++)
1579 {
1580 for (u=0; u < (long) width; u++)
1581 {
1582 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
1583 kernel_pixels[u].opacity));
1584 pixel.index+=(*k)*alpha*kernel_indexes[u];
1585 k++;
1586 }
1587 kernel_pixels+=image->columns+width;
1588 kernel_indexes+=image->columns+width;
1589 }
cristy24b06da2010-01-09 23:05:56 +00001590 convolve_indexes[x]=ClampToQuantum(gamma*
1591 GetIndexPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001592 }
1593 }
1594 p++;
1595 q++;
1596 }
1597 sync=SyncCacheViewAuthenticPixels(convolve_view,exception);
1598 if (sync == MagickFalse)
1599 status=MagickFalse;
1600 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1601 {
1602 MagickBooleanType
1603 proceed;
1604
1605#if defined(MAGICKCORE_OPENMP_SUPPORT)
1606 #pragma omp critical (MagickCore_ConvolveImageChannel)
1607#endif
1608 proceed=SetImageProgress(image,ConvolveImageTag,progress++,image->rows);
1609 if (proceed == MagickFalse)
1610 status=MagickFalse;
1611 }
1612 }
1613 convolve_image->type=image->type;
1614 convolve_view=DestroyCacheView(convolve_view);
1615 image_view=DestroyCacheView(image_view);
1616 normal_kernel=(double *) RelinquishMagickMemory(normal_kernel);
1617 if (status == MagickFalse)
1618 convolve_image=DestroyImage(convolve_image);
1619 return(convolve_image);
1620}
1621
1622/*
1623%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1624% %
1625% %
1626% %
cristy3ed852e2009-09-05 21:47:34 +00001627% D e s p e c k l e I m a g e %
1628% %
1629% %
1630% %
1631%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1632%
1633% DespeckleImage() reduces the speckle noise in an image while perserving the
1634% edges of the original image.
1635%
1636% The format of the DespeckleImage method is:
1637%
1638% Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1639%
1640% A description of each parameter follows:
1641%
1642% o image: the image.
1643%
1644% o exception: return any errors or warnings in this structure.
1645%
1646*/
1647
1648static Quantum **DestroyPixelThreadSet(Quantum **pixels)
1649{
1650 register long
1651 i;
1652
1653 assert(pixels != (Quantum **) NULL);
1654 for (i=0; i < (long) GetOpenMPMaximumThreads(); i++)
1655 if (pixels[i] != (Quantum *) NULL)
1656 pixels[i]=(Quantum *) RelinquishMagickMemory(pixels[i]);
1657 pixels=(Quantum **) RelinquishAlignedMemory(pixels);
1658 return(pixels);
1659}
1660
1661static Quantum **AcquirePixelThreadSet(const size_t count)
1662{
1663 register long
1664 i;
1665
1666 Quantum
1667 **pixels;
1668
1669 unsigned long
1670 number_threads;
1671
1672 number_threads=GetOpenMPMaximumThreads();
1673 pixels=(Quantum **) AcquireAlignedMemory(number_threads,sizeof(*pixels));
1674 if (pixels == (Quantum **) NULL)
1675 return((Quantum **) NULL);
1676 (void) ResetMagickMemory(pixels,0,number_threads*sizeof(*pixels));
1677 for (i=0; i < (long) number_threads; i++)
1678 {
1679 pixels[i]=(Quantum *) AcquireQuantumMemory(count,sizeof(**pixels));
1680 if (pixels[i] == (Quantum *) NULL)
1681 return(DestroyPixelThreadSet(pixels));
1682 }
1683 return(pixels);
1684}
1685
1686static void Hull(const long x_offset,const long y_offset,
1687 const unsigned long columns,const unsigned long rows,Quantum *f,Quantum *g,
1688 const int polarity)
1689{
1690 long
1691 y;
1692
1693 MagickRealType
1694 v;
1695
1696 register long
1697 x;
1698
1699 register Quantum
1700 *p,
1701 *q,
1702 *r,
1703 *s;
1704
1705 assert(f != (Quantum *) NULL);
1706 assert(g != (Quantum *) NULL);
1707 p=f+(columns+2);
1708 q=g+(columns+2);
1709 r=p+(y_offset*((long) columns+2)+x_offset);
1710 for (y=0; y < (long) rows; y++)
1711 {
1712 p++;
1713 q++;
1714 r++;
1715 if (polarity > 0)
1716 for (x=(long) columns; x != 0; x--)
1717 {
1718 v=(MagickRealType) (*p);
1719 if ((MagickRealType) *r >= (v+(MagickRealType) ScaleCharToQuantum(2)))
1720 v+=ScaleCharToQuantum(1);
1721 *q=(Quantum) v;
1722 p++;
1723 q++;
1724 r++;
1725 }
1726 else
1727 for (x=(long) columns; x != 0; x--)
1728 {
1729 v=(MagickRealType) (*p);
1730 if ((MagickRealType) *r <= (v-(MagickRealType) ScaleCharToQuantum(2)))
1731 v-=(long) ScaleCharToQuantum(1);
1732 *q=(Quantum) v;
1733 p++;
1734 q++;
1735 r++;
1736 }
1737 p++;
1738 q++;
1739 r++;
1740 }
1741 p=f+(columns+2);
1742 q=g+(columns+2);
1743 r=q+(y_offset*((long) columns+2)+x_offset);
1744 s=q-(y_offset*((long) columns+2)+x_offset);
1745 for (y=0; y < (long) rows; y++)
1746 {
1747 p++;
1748 q++;
1749 r++;
1750 s++;
1751 if (polarity > 0)
1752 for (x=(long) columns; x != 0; x--)
1753 {
1754 v=(MagickRealType) (*q);
1755 if (((MagickRealType) *s >=
1756 (v+(MagickRealType) ScaleCharToQuantum(2))) &&
1757 ((MagickRealType) *r > v))
1758 v+=ScaleCharToQuantum(1);
1759 *p=(Quantum) v;
1760 p++;
1761 q++;
1762 r++;
1763 s++;
1764 }
1765 else
1766 for (x=(long) columns; x != 0; x--)
1767 {
1768 v=(MagickRealType) (*q);
1769 if (((MagickRealType) *s <=
1770 (v-(MagickRealType) ScaleCharToQuantum(2))) &&
1771 ((MagickRealType) *r < v))
1772 v-=(MagickRealType) ScaleCharToQuantum(1);
1773 *p=(Quantum) v;
1774 p++;
1775 q++;
1776 r++;
1777 s++;
1778 }
1779 p++;
1780 q++;
1781 r++;
1782 s++;
1783 }
1784}
1785
1786MagickExport Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1787{
1788#define DespeckleImageTag "Despeckle/Image"
1789
cristy2407fc22009-09-11 00:55:25 +00001790 CacheView
1791 *despeckle_view,
1792 *image_view;
1793
cristy3ed852e2009-09-05 21:47:34 +00001794 Image
1795 *despeckle_image;
1796
1797 long
1798 channel;
1799
1800 MagickBooleanType
1801 status;
1802
1803 Quantum
cristyfa112112010-01-04 17:48:07 +00001804 **restrict buffers,
1805 **restrict pixels;
cristy3ed852e2009-09-05 21:47:34 +00001806
1807 size_t
1808 length;
1809
1810 static const int
cristy691a29e2009-09-11 00:44:10 +00001811 X[4] = {0, 1, 1,-1},
1812 Y[4] = {1, 0, 1, 1};
cristy3ed852e2009-09-05 21:47:34 +00001813
cristy3ed852e2009-09-05 21:47:34 +00001814 /*
1815 Allocate despeckled image.
1816 */
1817 assert(image != (const Image *) NULL);
1818 assert(image->signature == MagickSignature);
1819 if (image->debug != MagickFalse)
1820 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1821 assert(exception != (ExceptionInfo *) NULL);
1822 assert(exception->signature == MagickSignature);
1823 despeckle_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1824 exception);
1825 if (despeckle_image == (Image *) NULL)
1826 return((Image *) NULL);
1827 if (SetImageStorageClass(despeckle_image,DirectClass) == MagickFalse)
1828 {
1829 InheritException(exception,&despeckle_image->exception);
1830 despeckle_image=DestroyImage(despeckle_image);
1831 return((Image *) NULL);
1832 }
1833 /*
1834 Allocate image buffers.
1835 */
1836 length=(size_t) ((image->columns+2)*(image->rows+2));
1837 pixels=AcquirePixelThreadSet(length);
1838 buffers=AcquirePixelThreadSet(length);
1839 if ((pixels == (Quantum **) NULL) || (buffers == (Quantum **) NULL))
1840 {
1841 if (buffers != (Quantum **) NULL)
1842 buffers=DestroyPixelThreadSet(buffers);
1843 if (pixels != (Quantum **) NULL)
1844 pixels=DestroyPixelThreadSet(pixels);
1845 despeckle_image=DestroyImage(despeckle_image);
1846 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1847 }
1848 /*
1849 Reduce speckle in the image.
1850 */
1851 status=MagickTrue;
1852 image_view=AcquireCacheView(image);
1853 despeckle_view=AcquireCacheView(despeckle_image);
cristyb5d5f722009-11-04 03:03:49 +00001854#if defined(MAGICKCORE_OPENMP_SUPPORT)
1855 #pragma omp parallel for schedule(dynamic,4) shared(status)
cristy3ed852e2009-09-05 21:47:34 +00001856#endif
1857 for (channel=0; channel <= 3; channel++)
1858 {
1859 long
1860 j,
1861 y;
1862
1863 register long
1864 i,
cristy691a29e2009-09-11 00:44:10 +00001865 id,
cristy3ed852e2009-09-05 21:47:34 +00001866 x;
1867
1868 register Quantum
1869 *buffer,
1870 *pixel;
1871
1872 if (status == MagickFalse)
1873 continue;
cristy691a29e2009-09-11 00:44:10 +00001874 id=GetOpenMPThreadId();
1875 pixel=pixels[id];
cristy3ed852e2009-09-05 21:47:34 +00001876 (void) ResetMagickMemory(pixel,0,length*sizeof(*pixel));
cristy691a29e2009-09-11 00:44:10 +00001877 buffer=buffers[id];
cristy3ed852e2009-09-05 21:47:34 +00001878 j=(long) image->columns+2;
1879 for (y=0; y < (long) image->rows; y++)
1880 {
1881 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001882 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001883
1884 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1885 if (p == (const PixelPacket *) NULL)
1886 break;
1887 j++;
1888 for (x=0; x < (long) image->columns; x++)
1889 {
1890 switch (channel)
1891 {
cristyce70c172010-01-07 17:15:30 +00001892 case 0: pixel[j]=GetRedPixelComponent(p); break;
1893 case 1: pixel[j]=GetGreenPixelComponent(p); break;
1894 case 2: pixel[j]=GetBluePixelComponent(p); break;
1895 case 3: pixel[j]=GetOpacityPixelComponent(p); break;
cristy3ed852e2009-09-05 21:47:34 +00001896 default: break;
1897 }
1898 p++;
1899 j++;
1900 }
1901 j++;
1902 }
cristy3ed852e2009-09-05 21:47:34 +00001903 (void) ResetMagickMemory(buffer,0,length*sizeof(*buffer));
1904 for (i=0; i < 4; i++)
1905 {
1906 Hull(X[i],Y[i],image->columns,image->rows,pixel,buffer,1);
1907 Hull(-X[i],-Y[i],image->columns,image->rows,pixel,buffer,1);
1908 Hull(-X[i],-Y[i],image->columns,image->rows,pixel,buffer,-1);
1909 Hull(X[i],Y[i],image->columns,image->rows,pixel,buffer,-1);
1910 }
1911 j=(long) image->columns+2;
1912 for (y=0; y < (long) image->rows; y++)
1913 {
1914 MagickBooleanType
1915 sync;
1916
1917 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001918 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001919
1920 q=GetCacheViewAuthenticPixels(despeckle_view,0,y,despeckle_image->columns,
1921 1,exception);
1922 if (q == (PixelPacket *) NULL)
1923 break;
1924 j++;
1925 for (x=0; x < (long) image->columns; x++)
1926 {
1927 switch (channel)
1928 {
1929 case 0: q->red=pixel[j]; break;
1930 case 1: q->green=pixel[j]; break;
1931 case 2: q->blue=pixel[j]; break;
1932 case 3: q->opacity=pixel[j]; break;
1933 default: break;
1934 }
1935 q++;
1936 j++;
1937 }
1938 sync=SyncCacheViewAuthenticPixels(despeckle_view,exception);
1939 if (sync == MagickFalse)
1940 {
1941 status=MagickFalse;
1942 break;
1943 }
1944 j++;
1945 }
1946 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1947 {
1948 MagickBooleanType
1949 proceed;
1950
cristyb5d5f722009-11-04 03:03:49 +00001951#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001952 #pragma omp critical (MagickCore_DespeckleImage)
1953#endif
1954 proceed=SetImageProgress(image,DespeckleImageTag,channel,3);
1955 if (proceed == MagickFalse)
1956 status=MagickFalse;
1957 }
1958 }
1959 despeckle_view=DestroyCacheView(despeckle_view);
1960 image_view=DestroyCacheView(image_view);
1961 buffers=DestroyPixelThreadSet(buffers);
1962 pixels=DestroyPixelThreadSet(pixels);
1963 despeckle_image->type=image->type;
1964 if (status == MagickFalse)
1965 despeckle_image=DestroyImage(despeckle_image);
1966 return(despeckle_image);
1967}
1968
1969/*
1970%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1971% %
1972% %
1973% %
1974% E d g e I m a g e %
1975% %
1976% %
1977% %
1978%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1979%
1980% EdgeImage() finds edges in an image. Radius defines the radius of the
1981% convolution filter. Use a radius of 0 and EdgeImage() selects a suitable
1982% radius for you.
1983%
1984% The format of the EdgeImage method is:
1985%
1986% Image *EdgeImage(const Image *image,const double radius,
1987% ExceptionInfo *exception)
1988%
1989% A description of each parameter follows:
1990%
1991% o image: the image.
1992%
1993% o radius: the radius of the pixel neighborhood.
1994%
1995% o exception: return any errors or warnings in this structure.
1996%
1997*/
1998MagickExport Image *EdgeImage(const Image *image,const double radius,
1999 ExceptionInfo *exception)
2000{
2001 Image
2002 *edge_image;
2003
2004 double
2005 *kernel;
2006
2007 register long
2008 i;
2009
2010 unsigned long
2011 width;
2012
2013 assert(image != (const Image *) NULL);
2014 assert(image->signature == MagickSignature);
2015 if (image->debug != MagickFalse)
2016 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2017 assert(exception != (ExceptionInfo *) NULL);
2018 assert(exception->signature == MagickSignature);
2019 width=GetOptimalKernelWidth1D(radius,0.5);
2020 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2021 if (kernel == (double *) NULL)
2022 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2023 for (i=0; i < (long) (width*width); i++)
2024 kernel[i]=(-1.0);
2025 kernel[i/2]=(double) (width*width-1.0);
2026 edge_image=ConvolveImage(image,width,kernel,exception);
2027 kernel=(double *) RelinquishMagickMemory(kernel);
2028 return(edge_image);
2029}
2030
2031/*
2032%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2033% %
2034% %
2035% %
2036% E m b o s s I m a g e %
2037% %
2038% %
2039% %
2040%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2041%
2042% EmbossImage() returns a grayscale image with a three-dimensional effect.
2043% We convolve the image with a Gaussian operator of the given radius and
2044% standard deviation (sigma). For reasonable results, radius should be
2045% larger than sigma. Use a radius of 0 and Emboss() selects a suitable
2046% radius for you.
2047%
2048% The format of the EmbossImage method is:
2049%
2050% Image *EmbossImage(const Image *image,const double radius,
2051% const double sigma,ExceptionInfo *exception)
2052%
2053% A description of each parameter follows:
2054%
2055% o image: the image.
2056%
2057% o radius: the radius of the pixel neighborhood.
2058%
2059% o sigma: the standard deviation of the Gaussian, in pixels.
2060%
2061% o exception: return any errors or warnings in this structure.
2062%
2063*/
2064MagickExport Image *EmbossImage(const Image *image,const double radius,
2065 const double sigma,ExceptionInfo *exception)
2066{
2067 double
2068 *kernel;
2069
2070 Image
2071 *emboss_image;
2072
2073 long
cristy47e00502009-12-17 19:19:57 +00002074 j,
2075 k,
cristy3ed852e2009-09-05 21:47:34 +00002076 u,
2077 v;
2078
cristy47e00502009-12-17 19:19:57 +00002079 register long
2080 i;
2081
cristy3ed852e2009-09-05 21:47:34 +00002082 unsigned long
2083 width;
2084
2085 assert(image != (Image *) NULL);
2086 assert(image->signature == MagickSignature);
2087 if (image->debug != MagickFalse)
2088 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2089 assert(exception != (ExceptionInfo *) NULL);
2090 assert(exception->signature == MagickSignature);
2091 width=GetOptimalKernelWidth2D(radius,sigma);
2092 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2093 if (kernel == (double *) NULL)
2094 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy3ed852e2009-09-05 21:47:34 +00002095 j=(long) width/2;
cristy47e00502009-12-17 19:19:57 +00002096 k=j;
2097 i=0;
2098 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002099 {
cristy47e00502009-12-17 19:19:57 +00002100 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00002101 {
cristy47e00502009-12-17 19:19:57 +00002102 kernel[i]=((u < 0) || (v < 0) ? -8.0 : 8.0)*
2103 exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
2104 (2.0*MagickPI*MagickSigma*MagickSigma);
2105 if (u != k)
cristy3ed852e2009-09-05 21:47:34 +00002106 kernel[i]=0.0;
2107 i++;
2108 }
cristy47e00502009-12-17 19:19:57 +00002109 k--;
cristy3ed852e2009-09-05 21:47:34 +00002110 }
2111 emboss_image=ConvolveImage(image,width,kernel,exception);
2112 if (emboss_image != (Image *) NULL)
2113 (void) EqualizeImage(emboss_image);
2114 kernel=(double *) RelinquishMagickMemory(kernel);
2115 return(emboss_image);
2116}
2117
2118/*
2119%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2120% %
2121% %
2122% %
cristy56a9e512010-01-06 18:18:55 +00002123% F i l t e r I m a g e %
2124% %
2125% %
2126% %
2127%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2128%
2129% FilterImage() applies a custom convolution kernel to the image.
2130%
2131% The format of the FilterImage method is:
2132%
cristy2be15382010-01-21 02:38:03 +00002133% Image *FilterImage(const Image *image,const KernelInfo *kernel,
cristy56a9e512010-01-06 18:18:55 +00002134% ExceptionInfo *exception)
2135% Image *FilterImageChannel(const Image *image,const ChannelType channel,
cristy2be15382010-01-21 02:38:03 +00002136% const KernelInfo *kernel,ExceptionInfo *exception)
cristy56a9e512010-01-06 18:18:55 +00002137%
2138% A description of each parameter follows:
2139%
2140% o image: the image.
2141%
2142% o channel: the channel type.
2143%
2144% o kernel: the filtering kernel.
2145%
2146% o exception: return any errors or warnings in this structure.
2147%
2148*/
2149
cristy2be15382010-01-21 02:38:03 +00002150MagickExport Image *FilterImage(const Image *image,const KernelInfo *kernel,
cristy56a9e512010-01-06 18:18:55 +00002151 ExceptionInfo *exception)
2152{
2153 Image
2154 *filter_image;
2155
2156 filter_image=FilterImageChannel(image,DefaultChannels,kernel,exception);
2157 return(filter_image);
2158}
2159
2160MagickExport Image *FilterImageChannel(const Image *image,
cristy2be15382010-01-21 02:38:03 +00002161 const ChannelType channel,const KernelInfo *kernel,ExceptionInfo *exception)
cristy56a9e512010-01-06 18:18:55 +00002162{
2163#define FilterImageTag "Filter/Image"
2164
2165 CacheView
2166 *filter_view,
2167 *image_view;
2168
cristy6771f1e2010-03-05 19:43:39 +00002169 KernelInfo
2170 *normal_kernel;
2171
cristy56a9e512010-01-06 18:18:55 +00002172 Image
2173 *filter_image;
2174
2175 long
2176 progress,
2177 y;
2178
2179 MagickBooleanType
2180 status;
2181
2182 MagickPixelPacket
2183 bias;
2184
cristy56a9e512010-01-06 18:18:55 +00002185 /*
2186 Initialize filter image attributes.
2187 */
2188 assert(image != (Image *) NULL);
2189 assert(image->signature == MagickSignature);
2190 if (image->debug != MagickFalse)
2191 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2192 assert(exception != (ExceptionInfo *) NULL);
2193 assert(exception->signature == MagickSignature);
2194 if ((kernel->width % 2) == 0)
2195 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
2196 filter_image=CloneImage(image,0,0,MagickTrue,exception);
2197 if (filter_image == (Image *) NULL)
2198 return((Image *) NULL);
2199 if (SetImageStorageClass(filter_image,DirectClass) == MagickFalse)
2200 {
2201 InheritException(exception,&filter_image->exception);
2202 filter_image=DestroyImage(filter_image);
2203 return((Image *) NULL);
2204 }
2205 if (image->debug != MagickFalse)
2206 {
2207 char
2208 format[MaxTextExtent],
2209 *message;
2210
2211 long
2212 u,
2213 v;
2214
2215 register const double
2216 *k;
2217
2218 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
2219 " FilterImage with %ldx%ld kernel:",kernel->width,kernel->height);
2220 message=AcquireString("");
2221 k=kernel->values;
2222 for (v=0; v < (long) kernel->height; v++)
2223 {
2224 *message='\0';
2225 (void) FormatMagickString(format,MaxTextExtent,"%ld: ",v);
2226 (void) ConcatenateString(&message,format);
2227 for (u=0; u < (long) kernel->width; u++)
2228 {
cristye7f51092010-01-17 00:39:37 +00002229 (void) FormatMagickString(format,MaxTextExtent,"%g ",*k++);
cristy56a9e512010-01-06 18:18:55 +00002230 (void) ConcatenateString(&message,format);
2231 }
2232 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
2233 }
2234 message=DestroyString(message);
2235 }
cristy6771f1e2010-03-05 19:43:39 +00002236 normal_kernel=CloneKernelInfo(kernel);
2237 if (normal_kernel == (KernelInfo *) NULL)
2238 {
2239 filter_image=DestroyImage(filter_image);
2240 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2241 }
2242 ScaleKernelInfo(normal_kernel,1.0,NormalizeValue);
2243 status=AccelerateConvolveImage(image,normal_kernel,filter_image,exception);
cristyd43a46b2010-01-21 02:13:41 +00002244 if (status == MagickTrue)
2245 return(filter_image);
cristy56a9e512010-01-06 18:18:55 +00002246 /*
2247 Filter image.
2248 */
2249 status=MagickTrue;
2250 progress=0;
2251 GetMagickPixelPacket(image,&bias);
2252 SetMagickPixelPacketBias(image,&bias);
2253 image_view=AcquireCacheView(image);
2254 filter_view=AcquireCacheView(filter_image);
2255#if defined(MAGICKCORE_OPENMP_SUPPORT)
2256 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2257#endif
2258 for (y=0; y < (long) image->rows; y++)
2259 {
2260 MagickBooleanType
2261 sync;
2262
2263 register const IndexPacket
2264 *restrict indexes;
2265
2266 register const PixelPacket
2267 *restrict p;
2268
2269 register IndexPacket
2270 *restrict filter_indexes;
2271
2272 register long
2273 x;
2274
2275 register PixelPacket
2276 *restrict q;
2277
2278 if (status == MagickFalse)
2279 continue;
cristy6771f1e2010-03-05 19:43:39 +00002280 p=GetCacheViewVirtualPixels(image_view,-((long) normal_kernel->width/2L),
2281 y-(long) (normal_kernel->height/2L),image->columns+normal_kernel->width,
2282 normal_kernel->height,exception);
cristy56a9e512010-01-06 18:18:55 +00002283 q=GetCacheViewAuthenticPixels(filter_view,0,y,filter_image->columns,1,
2284 exception);
2285 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2286 {
2287 status=MagickFalse;
2288 continue;
2289 }
2290 indexes=GetCacheViewVirtualIndexQueue(image_view);
2291 filter_indexes=GetCacheViewAuthenticIndexQueue(filter_view);
2292 for (x=0; x < (long) image->columns; x++)
2293 {
2294 long
2295 v;
2296
2297 MagickPixelPacket
2298 pixel;
2299
2300 register const double
2301 *restrict k;
2302
2303 register const PixelPacket
2304 *restrict kernel_pixels;
2305
2306 register long
2307 u;
2308
2309 pixel=bias;
cristy6771f1e2010-03-05 19:43:39 +00002310 k=normal_kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002311 kernel_pixels=p;
2312 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
2313 {
cristy6771f1e2010-03-05 19:43:39 +00002314 for (v=0; v < (long) normal_kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002315 {
cristy6771f1e2010-03-05 19:43:39 +00002316 for (u=0; u < (long) normal_kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002317 {
2318 pixel.red+=(*k)*kernel_pixels[u].red;
2319 pixel.green+=(*k)*kernel_pixels[u].green;
2320 pixel.blue+=(*k)*kernel_pixels[u].blue;
2321 k++;
2322 }
cristy6771f1e2010-03-05 19:43:39 +00002323 kernel_pixels+=image->columns+normal_kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002324 }
2325 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002326 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002327 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002328 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002329 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002330 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002331 if ((channel & OpacityChannel) != 0)
2332 {
cristy6771f1e2010-03-05 19:43:39 +00002333 k=normal_kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002334 kernel_pixels=p;
cristy6771f1e2010-03-05 19:43:39 +00002335 for (v=0; v < (long) normal_kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002336 {
cristy6771f1e2010-03-05 19:43:39 +00002337 for (u=0; u < (long) normal_kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002338 {
2339 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
2340 k++;
2341 }
cristy6771f1e2010-03-05 19:43:39 +00002342 kernel_pixels+=image->columns+normal_kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002343 }
cristyce70c172010-01-07 17:15:30 +00002344 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002345 }
2346 if (((channel & IndexChannel) != 0) &&
2347 (image->colorspace == CMYKColorspace))
2348 {
2349 register const IndexPacket
2350 *restrict kernel_indexes;
2351
cristy6771f1e2010-03-05 19:43:39 +00002352 k=normal_kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002353 kernel_indexes=indexes;
cristy6771f1e2010-03-05 19:43:39 +00002354 for (v=0; v < (long) normal_kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002355 {
cristy6771f1e2010-03-05 19:43:39 +00002356 for (u=0; u < (long) normal_kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002357 {
2358 pixel.index+=(*k)*kernel_indexes[u];
2359 k++;
2360 }
cristy6771f1e2010-03-05 19:43:39 +00002361 kernel_indexes+=image->columns+normal_kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002362 }
cristyce70c172010-01-07 17:15:30 +00002363 filter_indexes[x]=ClampToQuantum(pixel.index);
cristy56a9e512010-01-06 18:18:55 +00002364 }
2365 }
2366 else
2367 {
2368 MagickRealType
2369 alpha,
2370 gamma;
2371
2372 gamma=0.0;
cristy6771f1e2010-03-05 19:43:39 +00002373 for (v=0; v < (long) normal_kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002374 {
cristy6771f1e2010-03-05 19:43:39 +00002375 for (u=0; u < (long) normal_kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002376 {
2377 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
2378 kernel_pixels[u].opacity));
2379 pixel.red+=(*k)*alpha*kernel_pixels[u].red;
2380 pixel.green+=(*k)*alpha*kernel_pixels[u].green;
2381 pixel.blue+=(*k)*alpha*kernel_pixels[u].blue;
2382 gamma+=(*k)*alpha;
2383 k++;
2384 }
cristy6771f1e2010-03-05 19:43:39 +00002385 kernel_pixels+=image->columns+normal_kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002386 }
2387 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
2388 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002389 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002390 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002391 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002392 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002393 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002394 if ((channel & OpacityChannel) != 0)
2395 {
cristy6771f1e2010-03-05 19:43:39 +00002396 k=normal_kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002397 kernel_pixels=p;
cristy6771f1e2010-03-05 19:43:39 +00002398 for (v=0; v < (long) normal_kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002399 {
cristy6771f1e2010-03-05 19:43:39 +00002400 for (u=0; u < (long) normal_kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002401 {
2402 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
2403 k++;
2404 }
cristy6771f1e2010-03-05 19:43:39 +00002405 kernel_pixels+=image->columns+normal_kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002406 }
cristyce70c172010-01-07 17:15:30 +00002407 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002408 }
2409 if (((channel & IndexChannel) != 0) &&
2410 (image->colorspace == CMYKColorspace))
2411 {
2412 register const IndexPacket
2413 *restrict kernel_indexes;
2414
cristy6771f1e2010-03-05 19:43:39 +00002415 k=normal_kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002416 kernel_pixels=p;
2417 kernel_indexes=indexes;
cristy6771f1e2010-03-05 19:43:39 +00002418 for (v=0; v < (long) normal_kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002419 {
cristy6771f1e2010-03-05 19:43:39 +00002420 for (u=0; u < (long) normal_kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002421 {
2422 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
2423 kernel_pixels[u].opacity));
2424 pixel.index+=(*k)*alpha*kernel_indexes[u];
2425 k++;
2426 }
cristy6771f1e2010-03-05 19:43:39 +00002427 kernel_pixels+=image->columns+normal_kernel->width;
2428 kernel_indexes+=image->columns+normal_kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002429 }
cristy2115aea2010-01-09 23:16:08 +00002430 filter_indexes[x]=ClampToQuantum(gamma*
2431 GetIndexPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002432 }
2433 }
2434 p++;
2435 q++;
2436 }
2437 sync=SyncCacheViewAuthenticPixels(filter_view,exception);
2438 if (sync == MagickFalse)
2439 status=MagickFalse;
2440 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2441 {
2442 MagickBooleanType
2443 proceed;
2444
2445#if defined(MAGICKCORE_OPENMP_SUPPORT)
2446 #pragma omp critical (MagickCore_FilterImageChannel)
2447#endif
2448 proceed=SetImageProgress(image,FilterImageTag,progress++,image->rows);
2449 if (proceed == MagickFalse)
2450 status=MagickFalse;
2451 }
2452 }
2453 filter_image->type=image->type;
2454 filter_view=DestroyCacheView(filter_view);
2455 image_view=DestroyCacheView(image_view);
cristy6771f1e2010-03-05 19:43:39 +00002456 normal_kernel=DestroyKernelInfo(normal_kernel);
cristy56a9e512010-01-06 18:18:55 +00002457 if (status == MagickFalse)
2458 filter_image=DestroyImage(filter_image);
2459 return(filter_image);
2460}
2461
2462/*
2463%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2464% %
2465% %
2466% %
cristy3ed852e2009-09-05 21:47:34 +00002467% G a u s s i a n B l u r I m a g e %
2468% %
2469% %
2470% %
2471%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2472%
2473% GaussianBlurImage() blurs an image. We convolve the image with a
2474% Gaussian operator of the given radius and standard deviation (sigma).
2475% For reasonable results, the radius should be larger than sigma. Use a
2476% radius of 0 and GaussianBlurImage() selects a suitable radius for you
2477%
2478% The format of the GaussianBlurImage method is:
2479%
2480% Image *GaussianBlurImage(const Image *image,onst double radius,
2481% const double sigma,ExceptionInfo *exception)
2482% Image *GaussianBlurImageChannel(const Image *image,
2483% const ChannelType channel,const double radius,const double sigma,
2484% ExceptionInfo *exception)
2485%
2486% A description of each parameter follows:
2487%
2488% o image: the image.
2489%
2490% o channel: the channel type.
2491%
2492% o radius: the radius of the Gaussian, in pixels, not counting the center
2493% pixel.
2494%
2495% o sigma: the standard deviation of the Gaussian, in pixels.
2496%
2497% o exception: return any errors or warnings in this structure.
2498%
2499*/
2500
2501MagickExport Image *GaussianBlurImage(const Image *image,const double radius,
2502 const double sigma,ExceptionInfo *exception)
2503{
2504 Image
2505 *blur_image;
2506
2507 blur_image=GaussianBlurImageChannel(image,DefaultChannels,radius,sigma,
2508 exception);
2509 return(blur_image);
2510}
2511
2512MagickExport Image *GaussianBlurImageChannel(const Image *image,
2513 const ChannelType channel,const double radius,const double sigma,
2514 ExceptionInfo *exception)
2515{
2516 double
2517 *kernel;
2518
2519 Image
2520 *blur_image;
2521
cristy47e00502009-12-17 19:19:57 +00002522 long
2523 j,
cristy3ed852e2009-09-05 21:47:34 +00002524 u,
2525 v;
2526
cristy47e00502009-12-17 19:19:57 +00002527 register long
2528 i;
2529
cristy3ed852e2009-09-05 21:47:34 +00002530 unsigned long
2531 width;
2532
2533 assert(image != (const Image *) NULL);
2534 assert(image->signature == MagickSignature);
2535 if (image->debug != MagickFalse)
2536 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2537 assert(exception != (ExceptionInfo *) NULL);
2538 assert(exception->signature == MagickSignature);
2539 width=GetOptimalKernelWidth2D(radius,sigma);
2540 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2541 if (kernel == (double *) NULL)
2542 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy47e00502009-12-17 19:19:57 +00002543 j=(long) width/2;
cristy3ed852e2009-09-05 21:47:34 +00002544 i=0;
cristy47e00502009-12-17 19:19:57 +00002545 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002546 {
cristy47e00502009-12-17 19:19:57 +00002547 for (u=(-j); u <= j; u++)
2548 kernel[i++]=exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
2549 (2.0*MagickPI*MagickSigma*MagickSigma);
cristy3ed852e2009-09-05 21:47:34 +00002550 }
2551 blur_image=ConvolveImageChannel(image,channel,width,kernel,exception);
2552 kernel=(double *) RelinquishMagickMemory(kernel);
2553 return(blur_image);
2554}
2555
2556/*
2557%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2558% %
2559% %
2560% %
2561% M e d i a n F i l t e r I m a g e %
2562% %
2563% %
2564% %
2565%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2566%
2567% MedianFilterImage() applies a digital filter that improves the quality
2568% of a noisy image. Each pixel is replaced by the median in a set of
2569% neighboring pixels as defined by radius.
2570%
2571% The algorithm was contributed by Mike Edmonds and implements an insertion
2572% sort for selecting median color-channel values. For more on this algorithm
2573% see "Skip Lists: A probabilistic Alternative to Balanced Trees" by William
2574% Pugh in the June 1990 of Communications of the ACM.
2575%
2576% The format of the MedianFilterImage method is:
2577%
2578% Image *MedianFilterImage(const Image *image,const double radius,
2579% ExceptionInfo *exception)
2580%
2581% A description of each parameter follows:
2582%
2583% o image: the image.
2584%
2585% o radius: the radius of the pixel neighborhood.
2586%
2587% o exception: return any errors or warnings in this structure.
2588%
2589*/
2590
2591#define MedianListChannels 5
2592
2593typedef struct _MedianListNode
2594{
2595 unsigned long
2596 next[9],
2597 count,
2598 signature;
2599} MedianListNode;
2600
2601typedef struct _MedianSkipList
2602{
2603 long
2604 level;
2605
2606 MedianListNode
2607 *nodes;
2608} MedianSkipList;
2609
2610typedef struct _MedianPixelList
2611{
2612 unsigned long
2613 center,
2614 seed,
2615 signature;
2616
2617 MedianSkipList
2618 lists[MedianListChannels];
2619} MedianPixelList;
2620
2621static MedianPixelList *DestroyMedianPixelList(MedianPixelList *pixel_list)
2622{
2623 register long
2624 i;
2625
2626 if (pixel_list == (MedianPixelList *) NULL)
2627 return((MedianPixelList *) NULL);
2628 for (i=0; i < MedianListChannels; i++)
2629 if (pixel_list->lists[i].nodes != (MedianListNode *) NULL)
2630 pixel_list->lists[i].nodes=(MedianListNode *) RelinquishMagickMemory(
2631 pixel_list->lists[i].nodes);
2632 pixel_list=(MedianPixelList *) RelinquishAlignedMemory(pixel_list);
2633 return(pixel_list);
2634}
2635
2636static MedianPixelList **DestroyMedianPixelListThreadSet(
2637 MedianPixelList **pixel_list)
2638{
2639 register long
2640 i;
2641
2642 assert(pixel_list != (MedianPixelList **) NULL);
2643 for (i=0; i < (long) GetOpenMPMaximumThreads(); i++)
2644 if (pixel_list[i] != (MedianPixelList *) NULL)
2645 pixel_list[i]=DestroyMedianPixelList(pixel_list[i]);
2646 pixel_list=(MedianPixelList **) RelinquishAlignedMemory(pixel_list);
2647 return(pixel_list);
2648}
2649
2650static MedianPixelList *AcquireMedianPixelList(const unsigned long width)
2651{
2652 MedianPixelList
2653 *pixel_list;
2654
2655 register long
2656 i;
2657
2658 pixel_list=(MedianPixelList *) AcquireAlignedMemory(1,sizeof(*pixel_list));
2659 if (pixel_list == (MedianPixelList *) NULL)
2660 return(pixel_list);
2661 (void) ResetMagickMemory((void *) pixel_list,0,sizeof(*pixel_list));
2662 pixel_list->center=width*width/2;
2663 for (i=0; i < MedianListChannels; i++)
2664 {
2665 pixel_list->lists[i].nodes=(MedianListNode *) AcquireQuantumMemory(65537UL,
2666 sizeof(*pixel_list->lists[i].nodes));
2667 if (pixel_list->lists[i].nodes == (MedianListNode *) NULL)
2668 return(DestroyMedianPixelList(pixel_list));
2669 (void) ResetMagickMemory(pixel_list->lists[i].nodes,0,65537UL*
2670 sizeof(*pixel_list->lists[i].nodes));
2671 }
2672 pixel_list->signature=MagickSignature;
2673 return(pixel_list);
2674}
2675
2676static MedianPixelList **AcquireMedianPixelListThreadSet(
2677 const unsigned long width)
2678{
2679 register long
2680 i;
2681
2682 MedianPixelList
2683 **pixel_list;
2684
2685 unsigned long
2686 number_threads;
2687
2688 number_threads=GetOpenMPMaximumThreads();
2689 pixel_list=(MedianPixelList **) AcquireAlignedMemory(number_threads,
2690 sizeof(*pixel_list));
2691 if (pixel_list == (MedianPixelList **) NULL)
2692 return((MedianPixelList **) NULL);
2693 (void) ResetMagickMemory(pixel_list,0,number_threads*sizeof(*pixel_list));
2694 for (i=0; i < (long) number_threads; i++)
2695 {
2696 pixel_list[i]=AcquireMedianPixelList(width);
2697 if (pixel_list[i] == (MedianPixelList *) NULL)
2698 return(DestroyMedianPixelListThreadSet(pixel_list));
2699 }
2700 return(pixel_list);
2701}
2702
2703static void AddNodeMedianPixelList(MedianPixelList *pixel_list,
2704 const long channel,const unsigned long color)
2705{
2706 register long
2707 level;
2708
2709 register MedianSkipList
2710 *list;
2711
2712 unsigned long
2713 search,
2714 update[9];
2715
2716 /*
2717 Initialize the node.
2718 */
2719 list=pixel_list->lists+channel;
2720 list->nodes[color].signature=pixel_list->signature;
2721 list->nodes[color].count=1;
2722 /*
2723 Determine where it belongs in the list.
2724 */
2725 search=65536UL;
2726 for (level=list->level; level >= 0; level--)
2727 {
2728 while (list->nodes[search].next[level] < color)
2729 search=list->nodes[search].next[level];
2730 update[level]=search;
2731 }
2732 /*
2733 Generate a pseudo-random level for this node.
2734 */
2735 for (level=0; ; level++)
2736 {
2737 pixel_list->seed=(pixel_list->seed*42893621L)+1L;
2738 if ((pixel_list->seed & 0x300) != 0x300)
2739 break;
2740 }
2741 if (level > 8)
2742 level=8;
2743 if (level > (list->level+2))
2744 level=list->level+2;
2745 /*
2746 If we're raising the list's level, link back to the root node.
2747 */
2748 while (level > list->level)
2749 {
2750 list->level++;
2751 update[list->level]=65536UL;
2752 }
2753 /*
2754 Link the node into the skip-list.
2755 */
2756 do
2757 {
2758 list->nodes[color].next[level]=list->nodes[update[level]].next[level];
2759 list->nodes[update[level]].next[level]=color;
2760 }
2761 while (level-- > 0);
2762}
2763
2764static MagickPixelPacket GetMedianPixelList(MedianPixelList *pixel_list)
2765{
2766 MagickPixelPacket
2767 pixel;
2768
2769 register long
2770 channel;
2771
2772 register MedianSkipList
2773 *list;
2774
2775 unsigned long
2776 center,
2777 color,
2778 count;
2779
2780 unsigned short
2781 channels[MedianListChannels];
2782
2783 /*
2784 Find the median value for each of the color.
2785 */
2786 center=pixel_list->center;
2787 for (channel=0; channel < 5; channel++)
2788 {
2789 list=pixel_list->lists+channel;
2790 color=65536UL;
2791 count=0;
2792 do
2793 {
2794 color=list->nodes[color].next[0];
2795 count+=list->nodes[color].count;
2796 }
2797 while (count <= center);
2798 channels[channel]=(unsigned short) color;
2799 }
2800 GetMagickPixelPacket((const Image *) NULL,&pixel);
2801 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
2802 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
2803 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
2804 pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
2805 pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]);
2806 return(pixel);
2807}
2808
2809static inline void InsertMedianPixelList(const Image *image,
2810 const PixelPacket *pixel,const IndexPacket *indexes,
2811 MedianPixelList *pixel_list)
2812{
2813 unsigned long
2814 signature;
2815
2816 unsigned short
2817 index;
2818
2819 index=ScaleQuantumToShort(pixel->red);
2820 signature=pixel_list->lists[0].nodes[index].signature;
2821 if (signature == pixel_list->signature)
2822 pixel_list->lists[0].nodes[index].count++;
2823 else
2824 AddNodeMedianPixelList(pixel_list,0,index);
2825 index=ScaleQuantumToShort(pixel->green);
2826 signature=pixel_list->lists[1].nodes[index].signature;
2827 if (signature == pixel_list->signature)
2828 pixel_list->lists[1].nodes[index].count++;
2829 else
2830 AddNodeMedianPixelList(pixel_list,1,index);
2831 index=ScaleQuantumToShort(pixel->blue);
2832 signature=pixel_list->lists[2].nodes[index].signature;
2833 if (signature == pixel_list->signature)
2834 pixel_list->lists[2].nodes[index].count++;
2835 else
2836 AddNodeMedianPixelList(pixel_list,2,index);
2837 index=ScaleQuantumToShort(pixel->opacity);
2838 signature=pixel_list->lists[3].nodes[index].signature;
2839 if (signature == pixel_list->signature)
2840 pixel_list->lists[3].nodes[index].count++;
2841 else
2842 AddNodeMedianPixelList(pixel_list,3,index);
2843 if (image->colorspace == CMYKColorspace)
2844 index=ScaleQuantumToShort(*indexes);
2845 signature=pixel_list->lists[4].nodes[index].signature;
2846 if (signature == pixel_list->signature)
2847 pixel_list->lists[4].nodes[index].count++;
2848 else
2849 AddNodeMedianPixelList(pixel_list,4,index);
2850}
2851
2852static void ResetMedianPixelList(MedianPixelList *pixel_list)
2853{
2854 int
2855 level;
2856
2857 register long
2858 channel;
2859
2860 register MedianListNode
2861 *root;
2862
2863 register MedianSkipList
2864 *list;
2865
2866 /*
2867 Reset the skip-list.
2868 */
2869 for (channel=0; channel < 5; channel++)
2870 {
2871 list=pixel_list->lists+channel;
2872 root=list->nodes+65536UL;
2873 list->level=0;
2874 for (level=0; level < 9; level++)
2875 root->next[level]=65536UL;
2876 }
2877 pixel_list->seed=pixel_list->signature++;
2878}
2879
2880MagickExport Image *MedianFilterImage(const Image *image,const double radius,
2881 ExceptionInfo *exception)
2882{
2883#define MedianFilterImageTag "MedianFilter/Image"
2884
cristyc4c8d132010-01-07 01:58:38 +00002885 CacheView
2886 *image_view,
2887 *median_view;
2888
cristy3ed852e2009-09-05 21:47:34 +00002889 Image
2890 *median_image;
2891
2892 long
2893 progress,
2894 y;
2895
2896 MagickBooleanType
2897 status;
2898
2899 MedianPixelList
cristyfa112112010-01-04 17:48:07 +00002900 **restrict pixel_list;
cristy3ed852e2009-09-05 21:47:34 +00002901
2902 unsigned long
2903 width;
2904
cristy3ed852e2009-09-05 21:47:34 +00002905 /*
2906 Initialize median image attributes.
2907 */
2908 assert(image != (Image *) NULL);
2909 assert(image->signature == MagickSignature);
2910 if (image->debug != MagickFalse)
2911 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2912 assert(exception != (ExceptionInfo *) NULL);
2913 assert(exception->signature == MagickSignature);
2914 width=GetOptimalKernelWidth2D(radius,0.5);
2915 if ((image->columns < width) || (image->rows < width))
2916 ThrowImageException(OptionError,"ImageSmallerThanKernelRadius");
2917 median_image=CloneImage(image,image->columns,image->rows,MagickTrue,
2918 exception);
2919 if (median_image == (Image *) NULL)
2920 return((Image *) NULL);
2921 if (SetImageStorageClass(median_image,DirectClass) == MagickFalse)
2922 {
2923 InheritException(exception,&median_image->exception);
2924 median_image=DestroyImage(median_image);
2925 return((Image *) NULL);
2926 }
2927 pixel_list=AcquireMedianPixelListThreadSet(width);
2928 if (pixel_list == (MedianPixelList **) NULL)
2929 {
2930 median_image=DestroyImage(median_image);
2931 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2932 }
2933 /*
2934 Median filter each image row.
2935 */
2936 status=MagickTrue;
2937 progress=0;
2938 image_view=AcquireCacheView(image);
2939 median_view=AcquireCacheView(median_image);
cristyb5d5f722009-11-04 03:03:49 +00002940#if defined(MAGICKCORE_OPENMP_SUPPORT)
2941 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002942#endif
2943 for (y=0; y < (long) median_image->rows; y++)
2944 {
2945 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002946 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002947
2948 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002949 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002950
2951 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002952 *restrict median_indexes;
cristy3ed852e2009-09-05 21:47:34 +00002953
2954 register long
2955 id,
2956 x;
2957
2958 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002959 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002960
2961 if (status == MagickFalse)
2962 continue;
2963 p=GetCacheViewVirtualPixels(image_view,-((long) width/2L),y-(long) (width/
2964 2L),image->columns+width,width,exception);
2965 q=QueueCacheViewAuthenticPixels(median_view,0,y,median_image->columns,1,
2966 exception);
2967 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2968 {
2969 status=MagickFalse;
2970 continue;
2971 }
2972 indexes=GetCacheViewVirtualIndexQueue(image_view);
2973 median_indexes=GetCacheViewAuthenticIndexQueue(median_view);
2974 id=GetOpenMPThreadId();
2975 for (x=0; x < (long) median_image->columns; x++)
2976 {
2977 MagickPixelPacket
2978 pixel;
2979
2980 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002981 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +00002982
2983 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002984 *restrict s;
cristy3ed852e2009-09-05 21:47:34 +00002985
2986 register long
2987 u,
2988 v;
2989
2990 r=p;
2991 s=indexes+x;
2992 ResetMedianPixelList(pixel_list[id]);
2993 for (v=0; v < (long) width; v++)
2994 {
2995 for (u=0; u < (long) width; u++)
2996 InsertMedianPixelList(image,r+u,s+u,pixel_list[id]);
2997 r+=image->columns+width;
2998 s+=image->columns+width;
2999 }
3000 pixel=GetMedianPixelList(pixel_list[id]);
3001 SetPixelPacket(median_image,&pixel,q,median_indexes+x);
3002 p++;
3003 q++;
3004 }
3005 if (SyncCacheViewAuthenticPixels(median_view,exception) == MagickFalse)
3006 status=MagickFalse;
3007 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3008 {
3009 MagickBooleanType
3010 proceed;
3011
cristyb5d5f722009-11-04 03:03:49 +00003012#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003013 #pragma omp critical (MagickCore_MedianFilterImage)
3014#endif
3015 proceed=SetImageProgress(image,MedianFilterImageTag,progress++,
3016 image->rows);
3017 if (proceed == MagickFalse)
3018 status=MagickFalse;
3019 }
3020 }
3021 median_view=DestroyCacheView(median_view);
3022 image_view=DestroyCacheView(image_view);
3023 pixel_list=DestroyMedianPixelListThreadSet(pixel_list);
3024 return(median_image);
3025}
3026
3027/*
3028%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3029% %
3030% %
3031% %
3032% M o t i o n B l u r I m a g e %
3033% %
3034% %
3035% %
3036%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3037%
3038% MotionBlurImage() simulates motion blur. We convolve the image with a
3039% Gaussian operator of the given radius and standard deviation (sigma).
3040% For reasonable results, radius should be larger than sigma. Use a
3041% radius of 0 and MotionBlurImage() selects a suitable radius for you.
3042% Angle gives the angle of the blurring motion.
3043%
3044% Andrew Protano contributed this effect.
3045%
3046% The format of the MotionBlurImage method is:
3047%
3048% Image *MotionBlurImage(const Image *image,const double radius,
3049% const double sigma,const double angle,ExceptionInfo *exception)
3050% Image *MotionBlurImageChannel(const Image *image,const ChannelType channel,
3051% const double radius,const double sigma,const double angle,
3052% ExceptionInfo *exception)
3053%
3054% A description of each parameter follows:
3055%
3056% o image: the image.
3057%
3058% o channel: the channel type.
3059%
3060% o radius: the radius of the Gaussian, in pixels, not counting the center
3061% o radius: the radius of the Gaussian, in pixels, not counting
3062% the center pixel.
3063%
3064% o sigma: the standard deviation of the Gaussian, in pixels.
3065%
3066% o angle: Apply the effect along this angle.
3067%
3068% o exception: return any errors or warnings in this structure.
3069%
3070*/
3071
cristy47e00502009-12-17 19:19:57 +00003072static double *GetMotionBlurKernel(const unsigned long width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +00003073{
cristy3ed852e2009-09-05 21:47:34 +00003074 double
cristy47e00502009-12-17 19:19:57 +00003075 *kernel,
cristy3ed852e2009-09-05 21:47:34 +00003076 normalize;
3077
3078 register long
3079 i;
3080
3081 /*
cristy47e00502009-12-17 19:19:57 +00003082 Generate a 1-D convolution kernel.
cristy3ed852e2009-09-05 21:47:34 +00003083 */
3084 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
3085 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
3086 if (kernel == (double *) NULL)
3087 return(kernel);
cristy3ed852e2009-09-05 21:47:34 +00003088 normalize=0.0;
3089 for (i=0; i < (long) width; i++)
cristy47e00502009-12-17 19:19:57 +00003090 {
3091 kernel[i]=exp((-((double) i*i)/(double) (2.0*MagickSigma*MagickSigma)))/
3092 (MagickSQ2PI*MagickSigma);
cristy3ed852e2009-09-05 21:47:34 +00003093 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +00003094 }
cristy3ed852e2009-09-05 21:47:34 +00003095 for (i=0; i < (long) width; i++)
3096 kernel[i]/=normalize;
3097 return(kernel);
3098}
3099
3100MagickExport Image *MotionBlurImage(const Image *image,const double radius,
3101 const double sigma,const double angle,ExceptionInfo *exception)
3102{
3103 Image
3104 *motion_blur;
3105
3106 motion_blur=MotionBlurImageChannel(image,DefaultChannels,radius,sigma,angle,
3107 exception);
3108 return(motion_blur);
3109}
3110
3111MagickExport Image *MotionBlurImageChannel(const Image *image,
3112 const ChannelType channel,const double radius,const double sigma,
3113 const double angle,ExceptionInfo *exception)
3114{
cristyc4c8d132010-01-07 01:58:38 +00003115 CacheView
3116 *blur_view,
3117 *image_view;
3118
cristy3ed852e2009-09-05 21:47:34 +00003119 double
3120 *kernel;
3121
3122 Image
3123 *blur_image;
3124
3125 long
3126 progress,
3127 y;
3128
3129 MagickBooleanType
3130 status;
3131
3132 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00003133 bias;
cristy3ed852e2009-09-05 21:47:34 +00003134
3135 OffsetInfo
3136 *offset;
3137
3138 PointInfo
3139 point;
3140
3141 register long
3142 i;
3143
3144 unsigned long
3145 width;
3146
cristy3ed852e2009-09-05 21:47:34 +00003147 assert(image != (Image *) NULL);
3148 assert(image->signature == MagickSignature);
3149 if (image->debug != MagickFalse)
3150 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3151 assert(exception != (ExceptionInfo *) NULL);
3152 width=GetOptimalKernelWidth1D(radius,sigma);
3153 kernel=GetMotionBlurKernel(width,sigma);
3154 if (kernel == (double *) NULL)
3155 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3156 offset=(OffsetInfo *) AcquireQuantumMemory(width,sizeof(*offset));
3157 if (offset == (OffsetInfo *) NULL)
3158 {
3159 kernel=(double *) RelinquishMagickMemory(kernel);
3160 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3161 }
3162 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3163 if (blur_image == (Image *) NULL)
3164 {
3165 kernel=(double *) RelinquishMagickMemory(kernel);
3166 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
3167 return((Image *) NULL);
3168 }
3169 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3170 {
3171 kernel=(double *) RelinquishMagickMemory(kernel);
3172 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
3173 InheritException(exception,&blur_image->exception);
3174 blur_image=DestroyImage(blur_image);
3175 return((Image *) NULL);
3176 }
3177 point.x=(double) width*sin(DegreesToRadians(angle));
3178 point.y=(double) width*cos(DegreesToRadians(angle));
3179 for (i=0; i < (long) width; i++)
3180 {
3181 offset[i].x=(long) ((i*point.y)/hypot(point.x,point.y)+0.5);
3182 offset[i].y=(long) ((i*point.x)/hypot(point.x,point.y)+0.5);
3183 }
3184 /*
3185 Motion blur image.
3186 */
3187 status=MagickTrue;
3188 progress=0;
cristyddd82202009-11-03 20:14:50 +00003189 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003190 image_view=AcquireCacheView(image);
3191 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003192#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy00f95372010-02-13 16:39:29 +00003193 #pragma omp parallel for schedule(static) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003194#endif
3195 for (y=0; y < (long) image->rows; y++)
3196 {
3197 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003198 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00003199
3200 register long
3201 x;
3202
3203 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003204 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003205
3206 if (status == MagickFalse)
3207 continue;
3208 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3209 exception);
3210 if (q == (PixelPacket *) NULL)
3211 {
3212 status=MagickFalse;
3213 continue;
3214 }
3215 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
3216 for (x=0; x < (long) image->columns; x++)
3217 {
3218 MagickPixelPacket
3219 qixel;
3220
3221 PixelPacket
3222 pixel;
3223
3224 register double
cristyc47d1f82009-11-26 01:44:43 +00003225 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00003226
3227 register long
3228 i;
3229
3230 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003231 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003232
3233 k=kernel;
cristyddd82202009-11-03 20:14:50 +00003234 qixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00003235 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
3236 {
3237 for (i=0; i < (long) width; i++)
3238 {
3239 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
3240 offset[i].y,&pixel,exception);
3241 qixel.red+=(*k)*pixel.red;
3242 qixel.green+=(*k)*pixel.green;
3243 qixel.blue+=(*k)*pixel.blue;
3244 qixel.opacity+=(*k)*pixel.opacity;
3245 if (image->colorspace == CMYKColorspace)
3246 {
3247 indexes=GetCacheViewVirtualIndexQueue(image_view);
3248 qixel.index+=(*k)*(*indexes);
3249 }
3250 k++;
3251 }
3252 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003253 q->red=ClampToQuantum(qixel.red);
cristy3ed852e2009-09-05 21:47:34 +00003254 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003255 q->green=ClampToQuantum(qixel.green);
cristy3ed852e2009-09-05 21:47:34 +00003256 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003257 q->blue=ClampToQuantum(qixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00003258 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003259 q->opacity=ClampToQuantum(qixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00003260 if (((channel & IndexChannel) != 0) &&
3261 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00003262 blur_indexes[x]=(IndexPacket) ClampToQuantum(qixel.index);
cristy3ed852e2009-09-05 21:47:34 +00003263 }
3264 else
3265 {
3266 MagickRealType
3267 alpha,
3268 gamma;
3269
3270 alpha=0.0;
3271 gamma=0.0;
3272 for (i=0; i < (long) width; i++)
3273 {
3274 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
3275 offset[i].y,&pixel,exception);
cristy46f08202010-01-10 04:04:21 +00003276 alpha=(MagickRealType) (QuantumScale*GetAlphaPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00003277 qixel.red+=(*k)*alpha*pixel.red;
3278 qixel.green+=(*k)*alpha*pixel.green;
3279 qixel.blue+=(*k)*alpha*pixel.blue;
3280 qixel.opacity+=(*k)*pixel.opacity;
3281 if (image->colorspace == CMYKColorspace)
3282 {
3283 indexes=GetCacheViewVirtualIndexQueue(image_view);
3284 qixel.index+=(*k)*alpha*(*indexes);
3285 }
3286 gamma+=(*k)*alpha;
3287 k++;
3288 }
3289 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3290 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003291 q->red=ClampToQuantum(gamma*qixel.red);
cristy3ed852e2009-09-05 21:47:34 +00003292 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003293 q->green=ClampToQuantum(gamma*qixel.green);
cristy3ed852e2009-09-05 21:47:34 +00003294 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003295 q->blue=ClampToQuantum(gamma*qixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00003296 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003297 q->opacity=ClampToQuantum(qixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00003298 if (((channel & IndexChannel) != 0) &&
3299 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00003300 blur_indexes[x]=(IndexPacket) ClampToQuantum(gamma*qixel.index);
cristy3ed852e2009-09-05 21:47:34 +00003301 }
3302 q++;
3303 }
3304 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
3305 status=MagickFalse;
3306 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3307 {
3308 MagickBooleanType
3309 proceed;
3310
cristyb5d5f722009-11-04 03:03:49 +00003311#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003312 #pragma omp critical (MagickCore_MotionBlurImageChannel)
3313#endif
3314 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
3315 if (proceed == MagickFalse)
3316 status=MagickFalse;
3317 }
3318 }
3319 blur_view=DestroyCacheView(blur_view);
3320 image_view=DestroyCacheView(image_view);
3321 kernel=(double *) RelinquishMagickMemory(kernel);
3322 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
3323 if (status == MagickFalse)
3324 blur_image=DestroyImage(blur_image);
3325 return(blur_image);
3326}
3327
3328/*
3329%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3330% %
3331% %
3332% %
3333% P r e v i e w I m a g e %
3334% %
3335% %
3336% %
3337%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3338%
3339% PreviewImage() tiles 9 thumbnails of the specified image with an image
3340% processing operation applied with varying parameters. This may be helpful
3341% pin-pointing an appropriate parameter for a particular image processing
3342% operation.
3343%
3344% The format of the PreviewImages method is:
3345%
3346% Image *PreviewImages(const Image *image,const PreviewType preview,
3347% ExceptionInfo *exception)
3348%
3349% A description of each parameter follows:
3350%
3351% o image: the image.
3352%
3353% o preview: the image processing operation.
3354%
3355% o exception: return any errors or warnings in this structure.
3356%
3357*/
3358MagickExport Image *PreviewImage(const Image *image,const PreviewType preview,
3359 ExceptionInfo *exception)
3360{
3361#define NumberTiles 9
3362#define PreviewImageTag "Preview/Image"
3363#define DefaultPreviewGeometry "204x204+10+10"
3364
3365 char
3366 factor[MaxTextExtent],
3367 label[MaxTextExtent];
3368
3369 double
3370 degrees,
3371 gamma,
3372 percentage,
3373 radius,
3374 sigma,
3375 threshold;
3376
3377 Image
3378 *images,
3379 *montage_image,
3380 *preview_image,
3381 *thumbnail;
3382
3383 ImageInfo
3384 *preview_info;
3385
3386 long
3387 y;
3388
3389 MagickBooleanType
3390 proceed;
3391
3392 MontageInfo
3393 *montage_info;
3394
3395 QuantizeInfo
3396 quantize_info;
3397
3398 RectangleInfo
3399 geometry;
3400
3401 register long
3402 i,
3403 x;
3404
3405 unsigned long
3406 colors;
3407
3408 /*
3409 Open output image file.
3410 */
3411 assert(image != (Image *) NULL);
3412 assert(image->signature == MagickSignature);
3413 if (image->debug != MagickFalse)
3414 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3415 colors=2;
3416 degrees=0.0;
3417 gamma=(-0.2f);
3418 preview_info=AcquireImageInfo();
3419 SetGeometry(image,&geometry);
3420 (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y,
3421 &geometry.width,&geometry.height);
3422 images=NewImageList();
3423 percentage=12.5;
3424 GetQuantizeInfo(&quantize_info);
3425 radius=0.0;
3426 sigma=1.0;
3427 threshold=0.0;
3428 x=0;
3429 y=0;
3430 for (i=0; i < NumberTiles; i++)
3431 {
3432 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
3433 if (thumbnail == (Image *) NULL)
3434 break;
3435 (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL,
3436 (void *) NULL);
3437 (void) SetImageProperty(thumbnail,"label",DefaultTileLabel);
3438 if (i == (NumberTiles/2))
3439 {
3440 (void) QueryColorDatabase("#dfdfdf",&thumbnail->matte_color,exception);
3441 AppendImageToList(&images,thumbnail);
3442 continue;
3443 }
3444 switch (preview)
3445 {
3446 case RotatePreview:
3447 {
3448 degrees+=45.0;
3449 preview_image=RotateImage(thumbnail,degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003450 (void) FormatMagickString(label,MaxTextExtent,"rotate %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003451 break;
3452 }
3453 case ShearPreview:
3454 {
3455 degrees+=5.0;
3456 preview_image=ShearImage(thumbnail,degrees,degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003457 (void) FormatMagickString(label,MaxTextExtent,"shear %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00003458 degrees,2.0*degrees);
3459 break;
3460 }
3461 case RollPreview:
3462 {
3463 x=(long) ((i+1)*thumbnail->columns)/NumberTiles;
3464 y=(long) ((i+1)*thumbnail->rows)/NumberTiles;
3465 preview_image=RollImage(thumbnail,x,y,exception);
3466 (void) FormatMagickString(label,MaxTextExtent,"roll %ldx%ld",x,y);
3467 break;
3468 }
3469 case HuePreview:
3470 {
3471 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3472 if (preview_image == (Image *) NULL)
3473 break;
cristye7f51092010-01-17 00:39:37 +00003474 (void) FormatMagickString(factor,MaxTextExtent,"100,100,%g",
cristy3ed852e2009-09-05 21:47:34 +00003475 2.0*percentage);
3476 (void) ModulateImage(preview_image,factor);
3477 (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor);
3478 break;
3479 }
3480 case SaturationPreview:
3481 {
3482 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3483 if (preview_image == (Image *) NULL)
3484 break;
cristye7f51092010-01-17 00:39:37 +00003485 (void) FormatMagickString(factor,MaxTextExtent,"100,%g",
cristy8cd5b312010-01-07 01:10:24 +00003486 2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00003487 (void) ModulateImage(preview_image,factor);
3488 (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor);
3489 break;
3490 }
3491 case BrightnessPreview:
3492 {
3493 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3494 if (preview_image == (Image *) NULL)
3495 break;
cristye7f51092010-01-17 00:39:37 +00003496 (void) FormatMagickString(factor,MaxTextExtent,"%g",2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00003497 (void) ModulateImage(preview_image,factor);
3498 (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor);
3499 break;
3500 }
3501 case GammaPreview:
3502 default:
3503 {
3504 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3505 if (preview_image == (Image *) NULL)
3506 break;
3507 gamma+=0.4f;
3508 (void) GammaImageChannel(preview_image,DefaultChannels,gamma);
cristye7f51092010-01-17 00:39:37 +00003509 (void) FormatMagickString(label,MaxTextExtent,"gamma %g",gamma);
cristy3ed852e2009-09-05 21:47:34 +00003510 break;
3511 }
3512 case SpiffPreview:
3513 {
3514 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3515 if (preview_image != (Image *) NULL)
3516 for (x=0; x < i; x++)
3517 (void) ContrastImage(preview_image,MagickTrue);
3518 (void) FormatMagickString(label,MaxTextExtent,"contrast (%ld)",i+1);
3519 break;
3520 }
3521 case DullPreview:
3522 {
3523 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3524 if (preview_image == (Image *) NULL)
3525 break;
3526 for (x=0; x < i; x++)
3527 (void) ContrastImage(preview_image,MagickFalse);
3528 (void) FormatMagickString(label,MaxTextExtent,"+contrast (%ld)",i+1);
3529 break;
3530 }
3531 case GrayscalePreview:
3532 {
3533 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3534 if (preview_image == (Image *) NULL)
3535 break;
3536 colors<<=1;
3537 quantize_info.number_colors=colors;
3538 quantize_info.colorspace=GRAYColorspace;
3539 (void) QuantizeImage(&quantize_info,preview_image);
3540 (void) FormatMagickString(label,MaxTextExtent,
3541 "-colorspace gray -colors %ld",colors);
3542 break;
3543 }
3544 case QuantizePreview:
3545 {
3546 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3547 if (preview_image == (Image *) NULL)
3548 break;
3549 colors<<=1;
3550 quantize_info.number_colors=colors;
3551 (void) QuantizeImage(&quantize_info,preview_image);
3552 (void) FormatMagickString(label,MaxTextExtent,"colors %ld",colors);
3553 break;
3554 }
3555 case DespecklePreview:
3556 {
3557 for (x=0; x < (i-1); x++)
3558 {
3559 preview_image=DespeckleImage(thumbnail,exception);
3560 if (preview_image == (Image *) NULL)
3561 break;
3562 thumbnail=DestroyImage(thumbnail);
3563 thumbnail=preview_image;
3564 }
3565 preview_image=DespeckleImage(thumbnail,exception);
3566 if (preview_image == (Image *) NULL)
3567 break;
3568 (void) FormatMagickString(label,MaxTextExtent,"despeckle (%ld)",i+1);
3569 break;
3570 }
3571 case ReduceNoisePreview:
3572 {
3573 preview_image=ReduceNoiseImage(thumbnail,radius,exception);
cristye7f51092010-01-17 00:39:37 +00003574 (void) FormatMagickString(label,MaxTextExtent,"noise %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003575 break;
3576 }
3577 case AddNoisePreview:
3578 {
3579 switch ((int) i)
3580 {
3581 case 0:
3582 {
3583 (void) CopyMagickString(factor,"uniform",MaxTextExtent);
3584 break;
3585 }
3586 case 1:
3587 {
3588 (void) CopyMagickString(factor,"gaussian",MaxTextExtent);
3589 break;
3590 }
3591 case 2:
3592 {
3593 (void) CopyMagickString(factor,"multiplicative",MaxTextExtent);
3594 break;
3595 }
3596 case 3:
3597 {
3598 (void) CopyMagickString(factor,"impulse",MaxTextExtent);
3599 break;
3600 }
3601 case 4:
3602 {
3603 (void) CopyMagickString(factor,"laplacian",MaxTextExtent);
3604 break;
3605 }
3606 case 5:
3607 {
3608 (void) CopyMagickString(factor,"Poisson",MaxTextExtent);
3609 break;
3610 }
3611 default:
3612 {
3613 (void) CopyMagickString(thumbnail->magick,"NULL",MaxTextExtent);
3614 break;
3615 }
3616 }
3617 preview_image=ReduceNoiseImage(thumbnail,(double) i,exception);
3618 (void) FormatMagickString(label,MaxTextExtent,"+noise %s",factor);
3619 break;
3620 }
3621 case SharpenPreview:
3622 {
3623 preview_image=SharpenImage(thumbnail,radius,sigma,exception);
cristye7f51092010-01-17 00:39:37 +00003624 (void) FormatMagickString(label,MaxTextExtent,"sharpen %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003625 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00003626 break;
3627 }
3628 case BlurPreview:
3629 {
3630 preview_image=BlurImage(thumbnail,radius,sigma,exception);
cristye7f51092010-01-17 00:39:37 +00003631 (void) FormatMagickString(label,MaxTextExtent,"blur %gx%g",radius,
cristy3ed852e2009-09-05 21:47:34 +00003632 sigma);
3633 break;
3634 }
3635 case ThresholdPreview:
3636 {
3637 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3638 if (preview_image == (Image *) NULL)
3639 break;
3640 (void) BilevelImage(thumbnail,
3641 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
cristye7f51092010-01-17 00:39:37 +00003642 (void) FormatMagickString(label,MaxTextExtent,"threshold %g",
cristy3ed852e2009-09-05 21:47:34 +00003643 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
3644 break;
3645 }
3646 case EdgeDetectPreview:
3647 {
3648 preview_image=EdgeImage(thumbnail,radius,exception);
cristye7f51092010-01-17 00:39:37 +00003649 (void) FormatMagickString(label,MaxTextExtent,"edge %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003650 break;
3651 }
3652 case SpreadPreview:
3653 {
3654 preview_image=SpreadImage(thumbnail,radius,exception);
cristye7f51092010-01-17 00:39:37 +00003655 (void) FormatMagickString(label,MaxTextExtent,"spread %g",
cristy8cd5b312010-01-07 01:10:24 +00003656 radius+0.5);
cristy3ed852e2009-09-05 21:47:34 +00003657 break;
3658 }
3659 case SolarizePreview:
3660 {
3661 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3662 if (preview_image == (Image *) NULL)
3663 break;
3664 (void) SolarizeImage(preview_image,(double) QuantumRange*
3665 percentage/100.0);
cristye7f51092010-01-17 00:39:37 +00003666 (void) FormatMagickString(label,MaxTextExtent,"solarize %g",
cristy3ed852e2009-09-05 21:47:34 +00003667 (QuantumRange*percentage)/100.0);
3668 break;
3669 }
3670 case ShadePreview:
3671 {
3672 degrees+=10.0;
3673 preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees,
3674 exception);
cristye7f51092010-01-17 00:39:37 +00003675 (void) FormatMagickString(label,MaxTextExtent,"shade %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003676 degrees,degrees);
cristy3ed852e2009-09-05 21:47:34 +00003677 break;
3678 }
3679 case RaisePreview:
3680 {
3681 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3682 if (preview_image == (Image *) NULL)
3683 break;
3684 geometry.width=(unsigned long) (2*i+2);
3685 geometry.height=(unsigned long) (2*i+2);
3686 geometry.x=i/2;
3687 geometry.y=i/2;
3688 (void) RaiseImage(preview_image,&geometry,MagickTrue);
3689 (void) FormatMagickString(label,MaxTextExtent,"raise %lux%lu%+ld%+ld",
3690 geometry.width,geometry.height,geometry.x,geometry.y);
3691 break;
3692 }
3693 case SegmentPreview:
3694 {
3695 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3696 if (preview_image == (Image *) NULL)
3697 break;
3698 threshold+=0.4f;
3699 (void) SegmentImage(preview_image,RGBColorspace,MagickFalse,threshold,
3700 threshold);
cristye7f51092010-01-17 00:39:37 +00003701 (void) FormatMagickString(label,MaxTextExtent,"segment %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00003702 threshold,threshold);
3703 break;
3704 }
3705 case SwirlPreview:
3706 {
3707 preview_image=SwirlImage(thumbnail,degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003708 (void) FormatMagickString(label,MaxTextExtent,"swirl %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003709 degrees+=45.0;
3710 break;
3711 }
3712 case ImplodePreview:
3713 {
3714 degrees+=0.1f;
3715 preview_image=ImplodeImage(thumbnail,degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003716 (void) FormatMagickString(label,MaxTextExtent,"implode %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003717 break;
3718 }
3719 case WavePreview:
3720 {
3721 degrees+=5.0f;
3722 preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003723 (void) FormatMagickString(label,MaxTextExtent,"wave %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003724 0.5*degrees,2.0*degrees);
cristy3ed852e2009-09-05 21:47:34 +00003725 break;
3726 }
3727 case OilPaintPreview:
3728 {
3729 preview_image=OilPaintImage(thumbnail,(double) radius,exception);
cristye7f51092010-01-17 00:39:37 +00003730 (void) FormatMagickString(label,MaxTextExtent,"paint %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003731 break;
3732 }
3733 case CharcoalDrawingPreview:
3734 {
3735 preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma,
3736 exception);
cristye7f51092010-01-17 00:39:37 +00003737 (void) FormatMagickString(label,MaxTextExtent,"charcoal %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003738 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00003739 break;
3740 }
3741 case JPEGPreview:
3742 {
3743 char
3744 filename[MaxTextExtent];
3745
3746 int
3747 file;
3748
3749 MagickBooleanType
3750 status;
3751
3752 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3753 if (preview_image == (Image *) NULL)
3754 break;
3755 preview_info->quality=(unsigned long) percentage;
3756 (void) FormatMagickString(factor,MaxTextExtent,"%lu",
3757 preview_info->quality);
3758 file=AcquireUniqueFileResource(filename);
3759 if (file != -1)
3760 file=close(file)-1;
3761 (void) FormatMagickString(preview_image->filename,MaxTextExtent,
3762 "jpeg:%s",filename);
3763 status=WriteImage(preview_info,preview_image);
3764 if (status != MagickFalse)
3765 {
3766 Image
3767 *quality_image;
3768
3769 (void) CopyMagickString(preview_info->filename,
3770 preview_image->filename,MaxTextExtent);
3771 quality_image=ReadImage(preview_info,exception);
3772 if (quality_image != (Image *) NULL)
3773 {
3774 preview_image=DestroyImage(preview_image);
3775 preview_image=quality_image;
3776 }
3777 }
3778 (void) RelinquishUniqueFileResource(preview_image->filename);
3779 if ((GetBlobSize(preview_image)/1024) >= 1024)
cristye7f51092010-01-17 00:39:37 +00003780 (void) FormatMagickString(label,MaxTextExtent,"quality %s\n%gmb ",
cristy3ed852e2009-09-05 21:47:34 +00003781 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
3782 1024.0/1024.0);
3783 else
3784 if (GetBlobSize(preview_image) >= 1024)
cristy8cd5b312010-01-07 01:10:24 +00003785 (void) FormatMagickString(label,MaxTextExtent,
cristye7f51092010-01-17 00:39:37 +00003786 "quality %s\n%gkb ",factor,(double) ((MagickOffsetType)
cristy8cd5b312010-01-07 01:10:24 +00003787 GetBlobSize(preview_image))/1024.0);
cristy3ed852e2009-09-05 21:47:34 +00003788 else
3789 (void) FormatMagickString(label,MaxTextExtent,"quality %s\n%lub ",
3790 factor,(unsigned long) GetBlobSize(thumbnail));
3791 break;
3792 }
3793 }
3794 thumbnail=DestroyImage(thumbnail);
3795 percentage+=12.5;
3796 radius+=0.5;
3797 sigma+=0.25;
3798 if (preview_image == (Image *) NULL)
3799 break;
3800 (void) DeleteImageProperty(preview_image,"label");
3801 (void) SetImageProperty(preview_image,"label",label);
3802 AppendImageToList(&images,preview_image);
3803 proceed=SetImageProgress(image,PreviewImageTag,i,NumberTiles);
3804 if (proceed == MagickFalse)
3805 break;
3806 }
3807 if (images == (Image *) NULL)
3808 {
3809 preview_info=DestroyImageInfo(preview_info);
3810 return((Image *) NULL);
3811 }
3812 /*
3813 Create the montage.
3814 */
3815 montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL);
3816 (void) CopyMagickString(montage_info->filename,image->filename,MaxTextExtent);
3817 montage_info->shadow=MagickTrue;
3818 (void) CloneString(&montage_info->tile,"3x3");
3819 (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry);
3820 (void) CloneString(&montage_info->frame,DefaultTileFrame);
3821 montage_image=MontageImages(images,montage_info,exception);
3822 montage_info=DestroyMontageInfo(montage_info);
3823 images=DestroyImageList(images);
3824 if (montage_image == (Image *) NULL)
3825 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3826 if (montage_image->montage != (char *) NULL)
3827 {
3828 /*
3829 Free image directory.
3830 */
3831 montage_image->montage=(char *) RelinquishMagickMemory(
3832 montage_image->montage);
3833 if (image->directory != (char *) NULL)
3834 montage_image->directory=(char *) RelinquishMagickMemory(
3835 montage_image->directory);
3836 }
3837 preview_info=DestroyImageInfo(preview_info);
3838 return(montage_image);
3839}
3840
3841/*
3842%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3843% %
3844% %
3845% %
3846% R a d i a l B l u r I m a g e %
3847% %
3848% %
3849% %
3850%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3851%
3852% RadialBlurImage() applies a radial blur to the image.
3853%
3854% Andrew Protano contributed this effect.
3855%
3856% The format of the RadialBlurImage method is:
3857%
3858% Image *RadialBlurImage(const Image *image,const double angle,
3859% ExceptionInfo *exception)
3860% Image *RadialBlurImageChannel(const Image *image,const ChannelType channel,
3861% const double angle,ExceptionInfo *exception)
3862%
3863% A description of each parameter follows:
3864%
3865% o image: the image.
3866%
3867% o channel: the channel type.
3868%
3869% o angle: the angle of the radial blur.
3870%
3871% o exception: return any errors or warnings in this structure.
3872%
3873*/
3874
3875MagickExport Image *RadialBlurImage(const Image *image,const double angle,
3876 ExceptionInfo *exception)
3877{
3878 Image
3879 *blur_image;
3880
3881 blur_image=RadialBlurImageChannel(image,DefaultChannels,angle,exception);
3882 return(blur_image);
3883}
3884
3885MagickExport Image *RadialBlurImageChannel(const Image *image,
3886 const ChannelType channel,const double angle,ExceptionInfo *exception)
3887{
cristyc4c8d132010-01-07 01:58:38 +00003888 CacheView
3889 *blur_view,
3890 *image_view;
3891
cristy3ed852e2009-09-05 21:47:34 +00003892 Image
3893 *blur_image;
3894
3895 long
3896 progress,
3897 y;
3898
3899 MagickBooleanType
3900 status;
3901
3902 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00003903 bias;
cristy3ed852e2009-09-05 21:47:34 +00003904
3905 MagickRealType
3906 blur_radius,
3907 *cos_theta,
3908 offset,
3909 *sin_theta,
3910 theta;
3911
3912 PointInfo
3913 blur_center;
3914
3915 register long
3916 i;
3917
3918 unsigned long
3919 n;
3920
cristy3ed852e2009-09-05 21:47:34 +00003921 /*
3922 Allocate blur image.
3923 */
3924 assert(image != (Image *) NULL);
3925 assert(image->signature == MagickSignature);
3926 if (image->debug != MagickFalse)
3927 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3928 assert(exception != (ExceptionInfo *) NULL);
3929 assert(exception->signature == MagickSignature);
3930 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3931 if (blur_image == (Image *) NULL)
3932 return((Image *) NULL);
3933 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3934 {
3935 InheritException(exception,&blur_image->exception);
3936 blur_image=DestroyImage(blur_image);
3937 return((Image *) NULL);
3938 }
3939 blur_center.x=(double) image->columns/2.0;
3940 blur_center.y=(double) image->rows/2.0;
3941 blur_radius=hypot(blur_center.x,blur_center.y);
3942 n=(unsigned long) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+
3943 2UL);
3944 theta=DegreesToRadians(angle)/(MagickRealType) (n-1);
3945 cos_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3946 sizeof(*cos_theta));
3947 sin_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3948 sizeof(*sin_theta));
3949 if ((cos_theta == (MagickRealType *) NULL) ||
3950 (sin_theta == (MagickRealType *) NULL))
3951 {
3952 blur_image=DestroyImage(blur_image);
3953 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3954 }
3955 offset=theta*(MagickRealType) (n-1)/2.0;
3956 for (i=0; i < (long) n; i++)
3957 {
3958 cos_theta[i]=cos((double) (theta*i-offset));
3959 sin_theta[i]=sin((double) (theta*i-offset));
3960 }
3961 /*
3962 Radial blur image.
3963 */
3964 status=MagickTrue;
3965 progress=0;
cristyddd82202009-11-03 20:14:50 +00003966 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003967 image_view=AcquireCacheView(image);
3968 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003969#if defined(MAGICKCORE_OPENMP_SUPPORT)
3970 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003971#endif
3972 for (y=0; y < (long) blur_image->rows; y++)
3973 {
3974 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003975 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003976
3977 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003978 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00003979
3980 register long
3981 x;
3982
3983 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003984 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003985
3986 if (status == MagickFalse)
3987 continue;
3988 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3989 exception);
3990 if (q == (PixelPacket *) NULL)
3991 {
3992 status=MagickFalse;
3993 continue;
3994 }
3995 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
3996 for (x=0; x < (long) blur_image->columns; x++)
3997 {
3998 MagickPixelPacket
3999 qixel;
4000
4001 MagickRealType
4002 normalize,
4003 radius;
4004
4005 PixelPacket
4006 pixel;
4007
4008 PointInfo
4009 center;
4010
4011 register long
4012 i;
4013
4014 unsigned long
4015 step;
4016
4017 center.x=(double) x-blur_center.x;
4018 center.y=(double) y-blur_center.y;
4019 radius=hypot((double) center.x,center.y);
4020 if (radius == 0)
4021 step=1;
4022 else
4023 {
4024 step=(unsigned long) (blur_radius/radius);
4025 if (step == 0)
4026 step=1;
4027 else
4028 if (step >= n)
4029 step=n-1;
4030 }
4031 normalize=0.0;
cristyddd82202009-11-03 20:14:50 +00004032 qixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00004033 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
4034 {
4035 for (i=0; i < (long) n; i+=step)
4036 {
4037 (void) GetOneCacheViewVirtualPixel(image_view,(long) (blur_center.x+
4038 center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),(long) (
4039 blur_center.y+center.x*sin_theta[i]+center.y*cos_theta[i]+0.5),
4040 &pixel,exception);
4041 qixel.red+=pixel.red;
4042 qixel.green+=pixel.green;
4043 qixel.blue+=pixel.blue;
4044 qixel.opacity+=pixel.opacity;
4045 if (image->colorspace == CMYKColorspace)
4046 {
4047 indexes=GetCacheViewVirtualIndexQueue(image_view);
4048 qixel.index+=(*indexes);
4049 }
4050 normalize+=1.0;
4051 }
4052 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
4053 normalize);
4054 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004055 q->red=ClampToQuantum(normalize*qixel.red);
cristy3ed852e2009-09-05 21:47:34 +00004056 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004057 q->green=ClampToQuantum(normalize*qixel.green);
cristy3ed852e2009-09-05 21:47:34 +00004058 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004059 q->blue=ClampToQuantum(normalize*qixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00004060 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004061 q->opacity=ClampToQuantum(normalize*qixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00004062 if (((channel & IndexChannel) != 0) &&
4063 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00004064 blur_indexes[x]=(IndexPacket) ClampToQuantum(normalize*qixel.index);
cristy3ed852e2009-09-05 21:47:34 +00004065 }
4066 else
4067 {
4068 MagickRealType
4069 alpha,
4070 gamma;
4071
4072 alpha=1.0;
4073 gamma=0.0;
4074 for (i=0; i < (long) n; i+=step)
4075 {
4076 (void) GetOneCacheViewVirtualPixel(image_view,(long) (blur_center.x+
4077 center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),(long) (
4078 blur_center.y+center.x*sin_theta[i]+center.y*cos_theta[i]+0.5),
4079 &pixel,exception);
cristy46f08202010-01-10 04:04:21 +00004080 alpha=(MagickRealType) (QuantumScale*
4081 GetAlphaPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004082 qixel.red+=alpha*pixel.red;
4083 qixel.green+=alpha*pixel.green;
4084 qixel.blue+=alpha*pixel.blue;
4085 qixel.opacity+=pixel.opacity;
4086 if (image->colorspace == CMYKColorspace)
4087 {
4088 indexes=GetCacheViewVirtualIndexQueue(image_view);
4089 qixel.index+=alpha*(*indexes);
4090 }
4091 gamma+=alpha;
4092 normalize+=1.0;
4093 }
4094 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
4095 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
4096 normalize);
4097 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004098 q->red=ClampToQuantum(gamma*qixel.red);
cristy3ed852e2009-09-05 21:47:34 +00004099 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004100 q->green=ClampToQuantum(gamma*qixel.green);
cristy3ed852e2009-09-05 21:47:34 +00004101 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004102 q->blue=ClampToQuantum(gamma*qixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00004103 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004104 q->opacity=ClampToQuantum(normalize*qixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00004105 if (((channel & IndexChannel) != 0) &&
4106 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00004107 blur_indexes[x]=(IndexPacket) ClampToQuantum(gamma*qixel.index);
cristy3ed852e2009-09-05 21:47:34 +00004108 }
4109 q++;
4110 }
4111 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
4112 status=MagickFalse;
4113 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4114 {
4115 MagickBooleanType
4116 proceed;
4117
cristyb5d5f722009-11-04 03:03:49 +00004118#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004119 #pragma omp critical (MagickCore_RadialBlurImageChannel)
4120#endif
4121 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
4122 if (proceed == MagickFalse)
4123 status=MagickFalse;
4124 }
4125 }
4126 blur_view=DestroyCacheView(blur_view);
4127 image_view=DestroyCacheView(image_view);
4128 cos_theta=(MagickRealType *) RelinquishMagickMemory(cos_theta);
4129 sin_theta=(MagickRealType *) RelinquishMagickMemory(sin_theta);
4130 if (status == MagickFalse)
4131 blur_image=DestroyImage(blur_image);
4132 return(blur_image);
4133}
4134
4135/*
4136%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4137% %
4138% %
4139% %
4140% R e d u c e N o i s e I m a g e %
4141% %
4142% %
4143% %
4144%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4145%
4146% ReduceNoiseImage() smooths the contours of an image while still preserving
4147% edge information. The algorithm works by replacing each pixel with its
4148% neighbor closest in value. A neighbor is defined by radius. Use a radius
4149% of 0 and ReduceNoise() selects a suitable radius for you.
4150%
4151% The format of the ReduceNoiseImage method is:
4152%
4153% Image *ReduceNoiseImage(const Image *image,const double radius,
4154% ExceptionInfo *exception)
4155%
4156% A description of each parameter follows:
4157%
4158% o image: the image.
4159%
4160% o radius: the radius of the pixel neighborhood.
4161%
4162% o exception: return any errors or warnings in this structure.
4163%
4164*/
4165
4166static MagickPixelPacket GetNonpeakMedianPixelList(MedianPixelList *pixel_list)
4167{
4168 MagickPixelPacket
4169 pixel;
4170
4171 register long
4172 channel;
4173
4174 register MedianSkipList
4175 *list;
4176
4177 unsigned long
4178 center,
4179 color,
4180 count,
4181 previous,
4182 next;
4183
4184 unsigned short
4185 channels[5];
4186
4187 /*
4188 Finds the median value for each of the color.
4189 */
4190 center=pixel_list->center;
4191 for (channel=0; channel < 5; channel++)
4192 {
4193 list=pixel_list->lists+channel;
4194 color=65536UL;
4195 next=list->nodes[color].next[0];
4196 count=0;
4197 do
4198 {
4199 previous=color;
4200 color=next;
4201 next=list->nodes[color].next[0];
4202 count+=list->nodes[color].count;
4203 }
4204 while (count <= center);
4205 if ((previous == 65536UL) && (next != 65536UL))
4206 color=next;
4207 else
4208 if ((previous != 65536UL) && (next == 65536UL))
4209 color=previous;
4210 channels[channel]=(unsigned short) color;
4211 }
4212 GetMagickPixelPacket((const Image *) NULL,&pixel);
4213 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4214 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4215 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
4216 pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
4217 pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]);
4218 return(pixel);
4219}
4220
4221MagickExport Image *ReduceNoiseImage(const Image *image,const double radius,
4222 ExceptionInfo *exception)
4223{
4224#define ReduceNoiseImageTag "ReduceNoise/Image"
4225
cristyfa112112010-01-04 17:48:07 +00004226 CacheView
4227 *image_view,
4228 *noise_view;
4229
cristy3ed852e2009-09-05 21:47:34 +00004230 Image
4231 *noise_image;
4232
4233 long
4234 progress,
4235 y;
4236
4237 MagickBooleanType
4238 status;
4239
4240 MedianPixelList
cristyfa112112010-01-04 17:48:07 +00004241 **restrict pixel_list;
cristy3ed852e2009-09-05 21:47:34 +00004242
4243 unsigned long
4244 width;
4245
cristy3ed852e2009-09-05 21:47:34 +00004246 /*
4247 Initialize noise image attributes.
4248 */
4249 assert(image != (Image *) NULL);
4250 assert(image->signature == MagickSignature);
4251 if (image->debug != MagickFalse)
4252 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4253 assert(exception != (ExceptionInfo *) NULL);
4254 assert(exception->signature == MagickSignature);
4255 width=GetOptimalKernelWidth2D(radius,0.5);
4256 if ((image->columns < width) || (image->rows < width))
4257 ThrowImageException(OptionError,"ImageSmallerThanKernelRadius");
4258 noise_image=CloneImage(image,image->columns,image->rows,MagickTrue,
4259 exception);
4260 if (noise_image == (Image *) NULL)
4261 return((Image *) NULL);
4262 if (SetImageStorageClass(noise_image,DirectClass) == MagickFalse)
4263 {
4264 InheritException(exception,&noise_image->exception);
4265 noise_image=DestroyImage(noise_image);
4266 return((Image *) NULL);
4267 }
4268 pixel_list=AcquireMedianPixelListThreadSet(width);
4269 if (pixel_list == (MedianPixelList **) NULL)
4270 {
4271 noise_image=DestroyImage(noise_image);
4272 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
4273 }
4274 /*
4275 Reduce noise image.
4276 */
4277 status=MagickTrue;
4278 progress=0;
4279 image_view=AcquireCacheView(image);
4280 noise_view=AcquireCacheView(noise_image);
cristyb5d5f722009-11-04 03:03:49 +00004281#if defined(MAGICKCORE_OPENMP_SUPPORT)
4282 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004283#endif
4284 for (y=0; y < (long) noise_image->rows; y++)
4285 {
4286 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004287 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00004288
4289 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004290 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00004291
4292 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004293 *restrict noise_indexes;
cristy3ed852e2009-09-05 21:47:34 +00004294
4295 register long
4296 id,
4297 x;
4298
4299 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004300 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004301
4302 if (status == MagickFalse)
4303 continue;
4304 p=GetCacheViewVirtualPixels(image_view,-((long) width/2L),y-(long) (width/
4305 2L),image->columns+width,width,exception);
4306 q=QueueCacheViewAuthenticPixels(noise_view,0,y,noise_image->columns,1,
4307 exception);
4308 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
4309 {
4310 status=MagickFalse;
4311 continue;
4312 }
4313 indexes=GetCacheViewVirtualIndexQueue(image_view);
4314 noise_indexes=GetCacheViewAuthenticIndexQueue(noise_view);
4315 id=GetOpenMPThreadId();
4316 for (x=0; x < (long) noise_image->columns; x++)
4317 {
4318 MagickPixelPacket
4319 pixel;
4320
4321 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004322 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +00004323
4324 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004325 *restrict s;
cristy3ed852e2009-09-05 21:47:34 +00004326
4327 register long
4328 u,
4329 v;
4330
4331 r=p;
4332 s=indexes+x;
4333 ResetMedianPixelList(pixel_list[id]);
4334 for (v=0; v < (long) width; v++)
4335 {
4336 for (u=0; u < (long) width; u++)
4337 InsertMedianPixelList(image,r+u,s+u,pixel_list[id]);
4338 r+=image->columns+width;
4339 s+=image->columns+width;
4340 }
4341 pixel=GetNonpeakMedianPixelList(pixel_list[id]);
4342 SetPixelPacket(noise_image,&pixel,q,noise_indexes+x);
4343 p++;
4344 q++;
4345 }
4346 if (SyncCacheViewAuthenticPixels(noise_view,exception) == MagickFalse)
4347 status=MagickFalse;
4348 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4349 {
4350 MagickBooleanType
4351 proceed;
4352
cristyb5d5f722009-11-04 03:03:49 +00004353#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004354 #pragma omp critical (MagickCore_ReduceNoiseImage)
4355#endif
4356 proceed=SetImageProgress(image,ReduceNoiseImageTag,progress++,
4357 image->rows);
4358 if (proceed == MagickFalse)
4359 status=MagickFalse;
4360 }
4361 }
4362 noise_view=DestroyCacheView(noise_view);
4363 image_view=DestroyCacheView(image_view);
4364 pixel_list=DestroyMedianPixelListThreadSet(pixel_list);
4365 return(noise_image);
4366}
4367
4368/*
4369%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4370% %
4371% %
4372% %
4373% S e l e c t i v e B l u r I m a g e %
4374% %
4375% %
4376% %
4377%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4378%
4379% SelectiveBlurImage() selectively blur pixels within a contrast threshold.
4380% It is similar to the unsharpen mask that sharpens everything with contrast
4381% above a certain threshold.
4382%
4383% The format of the SelectiveBlurImage method is:
4384%
4385% Image *SelectiveBlurImage(const Image *image,const double radius,
4386% const double sigma,const double threshold,ExceptionInfo *exception)
4387% Image *SelectiveBlurImageChannel(const Image *image,
4388% const ChannelType channel,const double radius,const double sigma,
4389% const double threshold,ExceptionInfo *exception)
4390%
4391% A description of each parameter follows:
4392%
4393% o image: the image.
4394%
4395% o channel: the channel type.
4396%
4397% o radius: the radius of the Gaussian, in pixels, not counting the center
4398% pixel.
4399%
4400% o sigma: the standard deviation of the Gaussian, in pixels.
4401%
4402% o threshold: only pixels within this contrast threshold are included
4403% in the blur operation.
4404%
4405% o exception: return any errors or warnings in this structure.
4406%
4407*/
4408
4409static inline MagickBooleanType SelectiveContrast(const PixelPacket *p,
4410 const PixelPacket *q,const double threshold)
4411{
4412 if (fabs(PixelIntensity(p)-PixelIntensity(q)) < threshold)
4413 return(MagickTrue);
4414 return(MagickFalse);
4415}
4416
4417MagickExport Image *SelectiveBlurImage(const Image *image,const double radius,
4418 const double sigma,const double threshold,ExceptionInfo *exception)
4419{
4420 Image
4421 *blur_image;
4422
4423 blur_image=SelectiveBlurImageChannel(image,DefaultChannels,radius,sigma,
4424 threshold,exception);
4425 return(blur_image);
4426}
4427
4428MagickExport Image *SelectiveBlurImageChannel(const Image *image,
4429 const ChannelType channel,const double radius,const double sigma,
4430 const double threshold,ExceptionInfo *exception)
4431{
4432#define SelectiveBlurImageTag "SelectiveBlur/Image"
4433
cristy47e00502009-12-17 19:19:57 +00004434 CacheView
4435 *blur_view,
4436 *image_view;
4437
cristy3ed852e2009-09-05 21:47:34 +00004438 double
cristy3ed852e2009-09-05 21:47:34 +00004439 *kernel;
4440
4441 Image
4442 *blur_image;
4443
4444 long
cristy47e00502009-12-17 19:19:57 +00004445 j,
cristy3ed852e2009-09-05 21:47:34 +00004446 progress,
cristy47e00502009-12-17 19:19:57 +00004447 u,
cristy3ed852e2009-09-05 21:47:34 +00004448 v,
4449 y;
4450
4451 MagickBooleanType
4452 status;
4453
4454 MagickPixelPacket
cristy3ed852e2009-09-05 21:47:34 +00004455 bias;
4456
4457 register long
cristy47e00502009-12-17 19:19:57 +00004458 i;
cristy3ed852e2009-09-05 21:47:34 +00004459
4460 unsigned long
4461 width;
4462
cristy3ed852e2009-09-05 21:47:34 +00004463 /*
4464 Initialize blur image attributes.
4465 */
4466 assert(image != (Image *) NULL);
4467 assert(image->signature == MagickSignature);
4468 if (image->debug != MagickFalse)
4469 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4470 assert(exception != (ExceptionInfo *) NULL);
4471 assert(exception->signature == MagickSignature);
4472 width=GetOptimalKernelWidth1D(radius,sigma);
4473 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
4474 if (kernel == (double *) NULL)
4475 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy47e00502009-12-17 19:19:57 +00004476 j=(long) width/2;
cristy3ed852e2009-09-05 21:47:34 +00004477 i=0;
cristy47e00502009-12-17 19:19:57 +00004478 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00004479 {
cristy47e00502009-12-17 19:19:57 +00004480 for (u=(-j); u <= j; u++)
4481 kernel[i++]=exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
4482 (2.0*MagickPI*MagickSigma*MagickSigma);
cristy3ed852e2009-09-05 21:47:34 +00004483 }
4484 if (image->debug != MagickFalse)
4485 {
4486 char
4487 format[MaxTextExtent],
4488 *message;
4489
4490 long
4491 u,
4492 v;
4493
4494 register const double
4495 *k;
4496
4497 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
4498 " SelectiveBlurImage with %ldx%ld kernel:",width,width);
4499 message=AcquireString("");
4500 k=kernel;
4501 for (v=0; v < (long) width; v++)
4502 {
4503 *message='\0';
4504 (void) FormatMagickString(format,MaxTextExtent,"%ld: ",v);
4505 (void) ConcatenateString(&message,format);
4506 for (u=0; u < (long) width; u++)
4507 {
4508 (void) FormatMagickString(format,MaxTextExtent,"%+f ",*k++);
4509 (void) ConcatenateString(&message,format);
4510 }
4511 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
4512 }
4513 message=DestroyString(message);
4514 }
4515 blur_image=CloneImage(image,0,0,MagickTrue,exception);
4516 if (blur_image == (Image *) NULL)
4517 return((Image *) NULL);
4518 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
4519 {
4520 InheritException(exception,&blur_image->exception);
4521 blur_image=DestroyImage(blur_image);
4522 return((Image *) NULL);
4523 }
4524 /*
4525 Threshold blur image.
4526 */
4527 status=MagickTrue;
4528 progress=0;
cristyddd82202009-11-03 20:14:50 +00004529 GetMagickPixelPacket(image,&bias);
4530 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00004531 image_view=AcquireCacheView(image);
4532 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00004533#if defined(MAGICKCORE_OPENMP_SUPPORT)
4534 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004535#endif
4536 for (y=0; y < (long) image->rows; y++)
4537 {
4538 MagickBooleanType
4539 sync;
4540
4541 MagickRealType
4542 gamma;
4543
4544 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004545 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00004546
4547 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004548 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00004549
4550 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004551 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00004552
4553 register long
4554 x;
4555
4556 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004557 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004558
4559 if (status == MagickFalse)
4560 continue;
4561 p=GetCacheViewVirtualPixels(image_view,-((long) width/2L),y-(long) (width/
4562 2L),image->columns+width,width,exception);
4563 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
4564 exception);
4565 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
4566 {
4567 status=MagickFalse;
4568 continue;
4569 }
4570 indexes=GetCacheViewVirtualIndexQueue(image_view);
4571 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
4572 for (x=0; x < (long) image->columns; x++)
4573 {
4574 long
4575 j,
4576 v;
4577
4578 MagickPixelPacket
4579 pixel;
4580
4581 register const double
cristyc47d1f82009-11-26 01:44:43 +00004582 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00004583
4584 register long
4585 u;
4586
cristyddd82202009-11-03 20:14:50 +00004587 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00004588 k=kernel;
4589 gamma=0.0;
4590 j=0;
4591 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
4592 {
4593 for (v=0; v < (long) width; v++)
4594 {
4595 for (u=0; u < (long) width; u++)
4596 {
4597 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4598 {
4599 pixel.red+=(*k)*(p+u+j)->red;
4600 pixel.green+=(*k)*(p+u+j)->green;
4601 pixel.blue+=(*k)*(p+u+j)->blue;
4602 gamma+=(*k);
4603 k++;
4604 }
4605 }
4606 j+=image->columns+width;
4607 }
4608 if (gamma != 0.0)
4609 {
4610 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
4611 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004612 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004613 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004614 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004615 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004616 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004617 }
4618 if ((channel & OpacityChannel) != 0)
4619 {
4620 gamma=0.0;
4621 j=0;
4622 for (v=0; v < (long) width; v++)
4623 {
4624 for (u=0; u < (long) width; u++)
4625 {
4626 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4627 {
4628 pixel.opacity+=(*k)*(p+u+j)->opacity;
4629 gamma+=(*k);
4630 k++;
4631 }
4632 }
4633 j+=image->columns+width;
4634 }
4635 if (gamma != 0.0)
4636 {
4637 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4638 gamma);
cristyce70c172010-01-07 17:15:30 +00004639 SetOpacityPixelComponent(q,ClampToQuantum(gamma*
4640 GetOpacityPixelComponent(&pixel)));
cristy3ed852e2009-09-05 21:47:34 +00004641 }
4642 }
4643 if (((channel & IndexChannel) != 0) &&
4644 (image->colorspace == CMYKColorspace))
4645 {
4646 gamma=0.0;
4647 j=0;
4648 for (v=0; v < (long) width; v++)
4649 {
4650 for (u=0; u < (long) width; u++)
4651 {
4652 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4653 {
4654 pixel.index+=(*k)*indexes[x+u+j];
4655 gamma+=(*k);
4656 k++;
4657 }
4658 }
4659 j+=image->columns+width;
4660 }
4661 if (gamma != 0.0)
4662 {
4663 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4664 gamma);
cristy6db48122010-01-11 00:18:07 +00004665 blur_indexes[x]=ClampToQuantum(gamma*
4666 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004667 }
4668 }
4669 }
4670 else
4671 {
4672 MagickRealType
4673 alpha;
4674
4675 for (v=0; v < (long) width; v++)
4676 {
4677 for (u=0; u < (long) width; u++)
4678 {
4679 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4680 {
cristy46f08202010-01-10 04:04:21 +00004681 alpha=(MagickRealType) (QuantumScale*
4682 GetAlphaPixelComponent(p+u+j));
cristy3ed852e2009-09-05 21:47:34 +00004683 pixel.red+=(*k)*alpha*(p+u+j)->red;
4684 pixel.green+=(*k)*alpha*(p+u+j)->green;
4685 pixel.blue+=(*k)*alpha*(p+u+j)->blue;
4686 pixel.opacity+=(*k)*(p+u+j)->opacity;
4687 gamma+=(*k)*alpha;
4688 k++;
4689 }
4690 }
4691 j+=image->columns+width;
4692 }
4693 if (gamma != 0.0)
4694 {
4695 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
4696 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004697 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004698 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004699 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004700 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004701 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004702 }
4703 if ((channel & OpacityChannel) != 0)
4704 {
4705 gamma=0.0;
4706 j=0;
4707 for (v=0; v < (long) width; v++)
4708 {
4709 for (u=0; u < (long) width; u++)
4710 {
4711 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4712 {
4713 pixel.opacity+=(*k)*(p+u+j)->opacity;
4714 gamma+=(*k);
4715 k++;
4716 }
4717 }
4718 j+=image->columns+width;
4719 }
4720 if (gamma != 0.0)
4721 {
4722 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4723 gamma);
cristy6db48122010-01-11 00:18:07 +00004724 SetOpacityPixelComponent(q,
4725 ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004726 }
4727 }
4728 if (((channel & IndexChannel) != 0) &&
4729 (image->colorspace == CMYKColorspace))
4730 {
4731 gamma=0.0;
4732 j=0;
4733 for (v=0; v < (long) width; v++)
4734 {
4735 for (u=0; u < (long) width; u++)
4736 {
4737 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4738 {
cristy46f08202010-01-10 04:04:21 +00004739 alpha=(MagickRealType) (QuantumScale*
4740 GetAlphaPixelComponent(p+u+j));
cristy3ed852e2009-09-05 21:47:34 +00004741 pixel.index+=(*k)*alpha*indexes[x+u+j];
4742 gamma+=(*k);
4743 k++;
4744 }
4745 }
4746 j+=image->columns+width;
4747 }
4748 if (gamma != 0.0)
4749 {
4750 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4751 gamma);
cristy6db48122010-01-11 00:18:07 +00004752 blur_indexes[x]=ClampToQuantum(gamma*
4753 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004754 }
4755 }
4756 }
4757 p++;
4758 q++;
4759 }
4760 sync=SyncCacheViewAuthenticPixels(blur_view,exception);
4761 if (sync == MagickFalse)
4762 status=MagickFalse;
4763 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4764 {
4765 MagickBooleanType
4766 proceed;
4767
cristyb5d5f722009-11-04 03:03:49 +00004768#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004769 #pragma omp critical (MagickCore_SelectiveBlurImageChannel)
4770#endif
4771 proceed=SetImageProgress(image,SelectiveBlurImageTag,progress++,
4772 image->rows);
4773 if (proceed == MagickFalse)
4774 status=MagickFalse;
4775 }
4776 }
4777 blur_image->type=image->type;
4778 blur_view=DestroyCacheView(blur_view);
4779 image_view=DestroyCacheView(image_view);
4780 kernel=(double *) RelinquishMagickMemory(kernel);
4781 if (status == MagickFalse)
4782 blur_image=DestroyImage(blur_image);
4783 return(blur_image);
4784}
4785
4786/*
4787%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4788% %
4789% %
4790% %
4791% S h a d e I m a g e %
4792% %
4793% %
4794% %
4795%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4796%
4797% ShadeImage() shines a distant light on an image to create a
4798% three-dimensional effect. You control the positioning of the light with
4799% azimuth and elevation; azimuth is measured in degrees off the x axis
4800% and elevation is measured in pixels above the Z axis.
4801%
4802% The format of the ShadeImage method is:
4803%
4804% Image *ShadeImage(const Image *image,const MagickBooleanType gray,
4805% const double azimuth,const double elevation,ExceptionInfo *exception)
4806%
4807% A description of each parameter follows:
4808%
4809% o image: the image.
4810%
4811% o gray: A value other than zero shades the intensity of each pixel.
4812%
4813% o azimuth, elevation: Define the light source direction.
4814%
4815% o exception: return any errors or warnings in this structure.
4816%
4817*/
4818MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray,
4819 const double azimuth,const double elevation,ExceptionInfo *exception)
4820{
4821#define ShadeImageTag "Shade/Image"
4822
cristyc4c8d132010-01-07 01:58:38 +00004823 CacheView
4824 *image_view,
4825 *shade_view;
4826
cristy3ed852e2009-09-05 21:47:34 +00004827 Image
4828 *shade_image;
4829
4830 long
4831 progress,
4832 y;
4833
4834 MagickBooleanType
4835 status;
4836
4837 PrimaryInfo
4838 light;
4839
cristy3ed852e2009-09-05 21:47:34 +00004840 /*
4841 Initialize shaded image attributes.
4842 */
4843 assert(image != (const Image *) NULL);
4844 assert(image->signature == MagickSignature);
4845 if (image->debug != MagickFalse)
4846 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4847 assert(exception != (ExceptionInfo *) NULL);
4848 assert(exception->signature == MagickSignature);
4849 shade_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
4850 if (shade_image == (Image *) NULL)
4851 return((Image *) NULL);
4852 if (SetImageStorageClass(shade_image,DirectClass) == MagickFalse)
4853 {
4854 InheritException(exception,&shade_image->exception);
4855 shade_image=DestroyImage(shade_image);
4856 return((Image *) NULL);
4857 }
4858 /*
4859 Compute the light vector.
4860 */
4861 light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))*
4862 cos(DegreesToRadians(elevation));
4863 light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))*
4864 cos(DegreesToRadians(elevation));
4865 light.z=(double) QuantumRange*sin(DegreesToRadians(elevation));
4866 /*
4867 Shade image.
4868 */
4869 status=MagickTrue;
4870 progress=0;
4871 image_view=AcquireCacheView(image);
4872 shade_view=AcquireCacheView(shade_image);
cristyb5d5f722009-11-04 03:03:49 +00004873#if defined(MAGICKCORE_OPENMP_SUPPORT)
4874 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004875#endif
4876 for (y=0; y < (long) image->rows; y++)
4877 {
4878 MagickRealType
4879 distance,
4880 normal_distance,
4881 shade;
4882
4883 PrimaryInfo
4884 normal;
4885
4886 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004887 *restrict p,
4888 *restrict s0,
4889 *restrict s1,
4890 *restrict s2;
cristy3ed852e2009-09-05 21:47:34 +00004891
4892 register long
4893 x;
4894
4895 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004896 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004897
4898 if (status == MagickFalse)
4899 continue;
4900 p=GetCacheViewVirtualPixels(image_view,-1,y-1,image->columns+2,3,exception);
4901 q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1,
4902 exception);
4903 if ((p == (PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
4904 {
4905 status=MagickFalse;
4906 continue;
4907 }
4908 /*
4909 Shade this row of pixels.
4910 */
4911 normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */
4912 s0=p+1;
4913 s1=s0+image->columns+2;
4914 s2=s1+image->columns+2;
4915 for (x=0; x < (long) image->columns; x++)
4916 {
4917 /*
4918 Determine the surface normal and compute shading.
4919 */
4920 normal.x=(double) (PixelIntensity(s0-1)+PixelIntensity(s1-1)+
4921 PixelIntensity(s2-1)-PixelIntensity(s0+1)-PixelIntensity(s1+1)-
4922 PixelIntensity(s2+1));
4923 normal.y=(double) (PixelIntensity(s2-1)+PixelIntensity(s2)+
4924 PixelIntensity(s2+1)-PixelIntensity(s0-1)-PixelIntensity(s0)-
4925 PixelIntensity(s0+1));
4926 if ((normal.x == 0.0) && (normal.y == 0.0))
4927 shade=light.z;
4928 else
4929 {
4930 shade=0.0;
4931 distance=normal.x*light.x+normal.y*light.y+normal.z*light.z;
4932 if (distance > MagickEpsilon)
4933 {
4934 normal_distance=
4935 normal.x*normal.x+normal.y*normal.y+normal.z*normal.z;
4936 if (normal_distance > (MagickEpsilon*MagickEpsilon))
4937 shade=distance/sqrt((double) normal_distance);
4938 }
4939 }
4940 if (gray != MagickFalse)
4941 {
4942 q->red=(Quantum) shade;
4943 q->green=(Quantum) shade;
4944 q->blue=(Quantum) shade;
4945 }
4946 else
4947 {
cristyce70c172010-01-07 17:15:30 +00004948 q->red=ClampToQuantum(QuantumScale*shade*s1->red);
4949 q->green=ClampToQuantum(QuantumScale*shade*s1->green);
4950 q->blue=ClampToQuantum(QuantumScale*shade*s1->blue);
cristy3ed852e2009-09-05 21:47:34 +00004951 }
4952 q->opacity=s1->opacity;
4953 s0++;
4954 s1++;
4955 s2++;
4956 q++;
4957 }
4958 if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
4959 status=MagickFalse;
4960 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4961 {
4962 MagickBooleanType
4963 proceed;
4964
cristyb5d5f722009-11-04 03:03:49 +00004965#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004966 #pragma omp critical (MagickCore_ShadeImage)
4967#endif
4968 proceed=SetImageProgress(image,ShadeImageTag,progress++,image->rows);
4969 if (proceed == MagickFalse)
4970 status=MagickFalse;
4971 }
4972 }
4973 shade_view=DestroyCacheView(shade_view);
4974 image_view=DestroyCacheView(image_view);
4975 if (status == MagickFalse)
4976 shade_image=DestroyImage(shade_image);
4977 return(shade_image);
4978}
4979
4980/*
4981%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4982% %
4983% %
4984% %
4985% S h a r p e n I m a g e %
4986% %
4987% %
4988% %
4989%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4990%
4991% SharpenImage() sharpens the image. We convolve the image with a Gaussian
4992% operator of the given radius and standard deviation (sigma). For
4993% reasonable results, radius should be larger than sigma. Use a radius of 0
4994% and SharpenImage() selects a suitable radius for you.
4995%
4996% Using a separable kernel would be faster, but the negative weights cancel
4997% out on the corners of the kernel producing often undesirable ringing in the
4998% filtered result; this can be avoided by using a 2D gaussian shaped image
4999% sharpening kernel instead.
5000%
5001% The format of the SharpenImage method is:
5002%
5003% Image *SharpenImage(const Image *image,const double radius,
5004% const double sigma,ExceptionInfo *exception)
5005% Image *SharpenImageChannel(const Image *image,const ChannelType channel,
5006% const double radius,const double sigma,ExceptionInfo *exception)
5007%
5008% A description of each parameter follows:
5009%
5010% o image: the image.
5011%
5012% o channel: the channel type.
5013%
5014% o radius: the radius of the Gaussian, in pixels, not counting the center
5015% pixel.
5016%
5017% o sigma: the standard deviation of the Laplacian, in pixels.
5018%
5019% o exception: return any errors or warnings in this structure.
5020%
5021*/
5022
5023MagickExport Image *SharpenImage(const Image *image,const double radius,
5024 const double sigma,ExceptionInfo *exception)
5025{
5026 Image
5027 *sharp_image;
5028
5029 sharp_image=SharpenImageChannel(image,DefaultChannels,radius,sigma,exception);
5030 return(sharp_image);
5031}
5032
5033MagickExport Image *SharpenImageChannel(const Image *image,
5034 const ChannelType channel,const double radius,const double sigma,
5035 ExceptionInfo *exception)
5036{
5037 double
cristy47e00502009-12-17 19:19:57 +00005038 *kernel,
5039 normalize;
cristy3ed852e2009-09-05 21:47:34 +00005040
5041 Image
5042 *sharp_image;
5043
cristy47e00502009-12-17 19:19:57 +00005044 long
5045 j,
cristy3ed852e2009-09-05 21:47:34 +00005046 u,
5047 v;
5048
cristy47e00502009-12-17 19:19:57 +00005049 register long
5050 i;
5051
cristy3ed852e2009-09-05 21:47:34 +00005052 unsigned long
5053 width;
5054
5055 assert(image != (const Image *) NULL);
5056 assert(image->signature == MagickSignature);
5057 if (image->debug != MagickFalse)
5058 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5059 assert(exception != (ExceptionInfo *) NULL);
5060 assert(exception->signature == MagickSignature);
5061 width=GetOptimalKernelWidth2D(radius,sigma);
5062 kernel=(double *) AcquireQuantumMemory((size_t) width*width,sizeof(*kernel));
5063 if (kernel == (double *) NULL)
5064 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy3ed852e2009-09-05 21:47:34 +00005065 normalize=0.0;
cristy47e00502009-12-17 19:19:57 +00005066 j=(long) width/2;
5067 i=0;
5068 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00005069 {
cristy47e00502009-12-17 19:19:57 +00005070 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00005071 {
cristy47e00502009-12-17 19:19:57 +00005072 kernel[i]=(-exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
5073 (2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00005074 normalize+=kernel[i];
5075 i++;
5076 }
5077 }
5078 kernel[i/2]=(double) ((-2.0)*normalize);
5079 sharp_image=ConvolveImageChannel(image,channel,width,kernel,exception);
5080 kernel=(double *) RelinquishMagickMemory(kernel);
5081 return(sharp_image);
5082}
5083
5084/*
5085%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5086% %
5087% %
5088% %
5089% S p r e a d I m a g e %
5090% %
5091% %
5092% %
5093%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5094%
5095% SpreadImage() is a special effects method that randomly displaces each
5096% pixel in a block defined by the radius parameter.
5097%
5098% The format of the SpreadImage method is:
5099%
5100% Image *SpreadImage(const Image *image,const double radius,
5101% ExceptionInfo *exception)
5102%
5103% A description of each parameter follows:
5104%
5105% o image: the image.
5106%
5107% o radius: Choose a random pixel in a neighborhood of this extent.
5108%
5109% o exception: return any errors or warnings in this structure.
5110%
5111*/
5112MagickExport Image *SpreadImage(const Image *image,const double radius,
5113 ExceptionInfo *exception)
5114{
5115#define SpreadImageTag "Spread/Image"
5116
cristyfa112112010-01-04 17:48:07 +00005117 CacheView
5118 *image_view;
5119
cristy3ed852e2009-09-05 21:47:34 +00005120 Image
5121 *spread_image;
5122
5123 long
5124 progress,
5125 y;
5126
5127 MagickBooleanType
5128 status;
5129
5130 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00005131 bias;
cristy3ed852e2009-09-05 21:47:34 +00005132
5133 RandomInfo
cristyfa112112010-01-04 17:48:07 +00005134 **restrict random_info;
cristy3ed852e2009-09-05 21:47:34 +00005135
5136 ResampleFilter
cristyfa112112010-01-04 17:48:07 +00005137 **restrict resample_filter;
cristy3ed852e2009-09-05 21:47:34 +00005138
5139 unsigned long
5140 width;
5141
cristy3ed852e2009-09-05 21:47:34 +00005142 /*
5143 Initialize spread image attributes.
5144 */
5145 assert(image != (Image *) NULL);
5146 assert(image->signature == MagickSignature);
5147 if (image->debug != MagickFalse)
5148 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5149 assert(exception != (ExceptionInfo *) NULL);
5150 assert(exception->signature == MagickSignature);
5151 spread_image=CloneImage(image,image->columns,image->rows,MagickTrue,
5152 exception);
5153 if (spread_image == (Image *) NULL)
5154 return((Image *) NULL);
5155 if (SetImageStorageClass(spread_image,DirectClass) == MagickFalse)
5156 {
5157 InheritException(exception,&spread_image->exception);
5158 spread_image=DestroyImage(spread_image);
5159 return((Image *) NULL);
5160 }
5161 /*
5162 Spread image.
5163 */
5164 status=MagickTrue;
5165 progress=0;
cristyddd82202009-11-03 20:14:50 +00005166 GetMagickPixelPacket(spread_image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00005167 width=GetOptimalKernelWidth1D(radius,0.5);
cristyb2a11ae2010-02-22 00:53:36 +00005168 resample_filter=AcquireResampleFilterThreadSet(image,
5169 UndefinedVirtualPixelMethod,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00005170 random_info=AcquireRandomInfoThreadSet();
5171 image_view=AcquireCacheView(spread_image);
cristyb5d5f722009-11-04 03:03:49 +00005172#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy00f95372010-02-13 16:39:29 +00005173 #pragma omp parallel for schedule(static) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00005174#endif
5175 for (y=0; y < (long) spread_image->rows; y++)
5176 {
5177 MagickPixelPacket
5178 pixel;
5179
5180 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00005181 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00005182
5183 register long
5184 id,
5185 x;
5186
5187 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00005188 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00005189
5190 if (status == MagickFalse)
5191 continue;
5192 q=QueueCacheViewAuthenticPixels(image_view,0,y,spread_image->columns,1,
5193 exception);
5194 if (q == (PixelPacket *) NULL)
5195 {
5196 status=MagickFalse;
5197 continue;
5198 }
5199 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristyddd82202009-11-03 20:14:50 +00005200 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00005201 id=GetOpenMPThreadId();
5202 for (x=0; x < (long) spread_image->columns; x++)
5203 {
5204 (void) ResamplePixelColor(resample_filter[id],(double) x+width*
5205 (GetPseudoRandomValue(random_info[id])-0.5),(double) y+width*
5206 (GetPseudoRandomValue(random_info[id])-0.5),&pixel);
5207 SetPixelPacket(spread_image,&pixel,q,indexes+x);
5208 q++;
5209 }
5210 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
5211 status=MagickFalse;
5212 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5213 {
5214 MagickBooleanType
5215 proceed;
5216
cristyb5d5f722009-11-04 03:03:49 +00005217#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00005218 #pragma omp critical (MagickCore_SpreadImage)
5219#endif
5220 proceed=SetImageProgress(image,SpreadImageTag,progress++,image->rows);
5221 if (proceed == MagickFalse)
5222 status=MagickFalse;
5223 }
5224 }
5225 image_view=DestroyCacheView(image_view);
5226 random_info=DestroyRandomInfoThreadSet(random_info);
5227 resample_filter=DestroyResampleFilterThreadSet(resample_filter);
5228 return(spread_image);
5229}
5230
5231/*
5232%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5233% %
5234% %
5235% %
5236% U n s h a r p M a s k I m a g e %
5237% %
5238% %
5239% %
5240%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5241%
5242% UnsharpMaskImage() sharpens one or more image channels. We convolve the
5243% image with a Gaussian operator of the given radius and standard deviation
5244% (sigma). For reasonable results, radius should be larger than sigma. Use a
5245% radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
5246%
5247% The format of the UnsharpMaskImage method is:
5248%
5249% Image *UnsharpMaskImage(const Image *image,const double radius,
5250% const double sigma,const double amount,const double threshold,
5251% ExceptionInfo *exception)
5252% Image *UnsharpMaskImageChannel(const Image *image,
5253% const ChannelType channel,const double radius,const double sigma,
5254% const double amount,const double threshold,ExceptionInfo *exception)
5255%
5256% A description of each parameter follows:
5257%
5258% o image: the image.
5259%
5260% o channel: the channel type.
5261%
5262% o radius: the radius of the Gaussian, in pixels, not counting the center
5263% pixel.
5264%
5265% o sigma: the standard deviation of the Gaussian, in pixels.
5266%
5267% o amount: the percentage of the difference between the original and the
5268% blur image that is added back into the original.
5269%
5270% o threshold: the threshold in pixels needed to apply the diffence amount.
5271%
5272% o exception: return any errors or warnings in this structure.
5273%
5274*/
5275
5276MagickExport Image *UnsharpMaskImage(const Image *image,const double radius,
5277 const double sigma,const double amount,const double threshold,
5278 ExceptionInfo *exception)
5279{
5280 Image
5281 *sharp_image;
5282
5283 sharp_image=UnsharpMaskImageChannel(image,DefaultChannels,radius,sigma,amount,
5284 threshold,exception);
5285 return(sharp_image);
5286}
5287
5288MagickExport Image *UnsharpMaskImageChannel(const Image *image,
5289 const ChannelType channel,const double radius,const double sigma,
5290 const double amount,const double threshold,ExceptionInfo *exception)
5291{
5292#define SharpenImageTag "Sharpen/Image"
5293
cristyc4c8d132010-01-07 01:58:38 +00005294 CacheView
5295 *image_view,
5296 *unsharp_view;
5297
cristy3ed852e2009-09-05 21:47:34 +00005298 Image
5299 *unsharp_image;
5300
5301 long
5302 progress,
5303 y;
5304
5305 MagickBooleanType
5306 status;
5307
5308 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00005309 bias;
cristy3ed852e2009-09-05 21:47:34 +00005310
5311 MagickRealType
5312 quantum_threshold;
5313
cristy3ed852e2009-09-05 21:47:34 +00005314 assert(image != (const Image *) NULL);
5315 assert(image->signature == MagickSignature);
5316 if (image->debug != MagickFalse)
5317 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5318 assert(exception != (ExceptionInfo *) NULL);
5319 unsharp_image=BlurImageChannel(image,channel,radius,sigma,exception);
5320 if (unsharp_image == (Image *) NULL)
5321 return((Image *) NULL);
5322 quantum_threshold=(MagickRealType) QuantumRange*threshold;
5323 /*
5324 Unsharp-mask image.
5325 */
5326 status=MagickTrue;
5327 progress=0;
cristyddd82202009-11-03 20:14:50 +00005328 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00005329 image_view=AcquireCacheView(image);
5330 unsharp_view=AcquireCacheView(unsharp_image);
cristyb5d5f722009-11-04 03:03:49 +00005331#if defined(MAGICKCORE_OPENMP_SUPPORT)
5332 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00005333#endif
5334 for (y=0; y < (long) image->rows; y++)
5335 {
5336 MagickPixelPacket
5337 pixel;
5338
5339 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00005340 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00005341
5342 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00005343 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00005344
5345 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00005346 *restrict unsharp_indexes;
cristy3ed852e2009-09-05 21:47:34 +00005347
5348 register long
5349 x;
5350
5351 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00005352 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00005353
5354 if (status == MagickFalse)
5355 continue;
5356 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
5357 q=GetCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
5358 exception);
5359 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
5360 {
5361 status=MagickFalse;
5362 continue;
5363 }
5364 indexes=GetCacheViewVirtualIndexQueue(image_view);
5365 unsharp_indexes=GetCacheViewAuthenticIndexQueue(unsharp_view);
cristyddd82202009-11-03 20:14:50 +00005366 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00005367 for (x=0; x < (long) image->columns; x++)
5368 {
5369 if ((channel & RedChannel) != 0)
5370 {
5371 pixel.red=p->red-(MagickRealType) q->red;
5372 if (fabs(2.0*pixel.red) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005373 pixel.red=(MagickRealType) GetRedPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005374 else
5375 pixel.red=(MagickRealType) p->red+(pixel.red*amount);
cristyce70c172010-01-07 17:15:30 +00005376 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00005377 }
5378 if ((channel & GreenChannel) != 0)
5379 {
5380 pixel.green=p->green-(MagickRealType) q->green;
5381 if (fabs(2.0*pixel.green) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005382 pixel.green=(MagickRealType) GetGreenPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005383 else
5384 pixel.green=(MagickRealType) p->green+(pixel.green*amount);
cristyce70c172010-01-07 17:15:30 +00005385 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00005386 }
5387 if ((channel & BlueChannel) != 0)
5388 {
5389 pixel.blue=p->blue-(MagickRealType) q->blue;
5390 if (fabs(2.0*pixel.blue) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005391 pixel.blue=(MagickRealType) GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005392 else
5393 pixel.blue=(MagickRealType) p->blue+(pixel.blue*amount);
cristyce70c172010-01-07 17:15:30 +00005394 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00005395 }
5396 if ((channel & OpacityChannel) != 0)
5397 {
5398 pixel.opacity=p->opacity-(MagickRealType) q->opacity;
5399 if (fabs(2.0*pixel.opacity) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005400 pixel.opacity=(MagickRealType) GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005401 else
5402 pixel.opacity=p->opacity+(pixel.opacity*amount);
cristyce70c172010-01-07 17:15:30 +00005403 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00005404 }
5405 if (((channel & IndexChannel) != 0) &&
5406 (image->colorspace == CMYKColorspace))
5407 {
5408 pixel.index=unsharp_indexes[x]-(MagickRealType) indexes[x];
5409 if (fabs(2.0*pixel.index) < quantum_threshold)
5410 pixel.index=(MagickRealType) unsharp_indexes[x];
5411 else
5412 pixel.index=(MagickRealType) unsharp_indexes[x]+(pixel.index*
5413 amount);
cristyce70c172010-01-07 17:15:30 +00005414 unsharp_indexes[x]=ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00005415 }
5416 p++;
5417 q++;
5418 }
5419 if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
5420 status=MagickFalse;
5421 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5422 {
5423 MagickBooleanType
5424 proceed;
5425
cristyb5d5f722009-11-04 03:03:49 +00005426#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00005427 #pragma omp critical (MagickCore_UnsharpMaskImageChannel)
5428#endif
5429 proceed=SetImageProgress(image,SharpenImageTag,progress++,image->rows);
5430 if (proceed == MagickFalse)
5431 status=MagickFalse;
5432 }
5433 }
5434 unsharp_image->type=image->type;
5435 unsharp_view=DestroyCacheView(unsharp_view);
5436 image_view=DestroyCacheView(image_view);
5437 if (status == MagickFalse)
5438 unsharp_image=DestroyImage(unsharp_image);
5439 return(unsharp_image);
5440}