diff --git a/magick/effect.c b/magick/effect.c
index 7ddc998..a5618e2 100644
--- a/magick/effect.c
+++ b/magick/effect.c
@@ -2116,6 +2116,361 @@
% %
% %
% %
+% F i l t e r I m a g e %
+% %
+% %
+% %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% FilterImage() applies a custom convolution kernel to the image.
+%
+% The format of the FilterImage method is:
+%
+% Image *FilterImage(const Image *image,const MagickKernel *kernel,
+% ExceptionInfo *exception)
+% Image *FilterImageChannel(const Image *image,const ChannelType channel,
+% const MagickKernel *kernel,ExceptionInfo *exception)
+%
+% A description of each parameter follows:
+%
+% o image: the image.
+%
+% o channel: the channel type.
+%
+% o kernel: the filtering kernel.
+%
+% o exception: return any errors or warnings in this structure.
+%
+*/
+
+MagickExport Image *FilterImage(const Image *image,const MagickKernel *kernel,
+ ExceptionInfo *exception)
+{
+ Image
+ *filter_image;
+
+ filter_image=FilterImageChannel(image,DefaultChannels,kernel,exception);
+ return(filter_image);
+}
+
+MagickExport Image *FilterImageChannel(const Image *image,
+ const ChannelType channel,const MagickKernel *kernel,ExceptionInfo *exception)
+{
+#define FilterImageTag "Filter/Image"
+
+ CacheView
+ *filter_view,
+ *image_view;
+
+ double
+ *normal_kernel;
+
+ Image
+ *filter_image;
+
+ long
+ progress,
+ y;
+
+ MagickBooleanType
+ status;
+
+ MagickPixelPacket
+ bias;
+
+ MagickRealType
+ gamma;
+
+ register long
+ i;
+
+ /*
+ Initialize filter 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);
+ if ((kernel->width % 2) == 0)
+ ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
+ filter_image=CloneImage(image,0,0,MagickTrue,exception);
+ if (filter_image == (Image *) NULL)
+ return((Image *) NULL);
+ if (SetImageStorageClass(filter_image,DirectClass) == MagickFalse)
+ {
+ InheritException(exception,&filter_image->exception);
+ filter_image=DestroyImage(filter_image);
+ return((Image *) NULL);
+ }
+ if (image->debug != MagickFalse)
+ {
+ char
+ format[MaxTextExtent],
+ *message;
+
+ long
+ u,
+ v;
+
+ register const double
+ *k;
+
+ (void) LogMagickEvent(TransformEvent,GetMagickModule(),
+ " FilterImage with %ldx%ld kernel:",kernel->width,kernel->height);
+ message=AcquireString("");
+ k=kernel->values;
+ for (v=0; v < (long) kernel->height; v++)
+ {
+ *message='\0';
+ (void) FormatMagickString(format,MaxTextExtent,"%ld: ",v);
+ (void) ConcatenateString(&message,format);
+ for (u=0; u < (long) kernel->width; u++)
+ {
+ (void) FormatMagickString(format,MaxTextExtent,"%g ",*k++);
+ (void) ConcatenateString(&message,format);
+ }
+ (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
+ }
+ message=DestroyString(message);
+ }
+ /*
+ Normalize kernel.
+ */
+ normal_kernel=(double *) AcquireQuantumMemory(kernel->width*kernel->height,
+ sizeof(*normal_kernel));
+ if (normal_kernel == (double *) NULL)
+ {
+ filter_image=DestroyImage(filter_image);
+ ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
+ }
+ gamma=0.0;
+ for (i=0; i < (long) (kernel->width*kernel->height); i++)
+ gamma+=kernel->values[i];
+ gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
+ for (i=0; i < (long) (kernel->width*kernel->height); i++)
+ normal_kernel[i]=gamma*kernel->values[i];
+ /*
+ Filter image.
+ */
+ status=MagickTrue;
+ progress=0;
+ GetMagickPixelPacket(image,&bias);
+ SetMagickPixelPacketBias(image,&bias);
+ image_view=AcquireCacheView(image);
+ filter_view=AcquireCacheView(filter_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 filter_indexes;
+
+ register long
+ x;
+
+ register PixelPacket
+ *restrict q;
+
+ if (status == MagickFalse)
+ continue;
+ p=GetCacheViewVirtualPixels(image_view,-((long) kernel->width/2L),
+ y-(long) (kernel->height/2L),image->columns+kernel->width,kernel->height,
+ exception);
+ q=GetCacheViewAuthenticPixels(filter_view,0,y,filter_image->columns,1,
+ exception);
+ if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
+ {
+ status=MagickFalse;
+ continue;
+ }
+ indexes=GetCacheViewVirtualIndexQueue(image_view);
+ filter_indexes=GetCacheViewAuthenticIndexQueue(filter_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) kernel->width; v++)
+ {
+ for (u=0; u < (long) kernel->height; 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+kernel->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) kernel->width; v++)
+ {
+ for (u=0; u < (long) kernel->height; u++)
+ {
+ pixel.opacity+=(*k)*kernel_pixels[u].opacity;
+ k++;
+ }
+ kernel_pixels+=image->columns+kernel->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) kernel->width; v++)
+ {
+ for (u=0; u < (long) kernel->height; u++)
+ {
+ pixel.index+=(*k)*kernel_indexes[u];
+ k++;
+ }
+ kernel_indexes+=image->columns+kernel->width;
+ }
+ filter_indexes[x]=RoundToQuantum(pixel.index);
+ }
+ }
+ else
+ {
+ MagickRealType
+ alpha,
+ gamma;
+
+ gamma=0.0;
+ for (v=0; v < (long) kernel->width; v++)
+ {
+ for (u=0; u < (long) kernel->height; 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;
+ gamma+=(*k)*alpha;
+ k++;
+ }
+ kernel_pixels+=image->columns+kernel->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) kernel->width; v++)
+ {
+ for (u=0; u < (long) kernel->height; u++)
+ {
+ pixel.opacity+=(*k)*kernel_pixels[u].opacity;
+ k++;
+ }
+ kernel_pixels+=image->columns+kernel->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) kernel->width; v++)
+ {
+ for (u=0; u < (long) kernel->height; u++)
+ {
+ alpha=(MagickRealType) (QuantumScale*(QuantumRange-
+ kernel_pixels[u].opacity));
+ pixel.index+=(*k)*alpha*kernel_indexes[u];
+ k++;
+ }
+ kernel_pixels+=image->columns+kernel->width;
+ kernel_indexes+=image->columns+kernel->width;
+ }
+ filter_indexes[x]=RoundToQuantum(gamma*pixel.index);
+ }
+ }
+ p++;
+ q++;
+ }
+ sync=SyncCacheViewAuthenticPixels(filter_view,exception);
+ if (sync == MagickFalse)
+ status=MagickFalse;
+ if (image->progress_monitor != (MagickProgressMonitor) NULL)
+ {
+ MagickBooleanType
+ proceed;
+
+#if defined(MAGICKCORE_OPENMP_SUPPORT)
+ #pragma omp critical (MagickCore_FilterImageChannel)
+#endif
+ proceed=SetImageProgress(image,FilterImageTag,progress++,image->rows);
+ if (proceed == MagickFalse)
+ status=MagickFalse;
+ }
+ }
+ filter_image->type=image->type;
+ filter_view=DestroyCacheView(filter_view);
+ image_view=DestroyCacheView(image_view);
+ normal_kernel=(double *) RelinquishMagickMemory(normal_kernel);
+ if (status == MagickFalse)
+ filter_image=DestroyImage(filter_image);
+ return(filter_image);
+}
+
+/*
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% %
+% %
+% %
% G a u s s i a n B l u r I m a g e %
% %
% %