Support -kmeans command-line option
diff --git a/MagickCore/quantize.c b/MagickCore/quantize.c
index 58f948c..410b573 100644
--- a/MagickCore/quantize.c
+++ b/MagickCore/quantize.c
@@ -182,6 +182,7 @@
 #include "MagickCore/colormap.h"
 #include "MagickCore/colorspace.h"
 #include "MagickCore/colorspace-private.h"
+#include "MagickCore/compare.h"
 #include "MagickCore/enhance.h"
 #include "MagickCore/exception.h"
 #include "MagickCore/exception-private.h"
@@ -2305,6 +2306,267 @@
 %                                                                             %
 %                                                                             %
 %                                                                             %
+%  K m e a n s I m a g e                                                      %
+%                                                                             %
+%                                                                             %
+%                                                                             %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+%  KmeansImage() applies k-means color reduction to an image. This is a
+%  colorspace clustering or segmentation technique.
+%
+%  The format of the KmeansImage method is:
+%
+%      MagickBooleanType KmeansImage(Image *image,const size_t number_colors,
+%        const size_t max_iterations,const double max_distortion,
+%        ExceptionInfo *exception)
+%
+%  A description of each parameter follows:
+%
+%    o image: the image.
+%
+%    o number_colors: number of colors to use as seeds.
+%
+%    o max_iterations: maximum number of iterations while converging.
+%
+%    o max_distortion: the maximum quantization distortion.
+%
+%    o exception: return any errors or warnings in this structure.
+%
+*/
+
+static inline double KmeansDistance(const Image *magick_restrict image,
+  const Quantum *magick_restrict p,const PixelInfo *magick_restrict q)
+{
+  double
+    distance,
+    gamma,
+    pixel;
+
+  gamma=1.0;
+  distance=0.0;
+  if (image->alpha_trait != UndefinedPixelTrait)
+    {
+      pixel=GetPixelAlpha(image,p)-q->alpha;
+      distance+=gamma*QuantumScale*pixel*pixel;
+      gamma=(QuantumScale*GetPixelAlpha(image,p))*(QuantumScale*q->alpha);
+    }
+  distance=0.0;
+  if (image->colorspace == CMYKColorspace)
+    {
+      pixel=GetPixelBlack(image,p)-q->black;
+      distance+=gamma*QuantumScale*pixel*pixel;
+      gamma*=(double) (QuantumScale*(QuantumRange-GetPixelBlack(image,p)));
+      gamma*=(double) (QuantumScale*(QuantumRange-q->black));
+    }
+  distance*=3.0;
+  pixel=GetPixelRed(image,p)-q->red;
+  if ((image->colorspace == HSLColorspace) ||
+      (image->colorspace == HSBColorspace) ||
+      (image->colorspace == HWBColorspace))
+    {
+      if (fabs((double) pixel) > (QuantumRange/2))
+        pixel-=QuantumRange;
+      pixel*=2.0;
+    }
+  distance+=gamma*QuantumScale*pixel*pixel;
+  pixel=GetPixelGreen(image,p)-q->green;
+  distance+=gamma*QuantumScale*pixel*pixel;
+  pixel=GetPixelBlue(image,p)-q->blue;
+  distance+=gamma*QuantumScale*pixel*pixel;
+  return(distance);
+}
+
+MagickExport MagickBooleanType KmeansImage(Image *image,
+  const size_t number_colors,const size_t max_iterations,
+  const double max_distortion,ExceptionInfo *exception)
+{
+#define KmeansImageTag  "Kmeans/Image"
+
+  double
+    last_distortion;
+
+  Image
+    *kmeans_image;
+
+  MagickBooleanType
+    status;
+
+  QuantizeInfo
+    *quantize_info;
+
+  register ssize_t
+    n;
+
+  /*
+    Initialize the initial set of k means.
+  */
+  assert(image != (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);
+  kmeans_image=CloneImage(image,0,0,MagickTrue,exception);
+  if (kmeans_image == (Image *) NULL)
+    return(MagickFalse);
+  quantize_info=AcquireQuantizeInfo((ImageInfo *) NULL);
+  quantize_info->number_colors=number_colors;
+  quantize_info->dither_method=NoDitherMethod;
+  status=QuantizeImage(quantize_info,kmeans_image,exception);
+  quantize_info=DestroyQuantizeInfo(quantize_info);
+  if (status == MagickFalse)
+    return(status);
+  if (AcquireImageColormap(image,kmeans_image->colors,exception) == MagickFalse)
+    {
+      kmeans_image=DestroyImage(kmeans_image);
+      return(MagickFalse);
+    }
+  (void) memcpy(image->colormap,kmeans_image->colormap,kmeans_image->colors*
+    sizeof(*kmeans_image->colormap));
+  /*
+    Iterative refinement.
+  */
+  last_distortion=0.0;
+  for (n=0; n < max_iterations; n++)
+  {
+    CacheView
+      *image_view;
+
+    double
+      distortion;
+
+    PixelInfo
+      *kmeans_colormap;
+
+    register ssize_t
+      i;
+
+    ssize_t
+      y;
+
+    kmeans_colormap=kmeans_image->colormap;
+    for (i=0; i < (ssize_t) kmeans_image->colors; i++)
+    {
+      kmeans_colormap[i].red=0.0;
+      kmeans_colormap[i].green=0.0;
+      kmeans_colormap[i].blue=0.0;
+      kmeans_colormap[i].alpha=0.0;
+      kmeans_colormap[i].black=0.0;
+      kmeans_colormap[i].count=0.0;
+      kmeans_colormap[i].distortion=0.0;
+    }
+    image_view=AcquireAuthenticCacheView(image,exception);
+#if defined(MAGICKCORE_OPENMP_SUPPORT)
+    #pragma omp parallel for schedule(static) shared(status) \
+      magick_number_threads(image,image,image->rows,1)
+#endif
+    for (y=0; y < (ssize_t) image->rows; y++)
+    {
+      register Quantum
+        *magick_restrict q;
+
+      register ssize_t
+        x;
+
+      if (status == MagickFalse)
+        continue;
+      q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
+      if (q == (Quantum *) NULL)
+        {
+          status=MagickFalse;
+          continue;
+        }
+      for (x=0; x < (ssize_t) image->columns; x++)
+      {
+        double
+          min_distance;
+
+        register ssize_t
+          i;
+
+        ssize_t
+          j;
+
+        /*
+          Assign each pixel whose mean has the least squared Euclidean distance.
+        */
+        j=0;
+        min_distance=KmeansDistance(image,q,image->colormap+0);
+        for (i=1; i < (ssize_t) image->colors; i++)
+        {
+          double
+            distance;
+
+          if (min_distance <= MagickEpsilon)
+            break;
+          distance=KmeansDistance(image,q,image->colormap+i);
+          if (distance < min_distance)
+            {
+              min_distance=distance;
+              j=i;
+            }
+        }
+        kmeans_colormap[j].red+=QuantumScale*GetPixelRed(image,q);
+        kmeans_colormap[j].green+=QuantumScale*GetPixelGreen(image,q);
+        kmeans_colormap[j].blue+=QuantumScale*GetPixelBlue(image,q);
+        if (kmeans_image->alpha_trait != BlendPixelTrait)
+          kmeans_colormap[j].alpha+=QuantumScale*GetPixelAlpha(image,q);
+        if (kmeans_image->colorspace == CMYKColorspace)
+          kmeans_colormap[j].black+=QuantumScale*GetPixelBlack(image,q);
+        kmeans_colormap[j].count++;
+        kmeans_colormap[j].distortion+=min_distance;
+        SetPixelIndex(image,j,q);
+        q+=GetPixelChannels(image);
+      }
+    }
+    /*
+      Calculate the new means (centroids) of the pixels in the new clusters.
+    */
+    distortion=0.0;
+    for (i=0; i < (ssize_t) kmeans_image->colors; i++)
+    {
+      image->colormap[i].red=QuantumRange*kmeans_colormap[i].red/(double)
+        kmeans_colormap[i].count;
+      image->colormap[i].green=QuantumRange*kmeans_colormap[i].green/(double)
+        kmeans_colormap[i].count;
+      image->colormap[i].blue=QuantumRange*kmeans_colormap[i].blue/(double)
+        kmeans_colormap[i].count;
+      if (kmeans_image->alpha_trait != BlendPixelTrait)
+        image->colormap[i].alpha=QuantumRange*kmeans_colormap[i].alpha/(double)
+          kmeans_colormap[i].count;
+      if (kmeans_image->colorspace == CMYKColorspace)
+        image->colormap[i].black=QuantumRange*kmeans_colormap[i].black/(double)
+          kmeans_colormap[i].count;
+      distortion+=kmeans_colormap[i].distortion/kmeans_image->colors;
+    }
+    image_view=DestroyCacheView(image_view);
+    if (fabs(distortion-last_distortion) <= max_distortion)
+      break;
+    last_distortion=distortion;
+    if (image->progress_monitor != (MagickProgressMonitor) NULL)
+      {
+        MagickBooleanType
+          proceed;
+
+        proceed=SetImageProgress(image,KmeansImageTag,n,max_iterations);
+        if (proceed == MagickFalse)
+          status=MagickFalse;
+      }
+  }
+  if (image->progress_monitor != (MagickProgressMonitor) NULL)
+    (void) SetImageProgress(image,KmeansImageTag,max_iterations+1,
+      max_iterations);
+  if (status != MagickFalse)
+    SyncImage(image,exception);
+  return(status);
+}
+
+/*
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%                                                                             %
+%                                                                             %
+%                                                                             %
 %     P o s t e r i z e I m a g e                                             %
 %                                                                             %
 %                                                                             %