Thread -kmeans option
diff --git a/MagickCore/quantize.c b/MagickCore/quantize.c
index a25d22d..a491441 100644
--- a/MagickCore/quantize.c
+++ b/MagickCore/quantize.c
@@ -2337,6 +2337,58 @@
%
*/
+typedef struct _KmeansInfo
+{
+ double
+ count,
+ distortion,
+ red,
+ green,
+ blue,
+ alpha,
+ black;
+} KmeansInfo;
+
+static KmeansInfo **DestroyKmeansThreadSet(KmeansInfo **kmeans_info)
+{
+ register ssize_t
+ i;
+
+ assert(kmeans_info != (KmeansInfo **) NULL);
+ for (i=0; i < (ssize_t) GetMagickResourceLimit(ThreadResource); i++)
+ if (kmeans_info[i] != (KmeansInfo *) NULL)
+ kmeans_info[i]=(KmeansInfo *) RelinquishMagickMemory(kmeans_info[i]);
+ kmeans_info=(KmeansInfo **) RelinquishMagickMemory(kmeans_info);
+ return(kmeans_info);
+}
+
+static KmeansInfo **AcquireKmeansThreadSet(const size_t number_colors)
+{
+ KmeansInfo
+ **kmeans_info;
+
+ register ssize_t
+ i;
+
+ size_t
+ number_threads;
+
+ number_threads=(size_t) GetMagickResourceLimit(ThreadResource);
+ kmeans_info=(KmeansInfo **) AcquireQuantumMemory(number_threads,
+ sizeof(*kmeans_info));
+ if (kmeans_info == (KmeansInfo **) NULL)
+ return((KmeansInfo **) NULL);
+ (void) memset(kmeans_info,0,number_threads*sizeof(*kmeans_info));
+ for (i=0; i < (ssize_t) number_threads; i++)
+ {
+ kmeans_info[i]=(KmeansInfo *) AcquireQuantumMemory(number_colors,
+ sizeof(**kmeans_info));
+ if (kmeans_info[i] == (KmeansInfo *) NULL)
+ return(DestroyKmeansThreadSet(kmeans_info));
+ }
+ return(kmeans_info);
+}
+
static inline double KmeansDistance(const Image *magick_restrict image,
const Quantum *magick_restrict p,const PixelInfo *magick_restrict q)
{
@@ -2395,19 +2447,22 @@
double
previous_distortion;
+ KmeansInfo
+ **kmeans_pixels;
+
MagickBooleanType
verbose,
status;
- PixelInfo
- *kmeans_colormap;
-
QuantizeInfo
*quantize_info;
register ssize_t
n;
+ size_t
+ number_threads;
+
/*
Initialize the initial set of K means.
*/
@@ -2419,13 +2474,10 @@
assert(exception->signature == MagickCoreSignature);
if (AcquireImageColormap(image,number_colors,exception) == MagickFalse)
return(MagickFalse);
- kmeans_colormap=(PixelInfo *) AcquireQuantumMemory(image->colors,
- sizeof(*kmeans_colormap));
- if (kmeans_colormap == (PixelInfo *) NULL)
+ kmeans_pixels=AcquireKmeansThreadSet(number_colors);
+ if (kmeans_pixels == (KmeansInfo **) NULL)
ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
image->filename);
- (void) memcpy(kmeans_colormap,image->colormap,image->colors*
- sizeof(*kmeans_colormap));
colors=GetImageArtifact(image,"kmeans:seed-colors");
if (colors == (const char *) NULL)
{
@@ -2434,7 +2486,10 @@
kmeans_image=CloneImage(image,0,0,MagickTrue,exception);
if (kmeans_image == (Image *) NULL)
- return(MagickFalse);
+ {
+ kmeans_pixels=DestroyKmeansThreadSet(kmeans_pixels);
+ return(MagickFalse);
+ }
quantize_info=AcquireQuantizeInfo((ImageInfo *) NULL);
quantize_info->number_colors=number_colors;
quantize_info->dither_method=NoDitherMethod;
@@ -2442,11 +2497,12 @@
quantize_info=DestroyQuantizeInfo(quantize_info);
if (status == MagickFalse)
{
+ kmeans_pixels=DestroyKmeansThreadSet(kmeans_pixels);
kmeans_image=DestroyImage(kmeans_image);
return(status);
}
- (void) memcpy(kmeans_colormap,kmeans_image->colormap,kmeans_image->colors*
- sizeof(*kmeans_colormap));
+ (void) memcpy(image->colormap,kmeans_image->colormap,kmeans_image->colors*
+ sizeof(*kmeans_image->colormap));
image->colors=kmeans_image->colors;
kmeans_image=DestroyImage(kmeans_image);
}
@@ -2468,7 +2524,7 @@
break;
(void) CopyMagickString(color,p,(size_t) MagickMin(q-p+1,
MagickPathExtent));
- status=QueryColorCompliance(color,AllCompliance,kmeans_colormap+n,
+ status=QueryColorCompliance(color,AllCompliance,image->colormap+n,
exception);
if (*q == '\0')
{
@@ -2488,23 +2544,24 @@
random_info=AcquireRandomInfo();
for ( ; n < (ssize_t) image->colors; n++)
{
- kmeans_colormap[n].red=RandomColorComponent(random_info);
- kmeans_colormap[n].green=RandomColorComponent(random_info);
- kmeans_colormap[n].blue=RandomColorComponent(random_info);
- kmeans_colormap[n].alpha=RandomColorComponent(random_info);
- kmeans_colormap[n].black=RandomColorComponent(random_info);
+ status=QueryColorCompliance("#000",AllCompliance,image->colormap+n,
+ exception);
+ image->colormap[n].red=RandomColorComponent(random_info);
+ image->colormap[n].green=RandomColorComponent(random_info);
+ image->colormap[n].blue=RandomColorComponent(random_info);
+ image->colormap[n].alpha=RandomColorComponent(random_info);
+ image->colormap[n].black=RandomColorComponent(random_info);
}
random_info=DestroyRandomInfo(random_info);
}
}
- (void) memcpy(image->colormap,kmeans_colormap,image->colors*
- sizeof(*kmeans_colormap));
/*
Iterative refinement.
*/
status=MagickTrue;
previous_distortion=0.0;
verbose=IsStringTrue(GetImageArtifact(image,"debug"));
+ number_threads=(size_t) GetMagickResourceLimit(ThreadResource);
image_view=AcquireAuthenticCacheView(image,exception);
for (n=0; n < (ssize_t) max_iterations; n++)
{
@@ -2517,21 +2574,30 @@
ssize_t
y;
- distortion=0.0;
- (void) memset(kmeans_colormap,0,image->colors*sizeof(*kmeans_colormap));
+ for (i=0; i < (ssize_t) number_threads; i++)
+ (void) memset(kmeans_pixels[i],0,image->colors*sizeof(*kmeans_pixels[i]));
+#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++)
{
+ const int
+ id = GetOpenMPThreadId();
+
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;
- break;
+ continue;
}
for (x=0; x < (ssize_t) image->columns; x++)
{
@@ -2545,7 +2611,7 @@
j;
/*
- Assign each pixel whose mean has the least squared Euclidean distance.
+ Assign each pixel whose mean has the least squared color distance.
*/
j=0;
min_distance=KmeansDistance(image,q,image->colormap+0);
@@ -2563,42 +2629,62 @@
j=i;
}
}
- distortion+=min_distance;
- kmeans_colormap[j].red+=QuantumScale*GetPixelRed(image,q);
- kmeans_colormap[j].green+=QuantumScale*GetPixelGreen(image,q);
- kmeans_colormap[j].blue+=QuantumScale*GetPixelBlue(image,q);
+ kmeans_pixels[id][j].red+=QuantumScale*GetPixelRed(image,q);
+ kmeans_pixels[id][j].green+=QuantumScale*GetPixelGreen(image,q);
+ kmeans_pixels[id][j].blue+=QuantumScale*GetPixelBlue(image,q);
if (image->alpha_trait != BlendPixelTrait)
- kmeans_colormap[j].alpha+=QuantumScale*GetPixelAlpha(image,q);
+ kmeans_pixels[id][j].alpha+=QuantumScale*GetPixelAlpha(image,q);
if (image->colorspace == CMYKColorspace)
- kmeans_colormap[j].black+=QuantumScale*GetPixelBlack(image,q);
- kmeans_colormap[j].count++;
+ kmeans_pixels[id][j].black+=QuantumScale*GetPixelBlack(image,q);
+ kmeans_pixels[id][j].count++;
+ kmeans_pixels[id][j].distortion+=min_distance;
SetPixelIndex(image,(Quantum) j,q);
q+=GetPixelChannels(image);
}
if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
- {
- status=MagickFalse;
- break;
- }
+ status=MagickFalse;
}
if (status == MagickFalse)
break;
/*
+ Reduction operation: results land in [0] thread.
+ */
+ for (i=1; i < (ssize_t) number_threads; i++)
+ {
+ register ssize_t
+ j;
+
+ for (j=0; j < (ssize_t) image->colors; j++)
+ {
+ kmeans_pixels[0][j].red+=kmeans_pixels[i][j].red;
+ kmeans_pixels[0][j].green+=kmeans_pixels[i][j].green;
+ kmeans_pixels[0][j].blue+=kmeans_pixels[i][j].blue;
+ if (image->alpha_trait != BlendPixelTrait)
+ kmeans_pixels[0][j].alpha+=kmeans_pixels[i][j].alpha;
+ if (image->colorspace == CMYKColorspace)
+ kmeans_pixels[0][j].black+=kmeans_pixels[i][j].black;
+ kmeans_pixels[0][j].count+=kmeans_pixels[i][j].count;
+ kmeans_pixels[0][j].distortion+=kmeans_pixels[i][j].distortion;
+ }
+ }
+ /*
Calculate the new means (centroids) of the pixels in the new clusters.
*/
+ distortion=0.0;
for (i=0; i < (ssize_t) image->colors; i++)
{
double
- count;
+ gamma;
- count=PerceptibleReciprocal((double) kmeans_colormap[i].count);
- image->colormap[i].red=QuantumRange*count*kmeans_colormap[i].red;
- image->colormap[i].green=QuantumRange*count*kmeans_colormap[i].green;
- image->colormap[i].blue=QuantumRange*count*kmeans_colormap[i].blue;
+ gamma=PerceptibleReciprocal((double) kmeans_pixels[0][i].count);
+ image->colormap[i].red=gamma*QuantumRange*kmeans_pixels[0][i].red;
+ image->colormap[i].green=gamma*QuantumRange*kmeans_pixels[0][i].green;
+ image->colormap[i].blue=gamma*QuantumRange*kmeans_pixels[0][i].blue;
if (image->alpha_trait != BlendPixelTrait)
- image->colormap[i].alpha=QuantumRange*count*kmeans_colormap[i].alpha;
+ image->colormap[i].alpha=gamma*QuantumRange*kmeans_pixels[0][i].alpha;
if (image->colorspace == CMYKColorspace)
- image->colormap[i].black=QuantumRange*count*kmeans_colormap[i].black;
+ image->colormap[i].black=gamma*QuantumRange*kmeans_pixels[0][i].black;
+ distortion+=kmeans_pixels[0][i].distortion;
}
if (verbose != MagickFalse)
(void) (void) FormatLocaleFile(stderr,"distortion[%ld]: %g %g\n",n,
@@ -2618,7 +2704,7 @@
}
}
image_view=DestroyCacheView(image_view);
- kmeans_colormap=(PixelInfo *) RelinquishMagickMemory(kmeans_colormap);
+ kmeans_pixels=DestroyKmeansThreadSet(kmeans_pixels);
if (image->progress_monitor != (MagickProgressMonitor) NULL)
(void) SetImageProgress(image,KmeansImageTag,(MagickOffsetType)
max_iterations-1,max_iterations);