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