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