blob: d6915eb12481b1acaa819f932616e9e6b37ede7f [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% %
20% Copyright 1999-2009 ImageMagick Studio LLC, a non-profit organization %
21% 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
144 double
145 **kernel;
146
147 Image
148 *blur_image,
149 *edge_image,
150 *gaussian_image;
151
152 long
153 j,
154 progress,
155 y;
156
157 MagickBooleanType
158 status;
159
160 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +0000161 bias;
cristy3ed852e2009-09-05 21:47:34 +0000162
163 MagickRealType
164 alpha,
cristy3ed852e2009-09-05 21:47:34 +0000165 normalize;
166
167 register long
168 i,
169 u,
170 v;
171
172 unsigned long
173 width;
174
175 CacheView
176 *blur_view,
177 *edge_view,
178 *image_view;
179
180 assert(image != (const Image *) NULL);
181 assert(image->signature == MagickSignature);
182 if (image->debug != MagickFalse)
183 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
184 assert(exception != (ExceptionInfo *) NULL);
185 assert(exception->signature == MagickSignature);
186 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
187 if (blur_image == (Image *) NULL)
188 return((Image *) NULL);
189 if (fabs(sigma) <= MagickEpsilon)
190 return(blur_image);
191 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
192 {
193 InheritException(exception,&blur_image->exception);
194 blur_image=DestroyImage(blur_image);
195 return((Image *) NULL);
196 }
197 /*
198 Edge detect the image brighness channel, level, blur, and level again.
199 */
200 edge_image=EdgeImage(image,radius,exception);
201 if (edge_image == (Image *) NULL)
202 {
203 blur_image=DestroyImage(blur_image);
204 return((Image *) NULL);
205 }
206 (void) LevelImage(edge_image,"20%,95%");
207 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,exception);
208 if (gaussian_image != (Image *) NULL)
209 {
210 edge_image=DestroyImage(edge_image);
211 edge_image=gaussian_image;
212 }
213 (void) LevelImage(edge_image,"10%,95%");
214 /*
215 Create a set of kernels from maximum (radius,sigma) to minimum.
216 */
217 width=GetOptimalKernelWidth2D(radius,sigma);
218 kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
219 if (kernel == (double **) NULL)
220 {
221 edge_image=DestroyImage(edge_image);
222 blur_image=DestroyImage(blur_image);
223 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
224 }
225 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
226 for (i=0; i < (long) width; i+=2)
227 {
228 kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)*
229 sizeof(**kernel));
230 if (kernel[i] == (double *) NULL)
231 break;
232 j=0;
233 for (v=(-((long) (width-i)/2)); v <= (long) ((width-i)/2); v++)
234 {
235 for (u=(-((long) (width-i)/2)); u <= (long) ((width-i)/2); u++)
236 {
237 alpha=exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma));
238 kernel[i][j]=(double) (alpha/(2.0*MagickPI*MagickSigma*MagickSigma));
239 j++;
240 }
241 }
242 normalize=0.0;
243 for (j=0; j < (long) ((width-i)*(width-i)); j++)
244 normalize+=kernel[i][j];
245 if (fabs(normalize) <= MagickEpsilon)
246 normalize=1.0;
247 normalize=1.0/normalize;
248 for (j=0; j < (long) ((width-i)*(width-i)); j++)
249 kernel[i][j]=(double) (normalize*kernel[i][j]);
250 }
251 if (i < (long) width)
252 {
253 for (i-=2; i >= 0; i-=2)
254 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
255 kernel=(double **) RelinquishMagickMemory(kernel);
256 edge_image=DestroyImage(edge_image);
257 blur_image=DestroyImage(blur_image);
258 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
259 }
260 /*
261 Adaptively blur image.
262 */
263 status=MagickTrue;
264 progress=0;
cristyddd82202009-11-03 20:14:50 +0000265 GetMagickPixelPacket(image,&bias);
266 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000267 image_view=AcquireCacheView(image);
268 edge_view=AcquireCacheView(edge_image);
269 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000270#if defined(MAGICKCORE_OPENMP_SUPPORT)
271 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000272#endif
273 for (y=0; y < (long) blur_image->rows; y++)
274 {
275 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000276 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000277
278 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000279 *restrict p,
280 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000281
282 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000283 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000284
285 register long
286 x;
287
288 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000289 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000290
291 if (status == MagickFalse)
292 continue;
293 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
294 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
295 exception);
296 if ((r == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
297 {
298 status=MagickFalse;
299 continue;
300 }
301 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
302 for (x=0; x < (long) blur_image->columns; x++)
303 {
304 MagickPixelPacket
305 pixel;
306
307 MagickRealType
308 alpha,
309 gamma;
310
311 register const double
cristyc47d1f82009-11-26 01:44:43 +0000312 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000313
314 register long
315 i,
316 u,
317 v;
318
319 gamma=0.0;
320 i=(long) (width*QuantumScale*PixelIntensity(r)+0.5);
321 if (i < 0)
322 i=0;
323 else
324 if (i > (long) width)
325 i=(long) width;
326 if ((i & 0x01) != 0)
327 i--;
328 p=GetCacheViewVirtualPixels(image_view,x-((long) (width-i)/2L),y-(long)
329 ((width-i)/2L),width-i,width-i,exception);
330 if (p == (const PixelPacket *) NULL)
331 break;
332 indexes=GetCacheViewVirtualIndexQueue(image_view);
cristyddd82202009-11-03 20:14:50 +0000333 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +0000334 k=kernel[i];
335 for (v=0; v < (long) (width-i); v++)
336 {
337 for (u=0; u < (long) (width-i); u++)
338 {
339 alpha=1.0;
340 if (((channel & OpacityChannel) != 0) &&
341 (image->matte != MagickFalse))
342 alpha=(MagickRealType) (QuantumScale*(QuantumRange-p->opacity));
343 if ((channel & RedChannel) != 0)
344 pixel.red+=(*k)*alpha*p->red;
345 if ((channel & GreenChannel) != 0)
346 pixel.green+=(*k)*alpha*p->green;
347 if ((channel & BlueChannel) != 0)
348 pixel.blue+=(*k)*alpha*p->blue;
349 if ((channel & OpacityChannel) != 0)
350 pixel.opacity+=(*k)*p->opacity;
351 if (((channel & IndexChannel) != 0) &&
352 (image->colorspace == CMYKColorspace))
353 pixel.index+=(*k)*alpha*indexes[x+(width-i)*v+u];
354 gamma+=(*k)*alpha;
355 k++;
356 p++;
357 }
358 }
359 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
360 if ((channel & RedChannel) != 0)
cristyddd82202009-11-03 20:14:50 +0000361 q->red=RoundToQuantum(gamma*pixel.red);
cristy3ed852e2009-09-05 21:47:34 +0000362 if ((channel & GreenChannel) != 0)
cristyddd82202009-11-03 20:14:50 +0000363 q->green=RoundToQuantum(gamma*pixel.green);
cristy3ed852e2009-09-05 21:47:34 +0000364 if ((channel & BlueChannel) != 0)
cristyddd82202009-11-03 20:14:50 +0000365 q->blue=RoundToQuantum(gamma*pixel.blue);
cristy3ed852e2009-09-05 21:47:34 +0000366 if ((channel & OpacityChannel) != 0)
cristyddd82202009-11-03 20:14:50 +0000367 q->opacity=RoundToQuantum(pixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +0000368 if (((channel & IndexChannel) != 0) &&
369 (image->colorspace == CMYKColorspace))
cristyddd82202009-11-03 20:14:50 +0000370 blur_indexes[x]=RoundToQuantum(gamma*pixel.index);
cristy3ed852e2009-09-05 21:47:34 +0000371 q++;
372 r++;
373 }
374 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
375 status=MagickFalse;
376 if (image->progress_monitor != (MagickProgressMonitor) NULL)
377 {
378 MagickBooleanType
379 proceed;
380
cristyb5d5f722009-11-04 03:03:49 +0000381#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000382 #pragma omp critical (MagickCore_AdaptiveBlurImageChannel)
383#endif
384 proceed=SetImageProgress(image,AdaptiveBlurImageTag,progress++,
385 image->rows);
386 if (proceed == MagickFalse)
387 status=MagickFalse;
388 }
389 }
390 blur_image->type=image->type;
391 blur_view=DestroyCacheView(blur_view);
392 edge_view=DestroyCacheView(edge_view);
393 image_view=DestroyCacheView(image_view);
394 edge_image=DestroyImage(edge_image);
395 for (i=0; i < (long) width; i+=2)
396 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
397 kernel=(double **) RelinquishMagickMemory(kernel);
398 if (status == MagickFalse)
399 blur_image=DestroyImage(blur_image);
400 return(blur_image);
401}
402
403/*
404%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
405% %
406% %
407% %
408% A d a p t i v e S h a r p e n I m a g e %
409% %
410% %
411% %
412%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
413%
414% AdaptiveSharpenImage() adaptively sharpens the image by sharpening more
415% intensely near image edges and less intensely far from edges. We sharpen the
416% image with a Gaussian operator of the given radius and standard deviation
417% (sigma). For reasonable results, radius should be larger than sigma. Use a
418% radius of 0 and AdaptiveSharpenImage() selects a suitable radius for you.
419%
420% The format of the AdaptiveSharpenImage method is:
421%
422% Image *AdaptiveSharpenImage(const Image *image,const double radius,
423% const double sigma,ExceptionInfo *exception)
424% Image *AdaptiveSharpenImageChannel(const Image *image,
425% const ChannelType channel,double radius,const double sigma,
426% ExceptionInfo *exception)
427%
428% A description of each parameter follows:
429%
430% o image: the image.
431%
432% o channel: the channel type.
433%
434% o radius: the radius of the Gaussian, in pixels, not counting the center
435% pixel.
436%
437% o sigma: the standard deviation of the Laplacian, in pixels.
438%
439% o exception: return any errors or warnings in this structure.
440%
441*/
442
443MagickExport Image *AdaptiveSharpenImage(const Image *image,const double radius,
444 const double sigma,ExceptionInfo *exception)
445{
446 Image
447 *sharp_image;
448
449 sharp_image=AdaptiveSharpenImageChannel(image,DefaultChannels,radius,sigma,
450 exception);
451 return(sharp_image);
452}
453
454MagickExport Image *AdaptiveSharpenImageChannel(const Image *image,
455 const ChannelType channel,const double radius,const double sigma,
456 ExceptionInfo *exception)
457{
458#define AdaptiveSharpenImageTag "Convolve/Image"
459#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
460
461 double
462 **kernel;
463
464 Image
465 *sharp_image,
466 *edge_image,
467 *gaussian_image;
468
469 long
470 j,
471 progress,
472 y;
473
474 MagickBooleanType
475 status;
476
477 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +0000478 bias;
cristy3ed852e2009-09-05 21:47:34 +0000479
480 MagickRealType
481 alpha,
cristy3ed852e2009-09-05 21:47:34 +0000482 normalize;
483
484 register long
485 i,
486 u,
487 v;
488
489 unsigned long
490 width;
491
492 CacheView
493 *sharp_view,
494 *edge_view,
495 *image_view;
496
497 assert(image != (const Image *) NULL);
498 assert(image->signature == MagickSignature);
499 if (image->debug != MagickFalse)
500 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
501 assert(exception != (ExceptionInfo *) NULL);
502 assert(exception->signature == MagickSignature);
503 sharp_image=CloneImage(image,0,0,MagickTrue,exception);
504 if (sharp_image == (Image *) NULL)
505 return((Image *) NULL);
506 if (fabs(sigma) <= MagickEpsilon)
507 return(sharp_image);
508 if (SetImageStorageClass(sharp_image,DirectClass) == MagickFalse)
509 {
510 InheritException(exception,&sharp_image->exception);
511 sharp_image=DestroyImage(sharp_image);
512 return((Image *) NULL);
513 }
514 /*
515 Edge detect the image brighness channel, level, sharp, and level again.
516 */
517 edge_image=EdgeImage(image,radius,exception);
518 if (edge_image == (Image *) NULL)
519 {
520 sharp_image=DestroyImage(sharp_image);
521 return((Image *) NULL);
522 }
523 (void) LevelImage(edge_image,"20%,95%");
524 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,exception);
525 if (gaussian_image != (Image *) NULL)
526 {
527 edge_image=DestroyImage(edge_image);
528 edge_image=gaussian_image;
529 }
530 (void) LevelImage(edge_image,"10%,95%");
531 /*
532 Create a set of kernels from maximum (radius,sigma) to minimum.
533 */
534 width=GetOptimalKernelWidth2D(radius,sigma);
535 kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
536 if (kernel == (double **) NULL)
537 {
538 edge_image=DestroyImage(edge_image);
539 sharp_image=DestroyImage(sharp_image);
540 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
541 }
542 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
543 for (i=0; i < (long) width; i+=2)
544 {
545 kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)*
546 sizeof(**kernel));
547 if (kernel[i] == (double *) NULL)
548 break;
549 j=0;
550 for (v=(-((long) (width-i)/2)); v <= (long) ((width-i)/2); v++)
551 {
552 for (u=(-((long) (width-i)/2)); u <= (long) ((width-i)/2); u++)
553 {
554 alpha=exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma));
555 kernel[i][j]=(double) (-alpha/(2.0*MagickPI*MagickSigma*MagickSigma));
556 j++;
557 }
558 }
559 normalize=0.0;
560 for (j=0; j < (long) ((width-i)*(width-i)); j++)
561 normalize+=kernel[i][j];
562 if (fabs(normalize) <= MagickEpsilon)
563 normalize=1.0;
564 normalize=1.0/normalize;
565 for (j=0; j < (long) ((width-i)*(width-i)); j++)
566 kernel[i][j]=(double) (normalize*kernel[i][j]);
567 }
568 if (i < (long) width)
569 {
570 for (i-=2; i >= 0; i-=2)
571 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
572 kernel=(double **) RelinquishMagickMemory(kernel);
573 edge_image=DestroyImage(edge_image);
574 sharp_image=DestroyImage(sharp_image);
575 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
576 }
577 /*
578 Adaptively sharpen image.
579 */
580 status=MagickTrue;
581 progress=0;
cristyddd82202009-11-03 20:14:50 +0000582 GetMagickPixelPacket(image,&bias);
583 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000584 image_view=AcquireCacheView(image);
585 edge_view=AcquireCacheView(edge_image);
586 sharp_view=AcquireCacheView(sharp_image);
cristyb5d5f722009-11-04 03:03:49 +0000587#if defined(MAGICKCORE_OPENMP_SUPPORT)
588 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000589#endif
590 for (y=0; y < (long) sharp_image->rows; y++)
591 {
592 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000593 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000594
595 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000596 *restrict p,
597 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000598
599 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000600 *restrict sharp_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000601
602 register long
603 x;
604
605 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000606 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000607
608 if (status == MagickFalse)
609 continue;
610 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
611 q=QueueCacheViewAuthenticPixels(sharp_view,0,y,sharp_image->columns,1,
612 exception);
613 if ((r == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
614 {
615 status=MagickFalse;
616 continue;
617 }
618 sharp_indexes=GetCacheViewAuthenticIndexQueue(sharp_view);
619 for (x=0; x < (long) sharp_image->columns; x++)
620 {
621 MagickPixelPacket
622 pixel;
623
624 MagickRealType
625 alpha,
626 gamma;
627
628 register const double
cristyc47d1f82009-11-26 01:44:43 +0000629 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000630
631 register long
632 i,
633 u,
634 v;
635
636 gamma=0.0;
637 i=(long) (width*(QuantumRange-QuantumScale*PixelIntensity(r))+0.5);
638 if (i < 0)
639 i=0;
640 else
641 if (i > (long) width)
642 i=(long) width;
643 if ((i & 0x01) != 0)
644 i--;
645 p=GetCacheViewVirtualPixels(image_view,x-((long) (width-i)/2L),y-(long)
646 ((width-i)/2L),width-i,width-i,exception);
647 if (p == (const PixelPacket *) NULL)
648 break;
649 indexes=GetCacheViewVirtualIndexQueue(image_view);
650 k=kernel[i];
cristyddd82202009-11-03 20:14:50 +0000651 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +0000652 for (v=0; v < (long) (width-i); v++)
653 {
654 for (u=0; u < (long) (width-i); u++)
655 {
656 alpha=1.0;
657 if (((channel & OpacityChannel) != 0) &&
658 (image->matte != MagickFalse))
659 alpha=(MagickRealType) (QuantumScale*(QuantumRange-p->opacity));
660 if ((channel & RedChannel) != 0)
661 pixel.red+=(*k)*alpha*p->red;
662 if ((channel & GreenChannel) != 0)
663 pixel.green+=(*k)*alpha*p->green;
664 if ((channel & BlueChannel) != 0)
665 pixel.blue+=(*k)*alpha*p->blue;
666 if ((channel & OpacityChannel) != 0)
667 pixel.opacity+=(*k)*p->opacity;
668 if (((channel & IndexChannel) != 0) &&
669 (image->colorspace == CMYKColorspace))
670 pixel.index+=(*k)*alpha*indexes[x+(width-i)*v+u];
671 gamma+=(*k)*alpha;
672 k++;
673 p++;
674 }
675 }
676 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
677 if ((channel & RedChannel) != 0)
cristyddd82202009-11-03 20:14:50 +0000678 q->red=RoundToQuantum(gamma*pixel.red);
cristy3ed852e2009-09-05 21:47:34 +0000679 if ((channel & GreenChannel) != 0)
cristyddd82202009-11-03 20:14:50 +0000680 q->green=RoundToQuantum(gamma*pixel.green);
cristy3ed852e2009-09-05 21:47:34 +0000681 if ((channel & BlueChannel) != 0)
cristyddd82202009-11-03 20:14:50 +0000682 q->blue=RoundToQuantum(gamma*pixel.blue);
cristy3ed852e2009-09-05 21:47:34 +0000683 if ((channel & OpacityChannel) != 0)
cristyddd82202009-11-03 20:14:50 +0000684 q->opacity=RoundToQuantum(pixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +0000685 if (((channel & IndexChannel) != 0) &&
686 (image->colorspace == CMYKColorspace))
cristyddd82202009-11-03 20:14:50 +0000687 sharp_indexes[x]=RoundToQuantum(gamma*pixel.index);
cristy3ed852e2009-09-05 21:47:34 +0000688 q++;
689 r++;
690 }
691 if (SyncCacheViewAuthenticPixels(sharp_view,exception) == MagickFalse)
692 status=MagickFalse;
693 if (image->progress_monitor != (MagickProgressMonitor) NULL)
694 {
695 MagickBooleanType
696 proceed;
697
cristyb5d5f722009-11-04 03:03:49 +0000698#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000699 #pragma omp critical (MagickCore_AdaptiveSharpenImageChannel)
700#endif
701 proceed=SetImageProgress(image,AdaptiveSharpenImageTag,progress++,
702 image->rows);
703 if (proceed == MagickFalse)
704 status=MagickFalse;
705 }
706 }
707 sharp_image->type=image->type;
708 sharp_view=DestroyCacheView(sharp_view);
709 edge_view=DestroyCacheView(edge_view);
710 image_view=DestroyCacheView(image_view);
711 edge_image=DestroyImage(edge_image);
712 for (i=0; i < (long) width; i+=2)
713 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
714 kernel=(double **) RelinquishMagickMemory(kernel);
715 if (status == MagickFalse)
716 sharp_image=DestroyImage(sharp_image);
717 return(sharp_image);
718}
719
720/*
721%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
722% %
723% %
724% %
725% B l u r I m a g e %
726% %
727% %
728% %
729%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
730%
731% BlurImage() blurs an image. We convolve the image with a Gaussian operator
732% of the given radius and standard deviation (sigma). For reasonable results,
733% the radius should be larger than sigma. Use a radius of 0 and BlurImage()
734% selects a suitable radius for you.
735%
736% BlurImage() differs from GaussianBlurImage() in that it uses a separable
737% kernel which is faster but mathematically equivalent to the non-separable
738% kernel.
739%
740% The format of the BlurImage method is:
741%
742% Image *BlurImage(const Image *image,const double radius,
743% const double sigma,ExceptionInfo *exception)
744% Image *BlurImageChannel(const Image *image,const ChannelType channel,
745% const double radius,const double sigma,ExceptionInfo *exception)
746%
747% A description of each parameter follows:
748%
749% o image: the image.
750%
751% o channel: the channel type.
752%
753% o radius: the radius of the Gaussian, in pixels, not counting the center
754% pixel.
755%
756% o sigma: the standard deviation of the Gaussian, in pixels.
757%
758% o exception: return any errors or warnings in this structure.
759%
760*/
761
762MagickExport Image *BlurImage(const Image *image,const double radius,
763 const double sigma,ExceptionInfo *exception)
764{
765 Image
766 *blur_image;
767
768 blur_image=BlurImageChannel(image,DefaultChannels,radius,sigma,exception);
769 return(blur_image);
770}
771
772static double *GetBlurKernel(unsigned long width,const MagickRealType sigma)
773{
774#define KernelRank 3
775
776 double
777 *kernel;
778
779 long
780 bias;
781
782 MagickRealType
783 alpha,
784 normalize;
785
786 register long
787 i;
788
789 /*
790 Generate a 1-D convolution kernel.
791 */
792 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
793 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
794 if (kernel == (double *) NULL)
795 return(0);
796 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
797 bias=KernelRank*(long) width/2;
798 for (i=(-bias); i <= bias; i++)
799 {
800 alpha=exp((-((double) (i*i))/(double) (2.0*KernelRank*KernelRank*
801 MagickSigma*MagickSigma)));
cristy4c982f02009-11-04 00:36:19 +0000802 kernel[(i+bias)/KernelRank]+=(double) (alpha/(MagickSQ2PI*sigma));
cristy3ed852e2009-09-05 21:47:34 +0000803 }
804 normalize=0.0;
805 for (i=0; i < (long) width; i++)
806 normalize+=kernel[i];
807 for (i=0; i < (long) width; i++)
808 kernel[i]/=normalize;
809 return(kernel);
810}
811
812MagickExport Image *BlurImageChannel(const Image *image,
813 const ChannelType channel,const double radius,const double sigma,
814 ExceptionInfo *exception)
815{
816#define BlurImageTag "Blur/Image"
817
818 double
819 *kernel;
820
821 Image
822 *blur_image;
823
824 long
825 progress,
826 x,
827 y;
828
829 MagickBooleanType
830 status;
831
832 MagickPixelPacket
cristy3ed852e2009-09-05 21:47:34 +0000833 bias;
834
835 register long
836 i;
837
838 unsigned long
839 width;
840
841 CacheView
842 *blur_view,
843 *image_view;
844
845 /*
846 Initialize blur image attributes.
847 */
848 assert(image != (Image *) NULL);
849 assert(image->signature == MagickSignature);
850 if (image->debug != MagickFalse)
851 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
852 assert(exception != (ExceptionInfo *) NULL);
853 assert(exception->signature == MagickSignature);
854 blur_image=CloneImage(image,0,0,MagickTrue,exception);
855 if (blur_image == (Image *) NULL)
856 return((Image *) NULL);
857 if (fabs(sigma) <= MagickEpsilon)
858 return(blur_image);
859 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
860 {
861 InheritException(exception,&blur_image->exception);
862 blur_image=DestroyImage(blur_image);
863 return((Image *) NULL);
864 }
865 width=GetOptimalKernelWidth1D(radius,sigma);
866 kernel=GetBlurKernel(width,sigma);
867 if (kernel == (double *) NULL)
868 {
869 blur_image=DestroyImage(blur_image);
870 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
871 }
872 if (image->debug != MagickFalse)
873 {
874 char
875 format[MaxTextExtent],
876 *message;
877
878 register const double
879 *k;
880
881 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
882 " BlurImage with %ld kernel:",width);
883 message=AcquireString("");
884 k=kernel;
885 for (i=0; i < (long) width; i++)
886 {
887 *message='\0';
888 (void) FormatMagickString(format,MaxTextExtent,"%ld: ",i);
889 (void) ConcatenateString(&message,format);
890 (void) FormatMagickString(format,MaxTextExtent,"%g ",*k++);
891 (void) ConcatenateString(&message,format);
892 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
893 }
894 message=DestroyString(message);
895 }
896 /*
897 Blur rows.
898 */
899 status=MagickTrue;
900 progress=0;
cristyddd82202009-11-03 20:14:50 +0000901 GetMagickPixelPacket(image,&bias);
902 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000903 image_view=AcquireCacheView(image);
904 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000905#if defined(MAGICKCORE_OPENMP_SUPPORT)
906 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000907#endif
908 for (y=0; y < (long) blur_image->rows; y++)
909 {
910 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000911 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000912
913 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000914 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000915
916 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000917 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000918
919 register long
920 x;
921
922 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000923 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000924
925 if (status == MagickFalse)
926 continue;
927 p=GetCacheViewVirtualPixels(image_view,-((long) width/2L),y,image->columns+
928 width,1,exception);
929 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
930 exception);
931 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
932 {
933 status=MagickFalse;
934 continue;
935 }
936 indexes=GetCacheViewVirtualIndexQueue(image_view);
937 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
938 for (x=0; x < (long) blur_image->columns; x++)
939 {
940 MagickPixelPacket
941 pixel;
942
943 register const double
cristyc47d1f82009-11-26 01:44:43 +0000944 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000945
946 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000947 *restrict kernel_pixels;
cristy3ed852e2009-09-05 21:47:34 +0000948
949 register long
950 i;
951
cristyddd82202009-11-03 20:14:50 +0000952 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +0000953 k=kernel;
954 kernel_pixels=p;
955 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
956 {
957 for (i=0; i < (long) width; i++)
958 {
959 pixel.red+=(*k)*kernel_pixels->red;
960 pixel.green+=(*k)*kernel_pixels->green;
961 pixel.blue+=(*k)*kernel_pixels->blue;
962 k++;
963 kernel_pixels++;
964 }
965 if ((channel & RedChannel) != 0)
cristyddd82202009-11-03 20:14:50 +0000966 q->red=RoundToQuantum(pixel.red);
cristy3ed852e2009-09-05 21:47:34 +0000967 if ((channel & GreenChannel) != 0)
cristyddd82202009-11-03 20:14:50 +0000968 q->green=RoundToQuantum(pixel.green);
cristy3ed852e2009-09-05 21:47:34 +0000969 if ((channel & BlueChannel) != 0)
cristyddd82202009-11-03 20:14:50 +0000970 q->blue=RoundToQuantum(pixel.blue);
cristy3ed852e2009-09-05 21:47:34 +0000971 if ((channel & OpacityChannel) != 0)
972 {
973 k=kernel;
974 kernel_pixels=p;
975 for (i=0; i < (long) width; i++)
976 {
977 pixel.opacity+=(*k)*kernel_pixels->opacity;
978 k++;
979 kernel_pixels++;
980 }
cristyddd82202009-11-03 20:14:50 +0000981 q->opacity=RoundToQuantum(pixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +0000982 }
983 if (((channel & IndexChannel) != 0) &&
984 (image->colorspace == CMYKColorspace))
985 {
986 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000987 *restrict kernel_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000988
989 k=kernel;
990 kernel_indexes=indexes;
991 for (i=0; i < (long) width; i++)
992 {
993 pixel.index+=(*k)*(*kernel_indexes);
994 k++;
995 kernel_indexes++;
996 }
cristyddd82202009-11-03 20:14:50 +0000997 blur_indexes[x]=RoundToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +0000998 }
999 }
1000 else
1001 {
1002 MagickRealType
1003 alpha,
1004 gamma;
1005
1006 gamma=0.0;
1007 for (i=0; i < (long) width; i++)
1008 {
1009 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
1010 kernel_pixels->opacity));
1011 pixel.red+=(*k)*alpha*kernel_pixels->red;
1012 pixel.green+=(*k)*alpha*kernel_pixels->green;
1013 pixel.blue+=(*k)*alpha*kernel_pixels->blue;
1014 gamma+=(*k)*alpha;
1015 k++;
1016 kernel_pixels++;
1017 }
1018 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1019 if ((channel & RedChannel) != 0)
cristyddd82202009-11-03 20:14:50 +00001020 q->red=RoundToQuantum(gamma*pixel.red);
cristy3ed852e2009-09-05 21:47:34 +00001021 if ((channel & GreenChannel) != 0)
cristyddd82202009-11-03 20:14:50 +00001022 q->green=RoundToQuantum(gamma*pixel.green);
cristy3ed852e2009-09-05 21:47:34 +00001023 if ((channel & BlueChannel) != 0)
cristyddd82202009-11-03 20:14:50 +00001024 q->blue=RoundToQuantum(gamma*pixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00001025 if ((channel & OpacityChannel) != 0)
1026 {
1027 k=kernel;
1028 kernel_pixels=p;
1029 for (i=0; i < (long) width; i++)
1030 {
1031 pixel.opacity+=(*k)*kernel_pixels->opacity;
1032 k++;
1033 kernel_pixels++;
1034 }
cristyddd82202009-11-03 20:14:50 +00001035 q->opacity=RoundToQuantum(pixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00001036 }
1037 if (((channel & IndexChannel) != 0) &&
1038 (image->colorspace == CMYKColorspace))
1039 {
1040 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001041 *restrict kernel_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001042
1043 k=kernel;
1044 kernel_pixels=p;
1045 kernel_indexes=indexes;
1046 for (i=0; i < (long) width; i++)
1047 {
1048 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
1049 kernel_pixels->opacity));
1050 pixel.index+=(*k)*alpha*(*kernel_indexes);
1051 k++;
1052 kernel_pixels++;
1053 kernel_indexes++;
1054 }
cristyddd82202009-11-03 20:14:50 +00001055 blur_indexes[x]=RoundToQuantum(gamma*pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00001056 }
1057 }
1058 p++;
1059 q++;
1060 }
1061 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1062 status=MagickFalse;
1063 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1064 {
1065 MagickBooleanType
1066 proceed;
1067
cristyb5d5f722009-11-04 03:03:49 +00001068#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001069 #pragma omp critical (MagickCore_BlurImageChannel)
1070#endif
1071 proceed=SetImageProgress(image,BlurImageTag,progress++,blur_image->rows+
1072 blur_image->columns);
1073 if (proceed == MagickFalse)
1074 status=MagickFalse;
1075 }
1076 }
1077 blur_view=DestroyCacheView(blur_view);
1078 image_view=DestroyCacheView(image_view);
1079 /*
1080 Blur columns.
1081 */
1082 image_view=AcquireCacheView(blur_image);
1083 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00001084#if defined(MAGICKCORE_OPENMP_SUPPORT)
1085 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001086#endif
1087 for (x=0; x < (long) blur_image->columns; x++)
1088 {
1089 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001090 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001091
1092 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001093 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001094
1095 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001096 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001097
1098 register long
1099 y;
1100
1101 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001102 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001103
1104 if (status == MagickFalse)
1105 continue;
1106 p=GetCacheViewVirtualPixels(image_view,x,-((long) width/2L),1,image->rows+
1107 width,exception);
1108 q=GetCacheViewAuthenticPixels(blur_view,x,0,1,blur_image->rows,exception);
1109 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1110 {
1111 status=MagickFalse;
1112 continue;
1113 }
1114 indexes=GetCacheViewVirtualIndexQueue(image_view);
1115 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
1116 for (y=0; y < (long) blur_image->rows; y++)
1117 {
1118 MagickPixelPacket
1119 pixel;
1120
1121 register const double
cristyc47d1f82009-11-26 01:44:43 +00001122 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00001123
1124 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001125 *restrict kernel_pixels;
cristy3ed852e2009-09-05 21:47:34 +00001126
1127 register long
1128 i;
1129
cristyddd82202009-11-03 20:14:50 +00001130 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00001131 k=kernel;
1132 kernel_pixels=p;
1133 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
1134 {
1135 for (i=0; i < (long) width; i++)
1136 {
1137 pixel.red+=(*k)*kernel_pixels->red;
1138 pixel.green+=(*k)*kernel_pixels->green;
1139 pixel.blue+=(*k)*kernel_pixels->blue;
1140 k++;
1141 kernel_pixels++;
1142 }
1143 if ((channel & RedChannel) != 0)
cristyddd82202009-11-03 20:14:50 +00001144 q->red=RoundToQuantum(pixel.red);
cristy3ed852e2009-09-05 21:47:34 +00001145 if ((channel & GreenChannel) != 0)
cristyddd82202009-11-03 20:14:50 +00001146 q->green=RoundToQuantum(pixel.green);
cristy3ed852e2009-09-05 21:47:34 +00001147 if ((channel & BlueChannel) != 0)
cristyddd82202009-11-03 20:14:50 +00001148 q->blue=RoundToQuantum(pixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00001149 if ((channel & OpacityChannel) != 0)
1150 {
1151 k=kernel;
1152 kernel_pixels=p;
1153 for (i=0; i < (long) width; i++)
1154 {
1155 pixel.opacity+=(*k)*kernel_pixels->opacity;
1156 k++;
1157 kernel_pixels++;
1158 }
cristyddd82202009-11-03 20:14:50 +00001159 q->opacity=RoundToQuantum(pixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00001160 }
1161 if (((channel & IndexChannel) != 0) &&
1162 (image->colorspace == CMYKColorspace))
1163 {
1164 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001165 *restrict kernel_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001166
1167 k=kernel;
1168 kernel_indexes=indexes;
1169 for (i=0; i < (long) width; i++)
1170 {
1171 pixel.index+=(*k)*(*kernel_indexes);
1172 k++;
1173 kernel_indexes++;
1174 }
cristyddd82202009-11-03 20:14:50 +00001175 blur_indexes[y]=RoundToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00001176 }
1177 }
1178 else
1179 {
1180 MagickRealType
1181 alpha,
1182 gamma;
1183
1184 gamma=0.0;
1185 for (i=0; i < (long) width; i++)
1186 {
1187 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
1188 kernel_pixels->opacity));
1189 pixel.red+=(*k)*alpha*kernel_pixels->red;
1190 pixel.green+=(*k)*alpha*kernel_pixels->green;
1191 pixel.blue+=(*k)*alpha*kernel_pixels->blue;
1192 gamma+=(*k)*alpha;
1193 k++;
1194 kernel_pixels++;
1195 }
1196 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1197 if ((channel & RedChannel) != 0)
cristyddd82202009-11-03 20:14:50 +00001198 q->red=RoundToQuantum(gamma*pixel.red);
cristy3ed852e2009-09-05 21:47:34 +00001199 if ((channel & GreenChannel) != 0)
cristyddd82202009-11-03 20:14:50 +00001200 q->green=RoundToQuantum(gamma*pixel.green);
cristy3ed852e2009-09-05 21:47:34 +00001201 if ((channel & BlueChannel) != 0)
cristyddd82202009-11-03 20:14:50 +00001202 q->blue=RoundToQuantum(gamma*pixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00001203 if ((channel & OpacityChannel) != 0)
1204 {
1205 k=kernel;
1206 kernel_pixels=p;
1207 for (i=0; i < (long) width; i++)
1208 {
1209 pixel.opacity+=(*k)*kernel_pixels->opacity;
1210 k++;
1211 kernel_pixels++;
1212 }
cristyddd82202009-11-03 20:14:50 +00001213 q->opacity=RoundToQuantum(pixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00001214 }
1215 if (((channel & IndexChannel) != 0) &&
1216 (image->colorspace == CMYKColorspace))
1217 {
1218 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001219 *restrict kernel_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001220
1221 k=kernel;
1222 kernel_pixels=p;
1223 kernel_indexes=indexes;
1224 for (i=0; i < (long) width; i++)
1225 {
1226 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
1227 kernel_pixels->opacity));
1228 pixel.index+=(*k)*alpha*(*kernel_indexes);
1229 k++;
1230 kernel_pixels++;
1231 kernel_indexes++;
1232 }
cristyddd82202009-11-03 20:14:50 +00001233 blur_indexes[y]=RoundToQuantum(gamma*pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00001234 }
1235 }
1236 p++;
1237 q++;
1238 }
1239 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1240 status=MagickFalse;
1241 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1242 {
1243 MagickBooleanType
1244 proceed;
1245
cristyb5d5f722009-11-04 03:03:49 +00001246#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001247 #pragma omp critical (MagickCore_BlurImageChannel)
1248#endif
1249 proceed=SetImageProgress(image,BlurImageTag,progress++,blur_image->rows+
1250 blur_image->columns);
1251 if (proceed == MagickFalse)
1252 status=MagickFalse;
1253 }
1254 }
1255 blur_view=DestroyCacheView(blur_view);
1256 image_view=DestroyCacheView(image_view);
1257 kernel=(double *) RelinquishMagickMemory(kernel);
1258 if (status == MagickFalse)
1259 blur_image=DestroyImage(blur_image);
1260 blur_image->type=image->type;
1261 return(blur_image);
1262}
1263
1264/*
1265%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1266% %
1267% %
1268% %
cristyfccdab92009-11-30 16:43:57 +00001269% C o n v o l v e I m a g e %
1270% %
1271% %
1272% %
1273%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1274%
1275% ConvolveImage() applies a custom convolution kernel to the image.
1276%
1277% The format of the ConvolveImage method is:
1278%
1279% Image *ConvolveImage(const Image *image,const unsigned long order,
1280% const double *kernel,ExceptionInfo *exception)
1281% Image *ConvolveImageChannel(const Image *image,const ChannelType channel,
1282% const unsigned long order,const double *kernel,
1283% ExceptionInfo *exception)
1284%
1285% A description of each parameter follows:
1286%
1287% o image: the image.
1288%
1289% o channel: the channel type.
1290%
1291% o order: the number of columns and rows in the filter kernel.
1292%
1293% o kernel: An array of double representing the convolution kernel.
1294%
1295% o exception: return any errors or warnings in this structure.
1296%
1297*/
1298
1299MagickExport Image *ConvolveImage(const Image *image,const unsigned long order,
1300 const double *kernel,ExceptionInfo *exception)
1301{
1302 Image
1303 *convolve_image;
1304
1305 convolve_image=ConvolveImageChannel(image,DefaultChannels,order,kernel,
1306 exception);
1307 return(convolve_image);
1308}
1309
1310MagickExport Image *ConvolveImageChannel(const Image *image,
1311 const ChannelType channel,const unsigned long order,const double *kernel,
1312 ExceptionInfo *exception)
1313{
1314#define ConvolveImageTag "Convolve/Image"
1315
1316 double
1317 *normal_kernel;
1318
1319 Image
1320 *convolve_image;
1321
1322 long
1323 progress,
1324 y;
1325
1326 MagickBooleanType
1327 status;
1328
1329 MagickPixelPacket
1330 bias;
1331
1332 MagickRealType
1333 gamma;
1334
1335 register long
1336 i;
1337
1338 unsigned long
1339 width;
1340
1341 CacheView
1342 *convolve_view,
1343 *image_view;
1344
1345 /*
1346 Initialize convolve image attributes.
1347 */
1348 assert(image != (Image *) NULL);
1349 assert(image->signature == MagickSignature);
1350 if (image->debug != MagickFalse)
1351 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1352 assert(exception != (ExceptionInfo *) NULL);
1353 assert(exception->signature == MagickSignature);
1354 width=order;
1355 if ((width % 2) == 0)
1356 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
1357 convolve_image=CloneImage(image,0,0,MagickTrue,exception);
1358 if (convolve_image == (Image *) NULL)
1359 return((Image *) NULL);
1360 if (SetImageStorageClass(convolve_image,DirectClass) == MagickFalse)
1361 {
1362 InheritException(exception,&convolve_image->exception);
1363 convolve_image=DestroyImage(convolve_image);
1364 return((Image *) NULL);
1365 }
1366 if (image->debug != MagickFalse)
1367 {
1368 char
1369 format[MaxTextExtent],
1370 *message;
1371
1372 long
1373 u,
1374 v;
1375
1376 register const double
1377 *k;
1378
1379 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
1380 " ConvolveImage with %ldx%ld kernel:",width,width);
1381 message=AcquireString("");
1382 k=kernel;
1383 for (v=0; v < (long) width; v++)
1384 {
1385 *message='\0';
1386 (void) FormatMagickString(format,MaxTextExtent,"%ld: ",v);
1387 (void) ConcatenateString(&message,format);
1388 for (u=0; u < (long) width; u++)
1389 {
1390 (void) FormatMagickString(format,MaxTextExtent,"%+f ",*k++);
1391 (void) ConcatenateString(&message,format);
1392 }
1393 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
1394 }
1395 message=DestroyString(message);
1396 }
1397 /*
1398 Normalize kernel.
1399 */
1400 normal_kernel=(double *) AcquireQuantumMemory(width*width,
1401 sizeof(*normal_kernel));
1402 if (normal_kernel == (double *) NULL)
1403 {
1404 convolve_image=DestroyImage(convolve_image);
1405 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1406 }
1407 gamma=0.0;
1408 for (i=0; i < (long) (width*width); i++)
1409 gamma+=kernel[i];
1410 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1411 for (i=0; i < (long) (width*width); i++)
1412 normal_kernel[i]=gamma*kernel[i];
1413 /*
1414 Convolve image.
1415 */
1416 status=MagickTrue;
1417 progress=0;
1418 GetMagickPixelPacket(image,&bias);
1419 SetMagickPixelPacketBias(image,&bias);
1420 image_view=AcquireCacheView(image);
1421 convolve_view=AcquireCacheView(convolve_image);
1422#if defined(MAGICKCORE_OPENMP_SUPPORT)
1423 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1424#endif
1425 for (y=0; y < (long) image->rows; y++)
1426 {
1427 MagickBooleanType
1428 sync;
1429
1430 register const IndexPacket
1431 *restrict indexes;
1432
1433 register const PixelPacket
1434 *restrict p;
1435
1436 register IndexPacket
1437 *restrict convolve_indexes;
1438
1439 register long
1440 x;
1441
1442 register PixelPacket
1443 *restrict q;
1444
1445 if (status == MagickFalse)
1446 continue;
1447 p=GetCacheViewVirtualPixels(image_view,-((long) width/2L),y-(long) (width/
1448 2L),image->columns+width,width,exception);
1449 q=GetCacheViewAuthenticPixels(convolve_view,0,y,convolve_image->columns,1,
1450 exception);
1451 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1452 {
1453 status=MagickFalse;
1454 continue;
1455 }
1456 indexes=GetCacheViewVirtualIndexQueue(image_view);
1457 convolve_indexes=GetCacheViewAuthenticIndexQueue(convolve_view);
1458 for (x=0; x < (long) image->columns; x++)
1459 {
1460 long
1461 v;
1462
1463 MagickPixelPacket
1464 pixel;
1465
1466 register const double
1467 *restrict k;
1468
1469 register const PixelPacket
1470 *restrict kernel_pixels;
1471
1472 register long
1473 u;
1474
1475 pixel=bias;
1476 k=normal_kernel;
1477 kernel_pixels=p;
1478 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
1479 {
1480 for (v=0; v < (long) width; v++)
1481 {
1482 for (u=0; u < (long) width; u++)
1483 {
1484 pixel.red+=(*k)*kernel_pixels[u].red;
1485 pixel.green+=(*k)*kernel_pixels[u].green;
1486 pixel.blue+=(*k)*kernel_pixels[u].blue;
1487 k++;
1488 }
1489 kernel_pixels+=image->columns+width;
1490 }
1491 if ((channel & RedChannel) != 0)
1492 q->red=RoundToQuantum(pixel.red);
1493 if ((channel & GreenChannel) != 0)
1494 q->green=RoundToQuantum(pixel.green);
1495 if ((channel & BlueChannel) != 0)
1496 q->blue=RoundToQuantum(pixel.blue);
1497 if ((channel & OpacityChannel) != 0)
1498 {
1499 k=normal_kernel;
1500 kernel_pixels=p;
1501 for (v=0; v < (long) width; v++)
1502 {
1503 for (u=0; u < (long) width; u++)
1504 {
1505 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
1506 k++;
1507 }
1508 kernel_pixels+=image->columns+width;
1509 }
1510 q->opacity=RoundToQuantum(pixel.opacity);
1511 }
1512 if (((channel & IndexChannel) != 0) &&
1513 (image->colorspace == CMYKColorspace))
1514 {
1515 register const IndexPacket
1516 *restrict kernel_indexes;
1517
1518 k=normal_kernel;
1519 kernel_indexes=indexes;
1520 for (v=0; v < (long) width; v++)
1521 {
1522 for (u=0; u < (long) width; u++)
1523 {
1524 pixel.index+=(*k)*kernel_indexes[u];
1525 k++;
1526 }
1527 kernel_indexes+=image->columns+width;
1528 }
1529 convolve_indexes[x]=RoundToQuantum(pixel.index);
1530 }
1531 }
1532 else
1533 {
1534 MagickRealType
1535 alpha,
1536 gamma;
1537
1538 gamma=0.0;
1539 for (v=0; v < (long) width; v++)
1540 {
1541 for (u=0; u < (long) width; u++)
1542 {
1543 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
1544 kernel_pixels[u].opacity));
1545 pixel.red+=(*k)*alpha*kernel_pixels[u].red;
1546 pixel.green+=(*k)*alpha*kernel_pixels[u].green;
1547 pixel.blue+=(*k)*alpha*kernel_pixels[u].blue;
1548 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
1549 gamma+=(*k)*alpha;
1550 k++;
1551 }
1552 kernel_pixels+=image->columns+width;
1553 }
1554 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1555 if ((channel & RedChannel) != 0)
1556 q->red=RoundToQuantum(gamma*pixel.red);
1557 if ((channel & GreenChannel) != 0)
1558 q->green=RoundToQuantum(gamma*pixel.green);
1559 if ((channel & BlueChannel) != 0)
1560 q->blue=RoundToQuantum(gamma*pixel.blue);
1561 if ((channel & OpacityChannel) != 0)
1562 {
1563 k=normal_kernel;
1564 kernel_pixels=p;
1565 for (v=0; v < (long) width; v++)
1566 {
1567 for (u=0; u < (long) width; u++)
1568 {
1569 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
1570 k++;
1571 }
1572 kernel_pixels+=image->columns+width;
1573 }
1574 q->opacity=RoundToQuantum(pixel.opacity);
1575 }
1576 if (((channel & IndexChannel) != 0) &&
1577 (image->colorspace == CMYKColorspace))
1578 {
1579 register const IndexPacket
1580 *restrict kernel_indexes;
1581
1582 k=normal_kernel;
1583 kernel_pixels=p;
1584 kernel_indexes=indexes;
1585 for (v=0; v < (long) width; v++)
1586 {
1587 for (u=0; u < (long) width; u++)
1588 {
1589 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
1590 kernel_pixels[u].opacity));
1591 pixel.index+=(*k)*alpha*kernel_indexes[u];
1592 k++;
1593 }
1594 kernel_pixels+=image->columns+width;
1595 kernel_indexes+=image->columns+width;
1596 }
1597 convolve_indexes[x]=RoundToQuantum(gamma*pixel.index);
1598 }
1599 }
1600 p++;
1601 q++;
1602 }
1603 sync=SyncCacheViewAuthenticPixels(convolve_view,exception);
1604 if (sync == MagickFalse)
1605 status=MagickFalse;
1606 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1607 {
1608 MagickBooleanType
1609 proceed;
1610
1611#if defined(MAGICKCORE_OPENMP_SUPPORT)
1612 #pragma omp critical (MagickCore_ConvolveImageChannel)
1613#endif
1614 proceed=SetImageProgress(image,ConvolveImageTag,progress++,image->rows);
1615 if (proceed == MagickFalse)
1616 status=MagickFalse;
1617 }
1618 }
1619 convolve_image->type=image->type;
1620 convolve_view=DestroyCacheView(convolve_view);
1621 image_view=DestroyCacheView(image_view);
1622 normal_kernel=(double *) RelinquishMagickMemory(normal_kernel);
1623 if (status == MagickFalse)
1624 convolve_image=DestroyImage(convolve_image);
1625 return(convolve_image);
1626}
1627
1628/*
1629%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1630% %
1631% %
1632% %
cristy3ed852e2009-09-05 21:47:34 +00001633% D e s p e c k l e I m a g e %
1634% %
1635% %
1636% %
1637%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1638%
1639% DespeckleImage() reduces the speckle noise in an image while perserving the
1640% edges of the original image.
1641%
1642% The format of the DespeckleImage method is:
1643%
1644% Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1645%
1646% A description of each parameter follows:
1647%
1648% o image: the image.
1649%
1650% o exception: return any errors or warnings in this structure.
1651%
1652*/
1653
1654static Quantum **DestroyPixelThreadSet(Quantum **pixels)
1655{
1656 register long
1657 i;
1658
1659 assert(pixels != (Quantum **) NULL);
1660 for (i=0; i < (long) GetOpenMPMaximumThreads(); i++)
1661 if (pixels[i] != (Quantum *) NULL)
1662 pixels[i]=(Quantum *) RelinquishMagickMemory(pixels[i]);
1663 pixels=(Quantum **) RelinquishAlignedMemory(pixels);
1664 return(pixels);
1665}
1666
1667static Quantum **AcquirePixelThreadSet(const size_t count)
1668{
1669 register long
1670 i;
1671
1672 Quantum
1673 **pixels;
1674
1675 unsigned long
1676 number_threads;
1677
1678 number_threads=GetOpenMPMaximumThreads();
1679 pixels=(Quantum **) AcquireAlignedMemory(number_threads,sizeof(*pixels));
1680 if (pixels == (Quantum **) NULL)
1681 return((Quantum **) NULL);
1682 (void) ResetMagickMemory(pixels,0,number_threads*sizeof(*pixels));
1683 for (i=0; i < (long) number_threads; i++)
1684 {
1685 pixels[i]=(Quantum *) AcquireQuantumMemory(count,sizeof(**pixels));
1686 if (pixels[i] == (Quantum *) NULL)
1687 return(DestroyPixelThreadSet(pixels));
1688 }
1689 return(pixels);
1690}
1691
1692static void Hull(const long x_offset,const long y_offset,
1693 const unsigned long columns,const unsigned long rows,Quantum *f,Quantum *g,
1694 const int polarity)
1695{
1696 long
1697 y;
1698
1699 MagickRealType
1700 v;
1701
1702 register long
1703 x;
1704
1705 register Quantum
1706 *p,
1707 *q,
1708 *r,
1709 *s;
1710
1711 assert(f != (Quantum *) NULL);
1712 assert(g != (Quantum *) NULL);
1713 p=f+(columns+2);
1714 q=g+(columns+2);
1715 r=p+(y_offset*((long) columns+2)+x_offset);
1716 for (y=0; y < (long) rows; y++)
1717 {
1718 p++;
1719 q++;
1720 r++;
1721 if (polarity > 0)
1722 for (x=(long) columns; x != 0; x--)
1723 {
1724 v=(MagickRealType) (*p);
1725 if ((MagickRealType) *r >= (v+(MagickRealType) ScaleCharToQuantum(2)))
1726 v+=ScaleCharToQuantum(1);
1727 *q=(Quantum) v;
1728 p++;
1729 q++;
1730 r++;
1731 }
1732 else
1733 for (x=(long) columns; x != 0; x--)
1734 {
1735 v=(MagickRealType) (*p);
1736 if ((MagickRealType) *r <= (v-(MagickRealType) ScaleCharToQuantum(2)))
1737 v-=(long) ScaleCharToQuantum(1);
1738 *q=(Quantum) v;
1739 p++;
1740 q++;
1741 r++;
1742 }
1743 p++;
1744 q++;
1745 r++;
1746 }
1747 p=f+(columns+2);
1748 q=g+(columns+2);
1749 r=q+(y_offset*((long) columns+2)+x_offset);
1750 s=q-(y_offset*((long) columns+2)+x_offset);
1751 for (y=0; y < (long) rows; y++)
1752 {
1753 p++;
1754 q++;
1755 r++;
1756 s++;
1757 if (polarity > 0)
1758 for (x=(long) columns; x != 0; x--)
1759 {
1760 v=(MagickRealType) (*q);
1761 if (((MagickRealType) *s >=
1762 (v+(MagickRealType) ScaleCharToQuantum(2))) &&
1763 ((MagickRealType) *r > v))
1764 v+=ScaleCharToQuantum(1);
1765 *p=(Quantum) v;
1766 p++;
1767 q++;
1768 r++;
1769 s++;
1770 }
1771 else
1772 for (x=(long) columns; x != 0; x--)
1773 {
1774 v=(MagickRealType) (*q);
1775 if (((MagickRealType) *s <=
1776 (v-(MagickRealType) ScaleCharToQuantum(2))) &&
1777 ((MagickRealType) *r < v))
1778 v-=(MagickRealType) ScaleCharToQuantum(1);
1779 *p=(Quantum) v;
1780 p++;
1781 q++;
1782 r++;
1783 s++;
1784 }
1785 p++;
1786 q++;
1787 r++;
1788 s++;
1789 }
1790}
1791
1792MagickExport Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1793{
1794#define DespeckleImageTag "Despeckle/Image"
1795
cristy2407fc22009-09-11 00:55:25 +00001796 CacheView
1797 *despeckle_view,
1798 *image_view;
1799
cristy3ed852e2009-09-05 21:47:34 +00001800 Image
1801 *despeckle_image;
1802
1803 long
1804 channel;
1805
1806 MagickBooleanType
1807 status;
1808
1809 Quantum
1810 **buffers,
1811 **pixels;
1812
1813 size_t
1814 length;
1815
1816 static const int
cristy691a29e2009-09-11 00:44:10 +00001817 X[4] = {0, 1, 1,-1},
1818 Y[4] = {1, 0, 1, 1};
cristy3ed852e2009-09-05 21:47:34 +00001819
cristy3ed852e2009-09-05 21:47:34 +00001820 /*
1821 Allocate despeckled image.
1822 */
1823 assert(image != (const Image *) NULL);
1824 assert(image->signature == MagickSignature);
1825 if (image->debug != MagickFalse)
1826 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1827 assert(exception != (ExceptionInfo *) NULL);
1828 assert(exception->signature == MagickSignature);
1829 despeckle_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1830 exception);
1831 if (despeckle_image == (Image *) NULL)
1832 return((Image *) NULL);
1833 if (SetImageStorageClass(despeckle_image,DirectClass) == MagickFalse)
1834 {
1835 InheritException(exception,&despeckle_image->exception);
1836 despeckle_image=DestroyImage(despeckle_image);
1837 return((Image *) NULL);
1838 }
1839 /*
1840 Allocate image buffers.
1841 */
1842 length=(size_t) ((image->columns+2)*(image->rows+2));
1843 pixels=AcquirePixelThreadSet(length);
1844 buffers=AcquirePixelThreadSet(length);
1845 if ((pixels == (Quantum **) NULL) || (buffers == (Quantum **) NULL))
1846 {
1847 if (buffers != (Quantum **) NULL)
1848 buffers=DestroyPixelThreadSet(buffers);
1849 if (pixels != (Quantum **) NULL)
1850 pixels=DestroyPixelThreadSet(pixels);
1851 despeckle_image=DestroyImage(despeckle_image);
1852 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1853 }
1854 /*
1855 Reduce speckle in the image.
1856 */
1857 status=MagickTrue;
1858 image_view=AcquireCacheView(image);
1859 despeckle_view=AcquireCacheView(despeckle_image);
cristyb5d5f722009-11-04 03:03:49 +00001860#if defined(MAGICKCORE_OPENMP_SUPPORT)
1861 #pragma omp parallel for schedule(dynamic,4) shared(status)
cristy3ed852e2009-09-05 21:47:34 +00001862#endif
1863 for (channel=0; channel <= 3; channel++)
1864 {
1865 long
1866 j,
1867 y;
1868
1869 register long
1870 i,
cristy691a29e2009-09-11 00:44:10 +00001871 id,
cristy3ed852e2009-09-05 21:47:34 +00001872 x;
1873
1874 register Quantum
1875 *buffer,
1876 *pixel;
1877
1878 if (status == MagickFalse)
1879 continue;
cristy691a29e2009-09-11 00:44:10 +00001880 id=GetOpenMPThreadId();
1881 pixel=pixels[id];
cristy3ed852e2009-09-05 21:47:34 +00001882 (void) ResetMagickMemory(pixel,0,length*sizeof(*pixel));
cristy691a29e2009-09-11 00:44:10 +00001883 buffer=buffers[id];
cristy3ed852e2009-09-05 21:47:34 +00001884 j=(long) image->columns+2;
1885 for (y=0; y < (long) image->rows; y++)
1886 {
1887 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001888 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001889
1890 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1891 if (p == (const PixelPacket *) NULL)
1892 break;
1893 j++;
1894 for (x=0; x < (long) image->columns; x++)
1895 {
1896 switch (channel)
1897 {
1898 case 0: pixel[j]=p->red; break;
1899 case 1: pixel[j]=p->green; break;
1900 case 2: pixel[j]=p->blue; break;
1901 case 3: pixel[j]=p->opacity; break;
1902 default: break;
1903 }
1904 p++;
1905 j++;
1906 }
1907 j++;
1908 }
cristy3ed852e2009-09-05 21:47:34 +00001909 (void) ResetMagickMemory(buffer,0,length*sizeof(*buffer));
1910 for (i=0; i < 4; i++)
1911 {
1912 Hull(X[i],Y[i],image->columns,image->rows,pixel,buffer,1);
1913 Hull(-X[i],-Y[i],image->columns,image->rows,pixel,buffer,1);
1914 Hull(-X[i],-Y[i],image->columns,image->rows,pixel,buffer,-1);
1915 Hull(X[i],Y[i],image->columns,image->rows,pixel,buffer,-1);
1916 }
1917 j=(long) image->columns+2;
1918 for (y=0; y < (long) image->rows; y++)
1919 {
1920 MagickBooleanType
1921 sync;
1922
1923 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001924 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001925
1926 q=GetCacheViewAuthenticPixels(despeckle_view,0,y,despeckle_image->columns,
1927 1,exception);
1928 if (q == (PixelPacket *) NULL)
1929 break;
1930 j++;
1931 for (x=0; x < (long) image->columns; x++)
1932 {
1933 switch (channel)
1934 {
1935 case 0: q->red=pixel[j]; break;
1936 case 1: q->green=pixel[j]; break;
1937 case 2: q->blue=pixel[j]; break;
1938 case 3: q->opacity=pixel[j]; break;
1939 default: break;
1940 }
1941 q++;
1942 j++;
1943 }
1944 sync=SyncCacheViewAuthenticPixels(despeckle_view,exception);
1945 if (sync == MagickFalse)
1946 {
1947 status=MagickFalse;
1948 break;
1949 }
1950 j++;
1951 }
1952 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1953 {
1954 MagickBooleanType
1955 proceed;
1956
cristyb5d5f722009-11-04 03:03:49 +00001957#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001958 #pragma omp critical (MagickCore_DespeckleImage)
1959#endif
1960 proceed=SetImageProgress(image,DespeckleImageTag,channel,3);
1961 if (proceed == MagickFalse)
1962 status=MagickFalse;
1963 }
1964 }
1965 despeckle_view=DestroyCacheView(despeckle_view);
1966 image_view=DestroyCacheView(image_view);
1967 buffers=DestroyPixelThreadSet(buffers);
1968 pixels=DestroyPixelThreadSet(pixels);
1969 despeckle_image->type=image->type;
1970 if (status == MagickFalse)
1971 despeckle_image=DestroyImage(despeckle_image);
1972 return(despeckle_image);
1973}
1974
1975/*
1976%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1977% %
1978% %
1979% %
1980% E d g e I m a g e %
1981% %
1982% %
1983% %
1984%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1985%
1986% EdgeImage() finds edges in an image. Radius defines the radius of the
1987% convolution filter. Use a radius of 0 and EdgeImage() selects a suitable
1988% radius for you.
1989%
1990% The format of the EdgeImage method is:
1991%
1992% Image *EdgeImage(const Image *image,const double radius,
1993% ExceptionInfo *exception)
1994%
1995% A description of each parameter follows:
1996%
1997% o image: the image.
1998%
1999% o radius: the radius of the pixel neighborhood.
2000%
2001% o exception: return any errors or warnings in this structure.
2002%
2003*/
2004MagickExport Image *EdgeImage(const Image *image,const double radius,
2005 ExceptionInfo *exception)
2006{
2007 Image
2008 *edge_image;
2009
2010 double
2011 *kernel;
2012
2013 register long
2014 i;
2015
2016 unsigned long
2017 width;
2018
2019 assert(image != (const Image *) NULL);
2020 assert(image->signature == MagickSignature);
2021 if (image->debug != MagickFalse)
2022 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2023 assert(exception != (ExceptionInfo *) NULL);
2024 assert(exception->signature == MagickSignature);
2025 width=GetOptimalKernelWidth1D(radius,0.5);
2026 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2027 if (kernel == (double *) NULL)
2028 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2029 for (i=0; i < (long) (width*width); i++)
2030 kernel[i]=(-1.0);
2031 kernel[i/2]=(double) (width*width-1.0);
2032 edge_image=ConvolveImage(image,width,kernel,exception);
2033 kernel=(double *) RelinquishMagickMemory(kernel);
2034 return(edge_image);
2035}
2036
2037/*
2038%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2039% %
2040% %
2041% %
2042% E m b o s s I m a g e %
2043% %
2044% %
2045% %
2046%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2047%
2048% EmbossImage() returns a grayscale image with a three-dimensional effect.
2049% We convolve the image with a Gaussian operator of the given radius and
2050% standard deviation (sigma). For reasonable results, radius should be
2051% larger than sigma. Use a radius of 0 and Emboss() selects a suitable
2052% radius for you.
2053%
2054% The format of the EmbossImage method is:
2055%
2056% Image *EmbossImage(const Image *image,const double radius,
2057% const double sigma,ExceptionInfo *exception)
2058%
2059% A description of each parameter follows:
2060%
2061% o image: the image.
2062%
2063% o radius: the radius of the pixel neighborhood.
2064%
2065% o sigma: the standard deviation of the Gaussian, in pixels.
2066%
2067% o exception: return any errors or warnings in this structure.
2068%
2069*/
2070MagickExport Image *EmbossImage(const Image *image,const double radius,
2071 const double sigma,ExceptionInfo *exception)
2072{
2073 double
2074 *kernel;
2075
2076 Image
2077 *emboss_image;
2078
2079 long
2080 j;
2081
2082 MagickRealType
2083 alpha;
2084
2085 register long
2086 i,
2087 u,
2088 v;
2089
2090 unsigned long
2091 width;
2092
2093 assert(image != (Image *) NULL);
2094 assert(image->signature == MagickSignature);
2095 if (image->debug != MagickFalse)
2096 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2097 assert(exception != (ExceptionInfo *) NULL);
2098 assert(exception->signature == MagickSignature);
2099 width=GetOptimalKernelWidth2D(radius,sigma);
2100 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2101 if (kernel == (double *) NULL)
2102 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2103 i=0;
2104 j=(long) width/2;
2105 for (v=(-((long) width/2)); v <= (long) (width/2); v++)
2106 {
2107 for (u=(-((long) width/2)); u <= (long) (width/2); u++)
2108 {
2109 alpha=exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma));
2110 kernel[i]=(double) (((u < 0) || (v < 0) ? -8.0 : 8.0)*alpha/
2111 (2.0*MagickPI*MagickSigma*MagickSigma));
2112 if (u != j)
2113 kernel[i]=0.0;
2114 i++;
2115 }
2116 j--;
2117 }
2118 emboss_image=ConvolveImage(image,width,kernel,exception);
2119 if (emboss_image != (Image *) NULL)
2120 (void) EqualizeImage(emboss_image);
2121 kernel=(double *) RelinquishMagickMemory(kernel);
2122 return(emboss_image);
2123}
2124
2125/*
2126%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2127% %
2128% %
2129% %
2130% G a u s s i a n B l u r I m a g e %
2131% %
2132% %
2133% %
2134%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2135%
2136% GaussianBlurImage() blurs an image. We convolve the image with a
2137% Gaussian operator of the given radius and standard deviation (sigma).
2138% For reasonable results, the radius should be larger than sigma. Use a
2139% radius of 0 and GaussianBlurImage() selects a suitable radius for you
2140%
2141% The format of the GaussianBlurImage method is:
2142%
2143% Image *GaussianBlurImage(const Image *image,onst double radius,
2144% const double sigma,ExceptionInfo *exception)
2145% Image *GaussianBlurImageChannel(const Image *image,
2146% const ChannelType channel,const double radius,const double sigma,
2147% ExceptionInfo *exception)
2148%
2149% A description of each parameter follows:
2150%
2151% o image: the image.
2152%
2153% o channel: the channel type.
2154%
2155% o radius: the radius of the Gaussian, in pixels, not counting the center
2156% pixel.
2157%
2158% o sigma: the standard deviation of the Gaussian, in pixels.
2159%
2160% o exception: return any errors or warnings in this structure.
2161%
2162*/
2163
2164MagickExport Image *GaussianBlurImage(const Image *image,const double radius,
2165 const double sigma,ExceptionInfo *exception)
2166{
2167 Image
2168 *blur_image;
2169
2170 blur_image=GaussianBlurImageChannel(image,DefaultChannels,radius,sigma,
2171 exception);
2172 return(blur_image);
2173}
2174
2175MagickExport Image *GaussianBlurImageChannel(const Image *image,
2176 const ChannelType channel,const double radius,const double sigma,
2177 ExceptionInfo *exception)
2178{
2179 double
2180 *kernel;
2181
2182 Image
2183 *blur_image;
2184
2185 MagickRealType
2186 alpha;
2187
2188 register long
2189 i,
2190 u,
2191 v;
2192
2193 unsigned long
2194 width;
2195
2196 assert(image != (const Image *) NULL);
2197 assert(image->signature == MagickSignature);
2198 if (image->debug != MagickFalse)
2199 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2200 assert(exception != (ExceptionInfo *) NULL);
2201 assert(exception->signature == MagickSignature);
2202 width=GetOptimalKernelWidth2D(radius,sigma);
2203 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2204 if (kernel == (double *) NULL)
2205 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2206 i=0;
2207 for (v=(-((long) width/2)); v <= (long) (width/2); v++)
2208 {
2209 for (u=(-((long) width/2)); u <= (long) (width/2); u++)
2210 {
2211 alpha=exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma));
2212 kernel[i]=(double) (alpha/(2.0*MagickPI*MagickSigma*MagickSigma));
2213 i++;
2214 }
2215 }
2216 blur_image=ConvolveImageChannel(image,channel,width,kernel,exception);
2217 kernel=(double *) RelinquishMagickMemory(kernel);
2218 return(blur_image);
2219}
2220
2221/*
2222%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2223% %
2224% %
2225% %
2226% M e d i a n F i l t e r I m a g e %
2227% %
2228% %
2229% %
2230%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2231%
2232% MedianFilterImage() applies a digital filter that improves the quality
2233% of a noisy image. Each pixel is replaced by the median in a set of
2234% neighboring pixels as defined by radius.
2235%
2236% The algorithm was contributed by Mike Edmonds and implements an insertion
2237% sort for selecting median color-channel values. For more on this algorithm
2238% see "Skip Lists: A probabilistic Alternative to Balanced Trees" by William
2239% Pugh in the June 1990 of Communications of the ACM.
2240%
2241% The format of the MedianFilterImage method is:
2242%
2243% Image *MedianFilterImage(const Image *image,const double radius,
2244% ExceptionInfo *exception)
2245%
2246% A description of each parameter follows:
2247%
2248% o image: the image.
2249%
2250% o radius: the radius of the pixel neighborhood.
2251%
2252% o exception: return any errors or warnings in this structure.
2253%
2254*/
2255
2256#define MedianListChannels 5
2257
2258typedef struct _MedianListNode
2259{
2260 unsigned long
2261 next[9],
2262 count,
2263 signature;
2264} MedianListNode;
2265
2266typedef struct _MedianSkipList
2267{
2268 long
2269 level;
2270
2271 MedianListNode
2272 *nodes;
2273} MedianSkipList;
2274
2275typedef struct _MedianPixelList
2276{
2277 unsigned long
2278 center,
2279 seed,
2280 signature;
2281
2282 MedianSkipList
2283 lists[MedianListChannels];
2284} MedianPixelList;
2285
2286static MedianPixelList *DestroyMedianPixelList(MedianPixelList *pixel_list)
2287{
2288 register long
2289 i;
2290
2291 if (pixel_list == (MedianPixelList *) NULL)
2292 return((MedianPixelList *) NULL);
2293 for (i=0; i < MedianListChannels; i++)
2294 if (pixel_list->lists[i].nodes != (MedianListNode *) NULL)
2295 pixel_list->lists[i].nodes=(MedianListNode *) RelinquishMagickMemory(
2296 pixel_list->lists[i].nodes);
2297 pixel_list=(MedianPixelList *) RelinquishAlignedMemory(pixel_list);
2298 return(pixel_list);
2299}
2300
2301static MedianPixelList **DestroyMedianPixelListThreadSet(
2302 MedianPixelList **pixel_list)
2303{
2304 register long
2305 i;
2306
2307 assert(pixel_list != (MedianPixelList **) NULL);
2308 for (i=0; i < (long) GetOpenMPMaximumThreads(); i++)
2309 if (pixel_list[i] != (MedianPixelList *) NULL)
2310 pixel_list[i]=DestroyMedianPixelList(pixel_list[i]);
2311 pixel_list=(MedianPixelList **) RelinquishAlignedMemory(pixel_list);
2312 return(pixel_list);
2313}
2314
2315static MedianPixelList *AcquireMedianPixelList(const unsigned long width)
2316{
2317 MedianPixelList
2318 *pixel_list;
2319
2320 register long
2321 i;
2322
2323 pixel_list=(MedianPixelList *) AcquireAlignedMemory(1,sizeof(*pixel_list));
2324 if (pixel_list == (MedianPixelList *) NULL)
2325 return(pixel_list);
2326 (void) ResetMagickMemory((void *) pixel_list,0,sizeof(*pixel_list));
2327 pixel_list->center=width*width/2;
2328 for (i=0; i < MedianListChannels; i++)
2329 {
2330 pixel_list->lists[i].nodes=(MedianListNode *) AcquireQuantumMemory(65537UL,
2331 sizeof(*pixel_list->lists[i].nodes));
2332 if (pixel_list->lists[i].nodes == (MedianListNode *) NULL)
2333 return(DestroyMedianPixelList(pixel_list));
2334 (void) ResetMagickMemory(pixel_list->lists[i].nodes,0,65537UL*
2335 sizeof(*pixel_list->lists[i].nodes));
2336 }
2337 pixel_list->signature=MagickSignature;
2338 return(pixel_list);
2339}
2340
2341static MedianPixelList **AcquireMedianPixelListThreadSet(
2342 const unsigned long width)
2343{
2344 register long
2345 i;
2346
2347 MedianPixelList
2348 **pixel_list;
2349
2350 unsigned long
2351 number_threads;
2352
2353 number_threads=GetOpenMPMaximumThreads();
2354 pixel_list=(MedianPixelList **) AcquireAlignedMemory(number_threads,
2355 sizeof(*pixel_list));
2356 if (pixel_list == (MedianPixelList **) NULL)
2357 return((MedianPixelList **) NULL);
2358 (void) ResetMagickMemory(pixel_list,0,number_threads*sizeof(*pixel_list));
2359 for (i=0; i < (long) number_threads; i++)
2360 {
2361 pixel_list[i]=AcquireMedianPixelList(width);
2362 if (pixel_list[i] == (MedianPixelList *) NULL)
2363 return(DestroyMedianPixelListThreadSet(pixel_list));
2364 }
2365 return(pixel_list);
2366}
2367
2368static void AddNodeMedianPixelList(MedianPixelList *pixel_list,
2369 const long channel,const unsigned long color)
2370{
2371 register long
2372 level;
2373
2374 register MedianSkipList
2375 *list;
2376
2377 unsigned long
2378 search,
2379 update[9];
2380
2381 /*
2382 Initialize the node.
2383 */
2384 list=pixel_list->lists+channel;
2385 list->nodes[color].signature=pixel_list->signature;
2386 list->nodes[color].count=1;
2387 /*
2388 Determine where it belongs in the list.
2389 */
2390 search=65536UL;
2391 for (level=list->level; level >= 0; level--)
2392 {
2393 while (list->nodes[search].next[level] < color)
2394 search=list->nodes[search].next[level];
2395 update[level]=search;
2396 }
2397 /*
2398 Generate a pseudo-random level for this node.
2399 */
2400 for (level=0; ; level++)
2401 {
2402 pixel_list->seed=(pixel_list->seed*42893621L)+1L;
2403 if ((pixel_list->seed & 0x300) != 0x300)
2404 break;
2405 }
2406 if (level > 8)
2407 level=8;
2408 if (level > (list->level+2))
2409 level=list->level+2;
2410 /*
2411 If we're raising the list's level, link back to the root node.
2412 */
2413 while (level > list->level)
2414 {
2415 list->level++;
2416 update[list->level]=65536UL;
2417 }
2418 /*
2419 Link the node into the skip-list.
2420 */
2421 do
2422 {
2423 list->nodes[color].next[level]=list->nodes[update[level]].next[level];
2424 list->nodes[update[level]].next[level]=color;
2425 }
2426 while (level-- > 0);
2427}
2428
2429static MagickPixelPacket GetMedianPixelList(MedianPixelList *pixel_list)
2430{
2431 MagickPixelPacket
2432 pixel;
2433
2434 register long
2435 channel;
2436
2437 register MedianSkipList
2438 *list;
2439
2440 unsigned long
2441 center,
2442 color,
2443 count;
2444
2445 unsigned short
2446 channels[MedianListChannels];
2447
2448 /*
2449 Find the median value for each of the color.
2450 */
2451 center=pixel_list->center;
2452 for (channel=0; channel < 5; channel++)
2453 {
2454 list=pixel_list->lists+channel;
2455 color=65536UL;
2456 count=0;
2457 do
2458 {
2459 color=list->nodes[color].next[0];
2460 count+=list->nodes[color].count;
2461 }
2462 while (count <= center);
2463 channels[channel]=(unsigned short) color;
2464 }
2465 GetMagickPixelPacket((const Image *) NULL,&pixel);
2466 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
2467 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
2468 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
2469 pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
2470 pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]);
2471 return(pixel);
2472}
2473
2474static inline void InsertMedianPixelList(const Image *image,
2475 const PixelPacket *pixel,const IndexPacket *indexes,
2476 MedianPixelList *pixel_list)
2477{
2478 unsigned long
2479 signature;
2480
2481 unsigned short
2482 index;
2483
2484 index=ScaleQuantumToShort(pixel->red);
2485 signature=pixel_list->lists[0].nodes[index].signature;
2486 if (signature == pixel_list->signature)
2487 pixel_list->lists[0].nodes[index].count++;
2488 else
2489 AddNodeMedianPixelList(pixel_list,0,index);
2490 index=ScaleQuantumToShort(pixel->green);
2491 signature=pixel_list->lists[1].nodes[index].signature;
2492 if (signature == pixel_list->signature)
2493 pixel_list->lists[1].nodes[index].count++;
2494 else
2495 AddNodeMedianPixelList(pixel_list,1,index);
2496 index=ScaleQuantumToShort(pixel->blue);
2497 signature=pixel_list->lists[2].nodes[index].signature;
2498 if (signature == pixel_list->signature)
2499 pixel_list->lists[2].nodes[index].count++;
2500 else
2501 AddNodeMedianPixelList(pixel_list,2,index);
2502 index=ScaleQuantumToShort(pixel->opacity);
2503 signature=pixel_list->lists[3].nodes[index].signature;
2504 if (signature == pixel_list->signature)
2505 pixel_list->lists[3].nodes[index].count++;
2506 else
2507 AddNodeMedianPixelList(pixel_list,3,index);
2508 if (image->colorspace == CMYKColorspace)
2509 index=ScaleQuantumToShort(*indexes);
2510 signature=pixel_list->lists[4].nodes[index].signature;
2511 if (signature == pixel_list->signature)
2512 pixel_list->lists[4].nodes[index].count++;
2513 else
2514 AddNodeMedianPixelList(pixel_list,4,index);
2515}
2516
2517static void ResetMedianPixelList(MedianPixelList *pixel_list)
2518{
2519 int
2520 level;
2521
2522 register long
2523 channel;
2524
2525 register MedianListNode
2526 *root;
2527
2528 register MedianSkipList
2529 *list;
2530
2531 /*
2532 Reset the skip-list.
2533 */
2534 for (channel=0; channel < 5; channel++)
2535 {
2536 list=pixel_list->lists+channel;
2537 root=list->nodes+65536UL;
2538 list->level=0;
2539 for (level=0; level < 9; level++)
2540 root->next[level]=65536UL;
2541 }
2542 pixel_list->seed=pixel_list->signature++;
2543}
2544
2545MagickExport Image *MedianFilterImage(const Image *image,const double radius,
2546 ExceptionInfo *exception)
2547{
2548#define MedianFilterImageTag "MedianFilter/Image"
2549
2550 Image
2551 *median_image;
2552
2553 long
2554 progress,
2555 y;
2556
2557 MagickBooleanType
2558 status;
2559
2560 MedianPixelList
2561 **pixel_list;
2562
2563 unsigned long
2564 width;
2565
2566 CacheView
2567 *image_view,
2568 *median_view;
2569
2570 /*
2571 Initialize median image attributes.
2572 */
2573 assert(image != (Image *) NULL);
2574 assert(image->signature == MagickSignature);
2575 if (image->debug != MagickFalse)
2576 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2577 assert(exception != (ExceptionInfo *) NULL);
2578 assert(exception->signature == MagickSignature);
2579 width=GetOptimalKernelWidth2D(radius,0.5);
2580 if ((image->columns < width) || (image->rows < width))
2581 ThrowImageException(OptionError,"ImageSmallerThanKernelRadius");
2582 median_image=CloneImage(image,image->columns,image->rows,MagickTrue,
2583 exception);
2584 if (median_image == (Image *) NULL)
2585 return((Image *) NULL);
2586 if (SetImageStorageClass(median_image,DirectClass) == MagickFalse)
2587 {
2588 InheritException(exception,&median_image->exception);
2589 median_image=DestroyImage(median_image);
2590 return((Image *) NULL);
2591 }
2592 pixel_list=AcquireMedianPixelListThreadSet(width);
2593 if (pixel_list == (MedianPixelList **) NULL)
2594 {
2595 median_image=DestroyImage(median_image);
2596 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2597 }
2598 /*
2599 Median filter each image row.
2600 */
2601 status=MagickTrue;
2602 progress=0;
2603 image_view=AcquireCacheView(image);
2604 median_view=AcquireCacheView(median_image);
cristyb5d5f722009-11-04 03:03:49 +00002605#if defined(MAGICKCORE_OPENMP_SUPPORT)
2606 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002607#endif
2608 for (y=0; y < (long) median_image->rows; y++)
2609 {
2610 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002611 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002612
2613 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002614 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002615
2616 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002617 *restrict median_indexes;
cristy3ed852e2009-09-05 21:47:34 +00002618
2619 register long
2620 id,
2621 x;
2622
2623 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002624 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002625
2626 if (status == MagickFalse)
2627 continue;
2628 p=GetCacheViewVirtualPixels(image_view,-((long) width/2L),y-(long) (width/
2629 2L),image->columns+width,width,exception);
2630 q=QueueCacheViewAuthenticPixels(median_view,0,y,median_image->columns,1,
2631 exception);
2632 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2633 {
2634 status=MagickFalse;
2635 continue;
2636 }
2637 indexes=GetCacheViewVirtualIndexQueue(image_view);
2638 median_indexes=GetCacheViewAuthenticIndexQueue(median_view);
2639 id=GetOpenMPThreadId();
2640 for (x=0; x < (long) median_image->columns; x++)
2641 {
2642 MagickPixelPacket
2643 pixel;
2644
2645 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002646 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +00002647
2648 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002649 *restrict s;
cristy3ed852e2009-09-05 21:47:34 +00002650
2651 register long
2652 u,
2653 v;
2654
2655 r=p;
2656 s=indexes+x;
2657 ResetMedianPixelList(pixel_list[id]);
2658 for (v=0; v < (long) width; v++)
2659 {
2660 for (u=0; u < (long) width; u++)
2661 InsertMedianPixelList(image,r+u,s+u,pixel_list[id]);
2662 r+=image->columns+width;
2663 s+=image->columns+width;
2664 }
2665 pixel=GetMedianPixelList(pixel_list[id]);
2666 SetPixelPacket(median_image,&pixel,q,median_indexes+x);
2667 p++;
2668 q++;
2669 }
2670 if (SyncCacheViewAuthenticPixels(median_view,exception) == MagickFalse)
2671 status=MagickFalse;
2672 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2673 {
2674 MagickBooleanType
2675 proceed;
2676
cristyb5d5f722009-11-04 03:03:49 +00002677#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002678 #pragma omp critical (MagickCore_MedianFilterImage)
2679#endif
2680 proceed=SetImageProgress(image,MedianFilterImageTag,progress++,
2681 image->rows);
2682 if (proceed == MagickFalse)
2683 status=MagickFalse;
2684 }
2685 }
2686 median_view=DestroyCacheView(median_view);
2687 image_view=DestroyCacheView(image_view);
2688 pixel_list=DestroyMedianPixelListThreadSet(pixel_list);
2689 return(median_image);
2690}
2691
2692/*
2693%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2694% %
2695% %
2696% %
2697% M o t i o n B l u r I m a g e %
2698% %
2699% %
2700% %
2701%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2702%
2703% MotionBlurImage() simulates motion blur. We convolve the image with a
2704% Gaussian operator of the given radius and standard deviation (sigma).
2705% For reasonable results, radius should be larger than sigma. Use a
2706% radius of 0 and MotionBlurImage() selects a suitable radius for you.
2707% Angle gives the angle of the blurring motion.
2708%
2709% Andrew Protano contributed this effect.
2710%
2711% The format of the MotionBlurImage method is:
2712%
2713% Image *MotionBlurImage(const Image *image,const double radius,
2714% const double sigma,const double angle,ExceptionInfo *exception)
2715% Image *MotionBlurImageChannel(const Image *image,const ChannelType channel,
2716% const double radius,const double sigma,const double angle,
2717% ExceptionInfo *exception)
2718%
2719% A description of each parameter follows:
2720%
2721% o image: the image.
2722%
2723% o channel: the channel type.
2724%
2725% o radius: the radius of the Gaussian, in pixels, not counting the center
2726% o radius: the radius of the Gaussian, in pixels, not counting
2727% the center pixel.
2728%
2729% o sigma: the standard deviation of the Gaussian, in pixels.
2730%
2731% o angle: Apply the effect along this angle.
2732%
2733% o exception: return any errors or warnings in this structure.
2734%
2735*/
2736
2737static double *GetMotionBlurKernel(unsigned long width,
2738 const MagickRealType sigma)
2739{
2740#define KernelRank 3
2741
2742 double
2743 *kernel;
2744
2745 long
2746 bias;
2747
2748 MagickRealType
2749 alpha,
2750 normalize;
2751
2752 register long
2753 i;
2754
2755 /*
2756 Generate a 1-D convolution kernel.
2757 */
2758 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
2759 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
2760 if (kernel == (double *) NULL)
2761 return(kernel);
2762 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
2763 bias=(long) (KernelRank*width);
2764 for (i=0; i < (long) bias; i++)
2765 {
2766 alpha=exp((-((double) (i*i))/(double) (2.0*KernelRank*KernelRank*
2767 MagickSigma*MagickSigma)));
2768 kernel[i/KernelRank]+=(double) alpha/(MagickSQ2PI*sigma);
2769 }
2770 normalize=0.0;
2771 for (i=0; i < (long) width; i++)
2772 normalize+=kernel[i];
2773 for (i=0; i < (long) width; i++)
2774 kernel[i]/=normalize;
2775 return(kernel);
2776}
2777
2778MagickExport Image *MotionBlurImage(const Image *image,const double radius,
2779 const double sigma,const double angle,ExceptionInfo *exception)
2780{
2781 Image
2782 *motion_blur;
2783
2784 motion_blur=MotionBlurImageChannel(image,DefaultChannels,radius,sigma,angle,
2785 exception);
2786 return(motion_blur);
2787}
2788
2789MagickExport Image *MotionBlurImageChannel(const Image *image,
2790 const ChannelType channel,const double radius,const double sigma,
2791 const double angle,ExceptionInfo *exception)
2792{
2793 typedef struct _OffsetInfo
2794 {
2795 long
2796 x,
2797 y;
2798 } OffsetInfo;
2799
2800 double
2801 *kernel;
2802
2803 Image
2804 *blur_image;
2805
2806 long
2807 progress,
2808 y;
2809
2810 MagickBooleanType
2811 status;
2812
2813 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00002814 bias;
cristy3ed852e2009-09-05 21:47:34 +00002815
2816 OffsetInfo
2817 *offset;
2818
2819 PointInfo
2820 point;
2821
2822 register long
2823 i;
2824
2825 unsigned long
2826 width;
2827
2828 CacheView
2829 *blur_view,
2830 *image_view;
2831
2832 assert(image != (Image *) NULL);
2833 assert(image->signature == MagickSignature);
2834 if (image->debug != MagickFalse)
2835 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2836 assert(exception != (ExceptionInfo *) NULL);
2837 width=GetOptimalKernelWidth1D(radius,sigma);
2838 kernel=GetMotionBlurKernel(width,sigma);
2839 if (kernel == (double *) NULL)
2840 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2841 offset=(OffsetInfo *) AcquireQuantumMemory(width,sizeof(*offset));
2842 if (offset == (OffsetInfo *) NULL)
2843 {
2844 kernel=(double *) RelinquishMagickMemory(kernel);
2845 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2846 }
2847 blur_image=CloneImage(image,0,0,MagickTrue,exception);
2848 if (blur_image == (Image *) NULL)
2849 {
2850 kernel=(double *) RelinquishMagickMemory(kernel);
2851 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2852 return((Image *) NULL);
2853 }
2854 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
2855 {
2856 kernel=(double *) RelinquishMagickMemory(kernel);
2857 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2858 InheritException(exception,&blur_image->exception);
2859 blur_image=DestroyImage(blur_image);
2860 return((Image *) NULL);
2861 }
2862 point.x=(double) width*sin(DegreesToRadians(angle));
2863 point.y=(double) width*cos(DegreesToRadians(angle));
2864 for (i=0; i < (long) width; i++)
2865 {
2866 offset[i].x=(long) ((i*point.y)/hypot(point.x,point.y)+0.5);
2867 offset[i].y=(long) ((i*point.x)/hypot(point.x,point.y)+0.5);
2868 }
2869 /*
2870 Motion blur image.
2871 */
2872 status=MagickTrue;
2873 progress=0;
cristyddd82202009-11-03 20:14:50 +00002874 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00002875 image_view=AcquireCacheView(image);
2876 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00002877#if defined(MAGICKCORE_OPENMP_SUPPORT)
2878 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002879#endif
2880 for (y=0; y < (long) image->rows; y++)
2881 {
2882 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002883 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00002884
2885 register long
2886 x;
2887
2888 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002889 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002890
2891 if (status == MagickFalse)
2892 continue;
2893 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
2894 exception);
2895 if (q == (PixelPacket *) NULL)
2896 {
2897 status=MagickFalse;
2898 continue;
2899 }
2900 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
2901 for (x=0; x < (long) image->columns; x++)
2902 {
2903 MagickPixelPacket
2904 qixel;
2905
2906 PixelPacket
2907 pixel;
2908
2909 register double
cristyc47d1f82009-11-26 01:44:43 +00002910 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00002911
2912 register long
2913 i;
2914
2915 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002916 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002917
2918 k=kernel;
cristyddd82202009-11-03 20:14:50 +00002919 qixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00002920 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
2921 {
2922 for (i=0; i < (long) width; i++)
2923 {
2924 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
2925 offset[i].y,&pixel,exception);
2926 qixel.red+=(*k)*pixel.red;
2927 qixel.green+=(*k)*pixel.green;
2928 qixel.blue+=(*k)*pixel.blue;
2929 qixel.opacity+=(*k)*pixel.opacity;
2930 if (image->colorspace == CMYKColorspace)
2931 {
2932 indexes=GetCacheViewVirtualIndexQueue(image_view);
2933 qixel.index+=(*k)*(*indexes);
2934 }
2935 k++;
2936 }
2937 if ((channel & RedChannel) != 0)
2938 q->red=RoundToQuantum(qixel.red);
2939 if ((channel & GreenChannel) != 0)
2940 q->green=RoundToQuantum(qixel.green);
2941 if ((channel & BlueChannel) != 0)
2942 q->blue=RoundToQuantum(qixel.blue);
2943 if ((channel & OpacityChannel) != 0)
2944 q->opacity=RoundToQuantum(qixel.opacity);
2945 if (((channel & IndexChannel) != 0) &&
2946 (image->colorspace == CMYKColorspace))
2947 blur_indexes[x]=(IndexPacket) RoundToQuantum(qixel.index);
2948 }
2949 else
2950 {
2951 MagickRealType
2952 alpha,
2953 gamma;
2954
2955 alpha=0.0;
2956 gamma=0.0;
2957 for (i=0; i < (long) width; i++)
2958 {
2959 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
2960 offset[i].y,&pixel,exception);
2961 alpha=(MagickRealType) (QuantumScale*(QuantumRange-pixel.opacity));
2962 qixel.red+=(*k)*alpha*pixel.red;
2963 qixel.green+=(*k)*alpha*pixel.green;
2964 qixel.blue+=(*k)*alpha*pixel.blue;
2965 qixel.opacity+=(*k)*pixel.opacity;
2966 if (image->colorspace == CMYKColorspace)
2967 {
2968 indexes=GetCacheViewVirtualIndexQueue(image_view);
2969 qixel.index+=(*k)*alpha*(*indexes);
2970 }
2971 gamma+=(*k)*alpha;
2972 k++;
2973 }
2974 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
2975 if ((channel & RedChannel) != 0)
2976 q->red=RoundToQuantum(gamma*qixel.red);
2977 if ((channel & GreenChannel) != 0)
2978 q->green=RoundToQuantum(gamma*qixel.green);
2979 if ((channel & BlueChannel) != 0)
2980 q->blue=RoundToQuantum(gamma*qixel.blue);
2981 if ((channel & OpacityChannel) != 0)
2982 q->opacity=RoundToQuantum(qixel.opacity);
2983 if (((channel & IndexChannel) != 0) &&
2984 (image->colorspace == CMYKColorspace))
2985 blur_indexes[x]=(IndexPacket) RoundToQuantum(gamma*qixel.index);
2986 }
2987 q++;
2988 }
2989 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
2990 status=MagickFalse;
2991 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2992 {
2993 MagickBooleanType
2994 proceed;
2995
cristyb5d5f722009-11-04 03:03:49 +00002996#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002997 #pragma omp critical (MagickCore_MotionBlurImageChannel)
2998#endif
2999 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
3000 if (proceed == MagickFalse)
3001 status=MagickFalse;
3002 }
3003 }
3004 blur_view=DestroyCacheView(blur_view);
3005 image_view=DestroyCacheView(image_view);
3006 kernel=(double *) RelinquishMagickMemory(kernel);
3007 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
3008 if (status == MagickFalse)
3009 blur_image=DestroyImage(blur_image);
3010 return(blur_image);
3011}
3012
3013/*
3014%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3015% %
3016% %
3017% %
3018% P r e v i e w I m a g e %
3019% %
3020% %
3021% %
3022%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3023%
3024% PreviewImage() tiles 9 thumbnails of the specified image with an image
3025% processing operation applied with varying parameters. This may be helpful
3026% pin-pointing an appropriate parameter for a particular image processing
3027% operation.
3028%
3029% The format of the PreviewImages method is:
3030%
3031% Image *PreviewImages(const Image *image,const PreviewType preview,
3032% ExceptionInfo *exception)
3033%
3034% A description of each parameter follows:
3035%
3036% o image: the image.
3037%
3038% o preview: the image processing operation.
3039%
3040% o exception: return any errors or warnings in this structure.
3041%
3042*/
3043MagickExport Image *PreviewImage(const Image *image,const PreviewType preview,
3044 ExceptionInfo *exception)
3045{
3046#define NumberTiles 9
3047#define PreviewImageTag "Preview/Image"
3048#define DefaultPreviewGeometry "204x204+10+10"
3049
3050 char
3051 factor[MaxTextExtent],
3052 label[MaxTextExtent];
3053
3054 double
3055 degrees,
3056 gamma,
3057 percentage,
3058 radius,
3059 sigma,
3060 threshold;
3061
3062 Image
3063 *images,
3064 *montage_image,
3065 *preview_image,
3066 *thumbnail;
3067
3068 ImageInfo
3069 *preview_info;
3070
3071 long
3072 y;
3073
3074 MagickBooleanType
3075 proceed;
3076
3077 MontageInfo
3078 *montage_info;
3079
3080 QuantizeInfo
3081 quantize_info;
3082
3083 RectangleInfo
3084 geometry;
3085
3086 register long
3087 i,
3088 x;
3089
3090 unsigned long
3091 colors;
3092
3093 /*
3094 Open output image file.
3095 */
3096 assert(image != (Image *) NULL);
3097 assert(image->signature == MagickSignature);
3098 if (image->debug != MagickFalse)
3099 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3100 colors=2;
3101 degrees=0.0;
3102 gamma=(-0.2f);
3103 preview_info=AcquireImageInfo();
3104 SetGeometry(image,&geometry);
3105 (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y,
3106 &geometry.width,&geometry.height);
3107 images=NewImageList();
3108 percentage=12.5;
3109 GetQuantizeInfo(&quantize_info);
3110 radius=0.0;
3111 sigma=1.0;
3112 threshold=0.0;
3113 x=0;
3114 y=0;
3115 for (i=0; i < NumberTiles; i++)
3116 {
3117 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
3118 if (thumbnail == (Image *) NULL)
3119 break;
3120 (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL,
3121 (void *) NULL);
3122 (void) SetImageProperty(thumbnail,"label",DefaultTileLabel);
3123 if (i == (NumberTiles/2))
3124 {
3125 (void) QueryColorDatabase("#dfdfdf",&thumbnail->matte_color,exception);
3126 AppendImageToList(&images,thumbnail);
3127 continue;
3128 }
3129 switch (preview)
3130 {
3131 case RotatePreview:
3132 {
3133 degrees+=45.0;
3134 preview_image=RotateImage(thumbnail,degrees,exception);
3135 (void) FormatMagickString(label,MaxTextExtent,"rotate %g",degrees);
3136 break;
3137 }
3138 case ShearPreview:
3139 {
3140 degrees+=5.0;
3141 preview_image=ShearImage(thumbnail,degrees,degrees,exception);
3142 (void) FormatMagickString(label,MaxTextExtent,"shear %gx%g",
3143 degrees,2.0*degrees);
3144 break;
3145 }
3146 case RollPreview:
3147 {
3148 x=(long) ((i+1)*thumbnail->columns)/NumberTiles;
3149 y=(long) ((i+1)*thumbnail->rows)/NumberTiles;
3150 preview_image=RollImage(thumbnail,x,y,exception);
3151 (void) FormatMagickString(label,MaxTextExtent,"roll %ldx%ld",x,y);
3152 break;
3153 }
3154 case HuePreview:
3155 {
3156 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3157 if (preview_image == (Image *) NULL)
3158 break;
3159 (void) FormatMagickString(factor,MaxTextExtent,"100,100,%g",
3160 2.0*percentage);
3161 (void) ModulateImage(preview_image,factor);
3162 (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor);
3163 break;
3164 }
3165 case SaturationPreview:
3166 {
3167 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3168 if (preview_image == (Image *) NULL)
3169 break;
3170 (void) FormatMagickString(factor,MaxTextExtent,"100,%g",2.0*percentage);
3171 (void) ModulateImage(preview_image,factor);
3172 (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor);
3173 break;
3174 }
3175 case BrightnessPreview:
3176 {
3177 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3178 if (preview_image == (Image *) NULL)
3179 break;
3180 (void) FormatMagickString(factor,MaxTextExtent,"%g",2.0*percentage);
3181 (void) ModulateImage(preview_image,factor);
3182 (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor);
3183 break;
3184 }
3185 case GammaPreview:
3186 default:
3187 {
3188 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3189 if (preview_image == (Image *) NULL)
3190 break;
3191 gamma+=0.4f;
3192 (void) GammaImageChannel(preview_image,DefaultChannels,gamma);
3193 (void) FormatMagickString(label,MaxTextExtent,"gamma %g",gamma);
3194 break;
3195 }
3196 case SpiffPreview:
3197 {
3198 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3199 if (preview_image != (Image *) NULL)
3200 for (x=0; x < i; x++)
3201 (void) ContrastImage(preview_image,MagickTrue);
3202 (void) FormatMagickString(label,MaxTextExtent,"contrast (%ld)",i+1);
3203 break;
3204 }
3205 case DullPreview:
3206 {
3207 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3208 if (preview_image == (Image *) NULL)
3209 break;
3210 for (x=0; x < i; x++)
3211 (void) ContrastImage(preview_image,MagickFalse);
3212 (void) FormatMagickString(label,MaxTextExtent,"+contrast (%ld)",i+1);
3213 break;
3214 }
3215 case GrayscalePreview:
3216 {
3217 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3218 if (preview_image == (Image *) NULL)
3219 break;
3220 colors<<=1;
3221 quantize_info.number_colors=colors;
3222 quantize_info.colorspace=GRAYColorspace;
3223 (void) QuantizeImage(&quantize_info,preview_image);
3224 (void) FormatMagickString(label,MaxTextExtent,
3225 "-colorspace gray -colors %ld",colors);
3226 break;
3227 }
3228 case QuantizePreview:
3229 {
3230 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3231 if (preview_image == (Image *) NULL)
3232 break;
3233 colors<<=1;
3234 quantize_info.number_colors=colors;
3235 (void) QuantizeImage(&quantize_info,preview_image);
3236 (void) FormatMagickString(label,MaxTextExtent,"colors %ld",colors);
3237 break;
3238 }
3239 case DespecklePreview:
3240 {
3241 for (x=0; x < (i-1); x++)
3242 {
3243 preview_image=DespeckleImage(thumbnail,exception);
3244 if (preview_image == (Image *) NULL)
3245 break;
3246 thumbnail=DestroyImage(thumbnail);
3247 thumbnail=preview_image;
3248 }
3249 preview_image=DespeckleImage(thumbnail,exception);
3250 if (preview_image == (Image *) NULL)
3251 break;
3252 (void) FormatMagickString(label,MaxTextExtent,"despeckle (%ld)",i+1);
3253 break;
3254 }
3255 case ReduceNoisePreview:
3256 {
3257 preview_image=ReduceNoiseImage(thumbnail,radius,exception);
3258 (void) FormatMagickString(label,MaxTextExtent,"noise %g",radius);
3259 break;
3260 }
3261 case AddNoisePreview:
3262 {
3263 switch ((int) i)
3264 {
3265 case 0:
3266 {
3267 (void) CopyMagickString(factor,"uniform",MaxTextExtent);
3268 break;
3269 }
3270 case 1:
3271 {
3272 (void) CopyMagickString(factor,"gaussian",MaxTextExtent);
3273 break;
3274 }
3275 case 2:
3276 {
3277 (void) CopyMagickString(factor,"multiplicative",MaxTextExtent);
3278 break;
3279 }
3280 case 3:
3281 {
3282 (void) CopyMagickString(factor,"impulse",MaxTextExtent);
3283 break;
3284 }
3285 case 4:
3286 {
3287 (void) CopyMagickString(factor,"laplacian",MaxTextExtent);
3288 break;
3289 }
3290 case 5:
3291 {
3292 (void) CopyMagickString(factor,"Poisson",MaxTextExtent);
3293 break;
3294 }
3295 default:
3296 {
3297 (void) CopyMagickString(thumbnail->magick,"NULL",MaxTextExtent);
3298 break;
3299 }
3300 }
3301 preview_image=ReduceNoiseImage(thumbnail,(double) i,exception);
3302 (void) FormatMagickString(label,MaxTextExtent,"+noise %s",factor);
3303 break;
3304 }
3305 case SharpenPreview:
3306 {
3307 preview_image=SharpenImage(thumbnail,radius,sigma,exception);
3308 (void) FormatMagickString(label,MaxTextExtent,"sharpen %gx%g",radius,
3309 sigma);
3310 break;
3311 }
3312 case BlurPreview:
3313 {
3314 preview_image=BlurImage(thumbnail,radius,sigma,exception);
3315 (void) FormatMagickString(label,MaxTextExtent,"blur %gx%g",radius,
3316 sigma);
3317 break;
3318 }
3319 case ThresholdPreview:
3320 {
3321 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3322 if (preview_image == (Image *) NULL)
3323 break;
3324 (void) BilevelImage(thumbnail,
3325 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
3326 (void) FormatMagickString(label,MaxTextExtent,"threshold %g",
3327 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
3328 break;
3329 }
3330 case EdgeDetectPreview:
3331 {
3332 preview_image=EdgeImage(thumbnail,radius,exception);
3333 (void) FormatMagickString(label,MaxTextExtent,"edge %g",radius);
3334 break;
3335 }
3336 case SpreadPreview:
3337 {
3338 preview_image=SpreadImage(thumbnail,radius,exception);
3339 (void) FormatMagickString(label,MaxTextExtent,"spread %g",radius+0.5);
3340 break;
3341 }
3342 case SolarizePreview:
3343 {
3344 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3345 if (preview_image == (Image *) NULL)
3346 break;
3347 (void) SolarizeImage(preview_image,(double) QuantumRange*
3348 percentage/100.0);
3349 (void) FormatMagickString(label,MaxTextExtent,"solarize %g",
3350 (QuantumRange*percentage)/100.0);
3351 break;
3352 }
3353 case ShadePreview:
3354 {
3355 degrees+=10.0;
3356 preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees,
3357 exception);
3358 (void) FormatMagickString(label,MaxTextExtent,"shade %gx%g",degrees,
3359 degrees);
3360 break;
3361 }
3362 case RaisePreview:
3363 {
3364 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3365 if (preview_image == (Image *) NULL)
3366 break;
3367 geometry.width=(unsigned long) (2*i+2);
3368 geometry.height=(unsigned long) (2*i+2);
3369 geometry.x=i/2;
3370 geometry.y=i/2;
3371 (void) RaiseImage(preview_image,&geometry,MagickTrue);
3372 (void) FormatMagickString(label,MaxTextExtent,"raise %lux%lu%+ld%+ld",
3373 geometry.width,geometry.height,geometry.x,geometry.y);
3374 break;
3375 }
3376 case SegmentPreview:
3377 {
3378 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3379 if (preview_image == (Image *) NULL)
3380 break;
3381 threshold+=0.4f;
3382 (void) SegmentImage(preview_image,RGBColorspace,MagickFalse,threshold,
3383 threshold);
3384 (void) FormatMagickString(label,MaxTextExtent,"segment %gx%g",
3385 threshold,threshold);
3386 break;
3387 }
3388 case SwirlPreview:
3389 {
3390 preview_image=SwirlImage(thumbnail,degrees,exception);
3391 (void) FormatMagickString(label,MaxTextExtent,"swirl %g",degrees);
3392 degrees+=45.0;
3393 break;
3394 }
3395 case ImplodePreview:
3396 {
3397 degrees+=0.1f;
3398 preview_image=ImplodeImage(thumbnail,degrees,exception);
3399 (void) FormatMagickString(label,MaxTextExtent,"implode %g",degrees);
3400 break;
3401 }
3402 case WavePreview:
3403 {
3404 degrees+=5.0f;
3405 preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,exception);
3406 (void) FormatMagickString(label,MaxTextExtent,"wave %gx%g",0.5*degrees,
3407 2.0*degrees);
3408 break;
3409 }
3410 case OilPaintPreview:
3411 {
3412 preview_image=OilPaintImage(thumbnail,(double) radius,exception);
3413 (void) FormatMagickString(label,MaxTextExtent,"paint %g",radius);
3414 break;
3415 }
3416 case CharcoalDrawingPreview:
3417 {
3418 preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma,
3419 exception);
3420 (void) FormatMagickString(label,MaxTextExtent,"charcoal %gx%g",radius,
3421 sigma);
3422 break;
3423 }
3424 case JPEGPreview:
3425 {
3426 char
3427 filename[MaxTextExtent];
3428
3429 int
3430 file;
3431
3432 MagickBooleanType
3433 status;
3434
3435 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3436 if (preview_image == (Image *) NULL)
3437 break;
3438 preview_info->quality=(unsigned long) percentage;
3439 (void) FormatMagickString(factor,MaxTextExtent,"%lu",
3440 preview_info->quality);
3441 file=AcquireUniqueFileResource(filename);
3442 if (file != -1)
3443 file=close(file)-1;
3444 (void) FormatMagickString(preview_image->filename,MaxTextExtent,
3445 "jpeg:%s",filename);
3446 status=WriteImage(preview_info,preview_image);
3447 if (status != MagickFalse)
3448 {
3449 Image
3450 *quality_image;
3451
3452 (void) CopyMagickString(preview_info->filename,
3453 preview_image->filename,MaxTextExtent);
3454 quality_image=ReadImage(preview_info,exception);
3455 if (quality_image != (Image *) NULL)
3456 {
3457 preview_image=DestroyImage(preview_image);
3458 preview_image=quality_image;
3459 }
3460 }
3461 (void) RelinquishUniqueFileResource(preview_image->filename);
3462 if ((GetBlobSize(preview_image)/1024) >= 1024)
3463 (void) FormatMagickString(label,MaxTextExtent,"quality %s\n%gmb ",
3464 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
3465 1024.0/1024.0);
3466 else
3467 if (GetBlobSize(preview_image) >= 1024)
3468 (void) FormatMagickString(label,MaxTextExtent,"quality %s\n%gkb ",
3469 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
3470 1024.0);
3471 else
3472 (void) FormatMagickString(label,MaxTextExtent,"quality %s\n%lub ",
3473 factor,(unsigned long) GetBlobSize(thumbnail));
3474 break;
3475 }
3476 }
3477 thumbnail=DestroyImage(thumbnail);
3478 percentage+=12.5;
3479 radius+=0.5;
3480 sigma+=0.25;
3481 if (preview_image == (Image *) NULL)
3482 break;
3483 (void) DeleteImageProperty(preview_image,"label");
3484 (void) SetImageProperty(preview_image,"label",label);
3485 AppendImageToList(&images,preview_image);
3486 proceed=SetImageProgress(image,PreviewImageTag,i,NumberTiles);
3487 if (proceed == MagickFalse)
3488 break;
3489 }
3490 if (images == (Image *) NULL)
3491 {
3492 preview_info=DestroyImageInfo(preview_info);
3493 return((Image *) NULL);
3494 }
3495 /*
3496 Create the montage.
3497 */
3498 montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL);
3499 (void) CopyMagickString(montage_info->filename,image->filename,MaxTextExtent);
3500 montage_info->shadow=MagickTrue;
3501 (void) CloneString(&montage_info->tile,"3x3");
3502 (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry);
3503 (void) CloneString(&montage_info->frame,DefaultTileFrame);
3504 montage_image=MontageImages(images,montage_info,exception);
3505 montage_info=DestroyMontageInfo(montage_info);
3506 images=DestroyImageList(images);
3507 if (montage_image == (Image *) NULL)
3508 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3509 if (montage_image->montage != (char *) NULL)
3510 {
3511 /*
3512 Free image directory.
3513 */
3514 montage_image->montage=(char *) RelinquishMagickMemory(
3515 montage_image->montage);
3516 if (image->directory != (char *) NULL)
3517 montage_image->directory=(char *) RelinquishMagickMemory(
3518 montage_image->directory);
3519 }
3520 preview_info=DestroyImageInfo(preview_info);
3521 return(montage_image);
3522}
3523
3524/*
3525%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3526% %
3527% %
3528% %
3529% R a d i a l B l u r I m a g e %
3530% %
3531% %
3532% %
3533%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3534%
3535% RadialBlurImage() applies a radial blur to the image.
3536%
3537% Andrew Protano contributed this effect.
3538%
3539% The format of the RadialBlurImage method is:
3540%
3541% Image *RadialBlurImage(const Image *image,const double angle,
3542% ExceptionInfo *exception)
3543% Image *RadialBlurImageChannel(const Image *image,const ChannelType channel,
3544% const double angle,ExceptionInfo *exception)
3545%
3546% A description of each parameter follows:
3547%
3548% o image: the image.
3549%
3550% o channel: the channel type.
3551%
3552% o angle: the angle of the radial blur.
3553%
3554% o exception: return any errors or warnings in this structure.
3555%
3556*/
3557
3558MagickExport Image *RadialBlurImage(const Image *image,const double angle,
3559 ExceptionInfo *exception)
3560{
3561 Image
3562 *blur_image;
3563
3564 blur_image=RadialBlurImageChannel(image,DefaultChannels,angle,exception);
3565 return(blur_image);
3566}
3567
3568MagickExport Image *RadialBlurImageChannel(const Image *image,
3569 const ChannelType channel,const double angle,ExceptionInfo *exception)
3570{
3571 Image
3572 *blur_image;
3573
3574 long
3575 progress,
3576 y;
3577
3578 MagickBooleanType
3579 status;
3580
3581 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00003582 bias;
cristy3ed852e2009-09-05 21:47:34 +00003583
3584 MagickRealType
3585 blur_radius,
3586 *cos_theta,
3587 offset,
3588 *sin_theta,
3589 theta;
3590
3591 PointInfo
3592 blur_center;
3593
3594 register long
3595 i;
3596
3597 unsigned long
3598 n;
3599
3600 CacheView
3601 *blur_view,
3602 *image_view;
3603
3604 /*
3605 Allocate blur image.
3606 */
3607 assert(image != (Image *) NULL);
3608 assert(image->signature == MagickSignature);
3609 if (image->debug != MagickFalse)
3610 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3611 assert(exception != (ExceptionInfo *) NULL);
3612 assert(exception->signature == MagickSignature);
3613 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3614 if (blur_image == (Image *) NULL)
3615 return((Image *) NULL);
3616 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3617 {
3618 InheritException(exception,&blur_image->exception);
3619 blur_image=DestroyImage(blur_image);
3620 return((Image *) NULL);
3621 }
3622 blur_center.x=(double) image->columns/2.0;
3623 blur_center.y=(double) image->rows/2.0;
3624 blur_radius=hypot(blur_center.x,blur_center.y);
3625 n=(unsigned long) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+
3626 2UL);
3627 theta=DegreesToRadians(angle)/(MagickRealType) (n-1);
3628 cos_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3629 sizeof(*cos_theta));
3630 sin_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3631 sizeof(*sin_theta));
3632 if ((cos_theta == (MagickRealType *) NULL) ||
3633 (sin_theta == (MagickRealType *) NULL))
3634 {
3635 blur_image=DestroyImage(blur_image);
3636 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3637 }
3638 offset=theta*(MagickRealType) (n-1)/2.0;
3639 for (i=0; i < (long) n; i++)
3640 {
3641 cos_theta[i]=cos((double) (theta*i-offset));
3642 sin_theta[i]=sin((double) (theta*i-offset));
3643 }
3644 /*
3645 Radial blur image.
3646 */
3647 status=MagickTrue;
3648 progress=0;
cristyddd82202009-11-03 20:14:50 +00003649 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003650 image_view=AcquireCacheView(image);
3651 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003652#if defined(MAGICKCORE_OPENMP_SUPPORT)
3653 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003654#endif
3655 for (y=0; y < (long) blur_image->rows; y++)
3656 {
3657 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003658 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003659
3660 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003661 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00003662
3663 register long
3664 x;
3665
3666 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003667 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003668
3669 if (status == MagickFalse)
3670 continue;
3671 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3672 exception);
3673 if (q == (PixelPacket *) NULL)
3674 {
3675 status=MagickFalse;
3676 continue;
3677 }
3678 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
3679 for (x=0; x < (long) blur_image->columns; x++)
3680 {
3681 MagickPixelPacket
3682 qixel;
3683
3684 MagickRealType
3685 normalize,
3686 radius;
3687
3688 PixelPacket
3689 pixel;
3690
3691 PointInfo
3692 center;
3693
3694 register long
3695 i;
3696
3697 unsigned long
3698 step;
3699
3700 center.x=(double) x-blur_center.x;
3701 center.y=(double) y-blur_center.y;
3702 radius=hypot((double) center.x,center.y);
3703 if (radius == 0)
3704 step=1;
3705 else
3706 {
3707 step=(unsigned long) (blur_radius/radius);
3708 if (step == 0)
3709 step=1;
3710 else
3711 if (step >= n)
3712 step=n-1;
3713 }
3714 normalize=0.0;
cristyddd82202009-11-03 20:14:50 +00003715 qixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00003716 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
3717 {
3718 for (i=0; i < (long) n; i+=step)
3719 {
3720 (void) GetOneCacheViewVirtualPixel(image_view,(long) (blur_center.x+
3721 center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),(long) (
3722 blur_center.y+center.x*sin_theta[i]+center.y*cos_theta[i]+0.5),
3723 &pixel,exception);
3724 qixel.red+=pixel.red;
3725 qixel.green+=pixel.green;
3726 qixel.blue+=pixel.blue;
3727 qixel.opacity+=pixel.opacity;
3728 if (image->colorspace == CMYKColorspace)
3729 {
3730 indexes=GetCacheViewVirtualIndexQueue(image_view);
3731 qixel.index+=(*indexes);
3732 }
3733 normalize+=1.0;
3734 }
3735 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
3736 normalize);
3737 if ((channel & RedChannel) != 0)
3738 q->red=RoundToQuantum(normalize*qixel.red);
3739 if ((channel & GreenChannel) != 0)
3740 q->green=RoundToQuantum(normalize*qixel.green);
3741 if ((channel & BlueChannel) != 0)
3742 q->blue=RoundToQuantum(normalize*qixel.blue);
3743 if ((channel & OpacityChannel) != 0)
3744 q->opacity=RoundToQuantum(normalize*qixel.opacity);
3745 if (((channel & IndexChannel) != 0) &&
3746 (image->colorspace == CMYKColorspace))
3747 blur_indexes[x]=(IndexPacket) RoundToQuantum(normalize*qixel.index);
3748 }
3749 else
3750 {
3751 MagickRealType
3752 alpha,
3753 gamma;
3754
3755 alpha=1.0;
3756 gamma=0.0;
3757 for (i=0; i < (long) n; i+=step)
3758 {
3759 (void) GetOneCacheViewVirtualPixel(image_view,(long) (blur_center.x+
3760 center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),(long) (
3761 blur_center.y+center.x*sin_theta[i]+center.y*cos_theta[i]+0.5),
3762 &pixel,exception);
3763 alpha=(MagickRealType) (QuantumScale*(QuantumRange-pixel.opacity));
3764 qixel.red+=alpha*pixel.red;
3765 qixel.green+=alpha*pixel.green;
3766 qixel.blue+=alpha*pixel.blue;
3767 qixel.opacity+=pixel.opacity;
3768 if (image->colorspace == CMYKColorspace)
3769 {
3770 indexes=GetCacheViewVirtualIndexQueue(image_view);
3771 qixel.index+=alpha*(*indexes);
3772 }
3773 gamma+=alpha;
3774 normalize+=1.0;
3775 }
3776 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3777 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
3778 normalize);
3779 if ((channel & RedChannel) != 0)
3780 q->red=RoundToQuantum(gamma*qixel.red);
3781 if ((channel & GreenChannel) != 0)
3782 q->green=RoundToQuantum(gamma*qixel.green);
3783 if ((channel & BlueChannel) != 0)
3784 q->blue=RoundToQuantum(gamma*qixel.blue);
3785 if ((channel & OpacityChannel) != 0)
3786 q->opacity=RoundToQuantum(normalize*qixel.opacity);
3787 if (((channel & IndexChannel) != 0) &&
3788 (image->colorspace == CMYKColorspace))
3789 blur_indexes[x]=(IndexPacket) RoundToQuantum(gamma*qixel.index);
3790 }
3791 q++;
3792 }
3793 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
3794 status=MagickFalse;
3795 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3796 {
3797 MagickBooleanType
3798 proceed;
3799
cristyb5d5f722009-11-04 03:03:49 +00003800#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003801 #pragma omp critical (MagickCore_RadialBlurImageChannel)
3802#endif
3803 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
3804 if (proceed == MagickFalse)
3805 status=MagickFalse;
3806 }
3807 }
3808 blur_view=DestroyCacheView(blur_view);
3809 image_view=DestroyCacheView(image_view);
3810 cos_theta=(MagickRealType *) RelinquishMagickMemory(cos_theta);
3811 sin_theta=(MagickRealType *) RelinquishMagickMemory(sin_theta);
3812 if (status == MagickFalse)
3813 blur_image=DestroyImage(blur_image);
3814 return(blur_image);
3815}
3816
3817/*
3818%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3819% %
3820% %
3821% %
3822% R e d u c e N o i s e I m a g e %
3823% %
3824% %
3825% %
3826%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3827%
3828% ReduceNoiseImage() smooths the contours of an image while still preserving
3829% edge information. The algorithm works by replacing each pixel with its
3830% neighbor closest in value. A neighbor is defined by radius. Use a radius
3831% of 0 and ReduceNoise() selects a suitable radius for you.
3832%
3833% The format of the ReduceNoiseImage method is:
3834%
3835% Image *ReduceNoiseImage(const Image *image,const double radius,
3836% ExceptionInfo *exception)
3837%
3838% A description of each parameter follows:
3839%
3840% o image: the image.
3841%
3842% o radius: the radius of the pixel neighborhood.
3843%
3844% o exception: return any errors or warnings in this structure.
3845%
3846*/
3847
3848static MagickPixelPacket GetNonpeakMedianPixelList(MedianPixelList *pixel_list)
3849{
3850 MagickPixelPacket
3851 pixel;
3852
3853 register long
3854 channel;
3855
3856 register MedianSkipList
3857 *list;
3858
3859 unsigned long
3860 center,
3861 color,
3862 count,
3863 previous,
3864 next;
3865
3866 unsigned short
3867 channels[5];
3868
3869 /*
3870 Finds the median value for each of the color.
3871 */
3872 center=pixel_list->center;
3873 for (channel=0; channel < 5; channel++)
3874 {
3875 list=pixel_list->lists+channel;
3876 color=65536UL;
3877 next=list->nodes[color].next[0];
3878 count=0;
3879 do
3880 {
3881 previous=color;
3882 color=next;
3883 next=list->nodes[color].next[0];
3884 count+=list->nodes[color].count;
3885 }
3886 while (count <= center);
3887 if ((previous == 65536UL) && (next != 65536UL))
3888 color=next;
3889 else
3890 if ((previous != 65536UL) && (next == 65536UL))
3891 color=previous;
3892 channels[channel]=(unsigned short) color;
3893 }
3894 GetMagickPixelPacket((const Image *) NULL,&pixel);
3895 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
3896 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
3897 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
3898 pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
3899 pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]);
3900 return(pixel);
3901}
3902
3903MagickExport Image *ReduceNoiseImage(const Image *image,const double radius,
3904 ExceptionInfo *exception)
3905{
3906#define ReduceNoiseImageTag "ReduceNoise/Image"
3907
3908 Image
3909 *noise_image;
3910
3911 long
3912 progress,
3913 y;
3914
3915 MagickBooleanType
3916 status;
3917
3918 MedianPixelList
3919 **pixel_list;
3920
3921 unsigned long
3922 width;
3923
3924 CacheView
3925 *image_view,
3926 *noise_view;
3927
3928 /*
3929 Initialize noise image attributes.
3930 */
3931 assert(image != (Image *) NULL);
3932 assert(image->signature == MagickSignature);
3933 if (image->debug != MagickFalse)
3934 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3935 assert(exception != (ExceptionInfo *) NULL);
3936 assert(exception->signature == MagickSignature);
3937 width=GetOptimalKernelWidth2D(radius,0.5);
3938 if ((image->columns < width) || (image->rows < width))
3939 ThrowImageException(OptionError,"ImageSmallerThanKernelRadius");
3940 noise_image=CloneImage(image,image->columns,image->rows,MagickTrue,
3941 exception);
3942 if (noise_image == (Image *) NULL)
3943 return((Image *) NULL);
3944 if (SetImageStorageClass(noise_image,DirectClass) == MagickFalse)
3945 {
3946 InheritException(exception,&noise_image->exception);
3947 noise_image=DestroyImage(noise_image);
3948 return((Image *) NULL);
3949 }
3950 pixel_list=AcquireMedianPixelListThreadSet(width);
3951 if (pixel_list == (MedianPixelList **) NULL)
3952 {
3953 noise_image=DestroyImage(noise_image);
3954 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3955 }
3956 /*
3957 Reduce noise image.
3958 */
3959 status=MagickTrue;
3960 progress=0;
3961 image_view=AcquireCacheView(image);
3962 noise_view=AcquireCacheView(noise_image);
cristyb5d5f722009-11-04 03:03:49 +00003963#if defined(MAGICKCORE_OPENMP_SUPPORT)
3964 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003965#endif
3966 for (y=0; y < (long) noise_image->rows; y++)
3967 {
3968 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003969 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003970
3971 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003972 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003973
3974 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003975 *restrict noise_indexes;
cristy3ed852e2009-09-05 21:47:34 +00003976
3977 register long
3978 id,
3979 x;
3980
3981 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003982 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003983
3984 if (status == MagickFalse)
3985 continue;
3986 p=GetCacheViewVirtualPixels(image_view,-((long) width/2L),y-(long) (width/
3987 2L),image->columns+width,width,exception);
3988 q=QueueCacheViewAuthenticPixels(noise_view,0,y,noise_image->columns,1,
3989 exception);
3990 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
3991 {
3992 status=MagickFalse;
3993 continue;
3994 }
3995 indexes=GetCacheViewVirtualIndexQueue(image_view);
3996 noise_indexes=GetCacheViewAuthenticIndexQueue(noise_view);
3997 id=GetOpenMPThreadId();
3998 for (x=0; x < (long) noise_image->columns; x++)
3999 {
4000 MagickPixelPacket
4001 pixel;
4002
4003 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004004 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +00004005
4006 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004007 *restrict s;
cristy3ed852e2009-09-05 21:47:34 +00004008
4009 register long
4010 u,
4011 v;
4012
4013 r=p;
4014 s=indexes+x;
4015 ResetMedianPixelList(pixel_list[id]);
4016 for (v=0; v < (long) width; v++)
4017 {
4018 for (u=0; u < (long) width; u++)
4019 InsertMedianPixelList(image,r+u,s+u,pixel_list[id]);
4020 r+=image->columns+width;
4021 s+=image->columns+width;
4022 }
4023 pixel=GetNonpeakMedianPixelList(pixel_list[id]);
4024 SetPixelPacket(noise_image,&pixel,q,noise_indexes+x);
4025 p++;
4026 q++;
4027 }
4028 if (SyncCacheViewAuthenticPixels(noise_view,exception) == MagickFalse)
4029 status=MagickFalse;
4030 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4031 {
4032 MagickBooleanType
4033 proceed;
4034
cristyb5d5f722009-11-04 03:03:49 +00004035#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004036 #pragma omp critical (MagickCore_ReduceNoiseImage)
4037#endif
4038 proceed=SetImageProgress(image,ReduceNoiseImageTag,progress++,
4039 image->rows);
4040 if (proceed == MagickFalse)
4041 status=MagickFalse;
4042 }
4043 }
4044 noise_view=DestroyCacheView(noise_view);
4045 image_view=DestroyCacheView(image_view);
4046 pixel_list=DestroyMedianPixelListThreadSet(pixel_list);
4047 return(noise_image);
4048}
4049
4050/*
4051%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4052% %
4053% %
4054% %
4055% S e l e c t i v e B l u r I m a g e %
4056% %
4057% %
4058% %
4059%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4060%
4061% SelectiveBlurImage() selectively blur pixels within a contrast threshold.
4062% It is similar to the unsharpen mask that sharpens everything with contrast
4063% above a certain threshold.
4064%
4065% The format of the SelectiveBlurImage method is:
4066%
4067% Image *SelectiveBlurImage(const Image *image,const double radius,
4068% const double sigma,const double threshold,ExceptionInfo *exception)
4069% Image *SelectiveBlurImageChannel(const Image *image,
4070% const ChannelType channel,const double radius,const double sigma,
4071% const double threshold,ExceptionInfo *exception)
4072%
4073% A description of each parameter follows:
4074%
4075% o image: the image.
4076%
4077% o channel: the channel type.
4078%
4079% o radius: the radius of the Gaussian, in pixels, not counting the center
4080% pixel.
4081%
4082% o sigma: the standard deviation of the Gaussian, in pixels.
4083%
4084% o threshold: only pixels within this contrast threshold are included
4085% in the blur operation.
4086%
4087% o exception: return any errors or warnings in this structure.
4088%
4089*/
4090
4091static inline MagickBooleanType SelectiveContrast(const PixelPacket *p,
4092 const PixelPacket *q,const double threshold)
4093{
4094 if (fabs(PixelIntensity(p)-PixelIntensity(q)) < threshold)
4095 return(MagickTrue);
4096 return(MagickFalse);
4097}
4098
4099MagickExport Image *SelectiveBlurImage(const Image *image,const double radius,
4100 const double sigma,const double threshold,ExceptionInfo *exception)
4101{
4102 Image
4103 *blur_image;
4104
4105 blur_image=SelectiveBlurImageChannel(image,DefaultChannels,radius,sigma,
4106 threshold,exception);
4107 return(blur_image);
4108}
4109
4110MagickExport Image *SelectiveBlurImageChannel(const Image *image,
4111 const ChannelType channel,const double radius,const double sigma,
4112 const double threshold,ExceptionInfo *exception)
4113{
4114#define SelectiveBlurImageTag "SelectiveBlur/Image"
4115
4116 double
4117 alpha,
4118 *kernel;
4119
4120 Image
4121 *blur_image;
4122
4123 long
4124 progress,
4125 v,
4126 y;
4127
4128 MagickBooleanType
4129 status;
4130
4131 MagickPixelPacket
cristy3ed852e2009-09-05 21:47:34 +00004132 bias;
4133
4134 register long
4135 i,
4136 u;
4137
4138 unsigned long
4139 width;
4140
4141 CacheView
4142 *blur_view,
4143 *image_view;
4144
4145 /*
4146 Initialize blur image attributes.
4147 */
4148 assert(image != (Image *) NULL);
4149 assert(image->signature == MagickSignature);
4150 if (image->debug != MagickFalse)
4151 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4152 assert(exception != (ExceptionInfo *) NULL);
4153 assert(exception->signature == MagickSignature);
4154 width=GetOptimalKernelWidth1D(radius,sigma);
4155 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
4156 if (kernel == (double *) NULL)
4157 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
4158 i=0;
4159 for (v=(-((long) width/2)); v <= (long) (width/2); v++)
4160 {
4161 for (u=(-((long) width/2)); u <= (long) (width/2); u++)
4162 {
4163 alpha=exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma));
4164 kernel[i]=(double) (alpha/(2.0*MagickPI*MagickSigma*MagickSigma));
4165 i++;
4166 }
4167 }
4168 if (image->debug != MagickFalse)
4169 {
4170 char
4171 format[MaxTextExtent],
4172 *message;
4173
4174 long
4175 u,
4176 v;
4177
4178 register const double
4179 *k;
4180
4181 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
4182 " SelectiveBlurImage with %ldx%ld kernel:",width,width);
4183 message=AcquireString("");
4184 k=kernel;
4185 for (v=0; v < (long) width; v++)
4186 {
4187 *message='\0';
4188 (void) FormatMagickString(format,MaxTextExtent,"%ld: ",v);
4189 (void) ConcatenateString(&message,format);
4190 for (u=0; u < (long) width; u++)
4191 {
4192 (void) FormatMagickString(format,MaxTextExtent,"%+f ",*k++);
4193 (void) ConcatenateString(&message,format);
4194 }
4195 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
4196 }
4197 message=DestroyString(message);
4198 }
4199 blur_image=CloneImage(image,0,0,MagickTrue,exception);
4200 if (blur_image == (Image *) NULL)
4201 return((Image *) NULL);
4202 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
4203 {
4204 InheritException(exception,&blur_image->exception);
4205 blur_image=DestroyImage(blur_image);
4206 return((Image *) NULL);
4207 }
4208 /*
4209 Threshold blur image.
4210 */
4211 status=MagickTrue;
4212 progress=0;
cristyddd82202009-11-03 20:14:50 +00004213 GetMagickPixelPacket(image,&bias);
4214 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00004215 image_view=AcquireCacheView(image);
4216 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00004217#if defined(MAGICKCORE_OPENMP_SUPPORT)
4218 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004219#endif
4220 for (y=0; y < (long) image->rows; y++)
4221 {
4222 MagickBooleanType
4223 sync;
4224
4225 MagickRealType
4226 gamma;
4227
4228 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004229 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00004230
4231 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004232 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00004233
4234 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004235 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00004236
4237 register long
4238 x;
4239
4240 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004241 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004242
4243 if (status == MagickFalse)
4244 continue;
4245 p=GetCacheViewVirtualPixels(image_view,-((long) width/2L),y-(long) (width/
4246 2L),image->columns+width,width,exception);
4247 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
4248 exception);
4249 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
4250 {
4251 status=MagickFalse;
4252 continue;
4253 }
4254 indexes=GetCacheViewVirtualIndexQueue(image_view);
4255 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
4256 for (x=0; x < (long) image->columns; x++)
4257 {
4258 long
4259 j,
4260 v;
4261
4262 MagickPixelPacket
4263 pixel;
4264
4265 register const double
cristyc47d1f82009-11-26 01:44:43 +00004266 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00004267
4268 register long
4269 u;
4270
cristyddd82202009-11-03 20:14:50 +00004271 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00004272 k=kernel;
4273 gamma=0.0;
4274 j=0;
4275 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
4276 {
4277 for (v=0; v < (long) width; v++)
4278 {
4279 for (u=0; u < (long) width; u++)
4280 {
4281 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4282 {
4283 pixel.red+=(*k)*(p+u+j)->red;
4284 pixel.green+=(*k)*(p+u+j)->green;
4285 pixel.blue+=(*k)*(p+u+j)->blue;
4286 gamma+=(*k);
4287 k++;
4288 }
4289 }
4290 j+=image->columns+width;
4291 }
4292 if (gamma != 0.0)
4293 {
4294 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
4295 if ((channel & RedChannel) != 0)
cristyddd82202009-11-03 20:14:50 +00004296 q->red=RoundToQuantum(gamma*pixel.red);
cristy3ed852e2009-09-05 21:47:34 +00004297 if ((channel & GreenChannel) != 0)
cristyddd82202009-11-03 20:14:50 +00004298 q->green=RoundToQuantum(gamma*pixel.green);
cristy3ed852e2009-09-05 21:47:34 +00004299 if ((channel & BlueChannel) != 0)
cristyddd82202009-11-03 20:14:50 +00004300 q->blue=RoundToQuantum(gamma*pixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00004301 }
4302 if ((channel & OpacityChannel) != 0)
4303 {
4304 gamma=0.0;
4305 j=0;
4306 for (v=0; v < (long) width; v++)
4307 {
4308 for (u=0; u < (long) width; u++)
4309 {
4310 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4311 {
4312 pixel.opacity+=(*k)*(p+u+j)->opacity;
4313 gamma+=(*k);
4314 k++;
4315 }
4316 }
4317 j+=image->columns+width;
4318 }
4319 if (gamma != 0.0)
4320 {
4321 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4322 gamma);
cristyddd82202009-11-03 20:14:50 +00004323 q->opacity=RoundToQuantum(gamma*pixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00004324 }
4325 }
4326 if (((channel & IndexChannel) != 0) &&
4327 (image->colorspace == CMYKColorspace))
4328 {
4329 gamma=0.0;
4330 j=0;
4331 for (v=0; v < (long) width; v++)
4332 {
4333 for (u=0; u < (long) width; u++)
4334 {
4335 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4336 {
4337 pixel.index+=(*k)*indexes[x+u+j];
4338 gamma+=(*k);
4339 k++;
4340 }
4341 }
4342 j+=image->columns+width;
4343 }
4344 if (gamma != 0.0)
4345 {
4346 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4347 gamma);
cristyddd82202009-11-03 20:14:50 +00004348 blur_indexes[x]=RoundToQuantum(gamma*pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00004349 }
4350 }
4351 }
4352 else
4353 {
4354 MagickRealType
4355 alpha;
4356
4357 for (v=0; v < (long) width; v++)
4358 {
4359 for (u=0; u < (long) width; u++)
4360 {
4361 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4362 {
4363 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
4364 (p+u+j)->opacity));
4365 pixel.red+=(*k)*alpha*(p+u+j)->red;
4366 pixel.green+=(*k)*alpha*(p+u+j)->green;
4367 pixel.blue+=(*k)*alpha*(p+u+j)->blue;
4368 pixel.opacity+=(*k)*(p+u+j)->opacity;
4369 gamma+=(*k)*alpha;
4370 k++;
4371 }
4372 }
4373 j+=image->columns+width;
4374 }
4375 if (gamma != 0.0)
4376 {
4377 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
4378 if ((channel & RedChannel) != 0)
cristyddd82202009-11-03 20:14:50 +00004379 q->red=RoundToQuantum(gamma*pixel.red);
cristy3ed852e2009-09-05 21:47:34 +00004380 if ((channel & GreenChannel) != 0)
cristyddd82202009-11-03 20:14:50 +00004381 q->green=RoundToQuantum(gamma*pixel.green);
cristy3ed852e2009-09-05 21:47:34 +00004382 if ((channel & BlueChannel) != 0)
cristyddd82202009-11-03 20:14:50 +00004383 q->blue=RoundToQuantum(gamma*pixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00004384 }
4385 if ((channel & OpacityChannel) != 0)
4386 {
4387 gamma=0.0;
4388 j=0;
4389 for (v=0; v < (long) width; v++)
4390 {
4391 for (u=0; u < (long) width; u++)
4392 {
4393 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4394 {
4395 pixel.opacity+=(*k)*(p+u+j)->opacity;
4396 gamma+=(*k);
4397 k++;
4398 }
4399 }
4400 j+=image->columns+width;
4401 }
4402 if (gamma != 0.0)
4403 {
4404 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4405 gamma);
cristyddd82202009-11-03 20:14:50 +00004406 q->opacity=RoundToQuantum(pixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00004407 }
4408 }
4409 if (((channel & IndexChannel) != 0) &&
4410 (image->colorspace == CMYKColorspace))
4411 {
4412 gamma=0.0;
4413 j=0;
4414 for (v=0; v < (long) width; v++)
4415 {
4416 for (u=0; u < (long) width; u++)
4417 {
4418 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4419 {
4420 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
4421 (p+u+j)->opacity));
4422 pixel.index+=(*k)*alpha*indexes[x+u+j];
4423 gamma+=(*k);
4424 k++;
4425 }
4426 }
4427 j+=image->columns+width;
4428 }
4429 if (gamma != 0.0)
4430 {
4431 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4432 gamma);
cristyddd82202009-11-03 20:14:50 +00004433 blur_indexes[x]=RoundToQuantum(gamma*pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00004434 }
4435 }
4436 }
4437 p++;
4438 q++;
4439 }
4440 sync=SyncCacheViewAuthenticPixels(blur_view,exception);
4441 if (sync == MagickFalse)
4442 status=MagickFalse;
4443 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4444 {
4445 MagickBooleanType
4446 proceed;
4447
cristyb5d5f722009-11-04 03:03:49 +00004448#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004449 #pragma omp critical (MagickCore_SelectiveBlurImageChannel)
4450#endif
4451 proceed=SetImageProgress(image,SelectiveBlurImageTag,progress++,
4452 image->rows);
4453 if (proceed == MagickFalse)
4454 status=MagickFalse;
4455 }
4456 }
4457 blur_image->type=image->type;
4458 blur_view=DestroyCacheView(blur_view);
4459 image_view=DestroyCacheView(image_view);
4460 kernel=(double *) RelinquishMagickMemory(kernel);
4461 if (status == MagickFalse)
4462 blur_image=DestroyImage(blur_image);
4463 return(blur_image);
4464}
4465
4466/*
4467%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4468% %
4469% %
4470% %
4471% S h a d e I m a g e %
4472% %
4473% %
4474% %
4475%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4476%
4477% ShadeImage() shines a distant light on an image to create a
4478% three-dimensional effect. You control the positioning of the light with
4479% azimuth and elevation; azimuth is measured in degrees off the x axis
4480% and elevation is measured in pixels above the Z axis.
4481%
4482% The format of the ShadeImage method is:
4483%
4484% Image *ShadeImage(const Image *image,const MagickBooleanType gray,
4485% const double azimuth,const double elevation,ExceptionInfo *exception)
4486%
4487% A description of each parameter follows:
4488%
4489% o image: the image.
4490%
4491% o gray: A value other than zero shades the intensity of each pixel.
4492%
4493% o azimuth, elevation: Define the light source direction.
4494%
4495% o exception: return any errors or warnings in this structure.
4496%
4497*/
4498MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray,
4499 const double azimuth,const double elevation,ExceptionInfo *exception)
4500{
4501#define ShadeImageTag "Shade/Image"
4502
4503 Image
4504 *shade_image;
4505
4506 long
4507 progress,
4508 y;
4509
4510 MagickBooleanType
4511 status;
4512
4513 PrimaryInfo
4514 light;
4515
4516 CacheView
4517 *image_view,
4518 *shade_view;
4519
4520 /*
4521 Initialize shaded image attributes.
4522 */
4523 assert(image != (const Image *) NULL);
4524 assert(image->signature == MagickSignature);
4525 if (image->debug != MagickFalse)
4526 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4527 assert(exception != (ExceptionInfo *) NULL);
4528 assert(exception->signature == MagickSignature);
4529 shade_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
4530 if (shade_image == (Image *) NULL)
4531 return((Image *) NULL);
4532 if (SetImageStorageClass(shade_image,DirectClass) == MagickFalse)
4533 {
4534 InheritException(exception,&shade_image->exception);
4535 shade_image=DestroyImage(shade_image);
4536 return((Image *) NULL);
4537 }
4538 /*
4539 Compute the light vector.
4540 */
4541 light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))*
4542 cos(DegreesToRadians(elevation));
4543 light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))*
4544 cos(DegreesToRadians(elevation));
4545 light.z=(double) QuantumRange*sin(DegreesToRadians(elevation));
4546 /*
4547 Shade image.
4548 */
4549 status=MagickTrue;
4550 progress=0;
4551 image_view=AcquireCacheView(image);
4552 shade_view=AcquireCacheView(shade_image);
cristyb5d5f722009-11-04 03:03:49 +00004553#if defined(MAGICKCORE_OPENMP_SUPPORT)
4554 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004555#endif
4556 for (y=0; y < (long) image->rows; y++)
4557 {
4558 MagickRealType
4559 distance,
4560 normal_distance,
4561 shade;
4562
4563 PrimaryInfo
4564 normal;
4565
4566 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004567 *restrict p,
4568 *restrict s0,
4569 *restrict s1,
4570 *restrict s2;
cristy3ed852e2009-09-05 21:47:34 +00004571
4572 register long
4573 x;
4574
4575 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004576 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004577
4578 if (status == MagickFalse)
4579 continue;
4580 p=GetCacheViewVirtualPixels(image_view,-1,y-1,image->columns+2,3,exception);
4581 q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1,
4582 exception);
4583 if ((p == (PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
4584 {
4585 status=MagickFalse;
4586 continue;
4587 }
4588 /*
4589 Shade this row of pixels.
4590 */
4591 normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */
4592 s0=p+1;
4593 s1=s0+image->columns+2;
4594 s2=s1+image->columns+2;
4595 for (x=0; x < (long) image->columns; x++)
4596 {
4597 /*
4598 Determine the surface normal and compute shading.
4599 */
4600 normal.x=(double) (PixelIntensity(s0-1)+PixelIntensity(s1-1)+
4601 PixelIntensity(s2-1)-PixelIntensity(s0+1)-PixelIntensity(s1+1)-
4602 PixelIntensity(s2+1));
4603 normal.y=(double) (PixelIntensity(s2-1)+PixelIntensity(s2)+
4604 PixelIntensity(s2+1)-PixelIntensity(s0-1)-PixelIntensity(s0)-
4605 PixelIntensity(s0+1));
4606 if ((normal.x == 0.0) && (normal.y == 0.0))
4607 shade=light.z;
4608 else
4609 {
4610 shade=0.0;
4611 distance=normal.x*light.x+normal.y*light.y+normal.z*light.z;
4612 if (distance > MagickEpsilon)
4613 {
4614 normal_distance=
4615 normal.x*normal.x+normal.y*normal.y+normal.z*normal.z;
4616 if (normal_distance > (MagickEpsilon*MagickEpsilon))
4617 shade=distance/sqrt((double) normal_distance);
4618 }
4619 }
4620 if (gray != MagickFalse)
4621 {
4622 q->red=(Quantum) shade;
4623 q->green=(Quantum) shade;
4624 q->blue=(Quantum) shade;
4625 }
4626 else
4627 {
4628 q->red=RoundToQuantum(QuantumScale*shade*s1->red);
4629 q->green=RoundToQuantum(QuantumScale*shade*s1->green);
4630 q->blue=RoundToQuantum(QuantumScale*shade*s1->blue);
4631 }
4632 q->opacity=s1->opacity;
4633 s0++;
4634 s1++;
4635 s2++;
4636 q++;
4637 }
4638 if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
4639 status=MagickFalse;
4640 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4641 {
4642 MagickBooleanType
4643 proceed;
4644
cristyb5d5f722009-11-04 03:03:49 +00004645#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004646 #pragma omp critical (MagickCore_ShadeImage)
4647#endif
4648 proceed=SetImageProgress(image,ShadeImageTag,progress++,image->rows);
4649 if (proceed == MagickFalse)
4650 status=MagickFalse;
4651 }
4652 }
4653 shade_view=DestroyCacheView(shade_view);
4654 image_view=DestroyCacheView(image_view);
4655 if (status == MagickFalse)
4656 shade_image=DestroyImage(shade_image);
4657 return(shade_image);
4658}
4659
4660/*
4661%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4662% %
4663% %
4664% %
4665% S h a r p e n I m a g e %
4666% %
4667% %
4668% %
4669%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4670%
4671% SharpenImage() sharpens the image. We convolve the image with a Gaussian
4672% operator of the given radius and standard deviation (sigma). For
4673% reasonable results, radius should be larger than sigma. Use a radius of 0
4674% and SharpenImage() selects a suitable radius for you.
4675%
4676% Using a separable kernel would be faster, but the negative weights cancel
4677% out on the corners of the kernel producing often undesirable ringing in the
4678% filtered result; this can be avoided by using a 2D gaussian shaped image
4679% sharpening kernel instead.
4680%
4681% The format of the SharpenImage method is:
4682%
4683% Image *SharpenImage(const Image *image,const double radius,
4684% const double sigma,ExceptionInfo *exception)
4685% Image *SharpenImageChannel(const Image *image,const ChannelType channel,
4686% const double radius,const double sigma,ExceptionInfo *exception)
4687%
4688% A description of each parameter follows:
4689%
4690% o image: the image.
4691%
4692% o channel: the channel type.
4693%
4694% o radius: the radius of the Gaussian, in pixels, not counting the center
4695% pixel.
4696%
4697% o sigma: the standard deviation of the Laplacian, in pixels.
4698%
4699% o exception: return any errors or warnings in this structure.
4700%
4701*/
4702
4703MagickExport Image *SharpenImage(const Image *image,const double radius,
4704 const double sigma,ExceptionInfo *exception)
4705{
4706 Image
4707 *sharp_image;
4708
4709 sharp_image=SharpenImageChannel(image,DefaultChannels,radius,sigma,exception);
4710 return(sharp_image);
4711}
4712
4713MagickExport Image *SharpenImageChannel(const Image *image,
4714 const ChannelType channel,const double radius,const double sigma,
4715 ExceptionInfo *exception)
4716{
4717 double
4718 *kernel;
4719
4720 Image
4721 *sharp_image;
4722
4723 MagickRealType
4724 alpha,
4725 normalize;
4726
4727 register long
4728 i,
4729 u,
4730 v;
4731
4732 unsigned long
4733 width;
4734
4735 assert(image != (const Image *) NULL);
4736 assert(image->signature == MagickSignature);
4737 if (image->debug != MagickFalse)
4738 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4739 assert(exception != (ExceptionInfo *) NULL);
4740 assert(exception->signature == MagickSignature);
4741 width=GetOptimalKernelWidth2D(radius,sigma);
4742 kernel=(double *) AcquireQuantumMemory((size_t) width*width,sizeof(*kernel));
4743 if (kernel == (double *) NULL)
4744 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
4745 i=0;
4746 normalize=0.0;
4747 for (v=(-((long) width/2)); v <= (long) (width/2); v++)
4748 {
4749 for (u=(-((long) width/2)); u <= (long) (width/2); u++)
4750 {
4751 alpha=exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma));
4752 kernel[i]=(double) (-alpha/(2.0*MagickPI*MagickSigma*MagickSigma));
4753 normalize+=kernel[i];
4754 i++;
4755 }
4756 }
4757 kernel[i/2]=(double) ((-2.0)*normalize);
4758 sharp_image=ConvolveImageChannel(image,channel,width,kernel,exception);
4759 kernel=(double *) RelinquishMagickMemory(kernel);
4760 return(sharp_image);
4761}
4762
4763/*
4764%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4765% %
4766% %
4767% %
4768% S p r e a d I m a g e %
4769% %
4770% %
4771% %
4772%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4773%
4774% SpreadImage() is a special effects method that randomly displaces each
4775% pixel in a block defined by the radius parameter.
4776%
4777% The format of the SpreadImage method is:
4778%
4779% Image *SpreadImage(const Image *image,const double radius,
4780% ExceptionInfo *exception)
4781%
4782% A description of each parameter follows:
4783%
4784% o image: the image.
4785%
4786% o radius: Choose a random pixel in a neighborhood of this extent.
4787%
4788% o exception: return any errors or warnings in this structure.
4789%
4790*/
4791MagickExport Image *SpreadImage(const Image *image,const double radius,
4792 ExceptionInfo *exception)
4793{
4794#define SpreadImageTag "Spread/Image"
4795
4796 Image
4797 *spread_image;
4798
4799 long
4800 progress,
4801 y;
4802
4803 MagickBooleanType
4804 status;
4805
4806 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00004807 bias;
cristy3ed852e2009-09-05 21:47:34 +00004808
4809 RandomInfo
4810 **random_info;
4811
4812 ResampleFilter
4813 **resample_filter;
4814
4815 unsigned long
4816 width;
4817
4818 CacheView
4819 *image_view;
4820
4821 /*
4822 Initialize spread image attributes.
4823 */
4824 assert(image != (Image *) NULL);
4825 assert(image->signature == MagickSignature);
4826 if (image->debug != MagickFalse)
4827 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4828 assert(exception != (ExceptionInfo *) NULL);
4829 assert(exception->signature == MagickSignature);
4830 spread_image=CloneImage(image,image->columns,image->rows,MagickTrue,
4831 exception);
4832 if (spread_image == (Image *) NULL)
4833 return((Image *) NULL);
4834 if (SetImageStorageClass(spread_image,DirectClass) == MagickFalse)
4835 {
4836 InheritException(exception,&spread_image->exception);
4837 spread_image=DestroyImage(spread_image);
4838 return((Image *) NULL);
4839 }
4840 /*
4841 Spread image.
4842 */
4843 status=MagickTrue;
4844 progress=0;
cristyddd82202009-11-03 20:14:50 +00004845 GetMagickPixelPacket(spread_image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00004846 width=GetOptimalKernelWidth1D(radius,0.5);
4847 resample_filter=AcquireResampleFilterThreadSet(image,MagickTrue,exception);
4848 random_info=AcquireRandomInfoThreadSet();
4849 image_view=AcquireCacheView(spread_image);
cristyb5d5f722009-11-04 03:03:49 +00004850#if defined(MAGICKCORE_OPENMP_SUPPORT)
4851 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004852#endif
4853 for (y=0; y < (long) spread_image->rows; y++)
4854 {
4855 MagickPixelPacket
4856 pixel;
4857
4858 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004859 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00004860
4861 register long
4862 id,
4863 x;
4864
4865 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004866 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004867
4868 if (status == MagickFalse)
4869 continue;
4870 q=QueueCacheViewAuthenticPixels(image_view,0,y,spread_image->columns,1,
4871 exception);
4872 if (q == (PixelPacket *) NULL)
4873 {
4874 status=MagickFalse;
4875 continue;
4876 }
4877 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristyddd82202009-11-03 20:14:50 +00004878 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00004879 id=GetOpenMPThreadId();
4880 for (x=0; x < (long) spread_image->columns; x++)
4881 {
4882 (void) ResamplePixelColor(resample_filter[id],(double) x+width*
4883 (GetPseudoRandomValue(random_info[id])-0.5),(double) y+width*
4884 (GetPseudoRandomValue(random_info[id])-0.5),&pixel);
4885 SetPixelPacket(spread_image,&pixel,q,indexes+x);
4886 q++;
4887 }
4888 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
4889 status=MagickFalse;
4890 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4891 {
4892 MagickBooleanType
4893 proceed;
4894
cristyb5d5f722009-11-04 03:03:49 +00004895#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004896 #pragma omp critical (MagickCore_SpreadImage)
4897#endif
4898 proceed=SetImageProgress(image,SpreadImageTag,progress++,image->rows);
4899 if (proceed == MagickFalse)
4900 status=MagickFalse;
4901 }
4902 }
4903 image_view=DestroyCacheView(image_view);
4904 random_info=DestroyRandomInfoThreadSet(random_info);
4905 resample_filter=DestroyResampleFilterThreadSet(resample_filter);
4906 return(spread_image);
4907}
4908
4909/*
4910%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4911% %
4912% %
4913% %
4914% U n s h a r p M a s k I m a g e %
4915% %
4916% %
4917% %
4918%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4919%
4920% UnsharpMaskImage() sharpens one or more image channels. We convolve the
4921% image with a Gaussian operator of the given radius and standard deviation
4922% (sigma). For reasonable results, radius should be larger than sigma. Use a
4923% radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
4924%
4925% The format of the UnsharpMaskImage method is:
4926%
4927% Image *UnsharpMaskImage(const Image *image,const double radius,
4928% const double sigma,const double amount,const double threshold,
4929% ExceptionInfo *exception)
4930% Image *UnsharpMaskImageChannel(const Image *image,
4931% const ChannelType channel,const double radius,const double sigma,
4932% const double amount,const double threshold,ExceptionInfo *exception)
4933%
4934% A description of each parameter follows:
4935%
4936% o image: the image.
4937%
4938% o channel: the channel type.
4939%
4940% o radius: the radius of the Gaussian, in pixels, not counting the center
4941% pixel.
4942%
4943% o sigma: the standard deviation of the Gaussian, in pixels.
4944%
4945% o amount: the percentage of the difference between the original and the
4946% blur image that is added back into the original.
4947%
4948% o threshold: the threshold in pixels needed to apply the diffence amount.
4949%
4950% o exception: return any errors or warnings in this structure.
4951%
4952*/
4953
4954MagickExport Image *UnsharpMaskImage(const Image *image,const double radius,
4955 const double sigma,const double amount,const double threshold,
4956 ExceptionInfo *exception)
4957{
4958 Image
4959 *sharp_image;
4960
4961 sharp_image=UnsharpMaskImageChannel(image,DefaultChannels,radius,sigma,amount,
4962 threshold,exception);
4963 return(sharp_image);
4964}
4965
4966MagickExport Image *UnsharpMaskImageChannel(const Image *image,
4967 const ChannelType channel,const double radius,const double sigma,
4968 const double amount,const double threshold,ExceptionInfo *exception)
4969{
4970#define SharpenImageTag "Sharpen/Image"
4971
4972 Image
4973 *unsharp_image;
4974
4975 long
4976 progress,
4977 y;
4978
4979 MagickBooleanType
4980 status;
4981
4982 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00004983 bias;
cristy3ed852e2009-09-05 21:47:34 +00004984
4985 MagickRealType
4986 quantum_threshold;
4987
4988 CacheView
4989 *image_view,
4990 *unsharp_view;
4991
4992 assert(image != (const Image *) NULL);
4993 assert(image->signature == MagickSignature);
4994 if (image->debug != MagickFalse)
4995 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4996 assert(exception != (ExceptionInfo *) NULL);
4997 unsharp_image=BlurImageChannel(image,channel,radius,sigma,exception);
4998 if (unsharp_image == (Image *) NULL)
4999 return((Image *) NULL);
5000 quantum_threshold=(MagickRealType) QuantumRange*threshold;
5001 /*
5002 Unsharp-mask image.
5003 */
5004 status=MagickTrue;
5005 progress=0;
cristyddd82202009-11-03 20:14:50 +00005006 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00005007 image_view=AcquireCacheView(image);
5008 unsharp_view=AcquireCacheView(unsharp_image);
cristyb5d5f722009-11-04 03:03:49 +00005009#if defined(MAGICKCORE_OPENMP_SUPPORT)
5010 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00005011#endif
5012 for (y=0; y < (long) image->rows; y++)
5013 {
5014 MagickPixelPacket
5015 pixel;
5016
5017 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00005018 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00005019
5020 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00005021 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00005022
5023 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00005024 *restrict unsharp_indexes;
cristy3ed852e2009-09-05 21:47:34 +00005025
5026 register long
5027 x;
5028
5029 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00005030 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00005031
5032 if (status == MagickFalse)
5033 continue;
5034 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
5035 q=GetCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
5036 exception);
5037 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
5038 {
5039 status=MagickFalse;
5040 continue;
5041 }
5042 indexes=GetCacheViewVirtualIndexQueue(image_view);
5043 unsharp_indexes=GetCacheViewAuthenticIndexQueue(unsharp_view);
cristyddd82202009-11-03 20:14:50 +00005044 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00005045 for (x=0; x < (long) image->columns; x++)
5046 {
5047 if ((channel & RedChannel) != 0)
5048 {
5049 pixel.red=p->red-(MagickRealType) q->red;
5050 if (fabs(2.0*pixel.red) < quantum_threshold)
5051 pixel.red=(MagickRealType) p->red;
5052 else
5053 pixel.red=(MagickRealType) p->red+(pixel.red*amount);
5054 q->red=RoundToQuantum(pixel.red);
5055 }
5056 if ((channel & GreenChannel) != 0)
5057 {
5058 pixel.green=p->green-(MagickRealType) q->green;
5059 if (fabs(2.0*pixel.green) < quantum_threshold)
5060 pixel.green=(MagickRealType) p->green;
5061 else
5062 pixel.green=(MagickRealType) p->green+(pixel.green*amount);
5063 q->green=RoundToQuantum(pixel.green);
5064 }
5065 if ((channel & BlueChannel) != 0)
5066 {
5067 pixel.blue=p->blue-(MagickRealType) q->blue;
5068 if (fabs(2.0*pixel.blue) < quantum_threshold)
5069 pixel.blue=(MagickRealType) p->blue;
5070 else
5071 pixel.blue=(MagickRealType) p->blue+(pixel.blue*amount);
5072 q->blue=RoundToQuantum(pixel.blue);
5073 }
5074 if ((channel & OpacityChannel) != 0)
5075 {
5076 pixel.opacity=p->opacity-(MagickRealType) q->opacity;
5077 if (fabs(2.0*pixel.opacity) < quantum_threshold)
5078 pixel.opacity=(MagickRealType) p->opacity;
5079 else
5080 pixel.opacity=p->opacity+(pixel.opacity*amount);
5081 q->opacity=RoundToQuantum(pixel.opacity);
5082 }
5083 if (((channel & IndexChannel) != 0) &&
5084 (image->colorspace == CMYKColorspace))
5085 {
5086 pixel.index=unsharp_indexes[x]-(MagickRealType) indexes[x];
5087 if (fabs(2.0*pixel.index) < quantum_threshold)
5088 pixel.index=(MagickRealType) unsharp_indexes[x];
5089 else
5090 pixel.index=(MagickRealType) unsharp_indexes[x]+(pixel.index*
5091 amount);
5092 unsharp_indexes[x]=RoundToQuantum(pixel.index);
5093 }
5094 p++;
5095 q++;
5096 }
5097 if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
5098 status=MagickFalse;
5099 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5100 {
5101 MagickBooleanType
5102 proceed;
5103
cristyb5d5f722009-11-04 03:03:49 +00005104#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00005105 #pragma omp critical (MagickCore_UnsharpMaskImageChannel)
5106#endif
5107 proceed=SetImageProgress(image,SharpenImageTag,progress++,image->rows);
5108 if (proceed == MagickFalse)
5109 status=MagickFalse;
5110 }
5111 }
5112 unsharp_image->type=image->type;
5113 unsharp_view=DestroyCacheView(unsharp_view);
5114 image_view=DestroyCacheView(image_view);
5115 if (status == MagickFalse)
5116 unsharp_image=DestroyImage(unsharp_image);
5117 return(unsharp_image);
5118}