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/composite-private.h"
55#include "magick/constitute.h"
56#include "magick/exception-private.h"
57#include "magick/geometry.h"
58#include "magick/image-private.h"
59#include "magick/list.h"
60#include "magick/log.h"
61#include "magick/memory_.h"
62#include "magick/monitor.h"
63#include "magick/monitor-private.h"
64#include "magick/option.h"
65#include "magick/pixel-private.h"
66#include "magick/property.h"
67#include "magick/resource_.h"
68#include "magick/statistic-private.h"
69#include "magick/string_.h"
70#include "magick/string-private.h"
71#include "magick/statistic.h"
72#include "magick/thread-private.h"
73#include "magick/transform.h"
74#include "magick/utility.h"
75#include "magick/version.h"
113MagickExport Image *CompareImages(Image *image,
const Image *reconstruct_image,
114 const MetricType metric,
double *distortion,ExceptionInfo *exception)
119 highlight_image=CompareImageChannels(image,reconstruct_image,
120 CompositeChannels,metric,distortion,exception);
121 return(highlight_image);
124static size_t GetNumberChannels(
const Image *image,
const ChannelType channel)
130 if ((channel & RedChannel) != 0)
132 if ((channel & GreenChannel) != 0)
134 if ((channel & BlueChannel) != 0)
136 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
138 if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
140 return(channels == 0 ? 1UL : channels);
143static void SetImageDistortionBounds(
const Image *image,
144 const Image *reconstruct_image,
size_t *columns,
size_t *rows)
149 *columns=MagickMax(image->columns,reconstruct_image->columns);
150 *rows=MagickMax(image->rows,reconstruct_image->rows);
151 artifact=GetImageArtifact(image,
"compare:virtual-pixels");
152 if ((artifact != (
const char *) NULL) &&
153 (IsStringTrue(artifact) == MagickFalse))
155 *columns=MagickMin(image->columns,reconstruct_image->columns);
156 *rows=MagickMin(image->rows,reconstruct_image->rows);
160static inline MagickBooleanType ValidateImageMorphology(
161 const Image *magick_restrict image,
162 const Image *magick_restrict reconstruct_image)
167 if (GetNumberChannels(image,DefaultChannels) !=
168 GetNumberChannels(reconstruct_image,DefaultChannels))
173MagickExport Image *CompareImageChannels(Image *image,
174 const Image *reconstruct_image,
const ChannelType channel,
175 const MetricType metric,
double *distortion,ExceptionInfo *exception)
208 assert(image != (Image *) NULL);
209 assert(image->signature == MagickCoreSignature);
210 assert(reconstruct_image != (
const Image *) NULL);
211 assert(reconstruct_image->signature == MagickCoreSignature);
212 assert(distortion != (
double *) NULL);
213 if (IsEventLogging() != MagickFalse)
214 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
216 if (metric != PerceptualHashErrorMetric)
217 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
218 ThrowImageException(ImageError,
"ImageMorphologyDiffers");
219 status=GetImageChannelDistortion(image,reconstruct_image,channel,metric,
220 distortion,exception);
221 if (status == MagickFalse)
222 return((Image *) NULL);
223 clone_image=CloneImage(image,0,0,MagickTrue,exception);
224 if (clone_image == (Image *) NULL)
225 return((Image *) NULL);
226 (void) SetImageMask(clone_image,(Image *) NULL);
227 difference_image=CloneImage(clone_image,0,0,MagickTrue,exception);
228 clone_image=DestroyImage(clone_image);
229 if (difference_image == (Image *) NULL)
230 return((Image *) NULL);
231 (void) SetImageAlphaChannel(difference_image,OpaqueAlphaChannel);
232 SetImageDistortionBounds(image,reconstruct_image,&columns,&rows);
233 highlight_image=CloneImage(image,columns,rows,MagickTrue,exception);
234 if (highlight_image == (Image *) NULL)
236 difference_image=DestroyImage(difference_image);
237 return((Image *) NULL);
239 if (SetImageStorageClass(highlight_image,DirectClass) == MagickFalse)
241 InheritException(exception,&highlight_image->exception);
242 difference_image=DestroyImage(difference_image);
243 highlight_image=DestroyImage(highlight_image);
244 return((Image *) NULL);
246 (void) SetImageMask(highlight_image,(Image *) NULL);
247 (void) SetImageAlphaChannel(highlight_image,OpaqueAlphaChannel);
248 (void) QueryMagickColor(
"#f1001ecc",&highlight,exception);
249 artifact=GetImageArtifact(image,
"compare:highlight-color");
250 if (artifact != (
const char *) NULL)
251 (void) QueryMagickColor(artifact,&highlight,exception);
252 (void) QueryMagickColor(
"#ffffffcc",&lowlight,exception);
253 artifact=GetImageArtifact(image,
"compare:lowlight-color");
254 if (artifact != (
const char *) NULL)
255 (void) QueryMagickColor(artifact,&lowlight,exception);
256 if (highlight_image->colorspace == CMYKColorspace)
258 ConvertRGBToCMYK(&highlight);
259 ConvertRGBToCMYK(&lowlight);
265 fuzz=GetFuzzyColorDistance(image,reconstruct_image);
266 GetMagickPixelPacket(image,&zero);
267 image_view=AcquireVirtualCacheView(image,exception);
268 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
269 highlight_view=AcquireAuthenticCacheView(highlight_image,exception);
270#if defined(MAGICKCORE_OPENMP_SUPPORT)
271 #pragma omp parallel for schedule(static) shared(status) \
272 magick_number_threads(image,highlight_image,rows,1)
274 for (y=0; y < (ssize_t) rows; y++)
284 *magick_restrict indexes,
285 *magick_restrict reconstruct_indexes;
292 *magick_restrict highlight_indexes;
300 if (status == MagickFalse)
302 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
303 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
304 r=QueueCacheViewAuthenticPixels(highlight_view,0,y,columns,1,exception);
305 if ((p == (
const PixelPacket *) NULL) ||
306 (q == (
const PixelPacket *) NULL) || (r == (PixelPacket *) NULL))
311 indexes=GetCacheViewVirtualIndexQueue(image_view);
312 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
313 highlight_indexes=GetCacheViewAuthenticIndexQueue(highlight_view);
315 reconstruct_pixel=zero;
316 for (x=0; x < (ssize_t) columns; x++)
321 SetMagickPixelPacket(image,p,indexes == (IndexPacket *) NULL ? NULL :
323 SetMagickPixelPacket(reconstruct_image,q,reconstruct_indexes ==
324 (IndexPacket *) NULL ? NULL : reconstruct_indexes+x,&reconstruct_pixel);
325 difference=MagickFalse;
326 if (channel == CompositeChannels)
328 if (IsMagickColorSimilar(&pixel,&reconstruct_pixel) == MagickFalse)
329 difference=MagickTrue;
339 Sa=QuantumScale*(image->matte != MagickFalse ? (double)
340 GetPixelAlpha(p) : ((double) QuantumRange-(double) OpaqueOpacity));
341 Da=QuantumScale*(image->matte != MagickFalse ? (double)
342 GetPixelAlpha(q) : ((double) QuantumRange-(double) OpaqueOpacity));
343 if ((channel & RedChannel) != 0)
345 pixel=Sa*(double) GetPixelRed(p)-Da*(double) GetPixelRed(q);
346 distance=pixel*pixel;
347 if (distance >= fuzz)
348 difference=MagickTrue;
350 if ((channel & GreenChannel) != 0)
352 pixel=Sa*(double) GetPixelGreen(p)-Da*(double) GetPixelGreen(q);
353 distance=pixel*pixel;
354 if (distance >= fuzz)
355 difference=MagickTrue;
357 if ((channel & BlueChannel) != 0)
359 pixel=Sa*(double) GetPixelBlue(p)-Da*(double) GetPixelBlue(q);
360 distance=pixel*pixel;
361 if (distance >= fuzz)
362 difference=MagickTrue;
364 if (((channel & OpacityChannel) != 0) &&
365 (image->matte != MagickFalse))
367 pixel=(double) GetPixelOpacity(p)-(double) GetPixelOpacity(q);
368 distance=pixel*pixel;
369 if (distance >= fuzz)
370 difference=MagickTrue;
372 if (((channel & IndexChannel) != 0) &&
373 (image->colorspace == CMYKColorspace))
375 pixel=Sa*(double) indexes[x]-Da*(double) reconstruct_indexes[x];
376 distance=pixel*pixel;
377 if (distance >= fuzz)
378 difference=MagickTrue;
381 if (difference != MagickFalse)
382 SetPixelPacket(highlight_image,&highlight,r,highlight_indexes ==
383 (IndexPacket *) NULL ? NULL : highlight_indexes+x);
385 SetPixelPacket(highlight_image,&lowlight,r,highlight_indexes ==
386 (IndexPacket *) NULL ? NULL : highlight_indexes+x);
391 sync=SyncCacheViewAuthenticPixels(highlight_view,exception);
392 if (sync == MagickFalse)
395 highlight_view=DestroyCacheView(highlight_view);
396 reconstruct_view=DestroyCacheView(reconstruct_view);
397 image_view=DestroyCacheView(image_view);
398 (void) CompositeImage(difference_image,image->compose,highlight_image,0,0);
399 highlight_image=DestroyImage(highlight_image);
400 if (status == MagickFalse)
401 difference_image=DestroyImage(difference_image);
402 return(difference_image);
441MagickExport MagickBooleanType GetImageDistortion(Image *image,
442 const Image *reconstruct_image,
const MetricType metric,
double *distortion,
443 ExceptionInfo *exception)
448 status=GetImageChannelDistortion(image,reconstruct_image,CompositeChannels,
449 metric,distortion,exception);
453static MagickBooleanType GetAbsoluteDistortion(
const Image *image,
454 const Image *reconstruct_image,
const ChannelType channel,
double *distortion,
455 ExceptionInfo *exception)
478 fuzz=GetFuzzyColorDistance(image,reconstruct_image);
479 SetImageDistortionBounds(image,reconstruct_image,&columns,&rows);
480 image_view=AcquireVirtualCacheView(image,exception);
481 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
482#if defined(MAGICKCORE_OPENMP_SUPPORT)
483 #pragma omp parallel for schedule(static) shared(status) \
484 magick_number_threads(image,image,rows,1)
486 for (y=0; y < (ssize_t) rows; y++)
489 channel_distortion[CompositeChannels+1];
492 *magick_restrict indexes,
493 *magick_restrict reconstruct_indexes;
503 if (status == MagickFalse)
505 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
506 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
507 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
512 indexes=GetCacheViewVirtualIndexQueue(image_view);
513 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
514 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
515 for (x=0; x < (ssize_t) columns; x++)
526 difference=MagickFalse;
527 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
528 ((double) QuantumRange-(double) OpaqueOpacity));
529 Da=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(q) :
530 ((double) QuantumRange-(double) OpaqueOpacity));
531 if ((channel & RedChannel) != 0)
533 pixel=Sa*(double) GetPixelRed(p)-Da*(double) GetPixelRed(q);
534 distance=pixel*pixel;
535 if (distance >= fuzz)
537 channel_distortion[RedChannel]++;
538 difference=MagickTrue;
541 if ((channel & GreenChannel) != 0)
543 pixel=Sa*(double) GetPixelGreen(p)-Da*(double) GetPixelGreen(q);
544 distance=pixel*pixel;
545 if (distance >= fuzz)
547 channel_distortion[GreenChannel]++;
548 difference=MagickTrue;
551 if ((channel & BlueChannel) != 0)
553 pixel=Sa*(double) GetPixelBlue(p)-Da*(double) GetPixelBlue(q);
554 distance=pixel*pixel;
555 if (distance >= fuzz)
557 channel_distortion[BlueChannel]++;
558 difference=MagickTrue;
561 if (((channel & OpacityChannel) != 0) &&
562 (image->matte != MagickFalse))
564 pixel=(double) GetPixelOpacity(p)-(double) GetPixelOpacity(q);
565 distance=pixel*pixel;
566 if (distance >= fuzz)
568 channel_distortion[OpacityChannel]++;
569 difference=MagickTrue;
572 if (((channel & IndexChannel) != 0) &&
573 (image->colorspace == CMYKColorspace))
575 pixel=Sa*(double) indexes[x]-Da*(
double) reconstruct_indexes[x];
576 distance=pixel*pixel;
577 if (distance >= fuzz)
579 channel_distortion[BlackChannel]++;
580 difference=MagickTrue;
583 if (difference != MagickFalse)
584 channel_distortion[CompositeChannels]++;
588#if defined(MAGICKCORE_OPENMP_SUPPORT)
589 #pragma omp critical (MagickCore_GetAbsoluteDistortion)
591 for (i=0; i <= (ssize_t) CompositeChannels; i++)
592 distortion[i]+=channel_distortion[i];
594 reconstruct_view=DestroyCacheView(reconstruct_view);
595 image_view=DestroyCacheView(image_view);
599static MagickBooleanType GetFuzzDistortion(
const Image *image,
600 const Image *reconstruct_image,
const ChannelType channel,
601 double *distortion,ExceptionInfo *exception)
621 SetImageDistortionBounds(image,reconstruct_image,&columns,&rows);
622 image_view=AcquireVirtualCacheView(image,exception);
623 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
624#if defined(MAGICKCORE_OPENMP_SUPPORT)
625 #pragma omp parallel for schedule(static) shared(status) \
626 magick_number_threads(image,image,rows,1)
628 for (y=0; y < (ssize_t) rows; y++)
631 channel_distortion[CompositeChannels+1];
634 *magick_restrict indexes,
635 *magick_restrict reconstruct_indexes;
645 if (status == MagickFalse)
647 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
648 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
649 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
654 indexes=GetCacheViewVirtualIndexQueue(image_view);
655 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
656 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
657 for (x=0; x < (ssize_t) columns; x++)
664 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
665 ((double) QuantumRange-(double) OpaqueOpacity));
666 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
667 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
669 if ((channel & RedChannel) != 0)
671 distance=QuantumScale*(Sa*(double) GetPixelRed(p)-Da*(double)
673 channel_distortion[RedChannel]+=distance*distance;
674 channel_distortion[CompositeChannels]+=distance*distance;
676 if ((channel & GreenChannel) != 0)
678 distance=QuantumScale*(Sa*(double) GetPixelGreen(p)-Da*(double)
680 channel_distortion[GreenChannel]+=distance*distance;
681 channel_distortion[CompositeChannels]+=distance*distance;
683 if ((channel & BlueChannel) != 0)
685 distance=QuantumScale*(Sa*(double) GetPixelBlue(p)-Da*(double)
687 channel_distortion[BlueChannel]+=distance*distance;
688 channel_distortion[CompositeChannels]+=distance*distance;
690 if (((channel & OpacityChannel) != 0) && ((image->matte != MagickFalse) ||
691 (reconstruct_image->matte != MagickFalse)))
693 distance=QuantumScale*((image->matte != MagickFalse ? (double)
694 GetPixelOpacity(p) : (double) OpaqueOpacity)-
695 (reconstruct_image->matte != MagickFalse ?
696 (double) GetPixelOpacity(q): (double) OpaqueOpacity));
697 channel_distortion[OpacityChannel]+=distance*distance;
698 channel_distortion[CompositeChannels]+=distance*distance;
700 if (((channel & IndexChannel) != 0) &&
701 (image->colorspace == CMYKColorspace) &&
702 (reconstruct_image->colorspace == CMYKColorspace))
704 distance=QuantumScale*(Sa*(double) GetPixelIndex(indexes+x)-
705 Da*(double) GetPixelIndex(reconstruct_indexes+x));
706 channel_distortion[BlackChannel]+=distance*distance;
707 channel_distortion[CompositeChannels]+=distance*distance;
712#if defined(MAGICKCORE_OPENMP_SUPPORT)
713 #pragma omp critical (MagickCore_GetFuzzDistortion)
715 for (i=0; i <= (ssize_t) CompositeChannels; i++)
716 distortion[i]+=channel_distortion[i];
718 reconstruct_view=DestroyCacheView(reconstruct_view);
719 image_view=DestroyCacheView(image_view);
720 for (i=0; i <= (ssize_t) CompositeChannels; i++)
721 distortion[i]/=((
double) columns*rows);
722 distortion[CompositeChannels]/=(double) GetNumberChannels(image,channel);
723 distortion[CompositeChannels]=sqrt(distortion[CompositeChannels]);
727static MagickBooleanType GetMeanAbsoluteDistortion(
const Image *image,
728 const Image *reconstruct_image,
const ChannelType channel,
729 double *distortion,ExceptionInfo *exception)
747 SetImageDistortionBounds(image,reconstruct_image,&columns,&rows);
748 image_view=AcquireVirtualCacheView(image,exception);
749 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
750#if defined(MAGICKCORE_OPENMP_SUPPORT)
751 #pragma omp parallel for schedule(static) shared(status) \
752 magick_number_threads(image,image,rows,1)
754 for (y=0; y < (ssize_t) rows; y++)
757 channel_distortion[CompositeChannels+1];
760 *magick_restrict indexes,
761 *magick_restrict reconstruct_indexes;
771 if (status == MagickFalse)
773 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
774 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
775 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
780 indexes=GetCacheViewVirtualIndexQueue(image_view);
781 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
782 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
783 for (x=0; x < (ssize_t) columns; x++)
790 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
791 ((double) QuantumRange-(double) OpaqueOpacity));
792 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
793 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
795 if ((channel & RedChannel) != 0)
797 distance=QuantumScale*fabs(Sa*(
double) GetPixelRed(p)-Da*
798 (
double) GetPixelRed(q));
799 channel_distortion[RedChannel]+=distance;
800 channel_distortion[CompositeChannels]+=distance;
802 if ((channel & GreenChannel) != 0)
804 distance=QuantumScale*fabs(Sa*(
double) GetPixelGreen(p)-Da*
805 (
double) GetPixelGreen(q));
806 channel_distortion[GreenChannel]+=distance;
807 channel_distortion[CompositeChannels]+=distance;
809 if ((channel & BlueChannel) != 0)
811 distance=QuantumScale*fabs(Sa*(
double) GetPixelBlue(p)-Da*
812 (
double) GetPixelBlue(q));
813 channel_distortion[BlueChannel]+=distance;
814 channel_distortion[CompositeChannels]+=distance;
816 if (((channel & OpacityChannel) != 0) &&
817 (image->matte != MagickFalse))
819 distance=QuantumScale*fabs((double) GetPixelOpacity(p)-(double)
821 channel_distortion[OpacityChannel]+=distance;
822 channel_distortion[CompositeChannels]+=distance;
824 if (((channel & IndexChannel) != 0) &&
825 (image->colorspace == CMYKColorspace))
827 distance=QuantumScale*fabs(Sa*(double) GetPixelIndex(indexes+x)-Da*
828 (double) GetPixelIndex(reconstruct_indexes+x));
829 channel_distortion[BlackChannel]+=distance;
830 channel_distortion[CompositeChannels]+=distance;
835#if defined(MAGICKCORE_OPENMP_SUPPORT)
836 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
838 for (i=0; i <= (ssize_t) CompositeChannels; i++)
839 distortion[i]+=channel_distortion[i];
841 reconstruct_view=DestroyCacheView(reconstruct_view);
842 image_view=DestroyCacheView(image_view);
843 for (i=0; i <= (ssize_t) CompositeChannels; i++)
844 distortion[i]/=((
double) columns*rows);
845 distortion[CompositeChannels]/=(double) GetNumberChannels(image,channel);
849static MagickBooleanType GetMeanErrorPerPixel(Image *image,
850 const Image *reconstruct_image,
const ChannelType channel,
double *distortion,
851 ExceptionInfo *exception)
858 maximum_error = MagickMinimumValue;
872 SetImageDistortionBounds(image,reconstruct_image,&columns,&rows);
873 image_view=AcquireVirtualCacheView(image,exception);
874 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
875#if defined(MAGICKCORE_OPENMP_SUPPORT)
876 #pragma omp parallel for schedule(static) shared(status) \
877 magick_number_threads(image,image,rows,1)
879 for (y=0; y < (ssize_t) rows; y++)
882 channel_distortion[CompositeChannels+1],
883 local_maximum = MinimumValue;
886 *magick_restrict indexes,
887 *magick_restrict reconstruct_indexes;
897 if (status == MagickFalse)
899 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
900 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
901 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
906 indexes=GetCacheViewVirtualIndexQueue(image_view);
907 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
908 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
909 for (x=0; x < (ssize_t) columns; x++)
916 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
917 ((double) QuantumRange-(double) OpaqueOpacity));
918 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
919 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
921 if ((channel & RedChannel) != 0)
923 distance=QuantumScale*fabs(Sa*(
double) GetPixelRed(p)-Da*
924 (
double) GetPixelRed(q));
925 channel_distortion[RedChannel]+=distance;
926 channel_distortion[CompositeChannels]+=distance;
927 if (distance > local_maximum)
928 local_maximum=distance;
930 if ((channel & GreenChannel) != 0)
932 distance=QuantumScale*fabs(Sa*(
double) GetPixelGreen(p)-Da*
933 (
double) GetPixelGreen(q));
934 channel_distortion[GreenChannel]+=distance;
935 channel_distortion[CompositeChannels]+=distance;
936 if (distance > local_maximum)
937 local_maximum=distance;
939 if ((channel & BlueChannel) != 0)
941 distance=QuantumScale*fabs(Sa*(
double) GetPixelBlue(p)-Da*
942 (
double) GetPixelBlue(q));
943 channel_distortion[BlueChannel]+=distance;
944 channel_distortion[CompositeChannels]+=distance;
945 if (distance > local_maximum)
946 local_maximum=distance;
948 if (((channel & OpacityChannel) != 0) &&
949 (image->matte != MagickFalse))
951 distance=QuantumScale*fabs((double) GetPixelOpacity(p)-(double)
953 channel_distortion[OpacityChannel]+=distance;
954 channel_distortion[CompositeChannels]+=distance;
955 if (distance > local_maximum)
956 local_maximum=distance;
958 if (((channel & IndexChannel) != 0) &&
959 (image->colorspace == CMYKColorspace))
961 distance=QuantumScale*fabs(Sa*(double) GetPixelIndex(indexes+x)-Da*
962 (double) GetPixelIndex(reconstruct_indexes+x));
963 channel_distortion[BlackChannel]+=distance;
964 channel_distortion[CompositeChannels]+=distance;
965 if (distance > local_maximum)
966 local_maximum=distance;
971#if defined(MAGICKCORE_OPENMP_SUPPORT)
972 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
975 for (i=0; i <= (ssize_t) CompositeChannels; i++)
976 distortion[i]+=channel_distortion[i];
977 if (local_maximum > maximum_error)
978 maximum_error=local_maximum;
981 reconstruct_view=DestroyCacheView(reconstruct_view);
982 image_view=DestroyCacheView(image_view);
983 distortion[CompositeChannels]/=(double) GetNumberChannels(image,channel);
984 for (i=0; i <= (ssize_t) CompositeChannels; i++)
985 distortion[i]/=((
double) columns*rows);
986 image->error.mean_error_per_pixel=distortion[CompositeChannels];
987 image->error.normalized_mean_error=distortion[CompositeChannels];
988 image->error.normalized_maximum_error=QuantumScale*maximum_error;
992static MagickBooleanType GetMeanSquaredDistortion(
const Image *image,
993 const Image *reconstruct_image,
const ChannelType channel,
994 double *distortion,ExceptionInfo *exception)
1014 SetImageDistortionBounds(image,reconstruct_image,&columns,&rows);
1015 image_view=AcquireVirtualCacheView(image,exception);
1016 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1017#if defined(MAGICKCORE_OPENMP_SUPPORT)
1018 #pragma omp parallel for schedule(static) shared(status) \
1019 magick_number_threads(image,image,rows,1)
1021 for (y=0; y < (ssize_t) rows; y++)
1024 channel_distortion[CompositeChannels+1];
1027 *magick_restrict indexes,
1028 *magick_restrict reconstruct_indexes;
1038 if (status == MagickFalse)
1040 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1041 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1042 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
1047 indexes=GetCacheViewVirtualIndexQueue(image_view);
1048 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1049 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
1050 for (x=0; x < (ssize_t) columns; x++)
1057 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
1058 ((double) QuantumRange-(double) OpaqueOpacity));
1059 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1060 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
1062 if ((channel & RedChannel) != 0)
1064 distance=QuantumScale*(Sa*(double) GetPixelRed(p)-Da*(double)
1066 channel_distortion[RedChannel]+=distance*distance;
1067 channel_distortion[CompositeChannels]+=distance*distance;
1069 if ((channel & GreenChannel) != 0)
1071 distance=QuantumScale*(Sa*(double) GetPixelGreen(p)-Da*(double)
1073 channel_distortion[GreenChannel]+=distance*distance;
1074 channel_distortion[CompositeChannels]+=distance*distance;
1076 if ((channel & BlueChannel) != 0)
1078 distance=QuantumScale*(Sa*(double) GetPixelBlue(p)-Da*(double)
1080 channel_distortion[BlueChannel]+=distance*distance;
1081 channel_distortion[CompositeChannels]+=distance*distance;
1083 if (((channel & OpacityChannel) != 0) &&
1084 (image->matte != MagickFalse))
1086 distance=QuantumScale*((double) GetPixelOpacity(p)-(double)
1087 GetPixelOpacity(q));
1088 channel_distortion[OpacityChannel]+=distance*distance;
1089 channel_distortion[CompositeChannels]+=distance*distance;
1091 if (((channel & IndexChannel) != 0) &&
1092 (image->colorspace == CMYKColorspace) &&
1093 (reconstruct_image->colorspace == CMYKColorspace))
1095 distance=QuantumScale*(Sa*(double) GetPixelIndex(indexes+x)-Da*
1096 (double) GetPixelIndex(reconstruct_indexes+x));
1097 channel_distortion[BlackChannel]+=distance*distance;
1098 channel_distortion[CompositeChannels]+=distance*distance;
1103#if defined(MAGICKCORE_OPENMP_SUPPORT)
1104 #pragma omp critical (MagickCore_GetMeanSquaredError)
1106 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1107 distortion[i]+=channel_distortion[i];
1109 reconstruct_view=DestroyCacheView(reconstruct_view);
1110 image_view=DestroyCacheView(image_view);
1111 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1112 distortion[i]/=((
double) columns*rows);
1113 distortion[CompositeChannels]/=(double) GetNumberChannels(image,channel);
1117static MagickBooleanType GetNormalizedCrossCorrelationDistortion(
1118 const Image *image,
const Image *reconstruct_image,
const ChannelType channel,
1119 double *distortion,ExceptionInfo *exception)
1121#define SimilarityImageTag "Similarity/Image"
1129 *reconstruct_statistics;
1132 alpha_variance[CompositeChannels+1],
1133 beta_variance[CompositeChannels+1];
1154 image_statistics=GetImageChannelStatistics(image,exception);
1155 reconstruct_statistics=GetImageChannelStatistics(reconstruct_image,exception);
1156 if ((image_statistics == (ChannelStatistics *) NULL) ||
1157 (reconstruct_statistics == (ChannelStatistics *) NULL))
1159 if (image_statistics != (ChannelStatistics *) NULL)
1160 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1162 if (reconstruct_statistics != (ChannelStatistics *) NULL)
1163 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1164 reconstruct_statistics);
1165 return(MagickFalse);
1167 (void) memset(distortion,0,(CompositeChannels+1)*
sizeof(*distortion));
1168 (void) memset(alpha_variance,0,(CompositeChannels+1)*
sizeof(*alpha_variance));
1169 (void) memset(beta_variance,0,(CompositeChannels+1)*
sizeof(*beta_variance));
1172 SetImageDistortionBounds(image,reconstruct_image,&columns,&rows);
1173 image_view=AcquireVirtualCacheView(image,exception);
1174 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1175 for (y=0; y < (ssize_t) rows; y++)
1178 *magick_restrict indexes,
1179 *magick_restrict reconstruct_indexes;
1188 if (status == MagickFalse)
1190 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1191 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1192 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
1197 indexes=GetCacheViewVirtualIndexQueue(image_view);
1198 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1199 for (x=0; x < (ssize_t) columns; x++)
1207 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
1208 (double) QuantumRange);
1209 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1210 (double) GetPixelAlpha(q) : (double) QuantumRange);
1211 if ((channel & RedChannel) != 0)
1213 alpha=QuantumScale*fabs(Sa*(
double) GetPixelRed(p)-
1214 image_statistics[RedChannel].mean);
1215 beta=QuantumScale*fabs(Da*(
double) GetPixelRed(q)-
1216 reconstruct_statistics[RedChannel].mean);
1217 distortion[RedChannel]+=alpha*beta;
1218 alpha_variance[RedChannel]+=alpha*alpha;
1219 beta_variance[RedChannel]+=beta*beta;
1221 if ((channel & GreenChannel) != 0)
1223 alpha=QuantumScale*fabs(Sa*(
double) GetPixelGreen(p)-
1224 image_statistics[GreenChannel].mean);
1225 beta=QuantumScale*fabs(Da*(
double) GetPixelGreen(q)-
1226 reconstruct_statistics[GreenChannel].mean);
1227 distortion[GreenChannel]+=alpha*beta;
1228 alpha_variance[GreenChannel]+=alpha*alpha;
1229 beta_variance[GreenChannel]+=beta*beta;
1231 if ((channel & BlueChannel) != 0)
1233 alpha=QuantumScale*fabs(Sa*(
double) GetPixelBlue(p)-
1234 image_statistics[BlueChannel].mean);
1235 beta=QuantumScale*fabs(Da*(
double) GetPixelBlue(q)-
1236 reconstruct_statistics[BlueChannel].mean);
1237 distortion[BlueChannel]+=alpha*beta;
1238 alpha_variance[BlueChannel]+=alpha*alpha;
1239 beta_variance[BlueChannel]+=beta*beta;
1241 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
1243 alpha=QuantumScale*fabs((double) GetPixelAlpha(p)-
1244 image_statistics[AlphaChannel].mean);
1245 beta=QuantumScale*fabs((double) GetPixelAlpha(q)-
1246 reconstruct_statistics[AlphaChannel].mean);
1247 distortion[OpacityChannel]+=alpha*beta;
1248 alpha_variance[OpacityChannel]+=alpha*alpha;
1249 beta_variance[OpacityChannel]+=beta*beta;
1251 if (((channel & IndexChannel) != 0) &&
1252 (image->colorspace == CMYKColorspace) &&
1253 (reconstruct_image->colorspace == CMYKColorspace))
1255 alpha=QuantumScale*fabs(Sa*(double) GetPixelIndex(indexes+x)-
1256 image_statistics[BlackChannel].mean);
1257 beta=QuantumScale*fabs(Da*(double) GetPixelIndex(reconstruct_indexes+
1258 x)-reconstruct_statistics[BlackChannel].mean);
1259 distortion[BlackChannel]+=alpha*beta;
1260 alpha_variance[BlackChannel]+=alpha*alpha;
1261 beta_variance[BlackChannel]+=beta*beta;
1266 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1271#if defined(MAGICKCORE_OPENMP_SUPPORT)
1275 proceed=SetImageProgress(image,SimilarityImageTag,progress,rows);
1276 if (proceed == MagickFalse)
1280 reconstruct_view=DestroyCacheView(reconstruct_view);
1281 image_view=DestroyCacheView(image_view);
1285 for (i=0; i < (ssize_t) CompositeChannels; i++)
1287 distortion[i]/=sqrt(alpha_variance[i]*beta_variance[i]);
1288 if (fabs(distortion[i]) >= MagickEpsilon)
1289 distortion[CompositeChannels]+=distortion[i];
1291 distortion[CompositeChannels]=distortion[CompositeChannels]/
1292 GetNumberChannels(image,channel);
1296 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1297 reconstruct_statistics);
1298 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1303static MagickBooleanType GetPeakAbsoluteDistortion(
const Image *image,
1304 const Image *reconstruct_image,
const ChannelType channel,
1305 double *distortion,ExceptionInfo *exception)
1322 (void) memset(distortion,0,(CompositeChannels+1)*
sizeof(*distortion));
1323 SetImageDistortionBounds(image,reconstruct_image,&columns,&rows);
1324 image_view=AcquireVirtualCacheView(image,exception);
1325 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1326#if defined(MAGICKCORE_OPENMP_SUPPORT)
1327 #pragma omp parallel for schedule(static) shared(status) \
1328 magick_number_threads(image,image,rows,1)
1330 for (y=0; y < (ssize_t) rows; y++)
1333 channel_distortion[CompositeChannels+1];
1336 *magick_restrict indexes,
1337 *magick_restrict reconstruct_indexes;
1347 if (status == MagickFalse)
1349 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1350 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1351 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
1356 indexes=GetCacheViewVirtualIndexQueue(image_view);
1357 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1358 (void) memset(channel_distortion,0,(CompositeChannels+1)*
1359 sizeof(*channel_distortion));
1360 for (x=0; x < (ssize_t) columns; x++)
1367 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
1368 ((double) QuantumRange-(double) OpaqueOpacity));
1369 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1370 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
1372 if ((channel & RedChannel) != 0)
1374 distance=QuantumScale*fabs(Sa*(
double) GetPixelRed(p)-Da*
1375 (
double) GetPixelRed(q));
1376 if (distance > channel_distortion[RedChannel])
1377 channel_distortion[RedChannel]=distance;
1378 if (distance > channel_distortion[CompositeChannels])
1379 channel_distortion[CompositeChannels]=distance;
1381 if ((channel & GreenChannel) != 0)
1383 distance=QuantumScale*fabs(Sa*(
double) GetPixelGreen(p)-Da*
1384 (
double) GetPixelGreen(q));
1385 if (distance > channel_distortion[GreenChannel])
1386 channel_distortion[GreenChannel]=distance;
1387 if (distance > channel_distortion[CompositeChannels])
1388 channel_distortion[CompositeChannels]=distance;
1390 if ((channel & BlueChannel) != 0)
1392 distance=QuantumScale*fabs(Sa*(
double) GetPixelBlue(p)-Da*
1393 (
double) GetPixelBlue(q));
1394 if (distance > channel_distortion[BlueChannel])
1395 channel_distortion[BlueChannel]=distance;
1396 if (distance > channel_distortion[CompositeChannels])
1397 channel_distortion[CompositeChannels]=distance;
1399 if (((channel & OpacityChannel) != 0) &&
1400 (image->matte != MagickFalse))
1402 distance=QuantumScale*fabs((double) GetPixelOpacity(p)-(double)
1403 GetPixelOpacity(q));
1404 if (distance > channel_distortion[OpacityChannel])
1405 channel_distortion[OpacityChannel]=distance;
1406 if (distance > channel_distortion[CompositeChannels])
1407 channel_distortion[CompositeChannels]=distance;
1409 if (((channel & IndexChannel) != 0) &&
1410 (image->colorspace == CMYKColorspace) &&
1411 (reconstruct_image->colorspace == CMYKColorspace))
1413 distance=QuantumScale*fabs(Sa*(double) GetPixelIndex(indexes+x)-Da*
1414 (double) GetPixelIndex(reconstruct_indexes+x));
1415 if (distance > channel_distortion[BlackChannel])
1416 channel_distortion[BlackChannel]=distance;
1417 if (distance > channel_distortion[CompositeChannels])
1418 channel_distortion[CompositeChannels]=distance;
1423#if defined(MAGICKCORE_OPENMP_SUPPORT)
1424 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
1426 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1427 if (channel_distortion[i] > distortion[i])
1428 distortion[i]=channel_distortion[i];
1430 reconstruct_view=DestroyCacheView(reconstruct_view);
1431 image_view=DestroyCacheView(image_view);
1435static MagickBooleanType GetPeakSignalToNoiseRatio(
const Image *image,
1436 const Image *reconstruct_image,
const ChannelType channel,
1437 double *distortion,ExceptionInfo *exception)
1442 status=GetMeanSquaredDistortion(image,reconstruct_image,channel,distortion,
1444 if ((channel & RedChannel) != 0)
1446 if (fabs(distortion[RedChannel]) >= MagickEpsilon)
1447 distortion[RedChannel]=fabs(-10.0*MagickLog10(PerceptibleReciprocal(
1448 distortion[RedChannel])))/48.1647;
1450 if ((channel & GreenChannel) != 0)
1452 if (fabs(distortion[GreenChannel]) >= MagickEpsilon)
1453 distortion[GreenChannel]=fabs(-10.0*MagickLog10(PerceptibleReciprocal(
1454 distortion[GreenChannel])))/48.1647;
1456 if ((channel & BlueChannel) != 0)
1458 if (fabs(distortion[BlueChannel]) >= MagickEpsilon)
1459 distortion[BlueChannel]=fabs(-10.0*MagickLog10(PerceptibleReciprocal(
1460 distortion[BlueChannel])))/48.1647;
1462 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
1464 if (fabs(distortion[OpacityChannel]) >= MagickEpsilon)
1465 distortion[OpacityChannel]=fabs(-10.0*MagickLog10(PerceptibleReciprocal(
1466 distortion[OpacityChannel])))/48.1647;
1468 if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
1470 if (fabs(distortion[BlackChannel]) >= MagickEpsilon)
1471 distortion[BlackChannel]=fabs(-10.0*MagickLog10(PerceptibleReciprocal(
1472 distortion[BlackChannel])))/48.1647;
1474 if (fabs(distortion[CompositeChannels]) >= MagickEpsilon)
1475 distortion[CompositeChannels]=fabs(-10.0*MagickLog10(PerceptibleReciprocal(
1476 distortion[CompositeChannels])))/48.1647;
1480static MagickBooleanType GetPerceptualHashDistortion(
const Image *image,
1481 const Image *reconstruct_image,
const ChannelType channel,
double *distortion,
1482 ExceptionInfo *exception)
1484 ChannelPerceptualHash
1497 image_phash=GetImageChannelPerceptualHash(image,exception);
1498 if (image_phash == (ChannelPerceptualHash *) NULL)
1499 return(MagickFalse);
1500 reconstruct_phash=GetImageChannelPerceptualHash(reconstruct_image,exception);
1501 if (reconstruct_phash == (ChannelPerceptualHash *) NULL)
1503 image_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(image_phash);
1504 return(MagickFalse);
1506 for (i=0; i < MaximumNumberOfImageMoments; i++)
1511 if ((channel & RedChannel) != 0)
1513 difference=reconstruct_phash[RedChannel].P[i]-
1514 image_phash[RedChannel].P[i];
1515 distortion[RedChannel]+=difference*difference;
1516 distortion[CompositeChannels]+=difference*difference;
1518 if ((channel & GreenChannel) != 0)
1520 difference=reconstruct_phash[GreenChannel].P[i]-
1521 image_phash[GreenChannel].P[i];
1522 distortion[GreenChannel]+=difference*difference;
1523 distortion[CompositeChannels]+=difference*difference;
1525 if ((channel & BlueChannel) != 0)
1527 difference=reconstruct_phash[BlueChannel].P[i]-
1528 image_phash[BlueChannel].P[i];
1529 distortion[BlueChannel]+=difference*difference;
1530 distortion[CompositeChannels]+=difference*difference;
1532 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse) &&
1533 (reconstruct_image->matte != MagickFalse))
1535 difference=reconstruct_phash[OpacityChannel].P[i]-
1536 image_phash[OpacityChannel].P[i];
1537 distortion[OpacityChannel]+=difference*difference;
1538 distortion[CompositeChannels]+=difference*difference;
1540 if (((channel & IndexChannel) != 0) &&
1541 (image->colorspace == CMYKColorspace) &&
1542 (reconstruct_image->colorspace == CMYKColorspace))
1544 difference=reconstruct_phash[IndexChannel].P[i]-
1545 image_phash[IndexChannel].P[i];
1546 distortion[IndexChannel]+=difference*difference;
1547 distortion[CompositeChannels]+=difference*difference;
1553 for (i=0; i < MaximumNumberOfImageMoments; i++)
1558 if ((channel & RedChannel) != 0)
1560 difference=reconstruct_phash[RedChannel].Q[i]-
1561 image_phash[RedChannel].Q[i];
1562 distortion[RedChannel]+=difference*difference;
1563 distortion[CompositeChannels]+=difference*difference;
1565 if ((channel & GreenChannel) != 0)
1567 difference=reconstruct_phash[GreenChannel].Q[i]-
1568 image_phash[GreenChannel].Q[i];
1569 distortion[GreenChannel]+=difference*difference;
1570 distortion[CompositeChannels]+=difference*difference;
1572 if ((channel & BlueChannel) != 0)
1574 difference=reconstruct_phash[BlueChannel].Q[i]-
1575 image_phash[BlueChannel].Q[i];
1576 distortion[BlueChannel]+=difference*difference;
1577 distortion[CompositeChannels]+=difference*difference;
1579 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse) &&
1580 (reconstruct_image->matte != MagickFalse))
1582 difference=reconstruct_phash[OpacityChannel].Q[i]-
1583 image_phash[OpacityChannel].Q[i];
1584 distortion[OpacityChannel]+=difference*difference;
1585 distortion[CompositeChannels]+=difference*difference;
1587 if (((channel & IndexChannel) != 0) &&
1588 (image->colorspace == CMYKColorspace) &&
1589 (reconstruct_image->colorspace == CMYKColorspace))
1591 difference=reconstruct_phash[IndexChannel].Q[i]-
1592 image_phash[IndexChannel].Q[i];
1593 distortion[IndexChannel]+=difference*difference;
1594 distortion[CompositeChannels]+=difference*difference;
1600 reconstruct_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(
1602 image_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(image_phash);
1606static MagickBooleanType GetRootMeanSquaredDistortion(
const Image *image,
1607 const Image *reconstruct_image,
const ChannelType channel,
double *distortion,
1608 ExceptionInfo *exception)
1613 status=GetMeanSquaredDistortion(image,reconstruct_image,channel,distortion,
1615 if ((channel & RedChannel) != 0)
1616 distortion[RedChannel]=sqrt(distortion[RedChannel]);
1617 if ((channel & GreenChannel) != 0)
1618 distortion[GreenChannel]=sqrt(distortion[GreenChannel]);
1619 if ((channel & BlueChannel) != 0)
1620 distortion[BlueChannel]=sqrt(distortion[BlueChannel]);
1621 if (((channel & OpacityChannel) != 0) &&
1622 (image->matte != MagickFalse))
1623 distortion[OpacityChannel]=sqrt(distortion[OpacityChannel]);
1624 if (((channel & IndexChannel) != 0) &&
1625 (image->colorspace == CMYKColorspace))
1626 distortion[BlackChannel]=sqrt(distortion[BlackChannel]);
1627 distortion[CompositeChannels]=sqrt(distortion[CompositeChannels]);
1631MagickExport MagickBooleanType GetImageChannelDistortion(Image *image,
1632 const Image *reconstruct_image,
const ChannelType channel,
1633 const MetricType metric,
double *distortion,ExceptionInfo *exception)
1636 *channel_distortion;
1644 assert(image != (Image *) NULL);
1645 assert(image->signature == MagickCoreSignature);
1646 assert(reconstruct_image != (
const Image *) NULL);
1647 assert(reconstruct_image->signature == MagickCoreSignature);
1648 assert(distortion != (
double *) NULL);
1649 if (IsEventLogging() != MagickFalse)
1650 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
1652 if (metric != PerceptualHashErrorMetric)
1653 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1654 ThrowBinaryException(ImageError,
"ImageMorphologyDiffers",image->filename);
1658 length=CompositeChannels+1UL;
1659 channel_distortion=(
double *) AcquireQuantumMemory(length,
1660 sizeof(*channel_distortion));
1661 if (channel_distortion == (
double *) NULL)
1662 ThrowFatalException(ResourceLimitFatalError,
"MemoryAllocationFailed");
1663 (void) memset(channel_distortion,0,length*
sizeof(*channel_distortion));
1666 case AbsoluteErrorMetric:
1668 status=GetAbsoluteDistortion(image,reconstruct_image,channel,
1669 channel_distortion,exception);
1672 case FuzzErrorMetric:
1674 status=GetFuzzDistortion(image,reconstruct_image,channel,
1675 channel_distortion,exception);
1678 case MeanAbsoluteErrorMetric:
1680 status=GetMeanAbsoluteDistortion(image,reconstruct_image,channel,
1681 channel_distortion,exception);
1684 case MeanErrorPerPixelMetric:
1686 status=GetMeanErrorPerPixel(image,reconstruct_image,channel,
1687 channel_distortion,exception);
1690 case MeanSquaredErrorMetric:
1692 status=GetMeanSquaredDistortion(image,reconstruct_image,channel,
1693 channel_distortion,exception);
1696 case NormalizedCrossCorrelationErrorMetric:
1699 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1700 channel,channel_distortion,exception);
1703 case PeakAbsoluteErrorMetric:
1705 status=GetPeakAbsoluteDistortion(image,reconstruct_image,channel,
1706 channel_distortion,exception);
1709 case PeakSignalToNoiseRatioMetric:
1711 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,channel,
1712 channel_distortion,exception);
1715 case PerceptualHashErrorMetric:
1717 status=GetPerceptualHashDistortion(image,reconstruct_image,channel,
1718 channel_distortion,exception);
1721 case RootMeanSquaredErrorMetric:
1723 status=GetRootMeanSquaredDistortion(image,reconstruct_image,channel,
1724 channel_distortion,exception);
1728 *distortion=channel_distortion[CompositeChannels];
1729 channel_distortion=(
double *) RelinquishMagickMemory(channel_distortion);
1730 (void) FormatImageProperty(image,
"distortion",
"%.*g",GetMagickPrecision(),
1767MagickExport
double *GetImageChannelDistortions(Image *image,
1768 const Image *reconstruct_image,
const MetricType metric,
1769 ExceptionInfo *exception)
1772 *channel_distortion;
1780 assert(image != (Image *) NULL);
1781 assert(image->signature == MagickCoreSignature);
1782 assert(reconstruct_image != (
const Image *) NULL);
1783 assert(reconstruct_image->signature == MagickCoreSignature);
1784 if (IsEventLogging() != MagickFalse)
1785 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
1786 if (metric != PerceptualHashErrorMetric)
1787 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1789 (void) ThrowMagickException(&image->exception,GetMagickModule(),
1790 ImageError,
"ImageMorphologyDiffers",
"`%s'",image->filename);
1791 return((
double *) NULL);
1796 length=CompositeChannels+1UL;
1797 channel_distortion=(
double *) AcquireQuantumMemory(length,
1798 sizeof(*channel_distortion));
1799 if (channel_distortion == (
double *) NULL)
1800 ThrowFatalException(ResourceLimitFatalError,
"MemoryAllocationFailed");
1801 (void) memset(channel_distortion,0,length*
1802 sizeof(*channel_distortion));
1806 case AbsoluteErrorMetric:
1808 status=GetAbsoluteDistortion(image,reconstruct_image,CompositeChannels,
1809 channel_distortion,exception);
1812 case FuzzErrorMetric:
1814 status=GetFuzzDistortion(image,reconstruct_image,CompositeChannels,
1815 channel_distortion,exception);
1818 case MeanAbsoluteErrorMetric:
1820 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1821 CompositeChannels,channel_distortion,exception);
1824 case MeanErrorPerPixelMetric:
1826 status=GetMeanErrorPerPixel(image,reconstruct_image,CompositeChannels,
1827 channel_distortion,exception);
1830 case MeanSquaredErrorMetric:
1832 status=GetMeanSquaredDistortion(image,reconstruct_image,CompositeChannels,
1833 channel_distortion,exception);
1836 case NormalizedCrossCorrelationErrorMetric:
1839 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1840 CompositeChannels,channel_distortion,exception);
1843 case PeakAbsoluteErrorMetric:
1845 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1846 CompositeChannels,channel_distortion,exception);
1849 case PeakSignalToNoiseRatioMetric:
1851 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1852 CompositeChannels,channel_distortion,exception);
1855 case PerceptualHashErrorMetric:
1857 status=GetPerceptualHashDistortion(image,reconstruct_image,
1858 CompositeChannels,channel_distortion,exception);
1861 case RootMeanSquaredErrorMetric:
1863 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1864 CompositeChannels,channel_distortion,exception);
1868 if (status == MagickFalse)
1870 channel_distortion=(
double *) RelinquishMagickMemory(channel_distortion);
1871 return((
double *) NULL);
1873 return(channel_distortion);
1923MagickExport MagickBooleanType IsImagesEqual(Image *image,
1924 const Image *reconstruct_image)
1941 mean_error_per_pixel;
1950 assert(image != (Image *) NULL);
1951 assert(image->signature == MagickCoreSignature);
1952 assert(reconstruct_image != (
const Image *) NULL);
1953 assert(reconstruct_image->signature == MagickCoreSignature);
1954 exception=(&image->exception);
1955 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1956 ThrowBinaryException(ImageError,
"ImageMorphologyDiffers",image->filename);
1959 mean_error_per_pixel=0.0;
1961 SetImageDistortionBounds(image,reconstruct_image,&columns,&rows);
1962 image_view=AcquireVirtualCacheView(image,exception);
1963 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1964 for (y=0; y < (ssize_t) rows; y++)
1967 *magick_restrict indexes,
1968 *magick_restrict reconstruct_indexes;
1977 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1978 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1979 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
1981 indexes=GetCacheViewVirtualIndexQueue(image_view);
1982 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1983 for (x=0; x < (ssize_t) columns; x++)
1988 distance=fabs((
double) GetPixelRed(p)-(
double) GetPixelRed(q));
1989 mean_error_per_pixel+=distance;
1990 mean_error+=distance*distance;
1991 if (distance > maximum_error)
1992 maximum_error=distance;
1994 distance=fabs((
double) GetPixelGreen(p)-(
double) GetPixelGreen(q));
1995 mean_error_per_pixel+=distance;
1996 mean_error+=distance*distance;
1997 if (distance > maximum_error)
1998 maximum_error=distance;
2000 distance=fabs((
double) GetPixelBlue(p)-(
double) GetPixelBlue(q));
2001 mean_error_per_pixel+=distance;
2002 mean_error+=distance*distance;
2003 if (distance > maximum_error)
2004 maximum_error=distance;
2006 if (image->matte != MagickFalse)
2008 distance=fabs((
double) GetPixelOpacity(p)-(
double)
2009 GetPixelOpacity(q));
2010 mean_error_per_pixel+=distance;
2011 mean_error+=distance*distance;
2012 if (distance > maximum_error)
2013 maximum_error=distance;
2016 if ((image->colorspace == CMYKColorspace) &&
2017 (reconstruct_image->colorspace == CMYKColorspace))
2019 distance=fabs((
double) GetPixelIndex(indexes+x)-(
double)
2020 GetPixelIndex(reconstruct_indexes+x));
2021 mean_error_per_pixel+=distance;
2022 mean_error+=distance*distance;
2023 if (distance > maximum_error)
2024 maximum_error=distance;
2031 reconstruct_view=DestroyCacheView(reconstruct_view);
2032 image_view=DestroyCacheView(image_view);
2033 gamma=PerceptibleReciprocal(area);
2034 image->error.mean_error_per_pixel=gamma*mean_error_per_pixel;
2035 image->error.normalized_mean_error=gamma*QuantumScale*QuantumScale*mean_error;
2036 image->error.normalized_maximum_error=QuantumScale*maximum_error;
2037 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
2076static double GetSimilarityMetric(
const Image *image,
const Image *reference,
2077 const MetricType metric,
const ssize_t x_offset,
const ssize_t y_offset,
2078 ExceptionInfo *exception)
2092 SetGeometry(reference,&geometry);
2093 geometry.x=x_offset;
2094 geometry.y=y_offset;
2095 similarity_image=CropImage(image,&geometry,exception);
2096 if (similarity_image == (Image *) NULL)
2099 status=GetImageDistortion(similarity_image,reference,metric,&distortion,
2102 similarity_image=DestroyImage(similarity_image);
2106MagickExport Image *SimilarityImage(Image *image,
const Image *reference,
2107 RectangleInfo *offset,
double *similarity_metric,ExceptionInfo *exception)
2112 similarity_image=SimilarityMetricImage(image,reference,
2113 RootMeanSquaredErrorMetric,offset,similarity_metric,exception);
2114 return(similarity_image);
2117MagickExport Image *SimilarityMetricImage(Image *image,
const Image *reference,
2118 const MetricType metric,RectangleInfo *offset,
double *similarity_metric,
2119 ExceptionInfo *exception)
2121#define SimilarityImageTag "Similarity/Image"
2130 similarity_threshold;
2133 *similarity_image = (Image *) NULL;
2147 assert(image != (
const Image *) NULL);
2148 assert(image->signature == MagickCoreSignature);
2149 assert(exception != (ExceptionInfo *) NULL);
2150 assert(exception->signature == MagickCoreSignature);
2151 assert(offset != (RectangleInfo *) NULL);
2152 if (IsEventLogging() != MagickFalse)
2153 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
2154 SetGeometry(reference,offset);
2155 *similarity_metric=MagickMaximumValue;
2156 if (ValidateImageMorphology(image,reference) == MagickFalse)
2157 ThrowImageException(ImageError,
"ImageMorphologyDiffers");
2158 if ((image->columns < reference->columns) || (image->rows < reference->rows))
2160 (void) ThrowMagickException(&image->exception,GetMagickModule(),
2161 OptionWarning,
"GeometryDoesNotContainImage",
"`%s'",image->filename);
2162 return((Image *) NULL);
2164 if (metric == PeakAbsoluteErrorMetric)
2166 (void) ThrowMagickException(&image->exception,GetMagickModule(),
2167 OptionError,
"InvalidUseOfOption",
"`%s'",image->filename);
2168 return((Image *) NULL);
2170 similarity_image=CloneImage(image,image->columns,image->rows,MagickTrue,
2172 if (similarity_image == (Image *) NULL)
2173 return((Image *) NULL);
2174 similarity_image->depth=MAGICKCORE_QUANTUM_DEPTH;
2175 similarity_image->matte=MagickFalse;
2176 status=SetImageStorageClass(similarity_image,DirectClass);
2177 if (status == MagickFalse)
2179 InheritException(exception,&similarity_image->exception);
2180 return(DestroyImage(similarity_image));
2185 similarity_threshold=(-1.0);
2186 artifact=GetImageArtifact(image,
"compare:similarity-threshold");
2187 if (artifact != (
const char *) NULL)
2188 similarity_threshold=StringToDouble(artifact,(
char **) NULL);
2191 similarity_view=AcquireVirtualCacheView(similarity_image,exception);
2192 rows=similarity_image->rows;
2193 for (y=0; y < (ssize_t) rows; y++)
2204 if (status == MagickFalse)
2206 if (*similarity_metric <= similarity_threshold)
2208 q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
2210 if (q == (
const PixelPacket *) NULL)
2215 for (x=0; x < (ssize_t) similarity_image->columns; x++)
2217 if (*similarity_metric <= similarity_threshold)
2219 similarity=GetSimilarityMetric(image,reference,metric,x,y,exception);
2222 case NormalizedCrossCorrelationErrorMetric:
2223 case UndefinedErrorMetric:
2225 similarity=1.0-similarity;
2228 case PerceptualHashErrorMetric:
2230 similarity=1.0-MagickMin(0.01*similarity,1.0);
2236 if (similarity < *similarity_metric)
2238 *similarity_metric=similarity;
2242 if ((metric == PeakSignalToNoiseRatioMetric) &&
2243 (fabs(similarity) < MagickEpsilon))
2244 similarity=1.0-similarity;
2247 case AbsoluteErrorMetric:
2248 case FuzzErrorMetric:
2249 case MeanAbsoluteErrorMetric:
2250 case MeanErrorPerPixelMetric:
2251 case MeanSquaredErrorMetric:
2252 case NormalizedCrossCorrelationErrorMetric:
2253 case PeakAbsoluteErrorMetric:
2254 case PerceptualHashErrorMetric:
2255 case RootMeanSquaredErrorMetric:
2257 SetPixelRed(q,ClampToQuantum((
double) QuantumRange-QuantumRange*
2263 SetPixelRed(q,ClampToQuantum((
double) QuantumRange*similarity));
2267 SetPixelGreen(q,GetPixelRed(q));
2268 SetPixelBlue(q,GetPixelRed(q));
2271 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
2273 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2279 proceed=SetImageProgress(image,SimilarityImageTag,progress,image->rows);
2280 if (proceed == MagickFalse)
2284 similarity_view=DestroyCacheView(similarity_view);
2285 (void) SetImageType(similarity_image,GrayscaleType);
2286 if (status == MagickFalse)
2287 similarity_image=DestroyImage(similarity_image);
2288 return(similarity_image);