MagickCore 7.1.2-33
Convert, Edit, Or Compose Bitmap Images
Loading...
Searching...
No Matches
effect.c
1/*
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% Cristy %
17% October 1996 %
18% %
19% %
20% Copyright @ 1999 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% https://imagemagick.org/license/ %
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 "MagickCore/studio.h"
44#include "MagickCore/accelerate-private.h"
45#include "MagickCore/blob.h"
46#include "MagickCore/cache-view.h"
47#include "MagickCore/color.h"
48#include "MagickCore/color-private.h"
49#include "MagickCore/colorspace.h"
50#include "MagickCore/constitute.h"
51#include "MagickCore/decorate.h"
52#include "MagickCore/distort.h"
53#include "MagickCore/draw.h"
54#include "MagickCore/enhance.h"
55#include "MagickCore/exception.h"
56#include "MagickCore/exception-private.h"
57#include "MagickCore/effect.h"
58#include "MagickCore/fx.h"
59#include "MagickCore/gem.h"
60#include "MagickCore/gem-private.h"
61#include "MagickCore/geometry.h"
62#include "MagickCore/image-private.h"
63#include "MagickCore/list.h"
64#include "MagickCore/log.h"
65#include "MagickCore/matrix.h"
66#include "MagickCore/memory_.h"
67#include "MagickCore/memory-private.h"
68#include "MagickCore/monitor.h"
69#include "MagickCore/monitor-private.h"
70#include "MagickCore/montage.h"
71#include "MagickCore/morphology.h"
72#include "MagickCore/morphology-private.h"
73#include "MagickCore/paint.h"
74#include "MagickCore/pixel-accessor.h"
75#include "MagickCore/property.h"
76#include "MagickCore/quantize.h"
77#include "MagickCore/quantum.h"
78#include "MagickCore/quantum-private.h"
79#include "MagickCore/random_.h"
80#include "MagickCore/random-private.h"
81#include "MagickCore/resample.h"
82#include "MagickCore/resample-private.h"
83#include "MagickCore/resize.h"
84#include "MagickCore/resource_.h"
85#include "MagickCore/segment.h"
86#include "MagickCore/shear.h"
87#include "MagickCore/signature-private.h"
88#include "MagickCore/statistic.h"
89#include "MagickCore/string_.h"
90#include "MagickCore/thread-private.h"
91#include "MagickCore/transform.h"
92#include "MagickCore/threshold.h"
93#include "MagickCore/utility-private.h"
94␌
95/*
96%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
97% %
98% %
99% %
100% A d a p t i v e B l u r I m a g e %
101% %
102% %
103% %
104%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
105%
106% AdaptiveBlurImage() adaptively blurs the image by blurring less
107% intensely near image edges and more intensely far from edges. We blur the
108% image with a Gaussian operator of the given radius and standard deviation
109% (sigma). For reasonable results, radius should be larger than sigma. Use a
110% radius of 0 and AdaptiveBlurImage() selects a suitable radius for you.
111%
112% The format of the AdaptiveBlurImage method is:
113%
114% Image *AdaptiveBlurImage(const Image *image,const double radius,
115% const double sigma,ExceptionInfo *exception)
116%
117% A description of each parameter follows:
118%
119% o image: the image.
120%
121% o radius: the radius of the Gaussian, in pixels, not counting the center
122% pixel.
123%
124% o sigma: the standard deviation of the Laplacian, in pixels.
125%
126% o exception: return any errors or warnings in this structure.
127%
128*/
129MagickExport Image *AdaptiveBlurImage(const Image *image,const double radius,
130 const double sigma,ExceptionInfo *exception)
131{
132#define AdaptiveBlurImageTag "Convolve/Image"
133#define MagickSigma (fabs(sigma) < MagickEpsilon ? MagickEpsilon : sigma)
134
135 CacheView
136 *blur_view,
137 *edge_view,
138 *image_view;
139
140 double
141 normalize,
142 **kernel;
143
144 Image
145 *blur_image,
146 *edge_image,
147 *gaussian_image;
148
149 MagickBooleanType
150 status;
151
152 MagickOffsetType
153 progress;
154
155 size_t
156 width;
157
158 ssize_t
159 w,
160 y;
161
162 assert(image != (const Image *) NULL);
163 assert(image->signature == MagickCoreSignature);
164 assert(exception != (ExceptionInfo *) NULL);
165 assert(exception->signature == MagickCoreSignature);
166 if (IsEventLogging() != MagickFalse)
167 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
168 blur_image=CloneImage(image,0,0,MagickTrue,exception);
169 if (blur_image == (Image *) NULL)
170 return((Image *) NULL);
171 if (fabs(sigma) < MagickEpsilon)
172 return(blur_image);
173 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
174 {
175 blur_image=DestroyImage(blur_image);
176 return((Image *) NULL);
177 }
178 /*
179 Edge detect the image brightness channel, level, blur, and level again.
180 */
181 edge_image=EdgeImage(image,radius,exception);
182 if (edge_image == (Image *) NULL)
183 {
184 blur_image=DestroyImage(blur_image);
185 return((Image *) NULL);
186 }
187 (void) AutoLevelImage(edge_image,exception);
188 gaussian_image=BlurImage(edge_image,radius,sigma,exception);
189 if (gaussian_image != (Image *) NULL)
190 {
191 edge_image=DestroyImage(edge_image);
192 edge_image=gaussian_image;
193 }
194 (void) AutoLevelImage(edge_image,exception);
195 /*
196 Create a set of kernels from maximum (radius,sigma) to minimum.
197 */
198 width=GetOptimalKernelWidth2D(radius,sigma);
199 kernel=(double **) MagickAssumeAligned(AcquireAlignedMemory((size_t) width,
200 sizeof(*kernel)));
201 if (kernel == (double **) NULL)
202 {
203 edge_image=DestroyImage(edge_image);
204 blur_image=DestroyImage(blur_image);
205 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
206 }
207 (void) memset(kernel,0,(size_t) width*sizeof(*kernel));
208 for (w=0; w < (ssize_t) width; w+=2)
209 {
210 ssize_t
211 j,
212 k,
213 u,
214 v;
215
216 kernel[w]=(double *) MagickAssumeAligned(AcquireAlignedMemory(
217 (width-(size_t) w),(width-(size_t) w)*sizeof(**kernel)));
218 if (kernel[w] == (double *) NULL)
219 break;
220 normalize=0.0;
221 j=((ssize_t) width-w-1)/2;
222 k=0;
223 for (v=(-j); v <= j; v++)
224 {
225 for (u=(-j); u <= j; u++)
226 {
227 kernel[w][k]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
228 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
229 normalize+=kernel[w][k];
230 k++;
231 }
232 }
233 kernel[w][(k-1)/2]+=(double) (1.0-normalize);
234 if (sigma < MagickEpsilon)
235 kernel[w][(k-1)/2]=1.0;
236 }
237 if (w < (ssize_t) width)
238 {
239 for (w-=2; w >= 0; w-=2)
240 kernel[w]=(double *) RelinquishAlignedMemory(kernel[w]);
241 kernel=(double **) RelinquishAlignedMemory(kernel);
242 edge_image=DestroyImage(edge_image);
243 blur_image=DestroyImage(blur_image);
244 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
245 }
246 /*
247 Adaptively blur image.
248 */
249 status=MagickTrue;
250 progress=0;
251 image_view=AcquireVirtualCacheView(image,exception);
252 edge_view=AcquireVirtualCacheView(edge_image,exception);
253 blur_view=AcquireAuthenticCacheView(blur_image,exception);
254#if defined(MAGICKCORE_OPENMP_SUPPORT)
255 #pragma omp parallel for schedule(static) shared(progress,status) \
256 magick_number_threads(image,blur_image,blur_image->rows,1)
257#endif
258 for (y=0; y < (ssize_t) blur_image->rows; y++)
259 {
260 const Quantum
261 *magick_restrict r;
262
263 Quantum
264 *magick_restrict q;
265
266 ssize_t
267 x;
268
269 if (status == MagickFalse)
270 continue;
271 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
272 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
273 exception);
274 if ((r == (const Quantum *) NULL) || (q == (Quantum *) NULL))
275 {
276 status=MagickFalse;
277 continue;
278 }
279 for (x=0; x < (ssize_t) blur_image->columns; x++)
280 {
281 const Quantum
282 *magick_restrict p;
283
284 ssize_t
285 i;
286
287 ssize_t
288 center,
289 j;
290
291 j=CastDoubleToSsizeT(ceil((double) width*(1.0-QuantumScale*
292 GetPixelIntensity(edge_image,r))-0.5));
293 if (j < 0)
294 j=0;
295 else
296 if (j > (ssize_t) width)
297 j=(ssize_t) width;
298 if ((j & 0x01) != 0)
299 j--;
300 p=GetCacheViewVirtualPixels(image_view,x-((ssize_t) width-j)/2L,y-
301 ((ssize_t) width-j)/2L,width-(size_t) j,width-(size_t) j,exception);
302 if (p == (const Quantum *) NULL)
303 break;
304 center=(ssize_t) (GetPixelChannels(image)*(width-(size_t) j)*
305 ((width-(size_t) j)/2L)+GetPixelChannels(image)*((width-(size_t) j)/2));
306 for (i=0; i < (ssize_t) GetPixelChannels(blur_image); i++)
307 {
308 const double
309 *magick_restrict k;
310
311 const Quantum
312 *magick_restrict pixels;
313
314 double
315 alpha,
316 gamma,
317 pixel;
318
319 PixelChannel
320 channel;
321
322 PixelTrait
323 blur_traits,
324 traits;
325
326 ssize_t
327 u,
328 v;
329
330 channel=GetPixelChannelChannel(image,i);
331 traits=GetPixelChannelTraits(image,channel);
332 blur_traits=GetPixelChannelTraits(blur_image,channel);
333 if ((traits == UndefinedPixelTrait) ||
334 (blur_traits == UndefinedPixelTrait))
335 continue;
336 if ((blur_traits & CopyPixelTrait) != 0)
337 {
338 SetPixelChannel(blur_image,channel,p[center+i],q);
339 continue;
340 }
341 k=kernel[j];
342 pixels=p;
343 pixel=0.0;
344 gamma=0.0;
345 if ((blur_traits & BlendPixelTrait) == 0)
346 {
347 /*
348 No alpha blending.
349 */
350 for (v=0; v < ((ssize_t) width-j); v++)
351 {
352 for (u=0; u < ((ssize_t) width-j); u++)
353 {
354 pixel+=(*k)*(double) pixels[i];
355 gamma+=(*k);
356 k++;
357 pixels+=(ptrdiff_t) GetPixelChannels(image);
358 }
359 }
360 gamma=MagickSafeReciprocal(gamma);
361 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
362 continue;
363 }
364 /*
365 Alpha blending.
366 */
367 for (v=0; v < ((ssize_t) width-j); v++)
368 {
369 for (u=0; u < ((ssize_t) width-j); u++)
370 {
371 alpha=(double) (QuantumScale*(double) GetPixelAlpha(image,pixels));
372 pixel+=(*k)*alpha*(double) pixels[i];
373 gamma+=(*k)*alpha;
374 k++;
375 pixels+=(ptrdiff_t) GetPixelChannels(image);
376 }
377 }
378 gamma=MagickSafeReciprocal(gamma);
379 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
380 }
381 q+=(ptrdiff_t) GetPixelChannels(blur_image);
382 r+=(ptrdiff_t) GetPixelChannels(edge_image);
383 }
384 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
385 status=MagickFalse;
386 if (image->progress_monitor != (MagickProgressMonitor) NULL)
387 {
388 MagickBooleanType
389 proceed;
390
391#if defined(MAGICKCORE_OPENMP_SUPPORT)
392 #pragma omp atomic
393#endif
394 progress++;
395 proceed=SetImageProgress(image,AdaptiveBlurImageTag,progress,
396 image->rows);
397 if (proceed == MagickFalse)
398 status=MagickFalse;
399 }
400 }
401 blur_image->type=image->type;
402 blur_view=DestroyCacheView(blur_view);
403 edge_view=DestroyCacheView(edge_view);
404 image_view=DestroyCacheView(image_view);
405 edge_image=DestroyImage(edge_image);
406 for (w=0; w < (ssize_t) width; w+=2)
407 kernel[w]=(double *) RelinquishAlignedMemory(kernel[w]);
408 kernel=(double **) RelinquishAlignedMemory(kernel);
409 if (status == MagickFalse)
410 blur_image=DestroyImage(blur_image);
411 return(blur_image);
412}
413␌
414/*
415%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
416% %
417% %
418% %
419% A d a p t i v e S h a r p e n I m a g e %
420% %
421% %
422% %
423%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
424%
425% AdaptiveSharpenImage() adaptively sharpens the image by sharpening more
426% intensely near image edges and less intensely far from edges. We sharpen the
427% image with a Gaussian operator of the given radius and standard deviation
428% (sigma). For reasonable results, radius should be larger than sigma. Use a
429% radius of 0 and AdaptiveSharpenImage() selects a suitable radius for you.
430%
431% The format of the AdaptiveSharpenImage method is:
432%
433% Image *AdaptiveSharpenImage(const Image *image,const double radius,
434% const double sigma,ExceptionInfo *exception)
435%
436% A description of each parameter follows:
437%
438% o image: the image.
439%
440% o radius: the radius of the Gaussian, in pixels, not counting the center
441% pixel.
442%
443% o sigma: the standard deviation of the Laplacian, in pixels.
444%
445% o exception: return any errors or warnings in this structure.
446%
447*/
448MagickExport Image *AdaptiveSharpenImage(const Image *image,const double radius,
449 const double sigma,ExceptionInfo *exception)
450{
451#define AdaptiveSharpenImageTag "Convolve/Image"
452#define MagickSigma (fabs(sigma) < MagickEpsilon ? MagickEpsilon : sigma)
453
454 CacheView
455 *sharp_view,
456 *edge_view,
457 *image_view;
458
459 double
460 normalize,
461 **kernel;
462
463 Image
464 *sharp_image,
465 *edge_image,
466 *gaussian_image;
467
468 MagickBooleanType
469 status;
470
471 MagickOffsetType
472 progress;
473
474 size_t
475 width;
476
477 ssize_t
478 w,
479 y;
480
481 assert(image != (const Image *) NULL);
482 assert(image->signature == MagickCoreSignature);
483 assert(exception != (ExceptionInfo *) NULL);
484 assert(exception->signature == MagickCoreSignature);
485 if (IsEventLogging() != MagickFalse)
486 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
487 sharp_image=CloneImage(image,0,0,MagickTrue,exception);
488 if (sharp_image == (Image *) NULL)
489 return((Image *) NULL);
490 if (fabs(sigma) < MagickEpsilon)
491 return(sharp_image);
492 if (SetImageStorageClass(sharp_image,DirectClass,exception) == MagickFalse)
493 {
494 sharp_image=DestroyImage(sharp_image);
495 return((Image *) NULL);
496 }
497 /*
498 Edge detect the image brightness channel, level, sharp, and level again.
499 */
500 edge_image=EdgeImage(image,radius,exception);
501 if (edge_image == (Image *) NULL)
502 {
503 sharp_image=DestroyImage(sharp_image);
504 return((Image *) NULL);
505 }
506 (void) AutoLevelImage(edge_image,exception);
507 gaussian_image=BlurImage(edge_image,radius,sigma,exception);
508 if (gaussian_image != (Image *) NULL)
509 {
510 edge_image=DestroyImage(edge_image);
511 edge_image=gaussian_image;
512 }
513 (void) AutoLevelImage(edge_image,exception);
514 /*
515 Create a set of kernels from maximum (radius,sigma) to minimum.
516 */
517 width=GetOptimalKernelWidth2D(radius,sigma);
518 kernel=(double **) MagickAssumeAligned(AcquireAlignedMemory((size_t)
519 width,sizeof(*kernel)));
520 if (kernel == (double **) NULL)
521 {
522 edge_image=DestroyImage(edge_image);
523 sharp_image=DestroyImage(sharp_image);
524 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
525 }
526 (void) memset(kernel,0,(size_t) width*sizeof(*kernel));
527 for (w=0; w < (ssize_t) width; w+=2)
528 {
529 ssize_t
530 j,
531 k,
532 u,
533 v;
534
535 kernel[w]=(double *) MagickAssumeAligned(AcquireAlignedMemory((size_t)
536 (width-(size_t) w),(width-(size_t) w)*sizeof(**kernel)));
537 if (kernel[w] == (double *) NULL)
538 break;
539 normalize=0.0;
540 j=((ssize_t) width-w-1)/2;
541 k=0;
542 for (v=(-j); v <= j; v++)
543 {
544 for (u=(-j); u <= j; u++)
545 {
546 kernel[w][k]=(double) (-exp(-((double) u*u+v*v)/(2.0*MagickSigma*
547 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
548 normalize+=kernel[w][k];
549 k++;
550 }
551 }
552 kernel[w][(k-1)/2]=(double) ((-2.0)*normalize);
553 if (sigma < MagickEpsilon)
554 kernel[w][(k-1)/2]=1.0;
555 }
556 if (w < (ssize_t) width)
557 {
558 for (w-=2; w >= 0; w-=2)
559 kernel[w]=(double *) RelinquishAlignedMemory(kernel[w]);
560 kernel=(double **) RelinquishAlignedMemory(kernel);
561 edge_image=DestroyImage(edge_image);
562 sharp_image=DestroyImage(sharp_image);
563 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
564 }
565 /*
566 Adaptively sharpen image.
567 */
568 status=MagickTrue;
569 progress=0;
570 image_view=AcquireVirtualCacheView(image,exception);
571 edge_view=AcquireVirtualCacheView(edge_image,exception);
572 sharp_view=AcquireAuthenticCacheView(sharp_image,exception);
573#if defined(MAGICKCORE_OPENMP_SUPPORT)
574 #pragma omp parallel for schedule(static) shared(progress,status) \
575 magick_number_threads(image,sharp_image,sharp_image->rows,1)
576#endif
577 for (y=0; y < (ssize_t) sharp_image->rows; y++)
578 {
579 const Quantum
580 *magick_restrict r;
581
582 Quantum
583 *magick_restrict q;
584
585 ssize_t
586 x;
587
588 if (status == MagickFalse)
589 continue;
590 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
591 q=QueueCacheViewAuthenticPixels(sharp_view,0,y,sharp_image->columns,1,
592 exception);
593 if ((r == (const Quantum *) NULL) || (q == (Quantum *) NULL))
594 {
595 status=MagickFalse;
596 continue;
597 }
598 for (x=0; x < (ssize_t) sharp_image->columns; x++)
599 {
600 const Quantum
601 *magick_restrict p;
602
603 ssize_t
604 i;
605
606 ssize_t
607 center,
608 j;
609
610 j=CastDoubleToSsizeT(ceil((double) width*(1.0-QuantumScale*
611 GetPixelIntensity(edge_image,r))-0.5));
612 if (j < 0)
613 j=0;
614 else
615 if (j > (ssize_t) width)
616 j=(ssize_t) width;
617 if ((j & 0x01) != 0)
618 j--;
619 p=GetCacheViewVirtualPixels(image_view,x-(((ssize_t) width-j)/2L),y-
620 (((ssize_t) width-j)/2L),width-(size_t) j,width-(size_t) j,exception);
621 if (p == (const Quantum *) NULL)
622 break;
623 center=(ssize_t) (GetPixelChannels(image)*(width-(size_t) j)*
624 ((width-(size_t) j)/2L)+GetPixelChannels(image)*((width-(size_t) j)/2));
625 for (i=0; i < (ssize_t) GetPixelChannels(sharp_image); i++)
626 {
627 const double
628 *magick_restrict k;
629
630 const Quantum
631 *magick_restrict pixels;
632
633 double
634 alpha,
635 gamma,
636 pixel;
637
638 PixelChannel
639 channel;
640
641 PixelTrait
642 sharp_traits,
643 traits;
644
645 ssize_t
646 u,
647 v;
648
649 channel=GetPixelChannelChannel(image,i);
650 traits=GetPixelChannelTraits(image,channel);
651 sharp_traits=GetPixelChannelTraits(sharp_image,channel);
652 if ((traits == UndefinedPixelTrait) ||
653 (sharp_traits == UndefinedPixelTrait))
654 continue;
655 if ((sharp_traits & CopyPixelTrait) != 0)
656 {
657 SetPixelChannel(sharp_image,channel,p[center+i],q);
658 continue;
659 }
660 k=kernel[j];
661 pixels=p;
662 pixel=0.0;
663 gamma=0.0;
664 if ((sharp_traits & BlendPixelTrait) == 0)
665 {
666 /*
667 No alpha blending.
668 */
669 for (v=0; v < ((ssize_t) width-j); v++)
670 {
671 for (u=0; u < ((ssize_t) width-j); u++)
672 {
673 pixel+=(*k)*(double) pixels[i];
674 gamma+=(*k);
675 k++;
676 pixels+=(ptrdiff_t) GetPixelChannels(image);
677 }
678 }
679 gamma=MagickSafeReciprocal(gamma);
680 SetPixelChannel(sharp_image,channel,ClampToQuantum(gamma*pixel),q);
681 continue;
682 }
683 /*
684 Alpha blending.
685 */
686 for (v=0; v < ((ssize_t) width-j); v++)
687 {
688 for (u=0; u < ((ssize_t) width-j); u++)
689 {
690 alpha=(double) (QuantumScale*(double) GetPixelAlpha(image,pixels));
691 pixel+=(*k)*alpha*(double) pixels[i];
692 gamma+=(*k)*alpha;
693 k++;
694 pixels+=(ptrdiff_t) GetPixelChannels(image);
695 }
696 }
697 gamma=MagickSafeReciprocal(gamma);
698 SetPixelChannel(sharp_image,channel,ClampToQuantum(gamma*pixel),q);
699 }
700 q+=(ptrdiff_t) GetPixelChannels(sharp_image);
701 r+=(ptrdiff_t) GetPixelChannels(edge_image);
702 }
703 if (SyncCacheViewAuthenticPixels(sharp_view,exception) == MagickFalse)
704 status=MagickFalse;
705 if (image->progress_monitor != (MagickProgressMonitor) NULL)
706 {
707 MagickBooleanType
708 proceed;
709
710#if defined(MAGICKCORE_OPENMP_SUPPORT)
711 #pragma omp atomic
712#endif
713 progress++;
714 proceed=SetImageProgress(image,AdaptiveSharpenImageTag,progress,
715 image->rows);
716 if (proceed == MagickFalse)
717 status=MagickFalse;
718 }
719 }
720 sharp_image->type=image->type;
721 sharp_view=DestroyCacheView(sharp_view);
722 edge_view=DestroyCacheView(edge_view);
723 image_view=DestroyCacheView(image_view);
724 edge_image=DestroyImage(edge_image);
725 for (w=0; w < (ssize_t) width; w+=2)
726 kernel[w]=(double *) RelinquishAlignedMemory(kernel[w]);
727 kernel=(double **) RelinquishAlignedMemory(kernel);
728 if (status == MagickFalse)
729 sharp_image=DestroyImage(sharp_image);
730 return(sharp_image);
731}
732␌
733/*
734%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
735% %
736% %
737% %
738% B l u r I m a g e %
739% %
740% %
741% %
742%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
743%
744% BlurImage() blurs an image. We convolve the image with a Gaussian operator
745% of the given radius and standard deviation (sigma). For reasonable results,
746% the radius should be larger than sigma. Use a radius of 0 and BlurImage()
747% selects a suitable radius for you.
748%
749% The format of the BlurImage method is:
750%
751% Image *BlurImage(const Image *image,const double radius,
752% const double sigma,ExceptionInfo *exception)
753%
754% A description of each parameter follows:
755%
756% o image: the image.
757%
758% o radius: the radius of the Gaussian, in pixels, not counting the center
759% pixel.
760%
761% o sigma: the standard deviation of the Gaussian, in pixels.
762%
763% o exception: return any errors or warnings in this structure.
764%
765*/
766MagickExport Image *BlurImage(const Image *image,const double radius,
767 const double sigma,ExceptionInfo *exception)
768{
769 char
770 geometry[MagickPathExtent];
771
772 KernelInfo
773 *kernel_info;
774
775 Image
776 *blur_image;
777
778 assert(image != (const Image *) NULL);
779 assert(image->signature == MagickCoreSignature);
780 assert(exception != (ExceptionInfo *) NULL);
781 assert(exception->signature == MagickCoreSignature);
782 if (IsEventLogging() != MagickFalse)
783 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
784#if defined(MAGICKCORE_OPENCL_SUPPORT)
785 blur_image=AccelerateBlurImage(image,radius,sigma,exception);
786 if (blur_image != (Image *) NULL)
787 return(blur_image);
788#endif
789 (void) FormatLocaleString(geometry,MagickPathExtent,
790 "blur:%.17gx%.17g;blur:%.17gx%.17g+90",radius,sigma,radius,sigma);
791 kernel_info=AcquireKernelInfo(geometry,exception);
792 if (kernel_info == (KernelInfo *) NULL)
793 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
794 blur_image=ConvolveImage(image,kernel_info,exception);
795 kernel_info=DestroyKernelInfo(kernel_info);
796 return(blur_image);
797}
798␌
799/*
800%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
801% %
802% %
803% %
804% B i l a t e r a l B l u r I m a g e %
805% %
806% %
807% %
808%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
809%
810% BilateralBlurImage() is a non-linear, edge-preserving, and noise-reducing
811% smoothing filter for images. It replaces the intensity of each pixel with
812% a weighted average of intensity values from nearby pixels. This weight is
813% based on a Gaussian distribution. The weights depend not only on Euclidean
814% distance of pixels, but also on the radiometric differences (e.g., range
815% differences, such as color intensity, depth distance, etc.). This preserves
816% sharp edges.
817%
818% The format of the BilateralBlurImage method is:
819%
820% Image *BilateralBlurImage(const Image *image,const size_t width,
821% const size_t height,const double intensity_sigma,
822% const double spatial_sigma,ExceptionInfo *exception)
823%
824% A description of each parameter follows:
825%
826% o image: the image.
827%
828% o width: the width of the neighborhood in pixels.
829%
830% o height: the height of the neighborhood in pixels.
831%
832% o intensity_sigma: sigma in the intensity space. A larger value means
833% that farther colors within the pixel neighborhood (see spatial_sigma)
834% will be mixed together, resulting in larger areas of semi-equal color.
835%
836% o spatial_sigma: sigma in the coordinate space. A larger value means that
837% farther pixels influence each other as long as their colors are close
838% enough (see intensity_sigma ). When the neighborhood diameter is greater
839% than zero, it specifies the neighborhood size regardless of
840% spatial_sigma. Otherwise, the neighborhood diameter is proportional to
841% spatial_sigma.
842%
843% o exception: return any errors or warnings in this structure.
844%
845*/
846
847static inline double BlurDistance(const ssize_t x,const ssize_t y,
848 const ssize_t u,const ssize_t v)
849{
850 return(sqrt(((double) x-u)*((double) x-u)+((double) y-v)*((double) y-v)));
851}
852
853static inline double BlurGaussian(const double x,const double sigma)
854{
855 return(exp(-((double) x*x)*MagickSafeReciprocal(2.0*sigma*sigma))*
856 MagickSafeReciprocal(Magick2PI*sigma*sigma));
857}
858
859static double **DestroyBilateralTLS(const size_t number_threads,
860 double **weights)
861{
862 ssize_t
863 i;
864
865 assert(weights != (double **) NULL);
866 for (i=0; i <= (ssize_t) number_threads; i++)
867 if (weights[i] != (double *) NULL)
868 weights[i]=(double *) RelinquishMagickMemory(weights[i]);
869 weights=(double **) RelinquishMagickMemory(weights);
870 return(weights);
871}
872
873static double **AcquireBilateralTLS(const size_t number_threads,
874 const size_t width,const size_t height)
875{
876 double
877 **weights;
878
879 size_t
880 count;
881
882 ssize_t
883 i;
884
885 if (HeapOverflowSanityCheckGetSize(height,sizeof(**weights),&count) != MagickFalse)
886 return((double **) NULL);
887 weights=(double **) AcquireQuantumMemory(number_threads+1,sizeof(*weights));
888 if (weights == (double **) NULL)
889 return((double **) NULL);
890 (void) memset(weights,0,(number_threads+1)*sizeof(*weights));
891 for (i=0; i <= (ssize_t) number_threads; i++)
892 {
893 weights[i]=(double *) AcquireQuantumMemory(width,count);
894 if (weights[i] == (double *) NULL)
895 return(DestroyBilateralTLS(number_threads,weights));
896 }
897 return(weights);
898}
899
900MagickExport Image *BilateralBlurImage(const Image *image,const size_t width,
901 const size_t height,const double intensity_sigma,const double spatial_sigma,
902 ExceptionInfo *exception)
903{
904#define MaxIntensity (255)
905#define BilateralBlurImageTag "Blur/Image"
906
907 CacheView
908 *blur_view,
909 *image_view;
910
911 double
912 intensity_gaussian[2*(MaxIntensity+1)],
913 *spatial_gaussian,
914 **weights;
915
916 Image
917 *blur_image;
918
919 MagickBooleanType
920 status;
921
922 MagickOffsetType
923 progress;
924
925 OffsetInfo
926 mid;
927
928 size_t
929 number_threads;
930
931 ssize_t
932 w,
933 y;
934
935 assert(image != (const Image *) NULL);
936 assert(image->signature == MagickCoreSignature);
937 assert(exception != (ExceptionInfo *) NULL);
938 assert(exception->signature == MagickCoreSignature);
939 if (IsEventLogging() != MagickFalse)
940 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
941 blur_image=CloneImage(image,0,0,MagickTrue,exception);
942 if (blur_image == (Image *) NULL)
943 return((Image *) NULL);
944 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
945 {
946 blur_image=DestroyImage(blur_image);
947 return((Image *) NULL);
948 }
949 number_threads=(size_t) GetMagickResourceLimit(ThreadResource);
950 weights=AcquireBilateralTLS(number_threads,MagickMax(width,1),
951 MagickMax(height,1));
952 if (weights == (double **) NULL)
953 {
954 blur_image=DestroyImage(blur_image);
955 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
956 }
957 for (w=(-MaxIntensity); w <= MaxIntensity; w++)
958 intensity_gaussian[w+MaxIntensity]=BlurGaussian((double) w,intensity_sigma);
959 spatial_gaussian=weights[number_threads];
960 {
961 ssize_t
962 n,
963 v;
964
965 n=0;
966 mid.x=(ssize_t) (MagickMax(width,1)/2L);
967 mid.y=(ssize_t) (MagickMax(height,1)/2L);
968 for (v=0; v < (ssize_t) MagickMax(height,1); v++)
969 {
970 ssize_t
971 u;
972
973 for (u=0; u < (ssize_t) MagickMax(width,1); u++)
974 spatial_gaussian[n++]=BlurGaussian(BlurDistance(0,0,u-mid.x,v-mid.y),
975 spatial_sigma);
976 }
977 }
978 /*
979 Bilateral blur image.
980 */
981 status=MagickTrue;
982 progress=0;
983 image_view=AcquireVirtualCacheView(image,exception);
984 blur_view=AcquireAuthenticCacheView(blur_image,exception);
985#if defined(MAGICKCORE_OPENMP_SUPPORT)
986 #pragma omp parallel for schedule(static) shared(progress,status) \
987 magick_number_threads(image,blur_image,blur_image->rows,1)
988#endif
989 for (y=0; y < (ssize_t) blur_image->rows; y++)
990 {
991 const int
992 id = GetOpenMPThreadId();
993
994 Quantum
995 *magick_restrict q;
996
997 ssize_t
998 x;
999
1000 if (status == MagickFalse)
1001 continue;
1002 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
1003 exception);
1004 if (q == (Quantum *) NULL)
1005 {
1006 status=MagickFalse;
1007 continue;
1008 }
1009 for (x=0; x < (ssize_t) blur_image->columns; x++)
1010 {
1011 const Quantum
1012 *magick_restrict p,
1013 *magick_restrict r;
1014
1015 double
1016 gamma,
1017 pixel;
1018
1019 ssize_t
1020 i,
1021 n,
1022 u,
1023 v;
1024
1025 /*
1026 Tonal weighting preserves edges while smoothing in the flat regions.
1027 */
1028 p=GetCacheViewVirtualPixels(image_view,x-mid.x,y-mid.y,MagickMax(width,1),
1029 MagickMax(height,1),exception);
1030 if (p == (const Quantum *) NULL)
1031 break;
1032 p+=(ptrdiff_t) (GetPixelChannels(image)*MagickMax(width,1)*(size_t) mid.y+
1033 GetPixelChannels(image)*(size_t) mid.x);
1034 n=0;
1035 for (v=0; v < (ssize_t) MagickMax(height,1); v++)
1036 {
1037 for (u=0; u < (ssize_t) MagickMax(width,1); u++)
1038 {
1039 double
1040 intensity;
1041
1042 r=p-(ssize_t) (GetPixelChannels(image)*MagickMax(width,1)*
1043 (size_t) (mid.y-v)+GetPixelChannels(image)*(size_t) (mid.x-u));
1044 intensity=ScaleQuantumToChar((const Quantum)
1045 GetPixelIntensity(image,r))-(double)
1046 ScaleQuantumToChar((const Quantum) GetPixelIntensity(image,p));
1047 if ((intensity >= -MaxIntensity) && (intensity <= MaxIntensity))
1048 weights[id][n]=intensity_gaussian[(ssize_t) intensity+MaxIntensity]*
1049 spatial_gaussian[n];
1050 else
1051 weights[id][n]=BlurGaussian(intensity,intensity_sigma)*
1052 BlurGaussian(BlurDistance(x,y,x+u-mid.x,y+v-mid.y),spatial_sigma);
1053 n++;
1054 }
1055 }
1056 for (i=0; i < (ssize_t) GetPixelChannels(blur_image); i++)
1057 {
1058 PixelChannel
1059 channel;
1060
1061 PixelTrait
1062 blur_traits,
1063 traits;
1064
1065 channel=GetPixelChannelChannel(image,i);
1066 traits=GetPixelChannelTraits(image,channel);
1067 blur_traits=GetPixelChannelTraits(blur_image,channel);
1068 if ((traits == UndefinedPixelTrait) ||
1069 (blur_traits == UndefinedPixelTrait))
1070 continue;
1071 if ((blur_traits & CopyPixelTrait) != 0)
1072 {
1073 SetPixelChannel(blur_image,channel,p[i],q);
1074 continue;
1075 }
1076 pixel=0.0;
1077 gamma=0.0;
1078 n=0;
1079 if ((blur_traits & BlendPixelTrait) == 0)
1080 {
1081 /*
1082 No alpha blending.
1083 */
1084 for (v=0; v < (ssize_t) MagickMax(height,1); v++)
1085 {
1086 for (u=0; u < (ssize_t) MagickMax(width,1); u++)
1087 {
1088 r=p-(ssize_t) (GetPixelChannels(image)*MagickMax(width,1)*
1089 (size_t) (mid.y-v)+GetPixelChannels(image)*(size_t)
1090 (mid.x-u));
1091 pixel+=weights[id][n]*(double) r[i];
1092 gamma+=weights[id][n];
1093 n++;
1094 }
1095 }
1096 SetPixelChannel(blur_image,channel,ClampToQuantum(
1097 MagickSafeReciprocal(gamma)*pixel),q);
1098 continue;
1099 }
1100 /*
1101 Alpha blending.
1102 */
1103 for (v=0; v < (ssize_t) MagickMax(height,1); v++)
1104 {
1105 for (u=0; u < (ssize_t) MagickMax(width,1); u++)
1106 {
1107 double
1108 alpha,
1109 beta;
1110
1111 r=p-(ssize_t) (GetPixelChannels(image)*MagickMax(width,1)*(size_t)
1112 (mid.y-v)+GetPixelChannels(image)*(size_t) (mid.x-u));
1113 alpha=(double) (QuantumScale*(double) GetPixelAlpha(image,p));
1114 beta=(double) (QuantumScale*(double) GetPixelAlpha(image,r));
1115 pixel+=weights[id][n]*(double) r[i];
1116 gamma+=weights[id][n]*alpha*beta;
1117 n++;
1118 }
1119 }
1120 SetPixelChannel(blur_image,channel,ClampToQuantum(
1121 MagickSafeReciprocal(gamma)*pixel),q);
1122 }
1123 q+=(ptrdiff_t) GetPixelChannels(blur_image);
1124 }
1125 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1126 status=MagickFalse;
1127 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1128 {
1129 MagickBooleanType
1130 proceed;
1131
1132#if defined(MAGICKCORE_OPENMP_SUPPORT)
1133 #pragma omp atomic
1134#endif
1135 progress++;
1136 proceed=SetImageProgress(image,BilateralBlurImageTag,progress,
1137 image->rows);
1138 if (proceed == MagickFalse)
1139 status=MagickFalse;
1140 }
1141 }
1142 blur_image->type=image->type;
1143 blur_view=DestroyCacheView(blur_view);
1144 image_view=DestroyCacheView(image_view);
1145 weights=DestroyBilateralTLS(number_threads,weights);
1146 if (status == MagickFalse)
1147 blur_image=DestroyImage(blur_image);
1148 return(blur_image);
1149}
1150␌
1151/*
1152%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1153% %
1154% %
1155% %
1156% C o n v o l v e I m a g e %
1157% %
1158% %
1159% %
1160%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1161%
1162% ConvolveImage() applies a custom convolution kernel to the image.
1163%
1164% The format of the ConvolveImage method is:
1165%
1166% Image *ConvolveImage(const Image *image,const KernelInfo *kernel,
1167% ExceptionInfo *exception)
1168%
1169% A description of each parameter follows:
1170%
1171% o image: the image.
1172%
1173% o kernel: the filtering kernel.
1174%
1175% o exception: return any errors or warnings in this structure.
1176%
1177*/
1178MagickExport Image *ConvolveImage(const Image *image,
1179 const KernelInfo *kernel_info,ExceptionInfo *exception)
1180{
1181 Image
1182 *convolve_image;
1183
1184 convolve_image=MorphologyImage(image,ConvolveMorphology,1,kernel_info,
1185 exception);
1186 return(convolve_image);
1187}
1188␌
1189/*
1190%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1191% %
1192% %
1193% %
1194% D e s p e c k l e I m a g e %
1195% %
1196% %
1197% %
1198%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1199%
1200% DespeckleImage() reduces the speckle noise in an image while preserving the
1201% edges of the original image. A speckle removing filter uses a complementary
1202% hulling technique (raising pixels that are darker than their surrounding
1203% neighbors, then complementarily lowering pixels that are brighter than their
1204% surrounding neighbors) to reduce the speckle index of that image (reference
1205% Crimmins speckle removal).
1206%
1207% The format of the DespeckleImage method is:
1208%
1209% Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1210%
1211% A description of each parameter follows:
1212%
1213% o image: the image.
1214%
1215% o exception: return any errors or warnings in this structure.
1216%
1217*/
1218
1219static void Hull(const Image *image,const ssize_t x_offset,
1220 const ssize_t y_offset,const size_t columns,const size_t rows,
1221 const int polarity,Quantum *magick_restrict f,Quantum *magick_restrict g)
1222{
1223 Quantum
1224 *p,
1225 *q,
1226 *r,
1227 *s;
1228
1229 ssize_t
1230 y;
1231
1232 assert(image != (const Image *) NULL);
1233 assert(image->signature == MagickCoreSignature);
1234 assert(f != (Quantum *) NULL);
1235 assert(g != (Quantum *) NULL);
1236 assert(columns <= (size_t) (MAGICK_SSIZE_MAX-2));
1237 if (IsEventLogging() != MagickFalse)
1238 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1239 p=f+(ptrdiff_t) (columns+2);
1240 q=g+(ptrdiff_t) (columns+2);
1241 r=p+(ptrdiff_t) (y_offset*((ssize_t) columns+2)+x_offset);
1242#if defined(MAGICKCORE_OPENMP_SUPPORT)
1243 #pragma omp parallel for schedule(static) \
1244 magick_number_threads(image,image,rows,2)
1245#endif
1246 for (y=0; y < (ssize_t) rows; y++)
1247 {
1248 MagickRealType
1249 v;
1250
1251 ssize_t
1252 i,
1253 x;
1254
1255 i=(2*y+1)+y*(ssize_t) columns;
1256 if (polarity > 0)
1257 for (x=0; x < (ssize_t) columns; x++)
1258 {
1259 v=(MagickRealType) p[i];
1260 if ((MagickRealType) r[i] >= (v+(double) ScaleCharToQuantum(2)))
1261 v+=(double) ScaleCharToQuantum(1);
1262 q[i]=(Quantum) v;
1263 i++;
1264 }
1265 else
1266 for (x=0; x < (ssize_t) columns; x++)
1267 {
1268 v=(MagickRealType) p[i];
1269 if ((MagickRealType) r[i] <= (v-(double) ScaleCharToQuantum(2)))
1270 v-=(double) ScaleCharToQuantum(1);
1271 q[i]=(Quantum) v;
1272 i++;
1273 }
1274 }
1275 p=f+(ptrdiff_t) (columns+2);
1276 q=g+(ptrdiff_t) (columns+2);
1277 r=q+(ptrdiff_t) (y_offset*((ssize_t) columns+2)+x_offset);
1278 s=q-(ptrdiff_t) (y_offset*((ssize_t) columns+2)+x_offset);
1279#if defined(MAGICKCORE_OPENMP_SUPPORT)
1280 #pragma omp parallel for schedule(static) \
1281 magick_number_threads(image,image,rows,2)
1282#endif
1283 for (y=0; y < (ssize_t) rows; y++)
1284 {
1285 ssize_t
1286 i,
1287 x;
1288
1289 MagickRealType
1290 v;
1291
1292 i=(2*y+1)+y*(ssize_t) columns;
1293 if (polarity > 0)
1294 for (x=0; x < (ssize_t) columns; x++)
1295 {
1296 v=(MagickRealType) q[i];
1297 if (((MagickRealType) s[i] >= (v+(double) ScaleCharToQuantum(2))) &&
1298 ((MagickRealType) r[i] > v))
1299 v+=(double) ScaleCharToQuantum(1);
1300 p[i]=(Quantum) v;
1301 i++;
1302 }
1303 else
1304 for (x=0; x < (ssize_t) columns; x++)
1305 {
1306 v=(MagickRealType) q[i];
1307 if (((MagickRealType) s[i] <= (v-(double) ScaleCharToQuantum(2))) &&
1308 ((MagickRealType) r[i] < v))
1309 v-=(double) ScaleCharToQuantum(1);
1310 p[i]=(Quantum) v;
1311 i++;
1312 }
1313 }
1314}
1315
1316MagickExport Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1317{
1318#define DespeckleImageTag "Despeckle/Image"
1319
1320 CacheView
1321 *despeckle_view,
1322 *image_view;
1323
1324 Image
1325 *despeckle_image;
1326
1327 MagickBooleanType
1328 status;
1329
1330 MemoryInfo
1331 *buffer_info,
1332 *pixel_info;
1333
1334 Quantum
1335 *magick_restrict buffer,
1336 *magick_restrict pixels;
1337
1338 size_t
1339 length;
1340
1341 ssize_t
1342 i;
1343
1344 static const ssize_t
1345 X[4] = {0, 1, 1,-1},
1346 Y[4] = {1, 0, 1, 1};
1347
1348 /*
1349 Allocate despeckled image.
1350 */
1351 assert(image != (const Image *) NULL);
1352 assert(image->signature == MagickCoreSignature);
1353 assert(exception != (ExceptionInfo *) NULL);
1354 assert(exception->signature == MagickCoreSignature);
1355 if (IsEventLogging() != MagickFalse)
1356 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1357#if defined(MAGICKCORE_OPENCL_SUPPORT)
1358 despeckle_image=AccelerateDespeckleImage(image,exception);
1359 if (despeckle_image != (Image *) NULL)
1360 return(despeckle_image);
1361#endif
1362 despeckle_image=CloneImage(image,0,0,MagickTrue,exception);
1363 if (despeckle_image == (Image *) NULL)
1364 return((Image *) NULL);
1365 status=SetImageStorageClass(despeckle_image,DirectClass,exception);
1366 if (status == MagickFalse)
1367 {
1368 despeckle_image=DestroyImage(despeckle_image);
1369 return((Image *) NULL);
1370 }
1371 /*
1372 Allocate image buffer.
1373 */
1374 if ((image->columns > (MAGICK_SIZE_MAX-2)) ||
1375 (image->rows > (MAGICK_SIZE_MAX-2)))
1376 {
1377 despeckle_image=DestroyImage(despeckle_image);
1378 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1379 }
1380 if (HeapOverflowSanityCheckGetSize(image->columns+2,image->rows+2,&length) != MagickFalse)
1381 {
1382 despeckle_image=DestroyImage(despeckle_image);
1383 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1384 }
1385 pixel_info=AcquireVirtualMemory(length,sizeof(*pixels));
1386 buffer_info=AcquireVirtualMemory(length,sizeof(*buffer));
1387 if ((pixel_info == (MemoryInfo *) NULL) ||
1388 (buffer_info == (MemoryInfo *) NULL))
1389 {
1390 if (buffer_info != (MemoryInfo *) NULL)
1391 buffer_info=RelinquishVirtualMemory(buffer_info);
1392 if (pixel_info != (MemoryInfo *) NULL)
1393 pixel_info=RelinquishVirtualMemory(pixel_info);
1394 despeckle_image=DestroyImage(despeckle_image);
1395 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1396 }
1397 pixels=(Quantum *) GetVirtualMemoryBlob(pixel_info);
1398 buffer=(Quantum *) GetVirtualMemoryBlob(buffer_info);
1399 /*
1400 Reduce speckle in the image.
1401 */
1402 status=MagickTrue;
1403 image_view=AcquireVirtualCacheView(image,exception);
1404 despeckle_view=AcquireAuthenticCacheView(despeckle_image,exception);
1405 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1406 {
1407 PixelChannel
1408 channel;
1409
1410 PixelTrait
1411 despeckle_traits,
1412 traits;
1413
1414 ssize_t
1415 k,
1416 x;
1417
1418 ssize_t
1419 j,
1420 y;
1421
1422 if (status == MagickFalse)
1423 continue;
1424 channel=GetPixelChannelChannel(image,i);
1425 traits=GetPixelChannelTraits(image,channel);
1426 despeckle_traits=GetPixelChannelTraits(despeckle_image,channel);
1427 if ((traits == UndefinedPixelTrait) ||
1428 (despeckle_traits == UndefinedPixelTrait))
1429 continue;
1430 if ((despeckle_traits & CopyPixelTrait) != 0)
1431 continue;
1432 (void) memset(pixels,0,length*sizeof(*pixels));
1433 j=(ssize_t) image->columns+2;
1434 for (y=0; y < (ssize_t) image->rows; y++)
1435 {
1436 const Quantum
1437 *magick_restrict p;
1438
1439 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1440 if (p == (const Quantum *) NULL)
1441 {
1442 status=MagickFalse;
1443 continue;
1444 }
1445 j++;
1446 for (x=0; x < (ssize_t) image->columns; x++)
1447 {
1448 pixels[j++]=p[i];
1449 p+=(ptrdiff_t) GetPixelChannels(image);
1450 }
1451 j++;
1452 }
1453 (void) memset(buffer,0,length*sizeof(*buffer));
1454 for (k=0; k < 4; k++)
1455 {
1456 Hull(image,X[k],Y[k],image->columns,image->rows,1,pixels,buffer);
1457 Hull(image,-X[k],-Y[k],image->columns,image->rows,1,pixels,buffer);
1458 Hull(image,-X[k],-Y[k],image->columns,image->rows,-1,pixels,buffer);
1459 Hull(image,X[k],Y[k],image->columns,image->rows,-1,pixels,buffer);
1460 }
1461 j=(ssize_t) image->columns+2;
1462 for (y=0; y < (ssize_t) image->rows; y++)
1463 {
1464 MagickBooleanType
1465 sync;
1466
1467 Quantum
1468 *magick_restrict q;
1469
1470 q=GetCacheViewAuthenticPixels(despeckle_view,0,y,despeckle_image->columns,
1471 1,exception);
1472 if (q == (Quantum *) NULL)
1473 {
1474 status=MagickFalse;
1475 continue;
1476 }
1477 j++;
1478 for (x=0; x < (ssize_t) image->columns; x++)
1479 {
1480 SetPixelChannel(despeckle_image,channel,pixels[j++],q);
1481 q+=(ptrdiff_t) GetPixelChannels(despeckle_image);
1482 }
1483 sync=SyncCacheViewAuthenticPixels(despeckle_view,exception);
1484 if (sync == MagickFalse)
1485 status=MagickFalse;
1486 j++;
1487 }
1488 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1489 {
1490 MagickBooleanType
1491 proceed;
1492
1493 proceed=SetImageProgress(image,DespeckleImageTag,(MagickOffsetType) i,
1494 GetPixelChannels(image));
1495 if (proceed == MagickFalse)
1496 status=MagickFalse;
1497 }
1498 }
1499 despeckle_view=DestroyCacheView(despeckle_view);
1500 image_view=DestroyCacheView(image_view);
1501 buffer_info=RelinquishVirtualMemory(buffer_info);
1502 pixel_info=RelinquishVirtualMemory(pixel_info);
1503 despeckle_image->type=image->type;
1504 if (status == MagickFalse)
1505 despeckle_image=DestroyImage(despeckle_image);
1506 return(despeckle_image);
1507}
1508␌
1509/*
1510%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1511% %
1512% %
1513% %
1514% E d g e I m a g e %
1515% %
1516% %
1517% %
1518%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1519%
1520% EdgeImage() finds edges in an image. Radius defines the radius of the
1521% convolution filter. Use a radius of 0 and EdgeImage() selects a suitable
1522% radius for you.
1523%
1524% The format of the EdgeImage method is:
1525%
1526% Image *EdgeImage(const Image *image,const double radius,
1527% ExceptionInfo *exception)
1528%
1529% A description of each parameter follows:
1530%
1531% o image: the image.
1532%
1533% o radius: the radius of the pixel neighborhood.
1534%
1535% o exception: return any errors or warnings in this structure.
1536%
1537*/
1538MagickExport Image *EdgeImage(const Image *image,const double radius,
1539 ExceptionInfo *exception)
1540{
1541 Image
1542 *edge_image;
1543
1544 KernelInfo
1545 *kernel_info;
1546
1547 ssize_t
1548 i;
1549
1550 size_t
1551 width;
1552
1553 assert(image != (const Image *) NULL);
1554 assert(image->signature == MagickCoreSignature);
1555 assert(exception != (ExceptionInfo *) NULL);
1556 assert(exception->signature == MagickCoreSignature);
1557 if (IsEventLogging() != MagickFalse)
1558 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1559 width=GetOptimalKernelWidth1D(radius,0.5);
1560 kernel_info=AcquireKernelInfo((const char *) NULL,exception);
1561 if (kernel_info == (KernelInfo *) NULL)
1562 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1563 (void) memset(kernel_info,0,sizeof(*kernel_info));
1564 kernel_info->width=width;
1565 kernel_info->height=width;
1566 kernel_info->x=(ssize_t) (kernel_info->width-1)/2;
1567 kernel_info->y=(ssize_t) (kernel_info->height-1)/2;
1568 kernel_info->signature=MagickCoreSignature;
1569 kernel_info->values=(MagickRealType *) MagickAssumeAligned(
1570 AcquireAlignedMemory(kernel_info->width,kernel_info->height*
1571 sizeof(*kernel_info->values)));
1572 if (kernel_info->values == (MagickRealType *) NULL)
1573 {
1574 kernel_info=DestroyKernelInfo(kernel_info);
1575 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1576 }
1577 for (i=0; i < (ssize_t) (kernel_info->width*kernel_info->height); i++)
1578 kernel_info->values[i]=(-1.0);
1579 kernel_info->values[i/2]=(double) kernel_info->width*kernel_info->height-1.0;
1580 edge_image=ConvolveImage(image,kernel_info,exception);
1581 kernel_info=DestroyKernelInfo(kernel_info);
1582 return(edge_image);
1583}
1584␌
1585/*
1586%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1587% %
1588% %
1589% %
1590% E m b o s s I m a g e %
1591% %
1592% %
1593% %
1594%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1595%
1596% EmbossImage() returns a grayscale image with a three-dimensional effect.
1597% We convolve the image with a Gaussian operator of the given radius and
1598% standard deviation (sigma). For reasonable results, radius should be
1599% larger than sigma. Use a radius of 0 and Emboss() selects a suitable
1600% radius for you.
1601%
1602% The format of the EmbossImage method is:
1603%
1604% Image *EmbossImage(const Image *image,const double radius,
1605% const double sigma,ExceptionInfo *exception)
1606%
1607% A description of each parameter follows:
1608%
1609% o image: the image.
1610%
1611% o radius: the radius of the pixel neighborhood.
1612%
1613% o sigma: the standard deviation of the Gaussian, in pixels.
1614%
1615% o exception: return any errors or warnings in this structure.
1616%
1617*/
1618MagickExport Image *EmbossImage(const Image *image,const double radius,
1619 const double sigma,ExceptionInfo *exception)
1620{
1621 double
1622 gamma,
1623 normalize;
1624
1625 Image
1626 *emboss_image;
1627
1628 KernelInfo
1629 *kernel_info;
1630
1631 ssize_t
1632 i;
1633
1634 size_t
1635 width;
1636
1637 ssize_t
1638 j,
1639 k,
1640 u,
1641 v;
1642
1643 assert(image != (const Image *) NULL);
1644 assert(image->signature == MagickCoreSignature);
1645 assert(exception != (ExceptionInfo *) NULL);
1646 assert(exception->signature == MagickCoreSignature);
1647 if (IsEventLogging() != MagickFalse)
1648 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1649 width=GetOptimalKernelWidth1D(radius,sigma);
1650 kernel_info=AcquireKernelInfo((const char *) NULL,exception);
1651 if (kernel_info == (KernelInfo *) NULL)
1652 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1653 kernel_info->width=width;
1654 kernel_info->height=width;
1655 kernel_info->x=(ssize_t) (width-1)/2;
1656 kernel_info->y=(ssize_t) (width-1)/2;
1657 kernel_info->values=(MagickRealType *) MagickAssumeAligned(
1658 AcquireAlignedMemory(kernel_info->width,kernel_info->width*
1659 sizeof(*kernel_info->values)));
1660 if (kernel_info->values == (MagickRealType *) NULL)
1661 {
1662 kernel_info=DestroyKernelInfo(kernel_info);
1663 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1664 }
1665 j=(ssize_t) (kernel_info->width-1)/2;
1666 k=j;
1667 i=0;
1668 for (v=(-j); v <= j; v++)
1669 {
1670 for (u=(-j); u <= j; u++)
1671 {
1672 kernel_info->values[i]=(MagickRealType) (((u < 0) || (v < 0) ? -8.0 :
1673 8.0)*exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
1674 (2.0*MagickPI*MagickSigma*MagickSigma));
1675 if (u != k)
1676 kernel_info->values[i]=0.0;
1677 i++;
1678 }
1679 k--;
1680 }
1681 normalize=0.0;
1682 for (i=0; i < (ssize_t) (kernel_info->width*kernel_info->height); i++)
1683 normalize+=kernel_info->values[i];
1684 gamma=MagickSafeReciprocal(normalize);
1685 for (i=0; i < (ssize_t) (kernel_info->width*kernel_info->height); i++)
1686 kernel_info->values[i]*=gamma;
1687 emboss_image=ConvolveImage(image,kernel_info,exception);
1688 kernel_info=DestroyKernelInfo(kernel_info);
1689 if (emboss_image != (Image *) NULL)
1690 (void) EqualizeImage(emboss_image,exception);
1691 return(emboss_image);
1692}
1693␌
1694/*
1695%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1696% %
1697% %
1698% %
1699% G a u s s i a n B l u r I m a g e %
1700% %
1701% %
1702% %
1703%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1704%
1705% GaussianBlurImage() blurs an image. We convolve the image with a
1706% Gaussian operator of the given radius and standard deviation (sigma).
1707% For reasonable results, the radius should be larger than sigma. Use a
1708% radius of 0 and GaussianBlurImage() selects a suitable radius for you.
1709%
1710% The format of the GaussianBlurImage method is:
1711%
1712% Image *GaussianBlurImage(const Image *image,const double radius,
1713% const double sigma,ExceptionInfo *exception)
1714%
1715% A description of each parameter follows:
1716%
1717% o image: the image.
1718%
1719% o radius: the radius of the Gaussian, in pixels, not counting the center
1720% pixel.
1721%
1722% o sigma: the standard deviation of the Gaussian, in pixels.
1723%
1724% o exception: return any errors or warnings in this structure.
1725%
1726*/
1727MagickExport Image *GaussianBlurImage(const Image *image,const double radius,
1728 const double sigma,ExceptionInfo *exception)
1729{
1730 char
1731 geometry[MagickPathExtent];
1732
1733 KernelInfo
1734 *kernel_info;
1735
1736 Image
1737 *blur_image;
1738
1739 assert(image != (const Image *) NULL);
1740 assert(image->signature == MagickCoreSignature);
1741 assert(exception != (ExceptionInfo *) NULL);
1742 assert(exception->signature == MagickCoreSignature);
1743 if (IsEventLogging() != MagickFalse)
1744 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1745 (void) FormatLocaleString(geometry,MagickPathExtent,"gaussian:%.17gx%.17g",
1746 radius,sigma);
1747 kernel_info=AcquireKernelInfo(geometry,exception);
1748 if (kernel_info == (KernelInfo *) NULL)
1749 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1750 blur_image=ConvolveImage(image,kernel_info,exception);
1751 kernel_info=DestroyKernelInfo(kernel_info);
1752 return(blur_image);
1753}
1754␌
1755/*
1756%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1757% %
1758% %
1759% %
1760% K u w a h a r a I m a g e %
1761% %
1762% %
1763% %
1764%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1765%
1766% KuwaharaImage() is an edge preserving noise reduction filter.
1767%
1768% The format of the KuwaharaImage method is:
1769%
1770% Image *KuwaharaImage(const Image *image,const double radius,
1771% const double sigma,ExceptionInfo *exception)
1772%
1773% A description of each parameter follows:
1774%
1775% o image: the image.
1776%
1777% o radius: the square window radius.
1778%
1779% o sigma: the standard deviation of the Gaussian, in pixels.
1780%
1781% o exception: return any errors or warnings in this structure.
1782%
1783*/
1784
1785static inline MagickRealType GetMeanLuma(const Image *magick_restrict image,
1786 const double *magick_restrict pixel)
1787{
1788 return(0.212656*pixel[image->channel_map[RedPixelChannel].offset]+
1789 0.715158*pixel[image->channel_map[GreenPixelChannel].offset]+
1790 0.072186*pixel[image->channel_map[BluePixelChannel].offset]); /* Rec709 */
1791}
1792
1793MagickExport Image *KuwaharaImage(const Image *image,const double radius,
1794 const double sigma,ExceptionInfo *exception)
1795{
1796#define KuwaharaImageTag "Kuwahara/Image"
1797
1798 CacheView
1799 *image_view,
1800 *kuwahara_view;
1801
1802 Image
1803 *gaussian_image,
1804 *kuwahara_image;
1805
1806 MagickBooleanType
1807 status;
1808
1809 MagickOffsetType
1810 progress;
1811
1812 size_t
1813 width;
1814
1815 ssize_t
1816 y;
1817
1818 /*
1819 Initialize Kuwahara image attributes.
1820 */
1821 assert(image != (Image *) NULL);
1822 assert(image->signature == MagickCoreSignature);
1823 assert(exception != (ExceptionInfo *) NULL);
1824 assert(exception->signature == MagickCoreSignature);
1825 if (IsEventLogging() != MagickFalse)
1826 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1827 width=(size_t) radius+1;
1828 gaussian_image=BlurImage(image,radius,sigma,exception);
1829 if (gaussian_image == (Image *) NULL)
1830 return((Image *) NULL);
1831 kuwahara_image=CloneImage(image,0,0,MagickTrue,exception);
1832 if (kuwahara_image == (Image *) NULL)
1833 {
1834 gaussian_image=DestroyImage(gaussian_image);
1835 return((Image *) NULL);
1836 }
1837 if (SetImageStorageClass(kuwahara_image,DirectClass,exception) == MagickFalse)
1838 {
1839 gaussian_image=DestroyImage(gaussian_image);
1840 kuwahara_image=DestroyImage(kuwahara_image);
1841 return((Image *) NULL);
1842 }
1843 /*
1844 Edge preserving noise reduction filter.
1845 */
1846 status=MagickTrue;
1847 progress=0;
1848 image_view=AcquireVirtualCacheView(gaussian_image,exception);
1849 kuwahara_view=AcquireAuthenticCacheView(kuwahara_image,exception);
1850#if defined(MAGICKCORE_OPENMP_SUPPORT)
1851 #pragma omp parallel for schedule(static) shared(progress,status) \
1852 magick_number_threads(image,kuwahara_image,gaussian_image->rows,1)
1853#endif
1854 for (y=0; y < (ssize_t) gaussian_image->rows; y++)
1855 {
1856 Quantum
1857 *magick_restrict q;
1858
1859 ssize_t
1860 x;
1861
1862 if (status == MagickFalse)
1863 continue;
1864 q=QueueCacheViewAuthenticPixels(kuwahara_view,0,y,kuwahara_image->columns,1,
1865 exception);
1866 if (q == (Quantum *) NULL)
1867 {
1868 status=MagickFalse;
1869 continue;
1870 }
1871 for (x=0; x < (ssize_t) gaussian_image->columns; x++)
1872 {
1873 const Quantum
1874 *magick_restrict p;
1875
1876 double
1877 min_variance;
1878
1879 RectangleInfo
1880 quadrant,
1881 target;
1882
1883 size_t
1884 i;
1885
1886 min_variance=MagickMaximumValue;
1887 SetGeometry(gaussian_image,&target);
1888 quadrant.width=width;
1889 quadrant.height=width;
1890 for (i=0; i < 4; i++)
1891 {
1892 const Quantum
1893 *magick_restrict k;
1894
1895 double
1896 mean[MaxPixelChannels],
1897 variance;
1898
1899 ssize_t
1900 n;
1901
1902 ssize_t
1903 j;
1904
1905 quadrant.x=x;
1906 quadrant.y=y;
1907 switch (i)
1908 {
1909 case 0:
1910 {
1911 quadrant.x=x-(ssize_t) (width-1);
1912 quadrant.y=y-(ssize_t) (width-1);
1913 break;
1914 }
1915 case 1:
1916 {
1917 quadrant.y=y-(ssize_t) (width-1);
1918 break;
1919 }
1920 case 2:
1921 {
1922 quadrant.x=x-(ssize_t) (width-1);
1923 break;
1924 }
1925 case 3:
1926 default:
1927 break;
1928 }
1929 p=GetCacheViewVirtualPixels(image_view,quadrant.x,quadrant.y,
1930 quadrant.width,quadrant.height,exception);
1931 if (p == (const Quantum *) NULL)
1932 break;
1933 for (j=0; j < (ssize_t) GetPixelChannels(gaussian_image); j++)
1934 mean[j]=0.0;
1935 k=p;
1936 for (n=0; n < (ssize_t) (width*width); n++)
1937 {
1938 for (j=0; j < (ssize_t) GetPixelChannels(gaussian_image); j++)
1939 mean[j]+=(double) k[j];
1940 k+=(ptrdiff_t) GetPixelChannels(gaussian_image);
1941 }
1942 for (j=0; j < (ssize_t) GetPixelChannels(gaussian_image); j++)
1943 mean[j]/=(double) (width*width);
1944 k=p;
1945 variance=0.0;
1946 for (n=0; n < (ssize_t) (width*width); n++)
1947 {
1948 double
1949 luma;
1950
1951 luma=GetPixelLuma(gaussian_image,k);
1952 variance+=(luma-GetMeanLuma(gaussian_image,mean))*
1953 (luma-GetMeanLuma(gaussian_image,mean));
1954 k+=(ptrdiff_t) GetPixelChannels(gaussian_image);
1955 }
1956 if (variance < min_variance)
1957 {
1958 min_variance=variance;
1959 target=quadrant;
1960 }
1961 }
1962 if (i < 4)
1963 {
1964 status=MagickFalse;
1965 break;
1966 }
1967 status=InterpolatePixelChannels(gaussian_image,image_view,kuwahara_image,
1968 UndefinedInterpolatePixel,(double) target.x+target.width/2.0,(double)
1969 target.y+target.height/2.0,q,exception);
1970 if (status == MagickFalse)
1971 break;
1972 q+=(ptrdiff_t) GetPixelChannels(kuwahara_image);
1973 }
1974 if (SyncCacheViewAuthenticPixels(kuwahara_view,exception) == MagickFalse)
1975 status=MagickFalse;
1976 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1977 {
1978 MagickBooleanType
1979 proceed;
1980
1981#if defined(MAGICKCORE_OPENMP_SUPPORT)
1982 #pragma omp atomic
1983#endif
1984 progress++;
1985 proceed=SetImageProgress(image,KuwaharaImageTag,progress,image->rows);
1986 if (proceed == MagickFalse)
1987 status=MagickFalse;
1988 }
1989 }
1990 kuwahara_view=DestroyCacheView(kuwahara_view);
1991 image_view=DestroyCacheView(image_view);
1992 gaussian_image=DestroyImage(gaussian_image);
1993 if (status == MagickFalse)
1994 kuwahara_image=DestroyImage(kuwahara_image);
1995 return(kuwahara_image);
1996}
1997␌
1998/*
1999%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2000% %
2001% %
2002% %
2003% L o c a l C o n t r a s t I m a g e %
2004% %
2005% %
2006% %
2007%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2008%
2009% LocalContrastImage() attempts to increase the appearance of large-scale
2010% light-dark transitions. Local contrast enhancement works similarly to
2011% sharpening with an unsharp mask, however the mask is instead created using
2012% an image with a greater blur distance.
2013%
2014% The format of the LocalContrastImage method is:
2015%
2016% Image *LocalContrastImage(const Image *image, const double radius,
2017% const double strength,ExceptionInfo *exception)
2018%
2019% A description of each parameter follows:
2020%
2021% o image: the image.
2022%
2023% o radius: the radius of the Gaussian blur, in percentage with 100%
2024% resulting in a blur radius of 20% of largest dimension.
2025%
2026% o strength: the strength of the blur mask in percentage.
2027%
2028% o exception: return any errors or warnings in this structure.
2029%
2030*/
2031MagickExport Image *LocalContrastImage(const Image *image,const double radius,
2032 const double strength,ExceptionInfo *exception)
2033{
2034#define LocalContrastImageTag "LocalContrast/Image"
2035
2036 CacheView
2037 *image_view,
2038 *contrast_view;
2039
2040 double
2041 totalWeight;
2042
2043 float
2044 *interImage,
2045 *scanline;
2046
2047 Image
2048 *contrast_image;
2049
2050 MagickBooleanType
2051 status;
2052
2053 MemoryInfo
2054 *scanline_info,
2055 *interImage_info;
2056
2057 ssize_t
2058 scanLineSize,
2059 width;
2060
2061 /*
2062 Initialize contrast image attributes.
2063 */
2064 assert(image != (const Image *) NULL);
2065 assert(image->signature == MagickCoreSignature);
2066 assert(exception != (ExceptionInfo *) NULL);
2067 assert(exception->signature == MagickCoreSignature);
2068 if (IsEventLogging() != MagickFalse)
2069 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2070#if defined(MAGICKCORE_OPENCL_SUPPORT)
2071 contrast_image=AccelerateLocalContrastImage(image,radius,strength,exception);
2072 if (contrast_image != (Image *) NULL)
2073 return(contrast_image);
2074#endif
2075 contrast_image=CloneImage(image,0,0,MagickTrue,exception);
2076 if (contrast_image == (Image *) NULL)
2077 return((Image *) NULL);
2078 if (SetImageStorageClass(contrast_image,DirectClass,exception) == MagickFalse)
2079 {
2080 contrast_image=DestroyImage(contrast_image);
2081 return((Image *) NULL);
2082 }
2083 image_view=AcquireVirtualCacheView(image,exception);
2084 contrast_view=AcquireAuthenticCacheView(contrast_image,exception);
2085 scanLineSize=(ssize_t) MagickMax(image->columns,image->rows);
2086 width=(ssize_t) (scanLineSize*0.002*fabs(radius));
2087 scanLineSize+=(2*width);
2088 scanline_info=AcquireVirtualMemory(GetOpenMPMaximumThreads()*
2089 (size_t) scanLineSize,sizeof(*scanline));
2090 if (scanline_info == (MemoryInfo *) NULL)
2091 {
2092 contrast_view=DestroyCacheView(contrast_view);
2093 image_view=DestroyCacheView(image_view);
2094 contrast_image=DestroyImage(contrast_image);
2095 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2096 }
2097 scanline=(float *) GetVirtualMemoryBlob(scanline_info);
2098 /*
2099 Create intermediate buffer.
2100 */
2101 interImage_info=AcquireVirtualMemory(image->rows*(image->columns+(size_t)
2102 (2*width)),sizeof(*interImage));
2103 if (interImage_info == (MemoryInfo *) NULL)
2104 {
2105 scanline_info=RelinquishVirtualMemory(scanline_info);
2106 contrast_view=DestroyCacheView(contrast_view);
2107 image_view=DestroyCacheView(image_view);
2108 contrast_image=DestroyImage(contrast_image);
2109 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2110 }
2111 interImage=(float *) GetVirtualMemoryBlob(interImage_info);
2112 totalWeight=(float) ((width+1)*(width+1));
2113 /*
2114 Vertical pass.
2115 */
2116 status=MagickTrue;
2117 {
2118 ssize_t
2119 x;
2120
2121#if defined(MAGICKCORE_OPENMP_SUPPORT)
2122#pragma omp parallel for schedule(static) \
2123 magick_number_threads(image,image,image->columns,1)
2124#endif
2125 for (x=0; x < (ssize_t) image->columns; x++)
2126 {
2127 const int
2128 id = GetOpenMPThreadId();
2129
2130 const Quantum
2131 *magick_restrict p;
2132
2133 float
2134 *out,
2135 *pix,
2136 *pixels;
2137
2138 ssize_t
2139 y;
2140
2141 ssize_t
2142 i;
2143
2144 if (status == MagickFalse)
2145 continue;
2146 pixels=scanline;
2147 pixels+=id*scanLineSize;
2148 pix=pixels;
2149 p=GetCacheViewVirtualPixels(image_view,x,-(ssize_t) width,1,
2150 image->rows+(size_t) (2*width),exception);
2151 if (p == (const Quantum *) NULL)
2152 {
2153 status=MagickFalse;
2154 continue;
2155 }
2156 for (y=0; y < (ssize_t) image->rows+(2*width); y++)
2157 {
2158 *pix++=(float)GetPixelLuma(image,p);
2159 p+=(ptrdiff_t) image->number_channels;
2160 }
2161 out=interImage+x+width;
2162 for (y=0; y < (ssize_t) image->rows; y++)
2163 {
2164 double
2165 sum,
2166 weight;
2167
2168 weight=1.0;
2169 sum=0;
2170 pix=pixels+y;
2171 for (i=0; i < width; i++)
2172 {
2173 sum+=weight*((double) *pix++);
2174 weight+=1.0;
2175 }
2176 for (i=width+1; i < (2*width); i++)
2177 {
2178 sum+=weight*((double) *pix++);
2179 weight-=1.0;
2180 }
2181 /* write to output */
2182 *out=(float) (sum/totalWeight);
2183 /* mirror into padding */
2184 if ((x <= width) && (x != 0))
2185 *(out-(x*2))=*out;
2186 if ((x > (ssize_t) image->columns-width-2) &&
2187 (x != (ssize_t) image->columns-1))
2188 *(out+((image->columns-(size_t) x-1)*2))=*out;
2189 out+=image->columns+(size_t) (width*2);
2190 }
2191 }
2192 }
2193 /*
2194 Horizontal pass.
2195 */
2196 {
2197 ssize_t
2198 y;
2199
2200#if defined(MAGICKCORE_OPENMP_SUPPORT)
2201#pragma omp parallel for schedule(static) \
2202 magick_number_threads(image,image,image->rows,1)
2203#endif
2204 for (y=0; y < (ssize_t) image->rows; y++)
2205 {
2206 const int
2207 id = GetOpenMPThreadId();
2208
2209 const Quantum
2210 *magick_restrict p;
2211
2212 float
2213 *pix,
2214 *pixels;
2215
2216 Quantum
2217 *magick_restrict q;
2218
2219 ssize_t
2220 i,
2221 x;
2222
2223 if (status == MagickFalse)
2224 continue;
2225 pixels=scanline;
2226 pixels+=id*scanLineSize;
2227 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
2228 q=GetCacheViewAuthenticPixels(contrast_view,0,y,image->columns,1,
2229 exception);
2230 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
2231 {
2232 status=MagickFalse;
2233 continue;
2234 }
2235 memcpy(pixels,interImage+((size_t) y*(image->columns+(size_t) (2*width))),
2236 (image->columns+(size_t) (2*width))*sizeof(float));
2237 for (x=0; x < (ssize_t) image->columns; x++)
2238 {
2239 double
2240 mult,
2241 srcVal,
2242 sum,
2243 weight;
2244
2245 PixelTrait
2246 traits;
2247
2248 weight=1.0;
2249 sum=0;
2250 pix=pixels+x;
2251 for (i=0; i < width; i++)
2252 {
2253 sum+=weight*((double) *pix++);
2254 weight+=1.0;
2255 }
2256 for (i=width+1; i < (2*width); i++)
2257 {
2258 sum+=weight*((double) *pix++);
2259 weight-=1.0;
2260 }
2261 /*
2262 Apply and write.
2263 */
2264 srcVal=(float) GetPixelLuma(image,p);
2265 mult=(srcVal-(sum/totalWeight))*(strength/100.0);
2266 mult=(srcVal+mult)/srcVal;
2267 traits=GetPixelChannelTraits(image,RedPixelChannel);
2268 if ((traits & UpdatePixelTrait) != 0)
2269 SetPixelRed(contrast_image,ClampToQuantum((MagickRealType)
2270 GetPixelRed(image,p)*mult),q);
2271 traits=GetPixelChannelTraits(image,GreenPixelChannel);
2272 if ((traits & UpdatePixelTrait) != 0)
2273 SetPixelGreen(contrast_image,ClampToQuantum((MagickRealType)
2274 GetPixelGreen(image,p)*mult),q);
2275 traits=GetPixelChannelTraits(image,BluePixelChannel);
2276 if ((traits & UpdatePixelTrait) != 0)
2277 SetPixelBlue(contrast_image,ClampToQuantum((MagickRealType)
2278 GetPixelBlue(image,p)*mult),q);
2279 p+=(ptrdiff_t) image->number_channels;
2280 q+=(ptrdiff_t) contrast_image->number_channels;
2281 }
2282 if (SyncCacheViewAuthenticPixels(contrast_view,exception) == MagickFalse)
2283 status=MagickFalse;
2284 }
2285 }
2286 scanline_info=RelinquishVirtualMemory(scanline_info);
2287 interImage_info=RelinquishVirtualMemory(interImage_info);
2288 contrast_view=DestroyCacheView(contrast_view);
2289 image_view=DestroyCacheView(image_view);
2290 if (status == MagickFalse)
2291 contrast_image=DestroyImage(contrast_image);
2292 return(contrast_image);
2293}
2294␌
2295/*
2296%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2297% %
2298% %
2299% %
2300% M o t i o n B l u r I m a g e %
2301% %
2302% %
2303% %
2304%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2305%
2306% MotionBlurImage() simulates motion blur. We convolve the image with a
2307% Gaussian operator of the given radius and standard deviation (sigma).
2308% For reasonable results, radius should be larger than sigma. Use a
2309% radius of 0 and MotionBlurImage() selects a suitable radius for you.
2310% Angle gives the angle of the blurring motion.
2311%
2312% Andrew Protano contributed this effect.
2313%
2314% The format of the MotionBlurImage method is:
2315%
2316% Image *MotionBlurImage(const Image *image,const double radius,
2317% const double sigma,const double angle,ExceptionInfo *exception)
2318%
2319% A description of each parameter follows:
2320%
2321% o image: the image.
2322%
2323% o radius: the radius of the Gaussian, in pixels, not counting
2324% the center pixel.
2325%
2326% o sigma: the standard deviation of the Gaussian, in pixels.
2327%
2328% o angle: Apply the effect along this angle.
2329%
2330% o exception: return any errors or warnings in this structure.
2331%
2332*/
2333
2334static MagickRealType *GetMotionBlurKernel(const size_t width,
2335 const double sigma)
2336{
2337 MagickRealType
2338 *kernel,
2339 normalize;
2340
2341 ssize_t
2342 i;
2343
2344 /*
2345 Generate a 1-D convolution kernel.
2346 */
2347 if (IsEventLogging() != MagickFalse)
2348 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
2349 kernel=(MagickRealType *) MagickAssumeAligned(AcquireAlignedMemory((size_t)
2350 width,sizeof(*kernel)));
2351 if (kernel == (MagickRealType *) NULL)
2352 return(kernel);
2353 normalize=0.0;
2354 for (i=0; i < (ssize_t) width; i++)
2355 {
2356 kernel[i]=(MagickRealType) (exp((-((double) i*i)/(double) (2.0*MagickSigma*
2357 MagickSigma)))/(MagickSQ2PI*MagickSigma));
2358 normalize+=kernel[i];
2359 }
2360 for (i=0; i < (ssize_t) width; i++)
2361 kernel[i]/=normalize;
2362 return(kernel);
2363}
2364
2365MagickExport Image *MotionBlurImage(const Image *image,const double radius,
2366 const double sigma,const double angle,ExceptionInfo *exception)
2367{
2368#define BlurImageTag "Blur/Image"
2369
2370 CacheView
2371 *blur_view,
2372 *image_view,
2373 *motion_view;
2374
2375 Image
2376 *blur_image;
2377
2378 MagickBooleanType
2379 status;
2380
2381 MagickOffsetType
2382 progress;
2383
2384 MagickRealType
2385 *kernel;
2386
2387 OffsetInfo
2388 *offset;
2389
2390 PointInfo
2391 point;
2392
2393 size_t
2394 width;
2395
2396 ssize_t
2397 w,
2398 y;
2399
2400 assert(image != (Image *) NULL);
2401 assert(image->signature == MagickCoreSignature);
2402 assert(exception != (ExceptionInfo *) NULL);
2403 assert(exception->signature == MagickCoreSignature);
2404 if (IsEventLogging() != MagickFalse)
2405 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2406 width=GetOptimalKernelWidth1D(radius,sigma);
2407 kernel=GetMotionBlurKernel(width,sigma);
2408 if (kernel == (MagickRealType *) NULL)
2409 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2410 offset=(OffsetInfo *) AcquireQuantumMemory(width,sizeof(*offset));
2411 if (offset == (OffsetInfo *) NULL)
2412 {
2413 kernel=(MagickRealType *) RelinquishAlignedMemory(kernel);
2414 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2415 }
2416 point.x=(double) width*sin(DegreesToRadians(angle));
2417 point.y=(double) width*cos(DegreesToRadians(angle));
2418 for (w=0; w < (ssize_t) width; w++)
2419 {
2420 offset[w].x=CastDoubleToSsizeT(ceil((double) (w*point.y)/
2421 hypot(point.x,point.y)-0.5));
2422 offset[w].y=CastDoubleToSsizeT(ceil((double) (w*point.x)/
2423 hypot(point.x,point.y)-0.5));
2424 }
2425 /*
2426 Motion blur image.
2427 */
2428#if defined(MAGICKCORE_OPENCL_SUPPORT)
2429 blur_image=AccelerateMotionBlurImage(image,kernel,width,offset,exception);
2430 if (blur_image != (Image *) NULL)
2431 {
2432 kernel=(MagickRealType *) RelinquishAlignedMemory(kernel);
2433 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2434 return(blur_image);
2435 }
2436#endif
2437 blur_image=CloneImage(image,0,0,MagickTrue,exception);
2438 if (blur_image == (Image *) NULL)
2439 {
2440 kernel=(MagickRealType *) RelinquishAlignedMemory(kernel);
2441 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2442 return((Image *) NULL);
2443 }
2444 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
2445 {
2446 kernel=(MagickRealType *) RelinquishAlignedMemory(kernel);
2447 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2448 blur_image=DestroyImage(blur_image);
2449 return((Image *) NULL);
2450 }
2451 status=MagickTrue;
2452 progress=0;
2453 image_view=AcquireVirtualCacheView(image,exception);
2454 motion_view=AcquireVirtualCacheView(image,exception);
2455 blur_view=AcquireAuthenticCacheView(blur_image,exception);
2456#if defined(MAGICKCORE_OPENMP_SUPPORT)
2457 #pragma omp parallel for schedule(static) shared(progress,status) \
2458 magick_number_threads(image,blur_image,image->rows,1)
2459#endif
2460 for (y=0; y < (ssize_t) image->rows; y++)
2461 {
2462 const Quantum
2463 *magick_restrict p;
2464
2465 Quantum
2466 *magick_restrict q;
2467
2468 ssize_t
2469 x;
2470
2471 if (status == MagickFalse)
2472 continue;
2473 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
2474 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
2475 exception);
2476 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
2477 {
2478 status=MagickFalse;
2479 continue;
2480 }
2481 for (x=0; x < (ssize_t) image->columns; x++)
2482 {
2483 ssize_t
2484 i;
2485
2486 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2487 {
2488 double
2489 alpha = 0.0,
2490 gamma = 0.0,
2491 pixel;
2492
2493 PixelChannel
2494 channel;
2495
2496 PixelTrait
2497 blur_traits,
2498 traits;
2499
2500 const Quantum
2501 *magick_restrict r;
2502
2503 MagickRealType
2504 *magick_restrict k;
2505
2506 ssize_t
2507 j;
2508
2509 channel=GetPixelChannelChannel(image,i);
2510 traits=GetPixelChannelTraits(image,channel);
2511 blur_traits=GetPixelChannelTraits(blur_image,channel);
2512 if ((traits == UndefinedPixelTrait) ||
2513 (blur_traits == UndefinedPixelTrait))
2514 continue;
2515 if ((blur_traits & CopyPixelTrait) != 0)
2516 {
2517 SetPixelChannel(blur_image,channel,p[i],q);
2518 continue;
2519 }
2520 k=kernel;
2521 pixel=0.0;
2522 if ((blur_traits & BlendPixelTrait) == 0)
2523 {
2524 for (j=0; j < (ssize_t) width; j++)
2525 {
2526 r=GetCacheViewVirtualPixels(motion_view,x+offset[j].x,y+
2527 offset[j].y,1,1,exception);
2528 if (r == (const Quantum *) NULL)
2529 {
2530 status=MagickFalse;
2531 continue;
2532 }
2533 pixel+=(*k)*(double) r[i];
2534 k++;
2535 }
2536 SetPixelChannel(blur_image,channel,ClampToQuantum(pixel),q);
2537 continue;
2538 }
2539 for (j=0; j < (ssize_t) width; j++)
2540 {
2541 r=GetCacheViewVirtualPixels(motion_view,x+offset[j].x,y+offset[j].y,1,
2542 1,exception);
2543 if (r == (const Quantum *) NULL)
2544 {
2545 status=MagickFalse;
2546 continue;
2547 }
2548 alpha=QuantumScale*(double) GetPixelAlpha(image,r);
2549 pixel+=(*k)*alpha*(double) r[i];
2550 gamma+=(*k)*alpha;
2551 k++;
2552 }
2553 gamma=MagickSafeReciprocal(gamma);
2554 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
2555 }
2556 p+=(ptrdiff_t) GetPixelChannels(image);
2557 q+=(ptrdiff_t) GetPixelChannels(blur_image);
2558 }
2559 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
2560 status=MagickFalse;
2561 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2562 {
2563 MagickBooleanType
2564 proceed;
2565
2566#if defined(MAGICKCORE_OPENMP_SUPPORT)
2567 #pragma omp atomic
2568#endif
2569 progress++;
2570 proceed=SetImageProgress(image,BlurImageTag,progress,image->rows);
2571 if (proceed == MagickFalse)
2572 status=MagickFalse;
2573 }
2574 }
2575 blur_view=DestroyCacheView(blur_view);
2576 motion_view=DestroyCacheView(motion_view);
2577 image_view=DestroyCacheView(image_view);
2578 kernel=(MagickRealType *) RelinquishAlignedMemory(kernel);
2579 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2580 if (status == MagickFalse)
2581 blur_image=DestroyImage(blur_image);
2582 return(blur_image);
2583}
2584␌
2585/*
2586%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2587% %
2588% %
2589% %
2590% P r e v i e w I m a g e %
2591% %
2592% %
2593% %
2594%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2595%
2596% PreviewImage() tiles 9 thumbnails of the specified image with an image
2597% processing operation applied with varying parameters. This may be helpful
2598% pin-pointing an appropriate parameter for a particular image processing
2599% operation.
2600%
2601% The format of the PreviewImages method is:
2602%
2603% Image *PreviewImages(const Image *image,const PreviewType preview,
2604% ExceptionInfo *exception)
2605%
2606% A description of each parameter follows:
2607%
2608% o image: the image.
2609%
2610% o preview: the image processing operation.
2611%
2612% o exception: return any errors or warnings in this structure.
2613%
2614*/
2615MagickExport Image *PreviewImage(const Image *image,const PreviewType preview,
2616 ExceptionInfo *exception)
2617{
2618#define NumberTiles 9
2619#define PreviewImageTag "Preview/Image"
2620#define DefaultPreviewGeometry "204x204+10+10"
2621
2622 char
2623 factor[MagickPathExtent],
2624 label[MagickPathExtent];
2625
2626 double
2627 degrees,
2628 gamma,
2629 percentage,
2630 radius,
2631 sigma,
2632 threshold;
2633
2634 Image
2635 *images,
2636 *montage_image,
2637 *preview_image,
2638 *thumbnail;
2639
2640 ImageInfo
2641 *preview_info;
2642
2643 MagickBooleanType
2644 proceed;
2645
2646 MontageInfo
2647 *montage_info;
2648
2649 QuantizeInfo
2650 quantize_info;
2651
2652 RectangleInfo
2653 geometry;
2654
2655 size_t
2656 colors;
2657
2658 ssize_t
2659 i,
2660 x = 0,
2661 y = 0;
2662
2663 /*
2664 Open output image file.
2665 */
2666 assert(image != (Image *) NULL);
2667 assert(image->signature == MagickCoreSignature);
2668 if (IsEventLogging() != MagickFalse)
2669 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2670 colors=2;
2671 degrees=0.0;
2672 gamma=(-0.2f);
2673 preview_info=AcquireImageInfo();
2674 SetGeometry(image,&geometry);
2675 (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y,
2676 &geometry.width,&geometry.height);
2677 images=NewImageList();
2678 percentage=12.5;
2679 GetQuantizeInfo(&quantize_info);
2680 radius=0.0;
2681 sigma=1.0;
2682 threshold=0.0;
2683 for (i=0; i < NumberTiles; i++)
2684 {
2685 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
2686 if (thumbnail == (Image *) NULL)
2687 break;
2688 (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL,
2689 (void *) NULL);
2690 (void) SetImageProperty(thumbnail,"label",DefaultTileLabel,exception);
2691 if (i == (NumberTiles/2))
2692 {
2693 (void) QueryColorCompliance("#dfdfdf",AllCompliance,
2694 &thumbnail->matte_color,exception);
2695 AppendImageToList(&images,thumbnail);
2696 continue;
2697 }
2698 switch (preview)
2699 {
2700 case RotatePreview:
2701 {
2702 degrees+=45.0;
2703 preview_image=RotateImage(thumbnail,degrees,exception);
2704 (void) FormatLocaleString(label,MagickPathExtent,"rotate %g",degrees);
2705 break;
2706 }
2707 case ShearPreview:
2708 {
2709 degrees+=5.0;
2710 preview_image=ShearImage(thumbnail,degrees,degrees,exception);
2711 (void) FormatLocaleString(label,MagickPathExtent,"shear %gx%g",degrees,
2712 2.0*degrees);
2713 break;
2714 }
2715 case RollPreview:
2716 {
2717 x=((i+1)*(ssize_t) thumbnail->columns)/NumberTiles;
2718 y=((i+1)*(ssize_t) thumbnail->rows)/NumberTiles;
2719 preview_image=RollImage(thumbnail,x,y,exception);
2720 (void) FormatLocaleString(label,MagickPathExtent,"roll %+.20gx%+.20g",
2721 (double) x,(double) y);
2722 break;
2723 }
2724 case HuePreview:
2725 {
2726 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2727 if (preview_image == (Image *) NULL)
2728 break;
2729 (void) FormatLocaleString(factor,MagickPathExtent,"100,100,%g",2.0*
2730 percentage);
2731 (void) ModulateImage(preview_image,factor,exception);
2732 (void) FormatLocaleString(label,MagickPathExtent,"modulate %s",factor);
2733 break;
2734 }
2735 case SaturationPreview:
2736 {
2737 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2738 if (preview_image == (Image *) NULL)
2739 break;
2740 (void) FormatLocaleString(factor,MagickPathExtent,"100,%g",2.0*
2741 percentage);
2742 (void) ModulateImage(preview_image,factor,exception);
2743 (void) FormatLocaleString(label,MagickPathExtent,"modulate %s",factor);
2744 break;
2745 }
2746 case BrightnessPreview:
2747 {
2748 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2749 if (preview_image == (Image *) NULL)
2750 break;
2751 (void) FormatLocaleString(factor,MagickPathExtent,"%g",2.0*percentage);
2752 (void) ModulateImage(preview_image,factor,exception);
2753 (void) FormatLocaleString(label,MagickPathExtent,"modulate %s",factor);
2754 break;
2755 }
2756 case GammaPreview:
2757 default:
2758 {
2759 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2760 if (preview_image == (Image *) NULL)
2761 break;
2762 gamma+=0.4;
2763 (void) GammaImage(preview_image,gamma,exception);
2764 (void) FormatLocaleString(label,MagickPathExtent,"gamma %g",gamma);
2765 break;
2766 }
2767 case SpiffPreview:
2768 {
2769 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2770 if (preview_image != (Image *) NULL)
2771 for (x=0; x < i; x++)
2772 (void) ContrastImage(preview_image,MagickTrue,exception);
2773 (void) FormatLocaleString(label,MagickPathExtent,"contrast (%.17g)",
2774 (double) i+1);
2775 break;
2776 }
2777 case DullPreview:
2778 {
2779 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2780 if (preview_image == (Image *) NULL)
2781 break;
2782 for (x=0; x < i; x++)
2783 (void) ContrastImage(preview_image,MagickFalse,exception);
2784 (void) FormatLocaleString(label,MagickPathExtent,"+contrast (%.17g)",
2785 (double) i+1);
2786 break;
2787 }
2788 case GrayscalePreview:
2789 {
2790 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2791 if (preview_image == (Image *) NULL)
2792 break;
2793 colors<<=1;
2794 quantize_info.number_colors=colors;
2795 quantize_info.colorspace=GRAYColorspace;
2796 (void) QuantizeImage(&quantize_info,preview_image,exception);
2797 (void) FormatLocaleString(label,MagickPathExtent,
2798 "-colorspace gray -colors %.17g",(double) colors);
2799 break;
2800 }
2801 case QuantizePreview:
2802 {
2803 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2804 if (preview_image == (Image *) NULL)
2805 break;
2806 colors<<=1;
2807 quantize_info.number_colors=colors;
2808 (void) QuantizeImage(&quantize_info,preview_image,exception);
2809 (void) FormatLocaleString(label,MagickPathExtent,"colors %.17g",
2810 (double) colors);
2811 break;
2812 }
2813 case DespecklePreview:
2814 {
2815 for (x=0; x < (i-1); x++)
2816 {
2817 preview_image=DespeckleImage(thumbnail,exception);
2818 if (preview_image == (Image *) NULL)
2819 break;
2820 thumbnail=DestroyImage(thumbnail);
2821 thumbnail=preview_image;
2822 }
2823 preview_image=DespeckleImage(thumbnail,exception);
2824 if (preview_image == (Image *) NULL)
2825 break;
2826 (void) FormatLocaleString(label,MagickPathExtent,"despeckle (%.17g)",
2827 (double) i+1);
2828 break;
2829 }
2830 case ReduceNoisePreview:
2831 {
2832 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t)
2833 radius,(size_t) radius,exception);
2834 (void) FormatLocaleString(label,MagickPathExtent,"noise %g",radius);
2835 break;
2836 }
2837 case AddNoisePreview:
2838 {
2839 switch ((int) i)
2840 {
2841 case 0:
2842 {
2843 (void) CopyMagickString(factor,"uniform",MagickPathExtent);
2844 break;
2845 }
2846 case 1:
2847 {
2848 (void) CopyMagickString(factor,"gaussian",MagickPathExtent);
2849 break;
2850 }
2851 case 2:
2852 {
2853 (void) CopyMagickString(factor,"multiplicative",MagickPathExtent);
2854 break;
2855 }
2856 case 3:
2857 {
2858 (void) CopyMagickString(factor,"impulse",MagickPathExtent);
2859 break;
2860 }
2861 case 5:
2862 {
2863 (void) CopyMagickString(factor,"laplacian",MagickPathExtent);
2864 break;
2865 }
2866 case 6:
2867 {
2868 (void) CopyMagickString(factor,"Poisson",MagickPathExtent);
2869 break;
2870 }
2871 default:
2872 {
2873 (void) CopyMagickString(thumbnail->magick,"NULL",MagickPathExtent);
2874 break;
2875 }
2876 }
2877 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) i,
2878 (size_t) i,exception);
2879 (void) FormatLocaleString(label,MagickPathExtent,"+noise %s",factor);
2880 break;
2881 }
2882 case SharpenPreview:
2883 {
2884 preview_image=SharpenImage(thumbnail,radius,sigma,exception);
2885 (void) FormatLocaleString(label,MagickPathExtent,"sharpen %gx%g",
2886 radius,sigma);
2887 break;
2888 }
2889 case BlurPreview:
2890 {
2891 preview_image=BlurImage(thumbnail,radius,sigma,exception);
2892 (void) FormatLocaleString(label,MagickPathExtent,"blur %gx%g",radius,
2893 sigma);
2894 break;
2895 }
2896 case ThresholdPreview:
2897 {
2898 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2899 if (preview_image == (Image *) NULL)
2900 break;
2901 (void) BilevelImage(thumbnail,(double) (percentage*((double)
2902 QuantumRange+1.0))/100.0,exception);
2903 (void) FormatLocaleString(label,MagickPathExtent,"threshold %g",
2904 (double) (percentage*((double) QuantumRange+1.0))/100.0);
2905 break;
2906 }
2907 case EdgeDetectPreview:
2908 {
2909 preview_image=EdgeImage(thumbnail,radius,exception);
2910 (void) FormatLocaleString(label,MagickPathExtent,"edge %g",radius);
2911 break;
2912 }
2913 case SpreadPreview:
2914 {
2915 preview_image=SpreadImage(thumbnail,image->interpolate,radius,
2916 exception);
2917 (void) FormatLocaleString(label,MagickPathExtent,"spread %g",
2918 radius+0.5);
2919 break;
2920 }
2921 case SolarizePreview:
2922 {
2923 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2924 if (preview_image == (Image *) NULL)
2925 break;
2926 (void) SolarizeImage(preview_image,(double) QuantumRange*percentage/
2927 100.0,exception);
2928 (void) FormatLocaleString(label,MagickPathExtent,"solarize %g",
2929 ((double) QuantumRange*percentage)/100.0);
2930 break;
2931 }
2932 case ShadePreview:
2933 {
2934 degrees+=10.0;
2935 preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees,
2936 exception);
2937 (void) FormatLocaleString(label,MagickPathExtent,"shade %gx%g",degrees,
2938 degrees);
2939 break;
2940 }
2941 case RaisePreview:
2942 {
2943 RectangleInfo
2944 raise;
2945
2946 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2947 if (preview_image == (Image *) NULL)
2948 break;
2949 raise.width=(size_t) (2*i+2);
2950 raise.height=(size_t) (2*i+2);
2951 raise.x=(i-1)/2;
2952 raise.y=(i-1)/2;
2953 (void) RaiseImage(preview_image,&raise,MagickTrue,exception);
2954 (void) FormatLocaleString(label,MagickPathExtent,
2955 "raise %.17gx%.17g%+.20g%+.20g",(double) raise.width,(double)
2956 raise.height,(double) raise.x,(double) raise.y);
2957 break;
2958 }
2959 case SegmentPreview:
2960 {
2961 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2962 if (preview_image == (Image *) NULL)
2963 break;
2964 threshold+=0.4;
2965 (void) SegmentImage(preview_image,sRGBColorspace,MagickFalse,threshold,
2966 threshold,exception);
2967 (void) FormatLocaleString(label,MagickPathExtent,"segment %gx%g",
2968 threshold,threshold);
2969 break;
2970 }
2971 case SwirlPreview:
2972 {
2973 preview_image=SwirlImage(thumbnail,degrees,image->interpolate,
2974 exception);
2975 (void) FormatLocaleString(label,MagickPathExtent,"swirl %g",degrees);
2976 degrees+=45.0;
2977 break;
2978 }
2979 case ImplodePreview:
2980 {
2981 degrees+=0.1;
2982 preview_image=ImplodeImage(thumbnail,degrees,image->interpolate,
2983 exception);
2984 (void) FormatLocaleString(label,MagickPathExtent,"implode %g",degrees);
2985 break;
2986 }
2987 case WavePreview:
2988 {
2989 degrees+=5.0;
2990 preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,
2991 image->interpolate,exception);
2992 (void) FormatLocaleString(label,MagickPathExtent,"wave %gx%g",0.5*
2993 degrees,2.0*degrees);
2994 break;
2995 }
2996 case OilPaintPreview:
2997 {
2998 preview_image=OilPaintImage(thumbnail,(double) radius,(double) sigma,
2999 exception);
3000 (void) FormatLocaleString(label,MagickPathExtent,"charcoal %gx%g",
3001 radius,sigma);
3002 break;
3003 }
3004 case CharcoalDrawingPreview:
3005 {
3006 preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma,
3007 exception);
3008 (void) FormatLocaleString(label,MagickPathExtent,"charcoal %gx%g",
3009 radius,sigma);
3010 break;
3011 }
3012 case JPEGPreview:
3013 {
3014 char
3015 filename[MagickPathExtent];
3016
3017 int
3018 file;
3019
3020 MagickBooleanType
3021 status;
3022
3023 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3024 if (preview_image == (Image *) NULL)
3025 break;
3026 preview_info->quality=(size_t) percentage;
3027 (void) FormatLocaleString(factor,MagickPathExtent,"%.17g",(double)
3028 preview_info->quality);
3029 file=AcquireUniqueFileResource(filename);
3030 if (file != -1)
3031 file=close_utf8(file)-1;
3032 (void) FormatLocaleString(preview_image->filename,MagickPathExtent,
3033 "jpeg:%s",filename);
3034 status=WriteImage(preview_info,preview_image,exception);
3035 if (status != MagickFalse)
3036 {
3037 Image
3038 *quality_image;
3039
3040 (void) CopyMagickString(preview_info->filename,
3041 preview_image->filename,MagickPathExtent);
3042 quality_image=ReadImage(preview_info,exception);
3043 if (quality_image != (Image *) NULL)
3044 {
3045 preview_image=DestroyImage(preview_image);
3046 preview_image=quality_image;
3047 }
3048 }
3049 (void) RelinquishUniqueFileResource(preview_image->filename);
3050 if ((GetBlobSize(preview_image)/1024) >= 1024)
3051 (void) FormatLocaleString(label,MagickPathExtent,"quality %s\n%gmb ",
3052 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
3053 1024.0/1024.0);
3054 else
3055 if (GetBlobSize(preview_image) >= 1024)
3056 (void) FormatLocaleString(label,MagickPathExtent,
3057 "quality %s\n%gkb ",factor,(double) ((MagickOffsetType)
3058 GetBlobSize(preview_image))/1024.0);
3059 else
3060 (void) FormatLocaleString(label,MagickPathExtent,
3061 "quality %s\n%.17gb ",factor,(double) ((MagickOffsetType)
3062 GetBlobSize(thumbnail)));
3063 break;
3064 }
3065 }
3066 thumbnail=DestroyImage(thumbnail);
3067 percentage+=12.5;
3068 radius+=0.5;
3069 sigma+=0.25;
3070 if (preview_image == (Image *) NULL)
3071 break;
3072 preview_image->alpha_trait=UndefinedPixelTrait;
3073 (void) DeleteImageProperty(preview_image,"label");
3074 (void) SetImageProperty(preview_image,"label",label,exception);
3075 AppendImageToList(&images,preview_image);
3076 proceed=SetImageProgress(image,PreviewImageTag,(MagickOffsetType) i,
3077 NumberTiles);
3078 if (proceed == MagickFalse)
3079 break;
3080 }
3081 if (images == (Image *) NULL)
3082 {
3083 preview_info=DestroyImageInfo(preview_info);
3084 return((Image *) NULL);
3085 }
3086 /*
3087 Create the montage.
3088 */
3089 montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL);
3090 (void) CopyMagickString(montage_info->filename,image->filename,
3091 MagickPathExtent);
3092 montage_info->shadow=MagickTrue;
3093 (void) CloneString(&montage_info->tile,"3x3");
3094 (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry);
3095 (void) CloneString(&montage_info->frame,DefaultTileFrame);
3096 montage_image=MontageImages(images,montage_info,exception);
3097 montage_info=DestroyMontageInfo(montage_info);
3098 images=DestroyImageList(images);
3099 if (montage_image == (Image *) NULL)
3100 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3101 if (montage_image->montage != (char *) NULL)
3102 {
3103 /*
3104 Free image directory.
3105 */
3106 montage_image->montage=(char *) RelinquishMagickMemory(
3107 montage_image->montage);
3108 if (image->directory != (char *) NULL)
3109 montage_image->directory=(char *) RelinquishMagickMemory(
3110 montage_image->directory);
3111 }
3112 preview_info=DestroyImageInfo(preview_info);
3113 return(montage_image);
3114}
3115␌
3116/*
3117%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3118% %
3119% %
3120% %
3121% R o t a t i o n a l B l u r I m a g e %
3122% %
3123% %
3124% %
3125%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3126%
3127% RotationalBlurImage() applies a radial blur to the image.
3128%
3129% Andrew Protano contributed this effect.
3130%
3131% The format of the RotationalBlurImage method is:
3132%
3133% Image *RotationalBlurImage(const Image *image,const double angle,
3134% ExceptionInfo *exception)
3135%
3136% A description of each parameter follows:
3137%
3138% o image: the image.
3139%
3140% o angle: the angle of the radial blur.
3141%
3142% o blur: the blur.
3143%
3144% o exception: return any errors or warnings in this structure.
3145%
3146*/
3147MagickExport Image *RotationalBlurImage(const Image *image,const double angle,
3148 ExceptionInfo *exception)
3149{
3150 CacheView
3151 *blur_view,
3152 *image_view,
3153 *radial_view;
3154
3155 double
3156 blur_radius,
3157 *cos_theta,
3158 offset,
3159 *sin_theta,
3160 theta;
3161
3162 Image
3163 *blur_image;
3164
3165 MagickBooleanType
3166 status;
3167
3168 MagickOffsetType
3169 progress;
3170
3171 PointInfo
3172 blur_center;
3173
3174 size_t
3175 n;
3176
3177 ssize_t
3178 w,
3179 y;
3180
3181 /*
3182 Allocate blur image.
3183 */
3184 assert(image != (Image *) NULL);
3185 assert(image->signature == MagickCoreSignature);
3186 assert(exception != (ExceptionInfo *) NULL);
3187 assert(exception->signature == MagickCoreSignature);
3188 if (IsEventLogging() != MagickFalse)
3189 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3190#if defined(MAGICKCORE_OPENCL_SUPPORT)
3191 blur_image=AccelerateRotationalBlurImage(image,angle,exception);
3192 if (blur_image != (Image *) NULL)
3193 return(blur_image);
3194#endif
3195 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3196 if (blur_image == (Image *) NULL)
3197 return((Image *) NULL);
3198 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
3199 {
3200 blur_image=DestroyImage(blur_image);
3201 return((Image *) NULL);
3202 }
3203 blur_center.x=(double) (image->columns-1)/2.0;
3204 blur_center.y=(double) (image->rows-1)/2.0;
3205 blur_radius=hypot(blur_center.x,blur_center.y);
3206 n=(size_t) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+2UL);
3207 theta=DegreesToRadians(angle)/(double) (n-1);
3208 cos_theta=(double *) AcquireQuantumMemory((size_t) n,sizeof(*cos_theta));
3209 sin_theta=(double *) AcquireQuantumMemory((size_t) n,sizeof(*sin_theta));
3210 if ((cos_theta == (double *) NULL) || (sin_theta == (double *) NULL))
3211 {
3212 if (cos_theta != (double *) NULL)
3213 cos_theta=(double *) RelinquishMagickMemory(cos_theta);
3214 if (sin_theta != (double *) NULL)
3215 sin_theta=(double *) RelinquishMagickMemory(sin_theta);
3216 blur_image=DestroyImage(blur_image);
3217 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3218 }
3219 offset=theta*(double) (n-1)/2.0;
3220 for (w=0; w < (ssize_t) n; w++)
3221 {
3222 cos_theta[w]=cos((double) (theta*w-offset));
3223 sin_theta[w]=sin((double) (theta*w-offset));
3224 }
3225 /*
3226 Radial blur image.
3227 */
3228 status=MagickTrue;
3229 progress=0;
3230 image_view=AcquireVirtualCacheView(image,exception);
3231 radial_view=AcquireVirtualCacheView(image,exception);
3232 blur_view=AcquireAuthenticCacheView(blur_image,exception);
3233#if defined(MAGICKCORE_OPENMP_SUPPORT)
3234 #pragma omp parallel for schedule(static) shared(progress,status) \
3235 magick_number_threads(image,blur_image,image->rows,1)
3236#endif
3237 for (y=0; y < (ssize_t) image->rows; y++)
3238 {
3239 const Quantum
3240 *magick_restrict p;
3241
3242 Quantum
3243 *magick_restrict q;
3244
3245 ssize_t
3246 x;
3247
3248 if (status == MagickFalse)
3249 continue;
3250 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
3251 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3252 exception);
3253 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
3254 {
3255 status=MagickFalse;
3256 continue;
3257 }
3258 for (x=0; x < (ssize_t) image->columns; x++)
3259 {
3260 double
3261 radius;
3262
3263 PointInfo
3264 center;
3265
3266 ssize_t
3267 i;
3268
3269 size_t
3270 step;
3271
3272 center.x=(double) x-blur_center.x;
3273 center.y=(double) y-blur_center.y;
3274 radius=hypot((double) center.x,center.y);
3275 if (radius == 0)
3276 step=1;
3277 else
3278 {
3279 step=(size_t) (blur_radius/radius);
3280 if (step == 0)
3281 step=1;
3282 else
3283 if (step >= n)
3284 step=n-1;
3285 }
3286 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3287 {
3288 double
3289 gamma,
3290 pixel;
3291
3292 PixelChannel
3293 channel;
3294
3295 PixelTrait
3296 blur_traits,
3297 traits;
3298
3299 const Quantum
3300 *magick_restrict r;
3301
3302 ssize_t
3303 j;
3304
3305 channel=GetPixelChannelChannel(image,i);
3306 traits=GetPixelChannelTraits(image,channel);
3307 blur_traits=GetPixelChannelTraits(blur_image,channel);
3308 if ((traits == UndefinedPixelTrait) ||
3309 (blur_traits == UndefinedPixelTrait))
3310 continue;
3311 if ((blur_traits & CopyPixelTrait) != 0)
3312 {
3313 SetPixelChannel(blur_image,channel,p[i],q);
3314 continue;
3315 }
3316 gamma=0.0;
3317 pixel=0.0;
3318 if ((GetPixelChannelTraits(image,AlphaPixelChannel) == UndefinedPixelTrait) ||
3319 (channel == AlphaPixelChannel))
3320 {
3321 for (j=0; j < (ssize_t) n; j+=(ssize_t) step)
3322 {
3323 r=GetCacheViewVirtualPixels(radial_view, (ssize_t) (blur_center.x+
3324 center.x*cos_theta[j]-center.y*sin_theta[j]+0.5),(ssize_t)
3325 (blur_center.y+center.x*sin_theta[j]+center.y*cos_theta[j]+0.5),
3326 1,1,exception);
3327 if (r == (const Quantum *) NULL)
3328 {
3329 status=MagickFalse;
3330 continue;
3331 }
3332 pixel+=(double) r[i];
3333 gamma++;
3334 }
3335 gamma=MagickSafeReciprocal(gamma);
3336 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
3337 continue;
3338 }
3339 for (j=0; j < (ssize_t) n; j+=(ssize_t) step)
3340 {
3341 double
3342 alpha;
3343
3344 r=GetCacheViewVirtualPixels(radial_view, (ssize_t) (blur_center.x+
3345 center.x*cos_theta[j]-center.y*sin_theta[j]+0.5),(ssize_t)
3346 (blur_center.y+center.x*sin_theta[j]+center.y*cos_theta[j]+0.5),
3347 1,1,exception);
3348 if (r == (const Quantum *) NULL)
3349 {
3350 status=MagickFalse;
3351 continue;
3352 }
3353 alpha=QuantumScale*(double) GetPixelAlpha(image,r);
3354 pixel+=alpha*(double) r[i];
3355 gamma+=alpha;
3356 }
3357 gamma=MagickSafeReciprocal(gamma);
3358 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
3359 }
3360 p+=(ptrdiff_t) GetPixelChannels(image);
3361 q+=(ptrdiff_t) GetPixelChannels(blur_image);
3362 }
3363 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
3364 status=MagickFalse;
3365 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3366 {
3367 MagickBooleanType
3368 proceed;
3369
3370#if defined(MAGICKCORE_OPENMP_SUPPORT)
3371 #pragma omp atomic
3372#endif
3373 progress++;
3374 proceed=SetImageProgress(image,BlurImageTag,progress,image->rows);
3375 if (proceed == MagickFalse)
3376 status=MagickFalse;
3377 }
3378 }
3379 blur_view=DestroyCacheView(blur_view);
3380 radial_view=DestroyCacheView(radial_view);
3381 image_view=DestroyCacheView(image_view);
3382 cos_theta=(double *) RelinquishMagickMemory(cos_theta);
3383 sin_theta=(double *) RelinquishMagickMemory(sin_theta);
3384 if (status == MagickFalse)
3385 blur_image=DestroyImage(blur_image);
3386 return(blur_image);
3387}
3388␌
3389/*
3390%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3391% %
3392% %
3393% %
3394% S e l e c t i v e B l u r I m a g e %
3395% %
3396% %
3397% %
3398%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3399%
3400% SelectiveBlurImage() selectively blur pixels within a contrast threshold.
3401% It is similar to the unsharpen mask that sharpens everything with contrast
3402% above a certain threshold.
3403%
3404% The format of the SelectiveBlurImage method is:
3405%
3406% Image *SelectiveBlurImage(const Image *image,const double radius,
3407% const double sigma,const double threshold,ExceptionInfo *exception)
3408%
3409% A description of each parameter follows:
3410%
3411% o image: the image.
3412%
3413% o radius: the radius of the Gaussian, in pixels, not counting the center
3414% pixel.
3415%
3416% o sigma: the standard deviation of the Gaussian, in pixels.
3417%
3418% o threshold: only pixels within this contrast threshold are included
3419% in the blur operation.
3420%
3421% o exception: return any errors or warnings in this structure.
3422%
3423*/
3424MagickExport Image *SelectiveBlurImage(const Image *image,const double radius,
3425 const double sigma,const double threshold,ExceptionInfo *exception)
3426{
3427#define SelectiveBlurImageTag "SelectiveBlur/Image"
3428
3429 CacheView
3430 *blur_view,
3431 *image_view,
3432 *luminance_view;
3433
3434 Image
3435 *blur_image,
3436 *luminance_image;
3437
3438 MagickBooleanType
3439 status;
3440
3441 MagickOffsetType
3442 progress;
3443
3444 MagickRealType
3445 *kernel;
3446
3447 size_t
3448 width;
3449
3450 ssize_t
3451 center,
3452 y;
3453
3454 /*
3455 Initialize blur image attributes.
3456 */
3457 assert(image != (Image *) NULL);
3458 assert(image->signature == MagickCoreSignature);
3459 assert(exception != (ExceptionInfo *) NULL);
3460 assert(exception->signature == MagickCoreSignature);
3461 if (IsEventLogging() != MagickFalse)
3462 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3463 width=GetOptimalKernelWidth1D(radius,sigma);
3464 kernel=(MagickRealType *) MagickAssumeAligned(AcquireAlignedMemory((size_t)
3465 width,width*sizeof(*kernel)));
3466 if (kernel == (MagickRealType *) NULL)
3467 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3468 {
3469 ssize_t
3470 i,
3471 j,
3472 v;
3473
3474 j=(ssize_t) (width-1)/2;
3475 i=0;
3476 for (v=(-j); v <= j; v++)
3477 {
3478 ssize_t
3479 u;
3480
3481 for (u=(-j); u <= j; u++)
3482 kernel[i++]=(MagickRealType) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
3483 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
3484 }
3485 }
3486 if (image->debug != MagickFalse)
3487 {
3488 char
3489 format[MagickPathExtent],
3490 *message;
3491
3492 const MagickRealType
3493 *k;
3494
3495 ssize_t
3496 u,
3497 v;
3498
3499 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
3500 " SelectiveBlurImage with %.17gx%.17g kernel:",(double) width,(double)
3501 width);
3502 message=AcquireString("");
3503 k=kernel;
3504 for (v=0; v < (ssize_t) width; v++)
3505 {
3506 *message='\0';
3507 (void) FormatLocaleString(format,MagickPathExtent,"%.17g: ",(double) v);
3508 (void) ConcatenateString(&message,format);
3509 for (u=0; u < (ssize_t) width; u++)
3510 {
3511 (void) FormatLocaleString(format,MagickPathExtent,"%+f ",(double)
3512 *k++);
3513 (void) ConcatenateString(&message,format);
3514 }
3515 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
3516 }
3517 message=DestroyString(message);
3518 }
3519 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3520 if (blur_image == (Image *) NULL)
3521 return((Image *) NULL);
3522 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
3523 {
3524 blur_image=DestroyImage(blur_image);
3525 kernel=(MagickRealType *) RelinquishAlignedMemory(kernel);
3526 return((Image *) NULL);
3527 }
3528 luminance_image=CloneImage(image,0,0,MagickTrue,exception);
3529 if (luminance_image == (Image *) NULL)
3530 {
3531 blur_image=DestroyImage(blur_image);
3532 kernel=(MagickRealType *) RelinquishAlignedMemory(kernel);
3533 return((Image *) NULL);
3534 }
3535 status=TransformImageColorspace(luminance_image,GRAYColorspace,exception);
3536 if (status == MagickFalse)
3537 {
3538 luminance_image=DestroyImage(luminance_image);
3539 blur_image=DestroyImage(blur_image);
3540 kernel=(MagickRealType *) RelinquishAlignedMemory(kernel);
3541 return((Image *) NULL);
3542 }
3543 /*
3544 Threshold blur image.
3545 */
3546 status=MagickTrue;
3547 progress=0;
3548 center=(ssize_t) (GetPixelChannels(image)*(image->columns+width)*
3549 ((width-1)/2L)+GetPixelChannels(image)*((width-1)/2L));
3550 image_view=AcquireVirtualCacheView(image,exception);
3551 luminance_view=AcquireVirtualCacheView(luminance_image,exception);
3552 blur_view=AcquireAuthenticCacheView(blur_image,exception);
3553#if defined(MAGICKCORE_OPENMP_SUPPORT)
3554 #pragma omp parallel for schedule(static) shared(progress,status) \
3555 magick_number_threads(image,blur_image,image->rows,1)
3556#endif
3557 for (y=0; y < (ssize_t) image->rows; y++)
3558 {
3559 double
3560 contrast;
3561
3562 MagickBooleanType
3563 sync;
3564
3565 const Quantum
3566 *magick_restrict l,
3567 *magick_restrict p;
3568
3569 Quantum
3570 *magick_restrict q;
3571
3572 ssize_t
3573 x;
3574
3575 if (status == MagickFalse)
3576 continue;
3577 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) (width-1)/2L),y-(ssize_t)
3578 ((width-1)/2L),image->columns+width,width,exception);
3579 l=GetCacheViewVirtualPixels(luminance_view,-((ssize_t) (width-1)/2L),y-
3580 (ssize_t) ((width-1)/2L),luminance_image->columns+width,width,exception);
3581 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3582 exception);
3583 if ((p == (const Quantum *) NULL) || (l == (const Quantum *) NULL) ||
3584 (q == (Quantum *) NULL))
3585 {
3586 status=MagickFalse;
3587 continue;
3588 }
3589 for (x=0; x < (ssize_t) image->columns; x++)
3590 {
3591 double
3592 intensity;
3593
3594 ssize_t
3595 i;
3596
3597 intensity=GetPixelIntensity(image,p+center);
3598 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3599 {
3600 double
3601 alpha,
3602 gamma,
3603 pixel;
3604
3605 PixelChannel
3606 channel;
3607
3608 PixelTrait
3609 blur_traits,
3610 traits;
3611
3612 const MagickRealType
3613 *magick_restrict k;
3614
3615 const Quantum
3616 *magick_restrict luminance_pixels,
3617 *magick_restrict pixels;
3618
3619 ssize_t
3620 u;
3621
3622 ssize_t
3623 v;
3624
3625 channel=GetPixelChannelChannel(image,i);
3626 traits=GetPixelChannelTraits(image,channel);
3627 blur_traits=GetPixelChannelTraits(blur_image,channel);
3628 if ((traits == UndefinedPixelTrait) ||
3629 (blur_traits == UndefinedPixelTrait))
3630 continue;
3631 if ((blur_traits & CopyPixelTrait) != 0)
3632 {
3633 SetPixelChannel(blur_image,channel,p[center+i],q);
3634 continue;
3635 }
3636 k=kernel;
3637 pixel=0.0;
3638 pixels=p;
3639 luminance_pixels=l;
3640 gamma=0.0;
3641 if ((blur_traits & BlendPixelTrait) == 0)
3642 {
3643 for (v=0; v < (ssize_t) width; v++)
3644 {
3645 for (u=0; u < (ssize_t) width; u++)
3646 {
3647 contrast=GetPixelIntensity(luminance_image,luminance_pixels)-
3648 intensity;
3649 if (fabs(contrast) < threshold)
3650 {
3651 pixel+=(*k)*(double) pixels[i];
3652 gamma+=(*k);
3653 }
3654 k++;
3655 pixels+=(ptrdiff_t) GetPixelChannels(image);
3656 luminance_pixels+=(ptrdiff_t) GetPixelChannels(luminance_image);
3657 }
3658 pixels+=(ptrdiff_t) GetPixelChannels(image)*image->columns;
3659 luminance_pixels+=(ptrdiff_t) GetPixelChannels(luminance_image)*
3660 luminance_image->columns;
3661 }
3662 if (fabs((double) gamma) < MagickEpsilon)
3663 {
3664 SetPixelChannel(blur_image,channel,p[center+i],q);
3665 continue;
3666 }
3667 gamma=MagickSafeReciprocal(gamma);
3668 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
3669 continue;
3670 }
3671 for (v=0; v < (ssize_t) width; v++)
3672 {
3673 for (u=0; u < (ssize_t) width; u++)
3674 {
3675 contrast=GetPixelIntensity(image,pixels)-intensity;
3676 if (fabs(contrast) < threshold)
3677 {
3678 alpha=QuantumScale*(double) GetPixelAlpha(image,pixels);
3679 pixel+=(*k)*alpha*(double) pixels[i];
3680 gamma+=(*k)*alpha;
3681 }
3682 k++;
3683 pixels+=(ptrdiff_t) GetPixelChannels(image);
3684 luminance_pixels+=(ptrdiff_t) GetPixelChannels(luminance_image);
3685 }
3686 pixels+=(ptrdiff_t) GetPixelChannels(image)*image->columns;
3687 luminance_pixels+=(ptrdiff_t) GetPixelChannels(luminance_image)*
3688 luminance_image->columns;
3689 }
3690 if (fabs((double) gamma) < MagickEpsilon)
3691 {
3692 SetPixelChannel(blur_image,channel,p[center+i],q);
3693 continue;
3694 }
3695 gamma=MagickSafeReciprocal(gamma);
3696 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
3697 }
3698 p+=(ptrdiff_t) GetPixelChannels(image);
3699 l+=(ptrdiff_t) GetPixelChannels(luminance_image);
3700 q+=(ptrdiff_t) GetPixelChannels(blur_image);
3701 }
3702 sync=SyncCacheViewAuthenticPixels(blur_view,exception);
3703 if (sync == MagickFalse)
3704 status=MagickFalse;
3705 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3706 {
3707 MagickBooleanType
3708 proceed;
3709
3710#if defined(MAGICKCORE_OPENMP_SUPPORT)
3711 #pragma omp atomic
3712#endif
3713 progress++;
3714 proceed=SetImageProgress(image,SelectiveBlurImageTag,progress,
3715 image->rows);
3716 if (proceed == MagickFalse)
3717 status=MagickFalse;
3718 }
3719 }
3720 blur_image->type=image->type;
3721 blur_view=DestroyCacheView(blur_view);
3722 luminance_view=DestroyCacheView(luminance_view);
3723 image_view=DestroyCacheView(image_view);
3724 luminance_image=DestroyImage(luminance_image);
3725 kernel=(MagickRealType *) RelinquishAlignedMemory(kernel);
3726 if (status == MagickFalse)
3727 blur_image=DestroyImage(blur_image);
3728 return(blur_image);
3729}
3730␌
3731/*
3732%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3733% %
3734% %
3735% %
3736% S h a d e I m a g e %
3737% %
3738% %
3739% %
3740%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3741%
3742% ShadeImage() shines a distant light on an image to create a
3743% three-dimensional effect. You control the positioning of the light with
3744% azimuth and elevation; azimuth is measured in degrees off the x axis
3745% and elevation is measured in pixels above the Z axis.
3746%
3747% The format of the ShadeImage method is:
3748%
3749% Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3750% const double azimuth,const double elevation,ExceptionInfo *exception)
3751%
3752% A description of each parameter follows:
3753%
3754% o image: the image.
3755%
3756% o gray: A value other than zero shades the intensity of each pixel.
3757%
3758% o azimuth, elevation: Define the light source direction.
3759%
3760% o exception: return any errors or warnings in this structure.
3761%
3762*/
3763MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3764 const double azimuth,const double elevation,ExceptionInfo *exception)
3765{
3766#define GetShadeIntensity(image,pixel) \
3767 ClampPixel(GetPixelIntensity((image),(pixel)))
3768#define ShadeImageTag "Shade/Image"
3769
3770 CacheView
3771 *image_view,
3772 *shade_view;
3773
3774 Image
3775 *linear_image,
3776 *shade_image;
3777
3778 MagickBooleanType
3779 status;
3780
3781 MagickOffsetType
3782 progress;
3783
3784 PrimaryInfo
3785 light;
3786
3787 ssize_t
3788 y;
3789
3790 /*
3791 Initialize shaded image attributes.
3792 */
3793 assert(image != (const Image *) NULL);
3794 assert(image->signature == MagickCoreSignature);
3795 assert(exception != (ExceptionInfo *) NULL);
3796 assert(exception->signature == MagickCoreSignature);
3797 if (IsEventLogging() != MagickFalse)
3798 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3799 linear_image=CloneImage(image,0,0,MagickTrue,exception);
3800 shade_image=CloneImage(image,0,0,MagickTrue,exception);
3801 if ((linear_image == (Image *) NULL) || (shade_image == (Image *) NULL))
3802 {
3803 if (linear_image != (Image *) NULL)
3804 linear_image=DestroyImage(linear_image);
3805 if (shade_image != (Image *) NULL)
3806 shade_image=DestroyImage(shade_image);
3807 return((Image *) NULL);
3808 }
3809 if (SetImageStorageClass(shade_image,DirectClass,exception) == MagickFalse)
3810 {
3811 linear_image=DestroyImage(linear_image);
3812 shade_image=DestroyImage(shade_image);
3813 return((Image *) NULL);
3814 }
3815 /*
3816 Compute the light vector.
3817 */
3818 light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))*
3819 cos(DegreesToRadians(elevation));
3820 light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))*
3821 cos(DegreesToRadians(elevation));
3822 light.z=(double) QuantumRange*sin(DegreesToRadians(elevation));
3823 /*
3824 Shade image.
3825 */
3826 status=MagickTrue;
3827 progress=0;
3828 image_view=AcquireVirtualCacheView(linear_image,exception);
3829 shade_view=AcquireAuthenticCacheView(shade_image,exception);
3830#if defined(MAGICKCORE_OPENMP_SUPPORT)
3831 #pragma omp parallel for schedule(static) shared(progress,status) \
3832 magick_number_threads(linear_image,shade_image,linear_image->rows,1)
3833#endif
3834 for (y=0; y < (ssize_t) linear_image->rows; y++)
3835 {
3836 double
3837 distance,
3838 normal_distance,
3839 shade;
3840
3841 PrimaryInfo
3842 normal;
3843
3844 const Quantum
3845 *magick_restrict center,
3846 *magick_restrict p,
3847 *magick_restrict post,
3848 *magick_restrict pre;
3849
3850 Quantum
3851 *magick_restrict q;
3852
3853 ssize_t
3854 x;
3855
3856 if (status == MagickFalse)
3857 continue;
3858 p=GetCacheViewVirtualPixels(image_view,-1,y-1,linear_image->columns+2,3,
3859 exception);
3860 q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1,
3861 exception);
3862 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
3863 {
3864 status=MagickFalse;
3865 continue;
3866 }
3867 /*
3868 Shade this row of pixels.
3869 */
3870 normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */
3871 for (x=0; x < (ssize_t) linear_image->columns; x++)
3872 {
3873 ssize_t
3874 i;
3875
3876 /*
3877 Determine the surface normal and compute shading.
3878 */
3879 pre=p+GetPixelChannels(linear_image);
3880 center=pre+(linear_image->columns+2)*GetPixelChannels(linear_image);
3881 post=center+(linear_image->columns+2)*GetPixelChannels(linear_image);
3882 normal.x=(double) (
3883 GetShadeIntensity(linear_image,pre-GetPixelChannels(linear_image))+
3884 GetShadeIntensity(linear_image,center-GetPixelChannels(linear_image))+
3885 GetShadeIntensity(linear_image,post-GetPixelChannels(linear_image))-
3886 GetShadeIntensity(linear_image,pre+GetPixelChannels(linear_image))-
3887 GetShadeIntensity(linear_image,center+GetPixelChannels(linear_image))-
3888 GetShadeIntensity(linear_image,post+GetPixelChannels(linear_image)));
3889 normal.y=(double) (
3890 GetShadeIntensity(linear_image,post-GetPixelChannels(linear_image))+
3891 GetShadeIntensity(linear_image,post)+
3892 GetShadeIntensity(linear_image,post+GetPixelChannels(linear_image))-
3893 GetShadeIntensity(linear_image,pre-GetPixelChannels(linear_image))-
3894 GetShadeIntensity(linear_image,pre)-
3895 GetShadeIntensity(linear_image,pre+GetPixelChannels(linear_image)));
3896 if ((fabs(normal.x) <= MagickEpsilon) &&
3897 (fabs(normal.y) <= MagickEpsilon))
3898 shade=light.z;
3899 else
3900 {
3901 shade=0.0;
3902 distance=normal.x*light.x+normal.y*light.y+normal.z*light.z;
3903 if (distance > MagickEpsilon)
3904 {
3905 normal_distance=normal.x*normal.x+normal.y*normal.y+
3906 normal.z*normal.z;
3907 if (normal_distance > (MagickEpsilon*MagickEpsilon))
3908 shade=distance/sqrt((double) normal_distance);
3909 }
3910 }
3911 for (i=0; i < (ssize_t) GetPixelChannels(linear_image); i++)
3912 {
3913 PixelChannel
3914 channel;
3915
3916 PixelTrait
3917 shade_traits,
3918 traits;
3919
3920 channel=GetPixelChannelChannel(linear_image,i);
3921 traits=GetPixelChannelTraits(linear_image,channel);
3922 shade_traits=GetPixelChannelTraits(shade_image,channel);
3923 if ((traits == UndefinedPixelTrait) ||
3924 (shade_traits == UndefinedPixelTrait))
3925 continue;
3926 if ((shade_traits & CopyPixelTrait) != 0)
3927 {
3928 SetPixelChannel(shade_image,channel,center[i],q);
3929 continue;
3930 }
3931 if ((traits & UpdatePixelTrait) == 0)
3932 {
3933 SetPixelChannel(shade_image,channel,center[i],q);
3934 continue;
3935 }
3936 if (gray != MagickFalse)
3937 {
3938 SetPixelChannel(shade_image,channel,ClampToQuantum(shade),q);
3939 continue;
3940 }
3941 SetPixelChannel(shade_image,channel,ClampToQuantum(QuantumScale*
3942 shade*(double) center[i]),q);
3943 }
3944 p+=(ptrdiff_t) GetPixelChannels(linear_image);
3945 q+=(ptrdiff_t) GetPixelChannels(shade_image);
3946 }
3947 if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
3948 status=MagickFalse;
3949 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3950 {
3951 MagickBooleanType
3952 proceed;
3953
3954#if defined(MAGICKCORE_OPENMP_SUPPORT)
3955 #pragma omp atomic
3956#endif
3957 progress++;
3958 proceed=SetImageProgress(image,ShadeImageTag,progress,image->rows);
3959 if (proceed == MagickFalse)
3960 status=MagickFalse;
3961 }
3962 }
3963 shade_view=DestroyCacheView(shade_view);
3964 image_view=DestroyCacheView(image_view);
3965 linear_image=DestroyImage(linear_image);
3966 if (status == MagickFalse)
3967 shade_image=DestroyImage(shade_image);
3968 return(shade_image);
3969}
3970␌
3971/*
3972%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3973% %
3974% %
3975% %
3976% S h a r p e n I m a g e %
3977% %
3978% %
3979% %
3980%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3981%
3982% SharpenImage() sharpens the image. We convolve the image with a Gaussian
3983% operator of the given radius and standard deviation (sigma). For
3984% reasonable results, radius should be larger than sigma. Use a radius of 0
3985% and SharpenImage() selects a suitable radius for you.
3986%
3987% Using a separable kernel would be faster, but the negative weights cancel
3988% out on the corners of the kernel producing often undesirable ringing in the
3989% filtered result; this can be avoided by using a 2D gaussian shaped image
3990% sharpening kernel instead.
3991%
3992% The format of the SharpenImage method is:
3993%
3994% Image *SharpenImage(const Image *image,const double radius,
3995% const double sigma,ExceptionInfo *exception)
3996%
3997% A description of each parameter follows:
3998%
3999% o image: the image.
4000%
4001% o radius: the radius of the Gaussian, in pixels, not counting the center
4002% pixel.
4003%
4004% o sigma: the standard deviation of the Laplacian, in pixels.
4005%
4006% o exception: return any errors or warnings in this structure.
4007%
4008*/
4009MagickExport Image *SharpenImage(const Image *image,const double radius,
4010 const double sigma,ExceptionInfo *exception)
4011{
4012 double
4013 gamma,
4014 normalize;
4015
4016 Image
4017 *sharp_image;
4018
4019 KernelInfo
4020 *kernel_info;
4021
4022 ssize_t
4023 i;
4024
4025 size_t
4026 width;
4027
4028 ssize_t
4029 j,
4030 u,
4031 v;
4032
4033 assert(image != (const Image *) NULL);
4034 assert(image->signature == MagickCoreSignature);
4035 assert(exception != (ExceptionInfo *) NULL);
4036 assert(exception->signature == MagickCoreSignature);
4037 if (IsEventLogging() != MagickFalse)
4038 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4039 width=GetOptimalKernelWidth2D(radius,sigma);
4040 kernel_info=AcquireKernelInfo((const char *) NULL,exception);
4041 if (kernel_info == (KernelInfo *) NULL)
4042 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
4043 (void) memset(kernel_info,0,sizeof(*kernel_info));
4044 kernel_info->width=width;
4045 kernel_info->height=width;
4046 kernel_info->x=(ssize_t) (width-1)/2;
4047 kernel_info->y=(ssize_t) (width-1)/2;
4048 kernel_info->signature=MagickCoreSignature;
4049 kernel_info->values=(MagickRealType *) MagickAssumeAligned(
4050 AcquireAlignedMemory(kernel_info->width,kernel_info->height*
4051 sizeof(*kernel_info->values)));
4052 if (kernel_info->values == (MagickRealType *) NULL)
4053 {
4054 kernel_info=DestroyKernelInfo(kernel_info);
4055 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
4056 }
4057 normalize=0.0;
4058 j=(ssize_t) (kernel_info->width-1)/2;
4059 i=0;
4060 for (v=(-j); v <= j; v++)
4061 {
4062 for (u=(-j); u <= j; u++)
4063 {
4064 kernel_info->values[i]=(MagickRealType) (-exp(-((double) u*u+v*v)/(2.0*
4065 MagickSigma*MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
4066 normalize+=kernel_info->values[i];
4067 i++;
4068 }
4069 }
4070 kernel_info->values[i/2]=(double) ((-2.0)*normalize);
4071 normalize=0.0;
4072 for (i=0; i < (ssize_t) (kernel_info->width*kernel_info->height); i++)
4073 normalize+=kernel_info->values[i];
4074 gamma=MagickSafeReciprocal(normalize);
4075 for (i=0; i < (ssize_t) (kernel_info->width*kernel_info->height); i++)
4076 kernel_info->values[i]*=gamma;
4077 sharp_image=ConvolveImage(image,kernel_info,exception);
4078 kernel_info=DestroyKernelInfo(kernel_info);
4079 return(sharp_image);
4080}
4081␌
4082/*
4083%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4084% %
4085% %
4086% %
4087% S p r e a d I m a g e %
4088% %
4089% %
4090% %
4091%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4092%
4093% SpreadImage() is a special effects method that randomly displaces each
4094% pixel in a square area defined by the radius parameter.
4095%
4096% The format of the SpreadImage method is:
4097%
4098% Image *SpreadImage(const Image *image,
4099% const PixelInterpolateMethod method,const double radius,
4100% ExceptionInfo *exception)
4101%
4102% A description of each parameter follows:
4103%
4104% o image: the image.
4105%
4106% o method: interpolation method.
4107%
4108% o radius: choose a random pixel in a neighborhood of this extent.
4109%
4110% o exception: return any errors or warnings in this structure.
4111%
4112*/
4113MagickExport Image *SpreadImage(const Image *image,
4114 const PixelInterpolateMethod method,const double radius,
4115 ExceptionInfo *exception)
4116{
4117#define SpreadImageTag "Spread/Image"
4118
4119 CacheView
4120 *image_view,
4121 *spread_view;
4122
4123 Image
4124 *spread_image;
4125
4126 MagickBooleanType
4127 status;
4128
4129 MagickOffsetType
4130 progress;
4131
4132 RandomInfo
4133 **magick_restrict random_info;
4134
4135 size_t
4136 width;
4137
4138 ssize_t
4139 y;
4140
4141#if defined(MAGICKCORE_OPENMP_SUPPORT)
4142 unsigned long
4143 key;
4144#endif
4145
4146 /*
4147 Initialize spread image attributes.
4148 */
4149 assert(image != (Image *) NULL);
4150 assert(image->signature == MagickCoreSignature);
4151 assert(exception != (ExceptionInfo *) NULL);
4152 assert(exception->signature == MagickCoreSignature);
4153 if (IsEventLogging() != MagickFalse)
4154 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4155 spread_image=CloneImage(image,0,0,MagickTrue,exception);
4156 if (spread_image == (Image *) NULL)
4157 return((Image *) NULL);
4158 if (SetImageStorageClass(spread_image,DirectClass,exception) == MagickFalse)
4159 {
4160 spread_image=DestroyImage(spread_image);
4161 return((Image *) NULL);
4162 }
4163 /*
4164 Spread image.
4165 */
4166 status=MagickTrue;
4167 progress=0;
4168 width=GetOptimalKernelWidth1D(radius,0.5);
4169 random_info=AcquireRandomInfoTLS();
4170 image_view=AcquireVirtualCacheView(image,exception);
4171 spread_view=AcquireAuthenticCacheView(spread_image,exception);
4172#if defined(MAGICKCORE_OPENMP_SUPPORT)
4173 key=GetRandomSecretKey(random_info[0]);
4174 #pragma omp parallel for schedule(static) shared(progress,status) \
4175 magick_number_threads(image,spread_image,image->rows,key == ~0UL)
4176#endif
4177 for (y=0; y < (ssize_t) image->rows; y++)
4178 {
4179 const int
4180 id = GetOpenMPThreadId();
4181
4182 Quantum
4183 *magick_restrict q;
4184
4185 ssize_t
4186 x;
4187
4188 if (status == MagickFalse)
4189 continue;
4190 q=QueueCacheViewAuthenticPixels(spread_view,0,y,spread_image->columns,1,
4191 exception);
4192 if (q == (Quantum *) NULL)
4193 {
4194 status=MagickFalse;
4195 continue;
4196 }
4197 for (x=0; x < (ssize_t) image->columns; x++)
4198 {
4199 PointInfo
4200 point;
4201
4202 point.x=GetPseudoRandomValue(random_info[id]);
4203 point.y=GetPseudoRandomValue(random_info[id]);
4204 status=InterpolatePixelChannels(image,image_view,spread_image,method,
4205 (double) x+width*(point.x-0.5),(double) y+width*(point.y-0.5),q,
4206 exception);
4207 if (status == MagickFalse)
4208 break;
4209 q+=(ptrdiff_t) GetPixelChannels(spread_image);
4210 }
4211 if (SyncCacheViewAuthenticPixels(spread_view,exception) == MagickFalse)
4212 status=MagickFalse;
4213 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4214 {
4215 MagickBooleanType
4216 proceed;
4217
4218#if defined(MAGICKCORE_OPENMP_SUPPORT)
4219 #pragma omp atomic
4220#endif
4221 progress++;
4222 proceed=SetImageProgress(image,SpreadImageTag,progress,image->rows);
4223 if (proceed == MagickFalse)
4224 status=MagickFalse;
4225 }
4226 }
4227 spread_view=DestroyCacheView(spread_view);
4228 image_view=DestroyCacheView(image_view);
4229 random_info=DestroyRandomInfoTLS(random_info);
4230 if (status == MagickFalse)
4231 spread_image=DestroyImage(spread_image);
4232 return(spread_image);
4233}
4234␌
4235/*
4236%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4237% %
4238% %
4239% %
4240% U n s h a r p M a s k I m a g e %
4241% %
4242% %
4243% %
4244%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4245%
4246% UnsharpMaskImage() sharpens one or more image channels. We convolve the
4247% image with a Gaussian operator of the given radius and standard deviation
4248% (sigma). For reasonable results, radius should be larger than sigma. Use a
4249% radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
4250%
4251% The format of the UnsharpMaskImage method is:
4252%
4253% Image *UnsharpMaskImage(const Image *image,const double radius,
4254% const double sigma,const double amount,const double threshold,
4255% ExceptionInfo *exception)
4256%
4257% A description of each parameter follows:
4258%
4259% o image: the image.
4260%
4261% o radius: the radius of the Gaussian, in pixels, not counting the center
4262% pixel.
4263%
4264% o sigma: the standard deviation of the Gaussian, in pixels.
4265%
4266% o gain: the percentage of the difference between the original and the
4267% blur image that is added back into the original.
4268%
4269% o threshold: the threshold in pixels needed to apply the difference gain.
4270%
4271% o exception: return any errors or warnings in this structure.
4272%
4273*/
4274MagickExport Image *UnsharpMaskImage(const Image *image,const double radius,
4275 const double sigma,const double gain,const double threshold,
4276 ExceptionInfo *exception)
4277{
4278#define SharpenImageTag "Sharpen/Image"
4279
4280 CacheView
4281 *image_view,
4282 *unsharp_view;
4283
4284 Image
4285 *unsharp_image;
4286
4287 MagickBooleanType
4288 status;
4289
4290 MagickOffsetType
4291 progress;
4292
4293 double
4294 quantum_threshold;
4295
4296 ssize_t
4297 y;
4298
4299 assert(image != (const Image *) NULL);
4300 assert(image->signature == MagickCoreSignature);
4301 assert(exception != (ExceptionInfo *) NULL);
4302 assert(exception->signature == MagickCoreSignature);
4303 if (IsEventLogging() != MagickFalse)
4304 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4305/* This kernel appears to be broken.
4306#if defined(MAGICKCORE_OPENCL_SUPPORT)
4307 unsharp_image=AccelerateUnsharpMaskImage(image,radius,sigma,gain,threshold,
4308 exception);
4309 if (unsharp_image != (Image *) NULL)
4310 return(unsharp_image);
4311#endif
4312*/
4313 unsharp_image=BlurImage(image,radius,sigma,exception);
4314 if (unsharp_image == (Image *) NULL)
4315 return((Image *) NULL);
4316 quantum_threshold=(double) QuantumRange*threshold;
4317 /*
4318 Unsharp-mask image.
4319 */
4320 status=MagickTrue;
4321 progress=0;
4322 image_view=AcquireVirtualCacheView(image,exception);
4323 unsharp_view=AcquireAuthenticCacheView(unsharp_image,exception);
4324#if defined(MAGICKCORE_OPENMP_SUPPORT)
4325 #pragma omp parallel for schedule(static) shared(progress,status) \
4326 magick_number_threads(image,unsharp_image,image->rows,1)
4327#endif
4328 for (y=0; y < (ssize_t) image->rows; y++)
4329 {
4330 const Quantum
4331 *magick_restrict p;
4332
4333 Quantum
4334 *magick_restrict q;
4335
4336 ssize_t
4337 x;
4338
4339 if (status == MagickFalse)
4340 continue;
4341 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
4342 q=GetCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
4343 exception);
4344 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
4345 {
4346 status=MagickFalse;
4347 continue;
4348 }
4349 for (x=0; x < (ssize_t) image->columns; x++)
4350 {
4351 ssize_t
4352 i;
4353
4354 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4355 {
4356 double
4357 pixel;
4358
4359 PixelChannel
4360 channel;
4361
4362 PixelTrait
4363 traits,
4364 unsharp_traits;
4365
4366 channel=GetPixelChannelChannel(image,i);
4367 traits=GetPixelChannelTraits(image,channel);
4368 unsharp_traits=GetPixelChannelTraits(unsharp_image,channel);
4369 if ((traits == UndefinedPixelTrait) ||
4370 (unsharp_traits == UndefinedPixelTrait))
4371 continue;
4372 if ((unsharp_traits & CopyPixelTrait) != 0)
4373 {
4374 SetPixelChannel(unsharp_image,channel,p[i],q);
4375 continue;
4376 }
4377 pixel=(double) p[i]-(double) GetPixelChannel(unsharp_image,channel,q);
4378 if (fabs(2.0*pixel) < quantum_threshold)
4379 pixel=(double) p[i];
4380 else
4381 pixel=(double) p[i]+gain*pixel;
4382 SetPixelChannel(unsharp_image,channel,ClampToQuantum(pixel),q);
4383 }
4384 p+=(ptrdiff_t) GetPixelChannels(image);
4385 q+=(ptrdiff_t) GetPixelChannels(unsharp_image);
4386 }
4387 if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
4388 status=MagickFalse;
4389 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4390 {
4391 MagickBooleanType
4392 proceed;
4393
4394#if defined(MAGICKCORE_OPENMP_SUPPORT)
4395 #pragma omp atomic
4396#endif
4397 progress++;
4398 proceed=SetImageProgress(image,SharpenImageTag,progress,image->rows);
4399 if (proceed == MagickFalse)
4400 status=MagickFalse;
4401 }
4402 }
4403 unsharp_image->type=image->type;
4404 unsharp_view=DestroyCacheView(unsharp_view);
4405 image_view=DestroyCacheView(image_view);
4406 if (status == MagickFalse)
4407 unsharp_image=DestroyImage(unsharp_image);
4408 return(unsharp_image);
4409}