43#include "magick/studio.h"
44#include "magick/artifact.h"
45#include "magick/attribute.h"
46#include "magick/cache-view.h"
47#include "magick/channel.h"
48#include "magick/client.h"
49#include "magick/color.h"
50#include "magick/color-private.h"
51#include "magick/colorspace.h"
52#include "magick/colorspace-private.h"
53#include "magick/compare.h"
54#include "magick/compare-private.h"
55#include "magick/composite-private.h"
56#include "magick/constitute.h"
57#include "magick/exception-private.h"
58#include "magick/geometry.h"
59#include "magick/image-private.h"
60#include "magick/list.h"
61#include "magick/log.h"
62#include "magick/memory_.h"
63#include "magick/monitor.h"
64#include "magick/monitor-private.h"
65#include "magick/option.h"
66#include "magick/pixel-private.h"
67#include "magick/property.h"
68#include "magick/resource_.h"
69#include "magick/statistic-private.h"
70#include "magick/string_.h"
71#include "magick/string-private.h"
72#include "magick/statistic.h"
73#include "magick/thread-private.h"
74#include "magick/transform.h"
75#include "magick/utility.h"
76#include "magick/version.h"
114MagickExport Image *CompareImages(Image *image,
const Image *reconstruct_image,
115 const MetricType metric,
double *distortion,ExceptionInfo *exception)
120 highlight_image=CompareImageChannels(image,reconstruct_image,
121 CompositeChannels,metric,distortion,exception);
122 return(highlight_image);
125static size_t GetNumberChannels(
const Image *image,
const ChannelType channel)
131 if ((channel & RedChannel) != 0)
133 if ((channel & GreenChannel) != 0)
135 if ((channel & BlueChannel) != 0)
137 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
139 if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
141 return(channels == 0 ? 1UL : channels);
144static inline MagickBooleanType ValidateImageMorphology(
145 const Image *magick_restrict image,
146 const Image *magick_restrict reconstruct_image)
151 if (GetNumberChannels(image,DefaultChannels) !=
152 GetNumberChannels(reconstruct_image,DefaultChannels))
157MagickExport Image *CompareImageChannels(Image *image,
158 const Image *reconstruct_image,
const ChannelType channel,
159 const MetricType metric,
double *distortion,ExceptionInfo *exception)
189 assert(image != (Image *) NULL);
190 assert(image->signature == MagickCoreSignature);
191 assert(reconstruct_image != (
const Image *) NULL);
192 assert(reconstruct_image->signature == MagickCoreSignature);
193 assert(distortion != (
double *) NULL);
194 if (IsEventLogging() != MagickFalse)
195 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
197 if (metric != PerceptualHashErrorMetric)
198 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
199 ThrowImageException(ImageError,
"ImageMorphologyDiffers");
200 status=GetImageChannelDistortion(image,reconstruct_image,channel,metric,
201 distortion,exception);
202 if (status == MagickFalse)
203 return((Image *) NULL);
204 clone_image=CloneImage(image,0,0,MagickTrue,exception);
205 if (clone_image == (Image *) NULL)
206 return((Image *) NULL);
207 (void) SetImageMask(clone_image,(Image *) NULL);
208 difference_image=CloneImage(clone_image,0,0,MagickTrue,exception);
209 clone_image=DestroyImage(clone_image);
210 if (difference_image == (Image *) NULL)
211 return((Image *) NULL);
212 (void) SetImageAlphaChannel(difference_image,OpaqueAlphaChannel);
213 SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
214 highlight_image=CloneImage(image,columns,rows,MagickTrue,exception);
215 if (highlight_image == (Image *) NULL)
217 difference_image=DestroyImage(difference_image);
218 return((Image *) NULL);
220 if (SetImageStorageClass(highlight_image,DirectClass) == MagickFalse)
222 InheritException(exception,&highlight_image->exception);
223 difference_image=DestroyImage(difference_image);
224 highlight_image=DestroyImage(highlight_image);
225 return((Image *) NULL);
227 (void) SetImageMask(highlight_image,(Image *) NULL);
228 (void) SetImageAlphaChannel(highlight_image,OpaqueAlphaChannel);
229 (void) QueryMagickColor(
"#f1001ecc",&highlight,exception);
230 artifact=GetImageArtifact(image,
"compare:highlight-color");
231 if (artifact != (
const char *) NULL)
232 (void) QueryMagickColor(artifact,&highlight,exception);
233 (void) QueryMagickColor(
"#ffffffcc",&lowlight,exception);
234 artifact=GetImageArtifact(image,
"compare:lowlight-color");
235 if (artifact != (
const char *) NULL)
236 (void) QueryMagickColor(artifact,&lowlight,exception);
237 if (highlight_image->colorspace == CMYKColorspace)
239 ConvertRGBToCMYK(&highlight);
240 ConvertRGBToCMYK(&lowlight);
245 GetMagickPixelPacket(image,&zero);
246 image_view=AcquireVirtualCacheView(image,exception);
247 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
248 highlight_view=AcquireAuthenticCacheView(highlight_image,exception);
249#if defined(MAGICKCORE_OPENMP_SUPPORT)
250 #pragma omp parallel for schedule(static) shared(status) \
251 magick_number_threads(image,highlight_image,rows,1)
253 for (y=0; y < (ssize_t) rows; y++)
263 *magick_restrict indexes,
264 *magick_restrict reconstruct_indexes;
271 *magick_restrict highlight_indexes;
279 if (status == MagickFalse)
281 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
282 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
283 r=QueueCacheViewAuthenticPixels(highlight_view,0,y,columns,1,exception);
284 if ((p == (
const PixelPacket *) NULL) ||
285 (q == (
const PixelPacket *) NULL) || (r == (PixelPacket *) NULL))
290 indexes=GetCacheViewVirtualIndexQueue(image_view);
291 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
292 highlight_indexes=GetCacheViewAuthenticIndexQueue(highlight_view);
294 reconstruct_pixel=zero;
295 for (x=0; x < (ssize_t) columns; x++)
297 SetMagickPixelPacket(image,p,indexes == (IndexPacket *) NULL ? NULL :
299 SetMagickPixelPacket(reconstruct_image,q,reconstruct_indexes ==
300 (IndexPacket *) NULL ? NULL : reconstruct_indexes+x,&reconstruct_pixel);
301 if (IsMagickColorSimilar(&pixel,&reconstruct_pixel) == MagickFalse)
302 SetPixelPacket(highlight_image,&highlight,r,highlight_indexes ==
303 (IndexPacket *) NULL ? NULL : highlight_indexes+x);
305 SetPixelPacket(highlight_image,&lowlight,r,highlight_indexes ==
306 (IndexPacket *) NULL ? NULL : highlight_indexes+x);
311 sync=SyncCacheViewAuthenticPixels(highlight_view,exception);
312 if (sync == MagickFalse)
315 highlight_view=DestroyCacheView(highlight_view);
316 reconstruct_view=DestroyCacheView(reconstruct_view);
317 image_view=DestroyCacheView(image_view);
318 (void) CompositeImage(difference_image,image->compose,highlight_image,0,0);
319 highlight_image=DestroyImage(highlight_image);
320 if (status == MagickFalse)
321 difference_image=DestroyImage(difference_image);
322 return(difference_image);
361MagickExport MagickBooleanType GetImageDistortion(Image *image,
362 const Image *reconstruct_image,
const MetricType metric,
double *distortion,
363 ExceptionInfo *exception)
368 status=GetImageChannelDistortion(image,reconstruct_image,CompositeChannels,
369 metric,distortion,exception);
373static MagickBooleanType GetAESimilarity(
const Image *image,
374 const Image *reconstruct_image,
const ChannelType channel,
double *similarity,
375 ExceptionInfo *exception)
399 fuzz=GetFuzzyColorDistance(image,reconstruct_image);
400 SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
401 image_view=AcquireVirtualCacheView(image,exception);
402 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
403#if defined(MAGICKCORE_OPENMP_SUPPORT)
404 #pragma omp parallel for schedule(static) shared(similarity,status) \
405 magick_number_threads(image,image,rows,1)
407 for (y=0; y < (ssize_t) rows; y++)
410 *magick_restrict indexes,
411 *magick_restrict reconstruct_indexes;
418 channel_similarity[CompositeChannels+1] = { 0.0 };
424 if (status == MagickFalse)
426 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
427 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
428 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
433 indexes=GetCacheViewVirtualIndexQueue(image_view);
434 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
435 (void) memset(channel_similarity,0,
sizeof(channel_similarity));
436 for (x=0; x < (ssize_t) columns; x++)
446 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
447 ((double) QuantumRange-(double) OpaqueOpacity));
448 Da=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(q) :
449 ((double) QuantumRange-(double) OpaqueOpacity));
450 if ((channel & RedChannel) != 0)
452 error=Sa*(double) GetPixelRed(p)-Da*(double)
454 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
456 channel_similarity[RedChannel]++;
460 if ((channel & GreenChannel) != 0)
462 error=Sa*(double) GetPixelGreen(p)-Da*(double)
464 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
466 channel_similarity[GreenChannel]++;
470 if ((channel & BlueChannel) != 0)
472 error=Sa*(double) GetPixelBlue(p)-Da*(double)
474 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
476 channel_similarity[BlueChannel]++;
480 if (((channel & OpacityChannel) != 0) &&
481 (image->matte != MagickFalse))
483 error=(double) GetPixelOpacity(p)-(double) GetPixelOpacity(q);
484 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
486 channel_similarity[OpacityChannel]++;
490 if (((channel & IndexChannel) != 0) &&
491 (image->colorspace == CMYKColorspace))
493 error=Sa*(double) indexes[x]-Da*(
double) reconstruct_indexes[x];
494 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
496 channel_similarity[IndexChannel]++;
501 channel_similarity[CompositeChannels]++;
505#if defined(MAGICKCORE_OPENMP_SUPPORT)
506 #pragma omp critical (MagickCore_GetAESimilarity)
508 for (i=0; i <= (ssize_t) CompositeChannels; i++)
509 similarity[i]+=channel_similarity[i];
511 reconstruct_view=DestroyCacheView(reconstruct_view);
512 image_view=DestroyCacheView(image_view);
513 area=MagickSafeReciprocal((
double) columns*rows);
514 for (j=0; j <= CompositeChannels; j++)
519static MagickBooleanType GetFUZZSimilarity(
const Image *image,
520 const Image *reconstruct_image,
const ChannelType channel,
521 double *similarity,ExceptionInfo *exception)
542 fuzz=GetFuzzyColorDistance(image,reconstruct_image);
543 SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
544 image_view=AcquireVirtualCacheView(image,exception);
545 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
546#if defined(MAGICKCORE_OPENMP_SUPPORT)
547 #pragma omp parallel for schedule(static) shared(status) \
548 magick_number_threads(image,image,rows,1)
550 for (y=0; y < (ssize_t) rows; y++)
554 channel_similarity[CompositeChannels+1] = { 0.0 };
557 *magick_restrict indexes,
558 *magick_restrict reconstruct_indexes;
568 if (status == MagickFalse)
570 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
571 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
572 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
577 indexes=GetCacheViewVirtualIndexQueue(image_view);
578 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
579 for (x=0; x < (ssize_t) columns; x++)
586 Sa=QuantumScale*(image->matte != MagickFalse ? (double)
587 GetPixelAlpha(p) : ((double) QuantumRange-(double) OpaqueOpacity));
588 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
589 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
591 if ((channel & RedChannel) != 0)
593 error=QuantumScale*(Sa*GetPixelRed(p)-Da*GetPixelRed(q));
594 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
596 channel_similarity[RedChannel]+=error*error;
597 channel_similarity[CompositeChannels]+=error*error;
601 if ((channel & GreenChannel) != 0)
603 error=QuantumScale*(Sa*GetPixelGreen(p)-Da*GetPixelGreen(q));
604 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
606 channel_similarity[GreenChannel]+=error*error;
607 channel_similarity[CompositeChannels]+=error*error;
611 if ((channel & BlueChannel) != 0)
613 error=QuantumScale*(Sa*GetPixelBlue(p)-Da*GetPixelBlue(q));
614 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
616 channel_similarity[BlueChannel]+=error*error;
617 channel_similarity[CompositeChannels]+=error*error;
621 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
623 error=QuantumScale*((double) GetPixelOpacity(p)-GetPixelOpacity(q));
624 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
626 channel_similarity[OpacityChannel]+=error*error;
627 channel_similarity[CompositeChannels]+=error*error;
631 if (((channel & IndexChannel) != 0) &&
632 (image->colorspace == CMYKColorspace))
634 error=QuantumScale*(Sa*GetPixelIndex(indexes+x)-Da*
635 GetPixelIndex(reconstruct_indexes+x));
636 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
638 channel_similarity[BlackChannel]+=error*error;
639 channel_similarity[CompositeChannels]+=error*error;
646#if defined(MAGICKCORE_OPENMP_SUPPORT)
647 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
651 for (i=0; i <= (ssize_t) CompositeChannels; i++)
652 similarity[i]+=channel_similarity[i];
655 reconstruct_view=DestroyCacheView(reconstruct_view);
656 image_view=DestroyCacheView(image_view);
657 area=MagickSafeReciprocal(area);
658 for (i=0; i <= (ssize_t) CompositeChannels; i++)
663static MagickBooleanType GetPDCSimilarity(
const Image *image,
664 const Image *reconstruct_image,
const ChannelType channel,
double *similarity,
665 ExceptionInfo *exception)
689 fuzz=GetFuzzyColorDistance(image,reconstruct_image);
690 SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
691 image_view=AcquireVirtualCacheView(image,exception);
692 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
693#if defined(MAGICKCORE_OPENMP_SUPPORT)
694 #pragma omp parallel for schedule(static) shared(similarity,status) \
695 magick_number_threads(image,image,rows,1)
697 for (y=0; y < (ssize_t) rows; y++)
700 *magick_restrict indexes,
701 *magick_restrict reconstruct_indexes;
708 channel_similarity[CompositeChannels+1] = { 0.0 };
714 if (status == MagickFalse)
716 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
717 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
718 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
723 indexes=GetCacheViewVirtualIndexQueue(image_view);
724 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
725 (void) memset(channel_similarity,0,
sizeof(channel_similarity));
726 for (x=0; x < (ssize_t) columns; x++)
736 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
737 ((double) QuantumRange-(double) OpaqueOpacity));
738 Da=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(q) :
739 ((double) QuantumRange-(double) OpaqueOpacity));
740 if ((channel & RedChannel) != 0)
742 error=Sa*(double) GetPixelRed(p)-Da*(double)
744 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
746 channel_similarity[RedChannel]++;
750 if ((channel & GreenChannel) != 0)
752 error=Sa*(double) GetPixelGreen(p)-Da*(double)
754 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
756 channel_similarity[GreenChannel]++;
760 if ((channel & BlueChannel) != 0)
762 error=Sa*(double) GetPixelBlue(p)-Da*(double)
764 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
766 channel_similarity[BlueChannel]++;
770 if (((channel & OpacityChannel) != 0) &&
771 (image->matte != MagickFalse))
773 error=(double) GetPixelOpacity(p)-(double) GetPixelOpacity(q);
774 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
776 channel_similarity[OpacityChannel]++;
780 if (((channel & IndexChannel) != 0) &&
781 (image->colorspace == CMYKColorspace))
783 error=Sa*(double) indexes[x]-Da*(
double) reconstruct_indexes[x];
784 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
786 channel_similarity[IndexChannel]++;
791 channel_similarity[CompositeChannels]++;
795#if defined(MAGICKCORE_OPENMP_SUPPORT)
796 #pragma omp critical (MagickCore_GetAESimilarity)
798 for (i=0; i <= (ssize_t) CompositeChannels; i++)
799 similarity[i]+=channel_similarity[i];
801 reconstruct_view=DestroyCacheView(reconstruct_view);
802 image_view=DestroyCacheView(image_view);
803 area=MagickSafeReciprocal((
double) columns*rows);
804 for (j=0; j <= CompositeChannels; j++)
809static MagickBooleanType GetMAESimilarity(
const Image *image,
810 const Image *reconstruct_image,
const ChannelType channel,
811 double *similarity,ExceptionInfo *exception)
829 SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
830 image_view=AcquireVirtualCacheView(image,exception);
831 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
832#if defined(MAGICKCORE_OPENMP_SUPPORT)
833 #pragma omp parallel for schedule(static) shared(status) \
834 magick_number_threads(image,image,rows,1)
836 for (y=0; y < (ssize_t) rows; y++)
839 channel_similarity[CompositeChannels+1];
842 *magick_restrict indexes,
843 *magick_restrict reconstruct_indexes;
853 if (status == MagickFalse)
855 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
856 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
857 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
862 indexes=GetCacheViewVirtualIndexQueue(image_view);
863 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
864 (void) memset(channel_similarity,0,
sizeof(channel_similarity));
865 for (x=0; x < (ssize_t) columns; x++)
872 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
873 ((double) QuantumRange-(double) OpaqueOpacity));
874 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
875 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
877 if ((channel & RedChannel) != 0)
879 distance=QuantumScale*fabs(Sa*(
double) GetPixelRed(p)-Da*
880 (
double) GetPixelRed(q));
881 channel_similarity[RedChannel]+=distance;
882 channel_similarity[CompositeChannels]+=distance;
884 if ((channel & GreenChannel) != 0)
886 distance=QuantumScale*fabs(Sa*(
double) GetPixelGreen(p)-Da*
887 (
double) GetPixelGreen(q));
888 channel_similarity[GreenChannel]+=distance;
889 channel_similarity[CompositeChannels]+=distance;
891 if ((channel & BlueChannel) != 0)
893 distance=QuantumScale*fabs(Sa*(
double) GetPixelBlue(p)-Da*
894 (
double) GetPixelBlue(q));
895 channel_similarity[BlueChannel]+=distance;
896 channel_similarity[CompositeChannels]+=distance;
898 if (((channel & OpacityChannel) != 0) &&
899 (image->matte != MagickFalse))
901 distance=QuantumScale*fabs((double) GetPixelOpacity(p)-(double)
903 channel_similarity[OpacityChannel]+=distance;
904 channel_similarity[CompositeChannels]+=distance;
906 if (((channel & IndexChannel) != 0) &&
907 (image->colorspace == CMYKColorspace))
909 distance=QuantumScale*fabs(Sa*(double) GetPixelIndex(indexes+x)-Da*
910 (double) GetPixelIndex(reconstruct_indexes+x));
911 channel_similarity[BlackChannel]+=distance;
912 channel_similarity[CompositeChannels]+=distance;
917#if defined(MAGICKCORE_OPENMP_SUPPORT)
918 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
920 for (i=0; i <= (ssize_t) CompositeChannels; i++)
921 similarity[i]+=channel_similarity[i];
923 reconstruct_view=DestroyCacheView(reconstruct_view);
924 image_view=DestroyCacheView(image_view);
925 for (i=0; i <= (ssize_t) CompositeChannels; i++)
926 similarity[i]/=((
double) columns*rows);
927 similarity[CompositeChannels]/=(double) GetNumberChannels(image,channel);
931static MagickBooleanType GetMEPPSimilarity(Image *image,
932 const Image *reconstruct_image,
const ChannelType channel,
double *similarity,
933 ExceptionInfo *exception)
940 maximum_error = -MagickMaximumValue,
955 SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
956 image_view=AcquireVirtualCacheView(image,exception);
957 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
958#if defined(MAGICKCORE_OPENMP_SUPPORT)
959 #pragma omp parallel for schedule(static) shared(maximum_error,status) \
960 magick_number_threads(image,image,rows,1)
962 for (y=0; y < (ssize_t) rows; y++)
965 channel_similarity[CompositeChannels+1] = { 0.0 },
966 local_maximum = maximum_error,
967 local_mean_error = 0.0;
970 *magick_restrict indexes,
971 *magick_restrict reconstruct_indexes;
981 if (status == MagickFalse)
983 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
984 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
985 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
990 indexes=GetCacheViewVirtualIndexQueue(image_view);
991 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
992 (void) memset(channel_similarity,0,
sizeof(channel_similarity));
993 for (x=0; x < (ssize_t) columns; x++)
1000 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
1001 ((double) QuantumRange-(double) OpaqueOpacity));
1002 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1003 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
1005 if ((channel & RedChannel) != 0)
1007 distance=QuantumScale*fabs(Sa*(
double) GetPixelRed(p)-Da*
1008 (
double) GetPixelRed(q));
1009 channel_similarity[RedChannel]+=distance;
1010 channel_similarity[CompositeChannels]+=distance;
1011 local_mean_error+=distance*distance;
1012 if (distance > local_maximum)
1013 local_maximum=distance;
1015 if ((channel & GreenChannel) != 0)
1017 distance=QuantumScale*fabs(Sa*(
double) GetPixelGreen(p)-Da*
1018 (
double) GetPixelGreen(q));
1019 channel_similarity[GreenChannel]+=distance;
1020 channel_similarity[CompositeChannels]+=distance;
1021 local_mean_error+=distance*distance;
1022 if (distance > local_maximum)
1023 local_maximum=distance;
1025 if ((channel & BlueChannel) != 0)
1027 distance=QuantumScale*fabs(Sa*(
double) GetPixelBlue(p)-Da*
1028 (
double) GetPixelBlue(q));
1029 channel_similarity[BlueChannel]+=distance;
1030 channel_similarity[CompositeChannels]+=distance;
1031 local_mean_error+=distance*distance;
1032 if (distance > local_maximum)
1033 local_maximum=distance;
1035 if (((channel & OpacityChannel) != 0) &&
1036 (image->matte != MagickFalse))
1038 distance=QuantumScale*fabs((double) GetPixelOpacity(p)-(double)
1039 GetPixelOpacity(q));
1040 channel_similarity[OpacityChannel]+=distance;
1041 channel_similarity[CompositeChannels]+=distance;
1042 local_mean_error+=distance*distance;
1043 if (distance > local_maximum)
1044 local_maximum=distance;
1046 if (((channel & IndexChannel) != 0) &&
1047 (image->colorspace == CMYKColorspace))
1049 distance=QuantumScale*fabs(Sa*(double) GetPixelIndex(indexes+x)-Da*
1050 (double) GetPixelIndex(reconstruct_indexes+x));
1051 channel_similarity[BlackChannel]+=distance;
1052 channel_similarity[CompositeChannels]+=distance;
1053 local_mean_error+=distance*distance;
1054 if (distance > local_maximum)
1055 local_maximum=distance;
1060#if defined(MAGICKCORE_OPENMP_SUPPORT)
1061 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
1064 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1065 similarity[i]+=channel_similarity[i];
1066 mean_error+=local_mean_error;
1067 if (local_maximum > maximum_error)
1068 maximum_error=local_maximum;
1071 reconstruct_view=DestroyCacheView(reconstruct_view);
1072 image_view=DestroyCacheView(image_view);
1073 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1074 similarity[i]/=((
double) columns*rows);
1075 similarity[CompositeChannels]/=(double) GetNumberChannels(image,channel);
1076 image->error.mean_error_per_pixel=QuantumRange*similarity[CompositeChannels];
1077 image->error.normalized_mean_error=mean_error/((double) columns*rows);
1078 image->error.normalized_maximum_error=maximum_error;
1082static MagickBooleanType GetMSESimilarity(
const Image *image,
1083 const Image *reconstruct_image,
const ChannelType channel,
1084 double *similarity,ExceptionInfo *exception)
1105 SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
1106 image_view=AcquireVirtualCacheView(image,exception);
1107 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1108#if defined(MAGICKCORE_OPENMP_SUPPORT)
1109 #pragma omp parallel for schedule(static) shared(similarity,status) \
1110 magick_number_threads(image,image,rows,1)
1112 for (y=0; y < (ssize_t) rows; y++)
1115 channel_similarity[CompositeChannels+1] = { 0.0 };
1118 *magick_restrict indexes,
1119 *magick_restrict reconstruct_indexes;
1129 if (status == MagickFalse)
1131 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1132 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1133 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
1138 indexes=GetCacheViewVirtualIndexQueue(image_view);
1139 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1140 for (x=0; x < (ssize_t) columns; x++)
1147 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
1148 ((double) QuantumRange-(double) OpaqueOpacity));
1149 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1150 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
1152 if ((channel & RedChannel) != 0)
1154 distance=QuantumScale*(Sa*(double) GetPixelRed(p)-Da*(double)
1156 channel_similarity[RedChannel]+=distance*distance;
1157 channel_similarity[CompositeChannels]+=distance*distance;
1159 if ((channel & GreenChannel) != 0)
1161 distance=QuantumScale*(Sa*(double) GetPixelGreen(p)-Da*(double)
1163 channel_similarity[GreenChannel]+=distance*distance;
1164 channel_similarity[CompositeChannels]+=distance*distance;
1166 if ((channel & BlueChannel) != 0)
1168 distance=QuantumScale*(Sa*(double) GetPixelBlue(p)-Da*(double)
1170 channel_similarity[BlueChannel]+=distance*distance;
1171 channel_similarity[CompositeChannels]+=distance*distance;
1173 if (((channel & OpacityChannel) != 0) &&
1174 (image->matte != MagickFalse))
1176 distance=QuantumScale*((double) GetPixelOpacity(p)-(double)
1177 GetPixelOpacity(q));
1178 channel_similarity[OpacityChannel]+=distance*distance;
1179 channel_similarity[CompositeChannels]+=distance*distance;
1181 if (((channel & IndexChannel) != 0) &&
1182 (image->colorspace == CMYKColorspace) &&
1183 (reconstruct_image->colorspace == CMYKColorspace))
1185 distance=QuantumScale*(Sa*(double) GetPixelIndex(indexes+x)-Da*
1186 (double) GetPixelIndex(reconstruct_indexes+x));
1187 channel_similarity[BlackChannel]+=distance*distance;
1188 channel_similarity[CompositeChannels]+=distance*distance;
1193#if defined(MAGICKCORE_OPENMP_SUPPORT)
1194 #pragma omp critical (MagickCore_GetMeanSquaredError)
1196 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1197 similarity[i]+=channel_similarity[i];
1199 reconstruct_view=DestroyCacheView(reconstruct_view);
1200 image_view=DestroyCacheView(image_view);
1201 area=MagickSafeReciprocal((
double) columns*rows);
1202 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1203 similarity[i]*=area;
1204 similarity[CompositeChannels]/=(double) GetNumberChannels(image,channel);
1208static MagickBooleanType GetNCCSimilarity(
const Image *image,
1209 const Image *reconstruct_image,
const ChannelType channel,
double *similarity,
1210 ExceptionInfo *exception)
1212#define SimilarityImageTag "Similarity/Image"
1220 *reconstruct_statistics;
1223 alpha_variance[CompositeChannels+1] = { 0.0 },
1224 beta_variance[CompositeChannels+1] = { 0.0 };
1243 image_statistics=GetImageChannelStatistics(image,exception);
1244 reconstruct_statistics=GetImageChannelStatistics(reconstruct_image,exception);
1245 if ((image_statistics == (ChannelStatistics *) NULL) ||
1246 (reconstruct_statistics == (ChannelStatistics *) NULL))
1248 if (image_statistics != (ChannelStatistics *) NULL)
1249 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1251 if (reconstruct_statistics != (ChannelStatistics *) NULL)
1252 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1253 reconstruct_statistics);
1254 return(MagickFalse);
1256 (void) memset(similarity,0,(CompositeChannels+1)*
sizeof(*similarity));
1259 SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
1260 image_view=AcquireVirtualCacheView(image,exception);
1261 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1262#if defined(MAGICKCORE_OPENMP_SUPPORT)
1263 #pragma omp parallel for schedule(static) shared(status) \
1264 magick_number_threads(image,image,rows,1)
1266 for (y=0; y < (ssize_t) rows; y++)
1269 *magick_restrict indexes,
1270 *magick_restrict reconstruct_indexes;
1277 channel_alpha_variance[CompositeChannels+1] = { 0.0 },
1278 channel_beta_variance[CompositeChannels+1] = { 0.0 },
1279 channel_similarity[CompositeChannels+1] = { 0.0 };
1284 if (status == MagickFalse)
1286 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1287 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1288 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
1293 indexes=GetCacheViewVirtualIndexQueue(image_view);
1294 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1295 for (x=0; x < (ssize_t) columns; x++)
1303 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
1304 (double) QuantumRange);
1305 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1306 (double) GetPixelAlpha(q) : (double) QuantumRange);
1307 if ((channel & RedChannel) != 0)
1309 alpha=QuantumScale*(Sa*(double) GetPixelRed(p)-
1310 image_statistics[RedChannel].mean);
1311 beta=QuantumScale*(Da*(double) GetPixelRed(q)-
1312 reconstruct_statistics[RedChannel].mean);
1313 channel_similarity[RedChannel]+=alpha*beta;
1314 channel_similarity[CompositeChannels]+=alpha*beta;
1315 channel_alpha_variance[RedChannel]+=alpha*alpha;
1316 channel_alpha_variance[CompositeChannels]+=alpha*alpha;
1317 channel_beta_variance[RedChannel]+=beta*beta;
1318 channel_beta_variance[CompositeChannels]+=beta*beta;
1320 if ((channel & GreenChannel) != 0)
1322 alpha=QuantumScale*(Sa*(double) GetPixelGreen(p)-
1323 image_statistics[GreenChannel].mean);
1324 beta=QuantumScale*(Da*(double) GetPixelGreen(q)-
1325 reconstruct_statistics[GreenChannel].mean);
1326 channel_similarity[GreenChannel]+=alpha*beta;
1327 channel_similarity[CompositeChannels]+=alpha*beta;
1328 channel_alpha_variance[GreenChannel]+=alpha*alpha;
1329 channel_alpha_variance[CompositeChannels]+=alpha*alpha;
1330 channel_beta_variance[GreenChannel]+=beta*beta;
1331 channel_beta_variance[CompositeChannels]+=beta*beta;
1333 if ((channel & BlueChannel) != 0)
1335 alpha=QuantumScale*(Sa*(double) GetPixelBlue(p)-
1336 image_statistics[BlueChannel].mean);
1337 beta=QuantumScale*(Da*(double) GetPixelBlue(q)-
1338 reconstruct_statistics[BlueChannel].mean);
1339 channel_similarity[BlueChannel]+=alpha*beta;
1340 channel_alpha_variance[BlueChannel]+=alpha*alpha;
1341 channel_beta_variance[BlueChannel]+=beta*beta;
1343 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
1345 alpha=QuantumScale*((double) GetPixelAlpha(p)-
1346 image_statistics[AlphaChannel].mean);
1347 beta=QuantumScale*((double) GetPixelAlpha(q)-
1348 reconstruct_statistics[AlphaChannel].mean);
1349 channel_similarity[OpacityChannel]+=alpha*beta;
1350 channel_similarity[CompositeChannels]+=alpha*beta;
1351 channel_alpha_variance[OpacityChannel]+=alpha*alpha;
1352 channel_alpha_variance[CompositeChannels]+=alpha*alpha;
1353 channel_beta_variance[OpacityChannel]+=beta*beta;
1354 channel_beta_variance[CompositeChannels]+=beta*beta;
1356 if (((channel & IndexChannel) != 0) &&
1357 (image->colorspace == CMYKColorspace) &&
1358 (reconstruct_image->colorspace == CMYKColorspace))
1360 alpha=QuantumScale*(Sa*(double) GetPixelIndex(indexes+x)-
1361 image_statistics[BlackChannel].mean);
1362 beta=QuantumScale*(Da*(double) GetPixelIndex(reconstruct_indexes+
1363 x)-reconstruct_statistics[BlackChannel].mean);
1364 channel_similarity[BlackChannel]+=alpha*beta;
1365 channel_similarity[CompositeChannels]+=alpha*beta;
1366 channel_alpha_variance[BlackChannel]+=alpha*alpha;
1367 channel_alpha_variance[CompositeChannels]+=alpha*alpha;
1368 channel_beta_variance[BlackChannel]+=beta*beta;
1369 channel_beta_variance[CompositeChannels]+=beta*beta;
1374#if defined(MAGICKCORE_OPENMP_SUPPORT)
1375 #pragma omp critical (GetNCCSimilarity)
1381 for (j=0; j <= (ssize_t) CompositeChannels; j++)
1383 similarity[j]+=channel_similarity[j];
1384 alpha_variance[j]+=channel_alpha_variance[j];
1385 beta_variance[j]+=channel_beta_variance[j];
1388 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1393#if defined(MAGICKCORE_OPENMP_SUPPORT)
1397 proceed=SetImageProgress(image,SimilarityImageTag,progress,rows);
1398 if (proceed == MagickFalse)
1402 reconstruct_view=DestroyCacheView(reconstruct_view);
1403 image_view=DestroyCacheView(image_view);
1407 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1408 similarity[i]*=MagickSafeReciprocal(sqrt(alpha_variance[i])*
1409 sqrt(beta_variance[i]));
1413 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1414 reconstruct_statistics);
1415 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1420static MagickBooleanType GetPASimilarity(
const Image *image,
1421 const Image *reconstruct_image,
const ChannelType channel,
1422 double *similarity,ExceptionInfo *exception)
1439 (void) memset(similarity,0,(CompositeChannels+1)*
sizeof(*similarity));
1440 SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
1441 image_view=AcquireVirtualCacheView(image,exception);
1442 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1443#if defined(MAGICKCORE_OPENMP_SUPPORT)
1444 #pragma omp parallel for schedule(static) shared(status) \
1445 magick_number_threads(image,image,rows,1)
1447 for (y=0; y < (ssize_t) rows; y++)
1450 channel_similarity[CompositeChannels+1];
1453 *magick_restrict indexes,
1454 *magick_restrict reconstruct_indexes;
1464 if (status == MagickFalse)
1466 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1467 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1468 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
1473 indexes=GetCacheViewVirtualIndexQueue(image_view);
1474 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1475 (void) memset(channel_similarity,0,(CompositeChannels+1)*
1476 sizeof(*channel_similarity));
1477 for (x=0; x < (ssize_t) columns; x++)
1484 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
1485 ((double) QuantumRange-(double) OpaqueOpacity));
1486 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1487 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
1489 if ((channel & RedChannel) != 0)
1491 distance=QuantumScale*fabs(Sa*(
double) GetPixelRed(p)-Da*
1492 (
double) GetPixelRed(q));
1493 if (distance > channel_similarity[RedChannel])
1494 channel_similarity[RedChannel]=distance;
1495 if (distance > channel_similarity[CompositeChannels])
1496 channel_similarity[CompositeChannels]=distance;
1498 if ((channel & GreenChannel) != 0)
1500 distance=QuantumScale*fabs(Sa*(
double) GetPixelGreen(p)-Da*
1501 (
double) GetPixelGreen(q));
1502 if (distance > channel_similarity[GreenChannel])
1503 channel_similarity[GreenChannel]=distance;
1504 if (distance > channel_similarity[CompositeChannels])
1505 channel_similarity[CompositeChannels]=distance;
1507 if ((channel & BlueChannel) != 0)
1509 distance=QuantumScale*fabs(Sa*(
double) GetPixelBlue(p)-Da*
1510 (
double) GetPixelBlue(q));
1511 if (distance > channel_similarity[BlueChannel])
1512 channel_similarity[BlueChannel]=distance;
1513 if (distance > channel_similarity[CompositeChannels])
1514 channel_similarity[CompositeChannels]=distance;
1516 if (((channel & OpacityChannel) != 0) &&
1517 (image->matte != MagickFalse))
1519 distance=QuantumScale*fabs((double) GetPixelOpacity(p)-(double)
1520 GetPixelOpacity(q));
1521 if (distance > channel_similarity[OpacityChannel])
1522 channel_similarity[OpacityChannel]=distance;
1523 if (distance > channel_similarity[CompositeChannels])
1524 channel_similarity[CompositeChannels]=distance;
1526 if (((channel & IndexChannel) != 0) &&
1527 (image->colorspace == CMYKColorspace) &&
1528 (reconstruct_image->colorspace == CMYKColorspace))
1530 distance=QuantumScale*fabs(Sa*(double) GetPixelIndex(indexes+x)-Da*
1531 (double) GetPixelIndex(reconstruct_indexes+x));
1532 if (distance > channel_similarity[BlackChannel])
1533 channel_similarity[BlackChannel]=distance;
1534 if (distance > channel_similarity[CompositeChannels])
1535 channel_similarity[CompositeChannels]=distance;
1540#if defined(MAGICKCORE_OPENMP_SUPPORT)
1541 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
1543 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1544 if (channel_similarity[i] > similarity[i])
1545 similarity[i]=channel_similarity[i];
1547 reconstruct_view=DestroyCacheView(reconstruct_view);
1548 image_view=DestroyCacheView(image_view);
1552static MagickBooleanType GetPSNRSimilarity(
const Image *image,
1553 const Image *reconstruct_image,
const ChannelType channel,
1554 double *similarity,ExceptionInfo *exception)
1559 status=GetMSESimilarity(image,reconstruct_image,channel,similarity,
1561 if ((channel & RedChannel) != 0)
1562 similarity[RedChannel]=10.0*MagickSafeLog10(MagickSafeReciprocal(
1563 similarity[RedChannel]))/MagickSafePSNRRecipicol(10.0);
1564 if ((channel & GreenChannel) != 0)
1565 similarity[GreenChannel]=10.0*MagickSafeLog10(MagickSafeReciprocal(
1566 similarity[GreenChannel]))/MagickSafePSNRRecipicol(10.0);
1567 if ((channel & BlueChannel) != 0)
1568 similarity[BlueChannel]=10.0*MagickSafeLog10(MagickSafeReciprocal(
1569 similarity[BlueChannel]))/MagickSafePSNRRecipicol(10.0);
1570 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
1571 similarity[OpacityChannel]=10.0*MagickSafeLog10(MagickSafeReciprocal(
1572 similarity[OpacityChannel]))/MagickSafePSNRRecipicol(10.0);
1573 if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
1574 similarity[BlackChannel]=10.0*MagickSafeLog10(MagickSafeReciprocal(
1575 similarity[BlackChannel]))/MagickSafePSNRRecipicol(10.0);
1576 similarity[CompositeChannels]=10.0*MagickSafeLog10(MagickSafeReciprocal(
1577 similarity[CompositeChannels]))/MagickSafePSNRRecipicol(10.0);
1581static MagickBooleanType GetPHASHSimilarity(
const Image *image,
1582 const Image *reconstruct_image,
const ChannelType channel,
double *similarity,
1583 ExceptionInfo *exception)
1585 ChannelPerceptualHash
1599 image_phash=GetImageChannelPerceptualHash(image,exception);
1600 if (image_phash == (ChannelPerceptualHash *) NULL)
1601 return(MagickFalse);
1602 reconstruct_phash=GetImageChannelPerceptualHash(reconstruct_image,exception);
1603 if (reconstruct_phash == (ChannelPerceptualHash *) NULL)
1605 image_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(image_phash);
1606 return(MagickFalse);
1608 for (i=0; i < MaximumNumberOfImageMoments; i++)
1613 if ((channel & RedChannel) != 0)
1615 error=reconstruct_phash[RedChannel].P[i]-image_phash[RedChannel].P[i];
1616 if (IsNaN(error) != 0)
1618 difference=error*error;
1619 similarity[RedChannel]+=difference;
1620 similarity[CompositeChannels]+=difference;
1622 if ((channel & GreenChannel) != 0)
1624 error=reconstruct_phash[GreenChannel].P[i]-
1625 image_phash[GreenChannel].P[i];
1626 if (IsNaN(error) != 0)
1628 difference=error*error;
1629 similarity[GreenChannel]+=difference;
1630 similarity[CompositeChannels]+=difference;
1632 if ((channel & BlueChannel) != 0)
1634 error=reconstruct_phash[BlueChannel].P[i]-image_phash[BlueChannel].P[i];
1635 if (IsNaN(error) != 0)
1637 difference=error*error;
1638 similarity[BlueChannel]+=difference;
1639 similarity[CompositeChannels]+=difference;
1641 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse) &&
1642 (reconstruct_image->matte != MagickFalse))
1644 error=reconstruct_phash[OpacityChannel].P[i]-
1645 image_phash[OpacityChannel].P[i];
1646 if (IsNaN(error) != 0)
1648 difference=error*error;
1649 similarity[OpacityChannel]+=difference;
1650 similarity[CompositeChannels]+=difference;
1652 if (((channel & IndexChannel) != 0) &&
1653 (image->colorspace == CMYKColorspace) &&
1654 (reconstruct_image->colorspace == CMYKColorspace))
1656 error=reconstruct_phash[IndexChannel].P[i]-
1657 image_phash[IndexChannel].P[i];
1658 if (IsNaN(error) != 0)
1660 difference=error*error;
1661 similarity[IndexChannel]+=difference;
1662 similarity[CompositeChannels]+=difference;
1668 for (i=0; i < MaximumNumberOfImageMoments; i++)
1673 if ((channel & RedChannel) != 0)
1675 error=reconstruct_phash[RedChannel].Q[i]-image_phash[RedChannel].Q[i];
1676 if (IsNaN(error) != 0)
1678 difference=error*error;
1679 similarity[RedChannel]+=difference;
1680 similarity[CompositeChannels]+=difference;
1682 if ((channel & GreenChannel) != 0)
1684 error=reconstruct_phash[GreenChannel].Q[i]-
1685 image_phash[GreenChannel].Q[i];
1686 if (IsNaN(error) != 0)
1688 difference=error*error;
1689 similarity[GreenChannel]+=difference;
1690 similarity[CompositeChannels]+=difference;
1692 if ((channel & BlueChannel) != 0)
1694 error=reconstruct_phash[BlueChannel].Q[i]-image_phash[BlueChannel].Q[i];
1695 if (IsNaN(error) != 0)
1697 difference=error*error;
1698 similarity[BlueChannel]+=difference;
1699 similarity[CompositeChannels]+=difference;
1701 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse) &&
1702 (reconstruct_image->matte != MagickFalse))
1704 error=reconstruct_phash[OpacityChannel].Q[i]-
1705 image_phash[OpacityChannel].Q[i];
1706 if (IsNaN(error) != 0)
1708 difference=error*error;
1709 similarity[OpacityChannel]+=difference;
1710 similarity[CompositeChannels]+=difference;
1712 if (((channel & IndexChannel) != 0) &&
1713 (image->colorspace == CMYKColorspace) &&
1714 (reconstruct_image->colorspace == CMYKColorspace))
1716 error=reconstruct_phash[IndexChannel].Q[i]-
1717 image_phash[IndexChannel].Q[i];
1718 if (IsNaN(error) != 0)
1720 difference=error*error;
1721 similarity[IndexChannel]+=difference;
1722 similarity[CompositeChannels]+=difference;
1725 similarity[CompositeChannels]/=(double) GetNumberChannels(image,channel);
1729 reconstruct_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(
1731 image_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(image_phash);
1735static MagickBooleanType GetRMSESimilarity(
const Image *image,
1736 const Image *reconstruct_image,
const ChannelType channel,
double *similarity,
1737 ExceptionInfo *exception)
1739#define RMSESquareRoot(x) sqrt((x) < 0.0 ? 0.0 : (x))
1744 status=GetMSESimilarity(image,reconstruct_image,channel,similarity,
1746 if ((channel & RedChannel) != 0)
1747 similarity[RedChannel]=RMSESquareRoot(similarity[RedChannel]);
1748 if ((channel & GreenChannel) != 0)
1749 similarity[GreenChannel]=RMSESquareRoot(similarity[GreenChannel]);
1750 if ((channel & BlueChannel) != 0)
1751 similarity[BlueChannel]=RMSESquareRoot(similarity[BlueChannel]);
1752 if (((channel & OpacityChannel) != 0) &&
1753 (image->matte != MagickFalse))
1754 similarity[OpacityChannel]=RMSESquareRoot(similarity[OpacityChannel]);
1755 if (((channel & IndexChannel) != 0) &&
1756 (image->colorspace == CMYKColorspace))
1757 similarity[BlackChannel]=RMSESquareRoot(similarity[BlackChannel]);
1758 similarity[CompositeChannels]=RMSESquareRoot(similarity[CompositeChannels]);
1762MagickExport MagickBooleanType GetImageChannelDistortion(Image *image,
1763 const Image *reconstruct_image,
const ChannelType channel,
1764 const MetricType metric,
double *distortion,ExceptionInfo *exception)
1767 *channel_similarity;
1775 assert(image != (Image *) NULL);
1776 assert(image->signature == MagickCoreSignature);
1777 assert(reconstruct_image != (
const Image *) NULL);
1778 assert(reconstruct_image->signature == MagickCoreSignature);
1779 assert(distortion != (
double *) NULL);
1780 if (IsEventLogging() != MagickFalse)
1781 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
1783 if (metric != PerceptualHashErrorMetric)
1784 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1785 ThrowBinaryException(ImageError,
"ImageMorphologyDiffers",image->filename);
1789 length=CompositeChannels+1UL;
1790 channel_similarity=(
double *) AcquireQuantumMemory(length,
1791 sizeof(*channel_similarity));
1792 if (channel_similarity == (
double *) NULL)
1793 ThrowFatalException(ResourceLimitFatalError,
"MemoryAllocationFailed");
1794 (void) memset(channel_similarity,0,length*
sizeof(*channel_similarity));
1797 case AbsoluteErrorMetric:
1799 status=GetAESimilarity(image,reconstruct_image,channel,
1800 channel_similarity,exception);
1803 case FuzzErrorMetric:
1805 status=GetFUZZSimilarity(image,reconstruct_image,channel,
1806 channel_similarity,exception);
1809 case MeanAbsoluteErrorMetric:
1811 status=GetMAESimilarity(image,reconstruct_image,channel,
1812 channel_similarity,exception);
1815 case MeanErrorPerPixelMetric:
1817 status=GetMEPPSimilarity(image,reconstruct_image,channel,
1818 channel_similarity,exception);
1821 case MeanSquaredErrorMetric:
1823 status=GetMSESimilarity(image,reconstruct_image,channel,
1824 channel_similarity,exception);
1827 case NormalizedCrossCorrelationErrorMetric:
1829 status=GetNCCSimilarity(image,reconstruct_image,channel,
1830 channel_similarity,exception);
1833 case PeakAbsoluteErrorMetric:
1835 status=GetPASimilarity(image,reconstruct_image,channel,
1836 channel_similarity,exception);
1839 case PeakSignalToNoiseRatioMetric:
1841 status=GetPSNRSimilarity(image,reconstruct_image,channel,
1842 channel_similarity,exception);
1845 case PerceptualHashErrorMetric:
1847 status=GetPHASHSimilarity(image,reconstruct_image,channel,
1848 channel_similarity,exception);
1851 case PixelDifferenceCountErrorMetric:
1853 status=GetPDCSimilarity(image,reconstruct_image,channel,
1854 channel_similarity,exception);
1857 case RootMeanSquaredErrorMetric:
1858 case UndefinedErrorMetric:
1861 status=GetRMSESimilarity(image,reconstruct_image,channel,
1862 channel_similarity,exception);
1866 *distortion=channel_similarity[CompositeChannels];
1869 case NormalizedCrossCorrelationErrorMetric:
1871 *distortion=(1.0-(*distortion))/2.0;
1876 if (fabs(*distortion) < MagickEpsilon)
1878 channel_similarity=(
double *) RelinquishMagickMemory(channel_similarity);
1879 (void) FormatImageProperty(image,
"distortion",
"%.*g",GetMagickPrecision(),
1916MagickExport
double *GetImageChannelDistortions(Image *image,
1917 const Image *reconstruct_image,
const MetricType metric,
1918 ExceptionInfo *exception)
1933 assert(image != (Image *) NULL);
1934 assert(image->signature == MagickCoreSignature);
1935 assert(reconstruct_image != (
const Image *) NULL);
1936 assert(reconstruct_image->signature == MagickCoreSignature);
1937 if (IsEventLogging() != MagickFalse)
1938 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
1939 if (metric != PerceptualHashErrorMetric)
1940 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1942 (void) ThrowMagickException(&image->exception,GetMagickModule(),
1943 ImageError,
"ImageMorphologyDiffers",
"`%s'",image->filename);
1944 return((
double *) NULL);
1949 length=CompositeChannels+1UL;
1950 similarity=(
double *) AcquireQuantumMemory(length,
1951 sizeof(*similarity));
1952 if (similarity == (
double *) NULL)
1953 ThrowFatalException(ResourceLimitFatalError,
"MemoryAllocationFailed");
1954 (void) memset(similarity,0,length*
sizeof(*similarity));
1958 case AbsoluteErrorMetric:
1960 status=GetAESimilarity(image,reconstruct_image,CompositeChannels,
1961 similarity,exception);
1964 case FuzzErrorMetric:
1966 status=GetFUZZSimilarity(image,reconstruct_image,CompositeChannels,
1967 similarity,exception);
1970 case MeanAbsoluteErrorMetric:
1972 status=GetMAESimilarity(image,reconstruct_image,CompositeChannels,
1973 similarity,exception);
1976 case MeanErrorPerPixelMetric:
1978 status=GetMEPPSimilarity(image,reconstruct_image,CompositeChannels,
1979 similarity,exception);
1982 case MeanSquaredErrorMetric:
1984 status=GetMSESimilarity(image,reconstruct_image,CompositeChannels,
1985 similarity,exception);
1988 case NormalizedCrossCorrelationErrorMetric:
1990 status=GetNCCSimilarity(image,reconstruct_image,CompositeChannels,
1991 similarity,exception);
1994 case PeakAbsoluteErrorMetric:
1996 status=GetPASimilarity(image,reconstruct_image,CompositeChannels,
1997 similarity,exception);
2000 case PeakSignalToNoiseRatioMetric:
2002 status=GetPSNRSimilarity(image,reconstruct_image,CompositeChannels,
2003 similarity,exception);
2006 case PerceptualHashErrorMetric:
2008 status=GetPHASHSimilarity(image,reconstruct_image,CompositeChannels,
2009 similarity,exception);
2012 case PixelDifferenceCountErrorMetric:
2014 status=GetPDCSimilarity(image,reconstruct_image,CompositeChannels,
2015 similarity,exception);
2018 case RootMeanSquaredErrorMetric:
2019 case UndefinedErrorMetric:
2022 status=GetRMSESimilarity(image,reconstruct_image,CompositeChannels,
2023 similarity,exception);
2027 if (status == MagickFalse)
2029 similarity=(
double *) RelinquishMagickMemory(similarity);
2030 return((
double *) NULL);
2032 distortion=similarity;
2035 case NormalizedCrossCorrelationErrorMetric:
2037 for (i=0; i <= (ssize_t) CompositeChannels; i++)
2038 distortion[i]=(1.0-distortion[i])/2.0;
2043 for (i=0; i <= (ssize_t) CompositeChannels; i++)
2044 if (fabs(distortion[i]) < MagickEpsilon)
2096MagickExport MagickBooleanType IsImagesEqual(Image *image,
2097 const Image *reconstruct_image)
2114 mean_error_per_pixel;
2123 assert(image != (Image *) NULL);
2124 assert(image->signature == MagickCoreSignature);
2125 assert(reconstruct_image != (
const Image *) NULL);
2126 assert(reconstruct_image->signature == MagickCoreSignature);
2127 exception=(&image->exception);
2128 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
2129 ThrowBinaryException(ImageError,
"ImageMorphologyDiffers",image->filename);
2132 mean_error_per_pixel=0.0;
2134 SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
2135 image_view=AcquireVirtualCacheView(image,exception);
2136 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
2137 for (y=0; y < (ssize_t) rows; y++)
2140 *magick_restrict indexes,
2141 *magick_restrict reconstruct_indexes;
2150 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
2151 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
2152 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
2154 indexes=GetCacheViewVirtualIndexQueue(image_view);
2155 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
2156 for (x=0; x < (ssize_t) columns; x++)
2161 distance=fabs((
double) GetPixelRed(p)-(
double) GetPixelRed(q));
2162 mean_error_per_pixel+=distance;
2163 mean_error+=distance*distance;
2164 if (distance > maximum_error)
2165 maximum_error=distance;
2167 distance=fabs((
double) GetPixelGreen(p)-(
double) GetPixelGreen(q));
2168 mean_error_per_pixel+=distance;
2169 mean_error+=distance*distance;
2170 if (distance > maximum_error)
2171 maximum_error=distance;
2173 distance=fabs((
double) GetPixelBlue(p)-(
double) GetPixelBlue(q));
2174 mean_error_per_pixel+=distance;
2175 mean_error+=distance*distance;
2176 if (distance > maximum_error)
2177 maximum_error=distance;
2179 if (image->matte != MagickFalse)
2181 distance=fabs((
double) GetPixelOpacity(p)-(
double)
2182 GetPixelOpacity(q));
2183 mean_error_per_pixel+=distance;
2184 mean_error+=distance*distance;
2185 if (distance > maximum_error)
2186 maximum_error=distance;
2189 if ((image->colorspace == CMYKColorspace) &&
2190 (reconstruct_image->colorspace == CMYKColorspace))
2192 distance=fabs((
double) GetPixelIndex(indexes+x)-(
double)
2193 GetPixelIndex(reconstruct_indexes+x));
2194 mean_error_per_pixel+=distance;
2195 mean_error+=distance*distance;
2196 if (distance > maximum_error)
2197 maximum_error=distance;
2204 reconstruct_view=DestroyCacheView(reconstruct_view);
2205 image_view=DestroyCacheView(image_view);
2206 gamma=MagickSafeReciprocal(area);
2207 image->error.mean_error_per_pixel=gamma*mean_error_per_pixel;
2208 image->error.normalized_mean_error=gamma*QuantumScale*QuantumScale*mean_error;
2209 image->error.normalized_maximum_error=QuantumScale*maximum_error;
2210 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
2249static double GetSimilarityMetric(
const Image *image,
2250 const Image *reconstruct_image,
const MetricType metric,
2251 const ssize_t x_offset,
const ssize_t y_offset,ExceptionInfo *exception)
2254 *channel_similarity,
2258 *sans_exception = AcquireExceptionInfo();
2264 status = MagickTrue;
2270 length = CompositeChannels+1UL;
2272 SetGeometry(reconstruct_image,&geometry);
2273 geometry.x=x_offset;
2274 geometry.y=y_offset;
2275 similarity_image=CropImage(image,&geometry,sans_exception);
2276 sans_exception=DestroyExceptionInfo(sans_exception);
2277 if (similarity_image == (Image *) NULL)
2282 channel_similarity=(
double *) AcquireQuantumMemory(length,
2283 sizeof(*channel_similarity));
2284 if (channel_similarity == (
double *) NULL)
2285 ThrowFatalException(ResourceLimitFatalError,
"MemoryAllocationFailed");
2286 (void) memset(channel_similarity,0,length*
sizeof(*channel_similarity));
2289 case AbsoluteErrorMetric:
2291 status=GetAESimilarity(similarity_image,reconstruct_image,
2292 CompositeChannels,channel_similarity,exception);
2295 case FuzzErrorMetric:
2297 status=GetFUZZSimilarity(similarity_image,reconstruct_image,
2298 CompositeChannels,channel_similarity,exception);
2301 case MeanAbsoluteErrorMetric:
2303 status=GetMAESimilarity(similarity_image,reconstruct_image,
2304 CompositeChannels,channel_similarity,exception);
2307 case MeanErrorPerPixelMetric:
2309 status=GetMEPPSimilarity(similarity_image,reconstruct_image,
2310 CompositeChannels,channel_similarity,exception);
2313 case MeanSquaredErrorMetric:
2315 status=GetMSESimilarity(similarity_image,reconstruct_image,
2316 CompositeChannels,channel_similarity,exception);
2319 case NormalizedCrossCorrelationErrorMetric:
2321 status=GetNCCSimilarity(similarity_image,reconstruct_image,
2322 CompositeChannels,channel_similarity,exception);
2325 case PeakAbsoluteErrorMetric:
2327 status=GetPASimilarity(similarity_image,reconstruct_image,
2328 CompositeChannels,channel_similarity,exception);
2331 case PeakSignalToNoiseRatioMetric:
2333 status=GetPSNRSimilarity(similarity_image,reconstruct_image,
2334 CompositeChannels,channel_similarity,exception);
2337 case PerceptualHashErrorMetric:
2339 status=GetPHASHSimilarity(similarity_image,reconstruct_image,
2340 CompositeChannels,channel_similarity,exception);
2343 case PixelDifferenceCountErrorMetric:
2345 status=GetPDCSimilarity(similarity_image,reconstruct_image,
2346 CompositeChannels,channel_similarity,exception);
2349 case RootMeanSquaredErrorMetric:
2350 case UndefinedErrorMetric:
2353 status=GetRMSESimilarity(similarity_image,reconstruct_image,
2354 CompositeChannels,channel_similarity,exception);
2358 similarity_image=DestroyImage(similarity_image);
2359 similarity=channel_similarity[CompositeChannels];
2360 channel_similarity=(
double *) RelinquishMagickMemory(channel_similarity);
2361 if (status == MagickFalse)
2366MagickExport Image *SimilarityImage(Image *image,
const Image *reference,
2367 RectangleInfo *offset,
double *similarity_metric,ExceptionInfo *exception)
2372 similarity_image=SimilarityMetricImage(image,reference,
2373 RootMeanSquaredErrorMetric,offset,similarity_metric,exception);
2374 return(similarity_image);
2377MagickExport Image *SimilarityMetricImage(Image *image,
const Image *reconstruct,
2378 const MetricType metric,RectangleInfo *offset,
double *similarity_metric,
2379 ExceptionInfo *exception)
2381#define SimilarityImageTag "Similarity/Image"
2400 similarity_threshold;
2403 *similarity_image = (Image *) NULL;
2412 similarity_info = { 0 };
2417 assert(image != (
const Image *) NULL);
2418 assert(image->signature == MagickCoreSignature);
2419 assert(exception != (ExceptionInfo *) NULL);
2420 assert(exception->signature == MagickCoreSignature);
2421 assert(offset != (RectangleInfo *) NULL);
2422 if (IsEventLogging() != MagickFalse)
2423 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
2424 SetGeometry(reconstruct,offset);
2425 *similarity_metric=0.0;
2428 if (ValidateImageMorphology(image,reconstruct) == MagickFalse)
2429 ThrowImageException(ImageError,
"ImageMorphologyDiffers");
2430 if ((image->columns < reconstruct->columns) ||
2431 (image->rows < reconstruct->rows))
2433 (void) ThrowMagickException(&image->exception,GetMagickModule(),
2434 OptionWarning,
"GeometryDoesNotContainImage",
"`%s'",image->filename);
2435 return((Image *) NULL);
2437 similarity_image=CloneImage(image,image->columns-reconstruct->columns+1,
2438 image->rows-reconstruct->rows+1,MagickTrue,exception);
2439 if (similarity_image == (Image *) NULL)
2440 return((Image *) NULL);
2441 similarity_image->depth=32;
2442 similarity_image->colorspace=GRAYColorspace;
2443 similarity_image->matte=MagickFalse;
2444 status=SetImageStorageClass(similarity_image,DirectClass);
2445 if (status == MagickFalse)
2447 InheritException(exception,&similarity_image->exception);
2448 return(DestroyImage(similarity_image));
2453 similarity_threshold=DefaultSimilarityThreshold;
2454 artifact=GetImageArtifact(image,
"compare:similarity-threshold");
2455 if (artifact != (
const char *) NULL)
2456 similarity_threshold=StringToDouble(artifact,(
char **) NULL);
2458 similarity_info.similarity=GetSimilarityMetric(image,reconstruct,metric,
2459 similarity_info.x,similarity_info.y,exception);
2461 similarity_view=AcquireVirtualCacheView(similarity_image,exception);
2462#if defined(MAGICKCORE_OPENMP_SUPPORT)
2463 #pragma omp parallel for schedule(static) shared(status,similarity_info) \
2464 magick_number_threads(image,reconstruct,similarity_image->rows << 2,1)
2466 for (y=0; y < (ssize_t) similarity_image->rows; y++)
2472 threshold_trigger = MagickFalse;
2478 channel_info = similarity_info;
2483 if (status == MagickFalse)
2485 if (threshold_trigger != MagickFalse)
2487 q=QueueCacheViewAuthenticPixels(similarity_view,0,y,
2488 similarity_image->columns,1,exception);
2489 if (q == (PixelPacket *) NULL)
2494 for (x=0; x < (ssize_t) similarity_image->columns; x++)
2497 update = MagickFalse;
2499 similarity=GetSimilarityMetric(image,reconstruct,metric,x,y,exception);
2502 case NormalizedCrossCorrelationErrorMetric:
2503 case PeakSignalToNoiseRatioMetric:
2505 if (similarity > channel_info.similarity)
2511 if (similarity < channel_info.similarity)
2516 if (update != MagickFalse)
2518 channel_info.similarity=similarity;
2524 case NormalizedCrossCorrelationErrorMetric:
2525 case PeakSignalToNoiseRatioMetric:
2527 SetPixelRed(q,ClampToQuantum((
double) QuantumRange*similarity));
2532 SetPixelRed(q,ClampToQuantum((
double) QuantumRange*(1.0-similarity)));
2536 SetPixelGreen(q,GetPixelRed(q));
2537 SetPixelBlue(q,GetPixelRed(q));
2540#if defined(MAGICKCORE_OPENMP_SUPPORT)
2541 #pragma omp critical (MagickCore_SimilarityMetricImage)
2545 case NormalizedCrossCorrelationErrorMetric:
2546 case PeakSignalToNoiseRatioMetric:
2548 if (similarity_threshold != DefaultSimilarityThreshold)
2549 if (channel_info.similarity >= similarity_threshold)
2550 threshold_trigger=MagickTrue;
2551 if (channel_info.similarity >= similarity_info.similarity)
2552 similarity_info=channel_info;
2557 if (similarity_threshold != DefaultSimilarityThreshold)
2558 if (channel_info.similarity < similarity_threshold)
2559 threshold_trigger=MagickTrue;
2560 if (channel_info.similarity < similarity_info.similarity)
2561 similarity_info=channel_info;
2565 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
2567 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2573 proceed=SetImageProgress(image,SimilarityImageTag,progress,image->rows);
2574 if (proceed == MagickFalse)
2578 similarity_view=DestroyCacheView(similarity_view);
2579 if (status == MagickFalse)
2580 similarity_image=DestroyImage(similarity_image);
2581 *similarity_metric=similarity_info.similarity;
2582 if (fabs(*similarity_metric) < MagickEpsilon)
2583 *similarity_metric=0.0;
2584 offset->x=similarity_info.x;
2585 offset->y=similarity_info.y;
2586 (void) FormatImageProperty((Image *) image,
"similarity",
"%.*g",
2587 GetMagickPrecision(),*similarity_metric);
2588 (void) FormatImageProperty((Image *) image,
"similarity.offset.x",
"%.*g",
2589 GetMagickPrecision(),(
double) offset->x);
2590 (void) FormatImageProperty((Image *) image,
"similarity.offset.y",
"%.*g",
2591 GetMagickPrecision(),(
double) offset->y);
2592 return(similarity_image);