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 inline MagickBooleanType ValidateImageMorphology(
126 const Image *magick_restrict image,
127 const Image *magick_restrict reconstruct_image)
132 if (GetNumberChannels(image,DefaultChannels) !=
133 GetNumberChannels(reconstruct_image,DefaultChannels))
138MagickExport Image *CompareImageChannels(Image *image,
139 const Image *reconstruct_image,
const ChannelType channel,
140 const MetricType metric,
double *distortion,ExceptionInfo *exception)
170 assert(image != (Image *) NULL);
171 assert(image->signature == MagickCoreSignature);
172 assert(reconstruct_image != (
const Image *) NULL);
173 assert(reconstruct_image->signature == MagickCoreSignature);
174 assert(distortion != (
double *) NULL);
175 if (IsEventLogging() != MagickFalse)
176 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
178 if (metric != PerceptualHashErrorMetric)
179 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
180 ThrowImageException(ImageError,
"ImageMorphologyDiffers");
181 status=GetImageChannelDistortion(image,reconstruct_image,channel,metric,
182 distortion,exception);
183 if (status == MagickFalse)
184 return((Image *) NULL);
185 clone_image=CloneImage(image,0,0,MagickTrue,exception);
186 if (clone_image == (Image *) NULL)
187 return((Image *) NULL);
188 (void) SetImageMask(clone_image,(Image *) NULL);
189 difference_image=CloneImage(clone_image,0,0,MagickTrue,exception);
190 clone_image=DestroyImage(clone_image);
191 if (difference_image == (Image *) NULL)
192 return((Image *) NULL);
193 (void) SetImageAlphaChannel(difference_image,OpaqueAlphaChannel);
194 SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
195 highlight_image=CloneImage(image,columns,rows,MagickTrue,exception);
196 if (highlight_image == (Image *) NULL)
198 difference_image=DestroyImage(difference_image);
199 return((Image *) NULL);
201 if (SetImageStorageClass(highlight_image,DirectClass) == MagickFalse)
203 InheritException(exception,&highlight_image->exception);
204 difference_image=DestroyImage(difference_image);
205 highlight_image=DestroyImage(highlight_image);
206 return((Image *) NULL);
208 (void) SetImageMask(highlight_image,(Image *) NULL);
209 (void) SetImageAlphaChannel(highlight_image,OpaqueAlphaChannel);
210 (void) QueryMagickColor(
"#f1001ecc",&highlight,exception);
211 artifact=GetImageArtifact(image,
"compare:highlight-color");
212 if (artifact != (
const char *) NULL)
213 (void) QueryMagickColor(artifact,&highlight,exception);
214 (void) QueryMagickColor(
"#ffffffcc",&lowlight,exception);
215 artifact=GetImageArtifact(image,
"compare:lowlight-color");
216 if (artifact != (
const char *) NULL)
217 (void) QueryMagickColor(artifact,&lowlight,exception);
218 if (highlight_image->colorspace == CMYKColorspace)
220 ConvertRGBToCMYK(&highlight);
221 ConvertRGBToCMYK(&lowlight);
226 GetMagickPixelPacket(image,&zero);
227 image_view=AcquireVirtualCacheView(image,exception);
228 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
229 highlight_view=AcquireAuthenticCacheView(highlight_image,exception);
230#if defined(MAGICKCORE_OPENMP_SUPPORT)
231 #pragma omp parallel for schedule(static) shared(status) \
232 magick_number_threads(image,highlight_image,rows,1)
234 for (y=0; y < (ssize_t) rows; y++)
244 *magick_restrict indexes,
245 *magick_restrict reconstruct_indexes;
252 *magick_restrict highlight_indexes;
260 if (status == MagickFalse)
262 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
263 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
264 r=QueueCacheViewAuthenticPixels(highlight_view,0,y,columns,1,exception);
265 if ((p == (
const PixelPacket *) NULL) ||
266 (q == (
const PixelPacket *) NULL) || (r == (PixelPacket *) NULL))
271 indexes=GetCacheViewVirtualIndexQueue(image_view);
272 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
273 highlight_indexes=GetCacheViewAuthenticIndexQueue(highlight_view);
275 reconstruct_pixel=zero;
276 for (x=0; x < (ssize_t) columns; x++)
278 SetMagickPixelPacket(image,p,indexes == (IndexPacket *) NULL ? NULL :
280 SetMagickPixelPacket(reconstruct_image,q,reconstruct_indexes ==
281 (IndexPacket *) NULL ? NULL : reconstruct_indexes+x,&reconstruct_pixel);
282 if (IsMagickColorSimilar(&pixel,&reconstruct_pixel) == MagickFalse)
283 SetPixelPacket(highlight_image,&highlight,r,highlight_indexes ==
284 (IndexPacket *) NULL ? NULL : highlight_indexes+x);
286 SetPixelPacket(highlight_image,&lowlight,r,highlight_indexes ==
287 (IndexPacket *) NULL ? NULL : highlight_indexes+x);
292 sync=SyncCacheViewAuthenticPixels(highlight_view,exception);
293 if (sync == MagickFalse)
296 highlight_view=DestroyCacheView(highlight_view);
297 reconstruct_view=DestroyCacheView(reconstruct_view);
298 image_view=DestroyCacheView(image_view);
299 (void) CompositeImage(difference_image,image->compose,highlight_image,0,0);
300 highlight_image=DestroyImage(highlight_image);
301 if (status == MagickFalse)
302 difference_image=DestroyImage(difference_image);
303 return(difference_image);
342MagickExport MagickBooleanType GetImageDistortion(Image *image,
343 const Image *reconstruct_image,
const MetricType metric,
double *distortion,
344 ExceptionInfo *exception)
349 status=GetImageChannelDistortion(image,reconstruct_image,CompositeChannels,
350 metric,distortion,exception);
354static MagickBooleanType GetAESimilarity(
const Image *image,
355 const Image *reconstruct_image,
const ChannelType channel,
double *similarity,
356 ExceptionInfo *exception)
380 fuzz=GetFuzzyColorDistance(image,reconstruct_image);
381 SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
382 image_view=AcquireVirtualCacheView(image,exception);
383 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
384#if defined(MAGICKCORE_OPENMP_SUPPORT)
385 #pragma omp parallel for schedule(static) shared(similarity,status) \
386 magick_number_threads(image,image,rows,1)
388 for (y=0; y < (ssize_t) rows; y++)
391 *magick_restrict indexes,
392 *magick_restrict reconstruct_indexes;
399 channel_similarity[CompositeChannels+1] = { 0.0 };
405 if (status == MagickFalse)
407 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
408 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
409 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
414 indexes=GetCacheViewVirtualIndexQueue(image_view);
415 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
416 (void) memset(channel_similarity,0,
sizeof(channel_similarity));
417 for (x=0; x < (ssize_t) columns; x++)
424 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
425 ((double) QuantumRange-(double) OpaqueOpacity));
426 Da=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(q) :
427 ((double) QuantumRange-(double) OpaqueOpacity));
428 if ((channel & RedChannel) != 0)
430 error=Sa*(double) GetPixelRed(p)-Da*(double)
432 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
434 channel_similarity[RedChannel]+=fabs(QuantumScale*error);
435 channel_similarity[CompositeChannels]+=fabs(QuantumScale*error);
438 if ((channel & GreenChannel) != 0)
440 error=Sa*(double) GetPixelGreen(p)-Da*(double)
442 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
444 channel_similarity[GreenChannel]+=fabs(QuantumScale*error);
445 channel_similarity[CompositeChannels]+=fabs(QuantumScale*error);
448 if ((channel & BlueChannel) != 0)
450 error=Sa*(double) GetPixelBlue(p)-Da*(double)
452 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
454 channel_similarity[BlueChannel]+=fabs(QuantumScale*error);
455 channel_similarity[CompositeChannels]+=fabs(QuantumScale*error);
458 if (((channel & OpacityChannel) != 0) &&
459 (image->matte != MagickFalse))
461 error=(double) GetPixelOpacity(p)-(double) GetPixelOpacity(q);
462 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
464 channel_similarity[OpacityChannel]+=fabs(QuantumScale*error);
465 channel_similarity[CompositeChannels]+=fabs(QuantumScale*error);
468 if (((channel & IndexChannel) != 0) &&
469 (image->colorspace == CMYKColorspace))
471 error=Sa*(double) indexes[x]-Da*(
double) reconstruct_indexes[x];
472 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
474 channel_similarity[IndexChannel]+=fabs(QuantumScale*error);
475 channel_similarity[CompositeChannels]+=fabs(QuantumScale*error);
481#if defined(MAGICKCORE_OPENMP_SUPPORT)
482 #pragma omp critical (MagickCore_GetAESimilarity)
484 for (i=0; i <= (ssize_t) CompositeChannels; i++)
485 similarity[i]+=channel_similarity[i];
487 similarity[CompositeChannels]/=(double) GetNumberChannels(image,channel);
488 reconstruct_view=DestroyCacheView(reconstruct_view);
489 image_view=DestroyCacheView(image_view);
490 area=MagickSafeReciprocal((
double) columns*rows);
491 for (j=0; j <= CompositeChannels; j++)
496static MagickBooleanType GetFUZZSimilarity(
const Image *image,
497 const Image *reconstruct_image,
const ChannelType channel,
498 double *similarity,ExceptionInfo *exception)
519 fuzz=GetFuzzyColorDistance(image,reconstruct_image);
520 SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
521 image_view=AcquireVirtualCacheView(image,exception);
522 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
523#if defined(MAGICKCORE_OPENMP_SUPPORT)
524 #pragma omp parallel for schedule(static) shared(status) \
525 magick_number_threads(image,image,rows,1)
527 for (y=0; y < (ssize_t) rows; y++)
531 channel_similarity[CompositeChannels+1] = { 0.0 };
534 *magick_restrict indexes,
535 *magick_restrict reconstruct_indexes;
545 if (status == MagickFalse)
547 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
548 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
549 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
554 indexes=GetCacheViewVirtualIndexQueue(image_view);
555 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
556 for (x=0; x < (ssize_t) columns; x++)
563 Sa=QuantumScale*(image->matte != MagickFalse ? (double)
564 GetPixelAlpha(p) : ((double) QuantumRange-(double) OpaqueOpacity));
565 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
566 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
568 if ((channel & RedChannel) != 0)
570 error=QuantumScale*(Sa*GetPixelRed(p)-Da*GetPixelRed(q));
571 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
573 channel_similarity[RedChannel]+=error*error;
574 channel_similarity[CompositeChannels]+=error*error;
578 if ((channel & GreenChannel) != 0)
580 error=QuantumScale*(Sa*GetPixelGreen(p)-Da*GetPixelGreen(q));
581 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
583 channel_similarity[GreenChannel]+=error*error;
584 channel_similarity[CompositeChannels]+=error*error;
588 if ((channel & BlueChannel) != 0)
590 error=QuantumScale*(Sa*GetPixelBlue(p)-Da*GetPixelBlue(q));
591 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
593 channel_similarity[BlueChannel]+=error*error;
594 channel_similarity[CompositeChannels]+=error*error;
598 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
600 error=QuantumScale*((double) GetPixelOpacity(p)-GetPixelOpacity(q));
601 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
603 channel_similarity[OpacityChannel]+=error*error;
604 channel_similarity[CompositeChannels]+=error*error;
608 if (((channel & IndexChannel) != 0) &&
609 (image->colorspace == CMYKColorspace))
611 error=QuantumScale*(Sa*GetPixelIndex(indexes+x)-Da*
612 GetPixelIndex(reconstruct_indexes+x));
613 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
615 channel_similarity[BlackChannel]+=error*error;
616 channel_similarity[CompositeChannels]+=error*error;
623#if defined(MAGICKCORE_OPENMP_SUPPORT)
624 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
628 for (i=0; i <= (ssize_t) CompositeChannels; i++)
629 similarity[i]+=channel_similarity[i];
632 reconstruct_view=DestroyCacheView(reconstruct_view);
633 image_view=DestroyCacheView(image_view);
634 area=MagickSafeReciprocal(area);
635 for (i=0; i <= (ssize_t) CompositeChannels; i++)
640static MagickBooleanType GetPDCSimilarity(
const Image *image,
641 const Image *reconstruct_image,
const ChannelType channel,
double *similarity,
642 ExceptionInfo *exception)
666 fuzz=GetFuzzyColorDistance(image,reconstruct_image);
667 SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
668 image_view=AcquireVirtualCacheView(image,exception);
669 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
670#if defined(MAGICKCORE_OPENMP_SUPPORT)
671 #pragma omp parallel for schedule(static) shared(similarity,status) \
672 magick_number_threads(image,image,rows,1)
674 for (y=0; y < (ssize_t) rows; y++)
677 *magick_restrict indexes,
678 *magick_restrict reconstruct_indexes;
685 channel_similarity[CompositeChannels+1] = { 0.0 };
691 if (status == MagickFalse)
693 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
694 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
695 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
700 indexes=GetCacheViewVirtualIndexQueue(image_view);
701 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
702 (void) memset(channel_similarity,0,
sizeof(channel_similarity));
703 for (x=0; x < (ssize_t) columns; x++)
713 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
714 ((double) QuantumRange-(double) OpaqueOpacity));
715 Da=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(q) :
716 ((double) QuantumRange-(double) OpaqueOpacity));
717 if ((channel & RedChannel) != 0)
719 error=Sa*(double) GetPixelRed(p)-Da*(double)
721 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
723 channel_similarity[RedChannel]++;
727 if ((channel & GreenChannel) != 0)
729 error=Sa*(double) GetPixelGreen(p)-Da*(double)
731 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
733 channel_similarity[GreenChannel]++;
737 if ((channel & BlueChannel) != 0)
739 error=Sa*(double) GetPixelBlue(p)-Da*(double)
741 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
743 channel_similarity[BlueChannel]++;
747 if (((channel & OpacityChannel) != 0) &&
748 (image->matte != MagickFalse))
750 error=(double) GetPixelOpacity(p)-(double) GetPixelOpacity(q);
751 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
753 channel_similarity[OpacityChannel]++;
757 if (((channel & IndexChannel) != 0) &&
758 (image->colorspace == CMYKColorspace))
760 error=Sa*(double) indexes[x]-Da*(
double) reconstruct_indexes[x];
761 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
763 channel_similarity[IndexChannel]++;
768 channel_similarity[CompositeChannels]++;
772#if defined(MAGICKCORE_OPENMP_SUPPORT)
773 #pragma omp critical (MagickCore_GetAESimilarity)
775 for (i=0; i <= (ssize_t) CompositeChannels; i++)
776 similarity[i]+=channel_similarity[i];
778 reconstruct_view=DestroyCacheView(reconstruct_view);
779 image_view=DestroyCacheView(image_view);
780 area=MagickSafeReciprocal((
double) columns*rows);
781 for (j=0; j <= CompositeChannels; j++)
786static MagickBooleanType GetMAESimilarity(
const Image *image,
787 const Image *reconstruct_image,
const ChannelType channel,
788 double *similarity,ExceptionInfo *exception)
806 SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
807 image_view=AcquireVirtualCacheView(image,exception);
808 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
809#if defined(MAGICKCORE_OPENMP_SUPPORT)
810 #pragma omp parallel for schedule(static) shared(status) \
811 magick_number_threads(image,image,rows,1)
813 for (y=0; y < (ssize_t) rows; y++)
816 channel_similarity[CompositeChannels+1];
819 *magick_restrict indexes,
820 *magick_restrict reconstruct_indexes;
830 if (status == MagickFalse)
832 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
833 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
834 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
839 indexes=GetCacheViewVirtualIndexQueue(image_view);
840 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
841 (void) memset(channel_similarity,0,
sizeof(channel_similarity));
842 for (x=0; x < (ssize_t) columns; x++)
849 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
850 ((double) QuantumRange-(double) OpaqueOpacity));
851 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
852 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
854 if ((channel & RedChannel) != 0)
856 distance=QuantumScale*fabs(Sa*(
double) GetPixelRed(p)-Da*
857 (
double) GetPixelRed(q));
858 channel_similarity[RedChannel]+=distance;
859 channel_similarity[CompositeChannels]+=distance;
861 if ((channel & GreenChannel) != 0)
863 distance=QuantumScale*fabs(Sa*(
double) GetPixelGreen(p)-Da*
864 (
double) GetPixelGreen(q));
865 channel_similarity[GreenChannel]+=distance;
866 channel_similarity[CompositeChannels]+=distance;
868 if ((channel & BlueChannel) != 0)
870 distance=QuantumScale*fabs(Sa*(
double) GetPixelBlue(p)-Da*
871 (
double) GetPixelBlue(q));
872 channel_similarity[BlueChannel]+=distance;
873 channel_similarity[CompositeChannels]+=distance;
875 if (((channel & OpacityChannel) != 0) &&
876 (image->matte != MagickFalse))
878 distance=QuantumScale*fabs((double) GetPixelOpacity(p)-(double)
880 channel_similarity[OpacityChannel]+=distance;
881 channel_similarity[CompositeChannels]+=distance;
883 if (((channel & IndexChannel) != 0) &&
884 (image->colorspace == CMYKColorspace))
886 distance=QuantumScale*fabs(Sa*(double) GetPixelIndex(indexes+x)-Da*
887 (double) GetPixelIndex(reconstruct_indexes+x));
888 channel_similarity[BlackChannel]+=distance;
889 channel_similarity[CompositeChannels]+=distance;
894#if defined(MAGICKCORE_OPENMP_SUPPORT)
895 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
897 for (i=0; i <= (ssize_t) CompositeChannels; i++)
898 similarity[i]+=channel_similarity[i];
900 reconstruct_view=DestroyCacheView(reconstruct_view);
901 image_view=DestroyCacheView(image_view);
902 for (i=0; i <= (ssize_t) CompositeChannels; i++)
903 similarity[i]/=((
double) columns*rows);
904 similarity[CompositeChannels]/=(double) GetNumberChannels(image,channel);
908static MagickBooleanType GetMEPPSimilarity(Image *image,
909 const Image *reconstruct_image,
const ChannelType channel,
double *similarity,
910 ExceptionInfo *exception)
917 maximum_error = -MagickMaximumValue,
932 SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
933 image_view=AcquireVirtualCacheView(image,exception);
934 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
935#if defined(MAGICKCORE_OPENMP_SUPPORT)
936 #pragma omp parallel for schedule(static) shared(maximum_error,status) \
937 magick_number_threads(image,image,rows,1)
939 for (y=0; y < (ssize_t) rows; y++)
942 channel_similarity[CompositeChannels+1] = { 0.0 },
943 local_maximum = maximum_error,
944 local_mean_error = 0.0;
947 *magick_restrict indexes,
948 *magick_restrict reconstruct_indexes;
958 if (status == MagickFalse)
960 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
961 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
962 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
967 indexes=GetCacheViewVirtualIndexQueue(image_view);
968 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
969 (void) memset(channel_similarity,0,
sizeof(channel_similarity));
970 for (x=0; x < (ssize_t) columns; x++)
977 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
978 ((double) QuantumRange-(double) OpaqueOpacity));
979 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
980 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
982 if ((channel & RedChannel) != 0)
984 distance=QuantumScale*fabs(Sa*(
double) GetPixelRed(p)-Da*
985 (
double) GetPixelRed(q));
986 channel_similarity[RedChannel]+=distance;
987 channel_similarity[CompositeChannels]+=distance;
988 local_mean_error+=distance*distance;
989 if (distance > local_maximum)
990 local_maximum=distance;
992 if ((channel & GreenChannel) != 0)
994 distance=QuantumScale*fabs(Sa*(
double) GetPixelGreen(p)-Da*
995 (
double) GetPixelGreen(q));
996 channel_similarity[GreenChannel]+=distance;
997 channel_similarity[CompositeChannels]+=distance;
998 local_mean_error+=distance*distance;
999 if (distance > local_maximum)
1000 local_maximum=distance;
1002 if ((channel & BlueChannel) != 0)
1004 distance=QuantumScale*fabs(Sa*(
double) GetPixelBlue(p)-Da*
1005 (
double) GetPixelBlue(q));
1006 channel_similarity[BlueChannel]+=distance;
1007 channel_similarity[CompositeChannels]+=distance;
1008 local_mean_error+=distance*distance;
1009 if (distance > local_maximum)
1010 local_maximum=distance;
1012 if (((channel & OpacityChannel) != 0) &&
1013 (image->matte != MagickFalse))
1015 distance=QuantumScale*fabs((double) GetPixelOpacity(p)-(double)
1016 GetPixelOpacity(q));
1017 channel_similarity[OpacityChannel]+=distance;
1018 channel_similarity[CompositeChannels]+=distance;
1019 local_mean_error+=distance*distance;
1020 if (distance > local_maximum)
1021 local_maximum=distance;
1023 if (((channel & IndexChannel) != 0) &&
1024 (image->colorspace == CMYKColorspace))
1026 distance=QuantumScale*fabs(Sa*(double) GetPixelIndex(indexes+x)-Da*
1027 (double) GetPixelIndex(reconstruct_indexes+x));
1028 channel_similarity[BlackChannel]+=distance;
1029 channel_similarity[CompositeChannels]+=distance;
1030 local_mean_error+=distance*distance;
1031 if (distance > local_maximum)
1032 local_maximum=distance;
1037#if defined(MAGICKCORE_OPENMP_SUPPORT)
1038 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
1041 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1042 similarity[i]+=channel_similarity[i];
1043 mean_error+=local_mean_error;
1044 if (local_maximum > maximum_error)
1045 maximum_error=local_maximum;
1048 reconstruct_view=DestroyCacheView(reconstruct_view);
1049 image_view=DestroyCacheView(image_view);
1050 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1051 similarity[i]/=((
double) columns*rows);
1052 similarity[CompositeChannels]/=(double) GetNumberChannels(image,channel);
1053 image->error.mean_error_per_pixel=QuantumRange*similarity[CompositeChannels];
1054 image->error.normalized_mean_error=mean_error/((double) columns*rows);
1055 image->error.normalized_maximum_error=maximum_error;
1059static MagickBooleanType GetMSESimilarity(
const Image *image,
1060 const Image *reconstruct_image,
const ChannelType channel,
1061 double *similarity,ExceptionInfo *exception)
1082 SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
1083 image_view=AcquireVirtualCacheView(image,exception);
1084 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1085#if defined(MAGICKCORE_OPENMP_SUPPORT)
1086 #pragma omp parallel for schedule(static) shared(similarity,status) \
1087 magick_number_threads(image,image,rows,1)
1089 for (y=0; y < (ssize_t) rows; y++)
1092 channel_similarity[CompositeChannels+1] = { 0.0 };
1095 *magick_restrict indexes,
1096 *magick_restrict reconstruct_indexes;
1106 if (status == MagickFalse)
1108 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1109 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1110 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
1115 indexes=GetCacheViewVirtualIndexQueue(image_view);
1116 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1117 for (x=0; x < (ssize_t) columns; x++)
1124 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
1125 ((double) QuantumRange-(double) OpaqueOpacity));
1126 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1127 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
1129 if ((channel & RedChannel) != 0)
1131 distance=QuantumScale*(Sa*(double) GetPixelRed(p)-Da*(double)
1133 channel_similarity[RedChannel]+=distance*distance;
1134 channel_similarity[CompositeChannels]+=distance*distance;
1136 if ((channel & GreenChannel) != 0)
1138 distance=QuantumScale*(Sa*(double) GetPixelGreen(p)-Da*(double)
1140 channel_similarity[GreenChannel]+=distance*distance;
1141 channel_similarity[CompositeChannels]+=distance*distance;
1143 if ((channel & BlueChannel) != 0)
1145 distance=QuantumScale*(Sa*(double) GetPixelBlue(p)-Da*(double)
1147 channel_similarity[BlueChannel]+=distance*distance;
1148 channel_similarity[CompositeChannels]+=distance*distance;
1150 if (((channel & OpacityChannel) != 0) &&
1151 (image->matte != MagickFalse))
1153 distance=QuantumScale*((double) GetPixelOpacity(p)-(double)
1154 GetPixelOpacity(q));
1155 channel_similarity[OpacityChannel]+=distance*distance;
1156 channel_similarity[CompositeChannels]+=distance*distance;
1158 if (((channel & IndexChannel) != 0) &&
1159 (image->colorspace == CMYKColorspace) &&
1160 (reconstruct_image->colorspace == CMYKColorspace))
1162 distance=QuantumScale*(Sa*(double) GetPixelIndex(indexes+x)-Da*
1163 (double) GetPixelIndex(reconstruct_indexes+x));
1164 channel_similarity[BlackChannel]+=distance*distance;
1165 channel_similarity[CompositeChannels]+=distance*distance;
1170#if defined(MAGICKCORE_OPENMP_SUPPORT)
1171 #pragma omp critical (MagickCore_GetMeanSquaredError)
1173 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1174 similarity[i]+=channel_similarity[i];
1176 reconstruct_view=DestroyCacheView(reconstruct_view);
1177 image_view=DestroyCacheView(image_view);
1178 area=MagickSafeReciprocal((
double) columns*rows);
1179 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1180 similarity[i]*=area;
1181 similarity[CompositeChannels]/=(double) GetNumberChannels(image,channel);
1185static MagickBooleanType GetNCCSimilarity(
const Image *image,
1186 const Image *reconstruct_image,
const ChannelType channel,
double *similarity,
1187 ExceptionInfo *exception)
1189#define SimilarityImageTag "Similarity/Image"
1197 *reconstruct_statistics;
1200 alpha_variance[CompositeChannels+1] = { 0.0 },
1201 beta_variance[CompositeChannels+1] = { 0.0 };
1220 image_statistics=GetImageChannelStatistics(image,exception);
1221 reconstruct_statistics=GetImageChannelStatistics(reconstruct_image,exception);
1222 if ((image_statistics == (ChannelStatistics *) NULL) ||
1223 (reconstruct_statistics == (ChannelStatistics *) NULL))
1225 if (image_statistics != (ChannelStatistics *) NULL)
1226 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1228 if (reconstruct_statistics != (ChannelStatistics *) NULL)
1229 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1230 reconstruct_statistics);
1231 return(MagickFalse);
1233 (void) memset(similarity,0,(CompositeChannels+1)*
sizeof(*similarity));
1236 SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
1237 image_view=AcquireVirtualCacheView(image,exception);
1238 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1239#if defined(MAGICKCORE_OPENMP_SUPPORT)
1240 #pragma omp parallel for schedule(static) shared(status) \
1241 magick_number_threads(image,image,rows,1)
1243 for (y=0; y < (ssize_t) rows; y++)
1246 *magick_restrict indexes,
1247 *magick_restrict reconstruct_indexes;
1254 channel_alpha_variance[CompositeChannels+1] = { 0.0 },
1255 channel_beta_variance[CompositeChannels+1] = { 0.0 },
1256 channel_similarity[CompositeChannels+1] = { 0.0 };
1261 if (status == MagickFalse)
1263 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1264 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1265 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
1270 indexes=GetCacheViewVirtualIndexQueue(image_view);
1271 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1272 for (x=0; x < (ssize_t) columns; x++)
1280 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
1281 (double) QuantumRange);
1282 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1283 (double) GetPixelAlpha(q) : (double) QuantumRange);
1284 if ((channel & RedChannel) != 0)
1286 alpha=QuantumScale*(Sa*(double) GetPixelRed(p)-
1287 image_statistics[RedChannel].mean);
1288 beta=QuantumScale*(Da*(double) GetPixelRed(q)-
1289 reconstruct_statistics[RedChannel].mean);
1290 channel_similarity[RedChannel]+=alpha*beta;
1291 channel_similarity[CompositeChannels]+=alpha*beta;
1292 channel_alpha_variance[RedChannel]+=alpha*alpha;
1293 channel_alpha_variance[CompositeChannels]+=alpha*alpha;
1294 channel_beta_variance[RedChannel]+=beta*beta;
1295 channel_beta_variance[CompositeChannels]+=beta*beta;
1297 if ((channel & GreenChannel) != 0)
1299 alpha=QuantumScale*(Sa*(double) GetPixelGreen(p)-
1300 image_statistics[GreenChannel].mean);
1301 beta=QuantumScale*(Da*(double) GetPixelGreen(q)-
1302 reconstruct_statistics[GreenChannel].mean);
1303 channel_similarity[GreenChannel]+=alpha*beta;
1304 channel_similarity[CompositeChannels]+=alpha*beta;
1305 channel_alpha_variance[GreenChannel]+=alpha*alpha;
1306 channel_alpha_variance[CompositeChannels]+=alpha*alpha;
1307 channel_beta_variance[GreenChannel]+=beta*beta;
1308 channel_beta_variance[CompositeChannels]+=beta*beta;
1310 if ((channel & BlueChannel) != 0)
1312 alpha=QuantumScale*(Sa*(double) GetPixelBlue(p)-
1313 image_statistics[BlueChannel].mean);
1314 beta=QuantumScale*(Da*(double) GetPixelBlue(q)-
1315 reconstruct_statistics[BlueChannel].mean);
1316 channel_similarity[BlueChannel]+=alpha*beta;
1317 channel_alpha_variance[BlueChannel]+=alpha*alpha;
1318 channel_beta_variance[BlueChannel]+=beta*beta;
1320 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
1322 alpha=QuantumScale*((double) GetPixelAlpha(p)-
1323 image_statistics[AlphaChannel].mean);
1324 beta=QuantumScale*((double) GetPixelAlpha(q)-
1325 reconstruct_statistics[AlphaChannel].mean);
1326 channel_similarity[OpacityChannel]+=alpha*beta;
1327 channel_similarity[CompositeChannels]+=alpha*beta;
1328 channel_alpha_variance[OpacityChannel]+=alpha*alpha;
1329 channel_alpha_variance[CompositeChannels]+=alpha*alpha;
1330 channel_beta_variance[OpacityChannel]+=beta*beta;
1331 channel_beta_variance[CompositeChannels]+=beta*beta;
1333 if (((channel & IndexChannel) != 0) &&
1334 (image->colorspace == CMYKColorspace) &&
1335 (reconstruct_image->colorspace == CMYKColorspace))
1337 alpha=QuantumScale*(Sa*(double) GetPixelIndex(indexes+x)-
1338 image_statistics[BlackChannel].mean);
1339 beta=QuantumScale*(Da*(double) GetPixelIndex(reconstruct_indexes+
1340 x)-reconstruct_statistics[BlackChannel].mean);
1341 channel_similarity[BlackChannel]+=alpha*beta;
1342 channel_similarity[CompositeChannels]+=alpha*beta;
1343 channel_alpha_variance[BlackChannel]+=alpha*alpha;
1344 channel_alpha_variance[CompositeChannels]+=alpha*alpha;
1345 channel_beta_variance[BlackChannel]+=beta*beta;
1346 channel_beta_variance[CompositeChannels]+=beta*beta;
1351#if defined(MAGICKCORE_OPENMP_SUPPORT)
1352 #pragma omp critical (GetNCCSimilarity)
1358 for (j=0; j <= (ssize_t) CompositeChannels; j++)
1360 similarity[j]+=channel_similarity[j];
1361 alpha_variance[j]+=channel_alpha_variance[j];
1362 beta_variance[j]+=channel_beta_variance[j];
1365 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1370#if defined(MAGICKCORE_OPENMP_SUPPORT)
1374 proceed=SetImageProgress(image,SimilarityImageTag,progress,rows);
1375 if (proceed == MagickFalse)
1379 reconstruct_view=DestroyCacheView(reconstruct_view);
1380 image_view=DestroyCacheView(image_view);
1384 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1385 similarity[i]*=MagickSafeReciprocal(sqrt(alpha_variance[i])*
1386 sqrt(beta_variance[i]));
1390 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1391 reconstruct_statistics);
1392 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1397static MagickBooleanType GetPASimilarity(
const Image *image,
1398 const Image *reconstruct_image,
const ChannelType channel,
1399 double *similarity,ExceptionInfo *exception)
1416 (void) memset(similarity,0,(CompositeChannels+1)*
sizeof(*similarity));
1417 SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
1418 image_view=AcquireVirtualCacheView(image,exception);
1419 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1420#if defined(MAGICKCORE_OPENMP_SUPPORT)
1421 #pragma omp parallel for schedule(static) shared(status) \
1422 magick_number_threads(image,image,rows,1)
1424 for (y=0; y < (ssize_t) rows; y++)
1427 channel_similarity[CompositeChannels+1];
1430 *magick_restrict indexes,
1431 *magick_restrict reconstruct_indexes;
1441 if (status == MagickFalse)
1443 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1444 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1445 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
1450 indexes=GetCacheViewVirtualIndexQueue(image_view);
1451 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1452 (void) memset(channel_similarity,0,(CompositeChannels+1)*
1453 sizeof(*channel_similarity));
1454 for (x=0; x < (ssize_t) columns; x++)
1461 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
1462 ((double) QuantumRange-(double) OpaqueOpacity));
1463 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1464 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
1466 if ((channel & RedChannel) != 0)
1468 distance=QuantumScale*fabs(Sa*(
double) GetPixelRed(p)-Da*
1469 (
double) GetPixelRed(q));
1470 if (distance > channel_similarity[RedChannel])
1471 channel_similarity[RedChannel]=distance;
1472 if (distance > channel_similarity[CompositeChannels])
1473 channel_similarity[CompositeChannels]=distance;
1475 if ((channel & GreenChannel) != 0)
1477 distance=QuantumScale*fabs(Sa*(
double) GetPixelGreen(p)-Da*
1478 (
double) GetPixelGreen(q));
1479 if (distance > channel_similarity[GreenChannel])
1480 channel_similarity[GreenChannel]=distance;
1481 if (distance > channel_similarity[CompositeChannels])
1482 channel_similarity[CompositeChannels]=distance;
1484 if ((channel & BlueChannel) != 0)
1486 distance=QuantumScale*fabs(Sa*(
double) GetPixelBlue(p)-Da*
1487 (
double) GetPixelBlue(q));
1488 if (distance > channel_similarity[BlueChannel])
1489 channel_similarity[BlueChannel]=distance;
1490 if (distance > channel_similarity[CompositeChannels])
1491 channel_similarity[CompositeChannels]=distance;
1493 if (((channel & OpacityChannel) != 0) &&
1494 (image->matte != MagickFalse))
1496 distance=QuantumScale*fabs((double) GetPixelOpacity(p)-(double)
1497 GetPixelOpacity(q));
1498 if (distance > channel_similarity[OpacityChannel])
1499 channel_similarity[OpacityChannel]=distance;
1500 if (distance > channel_similarity[CompositeChannels])
1501 channel_similarity[CompositeChannels]=distance;
1503 if (((channel & IndexChannel) != 0) &&
1504 (image->colorspace == CMYKColorspace) &&
1505 (reconstruct_image->colorspace == CMYKColorspace))
1507 distance=QuantumScale*fabs(Sa*(double) GetPixelIndex(indexes+x)-Da*
1508 (double) GetPixelIndex(reconstruct_indexes+x));
1509 if (distance > channel_similarity[BlackChannel])
1510 channel_similarity[BlackChannel]=distance;
1511 if (distance > channel_similarity[CompositeChannels])
1512 channel_similarity[CompositeChannels]=distance;
1517#if defined(MAGICKCORE_OPENMP_SUPPORT)
1518 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
1520 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1521 if (channel_similarity[i] > similarity[i])
1522 similarity[i]=channel_similarity[i];
1524 reconstruct_view=DestroyCacheView(reconstruct_view);
1525 image_view=DestroyCacheView(image_view);
1529static MagickBooleanType GetPSNRSimilarity(
const Image *image,
1530 const Image *reconstruct_image,
const ChannelType channel,
1531 double *similarity,ExceptionInfo *exception)
1536 status=GetMSESimilarity(image,reconstruct_image,channel,similarity,
1538 if ((channel & RedChannel) != 0)
1539 similarity[RedChannel]=10.0*MagickSafeLog10(MagickSafeReciprocal(
1540 similarity[RedChannel]))/MagickSafePSNRRecipicol(10.0);
1541 if ((channel & GreenChannel) != 0)
1542 similarity[GreenChannel]=10.0*MagickSafeLog10(MagickSafeReciprocal(
1543 similarity[GreenChannel]))/MagickSafePSNRRecipicol(10.0);
1544 if ((channel & BlueChannel) != 0)
1545 similarity[BlueChannel]=10.0*MagickSafeLog10(MagickSafeReciprocal(
1546 similarity[BlueChannel]))/MagickSafePSNRRecipicol(10.0);
1547 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
1548 similarity[OpacityChannel]=10.0*MagickSafeLog10(MagickSafeReciprocal(
1549 similarity[OpacityChannel]))/MagickSafePSNRRecipicol(10.0);
1550 if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
1551 similarity[BlackChannel]=10.0*MagickSafeLog10(MagickSafeReciprocal(
1552 similarity[BlackChannel]))/MagickSafePSNRRecipicol(10.0);
1553 similarity[CompositeChannels]=10.0*MagickSafeLog10(MagickSafeReciprocal(
1554 similarity[CompositeChannels]))/MagickSafePSNRRecipicol(10.0);
1558static MagickBooleanType GetPHASHSimilarity(
const Image *image,
1559 const Image *reconstruct_image,
const ChannelType channel,
double *similarity,
1560 ExceptionInfo *exception)
1562 ChannelPerceptualHash
1576 image_phash=GetImageChannelPerceptualHash(image,exception);
1577 if (image_phash == (ChannelPerceptualHash *) NULL)
1578 return(MagickFalse);
1579 reconstruct_phash=GetImageChannelPerceptualHash(reconstruct_image,exception);
1580 if (reconstruct_phash == (ChannelPerceptualHash *) NULL)
1582 image_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(image_phash);
1583 return(MagickFalse);
1585 for (i=0; i < MaximumNumberOfImageMoments; i++)
1590 if ((channel & RedChannel) != 0)
1592 error=reconstruct_phash[RedChannel].P[i]-image_phash[RedChannel].P[i];
1593 if (IsNaN(error) != 0)
1595 difference=error*error;
1596 similarity[RedChannel]+=difference;
1597 similarity[CompositeChannels]+=difference;
1599 if ((channel & GreenChannel) != 0)
1601 error=reconstruct_phash[GreenChannel].P[i]-
1602 image_phash[GreenChannel].P[i];
1603 if (IsNaN(error) != 0)
1605 difference=error*error;
1606 similarity[GreenChannel]+=difference;
1607 similarity[CompositeChannels]+=difference;
1609 if ((channel & BlueChannel) != 0)
1611 error=reconstruct_phash[BlueChannel].P[i]-image_phash[BlueChannel].P[i];
1612 if (IsNaN(error) != 0)
1614 difference=error*error;
1615 similarity[BlueChannel]+=difference;
1616 similarity[CompositeChannels]+=difference;
1618 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse) &&
1619 (reconstruct_image->matte != MagickFalse))
1621 error=reconstruct_phash[OpacityChannel].P[i]-
1622 image_phash[OpacityChannel].P[i];
1623 if (IsNaN(error) != 0)
1625 difference=error*error;
1626 similarity[OpacityChannel]+=difference;
1627 similarity[CompositeChannels]+=difference;
1629 if (((channel & IndexChannel) != 0) &&
1630 (image->colorspace == CMYKColorspace) &&
1631 (reconstruct_image->colorspace == CMYKColorspace))
1633 error=reconstruct_phash[IndexChannel].P[i]-
1634 image_phash[IndexChannel].P[i];
1635 if (IsNaN(error) != 0)
1637 difference=error*error;
1638 similarity[IndexChannel]+=difference;
1639 similarity[CompositeChannels]+=difference;
1645 for (i=0; i < MaximumNumberOfImageMoments; i++)
1650 if ((channel & RedChannel) != 0)
1652 error=reconstruct_phash[RedChannel].Q[i]-image_phash[RedChannel].Q[i];
1653 if (IsNaN(error) != 0)
1655 difference=error*error;
1656 similarity[RedChannel]+=difference;
1657 similarity[CompositeChannels]+=difference;
1659 if ((channel & GreenChannel) != 0)
1661 error=reconstruct_phash[GreenChannel].Q[i]-
1662 image_phash[GreenChannel].Q[i];
1663 if (IsNaN(error) != 0)
1665 difference=error*error;
1666 similarity[GreenChannel]+=difference;
1667 similarity[CompositeChannels]+=difference;
1669 if ((channel & BlueChannel) != 0)
1671 error=reconstruct_phash[BlueChannel].Q[i]-image_phash[BlueChannel].Q[i];
1672 if (IsNaN(error) != 0)
1674 difference=error*error;
1675 similarity[BlueChannel]+=difference;
1676 similarity[CompositeChannels]+=difference;
1678 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse) &&
1679 (reconstruct_image->matte != MagickFalse))
1681 error=reconstruct_phash[OpacityChannel].Q[i]-
1682 image_phash[OpacityChannel].Q[i];
1683 if (IsNaN(error) != 0)
1685 difference=error*error;
1686 similarity[OpacityChannel]+=difference;
1687 similarity[CompositeChannels]+=difference;
1689 if (((channel & IndexChannel) != 0) &&
1690 (image->colorspace == CMYKColorspace) &&
1691 (reconstruct_image->colorspace == CMYKColorspace))
1693 error=reconstruct_phash[IndexChannel].Q[i]-
1694 image_phash[IndexChannel].Q[i];
1695 if (IsNaN(error) != 0)
1697 difference=error*error;
1698 similarity[IndexChannel]+=difference;
1699 similarity[CompositeChannels]+=difference;
1702 similarity[CompositeChannels]/=(double) GetNumberChannels(image,channel);
1706 reconstruct_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(
1708 image_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(image_phash);
1712static MagickBooleanType GetRMSESimilarity(
const Image *image,
1713 const Image *reconstruct_image,
const ChannelType channel,
double *similarity,
1714 ExceptionInfo *exception)
1716#define RMSESquareRoot(x) sqrt((x) < 0.0 ? 0.0 : (x))
1721 status=GetMSESimilarity(image,reconstruct_image,channel,similarity,
1723 if ((channel & RedChannel) != 0)
1724 similarity[RedChannel]=RMSESquareRoot(similarity[RedChannel]);
1725 if ((channel & GreenChannel) != 0)
1726 similarity[GreenChannel]=RMSESquareRoot(similarity[GreenChannel]);
1727 if ((channel & BlueChannel) != 0)
1728 similarity[BlueChannel]=RMSESquareRoot(similarity[BlueChannel]);
1729 if (((channel & OpacityChannel) != 0) &&
1730 (image->matte != MagickFalse))
1731 similarity[OpacityChannel]=RMSESquareRoot(similarity[OpacityChannel]);
1732 if (((channel & IndexChannel) != 0) &&
1733 (image->colorspace == CMYKColorspace))
1734 similarity[BlackChannel]=RMSESquareRoot(similarity[BlackChannel]);
1735 similarity[CompositeChannels]=RMSESquareRoot(similarity[CompositeChannels]);
1739MagickExport MagickBooleanType GetImageChannelDistortion(Image *image,
1740 const Image *reconstruct_image,
const ChannelType channel,
1741 const MetricType metric,
double *distortion,ExceptionInfo *exception)
1744 *channel_similarity;
1752 assert(image != (Image *) NULL);
1753 assert(image->signature == MagickCoreSignature);
1754 assert(reconstruct_image != (
const Image *) NULL);
1755 assert(reconstruct_image->signature == MagickCoreSignature);
1756 assert(distortion != (
double *) NULL);
1757 if (IsEventLogging() != MagickFalse)
1758 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
1760 if (metric != PerceptualHashErrorMetric)
1761 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1762 ThrowBinaryException(ImageError,
"ImageMorphologyDiffers",image->filename);
1766 length=CompositeChannels+1UL;
1767 channel_similarity=(
double *) AcquireQuantumMemory(length,
1768 sizeof(*channel_similarity));
1769 if (channel_similarity == (
double *) NULL)
1770 ThrowFatalException(ResourceLimitFatalError,
"MemoryAllocationFailed");
1771 (void) memset(channel_similarity,0,length*
sizeof(*channel_similarity));
1774 case AbsoluteErrorMetric:
1776 status=GetAESimilarity(image,reconstruct_image,channel,
1777 channel_similarity,exception);
1780 case FuzzErrorMetric:
1782 status=GetFUZZSimilarity(image,reconstruct_image,channel,
1783 channel_similarity,exception);
1786 case MeanAbsoluteErrorMetric:
1788 status=GetMAESimilarity(image,reconstruct_image,channel,
1789 channel_similarity,exception);
1792 case MeanErrorPerPixelMetric:
1794 status=GetMEPPSimilarity(image,reconstruct_image,channel,
1795 channel_similarity,exception);
1798 case MeanSquaredErrorMetric:
1800 status=GetMSESimilarity(image,reconstruct_image,channel,
1801 channel_similarity,exception);
1804 case NormalizedCrossCorrelationErrorMetric:
1806 status=GetNCCSimilarity(image,reconstruct_image,channel,
1807 channel_similarity,exception);
1810 case PeakAbsoluteErrorMetric:
1812 status=GetPASimilarity(image,reconstruct_image,channel,
1813 channel_similarity,exception);
1816 case PeakSignalToNoiseRatioMetric:
1818 status=GetPSNRSimilarity(image,reconstruct_image,channel,
1819 channel_similarity,exception);
1822 case PerceptualHashErrorMetric:
1824 status=GetPHASHSimilarity(image,reconstruct_image,channel,
1825 channel_similarity,exception);
1828 case PixelDifferenceCountErrorMetric:
1830 status=GetPDCSimilarity(image,reconstruct_image,channel,
1831 channel_similarity,exception);
1834 case RootMeanSquaredErrorMetric:
1835 case UndefinedErrorMetric:
1838 status=GetRMSESimilarity(image,reconstruct_image,channel,
1839 channel_similarity,exception);
1843 *distortion=channel_similarity[CompositeChannels];
1846 case NormalizedCrossCorrelationErrorMetric:
1848 *distortion=(1.0-(*distortion))/2.0;
1853 if (fabs(*distortion) < MagickEpsilon)
1855 channel_similarity=(
double *) RelinquishMagickMemory(channel_similarity);
1856 (void) FormatImageProperty(image,
"distortion",
"%.*g",GetMagickPrecision(),
1893MagickExport
double *GetImageChannelDistortions(Image *image,
1894 const Image *reconstruct_image,
const MetricType metric,
1895 ExceptionInfo *exception)
1910 assert(image != (Image *) NULL);
1911 assert(image->signature == MagickCoreSignature);
1912 assert(reconstruct_image != (
const Image *) NULL);
1913 assert(reconstruct_image->signature == MagickCoreSignature);
1914 if (IsEventLogging() != MagickFalse)
1915 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
1916 if (metric != PerceptualHashErrorMetric)
1917 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1919 (void) ThrowMagickException(&image->exception,GetMagickModule(),
1920 ImageError,
"ImageMorphologyDiffers",
"`%s'",image->filename);
1921 return((
double *) NULL);
1926 length=CompositeChannels+1UL;
1927 similarity=(
double *) AcquireQuantumMemory(length,
1928 sizeof(*similarity));
1929 if (similarity == (
double *) NULL)
1930 ThrowFatalException(ResourceLimitFatalError,
"MemoryAllocationFailed");
1931 (void) memset(similarity,0,length*
sizeof(*similarity));
1935 case AbsoluteErrorMetric:
1937 status=GetAESimilarity(image,reconstruct_image,CompositeChannels,
1938 similarity,exception);
1941 case FuzzErrorMetric:
1943 status=GetFUZZSimilarity(image,reconstruct_image,CompositeChannels,
1944 similarity,exception);
1947 case MeanAbsoluteErrorMetric:
1949 status=GetMAESimilarity(image,reconstruct_image,CompositeChannels,
1950 similarity,exception);
1953 case MeanErrorPerPixelMetric:
1955 status=GetMEPPSimilarity(image,reconstruct_image,CompositeChannels,
1956 similarity,exception);
1959 case MeanSquaredErrorMetric:
1961 status=GetMSESimilarity(image,reconstruct_image,CompositeChannels,
1962 similarity,exception);
1965 case NormalizedCrossCorrelationErrorMetric:
1967 status=GetNCCSimilarity(image,reconstruct_image,CompositeChannels,
1968 similarity,exception);
1971 case PeakAbsoluteErrorMetric:
1973 status=GetPASimilarity(image,reconstruct_image,CompositeChannels,
1974 similarity,exception);
1977 case PeakSignalToNoiseRatioMetric:
1979 status=GetPSNRSimilarity(image,reconstruct_image,CompositeChannels,
1980 similarity,exception);
1983 case PerceptualHashErrorMetric:
1985 status=GetPHASHSimilarity(image,reconstruct_image,CompositeChannels,
1986 similarity,exception);
1989 case PixelDifferenceCountErrorMetric:
1991 status=GetPDCSimilarity(image,reconstruct_image,CompositeChannels,
1992 similarity,exception);
1995 case RootMeanSquaredErrorMetric:
1996 case UndefinedErrorMetric:
1999 status=GetRMSESimilarity(image,reconstruct_image,CompositeChannels,
2000 similarity,exception);
2004 if (status == MagickFalse)
2006 similarity=(
double *) RelinquishMagickMemory(similarity);
2007 return((
double *) NULL);
2009 distortion=similarity;
2012 case NormalizedCrossCorrelationErrorMetric:
2014 for (i=0; i <= (ssize_t) CompositeChannels; i++)
2015 distortion[i]=(1.0-distortion[i])/2.0;
2020 for (i=0; i <= (ssize_t) CompositeChannels; i++)
2021 if (fabs(distortion[i]) < MagickEpsilon)
2073MagickExport MagickBooleanType IsImagesEqual(Image *image,
2074 const Image *reconstruct_image)
2091 mean_error_per_pixel;
2100 assert(image != (Image *) NULL);
2101 assert(image->signature == MagickCoreSignature);
2102 assert(reconstruct_image != (
const Image *) NULL);
2103 assert(reconstruct_image->signature == MagickCoreSignature);
2104 exception=(&image->exception);
2105 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
2106 ThrowBinaryException(ImageError,
"ImageMorphologyDiffers",image->filename);
2109 mean_error_per_pixel=0.0;
2111 SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
2112 image_view=AcquireVirtualCacheView(image,exception);
2113 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
2114 for (y=0; y < (ssize_t) rows; y++)
2117 *magick_restrict indexes,
2118 *magick_restrict reconstruct_indexes;
2127 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
2128 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
2129 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
2131 indexes=GetCacheViewVirtualIndexQueue(image_view);
2132 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
2133 for (x=0; x < (ssize_t) columns; x++)
2138 distance=fabs((
double) GetPixelRed(p)-(
double) GetPixelRed(q));
2139 mean_error_per_pixel+=distance;
2140 mean_error+=distance*distance;
2141 if (distance > maximum_error)
2142 maximum_error=distance;
2144 distance=fabs((
double) GetPixelGreen(p)-(
double) GetPixelGreen(q));
2145 mean_error_per_pixel+=distance;
2146 mean_error+=distance*distance;
2147 if (distance > maximum_error)
2148 maximum_error=distance;
2150 distance=fabs((
double) GetPixelBlue(p)-(
double) GetPixelBlue(q));
2151 mean_error_per_pixel+=distance;
2152 mean_error+=distance*distance;
2153 if (distance > maximum_error)
2154 maximum_error=distance;
2156 if (image->matte != MagickFalse)
2158 distance=fabs((
double) GetPixelOpacity(p)-(
double)
2159 GetPixelOpacity(q));
2160 mean_error_per_pixel+=distance;
2161 mean_error+=distance*distance;
2162 if (distance > maximum_error)
2163 maximum_error=distance;
2166 if ((image->colorspace == CMYKColorspace) &&
2167 (reconstruct_image->colorspace == CMYKColorspace))
2169 distance=fabs((
double) GetPixelIndex(indexes+x)-(
double)
2170 GetPixelIndex(reconstruct_indexes+x));
2171 mean_error_per_pixel+=distance;
2172 mean_error+=distance*distance;
2173 if (distance > maximum_error)
2174 maximum_error=distance;
2181 reconstruct_view=DestroyCacheView(reconstruct_view);
2182 image_view=DestroyCacheView(image_view);
2183 gamma=MagickSafeReciprocal(area);
2184 image->error.mean_error_per_pixel=gamma*mean_error_per_pixel;
2185 image->error.normalized_mean_error=gamma*QuantumScale*QuantumScale*mean_error;
2186 image->error.normalized_maximum_error=QuantumScale*maximum_error;
2187 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
2226static double GetSimilarityMetric(
const Image *image,
2227 const Image *reconstruct_image,
const MetricType metric,
2228 const ssize_t x_offset,
const ssize_t y_offset,ExceptionInfo *exception)
2231 *channel_similarity,
2235 *sans_exception = AcquireExceptionInfo();
2241 status = MagickTrue;
2247 length = CompositeChannels+1UL;
2249 SetGeometry(reconstruct_image,&geometry);
2250 geometry.x=x_offset;
2251 geometry.y=y_offset;
2252 similarity_image=CropImage(image,&geometry,sans_exception);
2253 sans_exception=DestroyExceptionInfo(sans_exception);
2254 if (similarity_image == (Image *) NULL)
2259 channel_similarity=(
double *) AcquireQuantumMemory(length,
2260 sizeof(*channel_similarity));
2261 if (channel_similarity == (
double *) NULL)
2262 ThrowFatalException(ResourceLimitFatalError,
"MemoryAllocationFailed");
2263 (void) memset(channel_similarity,0,length*
sizeof(*channel_similarity));
2266 case AbsoluteErrorMetric:
2268 status=GetAESimilarity(similarity_image,reconstruct_image,
2269 CompositeChannels,channel_similarity,exception);
2272 case FuzzErrorMetric:
2274 status=GetFUZZSimilarity(similarity_image,reconstruct_image,
2275 CompositeChannels,channel_similarity,exception);
2278 case MeanAbsoluteErrorMetric:
2280 status=GetMAESimilarity(similarity_image,reconstruct_image,
2281 CompositeChannels,channel_similarity,exception);
2284 case MeanErrorPerPixelMetric:
2286 status=GetMEPPSimilarity(similarity_image,reconstruct_image,
2287 CompositeChannels,channel_similarity,exception);
2290 case MeanSquaredErrorMetric:
2292 status=GetMSESimilarity(similarity_image,reconstruct_image,
2293 CompositeChannels,channel_similarity,exception);
2296 case NormalizedCrossCorrelationErrorMetric:
2298 status=GetNCCSimilarity(similarity_image,reconstruct_image,
2299 CompositeChannels,channel_similarity,exception);
2302 case PeakAbsoluteErrorMetric:
2304 status=GetPASimilarity(similarity_image,reconstruct_image,
2305 CompositeChannels,channel_similarity,exception);
2308 case PeakSignalToNoiseRatioMetric:
2310 status=GetPSNRSimilarity(similarity_image,reconstruct_image,
2311 CompositeChannels,channel_similarity,exception);
2314 case PerceptualHashErrorMetric:
2316 status=GetPHASHSimilarity(similarity_image,reconstruct_image,
2317 CompositeChannels,channel_similarity,exception);
2320 case PixelDifferenceCountErrorMetric:
2322 status=GetPDCSimilarity(similarity_image,reconstruct_image,
2323 CompositeChannels,channel_similarity,exception);
2326 case RootMeanSquaredErrorMetric:
2327 case UndefinedErrorMetric:
2330 status=GetRMSESimilarity(similarity_image,reconstruct_image,
2331 CompositeChannels,channel_similarity,exception);
2335 similarity_image=DestroyImage(similarity_image);
2336 similarity=channel_similarity[CompositeChannels];
2337 channel_similarity=(
double *) RelinquishMagickMemory(channel_similarity);
2338 if (status == MagickFalse)
2343MagickExport Image *SimilarityImage(Image *image,
const Image *reference,
2344 RectangleInfo *offset,
double *similarity_metric,ExceptionInfo *exception)
2349 similarity_image=SimilarityMetricImage(image,reference,
2350 RootMeanSquaredErrorMetric,offset,similarity_metric,exception);
2351 return(similarity_image);
2354MagickExport Image *SimilarityMetricImage(Image *image,
const Image *reconstruct,
2355 const MetricType metric,RectangleInfo *offset,
double *similarity_metric,
2356 ExceptionInfo *exception)
2358#define SimilarityImageTag "Similarity/Image"
2377 similarity_threshold;
2380 *similarity_image = (Image *) NULL;
2389 similarity_info = { 0 };
2398 assert(image != (
const Image *) NULL);
2399 assert(image->signature == MagickCoreSignature);
2400 assert(exception != (ExceptionInfo *) NULL);
2401 assert(exception->signature == MagickCoreSignature);
2402 assert(offset != (RectangleInfo *) NULL);
2403 if (IsEventLogging() != MagickFalse)
2404 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
2405 SetGeometry(reconstruct,offset);
2406 *similarity_metric=0.0;
2409 if (ValidateImageMorphology(image,reconstruct) == MagickFalse)
2410 ThrowImageException(ImageError,
"ImageMorphologyDiffers");
2411 if ((image->columns < reconstruct->columns) ||
2412 (image->rows < reconstruct->rows))
2414 (void) ThrowMagickException(&image->exception,GetMagickModule(),
2415 OptionWarning,
"GeometryDoesNotContainImage",
"`%s'",image->filename);
2416 return((Image *) NULL);
2418 SetImageCompareBounds(image,reconstruct,&columns,&rows);
2419 similarity_image=CloneImage(image,columns,rows,MagickTrue,exception);
2420 if (similarity_image == (Image *) NULL)
2421 return((Image *) NULL);
2422 similarity_image->depth=32;
2423 similarity_image->colorspace=GRAYColorspace;
2424 similarity_image->matte=MagickFalse;
2425 status=SetImageStorageClass(similarity_image,DirectClass);
2426 if (status == MagickFalse)
2428 InheritException(exception,&similarity_image->exception);
2429 return(DestroyImage(similarity_image));
2434 similarity_threshold=DefaultSimilarityThreshold;
2435 artifact=GetImageArtifact(image,
"compare:similarity-threshold");
2436 if (artifact != (
const char *) NULL)
2437 similarity_threshold=StringToDouble(artifact,(
char **) NULL);
2439 similarity_info.similarity=GetSimilarityMetric(image,reconstruct,metric,
2440 similarity_info.x,similarity_info.y,exception);
2442 similarity_view=AcquireVirtualCacheView(similarity_image,exception);
2443#if defined(MAGICKCORE_OPENMP_SUPPORT)
2444 #pragma omp parallel for schedule(static) shared(status,similarity_info) \
2445 magick_number_threads(image,reconstruct,similarity_image->rows << 2,1)
2447 for (y=0; y < (ssize_t) similarity_image->rows; y++)
2453 threshold_trigger = MagickFalse;
2459 channel_info = similarity_info;
2464 if (status == MagickFalse)
2466 if (threshold_trigger != MagickFalse)
2468 q=QueueCacheViewAuthenticPixels(similarity_view,0,y,
2469 similarity_image->columns,1,exception);
2470 if (q == (PixelPacket *) NULL)
2475 for (x=0; x < (ssize_t) similarity_image->columns; x++)
2478 update = MagickFalse;
2480 similarity=GetSimilarityMetric(image,reconstruct,metric,x,y,exception);
2483 case NormalizedCrossCorrelationErrorMetric:
2484 case PeakSignalToNoiseRatioMetric:
2486 if (similarity > channel_info.similarity)
2492 if (similarity < channel_info.similarity)
2497 if (update != MagickFalse)
2499 channel_info.similarity=similarity;
2505 case NormalizedCrossCorrelationErrorMetric:
2506 case PeakSignalToNoiseRatioMetric:
2508 SetPixelRed(q,ClampToQuantum((
double) QuantumRange*similarity));
2513 SetPixelRed(q,ClampToQuantum((
double) QuantumRange*(1.0-similarity)));
2517 SetPixelGreen(q,GetPixelRed(q));
2518 SetPixelBlue(q,GetPixelRed(q));
2521#if defined(MAGICKCORE_OPENMP_SUPPORT)
2522 #pragma omp critical (MagickCore_SimilarityMetricImage)
2526 case NormalizedCrossCorrelationErrorMetric:
2527 case PeakSignalToNoiseRatioMetric:
2529 if (similarity_threshold != DefaultSimilarityThreshold)
2530 if (channel_info.similarity >= similarity_threshold)
2531 threshold_trigger=MagickTrue;
2532 if (channel_info.similarity >= similarity_info.similarity)
2533 similarity_info=channel_info;
2538 if (similarity_threshold != DefaultSimilarityThreshold)
2539 if (channel_info.similarity < similarity_threshold)
2540 threshold_trigger=MagickTrue;
2541 if (channel_info.similarity < similarity_info.similarity)
2542 similarity_info=channel_info;
2546 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
2548 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2554 proceed=SetImageProgress(image,SimilarityImageTag,progress,image->rows);
2555 if (proceed == MagickFalse)
2559 similarity_view=DestroyCacheView(similarity_view);
2560 if (status == MagickFalse)
2561 similarity_image=DestroyImage(similarity_image);
2562 *similarity_metric=similarity_info.similarity;
2563 if (fabs(*similarity_metric) < MagickEpsilon)
2564 *similarity_metric=0.0;
2565 offset->x=similarity_info.x;
2566 offset->y=similarity_info.y;
2567 (void) FormatImageProperty((Image *) image,
"similarity",
"%.*g",
2568 GetMagickPrecision(),*similarity_metric);
2569 (void) FormatImageProperty((Image *) image,
"similarity.offset.x",
"%.*g",
2570 GetMagickPrecision(),(
double) offset->x);
2571 (void) FormatImageProperty((Image *) image,
"similarity.offset.y",
"%.*g",
2572 GetMagickPrecision(),(
double) offset->y);
2573 return(similarity_image);