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