% December 2003 %
% %
% %
-% Copyright 1999-2015 ImageMagick Studio LLC, a non-profit organization %
+% Copyright 1999-2017 ImageMagick Studio LLC, a non-profit organization %
% dedicated to making software imaging solutions freely available. %
% %
% You may not use this file except in compliance with the License. You may %
if ((traits & UpdatePixelTrait) != 0)
channels++;
}
- return(channels == 0 ? 1 : channels);
-}
-
-static inline MagickBooleanType ValidateImageMorphology(
- const Image *restrict image,const Image *restrict reconstruct_image)
-{
- /*
- Does the image match the reconstructed image morphology?
- */
- return(MagickTrue);
+ return(channels == 0 ? (size_t) 1 : channels);
}
MagickExport Image *CompareImages(Image *image,const Image *reconstruct_image,
*artifact;
Image
+ *clone_image,
*difference_image,
*highlight_image;
PixelInfo
highlight,
- lowlight;
+ lowlight,
+ masklight;
+
+ RectangleInfo
+ geometry;
size_t
columns,
y;
assert(image != (Image *) NULL);
- assert(image->signature == MagickSignature);
+ assert(image->signature == MagickCoreSignature);
if (image->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
assert(reconstruct_image != (const Image *) NULL);
- assert(reconstruct_image->signature == MagickSignature);
+ assert(reconstruct_image->signature == MagickCoreSignature);
assert(distortion != (double *) NULL);
*distortion=0.0;
if (image->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
- if (metric != PerceptualHashErrorMetric)
- if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
- ThrowImageException(ImageError,"ImageMorphologyDiffers");
status=GetImageDistortion(image,reconstruct_image,metric,distortion,
exception);
if (status == MagickFalse)
return((Image *) NULL);
- rows=MagickMax(image->rows,reconstruct_image->rows);
columns=MagickMax(image->columns,reconstruct_image->columns);
- difference_image=CloneImage(image,columns,rows,MagickTrue,exception);
+ rows=MagickMax(image->rows,reconstruct_image->rows);
+ SetGeometry(image,&geometry);
+ geometry.width=columns;
+ geometry.height=rows;
+ clone_image=CloneImage(image,0,0,MagickTrue,exception);
+ if (clone_image == (Image *) NULL)
+ return((Image *) NULL);
+ (void) SetImageMask(clone_image,ReadPixelMask,(Image *) NULL,exception);
+ difference_image=ExtentImage(clone_image,&geometry,exception);
+ clone_image=DestroyImage(clone_image);
if (difference_image == (Image *) NULL)
return((Image *) NULL);
(void) SetImageAlphaChannel(difference_image,OpaqueAlphaChannel,exception);
difference_image=DestroyImage(difference_image);
return((Image *) NULL);
}
+ (void) SetImageMask(highlight_image,ReadPixelMask,(Image *) NULL,exception);
status=SetImageStorageClass(highlight_image,DirectClass,exception);
if (status == MagickFalse)
{
artifact=GetImageArtifact(image,"lowlight-color");
if (artifact != (const char *) NULL)
(void) QueryColorCompliance(artifact,AllCompliance,&lowlight,exception);
+ (void) QueryColorCompliance("#888888cc",AllCompliance,&masklight,exception);
+ artifact=GetImageArtifact(image,"masklight-color");
+ if (artifact != (const char *) NULL)
+ (void) QueryColorCompliance(artifact,AllCompliance,&masklight,exception);
/*
Generate difference image.
*/
sync;
register const Quantum
- *restrict p,
- *restrict q;
+ *magick_restrict p,
+ *magick_restrict q;
register Quantum
- *restrict r;
+ *magick_restrict r;
register ssize_t
x;
register ssize_t
i;
- if (GetPixelReadMask(image,p) == 0)
+ if ((GetPixelReadMask(image,p) == 0) ||
+ (GetPixelReadMask(reconstruct_image,q) == 0))
{
- SetPixelViaPixelInfo(highlight_image,&lowlight,r);
+ SetPixelViaPixelInfo(highlight_image,&masklight,r);
p+=GetPixelChannels(image);
q+=GetPixelChannels(reconstruct_image);
r+=GetPixelChannels(highlight_image);
image_view=DestroyCacheView(image_view);
(void) CompositeImage(difference_image,highlight_image,image->compose,
MagickTrue,0,0,exception);
+ (void) SetImageAlphaChannel(difference_image,OffAlphaChannel,exception);
highlight_image=DestroyImage(highlight_image);
if (status == MagickFalse)
difference_image=DestroyImage(difference_image);
channel_distortion[MaxPixelChannels+1];
register const Quantum
- *restrict p,
- *restrict q;
+ *magick_restrict p,
+ *magick_restrict q;
register ssize_t
- i,
+ j,
x;
if (status == MagickFalse)
register ssize_t
i;
- if (GetPixelReadMask(image,p) == 0)
+ if (GetPixelWriteMask(image,p) == 0)
{
p+=GetPixelChannels(image);
q+=GetPixelChannels(reconstruct_image);
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp critical (MagickCore_GetAbsoluteError)
#endif
- for (i=0; i <= MaxPixelChannels; i++)
- distortion[i]+=channel_distortion[i];
+ for (j=0; j <= MaxPixelChannels; j++)
+ distortion[j]+=channel_distortion[j];
}
reconstruct_view=DestroyCacheView(reconstruct_view);
image_view=DestroyCacheView(image_view);
*image_view,
*reconstruct_view;
+ double
+ area;
+
MagickBooleanType
status;
register ssize_t
- i;
+ j;
size_t
columns,
status=MagickTrue;
rows=MagickMax(image->rows,reconstruct_image->rows);
columns=MagickMax(image->columns,reconstruct_image->columns);
+ area=0.0;
image_view=AcquireVirtualCacheView(image,exception);
reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
#if defined(MAGICKCORE_OPENMP_SUPPORT)
channel_distortion[MaxPixelChannels+1];
register const Quantum
- *restrict p,
- *restrict q;
+ *magick_restrict p,
+ *magick_restrict q;
register ssize_t
- i,
x;
if (status == MagickFalse)
register ssize_t
i;
- if (GetPixelReadMask(image,p) == 0)
+ if ((GetPixelReadMask(image,p) == 0) ||
+ (GetPixelReadMask(reconstruct_image,q) == 0))
{
p+=GetPixelChannels(image);
q+=GetPixelChannels(reconstruct_image);
channel_distortion[i]+=distance*distance;
channel_distortion[CompositePixelChannel]+=distance*distance;
}
+ area++;
p+=GetPixelChannels(image);
q+=GetPixelChannels(reconstruct_image);
}
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp critical (MagickCore_GetFuzzDistortion)
#endif
- for (i=0; i <= MaxPixelChannels; i++)
- distortion[i]+=channel_distortion[i];
+ for (j=0; j <= MaxPixelChannels; j++)
+ distortion[j]+=channel_distortion[j];
}
reconstruct_view=DestroyCacheView(reconstruct_view);
image_view=DestroyCacheView(image_view);
- for (i=0; i <= MaxPixelChannels; i++)
- distortion[i]/=((double) columns*rows);
+ area=PerceptibleReciprocal(area);
+ for (j=0; j <= MaxPixelChannels; j++)
+ distortion[j]*=area;
distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]);
return(status);
*image_view,
*reconstruct_view;
+ double
+ area;
+
MagickBooleanType
status;
register ssize_t
- i;
+ j;
size_t
columns,
status=MagickTrue;
rows=MagickMax(image->rows,reconstruct_image->rows);
columns=MagickMax(image->columns,reconstruct_image->columns);
+ area=0.0;
image_view=AcquireVirtualCacheView(image,exception);
reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
#if defined(MAGICKCORE_OPENMP_SUPPORT)
channel_distortion[MaxPixelChannels+1];
register const Quantum
- *restrict p,
- *restrict q;
+ *magick_restrict p,
+ *magick_restrict q;
register ssize_t
- i,
x;
if (status == MagickFalse)
register ssize_t
i;
- if (GetPixelReadMask(image,p) == 0)
+ if ((GetPixelReadMask(image,p) == 0) ||
+ (GetPixelReadMask(reconstruct_image,q) == 0))
{
p+=GetPixelChannels(image);
q+=GetPixelChannels(reconstruct_image);
channel_distortion[i]+=distance;
channel_distortion[CompositePixelChannel]+=distance;
}
+ area++;
p+=GetPixelChannels(image);
q+=GetPixelChannels(reconstruct_image);
}
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp critical (MagickCore_GetMeanAbsoluteError)
#endif
- for (i=0; i <= MaxPixelChannels; i++)
- distortion[i]+=channel_distortion[i];
+ for (j=0; j <= MaxPixelChannels; j++)
+ distortion[j]+=channel_distortion[j];
}
reconstruct_view=DestroyCacheView(reconstruct_view);
image_view=DestroyCacheView(image_view);
- for (i=0; i <= MaxPixelChannels; i++)
- distortion[i]/=((double) columns*rows);
+ area=PerceptibleReciprocal(area);
+ for (j=0; j <= MaxPixelChannels; j++)
+ distortion[j]*=area;
distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
return(status);
}
for (y=0; y < (ssize_t) rows; y++)
{
register const Quantum
- *restrict p,
- *restrict q;
+ *magick_restrict p,
+ *magick_restrict q;
register ssize_t
x;
register ssize_t
i;
- if (GetPixelReadMask(image,p) == 0)
+ if ((GetPixelReadMask(image,p) == 0) ||
+ (GetPixelReadMask(reconstruct_image,q) == 0))
{
p+=GetPixelChannels(image);
q+=GetPixelChannels(reconstruct_image);
*image_view,
*reconstruct_view;
+ double
+ area;
+
MagickBooleanType
status;
register ssize_t
- i;
+ j;
size_t
columns,
status=MagickTrue;
rows=MagickMax(image->rows,reconstruct_image->rows);
columns=MagickMax(image->columns,reconstruct_image->columns);
+ area=0.0;
image_view=AcquireVirtualCacheView(image,exception);
reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
#if defined(MAGICKCORE_OPENMP_SUPPORT)
channel_distortion[MaxPixelChannels+1];
register const Quantum
- *restrict p,
- *restrict q;
+ *magick_restrict p,
+ *magick_restrict q;
register ssize_t
- i,
x;
if (status == MagickFalse)
register ssize_t
i;
- if (GetPixelReadMask(image,p) == 0)
+ if ((GetPixelReadMask(image,p) == 0) ||
+ (GetPixelReadMask(reconstruct_image,q) == 0))
{
p+=GetPixelChannels(image);
q+=GetPixelChannels(reconstruct_image);
channel_distortion[i]+=distance*distance;
channel_distortion[CompositePixelChannel]+=distance*distance;
}
+ area++;
p+=GetPixelChannels(image);
q+=GetPixelChannels(reconstruct_image);
}
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp critical (MagickCore_GetMeanSquaredError)
#endif
- for (i=0; i <= MaxPixelChannels; i++)
- distortion[i]+=channel_distortion[i];
+ for (j=0; j <= MaxPixelChannels; j++)
+ distortion[j]+=channel_distortion[j];
}
reconstruct_view=DestroyCacheView(reconstruct_view);
image_view=DestroyCacheView(image_view);
- for (i=0; i <= MaxPixelChannels; i++)
- distortion[i]/=((double) columns*rows);
+ area=PerceptibleReciprocal(area);
+ for (j=0; j <= MaxPixelChannels; j++)
+ distortion[j]*=area;
distortion[CompositePixelChannel]/=GetImageChannels(image);
return(status);
}
distortion[i]=0.0;
rows=MagickMax(image->rows,reconstruct_image->rows);
columns=MagickMax(image->columns,reconstruct_image->columns);
- area=1.0/((double) columns*rows);
+ area=0.0;
image_view=AcquireVirtualCacheView(image,exception);
reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
for (y=0; y < (ssize_t) rows; y++)
{
register const Quantum
- *restrict p,
- *restrict q;
+ *magick_restrict p,
+ *magick_restrict q;
register 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;
+ break;
+ }
+ for (x=0; x < (ssize_t) columns; x++)
+ {
+ if ((GetPixelReadMask(image,p) == 0) ||
+ (GetPixelReadMask(reconstruct_image,q) == 0))
+ {
+ p+=GetPixelChannels(image);
+ q+=GetPixelChannels(reconstruct_image);
+ continue;
+ }
+ area++;
+ p+=GetPixelChannels(image);
+ q+=GetPixelChannels(reconstruct_image);
+ }
+ }
+ area=PerceptibleReciprocal(area);
+ for (y=0; y < (ssize_t) rows; y++)
+ {
+ register const Quantum
+ *magick_restrict p,
+ *magick_restrict q;
+
+ register ssize_t
+ x;
+
+ 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;
+ break;
}
for (x=0; x < (ssize_t) columns; x++)
{
Da,
Sa;
- register ssize_t
- i;
-
- if (GetPixelReadMask(image,p) == 0)
+ if ((GetPixelReadMask(image,p) == 0) ||
+ (GetPixelReadMask(reconstruct_image,q) == 0))
{
p+=GetPixelChannels(image);
q+=GetPixelChannels(reconstruct_image);
continue;
}
- Sa=QuantumScale*GetPixelAlpha(image,p);
- Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
+ Sa=QuantumScale*(image->alpha_trait != UndefinedPixelTrait ?
+ GetPixelAlpha(image,p) : OpaqueAlpha);
+ Da=QuantumScale*(reconstruct_image->alpha_trait != UndefinedPixelTrait ?
+ GetPixelAlpha(reconstruct_image,q) : OpaqueAlpha);
for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
{
PixelChannel channel=GetPixelChannelChannel(image,i);
(reconstruct_traits == UndefinedPixelTrait) ||
((reconstruct_traits & UpdatePixelTrait) == 0))
continue;
- distortion[i]+=area*QuantumScale*(Sa*p[i]-image_statistics[i].mean)*
- (Da*GetPixelChannel(reconstruct_image,channel,q)-
- reconstruct_statistics[channel].mean);
+ if (channel == AlphaPixelChannel)
+ {
+ distortion[i]+=area*QuantumScale*(p[i]-
+ image_statistics[channel].mean)*(GetPixelChannel(
+ reconstruct_image,channel,q)-
+ reconstruct_statistics[channel].mean);
+ }
+ else
+ {
+ distortion[i]+=area*QuantumScale*(Sa*p[i]-
+ image_statistics[channel].mean)*(Da*GetPixelChannel(
+ reconstruct_image,channel,q)-
+ reconstruct_statistics[channel].mean);
+ }
}
p+=GetPixelChannels(image);
q+=GetPixelChannels(reconstruct_image);
proceed=SetImageProgress(image,SimilarityImageTag,progress++,rows);
if (proceed == MagickFalse)
- status=MagickFalse;
+ {
+ status=MagickFalse;
+ break;
+ }
}
}
reconstruct_view=DestroyCacheView(reconstruct_view);
Divide by the standard deviation.
*/
distortion[CompositePixelChannel]=0.0;
- for (i=0; i < MaxPixelChannels; i++)
+ for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
{
double
gamma;
PixelChannel channel=GetPixelChannelChannel(image,i);
- gamma=image_statistics[i].standard_deviation*
+ gamma=image_statistics[channel].standard_deviation*
reconstruct_statistics[channel].standard_deviation;
gamma=PerceptibleReciprocal(gamma);
distortion[i]=QuantumRange*gamma*distortion[i];
channel_distortion[MaxPixelChannels+1];
register const Quantum
- *restrict p,
- *restrict q;
+ *magick_restrict p,
+ *magick_restrict q;
register ssize_t
- i,
+ j,
x;
if (status == MagickFalse)
register ssize_t
i;
- if (GetPixelReadMask(image,p) == 0)
+ if ((GetPixelReadMask(image,p) == 0) ||
+ (GetPixelReadMask(reconstruct_image,q) == 0))
{
p+=GetPixelChannels(image);
q+=GetPixelChannels(reconstruct_image);
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp critical (MagickCore_GetPeakAbsoluteError)
#endif
- for (i=0; i <= MaxPixelChannels; i++)
- if (channel_distortion[i] > distortion[i])
- distortion[i]=channel_distortion[i];
+ for (j=0; j <= MaxPixelChannels; j++)
+ if (channel_distortion[j] > distortion[j])
+ distortion[j]=channel_distortion[j];
}
reconstruct_view=DestroyCacheView(reconstruct_view);
image_view=DestroyCacheView(image_view);
status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
for (i=0; i <= MaxPixelChannels; i++)
- distortion[i]=20.0*MagickLog10((double) 1.0/sqrt(distortion[i]));
+ if (fabs(distortion[i]) >= MagickEpsilon)
+ distortion[i]=20.0*MagickLog10((double) 1.0/sqrt(distortion[i]));
return(status);
}
const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
{
ChannelPerceptualHash
- *image_phash,
+ *channel_phash,
*reconstruct_phash;
+ const char
+ *artifact;
+
+ MagickBooleanType
+ normalize;
+
ssize_t
channel;
/*
Compute perceptual hash in the sRGB colorspace.
*/
- image_phash=GetImagePerceptualHash(image,exception);
- if (image_phash == (ChannelPerceptualHash *) NULL)
+ channel_phash=GetImagePerceptualHash(image,exception);
+ if (channel_phash == (ChannelPerceptualHash *) NULL)
return(MagickFalse);
reconstruct_phash=GetImagePerceptualHash(reconstruct_image,exception);
if (reconstruct_phash == (ChannelPerceptualHash *) NULL)
{
- image_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(image_phash);
+ channel_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(
+ channel_phash);
return(MagickFalse);
}
+ artifact=GetImageArtifact(image,"phash:normalize");
+ normalize=(artifact == (const char *) NULL) ||
+ (IsStringTrue(artifact) == MagickFalse) ? MagickFalse : MagickTrue;
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp parallel for schedule(static,4)
#endif
alpha,
beta;
- alpha=image_phash[channel].srgb_hu_phash[i];
- beta=reconstruct_phash[channel].srgb_hu_phash[i];
- difference+=(beta-alpha)*(beta-alpha);
- }
- distortion[channel]+=difference;
-#if defined(MAGICKCORE_OPENMP_SUPPORT)
- #pragma omp critical (MagickCore_GetPerceptualHashDistortion)
-#endif
- distortion[CompositePixelChannel]+=difference;
- }
- /*
- Compute perceptual hash in the HCLP colorspace.
- */
-#if defined(MAGICKCORE_OPENMP_SUPPORT)
- #pragma omp parallel for schedule(static,4)
-#endif
- for (channel=0; channel < MaxPixelChannels; channel++)
- {
- double
- difference;
-
- register ssize_t
- i;
-
- difference=0.0;
- for (i=0; i < MaximumNumberOfImageMoments; i++)
- {
- double
- alpha,
- beta;
+ register ssize_t
+ j;
- alpha=image_phash[channel].hclp_hu_phash[i];
- beta=reconstruct_phash[channel].hclp_hu_phash[i];
- difference+=(beta-alpha)*(beta-alpha);
+ for (j=0; j < (ssize_t) channel_phash[0].number_colorspaces; j++)
+ {
+ alpha=channel_phash[channel].phash[j][i];
+ beta=reconstruct_phash[channel].phash[j][i];
+ if (normalize == MagickFalse)
+ difference+=(beta-alpha)*(beta-alpha);
+ else
+ difference=sqrt((beta-alpha)*(beta-alpha)/
+ channel_phash[0].number_channels);
+ }
}
distortion[channel]+=difference;
#if defined(MAGICKCORE_OPENMP_SUPPORT)
*/
reconstruct_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(
reconstruct_phash);
- image_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(image_phash);
+ channel_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(channel_phash);
return(MagickTrue);
}
length;
assert(image != (Image *) NULL);
- assert(image->signature == MagickSignature);
+ assert(image->signature == MagickCoreSignature);
if (image->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
assert(reconstruct_image != (const Image *) NULL);
- assert(reconstruct_image->signature == MagickSignature);
+ assert(reconstruct_image->signature == MagickCoreSignature);
assert(distortion != (double *) NULL);
*distortion=0.0;
if (image->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
- if (metric != PerceptualHashErrorMetric)
- if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
- ThrowBinaryException(ImageError,"ImageMorphologyDiffers",image->filename);
/*
Get image distortion.
*/
length;
assert(image != (Image *) NULL);
- assert(image->signature == MagickSignature);
+ assert(image->signature == MagickCoreSignature);
if (image->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
assert(reconstruct_image != (const Image *) NULL);
- assert(reconstruct_image->signature == MagickSignature);
+ assert(reconstruct_image->signature == MagickCoreSignature);
if (image->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
- if (metric != PerceptualHashErrorMetric)
- if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
- {
- (void) ThrowMagickException(exception,GetMagickModule(),ImageError,
- "ImageMorphologyDiffers","`%s'",image->filename);
- return((double *) NULL);
- }
/*
Get image distortion.
*/
% %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
-% IsImagesEqual() measures the difference between colors at each pixel
+% IsImagesEqual() compare the pixels of two images and returns immediately
+% if any pixel is not identical.
+%
+% The format of the IsImagesEqual method is:
+%
+% MagickBooleanType IsImagesEqual(const Image *image,
+% const Image *reconstruct_image,ExceptionInfo *exception)
+%
+% A description of each parameter follows.
+%
+% o image: the image.
+%
+% o reconstruct_image: the reconstruct image.
+%
+% o exception: return any errors or warnings in this structure.
+%
+*/
+MagickExport MagickBooleanType IsImagesEqual(const Image *image,
+ const Image *reconstruct_image,ExceptionInfo *exception)
+{
+ CacheView
+ *image_view,
+ *reconstruct_view;
+
+ size_t
+ columns,
+ rows;
+
+ ssize_t
+ y;
+
+ assert(image != (Image *) NULL);
+ assert(image->signature == MagickCoreSignature);
+ assert(reconstruct_image != (const Image *) NULL);
+ assert(reconstruct_image->signature == MagickCoreSignature);
+ rows=MagickMax(image->rows,reconstruct_image->rows);
+ columns=MagickMax(image->columns,reconstruct_image->columns);
+ image_view=AcquireVirtualCacheView(image,exception);
+ reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
+ for (y=0; y < (ssize_t) rows; y++)
+ {
+ register const Quantum
+ *magick_restrict p,
+ *magick_restrict q;
+
+ register ssize_t
+ x;
+
+ p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
+ q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
+ if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
+ break;
+ for (x=0; x < (ssize_t) columns; x++)
+ {
+ register ssize_t
+ i;
+
+ if (GetPixelWriteMask(image,p) == 0)
+ {
+ p+=GetPixelChannels(image);
+ q+=GetPixelChannels(reconstruct_image);
+ continue;
+ }
+ for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
+ {
+ double
+ distance;
+
+ PixelChannel channel=GetPixelChannelChannel(image,i);
+ PixelTrait traits=GetPixelChannelTraits(image,channel);
+ PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
+ channel);
+ if ((traits == UndefinedPixelTrait) ||
+ (reconstruct_traits == UndefinedPixelTrait) ||
+ ((reconstruct_traits & UpdatePixelTrait) == 0))
+ continue;
+ distance=fabs(p[i]-(double) GetPixelChannel(reconstruct_image,
+ channel,q));
+ if (distance >= MagickEpsilon)
+ break;
+ }
+ if (i < (ssize_t) GetPixelChannels(image))
+ break;
+ p+=GetPixelChannels(image);
+ q+=GetPixelChannels(reconstruct_image);
+ }
+ if (x < (ssize_t) columns)
+ break;
+ }
+ reconstruct_view=DestroyCacheView(reconstruct_view);
+ image_view=DestroyCacheView(image_view);
+ return(y < (ssize_t) rows ? MagickFalse : MagickTrue);
+}
+\f
+/*
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% %
+% %
+% %
+% S e t I m a g e C o l o r M e t r i c %
+% %
+% %
+% %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% SetImageColorMetric() measures the difference between colors at each pixel
% location of two images. A value other than 0 means the colors match
% exactly. Otherwise an error measure is computed by summing over all
% pixels in an image the distance squared in RGB space between each image
% image->normalized_mean_error, suggests the images are very similar in
% spatial layout and color.
%
-% The format of the IsImagesEqual method is:
+% The format of the SetImageColorMetric method is:
%
-% MagickBooleanType IsImagesEqual(Image *image,
+% MagickBooleanType SetImageColorMetric(Image *image,
% const Image *reconstruct_image,ExceptionInfo *exception)
%
% A description of each parameter follows.
% o exception: return any errors or warnings in this structure.
%
*/
-MagickExport MagickBooleanType IsImagesEqual(Image *image,
+MagickExport MagickBooleanType SetImageColorMetric(Image *image,
const Image *reconstruct_image,ExceptionInfo *exception)
{
CacheView
*image_view,
*reconstruct_view;
- MagickBooleanType
- status;
-
double
area,
maximum_error,
mean_error,
mean_error_per_pixel;
+ MagickBooleanType
+ status;
+
size_t
columns,
rows;
y;
assert(image != (Image *) NULL);
- assert(image->signature == MagickSignature);
+ assert(image->signature == MagickCoreSignature);
assert(reconstruct_image != (const Image *) NULL);
- assert(reconstruct_image->signature == MagickSignature);
- if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
- ThrowBinaryException(ImageError,"ImageMorphologyDiffers",image->filename);
+ assert(reconstruct_image->signature == MagickCoreSignature);
area=0.0;
maximum_error=0.0;
mean_error_per_pixel=0.0;
for (y=0; y < (ssize_t) rows; y++)
{
register const Quantum
- *restrict p,
- *restrict q;
+ *magick_restrict p,
+ *magick_restrict q;
register ssize_t
x;
register ssize_t
i;
- if (GetPixelReadMask(image,p) == 0)
+ if (GetPixelWriteMask(image,p) == 0)
{
p+=GetPixelChannels(image);
q+=GetPixelChannels(reconstruct_image);
return(distortion);
}
-MagickExport Image *SimilarityImage(Image *image,const Image *reference,
+MagickExport Image *SimilarityImage(const Image *image,const Image *reference,
const MetricType metric,const double similarity_threshold,
RectangleInfo *offset,double *similarity_metric,ExceptionInfo *exception)
{
y;
assert(image != (const Image *) NULL);
- assert(image->signature == MagickSignature);
+ assert(image->signature == MagickCoreSignature);
if (image->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
assert(exception != (ExceptionInfo *) NULL);
- assert(exception->signature == MagickSignature);
+ assert(exception->signature == MagickCoreSignature);
assert(offset != (RectangleInfo *) NULL);
SetGeometry(reference,offset);
*similarity_metric=MagickMaximumValue;
- if (ValidateImageMorphology(image,reference) == MagickFalse)
- ThrowImageException(ImageError,"ImageMorphologyDiffers");
similarity_image=CloneImage(image,image->columns-reference->columns+1,
image->rows-reference->rows+1,MagickTrue,exception);
if (similarity_image == (Image *) NULL)
similarity;
register Quantum
- *restrict q;
+ *magick_restrict q;
register ssize_t
x;
}
if (metric == PerceptualHashErrorMetric)
similarity=MagickMin(0.01*similarity,1.0);
- if (GetPixelReadMask(similarity_image,q) == 0)
+ if (GetPixelWriteMask(similarity_image,q) == 0)
{
SetPixelBackgoundColor(similarity_image,q);
q+=GetPixelChannels(similarity_image);
continue;
}
- for (i=0; i < (ssize_t) GetPixelChannels(similarity_image); i++)
+ for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
{
PixelChannel channel=GetPixelChannelChannel(image,i);
PixelTrait traits=GetPixelChannelTraits(image,channel);
similarity_view=DestroyCacheView(similarity_view);
if (status == MagickFalse)
similarity_image=DestroyImage(similarity_image);
- if ((metric == NormalizedCrossCorrelationErrorMetric) ||
- (metric == UndefinedErrorMetric))
- *similarity_metric=1.0-(*similarity_metric);
return(similarity_image);
}