/* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % EEEEE FFFFF FFFFF EEEEE CCCC TTTTT % % E F F E C T % % EEE FFF FFF EEE C T % % E F F E C T % % EEEEE F F EEEEE CCCC T % % % % % % MagickCore Image Effects Methods % % % % Software Design % % John Cristy % % October 1996 % % % % % % Copyright 1999-2009 ImageMagick Studio LLC, a non-profit organization % % dedicated to making software imaging solutions freely available. % % % % You may not use this file except in compliance with the License. You may % % obtain a copy of the License at % % % % http://www.imagemagick.org/script/license.php % % % % Unless required by applicable law or agreed to in writing, software % % distributed under the License is distributed on an "AS IS" BASIS, % % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. % % See the License for the specific language governing permissions and % % limitations under the License. % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % */ /* Include declarations. */ #include "magick/studio.h" #include "magick/property.h" #include "magick/blob.h" #include "magick/cache-view.h" #include "magick/color.h" #include "magick/color-private.h" #include "magick/colorspace.h" #include "magick/constitute.h" #include "magick/decorate.h" #include "magick/draw.h" #include "magick/enhance.h" #include "magick/exception.h" #include "magick/exception-private.h" #include "magick/effect.h" #include "magick/fx.h" #include "magick/gem.h" #include "magick/geometry.h" #include "magick/image-private.h" #include "magick/list.h" #include "magick/log.h" #include "magick/memory_.h" #include "magick/monitor.h" #include "magick/monitor-private.h" #include "magick/montage.h" #include "magick/paint.h" #include "magick/pixel-private.h" #include "magick/property.h" #include "magick/quantize.h" #include "magick/quantum.h" #include "magick/random_.h" #include "magick/random-private.h" #include "magick/resample.h" #include "magick/resample-private.h" #include "magick/resize.h" #include "magick/resource_.h" #include "magick/segment.h" #include "magick/shear.h" #include "magick/signature-private.h" #include "magick/string_.h" #include "magick/thread-private.h" #include "magick/transform.h" #include "magick/threshold.h" /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % A d a p t i v e B l u r I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % AdaptiveBlurImage() adaptively blurs the image by blurring less % intensely near image edges and more intensely far from edges. We blur the % image with a Gaussian operator of the given radius and standard deviation % (sigma). For reasonable results, radius should be larger than sigma. Use a % radius of 0 and AdaptiveBlurImage() selects a suitable radius for you. % % The format of the AdaptiveBlurImage method is: % % Image *AdaptiveBlurImage(const Image *image,const double radius, % const double sigma,ExceptionInfo *exception) % Image *AdaptiveBlurImageChannel(const Image *image, % const ChannelType channel,double radius,const double sigma, % ExceptionInfo *exception) % % A description of each parameter follows: % % o image: the image. % % o channel: the channel type. % % o radius: the radius of the Gaussian, in pixels, not counting the center % pixel. % % o sigma: the standard deviation of the Laplacian, in pixels. % % o exception: return any errors or warnings in this structure. % */ MagickExport Image *AdaptiveBlurImage(const Image *image,const double radius, const double sigma,ExceptionInfo *exception) { Image *blur_image; blur_image=AdaptiveBlurImageChannel(image,DefaultChannels,radius,sigma, exception); return(blur_image); } MagickExport Image *AdaptiveBlurImageChannel(const Image *image, const ChannelType channel,const double radius,const double sigma, ExceptionInfo *exception) { #define AdaptiveBlurImageTag "Convolve/Image" #define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma) double **kernel; Image *blur_image, *edge_image, *gaussian_image; long j, progress, y; MagickBooleanType status; MagickPixelPacket zero; MagickRealType alpha, bias, normalize; register long i, u, v; unsigned long width; CacheView *blur_view, *edge_view, *image_view; assert(image != (const Image *) NULL); assert(image->signature == MagickSignature); if (image->debug != MagickFalse) (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); assert(exception != (ExceptionInfo *) NULL); assert(exception->signature == MagickSignature); blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception); if (blur_image == (Image *) NULL) return((Image *) NULL); if (fabs(sigma) <= MagickEpsilon) return(blur_image); if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse) { InheritException(exception,&blur_image->exception); blur_image=DestroyImage(blur_image); return((Image *) NULL); } /* Edge detect the image brighness channel, level, blur, and level again. */ edge_image=EdgeImage(image,radius,exception); if (edge_image == (Image *) NULL) { blur_image=DestroyImage(blur_image); return((Image *) NULL); } (void) LevelImage(edge_image,"20%,95%"); gaussian_image=GaussianBlurImage(edge_image,radius,sigma,exception); if (gaussian_image != (Image *) NULL) { edge_image=DestroyImage(edge_image); edge_image=gaussian_image; } (void) LevelImage(edge_image,"10%,95%"); /* Create a set of kernels from maximum (radius,sigma) to minimum. */ width=GetOptimalKernelWidth2D(radius,sigma); kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel)); if (kernel == (double **) NULL) { edge_image=DestroyImage(edge_image); blur_image=DestroyImage(blur_image); ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); } (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel)); for (i=0; i < (long) width; i+=2) { kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)* sizeof(**kernel)); if (kernel[i] == (double *) NULL) break; j=0; for (v=(-((long) (width-i)/2)); v <= (long) ((width-i)/2); v++) { for (u=(-((long) (width-i)/2)); u <= (long) ((width-i)/2); u++) { alpha=exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma)); kernel[i][j]=(double) (alpha/(2.0*MagickPI*MagickSigma*MagickSigma)); j++; } } normalize=0.0; for (j=0; j < (long) ((width-i)*(width-i)); j++) normalize+=kernel[i][j]; if (fabs(normalize) <= MagickEpsilon) normalize=1.0; normalize=1.0/normalize; for (j=0; j < (long) ((width-i)*(width-i)); j++) kernel[i][j]=(double) (normalize*kernel[i][j]); } if (i < (long) width) { for (i-=2; i >= 0; i-=2) kernel[i]=(double *) RelinquishMagickMemory(kernel[i]); kernel=(double **) RelinquishMagickMemory(kernel); edge_image=DestroyImage(edge_image); blur_image=DestroyImage(blur_image); ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); } /* Adaptively blur image. */ status=MagickTrue; progress=0; bias=image->bias; GetMagickPixelPacket(image,&zero); image_view=AcquireCacheView(image); edge_view=AcquireCacheView(edge_image); blur_view=AcquireCacheView(blur_image); #if defined(MAGICKCORE_OPENMP_SUPPORT) #pragma omp parallel for schedule(dynamic) shared(progress,status) #endif for (y=0; y < (long) blur_image->rows; y++) { register const IndexPacket *__restrict indexes; register const PixelPacket *__restrict p, *__restrict r; register IndexPacket *__restrict blur_indexes; register long x; register PixelPacket *__restrict q; if (status == MagickFalse) continue; r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception); q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1, exception); if ((r == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL)) { status=MagickFalse; continue; } blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view); for (x=0; x < (long) blur_image->columns; x++) { MagickPixelPacket pixel; MagickRealType alpha, gamma; register const double *__restrict k; register long i, u, v; gamma=0.0; i=(long) (width*QuantumScale*PixelIntensity(r)+0.5); if (i < 0) i=0; else if (i > (long) width) i=(long) width; if ((i & 0x01) != 0) i--; p=GetCacheViewVirtualPixels(image_view,x-((long) (width-i)/2L),y-(long) ((width-i)/2L),width-i,width-i,exception); if (p == (const PixelPacket *) NULL) break; indexes=GetCacheViewVirtualIndexQueue(image_view); pixel=zero; k=kernel[i]; for (v=0; v < (long) (width-i); v++) { for (u=0; u < (long) (width-i); u++) { alpha=1.0; if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse)) alpha=(MagickRealType) (QuantumScale*(QuantumRange-p->opacity)); if ((channel & RedChannel) != 0) pixel.red+=(*k)*alpha*p->red; if ((channel & GreenChannel) != 0) pixel.green+=(*k)*alpha*p->green; if ((channel & BlueChannel) != 0) pixel.blue+=(*k)*alpha*p->blue; if ((channel & OpacityChannel) != 0) pixel.opacity+=(*k)*p->opacity; if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace)) pixel.index+=(*k)*alpha*indexes[x+(width-i)*v+u]; gamma+=(*k)*alpha; k++; p++; } } gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma); if ((channel & RedChannel) != 0) q->red=RoundToQuantum(gamma*pixel.red+bias); if ((channel & GreenChannel) != 0) q->green=RoundToQuantum(gamma*pixel.green+bias); if ((channel & BlueChannel) != 0) q->blue=RoundToQuantum(gamma*pixel.blue+bias); if ((channel & OpacityChannel) != 0) q->opacity=RoundToQuantum(pixel.opacity+bias); if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace)) blur_indexes[x]=RoundToQuantum(gamma*pixel.index+bias); q++; r++; } if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse) status=MagickFalse; if (image->progress_monitor != (MagickProgressMonitor) NULL) { MagickBooleanType proceed; #if defined(MAGICKCORE_OPENMP_SUPPORT) #pragma omp critical (MagickCore_AdaptiveBlurImageChannel) #endif proceed=SetImageProgress(image,AdaptiveBlurImageTag,progress++, image->rows); if (proceed == MagickFalse) status=MagickFalse; } } blur_image->type=image->type; blur_view=DestroyCacheView(blur_view); edge_view=DestroyCacheView(edge_view); image_view=DestroyCacheView(image_view); edge_image=DestroyImage(edge_image); for (i=0; i < (long) width; i+=2) kernel[i]=(double *) RelinquishMagickMemory(kernel[i]); kernel=(double **) RelinquishMagickMemory(kernel); if (status == MagickFalse) blur_image=DestroyImage(blur_image); return(blur_image); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % A d a p t i v e S h a r p e n I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % AdaptiveSharpenImage() adaptively sharpens the image by sharpening more % intensely near image edges and less intensely far from edges. We sharpen the % image with a Gaussian operator of the given radius and standard deviation % (sigma). For reasonable results, radius should be larger than sigma. Use a % radius of 0 and AdaptiveSharpenImage() selects a suitable radius for you. % % The format of the AdaptiveSharpenImage method is: % % Image *AdaptiveSharpenImage(const Image *image,const double radius, % const double sigma,ExceptionInfo *exception) % Image *AdaptiveSharpenImageChannel(const Image *image, % const ChannelType channel,double radius,const double sigma, % ExceptionInfo *exception) % % A description of each parameter follows: % % o image: the image. % % o channel: the channel type. % % o radius: the radius of the Gaussian, in pixels, not counting the center % pixel. % % o sigma: the standard deviation of the Laplacian, in pixels. % % o exception: return any errors or warnings in this structure. % */ MagickExport Image *AdaptiveSharpenImage(const Image *image,const double radius, const double sigma,ExceptionInfo *exception) { Image *sharp_image; sharp_image=AdaptiveSharpenImageChannel(image,DefaultChannels,radius,sigma, exception); return(sharp_image); } MagickExport Image *AdaptiveSharpenImageChannel(const Image *image, const ChannelType channel,const double radius,const double sigma, ExceptionInfo *exception) { #define AdaptiveSharpenImageTag "Convolve/Image" #define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma) double **kernel; Image *sharp_image, *edge_image, *gaussian_image; long j, progress, y; MagickBooleanType status; MagickPixelPacket zero; MagickRealType alpha, bias, normalize; register long i, u, v; unsigned long width; CacheView *sharp_view, *edge_view, *image_view; assert(image != (const Image *) NULL); assert(image->signature == MagickSignature); if (image->debug != MagickFalse) (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); assert(exception != (ExceptionInfo *) NULL); assert(exception->signature == MagickSignature); sharp_image=CloneImage(image,0,0,MagickTrue,exception); if (sharp_image == (Image *) NULL) return((Image *) NULL); if (fabs(sigma) <= MagickEpsilon) return(sharp_image); if (SetImageStorageClass(sharp_image,DirectClass) == MagickFalse) { InheritException(exception,&sharp_image->exception); sharp_image=DestroyImage(sharp_image); return((Image *) NULL); } /* Edge detect the image brighness channel, level, sharp, and level again. */ edge_image=EdgeImage(image,radius,exception); if (edge_image == (Image *) NULL) { sharp_image=DestroyImage(sharp_image); return((Image *) NULL); } (void) LevelImage(edge_image,"20%,95%"); gaussian_image=GaussianBlurImage(edge_image,radius,sigma,exception); if (gaussian_image != (Image *) NULL) { edge_image=DestroyImage(edge_image); edge_image=gaussian_image; } (void) LevelImage(edge_image,"10%,95%"); /* Create a set of kernels from maximum (radius,sigma) to minimum. */ width=GetOptimalKernelWidth2D(radius,sigma); kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel)); if (kernel == (double **) NULL) { edge_image=DestroyImage(edge_image); sharp_image=DestroyImage(sharp_image); ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); } (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel)); for (i=0; i < (long) width; i+=2) { kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)* sizeof(**kernel)); if (kernel[i] == (double *) NULL) break; j=0; for (v=(-((long) (width-i)/2)); v <= (long) ((width-i)/2); v++) { for (u=(-((long) (width-i)/2)); u <= (long) ((width-i)/2); u++) { alpha=exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma)); kernel[i][j]=(double) (-alpha/(2.0*MagickPI*MagickSigma*MagickSigma)); j++; } } normalize=0.0; for (j=0; j < (long) ((width-i)*(width-i)); j++) normalize+=kernel[i][j]; if (fabs(normalize) <= MagickEpsilon) normalize=1.0; normalize=1.0/normalize; for (j=0; j < (long) ((width-i)*(width-i)); j++) kernel[i][j]=(double) (normalize*kernel[i][j]); } if (i < (long) width) { for (i-=2; i >= 0; i-=2) kernel[i]=(double *) RelinquishMagickMemory(kernel[i]); kernel=(double **) RelinquishMagickMemory(kernel); edge_image=DestroyImage(edge_image); sharp_image=DestroyImage(sharp_image); ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); } /* Adaptively sharpen image. */ status=MagickTrue; progress=0; bias=image->bias; GetMagickPixelPacket(image,&zero); image_view=AcquireCacheView(image); edge_view=AcquireCacheView(edge_image); sharp_view=AcquireCacheView(sharp_image); #if defined(MAGICKCORE_OPENMP_SUPPORT) #pragma omp parallel for schedule(dynamic) shared(progress,status) #endif for (y=0; y < (long) sharp_image->rows; y++) { register const IndexPacket *__restrict indexes; register const PixelPacket *__restrict p, *__restrict r; register IndexPacket *__restrict sharp_indexes; register long x; register PixelPacket *__restrict q; if (status == MagickFalse) continue; r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception); q=QueueCacheViewAuthenticPixels(sharp_view,0,y,sharp_image->columns,1, exception); if ((r == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL)) { status=MagickFalse; continue; } sharp_indexes=GetCacheViewAuthenticIndexQueue(sharp_view); for (x=0; x < (long) sharp_image->columns; x++) { MagickPixelPacket pixel; MagickRealType alpha, gamma; register const double *__restrict k; register long i, u, v; gamma=0.0; i=(long) (width*(QuantumRange-QuantumScale*PixelIntensity(r))+0.5); if (i < 0) i=0; else if (i > (long) width) i=(long) width; if ((i & 0x01) != 0) i--; p=GetCacheViewVirtualPixels(image_view,x-((long) (width-i)/2L),y-(long) ((width-i)/2L),width-i,width-i,exception); if (p == (const PixelPacket *) NULL) break; indexes=GetCacheViewVirtualIndexQueue(image_view); k=kernel[i]; pixel=zero; for (v=0; v < (long) (width-i); v++) { for (u=0; u < (long) (width-i); u++) { alpha=1.0; if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse)) alpha=(MagickRealType) (QuantumScale*(QuantumRange-p->opacity)); if ((channel & RedChannel) != 0) pixel.red+=(*k)*alpha*p->red; if ((channel & GreenChannel) != 0) pixel.green+=(*k)*alpha*p->green; if ((channel & BlueChannel) != 0) pixel.blue+=(*k)*alpha*p->blue; if ((channel & OpacityChannel) != 0) pixel.opacity+=(*k)*p->opacity; if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace)) pixel.index+=(*k)*alpha*indexes[x+(width-i)*v+u]; gamma+=(*k)*alpha; k++; p++; } } gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma); if ((channel & RedChannel) != 0) q->red=RoundToQuantum(gamma*pixel.red+bias); if ((channel & GreenChannel) != 0) q->green=RoundToQuantum(gamma*pixel.green+bias); if ((channel & BlueChannel) != 0) q->blue=RoundToQuantum(gamma*pixel.blue+bias); if ((channel & OpacityChannel) != 0) q->opacity=RoundToQuantum(pixel.opacity+bias); if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace)) sharp_indexes[x]=RoundToQuantum(gamma*pixel.index+bias); q++; r++; } if (SyncCacheViewAuthenticPixels(sharp_view,exception) == MagickFalse) status=MagickFalse; if (image->progress_monitor != (MagickProgressMonitor) NULL) { MagickBooleanType proceed; #if defined(MAGICKCORE_OPENMP_SUPPORT) #pragma omp critical (MagickCore_AdaptiveSharpenImageChannel) #endif proceed=SetImageProgress(image,AdaptiveSharpenImageTag,progress++, image->rows); if (proceed == MagickFalse) status=MagickFalse; } } sharp_image->type=image->type; sharp_view=DestroyCacheView(sharp_view); edge_view=DestroyCacheView(edge_view); image_view=DestroyCacheView(image_view); edge_image=DestroyImage(edge_image); for (i=0; i < (long) width; i+=2) kernel[i]=(double *) RelinquishMagickMemory(kernel[i]); kernel=(double **) RelinquishMagickMemory(kernel); if (status == MagickFalse) sharp_image=DestroyImage(sharp_image); return(sharp_image); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % B l u r I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % BlurImage() blurs an image. We convolve the image with a Gaussian operator % of the given radius and standard deviation (sigma). For reasonable results, % the radius should be larger than sigma. Use a radius of 0 and BlurImage() % selects a suitable radius for you. % % BlurImage() differs from GaussianBlurImage() in that it uses a separable % kernel which is faster but mathematically equivalent to the non-separable % kernel. % % The format of the BlurImage method is: % % Image *BlurImage(const Image *image,const double radius, % const double sigma,ExceptionInfo *exception) % Image *BlurImageChannel(const Image *image,const ChannelType channel, % const double radius,const double sigma,ExceptionInfo *exception) % % A description of each parameter follows: % % o image: the image. % % o channel: the channel type. % % o radius: the radius of the Gaussian, in pixels, not counting the center % pixel. % % o sigma: the standard deviation of the Gaussian, in pixels. % % o exception: return any errors or warnings in this structure. % */ MagickExport Image *BlurImage(const Image *image,const double radius, const double sigma,ExceptionInfo *exception) { Image *blur_image; blur_image=BlurImageChannel(image,DefaultChannels,radius,sigma,exception); return(blur_image); } static double *GetBlurKernel(unsigned long width,const MagickRealType sigma) { #define KernelRank 3 double *kernel; long bias; MagickRealType alpha, normalize; register long i; /* Generate a 1-D convolution kernel. */ (void) LogMagickEvent(TraceEvent,GetMagickModule(),"..."); kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel)); if (kernel == (double *) NULL) return(0); (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel)); bias=KernelRank*(long) width/2; for (i=(-bias); i <= bias; i++) { alpha=exp((-((double) (i*i))/(double) (2.0*KernelRank*KernelRank* MagickSigma*MagickSigma))); kernel[(i+bias)/KernelRank]+=(double) (alpha/(MagickSQ2PI*sigma)); } normalize=0.0; for (i=0; i < (long) width; i++) normalize+=kernel[i]; for (i=0; i < (long) width; i++) kernel[i]/=normalize; return(kernel); } MagickExport Image *BlurImageChannel(const Image *image, const ChannelType channel,const double radius,const double sigma, ExceptionInfo *exception) { #define BlurImageTag "Blur/Image" double *kernel; Image *blur_image; long progress, x, y; MagickBooleanType status; MagickPixelPacket zero; MagickRealType bias; register long i; unsigned long width; CacheView *blur_view, *image_view; /* Initialize blur image attributes. */ assert(image != (Image *) NULL); assert(image->signature == MagickSignature); if (image->debug != MagickFalse) (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); assert(exception != (ExceptionInfo *) NULL); assert(exception->signature == MagickSignature); blur_image=CloneImage(image,0,0,MagickTrue,exception); if (blur_image == (Image *) NULL) return((Image *) NULL); if (fabs(sigma) <= MagickEpsilon) return(blur_image); if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse) { InheritException(exception,&blur_image->exception); blur_image=DestroyImage(blur_image); return((Image *) NULL); } width=GetOptimalKernelWidth1D(radius,sigma); kernel=GetBlurKernel(width,sigma); if (kernel == (double *) NULL) { blur_image=DestroyImage(blur_image); ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); } if (image->debug != MagickFalse) { char format[MaxTextExtent], *message; register const double *k; (void) LogMagickEvent(TransformEvent,GetMagickModule(), " BlurImage with %ld kernel:",width); message=AcquireString(""); k=kernel; for (i=0; i < (long) width; i++) { *message='\0'; (void) FormatMagickString(format,MaxTextExtent,"%ld: ",i); (void) ConcatenateString(&message,format); (void) FormatMagickString(format,MaxTextExtent,"%g ",*k++); (void) ConcatenateString(&message,format); (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message); } message=DestroyString(message); } /* Blur rows. */ status=MagickTrue; progress=0; GetMagickPixelPacket(image,&zero); bias=image->bias; image_view=AcquireCacheView(image); blur_view=AcquireCacheView(blur_image); #if defined(MAGICKCORE_OPENMP_SUPPORT) #pragma omp parallel for schedule(dynamic) shared(progress,status) #endif for (y=0; y < (long) blur_image->rows; y++) { register const IndexPacket *__restrict indexes; register const PixelPacket *__restrict p; register IndexPacket *__restrict blur_indexes; register long x; register PixelPacket *__restrict q; if (status == MagickFalse) continue; p=GetCacheViewVirtualPixels(image_view,-((long) width/2L),y,image->columns+ width,1,exception); q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1, exception); if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL)) { status=MagickFalse; continue; } indexes=GetCacheViewVirtualIndexQueue(image_view); blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view); for (x=0; x < (long) blur_image->columns; x++) { MagickPixelPacket pixel; register const double *__restrict k; register const PixelPacket *__restrict kernel_pixels; register long i; pixel=zero; k=kernel; kernel_pixels=p; if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse)) { for (i=0; i < (long) width; i++) { pixel.red+=(*k)*kernel_pixels->red; pixel.green+=(*k)*kernel_pixels->green; pixel.blue+=(*k)*kernel_pixels->blue; k++; kernel_pixels++; } if ((channel & RedChannel) != 0) q->red=RoundToQuantum(pixel.red+bias); if ((channel & GreenChannel) != 0) q->green=RoundToQuantum(pixel.green+bias); if ((channel & BlueChannel) != 0) q->blue=RoundToQuantum(pixel.blue+bias); if ((channel & OpacityChannel) != 0) { k=kernel; kernel_pixels=p; for (i=0; i < (long) width; i++) { pixel.opacity+=(*k)*kernel_pixels->opacity; k++; kernel_pixels++; } q->opacity=RoundToQuantum(pixel.opacity+bias); } if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace)) { register const IndexPacket *__restrict kernel_indexes; k=kernel; kernel_indexes=indexes; for (i=0; i < (long) width; i++) { pixel.index+=(*k)*(*kernel_indexes); k++; kernel_indexes++; } blur_indexes[x]=RoundToQuantum(pixel.index+bias); } } else { MagickRealType alpha, gamma; gamma=0.0; for (i=0; i < (long) width; i++) { alpha=(MagickRealType) (QuantumScale*(QuantumRange- kernel_pixels->opacity)); pixel.red+=(*k)*alpha*kernel_pixels->red; pixel.green+=(*k)*alpha*kernel_pixels->green; pixel.blue+=(*k)*alpha*kernel_pixels->blue; gamma+=(*k)*alpha; k++; kernel_pixels++; } gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma); if ((channel & RedChannel) != 0) q->red=RoundToQuantum(gamma*pixel.red+bias); if ((channel & GreenChannel) != 0) q->green=RoundToQuantum(gamma*pixel.green+bias); if ((channel & BlueChannel) != 0) q->blue=RoundToQuantum(gamma*pixel.blue+bias); if ((channel & OpacityChannel) != 0) { k=kernel; kernel_pixels=p; for (i=0; i < (long) width; i++) { pixel.opacity+=(*k)*kernel_pixels->opacity; k++; kernel_pixels++; } q->opacity=RoundToQuantum(pixel.opacity+bias); } if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace)) { register const IndexPacket *__restrict kernel_indexes; k=kernel; kernel_pixels=p; kernel_indexes=indexes; for (i=0; i < (long) width; i++) { alpha=(MagickRealType) (QuantumScale*(QuantumRange- kernel_pixels->opacity)); pixel.index+=(*k)*alpha*(*kernel_indexes); k++; kernel_pixels++; kernel_indexes++; } blur_indexes[x]=RoundToQuantum(gamma*pixel.index+bias); } } p++; q++; } if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse) status=MagickFalse; if (image->progress_monitor != (MagickProgressMonitor) NULL) { MagickBooleanType proceed; #if defined(MAGICKCORE_OPENMP_SUPPORT) #pragma omp critical (MagickCore_BlurImageChannel) #endif proceed=SetImageProgress(image,BlurImageTag,progress++,blur_image->rows+ blur_image->columns); if (proceed == MagickFalse) status=MagickFalse; } } blur_view=DestroyCacheView(blur_view); image_view=DestroyCacheView(image_view); /* Blur columns. */ image_view=AcquireCacheView(blur_image); blur_view=AcquireCacheView(blur_image); #if defined(MAGICKCORE_OPENMP_SUPPORT) #pragma omp parallel for shared(progress,status) #endif for (x=0; x < (long) blur_image->columns; x++) { register const IndexPacket *__restrict indexes; register const PixelPacket *__restrict p; register IndexPacket *__restrict blur_indexes; register long y; register PixelPacket *__restrict q; if (status == MagickFalse) continue; p=GetCacheViewVirtualPixels(image_view,x,-((long) width/2L),1,image->rows+ width,exception); q=GetCacheViewAuthenticPixels(blur_view,x,0,1,blur_image->rows,exception); if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL)) { status=MagickFalse; continue; } indexes=GetCacheViewVirtualIndexQueue(image_view); blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view); for (y=0; y < (long) blur_image->rows; y++) { MagickPixelPacket pixel; register const double *__restrict k; register const PixelPacket *__restrict kernel_pixels; register long i; pixel=zero; k=kernel; kernel_pixels=p; if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse)) { for (i=0; i < (long) width; i++) { pixel.red+=(*k)*kernel_pixels->red; pixel.green+=(*k)*kernel_pixels->green; pixel.blue+=(*k)*kernel_pixels->blue; k++; kernel_pixels++; } if ((channel & RedChannel) != 0) q->red=RoundToQuantum(pixel.red+bias); if ((channel & GreenChannel) != 0) q->green=RoundToQuantum(pixel.green+bias); if ((channel & BlueChannel) != 0) q->blue=RoundToQuantum(pixel.blue+bias); if ((channel & OpacityChannel) != 0) { k=kernel; kernel_pixels=p; for (i=0; i < (long) width; i++) { pixel.opacity+=(*k)*kernel_pixels->opacity; k++; kernel_pixels++; } q->opacity=RoundToQuantum(pixel.opacity+bias); } if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace)) { register const IndexPacket *__restrict kernel_indexes; k=kernel; kernel_indexes=indexes; for (i=0; i < (long) width; i++) { pixel.index+=(*k)*(*kernel_indexes); k++; kernel_indexes++; } blur_indexes[y]=RoundToQuantum(pixel.index+bias); } } else { MagickRealType alpha, gamma; gamma=0.0; for (i=0; i < (long) width; i++) { alpha=(MagickRealType) (QuantumScale*(QuantumRange- kernel_pixels->opacity)); pixel.red+=(*k)*alpha*kernel_pixels->red; pixel.green+=(*k)*alpha*kernel_pixels->green; pixel.blue+=(*k)*alpha*kernel_pixels->blue; gamma+=(*k)*alpha; k++; kernel_pixels++; } gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma); if ((channel & RedChannel) != 0) q->red=RoundToQuantum(gamma*pixel.red+bias); if ((channel & GreenChannel) != 0) q->green=RoundToQuantum(gamma*pixel.green+bias); if ((channel & BlueChannel) != 0) q->blue=RoundToQuantum(gamma*pixel.blue+bias); if ((channel & OpacityChannel) != 0) { k=kernel; kernel_pixels=p; for (i=0; i < (long) width; i++) { pixel.opacity+=(*k)*kernel_pixels->opacity; k++; kernel_pixels++; } q->opacity=RoundToQuantum(pixel.opacity+bias); } if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace)) { register const IndexPacket *__restrict kernel_indexes; k=kernel; kernel_pixels=p; kernel_indexes=indexes; for (i=0; i < (long) width; i++) { alpha=(MagickRealType) (QuantumScale*(QuantumRange- kernel_pixels->opacity)); pixel.index+=(*k)*alpha*(*kernel_indexes); k++; kernel_pixels++; kernel_indexes++; } blur_indexes[y]=RoundToQuantum(gamma*pixel.index+bias); } } p++; q++; } if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse) status=MagickFalse; if (image->progress_monitor != (MagickProgressMonitor) NULL) { MagickBooleanType proceed; #if defined(MAGICKCORE_OPENMP_SUPPORT) #pragma omp critical (MagickCore_BlurImageChannel) #endif proceed=SetImageProgress(image,BlurImageTag,progress++,blur_image->rows+ blur_image->columns); if (proceed == MagickFalse) status=MagickFalse; } } blur_view=DestroyCacheView(blur_view); image_view=DestroyCacheView(image_view); kernel=(double *) RelinquishMagickMemory(kernel); if (status == MagickFalse) blur_image=DestroyImage(blur_image); blur_image->type=image->type; return(blur_image); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % D e s p e c k l e I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % DespeckleImage() reduces the speckle noise in an image while perserving the % edges of the original image. % % The format of the DespeckleImage method is: % % Image *DespeckleImage(const Image *image,ExceptionInfo *exception) % % A description of each parameter follows: % % o image: the image. % % o exception: return any errors or warnings in this structure. % */ static Quantum **DestroyPixelThreadSet(Quantum **pixels) { register long i; assert(pixels != (Quantum **) NULL); for (i=0; i < (long) GetOpenMPMaximumThreads(); i++) if (pixels[i] != (Quantum *) NULL) pixels[i]=(Quantum *) RelinquishMagickMemory(pixels[i]); pixels=(Quantum **) RelinquishAlignedMemory(pixels); return(pixels); } static Quantum **AcquirePixelThreadSet(const size_t count) { register long i; Quantum **pixels; unsigned long number_threads; number_threads=GetOpenMPMaximumThreads(); pixels=(Quantum **) AcquireAlignedMemory(number_threads,sizeof(*pixels)); if (pixels == (Quantum **) NULL) return((Quantum **) NULL); (void) ResetMagickMemory(pixels,0,number_threads*sizeof(*pixels)); for (i=0; i < (long) number_threads; i++) { pixels[i]=(Quantum *) AcquireQuantumMemory(count,sizeof(**pixels)); if (pixels[i] == (Quantum *) NULL) return(DestroyPixelThreadSet(pixels)); } return(pixels); } static void Hull(const long x_offset,const long y_offset, const unsigned long columns,const unsigned long rows,Quantum *f,Quantum *g, const int polarity) { long y; MagickRealType v; register long x; register Quantum *p, *q, *r, *s; assert(f != (Quantum *) NULL); assert(g != (Quantum *) NULL); p=f+(columns+2); q=g+(columns+2); r=p+(y_offset*((long) columns+2)+x_offset); for (y=0; y < (long) rows; y++) { p++; q++; r++; if (polarity > 0) for (x=(long) columns; x != 0; x--) { v=(MagickRealType) (*p); if ((MagickRealType) *r >= (v+(MagickRealType) ScaleCharToQuantum(2))) v+=ScaleCharToQuantum(1); *q=(Quantum) v; p++; q++; r++; } else for (x=(long) columns; x != 0; x--) { v=(MagickRealType) (*p); if ((MagickRealType) *r <= (v-(MagickRealType) ScaleCharToQuantum(2))) v-=(long) ScaleCharToQuantum(1); *q=(Quantum) v; p++; q++; r++; } p++; q++; r++; } p=f+(columns+2); q=g+(columns+2); r=q+(y_offset*((long) columns+2)+x_offset); s=q-(y_offset*((long) columns+2)+x_offset); for (y=0; y < (long) rows; y++) { p++; q++; r++; s++; if (polarity > 0) for (x=(long) columns; x != 0; x--) { v=(MagickRealType) (*q); if (((MagickRealType) *s >= (v+(MagickRealType) ScaleCharToQuantum(2))) && ((MagickRealType) *r > v)) v+=ScaleCharToQuantum(1); *p=(Quantum) v; p++; q++; r++; s++; } else for (x=(long) columns; x != 0; x--) { v=(MagickRealType) (*q); if (((MagickRealType) *s <= (v-(MagickRealType) ScaleCharToQuantum(2))) && ((MagickRealType) *r < v)) v-=(MagickRealType) ScaleCharToQuantum(1); *p=(Quantum) v; p++; q++; r++; s++; } p++; q++; r++; s++; } } MagickExport Image *DespeckleImage(const Image *image,ExceptionInfo *exception) { #define DespeckleImageTag "Despeckle/Image" Image *despeckle_image; long channel; MagickBooleanType status; Quantum **buffers, **pixels; size_t length; static const int X[4]= {0, 1, 1,-1}, Y[4]= {1, 0, 1, 1}; CacheView *despeckle_view, *image_view; /* Allocate despeckled image. */ assert(image != (const Image *) NULL); assert(image->signature == MagickSignature); if (image->debug != MagickFalse) (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); assert(exception != (ExceptionInfo *) NULL); assert(exception->signature == MagickSignature); despeckle_image=CloneImage(image,image->columns,image->rows,MagickTrue, exception); if (despeckle_image == (Image *) NULL) return((Image *) NULL); if (SetImageStorageClass(despeckle_image,DirectClass) == MagickFalse) { InheritException(exception,&despeckle_image->exception); despeckle_image=DestroyImage(despeckle_image); return((Image *) NULL); } /* Allocate image buffers. */ length=(size_t) ((image->columns+2)*(image->rows+2)); pixels=AcquirePixelThreadSet(length); buffers=AcquirePixelThreadSet(length); if ((pixels == (Quantum **) NULL) || (buffers == (Quantum **) NULL)) { if (buffers != (Quantum **) NULL) buffers=DestroyPixelThreadSet(buffers); if (pixels != (Quantum **) NULL) pixels=DestroyPixelThreadSet(pixels); despeckle_image=DestroyImage(despeckle_image); ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); } /* Reduce speckle in the image. */ status=MagickTrue; image_view=AcquireCacheView(image); despeckle_view=AcquireCacheView(despeckle_image); #if defined(MAGICKCORE_OPENMP_SUPPORT) #pragma omp parallel for schedule(dynamic,1) shared(status) #endif for (channel=0; channel <= 3; channel++) { long j, y; register long i, x; register Quantum *buffer, *pixel; if (status == MagickFalse) continue; pixel=pixels[GetOpenMPThreadId()]; (void) ResetMagickMemory(pixel,0,length*sizeof(*pixel)); j=(long) image->columns+2; for (y=0; y < (long) image->rows; y++) { register const PixelPacket *__restrict p; p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); if (p == (const PixelPacket *) NULL) break; j++; for (x=0; x < (long) image->columns; x++) { switch (channel) { case 0: pixel[j]=p->red; break; case 1: pixel[j]=p->green; break; case 2: pixel[j]=p->blue; break; case 3: pixel[j]=p->opacity; break; default: break; } p++; j++; } j++; } buffer=buffers[GetOpenMPThreadId()]; (void) ResetMagickMemory(buffer,0,length*sizeof(*buffer)); for (i=0; i < 4; i++) { Hull(X[i],Y[i],image->columns,image->rows,pixel,buffer,1); Hull(-X[i],-Y[i],image->columns,image->rows,pixel,buffer,1); Hull(-X[i],-Y[i],image->columns,image->rows,pixel,buffer,-1); Hull(X[i],Y[i],image->columns,image->rows,pixel,buffer,-1); } j=(long) image->columns+2; for (y=0; y < (long) image->rows; y++) { MagickBooleanType sync; register PixelPacket *__restrict q; q=GetCacheViewAuthenticPixels(despeckle_view,0,y,despeckle_image->columns, 1,exception); if (q == (PixelPacket *) NULL) break; j++; for (x=0; x < (long) image->columns; x++) { switch (channel) { case 0: q->red=pixel[j]; break; case 1: q->green=pixel[j]; break; case 2: q->blue=pixel[j]; break; case 3: q->opacity=pixel[j]; break; default: break; } q++; j++; } sync=SyncCacheViewAuthenticPixels(despeckle_view,exception); if (sync == MagickFalse) { status=MagickFalse; break; } j++; } if (image->progress_monitor != (MagickProgressMonitor) NULL) { MagickBooleanType proceed; #if defined(MAGICKCORE_OPENMP_SUPPORT) #pragma omp critical (MagickCore_DespeckleImage) #endif proceed=SetImageProgress(image,DespeckleImageTag,channel,3); if (proceed == MagickFalse) status=MagickFalse; } } despeckle_view=DestroyCacheView(despeckle_view); image_view=DestroyCacheView(image_view); buffers=DestroyPixelThreadSet(buffers); pixels=DestroyPixelThreadSet(pixels); despeckle_image->type=image->type; if (status == MagickFalse) despeckle_image=DestroyImage(despeckle_image); return(despeckle_image); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % E d g e I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % EdgeImage() finds edges in an image. Radius defines the radius of the % convolution filter. Use a radius of 0 and EdgeImage() selects a suitable % radius for you. % % The format of the EdgeImage method is: % % Image *EdgeImage(const Image *image,const double radius, % ExceptionInfo *exception) % % A description of each parameter follows: % % o image: the image. % % o radius: the radius of the pixel neighborhood. % % o exception: return any errors or warnings in this structure. % */ MagickExport Image *EdgeImage(const Image *image,const double radius, ExceptionInfo *exception) { Image *edge_image; double *kernel; register long i; unsigned long width; assert(image != (const Image *) NULL); assert(image->signature == MagickSignature); if (image->debug != MagickFalse) (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); assert(exception != (ExceptionInfo *) NULL); assert(exception->signature == MagickSignature); width=GetOptimalKernelWidth1D(radius,0.5); kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel)); if (kernel == (double *) NULL) ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); for (i=0; i < (long) (width*width); i++) kernel[i]=(-1.0); kernel[i/2]=(double) (width*width-1.0); edge_image=ConvolveImage(image,width,kernel,exception); kernel=(double *) RelinquishMagickMemory(kernel); return(edge_image); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % E m b o s s I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % EmbossImage() returns a grayscale image with a three-dimensional effect. % We convolve the image with a Gaussian operator of the given radius and % standard deviation (sigma). For reasonable results, radius should be % larger than sigma. Use a radius of 0 and Emboss() selects a suitable % radius for you. % % The format of the EmbossImage method is: % % Image *EmbossImage(const Image *image,const double radius, % const double sigma,ExceptionInfo *exception) % % A description of each parameter follows: % % o image: the image. % % o radius: the radius of the pixel neighborhood. % % o sigma: the standard deviation of the Gaussian, in pixels. % % o exception: return any errors or warnings in this structure. % */ MagickExport Image *EmbossImage(const Image *image,const double radius, const double sigma,ExceptionInfo *exception) { double *kernel; Image *emboss_image; long j; MagickRealType alpha; register long i, u, v; unsigned long width; assert(image != (Image *) NULL); assert(image->signature == MagickSignature); if (image->debug != MagickFalse) (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); assert(exception != (ExceptionInfo *) NULL); assert(exception->signature == MagickSignature); width=GetOptimalKernelWidth2D(radius,sigma); kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel)); if (kernel == (double *) NULL) ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); i=0; j=(long) width/2; for (v=(-((long) width/2)); v <= (long) (width/2); v++) { for (u=(-((long) width/2)); u <= (long) (width/2); u++) { alpha=exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma)); kernel[i]=(double) (((u < 0) || (v < 0) ? -8.0 : 8.0)*alpha/ (2.0*MagickPI*MagickSigma*MagickSigma)); if (u != j) kernel[i]=0.0; i++; } j--; } emboss_image=ConvolveImage(image,width,kernel,exception); if (emboss_image != (Image *) NULL) (void) EqualizeImage(emboss_image); kernel=(double *) RelinquishMagickMemory(kernel); return(emboss_image); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % G a u s s i a n B l u r I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % GaussianBlurImage() blurs an image. We convolve the image with a % Gaussian operator of the given radius and standard deviation (sigma). % For reasonable results, the radius should be larger than sigma. Use a % radius of 0 and GaussianBlurImage() selects a suitable radius for you % % The format of the GaussianBlurImage method is: % % Image *GaussianBlurImage(const Image *image,onst double radius, % const double sigma,ExceptionInfo *exception) % Image *GaussianBlurImageChannel(const Image *image, % const ChannelType channel,const double radius,const double sigma, % ExceptionInfo *exception) % % A description of each parameter follows: % % o image: the image. % % o channel: the channel type. % % o radius: the radius of the Gaussian, in pixels, not counting the center % pixel. % % o sigma: the standard deviation of the Gaussian, in pixels. % % o exception: return any errors or warnings in this structure. % */ MagickExport Image *GaussianBlurImage(const Image *image,const double radius, const double sigma,ExceptionInfo *exception) { Image *blur_image; blur_image=GaussianBlurImageChannel(image,DefaultChannels,radius,sigma, exception); return(blur_image); } MagickExport Image *GaussianBlurImageChannel(const Image *image, const ChannelType channel,const double radius,const double sigma, ExceptionInfo *exception) { double *kernel; Image *blur_image; MagickRealType alpha; register long i, u, v; unsigned long width; assert(image != (const Image *) NULL); assert(image->signature == MagickSignature); if (image->debug != MagickFalse) (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); assert(exception != (ExceptionInfo *) NULL); assert(exception->signature == MagickSignature); width=GetOptimalKernelWidth2D(radius,sigma); kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel)); if (kernel == (double *) NULL) ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); i=0; for (v=(-((long) width/2)); v <= (long) (width/2); v++) { for (u=(-((long) width/2)); u <= (long) (width/2); u++) { alpha=exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma)); kernel[i]=(double) (alpha/(2.0*MagickPI*MagickSigma*MagickSigma)); i++; } } blur_image=ConvolveImageChannel(image,channel,width,kernel,exception); kernel=(double *) RelinquishMagickMemory(kernel); return(blur_image); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % M e d i a n F i l t e r I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % MedianFilterImage() applies a digital filter that improves the quality % of a noisy image. Each pixel is replaced by the median in a set of % neighboring pixels as defined by radius. % % The algorithm was contributed by Mike Edmonds and implements an insertion % sort for selecting median color-channel values. For more on this algorithm % see "Skip Lists: A probabilistic Alternative to Balanced Trees" by William % Pugh in the June 1990 of Communications of the ACM. % % The format of the MedianFilterImage method is: % % Image *MedianFilterImage(const Image *image,const double radius, % ExceptionInfo *exception) % % A description of each parameter follows: % % o image: the image. % % o radius: the radius of the pixel neighborhood. % % o exception: return any errors or warnings in this structure. % */ #define MedianListChannels 5 typedef struct _MedianListNode { unsigned long next[9], count, signature; } MedianListNode; typedef struct _MedianSkipList { long level; MedianListNode *nodes; } MedianSkipList; typedef struct _MedianPixelList { unsigned long center, seed, signature; MedianSkipList lists[MedianListChannels]; } MedianPixelList; static MedianPixelList *DestroyMedianPixelList(MedianPixelList *pixel_list) { register long i; if (pixel_list == (MedianPixelList *) NULL) return((MedianPixelList *) NULL); for (i=0; i < MedianListChannels; i++) if (pixel_list->lists[i].nodes != (MedianListNode *) NULL) pixel_list->lists[i].nodes=(MedianListNode *) RelinquishMagickMemory( pixel_list->lists[i].nodes); pixel_list=(MedianPixelList *) RelinquishAlignedMemory(pixel_list); return(pixel_list); } static MedianPixelList **DestroyMedianPixelListThreadSet( MedianPixelList **pixel_list) { register long i; assert(pixel_list != (MedianPixelList **) NULL); for (i=0; i < (long) GetOpenMPMaximumThreads(); i++) if (pixel_list[i] != (MedianPixelList *) NULL) pixel_list[i]=DestroyMedianPixelList(pixel_list[i]); pixel_list=(MedianPixelList **) RelinquishAlignedMemory(pixel_list); return(pixel_list); } static MedianPixelList *AcquireMedianPixelList(const unsigned long width) { MedianPixelList *pixel_list; register long i; pixel_list=(MedianPixelList *) AcquireAlignedMemory(1,sizeof(*pixel_list)); if (pixel_list == (MedianPixelList *) NULL) return(pixel_list); (void) ResetMagickMemory((void *) pixel_list,0,sizeof(*pixel_list)); pixel_list->center=width*width/2; for (i=0; i < MedianListChannels; i++) { pixel_list->lists[i].nodes=(MedianListNode *) AcquireQuantumMemory(65537UL, sizeof(*pixel_list->lists[i].nodes)); if (pixel_list->lists[i].nodes == (MedianListNode *) NULL) return(DestroyMedianPixelList(pixel_list)); (void) ResetMagickMemory(pixel_list->lists[i].nodes,0,65537UL* sizeof(*pixel_list->lists[i].nodes)); } pixel_list->signature=MagickSignature; return(pixel_list); } static MedianPixelList **AcquireMedianPixelListThreadSet( const unsigned long width) { register long i; MedianPixelList **pixel_list; unsigned long number_threads; number_threads=GetOpenMPMaximumThreads(); pixel_list=(MedianPixelList **) AcquireAlignedMemory(number_threads, sizeof(*pixel_list)); if (pixel_list == (MedianPixelList **) NULL) return((MedianPixelList **) NULL); (void) ResetMagickMemory(pixel_list,0,number_threads*sizeof(*pixel_list)); for (i=0; i < (long) number_threads; i++) { pixel_list[i]=AcquireMedianPixelList(width); if (pixel_list[i] == (MedianPixelList *) NULL) return(DestroyMedianPixelListThreadSet(pixel_list)); } return(pixel_list); } static void AddNodeMedianPixelList(MedianPixelList *pixel_list, const long channel,const unsigned long color) { register long level; register MedianSkipList *list; unsigned long search, update[9]; /* Initialize the node. */ list=pixel_list->lists+channel; list->nodes[color].signature=pixel_list->signature; list->nodes[color].count=1; /* Determine where it belongs in the list. */ search=65536UL; for (level=list->level; level >= 0; level--) { while (list->nodes[search].next[level] < color) search=list->nodes[search].next[level]; update[level]=search; } /* Generate a pseudo-random level for this node. */ for (level=0; ; level++) { pixel_list->seed=(pixel_list->seed*42893621L)+1L; if ((pixel_list->seed & 0x300) != 0x300) break; } if (level > 8) level=8; if (level > (list->level+2)) level=list->level+2; /* If we're raising the list's level, link back to the root node. */ while (level > list->level) { list->level++; update[list->level]=65536UL; } /* Link the node into the skip-list. */ do { list->nodes[color].next[level]=list->nodes[update[level]].next[level]; list->nodes[update[level]].next[level]=color; } while (level-- > 0); } static MagickPixelPacket GetMedianPixelList(MedianPixelList *pixel_list) { MagickPixelPacket pixel; register long channel; register MedianSkipList *list; unsigned long center, color, count; unsigned short channels[MedianListChannels]; /* Find the median value for each of the color. */ center=pixel_list->center; for (channel=0; channel < 5; channel++) { list=pixel_list->lists+channel; color=65536UL; count=0; do { color=list->nodes[color].next[0]; count+=list->nodes[color].count; } while (count <= center); channels[channel]=(unsigned short) color; } GetMagickPixelPacket((const Image *) NULL,&pixel); pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]); pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]); pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]); pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]); pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]); return(pixel); } static inline void InsertMedianPixelList(const Image *image, const PixelPacket *pixel,const IndexPacket *indexes, MedianPixelList *pixel_list) { unsigned long signature; unsigned short index; index=ScaleQuantumToShort(pixel->red); signature=pixel_list->lists[0].nodes[index].signature; if (signature == pixel_list->signature) pixel_list->lists[0].nodes[index].count++; else AddNodeMedianPixelList(pixel_list,0,index); index=ScaleQuantumToShort(pixel->green); signature=pixel_list->lists[1].nodes[index].signature; if (signature == pixel_list->signature) pixel_list->lists[1].nodes[index].count++; else AddNodeMedianPixelList(pixel_list,1,index); index=ScaleQuantumToShort(pixel->blue); signature=pixel_list->lists[2].nodes[index].signature; if (signature == pixel_list->signature) pixel_list->lists[2].nodes[index].count++; else AddNodeMedianPixelList(pixel_list,2,index); index=ScaleQuantumToShort(pixel->opacity); signature=pixel_list->lists[3].nodes[index].signature; if (signature == pixel_list->signature) pixel_list->lists[3].nodes[index].count++; else AddNodeMedianPixelList(pixel_list,3,index); if (image->colorspace == CMYKColorspace) index=ScaleQuantumToShort(*indexes); signature=pixel_list->lists[4].nodes[index].signature; if (signature == pixel_list->signature) pixel_list->lists[4].nodes[index].count++; else AddNodeMedianPixelList(pixel_list,4,index); } static void ResetMedianPixelList(MedianPixelList *pixel_list) { int level; register long channel; register MedianListNode *root; register MedianSkipList *list; /* Reset the skip-list. */ for (channel=0; channel < 5; channel++) { list=pixel_list->lists+channel; root=list->nodes+65536UL; list->level=0; for (level=0; level < 9; level++) root->next[level]=65536UL; } pixel_list->seed=pixel_list->signature++; } MagickExport Image *MedianFilterImage(const Image *image,const double radius, ExceptionInfo *exception) { #define MedianFilterImageTag "MedianFilter/Image" Image *median_image; long progress, y; MagickBooleanType status; MedianPixelList **pixel_list; unsigned long width; CacheView *image_view, *median_view; /* Initialize median image attributes. */ assert(image != (Image *) NULL); assert(image->signature == MagickSignature); if (image->debug != MagickFalse) (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); assert(exception != (ExceptionInfo *) NULL); assert(exception->signature == MagickSignature); width=GetOptimalKernelWidth2D(radius,0.5); if ((image->columns < width) || (image->rows < width)) ThrowImageException(OptionError,"ImageSmallerThanKernelRadius"); median_image=CloneImage(image,image->columns,image->rows,MagickTrue, exception); if (median_image == (Image *) NULL) return((Image *) NULL); if (SetImageStorageClass(median_image,DirectClass) == MagickFalse) { InheritException(exception,&median_image->exception); median_image=DestroyImage(median_image); return((Image *) NULL); } pixel_list=AcquireMedianPixelListThreadSet(width); if (pixel_list == (MedianPixelList **) NULL) { median_image=DestroyImage(median_image); ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); } /* Median filter each image row. */ status=MagickTrue; progress=0; image_view=AcquireCacheView(image); median_view=AcquireCacheView(median_image); #if defined(MAGICKCORE_OPENMP_SUPPORT) #pragma omp parallel for schedule(dynamic) shared(progress,status) #endif for (y=0; y < (long) median_image->rows; y++) { register const IndexPacket *__restrict indexes; register const PixelPacket *__restrict p; register IndexPacket *__restrict median_indexes; register long id, x; register PixelPacket *__restrict q; if (status == MagickFalse) continue; p=GetCacheViewVirtualPixels(image_view,-((long) width/2L),y-(long) (width/ 2L),image->columns+width,width,exception); q=QueueCacheViewAuthenticPixels(median_view,0,y,median_image->columns,1, exception); if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL)) { status=MagickFalse; continue; } indexes=GetCacheViewVirtualIndexQueue(image_view); median_indexes=GetCacheViewAuthenticIndexQueue(median_view); id=GetOpenMPThreadId(); for (x=0; x < (long) median_image->columns; x++) { MagickPixelPacket pixel; register const PixelPacket *__restrict r; register const IndexPacket *__restrict s; register long u, v; r=p; s=indexes+x; ResetMedianPixelList(pixel_list[id]); for (v=0; v < (long) width; v++) { for (u=0; u < (long) width; u++) InsertMedianPixelList(image,r+u,s+u,pixel_list[id]); r+=image->columns+width; s+=image->columns+width; } pixel=GetMedianPixelList(pixel_list[id]); SetPixelPacket(median_image,&pixel,q,median_indexes+x); p++; q++; } if (SyncCacheViewAuthenticPixels(median_view,exception) == MagickFalse) status=MagickFalse; if (image->progress_monitor != (MagickProgressMonitor) NULL) { MagickBooleanType proceed; #if defined(MAGICKCORE_OPENMP_SUPPORT) #pragma omp critical (MagickCore_MedianFilterImage) #endif proceed=SetImageProgress(image,MedianFilterImageTag,progress++, image->rows); if (proceed == MagickFalse) status=MagickFalse; } } median_view=DestroyCacheView(median_view); image_view=DestroyCacheView(image_view); pixel_list=DestroyMedianPixelListThreadSet(pixel_list); return(median_image); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % M o t i o n B l u r I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % MotionBlurImage() simulates motion blur. We convolve the image with a % Gaussian operator of the given radius and standard deviation (sigma). % For reasonable results, radius should be larger than sigma. Use a % radius of 0 and MotionBlurImage() selects a suitable radius for you. % Angle gives the angle of the blurring motion. % % Andrew Protano contributed this effect. % % The format of the MotionBlurImage method is: % % Image *MotionBlurImage(const Image *image,const double radius, % const double sigma,const double angle,ExceptionInfo *exception) % Image *MotionBlurImageChannel(const Image *image,const ChannelType channel, % const double radius,const double sigma,const double angle, % ExceptionInfo *exception) % % A description of each parameter follows: % % o image: the image. % % o channel: the channel type. % % o radius: the radius of the Gaussian, in pixels, not counting the center % o radius: the radius of the Gaussian, in pixels, not counting % the center pixel. % % o sigma: the standard deviation of the Gaussian, in pixels. % % o angle: Apply the effect along this angle. % % o exception: return any errors or warnings in this structure. % */ static double *GetMotionBlurKernel(unsigned long width, const MagickRealType sigma) { #define KernelRank 3 double *kernel; long bias; MagickRealType alpha, normalize; register long i; /* Generate a 1-D convolution kernel. */ (void) LogMagickEvent(TraceEvent,GetMagickModule(),"..."); kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel)); if (kernel == (double *) NULL) return(kernel); (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel)); bias=(long) (KernelRank*width); for (i=0; i < (long) bias; i++) { alpha=exp((-((double) (i*i))/(double) (2.0*KernelRank*KernelRank* MagickSigma*MagickSigma))); kernel[i/KernelRank]+=(double) alpha/(MagickSQ2PI*sigma); } normalize=0.0; for (i=0; i < (long) width; i++) normalize+=kernel[i]; for (i=0; i < (long) width; i++) kernel[i]/=normalize; return(kernel); } MagickExport Image *MotionBlurImage(const Image *image,const double radius, const double sigma,const double angle,ExceptionInfo *exception) { Image *motion_blur; motion_blur=MotionBlurImageChannel(image,DefaultChannels,radius,sigma,angle, exception); return(motion_blur); } MagickExport Image *MotionBlurImageChannel(const Image *image, const ChannelType channel,const double radius,const double sigma, const double angle,ExceptionInfo *exception) { typedef struct _OffsetInfo { long x, y; } OffsetInfo; double *kernel; Image *blur_image; long progress, y; MagickBooleanType status; MagickPixelPacket zero; OffsetInfo *offset; PointInfo point; register long i; unsigned long width; CacheView *blur_view, *image_view; assert(image != (Image *) NULL); assert(image->signature == MagickSignature); if (image->debug != MagickFalse) (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); assert(exception != (ExceptionInfo *) NULL); width=GetOptimalKernelWidth1D(radius,sigma); kernel=GetMotionBlurKernel(width,sigma); if (kernel == (double *) NULL) ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); offset=(OffsetInfo *) AcquireQuantumMemory(width,sizeof(*offset)); if (offset == (OffsetInfo *) NULL) { kernel=(double *) RelinquishMagickMemory(kernel); ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); } blur_image=CloneImage(image,0,0,MagickTrue,exception); if (blur_image == (Image *) NULL) { kernel=(double *) RelinquishMagickMemory(kernel); offset=(OffsetInfo *) RelinquishMagickMemory(offset); return((Image *) NULL); } if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse) { kernel=(double *) RelinquishMagickMemory(kernel); offset=(OffsetInfo *) RelinquishMagickMemory(offset); InheritException(exception,&blur_image->exception); blur_image=DestroyImage(blur_image); return((Image *) NULL); } point.x=(double) width*sin(DegreesToRadians(angle)); point.y=(double) width*cos(DegreesToRadians(angle)); for (i=0; i < (long) width; i++) { offset[i].x=(long) ((i*point.y)/hypot(point.x,point.y)+0.5); offset[i].y=(long) ((i*point.x)/hypot(point.x,point.y)+0.5); } /* Motion blur image. */ status=MagickTrue; progress=0; GetMagickPixelPacket(image,&zero); image_view=AcquireCacheView(image); blur_view=AcquireCacheView(blur_image); #if defined(MAGICKCORE_OPENMP_SUPPORT) #pragma omp parallel for schedule(dynamic) shared(progress,status) #endif for (y=0; y < (long) image->rows; y++) { register IndexPacket *__restrict blur_indexes; register long x; register PixelPacket *__restrict q; if (status == MagickFalse) continue; q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1, exception); if (q == (PixelPacket *) NULL) { status=MagickFalse; continue; } blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view); for (x=0; x < (long) image->columns; x++) { MagickPixelPacket qixel; PixelPacket pixel; register double *__restrict k; register long i; register const IndexPacket *__restrict indexes; k=kernel; qixel=zero; if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse)) { for (i=0; i < (long) width; i++) { (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+ offset[i].y,&pixel,exception); qixel.red+=(*k)*pixel.red; qixel.green+=(*k)*pixel.green; qixel.blue+=(*k)*pixel.blue; qixel.opacity+=(*k)*pixel.opacity; if (image->colorspace == CMYKColorspace) { indexes=GetCacheViewVirtualIndexQueue(image_view); qixel.index+=(*k)*(*indexes); } k++; } if ((channel & RedChannel) != 0) q->red=RoundToQuantum(qixel.red); if ((channel & GreenChannel) != 0) q->green=RoundToQuantum(qixel.green); if ((channel & BlueChannel) != 0) q->blue=RoundToQuantum(qixel.blue); if ((channel & OpacityChannel) != 0) q->opacity=RoundToQuantum(qixel.opacity); if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace)) blur_indexes[x]=(IndexPacket) RoundToQuantum(qixel.index); } else { MagickRealType alpha, gamma; alpha=0.0; gamma=0.0; for (i=0; i < (long) width; i++) { (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+ offset[i].y,&pixel,exception); alpha=(MagickRealType) (QuantumScale*(QuantumRange-pixel.opacity)); qixel.red+=(*k)*alpha*pixel.red; qixel.green+=(*k)*alpha*pixel.green; qixel.blue+=(*k)*alpha*pixel.blue; qixel.opacity+=(*k)*pixel.opacity; if (image->colorspace == CMYKColorspace) { indexes=GetCacheViewVirtualIndexQueue(image_view); qixel.index+=(*k)*alpha*(*indexes); } gamma+=(*k)*alpha; k++; } gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma); if ((channel & RedChannel) != 0) q->red=RoundToQuantum(gamma*qixel.red); if ((channel & GreenChannel) != 0) q->green=RoundToQuantum(gamma*qixel.green); if ((channel & BlueChannel) != 0) q->blue=RoundToQuantum(gamma*qixel.blue); if ((channel & OpacityChannel) != 0) q->opacity=RoundToQuantum(qixel.opacity); if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace)) blur_indexes[x]=(IndexPacket) RoundToQuantum(gamma*qixel.index); } q++; } if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse) status=MagickFalse; if (image->progress_monitor != (MagickProgressMonitor) NULL) { MagickBooleanType proceed; #if defined(MAGICKCORE_OPENMP_SUPPORT) #pragma omp critical (MagickCore_MotionBlurImageChannel) #endif proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows); if (proceed == MagickFalse) status=MagickFalse; } } blur_view=DestroyCacheView(blur_view); image_view=DestroyCacheView(image_view); kernel=(double *) RelinquishMagickMemory(kernel); offset=(OffsetInfo *) RelinquishMagickMemory(offset); if (status == MagickFalse) blur_image=DestroyImage(blur_image); return(blur_image); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % P r e v i e w I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % PreviewImage() tiles 9 thumbnails of the specified image with an image % processing operation applied with varying parameters. This may be helpful % pin-pointing an appropriate parameter for a particular image processing % operation. % % The format of the PreviewImages method is: % % Image *PreviewImages(const Image *image,const PreviewType preview, % ExceptionInfo *exception) % % A description of each parameter follows: % % o image: the image. % % o preview: the image processing operation. % % o exception: return any errors or warnings in this structure. % */ MagickExport Image *PreviewImage(const Image *image,const PreviewType preview, ExceptionInfo *exception) { #define NumberTiles 9 #define PreviewImageTag "Preview/Image" #define DefaultPreviewGeometry "204x204+10+10" char factor[MaxTextExtent], label[MaxTextExtent]; double degrees, gamma, percentage, radius, sigma, threshold; Image *images, *montage_image, *preview_image, *thumbnail; ImageInfo *preview_info; long y; MagickBooleanType proceed; MontageInfo *montage_info; QuantizeInfo quantize_info; RectangleInfo geometry; register long i, x; unsigned long colors; /* Open output image file. */ assert(image != (Image *) NULL); assert(image->signature == MagickSignature); if (image->debug != MagickFalse) (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); colors=2; degrees=0.0; gamma=(-0.2f); preview_info=AcquireImageInfo(); SetGeometry(image,&geometry); (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y, &geometry.width,&geometry.height); images=NewImageList(); percentage=12.5; GetQuantizeInfo(&quantize_info); radius=0.0; sigma=1.0; threshold=0.0; x=0; y=0; for (i=0; i < NumberTiles; i++) { thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception); if (thumbnail == (Image *) NULL) break; (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL, (void *) NULL); (void) SetImageProperty(thumbnail,"label",DefaultTileLabel); if (i == (NumberTiles/2)) { (void) QueryColorDatabase("#dfdfdf",&thumbnail->matte_color,exception); AppendImageToList(&images,thumbnail); continue; } switch (preview) { case RotatePreview: { degrees+=45.0; preview_image=RotateImage(thumbnail,degrees,exception); (void) FormatMagickString(label,MaxTextExtent,"rotate %g",degrees); break; } case ShearPreview: { degrees+=5.0; preview_image=ShearImage(thumbnail,degrees,degrees,exception); (void) FormatMagickString(label,MaxTextExtent,"shear %gx%g", degrees,2.0*degrees); break; } case RollPreview: { x=(long) ((i+1)*thumbnail->columns)/NumberTiles; y=(long) ((i+1)*thumbnail->rows)/NumberTiles; preview_image=RollImage(thumbnail,x,y,exception); (void) FormatMagickString(label,MaxTextExtent,"roll %ldx%ld",x,y); break; } case HuePreview: { preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception); if (preview_image == (Image *) NULL) break; (void) FormatMagickString(factor,MaxTextExtent,"100,100,%g", 2.0*percentage); (void) ModulateImage(preview_image,factor); (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor); break; } case SaturationPreview: { preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception); if (preview_image == (Image *) NULL) break; (void) FormatMagickString(factor,MaxTextExtent,"100,%g",2.0*percentage); (void) ModulateImage(preview_image,factor); (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor); break; } case BrightnessPreview: { preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception); if (preview_image == (Image *) NULL) break; (void) FormatMagickString(factor,MaxTextExtent,"%g",2.0*percentage); (void) ModulateImage(preview_image,factor); (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor); break; } case GammaPreview: default: { preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception); if (preview_image == (Image *) NULL) break; gamma+=0.4f; (void) GammaImageChannel(preview_image,DefaultChannels,gamma); (void) FormatMagickString(label,MaxTextExtent,"gamma %g",gamma); break; } case SpiffPreview: { preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception); if (preview_image != (Image *) NULL) for (x=0; x < i; x++) (void) ContrastImage(preview_image,MagickTrue); (void) FormatMagickString(label,MaxTextExtent,"contrast (%ld)",i+1); break; } case DullPreview: { preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception); if (preview_image == (Image *) NULL) break; for (x=0; x < i; x++) (void) ContrastImage(preview_image,MagickFalse); (void) FormatMagickString(label,MaxTextExtent,"+contrast (%ld)",i+1); break; } case GrayscalePreview: { preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception); if (preview_image == (Image *) NULL) break; colors<<=1; quantize_info.number_colors=colors; quantize_info.colorspace=GRAYColorspace; (void) QuantizeImage(&quantize_info,preview_image); (void) FormatMagickString(label,MaxTextExtent, "-colorspace gray -colors %ld",colors); break; } case QuantizePreview: { preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception); if (preview_image == (Image *) NULL) break; colors<<=1; quantize_info.number_colors=colors; (void) QuantizeImage(&quantize_info,preview_image); (void) FormatMagickString(label,MaxTextExtent,"colors %ld",colors); break; } case DespecklePreview: { for (x=0; x < (i-1); x++) { preview_image=DespeckleImage(thumbnail,exception); if (preview_image == (Image *) NULL) break; thumbnail=DestroyImage(thumbnail); thumbnail=preview_image; } preview_image=DespeckleImage(thumbnail,exception); if (preview_image == (Image *) NULL) break; (void) FormatMagickString(label,MaxTextExtent,"despeckle (%ld)",i+1); break; } case ReduceNoisePreview: { preview_image=ReduceNoiseImage(thumbnail,radius,exception); (void) FormatMagickString(label,MaxTextExtent,"noise %g",radius); break; } case AddNoisePreview: { switch ((int) i) { case 0: { (void) CopyMagickString(factor,"uniform",MaxTextExtent); break; } case 1: { (void) CopyMagickString(factor,"gaussian",MaxTextExtent); break; } case 2: { (void) CopyMagickString(factor,"multiplicative",MaxTextExtent); break; } case 3: { (void) CopyMagickString(factor,"impulse",MaxTextExtent); break; } case 4: { (void) CopyMagickString(factor,"laplacian",MaxTextExtent); break; } case 5: { (void) CopyMagickString(factor,"Poisson",MaxTextExtent); break; } default: { (void) CopyMagickString(thumbnail->magick,"NULL",MaxTextExtent); break; } } preview_image=ReduceNoiseImage(thumbnail,(double) i,exception); (void) FormatMagickString(label,MaxTextExtent,"+noise %s",factor); break; } case SharpenPreview: { preview_image=SharpenImage(thumbnail,radius,sigma,exception); (void) FormatMagickString(label,MaxTextExtent,"sharpen %gx%g",radius, sigma); break; } case BlurPreview: { preview_image=BlurImage(thumbnail,radius,sigma,exception); (void) FormatMagickString(label,MaxTextExtent,"blur %gx%g",radius, sigma); break; } case ThresholdPreview: { preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception); if (preview_image == (Image *) NULL) break; (void) BilevelImage(thumbnail, (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0); (void) FormatMagickString(label,MaxTextExtent,"threshold %g", (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0); break; } case EdgeDetectPreview: { preview_image=EdgeImage(thumbnail,radius,exception); (void) FormatMagickString(label,MaxTextExtent,"edge %g",radius); break; } case SpreadPreview: { preview_image=SpreadImage(thumbnail,radius,exception); (void) FormatMagickString(label,MaxTextExtent,"spread %g",radius+0.5); break; } case SolarizePreview: { preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception); if (preview_image == (Image *) NULL) break; (void) SolarizeImage(preview_image,(double) QuantumRange* percentage/100.0); (void) FormatMagickString(label,MaxTextExtent,"solarize %g", (QuantumRange*percentage)/100.0); break; } case ShadePreview: { degrees+=10.0; preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees, exception); (void) FormatMagickString(label,MaxTextExtent,"shade %gx%g",degrees, degrees); break; } case RaisePreview: { preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception); if (preview_image == (Image *) NULL) break; geometry.width=(unsigned long) (2*i+2); geometry.height=(unsigned long) (2*i+2); geometry.x=i/2; geometry.y=i/2; (void) RaiseImage(preview_image,&geometry,MagickTrue); (void) FormatMagickString(label,MaxTextExtent,"raise %lux%lu%+ld%+ld", geometry.width,geometry.height,geometry.x,geometry.y); break; } case SegmentPreview: { preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception); if (preview_image == (Image *) NULL) break; threshold+=0.4f; (void) SegmentImage(preview_image,RGBColorspace,MagickFalse,threshold, threshold); (void) FormatMagickString(label,MaxTextExtent,"segment %gx%g", threshold,threshold); break; } case SwirlPreview: { preview_image=SwirlImage(thumbnail,degrees,exception); (void) FormatMagickString(label,MaxTextExtent,"swirl %g",degrees); degrees+=45.0; break; } case ImplodePreview: { degrees+=0.1f; preview_image=ImplodeImage(thumbnail,degrees,exception); (void) FormatMagickString(label,MaxTextExtent,"implode %g",degrees); break; } case WavePreview: { degrees+=5.0f; preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,exception); (void) FormatMagickString(label,MaxTextExtent,"wave %gx%g",0.5*degrees, 2.0*degrees); break; } case OilPaintPreview: { preview_image=OilPaintImage(thumbnail,(double) radius,exception); (void) FormatMagickString(label,MaxTextExtent,"paint %g",radius); break; } case CharcoalDrawingPreview: { preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma, exception); (void) FormatMagickString(label,MaxTextExtent,"charcoal %gx%g",radius, sigma); break; } case JPEGPreview: { char filename[MaxTextExtent]; int file; MagickBooleanType status; preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception); if (preview_image == (Image *) NULL) break; preview_info->quality=(unsigned long) percentage; (void) FormatMagickString(factor,MaxTextExtent,"%lu", preview_info->quality); file=AcquireUniqueFileResource(filename); if (file != -1) file=close(file)-1; (void) FormatMagickString(preview_image->filename,MaxTextExtent, "jpeg:%s",filename); status=WriteImage(preview_info,preview_image); if (status != MagickFalse) { Image *quality_image; (void) CopyMagickString(preview_info->filename, preview_image->filename,MaxTextExtent); quality_image=ReadImage(preview_info,exception); if (quality_image != (Image *) NULL) { preview_image=DestroyImage(preview_image); preview_image=quality_image; } } (void) RelinquishUniqueFileResource(preview_image->filename); if ((GetBlobSize(preview_image)/1024) >= 1024) (void) FormatMagickString(label,MaxTextExtent,"quality %s\n%gmb ", factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/ 1024.0/1024.0); else if (GetBlobSize(preview_image) >= 1024) (void) FormatMagickString(label,MaxTextExtent,"quality %s\n%gkb ", factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/ 1024.0); else (void) FormatMagickString(label,MaxTextExtent,"quality %s\n%lub ", factor,(unsigned long) GetBlobSize(thumbnail)); break; } } thumbnail=DestroyImage(thumbnail); percentage+=12.5; radius+=0.5; sigma+=0.25; if (preview_image == (Image *) NULL) break; (void) DeleteImageProperty(preview_image,"label"); (void) SetImageProperty(preview_image,"label",label); AppendImageToList(&images,preview_image); proceed=SetImageProgress(image,PreviewImageTag,i,NumberTiles); if (proceed == MagickFalse) break; } if (images == (Image *) NULL) { preview_info=DestroyImageInfo(preview_info); return((Image *) NULL); } /* Create the montage. */ montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL); (void) CopyMagickString(montage_info->filename,image->filename,MaxTextExtent); montage_info->shadow=MagickTrue; (void) CloneString(&montage_info->tile,"3x3"); (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry); (void) CloneString(&montage_info->frame,DefaultTileFrame); montage_image=MontageImages(images,montage_info,exception); montage_info=DestroyMontageInfo(montage_info); images=DestroyImageList(images); if (montage_image == (Image *) NULL) ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); if (montage_image->montage != (char *) NULL) { /* Free image directory. */ montage_image->montage=(char *) RelinquishMagickMemory( montage_image->montage); if (image->directory != (char *) NULL) montage_image->directory=(char *) RelinquishMagickMemory( montage_image->directory); } preview_info=DestroyImageInfo(preview_info); return(montage_image); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % R a d i a l B l u r I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % RadialBlurImage() applies a radial blur to the image. % % Andrew Protano contributed this effect. % % The format of the RadialBlurImage method is: % % Image *RadialBlurImage(const Image *image,const double angle, % ExceptionInfo *exception) % Image *RadialBlurImageChannel(const Image *image,const ChannelType channel, % const double angle,ExceptionInfo *exception) % % A description of each parameter follows: % % o image: the image. % % o channel: the channel type. % % o angle: the angle of the radial blur. % % o exception: return any errors or warnings in this structure. % */ MagickExport Image *RadialBlurImage(const Image *image,const double angle, ExceptionInfo *exception) { Image *blur_image; blur_image=RadialBlurImageChannel(image,DefaultChannels,angle,exception); return(blur_image); } MagickExport Image *RadialBlurImageChannel(const Image *image, const ChannelType channel,const double angle,ExceptionInfo *exception) { Image *blur_image; long progress, y; MagickBooleanType status; MagickPixelPacket zero; MagickRealType blur_radius, *cos_theta, offset, *sin_theta, theta; PointInfo blur_center; register long i; unsigned long n; CacheView *blur_view, *image_view; /* Allocate blur image. */ assert(image != (Image *) NULL); assert(image->signature == MagickSignature); if (image->debug != MagickFalse) (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); assert(exception != (ExceptionInfo *) NULL); assert(exception->signature == MagickSignature); blur_image=CloneImage(image,0,0,MagickTrue,exception); if (blur_image == (Image *) NULL) return((Image *) NULL); if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse) { InheritException(exception,&blur_image->exception); blur_image=DestroyImage(blur_image); return((Image *) NULL); } blur_center.x=(double) image->columns/2.0; blur_center.y=(double) image->rows/2.0; blur_radius=hypot(blur_center.x,blur_center.y); n=(unsigned long) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+ 2UL); theta=DegreesToRadians(angle)/(MagickRealType) (n-1); cos_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n, sizeof(*cos_theta)); sin_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n, sizeof(*sin_theta)); if ((cos_theta == (MagickRealType *) NULL) || (sin_theta == (MagickRealType *) NULL)) { blur_image=DestroyImage(blur_image); ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); } offset=theta*(MagickRealType) (n-1)/2.0; for (i=0; i < (long) n; i++) { cos_theta[i]=cos((double) (theta*i-offset)); sin_theta[i]=sin((double) (theta*i-offset)); } /* Radial blur image. */ status=MagickTrue; progress=0; GetMagickPixelPacket(image,&zero); image_view=AcquireCacheView(image); blur_view=AcquireCacheView(blur_image); #if defined(MAGICKCORE_OPENMP_SUPPORT) #pragma omp parallel for schedule(dynamic) shared(progress,status) #endif for (y=0; y < (long) blur_image->rows; y++) { register const IndexPacket *__restrict indexes; register IndexPacket *__restrict blur_indexes; register long x; register PixelPacket *__restrict q; if (status == MagickFalse) continue; q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1, exception); if (q == (PixelPacket *) NULL) { status=MagickFalse; continue; } blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view); for (x=0; x < (long) blur_image->columns; x++) { MagickPixelPacket qixel; MagickRealType normalize, radius; PixelPacket pixel; PointInfo center; register long i; unsigned long step; center.x=(double) x-blur_center.x; center.y=(double) y-blur_center.y; radius=hypot((double) center.x,center.y); if (radius == 0) step=1; else { step=(unsigned long) (blur_radius/radius); if (step == 0) step=1; else if (step >= n) step=n-1; } normalize=0.0; qixel=zero; if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse)) { for (i=0; i < (long) n; i+=step) { (void) GetOneCacheViewVirtualPixel(image_view,(long) (blur_center.x+ center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),(long) ( blur_center.y+center.x*sin_theta[i]+center.y*cos_theta[i]+0.5), &pixel,exception); qixel.red+=pixel.red; qixel.green+=pixel.green; qixel.blue+=pixel.blue; qixel.opacity+=pixel.opacity; if (image->colorspace == CMYKColorspace) { indexes=GetCacheViewVirtualIndexQueue(image_view); qixel.index+=(*indexes); } normalize+=1.0; } normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 : normalize); if ((channel & RedChannel) != 0) q->red=RoundToQuantum(normalize*qixel.red); if ((channel & GreenChannel) != 0) q->green=RoundToQuantum(normalize*qixel.green); if ((channel & BlueChannel) != 0) q->blue=RoundToQuantum(normalize*qixel.blue); if ((channel & OpacityChannel) != 0) q->opacity=RoundToQuantum(normalize*qixel.opacity); if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace)) blur_indexes[x]=(IndexPacket) RoundToQuantum(normalize*qixel.index); } else { MagickRealType alpha, gamma; alpha=1.0; gamma=0.0; for (i=0; i < (long) n; i+=step) { (void) GetOneCacheViewVirtualPixel(image_view,(long) (blur_center.x+ center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),(long) ( blur_center.y+center.x*sin_theta[i]+center.y*cos_theta[i]+0.5), &pixel,exception); alpha=(MagickRealType) (QuantumScale*(QuantumRange-pixel.opacity)); qixel.red+=alpha*pixel.red; qixel.green+=alpha*pixel.green; qixel.blue+=alpha*pixel.blue; qixel.opacity+=pixel.opacity; if (image->colorspace == CMYKColorspace) { indexes=GetCacheViewVirtualIndexQueue(image_view); qixel.index+=alpha*(*indexes); } gamma+=alpha; normalize+=1.0; } gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma); normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 : normalize); if ((channel & RedChannel) != 0) q->red=RoundToQuantum(gamma*qixel.red); if ((channel & GreenChannel) != 0) q->green=RoundToQuantum(gamma*qixel.green); if ((channel & BlueChannel) != 0) q->blue=RoundToQuantum(gamma*qixel.blue); if ((channel & OpacityChannel) != 0) q->opacity=RoundToQuantum(normalize*qixel.opacity); if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace)) blur_indexes[x]=(IndexPacket) RoundToQuantum(gamma*qixel.index); } q++; } if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse) status=MagickFalse; if (image->progress_monitor != (MagickProgressMonitor) NULL) { MagickBooleanType proceed; #if defined(MAGICKCORE_OPENMP_SUPPORT) #pragma omp critical (MagickCore_RadialBlurImageChannel) #endif proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows); if (proceed == MagickFalse) status=MagickFalse; } } blur_view=DestroyCacheView(blur_view); image_view=DestroyCacheView(image_view); cos_theta=(MagickRealType *) RelinquishMagickMemory(cos_theta); sin_theta=(MagickRealType *) RelinquishMagickMemory(sin_theta); if (status == MagickFalse) blur_image=DestroyImage(blur_image); return(blur_image); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % R e d u c e N o i s e I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % ReduceNoiseImage() smooths the contours of an image while still preserving % edge information. The algorithm works by replacing each pixel with its % neighbor closest in value. A neighbor is defined by radius. Use a radius % of 0 and ReduceNoise() selects a suitable radius for you. % % The format of the ReduceNoiseImage method is: % % Image *ReduceNoiseImage(const Image *image,const double radius, % ExceptionInfo *exception) % % A description of each parameter follows: % % o image: the image. % % o radius: the radius of the pixel neighborhood. % % o exception: return any errors or warnings in this structure. % */ static MagickPixelPacket GetNonpeakMedianPixelList(MedianPixelList *pixel_list) { MagickPixelPacket pixel; register long channel; register MedianSkipList *list; unsigned long center, color, count, previous, next; unsigned short channels[5]; /* Finds the median value for each of the color. */ center=pixel_list->center; for (channel=0; channel < 5; channel++) { list=pixel_list->lists+channel; color=65536UL; next=list->nodes[color].next[0]; count=0; do { previous=color; color=next; next=list->nodes[color].next[0]; count+=list->nodes[color].count; } while (count <= center); if ((previous == 65536UL) && (next != 65536UL)) color=next; else if ((previous != 65536UL) && (next == 65536UL)) color=previous; channels[channel]=(unsigned short) color; } GetMagickPixelPacket((const Image *) NULL,&pixel); pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]); pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]); pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]); pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]); pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]); return(pixel); } MagickExport Image *ReduceNoiseImage(const Image *image,const double radius, ExceptionInfo *exception) { #define ReduceNoiseImageTag "ReduceNoise/Image" Image *noise_image; long progress, y; MagickBooleanType status; MedianPixelList **pixel_list; unsigned long width; CacheView *image_view, *noise_view; /* Initialize noise image attributes. */ assert(image != (Image *) NULL); assert(image->signature == MagickSignature); if (image->debug != MagickFalse) (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); assert(exception != (ExceptionInfo *) NULL); assert(exception->signature == MagickSignature); width=GetOptimalKernelWidth2D(radius,0.5); if ((image->columns < width) || (image->rows < width)) ThrowImageException(OptionError,"ImageSmallerThanKernelRadius"); noise_image=CloneImage(image,image->columns,image->rows,MagickTrue, exception); if (noise_image == (Image *) NULL) return((Image *) NULL); if (SetImageStorageClass(noise_image,DirectClass) == MagickFalse) { InheritException(exception,&noise_image->exception); noise_image=DestroyImage(noise_image); return((Image *) NULL); } pixel_list=AcquireMedianPixelListThreadSet(width); if (pixel_list == (MedianPixelList **) NULL) { noise_image=DestroyImage(noise_image); ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); } /* Reduce noise image. */ status=MagickTrue; progress=0; image_view=AcquireCacheView(image); noise_view=AcquireCacheView(noise_image); #if defined(MAGICKCORE_OPENMP_SUPPORT) #pragma omp parallel for schedule(dynamic) shared(progress,status) #endif for (y=0; y < (long) noise_image->rows; y++) { register const IndexPacket *__restrict indexes; register const PixelPacket *__restrict p; register IndexPacket *__restrict noise_indexes; register long id, x; register PixelPacket *__restrict q; if (status == MagickFalse) continue; p=GetCacheViewVirtualPixels(image_view,-((long) width/2L),y-(long) (width/ 2L),image->columns+width,width,exception); q=QueueCacheViewAuthenticPixels(noise_view,0,y,noise_image->columns,1, exception); if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL)) { status=MagickFalse; continue; } indexes=GetCacheViewVirtualIndexQueue(image_view); noise_indexes=GetCacheViewAuthenticIndexQueue(noise_view); id=GetOpenMPThreadId(); for (x=0; x < (long) noise_image->columns; x++) { MagickPixelPacket pixel; register const PixelPacket *__restrict r; register const IndexPacket *__restrict s; register long u, v; r=p; s=indexes+x; ResetMedianPixelList(pixel_list[id]); for (v=0; v < (long) width; v++) { for (u=0; u < (long) width; u++) InsertMedianPixelList(image,r+u,s+u,pixel_list[id]); r+=image->columns+width; s+=image->columns+width; } pixel=GetNonpeakMedianPixelList(pixel_list[id]); SetPixelPacket(noise_image,&pixel,q,noise_indexes+x); p++; q++; } if (SyncCacheViewAuthenticPixels(noise_view,exception) == MagickFalse) status=MagickFalse; if (image->progress_monitor != (MagickProgressMonitor) NULL) { MagickBooleanType proceed; #if defined(MAGICKCORE_OPENMP_SUPPORT) #pragma omp critical (MagickCore_ReduceNoiseImage) #endif proceed=SetImageProgress(image,ReduceNoiseImageTag,progress++, image->rows); if (proceed == MagickFalse) status=MagickFalse; } } noise_view=DestroyCacheView(noise_view); image_view=DestroyCacheView(image_view); pixel_list=DestroyMedianPixelListThreadSet(pixel_list); return(noise_image); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % S e l e c t i v e B l u r I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % SelectiveBlurImage() selectively blur pixels within a contrast threshold. % It is similar to the unsharpen mask that sharpens everything with contrast % above a certain threshold. % % The format of the SelectiveBlurImage method is: % % Image *SelectiveBlurImage(const Image *image,const double radius, % const double sigma,const double threshold,ExceptionInfo *exception) % Image *SelectiveBlurImageChannel(const Image *image, % const ChannelType channel,const double radius,const double sigma, % const double threshold,ExceptionInfo *exception) % % A description of each parameter follows: % % o image: the image. % % o channel: the channel type. % % o radius: the radius of the Gaussian, in pixels, not counting the center % pixel. % % o sigma: the standard deviation of the Gaussian, in pixels. % % o threshold: only pixels within this contrast threshold are included % in the blur operation. % % o exception: return any errors or warnings in this structure. % */ static inline MagickBooleanType SelectiveContrast(const PixelPacket *p, const PixelPacket *q,const double threshold) { if (fabs(PixelIntensity(p)-PixelIntensity(q)) < threshold) return(MagickTrue); return(MagickFalse); } MagickExport Image *SelectiveBlurImage(const Image *image,const double radius, const double sigma,const double threshold,ExceptionInfo *exception) { Image *blur_image; blur_image=SelectiveBlurImageChannel(image,DefaultChannels,radius,sigma, threshold,exception); return(blur_image); } MagickExport Image *SelectiveBlurImageChannel(const Image *image, const ChannelType channel,const double radius,const double sigma, const double threshold,ExceptionInfo *exception) { #define SelectiveBlurImageTag "SelectiveBlur/Image" double alpha, *kernel; Image *blur_image; long progress, v, y; MagickBooleanType status; MagickPixelPacket zero; MagickRealType bias; register long i, u; unsigned long width; CacheView *blur_view, *image_view; /* Initialize blur image attributes. */ assert(image != (Image *) NULL); assert(image->signature == MagickSignature); if (image->debug != MagickFalse) (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); assert(exception != (ExceptionInfo *) NULL); assert(exception->signature == MagickSignature); width=GetOptimalKernelWidth1D(radius,sigma); kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel)); if (kernel == (double *) NULL) ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); i=0; for (v=(-((long) width/2)); v <= (long) (width/2); v++) { for (u=(-((long) width/2)); u <= (long) (width/2); u++) { alpha=exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma)); kernel[i]=(double) (alpha/(2.0*MagickPI*MagickSigma*MagickSigma)); i++; } } if (image->debug != MagickFalse) { char format[MaxTextExtent], *message; long u, v; register const double *k; (void) LogMagickEvent(TransformEvent,GetMagickModule(), " SelectiveBlurImage with %ldx%ld kernel:",width,width); message=AcquireString(""); k=kernel; for (v=0; v < (long) width; v++) { *message='\0'; (void) FormatMagickString(format,MaxTextExtent,"%ld: ",v); (void) ConcatenateString(&message,format); for (u=0; u < (long) width; u++) { (void) FormatMagickString(format,MaxTextExtent,"%+f ",*k++); (void) ConcatenateString(&message,format); } (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message); } message=DestroyString(message); } blur_image=CloneImage(image,0,0,MagickTrue,exception); if (blur_image == (Image *) NULL) return((Image *) NULL); if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse) { InheritException(exception,&blur_image->exception); blur_image=DestroyImage(blur_image); return((Image *) NULL); } /* Threshold blur image. */ status=MagickTrue; progress=0; GetMagickPixelPacket(image,&zero); bias=image->bias; image_view=AcquireCacheView(image); blur_view=AcquireCacheView(blur_image); #if defined(MAGICKCORE_OPENMP_SUPPORT) #pragma omp parallel for schedule(dynamic) shared(progress,status) #endif for (y=0; y < (long) image->rows; y++) { MagickBooleanType sync; MagickRealType gamma; register const IndexPacket *__restrict indexes; register const PixelPacket *__restrict p; register IndexPacket *__restrict blur_indexes; register long x; register PixelPacket *__restrict q; if (status == MagickFalse) continue; p=GetCacheViewVirtualPixels(image_view,-((long) width/2L),y-(long) (width/ 2L),image->columns+width,width,exception); q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1, exception); if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL)) { status=MagickFalse; continue; } indexes=GetCacheViewVirtualIndexQueue(image_view); blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view); for (x=0; x < (long) image->columns; x++) { long j, v; MagickPixelPacket pixel; register const double *__restrict k; register long u; pixel=zero; k=kernel; gamma=0.0; j=0; if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse)) { for (v=0; v < (long) width; v++) { for (u=0; u < (long) width; u++) { if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse) { pixel.red+=(*k)*(p+u+j)->red; pixel.green+=(*k)*(p+u+j)->green; pixel.blue+=(*k)*(p+u+j)->blue; gamma+=(*k); k++; } } j+=image->columns+width; } if (gamma != 0.0) { gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma); if ((channel & RedChannel) != 0) q->red=RoundToQuantum(gamma*pixel.red+bias); if ((channel & GreenChannel) != 0) q->green=RoundToQuantum(gamma*pixel.green+bias); if ((channel & BlueChannel) != 0) q->blue=RoundToQuantum(gamma*pixel.blue+bias); } if ((channel & OpacityChannel) != 0) { gamma=0.0; j=0; for (v=0; v < (long) width; v++) { for (u=0; u < (long) width; u++) { if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse) { pixel.opacity+=(*k)*(p+u+j)->opacity; gamma+=(*k); k++; } } j+=image->columns+width; } if (gamma != 0.0) { gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma); q->opacity=RoundToQuantum(gamma*pixel.opacity+bias); } } if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace)) { gamma=0.0; j=0; for (v=0; v < (long) width; v++) { for (u=0; u < (long) width; u++) { if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse) { pixel.index+=(*k)*indexes[x+u+j]; gamma+=(*k); k++; } } j+=image->columns+width; } if (gamma != 0.0) { gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma); blur_indexes[x]=RoundToQuantum(gamma*pixel.index+bias); } } } else { MagickRealType alpha; for (v=0; v < (long) width; v++) { for (u=0; u < (long) width; u++) { if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse) { alpha=(MagickRealType) (QuantumScale*(QuantumRange- (p+u+j)->opacity)); pixel.red+=(*k)*alpha*(p+u+j)->red; pixel.green+=(*k)*alpha*(p+u+j)->green; pixel.blue+=(*k)*alpha*(p+u+j)->blue; pixel.opacity+=(*k)*(p+u+j)->opacity; gamma+=(*k)*alpha; k++; } } j+=image->columns+width; } if (gamma != 0.0) { gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma); if ((channel & RedChannel) != 0) q->red=RoundToQuantum(gamma*pixel.red+bias); if ((channel & GreenChannel) != 0) q->green=RoundToQuantum(gamma*pixel.green+bias); if ((channel & BlueChannel) != 0) q->blue=RoundToQuantum(gamma*pixel.blue+bias); } if ((channel & OpacityChannel) != 0) { gamma=0.0; j=0; for (v=0; v < (long) width; v++) { for (u=0; u < (long) width; u++) { if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse) { pixel.opacity+=(*k)*(p+u+j)->opacity; gamma+=(*k); k++; } } j+=image->columns+width; } if (gamma != 0.0) { gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma); q->opacity=RoundToQuantum(pixel.opacity+bias); } } if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace)) { gamma=0.0; j=0; for (v=0; v < (long) width; v++) { for (u=0; u < (long) width; u++) { if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse) { alpha=(MagickRealType) (QuantumScale*(QuantumRange- (p+u+j)->opacity)); pixel.index+=(*k)*alpha*indexes[x+u+j]; gamma+=(*k); k++; } } j+=image->columns+width; } if (gamma != 0.0) { gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma); blur_indexes[x]=RoundToQuantum(gamma*pixel.index+bias); } } } p++; q++; } sync=SyncCacheViewAuthenticPixels(blur_view,exception); if (sync == MagickFalse) status=MagickFalse; if (image->progress_monitor != (MagickProgressMonitor) NULL) { MagickBooleanType proceed; #if defined(MAGICKCORE_OPENMP_SUPPORT) #pragma omp critical (MagickCore_SelectiveBlurImageChannel) #endif proceed=SetImageProgress(image,SelectiveBlurImageTag,progress++, image->rows); if (proceed == MagickFalse) status=MagickFalse; } } blur_image->type=image->type; blur_view=DestroyCacheView(blur_view); image_view=DestroyCacheView(image_view); kernel=(double *) RelinquishMagickMemory(kernel); if (status == MagickFalse) blur_image=DestroyImage(blur_image); return(blur_image); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % S h a d e I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % ShadeImage() shines a distant light on an image to create a % three-dimensional effect. You control the positioning of the light with % azimuth and elevation; azimuth is measured in degrees off the x axis % and elevation is measured in pixels above the Z axis. % % The format of the ShadeImage method is: % % Image *ShadeImage(const Image *image,const MagickBooleanType gray, % const double azimuth,const double elevation,ExceptionInfo *exception) % % A description of each parameter follows: % % o image: the image. % % o gray: A value other than zero shades the intensity of each pixel. % % o azimuth, elevation: Define the light source direction. % % o exception: return any errors or warnings in this structure. % */ MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray, const double azimuth,const double elevation,ExceptionInfo *exception) { #define ShadeImageTag "Shade/Image" Image *shade_image; long progress, y; MagickBooleanType status; PrimaryInfo light; CacheView *image_view, *shade_view; /* Initialize shaded image attributes. */ assert(image != (const Image *) NULL); assert(image->signature == MagickSignature); if (image->debug != MagickFalse) (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); assert(exception != (ExceptionInfo *) NULL); assert(exception->signature == MagickSignature); shade_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception); if (shade_image == (Image *) NULL) return((Image *) NULL); if (SetImageStorageClass(shade_image,DirectClass) == MagickFalse) { InheritException(exception,&shade_image->exception); shade_image=DestroyImage(shade_image); return((Image *) NULL); } /* Compute the light vector. */ light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))* cos(DegreesToRadians(elevation)); light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))* cos(DegreesToRadians(elevation)); light.z=(double) QuantumRange*sin(DegreesToRadians(elevation)); /* Shade image. */ status=MagickTrue; progress=0; image_view=AcquireCacheView(image); shade_view=AcquireCacheView(shade_image); #if defined(MAGICKCORE_OPENMP_SUPPORT) #pragma omp parallel for schedule(dynamic) shared(progress,status) #endif for (y=0; y < (long) image->rows; y++) { MagickRealType distance, normal_distance, shade; PrimaryInfo normal; register const PixelPacket *__restrict p, *__restrict s0, *__restrict s1, *__restrict s2; register long x; register PixelPacket *__restrict q; if (status == MagickFalse) continue; p=GetCacheViewVirtualPixels(image_view,-1,y-1,image->columns+2,3,exception); q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1, exception); if ((p == (PixelPacket *) NULL) || (q == (PixelPacket *) NULL)) { status=MagickFalse; continue; } /* Shade this row of pixels. */ normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */ s0=p+1; s1=s0+image->columns+2; s2=s1+image->columns+2; for (x=0; x < (long) image->columns; x++) { /* Determine the surface normal and compute shading. */ normal.x=(double) (PixelIntensity(s0-1)+PixelIntensity(s1-1)+ PixelIntensity(s2-1)-PixelIntensity(s0+1)-PixelIntensity(s1+1)- PixelIntensity(s2+1)); normal.y=(double) (PixelIntensity(s2-1)+PixelIntensity(s2)+ PixelIntensity(s2+1)-PixelIntensity(s0-1)-PixelIntensity(s0)- PixelIntensity(s0+1)); if ((normal.x == 0.0) && (normal.y == 0.0)) shade=light.z; else { shade=0.0; distance=normal.x*light.x+normal.y*light.y+normal.z*light.z; if (distance > MagickEpsilon) { normal_distance= normal.x*normal.x+normal.y*normal.y+normal.z*normal.z; if (normal_distance > (MagickEpsilon*MagickEpsilon)) shade=distance/sqrt((double) normal_distance); } } if (gray != MagickFalse) { q->red=(Quantum) shade; q->green=(Quantum) shade; q->blue=(Quantum) shade; } else { q->red=RoundToQuantum(QuantumScale*shade*s1->red); q->green=RoundToQuantum(QuantumScale*shade*s1->green); q->blue=RoundToQuantum(QuantumScale*shade*s1->blue); } q->opacity=s1->opacity; s0++; s1++; s2++; q++; } if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse) status=MagickFalse; if (image->progress_monitor != (MagickProgressMonitor) NULL) { MagickBooleanType proceed; #if defined(MAGICKCORE_OPENMP_SUPPORT) #pragma omp critical (MagickCore_ShadeImage) #endif proceed=SetImageProgress(image,ShadeImageTag,progress++,image->rows); if (proceed == MagickFalse) status=MagickFalse; } } shade_view=DestroyCacheView(shade_view); image_view=DestroyCacheView(image_view); if (status == MagickFalse) shade_image=DestroyImage(shade_image); return(shade_image); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % S h a r p e n I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % SharpenImage() sharpens the image. We convolve the image with a Gaussian % operator of the given radius and standard deviation (sigma). For % reasonable results, radius should be larger than sigma. Use a radius of 0 % and SharpenImage() selects a suitable radius for you. % % Using a separable kernel would be faster, but the negative weights cancel % out on the corners of the kernel producing often undesirable ringing in the % filtered result; this can be avoided by using a 2D gaussian shaped image % sharpening kernel instead. % % The format of the SharpenImage method is: % % Image *SharpenImage(const Image *image,const double radius, % const double sigma,ExceptionInfo *exception) % Image *SharpenImageChannel(const Image *image,const ChannelType channel, % const double radius,const double sigma,ExceptionInfo *exception) % % A description of each parameter follows: % % o image: the image. % % o channel: the channel type. % % o radius: the radius of the Gaussian, in pixels, not counting the center % pixel. % % o sigma: the standard deviation of the Laplacian, in pixels. % % o exception: return any errors or warnings in this structure. % */ MagickExport Image *SharpenImage(const Image *image,const double radius, const double sigma,ExceptionInfo *exception) { Image *sharp_image; sharp_image=SharpenImageChannel(image,DefaultChannels,radius,sigma,exception); return(sharp_image); } MagickExport Image *SharpenImageChannel(const Image *image, const ChannelType channel,const double radius,const double sigma, ExceptionInfo *exception) { double *kernel; Image *sharp_image; MagickRealType alpha, normalize; register long i, u, v; unsigned long width; assert(image != (const Image *) NULL); assert(image->signature == MagickSignature); if (image->debug != MagickFalse) (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); assert(exception != (ExceptionInfo *) NULL); assert(exception->signature == MagickSignature); width=GetOptimalKernelWidth2D(radius,sigma); kernel=(double *) AcquireQuantumMemory((size_t) width*width,sizeof(*kernel)); if (kernel == (double *) NULL) ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); i=0; normalize=0.0; for (v=(-((long) width/2)); v <= (long) (width/2); v++) { for (u=(-((long) width/2)); u <= (long) (width/2); u++) { alpha=exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma)); kernel[i]=(double) (-alpha/(2.0*MagickPI*MagickSigma*MagickSigma)); normalize+=kernel[i]; i++; } } kernel[i/2]=(double) ((-2.0)*normalize); sharp_image=ConvolveImageChannel(image,channel,width,kernel,exception); kernel=(double *) RelinquishMagickMemory(kernel); return(sharp_image); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % S p r e a d I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % SpreadImage() is a special effects method that randomly displaces each % pixel in a block defined by the radius parameter. % % The format of the SpreadImage method is: % % Image *SpreadImage(const Image *image,const double radius, % ExceptionInfo *exception) % % A description of each parameter follows: % % o image: the image. % % o radius: Choose a random pixel in a neighborhood of this extent. % % o exception: return any errors or warnings in this structure. % */ MagickExport Image *SpreadImage(const Image *image,const double radius, ExceptionInfo *exception) { #define SpreadImageTag "Spread/Image" Image *spread_image; long progress, y; MagickBooleanType status; MagickPixelPacket zero; RandomInfo **random_info; ResampleFilter **resample_filter; unsigned long width; CacheView *image_view; /* Initialize spread image attributes. */ assert(image != (Image *) NULL); assert(image->signature == MagickSignature); if (image->debug != MagickFalse) (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); assert(exception != (ExceptionInfo *) NULL); assert(exception->signature == MagickSignature); spread_image=CloneImage(image,image->columns,image->rows,MagickTrue, exception); if (spread_image == (Image *) NULL) return((Image *) NULL); if (SetImageStorageClass(spread_image,DirectClass) == MagickFalse) { InheritException(exception,&spread_image->exception); spread_image=DestroyImage(spread_image); return((Image *) NULL); } /* Spread image. */ status=MagickTrue; progress=0; GetMagickPixelPacket(spread_image,&zero); width=GetOptimalKernelWidth1D(radius,0.5); resample_filter=AcquireResampleFilterThreadSet(image,MagickTrue,exception); random_info=AcquireRandomInfoThreadSet(); image_view=AcquireCacheView(spread_image); #if defined(MAGICKCORE_OPENMP_SUPPORT) #pragma omp parallel for schedule(dynamic,8) shared(progress,status) #endif for (y=0; y < (long) spread_image->rows; y++) { MagickPixelPacket pixel; register IndexPacket *__restrict indexes; register long id, x; register PixelPacket *__restrict q; if (status == MagickFalse) continue; q=QueueCacheViewAuthenticPixels(image_view,0,y,spread_image->columns,1, exception); if (q == (PixelPacket *) NULL) { status=MagickFalse; continue; } indexes=GetCacheViewAuthenticIndexQueue(image_view); pixel=zero; id=GetOpenMPThreadId(); for (x=0; x < (long) spread_image->columns; x++) { (void) ResamplePixelColor(resample_filter[id],(double) x+width* (GetPseudoRandomValue(random_info[id])-0.5),(double) y+width* (GetPseudoRandomValue(random_info[id])-0.5),&pixel); SetPixelPacket(spread_image,&pixel,q,indexes+x); q++; } if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse) status=MagickFalse; if (image->progress_monitor != (MagickProgressMonitor) NULL) { MagickBooleanType proceed; #if defined(MAGICKCORE_OPENMP_SUPPORT) #pragma omp critical (MagickCore_SpreadImage) #endif proceed=SetImageProgress(image,SpreadImageTag,progress++,image->rows); if (proceed == MagickFalse) status=MagickFalse; } } image_view=DestroyCacheView(image_view); random_info=DestroyRandomInfoThreadSet(random_info); resample_filter=DestroyResampleFilterThreadSet(resample_filter); return(spread_image); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % U n s h a r p M a s k I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % UnsharpMaskImage() sharpens one or more image channels. We convolve the % image with a Gaussian operator of the given radius and standard deviation % (sigma). For reasonable results, radius should be larger than sigma. Use a % radius of 0 and UnsharpMaskImage() selects a suitable radius for you. % % The format of the UnsharpMaskImage method is: % % Image *UnsharpMaskImage(const Image *image,const double radius, % const double sigma,const double amount,const double threshold, % ExceptionInfo *exception) % Image *UnsharpMaskImageChannel(const Image *image, % const ChannelType channel,const double radius,const double sigma, % const double amount,const double threshold,ExceptionInfo *exception) % % A description of each parameter follows: % % o image: the image. % % o channel: the channel type. % % o radius: the radius of the Gaussian, in pixels, not counting the center % pixel. % % o sigma: the standard deviation of the Gaussian, in pixels. % % o amount: the percentage of the difference between the original and the % blur image that is added back into the original. % % o threshold: the threshold in pixels needed to apply the diffence amount. % % o exception: return any errors or warnings in this structure. % */ MagickExport Image *UnsharpMaskImage(const Image *image,const double radius, const double sigma,const double amount,const double threshold, ExceptionInfo *exception) { Image *sharp_image; sharp_image=UnsharpMaskImageChannel(image,DefaultChannels,radius,sigma,amount, threshold,exception); return(sharp_image); } MagickExport Image *UnsharpMaskImageChannel(const Image *image, const ChannelType channel,const double radius,const double sigma, const double amount,const double threshold,ExceptionInfo *exception) { #define SharpenImageTag "Sharpen/Image" Image *unsharp_image; long progress, y; MagickBooleanType status; MagickPixelPacket zero; MagickRealType quantum_threshold; CacheView *image_view, *unsharp_view; assert(image != (const Image *) NULL); assert(image->signature == MagickSignature); if (image->debug != MagickFalse) (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); assert(exception != (ExceptionInfo *) NULL); unsharp_image=BlurImageChannel(image,channel,radius,sigma,exception); if (unsharp_image == (Image *) NULL) return((Image *) NULL); quantum_threshold=(MagickRealType) QuantumRange*threshold; /* Unsharp-mask image. */ status=MagickTrue; progress=0; GetMagickPixelPacket(image,&zero); image_view=AcquireCacheView(image); unsharp_view=AcquireCacheView(unsharp_image); #if defined(MAGICKCORE_OPENMP_SUPPORT) #pragma omp parallel for schedule(dynamic) shared(progress,status) #endif for (y=0; y < (long) image->rows; y++) { MagickPixelPacket pixel; register const IndexPacket *__restrict indexes; register const PixelPacket *__restrict p; register IndexPacket *__restrict unsharp_indexes; register long x; register PixelPacket *__restrict q; if (status == MagickFalse) continue; p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); q=GetCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1, exception); if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL)) { status=MagickFalse; continue; } indexes=GetCacheViewVirtualIndexQueue(image_view); unsharp_indexes=GetCacheViewAuthenticIndexQueue(unsharp_view); pixel=zero; for (x=0; x < (long) image->columns; x++) { if ((channel & RedChannel) != 0) { pixel.red=p->red-(MagickRealType) q->red; if (fabs(2.0*pixel.red) < quantum_threshold) pixel.red=(MagickRealType) p->red; else pixel.red=(MagickRealType) p->red+(pixel.red*amount); q->red=RoundToQuantum(pixel.red); } if ((channel & GreenChannel) != 0) { pixel.green=p->green-(MagickRealType) q->green; if (fabs(2.0*pixel.green) < quantum_threshold) pixel.green=(MagickRealType) p->green; else pixel.green=(MagickRealType) p->green+(pixel.green*amount); q->green=RoundToQuantum(pixel.green); } if ((channel & BlueChannel) != 0) { pixel.blue=p->blue-(MagickRealType) q->blue; if (fabs(2.0*pixel.blue) < quantum_threshold) pixel.blue=(MagickRealType) p->blue; else pixel.blue=(MagickRealType) p->blue+(pixel.blue*amount); q->blue=RoundToQuantum(pixel.blue); } if ((channel & OpacityChannel) != 0) { pixel.opacity=p->opacity-(MagickRealType) q->opacity; if (fabs(2.0*pixel.opacity) < quantum_threshold) pixel.opacity=(MagickRealType) p->opacity; else pixel.opacity=p->opacity+(pixel.opacity*amount); q->opacity=RoundToQuantum(pixel.opacity); } if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace)) { pixel.index=unsharp_indexes[x]-(MagickRealType) indexes[x]; if (fabs(2.0*pixel.index) < quantum_threshold) pixel.index=(MagickRealType) unsharp_indexes[x]; else pixel.index=(MagickRealType) unsharp_indexes[x]+(pixel.index* amount); unsharp_indexes[x]=RoundToQuantum(pixel.index); } p++; q++; } if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse) status=MagickFalse; if (image->progress_monitor != (MagickProgressMonitor) NULL) { MagickBooleanType proceed; #if defined(MAGICKCORE_OPENMP_SUPPORT) #pragma omp critical (MagickCore_UnsharpMaskImageChannel) #endif proceed=SetImageProgress(image,SharpenImageTag,progress++,image->rows); if (proceed == MagickFalse) status=MagickFalse; } } unsharp_image->type=image->type; unsharp_view=DestroyCacheView(unsharp_view); image_view=DestroyCacheView(image_view); if (status == MagickFalse) unsharp_image=DestroyImage(unsharp_image); return(unsharp_image); }