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 ((error*error) >= fuzz)
456 channel_similarity[RedChannel]++;
460 if ((channel & GreenChannel) != 0)
462 error=Sa*(double) GetPixelGreen(p)-Da*(double)
464 if ((error*error) >= fuzz)
466 channel_similarity[GreenChannel]++;
470 if ((channel & BlueChannel) != 0)
472 error=Sa*(double) GetPixelBlue(p)-Da*(double)
474 if ((error*error) >= fuzz)
476 channel_similarity[BlueChannel]++;
480 if (((channel & OpacityChannel) != 0) &&
481 (image->matte != MagickFalse))
483 error=(double) GetPixelOpacity(p)-(double)
485 if ((error*error) >= fuzz)
487 channel_similarity[OpacityChannel]++;
491 if (((channel & IndexChannel) != 0) &&
492 (image->colorspace == CMYKColorspace))
494 error=Sa*(double) indexes[x]-Da*(
double)
495 reconstruct_indexes[x];
496 if ((error*error) >= fuzz)
498 channel_similarity[IndexChannel]++;
503 channel_similarity[CompositeChannels]++;
507#if defined(MAGICKCORE_OPENMP_SUPPORT)
508 #pragma omp critical (MagickCore_GetAESimilarity)
510 for (i=0; i <= (ssize_t) CompositeChannels; i++)
511 similarity[i]+=channel_similarity[i];
513 reconstruct_view=DestroyCacheView(reconstruct_view);
514 image_view=DestroyCacheView(image_view);
515 area=MagickSafeReciprocal((
double) columns*rows);
516 for (j=0; j <= CompositeChannels; j++)
521static MagickBooleanType GetFUZZSimilarity(
const Image *image,
522 const Image *reconstruct_image,
const ChannelType channel,
523 double *similarity,ExceptionInfo *exception)
544 fuzz=GetFuzzyColorDistance(image,reconstruct_image);
545 SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
546 image_view=AcquireVirtualCacheView(image,exception);
547 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
548#if defined(MAGICKCORE_OPENMP_SUPPORT)
549 #pragma omp parallel for schedule(static) shared(status) \
550 magick_number_threads(image,image,rows,1)
552 for (y=0; y < (ssize_t) rows; y++)
556 channel_similarity[CompositeChannels+1] = { 0.0 };
559 *magick_restrict indexes,
560 *magick_restrict reconstruct_indexes;
570 if (status == MagickFalse)
572 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
573 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
574 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
579 indexes=GetCacheViewVirtualIndexQueue(image_view);
580 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
581 for (x=0; x < (ssize_t) columns; x++)
588 Sa=QuantumScale*(image->matte != MagickFalse ? (double)
589 GetPixelAlpha(p) : ((double) QuantumRange-(double) OpaqueOpacity));
590 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
591 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
593 if ((channel & RedChannel) != 0)
595 error=QuantumScale*(Sa*GetPixelRed(p)-Da*GetPixelRed(q));
596 if ((error*error) >= fuzz)
598 channel_similarity[RedChannel]+=error*error;
599 channel_similarity[CompositeChannels]+=error*error;
603 if ((channel & GreenChannel) != 0)
605 error=QuantumScale*(Sa*GetPixelGreen(p)-Da*GetPixelGreen(q));
606 if ((error*error) >= fuzz)
608 channel_similarity[GreenChannel]+=error*error;
609 channel_similarity[CompositeChannels]+=error*error;
613 if ((channel & BlueChannel) != 0)
615 error=QuantumScale*(Sa*GetPixelBlue(p)-Da*GetPixelBlue(q));
616 if ((error*error) >= fuzz)
618 channel_similarity[BlueChannel]+=error*error;
619 channel_similarity[CompositeChannels]+=error*error;
623 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
625 error=QuantumScale*((double) GetPixelOpacity(p)-GetPixelOpacity(q));
626 if ((error*error) >= fuzz)
628 channel_similarity[OpacityChannel]+=error*error;
629 channel_similarity[CompositeChannels]+=error*error;
633 if (((channel & IndexChannel) != 0) &&
634 (image->colorspace == CMYKColorspace))
636 error=QuantumScale*(Sa*GetPixelIndex(indexes+x)-Da*
637 GetPixelIndex(reconstruct_indexes+x));
638 if ((error*error) >= fuzz)
640 channel_similarity[BlackChannel]+=error*error;
641 channel_similarity[CompositeChannels]+=error*error;
648#if defined(MAGICKCORE_OPENMP_SUPPORT)
649 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
653 for (i=0; i <= (ssize_t) CompositeChannels; i++)
654 similarity[i]+=channel_similarity[i];
657 reconstruct_view=DestroyCacheView(reconstruct_view);
658 image_view=DestroyCacheView(image_view);
659 area=MagickSafeReciprocal(area);
660 for (i=0; i <= (ssize_t) CompositeChannels; i++)
665static MagickBooleanType GetMAESimilarity(
const Image *image,
666 const Image *reconstruct_image,
const ChannelType channel,
667 double *similarity,ExceptionInfo *exception)
685 SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
686 image_view=AcquireVirtualCacheView(image,exception);
687 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
688#if defined(MAGICKCORE_OPENMP_SUPPORT)
689 #pragma omp parallel for schedule(static) shared(status) \
690 magick_number_threads(image,image,rows,1)
692 for (y=0; y < (ssize_t) rows; y++)
695 channel_similarity[CompositeChannels+1];
698 *magick_restrict indexes,
699 *magick_restrict reconstruct_indexes;
709 if (status == MagickFalse)
711 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
712 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
713 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
718 indexes=GetCacheViewVirtualIndexQueue(image_view);
719 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
720 (void) memset(channel_similarity,0,
sizeof(channel_similarity));
721 for (x=0; x < (ssize_t) columns; x++)
728 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
729 ((double) QuantumRange-(double) OpaqueOpacity));
730 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
731 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
733 if ((channel & RedChannel) != 0)
735 distance=QuantumScale*fabs(Sa*(
double) GetPixelRed(p)-Da*
736 (
double) GetPixelRed(q));
737 channel_similarity[RedChannel]+=distance;
738 channel_similarity[CompositeChannels]+=distance;
740 if ((channel & GreenChannel) != 0)
742 distance=QuantumScale*fabs(Sa*(
double) GetPixelGreen(p)-Da*
743 (
double) GetPixelGreen(q));
744 channel_similarity[GreenChannel]+=distance;
745 channel_similarity[CompositeChannels]+=distance;
747 if ((channel & BlueChannel) != 0)
749 distance=QuantumScale*fabs(Sa*(
double) GetPixelBlue(p)-Da*
750 (
double) GetPixelBlue(q));
751 channel_similarity[BlueChannel]+=distance;
752 channel_similarity[CompositeChannels]+=distance;
754 if (((channel & OpacityChannel) != 0) &&
755 (image->matte != MagickFalse))
757 distance=QuantumScale*fabs((double) GetPixelOpacity(p)-(double)
759 channel_similarity[OpacityChannel]+=distance;
760 channel_similarity[CompositeChannels]+=distance;
762 if (((channel & IndexChannel) != 0) &&
763 (image->colorspace == CMYKColorspace))
765 distance=QuantumScale*fabs(Sa*(double) GetPixelIndex(indexes+x)-Da*
766 (double) GetPixelIndex(reconstruct_indexes+x));
767 channel_similarity[BlackChannel]+=distance;
768 channel_similarity[CompositeChannels]+=distance;
773#if defined(MAGICKCORE_OPENMP_SUPPORT)
774 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
776 for (i=0; i <= (ssize_t) CompositeChannels; i++)
777 similarity[i]+=channel_similarity[i];
779 reconstruct_view=DestroyCacheView(reconstruct_view);
780 image_view=DestroyCacheView(image_view);
781 for (i=0; i <= (ssize_t) CompositeChannels; i++)
782 similarity[i]/=((
double) columns*rows);
783 similarity[CompositeChannels]/=(double) GetNumberChannels(image,channel);
787static MagickBooleanType GetMEPPSimilarity(Image *image,
788 const Image *reconstruct_image,
const ChannelType channel,
double *similarity,
789 ExceptionInfo *exception)
796 maximum_error = -MagickMaximumValue,
811 SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
812 image_view=AcquireVirtualCacheView(image,exception);
813 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
814#if defined(MAGICKCORE_OPENMP_SUPPORT)
815 #pragma omp parallel for schedule(static) shared(maximum_error,status) \
816 magick_number_threads(image,image,rows,1)
818 for (y=0; y < (ssize_t) rows; y++)
821 channel_similarity[CompositeChannels+1] = { 0.0 },
822 local_maximum = maximum_error,
823 local_mean_error = 0.0;
826 *magick_restrict indexes,
827 *magick_restrict reconstruct_indexes;
837 if (status == MagickFalse)
839 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
840 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
841 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
846 indexes=GetCacheViewVirtualIndexQueue(image_view);
847 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
848 (void) memset(channel_similarity,0,
sizeof(channel_similarity));
849 for (x=0; x < (ssize_t) columns; x++)
856 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
857 ((double) QuantumRange-(double) OpaqueOpacity));
858 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
859 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
861 if ((channel & RedChannel) != 0)
863 distance=QuantumScale*fabs(Sa*(
double) GetPixelRed(p)-Da*
864 (
double) GetPixelRed(q));
865 channel_similarity[RedChannel]+=distance;
866 channel_similarity[CompositeChannels]+=distance;
867 local_mean_error+=distance*distance;
868 if (distance > local_maximum)
869 local_maximum=distance;
871 if ((channel & GreenChannel) != 0)
873 distance=QuantumScale*fabs(Sa*(
double) GetPixelGreen(p)-Da*
874 (
double) GetPixelGreen(q));
875 channel_similarity[GreenChannel]+=distance;
876 channel_similarity[CompositeChannels]+=distance;
877 local_mean_error+=distance*distance;
878 if (distance > local_maximum)
879 local_maximum=distance;
881 if ((channel & BlueChannel) != 0)
883 distance=QuantumScale*fabs(Sa*(
double) GetPixelBlue(p)-Da*
884 (
double) GetPixelBlue(q));
885 channel_similarity[BlueChannel]+=distance;
886 channel_similarity[CompositeChannels]+=distance;
887 local_mean_error+=distance*distance;
888 if (distance > local_maximum)
889 local_maximum=distance;
891 if (((channel & OpacityChannel) != 0) &&
892 (image->matte != MagickFalse))
894 distance=QuantumScale*fabs((double) GetPixelOpacity(p)-(double)
896 channel_similarity[OpacityChannel]+=distance;
897 channel_similarity[CompositeChannels]+=distance;
898 local_mean_error+=distance*distance;
899 if (distance > local_maximum)
900 local_maximum=distance;
902 if (((channel & IndexChannel) != 0) &&
903 (image->colorspace == CMYKColorspace))
905 distance=QuantumScale*fabs(Sa*(double) GetPixelIndex(indexes+x)-Da*
906 (double) GetPixelIndex(reconstruct_indexes+x));
907 channel_similarity[BlackChannel]+=distance;
908 channel_similarity[CompositeChannels]+=distance;
909 local_mean_error+=distance*distance;
910 if (distance > local_maximum)
911 local_maximum=distance;
916#if defined(MAGICKCORE_OPENMP_SUPPORT)
917 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
920 for (i=0; i <= (ssize_t) CompositeChannels; i++)
921 similarity[i]+=channel_similarity[i];
922 mean_error+=local_mean_error;
923 if (local_maximum > maximum_error)
924 maximum_error=local_maximum;
927 reconstruct_view=DestroyCacheView(reconstruct_view);
928 image_view=DestroyCacheView(image_view);
929 for (i=0; i <= (ssize_t) CompositeChannels; i++)
930 similarity[i]/=((
double) columns*rows);
931 similarity[CompositeChannels]/=(double) GetNumberChannels(image,channel);
932 image->error.mean_error_per_pixel=QuantumRange*similarity[CompositeChannels];
933 image->error.normalized_mean_error=mean_error/((double) columns*rows);
934 image->error.normalized_maximum_error=maximum_error;
938static MagickBooleanType GetMSESimilarity(
const Image *image,
939 const Image *reconstruct_image,
const ChannelType channel,
940 double *similarity,ExceptionInfo *exception)
961 SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
962 image_view=AcquireVirtualCacheView(image,exception);
963 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
964#if defined(MAGICKCORE_OPENMP_SUPPORT)
965 #pragma omp parallel for schedule(static) shared(similarity,status) \
966 magick_number_threads(image,image,rows,1)
968 for (y=0; y < (ssize_t) rows; y++)
971 channel_similarity[CompositeChannels+1] = { 0.0 };
974 *magick_restrict indexes,
975 *magick_restrict reconstruct_indexes;
985 if (status == MagickFalse)
987 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
988 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
989 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
994 indexes=GetCacheViewVirtualIndexQueue(image_view);
995 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
996 for (x=0; x < (ssize_t) columns; x++)
1003 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
1004 ((double) QuantumRange-(double) OpaqueOpacity));
1005 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1006 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
1008 if ((channel & RedChannel) != 0)
1010 distance=QuantumScale*(Sa*(double) GetPixelRed(p)-Da*(double)
1012 channel_similarity[RedChannel]+=distance*distance;
1013 channel_similarity[CompositeChannels]+=distance*distance;
1015 if ((channel & GreenChannel) != 0)
1017 distance=QuantumScale*(Sa*(double) GetPixelGreen(p)-Da*(double)
1019 channel_similarity[GreenChannel]+=distance*distance;
1020 channel_similarity[CompositeChannels]+=distance*distance;
1022 if ((channel & BlueChannel) != 0)
1024 distance=QuantumScale*(Sa*(double) GetPixelBlue(p)-Da*(double)
1026 channel_similarity[BlueChannel]+=distance*distance;
1027 channel_similarity[CompositeChannels]+=distance*distance;
1029 if (((channel & OpacityChannel) != 0) &&
1030 (image->matte != MagickFalse))
1032 distance=QuantumScale*((double) GetPixelOpacity(p)-(double)
1033 GetPixelOpacity(q));
1034 channel_similarity[OpacityChannel]+=distance*distance;
1035 channel_similarity[CompositeChannels]+=distance*distance;
1037 if (((channel & IndexChannel) != 0) &&
1038 (image->colorspace == CMYKColorspace) &&
1039 (reconstruct_image->colorspace == CMYKColorspace))
1041 distance=QuantumScale*(Sa*(double) GetPixelIndex(indexes+x)-Da*
1042 (double) GetPixelIndex(reconstruct_indexes+x));
1043 channel_similarity[BlackChannel]+=distance*distance;
1044 channel_similarity[CompositeChannels]+=distance*distance;
1049#if defined(MAGICKCORE_OPENMP_SUPPORT)
1050 #pragma omp critical (MagickCore_GetMeanSquaredError)
1052 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1053 similarity[i]+=channel_similarity[i];
1055 reconstruct_view=DestroyCacheView(reconstruct_view);
1056 image_view=DestroyCacheView(image_view);
1057 area=MagickSafeReciprocal((
double) columns*rows);
1058 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1059 similarity[i]*=area;
1060 similarity[CompositeChannels]/=(double) GetNumberChannels(image,channel);
1064static MagickBooleanType GetNCCSimilarity(
const Image *image,
1065 const Image *reconstruct_image,
const ChannelType channel,
double *similarity,
1066 ExceptionInfo *exception)
1068#define SimilarityImageTag "Similarity/Image"
1076 *reconstruct_statistics;
1079 alpha_variance[CompositeChannels+1] = { 0.0 },
1080 beta_variance[CompositeChannels+1] = { 0.0 };
1099 image_statistics=GetImageChannelStatistics(image,exception);
1100 reconstruct_statistics=GetImageChannelStatistics(reconstruct_image,exception);
1101 if ((image_statistics == (ChannelStatistics *) NULL) ||
1102 (reconstruct_statistics == (ChannelStatistics *) NULL))
1104 if (image_statistics != (ChannelStatistics *) NULL)
1105 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1107 if (reconstruct_statistics != (ChannelStatistics *) NULL)
1108 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1109 reconstruct_statistics);
1110 return(MagickFalse);
1112 (void) memset(similarity,0,(CompositeChannels+1)*
sizeof(*similarity));
1115 SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
1116 image_view=AcquireVirtualCacheView(image,exception);
1117 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1118#if defined(MAGICKCORE_OPENMP_SUPPORT)
1119 #pragma omp parallel for schedule(static) shared(status) \
1120 magick_number_threads(image,image,rows,1)
1122 for (y=0; y < (ssize_t) rows; y++)
1125 *magick_restrict indexes,
1126 *magick_restrict reconstruct_indexes;
1133 channel_alpha_variance[CompositeChannels+1] = { 0.0 },
1134 channel_beta_variance[CompositeChannels+1] = { 0.0 },
1135 channel_similarity[CompositeChannels+1] = { 0.0 };
1140 if (status == MagickFalse)
1142 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1143 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1144 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
1149 indexes=GetCacheViewVirtualIndexQueue(image_view);
1150 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1151 for (x=0; x < (ssize_t) columns; x++)
1159 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
1160 (double) QuantumRange);
1161 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1162 (double) GetPixelAlpha(q) : (double) QuantumRange);
1163 if ((channel & RedChannel) != 0)
1165 alpha=QuantumScale*(Sa*(double) GetPixelRed(p)-
1166 image_statistics[RedChannel].mean);
1167 beta=QuantumScale*(Da*(double) GetPixelRed(q)-
1168 reconstruct_statistics[RedChannel].mean);
1169 channel_similarity[RedChannel]+=alpha*beta;
1170 channel_similarity[CompositeChannels]+=alpha*beta;
1171 channel_alpha_variance[RedChannel]+=alpha*alpha;
1172 channel_alpha_variance[CompositeChannels]+=alpha*alpha;
1173 channel_beta_variance[RedChannel]+=beta*beta;
1174 channel_beta_variance[CompositeChannels]+=beta*beta;
1176 if ((channel & GreenChannel) != 0)
1178 alpha=QuantumScale*(Sa*(double) GetPixelGreen(p)-
1179 image_statistics[GreenChannel].mean);
1180 beta=QuantumScale*(Da*(double) GetPixelGreen(q)-
1181 reconstruct_statistics[GreenChannel].mean);
1182 channel_similarity[GreenChannel]+=alpha*beta;
1183 channel_similarity[CompositeChannels]+=alpha*beta;
1184 channel_alpha_variance[GreenChannel]+=alpha*alpha;
1185 channel_alpha_variance[CompositeChannels]+=alpha*alpha;
1186 channel_beta_variance[GreenChannel]+=beta*beta;
1187 channel_beta_variance[CompositeChannels]+=beta*beta;
1189 if ((channel & BlueChannel) != 0)
1191 alpha=QuantumScale*(Sa*(double) GetPixelBlue(p)-
1192 image_statistics[BlueChannel].mean);
1193 beta=QuantumScale*(Da*(double) GetPixelBlue(q)-
1194 reconstruct_statistics[BlueChannel].mean);
1195 channel_similarity[BlueChannel]+=alpha*beta;
1196 channel_alpha_variance[BlueChannel]+=alpha*alpha;
1197 channel_beta_variance[BlueChannel]+=beta*beta;
1199 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
1201 alpha=QuantumScale*((double) GetPixelAlpha(p)-
1202 image_statistics[AlphaChannel].mean);
1203 beta=QuantumScale*((double) GetPixelAlpha(q)-
1204 reconstruct_statistics[AlphaChannel].mean);
1205 channel_similarity[OpacityChannel]+=alpha*beta;
1206 channel_similarity[CompositeChannels]+=alpha*beta;
1207 channel_alpha_variance[OpacityChannel]+=alpha*alpha;
1208 channel_alpha_variance[CompositeChannels]+=alpha*alpha;
1209 channel_beta_variance[OpacityChannel]+=beta*beta;
1210 channel_beta_variance[CompositeChannels]+=beta*beta;
1212 if (((channel & IndexChannel) != 0) &&
1213 (image->colorspace == CMYKColorspace) &&
1214 (reconstruct_image->colorspace == CMYKColorspace))
1216 alpha=QuantumScale*(Sa*(double) GetPixelIndex(indexes+x)-
1217 image_statistics[BlackChannel].mean);
1218 beta=QuantumScale*(Da*(double) GetPixelIndex(reconstruct_indexes+
1219 x)-reconstruct_statistics[BlackChannel].mean);
1220 channel_similarity[BlackChannel]+=alpha*beta;
1221 channel_similarity[CompositeChannels]+=alpha*beta;
1222 channel_alpha_variance[BlackChannel]+=alpha*alpha;
1223 channel_alpha_variance[CompositeChannels]+=alpha*alpha;
1224 channel_beta_variance[BlackChannel]+=beta*beta;
1225 channel_beta_variance[CompositeChannels]+=beta*beta;
1230#if defined(MAGICKCORE_OPENMP_SUPPORT)
1231 #pragma omp critical (GetNCCSimilarity)
1237 for (j=0; j <= (ssize_t) CompositeChannels; j++)
1239 similarity[j]+=channel_similarity[j];
1240 alpha_variance[j]+=channel_alpha_variance[j];
1241 beta_variance[j]+=channel_beta_variance[j];
1244 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1249#if defined(MAGICKCORE_OPENMP_SUPPORT)
1253 proceed=SetImageProgress(image,SimilarityImageTag,progress,rows);
1254 if (proceed == MagickFalse)
1258 reconstruct_view=DestroyCacheView(reconstruct_view);
1259 image_view=DestroyCacheView(image_view);
1263 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1264 similarity[i]*=MagickSafeReciprocal(sqrt(alpha_variance[i])*
1265 sqrt(beta_variance[i]));
1269 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1270 reconstruct_statistics);
1271 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1276static MagickBooleanType GetPASimilarity(
const Image *image,
1277 const Image *reconstruct_image,
const ChannelType channel,
1278 double *similarity,ExceptionInfo *exception)
1295 (void) memset(similarity,0,(CompositeChannels+1)*
sizeof(*similarity));
1296 SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
1297 image_view=AcquireVirtualCacheView(image,exception);
1298 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1299#if defined(MAGICKCORE_OPENMP_SUPPORT)
1300 #pragma omp parallel for schedule(static) shared(status) \
1301 magick_number_threads(image,image,rows,1)
1303 for (y=0; y < (ssize_t) rows; y++)
1306 channel_similarity[CompositeChannels+1];
1309 *magick_restrict indexes,
1310 *magick_restrict reconstruct_indexes;
1320 if (status == MagickFalse)
1322 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1323 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1324 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
1329 indexes=GetCacheViewVirtualIndexQueue(image_view);
1330 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1331 (void) memset(channel_similarity,0,(CompositeChannels+1)*
1332 sizeof(*channel_similarity));
1333 for (x=0; x < (ssize_t) columns; x++)
1340 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
1341 ((double) QuantumRange-(double) OpaqueOpacity));
1342 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1343 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
1345 if ((channel & RedChannel) != 0)
1347 distance=QuantumScale*fabs(Sa*(
double) GetPixelRed(p)-Da*
1348 (
double) GetPixelRed(q));
1349 if (distance > channel_similarity[RedChannel])
1350 channel_similarity[RedChannel]=distance;
1351 if (distance > channel_similarity[CompositeChannels])
1352 channel_similarity[CompositeChannels]=distance;
1354 if ((channel & GreenChannel) != 0)
1356 distance=QuantumScale*fabs(Sa*(
double) GetPixelGreen(p)-Da*
1357 (
double) GetPixelGreen(q));
1358 if (distance > channel_similarity[GreenChannel])
1359 channel_similarity[GreenChannel]=distance;
1360 if (distance > channel_similarity[CompositeChannels])
1361 channel_similarity[CompositeChannels]=distance;
1363 if ((channel & BlueChannel) != 0)
1365 distance=QuantumScale*fabs(Sa*(
double) GetPixelBlue(p)-Da*
1366 (
double) GetPixelBlue(q));
1367 if (distance > channel_similarity[BlueChannel])
1368 channel_similarity[BlueChannel]=distance;
1369 if (distance > channel_similarity[CompositeChannels])
1370 channel_similarity[CompositeChannels]=distance;
1372 if (((channel & OpacityChannel) != 0) &&
1373 (image->matte != MagickFalse))
1375 distance=QuantumScale*fabs((double) GetPixelOpacity(p)-(double)
1376 GetPixelOpacity(q));
1377 if (distance > channel_similarity[OpacityChannel])
1378 channel_similarity[OpacityChannel]=distance;
1379 if (distance > channel_similarity[CompositeChannels])
1380 channel_similarity[CompositeChannels]=distance;
1382 if (((channel & IndexChannel) != 0) &&
1383 (image->colorspace == CMYKColorspace) &&
1384 (reconstruct_image->colorspace == CMYKColorspace))
1386 distance=QuantumScale*fabs(Sa*(double) GetPixelIndex(indexes+x)-Da*
1387 (double) GetPixelIndex(reconstruct_indexes+x));
1388 if (distance > channel_similarity[BlackChannel])
1389 channel_similarity[BlackChannel]=distance;
1390 if (distance > channel_similarity[CompositeChannels])
1391 channel_similarity[CompositeChannels]=distance;
1396#if defined(MAGICKCORE_OPENMP_SUPPORT)
1397 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
1399 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1400 if (channel_similarity[i] > similarity[i])
1401 similarity[i]=channel_similarity[i];
1403 reconstruct_view=DestroyCacheView(reconstruct_view);
1404 image_view=DestroyCacheView(image_view);
1408static MagickBooleanType GetPSNRSimilarity(
const Image *image,
1409 const Image *reconstruct_image,
const ChannelType channel,
1410 double *similarity,ExceptionInfo *exception)
1415 status=GetMSESimilarity(image,reconstruct_image,channel,similarity,
1417 if ((channel & RedChannel) != 0)
1418 similarity[RedChannel]=10.0*MagickSafeLog10(MagickSafeReciprocal(
1419 similarity[RedChannel]))/MagickSafePSNRRecipicol(10.0);
1420 if ((channel & GreenChannel) != 0)
1421 similarity[GreenChannel]=10.0*MagickSafeLog10(MagickSafeReciprocal(
1422 similarity[GreenChannel]))/MagickSafePSNRRecipicol(10.0);
1423 if ((channel & BlueChannel) != 0)
1424 similarity[BlueChannel]=10.0*MagickSafeLog10(MagickSafeReciprocal(
1425 similarity[BlueChannel]))/MagickSafePSNRRecipicol(10.0);
1426 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
1427 similarity[OpacityChannel]=10.0*MagickSafeLog10(MagickSafeReciprocal(
1428 similarity[OpacityChannel]))/MagickSafePSNRRecipicol(10.0);
1429 if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
1430 similarity[BlackChannel]=10.0*MagickSafeLog10(MagickSafeReciprocal(
1431 similarity[BlackChannel]))/MagickSafePSNRRecipicol(10.0);
1432 similarity[CompositeChannels]=10.0*MagickSafeLog10(MagickSafeReciprocal(
1433 similarity[CompositeChannels]))/MagickSafePSNRRecipicol(10.0);
1437static MagickBooleanType GetPHASHSimilarity(
const Image *image,
1438 const Image *reconstruct_image,
const ChannelType channel,
double *similarity,
1439 ExceptionInfo *exception)
1441#define PHASHNormalizationFactor 389.373723242
1443 ChannelPerceptualHash
1457 image_phash=GetImageChannelPerceptualHash(image,exception);
1458 if (image_phash == (ChannelPerceptualHash *) NULL)
1459 return(MagickFalse);
1460 reconstruct_phash=GetImageChannelPerceptualHash(reconstruct_image,exception);
1461 if (reconstruct_phash == (ChannelPerceptualHash *) NULL)
1463 image_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(image_phash);
1464 return(MagickFalse);
1466 for (i=0; i < MaximumNumberOfImageMoments; i++)
1471 if ((channel & RedChannel) != 0)
1473 error=reconstruct_phash[RedChannel].P[i]-image_phash[RedChannel].P[i];
1474 if (IsNaN(error) != 0)
1476 difference=error*error/PHASHNormalizationFactor;
1477 similarity[RedChannel]+=difference;
1478 similarity[CompositeChannels]+=difference;
1480 if ((channel & GreenChannel) != 0)
1482 error=reconstruct_phash[GreenChannel].P[i]-
1483 image_phash[GreenChannel].P[i];
1484 if (IsNaN(error) != 0)
1486 difference=error*error/PHASHNormalizationFactor;
1487 similarity[GreenChannel]+=difference;
1488 similarity[CompositeChannels]+=difference;
1490 if ((channel & BlueChannel) != 0)
1492 error=reconstruct_phash[BlueChannel].P[i]-image_phash[BlueChannel].P[i];
1493 if (IsNaN(error) != 0)
1495 difference=error*error/PHASHNormalizationFactor;
1496 similarity[BlueChannel]+=difference;
1497 similarity[CompositeChannels]+=difference;
1499 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse) &&
1500 (reconstruct_image->matte != MagickFalse))
1502 error=reconstruct_phash[OpacityChannel].P[i]-
1503 image_phash[OpacityChannel].P[i];
1504 if (IsNaN(error) != 0)
1506 difference=error*error/PHASHNormalizationFactor;
1507 similarity[OpacityChannel]+=difference;
1508 similarity[CompositeChannels]+=difference;
1510 if (((channel & IndexChannel) != 0) &&
1511 (image->colorspace == CMYKColorspace) &&
1512 (reconstruct_image->colorspace == CMYKColorspace))
1514 error=reconstruct_phash[IndexChannel].P[i]-
1515 image_phash[IndexChannel].P[i];
1516 if (IsNaN(error) != 0)
1518 difference=error*error/PHASHNormalizationFactor;
1519 similarity[IndexChannel]+=difference;
1520 similarity[CompositeChannels]+=difference;
1526 for (i=0; i < MaximumNumberOfImageMoments; i++)
1531 if ((channel & RedChannel) != 0)
1533 error=reconstruct_phash[RedChannel].Q[i]-image_phash[RedChannel].Q[i];
1534 if (IsNaN(error) != 0)
1536 difference=error*error/PHASHNormalizationFactor;
1537 similarity[RedChannel]+=difference;
1538 similarity[CompositeChannels]+=difference;
1540 if ((channel & GreenChannel) != 0)
1542 error=reconstruct_phash[GreenChannel].Q[i]-
1543 image_phash[GreenChannel].Q[i];
1544 if (IsNaN(error) != 0)
1546 difference=error*error/PHASHNormalizationFactor;
1547 similarity[GreenChannel]+=difference;
1548 similarity[CompositeChannels]+=difference;
1550 if ((channel & BlueChannel) != 0)
1552 error=reconstruct_phash[BlueChannel].Q[i]-image_phash[BlueChannel].Q[i];
1553 if (IsNaN(error) != 0)
1555 difference=error*error/PHASHNormalizationFactor;
1556 similarity[BlueChannel]+=difference;
1557 similarity[CompositeChannels]+=difference;
1559 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse) &&
1560 (reconstruct_image->matte != MagickFalse))
1562 error=reconstruct_phash[OpacityChannel].Q[i]-
1563 image_phash[OpacityChannel].Q[i];
1564 if (IsNaN(error) != 0)
1566 difference=error*error/PHASHNormalizationFactor;
1567 similarity[OpacityChannel]+=difference;
1568 similarity[CompositeChannels]+=difference;
1570 if (((channel & IndexChannel) != 0) &&
1571 (image->colorspace == CMYKColorspace) &&
1572 (reconstruct_image->colorspace == CMYKColorspace))
1574 error=reconstruct_phash[IndexChannel].Q[i]-
1575 image_phash[IndexChannel].Q[i];
1576 if (IsNaN(error) != 0)
1578 difference=error*error/PHASHNormalizationFactor;
1579 similarity[IndexChannel]+=difference;
1580 similarity[CompositeChannels]+=difference;
1583 similarity[CompositeChannels]/=(double) GetNumberChannels(image,channel);
1587 reconstruct_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(
1589 image_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(image_phash);
1593static MagickBooleanType GetRMSESimilarity(
const Image *image,
1594 const Image *reconstruct_image,
const ChannelType channel,
double *similarity,
1595 ExceptionInfo *exception)
1597#define RMSESquareRoot(x) sqrt((x) < 0.0 ? 0.0 : (x))
1602 status=GetMSESimilarity(image,reconstruct_image,channel,similarity,
1604 if ((channel & RedChannel) != 0)
1605 similarity[RedChannel]=RMSESquareRoot(similarity[RedChannel]);
1606 if ((channel & GreenChannel) != 0)
1607 similarity[GreenChannel]=RMSESquareRoot(similarity[GreenChannel]);
1608 if ((channel & BlueChannel) != 0)
1609 similarity[BlueChannel]=RMSESquareRoot(similarity[BlueChannel]);
1610 if (((channel & OpacityChannel) != 0) &&
1611 (image->matte != MagickFalse))
1612 similarity[OpacityChannel]=RMSESquareRoot(similarity[OpacityChannel]);
1613 if (((channel & IndexChannel) != 0) &&
1614 (image->colorspace == CMYKColorspace))
1615 similarity[BlackChannel]=RMSESquareRoot(similarity[BlackChannel]);
1616 similarity[CompositeChannels]=RMSESquareRoot(similarity[CompositeChannels]);
1620MagickExport MagickBooleanType GetImageChannelDistortion(Image *image,
1621 const Image *reconstruct_image,
const ChannelType channel,
1622 const MetricType metric,
double *distortion,ExceptionInfo *exception)
1625 *channel_similarity;
1633 assert(image != (Image *) NULL);
1634 assert(image->signature == MagickCoreSignature);
1635 assert(reconstruct_image != (
const Image *) NULL);
1636 assert(reconstruct_image->signature == MagickCoreSignature);
1637 assert(distortion != (
double *) NULL);
1638 if (IsEventLogging() != MagickFalse)
1639 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
1641 if (metric != PerceptualHashErrorMetric)
1642 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1643 ThrowBinaryException(ImageError,
"ImageMorphologyDiffers",image->filename);
1647 length=CompositeChannels+1UL;
1648 channel_similarity=(
double *) AcquireQuantumMemory(length,
1649 sizeof(*channel_similarity));
1650 if (channel_similarity == (
double *) NULL)
1651 ThrowFatalException(ResourceLimitFatalError,
"MemoryAllocationFailed");
1652 (void) memset(channel_similarity,0,length*
sizeof(*channel_similarity));
1655 case AbsoluteErrorMetric:
1657 status=GetAESimilarity(image,reconstruct_image,channel,
1658 channel_similarity,exception);
1661 case FuzzErrorMetric:
1663 status=GetFUZZSimilarity(image,reconstruct_image,channel,
1664 channel_similarity,exception);
1667 case MeanAbsoluteErrorMetric:
1669 status=GetMAESimilarity(image,reconstruct_image,channel,
1670 channel_similarity,exception);
1673 case MeanErrorPerPixelMetric:
1675 status=GetMEPPSimilarity(image,reconstruct_image,channel,
1676 channel_similarity,exception);
1679 case MeanSquaredErrorMetric:
1681 status=GetMSESimilarity(image,reconstruct_image,channel,
1682 channel_similarity,exception);
1685 case NormalizedCrossCorrelationErrorMetric:
1687 status=GetNCCSimilarity(image,reconstruct_image,channel,
1688 channel_similarity,exception);
1691 case PeakAbsoluteErrorMetric:
1693 status=GetPASimilarity(image,reconstruct_image,channel,
1694 channel_similarity,exception);
1697 case PeakSignalToNoiseRatioMetric:
1699 status=GetPSNRSimilarity(image,reconstruct_image,channel,
1700 channel_similarity,exception);
1703 case PerceptualHashErrorMetric:
1705 status=GetPHASHSimilarity(image,reconstruct_image,channel,
1706 channel_similarity,exception);
1709 case RootMeanSquaredErrorMetric:
1710 case UndefinedErrorMetric:
1713 status=GetRMSESimilarity(image,reconstruct_image,channel,
1714 channel_similarity,exception);
1718 *distortion=channel_similarity[CompositeChannels];
1721 case NormalizedCrossCorrelationErrorMetric:
1723 *distortion=(1.0-(*distortion))/2.0;
1728 if (fabs(*distortion) < MagickEpsilon)
1730 channel_similarity=(
double *) RelinquishMagickMemory(channel_similarity);
1731 (void) FormatImageProperty(image,
"distortion",
"%.*g",GetMagickPrecision(),
1768MagickExport
double *GetImageChannelDistortions(Image *image,
1769 const Image *reconstruct_image,
const MetricType metric,
1770 ExceptionInfo *exception)
1785 assert(image != (Image *) NULL);
1786 assert(image->signature == MagickCoreSignature);
1787 assert(reconstruct_image != (
const Image *) NULL);
1788 assert(reconstruct_image->signature == MagickCoreSignature);
1789 if (IsEventLogging() != MagickFalse)
1790 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
1791 if (metric != PerceptualHashErrorMetric)
1792 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1794 (void) ThrowMagickException(&image->exception,GetMagickModule(),
1795 ImageError,
"ImageMorphologyDiffers",
"`%s'",image->filename);
1796 return((
double *) NULL);
1801 length=CompositeChannels+1UL;
1802 similarity=(
double *) AcquireQuantumMemory(length,
1803 sizeof(*similarity));
1804 if (similarity == (
double *) NULL)
1805 ThrowFatalException(ResourceLimitFatalError,
"MemoryAllocationFailed");
1806 (void) memset(similarity,0,length*
sizeof(*similarity));
1810 case AbsoluteErrorMetric:
1812 status=GetAESimilarity(image,reconstruct_image,CompositeChannels,
1813 similarity,exception);
1816 case FuzzErrorMetric:
1818 status=GetFUZZSimilarity(image,reconstruct_image,CompositeChannels,
1819 similarity,exception);
1822 case MeanAbsoluteErrorMetric:
1824 status=GetMAESimilarity(image,reconstruct_image,CompositeChannels,
1825 similarity,exception);
1828 case MeanErrorPerPixelMetric:
1830 status=GetMEPPSimilarity(image,reconstruct_image,CompositeChannels,
1831 similarity,exception);
1834 case MeanSquaredErrorMetric:
1836 status=GetMSESimilarity(image,reconstruct_image,CompositeChannels,
1837 similarity,exception);
1840 case NormalizedCrossCorrelationErrorMetric:
1842 status=GetNCCSimilarity(image,reconstruct_image,CompositeChannels,
1843 similarity,exception);
1846 case PeakAbsoluteErrorMetric:
1848 status=GetPASimilarity(image,reconstruct_image,CompositeChannels,
1849 similarity,exception);
1852 case PeakSignalToNoiseRatioMetric:
1854 status=GetPSNRSimilarity(image,reconstruct_image,CompositeChannels,
1855 similarity,exception);
1858 case PerceptualHashErrorMetric:
1860 status=GetPHASHSimilarity(image,reconstruct_image,CompositeChannels,
1861 similarity,exception);
1864 case RootMeanSquaredErrorMetric:
1865 case UndefinedErrorMetric:
1868 status=GetRMSESimilarity(image,reconstruct_image,CompositeChannels,
1869 similarity,exception);
1873 if (status == MagickFalse)
1875 similarity=(
double *) RelinquishMagickMemory(similarity);
1876 return((
double *) NULL);
1878 distortion=similarity;
1881 case NormalizedCrossCorrelationErrorMetric:
1883 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1884 distortion[i]=(1.0-distortion[i])/2.0;
1889 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1890 if (fabs(distortion[i]) < MagickEpsilon)
1942MagickExport MagickBooleanType IsImagesEqual(Image *image,
1943 const Image *reconstruct_image)
1960 mean_error_per_pixel;
1969 assert(image != (Image *) NULL);
1970 assert(image->signature == MagickCoreSignature);
1971 assert(reconstruct_image != (
const Image *) NULL);
1972 assert(reconstruct_image->signature == MagickCoreSignature);
1973 exception=(&image->exception);
1974 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1975 ThrowBinaryException(ImageError,
"ImageMorphologyDiffers",image->filename);
1978 mean_error_per_pixel=0.0;
1980 SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
1981 image_view=AcquireVirtualCacheView(image,exception);
1982 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1983 for (y=0; y < (ssize_t) rows; y++)
1986 *magick_restrict indexes,
1987 *magick_restrict reconstruct_indexes;
1996 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1997 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1998 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
2000 indexes=GetCacheViewVirtualIndexQueue(image_view);
2001 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
2002 for (x=0; x < (ssize_t) columns; x++)
2007 distance=fabs((
double) GetPixelRed(p)-(
double) GetPixelRed(q));
2008 mean_error_per_pixel+=distance;
2009 mean_error+=distance*distance;
2010 if (distance > maximum_error)
2011 maximum_error=distance;
2013 distance=fabs((
double) GetPixelGreen(p)-(
double) GetPixelGreen(q));
2014 mean_error_per_pixel+=distance;
2015 mean_error+=distance*distance;
2016 if (distance > maximum_error)
2017 maximum_error=distance;
2019 distance=fabs((
double) GetPixelBlue(p)-(
double) GetPixelBlue(q));
2020 mean_error_per_pixel+=distance;
2021 mean_error+=distance*distance;
2022 if (distance > maximum_error)
2023 maximum_error=distance;
2025 if (image->matte != MagickFalse)
2027 distance=fabs((
double) GetPixelOpacity(p)-(
double)
2028 GetPixelOpacity(q));
2029 mean_error_per_pixel+=distance;
2030 mean_error+=distance*distance;
2031 if (distance > maximum_error)
2032 maximum_error=distance;
2035 if ((image->colorspace == CMYKColorspace) &&
2036 (reconstruct_image->colorspace == CMYKColorspace))
2038 distance=fabs((
double) GetPixelIndex(indexes+x)-(
double)
2039 GetPixelIndex(reconstruct_indexes+x));
2040 mean_error_per_pixel+=distance;
2041 mean_error+=distance*distance;
2042 if (distance > maximum_error)
2043 maximum_error=distance;
2050 reconstruct_view=DestroyCacheView(reconstruct_view);
2051 image_view=DestroyCacheView(image_view);
2052 gamma=MagickSafeReciprocal(area);
2053 image->error.mean_error_per_pixel=gamma*mean_error_per_pixel;
2054 image->error.normalized_mean_error=gamma*QuantumScale*QuantumScale*mean_error;
2055 image->error.normalized_maximum_error=QuantumScale*maximum_error;
2056 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
2095static double GetSimilarityMetric(
const Image *image,
const Image *reference,
2096 const MetricType metric,
const ssize_t x_offset,
const ssize_t y_offset,
2097 ExceptionInfo *exception)
2112 SetGeometry(reference,&geometry);
2113 geometry.x=x_offset;
2114 geometry.y=y_offset;
2115 similarity_image=CropImage(image,&geometry,exception);
2116 if (similarity_image == (Image *) NULL)
2119 status=GetImageDistortion(similarity_image,reference,metric,&distortion,
2121 similarity_image=DestroyImage(similarity_image);
2122 if (status == MagickFalse)
2124 similarity=distortion;
2127 case NormalizedCrossCorrelationErrorMetric:
2129 similarity=1.0-similarity;
2137MagickExport Image *SimilarityImage(Image *image,
const Image *reference,
2138 RectangleInfo *offset,
double *similarity_metric,ExceptionInfo *exception)
2143 similarity_image=SimilarityMetricImage(image,reference,
2144 RootMeanSquaredErrorMetric,offset,similarity_metric,exception);
2145 return(similarity_image);
2148MagickExport Image *SimilarityMetricImage(Image *image,
const Image *reconstruct,
2149 const MetricType metric,RectangleInfo *offset,
double *similarity_metric,
2150 ExceptionInfo *exception)
2152#define SimilarityImageTag "Similarity/Image"
2171 similarity_threshold;
2174 *similarity_image = (Image *) NULL;
2183 similarity_info = { 0 };
2188 assert(image != (
const Image *) NULL);
2189 assert(image->signature == MagickCoreSignature);
2190 assert(exception != (ExceptionInfo *) NULL);
2191 assert(exception->signature == MagickCoreSignature);
2192 assert(offset != (RectangleInfo *) NULL);
2193 if (IsEventLogging() != MagickFalse)
2194 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
2195 SetGeometry(reconstruct,offset);
2196 *similarity_metric=0.0;
2199 if (ValidateImageMorphology(image,reconstruct) == MagickFalse)
2200 ThrowImageException(ImageError,
"ImageMorphologyDiffers");
2201 if ((image->columns < reconstruct->columns) ||
2202 (image->rows < reconstruct->rows))
2204 (void) ThrowMagickException(&image->exception,GetMagickModule(),
2205 OptionWarning,
"GeometryDoesNotContainImage",
"`%s'",image->filename);
2206 return((Image *) NULL);
2208 similarity_image=CloneImage(image,image->columns,image->rows,MagickTrue,
2210 if (similarity_image == (Image *) NULL)
2211 return((Image *) NULL);
2212 similarity_image->depth=32;
2213 similarity_image->colorspace=GRAYColorspace;
2214 similarity_image->matte=MagickFalse;
2215 status=SetImageStorageClass(similarity_image,DirectClass);
2216 if (status == MagickFalse)
2218 InheritException(exception,&similarity_image->exception);
2219 return(DestroyImage(similarity_image));
2224 similarity_threshold=DefaultSimilarityThreshold;
2225 artifact=GetImageArtifact(image,
"compare:similarity-threshold");
2226 if (artifact != (
const char *) NULL)
2227 similarity_threshold=StringToDouble(artifact,(
char **) NULL);
2229 similarity_info.similarity=GetSimilarityMetric(image,reconstruct,metric,
2230 similarity_info.x,similarity_info.y,exception);
2232 similarity_view=AcquireVirtualCacheView(similarity_image,exception);
2233#if defined(MAGICKCORE_OPENMP_SUPPORT)
2234 #pragma omp parallel for schedule(static) shared(status,similarity_info) \
2235 magick_number_threads(image,reconstruct,similarity_image->rows << 2,1)
2237 for (y=0; y < (ssize_t) similarity_image->rows; y++)
2243 threshold_trigger = MagickFalse;
2249 channel_info = similarity_info;
2254 if (status == MagickFalse)
2256 if (threshold_trigger != MagickFalse)
2258 q=QueueCacheViewAuthenticPixels(similarity_view,0,y,
2259 similarity_image->columns,1,exception);
2260 if (q == (PixelPacket *) NULL)
2265 for (x=0; x < (ssize_t) similarity_image->columns; x++)
2268 update = MagickFalse;
2270 similarity=GetSimilarityMetric(image,reconstruct,metric,x,y,exception);
2273 case NormalizedCrossCorrelationErrorMetric:
2274 case PeakSignalToNoiseRatioMetric:
2276 if (similarity > channel_info.similarity)
2282 if (similarity < channel_info.similarity)
2287 if (update != MagickFalse)
2289 channel_info.similarity=similarity;
2295 case NormalizedCrossCorrelationErrorMetric:
2296 case PeakSignalToNoiseRatioMetric:
2298 SetPixelRed(q,ClampToQuantum((
double) QuantumRange*similarity));
2303 SetPixelRed(q,ClampToQuantum((
double) QuantumRange*(1.0-similarity)));
2307 SetPixelGreen(q,GetPixelRed(q));
2308 SetPixelBlue(q,GetPixelRed(q));
2311#if defined(MAGICKCORE_OPENMP_SUPPORT)
2312 #pragma omp critical (MagickCore_SimilarityMetricImage)
2316 case NormalizedCrossCorrelationErrorMetric:
2317 case PeakSignalToNoiseRatioMetric:
2319 if (similarity_threshold != DefaultSimilarityThreshold)
2320 if (channel_info.similarity >= similarity_threshold)
2321 threshold_trigger=MagickTrue;
2322 if (channel_info.similarity >= similarity_info.similarity)
2323 similarity_info=channel_info;
2328 if (similarity_threshold != DefaultSimilarityThreshold)
2329 if (channel_info.similarity < similarity_threshold)
2330 threshold_trigger=MagickTrue;
2331 if (channel_info.similarity < similarity_info.similarity)
2332 similarity_info=channel_info;
2336 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
2338 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2344 proceed=SetImageProgress(image,SimilarityImageTag,progress,image->rows);
2345 if (proceed == MagickFalse)
2349 similarity_view=DestroyCacheView(similarity_view);
2350 if (status == MagickFalse)
2351 similarity_image=DestroyImage(similarity_image);
2352 *similarity_metric=similarity_info.similarity;
2353 offset->x=similarity_info.x;
2354 offset->y=similarity_info.y;
2355 return(similarity_image);