Commit bfd1fcf35 for imagemagick.org
commit bfd1fcf351277688ba82d88d3d70e7f0d6811a48
Author: Cristy <urban-warrior@imagemagick.org>
Date: Sun Apr 19 18:58:10 2026 -0400
support pixel difference count (PDC) compare metric
diff --git a/MagickCore/compare.c b/MagickCore/compare.c
index 2da0a8b6d..b29bf4f18 100644
--- a/MagickCore/compare.c
+++ b/MagickCore/compare.c
@@ -1523,6 +1523,141 @@ static MagickBooleanType GetPASimilarity(const Image *image,
return(status);
}
+static MagickBooleanType GetPDCSimilarity(const Image *image,
+ const Image *reconstruct_image,double *similarity,ExceptionInfo *exception)
+{
+ CacheView
+ *image_view,
+ *reconstruct_view;
+
+ double
+ area,
+ fuzz;
+
+ MagickBooleanType
+ status = MagickTrue;
+
+ size_t
+ columns,
+ rows;
+
+ ssize_t
+ k,
+ y;
+
+ /*
+ Compute the pixel difference count similarity.
+ */
+ fuzz=GetFuzzyColorDistance(image,reconstruct_image);
+ (void) memset(similarity,0,(MaxPixelChannels+1)*sizeof(*similarity));
+ SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
+ image_view=AcquireVirtualCacheView(image,exception);
+ reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
+#if defined(MMAGICKCORE_OPENMP_SUPPORT)
+ #pragma omp parallel for schedule(static) shared(similarity,status) \
+ magick_number_threads(image,image,rows,1)
+#endif
+ for (y=0; y < (ssize_t) rows; y++)
+ {
+ const Quantum
+ *magick_restrict p,
+ *magick_restrict q;
+
+ double
+ channel_similarity[MaxPixelChannels+1] = { 0.0 };
+
+ ssize_t
+ x;
+
+ if (status == MagickFalse)
+ continue;
+ p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
+ q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
+ if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
+ {
+ status=MagickFalse;
+ continue;
+ }
+ for (x=0; x < (ssize_t) columns; x++)
+ {
+ double
+ Da,
+ Sa;
+
+ size_t
+ count = 0;
+
+ ssize_t
+ i;
+
+ if ((GetPixelReadMask(image,p) <= (QuantumRange/2)) ||
+ (GetPixelReadMask(reconstruct_image,q) <= (QuantumRange/2)))
+ {
+ p+=(ptrdiff_t) GetPixelChannels(image);
+ q+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
+ continue;
+ }
+ Sa=QuantumScale*(double) GetPixelAlpha(image,p);
+ Da=QuantumScale*(double) GetPixelAlpha(reconstruct_image,q);
+ for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
+ {
+ double
+ error;
+
+ PixelChannel channel = GetPixelChannelChannel(image,i);
+ PixelTrait traits = GetPixelChannelTraits(image,channel);
+ PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
+ channel);
+ if (((traits & UpdatePixelTrait) == 0) ||
+ ((reconstruct_traits & UpdatePixelTrait) == 0))
+ continue;
+ if (channel == AlphaPixelChannel)
+ error=(double) p[i]-(double) GetPixelChannel(reconstruct_image,
+ channel,q);
+ else
+ error=Sa*p[i]-Da*GetPixelChannel(reconstruct_image,channel,q);
+ if ((error*error) > fuzz)
+ {
+ channel_similarity[i]++;
+ count++;
+ }
+ }
+ if (count != 0)
+ channel_similarity[CompositePixelChannel]++;
+ p+=(ptrdiff_t) GetPixelChannels(image);
+ q+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
+ }
+#if defined(MAGICKCORE_OPENMP_SUPPORT)
+ #pragma omp critical (MagickCore_GetPDCSimilarity)
+#endif
+ {
+ ssize_t
+ j;
+
+ for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
+ {
+ PixelChannel channel = GetPixelChannelChannel(image,j);
+ PixelTrait traits = GetPixelChannelTraits(image,channel);
+ PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
+ channel);
+ if (((traits & UpdatePixelTrait) == 0) ||
+ ((reconstruct_traits & UpdatePixelTrait) == 0))
+ continue;
+ similarity[j]+=channel_similarity[j];
+ }
+ similarity[CompositePixelChannel]+=
+ channel_similarity[CompositePixelChannel];
+ }
+ }
+ reconstruct_view=DestroyCacheView(reconstruct_view);
+ image_view=DestroyCacheView(image_view);
+ area=MagickSafeReciprocal((double) columns*rows);
+ for (k=0; k < (ssize_t) GetPixelChannels(image); k++)
+ similarity[k]*=area;
+ similarity[CompositePixelChannel]*=area;
+ return(status);
+}
+
static MagickBooleanType DFTPhaseSpectrum(const Image *image,const ssize_t u,
const ssize_t v,double *phase,ExceptionInfo *exception)
{
@@ -2270,6 +2405,12 @@ MagickExport MagickBooleanType GetImageDistortion(Image *image,
exception);
break;
}
+ case PixelDifferenceCountErrorMetric:
+ {
+ status=GetPDCSimilarity(image,reconstruct_image,channel_similarity,
+ exception);
+ break;
+ }
case RootMeanSquaredErrorMetric:
case UndefinedErrorMetric:
default:
@@ -2443,6 +2584,12 @@ MagickExport double *GetImageDistortions(Image *image,
exception);
break;
}
+ case PixelDifferenceCountErrorMetric:
+ {
+ status=GetPDCSimilarity(image,reconstruct_image,channel_similarity,
+ exception);
+ break;
+ }
case RootMeanSquaredErrorMetric:
case UndefinedErrorMetric:
default:
@@ -4480,6 +4627,12 @@ static double GetSimilarityMetric(const Image *image,
channel_similarity,exception);
break;
}
+ case PixelDifferenceCountErrorMetric:
+ {
+ status=GetPDCSimilarity(similarity_image,reconstruct_image,
+ channel_similarity,exception);
+ break;
+ }
case RootMeanSquaredErrorMetric:
case UndefinedErrorMetric:
default:
diff --git a/MagickCore/compare.h b/MagickCore/compare.h
index dd2a48c47..3991b8ea8 100644
--- a/MagickCore/compare.h
+++ b/MagickCore/compare.h
@@ -40,7 +40,8 @@ typedef enum
StructuralSimilarityErrorMetric,
StructuralDissimilarityErrorMetric,
PhaseCorrelationErrorMetric,
- DotProductCorrelationErrorMetric
+ DotProductCorrelationErrorMetric,
+ PixelDifferenceCountErrorMetric
} MetricType;
extern MagickExport double
diff --git a/MagickCore/option.c b/MagickCore/option.c
index 1f020e5a3..31be5efe4 100644
--- a/MagickCore/option.c
+++ b/MagickCore/option.c
@@ -1853,6 +1853,7 @@ static const OptionInfo
{ "MSE", MeanSquaredErrorMetric, UndefinedOptionFlag, MagickFalse },
{ "NCC", NormalizedCrossCorrelationErrorMetric, UndefinedOptionFlag, MagickFalse },
{ "PAE", PeakAbsoluteErrorMetric, UndefinedOptionFlag, MagickFalse },
+ { "PDC", PixelDifferenceCountErrorMetric, UndefinedOptionFlag, MagickFalse },
{ "PHASE", PhaseCorrelationErrorMetric, UndefinedOptionFlag, MagickFalse },
{ "PHASH", PerceptualHashErrorMetric, UndefinedOptionFlag, MagickFalse },
{ "PSNR", PeakSignalToNoiseRatioErrorMetric, UndefinedOptionFlag, MagickFalse },
diff --git a/MagickWand/compare.c b/MagickWand/compare.c
index 7fc191e97..3ceb2652d 100644
--- a/MagickWand/compare.c
+++ b/MagickWand/compare.c
@@ -1243,6 +1243,7 @@ WandExport MagickBooleanType CompareImagesCommand(ImageInfo *image_info,
switch (metric)
{
case AbsoluteErrorMetric:
+ case PixelDifferenceCountErrorMetric:
{
size_t
columns,
@@ -1323,6 +1324,7 @@ WandExport MagickBooleanType CompareImagesCommand(ImageInfo *image_info,
switch (metric)
{
case AbsoluteErrorMetric:
+ case PixelDifferenceCountErrorMetric:
{
(void) FormatLocaleFile(stderr,"%.*g (%.*g)",GetMagickPrecision(),
ceil(scale*distortion),GetMagickPrecision(),distortion);
@@ -1453,6 +1455,7 @@ WandExport MagickBooleanType CompareImagesCommand(ImageInfo *image_info,
case PeakSignalToNoiseRatioErrorMetric:
case PerceptualHashErrorMetric:
case PhaseCorrelationErrorMetric:
+ case PixelDifferenceCountErrorMetric:
case StructuralSimilarityErrorMetric:
case StructuralDissimilarityErrorMetric:
{