MagickCore 7.1.2-19
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
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
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
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:%.20gx%.20g;blur:%.20gx%.20g+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
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
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) GetPixelIntensity(image,r))-
1045 (double) ScaleQuantumToChar((const Quantum) GetPixelIntensity(image,p));
1046 if ((intensity >= -MaxIntensity) && (intensity <= MaxIntensity))
1047 weights[id][n]=intensity_gaussian[(ssize_t) intensity+MaxIntensity]*
1048 spatial_gaussian[n];
1049 else
1050 weights[id][n]=BlurGaussian(intensity,intensity_sigma)*
1051 BlurGaussian(BlurDistance(x,y,x+u-mid.x,y+v-mid.y),spatial_sigma);
1052 n++;
1053 }
1054 }
1055 for (i=0; i < (ssize_t) GetPixelChannels(blur_image); i++)
1056 {
1057 PixelChannel
1058 channel;
1059
1060 PixelTrait
1061 blur_traits,
1062 traits;
1063
1064 channel=GetPixelChannelChannel(image,i);
1065 traits=GetPixelChannelTraits(image,channel);
1066 blur_traits=GetPixelChannelTraits(blur_image,channel);
1067 if ((traits == UndefinedPixelTrait) ||
1068 (blur_traits == UndefinedPixelTrait))
1069 continue;
1070 if ((blur_traits & CopyPixelTrait) != 0)
1071 {
1072 SetPixelChannel(blur_image,channel,p[i],q);
1073 continue;
1074 }
1075 pixel=0.0;
1076 gamma=0.0;
1077 n=0;
1078 if ((blur_traits & BlendPixelTrait) == 0)
1079 {
1080 /*
1081 No alpha blending.
1082 */
1083 for (v=0; v < (ssize_t) MagickMax(height,1); v++)
1084 {
1085 for (u=0; u < (ssize_t) MagickMax(width,1); u++)
1086 {
1087 r=p-(ssize_t) (GetPixelChannels(image)*MagickMax(width,1)*(size_t)
1088 (mid.y-v)+GetPixelChannels(image)*(size_t) (mid.x-u));
1089 pixel+=weights[id][n]*(double) r[i];
1090 gamma+=weights[id][n];
1091 n++;
1092 }
1093 }
1094 SetPixelChannel(blur_image,channel,ClampToQuantum(
1095 MagickSafeReciprocal(gamma)*pixel),q);
1096 continue;
1097 }
1098 /*
1099 Alpha blending.
1100 */
1101 for (v=0; v < (ssize_t) MagickMax(height,1); v++)
1102 {
1103 for (u=0; u < (ssize_t) MagickMax(width,1); u++)
1104 {
1105 double
1106 alpha,
1107 beta;
1108
1109 r=p-(ssize_t) (GetPixelChannels(image)*MagickMax(width,1)*(size_t) (mid.y-v)+
1110 GetPixelChannels(image)*(size_t) (mid.x-u));
1111 alpha=(double) (QuantumScale*(double) GetPixelAlpha(image,p));
1112 beta=(double) (QuantumScale*(double) GetPixelAlpha(image,r));
1113 pixel+=weights[id][n]*(double) r[i];
1114 gamma+=weights[id][n]*alpha*beta;
1115 n++;
1116 }
1117 }
1118 SetPixelChannel(blur_image,channel,ClampToQuantum(
1119 MagickSafeReciprocal(gamma)*pixel),q);
1120 }
1121 q+=(ptrdiff_t) GetPixelChannels(blur_image);
1122 }
1123 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1124 status=MagickFalse;
1125 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1126 {
1127 MagickBooleanType
1128 proceed;
1129
1130#if defined(MAGICKCORE_OPENMP_SUPPORT)
1131 #pragma omp atomic
1132#endif
1133 progress++;
1134 proceed=SetImageProgress(image,BilateralBlurImageTag,progress,
1135 image->rows);
1136 if (proceed == MagickFalse)
1137 status=MagickFalse;
1138 }
1139 }
1140 blur_image->type=image->type;
1141 blur_view=DestroyCacheView(blur_view);
1142 image_view=DestroyCacheView(image_view);
1143 weights=DestroyBilateralTLS(number_threads,weights);
1144 if (status == MagickFalse)
1145 blur_image=DestroyImage(blur_image);
1146 return(blur_image);
1147}
1148
1149/*
1150%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1151% %
1152% %
1153% %
1154% C o n v o l v e I m a g e %
1155% %
1156% %
1157% %
1158%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1159%
1160% ConvolveImage() applies a custom convolution kernel to the image.
1161%
1162% The format of the ConvolveImage method is:
1163%
1164% Image *ConvolveImage(const Image *image,const KernelInfo *kernel,
1165% ExceptionInfo *exception)
1166%
1167% A description of each parameter follows:
1168%
1169% o image: the image.
1170%
1171% o kernel: the filtering kernel.
1172%
1173% o exception: return any errors or warnings in this structure.
1174%
1175*/
1176MagickExport Image *ConvolveImage(const Image *image,
1177 const KernelInfo *kernel_info,ExceptionInfo *exception)
1178{
1179 Image
1180 *convolve_image;
1181
1182 convolve_image=MorphologyImage(image,ConvolveMorphology,1,kernel_info,
1183 exception);
1184 return(convolve_image);
1185}
1186
1187/*
1188%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1189% %
1190% %
1191% %
1192% D e s p e c k l e I m a g e %
1193% %
1194% %
1195% %
1196%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1197%
1198% DespeckleImage() reduces the speckle noise in an image while preserving the
1199% edges of the original image. A speckle removing filter uses a complementary
1200% hulling technique (raising pixels that are darker than their surrounding
1201% neighbors, then complementarily lowering pixels that are brighter than their
1202% surrounding neighbors) to reduce the speckle index of that image (reference
1203% Crimmins speckle removal).
1204%
1205% The format of the DespeckleImage method is:
1206%
1207% Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1208%
1209% A description of each parameter follows:
1210%
1211% o image: the image.
1212%
1213% o exception: return any errors or warnings in this structure.
1214%
1215*/
1216
1217static void Hull(const Image *image,const ssize_t x_offset,
1218 const ssize_t y_offset,const size_t columns,const size_t rows,
1219 const int polarity,Quantum *magick_restrict f,Quantum *magick_restrict g)
1220{
1221 Quantum
1222 *p,
1223 *q,
1224 *r,
1225 *s;
1226
1227 ssize_t
1228 y;
1229
1230 assert(image != (const Image *) NULL);
1231 assert(image->signature == MagickCoreSignature);
1232 assert(f != (Quantum *) NULL);
1233 assert(g != (Quantum *) NULL);
1234 assert(columns <= (size_t) (MAGICK_SSIZE_MAX-2));
1235 if (IsEventLogging() != MagickFalse)
1236 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1237 p=f+(ptrdiff_t) (columns+2);
1238 q=g+(ptrdiff_t) (columns+2);
1239 r=p+(ptrdiff_t) (y_offset*((ssize_t) columns+2)+x_offset);
1240#if defined(MAGICKCORE_OPENMP_SUPPORT)
1241 #pragma omp parallel for schedule(static) \
1242 magick_number_threads(image,image,rows,2)
1243#endif
1244 for (y=0; y < (ssize_t) rows; y++)
1245 {
1246 MagickRealType
1247 v;
1248
1249 ssize_t
1250 i,
1251 x;
1252
1253 i=(2*y+1)+y*(ssize_t) columns;
1254 if (polarity > 0)
1255 for (x=0; x < (ssize_t) columns; x++)
1256 {
1257 v=(MagickRealType) p[i];
1258 if ((MagickRealType) r[i] >= (v+(double) ScaleCharToQuantum(2)))
1259 v+=(double) ScaleCharToQuantum(1);
1260 q[i]=(Quantum) v;
1261 i++;
1262 }
1263 else
1264 for (x=0; x < (ssize_t) columns; x++)
1265 {
1266 v=(MagickRealType) p[i];
1267 if ((MagickRealType) r[i] <= (v-(double) ScaleCharToQuantum(2)))
1268 v-=(double) ScaleCharToQuantum(1);
1269 q[i]=(Quantum) v;
1270 i++;
1271 }
1272 }
1273 p=f+(ptrdiff_t) (columns+2);
1274 q=g+(ptrdiff_t) (columns+2);
1275 r=q+(ptrdiff_t) (y_offset*((ssize_t) columns+2)+x_offset);
1276 s=q-(ptrdiff_t) (y_offset*((ssize_t) columns+2)+x_offset);
1277#if defined(MAGICKCORE_OPENMP_SUPPORT)
1278 #pragma omp parallel for schedule(static) \
1279 magick_number_threads(image,image,rows,2)
1280#endif
1281 for (y=0; y < (ssize_t) rows; y++)
1282 {
1283 ssize_t
1284 i,
1285 x;
1286
1287 MagickRealType
1288 v;
1289
1290 i=(2*y+1)+y*(ssize_t) columns;
1291 if (polarity > 0)
1292 for (x=0; x < (ssize_t) columns; x++)
1293 {
1294 v=(MagickRealType) q[i];
1295 if (((MagickRealType) s[i] >= (v+(double) ScaleCharToQuantum(2))) &&
1296 ((MagickRealType) r[i] > v))
1297 v+=(double) ScaleCharToQuantum(1);
1298 p[i]=(Quantum) v;
1299 i++;
1300 }
1301 else
1302 for (x=0; x < (ssize_t) columns; x++)
1303 {
1304 v=(MagickRealType) q[i];
1305 if (((MagickRealType) s[i] <= (v-(double) ScaleCharToQuantum(2))) &&
1306 ((MagickRealType) r[i] < v))
1307 v-=(double) ScaleCharToQuantum(1);
1308 p[i]=(Quantum) v;
1309 i++;
1310 }
1311 }
1312}
1313
1314MagickExport Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1315{
1316#define DespeckleImageTag "Despeckle/Image"
1317
1318 CacheView
1319 *despeckle_view,
1320 *image_view;
1321
1322 Image
1323 *despeckle_image;
1324
1325 MagickBooleanType
1326 status;
1327
1329 *buffer_info,
1330 *pixel_info;
1331
1332 Quantum
1333 *magick_restrict buffer,
1334 *magick_restrict pixels;
1335
1336 size_t
1337 length;
1338
1339 ssize_t
1340 i;
1341
1342 static const ssize_t
1343 X[4] = {0, 1, 1,-1},
1344 Y[4] = {1, 0, 1, 1};
1345
1346 /*
1347 Allocate despeckled image.
1348 */
1349 assert(image != (const Image *) NULL);
1350 assert(image->signature == MagickCoreSignature);
1351 assert(exception != (ExceptionInfo *) NULL);
1352 assert(exception->signature == MagickCoreSignature);
1353 if (IsEventLogging() != MagickFalse)
1354 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1355#if defined(MAGICKCORE_OPENCL_SUPPORT)
1356 despeckle_image=AccelerateDespeckleImage(image,exception);
1357 if (despeckle_image != (Image *) NULL)
1358 return(despeckle_image);
1359#endif
1360 despeckle_image=CloneImage(image,0,0,MagickTrue,exception);
1361 if (despeckle_image == (Image *) NULL)
1362 return((Image *) NULL);
1363 status=SetImageStorageClass(despeckle_image,DirectClass,exception);
1364 if (status == MagickFalse)
1365 {
1366 despeckle_image=DestroyImage(despeckle_image);
1367 return((Image *) NULL);
1368 }
1369 /*
1370 Allocate image buffer.
1371 */
1372 if ((image->columns > (MAGICK_SIZE_MAX-2)) ||
1373 (image->rows > (MAGICK_SIZE_MAX-2)))
1374 {
1375 despeckle_image=DestroyImage(despeckle_image);
1376 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1377 }
1378 length=(image->columns+2)*(image->rows+2);
1379 pixel_info=AcquireVirtualMemory(length,sizeof(*pixels));
1380 buffer_info=AcquireVirtualMemory(length,sizeof(*buffer));
1381 if ((pixel_info == (MemoryInfo *) NULL) ||
1382 (buffer_info == (MemoryInfo *) NULL))
1383 {
1384 if (buffer_info != (MemoryInfo *) NULL)
1385 buffer_info=RelinquishVirtualMemory(buffer_info);
1386 if (pixel_info != (MemoryInfo *) NULL)
1387 pixel_info=RelinquishVirtualMemory(pixel_info);
1388 despeckle_image=DestroyImage(despeckle_image);
1389 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1390 }
1391 pixels=(Quantum *) GetVirtualMemoryBlob(pixel_info);
1392 buffer=(Quantum *) GetVirtualMemoryBlob(buffer_info);
1393 /*
1394 Reduce speckle in the image.
1395 */
1396 status=MagickTrue;
1397 image_view=AcquireVirtualCacheView(image,exception);
1398 despeckle_view=AcquireAuthenticCacheView(despeckle_image,exception);
1399 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1400 {
1401 PixelChannel
1402 channel;
1403
1404 PixelTrait
1405 despeckle_traits,
1406 traits;
1407
1408 ssize_t
1409 k,
1410 x;
1411
1412 ssize_t
1413 j,
1414 y;
1415
1416 if (status == MagickFalse)
1417 continue;
1418 channel=GetPixelChannelChannel(image,i);
1419 traits=GetPixelChannelTraits(image,channel);
1420 despeckle_traits=GetPixelChannelTraits(despeckle_image,channel);
1421 if ((traits == UndefinedPixelTrait) ||
1422 (despeckle_traits == UndefinedPixelTrait))
1423 continue;
1424 if ((despeckle_traits & CopyPixelTrait) != 0)
1425 continue;
1426 (void) memset(pixels,0,length*sizeof(*pixels));
1427 j=(ssize_t) image->columns+2;
1428 for (y=0; y < (ssize_t) image->rows; y++)
1429 {
1430 const Quantum
1431 *magick_restrict p;
1432
1433 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1434 if (p == (const Quantum *) NULL)
1435 {
1436 status=MagickFalse;
1437 continue;
1438 }
1439 j++;
1440 for (x=0; x < (ssize_t) image->columns; x++)
1441 {
1442 pixels[j++]=p[i];
1443 p+=(ptrdiff_t) GetPixelChannels(image);
1444 }
1445 j++;
1446 }
1447 (void) memset(buffer,0,length*sizeof(*buffer));
1448 for (k=0; k < 4; k++)
1449 {
1450 Hull(image,X[k],Y[k],image->columns,image->rows,1,pixels,buffer);
1451 Hull(image,-X[k],-Y[k],image->columns,image->rows,1,pixels,buffer);
1452 Hull(image,-X[k],-Y[k],image->columns,image->rows,-1,pixels,buffer);
1453 Hull(image,X[k],Y[k],image->columns,image->rows,-1,pixels,buffer);
1454 }
1455 j=(ssize_t) image->columns+2;
1456 for (y=0; y < (ssize_t) image->rows; y++)
1457 {
1458 MagickBooleanType
1459 sync;
1460
1461 Quantum
1462 *magick_restrict q;
1463
1464 q=GetCacheViewAuthenticPixels(despeckle_view,0,y,despeckle_image->columns,
1465 1,exception);
1466 if (q == (Quantum *) NULL)
1467 {
1468 status=MagickFalse;
1469 continue;
1470 }
1471 j++;
1472 for (x=0; x < (ssize_t) image->columns; x++)
1473 {
1474 SetPixelChannel(despeckle_image,channel,pixels[j++],q);
1475 q+=(ptrdiff_t) GetPixelChannels(despeckle_image);
1476 }
1477 sync=SyncCacheViewAuthenticPixels(despeckle_view,exception);
1478 if (sync == MagickFalse)
1479 status=MagickFalse;
1480 j++;
1481 }
1482 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1483 {
1484 MagickBooleanType
1485 proceed;
1486
1487 proceed=SetImageProgress(image,DespeckleImageTag,(MagickOffsetType) i,
1488 GetPixelChannels(image));
1489 if (proceed == MagickFalse)
1490 status=MagickFalse;
1491 }
1492 }
1493 despeckle_view=DestroyCacheView(despeckle_view);
1494 image_view=DestroyCacheView(image_view);
1495 buffer_info=RelinquishVirtualMemory(buffer_info);
1496 pixel_info=RelinquishVirtualMemory(pixel_info);
1497 despeckle_image->type=image->type;
1498 if (status == MagickFalse)
1499 despeckle_image=DestroyImage(despeckle_image);
1500 return(despeckle_image);
1501}
1502
1503/*
1504%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1505% %
1506% %
1507% %
1508% E d g e I m a g e %
1509% %
1510% %
1511% %
1512%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1513%
1514% EdgeImage() finds edges in an image. Radius defines the radius of the
1515% convolution filter. Use a radius of 0 and EdgeImage() selects a suitable
1516% radius for you.
1517%
1518% The format of the EdgeImage method is:
1519%
1520% Image *EdgeImage(const Image *image,const double radius,
1521% ExceptionInfo *exception)
1522%
1523% A description of each parameter follows:
1524%
1525% o image: the image.
1526%
1527% o radius: the radius of the pixel neighborhood.
1528%
1529% o exception: return any errors or warnings in this structure.
1530%
1531*/
1532MagickExport Image *EdgeImage(const Image *image,const double radius,
1533 ExceptionInfo *exception)
1534{
1535 Image
1536 *edge_image;
1537
1539 *kernel_info;
1540
1541 ssize_t
1542 i;
1543
1544 size_t
1545 width;
1546
1547 assert(image != (const Image *) NULL);
1548 assert(image->signature == MagickCoreSignature);
1549 assert(exception != (ExceptionInfo *) NULL);
1550 assert(exception->signature == MagickCoreSignature);
1551 if (IsEventLogging() != MagickFalse)
1552 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1553 width=GetOptimalKernelWidth1D(radius,0.5);
1554 kernel_info=AcquireKernelInfo((const char *) NULL,exception);
1555 if (kernel_info == (KernelInfo *) NULL)
1556 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1557 (void) memset(kernel_info,0,sizeof(*kernel_info));
1558 kernel_info->width=width;
1559 kernel_info->height=width;
1560 kernel_info->x=(ssize_t) (kernel_info->width-1)/2;
1561 kernel_info->y=(ssize_t) (kernel_info->height-1)/2;
1562 kernel_info->signature=MagickCoreSignature;
1563 kernel_info->values=(MagickRealType *) MagickAssumeAligned(
1564 AcquireAlignedMemory(kernel_info->width,kernel_info->height*
1565 sizeof(*kernel_info->values)));
1566 if (kernel_info->values == (MagickRealType *) NULL)
1567 {
1568 kernel_info=DestroyKernelInfo(kernel_info);
1569 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1570 }
1571 for (i=0; i < (ssize_t) (kernel_info->width*kernel_info->height); i++)
1572 kernel_info->values[i]=(-1.0);
1573 kernel_info->values[i/2]=(double) kernel_info->width*kernel_info->height-1.0;
1574 edge_image=ConvolveImage(image,kernel_info,exception);
1575 kernel_info=DestroyKernelInfo(kernel_info);
1576 return(edge_image);
1577}
1578
1579/*
1580%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1581% %
1582% %
1583% %
1584% E m b o s s I m a g e %
1585% %
1586% %
1587% %
1588%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1589%
1590% EmbossImage() returns a grayscale image with a three-dimensional effect.
1591% We convolve the image with a Gaussian operator of the given radius and
1592% standard deviation (sigma). For reasonable results, radius should be
1593% larger than sigma. Use a radius of 0 and Emboss() selects a suitable
1594% radius for you.
1595%
1596% The format of the EmbossImage method is:
1597%
1598% Image *EmbossImage(const Image *image,const double radius,
1599% const double sigma,ExceptionInfo *exception)
1600%
1601% A description of each parameter follows:
1602%
1603% o image: the image.
1604%
1605% o radius: the radius of the pixel neighborhood.
1606%
1607% o sigma: the standard deviation of the Gaussian, in pixels.
1608%
1609% o exception: return any errors or warnings in this structure.
1610%
1611*/
1612MagickExport Image *EmbossImage(const Image *image,const double radius,
1613 const double sigma,ExceptionInfo *exception)
1614{
1615 double
1616 gamma,
1617 normalize;
1618
1619 Image
1620 *emboss_image;
1621
1623 *kernel_info;
1624
1625 ssize_t
1626 i;
1627
1628 size_t
1629 width;
1630
1631 ssize_t
1632 j,
1633 k,
1634 u,
1635 v;
1636
1637 assert(image != (const Image *) NULL);
1638 assert(image->signature == MagickCoreSignature);
1639 assert(exception != (ExceptionInfo *) NULL);
1640 assert(exception->signature == MagickCoreSignature);
1641 if (IsEventLogging() != MagickFalse)
1642 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1643 width=GetOptimalKernelWidth1D(radius,sigma);
1644 kernel_info=AcquireKernelInfo((const char *) NULL,exception);
1645 if (kernel_info == (KernelInfo *) NULL)
1646 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1647 kernel_info->width=width;
1648 kernel_info->height=width;
1649 kernel_info->x=(ssize_t) (width-1)/2;
1650 kernel_info->y=(ssize_t) (width-1)/2;
1651 kernel_info->values=(MagickRealType *) MagickAssumeAligned(
1652 AcquireAlignedMemory(kernel_info->width,kernel_info->width*
1653 sizeof(*kernel_info->values)));
1654 if (kernel_info->values == (MagickRealType *) NULL)
1655 {
1656 kernel_info=DestroyKernelInfo(kernel_info);
1657 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1658 }
1659 j=(ssize_t) (kernel_info->width-1)/2;
1660 k=j;
1661 i=0;
1662 for (v=(-j); v <= j; v++)
1663 {
1664 for (u=(-j); u <= j; u++)
1665 {
1666 kernel_info->values[i]=(MagickRealType) (((u < 0) || (v < 0) ? -8.0 :
1667 8.0)*exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
1668 (2.0*MagickPI*MagickSigma*MagickSigma));
1669 if (u != k)
1670 kernel_info->values[i]=0.0;
1671 i++;
1672 }
1673 k--;
1674 }
1675 normalize=0.0;
1676 for (i=0; i < (ssize_t) (kernel_info->width*kernel_info->height); i++)
1677 normalize+=kernel_info->values[i];
1678 gamma=MagickSafeReciprocal(normalize);
1679 for (i=0; i < (ssize_t) (kernel_info->width*kernel_info->height); i++)
1680 kernel_info->values[i]*=gamma;
1681 emboss_image=ConvolveImage(image,kernel_info,exception);
1682 kernel_info=DestroyKernelInfo(kernel_info);
1683 if (emboss_image != (Image *) NULL)
1684 (void) EqualizeImage(emboss_image,exception);
1685 return(emboss_image);
1686}
1687
1688/*
1689%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1690% %
1691% %
1692% %
1693% G a u s s i a n B l u r I m a g e %
1694% %
1695% %
1696% %
1697%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1698%
1699% GaussianBlurImage() blurs an image. We convolve the image with a
1700% Gaussian operator of the given radius and standard deviation (sigma).
1701% For reasonable results, the radius should be larger than sigma. Use a
1702% radius of 0 and GaussianBlurImage() selects a suitable radius for you.
1703%
1704% The format of the GaussianBlurImage method is:
1705%
1706% Image *GaussianBlurImage(const Image *image,const double radius,
1707% const double sigma,ExceptionInfo *exception)
1708%
1709% A description of each parameter follows:
1710%
1711% o image: the image.
1712%
1713% o radius: the radius of the Gaussian, in pixels, not counting the center
1714% pixel.
1715%
1716% o sigma: the standard deviation of the Gaussian, in pixels.
1717%
1718% o exception: return any errors or warnings in this structure.
1719%
1720*/
1721MagickExport Image *GaussianBlurImage(const Image *image,const double radius,
1722 const double sigma,ExceptionInfo *exception)
1723{
1724 char
1725 geometry[MagickPathExtent];
1726
1728 *kernel_info;
1729
1730 Image
1731 *blur_image;
1732
1733 assert(image != (const Image *) NULL);
1734 assert(image->signature == MagickCoreSignature);
1735 assert(exception != (ExceptionInfo *) NULL);
1736 assert(exception->signature == MagickCoreSignature);
1737 if (IsEventLogging() != MagickFalse)
1738 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1739 (void) FormatLocaleString(geometry,MagickPathExtent,"gaussian:%.20gx%.20g",
1740 radius,sigma);
1741 kernel_info=AcquireKernelInfo(geometry,exception);
1742 if (kernel_info == (KernelInfo *) NULL)
1743 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1744 blur_image=ConvolveImage(image,kernel_info,exception);
1745 kernel_info=DestroyKernelInfo(kernel_info);
1746 return(blur_image);
1747}
1748
1749/*
1750%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1751% %
1752% %
1753% %
1754% K u w a h a r a I m a g e %
1755% %
1756% %
1757% %
1758%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1759%
1760% KuwaharaImage() is an edge preserving noise reduction filter.
1761%
1762% The format of the KuwaharaImage method is:
1763%
1764% Image *KuwaharaImage(const Image *image,const double radius,
1765% const double sigma,ExceptionInfo *exception)
1766%
1767% A description of each parameter follows:
1768%
1769% o image: the image.
1770%
1771% o radius: the square window radius.
1772%
1773% o sigma: the standard deviation of the Gaussian, in pixels.
1774%
1775% o exception: return any errors or warnings in this structure.
1776%
1777*/
1778
1779static inline MagickRealType GetMeanLuma(const Image *magick_restrict image,
1780 const double *magick_restrict pixel)
1781{
1782 return(0.212656*pixel[image->channel_map[RedPixelChannel].offset]+
1783 0.715158*pixel[image->channel_map[GreenPixelChannel].offset]+
1784 0.072186*pixel[image->channel_map[BluePixelChannel].offset]); /* Rec709 */
1785}
1786
1787MagickExport Image *KuwaharaImage(const Image *image,const double radius,
1788 const double sigma,ExceptionInfo *exception)
1789{
1790#define KuwaharaImageTag "Kuwahara/Image"
1791
1792 CacheView
1793 *image_view,
1794 *kuwahara_view;
1795
1796 Image
1797 *gaussian_image,
1798 *kuwahara_image;
1799
1800 MagickBooleanType
1801 status;
1802
1803 MagickOffsetType
1804 progress;
1805
1806 size_t
1807 width;
1808
1809 ssize_t
1810 y;
1811
1812 /*
1813 Initialize Kuwahara image attributes.
1814 */
1815 assert(image != (Image *) NULL);
1816 assert(image->signature == MagickCoreSignature);
1817 assert(exception != (ExceptionInfo *) NULL);
1818 assert(exception->signature == MagickCoreSignature);
1819 if (IsEventLogging() != MagickFalse)
1820 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1821 width=(size_t) radius+1;
1822 gaussian_image=BlurImage(image,radius,sigma,exception);
1823 if (gaussian_image == (Image *) NULL)
1824 return((Image *) NULL);
1825 kuwahara_image=CloneImage(image,0,0,MagickTrue,exception);
1826 if (kuwahara_image == (Image *) NULL)
1827 {
1828 gaussian_image=DestroyImage(gaussian_image);
1829 return((Image *) NULL);
1830 }
1831 if (SetImageStorageClass(kuwahara_image,DirectClass,exception) == MagickFalse)
1832 {
1833 gaussian_image=DestroyImage(gaussian_image);
1834 kuwahara_image=DestroyImage(kuwahara_image);
1835 return((Image *) NULL);
1836 }
1837 /*
1838 Edge preserving noise reduction filter.
1839 */
1840 status=MagickTrue;
1841 progress=0;
1842 image_view=AcquireVirtualCacheView(gaussian_image,exception);
1843 kuwahara_view=AcquireAuthenticCacheView(kuwahara_image,exception);
1844#if defined(MAGICKCORE_OPENMP_SUPPORT)
1845 #pragma omp parallel for schedule(static) shared(progress,status) \
1846 magick_number_threads(image,kuwahara_image,gaussian_image->rows,1)
1847#endif
1848 for (y=0; y < (ssize_t) gaussian_image->rows; y++)
1849 {
1850 Quantum
1851 *magick_restrict q;
1852
1853 ssize_t
1854 x;
1855
1856 if (status == MagickFalse)
1857 continue;
1858 q=QueueCacheViewAuthenticPixels(kuwahara_view,0,y,kuwahara_image->columns,1,
1859 exception);
1860 if (q == (Quantum *) NULL)
1861 {
1862 status=MagickFalse;
1863 continue;
1864 }
1865 for (x=0; x < (ssize_t) gaussian_image->columns; x++)
1866 {
1867 const Quantum
1868 *magick_restrict p;
1869
1870 double
1871 min_variance;
1872
1874 quadrant,
1875 target;
1876
1877 size_t
1878 i;
1879
1880 min_variance=MagickMaximumValue;
1881 SetGeometry(gaussian_image,&target);
1882 quadrant.width=width;
1883 quadrant.height=width;
1884 for (i=0; i < 4; i++)
1885 {
1886 const Quantum
1887 *magick_restrict k;
1888
1889 double
1890 mean[MaxPixelChannels],
1891 variance;
1892
1893 ssize_t
1894 n;
1895
1896 ssize_t
1897 j;
1898
1899 quadrant.x=x;
1900 quadrant.y=y;
1901 switch (i)
1902 {
1903 case 0:
1904 {
1905 quadrant.x=x-(ssize_t) (width-1);
1906 quadrant.y=y-(ssize_t) (width-1);
1907 break;
1908 }
1909 case 1:
1910 {
1911 quadrant.y=y-(ssize_t) (width-1);
1912 break;
1913 }
1914 case 2:
1915 {
1916 quadrant.x=x-(ssize_t) (width-1);
1917 break;
1918 }
1919 case 3:
1920 default:
1921 break;
1922 }
1923 p=GetCacheViewVirtualPixels(image_view,quadrant.x,quadrant.y,
1924 quadrant.width,quadrant.height,exception);
1925 if (p == (const Quantum *) NULL)
1926 break;
1927 for (j=0; j < (ssize_t) GetPixelChannels(gaussian_image); j++)
1928 mean[j]=0.0;
1929 k=p;
1930 for (n=0; n < (ssize_t) (width*width); n++)
1931 {
1932 for (j=0; j < (ssize_t) GetPixelChannels(gaussian_image); j++)
1933 mean[j]+=(double) k[j];
1934 k+=(ptrdiff_t) GetPixelChannels(gaussian_image);
1935 }
1936 for (j=0; j < (ssize_t) GetPixelChannels(gaussian_image); j++)
1937 mean[j]/=(double) (width*width);
1938 k=p;
1939 variance=0.0;
1940 for (n=0; n < (ssize_t) (width*width); n++)
1941 {
1942 double
1943 luma;
1944
1945 luma=GetPixelLuma(gaussian_image,k);
1946 variance+=(luma-GetMeanLuma(gaussian_image,mean))*
1947 (luma-GetMeanLuma(gaussian_image,mean));
1948 k+=(ptrdiff_t) GetPixelChannels(gaussian_image);
1949 }
1950 if (variance < min_variance)
1951 {
1952 min_variance=variance;
1953 target=quadrant;
1954 }
1955 }
1956 if (i < 4)
1957 {
1958 status=MagickFalse;
1959 break;
1960 }
1961 status=InterpolatePixelChannels(gaussian_image,image_view,kuwahara_image,
1962 UndefinedInterpolatePixel,(double) target.x+target.width/2.0,(double)
1963 target.y+target.height/2.0,q,exception);
1964 if (status == MagickFalse)
1965 break;
1966 q+=(ptrdiff_t) GetPixelChannels(kuwahara_image);
1967 }
1968 if (SyncCacheViewAuthenticPixels(kuwahara_view,exception) == MagickFalse)
1969 status=MagickFalse;
1970 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1971 {
1972 MagickBooleanType
1973 proceed;
1974
1975#if defined(MAGICKCORE_OPENMP_SUPPORT)
1976 #pragma omp atomic
1977#endif
1978 progress++;
1979 proceed=SetImageProgress(image,KuwaharaImageTag,progress,image->rows);
1980 if (proceed == MagickFalse)
1981 status=MagickFalse;
1982 }
1983 }
1984 kuwahara_view=DestroyCacheView(kuwahara_view);
1985 image_view=DestroyCacheView(image_view);
1986 gaussian_image=DestroyImage(gaussian_image);
1987 if (status == MagickFalse)
1988 kuwahara_image=DestroyImage(kuwahara_image);
1989 return(kuwahara_image);
1990}
1991
1992/*
1993%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1994% %
1995% %
1996% %
1997% L o c a l C o n t r a s t I m a g e %
1998% %
1999% %
2000% %
2001%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2002%
2003% LocalContrastImage() attempts to increase the appearance of large-scale
2004% light-dark transitions. Local contrast enhancement works similarly to
2005% sharpening with an unsharp mask, however the mask is instead created using
2006% an image with a greater blur distance.
2007%
2008% The format of the LocalContrastImage method is:
2009%
2010% Image *LocalContrastImage(const Image *image, const double radius,
2011% const double strength,ExceptionInfo *exception)
2012%
2013% A description of each parameter follows:
2014%
2015% o image: the image.
2016%
2017% o radius: the radius of the Gaussian blur, in percentage with 100%
2018% resulting in a blur radius of 20% of largest dimension.
2019%
2020% o strength: the strength of the blur mask in percentage.
2021%
2022% o exception: return any errors or warnings in this structure.
2023%
2024*/
2025MagickExport Image *LocalContrastImage(const Image *image,const double radius,
2026 const double strength,ExceptionInfo *exception)
2027{
2028#define LocalContrastImageTag "LocalContrast/Image"
2029
2030 CacheView
2031 *image_view,
2032 *contrast_view;
2033
2034 double
2035 totalWeight;
2036
2037 float
2038 *interImage,
2039 *scanline;
2040
2041 Image
2042 *contrast_image;
2043
2044 MagickBooleanType
2045 status;
2046
2048 *scanline_info,
2049 *interImage_info;
2050
2051 ssize_t
2052 scanLineSize,
2053 width;
2054
2055 /*
2056 Initialize contrast image attributes.
2057 */
2058 assert(image != (const Image *) NULL);
2059 assert(image->signature == MagickCoreSignature);
2060 assert(exception != (ExceptionInfo *) NULL);
2061 assert(exception->signature == MagickCoreSignature);
2062 if (IsEventLogging() != MagickFalse)
2063 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2064#if defined(MAGICKCORE_OPENCL_SUPPORT)
2065 contrast_image=AccelerateLocalContrastImage(image,radius,strength,exception);
2066 if (contrast_image != (Image *) NULL)
2067 return(contrast_image);
2068#endif
2069 contrast_image=CloneImage(image,0,0,MagickTrue,exception);
2070 if (contrast_image == (Image *) NULL)
2071 return((Image *) NULL);
2072 if (SetImageStorageClass(contrast_image,DirectClass,exception) == MagickFalse)
2073 {
2074 contrast_image=DestroyImage(contrast_image);
2075 return((Image *) NULL);
2076 }
2077 image_view=AcquireVirtualCacheView(image,exception);
2078 contrast_view=AcquireAuthenticCacheView(contrast_image,exception);
2079 scanLineSize=(ssize_t) MagickMax(image->columns,image->rows);
2080 width=(ssize_t) (scanLineSize*0.002*fabs(radius));
2081 scanLineSize+=(2*width);
2082 scanline_info=AcquireVirtualMemory(GetOpenMPMaximumThreads()*
2083 (size_t) scanLineSize,sizeof(*scanline));
2084 if (scanline_info == (MemoryInfo *) NULL)
2085 {
2086 contrast_view=DestroyCacheView(contrast_view);
2087 image_view=DestroyCacheView(image_view);
2088 contrast_image=DestroyImage(contrast_image);
2089 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2090 }
2091 scanline=(float *) GetVirtualMemoryBlob(scanline_info);
2092 /*
2093 Create intermediate buffer.
2094 */
2095 interImage_info=AcquireVirtualMemory(image->rows*(image->columns+(size_t)
2096 (2*width)),sizeof(*interImage));
2097 if (interImage_info == (MemoryInfo *) NULL)
2098 {
2099 scanline_info=RelinquishVirtualMemory(scanline_info);
2100 contrast_view=DestroyCacheView(contrast_view);
2101 image_view=DestroyCacheView(image_view);
2102 contrast_image=DestroyImage(contrast_image);
2103 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2104 }
2105 interImage=(float *) GetVirtualMemoryBlob(interImage_info);
2106 totalWeight=(float) ((width+1)*(width+1));
2107 /*
2108 Vertical pass.
2109 */
2110 status=MagickTrue;
2111 {
2112 ssize_t
2113 x;
2114
2115#if defined(MAGICKCORE_OPENMP_SUPPORT)
2116#pragma omp parallel for schedule(static) \
2117 magick_number_threads(image,image,image->columns,1)
2118#endif
2119 for (x=0; x < (ssize_t) image->columns; x++)
2120 {
2121 const int
2122 id = GetOpenMPThreadId();
2123
2124 const Quantum
2125 *magick_restrict p;
2126
2127 float
2128 *out,
2129 *pix,
2130 *pixels;
2131
2132 ssize_t
2133 y;
2134
2135 ssize_t
2136 i;
2137
2138 if (status == MagickFalse)
2139 continue;
2140 pixels=scanline;
2141 pixels+=id*scanLineSize;
2142 pix=pixels;
2143 p=GetCacheViewVirtualPixels(image_view,x,-(ssize_t) width,1,
2144 image->rows+(size_t) (2*width),exception);
2145 if (p == (const Quantum *) NULL)
2146 {
2147 status=MagickFalse;
2148 continue;
2149 }
2150 for (y=0; y < (ssize_t) image->rows+(2*width); y++)
2151 {
2152 *pix++=(float)GetPixelLuma(image,p);
2153 p+=(ptrdiff_t) image->number_channels;
2154 }
2155 out=interImage+x+width;
2156 for (y=0; y < (ssize_t) image->rows; y++)
2157 {
2158 double
2159 sum,
2160 weight;
2161
2162 weight=1.0;
2163 sum=0;
2164 pix=pixels+y;
2165 for (i=0; i < width; i++)
2166 {
2167 sum+=weight*((double) *pix++);
2168 weight+=1.0;
2169 }
2170 for (i=width+1; i < (2*width); i++)
2171 {
2172 sum+=weight*((double) *pix++);
2173 weight-=1.0;
2174 }
2175 /* write to output */
2176 *out=(float) (sum/totalWeight);
2177 /* mirror into padding */
2178 if ((x <= width) && (x != 0))
2179 *(out-(x*2))=*out;
2180 if ((x > (ssize_t) image->columns-width-2) &&
2181 (x != (ssize_t) image->columns-1))
2182 *(out+((image->columns-(size_t) x-1)*2))=*out;
2183 out+=image->columns+(size_t) (width*2);
2184 }
2185 }
2186 }
2187 /*
2188 Horizontal pass.
2189 */
2190 {
2191 ssize_t
2192 y;
2193
2194#if defined(MAGICKCORE_OPENMP_SUPPORT)
2195#pragma omp parallel for schedule(static) \
2196 magick_number_threads(image,image,image->rows,1)
2197#endif
2198 for (y=0; y < (ssize_t) image->rows; y++)
2199 {
2200 const int
2201 id = GetOpenMPThreadId();
2202
2203 const Quantum
2204 *magick_restrict p;
2205
2206 float
2207 *pix,
2208 *pixels;
2209
2210 Quantum
2211 *magick_restrict q;
2212
2213 ssize_t
2214 i,
2215 x;
2216
2217 if (status == MagickFalse)
2218 continue;
2219 pixels=scanline;
2220 pixels+=id*scanLineSize;
2221 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
2222 q=GetCacheViewAuthenticPixels(contrast_view,0,y,image->columns,1,
2223 exception);
2224 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
2225 {
2226 status=MagickFalse;
2227 continue;
2228 }
2229 memcpy(pixels,interImage+((size_t) y*(image->columns+(size_t) (2*width))),
2230 (image->columns+(size_t) (2*width))*sizeof(float));
2231 for (x=0; x < (ssize_t) image->columns; x++)
2232 {
2233 double
2234 mult,
2235 srcVal,
2236 sum,
2237 weight;
2238
2239 PixelTrait
2240 traits;
2241
2242 weight=1.0;
2243 sum=0;
2244 pix=pixels+x;
2245 for (i=0; i < width; i++)
2246 {
2247 sum+=weight*((double) *pix++);
2248 weight+=1.0;
2249 }
2250 for (i=width+1; i < (2*width); i++)
2251 {
2252 sum+=weight*((double) *pix++);
2253 weight-=1.0;
2254 }
2255 /*
2256 Apply and write.
2257 */
2258 srcVal=(float) GetPixelLuma(image,p);
2259 mult=(srcVal-(sum/totalWeight))*(strength/100.0);
2260 mult=(srcVal+mult)/srcVal;
2261 traits=GetPixelChannelTraits(image,RedPixelChannel);
2262 if ((traits & UpdatePixelTrait) != 0)
2263 SetPixelRed(contrast_image,ClampToQuantum((MagickRealType)
2264 GetPixelRed(image,p)*mult),q);
2265 traits=GetPixelChannelTraits(image,GreenPixelChannel);
2266 if ((traits & UpdatePixelTrait) != 0)
2267 SetPixelGreen(contrast_image,ClampToQuantum((MagickRealType)
2268 GetPixelGreen(image,p)*mult),q);
2269 traits=GetPixelChannelTraits(image,BluePixelChannel);
2270 if ((traits & UpdatePixelTrait) != 0)
2271 SetPixelBlue(contrast_image,ClampToQuantum((MagickRealType)
2272 GetPixelBlue(image,p)*mult),q);
2273 p+=(ptrdiff_t) image->number_channels;
2274 q+=(ptrdiff_t) contrast_image->number_channels;
2275 }
2276 if (SyncCacheViewAuthenticPixels(contrast_view,exception) == MagickFalse)
2277 status=MagickFalse;
2278 }
2279 }
2280 scanline_info=RelinquishVirtualMemory(scanline_info);
2281 interImage_info=RelinquishVirtualMemory(interImage_info);
2282 contrast_view=DestroyCacheView(contrast_view);
2283 image_view=DestroyCacheView(image_view);
2284 if (status == MagickFalse)
2285 contrast_image=DestroyImage(contrast_image);
2286 return(contrast_image);
2287}
2288
2289/*
2290%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2291% %
2292% %
2293% %
2294% M o t i o n B l u r I m a g e %
2295% %
2296% %
2297% %
2298%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2299%
2300% MotionBlurImage() simulates motion blur. We convolve the image with a
2301% Gaussian operator of the given radius and standard deviation (sigma).
2302% For reasonable results, radius should be larger than sigma. Use a
2303% radius of 0 and MotionBlurImage() selects a suitable radius for you.
2304% Angle gives the angle of the blurring motion.
2305%
2306% Andrew Protano contributed this effect.
2307%
2308% The format of the MotionBlurImage method is:
2309%
2310% Image *MotionBlurImage(const Image *image,const double radius,
2311% const double sigma,const double angle,ExceptionInfo *exception)
2312%
2313% A description of each parameter follows:
2314%
2315% o image: the image.
2316%
2317% o radius: the radius of the Gaussian, in pixels, not counting
2318% the center pixel.
2319%
2320% o sigma: the standard deviation of the Gaussian, in pixels.
2321%
2322% o angle: Apply the effect along this angle.
2323%
2324% o exception: return any errors or warnings in this structure.
2325%
2326*/
2327
2328static MagickRealType *GetMotionBlurKernel(const size_t width,
2329 const double sigma)
2330{
2331 MagickRealType
2332 *kernel,
2333 normalize;
2334
2335 ssize_t
2336 i;
2337
2338 /*
2339 Generate a 1-D convolution kernel.
2340 */
2341 if (IsEventLogging() != MagickFalse)
2342 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
2343 kernel=(MagickRealType *) MagickAssumeAligned(AcquireAlignedMemory((size_t)
2344 width,sizeof(*kernel)));
2345 if (kernel == (MagickRealType *) NULL)
2346 return(kernel);
2347 normalize=0.0;
2348 for (i=0; i < (ssize_t) width; i++)
2349 {
2350 kernel[i]=(MagickRealType) (exp((-((double) i*i)/(double) (2.0*MagickSigma*
2351 MagickSigma)))/(MagickSQ2PI*MagickSigma));
2352 normalize+=kernel[i];
2353 }
2354 for (i=0; i < (ssize_t) width; i++)
2355 kernel[i]/=normalize;
2356 return(kernel);
2357}
2358
2359MagickExport Image *MotionBlurImage(const Image *image,const double radius,
2360 const double sigma,const double angle,ExceptionInfo *exception)
2361{
2362#define BlurImageTag "Blur/Image"
2363
2364 CacheView
2365 *blur_view,
2366 *image_view,
2367 *motion_view;
2368
2369 Image
2370 *blur_image;
2371
2372 MagickBooleanType
2373 status;
2374
2375 MagickOffsetType
2376 progress;
2377
2378 MagickRealType
2379 *kernel;
2380
2382 *offset;
2383
2384 PointInfo
2385 point;
2386
2387 size_t
2388 width;
2389
2390 ssize_t
2391 w,
2392 y;
2393
2394 assert(image != (Image *) NULL);
2395 assert(image->signature == MagickCoreSignature);
2396 assert(exception != (ExceptionInfo *) NULL);
2397 assert(exception->signature == MagickCoreSignature);
2398 if (IsEventLogging() != MagickFalse)
2399 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2400 width=GetOptimalKernelWidth1D(radius,sigma);
2401 kernel=GetMotionBlurKernel(width,sigma);
2402 if (kernel == (MagickRealType *) NULL)
2403 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2404 offset=(OffsetInfo *) AcquireQuantumMemory(width,sizeof(*offset));
2405 if (offset == (OffsetInfo *) NULL)
2406 {
2407 kernel=(MagickRealType *) RelinquishAlignedMemory(kernel);
2408 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2409 }
2410 point.x=(double) width*sin(DegreesToRadians(angle));
2411 point.y=(double) width*cos(DegreesToRadians(angle));
2412 for (w=0; w < (ssize_t) width; w++)
2413 {
2414 offset[w].x=CastDoubleToSsizeT(ceil((double) (w*point.y)/
2415 hypot(point.x,point.y)-0.5));
2416 offset[w].y=CastDoubleToSsizeT(ceil((double) (w*point.x)/
2417 hypot(point.x,point.y)-0.5));
2418 }
2419 /*
2420 Motion blur image.
2421 */
2422#if defined(MAGICKCORE_OPENCL_SUPPORT)
2423 blur_image=AccelerateMotionBlurImage(image,kernel,width,offset,exception);
2424 if (blur_image != (Image *) NULL)
2425 {
2426 kernel=(MagickRealType *) RelinquishAlignedMemory(kernel);
2427 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2428 return(blur_image);
2429 }
2430#endif
2431 blur_image=CloneImage(image,0,0,MagickTrue,exception);
2432 if (blur_image == (Image *) NULL)
2433 {
2434 kernel=(MagickRealType *) RelinquishAlignedMemory(kernel);
2435 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2436 return((Image *) NULL);
2437 }
2438 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
2439 {
2440 kernel=(MagickRealType *) RelinquishAlignedMemory(kernel);
2441 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2442 blur_image=DestroyImage(blur_image);
2443 return((Image *) NULL);
2444 }
2445 status=MagickTrue;
2446 progress=0;
2447 image_view=AcquireVirtualCacheView(image,exception);
2448 motion_view=AcquireVirtualCacheView(image,exception);
2449 blur_view=AcquireAuthenticCacheView(blur_image,exception);
2450#if defined(MAGICKCORE_OPENMP_SUPPORT)
2451 #pragma omp parallel for schedule(static) shared(progress,status) \
2452 magick_number_threads(image,blur_image,image->rows,1)
2453#endif
2454 for (y=0; y < (ssize_t) image->rows; y++)
2455 {
2456 const Quantum
2457 *magick_restrict p;
2458
2459 Quantum
2460 *magick_restrict q;
2461
2462 ssize_t
2463 x;
2464
2465 if (status == MagickFalse)
2466 continue;
2467 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
2468 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
2469 exception);
2470 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
2471 {
2472 status=MagickFalse;
2473 continue;
2474 }
2475 for (x=0; x < (ssize_t) image->columns; x++)
2476 {
2477 ssize_t
2478 i;
2479
2480 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2481 {
2482 double
2483 alpha = 0.0,
2484 gamma = 0.0,
2485 pixel;
2486
2487 PixelChannel
2488 channel;
2489
2490 PixelTrait
2491 blur_traits,
2492 traits;
2493
2494 const Quantum
2495 *magick_restrict r;
2496
2497 MagickRealType
2498 *magick_restrict k;
2499
2500 ssize_t
2501 j;
2502
2503 channel=GetPixelChannelChannel(image,i);
2504 traits=GetPixelChannelTraits(image,channel);
2505 blur_traits=GetPixelChannelTraits(blur_image,channel);
2506 if ((traits == UndefinedPixelTrait) ||
2507 (blur_traits == UndefinedPixelTrait))
2508 continue;
2509 if ((blur_traits & CopyPixelTrait) != 0)
2510 {
2511 SetPixelChannel(blur_image,channel,p[i],q);
2512 continue;
2513 }
2514 k=kernel;
2515 pixel=0.0;
2516 if ((blur_traits & BlendPixelTrait) == 0)
2517 {
2518 for (j=0; j < (ssize_t) width; j++)
2519 {
2520 r=GetCacheViewVirtualPixels(motion_view,x+offset[j].x,y+
2521 offset[j].y,1,1,exception);
2522 if (r == (const Quantum *) NULL)
2523 {
2524 status=MagickFalse;
2525 continue;
2526 }
2527 pixel+=(*k)*(double) r[i];
2528 k++;
2529 }
2530 SetPixelChannel(blur_image,channel,ClampToQuantum(pixel),q);
2531 continue;
2532 }
2533 for (j=0; j < (ssize_t) width; j++)
2534 {
2535 r=GetCacheViewVirtualPixels(motion_view,x+offset[j].x,y+offset[j].y,1,
2536 1,exception);
2537 if (r == (const Quantum *) NULL)
2538 {
2539 status=MagickFalse;
2540 continue;
2541 }
2542 alpha=QuantumScale*(double) GetPixelAlpha(image,r);
2543 pixel+=(*k)*alpha*(double) r[i];
2544 gamma+=(*k)*alpha;
2545 k++;
2546 }
2547 gamma=MagickSafeReciprocal(gamma);
2548 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
2549 }
2550 p+=(ptrdiff_t) GetPixelChannels(image);
2551 q+=(ptrdiff_t) GetPixelChannels(blur_image);
2552 }
2553 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
2554 status=MagickFalse;
2555 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2556 {
2557 MagickBooleanType
2558 proceed;
2559
2560#if defined(MAGICKCORE_OPENMP_SUPPORT)
2561 #pragma omp atomic
2562#endif
2563 progress++;
2564 proceed=SetImageProgress(image,BlurImageTag,progress,image->rows);
2565 if (proceed == MagickFalse)
2566 status=MagickFalse;
2567 }
2568 }
2569 blur_view=DestroyCacheView(blur_view);
2570 motion_view=DestroyCacheView(motion_view);
2571 image_view=DestroyCacheView(image_view);
2572 kernel=(MagickRealType *) RelinquishAlignedMemory(kernel);
2573 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2574 if (status == MagickFalse)
2575 blur_image=DestroyImage(blur_image);
2576 return(blur_image);
2577}
2578
2579/*
2580%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2581% %
2582% %
2583% %
2584% P r e v i e w I m a g e %
2585% %
2586% %
2587% %
2588%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2589%
2590% PreviewImage() tiles 9 thumbnails of the specified image with an image
2591% processing operation applied with varying parameters. This may be helpful
2592% pin-pointing an appropriate parameter for a particular image processing
2593% operation.
2594%
2595% The format of the PreviewImages method is:
2596%
2597% Image *PreviewImages(const Image *image,const PreviewType preview,
2598% ExceptionInfo *exception)
2599%
2600% A description of each parameter follows:
2601%
2602% o image: the image.
2603%
2604% o preview: the image processing operation.
2605%
2606% o exception: return any errors or warnings in this structure.
2607%
2608*/
2609MagickExport Image *PreviewImage(const Image *image,const PreviewType preview,
2610 ExceptionInfo *exception)
2611{
2612#define NumberTiles 9
2613#define PreviewImageTag "Preview/Image"
2614#define DefaultPreviewGeometry "204x204+10+10"
2615
2616 char
2617 factor[MagickPathExtent],
2618 label[MagickPathExtent];
2619
2620 double
2621 degrees,
2622 gamma,
2623 percentage,
2624 radius,
2625 sigma,
2626 threshold;
2627
2628 Image
2629 *images,
2630 *montage_image,
2631 *preview_image,
2632 *thumbnail;
2633
2634 ImageInfo
2635 *preview_info;
2636
2637 MagickBooleanType
2638 proceed;
2639
2641 *montage_info;
2642
2644 quantize_info;
2645
2647 geometry;
2648
2649 size_t
2650 colors;
2651
2652 ssize_t
2653 i,
2654 x = 0,
2655 y = 0;
2656
2657 /*
2658 Open output image file.
2659 */
2660 assert(image != (Image *) NULL);
2661 assert(image->signature == MagickCoreSignature);
2662 if (IsEventLogging() != MagickFalse)
2663 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2664 colors=2;
2665 degrees=0.0;
2666 gamma=(-0.2f);
2667 preview_info=AcquireImageInfo();
2668 SetGeometry(image,&geometry);
2669 (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y,
2670 &geometry.width,&geometry.height);
2671 images=NewImageList();
2672 percentage=12.5;
2673 GetQuantizeInfo(&quantize_info);
2674 radius=0.0;
2675 sigma=1.0;
2676 threshold=0.0;
2677 for (i=0; i < NumberTiles; i++)
2678 {
2679 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
2680 if (thumbnail == (Image *) NULL)
2681 break;
2682 (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL,
2683 (void *) NULL);
2684 (void) SetImageProperty(thumbnail,"label",DefaultTileLabel,exception);
2685 if (i == (NumberTiles/2))
2686 {
2687 (void) QueryColorCompliance("#dfdfdf",AllCompliance,
2688 &thumbnail->matte_color,exception);
2689 AppendImageToList(&images,thumbnail);
2690 continue;
2691 }
2692 switch (preview)
2693 {
2694 case RotatePreview:
2695 {
2696 degrees+=45.0;
2697 preview_image=RotateImage(thumbnail,degrees,exception);
2698 (void) FormatLocaleString(label,MagickPathExtent,"rotate %g",degrees);
2699 break;
2700 }
2701 case ShearPreview:
2702 {
2703 degrees+=5.0;
2704 preview_image=ShearImage(thumbnail,degrees,degrees,exception);
2705 (void) FormatLocaleString(label,MagickPathExtent,"shear %gx%g",degrees,
2706 2.0*degrees);
2707 break;
2708 }
2709 case RollPreview:
2710 {
2711 x=((i+1)*(ssize_t) thumbnail->columns)/NumberTiles;
2712 y=((i+1)*(ssize_t) thumbnail->rows)/NumberTiles;
2713 preview_image=RollImage(thumbnail,x,y,exception);
2714 (void) FormatLocaleString(label,MagickPathExtent,"roll %+.20gx%+.20g",
2715 (double) x,(double) y);
2716 break;
2717 }
2718 case HuePreview:
2719 {
2720 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2721 if (preview_image == (Image *) NULL)
2722 break;
2723 (void) FormatLocaleString(factor,MagickPathExtent,"100,100,%g",2.0*
2724 percentage);
2725 (void) ModulateImage(preview_image,factor,exception);
2726 (void) FormatLocaleString(label,MagickPathExtent,"modulate %s",factor);
2727 break;
2728 }
2729 case SaturationPreview:
2730 {
2731 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2732 if (preview_image == (Image *) NULL)
2733 break;
2734 (void) FormatLocaleString(factor,MagickPathExtent,"100,%g",2.0*
2735 percentage);
2736 (void) ModulateImage(preview_image,factor,exception);
2737 (void) FormatLocaleString(label,MagickPathExtent,"modulate %s",factor);
2738 break;
2739 }
2740 case BrightnessPreview:
2741 {
2742 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2743 if (preview_image == (Image *) NULL)
2744 break;
2745 (void) FormatLocaleString(factor,MagickPathExtent,"%g",2.0*percentage);
2746 (void) ModulateImage(preview_image,factor,exception);
2747 (void) FormatLocaleString(label,MagickPathExtent,"modulate %s",factor);
2748 break;
2749 }
2750 case GammaPreview:
2751 default:
2752 {
2753 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2754 if (preview_image == (Image *) NULL)
2755 break;
2756 gamma+=0.4;
2757 (void) GammaImage(preview_image,gamma,exception);
2758 (void) FormatLocaleString(label,MagickPathExtent,"gamma %g",gamma);
2759 break;
2760 }
2761 case SpiffPreview:
2762 {
2763 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2764 if (preview_image != (Image *) NULL)
2765 for (x=0; x < i; x++)
2766 (void) ContrastImage(preview_image,MagickTrue,exception);
2767 (void) FormatLocaleString(label,MagickPathExtent,"contrast (%.20g)",
2768 (double) i+1);
2769 break;
2770 }
2771 case DullPreview:
2772 {
2773 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2774 if (preview_image == (Image *) NULL)
2775 break;
2776 for (x=0; x < i; x++)
2777 (void) ContrastImage(preview_image,MagickFalse,exception);
2778 (void) FormatLocaleString(label,MagickPathExtent,"+contrast (%.20g)",
2779 (double) i+1);
2780 break;
2781 }
2782 case GrayscalePreview:
2783 {
2784 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2785 if (preview_image == (Image *) NULL)
2786 break;
2787 colors<<=1;
2788 quantize_info.number_colors=colors;
2789 quantize_info.colorspace=GRAYColorspace;
2790 (void) QuantizeImage(&quantize_info,preview_image,exception);
2791 (void) FormatLocaleString(label,MagickPathExtent,
2792 "-colorspace gray -colors %.20g",(double) colors);
2793 break;
2794 }
2795 case QuantizePreview:
2796 {
2797 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2798 if (preview_image == (Image *) NULL)
2799 break;
2800 colors<<=1;
2801 quantize_info.number_colors=colors;
2802 (void) QuantizeImage(&quantize_info,preview_image,exception);
2803 (void) FormatLocaleString(label,MagickPathExtent,"colors %.20g",
2804 (double) colors);
2805 break;
2806 }
2807 case DespecklePreview:
2808 {
2809 for (x=0; x < (i-1); x++)
2810 {
2811 preview_image=DespeckleImage(thumbnail,exception);
2812 if (preview_image == (Image *) NULL)
2813 break;
2814 thumbnail=DestroyImage(thumbnail);
2815 thumbnail=preview_image;
2816 }
2817 preview_image=DespeckleImage(thumbnail,exception);
2818 if (preview_image == (Image *) NULL)
2819 break;
2820 (void) FormatLocaleString(label,MagickPathExtent,"despeckle (%.20g)",
2821 (double) i+1);
2822 break;
2823 }
2824 case ReduceNoisePreview:
2825 {
2826 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t)
2827 radius,(size_t) radius,exception);
2828 (void) FormatLocaleString(label,MagickPathExtent,"noise %g",radius);
2829 break;
2830 }
2831 case AddNoisePreview:
2832 {
2833 switch ((int) i)
2834 {
2835 case 0:
2836 {
2837 (void) CopyMagickString(factor,"uniform",MagickPathExtent);
2838 break;
2839 }
2840 case 1:
2841 {
2842 (void) CopyMagickString(factor,"gaussian",MagickPathExtent);
2843 break;
2844 }
2845 case 2:
2846 {
2847 (void) CopyMagickString(factor,"multiplicative",MagickPathExtent);
2848 break;
2849 }
2850 case 3:
2851 {
2852 (void) CopyMagickString(factor,"impulse",MagickPathExtent);
2853 break;
2854 }
2855 case 5:
2856 {
2857 (void) CopyMagickString(factor,"laplacian",MagickPathExtent);
2858 break;
2859 }
2860 case 6:
2861 {
2862 (void) CopyMagickString(factor,"Poisson",MagickPathExtent);
2863 break;
2864 }
2865 default:
2866 {
2867 (void) CopyMagickString(thumbnail->magick,"NULL",MagickPathExtent);
2868 break;
2869 }
2870 }
2871 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) i,
2872 (size_t) i,exception);
2873 (void) FormatLocaleString(label,MagickPathExtent,"+noise %s",factor);
2874 break;
2875 }
2876 case SharpenPreview:
2877 {
2878 preview_image=SharpenImage(thumbnail,radius,sigma,exception);
2879 (void) FormatLocaleString(label,MagickPathExtent,"sharpen %gx%g",
2880 radius,sigma);
2881 break;
2882 }
2883 case BlurPreview:
2884 {
2885 preview_image=BlurImage(thumbnail,radius,sigma,exception);
2886 (void) FormatLocaleString(label,MagickPathExtent,"blur %gx%g",radius,
2887 sigma);
2888 break;
2889 }
2890 case ThresholdPreview:
2891 {
2892 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2893 if (preview_image == (Image *) NULL)
2894 break;
2895 (void) BilevelImage(thumbnail,(double) (percentage*((double)
2896 QuantumRange+1.0))/100.0,exception);
2897 (void) FormatLocaleString(label,MagickPathExtent,"threshold %g",
2898 (double) (percentage*((double) QuantumRange+1.0))/100.0);
2899 break;
2900 }
2901 case EdgeDetectPreview:
2902 {
2903 preview_image=EdgeImage(thumbnail,radius,exception);
2904 (void) FormatLocaleString(label,MagickPathExtent,"edge %g",radius);
2905 break;
2906 }
2907 case SpreadPreview:
2908 {
2909 preview_image=SpreadImage(thumbnail,image->interpolate,radius,
2910 exception);
2911 (void) FormatLocaleString(label,MagickPathExtent,"spread %g",
2912 radius+0.5);
2913 break;
2914 }
2915 case SolarizePreview:
2916 {
2917 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2918 if (preview_image == (Image *) NULL)
2919 break;
2920 (void) SolarizeImage(preview_image,(double) QuantumRange*percentage/
2921 100.0,exception);
2922 (void) FormatLocaleString(label,MagickPathExtent,"solarize %g",
2923 ((double) QuantumRange*percentage)/100.0);
2924 break;
2925 }
2926 case ShadePreview:
2927 {
2928 degrees+=10.0;
2929 preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees,
2930 exception);
2931 (void) FormatLocaleString(label,MagickPathExtent,"shade %gx%g",degrees,
2932 degrees);
2933 break;
2934 }
2935 case RaisePreview:
2936 {
2938 raise;
2939
2940 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2941 if (preview_image == (Image *) NULL)
2942 break;
2943 raise.width=(size_t) (2*i+2);
2944 raise.height=(size_t) (2*i+2);
2945 raise.x=(i-1)/2;
2946 raise.y=(i-1)/2;
2947 (void) RaiseImage(preview_image,&raise,MagickTrue,exception);
2948 (void) FormatLocaleString(label,MagickPathExtent,
2949 "raise %.20gx%.20g%+.20g%+.20g",(double) raise.width,(double)
2950 raise.height,(double) raise.x,(double) raise.y);
2951 break;
2952 }
2953 case SegmentPreview:
2954 {
2955 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2956 if (preview_image == (Image *) NULL)
2957 break;
2958 threshold+=0.4;
2959 (void) SegmentImage(preview_image,sRGBColorspace,MagickFalse,threshold,
2960 threshold,exception);
2961 (void) FormatLocaleString(label,MagickPathExtent,"segment %gx%g",
2962 threshold,threshold);
2963 break;
2964 }
2965 case SwirlPreview:
2966 {
2967 preview_image=SwirlImage(thumbnail,degrees,image->interpolate,
2968 exception);
2969 (void) FormatLocaleString(label,MagickPathExtent,"swirl %g",degrees);
2970 degrees+=45.0;
2971 break;
2972 }
2973 case ImplodePreview:
2974 {
2975 degrees+=0.1;
2976 preview_image=ImplodeImage(thumbnail,degrees,image->interpolate,
2977 exception);
2978 (void) FormatLocaleString(label,MagickPathExtent,"implode %g",degrees);
2979 break;
2980 }
2981 case WavePreview:
2982 {
2983 degrees+=5.0;
2984 preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,
2985 image->interpolate,exception);
2986 (void) FormatLocaleString(label,MagickPathExtent,"wave %gx%g",0.5*
2987 degrees,2.0*degrees);
2988 break;
2989 }
2990 case OilPaintPreview:
2991 {
2992 preview_image=OilPaintImage(thumbnail,(double) radius,(double) sigma,
2993 exception);
2994 (void) FormatLocaleString(label,MagickPathExtent,"charcoal %gx%g",
2995 radius,sigma);
2996 break;
2997 }
2998 case CharcoalDrawingPreview:
2999 {
3000 preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma,
3001 exception);
3002 (void) FormatLocaleString(label,MagickPathExtent,"charcoal %gx%g",
3003 radius,sigma);
3004 break;
3005 }
3006 case JPEGPreview:
3007 {
3008 char
3009 filename[MagickPathExtent];
3010
3011 int
3012 file;
3013
3014 MagickBooleanType
3015 status;
3016
3017 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3018 if (preview_image == (Image *) NULL)
3019 break;
3020 preview_info->quality=(size_t) percentage;
3021 (void) FormatLocaleString(factor,MagickPathExtent,"%.20g",(double)
3022 preview_info->quality);
3023 file=AcquireUniqueFileResource(filename);
3024 if (file != -1)
3025 file=close_utf8(file)-1;
3026 (void) FormatLocaleString(preview_image->filename,MagickPathExtent,
3027 "jpeg:%s",filename);
3028 status=WriteImage(preview_info,preview_image,exception);
3029 if (status != MagickFalse)
3030 {
3031 Image
3032 *quality_image;
3033
3034 (void) CopyMagickString(preview_info->filename,
3035 preview_image->filename,MagickPathExtent);
3036 quality_image=ReadImage(preview_info,exception);
3037 if (quality_image != (Image *) NULL)
3038 {
3039 preview_image=DestroyImage(preview_image);
3040 preview_image=quality_image;
3041 }
3042 }
3043 (void) RelinquishUniqueFileResource(preview_image->filename);
3044 if ((GetBlobSize(preview_image)/1024) >= 1024)
3045 (void) FormatLocaleString(label,MagickPathExtent,"quality %s\n%gmb ",
3046 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
3047 1024.0/1024.0);
3048 else
3049 if (GetBlobSize(preview_image) >= 1024)
3050 (void) FormatLocaleString(label,MagickPathExtent,
3051 "quality %s\n%gkb ",factor,(double) ((MagickOffsetType)
3052 GetBlobSize(preview_image))/1024.0);
3053 else
3054 (void) FormatLocaleString(label,MagickPathExtent,
3055 "quality %s\n%.20gb ",factor,(double) ((MagickOffsetType)
3056 GetBlobSize(thumbnail)));
3057 break;
3058 }
3059 }
3060 thumbnail=DestroyImage(thumbnail);
3061 percentage+=12.5;
3062 radius+=0.5;
3063 sigma+=0.25;
3064 if (preview_image == (Image *) NULL)
3065 break;
3066 preview_image->alpha_trait=UndefinedPixelTrait;
3067 (void) DeleteImageProperty(preview_image,"label");
3068 (void) SetImageProperty(preview_image,"label",label,exception);
3069 AppendImageToList(&images,preview_image);
3070 proceed=SetImageProgress(image,PreviewImageTag,(MagickOffsetType) i,
3071 NumberTiles);
3072 if (proceed == MagickFalse)
3073 break;
3074 }
3075 if (images == (Image *) NULL)
3076 {
3077 preview_info=DestroyImageInfo(preview_info);
3078 return((Image *) NULL);
3079 }
3080 /*
3081 Create the montage.
3082 */
3083 montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL);
3084 (void) CopyMagickString(montage_info->filename,image->filename,
3085 MagickPathExtent);
3086 montage_info->shadow=MagickTrue;
3087 (void) CloneString(&montage_info->tile,"3x3");
3088 (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry);
3089 (void) CloneString(&montage_info->frame,DefaultTileFrame);
3090 montage_image=MontageImages(images,montage_info,exception);
3091 montage_info=DestroyMontageInfo(montage_info);
3092 images=DestroyImageList(images);
3093 if (montage_image == (Image *) NULL)
3094 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3095 if (montage_image->montage != (char *) NULL)
3096 {
3097 /*
3098 Free image directory.
3099 */
3100 montage_image->montage=(char *) RelinquishMagickMemory(
3101 montage_image->montage);
3102 if (image->directory != (char *) NULL)
3103 montage_image->directory=(char *) RelinquishMagickMemory(
3104 montage_image->directory);
3105 }
3106 preview_info=DestroyImageInfo(preview_info);
3107 return(montage_image);
3108}
3109
3110/*
3111%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3112% %
3113% %
3114% %
3115% R o t a t i o n a l B l u r I m a g e %
3116% %
3117% %
3118% %
3119%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3120%
3121% RotationalBlurImage() applies a radial blur to the image.
3122%
3123% Andrew Protano contributed this effect.
3124%
3125% The format of the RotationalBlurImage method is:
3126%
3127% Image *RotationalBlurImage(const Image *image,const double angle,
3128% ExceptionInfo *exception)
3129%
3130% A description of each parameter follows:
3131%
3132% o image: the image.
3133%
3134% o angle: the angle of the radial blur.
3135%
3136% o blur: the blur.
3137%
3138% o exception: return any errors or warnings in this structure.
3139%
3140*/
3141MagickExport Image *RotationalBlurImage(const Image *image,const double angle,
3142 ExceptionInfo *exception)
3143{
3144 CacheView
3145 *blur_view,
3146 *image_view,
3147 *radial_view;
3148
3149 double
3150 blur_radius,
3151 *cos_theta,
3152 offset,
3153 *sin_theta,
3154 theta;
3155
3156 Image
3157 *blur_image;
3158
3159 MagickBooleanType
3160 status;
3161
3162 MagickOffsetType
3163 progress;
3164
3165 PointInfo
3166 blur_center;
3167
3168 size_t
3169 n;
3170
3171 ssize_t
3172 w,
3173 y;
3174
3175 /*
3176 Allocate blur image.
3177 */
3178 assert(image != (Image *) NULL);
3179 assert(image->signature == MagickCoreSignature);
3180 assert(exception != (ExceptionInfo *) NULL);
3181 assert(exception->signature == MagickCoreSignature);
3182 if (IsEventLogging() != MagickFalse)
3183 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3184#if defined(MAGICKCORE_OPENCL_SUPPORT)
3185 blur_image=AccelerateRotationalBlurImage(image,angle,exception);
3186 if (blur_image != (Image *) NULL)
3187 return(blur_image);
3188#endif
3189 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3190 if (blur_image == (Image *) NULL)
3191 return((Image *) NULL);
3192 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
3193 {
3194 blur_image=DestroyImage(blur_image);
3195 return((Image *) NULL);
3196 }
3197 blur_center.x=(double) (image->columns-1)/2.0;
3198 blur_center.y=(double) (image->rows-1)/2.0;
3199 blur_radius=hypot(blur_center.x,blur_center.y);
3200 n=(size_t) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+2UL);
3201 theta=DegreesToRadians(angle)/(double) (n-1);
3202 cos_theta=(double *) AcquireQuantumMemory((size_t) n,sizeof(*cos_theta));
3203 sin_theta=(double *) AcquireQuantumMemory((size_t) n,sizeof(*sin_theta));
3204 if ((cos_theta == (double *) NULL) || (sin_theta == (double *) NULL))
3205 {
3206 if (cos_theta != (double *) NULL)
3207 cos_theta=(double *) RelinquishMagickMemory(cos_theta);
3208 if (sin_theta != (double *) NULL)
3209 sin_theta=(double *) RelinquishMagickMemory(sin_theta);
3210 blur_image=DestroyImage(blur_image);
3211 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3212 }
3213 offset=theta*(double) (n-1)/2.0;
3214 for (w=0; w < (ssize_t) n; w++)
3215 {
3216 cos_theta[w]=cos((double) (theta*w-offset));
3217 sin_theta[w]=sin((double) (theta*w-offset));
3218 }
3219 /*
3220 Radial blur image.
3221 */
3222 status=MagickTrue;
3223 progress=0;
3224 image_view=AcquireVirtualCacheView(image,exception);
3225 radial_view=AcquireVirtualCacheView(image,exception);
3226 blur_view=AcquireAuthenticCacheView(blur_image,exception);
3227#if defined(MAGICKCORE_OPENMP_SUPPORT)
3228 #pragma omp parallel for schedule(static) shared(progress,status) \
3229 magick_number_threads(image,blur_image,image->rows,1)
3230#endif
3231 for (y=0; y < (ssize_t) image->rows; y++)
3232 {
3233 const Quantum
3234 *magick_restrict p;
3235
3236 Quantum
3237 *magick_restrict q;
3238
3239 ssize_t
3240 x;
3241
3242 if (status == MagickFalse)
3243 continue;
3244 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
3245 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3246 exception);
3247 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
3248 {
3249 status=MagickFalse;
3250 continue;
3251 }
3252 for (x=0; x < (ssize_t) image->columns; x++)
3253 {
3254 double
3255 radius;
3256
3257 PointInfo
3258 center;
3259
3260 ssize_t
3261 i;
3262
3263 size_t
3264 step;
3265
3266 center.x=(double) x-blur_center.x;
3267 center.y=(double) y-blur_center.y;
3268 radius=hypot((double) center.x,center.y);
3269 if (radius == 0)
3270 step=1;
3271 else
3272 {
3273 step=(size_t) (blur_radius/radius);
3274 if (step == 0)
3275 step=1;
3276 else
3277 if (step >= n)
3278 step=n-1;
3279 }
3280 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3281 {
3282 double
3283 gamma,
3284 pixel;
3285
3286 PixelChannel
3287 channel;
3288
3289 PixelTrait
3290 blur_traits,
3291 traits;
3292
3293 const Quantum
3294 *magick_restrict r;
3295
3296 ssize_t
3297 j;
3298
3299 channel=GetPixelChannelChannel(image,i);
3300 traits=GetPixelChannelTraits(image,channel);
3301 blur_traits=GetPixelChannelTraits(blur_image,channel);
3302 if ((traits == UndefinedPixelTrait) ||
3303 (blur_traits == UndefinedPixelTrait))
3304 continue;
3305 if ((blur_traits & CopyPixelTrait) != 0)
3306 {
3307 SetPixelChannel(blur_image,channel,p[i],q);
3308 continue;
3309 }
3310 gamma=0.0;
3311 pixel=0.0;
3312 if ((GetPixelChannelTraits(image,AlphaPixelChannel) == UndefinedPixelTrait) ||
3313 (channel == AlphaPixelChannel))
3314 {
3315 for (j=0; j < (ssize_t) n; j+=(ssize_t) step)
3316 {
3317 r=GetCacheViewVirtualPixels(radial_view, (ssize_t) (blur_center.x+
3318 center.x*cos_theta[j]-center.y*sin_theta[j]+0.5),(ssize_t)
3319 (blur_center.y+center.x*sin_theta[j]+center.y*cos_theta[j]+0.5),
3320 1,1,exception);
3321 if (r == (const Quantum *) NULL)
3322 {
3323 status=MagickFalse;
3324 continue;
3325 }
3326 pixel+=(double) r[i];
3327 gamma++;
3328 }
3329 gamma=MagickSafeReciprocal(gamma);
3330 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
3331 continue;
3332 }
3333 for (j=0; j < (ssize_t) n; j+=(ssize_t) step)
3334 {
3335 double
3336 alpha;
3337
3338 r=GetCacheViewVirtualPixels(radial_view, (ssize_t) (blur_center.x+
3339 center.x*cos_theta[j]-center.y*sin_theta[j]+0.5),(ssize_t)
3340 (blur_center.y+center.x*sin_theta[j]+center.y*cos_theta[j]+0.5),
3341 1,1,exception);
3342 if (r == (const Quantum *) NULL)
3343 {
3344 status=MagickFalse;
3345 continue;
3346 }
3347 alpha=QuantumScale*(double) GetPixelAlpha(image,r);
3348 pixel+=alpha*(double) r[i];
3349 gamma+=alpha;
3350 }
3351 gamma=MagickSafeReciprocal(gamma);
3352 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
3353 }
3354 p+=(ptrdiff_t) GetPixelChannels(image);
3355 q+=(ptrdiff_t) GetPixelChannels(blur_image);
3356 }
3357 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
3358 status=MagickFalse;
3359 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3360 {
3361 MagickBooleanType
3362 proceed;
3363
3364#if defined(MAGICKCORE_OPENMP_SUPPORT)
3365 #pragma omp atomic
3366#endif
3367 progress++;
3368 proceed=SetImageProgress(image,BlurImageTag,progress,image->rows);
3369 if (proceed == MagickFalse)
3370 status=MagickFalse;
3371 }
3372 }
3373 blur_view=DestroyCacheView(blur_view);
3374 radial_view=DestroyCacheView(radial_view);
3375 image_view=DestroyCacheView(image_view);
3376 cos_theta=(double *) RelinquishMagickMemory(cos_theta);
3377 sin_theta=(double *) RelinquishMagickMemory(sin_theta);
3378 if (status == MagickFalse)
3379 blur_image=DestroyImage(blur_image);
3380 return(blur_image);
3381}
3382
3383/*
3384%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3385% %
3386% %
3387% %
3388% S e l e c t i v e B l u r I m a g e %
3389% %
3390% %
3391% %
3392%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3393%
3394% SelectiveBlurImage() selectively blur pixels within a contrast threshold.
3395% It is similar to the unsharpen mask that sharpens everything with contrast
3396% above a certain threshold.
3397%
3398% The format of the SelectiveBlurImage method is:
3399%
3400% Image *SelectiveBlurImage(const Image *image,const double radius,
3401% const double sigma,const double threshold,ExceptionInfo *exception)
3402%
3403% A description of each parameter follows:
3404%
3405% o image: the image.
3406%
3407% o radius: the radius of the Gaussian, in pixels, not counting the center
3408% pixel.
3409%
3410% o sigma: the standard deviation of the Gaussian, in pixels.
3411%
3412% o threshold: only pixels within this contrast threshold are included
3413% in the blur operation.
3414%
3415% o exception: return any errors or warnings in this structure.
3416%
3417*/
3418MagickExport Image *SelectiveBlurImage(const Image *image,const double radius,
3419 const double sigma,const double threshold,ExceptionInfo *exception)
3420{
3421#define SelectiveBlurImageTag "SelectiveBlur/Image"
3422
3423 CacheView
3424 *blur_view,
3425 *image_view,
3426 *luminance_view;
3427
3428 Image
3429 *blur_image,
3430 *luminance_image;
3431
3432 MagickBooleanType
3433 status;
3434
3435 MagickOffsetType
3436 progress;
3437
3438 MagickRealType
3439 *kernel;
3440
3441 size_t
3442 width;
3443
3444 ssize_t
3445 center,
3446 y;
3447
3448 /*
3449 Initialize blur image attributes.
3450 */
3451 assert(image != (Image *) NULL);
3452 assert(image->signature == MagickCoreSignature);
3453 assert(exception != (ExceptionInfo *) NULL);
3454 assert(exception->signature == MagickCoreSignature);
3455 if (IsEventLogging() != MagickFalse)
3456 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3457 width=GetOptimalKernelWidth1D(radius,sigma);
3458 kernel=(MagickRealType *) MagickAssumeAligned(AcquireAlignedMemory((size_t)
3459 width,width*sizeof(*kernel)));
3460 if (kernel == (MagickRealType *) NULL)
3461 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3462 {
3463 ssize_t
3464 i,
3465 j,
3466 v;
3467
3468 j=(ssize_t) (width-1)/2;
3469 i=0;
3470 for (v=(-j); v <= j; v++)
3471 {
3472 ssize_t
3473 u;
3474
3475 for (u=(-j); u <= j; u++)
3476 kernel[i++]=(MagickRealType) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
3477 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
3478 }
3479 }
3480 if (image->debug != MagickFalse)
3481 {
3482 char
3483 format[MagickPathExtent],
3484 *message;
3485
3486 const MagickRealType
3487 *k;
3488
3489 ssize_t
3490 u,
3491 v;
3492
3493 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
3494 " SelectiveBlurImage with %.20gx%.20g kernel:",(double) width,(double)
3495 width);
3496 message=AcquireString("");
3497 k=kernel;
3498 for (v=0; v < (ssize_t) width; v++)
3499 {
3500 *message='\0';
3501 (void) FormatLocaleString(format,MagickPathExtent,"%.20g: ",(double) v);
3502 (void) ConcatenateString(&message,format);
3503 for (u=0; u < (ssize_t) width; u++)
3504 {
3505 (void) FormatLocaleString(format,MagickPathExtent,"%+f ",(double)
3506 *k++);
3507 (void) ConcatenateString(&message,format);
3508 }
3509 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
3510 }
3511 message=DestroyString(message);
3512 }
3513 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3514 if (blur_image == (Image *) NULL)
3515 return((Image *) NULL);
3516 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
3517 {
3518 blur_image=DestroyImage(blur_image);
3519 kernel=(MagickRealType *) RelinquishAlignedMemory(kernel);
3520 return((Image *) NULL);
3521 }
3522 luminance_image=CloneImage(image,0,0,MagickTrue,exception);
3523 if (luminance_image == (Image *) NULL)
3524 {
3525 blur_image=DestroyImage(blur_image);
3526 kernel=(MagickRealType *) RelinquishAlignedMemory(kernel);
3527 return((Image *) NULL);
3528 }
3529 status=TransformImageColorspace(luminance_image,GRAYColorspace,exception);
3530 if (status == MagickFalse)
3531 {
3532 luminance_image=DestroyImage(luminance_image);
3533 blur_image=DestroyImage(blur_image);
3534 kernel=(MagickRealType *) RelinquishAlignedMemory(kernel);
3535 return((Image *) NULL);
3536 }
3537 /*
3538 Threshold blur image.
3539 */
3540 status=MagickTrue;
3541 progress=0;
3542 center=(ssize_t) (GetPixelChannels(image)*(image->columns+width)*
3543 ((width-1)/2L)+GetPixelChannels(image)*((width-1)/2L));
3544 image_view=AcquireVirtualCacheView(image,exception);
3545 luminance_view=AcquireVirtualCacheView(luminance_image,exception);
3546 blur_view=AcquireAuthenticCacheView(blur_image,exception);
3547#if defined(MAGICKCORE_OPENMP_SUPPORT)
3548 #pragma omp parallel for schedule(static) shared(progress,status) \
3549 magick_number_threads(image,blur_image,image->rows,1)
3550#endif
3551 for (y=0; y < (ssize_t) image->rows; y++)
3552 {
3553 double
3554 contrast;
3555
3556 MagickBooleanType
3557 sync;
3558
3559 const Quantum
3560 *magick_restrict l,
3561 *magick_restrict p;
3562
3563 Quantum
3564 *magick_restrict q;
3565
3566 ssize_t
3567 x;
3568
3569 if (status == MagickFalse)
3570 continue;
3571 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) (width-1)/2L),y-(ssize_t)
3572 ((width-1)/2L),image->columns+width,width,exception);
3573 l=GetCacheViewVirtualPixels(luminance_view,-((ssize_t) (width-1)/2L),y-
3574 (ssize_t) ((width-1)/2L),luminance_image->columns+width,width,exception);
3575 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3576 exception);
3577 if ((p == (const Quantum *) NULL) || (l == (const Quantum *) NULL) ||
3578 (q == (Quantum *) NULL))
3579 {
3580 status=MagickFalse;
3581 continue;
3582 }
3583 for (x=0; x < (ssize_t) image->columns; x++)
3584 {
3585 double
3586 intensity;
3587
3588 ssize_t
3589 i;
3590
3591 intensity=GetPixelIntensity(image,p+center);
3592 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3593 {
3594 double
3595 alpha,
3596 gamma,
3597 pixel;
3598
3599 PixelChannel
3600 channel;
3601
3602 PixelTrait
3603 blur_traits,
3604 traits;
3605
3606 const MagickRealType
3607 *magick_restrict k;
3608
3609 const Quantum
3610 *magick_restrict luminance_pixels,
3611 *magick_restrict pixels;
3612
3613 ssize_t
3614 u;
3615
3616 ssize_t
3617 v;
3618
3619 channel=GetPixelChannelChannel(image,i);
3620 traits=GetPixelChannelTraits(image,channel);
3621 blur_traits=GetPixelChannelTraits(blur_image,channel);
3622 if ((traits == UndefinedPixelTrait) ||
3623 (blur_traits == UndefinedPixelTrait))
3624 continue;
3625 if ((blur_traits & CopyPixelTrait) != 0)
3626 {
3627 SetPixelChannel(blur_image,channel,p[center+i],q);
3628 continue;
3629 }
3630 k=kernel;
3631 pixel=0.0;
3632 pixels=p;
3633 luminance_pixels=l;
3634 gamma=0.0;
3635 if ((blur_traits & BlendPixelTrait) == 0)
3636 {
3637 for (v=0; v < (ssize_t) width; v++)
3638 {
3639 for (u=0; u < (ssize_t) width; u++)
3640 {
3641 contrast=GetPixelIntensity(luminance_image,luminance_pixels)-
3642 intensity;
3643 if (fabs(contrast) < threshold)
3644 {
3645 pixel+=(*k)*(double) pixels[i];
3646 gamma+=(*k);
3647 }
3648 k++;
3649 pixels+=(ptrdiff_t) GetPixelChannels(image);
3650 luminance_pixels+=(ptrdiff_t) GetPixelChannels(luminance_image);
3651 }
3652 pixels+=(ptrdiff_t) GetPixelChannels(image)*image->columns;
3653 luminance_pixels+=(ptrdiff_t) GetPixelChannels(luminance_image)*
3654 luminance_image->columns;
3655 }
3656 if (fabs((double) gamma) < MagickEpsilon)
3657 {
3658 SetPixelChannel(blur_image,channel,p[center+i],q);
3659 continue;
3660 }
3661 gamma=MagickSafeReciprocal(gamma);
3662 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
3663 continue;
3664 }
3665 for (v=0; v < (ssize_t) width; v++)
3666 {
3667 for (u=0; u < (ssize_t) width; u++)
3668 {
3669 contrast=GetPixelIntensity(image,pixels)-intensity;
3670 if (fabs(contrast) < threshold)
3671 {
3672 alpha=QuantumScale*(double) GetPixelAlpha(image,pixels);
3673 pixel+=(*k)*alpha*(double) pixels[i];
3674 gamma+=(*k)*alpha;
3675 }
3676 k++;
3677 pixels+=(ptrdiff_t) GetPixelChannels(image);
3678 luminance_pixels+=(ptrdiff_t) GetPixelChannels(luminance_image);
3679 }
3680 pixels+=(ptrdiff_t) GetPixelChannels(image)*image->columns;
3681 luminance_pixels+=(ptrdiff_t) GetPixelChannels(luminance_image)*
3682 luminance_image->columns;
3683 }
3684 if (fabs((double) gamma) < MagickEpsilon)
3685 {
3686 SetPixelChannel(blur_image,channel,p[center+i],q);
3687 continue;
3688 }
3689 gamma=MagickSafeReciprocal(gamma);
3690 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
3691 }
3692 p+=(ptrdiff_t) GetPixelChannels(image);
3693 l+=(ptrdiff_t) GetPixelChannels(luminance_image);
3694 q+=(ptrdiff_t) GetPixelChannels(blur_image);
3695 }
3696 sync=SyncCacheViewAuthenticPixels(blur_view,exception);
3697 if (sync == MagickFalse)
3698 status=MagickFalse;
3699 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3700 {
3701 MagickBooleanType
3702 proceed;
3703
3704#if defined(MAGICKCORE_OPENMP_SUPPORT)
3705 #pragma omp atomic
3706#endif
3707 progress++;
3708 proceed=SetImageProgress(image,SelectiveBlurImageTag,progress,
3709 image->rows);
3710 if (proceed == MagickFalse)
3711 status=MagickFalse;
3712 }
3713 }
3714 blur_image->type=image->type;
3715 blur_view=DestroyCacheView(blur_view);
3716 luminance_view=DestroyCacheView(luminance_view);
3717 image_view=DestroyCacheView(image_view);
3718 luminance_image=DestroyImage(luminance_image);
3719 kernel=(MagickRealType *) RelinquishAlignedMemory(kernel);
3720 if (status == MagickFalse)
3721 blur_image=DestroyImage(blur_image);
3722 return(blur_image);
3723}
3724
3725/*
3726%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3727% %
3728% %
3729% %
3730% S h a d e I m a g e %
3731% %
3732% %
3733% %
3734%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3735%
3736% ShadeImage() shines a distant light on an image to create a
3737% three-dimensional effect. You control the positioning of the light with
3738% azimuth and elevation; azimuth is measured in degrees off the x axis
3739% and elevation is measured in pixels above the Z axis.
3740%
3741% The format of the ShadeImage method is:
3742%
3743% Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3744% const double azimuth,const double elevation,ExceptionInfo *exception)
3745%
3746% A description of each parameter follows:
3747%
3748% o image: the image.
3749%
3750% o gray: A value other than zero shades the intensity of each pixel.
3751%
3752% o azimuth, elevation: Define the light source direction.
3753%
3754% o exception: return any errors or warnings in this structure.
3755%
3756*/
3757MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3758 const double azimuth,const double elevation,ExceptionInfo *exception)
3759{
3760#define GetShadeIntensity(image,pixel) \
3761 ClampPixel(GetPixelIntensity((image),(pixel)))
3762#define ShadeImageTag "Shade/Image"
3763
3764 CacheView
3765 *image_view,
3766 *shade_view;
3767
3768 Image
3769 *linear_image,
3770 *shade_image;
3771
3772 MagickBooleanType
3773 status;
3774
3775 MagickOffsetType
3776 progress;
3777
3779 light;
3780
3781 ssize_t
3782 y;
3783
3784 /*
3785 Initialize shaded image attributes.
3786 */
3787 assert(image != (const Image *) NULL);
3788 assert(image->signature == MagickCoreSignature);
3789 assert(exception != (ExceptionInfo *) NULL);
3790 assert(exception->signature == MagickCoreSignature);
3791 if (IsEventLogging() != MagickFalse)
3792 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3793 linear_image=CloneImage(image,0,0,MagickTrue,exception);
3794 shade_image=CloneImage(image,0,0,MagickTrue,exception);
3795 if ((linear_image == (Image *) NULL) || (shade_image == (Image *) NULL))
3796 {
3797 if (linear_image != (Image *) NULL)
3798 linear_image=DestroyImage(linear_image);
3799 if (shade_image != (Image *) NULL)
3800 shade_image=DestroyImage(shade_image);
3801 return((Image *) NULL);
3802 }
3803 if (SetImageStorageClass(shade_image,DirectClass,exception) == MagickFalse)
3804 {
3805 linear_image=DestroyImage(linear_image);
3806 shade_image=DestroyImage(shade_image);
3807 return((Image *) NULL);
3808 }
3809 /*
3810 Compute the light vector.
3811 */
3812 light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))*
3813 cos(DegreesToRadians(elevation));
3814 light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))*
3815 cos(DegreesToRadians(elevation));
3816 light.z=(double) QuantumRange*sin(DegreesToRadians(elevation));
3817 /*
3818 Shade image.
3819 */
3820 status=MagickTrue;
3821 progress=0;
3822 image_view=AcquireVirtualCacheView(linear_image,exception);
3823 shade_view=AcquireAuthenticCacheView(shade_image,exception);
3824#if defined(MAGICKCORE_OPENMP_SUPPORT)
3825 #pragma omp parallel for schedule(static) shared(progress,status) \
3826 magick_number_threads(linear_image,shade_image,linear_image->rows,1)
3827#endif
3828 for (y=0; y < (ssize_t) linear_image->rows; y++)
3829 {
3830 double
3831 distance,
3832 normal_distance,
3833 shade;
3834
3836 normal;
3837
3838 const Quantum
3839 *magick_restrict center,
3840 *magick_restrict p,
3841 *magick_restrict post,
3842 *magick_restrict pre;
3843
3844 Quantum
3845 *magick_restrict q;
3846
3847 ssize_t
3848 x;
3849
3850 if (status == MagickFalse)
3851 continue;
3852 p=GetCacheViewVirtualPixels(image_view,-1,y-1,linear_image->columns+2,3,
3853 exception);
3854 q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1,
3855 exception);
3856 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
3857 {
3858 status=MagickFalse;
3859 continue;
3860 }
3861 /*
3862 Shade this row of pixels.
3863 */
3864 normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */
3865 for (x=0; x < (ssize_t) linear_image->columns; x++)
3866 {
3867 ssize_t
3868 i;
3869
3870 /*
3871 Determine the surface normal and compute shading.
3872 */
3873 pre=p+GetPixelChannels(linear_image);
3874 center=pre+(linear_image->columns+2)*GetPixelChannels(linear_image);
3875 post=center+(linear_image->columns+2)*GetPixelChannels(linear_image);
3876 normal.x=(double) (
3877 GetShadeIntensity(linear_image,pre-GetPixelChannels(linear_image))+
3878 GetShadeIntensity(linear_image,center-GetPixelChannels(linear_image))+
3879 GetShadeIntensity(linear_image,post-GetPixelChannels(linear_image))-
3880 GetShadeIntensity(linear_image,pre+GetPixelChannels(linear_image))-
3881 GetShadeIntensity(linear_image,center+GetPixelChannels(linear_image))-
3882 GetShadeIntensity(linear_image,post+GetPixelChannels(linear_image)));
3883 normal.y=(double) (
3884 GetShadeIntensity(linear_image,post-GetPixelChannels(linear_image))+
3885 GetShadeIntensity(linear_image,post)+
3886 GetShadeIntensity(linear_image,post+GetPixelChannels(linear_image))-
3887 GetShadeIntensity(linear_image,pre-GetPixelChannels(linear_image))-
3888 GetShadeIntensity(linear_image,pre)-
3889 GetShadeIntensity(linear_image,pre+GetPixelChannels(linear_image)));
3890 if ((fabs(normal.x) <= MagickEpsilon) &&
3891 (fabs(normal.y) <= MagickEpsilon))
3892 shade=light.z;
3893 else
3894 {
3895 shade=0.0;
3896 distance=normal.x*light.x+normal.y*light.y+normal.z*light.z;
3897 if (distance > MagickEpsilon)
3898 {
3899 normal_distance=normal.x*normal.x+normal.y*normal.y+
3900 normal.z*normal.z;
3901 if (normal_distance > (MagickEpsilon*MagickEpsilon))
3902 shade=distance/sqrt((double) normal_distance);
3903 }
3904 }
3905 for (i=0; i < (ssize_t) GetPixelChannels(linear_image); i++)
3906 {
3907 PixelChannel
3908 channel;
3909
3910 PixelTrait
3911 shade_traits,
3912 traits;
3913
3914 channel=GetPixelChannelChannel(linear_image,i);
3915 traits=GetPixelChannelTraits(linear_image,channel);
3916 shade_traits=GetPixelChannelTraits(shade_image,channel);
3917 if ((traits == UndefinedPixelTrait) ||
3918 (shade_traits == UndefinedPixelTrait))
3919 continue;
3920 if ((shade_traits & CopyPixelTrait) != 0)
3921 {
3922 SetPixelChannel(shade_image,channel,center[i],q);
3923 continue;
3924 }
3925 if ((traits & UpdatePixelTrait) == 0)
3926 {
3927 SetPixelChannel(shade_image,channel,center[i],q);
3928 continue;
3929 }
3930 if (gray != MagickFalse)
3931 {
3932 SetPixelChannel(shade_image,channel,ClampToQuantum(shade),q);
3933 continue;
3934 }
3935 SetPixelChannel(shade_image,channel,ClampToQuantum(QuantumScale*
3936 shade*(double) center[i]),q);
3937 }
3938 p+=(ptrdiff_t) GetPixelChannels(linear_image);
3939 q+=(ptrdiff_t) GetPixelChannels(shade_image);
3940 }
3941 if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
3942 status=MagickFalse;
3943 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3944 {
3945 MagickBooleanType
3946 proceed;
3947
3948#if defined(MAGICKCORE_OPENMP_SUPPORT)
3949 #pragma omp atomic
3950#endif
3951 progress++;
3952 proceed=SetImageProgress(image,ShadeImageTag,progress,image->rows);
3953 if (proceed == MagickFalse)
3954 status=MagickFalse;
3955 }
3956 }
3957 shade_view=DestroyCacheView(shade_view);
3958 image_view=DestroyCacheView(image_view);
3959 linear_image=DestroyImage(linear_image);
3960 if (status == MagickFalse)
3961 shade_image=DestroyImage(shade_image);
3962 return(shade_image);
3963}
3964
3965/*
3966%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3967% %
3968% %
3969% %
3970% S h a r p e n I m a g e %
3971% %
3972% %
3973% %
3974%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3975%
3976% SharpenImage() sharpens the image. We convolve the image with a Gaussian
3977% operator of the given radius and standard deviation (sigma). For
3978% reasonable results, radius should be larger than sigma. Use a radius of 0
3979% and SharpenImage() selects a suitable radius for you.
3980%
3981% Using a separable kernel would be faster, but the negative weights cancel
3982% out on the corners of the kernel producing often undesirable ringing in the
3983% filtered result; this can be avoided by using a 2D gaussian shaped image
3984% sharpening kernel instead.
3985%
3986% The format of the SharpenImage method is:
3987%
3988% Image *SharpenImage(const Image *image,const double radius,
3989% const double sigma,ExceptionInfo *exception)
3990%
3991% A description of each parameter follows:
3992%
3993% o image: the image.
3994%
3995% o radius: the radius of the Gaussian, in pixels, not counting the center
3996% pixel.
3997%
3998% o sigma: the standard deviation of the Laplacian, in pixels.
3999%
4000% o exception: return any errors or warnings in this structure.
4001%
4002*/
4003MagickExport Image *SharpenImage(const Image *image,const double radius,
4004 const double sigma,ExceptionInfo *exception)
4005{
4006 double
4007 gamma,
4008 normalize;
4009
4010 Image
4011 *sharp_image;
4012
4014 *kernel_info;
4015
4016 ssize_t
4017 i;
4018
4019 size_t
4020 width;
4021
4022 ssize_t
4023 j,
4024 u,
4025 v;
4026
4027 assert(image != (const Image *) NULL);
4028 assert(image->signature == MagickCoreSignature);
4029 assert(exception != (ExceptionInfo *) NULL);
4030 assert(exception->signature == MagickCoreSignature);
4031 if (IsEventLogging() != MagickFalse)
4032 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4033 width=GetOptimalKernelWidth2D(radius,sigma);
4034 kernel_info=AcquireKernelInfo((const char *) NULL,exception);
4035 if (kernel_info == (KernelInfo *) NULL)
4036 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
4037 (void) memset(kernel_info,0,sizeof(*kernel_info));
4038 kernel_info->width=width;
4039 kernel_info->height=width;
4040 kernel_info->x=(ssize_t) (width-1)/2;
4041 kernel_info->y=(ssize_t) (width-1)/2;
4042 kernel_info->signature=MagickCoreSignature;
4043 kernel_info->values=(MagickRealType *) MagickAssumeAligned(
4044 AcquireAlignedMemory(kernel_info->width,kernel_info->height*
4045 sizeof(*kernel_info->values)));
4046 if (kernel_info->values == (MagickRealType *) NULL)
4047 {
4048 kernel_info=DestroyKernelInfo(kernel_info);
4049 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
4050 }
4051 normalize=0.0;
4052 j=(ssize_t) (kernel_info->width-1)/2;
4053 i=0;
4054 for (v=(-j); v <= j; v++)
4055 {
4056 for (u=(-j); u <= j; u++)
4057 {
4058 kernel_info->values[i]=(MagickRealType) (-exp(-((double) u*u+v*v)/(2.0*
4059 MagickSigma*MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
4060 normalize+=kernel_info->values[i];
4061 i++;
4062 }
4063 }
4064 kernel_info->values[i/2]=(double) ((-2.0)*normalize);
4065 normalize=0.0;
4066 for (i=0; i < (ssize_t) (kernel_info->width*kernel_info->height); i++)
4067 normalize+=kernel_info->values[i];
4068 gamma=MagickSafeReciprocal(normalize);
4069 for (i=0; i < (ssize_t) (kernel_info->width*kernel_info->height); i++)
4070 kernel_info->values[i]*=gamma;
4071 sharp_image=ConvolveImage(image,kernel_info,exception);
4072 kernel_info=DestroyKernelInfo(kernel_info);
4073 return(sharp_image);
4074}
4075
4076/*
4077%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4078% %
4079% %
4080% %
4081% S p r e a d I m a g e %
4082% %
4083% %
4084% %
4085%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4086%
4087% SpreadImage() is a special effects method that randomly displaces each
4088% pixel in a square area defined by the radius parameter.
4089%
4090% The format of the SpreadImage method is:
4091%
4092% Image *SpreadImage(const Image *image,
4093% const PixelInterpolateMethod method,const double radius,
4094% ExceptionInfo *exception)
4095%
4096% A description of each parameter follows:
4097%
4098% o image: the image.
4099%
4100% o method: interpolation method.
4101%
4102% o radius: choose a random pixel in a neighborhood of this extent.
4103%
4104% o exception: return any errors or warnings in this structure.
4105%
4106*/
4107MagickExport Image *SpreadImage(const Image *image,
4108 const PixelInterpolateMethod method,const double radius,
4109 ExceptionInfo *exception)
4110{
4111#define SpreadImageTag "Spread/Image"
4112
4113 CacheView
4114 *image_view,
4115 *spread_view;
4116
4117 Image
4118 *spread_image;
4119
4120 MagickBooleanType
4121 status;
4122
4123 MagickOffsetType
4124 progress;
4125
4127 **magick_restrict random_info;
4128
4129 size_t
4130 width;
4131
4132 ssize_t
4133 y;
4134
4135#if defined(MAGICKCORE_OPENMP_SUPPORT)
4136 unsigned long
4137 key;
4138#endif
4139
4140 /*
4141 Initialize spread image attributes.
4142 */
4143 assert(image != (Image *) NULL);
4144 assert(image->signature == MagickCoreSignature);
4145 assert(exception != (ExceptionInfo *) NULL);
4146 assert(exception->signature == MagickCoreSignature);
4147 if (IsEventLogging() != MagickFalse)
4148 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4149 spread_image=CloneImage(image,0,0,MagickTrue,exception);
4150 if (spread_image == (Image *) NULL)
4151 return((Image *) NULL);
4152 if (SetImageStorageClass(spread_image,DirectClass,exception) == MagickFalse)
4153 {
4154 spread_image=DestroyImage(spread_image);
4155 return((Image *) NULL);
4156 }
4157 /*
4158 Spread image.
4159 */
4160 status=MagickTrue;
4161 progress=0;
4162 width=GetOptimalKernelWidth1D(radius,0.5);
4163 random_info=AcquireRandomInfoTLS();
4164 image_view=AcquireVirtualCacheView(image,exception);
4165 spread_view=AcquireAuthenticCacheView(spread_image,exception);
4166#if defined(MAGICKCORE_OPENMP_SUPPORT)
4167 key=GetRandomSecretKey(random_info[0]);
4168 #pragma omp parallel for schedule(static) shared(progress,status) \
4169 magick_number_threads(image,spread_image,image->rows,key == ~0UL)
4170#endif
4171 for (y=0; y < (ssize_t) image->rows; y++)
4172 {
4173 const int
4174 id = GetOpenMPThreadId();
4175
4176 Quantum
4177 *magick_restrict q;
4178
4179 ssize_t
4180 x;
4181
4182 if (status == MagickFalse)
4183 continue;
4184 q=QueueCacheViewAuthenticPixels(spread_view,0,y,spread_image->columns,1,
4185 exception);
4186 if (q == (Quantum *) NULL)
4187 {
4188 status=MagickFalse;
4189 continue;
4190 }
4191 for (x=0; x < (ssize_t) image->columns; x++)
4192 {
4193 PointInfo
4194 point;
4195
4196 point.x=GetPseudoRandomValue(random_info[id]);
4197 point.y=GetPseudoRandomValue(random_info[id]);
4198 status=InterpolatePixelChannels(image,image_view,spread_image,method,
4199 (double) x+width*(point.x-0.5),(double) y+width*(point.y-0.5),q,
4200 exception);
4201 if (status == MagickFalse)
4202 break;
4203 q+=(ptrdiff_t) GetPixelChannels(spread_image);
4204 }
4205 if (SyncCacheViewAuthenticPixels(spread_view,exception) == MagickFalse)
4206 status=MagickFalse;
4207 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4208 {
4209 MagickBooleanType
4210 proceed;
4211
4212#if defined(MAGICKCORE_OPENMP_SUPPORT)
4213 #pragma omp atomic
4214#endif
4215 progress++;
4216 proceed=SetImageProgress(image,SpreadImageTag,progress,image->rows);
4217 if (proceed == MagickFalse)
4218 status=MagickFalse;
4219 }
4220 }
4221 spread_view=DestroyCacheView(spread_view);
4222 image_view=DestroyCacheView(image_view);
4223 random_info=DestroyRandomInfoTLS(random_info);
4224 if (status == MagickFalse)
4225 spread_image=DestroyImage(spread_image);
4226 return(spread_image);
4227}
4228
4229/*
4230%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4231% %
4232% %
4233% %
4234% U n s h a r p M a s k I m a g e %
4235% %
4236% %
4237% %
4238%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4239%
4240% UnsharpMaskImage() sharpens one or more image channels. We convolve the
4241% image with a Gaussian operator of the given radius and standard deviation
4242% (sigma). For reasonable results, radius should be larger than sigma. Use a
4243% radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
4244%
4245% The format of the UnsharpMaskImage method is:
4246%
4247% Image *UnsharpMaskImage(const Image *image,const double radius,
4248% const double sigma,const double amount,const double threshold,
4249% ExceptionInfo *exception)
4250%
4251% A description of each parameter follows:
4252%
4253% o image: the image.
4254%
4255% o radius: the radius of the Gaussian, in pixels, not counting the center
4256% pixel.
4257%
4258% o sigma: the standard deviation of the Gaussian, in pixels.
4259%
4260% o gain: the percentage of the difference between the original and the
4261% blur image that is added back into the original.
4262%
4263% o threshold: the threshold in pixels needed to apply the difference gain.
4264%
4265% o exception: return any errors or warnings in this structure.
4266%
4267*/
4268MagickExport Image *UnsharpMaskImage(const Image *image,const double radius,
4269 const double sigma,const double gain,const double threshold,
4270 ExceptionInfo *exception)
4271{
4272#define SharpenImageTag "Sharpen/Image"
4273
4274 CacheView
4275 *image_view,
4276 *unsharp_view;
4277
4278 Image
4279 *unsharp_image;
4280
4281 MagickBooleanType
4282 status;
4283
4284 MagickOffsetType
4285 progress;
4286
4287 double
4288 quantum_threshold;
4289
4290 ssize_t
4291 y;
4292
4293 assert(image != (const Image *) NULL);
4294 assert(image->signature == MagickCoreSignature);
4295 assert(exception != (ExceptionInfo *) NULL);
4296 assert(exception->signature == MagickCoreSignature);
4297 if (IsEventLogging() != MagickFalse)
4298 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4299/* This kernel appears to be broken.
4300#if defined(MAGICKCORE_OPENCL_SUPPORT)
4301 unsharp_image=AccelerateUnsharpMaskImage(image,radius,sigma,gain,threshold,
4302 exception);
4303 if (unsharp_image != (Image *) NULL)
4304 return(unsharp_image);
4305#endif
4306*/
4307 unsharp_image=BlurImage(image,radius,sigma,exception);
4308 if (unsharp_image == (Image *) NULL)
4309 return((Image *) NULL);
4310 quantum_threshold=(double) QuantumRange*threshold;
4311 /*
4312 Unsharp-mask image.
4313 */
4314 status=MagickTrue;
4315 progress=0;
4316 image_view=AcquireVirtualCacheView(image,exception);
4317 unsharp_view=AcquireAuthenticCacheView(unsharp_image,exception);
4318#if defined(MAGICKCORE_OPENMP_SUPPORT)
4319 #pragma omp parallel for schedule(static) shared(progress,status) \
4320 magick_number_threads(image,unsharp_image,image->rows,1)
4321#endif
4322 for (y=0; y < (ssize_t) image->rows; y++)
4323 {
4324 const Quantum
4325 *magick_restrict p;
4326
4327 Quantum
4328 *magick_restrict q;
4329
4330 ssize_t
4331 x;
4332
4333 if (status == MagickFalse)
4334 continue;
4335 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
4336 q=GetCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
4337 exception);
4338 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
4339 {
4340 status=MagickFalse;
4341 continue;
4342 }
4343 for (x=0; x < (ssize_t) image->columns; x++)
4344 {
4345 ssize_t
4346 i;
4347
4348 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4349 {
4350 double
4351 pixel;
4352
4353 PixelChannel
4354 channel;
4355
4356 PixelTrait
4357 traits,
4358 unsharp_traits;
4359
4360 channel=GetPixelChannelChannel(image,i);
4361 traits=GetPixelChannelTraits(image,channel);
4362 unsharp_traits=GetPixelChannelTraits(unsharp_image,channel);
4363 if ((traits == UndefinedPixelTrait) ||
4364 (unsharp_traits == UndefinedPixelTrait))
4365 continue;
4366 if ((unsharp_traits & CopyPixelTrait) != 0)
4367 {
4368 SetPixelChannel(unsharp_image,channel,p[i],q);
4369 continue;
4370 }
4371 pixel=(double) p[i]-(double) GetPixelChannel(unsharp_image,channel,q);
4372 if (fabs(2.0*pixel) < quantum_threshold)
4373 pixel=(double) p[i];
4374 else
4375 pixel=(double) p[i]+gain*pixel;
4376 SetPixelChannel(unsharp_image,channel,ClampToQuantum(pixel),q);
4377 }
4378 p+=(ptrdiff_t) GetPixelChannels(image);
4379 q+=(ptrdiff_t) GetPixelChannels(unsharp_image);
4380 }
4381 if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
4382 status=MagickFalse;
4383 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4384 {
4385 MagickBooleanType
4386 proceed;
4387
4388#if defined(MAGICKCORE_OPENMP_SUPPORT)
4389 #pragma omp atomic
4390#endif
4391 progress++;
4392 proceed=SetImageProgress(image,SharpenImageTag,progress,image->rows);
4393 if (proceed == MagickFalse)
4394 status=MagickFalse;
4395 }
4396 }
4397 unsharp_image->type=image->type;
4398 unsharp_view=DestroyCacheView(unsharp_view);
4399 image_view=DestroyCacheView(image_view);
4400 if (status == MagickFalse)
4401 unsharp_image=DestroyImage(unsharp_image);
4402 return(unsharp_image);
4403}