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