diff --git a/magick/effect.c b/magick/effect.c
index 293c824..d6915eb 100644
--- a/magick/effect.c
+++ b/magick/effect.c
@@ -1266,6 +1266,370 @@
% %
% %
% %
+% C o n v o l v e I m a g e %
+% %
+% %
+% %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% ConvolveImage() applies a custom convolution kernel to the image.
+%
+% The format of the ConvolveImage method is:
+%
+% Image *ConvolveImage(const Image *image,const unsigned long order,
+% const double *kernel,ExceptionInfo *exception)
+% Image *ConvolveImageChannel(const Image *image,const ChannelType channel,
+% const unsigned long order,const double *kernel,
+% ExceptionInfo *exception)
+%
+% A description of each parameter follows:
+%
+% o image: the image.
+%
+% o channel: the channel type.
+%
+% o order: the number of columns and rows in the filter kernel.
+%
+% o kernel: An array of double representing the convolution kernel.
+%
+% o exception: return any errors or warnings in this structure.
+%
+*/
+
+MagickExport Image *ConvolveImage(const Image *image,const unsigned long order,
+ const double *kernel,ExceptionInfo *exception)
+{
+ Image
+ *convolve_image;
+
+ convolve_image=ConvolveImageChannel(image,DefaultChannels,order,kernel,
+ exception);
+ return(convolve_image);
+}
+
+MagickExport Image *ConvolveImageChannel(const Image *image,
+ const ChannelType channel,const unsigned long order,const double *kernel,
+ ExceptionInfo *exception)
+{
+#define ConvolveImageTag "Convolve/Image"
+
+ double
+ *normal_kernel;
+
+ Image
+ *convolve_image;
+
+ long
+ progress,
+ y;
+
+ MagickBooleanType
+ status;
+
+ MagickPixelPacket
+ bias;
+
+ MagickRealType
+ gamma;
+
+ register long
+ i;
+
+ unsigned long
+ width;
+
+ CacheView
+ *convolve_view,
+ *image_view;
+
+ /*
+ Initialize convolve 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=order;
+ if ((width % 2) == 0)
+ ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
+ convolve_image=CloneImage(image,0,0,MagickTrue,exception);
+ if (convolve_image == (Image *) NULL)
+ return((Image *) NULL);
+ if (SetImageStorageClass(convolve_image,DirectClass) == MagickFalse)
+ {
+ InheritException(exception,&convolve_image->exception);
+ convolve_image=DestroyImage(convolve_image);
+ return((Image *) NULL);
+ }
+ if (image->debug != MagickFalse)
+ {
+ char
+ format[MaxTextExtent],
+ *message;
+
+ long
+ u,
+ v;
+
+ register const double
+ *k;
+
+ (void) LogMagickEvent(TransformEvent,GetMagickModule(),
+ " ConvolveImage 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);
+ }
+ /*
+ Normalize kernel.
+ */
+ normal_kernel=(double *) AcquireQuantumMemory(width*width,
+ sizeof(*normal_kernel));
+ if (normal_kernel == (double *) NULL)
+ {
+ convolve_image=DestroyImage(convolve_image);
+ ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
+ }
+ gamma=0.0;
+ for (i=0; i < (long) (width*width); i++)
+ gamma+=kernel[i];
+ gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
+ for (i=0; i < (long) (width*width); i++)
+ normal_kernel[i]=gamma*kernel[i];
+ /*
+ Convolve image.
+ */
+ status=MagickTrue;
+ progress=0;
+ GetMagickPixelPacket(image,&bias);
+ SetMagickPixelPacketBias(image,&bias);
+ image_view=AcquireCacheView(image);
+ convolve_view=AcquireCacheView(convolve_image);
+#if defined(MAGICKCORE_OPENMP_SUPPORT)
+ #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
+#endif
+ for (y=0; y < (long) image->rows; y++)
+ {
+ MagickBooleanType
+ sync;
+
+ register const IndexPacket
+ *restrict indexes;
+
+ register const PixelPacket
+ *restrict p;
+
+ register IndexPacket
+ *restrict convolve_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(convolve_view,0,y,convolve_image->columns,1,
+ exception);
+ if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
+ {
+ status=MagickFalse;
+ continue;
+ }
+ indexes=GetCacheViewVirtualIndexQueue(image_view);
+ convolve_indexes=GetCacheViewAuthenticIndexQueue(convolve_view);
+ for (x=0; x < (long) image->columns; x++)
+ {
+ long
+ v;
+
+ MagickPixelPacket
+ pixel;
+
+ register const double
+ *restrict k;
+
+ register const PixelPacket
+ *restrict kernel_pixels;
+
+ register long
+ u;
+
+ pixel=bias;
+ k=normal_kernel;
+ kernel_pixels=p;
+ if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
+ {
+ for (v=0; v < (long) width; v++)
+ {
+ for (u=0; u < (long) width; u++)
+ {
+ pixel.red+=(*k)*kernel_pixels[u].red;
+ pixel.green+=(*k)*kernel_pixels[u].green;
+ pixel.blue+=(*k)*kernel_pixels[u].blue;
+ k++;
+ }
+ kernel_pixels+=image->columns+width;
+ }
+ if ((channel & RedChannel) != 0)
+ q->red=RoundToQuantum(pixel.red);
+ if ((channel & GreenChannel) != 0)
+ q->green=RoundToQuantum(pixel.green);
+ if ((channel & BlueChannel) != 0)
+ q->blue=RoundToQuantum(pixel.blue);
+ if ((channel & OpacityChannel) != 0)
+ {
+ k=normal_kernel;
+ kernel_pixels=p;
+ for (v=0; v < (long) width; v++)
+ {
+ for (u=0; u < (long) width; u++)
+ {
+ pixel.opacity+=(*k)*kernel_pixels[u].opacity;
+ k++;
+ }
+ kernel_pixels+=image->columns+width;
+ }
+ q->opacity=RoundToQuantum(pixel.opacity);
+ }
+ if (((channel & IndexChannel) != 0) &&
+ (image->colorspace == CMYKColorspace))
+ {
+ register const IndexPacket
+ *restrict kernel_indexes;
+
+ k=normal_kernel;
+ kernel_indexes=indexes;
+ for (v=0; v < (long) width; v++)
+ {
+ for (u=0; u < (long) width; u++)
+ {
+ pixel.index+=(*k)*kernel_indexes[u];
+ k++;
+ }
+ kernel_indexes+=image->columns+width;
+ }
+ convolve_indexes[x]=RoundToQuantum(pixel.index);
+ }
+ }
+ else
+ {
+ MagickRealType
+ alpha,
+ gamma;
+
+ gamma=0.0;
+ for (v=0; v < (long) width; v++)
+ {
+ for (u=0; u < (long) width; u++)
+ {
+ alpha=(MagickRealType) (QuantumScale*(QuantumRange-
+ kernel_pixels[u].opacity));
+ pixel.red+=(*k)*alpha*kernel_pixels[u].red;
+ pixel.green+=(*k)*alpha*kernel_pixels[u].green;
+ pixel.blue+=(*k)*alpha*kernel_pixels[u].blue;
+ pixel.opacity+=(*k)*kernel_pixels[u].opacity;
+ gamma+=(*k)*alpha;
+ k++;
+ }
+ kernel_pixels+=image->columns+width;
+ }
+ gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
+ if ((channel & RedChannel) != 0)
+ q->red=RoundToQuantum(gamma*pixel.red);
+ if ((channel & GreenChannel) != 0)
+ q->green=RoundToQuantum(gamma*pixel.green);
+ if ((channel & BlueChannel) != 0)
+ q->blue=RoundToQuantum(gamma*pixel.blue);
+ if ((channel & OpacityChannel) != 0)
+ {
+ k=normal_kernel;
+ kernel_pixels=p;
+ for (v=0; v < (long) width; v++)
+ {
+ for (u=0; u < (long) width; u++)
+ {
+ pixel.opacity+=(*k)*kernel_pixels[u].opacity;
+ k++;
+ }
+ kernel_pixels+=image->columns+width;
+ }
+ q->opacity=RoundToQuantum(pixel.opacity);
+ }
+ if (((channel & IndexChannel) != 0) &&
+ (image->colorspace == CMYKColorspace))
+ {
+ register const IndexPacket
+ *restrict kernel_indexes;
+
+ k=normal_kernel;
+ kernel_pixels=p;
+ kernel_indexes=indexes;
+ for (v=0; v < (long) width; v++)
+ {
+ for (u=0; u < (long) width; u++)
+ {
+ alpha=(MagickRealType) (QuantumScale*(QuantumRange-
+ kernel_pixels[u].opacity));
+ pixel.index+=(*k)*alpha*kernel_indexes[u];
+ k++;
+ }
+ kernel_pixels+=image->columns+width;
+ kernel_indexes+=image->columns+width;
+ }
+ convolve_indexes[x]=RoundToQuantum(gamma*pixel.index);
+ }
+ }
+ p++;
+ q++;
+ }
+ sync=SyncCacheViewAuthenticPixels(convolve_view,exception);
+ if (sync == MagickFalse)
+ status=MagickFalse;
+ if (image->progress_monitor != (MagickProgressMonitor) NULL)
+ {
+ MagickBooleanType
+ proceed;
+
+#if defined(MAGICKCORE_OPENMP_SUPPORT)
+ #pragma omp critical (MagickCore_ConvolveImageChannel)
+#endif
+ proceed=SetImageProgress(image,ConvolveImageTag,progress++,image->rows);
+ if (proceed == MagickFalse)
+ status=MagickFalse;
+ }
+ }
+ convolve_image->type=image->type;
+ convolve_view=DestroyCacheView(convolve_view);
+ image_view=DestroyCacheView(image_view);
+ normal_kernel=(double *) RelinquishMagickMemory(normal_kernel);
+ if (status == MagickFalse)
+ convolve_image=DestroyImage(convolve_image);
+ return(convolve_image);
+}
+
+/*
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% %
+% %
+% %
% D e s p e c k l e I m a g e %
% %
% %