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