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