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