blob: 2a7b6b6de91210af5b7505c034ed1862e0a003de [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"
44#include "magick/property.h"
45#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"
67#include "magick/paint.h"
68#include "magick/pixel-private.h"
69#include "magick/property.h"
70#include "magick/quantize.h"
71#include "magick/quantum.h"
72#include "magick/random_.h"
73#include "magick/random-private.h"
74#include "magick/resample.h"
75#include "magick/resample-private.h"
76#include "magick/resize.h"
77#include "magick/resource_.h"
78#include "magick/segment.h"
79#include "magick/shear.h"
80#include "magick/signature-private.h"
81#include "magick/string_.h"
82#include "magick/thread-private.h"
83#include "magick/transform.h"
84#include "magick/threshold.h"
85
86/*
87%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
88% %
89% %
90% %
91% A d a p t i v e B l u r I m a g e %
92% %
93% %
94% %
95%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
96%
97% AdaptiveBlurImage() adaptively blurs the image by blurring less
98% intensely near image edges and more intensely far from edges. We blur the
99% image with a Gaussian operator of the given radius and standard deviation
100% (sigma). For reasonable results, radius should be larger than sigma. Use a
101% radius of 0 and AdaptiveBlurImage() selects a suitable radius for you.
102%
103% The format of the AdaptiveBlurImage method is:
104%
105% Image *AdaptiveBlurImage(const Image *image,const double radius,
106% const double sigma,ExceptionInfo *exception)
107% Image *AdaptiveBlurImageChannel(const Image *image,
108% const ChannelType channel,double radius,const double sigma,
109% ExceptionInfo *exception)
110%
111% A description of each parameter follows:
112%
113% o image: the image.
114%
115% o channel: the channel type.
116%
117% o radius: the radius of the Gaussian, in pixels, not counting the center
118% pixel.
119%
120% o sigma: the standard deviation of the Laplacian, in pixels.
121%
122% o exception: return any errors or warnings in this structure.
123%
124*/
125
126MagickExport Image *AdaptiveBlurImage(const Image *image,const double radius,
127 const double sigma,ExceptionInfo *exception)
128{
129 Image
130 *blur_image;
131
132 blur_image=AdaptiveBlurImageChannel(image,DefaultChannels,radius,sigma,
133 exception);
134 return(blur_image);
135}
136
137MagickExport Image *AdaptiveBlurImageChannel(const Image *image,
138 const ChannelType channel,const double radius,const double sigma,
139 ExceptionInfo *exception)
140{
141#define AdaptiveBlurImageTag "Convolve/Image"
142#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
143
cristyc4c8d132010-01-07 01:58:38 +0000144 CacheView
145 *blur_view,
146 *edge_view,
147 *image_view;
148
cristy3ed852e2009-09-05 21:47:34 +0000149 double
cristy47e00502009-12-17 19:19:57 +0000150 **kernel,
151 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000152
153 Image
154 *blur_image,
155 *edge_image,
156 *gaussian_image;
157
158 long
159 j,
cristy47e00502009-12-17 19:19:57 +0000160 k,
cristy3ed852e2009-09-05 21:47:34 +0000161 progress,
cristy47e00502009-12-17 19:19:57 +0000162 u,
163 v,
cristy3ed852e2009-09-05 21:47:34 +0000164 y;
165
166 MagickBooleanType
167 status;
168
169 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +0000170 bias;
cristy3ed852e2009-09-05 21:47:34 +0000171
cristy3ed852e2009-09-05 21:47:34 +0000172 register long
cristy47e00502009-12-17 19:19:57 +0000173 i;
cristy3ed852e2009-09-05 21:47:34 +0000174
175 unsigned long
176 width;
177
cristy3ed852e2009-09-05 21:47:34 +0000178 assert(image != (const Image *) NULL);
179 assert(image->signature == MagickSignature);
180 if (image->debug != MagickFalse)
181 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
182 assert(exception != (ExceptionInfo *) NULL);
183 assert(exception->signature == MagickSignature);
184 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
185 if (blur_image == (Image *) NULL)
186 return((Image *) NULL);
187 if (fabs(sigma) <= MagickEpsilon)
188 return(blur_image);
189 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
190 {
191 InheritException(exception,&blur_image->exception);
192 blur_image=DestroyImage(blur_image);
193 return((Image *) NULL);
194 }
195 /*
196 Edge detect the image brighness channel, level, blur, and level again.
197 */
198 edge_image=EdgeImage(image,radius,exception);
199 if (edge_image == (Image *) NULL)
200 {
201 blur_image=DestroyImage(blur_image);
202 return((Image *) NULL);
203 }
204 (void) LevelImage(edge_image,"20%,95%");
205 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,exception);
206 if (gaussian_image != (Image *) NULL)
207 {
208 edge_image=DestroyImage(edge_image);
209 edge_image=gaussian_image;
210 }
211 (void) LevelImage(edge_image,"10%,95%");
212 /*
213 Create a set of kernels from maximum (radius,sigma) to minimum.
214 */
215 width=GetOptimalKernelWidth2D(radius,sigma);
216 kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
217 if (kernel == (double **) NULL)
218 {
219 edge_image=DestroyImage(edge_image);
220 blur_image=DestroyImage(blur_image);
221 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
222 }
223 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
224 for (i=0; i < (long) width; i+=2)
225 {
226 kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)*
227 sizeof(**kernel));
228 if (kernel[i] == (double *) NULL)
229 break;
cristy47e00502009-12-17 19:19:57 +0000230 normalize=0.0;
231 j=(long) (width-i)/2;
232 k=0;
233 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000234 {
cristy47e00502009-12-17 19:19:57 +0000235 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000236 {
cristy47e00502009-12-17 19:19:57 +0000237 kernel[i][k]=exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
238 (2.0*MagickPI*MagickSigma*MagickSigma);
239 normalize+=kernel[i][k];
240 k++;
cristy3ed852e2009-09-05 21:47:34 +0000241 }
242 }
cristy3ed852e2009-09-05 21:47:34 +0000243 if (fabs(normalize) <= MagickEpsilon)
244 normalize=1.0;
245 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000246 for (k=0; k < (j*j); k++)
247 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000248 }
249 if (i < (long) width)
250 {
251 for (i-=2; i >= 0; i-=2)
252 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
253 kernel=(double **) RelinquishMagickMemory(kernel);
254 edge_image=DestroyImage(edge_image);
255 blur_image=DestroyImage(blur_image);
256 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
257 }
258 /*
259 Adaptively blur image.
260 */
261 status=MagickTrue;
262 progress=0;
cristyddd82202009-11-03 20:14:50 +0000263 GetMagickPixelPacket(image,&bias);
264 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000265 image_view=AcquireCacheView(image);
266 edge_view=AcquireCacheView(edge_image);
267 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000268#if defined(MAGICKCORE_OPENMP_SUPPORT)
269 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000270#endif
271 for (y=0; y < (long) blur_image->rows; y++)
272 {
273 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000274 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000275
276 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000277 *restrict p,
278 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000279
280 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000281 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000282
283 register long
284 x;
285
286 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000287 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000288
289 if (status == MagickFalse)
290 continue;
291 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
292 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
293 exception);
294 if ((r == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
295 {
296 status=MagickFalse;
297 continue;
298 }
299 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
300 for (x=0; x < (long) blur_image->columns; x++)
301 {
302 MagickPixelPacket
303 pixel;
304
305 MagickRealType
306 alpha,
307 gamma;
308
309 register const double
cristyc47d1f82009-11-26 01:44:43 +0000310 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000311
312 register long
313 i,
314 u,
315 v;
316
317 gamma=0.0;
318 i=(long) (width*QuantumScale*PixelIntensity(r)+0.5);
319 if (i < 0)
320 i=0;
321 else
322 if (i > (long) width)
323 i=(long) width;
324 if ((i & 0x01) != 0)
325 i--;
326 p=GetCacheViewVirtualPixels(image_view,x-((long) (width-i)/2L),y-(long)
327 ((width-i)/2L),width-i,width-i,exception);
328 if (p == (const PixelPacket *) NULL)
329 break;
330 indexes=GetCacheViewVirtualIndexQueue(image_view);
cristyddd82202009-11-03 20:14:50 +0000331 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +0000332 k=kernel[i];
333 for (v=0; v < (long) (width-i); v++)
334 {
335 for (u=0; u < (long) (width-i); u++)
336 {
337 alpha=1.0;
338 if (((channel & OpacityChannel) != 0) &&
339 (image->matte != MagickFalse))
cristyc4c8d132010-01-07 01:58:38 +0000340 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
341 GetOpacitySample(p)));
cristy3ed852e2009-09-05 21:47:34 +0000342 if ((channel & RedChannel) != 0)
cristyc4c8d132010-01-07 01:58:38 +0000343 pixel.red+=(*k)*alpha*GetRedSample(p);
cristy3ed852e2009-09-05 21:47:34 +0000344 if ((channel & GreenChannel) != 0)
cristyc4c8d132010-01-07 01:58:38 +0000345 pixel.green+=(*k)*alpha*GetGreenSample(p);
cristy3ed852e2009-09-05 21:47:34 +0000346 if ((channel & BlueChannel) != 0)
cristyc4c8d132010-01-07 01:58:38 +0000347 pixel.blue+=(*k)*alpha*GetBlueSample(p);
cristy3ed852e2009-09-05 21:47:34 +0000348 if ((channel & OpacityChannel) != 0)
cristyc4c8d132010-01-07 01:58:38 +0000349 pixel.opacity+=(*k)*GetOpacitySample(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)
cristyc4c8d132010-01-07 01:58:38 +0000360 q->red=RoundToQuantum(gamma*GetRedSample(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000361 if ((channel & GreenChannel) != 0)
cristyc4c8d132010-01-07 01:58:38 +0000362 q->green=RoundToQuantum(gamma*GetGreenSample(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000363 if ((channel & BlueChannel) != 0)
cristyc4c8d132010-01-07 01:58:38 +0000364 q->blue=RoundToQuantum(gamma*GetBlueSample(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000365 if ((channel & OpacityChannel) != 0)
cristyddd82202009-11-03 20:14:50 +0000366 q->opacity=RoundToQuantum(pixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +0000367 if (((channel & IndexChannel) != 0) &&
368 (image->colorspace == CMYKColorspace))
cristyc4c8d132010-01-07 01:58:38 +0000369 blur_indexes[x]=RoundToQuantum(gamma*GetIndexSample(&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))
cristyc4c8d132010-01-07 01:58:38 +0000656 alpha=(MagickRealType) (QuantumScale*(QuantumRange-GetOpacitySample(p)));
cristy3ed852e2009-09-05 21:47:34 +0000657 if ((channel & RedChannel) != 0)
cristyc4c8d132010-01-07 01:58:38 +0000658 pixel.red+=(*k)*alpha*GetRedSample(p);
cristy3ed852e2009-09-05 21:47:34 +0000659 if ((channel & GreenChannel) != 0)
cristyc4c8d132010-01-07 01:58:38 +0000660 pixel.green+=(*k)*alpha*GetGreenSample(p);
cristy3ed852e2009-09-05 21:47:34 +0000661 if ((channel & BlueChannel) != 0)
cristyc4c8d132010-01-07 01:58:38 +0000662 pixel.blue+=(*k)*alpha*GetBlueSample(p);
cristy3ed852e2009-09-05 21:47:34 +0000663 if ((channel & OpacityChannel) != 0)
cristyc4c8d132010-01-07 01:58:38 +0000664 pixel.opacity+=(*k)*GetOpacitySample(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)
cristyc4c8d132010-01-07 01:58:38 +0000675 q->red=RoundToQuantum(gamma*GetRedSample(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000676 if ((channel & GreenChannel) != 0)
cristyc4c8d132010-01-07 01:58:38 +0000677 q->green=RoundToQuantum(gamma*GetGreenSample(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000678 if ((channel & BlueChannel) != 0)
cristyc4c8d132010-01-07 01:58:38 +0000679 q->blue=RoundToQuantum(gamma*GetBlueSample(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000680 if ((channel & OpacityChannel) != 0)
cristyddd82202009-11-03 20:14:50 +0000681 q->opacity=RoundToQuantum(pixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +0000682 if (((channel & IndexChannel) != 0) &&
683 (image->colorspace == CMYKColorspace))
cristyc4c8d132010-01-07 01:58:38 +0000684 sharp_indexes[x]=RoundToQuantum(gamma*GetIndexSample(&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);
cristy8cd5b312010-01-07 01:10:24 +0000882 (void) FormatMagickString(format,MaxTextExtent,"%.15g ",*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)
cristyddd82202009-11-03 20:14:50 +0000958 q->red=RoundToQuantum(pixel.red);
cristy3ed852e2009-09-05 21:47:34 +0000959 if ((channel & GreenChannel) != 0)
cristyddd82202009-11-03 20:14:50 +0000960 q->green=RoundToQuantum(pixel.green);
cristy3ed852e2009-09-05 21:47:34 +0000961 if ((channel & BlueChannel) != 0)
cristyddd82202009-11-03 20:14:50 +0000962 q->blue=RoundToQuantum(pixel.blue);
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 }
cristyddd82202009-11-03 20:14:50 +0000973 q->opacity=RoundToQuantum(pixel.opacity);
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 }
cristyddd82202009-11-03 20:14:50 +0000989 blur_indexes[x]=RoundToQuantum(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 {
1001 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
1002 kernel_pixels->opacity));
1003 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)
cristyc4c8d132010-01-07 01:58:38 +00001012 q->red=RoundToQuantum(gamma*GetRedSample(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001013 if ((channel & GreenChannel) != 0)
cristyc4c8d132010-01-07 01:58:38 +00001014 q->green=RoundToQuantum(gamma*GetGreenSample(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001015 if ((channel & BlueChannel) != 0)
cristyc4c8d132010-01-07 01:58:38 +00001016 q->blue=RoundToQuantum(gamma*GetBlueSample(&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 }
cristyddd82202009-11-03 20:14:50 +00001027 q->opacity=RoundToQuantum(pixel.opacity);
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 {
1040 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
1041 kernel_pixels->opacity));
1042 pixel.index+=(*k)*alpha*(*kernel_indexes);
1043 k++;
1044 kernel_pixels++;
1045 kernel_indexes++;
1046 }
cristyc4c8d132010-01-07 01:58:38 +00001047 blur_indexes[x]=RoundToQuantum(gamma*GetIndexSample(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001048 }
1049 }
1050 p++;
1051 q++;
1052 }
1053 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1054 status=MagickFalse;
1055 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1056 {
1057 MagickBooleanType
1058 proceed;
1059
cristyb5d5f722009-11-04 03:03:49 +00001060#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001061 #pragma omp critical (MagickCore_BlurImageChannel)
1062#endif
1063 proceed=SetImageProgress(image,BlurImageTag,progress++,blur_image->rows+
1064 blur_image->columns);
1065 if (proceed == MagickFalse)
1066 status=MagickFalse;
1067 }
1068 }
1069 blur_view=DestroyCacheView(blur_view);
1070 image_view=DestroyCacheView(image_view);
1071 /*
1072 Blur columns.
1073 */
1074 image_view=AcquireCacheView(blur_image);
1075 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00001076#if defined(MAGICKCORE_OPENMP_SUPPORT)
1077 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001078#endif
1079 for (x=0; x < (long) blur_image->columns; x++)
1080 {
1081 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001082 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001083
1084 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001085 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001086
1087 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001088 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001089
1090 register long
1091 y;
1092
1093 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001094 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001095
1096 if (status == MagickFalse)
1097 continue;
1098 p=GetCacheViewVirtualPixels(image_view,x,-((long) width/2L),1,image->rows+
1099 width,exception);
1100 q=GetCacheViewAuthenticPixels(blur_view,x,0,1,blur_image->rows,exception);
1101 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1102 {
1103 status=MagickFalse;
1104 continue;
1105 }
1106 indexes=GetCacheViewVirtualIndexQueue(image_view);
1107 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
1108 for (y=0; y < (long) blur_image->rows; y++)
1109 {
1110 MagickPixelPacket
1111 pixel;
1112
1113 register const double
cristyc47d1f82009-11-26 01:44:43 +00001114 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00001115
1116 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001117 *restrict kernel_pixels;
cristy3ed852e2009-09-05 21:47:34 +00001118
1119 register long
1120 i;
1121
cristyddd82202009-11-03 20:14:50 +00001122 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00001123 k=kernel;
1124 kernel_pixels=p;
1125 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
1126 {
1127 for (i=0; i < (long) width; i++)
1128 {
1129 pixel.red+=(*k)*kernel_pixels->red;
1130 pixel.green+=(*k)*kernel_pixels->green;
1131 pixel.blue+=(*k)*kernel_pixels->blue;
1132 k++;
1133 kernel_pixels++;
1134 }
1135 if ((channel & RedChannel) != 0)
cristyddd82202009-11-03 20:14:50 +00001136 q->red=RoundToQuantum(pixel.red);
cristy3ed852e2009-09-05 21:47:34 +00001137 if ((channel & GreenChannel) != 0)
cristyddd82202009-11-03 20:14:50 +00001138 q->green=RoundToQuantum(pixel.green);
cristy3ed852e2009-09-05 21:47:34 +00001139 if ((channel & BlueChannel) != 0)
cristyddd82202009-11-03 20:14:50 +00001140 q->blue=RoundToQuantum(pixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00001141 if ((channel & OpacityChannel) != 0)
1142 {
1143 k=kernel;
1144 kernel_pixels=p;
1145 for (i=0; i < (long) width; i++)
1146 {
1147 pixel.opacity+=(*k)*kernel_pixels->opacity;
1148 k++;
1149 kernel_pixels++;
1150 }
cristyddd82202009-11-03 20:14:50 +00001151 q->opacity=RoundToQuantum(pixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00001152 }
1153 if (((channel & IndexChannel) != 0) &&
1154 (image->colorspace == CMYKColorspace))
1155 {
1156 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001157 *restrict kernel_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001158
1159 k=kernel;
1160 kernel_indexes=indexes;
1161 for (i=0; i < (long) width; i++)
1162 {
1163 pixel.index+=(*k)*(*kernel_indexes);
1164 k++;
1165 kernel_indexes++;
1166 }
cristyddd82202009-11-03 20:14:50 +00001167 blur_indexes[y]=RoundToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00001168 }
1169 }
1170 else
1171 {
1172 MagickRealType
1173 alpha,
1174 gamma;
1175
1176 gamma=0.0;
1177 for (i=0; i < (long) width; i++)
1178 {
1179 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
1180 kernel_pixels->opacity));
1181 pixel.red+=(*k)*alpha*kernel_pixels->red;
1182 pixel.green+=(*k)*alpha*kernel_pixels->green;
1183 pixel.blue+=(*k)*alpha*kernel_pixels->blue;
1184 gamma+=(*k)*alpha;
1185 k++;
1186 kernel_pixels++;
1187 }
1188 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1189 if ((channel & RedChannel) != 0)
cristyc4c8d132010-01-07 01:58:38 +00001190 q->red=RoundToQuantum(gamma*GetRedSample(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001191 if ((channel & GreenChannel) != 0)
cristyc4c8d132010-01-07 01:58:38 +00001192 q->green=RoundToQuantum(gamma*GetGreenSample(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001193 if ((channel & BlueChannel) != 0)
cristyc4c8d132010-01-07 01:58:38 +00001194 q->blue=RoundToQuantum(gamma*GetBlueSample(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001195 if ((channel & OpacityChannel) != 0)
1196 {
1197 k=kernel;
1198 kernel_pixels=p;
1199 for (i=0; i < (long) width; i++)
1200 {
1201 pixel.opacity+=(*k)*kernel_pixels->opacity;
1202 k++;
1203 kernel_pixels++;
1204 }
cristyddd82202009-11-03 20:14:50 +00001205 q->opacity=RoundToQuantum(pixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00001206 }
1207 if (((channel & IndexChannel) != 0) &&
1208 (image->colorspace == CMYKColorspace))
1209 {
1210 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001211 *restrict kernel_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001212
1213 k=kernel;
1214 kernel_pixels=p;
1215 kernel_indexes=indexes;
1216 for (i=0; i < (long) width; i++)
1217 {
1218 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
1219 kernel_pixels->opacity));
1220 pixel.index+=(*k)*alpha*(*kernel_indexes);
1221 k++;
1222 kernel_pixels++;
1223 kernel_indexes++;
1224 }
cristyc4c8d132010-01-07 01:58:38 +00001225 blur_indexes[y]=RoundToQuantum(gamma*GetIndexSample(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001226 }
1227 }
1228 p++;
1229 q++;
1230 }
1231 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1232 status=MagickFalse;
1233 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1234 {
1235 MagickBooleanType
1236 proceed;
1237
cristyb5d5f722009-11-04 03:03:49 +00001238#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001239 #pragma omp critical (MagickCore_BlurImageChannel)
1240#endif
1241 proceed=SetImageProgress(image,BlurImageTag,progress++,blur_image->rows+
1242 blur_image->columns);
1243 if (proceed == MagickFalse)
1244 status=MagickFalse;
1245 }
1246 }
1247 blur_view=DestroyCacheView(blur_view);
1248 image_view=DestroyCacheView(image_view);
1249 kernel=(double *) RelinquishMagickMemory(kernel);
1250 if (status == MagickFalse)
1251 blur_image=DestroyImage(blur_image);
1252 blur_image->type=image->type;
1253 return(blur_image);
1254}
1255
1256/*
1257%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1258% %
1259% %
1260% %
cristyfccdab92009-11-30 16:43:57 +00001261% C o n v o l v e I m a g e %
1262% %
1263% %
1264% %
1265%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1266%
1267% ConvolveImage() applies a custom convolution kernel to the image.
1268%
1269% The format of the ConvolveImage method is:
1270%
1271% Image *ConvolveImage(const Image *image,const unsigned long order,
1272% const double *kernel,ExceptionInfo *exception)
1273% Image *ConvolveImageChannel(const Image *image,const ChannelType channel,
1274% const unsigned long order,const double *kernel,
1275% ExceptionInfo *exception)
1276%
1277% A description of each parameter follows:
1278%
1279% o image: the image.
1280%
1281% o channel: the channel type.
1282%
1283% o order: the number of columns and rows in the filter kernel.
1284%
1285% o kernel: An array of double representing the convolution kernel.
1286%
1287% o exception: return any errors or warnings in this structure.
1288%
1289*/
1290
1291MagickExport Image *ConvolveImage(const Image *image,const unsigned long order,
1292 const double *kernel,ExceptionInfo *exception)
1293{
1294 Image
1295 *convolve_image;
1296
1297 convolve_image=ConvolveImageChannel(image,DefaultChannels,order,kernel,
1298 exception);
1299 return(convolve_image);
1300}
1301
1302MagickExport Image *ConvolveImageChannel(const Image *image,
1303 const ChannelType channel,const unsigned long order,const double *kernel,
1304 ExceptionInfo *exception)
1305{
1306#define ConvolveImageTag "Convolve/Image"
1307
cristyc4c8d132010-01-07 01:58:38 +00001308 CacheView
1309 *convolve_view,
1310 *image_view;
1311
cristyfccdab92009-11-30 16:43:57 +00001312 double
1313 *normal_kernel;
1314
1315 Image
1316 *convolve_image;
1317
1318 long
1319 progress,
1320 y;
1321
1322 MagickBooleanType
1323 status;
1324
1325 MagickPixelPacket
1326 bias;
1327
1328 MagickRealType
1329 gamma;
1330
1331 register long
1332 i;
1333
1334 unsigned long
1335 width;
1336
cristyfccdab92009-11-30 16:43:57 +00001337 /*
1338 Initialize convolve image attributes.
1339 */
1340 assert(image != (Image *) NULL);
1341 assert(image->signature == MagickSignature);
1342 if (image->debug != MagickFalse)
1343 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1344 assert(exception != (ExceptionInfo *) NULL);
1345 assert(exception->signature == MagickSignature);
1346 width=order;
1347 if ((width % 2) == 0)
1348 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
1349 convolve_image=CloneImage(image,0,0,MagickTrue,exception);
1350 if (convolve_image == (Image *) NULL)
1351 return((Image *) NULL);
1352 if (SetImageStorageClass(convolve_image,DirectClass) == MagickFalse)
1353 {
1354 InheritException(exception,&convolve_image->exception);
1355 convolve_image=DestroyImage(convolve_image);
1356 return((Image *) NULL);
1357 }
1358 if (image->debug != MagickFalse)
1359 {
1360 char
1361 format[MaxTextExtent],
1362 *message;
1363
1364 long
1365 u,
1366 v;
1367
1368 register const double
1369 *k;
1370
1371 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
1372 " ConvolveImage with %ldx%ld kernel:",width,width);
1373 message=AcquireString("");
1374 k=kernel;
1375 for (v=0; v < (long) width; v++)
1376 {
1377 *message='\0';
1378 (void) FormatMagickString(format,MaxTextExtent,"%ld: ",v);
1379 (void) ConcatenateString(&message,format);
1380 for (u=0; u < (long) width; u++)
1381 {
cristy8cd5b312010-01-07 01:10:24 +00001382 (void) FormatMagickString(format,MaxTextExtent,"%.15g ",*k++);
cristyfccdab92009-11-30 16:43:57 +00001383 (void) ConcatenateString(&message,format);
1384 }
1385 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
1386 }
1387 message=DestroyString(message);
1388 }
1389 /*
1390 Normalize kernel.
1391 */
1392 normal_kernel=(double *) AcquireQuantumMemory(width*width,
1393 sizeof(*normal_kernel));
1394 if (normal_kernel == (double *) NULL)
1395 {
1396 convolve_image=DestroyImage(convolve_image);
1397 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1398 }
1399 gamma=0.0;
1400 for (i=0; i < (long) (width*width); i++)
1401 gamma+=kernel[i];
1402 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1403 for (i=0; i < (long) (width*width); i++)
1404 normal_kernel[i]=gamma*kernel[i];
1405 /*
1406 Convolve image.
1407 */
1408 status=MagickTrue;
1409 progress=0;
1410 GetMagickPixelPacket(image,&bias);
1411 SetMagickPixelPacketBias(image,&bias);
1412 image_view=AcquireCacheView(image);
1413 convolve_view=AcquireCacheView(convolve_image);
1414#if defined(MAGICKCORE_OPENMP_SUPPORT)
1415 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1416#endif
1417 for (y=0; y < (long) image->rows; y++)
1418 {
1419 MagickBooleanType
1420 sync;
1421
1422 register const IndexPacket
1423 *restrict indexes;
1424
1425 register const PixelPacket
1426 *restrict p;
1427
1428 register IndexPacket
1429 *restrict convolve_indexes;
1430
1431 register long
1432 x;
1433
1434 register PixelPacket
1435 *restrict q;
1436
1437 if (status == MagickFalse)
1438 continue;
1439 p=GetCacheViewVirtualPixels(image_view,-((long) width/2L),y-(long) (width/
1440 2L),image->columns+width,width,exception);
1441 q=GetCacheViewAuthenticPixels(convolve_view,0,y,convolve_image->columns,1,
1442 exception);
1443 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1444 {
1445 status=MagickFalse;
1446 continue;
1447 }
1448 indexes=GetCacheViewVirtualIndexQueue(image_view);
1449 convolve_indexes=GetCacheViewAuthenticIndexQueue(convolve_view);
1450 for (x=0; x < (long) image->columns; x++)
1451 {
1452 long
1453 v;
1454
1455 MagickPixelPacket
1456 pixel;
1457
1458 register const double
1459 *restrict k;
1460
1461 register const PixelPacket
1462 *restrict kernel_pixels;
1463
1464 register long
1465 u;
1466
1467 pixel=bias;
1468 k=normal_kernel;
1469 kernel_pixels=p;
1470 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
1471 {
1472 for (v=0; v < (long) width; v++)
1473 {
1474 for (u=0; u < (long) width; u++)
1475 {
1476 pixel.red+=(*k)*kernel_pixels[u].red;
1477 pixel.green+=(*k)*kernel_pixels[u].green;
1478 pixel.blue+=(*k)*kernel_pixels[u].blue;
1479 k++;
1480 }
1481 kernel_pixels+=image->columns+width;
1482 }
1483 if ((channel & RedChannel) != 0)
1484 q->red=RoundToQuantum(pixel.red);
1485 if ((channel & GreenChannel) != 0)
1486 q->green=RoundToQuantum(pixel.green);
1487 if ((channel & BlueChannel) != 0)
1488 q->blue=RoundToQuantum(pixel.blue);
1489 if ((channel & OpacityChannel) != 0)
1490 {
1491 k=normal_kernel;
1492 kernel_pixels=p;
1493 for (v=0; v < (long) width; v++)
1494 {
1495 for (u=0; u < (long) width; u++)
1496 {
1497 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
1498 k++;
1499 }
1500 kernel_pixels+=image->columns+width;
1501 }
1502 q->opacity=RoundToQuantum(pixel.opacity);
1503 }
1504 if (((channel & IndexChannel) != 0) &&
1505 (image->colorspace == CMYKColorspace))
1506 {
1507 register const IndexPacket
1508 *restrict kernel_indexes;
1509
1510 k=normal_kernel;
1511 kernel_indexes=indexes;
1512 for (v=0; v < (long) width; v++)
1513 {
1514 for (u=0; u < (long) width; u++)
1515 {
1516 pixel.index+=(*k)*kernel_indexes[u];
1517 k++;
1518 }
1519 kernel_indexes+=image->columns+width;
1520 }
1521 convolve_indexes[x]=RoundToQuantum(pixel.index);
1522 }
1523 }
1524 else
1525 {
1526 MagickRealType
1527 alpha,
1528 gamma;
1529
1530 gamma=0.0;
1531 for (v=0; v < (long) width; v++)
1532 {
1533 for (u=0; u < (long) width; u++)
1534 {
1535 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
1536 kernel_pixels[u].opacity));
1537 pixel.red+=(*k)*alpha*kernel_pixels[u].red;
1538 pixel.green+=(*k)*alpha*kernel_pixels[u].green;
1539 pixel.blue+=(*k)*alpha*kernel_pixels[u].blue;
cristyfccdab92009-11-30 16:43:57 +00001540 gamma+=(*k)*alpha;
1541 k++;
1542 }
1543 kernel_pixels+=image->columns+width;
1544 }
1545 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1546 if ((channel & RedChannel) != 0)
cristyc4c8d132010-01-07 01:58:38 +00001547 q->red=RoundToQuantum(gamma*GetRedSample(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001548 if ((channel & GreenChannel) != 0)
cristyc4c8d132010-01-07 01:58:38 +00001549 q->green=RoundToQuantum(gamma*GetGreenSample(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001550 if ((channel & BlueChannel) != 0)
cristyc4c8d132010-01-07 01:58:38 +00001551 q->blue=RoundToQuantum(gamma*GetBlueSample(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001552 if ((channel & OpacityChannel) != 0)
1553 {
1554 k=normal_kernel;
1555 kernel_pixels=p;
1556 for (v=0; v < (long) width; v++)
1557 {
1558 for (u=0; u < (long) width; u++)
1559 {
1560 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
1561 k++;
1562 }
1563 kernel_pixels+=image->columns+width;
1564 }
1565 q->opacity=RoundToQuantum(pixel.opacity);
1566 }
1567 if (((channel & IndexChannel) != 0) &&
1568 (image->colorspace == CMYKColorspace))
1569 {
1570 register const IndexPacket
1571 *restrict kernel_indexes;
1572
1573 k=normal_kernel;
1574 kernel_pixels=p;
1575 kernel_indexes=indexes;
1576 for (v=0; v < (long) width; v++)
1577 {
1578 for (u=0; u < (long) width; u++)
1579 {
1580 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
1581 kernel_pixels[u].opacity));
1582 pixel.index+=(*k)*alpha*kernel_indexes[u];
1583 k++;
1584 }
1585 kernel_pixels+=image->columns+width;
1586 kernel_indexes+=image->columns+width;
1587 }
cristyc4c8d132010-01-07 01:58:38 +00001588 convolve_indexes[x]=RoundToQuantum(gamma*GetIndexSample(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001589 }
1590 }
1591 p++;
1592 q++;
1593 }
1594 sync=SyncCacheViewAuthenticPixels(convolve_view,exception);
1595 if (sync == MagickFalse)
1596 status=MagickFalse;
1597 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1598 {
1599 MagickBooleanType
1600 proceed;
1601
1602#if defined(MAGICKCORE_OPENMP_SUPPORT)
1603 #pragma omp critical (MagickCore_ConvolveImageChannel)
1604#endif
1605 proceed=SetImageProgress(image,ConvolveImageTag,progress++,image->rows);
1606 if (proceed == MagickFalse)
1607 status=MagickFalse;
1608 }
1609 }
1610 convolve_image->type=image->type;
1611 convolve_view=DestroyCacheView(convolve_view);
1612 image_view=DestroyCacheView(image_view);
1613 normal_kernel=(double *) RelinquishMagickMemory(normal_kernel);
1614 if (status == MagickFalse)
1615 convolve_image=DestroyImage(convolve_image);
1616 return(convolve_image);
1617}
1618
1619/*
1620%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1621% %
1622% %
1623% %
cristy3ed852e2009-09-05 21:47:34 +00001624% D e s p e c k l e I m a g e %
1625% %
1626% %
1627% %
1628%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1629%
1630% DespeckleImage() reduces the speckle noise in an image while perserving the
1631% edges of the original image.
1632%
1633% The format of the DespeckleImage method is:
1634%
1635% Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1636%
1637% A description of each parameter follows:
1638%
1639% o image: the image.
1640%
1641% o exception: return any errors or warnings in this structure.
1642%
1643*/
1644
1645static Quantum **DestroyPixelThreadSet(Quantum **pixels)
1646{
1647 register long
1648 i;
1649
1650 assert(pixels != (Quantum **) NULL);
1651 for (i=0; i < (long) GetOpenMPMaximumThreads(); i++)
1652 if (pixels[i] != (Quantum *) NULL)
1653 pixels[i]=(Quantum *) RelinquishMagickMemory(pixels[i]);
1654 pixels=(Quantum **) RelinquishAlignedMemory(pixels);
1655 return(pixels);
1656}
1657
1658static Quantum **AcquirePixelThreadSet(const size_t count)
1659{
1660 register long
1661 i;
1662
1663 Quantum
1664 **pixels;
1665
1666 unsigned long
1667 number_threads;
1668
1669 number_threads=GetOpenMPMaximumThreads();
1670 pixels=(Quantum **) AcquireAlignedMemory(number_threads,sizeof(*pixels));
1671 if (pixels == (Quantum **) NULL)
1672 return((Quantum **) NULL);
1673 (void) ResetMagickMemory(pixels,0,number_threads*sizeof(*pixels));
1674 for (i=0; i < (long) number_threads; i++)
1675 {
1676 pixels[i]=(Quantum *) AcquireQuantumMemory(count,sizeof(**pixels));
1677 if (pixels[i] == (Quantum *) NULL)
1678 return(DestroyPixelThreadSet(pixels));
1679 }
1680 return(pixels);
1681}
1682
1683static void Hull(const long x_offset,const long y_offset,
1684 const unsigned long columns,const unsigned long rows,Quantum *f,Quantum *g,
1685 const int polarity)
1686{
1687 long
1688 y;
1689
1690 MagickRealType
1691 v;
1692
1693 register long
1694 x;
1695
1696 register Quantum
1697 *p,
1698 *q,
1699 *r,
1700 *s;
1701
1702 assert(f != (Quantum *) NULL);
1703 assert(g != (Quantum *) NULL);
1704 p=f+(columns+2);
1705 q=g+(columns+2);
1706 r=p+(y_offset*((long) columns+2)+x_offset);
1707 for (y=0; y < (long) rows; y++)
1708 {
1709 p++;
1710 q++;
1711 r++;
1712 if (polarity > 0)
1713 for (x=(long) columns; x != 0; x--)
1714 {
1715 v=(MagickRealType) (*p);
1716 if ((MagickRealType) *r >= (v+(MagickRealType) ScaleCharToQuantum(2)))
1717 v+=ScaleCharToQuantum(1);
1718 *q=(Quantum) v;
1719 p++;
1720 q++;
1721 r++;
1722 }
1723 else
1724 for (x=(long) columns; x != 0; x--)
1725 {
1726 v=(MagickRealType) (*p);
1727 if ((MagickRealType) *r <= (v-(MagickRealType) ScaleCharToQuantum(2)))
1728 v-=(long) ScaleCharToQuantum(1);
1729 *q=(Quantum) v;
1730 p++;
1731 q++;
1732 r++;
1733 }
1734 p++;
1735 q++;
1736 r++;
1737 }
1738 p=f+(columns+2);
1739 q=g+(columns+2);
1740 r=q+(y_offset*((long) columns+2)+x_offset);
1741 s=q-(y_offset*((long) columns+2)+x_offset);
1742 for (y=0; y < (long) rows; y++)
1743 {
1744 p++;
1745 q++;
1746 r++;
1747 s++;
1748 if (polarity > 0)
1749 for (x=(long) columns; x != 0; x--)
1750 {
1751 v=(MagickRealType) (*q);
1752 if (((MagickRealType) *s >=
1753 (v+(MagickRealType) ScaleCharToQuantum(2))) &&
1754 ((MagickRealType) *r > v))
1755 v+=ScaleCharToQuantum(1);
1756 *p=(Quantum) v;
1757 p++;
1758 q++;
1759 r++;
1760 s++;
1761 }
1762 else
1763 for (x=(long) columns; x != 0; x--)
1764 {
1765 v=(MagickRealType) (*q);
1766 if (((MagickRealType) *s <=
1767 (v-(MagickRealType) ScaleCharToQuantum(2))) &&
1768 ((MagickRealType) *r < v))
1769 v-=(MagickRealType) ScaleCharToQuantum(1);
1770 *p=(Quantum) v;
1771 p++;
1772 q++;
1773 r++;
1774 s++;
1775 }
1776 p++;
1777 q++;
1778 r++;
1779 s++;
1780 }
1781}
1782
1783MagickExport Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1784{
1785#define DespeckleImageTag "Despeckle/Image"
1786
cristy2407fc22009-09-11 00:55:25 +00001787 CacheView
1788 *despeckle_view,
1789 *image_view;
1790
cristy3ed852e2009-09-05 21:47:34 +00001791 Image
1792 *despeckle_image;
1793
1794 long
1795 channel;
1796
1797 MagickBooleanType
1798 status;
1799
1800 Quantum
cristyfa112112010-01-04 17:48:07 +00001801 **restrict buffers,
1802 **restrict pixels;
cristy3ed852e2009-09-05 21:47:34 +00001803
1804 size_t
1805 length;
1806
1807 static const int
cristy691a29e2009-09-11 00:44:10 +00001808 X[4] = {0, 1, 1,-1},
1809 Y[4] = {1, 0, 1, 1};
cristy3ed852e2009-09-05 21:47:34 +00001810
cristy3ed852e2009-09-05 21:47:34 +00001811 /*
1812 Allocate despeckled image.
1813 */
1814 assert(image != (const Image *) NULL);
1815 assert(image->signature == MagickSignature);
1816 if (image->debug != MagickFalse)
1817 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1818 assert(exception != (ExceptionInfo *) NULL);
1819 assert(exception->signature == MagickSignature);
1820 despeckle_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1821 exception);
1822 if (despeckle_image == (Image *) NULL)
1823 return((Image *) NULL);
1824 if (SetImageStorageClass(despeckle_image,DirectClass) == MagickFalse)
1825 {
1826 InheritException(exception,&despeckle_image->exception);
1827 despeckle_image=DestroyImage(despeckle_image);
1828 return((Image *) NULL);
1829 }
1830 /*
1831 Allocate image buffers.
1832 */
1833 length=(size_t) ((image->columns+2)*(image->rows+2));
1834 pixels=AcquirePixelThreadSet(length);
1835 buffers=AcquirePixelThreadSet(length);
1836 if ((pixels == (Quantum **) NULL) || (buffers == (Quantum **) NULL))
1837 {
1838 if (buffers != (Quantum **) NULL)
1839 buffers=DestroyPixelThreadSet(buffers);
1840 if (pixels != (Quantum **) NULL)
1841 pixels=DestroyPixelThreadSet(pixels);
1842 despeckle_image=DestroyImage(despeckle_image);
1843 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1844 }
1845 /*
1846 Reduce speckle in the image.
1847 */
1848 status=MagickTrue;
1849 image_view=AcquireCacheView(image);
1850 despeckle_view=AcquireCacheView(despeckle_image);
cristyb5d5f722009-11-04 03:03:49 +00001851#if defined(MAGICKCORE_OPENMP_SUPPORT)
1852 #pragma omp parallel for schedule(dynamic,4) shared(status)
cristy3ed852e2009-09-05 21:47:34 +00001853#endif
1854 for (channel=0; channel <= 3; channel++)
1855 {
1856 long
1857 j,
1858 y;
1859
1860 register long
1861 i,
cristy691a29e2009-09-11 00:44:10 +00001862 id,
cristy3ed852e2009-09-05 21:47:34 +00001863 x;
1864
1865 register Quantum
1866 *buffer,
1867 *pixel;
1868
1869 if (status == MagickFalse)
1870 continue;
cristy691a29e2009-09-11 00:44:10 +00001871 id=GetOpenMPThreadId();
1872 pixel=pixels[id];
cristy3ed852e2009-09-05 21:47:34 +00001873 (void) ResetMagickMemory(pixel,0,length*sizeof(*pixel));
cristy691a29e2009-09-11 00:44:10 +00001874 buffer=buffers[id];
cristy3ed852e2009-09-05 21:47:34 +00001875 j=(long) image->columns+2;
1876 for (y=0; y < (long) image->rows; y++)
1877 {
1878 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001879 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001880
1881 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1882 if (p == (const PixelPacket *) NULL)
1883 break;
1884 j++;
1885 for (x=0; x < (long) image->columns; x++)
1886 {
1887 switch (channel)
1888 {
cristyc4c8d132010-01-07 01:58:38 +00001889 case 0: pixel[j]=GetRedSample(p); break;
1890 case 1: pixel[j]=GetGreenSample(p); break;
1891 case 2: pixel[j]=GetBlueSample(p); break;
1892 case 3: pixel[j]=GetOpacitySample(p); break;
cristy3ed852e2009-09-05 21:47:34 +00001893 default: break;
1894 }
1895 p++;
1896 j++;
1897 }
1898 j++;
1899 }
cristy3ed852e2009-09-05 21:47:34 +00001900 (void) ResetMagickMemory(buffer,0,length*sizeof(*buffer));
1901 for (i=0; i < 4; i++)
1902 {
1903 Hull(X[i],Y[i],image->columns,image->rows,pixel,buffer,1);
1904 Hull(-X[i],-Y[i],image->columns,image->rows,pixel,buffer,1);
1905 Hull(-X[i],-Y[i],image->columns,image->rows,pixel,buffer,-1);
1906 Hull(X[i],Y[i],image->columns,image->rows,pixel,buffer,-1);
1907 }
1908 j=(long) image->columns+2;
1909 for (y=0; y < (long) image->rows; y++)
1910 {
1911 MagickBooleanType
1912 sync;
1913
1914 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001915 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001916
1917 q=GetCacheViewAuthenticPixels(despeckle_view,0,y,despeckle_image->columns,
1918 1,exception);
1919 if (q == (PixelPacket *) NULL)
1920 break;
1921 j++;
1922 for (x=0; x < (long) image->columns; x++)
1923 {
1924 switch (channel)
1925 {
1926 case 0: q->red=pixel[j]; break;
1927 case 1: q->green=pixel[j]; break;
1928 case 2: q->blue=pixel[j]; break;
1929 case 3: q->opacity=pixel[j]; break;
1930 default: break;
1931 }
1932 q++;
1933 j++;
1934 }
1935 sync=SyncCacheViewAuthenticPixels(despeckle_view,exception);
1936 if (sync == MagickFalse)
1937 {
1938 status=MagickFalse;
1939 break;
1940 }
1941 j++;
1942 }
1943 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1944 {
1945 MagickBooleanType
1946 proceed;
1947
cristyb5d5f722009-11-04 03:03:49 +00001948#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001949 #pragma omp critical (MagickCore_DespeckleImage)
1950#endif
1951 proceed=SetImageProgress(image,DespeckleImageTag,channel,3);
1952 if (proceed == MagickFalse)
1953 status=MagickFalse;
1954 }
1955 }
1956 despeckle_view=DestroyCacheView(despeckle_view);
1957 image_view=DestroyCacheView(image_view);
1958 buffers=DestroyPixelThreadSet(buffers);
1959 pixels=DestroyPixelThreadSet(pixels);
1960 despeckle_image->type=image->type;
1961 if (status == MagickFalse)
1962 despeckle_image=DestroyImage(despeckle_image);
1963 return(despeckle_image);
1964}
1965
1966/*
1967%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1968% %
1969% %
1970% %
1971% E d g e I m a g e %
1972% %
1973% %
1974% %
1975%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1976%
1977% EdgeImage() finds edges in an image. Radius defines the radius of the
1978% convolution filter. Use a radius of 0 and EdgeImage() selects a suitable
1979% radius for you.
1980%
1981% The format of the EdgeImage method is:
1982%
1983% Image *EdgeImage(const Image *image,const double radius,
1984% ExceptionInfo *exception)
1985%
1986% A description of each parameter follows:
1987%
1988% o image: the image.
1989%
1990% o radius: the radius of the pixel neighborhood.
1991%
1992% o exception: return any errors or warnings in this structure.
1993%
1994*/
1995MagickExport Image *EdgeImage(const Image *image,const double radius,
1996 ExceptionInfo *exception)
1997{
1998 Image
1999 *edge_image;
2000
2001 double
2002 *kernel;
2003
2004 register long
2005 i;
2006
2007 unsigned long
2008 width;
2009
2010 assert(image != (const Image *) NULL);
2011 assert(image->signature == MagickSignature);
2012 if (image->debug != MagickFalse)
2013 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2014 assert(exception != (ExceptionInfo *) NULL);
2015 assert(exception->signature == MagickSignature);
2016 width=GetOptimalKernelWidth1D(radius,0.5);
2017 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2018 if (kernel == (double *) NULL)
2019 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2020 for (i=0; i < (long) (width*width); i++)
2021 kernel[i]=(-1.0);
2022 kernel[i/2]=(double) (width*width-1.0);
2023 edge_image=ConvolveImage(image,width,kernel,exception);
2024 kernel=(double *) RelinquishMagickMemory(kernel);
2025 return(edge_image);
2026}
2027
2028/*
2029%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2030% %
2031% %
2032% %
2033% E m b o s s I m a g e %
2034% %
2035% %
2036% %
2037%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2038%
2039% EmbossImage() returns a grayscale image with a three-dimensional effect.
2040% We convolve the image with a Gaussian operator of the given radius and
2041% standard deviation (sigma). For reasonable results, radius should be
2042% larger than sigma. Use a radius of 0 and Emboss() selects a suitable
2043% radius for you.
2044%
2045% The format of the EmbossImage method is:
2046%
2047% Image *EmbossImage(const Image *image,const double radius,
2048% const double sigma,ExceptionInfo *exception)
2049%
2050% A description of each parameter follows:
2051%
2052% o image: the image.
2053%
2054% o radius: the radius of the pixel neighborhood.
2055%
2056% o sigma: the standard deviation of the Gaussian, in pixels.
2057%
2058% o exception: return any errors or warnings in this structure.
2059%
2060*/
2061MagickExport Image *EmbossImage(const Image *image,const double radius,
2062 const double sigma,ExceptionInfo *exception)
2063{
2064 double
2065 *kernel;
2066
2067 Image
2068 *emboss_image;
2069
2070 long
cristy47e00502009-12-17 19:19:57 +00002071 j,
2072 k,
cristy3ed852e2009-09-05 21:47:34 +00002073 u,
2074 v;
2075
cristy47e00502009-12-17 19:19:57 +00002076 register long
2077 i;
2078
cristy3ed852e2009-09-05 21:47:34 +00002079 unsigned long
2080 width;
2081
2082 assert(image != (Image *) NULL);
2083 assert(image->signature == MagickSignature);
2084 if (image->debug != MagickFalse)
2085 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2086 assert(exception != (ExceptionInfo *) NULL);
2087 assert(exception->signature == MagickSignature);
2088 width=GetOptimalKernelWidth2D(radius,sigma);
2089 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2090 if (kernel == (double *) NULL)
2091 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy3ed852e2009-09-05 21:47:34 +00002092 j=(long) width/2;
cristy47e00502009-12-17 19:19:57 +00002093 k=j;
2094 i=0;
2095 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002096 {
cristy47e00502009-12-17 19:19:57 +00002097 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00002098 {
cristy47e00502009-12-17 19:19:57 +00002099 kernel[i]=((u < 0) || (v < 0) ? -8.0 : 8.0)*
2100 exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
2101 (2.0*MagickPI*MagickSigma*MagickSigma);
2102 if (u != k)
cristy3ed852e2009-09-05 21:47:34 +00002103 kernel[i]=0.0;
2104 i++;
2105 }
cristy47e00502009-12-17 19:19:57 +00002106 k--;
cristy3ed852e2009-09-05 21:47:34 +00002107 }
2108 emboss_image=ConvolveImage(image,width,kernel,exception);
2109 if (emboss_image != (Image *) NULL)
2110 (void) EqualizeImage(emboss_image);
2111 kernel=(double *) RelinquishMagickMemory(kernel);
2112 return(emboss_image);
2113}
2114
2115/*
2116%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2117% %
2118% %
2119% %
cristy56a9e512010-01-06 18:18:55 +00002120% F i l t e r I m a g e %
2121% %
2122% %
2123% %
2124%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2125%
2126% FilterImage() applies a custom convolution kernel to the image.
2127%
2128% The format of the FilterImage method is:
2129%
2130% Image *FilterImage(const Image *image,const MagickKernel *kernel,
2131% ExceptionInfo *exception)
2132% Image *FilterImageChannel(const Image *image,const ChannelType channel,
2133% const MagickKernel *kernel,ExceptionInfo *exception)
2134%
2135% A description of each parameter follows:
2136%
2137% o image: the image.
2138%
2139% o channel: the channel type.
2140%
2141% o kernel: the filtering kernel.
2142%
2143% o exception: return any errors or warnings in this structure.
2144%
2145*/
2146
2147MagickExport Image *FilterImage(const Image *image,const MagickKernel *kernel,
2148 ExceptionInfo *exception)
2149{
2150 Image
2151 *filter_image;
2152
2153 filter_image=FilterImageChannel(image,DefaultChannels,kernel,exception);
2154 return(filter_image);
2155}
2156
2157MagickExport Image *FilterImageChannel(const Image *image,
2158 const ChannelType channel,const MagickKernel *kernel,ExceptionInfo *exception)
2159{
2160#define FilterImageTag "Filter/Image"
2161
2162 CacheView
2163 *filter_view,
2164 *image_view;
2165
2166 double
2167 *normal_kernel;
2168
2169 Image
2170 *filter_image;
2171
2172 long
2173 progress,
2174 y;
2175
2176 MagickBooleanType
2177 status;
2178
2179 MagickPixelPacket
2180 bias;
2181
2182 MagickRealType
2183 gamma;
2184
2185 register long
2186 i;
2187
2188 /*
2189 Initialize filter image attributes.
2190 */
2191 assert(image != (Image *) NULL);
2192 assert(image->signature == MagickSignature);
2193 if (image->debug != MagickFalse)
2194 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2195 assert(exception != (ExceptionInfo *) NULL);
2196 assert(exception->signature == MagickSignature);
2197 if ((kernel->width % 2) == 0)
2198 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
2199 filter_image=CloneImage(image,0,0,MagickTrue,exception);
2200 if (filter_image == (Image *) NULL)
2201 return((Image *) NULL);
2202 if (SetImageStorageClass(filter_image,DirectClass) == MagickFalse)
2203 {
2204 InheritException(exception,&filter_image->exception);
2205 filter_image=DestroyImage(filter_image);
2206 return((Image *) NULL);
2207 }
2208 if (image->debug != MagickFalse)
2209 {
2210 char
2211 format[MaxTextExtent],
2212 *message;
2213
2214 long
2215 u,
2216 v;
2217
2218 register const double
2219 *k;
2220
2221 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
2222 " FilterImage with %ldx%ld kernel:",kernel->width,kernel->height);
2223 message=AcquireString("");
2224 k=kernel->values;
2225 for (v=0; v < (long) kernel->height; v++)
2226 {
2227 *message='\0';
2228 (void) FormatMagickString(format,MaxTextExtent,"%ld: ",v);
2229 (void) ConcatenateString(&message,format);
2230 for (u=0; u < (long) kernel->width; u++)
2231 {
cristy8cd5b312010-01-07 01:10:24 +00002232 (void) FormatMagickString(format,MaxTextExtent,"%.15g ",*k++);
cristy56a9e512010-01-06 18:18:55 +00002233 (void) ConcatenateString(&message,format);
2234 }
2235 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
2236 }
2237 message=DestroyString(message);
2238 }
2239 /*
2240 Normalize kernel.
2241 */
2242 normal_kernel=(double *) AcquireQuantumMemory(kernel->width*kernel->height,
2243 sizeof(*normal_kernel));
2244 if (normal_kernel == (double *) NULL)
2245 {
2246 filter_image=DestroyImage(filter_image);
2247 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2248 }
2249 gamma=0.0;
2250 for (i=0; i < (long) (kernel->width*kernel->height); i++)
2251 gamma+=kernel->values[i];
2252 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
2253 for (i=0; i < (long) (kernel->width*kernel->height); i++)
2254 normal_kernel[i]=gamma*kernel->values[i];
2255 /*
2256 Filter image.
2257 */
2258 status=MagickTrue;
2259 progress=0;
2260 GetMagickPixelPacket(image,&bias);
2261 SetMagickPixelPacketBias(image,&bias);
2262 image_view=AcquireCacheView(image);
2263 filter_view=AcquireCacheView(filter_image);
2264#if defined(MAGICKCORE_OPENMP_SUPPORT)
2265 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2266#endif
2267 for (y=0; y < (long) image->rows; y++)
2268 {
2269 MagickBooleanType
2270 sync;
2271
2272 register const IndexPacket
2273 *restrict indexes;
2274
2275 register const PixelPacket
2276 *restrict p;
2277
2278 register IndexPacket
2279 *restrict filter_indexes;
2280
2281 register long
2282 x;
2283
2284 register PixelPacket
2285 *restrict q;
2286
2287 if (status == MagickFalse)
2288 continue;
2289 p=GetCacheViewVirtualPixels(image_view,-((long) kernel->width/2L),
2290 y-(long) (kernel->height/2L),image->columns+kernel->width,kernel->height,
2291 exception);
2292 q=GetCacheViewAuthenticPixels(filter_view,0,y,filter_image->columns,1,
2293 exception);
2294 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2295 {
2296 status=MagickFalse;
2297 continue;
2298 }
2299 indexes=GetCacheViewVirtualIndexQueue(image_view);
2300 filter_indexes=GetCacheViewAuthenticIndexQueue(filter_view);
2301 for (x=0; x < (long) image->columns; x++)
2302 {
2303 long
2304 v;
2305
2306 MagickPixelPacket
2307 pixel;
2308
2309 register const double
2310 *restrict k;
2311
2312 register const PixelPacket
2313 *restrict kernel_pixels;
2314
2315 register long
2316 u;
2317
2318 pixel=bias;
2319 k=normal_kernel;
2320 kernel_pixels=p;
2321 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
2322 {
2323 for (v=0; v < (long) kernel->width; v++)
2324 {
2325 for (u=0; u < (long) kernel->height; u++)
2326 {
2327 pixel.red+=(*k)*kernel_pixels[u].red;
2328 pixel.green+=(*k)*kernel_pixels[u].green;
2329 pixel.blue+=(*k)*kernel_pixels[u].blue;
2330 k++;
2331 }
2332 kernel_pixels+=image->columns+kernel->width;
2333 }
2334 if ((channel & RedChannel) != 0)
2335 q->red=RoundToQuantum(pixel.red);
2336 if ((channel & GreenChannel) != 0)
2337 q->green=RoundToQuantum(pixel.green);
2338 if ((channel & BlueChannel) != 0)
2339 q->blue=RoundToQuantum(pixel.blue);
2340 if ((channel & OpacityChannel) != 0)
2341 {
2342 k=normal_kernel;
2343 kernel_pixels=p;
2344 for (v=0; v < (long) kernel->width; v++)
2345 {
2346 for (u=0; u < (long) kernel->height; u++)
2347 {
2348 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
2349 k++;
2350 }
2351 kernel_pixels+=image->columns+kernel->width;
2352 }
2353 q->opacity=RoundToQuantum(pixel.opacity);
2354 }
2355 if (((channel & IndexChannel) != 0) &&
2356 (image->colorspace == CMYKColorspace))
2357 {
2358 register const IndexPacket
2359 *restrict kernel_indexes;
2360
2361 k=normal_kernel;
2362 kernel_indexes=indexes;
2363 for (v=0; v < (long) kernel->width; v++)
2364 {
2365 for (u=0; u < (long) kernel->height; u++)
2366 {
2367 pixel.index+=(*k)*kernel_indexes[u];
2368 k++;
2369 }
2370 kernel_indexes+=image->columns+kernel->width;
2371 }
2372 filter_indexes[x]=RoundToQuantum(pixel.index);
2373 }
2374 }
2375 else
2376 {
2377 MagickRealType
2378 alpha,
2379 gamma;
2380
2381 gamma=0.0;
2382 for (v=0; v < (long) kernel->width; v++)
2383 {
2384 for (u=0; u < (long) kernel->height; u++)
2385 {
2386 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
2387 kernel_pixels[u].opacity));
2388 pixel.red+=(*k)*alpha*kernel_pixels[u].red;
2389 pixel.green+=(*k)*alpha*kernel_pixels[u].green;
2390 pixel.blue+=(*k)*alpha*kernel_pixels[u].blue;
2391 gamma+=(*k)*alpha;
2392 k++;
2393 }
2394 kernel_pixels+=image->columns+kernel->width;
2395 }
2396 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
2397 if ((channel & RedChannel) != 0)
cristyc4c8d132010-01-07 01:58:38 +00002398 q->red=RoundToQuantum(gamma*GetRedSample(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002399 if ((channel & GreenChannel) != 0)
cristyc4c8d132010-01-07 01:58:38 +00002400 q->green=RoundToQuantum(gamma*GetGreenSample(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002401 if ((channel & BlueChannel) != 0)
cristyc4c8d132010-01-07 01:58:38 +00002402 q->blue=RoundToQuantum(gamma*GetBlueSample(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002403 if ((channel & OpacityChannel) != 0)
2404 {
2405 k=normal_kernel;
2406 kernel_pixels=p;
2407 for (v=0; v < (long) kernel->width; v++)
2408 {
2409 for (u=0; u < (long) kernel->height; u++)
2410 {
2411 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
2412 k++;
2413 }
2414 kernel_pixels+=image->columns+kernel->width;
2415 }
2416 q->opacity=RoundToQuantum(pixel.opacity);
2417 }
2418 if (((channel & IndexChannel) != 0) &&
2419 (image->colorspace == CMYKColorspace))
2420 {
2421 register const IndexPacket
2422 *restrict kernel_indexes;
2423
2424 k=normal_kernel;
2425 kernel_pixels=p;
2426 kernel_indexes=indexes;
2427 for (v=0; v < (long) kernel->width; v++)
2428 {
2429 for (u=0; u < (long) kernel->height; u++)
2430 {
2431 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
2432 kernel_pixels[u].opacity));
2433 pixel.index+=(*k)*alpha*kernel_indexes[u];
2434 k++;
2435 }
2436 kernel_pixels+=image->columns+kernel->width;
2437 kernel_indexes+=image->columns+kernel->width;
2438 }
cristyc4c8d132010-01-07 01:58:38 +00002439 filter_indexes[x]=RoundToQuantum(gamma*GetIndexSample(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002440 }
2441 }
2442 p++;
2443 q++;
2444 }
2445 sync=SyncCacheViewAuthenticPixels(filter_view,exception);
2446 if (sync == MagickFalse)
2447 status=MagickFalse;
2448 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2449 {
2450 MagickBooleanType
2451 proceed;
2452
2453#if defined(MAGICKCORE_OPENMP_SUPPORT)
2454 #pragma omp critical (MagickCore_FilterImageChannel)
2455#endif
2456 proceed=SetImageProgress(image,FilterImageTag,progress++,image->rows);
2457 if (proceed == MagickFalse)
2458 status=MagickFalse;
2459 }
2460 }
2461 filter_image->type=image->type;
2462 filter_view=DestroyCacheView(filter_view);
2463 image_view=DestroyCacheView(image_view);
2464 normal_kernel=(double *) RelinquishMagickMemory(normal_kernel);
2465 if (status == MagickFalse)
2466 filter_image=DestroyImage(filter_image);
2467 return(filter_image);
2468}
2469
2470/*
2471%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2472% %
2473% %
2474% %
cristy3ed852e2009-09-05 21:47:34 +00002475% G a u s s i a n B l u r I m a g e %
2476% %
2477% %
2478% %
2479%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2480%
2481% GaussianBlurImage() blurs an image. We convolve the image with a
2482% Gaussian operator of the given radius and standard deviation (sigma).
2483% For reasonable results, the radius should be larger than sigma. Use a
2484% radius of 0 and GaussianBlurImage() selects a suitable radius for you
2485%
2486% The format of the GaussianBlurImage method is:
2487%
2488% Image *GaussianBlurImage(const Image *image,onst double radius,
2489% const double sigma,ExceptionInfo *exception)
2490% Image *GaussianBlurImageChannel(const Image *image,
2491% const ChannelType channel,const double radius,const double sigma,
2492% ExceptionInfo *exception)
2493%
2494% A description of each parameter follows:
2495%
2496% o image: the image.
2497%
2498% o channel: the channel type.
2499%
2500% o radius: the radius of the Gaussian, in pixels, not counting the center
2501% pixel.
2502%
2503% o sigma: the standard deviation of the Gaussian, in pixels.
2504%
2505% o exception: return any errors or warnings in this structure.
2506%
2507*/
2508
2509MagickExport Image *GaussianBlurImage(const Image *image,const double radius,
2510 const double sigma,ExceptionInfo *exception)
2511{
2512 Image
2513 *blur_image;
2514
2515 blur_image=GaussianBlurImageChannel(image,DefaultChannels,radius,sigma,
2516 exception);
2517 return(blur_image);
2518}
2519
2520MagickExport Image *GaussianBlurImageChannel(const Image *image,
2521 const ChannelType channel,const double radius,const double sigma,
2522 ExceptionInfo *exception)
2523{
2524 double
2525 *kernel;
2526
2527 Image
2528 *blur_image;
2529
cristy47e00502009-12-17 19:19:57 +00002530 long
2531 j,
cristy3ed852e2009-09-05 21:47:34 +00002532 u,
2533 v;
2534
cristy47e00502009-12-17 19:19:57 +00002535 register long
2536 i;
2537
cristy3ed852e2009-09-05 21:47:34 +00002538 unsigned long
2539 width;
2540
2541 assert(image != (const Image *) NULL);
2542 assert(image->signature == MagickSignature);
2543 if (image->debug != MagickFalse)
2544 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2545 assert(exception != (ExceptionInfo *) NULL);
2546 assert(exception->signature == MagickSignature);
2547 width=GetOptimalKernelWidth2D(radius,sigma);
2548 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2549 if (kernel == (double *) NULL)
2550 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy47e00502009-12-17 19:19:57 +00002551 j=(long) width/2;
cristy3ed852e2009-09-05 21:47:34 +00002552 i=0;
cristy47e00502009-12-17 19:19:57 +00002553 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002554 {
cristy47e00502009-12-17 19:19:57 +00002555 for (u=(-j); u <= j; u++)
2556 kernel[i++]=exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
2557 (2.0*MagickPI*MagickSigma*MagickSigma);
cristy3ed852e2009-09-05 21:47:34 +00002558 }
2559 blur_image=ConvolveImageChannel(image,channel,width,kernel,exception);
2560 kernel=(double *) RelinquishMagickMemory(kernel);
2561 return(blur_image);
2562}
2563
2564/*
2565%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2566% %
2567% %
2568% %
2569% M e d i a n F i l t e r I m a g e %
2570% %
2571% %
2572% %
2573%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2574%
2575% MedianFilterImage() applies a digital filter that improves the quality
2576% of a noisy image. Each pixel is replaced by the median in a set of
2577% neighboring pixels as defined by radius.
2578%
2579% The algorithm was contributed by Mike Edmonds and implements an insertion
2580% sort for selecting median color-channel values. For more on this algorithm
2581% see "Skip Lists: A probabilistic Alternative to Balanced Trees" by William
2582% Pugh in the June 1990 of Communications of the ACM.
2583%
2584% The format of the MedianFilterImage method is:
2585%
2586% Image *MedianFilterImage(const Image *image,const double radius,
2587% ExceptionInfo *exception)
2588%
2589% A description of each parameter follows:
2590%
2591% o image: the image.
2592%
2593% o radius: the radius of the pixel neighborhood.
2594%
2595% o exception: return any errors or warnings in this structure.
2596%
2597*/
2598
2599#define MedianListChannels 5
2600
2601typedef struct _MedianListNode
2602{
2603 unsigned long
2604 next[9],
2605 count,
2606 signature;
2607} MedianListNode;
2608
2609typedef struct _MedianSkipList
2610{
2611 long
2612 level;
2613
2614 MedianListNode
2615 *nodes;
2616} MedianSkipList;
2617
2618typedef struct _MedianPixelList
2619{
2620 unsigned long
2621 center,
2622 seed,
2623 signature;
2624
2625 MedianSkipList
2626 lists[MedianListChannels];
2627} MedianPixelList;
2628
2629static MedianPixelList *DestroyMedianPixelList(MedianPixelList *pixel_list)
2630{
2631 register long
2632 i;
2633
2634 if (pixel_list == (MedianPixelList *) NULL)
2635 return((MedianPixelList *) NULL);
2636 for (i=0; i < MedianListChannels; i++)
2637 if (pixel_list->lists[i].nodes != (MedianListNode *) NULL)
2638 pixel_list->lists[i].nodes=(MedianListNode *) RelinquishMagickMemory(
2639 pixel_list->lists[i].nodes);
2640 pixel_list=(MedianPixelList *) RelinquishAlignedMemory(pixel_list);
2641 return(pixel_list);
2642}
2643
2644static MedianPixelList **DestroyMedianPixelListThreadSet(
2645 MedianPixelList **pixel_list)
2646{
2647 register long
2648 i;
2649
2650 assert(pixel_list != (MedianPixelList **) NULL);
2651 for (i=0; i < (long) GetOpenMPMaximumThreads(); i++)
2652 if (pixel_list[i] != (MedianPixelList *) NULL)
2653 pixel_list[i]=DestroyMedianPixelList(pixel_list[i]);
2654 pixel_list=(MedianPixelList **) RelinquishAlignedMemory(pixel_list);
2655 return(pixel_list);
2656}
2657
2658static MedianPixelList *AcquireMedianPixelList(const unsigned long width)
2659{
2660 MedianPixelList
2661 *pixel_list;
2662
2663 register long
2664 i;
2665
2666 pixel_list=(MedianPixelList *) AcquireAlignedMemory(1,sizeof(*pixel_list));
2667 if (pixel_list == (MedianPixelList *) NULL)
2668 return(pixel_list);
2669 (void) ResetMagickMemory((void *) pixel_list,0,sizeof(*pixel_list));
2670 pixel_list->center=width*width/2;
2671 for (i=0; i < MedianListChannels; i++)
2672 {
2673 pixel_list->lists[i].nodes=(MedianListNode *) AcquireQuantumMemory(65537UL,
2674 sizeof(*pixel_list->lists[i].nodes));
2675 if (pixel_list->lists[i].nodes == (MedianListNode *) NULL)
2676 return(DestroyMedianPixelList(pixel_list));
2677 (void) ResetMagickMemory(pixel_list->lists[i].nodes,0,65537UL*
2678 sizeof(*pixel_list->lists[i].nodes));
2679 }
2680 pixel_list->signature=MagickSignature;
2681 return(pixel_list);
2682}
2683
2684static MedianPixelList **AcquireMedianPixelListThreadSet(
2685 const unsigned long width)
2686{
2687 register long
2688 i;
2689
2690 MedianPixelList
2691 **pixel_list;
2692
2693 unsigned long
2694 number_threads;
2695
2696 number_threads=GetOpenMPMaximumThreads();
2697 pixel_list=(MedianPixelList **) AcquireAlignedMemory(number_threads,
2698 sizeof(*pixel_list));
2699 if (pixel_list == (MedianPixelList **) NULL)
2700 return((MedianPixelList **) NULL);
2701 (void) ResetMagickMemory(pixel_list,0,number_threads*sizeof(*pixel_list));
2702 for (i=0; i < (long) number_threads; i++)
2703 {
2704 pixel_list[i]=AcquireMedianPixelList(width);
2705 if (pixel_list[i] == (MedianPixelList *) NULL)
2706 return(DestroyMedianPixelListThreadSet(pixel_list));
2707 }
2708 return(pixel_list);
2709}
2710
2711static void AddNodeMedianPixelList(MedianPixelList *pixel_list,
2712 const long channel,const unsigned long color)
2713{
2714 register long
2715 level;
2716
2717 register MedianSkipList
2718 *list;
2719
2720 unsigned long
2721 search,
2722 update[9];
2723
2724 /*
2725 Initialize the node.
2726 */
2727 list=pixel_list->lists+channel;
2728 list->nodes[color].signature=pixel_list->signature;
2729 list->nodes[color].count=1;
2730 /*
2731 Determine where it belongs in the list.
2732 */
2733 search=65536UL;
2734 for (level=list->level; level >= 0; level--)
2735 {
2736 while (list->nodes[search].next[level] < color)
2737 search=list->nodes[search].next[level];
2738 update[level]=search;
2739 }
2740 /*
2741 Generate a pseudo-random level for this node.
2742 */
2743 for (level=0; ; level++)
2744 {
2745 pixel_list->seed=(pixel_list->seed*42893621L)+1L;
2746 if ((pixel_list->seed & 0x300) != 0x300)
2747 break;
2748 }
2749 if (level > 8)
2750 level=8;
2751 if (level > (list->level+2))
2752 level=list->level+2;
2753 /*
2754 If we're raising the list's level, link back to the root node.
2755 */
2756 while (level > list->level)
2757 {
2758 list->level++;
2759 update[list->level]=65536UL;
2760 }
2761 /*
2762 Link the node into the skip-list.
2763 */
2764 do
2765 {
2766 list->nodes[color].next[level]=list->nodes[update[level]].next[level];
2767 list->nodes[update[level]].next[level]=color;
2768 }
2769 while (level-- > 0);
2770}
2771
2772static MagickPixelPacket GetMedianPixelList(MedianPixelList *pixel_list)
2773{
2774 MagickPixelPacket
2775 pixel;
2776
2777 register long
2778 channel;
2779
2780 register MedianSkipList
2781 *list;
2782
2783 unsigned long
2784 center,
2785 color,
2786 count;
2787
2788 unsigned short
2789 channels[MedianListChannels];
2790
2791 /*
2792 Find the median value for each of the color.
2793 */
2794 center=pixel_list->center;
2795 for (channel=0; channel < 5; channel++)
2796 {
2797 list=pixel_list->lists+channel;
2798 color=65536UL;
2799 count=0;
2800 do
2801 {
2802 color=list->nodes[color].next[0];
2803 count+=list->nodes[color].count;
2804 }
2805 while (count <= center);
2806 channels[channel]=(unsigned short) color;
2807 }
2808 GetMagickPixelPacket((const Image *) NULL,&pixel);
2809 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
2810 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
2811 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
2812 pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
2813 pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]);
2814 return(pixel);
2815}
2816
2817static inline void InsertMedianPixelList(const Image *image,
2818 const PixelPacket *pixel,const IndexPacket *indexes,
2819 MedianPixelList *pixel_list)
2820{
2821 unsigned long
2822 signature;
2823
2824 unsigned short
2825 index;
2826
2827 index=ScaleQuantumToShort(pixel->red);
2828 signature=pixel_list->lists[0].nodes[index].signature;
2829 if (signature == pixel_list->signature)
2830 pixel_list->lists[0].nodes[index].count++;
2831 else
2832 AddNodeMedianPixelList(pixel_list,0,index);
2833 index=ScaleQuantumToShort(pixel->green);
2834 signature=pixel_list->lists[1].nodes[index].signature;
2835 if (signature == pixel_list->signature)
2836 pixel_list->lists[1].nodes[index].count++;
2837 else
2838 AddNodeMedianPixelList(pixel_list,1,index);
2839 index=ScaleQuantumToShort(pixel->blue);
2840 signature=pixel_list->lists[2].nodes[index].signature;
2841 if (signature == pixel_list->signature)
2842 pixel_list->lists[2].nodes[index].count++;
2843 else
2844 AddNodeMedianPixelList(pixel_list,2,index);
2845 index=ScaleQuantumToShort(pixel->opacity);
2846 signature=pixel_list->lists[3].nodes[index].signature;
2847 if (signature == pixel_list->signature)
2848 pixel_list->lists[3].nodes[index].count++;
2849 else
2850 AddNodeMedianPixelList(pixel_list,3,index);
2851 if (image->colorspace == CMYKColorspace)
2852 index=ScaleQuantumToShort(*indexes);
2853 signature=pixel_list->lists[4].nodes[index].signature;
2854 if (signature == pixel_list->signature)
2855 pixel_list->lists[4].nodes[index].count++;
2856 else
2857 AddNodeMedianPixelList(pixel_list,4,index);
2858}
2859
2860static void ResetMedianPixelList(MedianPixelList *pixel_list)
2861{
2862 int
2863 level;
2864
2865 register long
2866 channel;
2867
2868 register MedianListNode
2869 *root;
2870
2871 register MedianSkipList
2872 *list;
2873
2874 /*
2875 Reset the skip-list.
2876 */
2877 for (channel=0; channel < 5; channel++)
2878 {
2879 list=pixel_list->lists+channel;
2880 root=list->nodes+65536UL;
2881 list->level=0;
2882 for (level=0; level < 9; level++)
2883 root->next[level]=65536UL;
2884 }
2885 pixel_list->seed=pixel_list->signature++;
2886}
2887
2888MagickExport Image *MedianFilterImage(const Image *image,const double radius,
2889 ExceptionInfo *exception)
2890{
2891#define MedianFilterImageTag "MedianFilter/Image"
2892
cristyc4c8d132010-01-07 01:58:38 +00002893 CacheView
2894 *image_view,
2895 *median_view;
2896
cristy3ed852e2009-09-05 21:47:34 +00002897 Image
2898 *median_image;
2899
2900 long
2901 progress,
2902 y;
2903
2904 MagickBooleanType
2905 status;
2906
2907 MedianPixelList
cristyfa112112010-01-04 17:48:07 +00002908 **restrict pixel_list;
cristy3ed852e2009-09-05 21:47:34 +00002909
2910 unsigned long
2911 width;
2912
cristy3ed852e2009-09-05 21:47:34 +00002913 /*
2914 Initialize median image attributes.
2915 */
2916 assert(image != (Image *) NULL);
2917 assert(image->signature == MagickSignature);
2918 if (image->debug != MagickFalse)
2919 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2920 assert(exception != (ExceptionInfo *) NULL);
2921 assert(exception->signature == MagickSignature);
2922 width=GetOptimalKernelWidth2D(radius,0.5);
2923 if ((image->columns < width) || (image->rows < width))
2924 ThrowImageException(OptionError,"ImageSmallerThanKernelRadius");
2925 median_image=CloneImage(image,image->columns,image->rows,MagickTrue,
2926 exception);
2927 if (median_image == (Image *) NULL)
2928 return((Image *) NULL);
2929 if (SetImageStorageClass(median_image,DirectClass) == MagickFalse)
2930 {
2931 InheritException(exception,&median_image->exception);
2932 median_image=DestroyImage(median_image);
2933 return((Image *) NULL);
2934 }
2935 pixel_list=AcquireMedianPixelListThreadSet(width);
2936 if (pixel_list == (MedianPixelList **) NULL)
2937 {
2938 median_image=DestroyImage(median_image);
2939 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2940 }
2941 /*
2942 Median filter each image row.
2943 */
2944 status=MagickTrue;
2945 progress=0;
2946 image_view=AcquireCacheView(image);
2947 median_view=AcquireCacheView(median_image);
cristyb5d5f722009-11-04 03:03:49 +00002948#if defined(MAGICKCORE_OPENMP_SUPPORT)
2949 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002950#endif
2951 for (y=0; y < (long) median_image->rows; y++)
2952 {
2953 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002954 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002955
2956 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002957 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002958
2959 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002960 *restrict median_indexes;
cristy3ed852e2009-09-05 21:47:34 +00002961
2962 register long
2963 id,
2964 x;
2965
2966 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002967 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002968
2969 if (status == MagickFalse)
2970 continue;
2971 p=GetCacheViewVirtualPixels(image_view,-((long) width/2L),y-(long) (width/
2972 2L),image->columns+width,width,exception);
2973 q=QueueCacheViewAuthenticPixels(median_view,0,y,median_image->columns,1,
2974 exception);
2975 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2976 {
2977 status=MagickFalse;
2978 continue;
2979 }
2980 indexes=GetCacheViewVirtualIndexQueue(image_view);
2981 median_indexes=GetCacheViewAuthenticIndexQueue(median_view);
2982 id=GetOpenMPThreadId();
2983 for (x=0; x < (long) median_image->columns; x++)
2984 {
2985 MagickPixelPacket
2986 pixel;
2987
2988 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002989 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +00002990
2991 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002992 *restrict s;
cristy3ed852e2009-09-05 21:47:34 +00002993
2994 register long
2995 u,
2996 v;
2997
2998 r=p;
2999 s=indexes+x;
3000 ResetMedianPixelList(pixel_list[id]);
3001 for (v=0; v < (long) width; v++)
3002 {
3003 for (u=0; u < (long) width; u++)
3004 InsertMedianPixelList(image,r+u,s+u,pixel_list[id]);
3005 r+=image->columns+width;
3006 s+=image->columns+width;
3007 }
3008 pixel=GetMedianPixelList(pixel_list[id]);
3009 SetPixelPacket(median_image,&pixel,q,median_indexes+x);
3010 p++;
3011 q++;
3012 }
3013 if (SyncCacheViewAuthenticPixels(median_view,exception) == MagickFalse)
3014 status=MagickFalse;
3015 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3016 {
3017 MagickBooleanType
3018 proceed;
3019
cristyb5d5f722009-11-04 03:03:49 +00003020#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003021 #pragma omp critical (MagickCore_MedianFilterImage)
3022#endif
3023 proceed=SetImageProgress(image,MedianFilterImageTag,progress++,
3024 image->rows);
3025 if (proceed == MagickFalse)
3026 status=MagickFalse;
3027 }
3028 }
3029 median_view=DestroyCacheView(median_view);
3030 image_view=DestroyCacheView(image_view);
3031 pixel_list=DestroyMedianPixelListThreadSet(pixel_list);
3032 return(median_image);
3033}
3034
3035/*
3036%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3037% %
3038% %
3039% %
3040% M o t i o n B l u r I m a g e %
3041% %
3042% %
3043% %
3044%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3045%
3046% MotionBlurImage() simulates motion blur. We convolve the image with a
3047% Gaussian operator of the given radius and standard deviation (sigma).
3048% For reasonable results, radius should be larger than sigma. Use a
3049% radius of 0 and MotionBlurImage() selects a suitable radius for you.
3050% Angle gives the angle of the blurring motion.
3051%
3052% Andrew Protano contributed this effect.
3053%
3054% The format of the MotionBlurImage method is:
3055%
3056% Image *MotionBlurImage(const Image *image,const double radius,
3057% const double sigma,const double angle,ExceptionInfo *exception)
3058% Image *MotionBlurImageChannel(const Image *image,const ChannelType channel,
3059% const double radius,const double sigma,const double angle,
3060% ExceptionInfo *exception)
3061%
3062% A description of each parameter follows:
3063%
3064% o image: the image.
3065%
3066% o channel: the channel type.
3067%
3068% o radius: the radius of the Gaussian, in pixels, not counting the center
3069% o radius: the radius of the Gaussian, in pixels, not counting
3070% the center pixel.
3071%
3072% o sigma: the standard deviation of the Gaussian, in pixels.
3073%
3074% o angle: Apply the effect along this angle.
3075%
3076% o exception: return any errors or warnings in this structure.
3077%
3078*/
3079
cristy47e00502009-12-17 19:19:57 +00003080static double *GetMotionBlurKernel(const unsigned long width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +00003081{
cristy3ed852e2009-09-05 21:47:34 +00003082 double
cristy47e00502009-12-17 19:19:57 +00003083 *kernel,
cristy3ed852e2009-09-05 21:47:34 +00003084 normalize;
3085
3086 register long
3087 i;
3088
3089 /*
cristy47e00502009-12-17 19:19:57 +00003090 Generate a 1-D convolution kernel.
cristy3ed852e2009-09-05 21:47:34 +00003091 */
3092 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
3093 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
3094 if (kernel == (double *) NULL)
3095 return(kernel);
cristy3ed852e2009-09-05 21:47:34 +00003096 normalize=0.0;
3097 for (i=0; i < (long) width; i++)
cristy47e00502009-12-17 19:19:57 +00003098 {
3099 kernel[i]=exp((-((double) i*i)/(double) (2.0*MagickSigma*MagickSigma)))/
3100 (MagickSQ2PI*MagickSigma);
cristy3ed852e2009-09-05 21:47:34 +00003101 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +00003102 }
cristy3ed852e2009-09-05 21:47:34 +00003103 for (i=0; i < (long) width; i++)
3104 kernel[i]/=normalize;
3105 return(kernel);
3106}
3107
3108MagickExport Image *MotionBlurImage(const Image *image,const double radius,
3109 const double sigma,const double angle,ExceptionInfo *exception)
3110{
3111 Image
3112 *motion_blur;
3113
3114 motion_blur=MotionBlurImageChannel(image,DefaultChannels,radius,sigma,angle,
3115 exception);
3116 return(motion_blur);
3117}
3118
3119MagickExport Image *MotionBlurImageChannel(const Image *image,
3120 const ChannelType channel,const double radius,const double sigma,
3121 const double angle,ExceptionInfo *exception)
3122{
3123 typedef struct _OffsetInfo
3124 {
3125 long
3126 x,
3127 y;
3128 } OffsetInfo;
3129
cristyc4c8d132010-01-07 01:58:38 +00003130 CacheView
3131 *blur_view,
3132 *image_view;
3133
cristy3ed852e2009-09-05 21:47:34 +00003134 double
3135 *kernel;
3136
3137 Image
3138 *blur_image;
3139
3140 long
3141 progress,
3142 y;
3143
3144 MagickBooleanType
3145 status;
3146
3147 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00003148 bias;
cristy3ed852e2009-09-05 21:47:34 +00003149
3150 OffsetInfo
3151 *offset;
3152
3153 PointInfo
3154 point;
3155
3156 register long
3157 i;
3158
3159 unsigned long
3160 width;
3161
cristy3ed852e2009-09-05 21:47:34 +00003162 assert(image != (Image *) NULL);
3163 assert(image->signature == MagickSignature);
3164 if (image->debug != MagickFalse)
3165 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3166 assert(exception != (ExceptionInfo *) NULL);
3167 width=GetOptimalKernelWidth1D(radius,sigma);
3168 kernel=GetMotionBlurKernel(width,sigma);
3169 if (kernel == (double *) NULL)
3170 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3171 offset=(OffsetInfo *) AcquireQuantumMemory(width,sizeof(*offset));
3172 if (offset == (OffsetInfo *) NULL)
3173 {
3174 kernel=(double *) RelinquishMagickMemory(kernel);
3175 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3176 }
3177 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3178 if (blur_image == (Image *) NULL)
3179 {
3180 kernel=(double *) RelinquishMagickMemory(kernel);
3181 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
3182 return((Image *) NULL);
3183 }
3184 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3185 {
3186 kernel=(double *) RelinquishMagickMemory(kernel);
3187 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
3188 InheritException(exception,&blur_image->exception);
3189 blur_image=DestroyImage(blur_image);
3190 return((Image *) NULL);
3191 }
3192 point.x=(double) width*sin(DegreesToRadians(angle));
3193 point.y=(double) width*cos(DegreesToRadians(angle));
3194 for (i=0; i < (long) width; i++)
3195 {
3196 offset[i].x=(long) ((i*point.y)/hypot(point.x,point.y)+0.5);
3197 offset[i].y=(long) ((i*point.x)/hypot(point.x,point.y)+0.5);
3198 }
3199 /*
3200 Motion blur image.
3201 */
3202 status=MagickTrue;
3203 progress=0;
cristyddd82202009-11-03 20:14:50 +00003204 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003205 image_view=AcquireCacheView(image);
3206 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003207#if defined(MAGICKCORE_OPENMP_SUPPORT)
3208 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003209#endif
3210 for (y=0; y < (long) image->rows; y++)
3211 {
3212 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003213 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00003214
3215 register long
3216 x;
3217
3218 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003219 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003220
3221 if (status == MagickFalse)
3222 continue;
3223 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3224 exception);
3225 if (q == (PixelPacket *) NULL)
3226 {
3227 status=MagickFalse;
3228 continue;
3229 }
3230 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
3231 for (x=0; x < (long) image->columns; x++)
3232 {
3233 MagickPixelPacket
3234 qixel;
3235
3236 PixelPacket
3237 pixel;
3238
3239 register double
cristyc47d1f82009-11-26 01:44:43 +00003240 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00003241
3242 register long
3243 i;
3244
3245 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003246 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003247
3248 k=kernel;
cristyddd82202009-11-03 20:14:50 +00003249 qixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00003250 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
3251 {
3252 for (i=0; i < (long) width; i++)
3253 {
3254 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
3255 offset[i].y,&pixel,exception);
3256 qixel.red+=(*k)*pixel.red;
3257 qixel.green+=(*k)*pixel.green;
3258 qixel.blue+=(*k)*pixel.blue;
3259 qixel.opacity+=(*k)*pixel.opacity;
3260 if (image->colorspace == CMYKColorspace)
3261 {
3262 indexes=GetCacheViewVirtualIndexQueue(image_view);
3263 qixel.index+=(*k)*(*indexes);
3264 }
3265 k++;
3266 }
3267 if ((channel & RedChannel) != 0)
3268 q->red=RoundToQuantum(qixel.red);
3269 if ((channel & GreenChannel) != 0)
3270 q->green=RoundToQuantum(qixel.green);
3271 if ((channel & BlueChannel) != 0)
3272 q->blue=RoundToQuantum(qixel.blue);
3273 if ((channel & OpacityChannel) != 0)
3274 q->opacity=RoundToQuantum(qixel.opacity);
3275 if (((channel & IndexChannel) != 0) &&
3276 (image->colorspace == CMYKColorspace))
3277 blur_indexes[x]=(IndexPacket) RoundToQuantum(qixel.index);
3278 }
3279 else
3280 {
3281 MagickRealType
3282 alpha,
3283 gamma;
3284
3285 alpha=0.0;
3286 gamma=0.0;
3287 for (i=0; i < (long) width; i++)
3288 {
3289 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
3290 offset[i].y,&pixel,exception);
3291 alpha=(MagickRealType) (QuantumScale*(QuantumRange-pixel.opacity));
3292 qixel.red+=(*k)*alpha*pixel.red;
3293 qixel.green+=(*k)*alpha*pixel.green;
3294 qixel.blue+=(*k)*alpha*pixel.blue;
3295 qixel.opacity+=(*k)*pixel.opacity;
3296 if (image->colorspace == CMYKColorspace)
3297 {
3298 indexes=GetCacheViewVirtualIndexQueue(image_view);
3299 qixel.index+=(*k)*alpha*(*indexes);
3300 }
3301 gamma+=(*k)*alpha;
3302 k++;
3303 }
3304 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3305 if ((channel & RedChannel) != 0)
3306 q->red=RoundToQuantum(gamma*qixel.red);
3307 if ((channel & GreenChannel) != 0)
3308 q->green=RoundToQuantum(gamma*qixel.green);
3309 if ((channel & BlueChannel) != 0)
3310 q->blue=RoundToQuantum(gamma*qixel.blue);
3311 if ((channel & OpacityChannel) != 0)
3312 q->opacity=RoundToQuantum(qixel.opacity);
3313 if (((channel & IndexChannel) != 0) &&
3314 (image->colorspace == CMYKColorspace))
3315 blur_indexes[x]=(IndexPacket) RoundToQuantum(gamma*qixel.index);
3316 }
3317 q++;
3318 }
3319 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
3320 status=MagickFalse;
3321 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3322 {
3323 MagickBooleanType
3324 proceed;
3325
cristyb5d5f722009-11-04 03:03:49 +00003326#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003327 #pragma omp critical (MagickCore_MotionBlurImageChannel)
3328#endif
3329 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
3330 if (proceed == MagickFalse)
3331 status=MagickFalse;
3332 }
3333 }
3334 blur_view=DestroyCacheView(blur_view);
3335 image_view=DestroyCacheView(image_view);
3336 kernel=(double *) RelinquishMagickMemory(kernel);
3337 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
3338 if (status == MagickFalse)
3339 blur_image=DestroyImage(blur_image);
3340 return(blur_image);
3341}
3342
3343/*
3344%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3345% %
3346% %
3347% %
3348% P r e v i e w I m a g e %
3349% %
3350% %
3351% %
3352%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3353%
3354% PreviewImage() tiles 9 thumbnails of the specified image with an image
3355% processing operation applied with varying parameters. This may be helpful
3356% pin-pointing an appropriate parameter for a particular image processing
3357% operation.
3358%
3359% The format of the PreviewImages method is:
3360%
3361% Image *PreviewImages(const Image *image,const PreviewType preview,
3362% ExceptionInfo *exception)
3363%
3364% A description of each parameter follows:
3365%
3366% o image: the image.
3367%
3368% o preview: the image processing operation.
3369%
3370% o exception: return any errors or warnings in this structure.
3371%
3372*/
3373MagickExport Image *PreviewImage(const Image *image,const PreviewType preview,
3374 ExceptionInfo *exception)
3375{
3376#define NumberTiles 9
3377#define PreviewImageTag "Preview/Image"
3378#define DefaultPreviewGeometry "204x204+10+10"
3379
3380 char
3381 factor[MaxTextExtent],
3382 label[MaxTextExtent];
3383
3384 double
3385 degrees,
3386 gamma,
3387 percentage,
3388 radius,
3389 sigma,
3390 threshold;
3391
3392 Image
3393 *images,
3394 *montage_image,
3395 *preview_image,
3396 *thumbnail;
3397
3398 ImageInfo
3399 *preview_info;
3400
3401 long
3402 y;
3403
3404 MagickBooleanType
3405 proceed;
3406
3407 MontageInfo
3408 *montage_info;
3409
3410 QuantizeInfo
3411 quantize_info;
3412
3413 RectangleInfo
3414 geometry;
3415
3416 register long
3417 i,
3418 x;
3419
3420 unsigned long
3421 colors;
3422
3423 /*
3424 Open output image file.
3425 */
3426 assert(image != (Image *) NULL);
3427 assert(image->signature == MagickSignature);
3428 if (image->debug != MagickFalse)
3429 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3430 colors=2;
3431 degrees=0.0;
3432 gamma=(-0.2f);
3433 preview_info=AcquireImageInfo();
3434 SetGeometry(image,&geometry);
3435 (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y,
3436 &geometry.width,&geometry.height);
3437 images=NewImageList();
3438 percentage=12.5;
3439 GetQuantizeInfo(&quantize_info);
3440 radius=0.0;
3441 sigma=1.0;
3442 threshold=0.0;
3443 x=0;
3444 y=0;
3445 for (i=0; i < NumberTiles; i++)
3446 {
3447 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
3448 if (thumbnail == (Image *) NULL)
3449 break;
3450 (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL,
3451 (void *) NULL);
3452 (void) SetImageProperty(thumbnail,"label",DefaultTileLabel);
3453 if (i == (NumberTiles/2))
3454 {
3455 (void) QueryColorDatabase("#dfdfdf",&thumbnail->matte_color,exception);
3456 AppendImageToList(&images,thumbnail);
3457 continue;
3458 }
3459 switch (preview)
3460 {
3461 case RotatePreview:
3462 {
3463 degrees+=45.0;
3464 preview_image=RotateImage(thumbnail,degrees,exception);
cristy8cd5b312010-01-07 01:10:24 +00003465 (void) FormatMagickString(label,MaxTextExtent,"rotate %.15g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003466 break;
3467 }
3468 case ShearPreview:
3469 {
3470 degrees+=5.0;
3471 preview_image=ShearImage(thumbnail,degrees,degrees,exception);
cristy8cd5b312010-01-07 01:10:24 +00003472 (void) FormatMagickString(label,MaxTextExtent,"shear %.15gx%.15g",
cristy3ed852e2009-09-05 21:47:34 +00003473 degrees,2.0*degrees);
3474 break;
3475 }
3476 case RollPreview:
3477 {
3478 x=(long) ((i+1)*thumbnail->columns)/NumberTiles;
3479 y=(long) ((i+1)*thumbnail->rows)/NumberTiles;
3480 preview_image=RollImage(thumbnail,x,y,exception);
3481 (void) FormatMagickString(label,MaxTextExtent,"roll %ldx%ld",x,y);
3482 break;
3483 }
3484 case HuePreview:
3485 {
3486 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3487 if (preview_image == (Image *) NULL)
3488 break;
cristy8cd5b312010-01-07 01:10:24 +00003489 (void) FormatMagickString(factor,MaxTextExtent,"100,100,%.15g",
cristy3ed852e2009-09-05 21:47:34 +00003490 2.0*percentage);
3491 (void) ModulateImage(preview_image,factor);
3492 (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor);
3493 break;
3494 }
3495 case SaturationPreview:
3496 {
3497 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3498 if (preview_image == (Image *) NULL)
3499 break;
cristy8cd5b312010-01-07 01:10:24 +00003500 (void) FormatMagickString(factor,MaxTextExtent,"100,%.15g",
3501 2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00003502 (void) ModulateImage(preview_image,factor);
3503 (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor);
3504 break;
3505 }
3506 case BrightnessPreview:
3507 {
3508 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3509 if (preview_image == (Image *) NULL)
3510 break;
cristy8cd5b312010-01-07 01:10:24 +00003511 (void) FormatMagickString(factor,MaxTextExtent,"%.15g",2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00003512 (void) ModulateImage(preview_image,factor);
3513 (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor);
3514 break;
3515 }
3516 case GammaPreview:
3517 default:
3518 {
3519 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3520 if (preview_image == (Image *) NULL)
3521 break;
3522 gamma+=0.4f;
3523 (void) GammaImageChannel(preview_image,DefaultChannels,gamma);
cristy8cd5b312010-01-07 01:10:24 +00003524 (void) FormatMagickString(label,MaxTextExtent,"gamma %.15g",gamma);
cristy3ed852e2009-09-05 21:47:34 +00003525 break;
3526 }
3527 case SpiffPreview:
3528 {
3529 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3530 if (preview_image != (Image *) NULL)
3531 for (x=0; x < i; x++)
3532 (void) ContrastImage(preview_image,MagickTrue);
3533 (void) FormatMagickString(label,MaxTextExtent,"contrast (%ld)",i+1);
3534 break;
3535 }
3536 case DullPreview:
3537 {
3538 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3539 if (preview_image == (Image *) NULL)
3540 break;
3541 for (x=0; x < i; x++)
3542 (void) ContrastImage(preview_image,MagickFalse);
3543 (void) FormatMagickString(label,MaxTextExtent,"+contrast (%ld)",i+1);
3544 break;
3545 }
3546 case GrayscalePreview:
3547 {
3548 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3549 if (preview_image == (Image *) NULL)
3550 break;
3551 colors<<=1;
3552 quantize_info.number_colors=colors;
3553 quantize_info.colorspace=GRAYColorspace;
3554 (void) QuantizeImage(&quantize_info,preview_image);
3555 (void) FormatMagickString(label,MaxTextExtent,
3556 "-colorspace gray -colors %ld",colors);
3557 break;
3558 }
3559 case QuantizePreview:
3560 {
3561 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3562 if (preview_image == (Image *) NULL)
3563 break;
3564 colors<<=1;
3565 quantize_info.number_colors=colors;
3566 (void) QuantizeImage(&quantize_info,preview_image);
3567 (void) FormatMagickString(label,MaxTextExtent,"colors %ld",colors);
3568 break;
3569 }
3570 case DespecklePreview:
3571 {
3572 for (x=0; x < (i-1); x++)
3573 {
3574 preview_image=DespeckleImage(thumbnail,exception);
3575 if (preview_image == (Image *) NULL)
3576 break;
3577 thumbnail=DestroyImage(thumbnail);
3578 thumbnail=preview_image;
3579 }
3580 preview_image=DespeckleImage(thumbnail,exception);
3581 if (preview_image == (Image *) NULL)
3582 break;
3583 (void) FormatMagickString(label,MaxTextExtent,"despeckle (%ld)",i+1);
3584 break;
3585 }
3586 case ReduceNoisePreview:
3587 {
3588 preview_image=ReduceNoiseImage(thumbnail,radius,exception);
cristy8cd5b312010-01-07 01:10:24 +00003589 (void) FormatMagickString(label,MaxTextExtent,"noise %.15g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003590 break;
3591 }
3592 case AddNoisePreview:
3593 {
3594 switch ((int) i)
3595 {
3596 case 0:
3597 {
3598 (void) CopyMagickString(factor,"uniform",MaxTextExtent);
3599 break;
3600 }
3601 case 1:
3602 {
3603 (void) CopyMagickString(factor,"gaussian",MaxTextExtent);
3604 break;
3605 }
3606 case 2:
3607 {
3608 (void) CopyMagickString(factor,"multiplicative",MaxTextExtent);
3609 break;
3610 }
3611 case 3:
3612 {
3613 (void) CopyMagickString(factor,"impulse",MaxTextExtent);
3614 break;
3615 }
3616 case 4:
3617 {
3618 (void) CopyMagickString(factor,"laplacian",MaxTextExtent);
3619 break;
3620 }
3621 case 5:
3622 {
3623 (void) CopyMagickString(factor,"Poisson",MaxTextExtent);
3624 break;
3625 }
3626 default:
3627 {
3628 (void) CopyMagickString(thumbnail->magick,"NULL",MaxTextExtent);
3629 break;
3630 }
3631 }
3632 preview_image=ReduceNoiseImage(thumbnail,(double) i,exception);
3633 (void) FormatMagickString(label,MaxTextExtent,"+noise %s",factor);
3634 break;
3635 }
3636 case SharpenPreview:
3637 {
3638 preview_image=SharpenImage(thumbnail,radius,sigma,exception);
cristy8cd5b312010-01-07 01:10:24 +00003639 (void) FormatMagickString(label,MaxTextExtent,"sharpen %.15gx%.15g",
3640 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00003641 break;
3642 }
3643 case BlurPreview:
3644 {
3645 preview_image=BlurImage(thumbnail,radius,sigma,exception);
cristy8cd5b312010-01-07 01:10:24 +00003646 (void) FormatMagickString(label,MaxTextExtent,"blur %.15gx%.15g",radius,
cristy3ed852e2009-09-05 21:47:34 +00003647 sigma);
3648 break;
3649 }
3650 case ThresholdPreview:
3651 {
3652 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3653 if (preview_image == (Image *) NULL)
3654 break;
3655 (void) BilevelImage(thumbnail,
3656 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
cristy8cd5b312010-01-07 01:10:24 +00003657 (void) FormatMagickString(label,MaxTextExtent,"threshold %.15g",
cristy3ed852e2009-09-05 21:47:34 +00003658 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
3659 break;
3660 }
3661 case EdgeDetectPreview:
3662 {
3663 preview_image=EdgeImage(thumbnail,radius,exception);
cristy8cd5b312010-01-07 01:10:24 +00003664 (void) FormatMagickString(label,MaxTextExtent,"edge %.15g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003665 break;
3666 }
3667 case SpreadPreview:
3668 {
3669 preview_image=SpreadImage(thumbnail,radius,exception);
cristy8cd5b312010-01-07 01:10:24 +00003670 (void) FormatMagickString(label,MaxTextExtent,"spread %.15g",
3671 radius+0.5);
cristy3ed852e2009-09-05 21:47:34 +00003672 break;
3673 }
3674 case SolarizePreview:
3675 {
3676 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3677 if (preview_image == (Image *) NULL)
3678 break;
3679 (void) SolarizeImage(preview_image,(double) QuantumRange*
3680 percentage/100.0);
cristy8cd5b312010-01-07 01:10:24 +00003681 (void) FormatMagickString(label,MaxTextExtent,"solarize %.15g",
cristy3ed852e2009-09-05 21:47:34 +00003682 (QuantumRange*percentage)/100.0);
3683 break;
3684 }
3685 case ShadePreview:
3686 {
3687 degrees+=10.0;
3688 preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees,
3689 exception);
cristy8cd5b312010-01-07 01:10:24 +00003690 (void) FormatMagickString(label,MaxTextExtent,"shade %.15gx%.15g",
3691 degrees,degrees);
cristy3ed852e2009-09-05 21:47:34 +00003692 break;
3693 }
3694 case RaisePreview:
3695 {
3696 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3697 if (preview_image == (Image *) NULL)
3698 break;
3699 geometry.width=(unsigned long) (2*i+2);
3700 geometry.height=(unsigned long) (2*i+2);
3701 geometry.x=i/2;
3702 geometry.y=i/2;
3703 (void) RaiseImage(preview_image,&geometry,MagickTrue);
3704 (void) FormatMagickString(label,MaxTextExtent,"raise %lux%lu%+ld%+ld",
3705 geometry.width,geometry.height,geometry.x,geometry.y);
3706 break;
3707 }
3708 case SegmentPreview:
3709 {
3710 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3711 if (preview_image == (Image *) NULL)
3712 break;
3713 threshold+=0.4f;
3714 (void) SegmentImage(preview_image,RGBColorspace,MagickFalse,threshold,
3715 threshold);
cristy8cd5b312010-01-07 01:10:24 +00003716 (void) FormatMagickString(label,MaxTextExtent,"segment %.15gx%.15g",
cristy3ed852e2009-09-05 21:47:34 +00003717 threshold,threshold);
3718 break;
3719 }
3720 case SwirlPreview:
3721 {
3722 preview_image=SwirlImage(thumbnail,degrees,exception);
cristy8cd5b312010-01-07 01:10:24 +00003723 (void) FormatMagickString(label,MaxTextExtent,"swirl %.15g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003724 degrees+=45.0;
3725 break;
3726 }
3727 case ImplodePreview:
3728 {
3729 degrees+=0.1f;
3730 preview_image=ImplodeImage(thumbnail,degrees,exception);
cristy8cd5b312010-01-07 01:10:24 +00003731 (void) FormatMagickString(label,MaxTextExtent,"implode %.15g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003732 break;
3733 }
3734 case WavePreview:
3735 {
3736 degrees+=5.0f;
3737 preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,exception);
cristy8cd5b312010-01-07 01:10:24 +00003738 (void) FormatMagickString(label,MaxTextExtent,"wave %.15gx%.15g",
3739 0.5*degrees,2.0*degrees);
cristy3ed852e2009-09-05 21:47:34 +00003740 break;
3741 }
3742 case OilPaintPreview:
3743 {
3744 preview_image=OilPaintImage(thumbnail,(double) radius,exception);
cristy8cd5b312010-01-07 01:10:24 +00003745 (void) FormatMagickString(label,MaxTextExtent,"paint %.15g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003746 break;
3747 }
3748 case CharcoalDrawingPreview:
3749 {
3750 preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma,
3751 exception);
cristy8cd5b312010-01-07 01:10:24 +00003752 (void) FormatMagickString(label,MaxTextExtent,"charcoal %.15gx%.15g",
3753 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00003754 break;
3755 }
3756 case JPEGPreview:
3757 {
3758 char
3759 filename[MaxTextExtent];
3760
3761 int
3762 file;
3763
3764 MagickBooleanType
3765 status;
3766
3767 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3768 if (preview_image == (Image *) NULL)
3769 break;
3770 preview_info->quality=(unsigned long) percentage;
3771 (void) FormatMagickString(factor,MaxTextExtent,"%lu",
3772 preview_info->quality);
3773 file=AcquireUniqueFileResource(filename);
3774 if (file != -1)
3775 file=close(file)-1;
3776 (void) FormatMagickString(preview_image->filename,MaxTextExtent,
3777 "jpeg:%s",filename);
3778 status=WriteImage(preview_info,preview_image);
3779 if (status != MagickFalse)
3780 {
3781 Image
3782 *quality_image;
3783
3784 (void) CopyMagickString(preview_info->filename,
3785 preview_image->filename,MaxTextExtent);
3786 quality_image=ReadImage(preview_info,exception);
3787 if (quality_image != (Image *) NULL)
3788 {
3789 preview_image=DestroyImage(preview_image);
3790 preview_image=quality_image;
3791 }
3792 }
3793 (void) RelinquishUniqueFileResource(preview_image->filename);
3794 if ((GetBlobSize(preview_image)/1024) >= 1024)
cristy8cd5b312010-01-07 01:10:24 +00003795 (void) FormatMagickString(label,MaxTextExtent,"quality %s\n%.15gmb ",
cristy3ed852e2009-09-05 21:47:34 +00003796 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
3797 1024.0/1024.0);
3798 else
3799 if (GetBlobSize(preview_image) >= 1024)
cristy8cd5b312010-01-07 01:10:24 +00003800 (void) FormatMagickString(label,MaxTextExtent,
3801 "quality %s\n%.15gkb ",factor,(double) ((MagickOffsetType)
3802 GetBlobSize(preview_image))/1024.0);
cristy3ed852e2009-09-05 21:47:34 +00003803 else
3804 (void) FormatMagickString(label,MaxTextExtent,"quality %s\n%lub ",
3805 factor,(unsigned long) GetBlobSize(thumbnail));
3806 break;
3807 }
3808 }
3809 thumbnail=DestroyImage(thumbnail);
3810 percentage+=12.5;
3811 radius+=0.5;
3812 sigma+=0.25;
3813 if (preview_image == (Image *) NULL)
3814 break;
3815 (void) DeleteImageProperty(preview_image,"label");
3816 (void) SetImageProperty(preview_image,"label",label);
3817 AppendImageToList(&images,preview_image);
3818 proceed=SetImageProgress(image,PreviewImageTag,i,NumberTiles);
3819 if (proceed == MagickFalse)
3820 break;
3821 }
3822 if (images == (Image *) NULL)
3823 {
3824 preview_info=DestroyImageInfo(preview_info);
3825 return((Image *) NULL);
3826 }
3827 /*
3828 Create the montage.
3829 */
3830 montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL);
3831 (void) CopyMagickString(montage_info->filename,image->filename,MaxTextExtent);
3832 montage_info->shadow=MagickTrue;
3833 (void) CloneString(&montage_info->tile,"3x3");
3834 (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry);
3835 (void) CloneString(&montage_info->frame,DefaultTileFrame);
3836 montage_image=MontageImages(images,montage_info,exception);
3837 montage_info=DestroyMontageInfo(montage_info);
3838 images=DestroyImageList(images);
3839 if (montage_image == (Image *) NULL)
3840 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3841 if (montage_image->montage != (char *) NULL)
3842 {
3843 /*
3844 Free image directory.
3845 */
3846 montage_image->montage=(char *) RelinquishMagickMemory(
3847 montage_image->montage);
3848 if (image->directory != (char *) NULL)
3849 montage_image->directory=(char *) RelinquishMagickMemory(
3850 montage_image->directory);
3851 }
3852 preview_info=DestroyImageInfo(preview_info);
3853 return(montage_image);
3854}
3855
3856/*
3857%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3858% %
3859% %
3860% %
3861% R a d i a l B l u r I m a g e %
3862% %
3863% %
3864% %
3865%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3866%
3867% RadialBlurImage() applies a radial blur to the image.
3868%
3869% Andrew Protano contributed this effect.
3870%
3871% The format of the RadialBlurImage method is:
3872%
3873% Image *RadialBlurImage(const Image *image,const double angle,
3874% ExceptionInfo *exception)
3875% Image *RadialBlurImageChannel(const Image *image,const ChannelType channel,
3876% const double angle,ExceptionInfo *exception)
3877%
3878% A description of each parameter follows:
3879%
3880% o image: the image.
3881%
3882% o channel: the channel type.
3883%
3884% o angle: the angle of the radial blur.
3885%
3886% o exception: return any errors or warnings in this structure.
3887%
3888*/
3889
3890MagickExport Image *RadialBlurImage(const Image *image,const double angle,
3891 ExceptionInfo *exception)
3892{
3893 Image
3894 *blur_image;
3895
3896 blur_image=RadialBlurImageChannel(image,DefaultChannels,angle,exception);
3897 return(blur_image);
3898}
3899
3900MagickExport Image *RadialBlurImageChannel(const Image *image,
3901 const ChannelType channel,const double angle,ExceptionInfo *exception)
3902{
cristyc4c8d132010-01-07 01:58:38 +00003903 CacheView
3904 *blur_view,
3905 *image_view;
3906
cristy3ed852e2009-09-05 21:47:34 +00003907 Image
3908 *blur_image;
3909
3910 long
3911 progress,
3912 y;
3913
3914 MagickBooleanType
3915 status;
3916
3917 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00003918 bias;
cristy3ed852e2009-09-05 21:47:34 +00003919
3920 MagickRealType
3921 blur_radius,
3922 *cos_theta,
3923 offset,
3924 *sin_theta,
3925 theta;
3926
3927 PointInfo
3928 blur_center;
3929
3930 register long
3931 i;
3932
3933 unsigned long
3934 n;
3935
cristy3ed852e2009-09-05 21:47:34 +00003936 /*
3937 Allocate blur image.
3938 */
3939 assert(image != (Image *) NULL);
3940 assert(image->signature == MagickSignature);
3941 if (image->debug != MagickFalse)
3942 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3943 assert(exception != (ExceptionInfo *) NULL);
3944 assert(exception->signature == MagickSignature);
3945 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3946 if (blur_image == (Image *) NULL)
3947 return((Image *) NULL);
3948 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3949 {
3950 InheritException(exception,&blur_image->exception);
3951 blur_image=DestroyImage(blur_image);
3952 return((Image *) NULL);
3953 }
3954 blur_center.x=(double) image->columns/2.0;
3955 blur_center.y=(double) image->rows/2.0;
3956 blur_radius=hypot(blur_center.x,blur_center.y);
3957 n=(unsigned long) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+
3958 2UL);
3959 theta=DegreesToRadians(angle)/(MagickRealType) (n-1);
3960 cos_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3961 sizeof(*cos_theta));
3962 sin_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3963 sizeof(*sin_theta));
3964 if ((cos_theta == (MagickRealType *) NULL) ||
3965 (sin_theta == (MagickRealType *) NULL))
3966 {
3967 blur_image=DestroyImage(blur_image);
3968 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3969 }
3970 offset=theta*(MagickRealType) (n-1)/2.0;
3971 for (i=0; i < (long) n; i++)
3972 {
3973 cos_theta[i]=cos((double) (theta*i-offset));
3974 sin_theta[i]=sin((double) (theta*i-offset));
3975 }
3976 /*
3977 Radial blur image.
3978 */
3979 status=MagickTrue;
3980 progress=0;
cristyddd82202009-11-03 20:14:50 +00003981 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003982 image_view=AcquireCacheView(image);
3983 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003984#if defined(MAGICKCORE_OPENMP_SUPPORT)
3985 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003986#endif
3987 for (y=0; y < (long) blur_image->rows; y++)
3988 {
3989 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003990 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003991
3992 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003993 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00003994
3995 register long
3996 x;
3997
3998 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003999 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004000
4001 if (status == MagickFalse)
4002 continue;
4003 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
4004 exception);
4005 if (q == (PixelPacket *) NULL)
4006 {
4007 status=MagickFalse;
4008 continue;
4009 }
4010 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
4011 for (x=0; x < (long) blur_image->columns; x++)
4012 {
4013 MagickPixelPacket
4014 qixel;
4015
4016 MagickRealType
4017 normalize,
4018 radius;
4019
4020 PixelPacket
4021 pixel;
4022
4023 PointInfo
4024 center;
4025
4026 register long
4027 i;
4028
4029 unsigned long
4030 step;
4031
4032 center.x=(double) x-blur_center.x;
4033 center.y=(double) y-blur_center.y;
4034 radius=hypot((double) center.x,center.y);
4035 if (radius == 0)
4036 step=1;
4037 else
4038 {
4039 step=(unsigned long) (blur_radius/radius);
4040 if (step == 0)
4041 step=1;
4042 else
4043 if (step >= n)
4044 step=n-1;
4045 }
4046 normalize=0.0;
cristyddd82202009-11-03 20:14:50 +00004047 qixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00004048 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
4049 {
4050 for (i=0; i < (long) n; i+=step)
4051 {
4052 (void) GetOneCacheViewVirtualPixel(image_view,(long) (blur_center.x+
4053 center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),(long) (
4054 blur_center.y+center.x*sin_theta[i]+center.y*cos_theta[i]+0.5),
4055 &pixel,exception);
4056 qixel.red+=pixel.red;
4057 qixel.green+=pixel.green;
4058 qixel.blue+=pixel.blue;
4059 qixel.opacity+=pixel.opacity;
4060 if (image->colorspace == CMYKColorspace)
4061 {
4062 indexes=GetCacheViewVirtualIndexQueue(image_view);
4063 qixel.index+=(*indexes);
4064 }
4065 normalize+=1.0;
4066 }
4067 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
4068 normalize);
4069 if ((channel & RedChannel) != 0)
4070 q->red=RoundToQuantum(normalize*qixel.red);
4071 if ((channel & GreenChannel) != 0)
4072 q->green=RoundToQuantum(normalize*qixel.green);
4073 if ((channel & BlueChannel) != 0)
4074 q->blue=RoundToQuantum(normalize*qixel.blue);
4075 if ((channel & OpacityChannel) != 0)
4076 q->opacity=RoundToQuantum(normalize*qixel.opacity);
4077 if (((channel & IndexChannel) != 0) &&
4078 (image->colorspace == CMYKColorspace))
4079 blur_indexes[x]=(IndexPacket) RoundToQuantum(normalize*qixel.index);
4080 }
4081 else
4082 {
4083 MagickRealType
4084 alpha,
4085 gamma;
4086
4087 alpha=1.0;
4088 gamma=0.0;
4089 for (i=0; i < (long) n; i+=step)
4090 {
4091 (void) GetOneCacheViewVirtualPixel(image_view,(long) (blur_center.x+
4092 center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),(long) (
4093 blur_center.y+center.x*sin_theta[i]+center.y*cos_theta[i]+0.5),
4094 &pixel,exception);
4095 alpha=(MagickRealType) (QuantumScale*(QuantumRange-pixel.opacity));
4096 qixel.red+=alpha*pixel.red;
4097 qixel.green+=alpha*pixel.green;
4098 qixel.blue+=alpha*pixel.blue;
4099 qixel.opacity+=pixel.opacity;
4100 if (image->colorspace == CMYKColorspace)
4101 {
4102 indexes=GetCacheViewVirtualIndexQueue(image_view);
4103 qixel.index+=alpha*(*indexes);
4104 }
4105 gamma+=alpha;
4106 normalize+=1.0;
4107 }
4108 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
4109 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
4110 normalize);
4111 if ((channel & RedChannel) != 0)
4112 q->red=RoundToQuantum(gamma*qixel.red);
4113 if ((channel & GreenChannel) != 0)
4114 q->green=RoundToQuantum(gamma*qixel.green);
4115 if ((channel & BlueChannel) != 0)
4116 q->blue=RoundToQuantum(gamma*qixel.blue);
4117 if ((channel & OpacityChannel) != 0)
4118 q->opacity=RoundToQuantum(normalize*qixel.opacity);
4119 if (((channel & IndexChannel) != 0) &&
4120 (image->colorspace == CMYKColorspace))
4121 blur_indexes[x]=(IndexPacket) RoundToQuantum(gamma*qixel.index);
4122 }
4123 q++;
4124 }
4125 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
4126 status=MagickFalse;
4127 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4128 {
4129 MagickBooleanType
4130 proceed;
4131
cristyb5d5f722009-11-04 03:03:49 +00004132#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004133 #pragma omp critical (MagickCore_RadialBlurImageChannel)
4134#endif
4135 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
4136 if (proceed == MagickFalse)
4137 status=MagickFalse;
4138 }
4139 }
4140 blur_view=DestroyCacheView(blur_view);
4141 image_view=DestroyCacheView(image_view);
4142 cos_theta=(MagickRealType *) RelinquishMagickMemory(cos_theta);
4143 sin_theta=(MagickRealType *) RelinquishMagickMemory(sin_theta);
4144 if (status == MagickFalse)
4145 blur_image=DestroyImage(blur_image);
4146 return(blur_image);
4147}
4148
4149/*
4150%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4151% %
4152% %
4153% %
4154% R e d u c e N o i s e I m a g e %
4155% %
4156% %
4157% %
4158%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4159%
4160% ReduceNoiseImage() smooths the contours of an image while still preserving
4161% edge information. The algorithm works by replacing each pixel with its
4162% neighbor closest in value. A neighbor is defined by radius. Use a radius
4163% of 0 and ReduceNoise() selects a suitable radius for you.
4164%
4165% The format of the ReduceNoiseImage method is:
4166%
4167% Image *ReduceNoiseImage(const Image *image,const double radius,
4168% ExceptionInfo *exception)
4169%
4170% A description of each parameter follows:
4171%
4172% o image: the image.
4173%
4174% o radius: the radius of the pixel neighborhood.
4175%
4176% o exception: return any errors or warnings in this structure.
4177%
4178*/
4179
4180static MagickPixelPacket GetNonpeakMedianPixelList(MedianPixelList *pixel_list)
4181{
4182 MagickPixelPacket
4183 pixel;
4184
4185 register long
4186 channel;
4187
4188 register MedianSkipList
4189 *list;
4190
4191 unsigned long
4192 center,
4193 color,
4194 count,
4195 previous,
4196 next;
4197
4198 unsigned short
4199 channels[5];
4200
4201 /*
4202 Finds the median value for each of the color.
4203 */
4204 center=pixel_list->center;
4205 for (channel=0; channel < 5; channel++)
4206 {
4207 list=pixel_list->lists+channel;
4208 color=65536UL;
4209 next=list->nodes[color].next[0];
4210 count=0;
4211 do
4212 {
4213 previous=color;
4214 color=next;
4215 next=list->nodes[color].next[0];
4216 count+=list->nodes[color].count;
4217 }
4218 while (count <= center);
4219 if ((previous == 65536UL) && (next != 65536UL))
4220 color=next;
4221 else
4222 if ((previous != 65536UL) && (next == 65536UL))
4223 color=previous;
4224 channels[channel]=(unsigned short) color;
4225 }
4226 GetMagickPixelPacket((const Image *) NULL,&pixel);
4227 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4228 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4229 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
4230 pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
4231 pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]);
4232 return(pixel);
4233}
4234
4235MagickExport Image *ReduceNoiseImage(const Image *image,const double radius,
4236 ExceptionInfo *exception)
4237{
4238#define ReduceNoiseImageTag "ReduceNoise/Image"
4239
cristyfa112112010-01-04 17:48:07 +00004240 CacheView
4241 *image_view,
4242 *noise_view;
4243
cristy3ed852e2009-09-05 21:47:34 +00004244 Image
4245 *noise_image;
4246
4247 long
4248 progress,
4249 y;
4250
4251 MagickBooleanType
4252 status;
4253
4254 MedianPixelList
cristyfa112112010-01-04 17:48:07 +00004255 **restrict pixel_list;
cristy3ed852e2009-09-05 21:47:34 +00004256
4257 unsigned long
4258 width;
4259
cristy3ed852e2009-09-05 21:47:34 +00004260 /*
4261 Initialize noise image attributes.
4262 */
4263 assert(image != (Image *) NULL);
4264 assert(image->signature == MagickSignature);
4265 if (image->debug != MagickFalse)
4266 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4267 assert(exception != (ExceptionInfo *) NULL);
4268 assert(exception->signature == MagickSignature);
4269 width=GetOptimalKernelWidth2D(radius,0.5);
4270 if ((image->columns < width) || (image->rows < width))
4271 ThrowImageException(OptionError,"ImageSmallerThanKernelRadius");
4272 noise_image=CloneImage(image,image->columns,image->rows,MagickTrue,
4273 exception);
4274 if (noise_image == (Image *) NULL)
4275 return((Image *) NULL);
4276 if (SetImageStorageClass(noise_image,DirectClass) == MagickFalse)
4277 {
4278 InheritException(exception,&noise_image->exception);
4279 noise_image=DestroyImage(noise_image);
4280 return((Image *) NULL);
4281 }
4282 pixel_list=AcquireMedianPixelListThreadSet(width);
4283 if (pixel_list == (MedianPixelList **) NULL)
4284 {
4285 noise_image=DestroyImage(noise_image);
4286 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
4287 }
4288 /*
4289 Reduce noise image.
4290 */
4291 status=MagickTrue;
4292 progress=0;
4293 image_view=AcquireCacheView(image);
4294 noise_view=AcquireCacheView(noise_image);
cristyb5d5f722009-11-04 03:03:49 +00004295#if defined(MAGICKCORE_OPENMP_SUPPORT)
4296 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004297#endif
4298 for (y=0; y < (long) noise_image->rows; y++)
4299 {
4300 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004301 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00004302
4303 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004304 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00004305
4306 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004307 *restrict noise_indexes;
cristy3ed852e2009-09-05 21:47:34 +00004308
4309 register long
4310 id,
4311 x;
4312
4313 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004314 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004315
4316 if (status == MagickFalse)
4317 continue;
4318 p=GetCacheViewVirtualPixels(image_view,-((long) width/2L),y-(long) (width/
4319 2L),image->columns+width,width,exception);
4320 q=QueueCacheViewAuthenticPixels(noise_view,0,y,noise_image->columns,1,
4321 exception);
4322 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
4323 {
4324 status=MagickFalse;
4325 continue;
4326 }
4327 indexes=GetCacheViewVirtualIndexQueue(image_view);
4328 noise_indexes=GetCacheViewAuthenticIndexQueue(noise_view);
4329 id=GetOpenMPThreadId();
4330 for (x=0; x < (long) noise_image->columns; x++)
4331 {
4332 MagickPixelPacket
4333 pixel;
4334
4335 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004336 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +00004337
4338 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004339 *restrict s;
cristy3ed852e2009-09-05 21:47:34 +00004340
4341 register long
4342 u,
4343 v;
4344
4345 r=p;
4346 s=indexes+x;
4347 ResetMedianPixelList(pixel_list[id]);
4348 for (v=0; v < (long) width; v++)
4349 {
4350 for (u=0; u < (long) width; u++)
4351 InsertMedianPixelList(image,r+u,s+u,pixel_list[id]);
4352 r+=image->columns+width;
4353 s+=image->columns+width;
4354 }
4355 pixel=GetNonpeakMedianPixelList(pixel_list[id]);
4356 SetPixelPacket(noise_image,&pixel,q,noise_indexes+x);
4357 p++;
4358 q++;
4359 }
4360 if (SyncCacheViewAuthenticPixels(noise_view,exception) == MagickFalse)
4361 status=MagickFalse;
4362 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4363 {
4364 MagickBooleanType
4365 proceed;
4366
cristyb5d5f722009-11-04 03:03:49 +00004367#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004368 #pragma omp critical (MagickCore_ReduceNoiseImage)
4369#endif
4370 proceed=SetImageProgress(image,ReduceNoiseImageTag,progress++,
4371 image->rows);
4372 if (proceed == MagickFalse)
4373 status=MagickFalse;
4374 }
4375 }
4376 noise_view=DestroyCacheView(noise_view);
4377 image_view=DestroyCacheView(image_view);
4378 pixel_list=DestroyMedianPixelListThreadSet(pixel_list);
4379 return(noise_image);
4380}
4381
4382/*
4383%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4384% %
4385% %
4386% %
4387% S e l e c t i v e B l u r I m a g e %
4388% %
4389% %
4390% %
4391%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4392%
4393% SelectiveBlurImage() selectively blur pixels within a contrast threshold.
4394% It is similar to the unsharpen mask that sharpens everything with contrast
4395% above a certain threshold.
4396%
4397% The format of the SelectiveBlurImage method is:
4398%
4399% Image *SelectiveBlurImage(const Image *image,const double radius,
4400% const double sigma,const double threshold,ExceptionInfo *exception)
4401% Image *SelectiveBlurImageChannel(const Image *image,
4402% const ChannelType channel,const double radius,const double sigma,
4403% const double threshold,ExceptionInfo *exception)
4404%
4405% A description of each parameter follows:
4406%
4407% o image: the image.
4408%
4409% o channel: the channel type.
4410%
4411% o radius: the radius of the Gaussian, in pixels, not counting the center
4412% pixel.
4413%
4414% o sigma: the standard deviation of the Gaussian, in pixels.
4415%
4416% o threshold: only pixels within this contrast threshold are included
4417% in the blur operation.
4418%
4419% o exception: return any errors or warnings in this structure.
4420%
4421*/
4422
4423static inline MagickBooleanType SelectiveContrast(const PixelPacket *p,
4424 const PixelPacket *q,const double threshold)
4425{
4426 if (fabs(PixelIntensity(p)-PixelIntensity(q)) < threshold)
4427 return(MagickTrue);
4428 return(MagickFalse);
4429}
4430
4431MagickExport Image *SelectiveBlurImage(const Image *image,const double radius,
4432 const double sigma,const double threshold,ExceptionInfo *exception)
4433{
4434 Image
4435 *blur_image;
4436
4437 blur_image=SelectiveBlurImageChannel(image,DefaultChannels,radius,sigma,
4438 threshold,exception);
4439 return(blur_image);
4440}
4441
4442MagickExport Image *SelectiveBlurImageChannel(const Image *image,
4443 const ChannelType channel,const double radius,const double sigma,
4444 const double threshold,ExceptionInfo *exception)
4445{
4446#define SelectiveBlurImageTag "SelectiveBlur/Image"
4447
cristy47e00502009-12-17 19:19:57 +00004448 CacheView
4449 *blur_view,
4450 *image_view;
4451
cristy3ed852e2009-09-05 21:47:34 +00004452 double
cristy3ed852e2009-09-05 21:47:34 +00004453 *kernel;
4454
4455 Image
4456 *blur_image;
4457
4458 long
cristy47e00502009-12-17 19:19:57 +00004459 j,
cristy3ed852e2009-09-05 21:47:34 +00004460 progress,
cristy47e00502009-12-17 19:19:57 +00004461 u,
cristy3ed852e2009-09-05 21:47:34 +00004462 v,
4463 y;
4464
4465 MagickBooleanType
4466 status;
4467
4468 MagickPixelPacket
cristy3ed852e2009-09-05 21:47:34 +00004469 bias;
4470
4471 register long
cristy47e00502009-12-17 19:19:57 +00004472 i;
cristy3ed852e2009-09-05 21:47:34 +00004473
4474 unsigned long
4475 width;
4476
cristy3ed852e2009-09-05 21:47:34 +00004477 /*
4478 Initialize blur image attributes.
4479 */
4480 assert(image != (Image *) NULL);
4481 assert(image->signature == MagickSignature);
4482 if (image->debug != MagickFalse)
4483 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4484 assert(exception != (ExceptionInfo *) NULL);
4485 assert(exception->signature == MagickSignature);
4486 width=GetOptimalKernelWidth1D(radius,sigma);
4487 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
4488 if (kernel == (double *) NULL)
4489 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy47e00502009-12-17 19:19:57 +00004490 j=(long) width/2;
cristy3ed852e2009-09-05 21:47:34 +00004491 i=0;
cristy47e00502009-12-17 19:19:57 +00004492 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00004493 {
cristy47e00502009-12-17 19:19:57 +00004494 for (u=(-j); u <= j; u++)
4495 kernel[i++]=exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
4496 (2.0*MagickPI*MagickSigma*MagickSigma);
cristy3ed852e2009-09-05 21:47:34 +00004497 }
4498 if (image->debug != MagickFalse)
4499 {
4500 char
4501 format[MaxTextExtent],
4502 *message;
4503
4504 long
4505 u,
4506 v;
4507
4508 register const double
4509 *k;
4510
4511 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
4512 " SelectiveBlurImage with %ldx%ld kernel:",width,width);
4513 message=AcquireString("");
4514 k=kernel;
4515 for (v=0; v < (long) width; v++)
4516 {
4517 *message='\0';
4518 (void) FormatMagickString(format,MaxTextExtent,"%ld: ",v);
4519 (void) ConcatenateString(&message,format);
4520 for (u=0; u < (long) width; u++)
4521 {
4522 (void) FormatMagickString(format,MaxTextExtent,"%+f ",*k++);
4523 (void) ConcatenateString(&message,format);
4524 }
4525 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
4526 }
4527 message=DestroyString(message);
4528 }
4529 blur_image=CloneImage(image,0,0,MagickTrue,exception);
4530 if (blur_image == (Image *) NULL)
4531 return((Image *) NULL);
4532 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
4533 {
4534 InheritException(exception,&blur_image->exception);
4535 blur_image=DestroyImage(blur_image);
4536 return((Image *) NULL);
4537 }
4538 /*
4539 Threshold blur image.
4540 */
4541 status=MagickTrue;
4542 progress=0;
cristyddd82202009-11-03 20:14:50 +00004543 GetMagickPixelPacket(image,&bias);
4544 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00004545 image_view=AcquireCacheView(image);
4546 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00004547#if defined(MAGICKCORE_OPENMP_SUPPORT)
4548 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004549#endif
4550 for (y=0; y < (long) image->rows; y++)
4551 {
4552 MagickBooleanType
4553 sync;
4554
4555 MagickRealType
4556 gamma;
4557
4558 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004559 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00004560
4561 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004562 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00004563
4564 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004565 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00004566
4567 register long
4568 x;
4569
4570 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004571 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004572
4573 if (status == MagickFalse)
4574 continue;
4575 p=GetCacheViewVirtualPixels(image_view,-((long) width/2L),y-(long) (width/
4576 2L),image->columns+width,width,exception);
4577 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
4578 exception);
4579 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
4580 {
4581 status=MagickFalse;
4582 continue;
4583 }
4584 indexes=GetCacheViewVirtualIndexQueue(image_view);
4585 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
4586 for (x=0; x < (long) image->columns; x++)
4587 {
4588 long
4589 j,
4590 v;
4591
4592 MagickPixelPacket
4593 pixel;
4594
4595 register const double
cristyc47d1f82009-11-26 01:44:43 +00004596 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00004597
4598 register long
4599 u;
4600
cristyddd82202009-11-03 20:14:50 +00004601 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00004602 k=kernel;
4603 gamma=0.0;
4604 j=0;
4605 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
4606 {
4607 for (v=0; v < (long) width; v++)
4608 {
4609 for (u=0; u < (long) width; u++)
4610 {
4611 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4612 {
4613 pixel.red+=(*k)*(p+u+j)->red;
4614 pixel.green+=(*k)*(p+u+j)->green;
4615 pixel.blue+=(*k)*(p+u+j)->blue;
4616 gamma+=(*k);
4617 k++;
4618 }
4619 }
4620 j+=image->columns+width;
4621 }
4622 if (gamma != 0.0)
4623 {
4624 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
4625 if ((channel & RedChannel) != 0)
cristyc4c8d132010-01-07 01:58:38 +00004626 q->red=RoundToQuantum(gamma*GetRedSample(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004627 if ((channel & GreenChannel) != 0)
cristyc4c8d132010-01-07 01:58:38 +00004628 q->green=RoundToQuantum(gamma*GetGreenSample(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004629 if ((channel & BlueChannel) != 0)
cristyc4c8d132010-01-07 01:58:38 +00004630 q->blue=RoundToQuantum(gamma*GetBlueSample(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004631 }
4632 if ((channel & OpacityChannel) != 0)
4633 {
4634 gamma=0.0;
4635 j=0;
4636 for (v=0; v < (long) width; v++)
4637 {
4638 for (u=0; u < (long) width; u++)
4639 {
4640 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4641 {
4642 pixel.opacity+=(*k)*(p+u+j)->opacity;
4643 gamma+=(*k);
4644 k++;
4645 }
4646 }
4647 j+=image->columns+width;
4648 }
4649 if (gamma != 0.0)
4650 {
4651 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4652 gamma);
cristyc4c8d132010-01-07 01:58:38 +00004653 q->opacity=RoundToQuantum(gamma*GetOpacitySample(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004654 }
4655 }
4656 if (((channel & IndexChannel) != 0) &&
4657 (image->colorspace == CMYKColorspace))
4658 {
4659 gamma=0.0;
4660 j=0;
4661 for (v=0; v < (long) width; v++)
4662 {
4663 for (u=0; u < (long) width; u++)
4664 {
4665 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4666 {
4667 pixel.index+=(*k)*indexes[x+u+j];
4668 gamma+=(*k);
4669 k++;
4670 }
4671 }
4672 j+=image->columns+width;
4673 }
4674 if (gamma != 0.0)
4675 {
4676 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4677 gamma);
cristyc4c8d132010-01-07 01:58:38 +00004678 blur_indexes[x]=RoundToQuantum(gamma*GetIndexSample(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004679 }
4680 }
4681 }
4682 else
4683 {
4684 MagickRealType
4685 alpha;
4686
4687 for (v=0; v < (long) width; v++)
4688 {
4689 for (u=0; u < (long) width; u++)
4690 {
4691 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4692 {
4693 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
4694 (p+u+j)->opacity));
4695 pixel.red+=(*k)*alpha*(p+u+j)->red;
4696 pixel.green+=(*k)*alpha*(p+u+j)->green;
4697 pixel.blue+=(*k)*alpha*(p+u+j)->blue;
4698 pixel.opacity+=(*k)*(p+u+j)->opacity;
4699 gamma+=(*k)*alpha;
4700 k++;
4701 }
4702 }
4703 j+=image->columns+width;
4704 }
4705 if (gamma != 0.0)
4706 {
4707 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
4708 if ((channel & RedChannel) != 0)
cristyc4c8d132010-01-07 01:58:38 +00004709 q->red=RoundToQuantum(gamma*GetRedSample(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004710 if ((channel & GreenChannel) != 0)
cristyc4c8d132010-01-07 01:58:38 +00004711 q->green=RoundToQuantum(gamma*GetGreenSample(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004712 if ((channel & BlueChannel) != 0)
cristyc4c8d132010-01-07 01:58:38 +00004713 q->blue=RoundToQuantum(gamma*GetBlueSample(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004714 }
4715 if ((channel & OpacityChannel) != 0)
4716 {
4717 gamma=0.0;
4718 j=0;
4719 for (v=0; v < (long) width; v++)
4720 {
4721 for (u=0; u < (long) width; u++)
4722 {
4723 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4724 {
4725 pixel.opacity+=(*k)*(p+u+j)->opacity;
4726 gamma+=(*k);
4727 k++;
4728 }
4729 }
4730 j+=image->columns+width;
4731 }
4732 if (gamma != 0.0)
4733 {
4734 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4735 gamma);
cristyddd82202009-11-03 20:14:50 +00004736 q->opacity=RoundToQuantum(pixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00004737 }
4738 }
4739 if (((channel & IndexChannel) != 0) &&
4740 (image->colorspace == CMYKColorspace))
4741 {
4742 gamma=0.0;
4743 j=0;
4744 for (v=0; v < (long) width; v++)
4745 {
4746 for (u=0; u < (long) width; u++)
4747 {
4748 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4749 {
4750 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
4751 (p+u+j)->opacity));
4752 pixel.index+=(*k)*alpha*indexes[x+u+j];
4753 gamma+=(*k);
4754 k++;
4755 }
4756 }
4757 j+=image->columns+width;
4758 }
4759 if (gamma != 0.0)
4760 {
4761 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4762 gamma);
cristyc4c8d132010-01-07 01:58:38 +00004763 blur_indexes[x]=RoundToQuantum(gamma*GetIndexSample(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004764 }
4765 }
4766 }
4767 p++;
4768 q++;
4769 }
4770 sync=SyncCacheViewAuthenticPixels(blur_view,exception);
4771 if (sync == MagickFalse)
4772 status=MagickFalse;
4773 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4774 {
4775 MagickBooleanType
4776 proceed;
4777
cristyb5d5f722009-11-04 03:03:49 +00004778#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004779 #pragma omp critical (MagickCore_SelectiveBlurImageChannel)
4780#endif
4781 proceed=SetImageProgress(image,SelectiveBlurImageTag,progress++,
4782 image->rows);
4783 if (proceed == MagickFalse)
4784 status=MagickFalse;
4785 }
4786 }
4787 blur_image->type=image->type;
4788 blur_view=DestroyCacheView(blur_view);
4789 image_view=DestroyCacheView(image_view);
4790 kernel=(double *) RelinquishMagickMemory(kernel);
4791 if (status == MagickFalse)
4792 blur_image=DestroyImage(blur_image);
4793 return(blur_image);
4794}
4795
4796/*
4797%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4798% %
4799% %
4800% %
4801% S h a d e I m a g e %
4802% %
4803% %
4804% %
4805%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4806%
4807% ShadeImage() shines a distant light on an image to create a
4808% three-dimensional effect. You control the positioning of the light with
4809% azimuth and elevation; azimuth is measured in degrees off the x axis
4810% and elevation is measured in pixels above the Z axis.
4811%
4812% The format of the ShadeImage method is:
4813%
4814% Image *ShadeImage(const Image *image,const MagickBooleanType gray,
4815% const double azimuth,const double elevation,ExceptionInfo *exception)
4816%
4817% A description of each parameter follows:
4818%
4819% o image: the image.
4820%
4821% o gray: A value other than zero shades the intensity of each pixel.
4822%
4823% o azimuth, elevation: Define the light source direction.
4824%
4825% o exception: return any errors or warnings in this structure.
4826%
4827*/
4828MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray,
4829 const double azimuth,const double elevation,ExceptionInfo *exception)
4830{
4831#define ShadeImageTag "Shade/Image"
4832
cristyc4c8d132010-01-07 01:58:38 +00004833 CacheView
4834 *image_view,
4835 *shade_view;
4836
cristy3ed852e2009-09-05 21:47:34 +00004837 Image
4838 *shade_image;
4839
4840 long
4841 progress,
4842 y;
4843
4844 MagickBooleanType
4845 status;
4846
4847 PrimaryInfo
4848 light;
4849
cristy3ed852e2009-09-05 21:47:34 +00004850 /*
4851 Initialize shaded image attributes.
4852 */
4853 assert(image != (const Image *) NULL);
4854 assert(image->signature == MagickSignature);
4855 if (image->debug != MagickFalse)
4856 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4857 assert(exception != (ExceptionInfo *) NULL);
4858 assert(exception->signature == MagickSignature);
4859 shade_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
4860 if (shade_image == (Image *) NULL)
4861 return((Image *) NULL);
4862 if (SetImageStorageClass(shade_image,DirectClass) == MagickFalse)
4863 {
4864 InheritException(exception,&shade_image->exception);
4865 shade_image=DestroyImage(shade_image);
4866 return((Image *) NULL);
4867 }
4868 /*
4869 Compute the light vector.
4870 */
4871 light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))*
4872 cos(DegreesToRadians(elevation));
4873 light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))*
4874 cos(DegreesToRadians(elevation));
4875 light.z=(double) QuantumRange*sin(DegreesToRadians(elevation));
4876 /*
4877 Shade image.
4878 */
4879 status=MagickTrue;
4880 progress=0;
4881 image_view=AcquireCacheView(image);
4882 shade_view=AcquireCacheView(shade_image);
cristyb5d5f722009-11-04 03:03:49 +00004883#if defined(MAGICKCORE_OPENMP_SUPPORT)
4884 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004885#endif
4886 for (y=0; y < (long) image->rows; y++)
4887 {
4888 MagickRealType
4889 distance,
4890 normal_distance,
4891 shade;
4892
4893 PrimaryInfo
4894 normal;
4895
4896 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004897 *restrict p,
4898 *restrict s0,
4899 *restrict s1,
4900 *restrict s2;
cristy3ed852e2009-09-05 21:47:34 +00004901
4902 register long
4903 x;
4904
4905 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004906 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004907
4908 if (status == MagickFalse)
4909 continue;
4910 p=GetCacheViewVirtualPixels(image_view,-1,y-1,image->columns+2,3,exception);
4911 q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1,
4912 exception);
4913 if ((p == (PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
4914 {
4915 status=MagickFalse;
4916 continue;
4917 }
4918 /*
4919 Shade this row of pixels.
4920 */
4921 normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */
4922 s0=p+1;
4923 s1=s0+image->columns+2;
4924 s2=s1+image->columns+2;
4925 for (x=0; x < (long) image->columns; x++)
4926 {
4927 /*
4928 Determine the surface normal and compute shading.
4929 */
4930 normal.x=(double) (PixelIntensity(s0-1)+PixelIntensity(s1-1)+
4931 PixelIntensity(s2-1)-PixelIntensity(s0+1)-PixelIntensity(s1+1)-
4932 PixelIntensity(s2+1));
4933 normal.y=(double) (PixelIntensity(s2-1)+PixelIntensity(s2)+
4934 PixelIntensity(s2+1)-PixelIntensity(s0-1)-PixelIntensity(s0)-
4935 PixelIntensity(s0+1));
4936 if ((normal.x == 0.0) && (normal.y == 0.0))
4937 shade=light.z;
4938 else
4939 {
4940 shade=0.0;
4941 distance=normal.x*light.x+normal.y*light.y+normal.z*light.z;
4942 if (distance > MagickEpsilon)
4943 {
4944 normal_distance=
4945 normal.x*normal.x+normal.y*normal.y+normal.z*normal.z;
4946 if (normal_distance > (MagickEpsilon*MagickEpsilon))
4947 shade=distance/sqrt((double) normal_distance);
4948 }
4949 }
4950 if (gray != MagickFalse)
4951 {
4952 q->red=(Quantum) shade;
4953 q->green=(Quantum) shade;
4954 q->blue=(Quantum) shade;
4955 }
4956 else
4957 {
4958 q->red=RoundToQuantum(QuantumScale*shade*s1->red);
4959 q->green=RoundToQuantum(QuantumScale*shade*s1->green);
4960 q->blue=RoundToQuantum(QuantumScale*shade*s1->blue);
4961 }
4962 q->opacity=s1->opacity;
4963 s0++;
4964 s1++;
4965 s2++;
4966 q++;
4967 }
4968 if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
4969 status=MagickFalse;
4970 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4971 {
4972 MagickBooleanType
4973 proceed;
4974
cristyb5d5f722009-11-04 03:03:49 +00004975#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004976 #pragma omp critical (MagickCore_ShadeImage)
4977#endif
4978 proceed=SetImageProgress(image,ShadeImageTag,progress++,image->rows);
4979 if (proceed == MagickFalse)
4980 status=MagickFalse;
4981 }
4982 }
4983 shade_view=DestroyCacheView(shade_view);
4984 image_view=DestroyCacheView(image_view);
4985 if (status == MagickFalse)
4986 shade_image=DestroyImage(shade_image);
4987 return(shade_image);
4988}
4989
4990/*
4991%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4992% %
4993% %
4994% %
4995% S h a r p e n I m a g e %
4996% %
4997% %
4998% %
4999%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5000%
5001% SharpenImage() sharpens the image. We convolve the image with a Gaussian
5002% operator of the given radius and standard deviation (sigma). For
5003% reasonable results, radius should be larger than sigma. Use a radius of 0
5004% and SharpenImage() selects a suitable radius for you.
5005%
5006% Using a separable kernel would be faster, but the negative weights cancel
5007% out on the corners of the kernel producing often undesirable ringing in the
5008% filtered result; this can be avoided by using a 2D gaussian shaped image
5009% sharpening kernel instead.
5010%
5011% The format of the SharpenImage method is:
5012%
5013% Image *SharpenImage(const Image *image,const double radius,
5014% const double sigma,ExceptionInfo *exception)
5015% Image *SharpenImageChannel(const Image *image,const ChannelType channel,
5016% const double radius,const double sigma,ExceptionInfo *exception)
5017%
5018% A description of each parameter follows:
5019%
5020% o image: the image.
5021%
5022% o channel: the channel type.
5023%
5024% o radius: the radius of the Gaussian, in pixels, not counting the center
5025% pixel.
5026%
5027% o sigma: the standard deviation of the Laplacian, in pixels.
5028%
5029% o exception: return any errors or warnings in this structure.
5030%
5031*/
5032
5033MagickExport Image *SharpenImage(const Image *image,const double radius,
5034 const double sigma,ExceptionInfo *exception)
5035{
5036 Image
5037 *sharp_image;
5038
5039 sharp_image=SharpenImageChannel(image,DefaultChannels,radius,sigma,exception);
5040 return(sharp_image);
5041}
5042
5043MagickExport Image *SharpenImageChannel(const Image *image,
5044 const ChannelType channel,const double radius,const double sigma,
5045 ExceptionInfo *exception)
5046{
5047 double
cristy47e00502009-12-17 19:19:57 +00005048 *kernel,
5049 normalize;
cristy3ed852e2009-09-05 21:47:34 +00005050
5051 Image
5052 *sharp_image;
5053
cristy47e00502009-12-17 19:19:57 +00005054 long
5055 j,
cristy3ed852e2009-09-05 21:47:34 +00005056 u,
5057 v;
5058
cristy47e00502009-12-17 19:19:57 +00005059 register long
5060 i;
5061
cristy3ed852e2009-09-05 21:47:34 +00005062 unsigned long
5063 width;
5064
5065 assert(image != (const Image *) NULL);
5066 assert(image->signature == MagickSignature);
5067 if (image->debug != MagickFalse)
5068 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5069 assert(exception != (ExceptionInfo *) NULL);
5070 assert(exception->signature == MagickSignature);
5071 width=GetOptimalKernelWidth2D(radius,sigma);
5072 kernel=(double *) AcquireQuantumMemory((size_t) width*width,sizeof(*kernel));
5073 if (kernel == (double *) NULL)
5074 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy3ed852e2009-09-05 21:47:34 +00005075 normalize=0.0;
cristy47e00502009-12-17 19:19:57 +00005076 j=(long) width/2;
5077 i=0;
5078 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00005079 {
cristy47e00502009-12-17 19:19:57 +00005080 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00005081 {
cristy47e00502009-12-17 19:19:57 +00005082 kernel[i]=(-exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
5083 (2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00005084 normalize+=kernel[i];
5085 i++;
5086 }
5087 }
5088 kernel[i/2]=(double) ((-2.0)*normalize);
5089 sharp_image=ConvolveImageChannel(image,channel,width,kernel,exception);
5090 kernel=(double *) RelinquishMagickMemory(kernel);
5091 return(sharp_image);
5092}
5093
5094/*
5095%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5096% %
5097% %
5098% %
5099% S p r e a d I m a g e %
5100% %
5101% %
5102% %
5103%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5104%
5105% SpreadImage() is a special effects method that randomly displaces each
5106% pixel in a block defined by the radius parameter.
5107%
5108% The format of the SpreadImage method is:
5109%
5110% Image *SpreadImage(const Image *image,const double radius,
5111% ExceptionInfo *exception)
5112%
5113% A description of each parameter follows:
5114%
5115% o image: the image.
5116%
5117% o radius: Choose a random pixel in a neighborhood of this extent.
5118%
5119% o exception: return any errors or warnings in this structure.
5120%
5121*/
5122MagickExport Image *SpreadImage(const Image *image,const double radius,
5123 ExceptionInfo *exception)
5124{
5125#define SpreadImageTag "Spread/Image"
5126
cristyfa112112010-01-04 17:48:07 +00005127 CacheView
5128 *image_view;
5129
cristy3ed852e2009-09-05 21:47:34 +00005130 Image
5131 *spread_image;
5132
5133 long
5134 progress,
5135 y;
5136
5137 MagickBooleanType
5138 status;
5139
5140 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00005141 bias;
cristy3ed852e2009-09-05 21:47:34 +00005142
5143 RandomInfo
cristyfa112112010-01-04 17:48:07 +00005144 **restrict random_info;
cristy3ed852e2009-09-05 21:47:34 +00005145
5146 ResampleFilter
cristyfa112112010-01-04 17:48:07 +00005147 **restrict resample_filter;
cristy3ed852e2009-09-05 21:47:34 +00005148
5149 unsigned long
5150 width;
5151
cristy3ed852e2009-09-05 21:47:34 +00005152 /*
5153 Initialize spread image attributes.
5154 */
5155 assert(image != (Image *) NULL);
5156 assert(image->signature == MagickSignature);
5157 if (image->debug != MagickFalse)
5158 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5159 assert(exception != (ExceptionInfo *) NULL);
5160 assert(exception->signature == MagickSignature);
5161 spread_image=CloneImage(image,image->columns,image->rows,MagickTrue,
5162 exception);
5163 if (spread_image == (Image *) NULL)
5164 return((Image *) NULL);
5165 if (SetImageStorageClass(spread_image,DirectClass) == MagickFalse)
5166 {
5167 InheritException(exception,&spread_image->exception);
5168 spread_image=DestroyImage(spread_image);
5169 return((Image *) NULL);
5170 }
5171 /*
5172 Spread image.
5173 */
5174 status=MagickTrue;
5175 progress=0;
cristyddd82202009-11-03 20:14:50 +00005176 GetMagickPixelPacket(spread_image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00005177 width=GetOptimalKernelWidth1D(radius,0.5);
5178 resample_filter=AcquireResampleFilterThreadSet(image,MagickTrue,exception);
5179 random_info=AcquireRandomInfoThreadSet();
5180 image_view=AcquireCacheView(spread_image);
cristyb5d5f722009-11-04 03:03:49 +00005181#if defined(MAGICKCORE_OPENMP_SUPPORT)
5182 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00005183#endif
5184 for (y=0; y < (long) spread_image->rows; y++)
5185 {
5186 MagickPixelPacket
5187 pixel;
5188
5189 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00005190 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00005191
5192 register long
5193 id,
5194 x;
5195
5196 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00005197 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00005198
5199 if (status == MagickFalse)
5200 continue;
5201 q=QueueCacheViewAuthenticPixels(image_view,0,y,spread_image->columns,1,
5202 exception);
5203 if (q == (PixelPacket *) NULL)
5204 {
5205 status=MagickFalse;
5206 continue;
5207 }
5208 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristyddd82202009-11-03 20:14:50 +00005209 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00005210 id=GetOpenMPThreadId();
5211 for (x=0; x < (long) spread_image->columns; x++)
5212 {
5213 (void) ResamplePixelColor(resample_filter[id],(double) x+width*
5214 (GetPseudoRandomValue(random_info[id])-0.5),(double) y+width*
5215 (GetPseudoRandomValue(random_info[id])-0.5),&pixel);
5216 SetPixelPacket(spread_image,&pixel,q,indexes+x);
5217 q++;
5218 }
5219 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
5220 status=MagickFalse;
5221 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5222 {
5223 MagickBooleanType
5224 proceed;
5225
cristyb5d5f722009-11-04 03:03:49 +00005226#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00005227 #pragma omp critical (MagickCore_SpreadImage)
5228#endif
5229 proceed=SetImageProgress(image,SpreadImageTag,progress++,image->rows);
5230 if (proceed == MagickFalse)
5231 status=MagickFalse;
5232 }
5233 }
5234 image_view=DestroyCacheView(image_view);
5235 random_info=DestroyRandomInfoThreadSet(random_info);
5236 resample_filter=DestroyResampleFilterThreadSet(resample_filter);
5237 return(spread_image);
5238}
5239
5240/*
5241%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5242% %
5243% %
5244% %
5245% U n s h a r p M a s k I m a g e %
5246% %
5247% %
5248% %
5249%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5250%
5251% UnsharpMaskImage() sharpens one or more image channels. We convolve the
5252% image with a Gaussian operator of the given radius and standard deviation
5253% (sigma). For reasonable results, radius should be larger than sigma. Use a
5254% radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
5255%
5256% The format of the UnsharpMaskImage method is:
5257%
5258% Image *UnsharpMaskImage(const Image *image,const double radius,
5259% const double sigma,const double amount,const double threshold,
5260% ExceptionInfo *exception)
5261% Image *UnsharpMaskImageChannel(const Image *image,
5262% const ChannelType channel,const double radius,const double sigma,
5263% const double amount,const double threshold,ExceptionInfo *exception)
5264%
5265% A description of each parameter follows:
5266%
5267% o image: the image.
5268%
5269% o channel: the channel type.
5270%
5271% o radius: the radius of the Gaussian, in pixels, not counting the center
5272% pixel.
5273%
5274% o sigma: the standard deviation of the Gaussian, in pixels.
5275%
5276% o amount: the percentage of the difference between the original and the
5277% blur image that is added back into the original.
5278%
5279% o threshold: the threshold in pixels needed to apply the diffence amount.
5280%
5281% o exception: return any errors or warnings in this structure.
5282%
5283*/
5284
5285MagickExport Image *UnsharpMaskImage(const Image *image,const double radius,
5286 const double sigma,const double amount,const double threshold,
5287 ExceptionInfo *exception)
5288{
5289 Image
5290 *sharp_image;
5291
5292 sharp_image=UnsharpMaskImageChannel(image,DefaultChannels,radius,sigma,amount,
5293 threshold,exception);
5294 return(sharp_image);
5295}
5296
5297MagickExport Image *UnsharpMaskImageChannel(const Image *image,
5298 const ChannelType channel,const double radius,const double sigma,
5299 const double amount,const double threshold,ExceptionInfo *exception)
5300{
5301#define SharpenImageTag "Sharpen/Image"
5302
cristyc4c8d132010-01-07 01:58:38 +00005303 CacheView
5304 *image_view,
5305 *unsharp_view;
5306
cristy3ed852e2009-09-05 21:47:34 +00005307 Image
5308 *unsharp_image;
5309
5310 long
5311 progress,
5312 y;
5313
5314 MagickBooleanType
5315 status;
5316
5317 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00005318 bias;
cristy3ed852e2009-09-05 21:47:34 +00005319
5320 MagickRealType
5321 quantum_threshold;
5322
cristy3ed852e2009-09-05 21:47:34 +00005323 assert(image != (const Image *) NULL);
5324 assert(image->signature == MagickSignature);
5325 if (image->debug != MagickFalse)
5326 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5327 assert(exception != (ExceptionInfo *) NULL);
5328 unsharp_image=BlurImageChannel(image,channel,radius,sigma,exception);
5329 if (unsharp_image == (Image *) NULL)
5330 return((Image *) NULL);
5331 quantum_threshold=(MagickRealType) QuantumRange*threshold;
5332 /*
5333 Unsharp-mask image.
5334 */
5335 status=MagickTrue;
5336 progress=0;
cristyddd82202009-11-03 20:14:50 +00005337 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00005338 image_view=AcquireCacheView(image);
5339 unsharp_view=AcquireCacheView(unsharp_image);
cristyb5d5f722009-11-04 03:03:49 +00005340#if defined(MAGICKCORE_OPENMP_SUPPORT)
5341 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00005342#endif
5343 for (y=0; y < (long) image->rows; y++)
5344 {
5345 MagickPixelPacket
5346 pixel;
5347
5348 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00005349 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00005350
5351 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00005352 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00005353
5354 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00005355 *restrict unsharp_indexes;
cristy3ed852e2009-09-05 21:47:34 +00005356
5357 register long
5358 x;
5359
5360 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00005361 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00005362
5363 if (status == MagickFalse)
5364 continue;
5365 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
5366 q=GetCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
5367 exception);
5368 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
5369 {
5370 status=MagickFalse;
5371 continue;
5372 }
5373 indexes=GetCacheViewVirtualIndexQueue(image_view);
5374 unsharp_indexes=GetCacheViewAuthenticIndexQueue(unsharp_view);
cristyddd82202009-11-03 20:14:50 +00005375 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00005376 for (x=0; x < (long) image->columns; x++)
5377 {
5378 if ((channel & RedChannel) != 0)
5379 {
5380 pixel.red=p->red-(MagickRealType) q->red;
5381 if (fabs(2.0*pixel.red) < quantum_threshold)
cristyc4c8d132010-01-07 01:58:38 +00005382 pixel.red=(MagickRealType) GetRedSample(p);
cristy3ed852e2009-09-05 21:47:34 +00005383 else
5384 pixel.red=(MagickRealType) p->red+(pixel.red*amount);
5385 q->red=RoundToQuantum(pixel.red);
5386 }
5387 if ((channel & GreenChannel) != 0)
5388 {
5389 pixel.green=p->green-(MagickRealType) q->green;
5390 if (fabs(2.0*pixel.green) < quantum_threshold)
cristyc4c8d132010-01-07 01:58:38 +00005391 pixel.green=(MagickRealType) GetGreenSample(p);
cristy3ed852e2009-09-05 21:47:34 +00005392 else
5393 pixel.green=(MagickRealType) p->green+(pixel.green*amount);
5394 q->green=RoundToQuantum(pixel.green);
5395 }
5396 if ((channel & BlueChannel) != 0)
5397 {
5398 pixel.blue=p->blue-(MagickRealType) q->blue;
5399 if (fabs(2.0*pixel.blue) < quantum_threshold)
cristyc4c8d132010-01-07 01:58:38 +00005400 pixel.blue=(MagickRealType) GetBlueSample(p);
cristy3ed852e2009-09-05 21:47:34 +00005401 else
5402 pixel.blue=(MagickRealType) p->blue+(pixel.blue*amount);
5403 q->blue=RoundToQuantum(pixel.blue);
5404 }
5405 if ((channel & OpacityChannel) != 0)
5406 {
5407 pixel.opacity=p->opacity-(MagickRealType) q->opacity;
5408 if (fabs(2.0*pixel.opacity) < quantum_threshold)
cristyc4c8d132010-01-07 01:58:38 +00005409 pixel.opacity=(MagickRealType) GetOpacitySample(p);
cristy3ed852e2009-09-05 21:47:34 +00005410 else
5411 pixel.opacity=p->opacity+(pixel.opacity*amount);
5412 q->opacity=RoundToQuantum(pixel.opacity);
5413 }
5414 if (((channel & IndexChannel) != 0) &&
5415 (image->colorspace == CMYKColorspace))
5416 {
5417 pixel.index=unsharp_indexes[x]-(MagickRealType) indexes[x];
5418 if (fabs(2.0*pixel.index) < quantum_threshold)
5419 pixel.index=(MagickRealType) unsharp_indexes[x];
5420 else
5421 pixel.index=(MagickRealType) unsharp_indexes[x]+(pixel.index*
5422 amount);
5423 unsharp_indexes[x]=RoundToQuantum(pixel.index);
5424 }
5425 p++;
5426 q++;
5427 }
5428 if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
5429 status=MagickFalse;
5430 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5431 {
5432 MagickBooleanType
5433 proceed;
5434
cristyb5d5f722009-11-04 03:03:49 +00005435#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00005436 #pragma omp critical (MagickCore_UnsharpMaskImageChannel)
5437#endif
5438 proceed=SetImageProgress(image,SharpenImageTag,progress++,image->rows);
5439 if (proceed == MagickFalse)
5440 status=MagickFalse;
5441 }
5442 }
5443 unsharp_image->type=image->type;
5444 unsharp_view=DestroyCacheView(unsharp_view);
5445 image_view=DestroyCacheView(image_view);
5446 if (status == MagickFalse)
5447 unsharp_image=DestroyImage(unsharp_image);
5448 return(unsharp_image);
5449}