blob: 69016704456626207310d5d43569ef68ca484eb4 [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
cristy56a9e512010-01-06 18:18:55 +00002169 Image
2170 *filter_image;
2171
2172 long
2173 progress,
2174 y;
2175
2176 MagickBooleanType
2177 status;
2178
2179 MagickPixelPacket
2180 bias;
2181
cristy56a9e512010-01-06 18:18:55 +00002182 /*
2183 Initialize filter image attributes.
2184 */
2185 assert(image != (Image *) NULL);
2186 assert(image->signature == MagickSignature);
2187 if (image->debug != MagickFalse)
2188 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2189 assert(exception != (ExceptionInfo *) NULL);
2190 assert(exception->signature == MagickSignature);
2191 if ((kernel->width % 2) == 0)
2192 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
2193 filter_image=CloneImage(image,0,0,MagickTrue,exception);
2194 if (filter_image == (Image *) NULL)
2195 return((Image *) NULL);
2196 if (SetImageStorageClass(filter_image,DirectClass) == MagickFalse)
2197 {
2198 InheritException(exception,&filter_image->exception);
2199 filter_image=DestroyImage(filter_image);
2200 return((Image *) NULL);
2201 }
2202 if (image->debug != MagickFalse)
2203 {
2204 char
2205 format[MaxTextExtent],
2206 *message;
2207
2208 long
2209 u,
2210 v;
2211
2212 register const double
2213 *k;
2214
2215 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
2216 " FilterImage with %ldx%ld kernel:",kernel->width,kernel->height);
2217 message=AcquireString("");
2218 k=kernel->values;
2219 for (v=0; v < (long) kernel->height; v++)
2220 {
2221 *message='\0';
2222 (void) FormatMagickString(format,MaxTextExtent,"%ld: ",v);
2223 (void) ConcatenateString(&message,format);
2224 for (u=0; u < (long) kernel->width; u++)
2225 {
cristye7f51092010-01-17 00:39:37 +00002226 (void) FormatMagickString(format,MaxTextExtent,"%g ",*k++);
cristy56a9e512010-01-06 18:18:55 +00002227 (void) ConcatenateString(&message,format);
2228 }
2229 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
2230 }
2231 message=DestroyString(message);
2232 }
cristy36826ab2010-03-06 01:29:30 +00002233 status=AccelerateConvolveImage(image,kernel,filter_image,exception);
cristyd43a46b2010-01-21 02:13:41 +00002234 if (status == MagickTrue)
2235 return(filter_image);
cristy56a9e512010-01-06 18:18:55 +00002236 /*
2237 Filter image.
2238 */
2239 status=MagickTrue;
2240 progress=0;
2241 GetMagickPixelPacket(image,&bias);
2242 SetMagickPixelPacketBias(image,&bias);
2243 image_view=AcquireCacheView(image);
2244 filter_view=AcquireCacheView(filter_image);
2245#if defined(MAGICKCORE_OPENMP_SUPPORT)
2246 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2247#endif
2248 for (y=0; y < (long) image->rows; y++)
2249 {
2250 MagickBooleanType
2251 sync;
2252
2253 register const IndexPacket
2254 *restrict indexes;
2255
2256 register const PixelPacket
2257 *restrict p;
2258
2259 register IndexPacket
2260 *restrict filter_indexes;
2261
2262 register long
2263 x;
2264
2265 register PixelPacket
2266 *restrict q;
2267
2268 if (status == MagickFalse)
2269 continue;
cristy36826ab2010-03-06 01:29:30 +00002270 p=GetCacheViewVirtualPixels(image_view,-((long) kernel->width/2L),
2271 y-(long) (kernel->height/2L),image->columns+kernel->width,kernel->height,
2272 exception);
cristy56a9e512010-01-06 18:18:55 +00002273 q=GetCacheViewAuthenticPixels(filter_view,0,y,filter_image->columns,1,
2274 exception);
2275 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2276 {
2277 status=MagickFalse;
2278 continue;
2279 }
2280 indexes=GetCacheViewVirtualIndexQueue(image_view);
2281 filter_indexes=GetCacheViewAuthenticIndexQueue(filter_view);
2282 for (x=0; x < (long) image->columns; x++)
2283 {
2284 long
2285 v;
2286
2287 MagickPixelPacket
2288 pixel;
2289
2290 register const double
2291 *restrict k;
2292
2293 register const PixelPacket
2294 *restrict kernel_pixels;
2295
2296 register long
2297 u;
2298
2299 pixel=bias;
cristy36826ab2010-03-06 01:29:30 +00002300 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002301 kernel_pixels=p;
2302 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
2303 {
cristy36826ab2010-03-06 01:29:30 +00002304 for (v=0; v < (long) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002305 {
cristy36826ab2010-03-06 01:29:30 +00002306 for (u=0; u < (long) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002307 {
2308 pixel.red+=(*k)*kernel_pixels[u].red;
2309 pixel.green+=(*k)*kernel_pixels[u].green;
2310 pixel.blue+=(*k)*kernel_pixels[u].blue;
2311 k++;
2312 }
cristy36826ab2010-03-06 01:29:30 +00002313 kernel_pixels+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002314 }
2315 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002316 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002317 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002318 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002319 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002320 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002321 if ((channel & OpacityChannel) != 0)
2322 {
cristy36826ab2010-03-06 01:29:30 +00002323 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002324 kernel_pixels=p;
cristy36826ab2010-03-06 01:29:30 +00002325 for (v=0; v < (long) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002326 {
cristy36826ab2010-03-06 01:29:30 +00002327 for (u=0; u < (long) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002328 {
2329 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
2330 k++;
2331 }
cristy36826ab2010-03-06 01:29:30 +00002332 kernel_pixels+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002333 }
cristyce70c172010-01-07 17:15:30 +00002334 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002335 }
2336 if (((channel & IndexChannel) != 0) &&
2337 (image->colorspace == CMYKColorspace))
2338 {
2339 register const IndexPacket
2340 *restrict kernel_indexes;
2341
cristy36826ab2010-03-06 01:29:30 +00002342 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002343 kernel_indexes=indexes;
cristy36826ab2010-03-06 01:29:30 +00002344 for (v=0; v < (long) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002345 {
cristy36826ab2010-03-06 01:29:30 +00002346 for (u=0; u < (long) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002347 {
2348 pixel.index+=(*k)*kernel_indexes[u];
2349 k++;
2350 }
cristy36826ab2010-03-06 01:29:30 +00002351 kernel_indexes+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002352 }
cristyce70c172010-01-07 17:15:30 +00002353 filter_indexes[x]=ClampToQuantum(pixel.index);
cristy56a9e512010-01-06 18:18:55 +00002354 }
2355 }
2356 else
2357 {
2358 MagickRealType
2359 alpha,
2360 gamma;
2361
2362 gamma=0.0;
cristy36826ab2010-03-06 01:29:30 +00002363 for (v=0; v < (long) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002364 {
cristy36826ab2010-03-06 01:29:30 +00002365 for (u=0; u < (long) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002366 {
2367 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
2368 kernel_pixels[u].opacity));
2369 pixel.red+=(*k)*alpha*kernel_pixels[u].red;
2370 pixel.green+=(*k)*alpha*kernel_pixels[u].green;
2371 pixel.blue+=(*k)*alpha*kernel_pixels[u].blue;
2372 gamma+=(*k)*alpha;
2373 k++;
2374 }
cristy36826ab2010-03-06 01:29:30 +00002375 kernel_pixels+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002376 }
2377 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
2378 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002379 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002380 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002381 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002382 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002383 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002384 if ((channel & OpacityChannel) != 0)
2385 {
cristy36826ab2010-03-06 01:29:30 +00002386 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002387 kernel_pixels=p;
cristy36826ab2010-03-06 01:29:30 +00002388 for (v=0; v < (long) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002389 {
cristy36826ab2010-03-06 01:29:30 +00002390 for (u=0; u < (long) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002391 {
2392 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
2393 k++;
2394 }
cristy36826ab2010-03-06 01:29:30 +00002395 kernel_pixels+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002396 }
cristyce70c172010-01-07 17:15:30 +00002397 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002398 }
2399 if (((channel & IndexChannel) != 0) &&
2400 (image->colorspace == CMYKColorspace))
2401 {
2402 register const IndexPacket
2403 *restrict kernel_indexes;
2404
cristy36826ab2010-03-06 01:29:30 +00002405 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002406 kernel_pixels=p;
2407 kernel_indexes=indexes;
cristy36826ab2010-03-06 01:29:30 +00002408 for (v=0; v < (long) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002409 {
cristy36826ab2010-03-06 01:29:30 +00002410 for (u=0; u < (long) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002411 {
2412 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
2413 kernel_pixels[u].opacity));
2414 pixel.index+=(*k)*alpha*kernel_indexes[u];
2415 k++;
2416 }
cristy36826ab2010-03-06 01:29:30 +00002417 kernel_pixels+=image->columns+kernel->width;
2418 kernel_indexes+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002419 }
cristy2115aea2010-01-09 23:16:08 +00002420 filter_indexes[x]=ClampToQuantum(gamma*
2421 GetIndexPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002422 }
2423 }
2424 p++;
2425 q++;
2426 }
2427 sync=SyncCacheViewAuthenticPixels(filter_view,exception);
2428 if (sync == MagickFalse)
2429 status=MagickFalse;
2430 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2431 {
2432 MagickBooleanType
2433 proceed;
2434
2435#if defined(MAGICKCORE_OPENMP_SUPPORT)
2436 #pragma omp critical (MagickCore_FilterImageChannel)
2437#endif
2438 proceed=SetImageProgress(image,FilterImageTag,progress++,image->rows);
2439 if (proceed == MagickFalse)
2440 status=MagickFalse;
2441 }
2442 }
2443 filter_image->type=image->type;
2444 filter_view=DestroyCacheView(filter_view);
2445 image_view=DestroyCacheView(image_view);
cristy56a9e512010-01-06 18:18:55 +00002446 if (status == MagickFalse)
2447 filter_image=DestroyImage(filter_image);
2448 return(filter_image);
2449}
2450
2451/*
2452%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2453% %
2454% %
2455% %
cristy3ed852e2009-09-05 21:47:34 +00002456% G a u s s i a n B l u r I m a g e %
2457% %
2458% %
2459% %
2460%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2461%
2462% GaussianBlurImage() blurs an image. We convolve the image with a
2463% Gaussian operator of the given radius and standard deviation (sigma).
2464% For reasonable results, the radius should be larger than sigma. Use a
2465% radius of 0 and GaussianBlurImage() selects a suitable radius for you
2466%
2467% The format of the GaussianBlurImage method is:
2468%
2469% Image *GaussianBlurImage(const Image *image,onst double radius,
2470% const double sigma,ExceptionInfo *exception)
2471% Image *GaussianBlurImageChannel(const Image *image,
2472% const ChannelType channel,const double radius,const double sigma,
2473% ExceptionInfo *exception)
2474%
2475% A description of each parameter follows:
2476%
2477% o image: the image.
2478%
2479% o channel: the channel type.
2480%
2481% o radius: the radius of the Gaussian, in pixels, not counting the center
2482% pixel.
2483%
2484% o sigma: the standard deviation of the Gaussian, in pixels.
2485%
2486% o exception: return any errors or warnings in this structure.
2487%
2488*/
2489
2490MagickExport Image *GaussianBlurImage(const Image *image,const double radius,
2491 const double sigma,ExceptionInfo *exception)
2492{
2493 Image
2494 *blur_image;
2495
2496 blur_image=GaussianBlurImageChannel(image,DefaultChannels,radius,sigma,
2497 exception);
2498 return(blur_image);
2499}
2500
2501MagickExport Image *GaussianBlurImageChannel(const Image *image,
2502 const ChannelType channel,const double radius,const double sigma,
2503 ExceptionInfo *exception)
2504{
2505 double
2506 *kernel;
2507
2508 Image
2509 *blur_image;
2510
cristy47e00502009-12-17 19:19:57 +00002511 long
2512 j,
cristy3ed852e2009-09-05 21:47:34 +00002513 u,
2514 v;
2515
cristy47e00502009-12-17 19:19:57 +00002516 register long
2517 i;
2518
cristy3ed852e2009-09-05 21:47:34 +00002519 unsigned long
2520 width;
2521
2522 assert(image != (const Image *) NULL);
2523 assert(image->signature == MagickSignature);
2524 if (image->debug != MagickFalse)
2525 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2526 assert(exception != (ExceptionInfo *) NULL);
2527 assert(exception->signature == MagickSignature);
2528 width=GetOptimalKernelWidth2D(radius,sigma);
2529 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2530 if (kernel == (double *) NULL)
2531 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy47e00502009-12-17 19:19:57 +00002532 j=(long) width/2;
cristy3ed852e2009-09-05 21:47:34 +00002533 i=0;
cristy47e00502009-12-17 19:19:57 +00002534 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002535 {
cristy47e00502009-12-17 19:19:57 +00002536 for (u=(-j); u <= j; u++)
2537 kernel[i++]=exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
2538 (2.0*MagickPI*MagickSigma*MagickSigma);
cristy3ed852e2009-09-05 21:47:34 +00002539 }
2540 blur_image=ConvolveImageChannel(image,channel,width,kernel,exception);
2541 kernel=(double *) RelinquishMagickMemory(kernel);
2542 return(blur_image);
2543}
2544
2545/*
2546%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2547% %
2548% %
2549% %
2550% M e d i a n F i l t e r I m a g e %
2551% %
2552% %
2553% %
2554%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2555%
2556% MedianFilterImage() applies a digital filter that improves the quality
2557% of a noisy image. Each pixel is replaced by the median in a set of
2558% neighboring pixels as defined by radius.
2559%
2560% The algorithm was contributed by Mike Edmonds and implements an insertion
2561% sort for selecting median color-channel values. For more on this algorithm
2562% see "Skip Lists: A probabilistic Alternative to Balanced Trees" by William
2563% Pugh in the June 1990 of Communications of the ACM.
2564%
2565% The format of the MedianFilterImage method is:
2566%
2567% Image *MedianFilterImage(const Image *image,const double radius,
2568% ExceptionInfo *exception)
2569%
2570% A description of each parameter follows:
2571%
2572% o image: the image.
2573%
2574% o radius: the radius of the pixel neighborhood.
2575%
2576% o exception: return any errors or warnings in this structure.
2577%
2578*/
2579
2580#define MedianListChannels 5
2581
2582typedef struct _MedianListNode
2583{
2584 unsigned long
2585 next[9],
2586 count,
2587 signature;
2588} MedianListNode;
2589
2590typedef struct _MedianSkipList
2591{
2592 long
2593 level;
2594
2595 MedianListNode
2596 *nodes;
2597} MedianSkipList;
2598
2599typedef struct _MedianPixelList
2600{
2601 unsigned long
2602 center,
2603 seed,
2604 signature;
2605
2606 MedianSkipList
2607 lists[MedianListChannels];
2608} MedianPixelList;
2609
2610static MedianPixelList *DestroyMedianPixelList(MedianPixelList *pixel_list)
2611{
2612 register long
2613 i;
2614
2615 if (pixel_list == (MedianPixelList *) NULL)
2616 return((MedianPixelList *) NULL);
2617 for (i=0; i < MedianListChannels; i++)
2618 if (pixel_list->lists[i].nodes != (MedianListNode *) NULL)
2619 pixel_list->lists[i].nodes=(MedianListNode *) RelinquishMagickMemory(
2620 pixel_list->lists[i].nodes);
2621 pixel_list=(MedianPixelList *) RelinquishAlignedMemory(pixel_list);
2622 return(pixel_list);
2623}
2624
2625static MedianPixelList **DestroyMedianPixelListThreadSet(
2626 MedianPixelList **pixel_list)
2627{
2628 register long
2629 i;
2630
2631 assert(pixel_list != (MedianPixelList **) NULL);
2632 for (i=0; i < (long) GetOpenMPMaximumThreads(); i++)
2633 if (pixel_list[i] != (MedianPixelList *) NULL)
2634 pixel_list[i]=DestroyMedianPixelList(pixel_list[i]);
2635 pixel_list=(MedianPixelList **) RelinquishAlignedMemory(pixel_list);
2636 return(pixel_list);
2637}
2638
2639static MedianPixelList *AcquireMedianPixelList(const unsigned long width)
2640{
2641 MedianPixelList
2642 *pixel_list;
2643
2644 register long
2645 i;
2646
2647 pixel_list=(MedianPixelList *) AcquireAlignedMemory(1,sizeof(*pixel_list));
2648 if (pixel_list == (MedianPixelList *) NULL)
2649 return(pixel_list);
2650 (void) ResetMagickMemory((void *) pixel_list,0,sizeof(*pixel_list));
2651 pixel_list->center=width*width/2;
2652 for (i=0; i < MedianListChannels; i++)
2653 {
2654 pixel_list->lists[i].nodes=(MedianListNode *) AcquireQuantumMemory(65537UL,
2655 sizeof(*pixel_list->lists[i].nodes));
2656 if (pixel_list->lists[i].nodes == (MedianListNode *) NULL)
2657 return(DestroyMedianPixelList(pixel_list));
2658 (void) ResetMagickMemory(pixel_list->lists[i].nodes,0,65537UL*
2659 sizeof(*pixel_list->lists[i].nodes));
2660 }
2661 pixel_list->signature=MagickSignature;
2662 return(pixel_list);
2663}
2664
2665static MedianPixelList **AcquireMedianPixelListThreadSet(
2666 const unsigned long width)
2667{
2668 register long
2669 i;
2670
2671 MedianPixelList
2672 **pixel_list;
2673
2674 unsigned long
2675 number_threads;
2676
2677 number_threads=GetOpenMPMaximumThreads();
2678 pixel_list=(MedianPixelList **) AcquireAlignedMemory(number_threads,
2679 sizeof(*pixel_list));
2680 if (pixel_list == (MedianPixelList **) NULL)
2681 return((MedianPixelList **) NULL);
2682 (void) ResetMagickMemory(pixel_list,0,number_threads*sizeof(*pixel_list));
2683 for (i=0; i < (long) number_threads; i++)
2684 {
2685 pixel_list[i]=AcquireMedianPixelList(width);
2686 if (pixel_list[i] == (MedianPixelList *) NULL)
2687 return(DestroyMedianPixelListThreadSet(pixel_list));
2688 }
2689 return(pixel_list);
2690}
2691
2692static void AddNodeMedianPixelList(MedianPixelList *pixel_list,
2693 const long channel,const unsigned long color)
2694{
2695 register long
2696 level;
2697
2698 register MedianSkipList
2699 *list;
2700
2701 unsigned long
2702 search,
2703 update[9];
2704
2705 /*
2706 Initialize the node.
2707 */
2708 list=pixel_list->lists+channel;
2709 list->nodes[color].signature=pixel_list->signature;
2710 list->nodes[color].count=1;
2711 /*
2712 Determine where it belongs in the list.
2713 */
2714 search=65536UL;
2715 for (level=list->level; level >= 0; level--)
2716 {
2717 while (list->nodes[search].next[level] < color)
2718 search=list->nodes[search].next[level];
2719 update[level]=search;
2720 }
2721 /*
2722 Generate a pseudo-random level for this node.
2723 */
2724 for (level=0; ; level++)
2725 {
2726 pixel_list->seed=(pixel_list->seed*42893621L)+1L;
2727 if ((pixel_list->seed & 0x300) != 0x300)
2728 break;
2729 }
2730 if (level > 8)
2731 level=8;
2732 if (level > (list->level+2))
2733 level=list->level+2;
2734 /*
2735 If we're raising the list's level, link back to the root node.
2736 */
2737 while (level > list->level)
2738 {
2739 list->level++;
2740 update[list->level]=65536UL;
2741 }
2742 /*
2743 Link the node into the skip-list.
2744 */
2745 do
2746 {
2747 list->nodes[color].next[level]=list->nodes[update[level]].next[level];
2748 list->nodes[update[level]].next[level]=color;
2749 }
2750 while (level-- > 0);
2751}
2752
2753static MagickPixelPacket GetMedianPixelList(MedianPixelList *pixel_list)
2754{
2755 MagickPixelPacket
2756 pixel;
2757
2758 register long
2759 channel;
2760
2761 register MedianSkipList
2762 *list;
2763
2764 unsigned long
2765 center,
2766 color,
2767 count;
2768
2769 unsigned short
2770 channels[MedianListChannels];
2771
2772 /*
2773 Find the median value for each of the color.
2774 */
2775 center=pixel_list->center;
2776 for (channel=0; channel < 5; channel++)
2777 {
2778 list=pixel_list->lists+channel;
2779 color=65536UL;
2780 count=0;
2781 do
2782 {
2783 color=list->nodes[color].next[0];
2784 count+=list->nodes[color].count;
2785 }
2786 while (count <= center);
2787 channels[channel]=(unsigned short) color;
2788 }
2789 GetMagickPixelPacket((const Image *) NULL,&pixel);
2790 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
2791 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
2792 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
2793 pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
2794 pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]);
2795 return(pixel);
2796}
2797
2798static inline void InsertMedianPixelList(const Image *image,
2799 const PixelPacket *pixel,const IndexPacket *indexes,
2800 MedianPixelList *pixel_list)
2801{
2802 unsigned long
2803 signature;
2804
2805 unsigned short
2806 index;
2807
2808 index=ScaleQuantumToShort(pixel->red);
2809 signature=pixel_list->lists[0].nodes[index].signature;
2810 if (signature == pixel_list->signature)
2811 pixel_list->lists[0].nodes[index].count++;
2812 else
2813 AddNodeMedianPixelList(pixel_list,0,index);
2814 index=ScaleQuantumToShort(pixel->green);
2815 signature=pixel_list->lists[1].nodes[index].signature;
2816 if (signature == pixel_list->signature)
2817 pixel_list->lists[1].nodes[index].count++;
2818 else
2819 AddNodeMedianPixelList(pixel_list,1,index);
2820 index=ScaleQuantumToShort(pixel->blue);
2821 signature=pixel_list->lists[2].nodes[index].signature;
2822 if (signature == pixel_list->signature)
2823 pixel_list->lists[2].nodes[index].count++;
2824 else
2825 AddNodeMedianPixelList(pixel_list,2,index);
2826 index=ScaleQuantumToShort(pixel->opacity);
2827 signature=pixel_list->lists[3].nodes[index].signature;
2828 if (signature == pixel_list->signature)
2829 pixel_list->lists[3].nodes[index].count++;
2830 else
2831 AddNodeMedianPixelList(pixel_list,3,index);
2832 if (image->colorspace == CMYKColorspace)
2833 index=ScaleQuantumToShort(*indexes);
2834 signature=pixel_list->lists[4].nodes[index].signature;
2835 if (signature == pixel_list->signature)
2836 pixel_list->lists[4].nodes[index].count++;
2837 else
2838 AddNodeMedianPixelList(pixel_list,4,index);
2839}
2840
2841static void ResetMedianPixelList(MedianPixelList *pixel_list)
2842{
2843 int
2844 level;
2845
2846 register long
2847 channel;
2848
2849 register MedianListNode
2850 *root;
2851
2852 register MedianSkipList
2853 *list;
2854
2855 /*
2856 Reset the skip-list.
2857 */
2858 for (channel=0; channel < 5; channel++)
2859 {
2860 list=pixel_list->lists+channel;
2861 root=list->nodes+65536UL;
2862 list->level=0;
2863 for (level=0; level < 9; level++)
2864 root->next[level]=65536UL;
2865 }
2866 pixel_list->seed=pixel_list->signature++;
2867}
2868
2869MagickExport Image *MedianFilterImage(const Image *image,const double radius,
2870 ExceptionInfo *exception)
2871{
2872#define MedianFilterImageTag "MedianFilter/Image"
2873
cristyc4c8d132010-01-07 01:58:38 +00002874 CacheView
2875 *image_view,
2876 *median_view;
2877
cristy3ed852e2009-09-05 21:47:34 +00002878 Image
2879 *median_image;
2880
2881 long
2882 progress,
2883 y;
2884
2885 MagickBooleanType
2886 status;
2887
2888 MedianPixelList
cristyfa112112010-01-04 17:48:07 +00002889 **restrict pixel_list;
cristy3ed852e2009-09-05 21:47:34 +00002890
2891 unsigned long
2892 width;
2893
cristy3ed852e2009-09-05 21:47:34 +00002894 /*
2895 Initialize median image attributes.
2896 */
2897 assert(image != (Image *) NULL);
2898 assert(image->signature == MagickSignature);
2899 if (image->debug != MagickFalse)
2900 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2901 assert(exception != (ExceptionInfo *) NULL);
2902 assert(exception->signature == MagickSignature);
2903 width=GetOptimalKernelWidth2D(radius,0.5);
2904 if ((image->columns < width) || (image->rows < width))
2905 ThrowImageException(OptionError,"ImageSmallerThanKernelRadius");
2906 median_image=CloneImage(image,image->columns,image->rows,MagickTrue,
2907 exception);
2908 if (median_image == (Image *) NULL)
2909 return((Image *) NULL);
2910 if (SetImageStorageClass(median_image,DirectClass) == MagickFalse)
2911 {
2912 InheritException(exception,&median_image->exception);
2913 median_image=DestroyImage(median_image);
2914 return((Image *) NULL);
2915 }
2916 pixel_list=AcquireMedianPixelListThreadSet(width);
2917 if (pixel_list == (MedianPixelList **) NULL)
2918 {
2919 median_image=DestroyImage(median_image);
2920 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2921 }
2922 /*
2923 Median filter each image row.
2924 */
2925 status=MagickTrue;
2926 progress=0;
2927 image_view=AcquireCacheView(image);
2928 median_view=AcquireCacheView(median_image);
cristyb5d5f722009-11-04 03:03:49 +00002929#if defined(MAGICKCORE_OPENMP_SUPPORT)
2930 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002931#endif
2932 for (y=0; y < (long) median_image->rows; y++)
2933 {
2934 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002935 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002936
2937 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002938 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002939
2940 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002941 *restrict median_indexes;
cristy3ed852e2009-09-05 21:47:34 +00002942
2943 register long
2944 id,
2945 x;
2946
2947 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002948 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002949
2950 if (status == MagickFalse)
2951 continue;
2952 p=GetCacheViewVirtualPixels(image_view,-((long) width/2L),y-(long) (width/
2953 2L),image->columns+width,width,exception);
2954 q=QueueCacheViewAuthenticPixels(median_view,0,y,median_image->columns,1,
2955 exception);
2956 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2957 {
2958 status=MagickFalse;
2959 continue;
2960 }
2961 indexes=GetCacheViewVirtualIndexQueue(image_view);
2962 median_indexes=GetCacheViewAuthenticIndexQueue(median_view);
2963 id=GetOpenMPThreadId();
2964 for (x=0; x < (long) median_image->columns; x++)
2965 {
2966 MagickPixelPacket
2967 pixel;
2968
2969 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002970 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +00002971
2972 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002973 *restrict s;
cristy3ed852e2009-09-05 21:47:34 +00002974
2975 register long
2976 u,
2977 v;
2978
2979 r=p;
2980 s=indexes+x;
2981 ResetMedianPixelList(pixel_list[id]);
2982 for (v=0; v < (long) width; v++)
2983 {
2984 for (u=0; u < (long) width; u++)
2985 InsertMedianPixelList(image,r+u,s+u,pixel_list[id]);
2986 r+=image->columns+width;
2987 s+=image->columns+width;
2988 }
2989 pixel=GetMedianPixelList(pixel_list[id]);
2990 SetPixelPacket(median_image,&pixel,q,median_indexes+x);
2991 p++;
2992 q++;
2993 }
2994 if (SyncCacheViewAuthenticPixels(median_view,exception) == MagickFalse)
2995 status=MagickFalse;
2996 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2997 {
2998 MagickBooleanType
2999 proceed;
3000
cristyb5d5f722009-11-04 03:03:49 +00003001#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003002 #pragma omp critical (MagickCore_MedianFilterImage)
3003#endif
3004 proceed=SetImageProgress(image,MedianFilterImageTag,progress++,
3005 image->rows);
3006 if (proceed == MagickFalse)
3007 status=MagickFalse;
3008 }
3009 }
3010 median_view=DestroyCacheView(median_view);
3011 image_view=DestroyCacheView(image_view);
3012 pixel_list=DestroyMedianPixelListThreadSet(pixel_list);
3013 return(median_image);
3014}
3015
3016/*
3017%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3018% %
3019% %
3020% %
3021% M o t i o n B l u r I m a g e %
3022% %
3023% %
3024% %
3025%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3026%
3027% MotionBlurImage() simulates motion blur. We convolve the image with a
3028% Gaussian operator of the given radius and standard deviation (sigma).
3029% For reasonable results, radius should be larger than sigma. Use a
3030% radius of 0 and MotionBlurImage() selects a suitable radius for you.
3031% Angle gives the angle of the blurring motion.
3032%
3033% Andrew Protano contributed this effect.
3034%
3035% The format of the MotionBlurImage method is:
3036%
3037% Image *MotionBlurImage(const Image *image,const double radius,
3038% const double sigma,const double angle,ExceptionInfo *exception)
3039% Image *MotionBlurImageChannel(const Image *image,const ChannelType channel,
3040% const double radius,const double sigma,const double angle,
3041% ExceptionInfo *exception)
3042%
3043% A description of each parameter follows:
3044%
3045% o image: the image.
3046%
3047% o channel: the channel type.
3048%
3049% o radius: the radius of the Gaussian, in pixels, not counting the center
3050% o radius: the radius of the Gaussian, in pixels, not counting
3051% the center pixel.
3052%
3053% o sigma: the standard deviation of the Gaussian, in pixels.
3054%
3055% o angle: Apply the effect along this angle.
3056%
3057% o exception: return any errors or warnings in this structure.
3058%
3059*/
3060
cristy47e00502009-12-17 19:19:57 +00003061static double *GetMotionBlurKernel(const unsigned long width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +00003062{
cristy3ed852e2009-09-05 21:47:34 +00003063 double
cristy47e00502009-12-17 19:19:57 +00003064 *kernel,
cristy3ed852e2009-09-05 21:47:34 +00003065 normalize;
3066
3067 register long
3068 i;
3069
3070 /*
cristy47e00502009-12-17 19:19:57 +00003071 Generate a 1-D convolution kernel.
cristy3ed852e2009-09-05 21:47:34 +00003072 */
3073 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
3074 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
3075 if (kernel == (double *) NULL)
3076 return(kernel);
cristy3ed852e2009-09-05 21:47:34 +00003077 normalize=0.0;
3078 for (i=0; i < (long) width; i++)
cristy47e00502009-12-17 19:19:57 +00003079 {
3080 kernel[i]=exp((-((double) i*i)/(double) (2.0*MagickSigma*MagickSigma)))/
3081 (MagickSQ2PI*MagickSigma);
cristy3ed852e2009-09-05 21:47:34 +00003082 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +00003083 }
cristy3ed852e2009-09-05 21:47:34 +00003084 for (i=0; i < (long) width; i++)
3085 kernel[i]/=normalize;
3086 return(kernel);
3087}
3088
3089MagickExport Image *MotionBlurImage(const Image *image,const double radius,
3090 const double sigma,const double angle,ExceptionInfo *exception)
3091{
3092 Image
3093 *motion_blur;
3094
3095 motion_blur=MotionBlurImageChannel(image,DefaultChannels,radius,sigma,angle,
3096 exception);
3097 return(motion_blur);
3098}
3099
3100MagickExport Image *MotionBlurImageChannel(const Image *image,
3101 const ChannelType channel,const double radius,const double sigma,
3102 const double angle,ExceptionInfo *exception)
3103{
cristyc4c8d132010-01-07 01:58:38 +00003104 CacheView
3105 *blur_view,
3106 *image_view;
3107
cristy3ed852e2009-09-05 21:47:34 +00003108 double
3109 *kernel;
3110
3111 Image
3112 *blur_image;
3113
3114 long
3115 progress,
3116 y;
3117
3118 MagickBooleanType
3119 status;
3120
3121 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00003122 bias;
cristy3ed852e2009-09-05 21:47:34 +00003123
3124 OffsetInfo
3125 *offset;
3126
3127 PointInfo
3128 point;
3129
3130 register long
3131 i;
3132
3133 unsigned long
3134 width;
3135
cristy3ed852e2009-09-05 21:47:34 +00003136 assert(image != (Image *) NULL);
3137 assert(image->signature == MagickSignature);
3138 if (image->debug != MagickFalse)
3139 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3140 assert(exception != (ExceptionInfo *) NULL);
3141 width=GetOptimalKernelWidth1D(radius,sigma);
3142 kernel=GetMotionBlurKernel(width,sigma);
3143 if (kernel == (double *) NULL)
3144 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3145 offset=(OffsetInfo *) AcquireQuantumMemory(width,sizeof(*offset));
3146 if (offset == (OffsetInfo *) NULL)
3147 {
3148 kernel=(double *) RelinquishMagickMemory(kernel);
3149 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3150 }
3151 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3152 if (blur_image == (Image *) NULL)
3153 {
3154 kernel=(double *) RelinquishMagickMemory(kernel);
3155 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
3156 return((Image *) NULL);
3157 }
3158 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3159 {
3160 kernel=(double *) RelinquishMagickMemory(kernel);
3161 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
3162 InheritException(exception,&blur_image->exception);
3163 blur_image=DestroyImage(blur_image);
3164 return((Image *) NULL);
3165 }
3166 point.x=(double) width*sin(DegreesToRadians(angle));
3167 point.y=(double) width*cos(DegreesToRadians(angle));
3168 for (i=0; i < (long) width; i++)
3169 {
3170 offset[i].x=(long) ((i*point.y)/hypot(point.x,point.y)+0.5);
3171 offset[i].y=(long) ((i*point.x)/hypot(point.x,point.y)+0.5);
3172 }
3173 /*
3174 Motion blur image.
3175 */
3176 status=MagickTrue;
3177 progress=0;
cristyddd82202009-11-03 20:14:50 +00003178 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003179 image_view=AcquireCacheView(image);
3180 blur_view=AcquireCacheView(blur_image);
cristy8a7ea362010-03-10 20:31:43 +00003181#if defined(MAGICKCORE_OPENMP_SUPPORT) && (_OPENMP > 202001)
3182 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003183#endif
3184 for (y=0; y < (long) image->rows; y++)
3185 {
3186 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003187 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00003188
3189 register long
3190 x;
3191
3192 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003193 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003194
3195 if (status == MagickFalse)
3196 continue;
3197 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3198 exception);
3199 if (q == (PixelPacket *) NULL)
3200 {
3201 status=MagickFalse;
3202 continue;
3203 }
3204 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
3205 for (x=0; x < (long) image->columns; x++)
3206 {
3207 MagickPixelPacket
3208 qixel;
3209
3210 PixelPacket
3211 pixel;
3212
3213 register double
cristyc47d1f82009-11-26 01:44:43 +00003214 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00003215
3216 register long
3217 i;
3218
3219 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003220 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003221
3222 k=kernel;
cristyddd82202009-11-03 20:14:50 +00003223 qixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00003224 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
3225 {
3226 for (i=0; i < (long) width; i++)
3227 {
3228 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
3229 offset[i].y,&pixel,exception);
3230 qixel.red+=(*k)*pixel.red;
3231 qixel.green+=(*k)*pixel.green;
3232 qixel.blue+=(*k)*pixel.blue;
3233 qixel.opacity+=(*k)*pixel.opacity;
3234 if (image->colorspace == CMYKColorspace)
3235 {
3236 indexes=GetCacheViewVirtualIndexQueue(image_view);
3237 qixel.index+=(*k)*(*indexes);
3238 }
3239 k++;
3240 }
3241 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003242 q->red=ClampToQuantum(qixel.red);
cristy3ed852e2009-09-05 21:47:34 +00003243 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003244 q->green=ClampToQuantum(qixel.green);
cristy3ed852e2009-09-05 21:47:34 +00003245 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003246 q->blue=ClampToQuantum(qixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00003247 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003248 q->opacity=ClampToQuantum(qixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00003249 if (((channel & IndexChannel) != 0) &&
3250 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00003251 blur_indexes[x]=(IndexPacket) ClampToQuantum(qixel.index);
cristy3ed852e2009-09-05 21:47:34 +00003252 }
3253 else
3254 {
3255 MagickRealType
3256 alpha,
3257 gamma;
3258
3259 alpha=0.0;
3260 gamma=0.0;
3261 for (i=0; i < (long) width; i++)
3262 {
3263 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
3264 offset[i].y,&pixel,exception);
cristy8a7ea362010-03-10 20:31:43 +00003265 alpha=(MagickRealType) (QuantumScale*
3266 GetAlphaPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00003267 qixel.red+=(*k)*alpha*pixel.red;
3268 qixel.green+=(*k)*alpha*pixel.green;
3269 qixel.blue+=(*k)*alpha*pixel.blue;
3270 qixel.opacity+=(*k)*pixel.opacity;
3271 if (image->colorspace == CMYKColorspace)
3272 {
3273 indexes=GetCacheViewVirtualIndexQueue(image_view);
3274 qixel.index+=(*k)*alpha*(*indexes);
3275 }
3276 gamma+=(*k)*alpha;
3277 k++;
3278 }
3279 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3280 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003281 q->red=ClampToQuantum(gamma*qixel.red);
cristy3ed852e2009-09-05 21:47:34 +00003282 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003283 q->green=ClampToQuantum(gamma*qixel.green);
cristy3ed852e2009-09-05 21:47:34 +00003284 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003285 q->blue=ClampToQuantum(gamma*qixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00003286 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003287 q->opacity=ClampToQuantum(qixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00003288 if (((channel & IndexChannel) != 0) &&
3289 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00003290 blur_indexes[x]=(IndexPacket) ClampToQuantum(gamma*qixel.index);
cristy3ed852e2009-09-05 21:47:34 +00003291 }
3292 q++;
3293 }
3294 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
3295 status=MagickFalse;
3296 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3297 {
3298 MagickBooleanType
3299 proceed;
3300
cristy8a7ea362010-03-10 20:31:43 +00003301#if defined(MAGICKCORE_OPENMP_SUPPORT) && (_OPENMP > 202001)
cristy3ed852e2009-09-05 21:47:34 +00003302 #pragma omp critical (MagickCore_MotionBlurImageChannel)
3303#endif
3304 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
3305 if (proceed == MagickFalse)
3306 status=MagickFalse;
3307 }
3308 }
3309 blur_view=DestroyCacheView(blur_view);
3310 image_view=DestroyCacheView(image_view);
3311 kernel=(double *) RelinquishMagickMemory(kernel);
3312 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
3313 if (status == MagickFalse)
3314 blur_image=DestroyImage(blur_image);
3315 return(blur_image);
3316}
3317
3318/*
3319%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3320% %
3321% %
3322% %
3323% P r e v i e w I m a g e %
3324% %
3325% %
3326% %
3327%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3328%
3329% PreviewImage() tiles 9 thumbnails of the specified image with an image
3330% processing operation applied with varying parameters. This may be helpful
3331% pin-pointing an appropriate parameter for a particular image processing
3332% operation.
3333%
3334% The format of the PreviewImages method is:
3335%
3336% Image *PreviewImages(const Image *image,const PreviewType preview,
3337% ExceptionInfo *exception)
3338%
3339% A description of each parameter follows:
3340%
3341% o image: the image.
3342%
3343% o preview: the image processing operation.
3344%
3345% o exception: return any errors or warnings in this structure.
3346%
3347*/
3348MagickExport Image *PreviewImage(const Image *image,const PreviewType preview,
3349 ExceptionInfo *exception)
3350{
3351#define NumberTiles 9
3352#define PreviewImageTag "Preview/Image"
3353#define DefaultPreviewGeometry "204x204+10+10"
3354
3355 char
3356 factor[MaxTextExtent],
3357 label[MaxTextExtent];
3358
3359 double
3360 degrees,
3361 gamma,
3362 percentage,
3363 radius,
3364 sigma,
3365 threshold;
3366
3367 Image
3368 *images,
3369 *montage_image,
3370 *preview_image,
3371 *thumbnail;
3372
3373 ImageInfo
3374 *preview_info;
3375
3376 long
3377 y;
3378
3379 MagickBooleanType
3380 proceed;
3381
3382 MontageInfo
3383 *montage_info;
3384
3385 QuantizeInfo
3386 quantize_info;
3387
3388 RectangleInfo
3389 geometry;
3390
3391 register long
3392 i,
3393 x;
3394
3395 unsigned long
3396 colors;
3397
3398 /*
3399 Open output image file.
3400 */
3401 assert(image != (Image *) NULL);
3402 assert(image->signature == MagickSignature);
3403 if (image->debug != MagickFalse)
3404 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3405 colors=2;
3406 degrees=0.0;
3407 gamma=(-0.2f);
3408 preview_info=AcquireImageInfo();
3409 SetGeometry(image,&geometry);
3410 (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y,
3411 &geometry.width,&geometry.height);
3412 images=NewImageList();
3413 percentage=12.5;
3414 GetQuantizeInfo(&quantize_info);
3415 radius=0.0;
3416 sigma=1.0;
3417 threshold=0.0;
3418 x=0;
3419 y=0;
3420 for (i=0; i < NumberTiles; i++)
3421 {
3422 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
3423 if (thumbnail == (Image *) NULL)
3424 break;
3425 (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL,
3426 (void *) NULL);
3427 (void) SetImageProperty(thumbnail,"label",DefaultTileLabel);
3428 if (i == (NumberTiles/2))
3429 {
3430 (void) QueryColorDatabase("#dfdfdf",&thumbnail->matte_color,exception);
3431 AppendImageToList(&images,thumbnail);
3432 continue;
3433 }
3434 switch (preview)
3435 {
3436 case RotatePreview:
3437 {
3438 degrees+=45.0;
3439 preview_image=RotateImage(thumbnail,degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003440 (void) FormatMagickString(label,MaxTextExtent,"rotate %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003441 break;
3442 }
3443 case ShearPreview:
3444 {
3445 degrees+=5.0;
3446 preview_image=ShearImage(thumbnail,degrees,degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003447 (void) FormatMagickString(label,MaxTextExtent,"shear %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00003448 degrees,2.0*degrees);
3449 break;
3450 }
3451 case RollPreview:
3452 {
3453 x=(long) ((i+1)*thumbnail->columns)/NumberTiles;
3454 y=(long) ((i+1)*thumbnail->rows)/NumberTiles;
3455 preview_image=RollImage(thumbnail,x,y,exception);
3456 (void) FormatMagickString(label,MaxTextExtent,"roll %ldx%ld",x,y);
3457 break;
3458 }
3459 case HuePreview:
3460 {
3461 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3462 if (preview_image == (Image *) NULL)
3463 break;
cristye7f51092010-01-17 00:39:37 +00003464 (void) FormatMagickString(factor,MaxTextExtent,"100,100,%g",
cristy3ed852e2009-09-05 21:47:34 +00003465 2.0*percentage);
3466 (void) ModulateImage(preview_image,factor);
3467 (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor);
3468 break;
3469 }
3470 case SaturationPreview:
3471 {
3472 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3473 if (preview_image == (Image *) NULL)
3474 break;
cristye7f51092010-01-17 00:39:37 +00003475 (void) FormatMagickString(factor,MaxTextExtent,"100,%g",
cristy8cd5b312010-01-07 01:10:24 +00003476 2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00003477 (void) ModulateImage(preview_image,factor);
3478 (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor);
3479 break;
3480 }
3481 case BrightnessPreview:
3482 {
3483 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3484 if (preview_image == (Image *) NULL)
3485 break;
cristye7f51092010-01-17 00:39:37 +00003486 (void) FormatMagickString(factor,MaxTextExtent,"%g",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 GammaPreview:
3492 default:
3493 {
3494 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3495 if (preview_image == (Image *) NULL)
3496 break;
3497 gamma+=0.4f;
3498 (void) GammaImageChannel(preview_image,DefaultChannels,gamma);
cristye7f51092010-01-17 00:39:37 +00003499 (void) FormatMagickString(label,MaxTextExtent,"gamma %g",gamma);
cristy3ed852e2009-09-05 21:47:34 +00003500 break;
3501 }
3502 case SpiffPreview:
3503 {
3504 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3505 if (preview_image != (Image *) NULL)
3506 for (x=0; x < i; x++)
3507 (void) ContrastImage(preview_image,MagickTrue);
3508 (void) FormatMagickString(label,MaxTextExtent,"contrast (%ld)",i+1);
3509 break;
3510 }
3511 case DullPreview:
3512 {
3513 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3514 if (preview_image == (Image *) NULL)
3515 break;
3516 for (x=0; x < i; x++)
3517 (void) ContrastImage(preview_image,MagickFalse);
3518 (void) FormatMagickString(label,MaxTextExtent,"+contrast (%ld)",i+1);
3519 break;
3520 }
3521 case GrayscalePreview:
3522 {
3523 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3524 if (preview_image == (Image *) NULL)
3525 break;
3526 colors<<=1;
3527 quantize_info.number_colors=colors;
3528 quantize_info.colorspace=GRAYColorspace;
3529 (void) QuantizeImage(&quantize_info,preview_image);
3530 (void) FormatMagickString(label,MaxTextExtent,
3531 "-colorspace gray -colors %ld",colors);
3532 break;
3533 }
3534 case QuantizePreview:
3535 {
3536 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3537 if (preview_image == (Image *) NULL)
3538 break;
3539 colors<<=1;
3540 quantize_info.number_colors=colors;
3541 (void) QuantizeImage(&quantize_info,preview_image);
3542 (void) FormatMagickString(label,MaxTextExtent,"colors %ld",colors);
3543 break;
3544 }
3545 case DespecklePreview:
3546 {
3547 for (x=0; x < (i-1); x++)
3548 {
3549 preview_image=DespeckleImage(thumbnail,exception);
3550 if (preview_image == (Image *) NULL)
3551 break;
3552 thumbnail=DestroyImage(thumbnail);
3553 thumbnail=preview_image;
3554 }
3555 preview_image=DespeckleImage(thumbnail,exception);
3556 if (preview_image == (Image *) NULL)
3557 break;
3558 (void) FormatMagickString(label,MaxTextExtent,"despeckle (%ld)",i+1);
3559 break;
3560 }
3561 case ReduceNoisePreview:
3562 {
3563 preview_image=ReduceNoiseImage(thumbnail,radius,exception);
cristye7f51092010-01-17 00:39:37 +00003564 (void) FormatMagickString(label,MaxTextExtent,"noise %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003565 break;
3566 }
3567 case AddNoisePreview:
3568 {
3569 switch ((int) i)
3570 {
3571 case 0:
3572 {
3573 (void) CopyMagickString(factor,"uniform",MaxTextExtent);
3574 break;
3575 }
3576 case 1:
3577 {
3578 (void) CopyMagickString(factor,"gaussian",MaxTextExtent);
3579 break;
3580 }
3581 case 2:
3582 {
3583 (void) CopyMagickString(factor,"multiplicative",MaxTextExtent);
3584 break;
3585 }
3586 case 3:
3587 {
3588 (void) CopyMagickString(factor,"impulse",MaxTextExtent);
3589 break;
3590 }
3591 case 4:
3592 {
3593 (void) CopyMagickString(factor,"laplacian",MaxTextExtent);
3594 break;
3595 }
3596 case 5:
3597 {
3598 (void) CopyMagickString(factor,"Poisson",MaxTextExtent);
3599 break;
3600 }
3601 default:
3602 {
3603 (void) CopyMagickString(thumbnail->magick,"NULL",MaxTextExtent);
3604 break;
3605 }
3606 }
3607 preview_image=ReduceNoiseImage(thumbnail,(double) i,exception);
3608 (void) FormatMagickString(label,MaxTextExtent,"+noise %s",factor);
3609 break;
3610 }
3611 case SharpenPreview:
3612 {
3613 preview_image=SharpenImage(thumbnail,radius,sigma,exception);
cristye7f51092010-01-17 00:39:37 +00003614 (void) FormatMagickString(label,MaxTextExtent,"sharpen %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003615 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00003616 break;
3617 }
3618 case BlurPreview:
3619 {
3620 preview_image=BlurImage(thumbnail,radius,sigma,exception);
cristye7f51092010-01-17 00:39:37 +00003621 (void) FormatMagickString(label,MaxTextExtent,"blur %gx%g",radius,
cristy3ed852e2009-09-05 21:47:34 +00003622 sigma);
3623 break;
3624 }
3625 case ThresholdPreview:
3626 {
3627 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3628 if (preview_image == (Image *) NULL)
3629 break;
3630 (void) BilevelImage(thumbnail,
3631 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
cristye7f51092010-01-17 00:39:37 +00003632 (void) FormatMagickString(label,MaxTextExtent,"threshold %g",
cristy3ed852e2009-09-05 21:47:34 +00003633 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
3634 break;
3635 }
3636 case EdgeDetectPreview:
3637 {
3638 preview_image=EdgeImage(thumbnail,radius,exception);
cristye7f51092010-01-17 00:39:37 +00003639 (void) FormatMagickString(label,MaxTextExtent,"edge %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003640 break;
3641 }
3642 case SpreadPreview:
3643 {
3644 preview_image=SpreadImage(thumbnail,radius,exception);
cristye7f51092010-01-17 00:39:37 +00003645 (void) FormatMagickString(label,MaxTextExtent,"spread %g",
cristy8cd5b312010-01-07 01:10:24 +00003646 radius+0.5);
cristy3ed852e2009-09-05 21:47:34 +00003647 break;
3648 }
3649 case SolarizePreview:
3650 {
3651 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3652 if (preview_image == (Image *) NULL)
3653 break;
3654 (void) SolarizeImage(preview_image,(double) QuantumRange*
3655 percentage/100.0);
cristye7f51092010-01-17 00:39:37 +00003656 (void) FormatMagickString(label,MaxTextExtent,"solarize %g",
cristy3ed852e2009-09-05 21:47:34 +00003657 (QuantumRange*percentage)/100.0);
3658 break;
3659 }
3660 case ShadePreview:
3661 {
3662 degrees+=10.0;
3663 preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees,
3664 exception);
cristye7f51092010-01-17 00:39:37 +00003665 (void) FormatMagickString(label,MaxTextExtent,"shade %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003666 degrees,degrees);
cristy3ed852e2009-09-05 21:47:34 +00003667 break;
3668 }
3669 case RaisePreview:
3670 {
3671 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3672 if (preview_image == (Image *) NULL)
3673 break;
3674 geometry.width=(unsigned long) (2*i+2);
3675 geometry.height=(unsigned long) (2*i+2);
3676 geometry.x=i/2;
3677 geometry.y=i/2;
3678 (void) RaiseImage(preview_image,&geometry,MagickTrue);
3679 (void) FormatMagickString(label,MaxTextExtent,"raise %lux%lu%+ld%+ld",
3680 geometry.width,geometry.height,geometry.x,geometry.y);
3681 break;
3682 }
3683 case SegmentPreview:
3684 {
3685 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3686 if (preview_image == (Image *) NULL)
3687 break;
3688 threshold+=0.4f;
3689 (void) SegmentImage(preview_image,RGBColorspace,MagickFalse,threshold,
3690 threshold);
cristye7f51092010-01-17 00:39:37 +00003691 (void) FormatMagickString(label,MaxTextExtent,"segment %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00003692 threshold,threshold);
3693 break;
3694 }
3695 case SwirlPreview:
3696 {
3697 preview_image=SwirlImage(thumbnail,degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003698 (void) FormatMagickString(label,MaxTextExtent,"swirl %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003699 degrees+=45.0;
3700 break;
3701 }
3702 case ImplodePreview:
3703 {
3704 degrees+=0.1f;
3705 preview_image=ImplodeImage(thumbnail,degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003706 (void) FormatMagickString(label,MaxTextExtent,"implode %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003707 break;
3708 }
3709 case WavePreview:
3710 {
3711 degrees+=5.0f;
3712 preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003713 (void) FormatMagickString(label,MaxTextExtent,"wave %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003714 0.5*degrees,2.0*degrees);
cristy3ed852e2009-09-05 21:47:34 +00003715 break;
3716 }
3717 case OilPaintPreview:
3718 {
3719 preview_image=OilPaintImage(thumbnail,(double) radius,exception);
cristye7f51092010-01-17 00:39:37 +00003720 (void) FormatMagickString(label,MaxTextExtent,"paint %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003721 break;
3722 }
3723 case CharcoalDrawingPreview:
3724 {
3725 preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma,
3726 exception);
cristye7f51092010-01-17 00:39:37 +00003727 (void) FormatMagickString(label,MaxTextExtent,"charcoal %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003728 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00003729 break;
3730 }
3731 case JPEGPreview:
3732 {
3733 char
3734 filename[MaxTextExtent];
3735
3736 int
3737 file;
3738
3739 MagickBooleanType
3740 status;
3741
3742 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3743 if (preview_image == (Image *) NULL)
3744 break;
3745 preview_info->quality=(unsigned long) percentage;
3746 (void) FormatMagickString(factor,MaxTextExtent,"%lu",
3747 preview_info->quality);
3748 file=AcquireUniqueFileResource(filename);
3749 if (file != -1)
3750 file=close(file)-1;
3751 (void) FormatMagickString(preview_image->filename,MaxTextExtent,
3752 "jpeg:%s",filename);
3753 status=WriteImage(preview_info,preview_image);
3754 if (status != MagickFalse)
3755 {
3756 Image
3757 *quality_image;
3758
3759 (void) CopyMagickString(preview_info->filename,
3760 preview_image->filename,MaxTextExtent);
3761 quality_image=ReadImage(preview_info,exception);
3762 if (quality_image != (Image *) NULL)
3763 {
3764 preview_image=DestroyImage(preview_image);
3765 preview_image=quality_image;
3766 }
3767 }
3768 (void) RelinquishUniqueFileResource(preview_image->filename);
3769 if ((GetBlobSize(preview_image)/1024) >= 1024)
cristye7f51092010-01-17 00:39:37 +00003770 (void) FormatMagickString(label,MaxTextExtent,"quality %s\n%gmb ",
cristy3ed852e2009-09-05 21:47:34 +00003771 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
3772 1024.0/1024.0);
3773 else
3774 if (GetBlobSize(preview_image) >= 1024)
cristy8cd5b312010-01-07 01:10:24 +00003775 (void) FormatMagickString(label,MaxTextExtent,
cristye7f51092010-01-17 00:39:37 +00003776 "quality %s\n%gkb ",factor,(double) ((MagickOffsetType)
cristy8cd5b312010-01-07 01:10:24 +00003777 GetBlobSize(preview_image))/1024.0);
cristy3ed852e2009-09-05 21:47:34 +00003778 else
3779 (void) FormatMagickString(label,MaxTextExtent,"quality %s\n%lub ",
3780 factor,(unsigned long) GetBlobSize(thumbnail));
3781 break;
3782 }
3783 }
3784 thumbnail=DestroyImage(thumbnail);
3785 percentage+=12.5;
3786 radius+=0.5;
3787 sigma+=0.25;
3788 if (preview_image == (Image *) NULL)
3789 break;
3790 (void) DeleteImageProperty(preview_image,"label");
3791 (void) SetImageProperty(preview_image,"label",label);
3792 AppendImageToList(&images,preview_image);
3793 proceed=SetImageProgress(image,PreviewImageTag,i,NumberTiles);
3794 if (proceed == MagickFalse)
3795 break;
3796 }
3797 if (images == (Image *) NULL)
3798 {
3799 preview_info=DestroyImageInfo(preview_info);
3800 return((Image *) NULL);
3801 }
3802 /*
3803 Create the montage.
3804 */
3805 montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL);
3806 (void) CopyMagickString(montage_info->filename,image->filename,MaxTextExtent);
3807 montage_info->shadow=MagickTrue;
3808 (void) CloneString(&montage_info->tile,"3x3");
3809 (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry);
3810 (void) CloneString(&montage_info->frame,DefaultTileFrame);
3811 montage_image=MontageImages(images,montage_info,exception);
3812 montage_info=DestroyMontageInfo(montage_info);
3813 images=DestroyImageList(images);
3814 if (montage_image == (Image *) NULL)
3815 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3816 if (montage_image->montage != (char *) NULL)
3817 {
3818 /*
3819 Free image directory.
3820 */
3821 montage_image->montage=(char *) RelinquishMagickMemory(
3822 montage_image->montage);
3823 if (image->directory != (char *) NULL)
3824 montage_image->directory=(char *) RelinquishMagickMemory(
3825 montage_image->directory);
3826 }
3827 preview_info=DestroyImageInfo(preview_info);
3828 return(montage_image);
3829}
3830
3831/*
3832%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3833% %
3834% %
3835% %
3836% R a d i a l B l u r I m a g e %
3837% %
3838% %
3839% %
3840%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3841%
3842% RadialBlurImage() applies a radial blur to the image.
3843%
3844% Andrew Protano contributed this effect.
3845%
3846% The format of the RadialBlurImage method is:
3847%
3848% Image *RadialBlurImage(const Image *image,const double angle,
3849% ExceptionInfo *exception)
3850% Image *RadialBlurImageChannel(const Image *image,const ChannelType channel,
3851% const double angle,ExceptionInfo *exception)
3852%
3853% A description of each parameter follows:
3854%
3855% o image: the image.
3856%
3857% o channel: the channel type.
3858%
3859% o angle: the angle of the radial blur.
3860%
3861% o exception: return any errors or warnings in this structure.
3862%
3863*/
3864
3865MagickExport Image *RadialBlurImage(const Image *image,const double angle,
3866 ExceptionInfo *exception)
3867{
3868 Image
3869 *blur_image;
3870
3871 blur_image=RadialBlurImageChannel(image,DefaultChannels,angle,exception);
3872 return(blur_image);
3873}
3874
3875MagickExport Image *RadialBlurImageChannel(const Image *image,
3876 const ChannelType channel,const double angle,ExceptionInfo *exception)
3877{
cristyc4c8d132010-01-07 01:58:38 +00003878 CacheView
3879 *blur_view,
3880 *image_view;
3881
cristy3ed852e2009-09-05 21:47:34 +00003882 Image
3883 *blur_image;
3884
3885 long
3886 progress,
3887 y;
3888
3889 MagickBooleanType
3890 status;
3891
3892 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00003893 bias;
cristy3ed852e2009-09-05 21:47:34 +00003894
3895 MagickRealType
3896 blur_radius,
3897 *cos_theta,
3898 offset,
3899 *sin_theta,
3900 theta;
3901
3902 PointInfo
3903 blur_center;
3904
3905 register long
3906 i;
3907
3908 unsigned long
3909 n;
3910
cristy3ed852e2009-09-05 21:47:34 +00003911 /*
3912 Allocate blur image.
3913 */
3914 assert(image != (Image *) NULL);
3915 assert(image->signature == MagickSignature);
3916 if (image->debug != MagickFalse)
3917 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3918 assert(exception != (ExceptionInfo *) NULL);
3919 assert(exception->signature == MagickSignature);
3920 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3921 if (blur_image == (Image *) NULL)
3922 return((Image *) NULL);
3923 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3924 {
3925 InheritException(exception,&blur_image->exception);
3926 blur_image=DestroyImage(blur_image);
3927 return((Image *) NULL);
3928 }
3929 blur_center.x=(double) image->columns/2.0;
3930 blur_center.y=(double) image->rows/2.0;
3931 blur_radius=hypot(blur_center.x,blur_center.y);
3932 n=(unsigned long) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+
3933 2UL);
3934 theta=DegreesToRadians(angle)/(MagickRealType) (n-1);
3935 cos_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3936 sizeof(*cos_theta));
3937 sin_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3938 sizeof(*sin_theta));
3939 if ((cos_theta == (MagickRealType *) NULL) ||
3940 (sin_theta == (MagickRealType *) NULL))
3941 {
3942 blur_image=DestroyImage(blur_image);
3943 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3944 }
3945 offset=theta*(MagickRealType) (n-1)/2.0;
3946 for (i=0; i < (long) n; i++)
3947 {
3948 cos_theta[i]=cos((double) (theta*i-offset));
3949 sin_theta[i]=sin((double) (theta*i-offset));
3950 }
3951 /*
3952 Radial blur image.
3953 */
3954 status=MagickTrue;
3955 progress=0;
cristyddd82202009-11-03 20:14:50 +00003956 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003957 image_view=AcquireCacheView(image);
3958 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003959#if defined(MAGICKCORE_OPENMP_SUPPORT)
3960 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003961#endif
3962 for (y=0; y < (long) blur_image->rows; y++)
3963 {
3964 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003965 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003966
3967 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003968 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00003969
3970 register long
3971 x;
3972
3973 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003974 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003975
3976 if (status == MagickFalse)
3977 continue;
3978 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3979 exception);
3980 if (q == (PixelPacket *) NULL)
3981 {
3982 status=MagickFalse;
3983 continue;
3984 }
3985 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
3986 for (x=0; x < (long) blur_image->columns; x++)
3987 {
3988 MagickPixelPacket
3989 qixel;
3990
3991 MagickRealType
3992 normalize,
3993 radius;
3994
3995 PixelPacket
3996 pixel;
3997
3998 PointInfo
3999 center;
4000
4001 register long
4002 i;
4003
4004 unsigned long
4005 step;
4006
4007 center.x=(double) x-blur_center.x;
4008 center.y=(double) y-blur_center.y;
4009 radius=hypot((double) center.x,center.y);
4010 if (radius == 0)
4011 step=1;
4012 else
4013 {
4014 step=(unsigned long) (blur_radius/radius);
4015 if (step == 0)
4016 step=1;
4017 else
4018 if (step >= n)
4019 step=n-1;
4020 }
4021 normalize=0.0;
cristyddd82202009-11-03 20:14:50 +00004022 qixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00004023 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
4024 {
4025 for (i=0; i < (long) n; i+=step)
4026 {
4027 (void) GetOneCacheViewVirtualPixel(image_view,(long) (blur_center.x+
4028 center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),(long) (
4029 blur_center.y+center.x*sin_theta[i]+center.y*cos_theta[i]+0.5),
4030 &pixel,exception);
4031 qixel.red+=pixel.red;
4032 qixel.green+=pixel.green;
4033 qixel.blue+=pixel.blue;
4034 qixel.opacity+=pixel.opacity;
4035 if (image->colorspace == CMYKColorspace)
4036 {
4037 indexes=GetCacheViewVirtualIndexQueue(image_view);
4038 qixel.index+=(*indexes);
4039 }
4040 normalize+=1.0;
4041 }
4042 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
4043 normalize);
4044 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004045 q->red=ClampToQuantum(normalize*qixel.red);
cristy3ed852e2009-09-05 21:47:34 +00004046 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004047 q->green=ClampToQuantum(normalize*qixel.green);
cristy3ed852e2009-09-05 21:47:34 +00004048 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004049 q->blue=ClampToQuantum(normalize*qixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00004050 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004051 q->opacity=ClampToQuantum(normalize*qixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00004052 if (((channel & IndexChannel) != 0) &&
4053 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00004054 blur_indexes[x]=(IndexPacket) ClampToQuantum(normalize*qixel.index);
cristy3ed852e2009-09-05 21:47:34 +00004055 }
4056 else
4057 {
4058 MagickRealType
4059 alpha,
4060 gamma;
4061
4062 alpha=1.0;
4063 gamma=0.0;
4064 for (i=0; i < (long) n; i+=step)
4065 {
4066 (void) GetOneCacheViewVirtualPixel(image_view,(long) (blur_center.x+
4067 center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),(long) (
4068 blur_center.y+center.x*sin_theta[i]+center.y*cos_theta[i]+0.5),
4069 &pixel,exception);
cristy46f08202010-01-10 04:04:21 +00004070 alpha=(MagickRealType) (QuantumScale*
4071 GetAlphaPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004072 qixel.red+=alpha*pixel.red;
4073 qixel.green+=alpha*pixel.green;
4074 qixel.blue+=alpha*pixel.blue;
4075 qixel.opacity+=pixel.opacity;
4076 if (image->colorspace == CMYKColorspace)
4077 {
4078 indexes=GetCacheViewVirtualIndexQueue(image_view);
4079 qixel.index+=alpha*(*indexes);
4080 }
4081 gamma+=alpha;
4082 normalize+=1.0;
4083 }
4084 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
4085 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
4086 normalize);
4087 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004088 q->red=ClampToQuantum(gamma*qixel.red);
cristy3ed852e2009-09-05 21:47:34 +00004089 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004090 q->green=ClampToQuantum(gamma*qixel.green);
cristy3ed852e2009-09-05 21:47:34 +00004091 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004092 q->blue=ClampToQuantum(gamma*qixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00004093 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004094 q->opacity=ClampToQuantum(normalize*qixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00004095 if (((channel & IndexChannel) != 0) &&
4096 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00004097 blur_indexes[x]=(IndexPacket) ClampToQuantum(gamma*qixel.index);
cristy3ed852e2009-09-05 21:47:34 +00004098 }
4099 q++;
4100 }
4101 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
4102 status=MagickFalse;
4103 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4104 {
4105 MagickBooleanType
4106 proceed;
4107
cristyb5d5f722009-11-04 03:03:49 +00004108#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004109 #pragma omp critical (MagickCore_RadialBlurImageChannel)
4110#endif
4111 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
4112 if (proceed == MagickFalse)
4113 status=MagickFalse;
4114 }
4115 }
4116 blur_view=DestroyCacheView(blur_view);
4117 image_view=DestroyCacheView(image_view);
4118 cos_theta=(MagickRealType *) RelinquishMagickMemory(cos_theta);
4119 sin_theta=(MagickRealType *) RelinquishMagickMemory(sin_theta);
4120 if (status == MagickFalse)
4121 blur_image=DestroyImage(blur_image);
4122 return(blur_image);
4123}
4124
4125/*
4126%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4127% %
4128% %
4129% %
4130% R e d u c e N o i s e I m a g e %
4131% %
4132% %
4133% %
4134%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4135%
4136% ReduceNoiseImage() smooths the contours of an image while still preserving
4137% edge information. The algorithm works by replacing each pixel with its
4138% neighbor closest in value. A neighbor is defined by radius. Use a radius
4139% of 0 and ReduceNoise() selects a suitable radius for you.
4140%
4141% The format of the ReduceNoiseImage method is:
4142%
4143% Image *ReduceNoiseImage(const Image *image,const double radius,
4144% ExceptionInfo *exception)
4145%
4146% A description of each parameter follows:
4147%
4148% o image: the image.
4149%
4150% o radius: the radius of the pixel neighborhood.
4151%
4152% o exception: return any errors or warnings in this structure.
4153%
4154*/
4155
4156static MagickPixelPacket GetNonpeakMedianPixelList(MedianPixelList *pixel_list)
4157{
4158 MagickPixelPacket
4159 pixel;
4160
4161 register long
4162 channel;
4163
4164 register MedianSkipList
4165 *list;
4166
4167 unsigned long
4168 center,
4169 color,
4170 count,
4171 previous,
4172 next;
4173
4174 unsigned short
4175 channels[5];
4176
4177 /*
4178 Finds the median value for each of the color.
4179 */
4180 center=pixel_list->center;
4181 for (channel=0; channel < 5; channel++)
4182 {
4183 list=pixel_list->lists+channel;
4184 color=65536UL;
4185 next=list->nodes[color].next[0];
4186 count=0;
4187 do
4188 {
4189 previous=color;
4190 color=next;
4191 next=list->nodes[color].next[0];
4192 count+=list->nodes[color].count;
4193 }
4194 while (count <= center);
4195 if ((previous == 65536UL) && (next != 65536UL))
4196 color=next;
4197 else
4198 if ((previous != 65536UL) && (next == 65536UL))
4199 color=previous;
4200 channels[channel]=(unsigned short) color;
4201 }
4202 GetMagickPixelPacket((const Image *) NULL,&pixel);
4203 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4204 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4205 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
4206 pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
4207 pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]);
4208 return(pixel);
4209}
4210
4211MagickExport Image *ReduceNoiseImage(const Image *image,const double radius,
4212 ExceptionInfo *exception)
4213{
4214#define ReduceNoiseImageTag "ReduceNoise/Image"
4215
cristyfa112112010-01-04 17:48:07 +00004216 CacheView
4217 *image_view,
4218 *noise_view;
4219
cristy3ed852e2009-09-05 21:47:34 +00004220 Image
4221 *noise_image;
4222
4223 long
4224 progress,
4225 y;
4226
4227 MagickBooleanType
4228 status;
4229
4230 MedianPixelList
cristyfa112112010-01-04 17:48:07 +00004231 **restrict pixel_list;
cristy3ed852e2009-09-05 21:47:34 +00004232
4233 unsigned long
4234 width;
4235
cristy3ed852e2009-09-05 21:47:34 +00004236 /*
4237 Initialize noise image attributes.
4238 */
4239 assert(image != (Image *) NULL);
4240 assert(image->signature == MagickSignature);
4241 if (image->debug != MagickFalse)
4242 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4243 assert(exception != (ExceptionInfo *) NULL);
4244 assert(exception->signature == MagickSignature);
4245 width=GetOptimalKernelWidth2D(radius,0.5);
4246 if ((image->columns < width) || (image->rows < width))
4247 ThrowImageException(OptionError,"ImageSmallerThanKernelRadius");
4248 noise_image=CloneImage(image,image->columns,image->rows,MagickTrue,
4249 exception);
4250 if (noise_image == (Image *) NULL)
4251 return((Image *) NULL);
4252 if (SetImageStorageClass(noise_image,DirectClass) == MagickFalse)
4253 {
4254 InheritException(exception,&noise_image->exception);
4255 noise_image=DestroyImage(noise_image);
4256 return((Image *) NULL);
4257 }
4258 pixel_list=AcquireMedianPixelListThreadSet(width);
4259 if (pixel_list == (MedianPixelList **) NULL)
4260 {
4261 noise_image=DestroyImage(noise_image);
4262 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
4263 }
4264 /*
4265 Reduce noise image.
4266 */
4267 status=MagickTrue;
4268 progress=0;
4269 image_view=AcquireCacheView(image);
4270 noise_view=AcquireCacheView(noise_image);
cristyb5d5f722009-11-04 03:03:49 +00004271#if defined(MAGICKCORE_OPENMP_SUPPORT)
4272 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004273#endif
4274 for (y=0; y < (long) noise_image->rows; y++)
4275 {
4276 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004277 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00004278
4279 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004280 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00004281
4282 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004283 *restrict noise_indexes;
cristy3ed852e2009-09-05 21:47:34 +00004284
4285 register long
4286 id,
4287 x;
4288
4289 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004290 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004291
4292 if (status == MagickFalse)
4293 continue;
4294 p=GetCacheViewVirtualPixels(image_view,-((long) width/2L),y-(long) (width/
4295 2L),image->columns+width,width,exception);
4296 q=QueueCacheViewAuthenticPixels(noise_view,0,y,noise_image->columns,1,
4297 exception);
4298 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
4299 {
4300 status=MagickFalse;
4301 continue;
4302 }
4303 indexes=GetCacheViewVirtualIndexQueue(image_view);
4304 noise_indexes=GetCacheViewAuthenticIndexQueue(noise_view);
4305 id=GetOpenMPThreadId();
4306 for (x=0; x < (long) noise_image->columns; x++)
4307 {
4308 MagickPixelPacket
4309 pixel;
4310
4311 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004312 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +00004313
4314 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004315 *restrict s;
cristy3ed852e2009-09-05 21:47:34 +00004316
4317 register long
4318 u,
4319 v;
4320
4321 r=p;
4322 s=indexes+x;
4323 ResetMedianPixelList(pixel_list[id]);
4324 for (v=0; v < (long) width; v++)
4325 {
4326 for (u=0; u < (long) width; u++)
4327 InsertMedianPixelList(image,r+u,s+u,pixel_list[id]);
4328 r+=image->columns+width;
4329 s+=image->columns+width;
4330 }
4331 pixel=GetNonpeakMedianPixelList(pixel_list[id]);
4332 SetPixelPacket(noise_image,&pixel,q,noise_indexes+x);
4333 p++;
4334 q++;
4335 }
4336 if (SyncCacheViewAuthenticPixels(noise_view,exception) == MagickFalse)
4337 status=MagickFalse;
4338 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4339 {
4340 MagickBooleanType
4341 proceed;
4342
cristyb5d5f722009-11-04 03:03:49 +00004343#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004344 #pragma omp critical (MagickCore_ReduceNoiseImage)
4345#endif
4346 proceed=SetImageProgress(image,ReduceNoiseImageTag,progress++,
4347 image->rows);
4348 if (proceed == MagickFalse)
4349 status=MagickFalse;
4350 }
4351 }
4352 noise_view=DestroyCacheView(noise_view);
4353 image_view=DestroyCacheView(image_view);
4354 pixel_list=DestroyMedianPixelListThreadSet(pixel_list);
4355 return(noise_image);
4356}
4357
4358/*
4359%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4360% %
4361% %
4362% %
4363% S e l e c t i v e B l u r I m a g e %
4364% %
4365% %
4366% %
4367%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4368%
4369% SelectiveBlurImage() selectively blur pixels within a contrast threshold.
4370% It is similar to the unsharpen mask that sharpens everything with contrast
4371% above a certain threshold.
4372%
4373% The format of the SelectiveBlurImage method is:
4374%
4375% Image *SelectiveBlurImage(const Image *image,const double radius,
4376% const double sigma,const double threshold,ExceptionInfo *exception)
4377% Image *SelectiveBlurImageChannel(const Image *image,
4378% const ChannelType channel,const double radius,const double sigma,
4379% const double threshold,ExceptionInfo *exception)
4380%
4381% A description of each parameter follows:
4382%
4383% o image: the image.
4384%
4385% o channel: the channel type.
4386%
4387% o radius: the radius of the Gaussian, in pixels, not counting the center
4388% pixel.
4389%
4390% o sigma: the standard deviation of the Gaussian, in pixels.
4391%
4392% o threshold: only pixels within this contrast threshold are included
4393% in the blur operation.
4394%
4395% o exception: return any errors or warnings in this structure.
4396%
4397*/
4398
4399static inline MagickBooleanType SelectiveContrast(const PixelPacket *p,
4400 const PixelPacket *q,const double threshold)
4401{
4402 if (fabs(PixelIntensity(p)-PixelIntensity(q)) < threshold)
4403 return(MagickTrue);
4404 return(MagickFalse);
4405}
4406
4407MagickExport Image *SelectiveBlurImage(const Image *image,const double radius,
4408 const double sigma,const double threshold,ExceptionInfo *exception)
4409{
4410 Image
4411 *blur_image;
4412
4413 blur_image=SelectiveBlurImageChannel(image,DefaultChannels,radius,sigma,
4414 threshold,exception);
4415 return(blur_image);
4416}
4417
4418MagickExport Image *SelectiveBlurImageChannel(const Image *image,
4419 const ChannelType channel,const double radius,const double sigma,
4420 const double threshold,ExceptionInfo *exception)
4421{
4422#define SelectiveBlurImageTag "SelectiveBlur/Image"
4423
cristy47e00502009-12-17 19:19:57 +00004424 CacheView
4425 *blur_view,
4426 *image_view;
4427
cristy3ed852e2009-09-05 21:47:34 +00004428 double
cristy3ed852e2009-09-05 21:47:34 +00004429 *kernel;
4430
4431 Image
4432 *blur_image;
4433
4434 long
cristy47e00502009-12-17 19:19:57 +00004435 j,
cristy3ed852e2009-09-05 21:47:34 +00004436 progress,
cristy47e00502009-12-17 19:19:57 +00004437 u,
cristy3ed852e2009-09-05 21:47:34 +00004438 v,
4439 y;
4440
4441 MagickBooleanType
4442 status;
4443
4444 MagickPixelPacket
cristy3ed852e2009-09-05 21:47:34 +00004445 bias;
4446
4447 register long
cristy47e00502009-12-17 19:19:57 +00004448 i;
cristy3ed852e2009-09-05 21:47:34 +00004449
4450 unsigned long
4451 width;
4452
cristy3ed852e2009-09-05 21:47:34 +00004453 /*
4454 Initialize blur image attributes.
4455 */
4456 assert(image != (Image *) NULL);
4457 assert(image->signature == MagickSignature);
4458 if (image->debug != MagickFalse)
4459 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4460 assert(exception != (ExceptionInfo *) NULL);
4461 assert(exception->signature == MagickSignature);
4462 width=GetOptimalKernelWidth1D(radius,sigma);
4463 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
4464 if (kernel == (double *) NULL)
4465 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy47e00502009-12-17 19:19:57 +00004466 j=(long) width/2;
cristy3ed852e2009-09-05 21:47:34 +00004467 i=0;
cristy47e00502009-12-17 19:19:57 +00004468 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00004469 {
cristy47e00502009-12-17 19:19:57 +00004470 for (u=(-j); u <= j; u++)
4471 kernel[i++]=exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
4472 (2.0*MagickPI*MagickSigma*MagickSigma);
cristy3ed852e2009-09-05 21:47:34 +00004473 }
4474 if (image->debug != MagickFalse)
4475 {
4476 char
4477 format[MaxTextExtent],
4478 *message;
4479
4480 long
4481 u,
4482 v;
4483
4484 register const double
4485 *k;
4486
4487 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
4488 " SelectiveBlurImage with %ldx%ld kernel:",width,width);
4489 message=AcquireString("");
4490 k=kernel;
4491 for (v=0; v < (long) width; v++)
4492 {
4493 *message='\0';
4494 (void) FormatMagickString(format,MaxTextExtent,"%ld: ",v);
4495 (void) ConcatenateString(&message,format);
4496 for (u=0; u < (long) width; u++)
4497 {
4498 (void) FormatMagickString(format,MaxTextExtent,"%+f ",*k++);
4499 (void) ConcatenateString(&message,format);
4500 }
4501 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
4502 }
4503 message=DestroyString(message);
4504 }
4505 blur_image=CloneImage(image,0,0,MagickTrue,exception);
4506 if (blur_image == (Image *) NULL)
4507 return((Image *) NULL);
4508 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
4509 {
4510 InheritException(exception,&blur_image->exception);
4511 blur_image=DestroyImage(blur_image);
4512 return((Image *) NULL);
4513 }
4514 /*
4515 Threshold blur image.
4516 */
4517 status=MagickTrue;
4518 progress=0;
cristyddd82202009-11-03 20:14:50 +00004519 GetMagickPixelPacket(image,&bias);
4520 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00004521 image_view=AcquireCacheView(image);
4522 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00004523#if defined(MAGICKCORE_OPENMP_SUPPORT)
4524 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004525#endif
4526 for (y=0; y < (long) image->rows; y++)
4527 {
4528 MagickBooleanType
4529 sync;
4530
4531 MagickRealType
4532 gamma;
4533
4534 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004535 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00004536
4537 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004538 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00004539
4540 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004541 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00004542
4543 register long
4544 x;
4545
4546 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004547 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004548
4549 if (status == MagickFalse)
4550 continue;
4551 p=GetCacheViewVirtualPixels(image_view,-((long) width/2L),y-(long) (width/
4552 2L),image->columns+width,width,exception);
4553 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
4554 exception);
4555 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
4556 {
4557 status=MagickFalse;
4558 continue;
4559 }
4560 indexes=GetCacheViewVirtualIndexQueue(image_view);
4561 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
4562 for (x=0; x < (long) image->columns; x++)
4563 {
4564 long
4565 j,
4566 v;
4567
4568 MagickPixelPacket
4569 pixel;
4570
4571 register const double
cristyc47d1f82009-11-26 01:44:43 +00004572 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00004573
4574 register long
4575 u;
4576
cristyddd82202009-11-03 20:14:50 +00004577 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00004578 k=kernel;
4579 gamma=0.0;
4580 j=0;
4581 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
4582 {
4583 for (v=0; v < (long) width; v++)
4584 {
4585 for (u=0; u < (long) width; u++)
4586 {
4587 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4588 {
4589 pixel.red+=(*k)*(p+u+j)->red;
4590 pixel.green+=(*k)*(p+u+j)->green;
4591 pixel.blue+=(*k)*(p+u+j)->blue;
4592 gamma+=(*k);
4593 k++;
4594 }
4595 }
4596 j+=image->columns+width;
4597 }
4598 if (gamma != 0.0)
4599 {
4600 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
4601 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004602 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004603 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004604 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004605 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004606 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004607 }
4608 if ((channel & OpacityChannel) != 0)
4609 {
4610 gamma=0.0;
4611 j=0;
4612 for (v=0; v < (long) width; v++)
4613 {
4614 for (u=0; u < (long) width; u++)
4615 {
4616 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4617 {
4618 pixel.opacity+=(*k)*(p+u+j)->opacity;
4619 gamma+=(*k);
4620 k++;
4621 }
4622 }
4623 j+=image->columns+width;
4624 }
4625 if (gamma != 0.0)
4626 {
4627 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4628 gamma);
cristyce70c172010-01-07 17:15:30 +00004629 SetOpacityPixelComponent(q,ClampToQuantum(gamma*
4630 GetOpacityPixelComponent(&pixel)));
cristy3ed852e2009-09-05 21:47:34 +00004631 }
4632 }
4633 if (((channel & IndexChannel) != 0) &&
4634 (image->colorspace == CMYKColorspace))
4635 {
4636 gamma=0.0;
4637 j=0;
4638 for (v=0; v < (long) width; v++)
4639 {
4640 for (u=0; u < (long) width; u++)
4641 {
4642 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4643 {
4644 pixel.index+=(*k)*indexes[x+u+j];
4645 gamma+=(*k);
4646 k++;
4647 }
4648 }
4649 j+=image->columns+width;
4650 }
4651 if (gamma != 0.0)
4652 {
4653 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4654 gamma);
cristy6db48122010-01-11 00:18:07 +00004655 blur_indexes[x]=ClampToQuantum(gamma*
4656 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004657 }
4658 }
4659 }
4660 else
4661 {
4662 MagickRealType
4663 alpha;
4664
4665 for (v=0; v < (long) width; v++)
4666 {
4667 for (u=0; u < (long) width; u++)
4668 {
4669 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4670 {
cristy46f08202010-01-10 04:04:21 +00004671 alpha=(MagickRealType) (QuantumScale*
4672 GetAlphaPixelComponent(p+u+j));
cristy3ed852e2009-09-05 21:47:34 +00004673 pixel.red+=(*k)*alpha*(p+u+j)->red;
4674 pixel.green+=(*k)*alpha*(p+u+j)->green;
4675 pixel.blue+=(*k)*alpha*(p+u+j)->blue;
4676 pixel.opacity+=(*k)*(p+u+j)->opacity;
4677 gamma+=(*k)*alpha;
4678 k++;
4679 }
4680 }
4681 j+=image->columns+width;
4682 }
4683 if (gamma != 0.0)
4684 {
4685 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
4686 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004687 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004688 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004689 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004690 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004691 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004692 }
4693 if ((channel & OpacityChannel) != 0)
4694 {
4695 gamma=0.0;
4696 j=0;
4697 for (v=0; v < (long) width; v++)
4698 {
4699 for (u=0; u < (long) width; u++)
4700 {
4701 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4702 {
4703 pixel.opacity+=(*k)*(p+u+j)->opacity;
4704 gamma+=(*k);
4705 k++;
4706 }
4707 }
4708 j+=image->columns+width;
4709 }
4710 if (gamma != 0.0)
4711 {
4712 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4713 gamma);
cristy6db48122010-01-11 00:18:07 +00004714 SetOpacityPixelComponent(q,
4715 ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004716 }
4717 }
4718 if (((channel & IndexChannel) != 0) &&
4719 (image->colorspace == CMYKColorspace))
4720 {
4721 gamma=0.0;
4722 j=0;
4723 for (v=0; v < (long) width; v++)
4724 {
4725 for (u=0; u < (long) width; u++)
4726 {
4727 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4728 {
cristy46f08202010-01-10 04:04:21 +00004729 alpha=(MagickRealType) (QuantumScale*
4730 GetAlphaPixelComponent(p+u+j));
cristy3ed852e2009-09-05 21:47:34 +00004731 pixel.index+=(*k)*alpha*indexes[x+u+j];
4732 gamma+=(*k);
4733 k++;
4734 }
4735 }
4736 j+=image->columns+width;
4737 }
4738 if (gamma != 0.0)
4739 {
4740 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4741 gamma);
cristy6db48122010-01-11 00:18:07 +00004742 blur_indexes[x]=ClampToQuantum(gamma*
4743 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004744 }
4745 }
4746 }
4747 p++;
4748 q++;
4749 }
4750 sync=SyncCacheViewAuthenticPixels(blur_view,exception);
4751 if (sync == MagickFalse)
4752 status=MagickFalse;
4753 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4754 {
4755 MagickBooleanType
4756 proceed;
4757
cristyb5d5f722009-11-04 03:03:49 +00004758#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004759 #pragma omp critical (MagickCore_SelectiveBlurImageChannel)
4760#endif
4761 proceed=SetImageProgress(image,SelectiveBlurImageTag,progress++,
4762 image->rows);
4763 if (proceed == MagickFalse)
4764 status=MagickFalse;
4765 }
4766 }
4767 blur_image->type=image->type;
4768 blur_view=DestroyCacheView(blur_view);
4769 image_view=DestroyCacheView(image_view);
4770 kernel=(double *) RelinquishMagickMemory(kernel);
4771 if (status == MagickFalse)
4772 blur_image=DestroyImage(blur_image);
4773 return(blur_image);
4774}
4775
4776/*
4777%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4778% %
4779% %
4780% %
4781% S h a d e I m a g e %
4782% %
4783% %
4784% %
4785%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4786%
4787% ShadeImage() shines a distant light on an image to create a
4788% three-dimensional effect. You control the positioning of the light with
4789% azimuth and elevation; azimuth is measured in degrees off the x axis
4790% and elevation is measured in pixels above the Z axis.
4791%
4792% The format of the ShadeImage method is:
4793%
4794% Image *ShadeImage(const Image *image,const MagickBooleanType gray,
4795% const double azimuth,const double elevation,ExceptionInfo *exception)
4796%
4797% A description of each parameter follows:
4798%
4799% o image: the image.
4800%
4801% o gray: A value other than zero shades the intensity of each pixel.
4802%
4803% o azimuth, elevation: Define the light source direction.
4804%
4805% o exception: return any errors or warnings in this structure.
4806%
4807*/
4808MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray,
4809 const double azimuth,const double elevation,ExceptionInfo *exception)
4810{
4811#define ShadeImageTag "Shade/Image"
4812
cristyc4c8d132010-01-07 01:58:38 +00004813 CacheView
4814 *image_view,
4815 *shade_view;
4816
cristy3ed852e2009-09-05 21:47:34 +00004817 Image
4818 *shade_image;
4819
4820 long
4821 progress,
4822 y;
4823
4824 MagickBooleanType
4825 status;
4826
4827 PrimaryInfo
4828 light;
4829
cristy3ed852e2009-09-05 21:47:34 +00004830 /*
4831 Initialize shaded image attributes.
4832 */
4833 assert(image != (const Image *) NULL);
4834 assert(image->signature == MagickSignature);
4835 if (image->debug != MagickFalse)
4836 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4837 assert(exception != (ExceptionInfo *) NULL);
4838 assert(exception->signature == MagickSignature);
4839 shade_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
4840 if (shade_image == (Image *) NULL)
4841 return((Image *) NULL);
4842 if (SetImageStorageClass(shade_image,DirectClass) == MagickFalse)
4843 {
4844 InheritException(exception,&shade_image->exception);
4845 shade_image=DestroyImage(shade_image);
4846 return((Image *) NULL);
4847 }
4848 /*
4849 Compute the light vector.
4850 */
4851 light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))*
4852 cos(DegreesToRadians(elevation));
4853 light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))*
4854 cos(DegreesToRadians(elevation));
4855 light.z=(double) QuantumRange*sin(DegreesToRadians(elevation));
4856 /*
4857 Shade image.
4858 */
4859 status=MagickTrue;
4860 progress=0;
4861 image_view=AcquireCacheView(image);
4862 shade_view=AcquireCacheView(shade_image);
cristyb5d5f722009-11-04 03:03:49 +00004863#if defined(MAGICKCORE_OPENMP_SUPPORT)
4864 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004865#endif
4866 for (y=0; y < (long) image->rows; y++)
4867 {
4868 MagickRealType
4869 distance,
4870 normal_distance,
4871 shade;
4872
4873 PrimaryInfo
4874 normal;
4875
4876 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004877 *restrict p,
4878 *restrict s0,
4879 *restrict s1,
4880 *restrict s2;
cristy3ed852e2009-09-05 21:47:34 +00004881
4882 register long
4883 x;
4884
4885 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004886 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004887
4888 if (status == MagickFalse)
4889 continue;
4890 p=GetCacheViewVirtualPixels(image_view,-1,y-1,image->columns+2,3,exception);
4891 q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1,
4892 exception);
4893 if ((p == (PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
4894 {
4895 status=MagickFalse;
4896 continue;
4897 }
4898 /*
4899 Shade this row of pixels.
4900 */
4901 normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */
4902 s0=p+1;
4903 s1=s0+image->columns+2;
4904 s2=s1+image->columns+2;
4905 for (x=0; x < (long) image->columns; x++)
4906 {
4907 /*
4908 Determine the surface normal and compute shading.
4909 */
4910 normal.x=(double) (PixelIntensity(s0-1)+PixelIntensity(s1-1)+
4911 PixelIntensity(s2-1)-PixelIntensity(s0+1)-PixelIntensity(s1+1)-
4912 PixelIntensity(s2+1));
4913 normal.y=(double) (PixelIntensity(s2-1)+PixelIntensity(s2)+
4914 PixelIntensity(s2+1)-PixelIntensity(s0-1)-PixelIntensity(s0)-
4915 PixelIntensity(s0+1));
4916 if ((normal.x == 0.0) && (normal.y == 0.0))
4917 shade=light.z;
4918 else
4919 {
4920 shade=0.0;
4921 distance=normal.x*light.x+normal.y*light.y+normal.z*light.z;
4922 if (distance > MagickEpsilon)
4923 {
4924 normal_distance=
4925 normal.x*normal.x+normal.y*normal.y+normal.z*normal.z;
4926 if (normal_distance > (MagickEpsilon*MagickEpsilon))
4927 shade=distance/sqrt((double) normal_distance);
4928 }
4929 }
4930 if (gray != MagickFalse)
4931 {
4932 q->red=(Quantum) shade;
4933 q->green=(Quantum) shade;
4934 q->blue=(Quantum) shade;
4935 }
4936 else
4937 {
cristyce70c172010-01-07 17:15:30 +00004938 q->red=ClampToQuantum(QuantumScale*shade*s1->red);
4939 q->green=ClampToQuantum(QuantumScale*shade*s1->green);
4940 q->blue=ClampToQuantum(QuantumScale*shade*s1->blue);
cristy3ed852e2009-09-05 21:47:34 +00004941 }
4942 q->opacity=s1->opacity;
4943 s0++;
4944 s1++;
4945 s2++;
4946 q++;
4947 }
4948 if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
4949 status=MagickFalse;
4950 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4951 {
4952 MagickBooleanType
4953 proceed;
4954
cristyb5d5f722009-11-04 03:03:49 +00004955#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004956 #pragma omp critical (MagickCore_ShadeImage)
4957#endif
4958 proceed=SetImageProgress(image,ShadeImageTag,progress++,image->rows);
4959 if (proceed == MagickFalse)
4960 status=MagickFalse;
4961 }
4962 }
4963 shade_view=DestroyCacheView(shade_view);
4964 image_view=DestroyCacheView(image_view);
4965 if (status == MagickFalse)
4966 shade_image=DestroyImage(shade_image);
4967 return(shade_image);
4968}
4969
4970/*
4971%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4972% %
4973% %
4974% %
4975% S h a r p e n I m a g e %
4976% %
4977% %
4978% %
4979%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4980%
4981% SharpenImage() sharpens the image. We convolve the image with a Gaussian
4982% operator of the given radius and standard deviation (sigma). For
4983% reasonable results, radius should be larger than sigma. Use a radius of 0
4984% and SharpenImage() selects a suitable radius for you.
4985%
4986% Using a separable kernel would be faster, but the negative weights cancel
4987% out on the corners of the kernel producing often undesirable ringing in the
4988% filtered result; this can be avoided by using a 2D gaussian shaped image
4989% sharpening kernel instead.
4990%
4991% The format of the SharpenImage method is:
4992%
4993% Image *SharpenImage(const Image *image,const double radius,
4994% const double sigma,ExceptionInfo *exception)
4995% Image *SharpenImageChannel(const Image *image,const ChannelType channel,
4996% const double radius,const double sigma,ExceptionInfo *exception)
4997%
4998% A description of each parameter follows:
4999%
5000% o image: the image.
5001%
5002% o channel: the channel type.
5003%
5004% o radius: the radius of the Gaussian, in pixels, not counting the center
5005% pixel.
5006%
5007% o sigma: the standard deviation of the Laplacian, in pixels.
5008%
5009% o exception: return any errors or warnings in this structure.
5010%
5011*/
5012
5013MagickExport Image *SharpenImage(const Image *image,const double radius,
5014 const double sigma,ExceptionInfo *exception)
5015{
5016 Image
5017 *sharp_image;
5018
5019 sharp_image=SharpenImageChannel(image,DefaultChannels,radius,sigma,exception);
5020 return(sharp_image);
5021}
5022
5023MagickExport Image *SharpenImageChannel(const Image *image,
5024 const ChannelType channel,const double radius,const double sigma,
5025 ExceptionInfo *exception)
5026{
5027 double
cristy47e00502009-12-17 19:19:57 +00005028 *kernel,
5029 normalize;
cristy3ed852e2009-09-05 21:47:34 +00005030
5031 Image
5032 *sharp_image;
5033
cristy47e00502009-12-17 19:19:57 +00005034 long
5035 j,
cristy3ed852e2009-09-05 21:47:34 +00005036 u,
5037 v;
5038
cristy47e00502009-12-17 19:19:57 +00005039 register long
5040 i;
5041
cristy3ed852e2009-09-05 21:47:34 +00005042 unsigned long
5043 width;
5044
5045 assert(image != (const Image *) NULL);
5046 assert(image->signature == MagickSignature);
5047 if (image->debug != MagickFalse)
5048 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5049 assert(exception != (ExceptionInfo *) NULL);
5050 assert(exception->signature == MagickSignature);
5051 width=GetOptimalKernelWidth2D(radius,sigma);
5052 kernel=(double *) AcquireQuantumMemory((size_t) width*width,sizeof(*kernel));
5053 if (kernel == (double *) NULL)
5054 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy3ed852e2009-09-05 21:47:34 +00005055 normalize=0.0;
cristy47e00502009-12-17 19:19:57 +00005056 j=(long) width/2;
5057 i=0;
5058 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00005059 {
cristy47e00502009-12-17 19:19:57 +00005060 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00005061 {
cristy47e00502009-12-17 19:19:57 +00005062 kernel[i]=(-exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
5063 (2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00005064 normalize+=kernel[i];
5065 i++;
5066 }
5067 }
5068 kernel[i/2]=(double) ((-2.0)*normalize);
5069 sharp_image=ConvolveImageChannel(image,channel,width,kernel,exception);
5070 kernel=(double *) RelinquishMagickMemory(kernel);
5071 return(sharp_image);
5072}
5073
5074/*
5075%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5076% %
5077% %
5078% %
5079% S p r e a d I m a g e %
5080% %
5081% %
5082% %
5083%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5084%
5085% SpreadImage() is a special effects method that randomly displaces each
5086% pixel in a block defined by the radius parameter.
5087%
5088% The format of the SpreadImage method is:
5089%
5090% Image *SpreadImage(const Image *image,const double radius,
5091% ExceptionInfo *exception)
5092%
5093% A description of each parameter follows:
5094%
5095% o image: the image.
5096%
5097% o radius: Choose a random pixel in a neighborhood of this extent.
5098%
5099% o exception: return any errors or warnings in this structure.
5100%
5101*/
5102MagickExport Image *SpreadImage(const Image *image,const double radius,
5103 ExceptionInfo *exception)
5104{
5105#define SpreadImageTag "Spread/Image"
5106
cristyfa112112010-01-04 17:48:07 +00005107 CacheView
5108 *image_view;
5109
cristy3ed852e2009-09-05 21:47:34 +00005110 Image
5111 *spread_image;
5112
5113 long
5114 progress,
5115 y;
5116
5117 MagickBooleanType
5118 status;
5119
5120 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00005121 bias;
cristy3ed852e2009-09-05 21:47:34 +00005122
5123 RandomInfo
cristyfa112112010-01-04 17:48:07 +00005124 **restrict random_info;
cristy3ed852e2009-09-05 21:47:34 +00005125
5126 ResampleFilter
cristyfa112112010-01-04 17:48:07 +00005127 **restrict resample_filter;
cristy3ed852e2009-09-05 21:47:34 +00005128
5129 unsigned long
5130 width;
5131
cristy3ed852e2009-09-05 21:47:34 +00005132 /*
5133 Initialize spread image attributes.
5134 */
5135 assert(image != (Image *) NULL);
5136 assert(image->signature == MagickSignature);
5137 if (image->debug != MagickFalse)
5138 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5139 assert(exception != (ExceptionInfo *) NULL);
5140 assert(exception->signature == MagickSignature);
5141 spread_image=CloneImage(image,image->columns,image->rows,MagickTrue,
5142 exception);
5143 if (spread_image == (Image *) NULL)
5144 return((Image *) NULL);
5145 if (SetImageStorageClass(spread_image,DirectClass) == MagickFalse)
5146 {
5147 InheritException(exception,&spread_image->exception);
5148 spread_image=DestroyImage(spread_image);
5149 return((Image *) NULL);
5150 }
5151 /*
5152 Spread image.
5153 */
5154 status=MagickTrue;
5155 progress=0;
cristyddd82202009-11-03 20:14:50 +00005156 GetMagickPixelPacket(spread_image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00005157 width=GetOptimalKernelWidth1D(radius,0.5);
cristyb2a11ae2010-02-22 00:53:36 +00005158 resample_filter=AcquireResampleFilterThreadSet(image,
5159 UndefinedVirtualPixelMethod,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00005160 random_info=AcquireRandomInfoThreadSet();
5161 image_view=AcquireCacheView(spread_image);
cristy8a7ea362010-03-10 20:31:43 +00005162#if defined(MAGICKCORE_OPENMP_SUPPORT) && (_OPENMP > 202001)
5163 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00005164#endif
5165 for (y=0; y < (long) spread_image->rows; y++)
5166 {
5167 MagickPixelPacket
5168 pixel;
5169
5170 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00005171 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00005172
5173 register long
5174 id,
5175 x;
5176
5177 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00005178 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00005179
5180 if (status == MagickFalse)
5181 continue;
5182 q=QueueCacheViewAuthenticPixels(image_view,0,y,spread_image->columns,1,
5183 exception);
5184 if (q == (PixelPacket *) NULL)
5185 {
5186 status=MagickFalse;
5187 continue;
5188 }
5189 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristyddd82202009-11-03 20:14:50 +00005190 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00005191 id=GetOpenMPThreadId();
5192 for (x=0; x < (long) spread_image->columns; x++)
5193 {
5194 (void) ResamplePixelColor(resample_filter[id],(double) x+width*
5195 (GetPseudoRandomValue(random_info[id])-0.5),(double) y+width*
5196 (GetPseudoRandomValue(random_info[id])-0.5),&pixel);
5197 SetPixelPacket(spread_image,&pixel,q,indexes+x);
5198 q++;
5199 }
5200 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
5201 status=MagickFalse;
5202 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5203 {
5204 MagickBooleanType
5205 proceed;
5206
cristy8a7ea362010-03-10 20:31:43 +00005207#if defined(MAGICKCORE_OPENMP_SUPPORT) && (_OPENMP > 202001)
cristy3ed852e2009-09-05 21:47:34 +00005208 #pragma omp critical (MagickCore_SpreadImage)
5209#endif
5210 proceed=SetImageProgress(image,SpreadImageTag,progress++,image->rows);
5211 if (proceed == MagickFalse)
5212 status=MagickFalse;
5213 }
5214 }
5215 image_view=DestroyCacheView(image_view);
5216 random_info=DestroyRandomInfoThreadSet(random_info);
5217 resample_filter=DestroyResampleFilterThreadSet(resample_filter);
5218 return(spread_image);
5219}
5220
5221/*
5222%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5223% %
5224% %
5225% %
5226% U n s h a r p M a s k I m a g e %
5227% %
5228% %
5229% %
5230%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5231%
5232% UnsharpMaskImage() sharpens one or more image channels. We convolve the
5233% image with a Gaussian operator of the given radius and standard deviation
5234% (sigma). For reasonable results, radius should be larger than sigma. Use a
5235% radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
5236%
5237% The format of the UnsharpMaskImage method is:
5238%
5239% Image *UnsharpMaskImage(const Image *image,const double radius,
5240% const double sigma,const double amount,const double threshold,
5241% ExceptionInfo *exception)
5242% Image *UnsharpMaskImageChannel(const Image *image,
5243% const ChannelType channel,const double radius,const double sigma,
5244% const double amount,const double threshold,ExceptionInfo *exception)
5245%
5246% A description of each parameter follows:
5247%
5248% o image: the image.
5249%
5250% o channel: the channel type.
5251%
5252% o radius: the radius of the Gaussian, in pixels, not counting the center
5253% pixel.
5254%
5255% o sigma: the standard deviation of the Gaussian, in pixels.
5256%
5257% o amount: the percentage of the difference between the original and the
5258% blur image that is added back into the original.
5259%
5260% o threshold: the threshold in pixels needed to apply the diffence amount.
5261%
5262% o exception: return any errors or warnings in this structure.
5263%
5264*/
5265
5266MagickExport Image *UnsharpMaskImage(const Image *image,const double radius,
5267 const double sigma,const double amount,const double threshold,
5268 ExceptionInfo *exception)
5269{
5270 Image
5271 *sharp_image;
5272
5273 sharp_image=UnsharpMaskImageChannel(image,DefaultChannels,radius,sigma,amount,
5274 threshold,exception);
5275 return(sharp_image);
5276}
5277
5278MagickExport Image *UnsharpMaskImageChannel(const Image *image,
5279 const ChannelType channel,const double radius,const double sigma,
5280 const double amount,const double threshold,ExceptionInfo *exception)
5281{
5282#define SharpenImageTag "Sharpen/Image"
5283
cristyc4c8d132010-01-07 01:58:38 +00005284 CacheView
5285 *image_view,
5286 *unsharp_view;
5287
cristy3ed852e2009-09-05 21:47:34 +00005288 Image
5289 *unsharp_image;
5290
5291 long
5292 progress,
5293 y;
5294
5295 MagickBooleanType
5296 status;
5297
5298 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00005299 bias;
cristy3ed852e2009-09-05 21:47:34 +00005300
5301 MagickRealType
5302 quantum_threshold;
5303
cristy3ed852e2009-09-05 21:47:34 +00005304 assert(image != (const Image *) NULL);
5305 assert(image->signature == MagickSignature);
5306 if (image->debug != MagickFalse)
5307 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5308 assert(exception != (ExceptionInfo *) NULL);
5309 unsharp_image=BlurImageChannel(image,channel,radius,sigma,exception);
5310 if (unsharp_image == (Image *) NULL)
5311 return((Image *) NULL);
5312 quantum_threshold=(MagickRealType) QuantumRange*threshold;
5313 /*
5314 Unsharp-mask image.
5315 */
5316 status=MagickTrue;
5317 progress=0;
cristyddd82202009-11-03 20:14:50 +00005318 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00005319 image_view=AcquireCacheView(image);
5320 unsharp_view=AcquireCacheView(unsharp_image);
cristyb5d5f722009-11-04 03:03:49 +00005321#if defined(MAGICKCORE_OPENMP_SUPPORT)
5322 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00005323#endif
5324 for (y=0; y < (long) image->rows; y++)
5325 {
5326 MagickPixelPacket
5327 pixel;
5328
5329 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00005330 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00005331
5332 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00005333 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00005334
5335 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00005336 *restrict unsharp_indexes;
cristy3ed852e2009-09-05 21:47:34 +00005337
5338 register long
5339 x;
5340
5341 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00005342 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00005343
5344 if (status == MagickFalse)
5345 continue;
5346 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
5347 q=GetCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
5348 exception);
5349 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
5350 {
5351 status=MagickFalse;
5352 continue;
5353 }
5354 indexes=GetCacheViewVirtualIndexQueue(image_view);
5355 unsharp_indexes=GetCacheViewAuthenticIndexQueue(unsharp_view);
cristyddd82202009-11-03 20:14:50 +00005356 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00005357 for (x=0; x < (long) image->columns; x++)
5358 {
5359 if ((channel & RedChannel) != 0)
5360 {
5361 pixel.red=p->red-(MagickRealType) q->red;
5362 if (fabs(2.0*pixel.red) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005363 pixel.red=(MagickRealType) GetRedPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005364 else
5365 pixel.red=(MagickRealType) p->red+(pixel.red*amount);
cristyce70c172010-01-07 17:15:30 +00005366 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00005367 }
5368 if ((channel & GreenChannel) != 0)
5369 {
5370 pixel.green=p->green-(MagickRealType) q->green;
5371 if (fabs(2.0*pixel.green) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005372 pixel.green=(MagickRealType) GetGreenPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005373 else
5374 pixel.green=(MagickRealType) p->green+(pixel.green*amount);
cristyce70c172010-01-07 17:15:30 +00005375 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00005376 }
5377 if ((channel & BlueChannel) != 0)
5378 {
5379 pixel.blue=p->blue-(MagickRealType) q->blue;
5380 if (fabs(2.0*pixel.blue) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005381 pixel.blue=(MagickRealType) GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005382 else
5383 pixel.blue=(MagickRealType) p->blue+(pixel.blue*amount);
cristyce70c172010-01-07 17:15:30 +00005384 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00005385 }
5386 if ((channel & OpacityChannel) != 0)
5387 {
5388 pixel.opacity=p->opacity-(MagickRealType) q->opacity;
5389 if (fabs(2.0*pixel.opacity) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005390 pixel.opacity=(MagickRealType) GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005391 else
5392 pixel.opacity=p->opacity+(pixel.opacity*amount);
cristyce70c172010-01-07 17:15:30 +00005393 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00005394 }
5395 if (((channel & IndexChannel) != 0) &&
5396 (image->colorspace == CMYKColorspace))
5397 {
5398 pixel.index=unsharp_indexes[x]-(MagickRealType) indexes[x];
5399 if (fabs(2.0*pixel.index) < quantum_threshold)
5400 pixel.index=(MagickRealType) unsharp_indexes[x];
5401 else
5402 pixel.index=(MagickRealType) unsharp_indexes[x]+(pixel.index*
5403 amount);
cristyce70c172010-01-07 17:15:30 +00005404 unsharp_indexes[x]=ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00005405 }
5406 p++;
5407 q++;
5408 }
5409 if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
5410 status=MagickFalse;
5411 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5412 {
5413 MagickBooleanType
5414 proceed;
5415
cristyb5d5f722009-11-04 03:03:49 +00005416#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00005417 #pragma omp critical (MagickCore_UnsharpMaskImageChannel)
5418#endif
5419 proceed=SetImageProgress(image,SharpenImageTag,progress++,image->rows);
5420 if (proceed == MagickFalse)
5421 status=MagickFalse;
5422 }
5423 }
5424 unsharp_image->type=image->type;
5425 unsharp_view=DestroyCacheView(unsharp_view);
5426 image_view=DestroyCacheView(image_view);
5427 if (status == MagickFalse)
5428 unsharp_image=DestroyImage(unsharp_image);
5429 return(unsharp_image);
5430}