introducing bilateral filtering
diff --git a/MagickCore/effect.c b/MagickCore/effect.c
index 60df30f..25ac58c 100644
--- a/MagickCore/effect.c
+++ b/MagickCore/effect.c
@@ -805,6 +805,233 @@
 %                                                                             %
 %                                                                             %
 %                                                                             %
+%     B i l a t e r a l F i l t e r I m a g e                                 %
+%                                                                             %
+%                                                                             %
+%                                                                             %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+%  BilateralFilterImage() smooths and reducees noise in an image while
+%  preserving edges.
+%
+%  The format of the AdaptiveBlurImage method is:
+%
+%      Image *BilateralFilterImage(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 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 *BilateralFilterImage(const Image *image,const double radius,
+  const double sigma,ExceptionInfo *exception)
+{
+#define BilateralFilterImageTag  "Convolve/Image"
+#define MagickSigma  (fabs(sigma) < MagickEpsilon ? MagickEpsilon : sigma)
+
+  CacheView
+    *bilateral_view,
+    *image_view;
+
+  Image
+    *bilateral_image;
+
+  MagickBooleanType
+    status;
+
+  MagickOffsetType
+    progress;
+
+  size_t
+    width;
+
+  ssize_t
+    y;
+
+  assert(image != (const Image *) NULL);
+  assert(image->signature == MagickCoreSignature);
+  if (image->debug != MagickFalse)
+    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
+  assert(exception != (ExceptionInfo *) NULL);
+  assert(exception->signature == MagickCoreSignature);
+  bilateral_image=CloneImage(image,0,0,MagickTrue,exception);
+  if (bilateral_image == (Image *) NULL)
+    return((Image *) NULL);
+  if (SetImageStorageClass(bilateral_image,DirectClass,exception) == MagickFalse)
+    {
+      bilateral_image=DestroyImage(bilateral_image);
+      return((Image *) NULL);
+    }
+  /*
+    Create a kernel.
+  */
+  /*
+    Adaptively blur image.
+  */
+  status=MagickTrue;
+  progress=0;
+  width=GetOptimalKernelWidth2D(radius,sigma);
+  image_view=AcquireVirtualCacheView(image,exception);
+  bilateral_view=AcquireAuthenticCacheView(bilateral_image,exception);
+#if defined(MAGICKCORE_OPENMP_SUPPORT)
+  #pragma omp parallel for schedule(static) shared(progress,status) \
+    magick_number_threads(image,bilateral_image,bilateral_image->rows,1)
+#endif
+  for (y=0; y < (ssize_t) bilateral_image->rows; y++)
+  {
+    register const Quantum
+      *magick_restrict r;
+
+    register Quantum
+      *magick_restrict q;
+
+    register ssize_t
+      x;
+
+    if (status == MagickFalse)
+      continue;
+    r=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
+    q=QueueCacheViewAuthenticPixels(bilateral_view,0,y,
+      bilateral_image->columns,1,exception);
+    if ((r == (const Quantum *) NULL) || (q == (Quantum *) NULL))
+      {
+        status=MagickFalse;
+        continue;
+      }
+    for (x=0; x < (ssize_t) bilateral_image->columns; x++)
+    {
+      register const Quantum
+        *magick_restrict p;
+
+      register ssize_t
+        i;
+
+      ssize_t
+        center;
+
+      p=GetCacheViewVirtualPixels(image_view,x-((ssize_t) width/2L),y-
+        (ssize_t) (width/2L),width,width,exception);
+      if (p == (const Quantum *) NULL)
+        break;
+      center=(ssize_t) GetPixelChannels(image)*width*(width/2L)+
+        GetPixelChannels(image)*(width/2);
+      for (i=0; i < (ssize_t) GetPixelChannels(bilateral_image); i++)
+      {
+        double
+          alpha,
+          gamma,
+          pixel;
+
+        PixelChannel
+          channel;
+
+        PixelTrait
+          bilateral_traits,
+          traits;
+
+        register const double
+          *magick_restrict k;
+
+        register const Quantum
+          *magick_restrict pixels;
+
+        register ssize_t
+          u;
+
+        ssize_t
+          v;
+
+        channel=GetPixelChannelChannel(image,i);
+        traits=GetPixelChannelTraits(image,channel);
+        bilateral_traits=GetPixelChannelTraits(bilateral_image,channel);
+        if ((traits == UndefinedPixelTrait) ||
+            (bilateral_traits == UndefinedPixelTrait))
+          continue;
+        if ((bilateral_traits & CopyPixelTrait) != 0)
+          {
+            SetPixelChannel(bilateral_image,channel,p[center+i],q);
+            continue;
+          }
+        pixels=p;
+        pixel=0.0;
+        gamma=0.0;
+        if ((bilateral_traits & BlendPixelTrait) == 0)
+          {
+            /*
+              No alpha blending.
+            */
+            for (v=0; v < (ssize_t) width; v++)
+            {
+              for (u=0; u < (ssize_t) width; u++)
+              {
+                pixel+=1.0*pixels[i];
+                gamma+=1.0;
+                k++;
+                pixels+=GetPixelChannels(image);
+              }
+            }
+            gamma=PerceptibleReciprocal(gamma);
+            SetPixelChannel(bilateral_image,channel,ClampToQuantum(gamma*pixel),q);
+            continue;
+          }
+        /*
+          Alpha blending.
+        */
+        for (v=0; v < (ssize_t) width; v++)
+        {
+          for (u=0; u < (ssize_t) width; u++)
+          {
+            alpha=(double) (QuantumScale*GetPixelAlpha(image,pixels));
+            pixel+=1.0*alpha*pixels[i];
+            gamma+=1.0*alpha;
+            k++;
+            pixels+=GetPixelChannels(image);
+          }
+        }
+        gamma=PerceptibleReciprocal(gamma);
+        SetPixelChannel(bilateral_image,channel,ClampToQuantum(gamma*pixel),q);
+      }
+      q+=GetPixelChannels(bilateral_image);
+      r+=GetPixelChannels(image);
+    }
+    if (SyncCacheViewAuthenticPixels(bilateral_view,exception) == MagickFalse)
+      status=MagickFalse;
+    if (image->progress_monitor != (MagickProgressMonitor) NULL)
+      {
+        MagickBooleanType
+          proceed;
+
+#if defined(MAGICKCORE_OPENMP_SUPPORT)
+        #pragma omp atomic
+#endif
+        progress++;
+        proceed=SetImageProgress(image,BilateralFilterImageTag,progress,
+          image->rows);
+        if (proceed == MagickFalse)
+          status=MagickFalse;
+      }
+  }
+  bilateral_image->type=image->type;
+  bilateral_view=DestroyCacheView(bilateral_view);
+  image_view=DestroyCacheView(image_view);
+  if (status == MagickFalse)
+    bilateral_image=DestroyImage(bilateral_image);
+  return(bilateral_image);
+}
+
+/*
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%                                                                             %
+%                                                                             %
+%                                                                             %
 %     C o n v o l v e I m a g e                                               %
 %                                                                             %
 %                                                                             %