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                                             %
 %                                                                             %
 %                                                                             %