43#include "magick/studio.h"
44#include "magick/artifact.h"
45#include "magick/cache-view.h"
46#include "magick/channel.h"
47#include "magick/client.h"
48#include "magick/color.h"
49#include "magick/color-private.h"
50#include "magick/colorspace.h"
51#include "magick/colorspace-private.h"
52#include "magick/compare.h"
53#include "magick/composite-private.h"
54#include "magick/constitute.h"
55#include "magick/exception-private.h"
56#include "magick/geometry.h"
57#include "magick/image-private.h"
58#include "magick/list.h"
59#include "magick/log.h"
60#include "magick/memory_.h"
61#include "magick/monitor.h"
62#include "magick/monitor-private.h"
63#include "magick/option.h"
64#include "magick/pixel-private.h"
65#include "magick/property.h"
66#include "magick/resource_.h"
67#include "magick/statistic-private.h"
68#include "magick/string_.h"
69#include "magick/string-private.h"
70#include "magick/statistic.h"
71#include "magick/thread-private.h"
72#include "magick/transform.h"
73#include "magick/utility.h"
74#include "magick/version.h"
112MagickExport
Image *CompareImages(
Image *image,
const Image *reconstruct_image,
113 const MetricType metric,
double *distortion,
ExceptionInfo *exception)
118 highlight_image=CompareImageChannels(image,reconstruct_image,
119 CompositeChannels,metric,distortion,exception);
120 return(highlight_image);
123static size_t GetNumberChannels(
const Image *image,
const ChannelType channel)
129 if ((channel & RedChannel) != 0)
131 if ((channel & GreenChannel) != 0)
133 if ((channel & BlueChannel) != 0)
135 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
137 if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
139 return(channels == 0 ? 1UL : channels);
142static inline MagickBooleanType ValidateImageMorphology(
143 const Image *magick_restrict image,
144 const Image *magick_restrict reconstruct_image)
149 if (GetNumberChannels(image,DefaultChannels) !=
150 GetNumberChannels(reconstruct_image,DefaultChannels))
155MagickExport
Image *CompareImageChannels(
Image *image,
156 const Image *reconstruct_image,
const ChannelType channel,
157 const MetricType metric,
double *distortion,
ExceptionInfo *exception)
190 assert(image != (
Image *) NULL);
191 assert(image->signature == MagickCoreSignature);
192 assert(reconstruct_image != (
const Image *) NULL);
193 assert(reconstruct_image->signature == MagickCoreSignature);
194 assert(distortion != (
double *) NULL);
195 if (IsEventLogging() != MagickFalse)
196 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
198 if (metric != PerceptualHashErrorMetric)
199 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
200 ThrowImageException(ImageError,
"ImageMorphologyDiffers");
201 status=GetImageChannelDistortion(image,reconstruct_image,channel,metric,
202 distortion,exception);
203 if (status == MagickFalse)
204 return((
Image *) NULL);
205 clone_image=CloneImage(image,0,0,MagickTrue,exception);
206 if (clone_image == (
Image *) NULL)
207 return((
Image *) NULL);
208 (void) SetImageMask(clone_image,(
Image *) NULL);
209 difference_image=CloneImage(clone_image,0,0,MagickTrue,exception);
210 clone_image=DestroyImage(clone_image);
211 if (difference_image == (
Image *) NULL)
212 return((
Image *) NULL);
213 (void) SetImageAlphaChannel(difference_image,OpaqueAlphaChannel);
214 rows=MagickMax(image->rows,reconstruct_image->rows);
215 columns=MagickMax(image->columns,reconstruct_image->columns);
216 highlight_image=CloneImage(image,columns,rows,MagickTrue,exception);
217 if (highlight_image == (
Image *) NULL)
219 difference_image=DestroyImage(difference_image);
220 return((
Image *) NULL);
222 if (SetImageStorageClass(highlight_image,DirectClass) == MagickFalse)
224 InheritException(exception,&highlight_image->exception);
225 difference_image=DestroyImage(difference_image);
226 highlight_image=DestroyImage(highlight_image);
227 return((
Image *) NULL);
229 (void) SetImageMask(highlight_image,(
Image *) NULL);
230 (void) SetImageAlphaChannel(highlight_image,OpaqueAlphaChannel);
231 (void) QueryMagickColor(
"#f1001ecc",&highlight,exception);
232 artifact=GetImageArtifact(image,
"compare:highlight-color");
233 if (artifact != (
const char *) NULL)
234 (void) QueryMagickColor(artifact,&highlight,exception);
235 (void) QueryMagickColor(
"#ffffffcc",&lowlight,exception);
236 artifact=GetImageArtifact(image,
"compare:lowlight-color");
237 if (artifact != (
const char *) NULL)
238 (void) QueryMagickColor(artifact,&lowlight,exception);
239 if (highlight_image->colorspace == CMYKColorspace)
241 ConvertRGBToCMYK(&highlight);
242 ConvertRGBToCMYK(&lowlight);
248 fuzz=GetFuzzyColorDistance(image,reconstruct_image);
249 GetMagickPixelPacket(image,&zero);
250 image_view=AcquireVirtualCacheView(image,exception);
251 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
252 highlight_view=AcquireAuthenticCacheView(highlight_image,exception);
253#if defined(MAGICKCORE_OPENMP_SUPPORT)
254 #pragma omp parallel for schedule(static) shared(status) \
255 magick_number_threads(image,highlight_image,rows,1)
257 for (y=0; y < (ssize_t) rows; y++)
267 *magick_restrict indexes,
268 *magick_restrict reconstruct_indexes;
275 *magick_restrict highlight_indexes;
283 if (status == MagickFalse)
285 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
286 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
287 r=QueueCacheViewAuthenticPixels(highlight_view,0,y,columns,1,exception);
294 indexes=GetCacheViewVirtualIndexQueue(image_view);
295 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
296 highlight_indexes=GetCacheViewAuthenticIndexQueue(highlight_view);
298 reconstruct_pixel=zero;
299 for (x=0; x < (ssize_t) columns; x++)
304 SetMagickPixelPacket(image,p,indexes == (IndexPacket *) NULL ? NULL :
306 SetMagickPixelPacket(reconstruct_image,q,reconstruct_indexes ==
307 (IndexPacket *) NULL ? NULL : reconstruct_indexes+x,&reconstruct_pixel);
308 difference=MagickFalse;
309 if (channel == CompositeChannels)
311 if (IsMagickColorSimilar(&pixel,&reconstruct_pixel) == MagickFalse)
312 difference=MagickTrue;
322 Sa=QuantumScale*(image->matte != MagickFalse ? (double)
323 GetPixelAlpha(p) : ((double) QuantumRange-(double) OpaqueOpacity));
324 Da=QuantumScale*(image->matte != MagickFalse ? (double)
325 GetPixelAlpha(q) : ((double) QuantumRange-(double) OpaqueOpacity));
326 if ((channel & RedChannel) != 0)
328 pixel=Sa*(double) GetPixelRed(p)-Da*(double) GetPixelRed(q);
329 distance=pixel*pixel;
330 if (distance >= fuzz)
331 difference=MagickTrue;
333 if ((channel & GreenChannel) != 0)
335 pixel=Sa*(double) GetPixelGreen(p)-Da*(double) GetPixelGreen(q);
336 distance=pixel*pixel;
337 if (distance >= fuzz)
338 difference=MagickTrue;
340 if ((channel & BlueChannel) != 0)
342 pixel=Sa*(double) GetPixelBlue(p)-Da*(double) GetPixelBlue(q);
343 distance=pixel*pixel;
344 if (distance >= fuzz)
345 difference=MagickTrue;
347 if (((channel & OpacityChannel) != 0) &&
348 (image->matte != MagickFalse))
350 pixel=(double) GetPixelOpacity(p)-(double) GetPixelOpacity(q);
351 distance=pixel*pixel;
352 if (distance >= fuzz)
353 difference=MagickTrue;
355 if (((channel & IndexChannel) != 0) &&
356 (image->colorspace == CMYKColorspace))
358 pixel=Sa*(double) indexes[x]-Da*(
double) reconstruct_indexes[x];
359 distance=pixel*pixel;
360 if (distance >= fuzz)
361 difference=MagickTrue;
364 if (difference != MagickFalse)
365 SetPixelPacket(highlight_image,&highlight,r,highlight_indexes ==
366 (IndexPacket *) NULL ? NULL : highlight_indexes+x);
368 SetPixelPacket(highlight_image,&lowlight,r,highlight_indexes ==
369 (IndexPacket *) NULL ? NULL : highlight_indexes+x);
374 sync=SyncCacheViewAuthenticPixels(highlight_view,exception);
375 if (sync == MagickFalse)
378 highlight_view=DestroyCacheView(highlight_view);
379 reconstruct_view=DestroyCacheView(reconstruct_view);
380 image_view=DestroyCacheView(image_view);
381 (void) CompositeImage(difference_image,image->compose,highlight_image,0,0);
382 highlight_image=DestroyImage(highlight_image);
383 if (status == MagickFalse)
384 difference_image=DestroyImage(difference_image);
385 return(difference_image);
424MagickExport MagickBooleanType GetImageDistortion(
Image *image,
425 const Image *reconstruct_image,
const MetricType metric,
double *distortion,
431 status=GetImageChannelDistortion(image,reconstruct_image,CompositeChannels,
432 metric,distortion,exception);
436static MagickBooleanType GetAbsoluteDistortion(
const Image *image,
437 const Image *reconstruct_image,
const ChannelType channel,
double *distortion,
461 fuzz=GetFuzzyColorDistance(image,reconstruct_image);
462 rows=MagickMax(image->rows,reconstruct_image->rows);
463 columns=MagickMax(image->columns,reconstruct_image->columns);
464 image_view=AcquireVirtualCacheView(image,exception);
465 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
466#if defined(MAGICKCORE_OPENMP_SUPPORT)
467 #pragma omp parallel for schedule(static) shared(status) \
468 magick_number_threads(image,image,rows,1)
470 for (y=0; y < (ssize_t) rows; y++)
473 channel_distortion[CompositeChannels+1];
476 *magick_restrict indexes,
477 *magick_restrict reconstruct_indexes;
487 if (status == MagickFalse)
489 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
490 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
496 indexes=GetCacheViewVirtualIndexQueue(image_view);
497 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
498 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
499 for (x=0; x < (ssize_t) columns; x++)
510 difference=MagickFalse;
511 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
512 ((double) QuantumRange-(double) OpaqueOpacity));
513 Da=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(q) :
514 ((double) QuantumRange-(double) OpaqueOpacity));
515 if ((channel & RedChannel) != 0)
517 pixel=Sa*(double) GetPixelRed(p)-Da*(double) GetPixelRed(q);
518 distance=pixel*pixel;
519 if (distance >= fuzz)
521 channel_distortion[RedChannel]++;
522 difference=MagickTrue;
525 if ((channel & GreenChannel) != 0)
527 pixel=Sa*(double) GetPixelGreen(p)-Da*(double) GetPixelGreen(q);
528 distance=pixel*pixel;
529 if (distance >= fuzz)
531 channel_distortion[GreenChannel]++;
532 difference=MagickTrue;
535 if ((channel & BlueChannel) != 0)
537 pixel=Sa*(double) GetPixelBlue(p)-Da*(double) GetPixelBlue(q);
538 distance=pixel*pixel;
539 if (distance >= fuzz)
541 channel_distortion[BlueChannel]++;
542 difference=MagickTrue;
545 if (((channel & OpacityChannel) != 0) &&
546 (image->matte != MagickFalse))
548 pixel=(double) GetPixelOpacity(p)-(double) GetPixelOpacity(q);
549 distance=pixel*pixel;
550 if (distance >= fuzz)
552 channel_distortion[OpacityChannel]++;
553 difference=MagickTrue;
556 if (((channel & IndexChannel) != 0) &&
557 (image->colorspace == CMYKColorspace))
559 pixel=Sa*(double) indexes[x]-Da*(
double) reconstruct_indexes[x];
560 distance=pixel*pixel;
561 if (distance >= fuzz)
563 channel_distortion[BlackChannel]++;
564 difference=MagickTrue;
567 if (difference != MagickFalse)
568 channel_distortion[CompositeChannels]++;
572#if defined(MAGICKCORE_OPENMP_SUPPORT)
573 #pragma omp critical (MagickCore_GetAbsoluteDistortion)
575 for (i=0; i <= (ssize_t) CompositeChannels; i++)
576 distortion[i]+=channel_distortion[i];
578 reconstruct_view=DestroyCacheView(reconstruct_view);
579 image_view=DestroyCacheView(image_view);
583static MagickBooleanType GetFuzzDistortion(
const Image *image,
584 const Image *reconstruct_image,
const ChannelType channel,
605 rows=MagickMax(image->rows,reconstruct_image->rows);
606 columns=MagickMax(image->columns,reconstruct_image->columns);
607 image_view=AcquireVirtualCacheView(image,exception);
608 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
609#if defined(MAGICKCORE_OPENMP_SUPPORT)
610 #pragma omp parallel for schedule(static) shared(status) \
611 magick_number_threads(image,image,rows,1)
613 for (y=0; y < (ssize_t) rows; y++)
616 channel_distortion[CompositeChannels+1];
619 *magick_restrict indexes,
620 *magick_restrict reconstruct_indexes;
630 if (status == MagickFalse)
632 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
633 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
639 indexes=GetCacheViewVirtualIndexQueue(image_view);
640 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
641 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
642 for (x=0; x < (ssize_t) columns; x++)
649 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
650 ((double) QuantumRange-(double) OpaqueOpacity));
651 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
652 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
654 if ((channel & RedChannel) != 0)
656 distance=QuantumScale*(Sa*(double) GetPixelRed(p)-Da*(double)
658 channel_distortion[RedChannel]+=distance*distance;
659 channel_distortion[CompositeChannels]+=distance*distance;
661 if ((channel & GreenChannel) != 0)
663 distance=QuantumScale*(Sa*(double) GetPixelGreen(p)-Da*(double)
665 channel_distortion[GreenChannel]+=distance*distance;
666 channel_distortion[CompositeChannels]+=distance*distance;
668 if ((channel & BlueChannel) != 0)
670 distance=QuantumScale*(Sa*(double) GetPixelBlue(p)-Da*(double)
672 channel_distortion[BlueChannel]+=distance*distance;
673 channel_distortion[CompositeChannels]+=distance*distance;
675 if (((channel & OpacityChannel) != 0) && ((image->matte != MagickFalse) ||
676 (reconstruct_image->matte != MagickFalse)))
678 distance=QuantumScale*((image->matte != MagickFalse ? (double)
679 GetPixelOpacity(p) : (double) OpaqueOpacity)-
680 (reconstruct_image->matte != MagickFalse ?
681 (double) GetPixelOpacity(q): (double) OpaqueOpacity));
682 channel_distortion[OpacityChannel]+=distance*distance;
683 channel_distortion[CompositeChannels]+=distance*distance;
685 if (((channel & IndexChannel) != 0) &&
686 (image->colorspace == CMYKColorspace) &&
687 (reconstruct_image->colorspace == CMYKColorspace))
689 distance=QuantumScale*(Sa*(double) GetPixelIndex(indexes+x)-
690 Da*(double) GetPixelIndex(reconstruct_indexes+x));
691 channel_distortion[BlackChannel]+=distance*distance;
692 channel_distortion[CompositeChannels]+=distance*distance;
697#if defined(MAGICKCORE_OPENMP_SUPPORT)
698 #pragma omp critical (MagickCore_GetFuzzDistortion)
700 for (i=0; i <= (ssize_t) CompositeChannels; i++)
701 distortion[i]+=channel_distortion[i];
703 reconstruct_view=DestroyCacheView(reconstruct_view);
704 image_view=DestroyCacheView(image_view);
705 for (i=0; i <= (ssize_t) CompositeChannels; i++)
706 distortion[i]/=((
double) columns*rows);
707 distortion[CompositeChannels]/=(double) GetNumberChannels(image,channel);
708 distortion[CompositeChannels]=sqrt(distortion[CompositeChannels]);
712static MagickBooleanType GetMeanAbsoluteDistortion(
const Image *image,
713 const Image *reconstruct_image,
const ChannelType channel,
734 rows=MagickMax(image->rows,reconstruct_image->rows);
735 columns=MagickMax(image->columns,reconstruct_image->columns);
736 image_view=AcquireVirtualCacheView(image,exception);
737 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
738#if defined(MAGICKCORE_OPENMP_SUPPORT)
739 #pragma omp parallel for schedule(static) shared(status) \
740 magick_number_threads(image,image,rows,1)
742 for (y=0; y < (ssize_t) rows; y++)
745 channel_distortion[CompositeChannels+1];
748 *magick_restrict indexes,
749 *magick_restrict reconstruct_indexes;
759 if (status == MagickFalse)
761 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
762 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
768 indexes=GetCacheViewVirtualIndexQueue(image_view);
769 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
770 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
771 for (x=0; x < (ssize_t) columns; x++)
778 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
779 ((double) QuantumRange-(double) OpaqueOpacity));
780 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
781 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
783 if ((channel & RedChannel) != 0)
785 distance=QuantumScale*fabs(Sa*(
double) GetPixelRed(p)-Da*
786 (
double) GetPixelRed(q));
787 channel_distortion[RedChannel]+=distance;
788 channel_distortion[CompositeChannels]+=distance;
790 if ((channel & GreenChannel) != 0)
792 distance=QuantumScale*fabs(Sa*(
double) GetPixelGreen(p)-Da*
793 (
double) GetPixelGreen(q));
794 channel_distortion[GreenChannel]+=distance;
795 channel_distortion[CompositeChannels]+=distance;
797 if ((channel & BlueChannel) != 0)
799 distance=QuantumScale*fabs(Sa*(
double) GetPixelBlue(p)-Da*
800 (
double) GetPixelBlue(q));
801 channel_distortion[BlueChannel]+=distance;
802 channel_distortion[CompositeChannels]+=distance;
804 if (((channel & OpacityChannel) != 0) &&
805 (image->matte != MagickFalse))
807 distance=QuantumScale*fabs((
double) GetPixelOpacity(p)-(
double)
809 channel_distortion[OpacityChannel]+=distance;
810 channel_distortion[CompositeChannels]+=distance;
812 if (((channel & IndexChannel) != 0) &&
813 (image->colorspace == CMYKColorspace))
815 distance=QuantumScale*fabs(Sa*(
double) GetPixelIndex(indexes+x)-Da*
816 (
double) GetPixelIndex(reconstruct_indexes+x));
817 channel_distortion[BlackChannel]+=distance;
818 channel_distortion[CompositeChannels]+=distance;
823#if defined(MAGICKCORE_OPENMP_SUPPORT)
824 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
826 for (i=0; i <= (ssize_t) CompositeChannels; i++)
827 distortion[i]+=channel_distortion[i];
829 reconstruct_view=DestroyCacheView(reconstruct_view);
830 image_view=DestroyCacheView(image_view);
831 for (i=0; i <= (ssize_t) CompositeChannels; i++)
832 distortion[i]/=((
double) columns*rows);
833 distortion[CompositeChannels]/=(double) GetNumberChannels(image,channel);
837static MagickBooleanType GetMeanErrorPerPixel(
Image *image,
838 const Image *reconstruct_image,
const ChannelType channel,
double *distortion,
865 rows=MagickMax(image->rows,reconstruct_image->rows);
866 columns=MagickMax(image->columns,reconstruct_image->columns);
867 image_view=AcquireVirtualCacheView(image,exception);
868 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
869 for (y=0; y < (ssize_t) rows; y++)
872 *magick_restrict indexes,
873 *magick_restrict reconstruct_indexes;
882 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
883 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
889 indexes=GetCacheViewVirtualIndexQueue(image_view);
890 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
891 for (x=0; x < (ssize_t) columns; x++)
898 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
899 ((double) QuantumRange-(double) OpaqueOpacity));
900 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
901 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
903 if ((channel & RedChannel) != 0)
905 distance=fabs(Sa*(
double) GetPixelRed(p)-Da*(
double) GetPixelRed(q));
906 distortion[RedChannel]+=distance;
907 distortion[CompositeChannels]+=distance;
908 mean_error+=distance*distance;
909 if (distance > maximum_error)
910 maximum_error=distance;
913 if ((channel & GreenChannel) != 0)
915 distance=fabs(Sa*(
double) GetPixelGreen(p)-Da*(
double)
917 distortion[GreenChannel]+=distance;
918 distortion[CompositeChannels]+=distance;
919 mean_error+=distance*distance;
920 if (distance > maximum_error)
921 maximum_error=distance;
924 if ((channel & BlueChannel) != 0)
926 distance=fabs(Sa*(
double) GetPixelBlue(p)-Da*(
double)
928 distortion[BlueChannel]+=distance;
929 distortion[CompositeChannels]+=distance;
930 mean_error+=distance*distance;
931 if (distance > maximum_error)
932 maximum_error=distance;
935 if (((channel & OpacityChannel) != 0) &&
936 (image->matte != MagickFalse))
938 distance=fabs((
double) GetPixelOpacity(p)-
939 (
double) GetPixelOpacity(q));
940 distortion[OpacityChannel]+=distance;
941 distortion[CompositeChannels]+=distance;
942 mean_error+=distance*distance;
943 if (distance > maximum_error)
944 maximum_error=distance;
947 if (((channel & IndexChannel) != 0) &&
948 (image->colorspace == CMYKColorspace) &&
949 (reconstruct_image->colorspace == CMYKColorspace))
951 distance=fabs(Sa*(
double) GetPixelIndex(indexes+x)-Da*
952 (
double) GetPixelIndex(reconstruct_indexes+x));
953 distortion[BlackChannel]+=distance;
954 distortion[CompositeChannels]+=distance;
955 mean_error+=distance*distance;
956 if (distance > maximum_error)
957 maximum_error=distance;
964 reconstruct_view=DestroyCacheView(reconstruct_view);
965 image_view=DestroyCacheView(image_view);
966 gamma=PerceptibleReciprocal(area);
967 image->error.mean_error_per_pixel=gamma*distortion[CompositeChannels];
968 image->error.normalized_mean_error=gamma*QuantumScale*QuantumScale*mean_error;
969 image->error.normalized_maximum_error=QuantumScale*maximum_error;
973static MagickBooleanType GetMeanSquaredDistortion(
const Image *image,
974 const Image *reconstruct_image,
const ChannelType channel,
995 rows=MagickMax(image->rows,reconstruct_image->rows);
996 columns=MagickMax(image->columns,reconstruct_image->columns);
997 image_view=AcquireVirtualCacheView(image,exception);
998 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
999#if defined(MAGICKCORE_OPENMP_SUPPORT)
1000 #pragma omp parallel for schedule(static) shared(status) \
1001 magick_number_threads(image,image,rows,1)
1003 for (y=0; y < (ssize_t) rows; y++)
1006 channel_distortion[CompositeChannels+1];
1009 *magick_restrict indexes,
1010 *magick_restrict reconstruct_indexes;
1020 if (status == MagickFalse)
1022 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1023 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1029 indexes=GetCacheViewVirtualIndexQueue(image_view);
1030 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1031 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
1032 for (x=0; x < (ssize_t) columns; x++)
1039 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
1040 ((double) QuantumRange-(double) OpaqueOpacity));
1041 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1042 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
1044 if ((channel & RedChannel) != 0)
1046 distance=QuantumScale*(Sa*(double) GetPixelRed(p)-Da*(double)
1048 channel_distortion[RedChannel]+=distance*distance;
1049 channel_distortion[CompositeChannels]+=distance*distance;
1051 if ((channel & GreenChannel) != 0)
1053 distance=QuantumScale*(Sa*(double) GetPixelGreen(p)-Da*(double)
1055 channel_distortion[GreenChannel]+=distance*distance;
1056 channel_distortion[CompositeChannels]+=distance*distance;
1058 if ((channel & BlueChannel) != 0)
1060 distance=QuantumScale*(Sa*(double) GetPixelBlue(p)-Da*(double)
1062 channel_distortion[BlueChannel]+=distance*distance;
1063 channel_distortion[CompositeChannels]+=distance*distance;
1065 if (((channel & OpacityChannel) != 0) &&
1066 (image->matte != MagickFalse))
1068 distance=QuantumScale*((double) GetPixelOpacity(p)-(double)
1069 GetPixelOpacity(q));
1070 channel_distortion[OpacityChannel]+=distance*distance;
1071 channel_distortion[CompositeChannels]+=distance*distance;
1073 if (((channel & IndexChannel) != 0) &&
1074 (image->colorspace == CMYKColorspace) &&
1075 (reconstruct_image->colorspace == CMYKColorspace))
1077 distance=QuantumScale*(Sa*(double) GetPixelIndex(indexes+x)-Da*
1078 (double) GetPixelIndex(reconstruct_indexes+x));
1079 channel_distortion[BlackChannel]+=distance*distance;
1080 channel_distortion[CompositeChannels]+=distance*distance;
1085#if defined(MAGICKCORE_OPENMP_SUPPORT)
1086 #pragma omp critical (MagickCore_GetMeanSquaredError)
1088 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1089 distortion[i]+=channel_distortion[i];
1091 reconstruct_view=DestroyCacheView(reconstruct_view);
1092 image_view=DestroyCacheView(image_view);
1093 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1094 distortion[i]/=((
double) columns*rows);
1095 distortion[CompositeChannels]/=(double) GetNumberChannels(image,channel);
1099static MagickBooleanType GetNormalizedCrossCorrelationDistortion(
1100 const Image *image,
const Image *reconstruct_image,
const ChannelType channel,
1103#define SimilarityImageTag "Similarity/Image"
1111 *reconstruct_statistics;
1114 alpha_variance[CompositeChannels+1],
1115 beta_variance[CompositeChannels+1];
1136 image_statistics=GetImageChannelStatistics(image,exception);
1137 reconstruct_statistics=GetImageChannelStatistics(reconstruct_image,exception);
1146 reconstruct_statistics);
1147 return(MagickFalse);
1149 (void) memset(distortion,0,(CompositeChannels+1)*
sizeof(*distortion));
1150 (void) memset(alpha_variance,0,(CompositeChannels+1)*
sizeof(*alpha_variance));
1151 (void) memset(beta_variance,0,(CompositeChannels+1)*
sizeof(*beta_variance));
1154 rows=MagickMax(image->rows,reconstruct_image->rows);
1155 columns=MagickMax(image->columns,reconstruct_image->columns);
1156 image_view=AcquireVirtualCacheView(image,exception);
1157 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1158 for (y=0; y < (ssize_t) rows; y++)
1161 *magick_restrict indexes,
1162 *magick_restrict reconstruct_indexes;
1171 if (status == MagickFalse)
1173 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1174 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1180 indexes=GetCacheViewVirtualIndexQueue(image_view);
1181 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1182 for (x=0; x < (ssize_t) columns; x++)
1190 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
1191 (double) QuantumRange);
1192 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1193 (double) GetPixelAlpha(q) : (double) QuantumRange);
1194 if ((channel & RedChannel) != 0)
1196 alpha=QuantumScale*(Sa*(double) GetPixelRed(p)-
1197 image_statistics[RedChannel].mean);
1198 beta=QuantumScale*(Da*(double) GetPixelRed(q)-
1199 reconstruct_statistics[RedChannel].mean);
1200 distortion[RedChannel]+=alpha*beta;
1201 alpha_variance[RedChannel]+=alpha*alpha;
1202 beta_variance[RedChannel]+=beta*beta;
1204 if ((channel & GreenChannel) != 0)
1206 alpha=QuantumScale*(Sa*(double) GetPixelGreen(p)-
1207 image_statistics[GreenChannel].mean);
1208 beta=QuantumScale*(Da*(double) GetPixelGreen(q)-
1209 reconstruct_statistics[GreenChannel].mean);
1210 distortion[GreenChannel]+=alpha*beta;
1211 alpha_variance[GreenChannel]+=alpha*alpha;
1212 beta_variance[GreenChannel]+=beta*beta;
1214 if ((channel & BlueChannel) != 0)
1216 alpha=QuantumScale*(Sa*(double) GetPixelBlue(p)-
1217 image_statistics[BlueChannel].mean);
1218 beta=QuantumScale*(Da*(double) GetPixelBlue(q)-
1219 reconstruct_statistics[BlueChannel].mean);
1220 distortion[BlueChannel]+=alpha*beta;
1221 alpha_variance[BlueChannel]+=alpha*alpha;
1222 beta_variance[BlueChannel]+=beta*beta;
1224 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
1226 alpha=QuantumScale*((double) GetPixelAlpha(p)-
1227 image_statistics[AlphaChannel].mean);
1228 beta=QuantumScale*((double) GetPixelAlpha(q)-
1229 reconstruct_statistics[AlphaChannel].mean);
1230 distortion[OpacityChannel]+=alpha*beta;
1231 alpha_variance[OpacityChannel]+=alpha*alpha;
1232 beta_variance[OpacityChannel]+=beta*beta;
1234 if (((channel & IndexChannel) != 0) &&
1235 (image->colorspace == CMYKColorspace) &&
1236 (reconstruct_image->colorspace == CMYKColorspace))
1238 alpha=QuantumScale*(Sa*(double) GetPixelIndex(indexes+x)-
1239 image_statistics[BlackChannel].mean);
1240 beta=QuantumScale*(Da*(double) GetPixelIndex(reconstruct_indexes+x)-
1241 reconstruct_statistics[BlackChannel].mean);
1242 distortion[BlackChannel]+=alpha*beta;
1243 alpha_variance[BlackChannel]+=alpha*alpha;
1244 beta_variance[BlackChannel]+=beta*beta;
1249 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1254#if defined(MAGICKCORE_OPENMP_SUPPORT)
1258 proceed=SetImageProgress(image,SimilarityImageTag,progress,rows);
1259 if (proceed == MagickFalse)
1263 reconstruct_view=DestroyCacheView(reconstruct_view);
1264 image_view=DestroyCacheView(image_view);
1268 for (i=0; i < (ssize_t) CompositeChannels; i++)
1270 distortion[i]/=sqrt(alpha_variance[i]*beta_variance[i]);
1271 if (fabs(distortion[i]) > MagickEpsilon)
1272 distortion[CompositeChannels]+=distortion[i];
1274 distortion[CompositeChannels]=distortion[CompositeChannels]/
1275 GetNumberChannels(image,channel);
1280 reconstruct_statistics);
1286static MagickBooleanType GetPeakAbsoluteDistortion(
const Image *image,
1287 const Image *reconstruct_image,
const ChannelType channel,
1305 rows=MagickMax(image->rows,reconstruct_image->rows);
1306 columns=MagickMax(image->columns,reconstruct_image->columns);
1307 image_view=AcquireVirtualCacheView(image,exception);
1308 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1309#if defined(MAGICKCORE_OPENMP_SUPPORT)
1310 #pragma omp parallel for schedule(static) shared(status) \
1311 magick_number_threads(image,image,rows,1)
1313 for (y=0; y < (ssize_t) rows; y++)
1316 channel_distortion[CompositeChannels+1];
1319 *magick_restrict indexes,
1320 *magick_restrict reconstruct_indexes;
1330 if (status == MagickFalse)
1332 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1333 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1339 indexes=GetCacheViewVirtualIndexQueue(image_view);
1340 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1341 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
1342 for (x=0; x < (ssize_t) columns; x++)
1349 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
1350 ((double) QuantumRange-(double) OpaqueOpacity));
1351 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1352 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
1354 if ((channel & RedChannel) != 0)
1356 distance=QuantumScale*fabs(Sa*(
double) GetPixelRed(p)-Da*
1357 (
double) GetPixelRed(q));
1358 if (distance > channel_distortion[RedChannel])
1359 channel_distortion[RedChannel]=distance;
1360 if (distance > channel_distortion[CompositeChannels])
1361 channel_distortion[CompositeChannels]=distance;
1363 if ((channel & GreenChannel) != 0)
1365 distance=QuantumScale*fabs(Sa*(
double) GetPixelGreen(p)-Da*
1366 (
double) GetPixelGreen(q));
1367 if (distance > channel_distortion[GreenChannel])
1368 channel_distortion[GreenChannel]=distance;
1369 if (distance > channel_distortion[CompositeChannels])
1370 channel_distortion[CompositeChannels]=distance;
1372 if ((channel & BlueChannel) != 0)
1374 distance=QuantumScale*fabs(Sa*(
double) GetPixelBlue(p)-Da*
1375 (
double) GetPixelBlue(q));
1376 if (distance > channel_distortion[BlueChannel])
1377 channel_distortion[BlueChannel]=distance;
1378 if (distance > channel_distortion[CompositeChannels])
1379 channel_distortion[CompositeChannels]=distance;
1381 if (((channel & OpacityChannel) != 0) &&
1382 (image->matte != MagickFalse))
1384 distance=QuantumScale*fabs((
double) GetPixelOpacity(p)-(
double)
1385 GetPixelOpacity(q));
1386 if (distance > channel_distortion[OpacityChannel])
1387 channel_distortion[OpacityChannel]=distance;
1388 if (distance > channel_distortion[CompositeChannels])
1389 channel_distortion[CompositeChannels]=distance;
1391 if (((channel & IndexChannel) != 0) &&
1392 (image->colorspace == CMYKColorspace) &&
1393 (reconstruct_image->colorspace == CMYKColorspace))
1395 distance=QuantumScale*fabs(Sa*(
double) GetPixelIndex(indexes+x)-Da*
1396 (
double) GetPixelIndex(reconstruct_indexes+x));
1397 if (distance > channel_distortion[BlackChannel])
1398 channel_distortion[BlackChannel]=distance;
1399 if (distance > channel_distortion[CompositeChannels])
1400 channel_distortion[CompositeChannels]=distance;
1405#if defined(MAGICKCORE_OPENMP_SUPPORT)
1406 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
1408 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1409 if (channel_distortion[i] > distortion[i])
1410 distortion[i]=channel_distortion[i];
1412 reconstruct_view=DestroyCacheView(reconstruct_view);
1413 image_view=DestroyCacheView(image_view);
1417static MagickBooleanType GetPeakSignalToNoiseRatio(
const Image *image,
1418 const Image *reconstruct_image,
const ChannelType channel,
1424 status=GetMeanSquaredDistortion(image,reconstruct_image,channel,distortion,
1426 if ((channel & RedChannel) != 0)
1428 if (fabs(distortion[RedChannel]) >= MagickEpsilon)
1429 distortion[RedChannel]=(-10.0*MagickLog10(distortion[RedChannel]));
1431 if ((channel & GreenChannel) != 0)
1433 if (fabs(distortion[GreenChannel]) >= MagickEpsilon)
1434 distortion[GreenChannel]=(-10.0*MagickLog10(distortion[GreenChannel]));
1436 if ((channel & BlueChannel) != 0)
1438 if (fabs(distortion[BlueChannel]) >= MagickEpsilon)
1439 distortion[BlueChannel]=(-10.0*MagickLog10(distortion[BlueChannel]));
1441 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
1443 if (fabs(distortion[OpacityChannel]) >= MagickEpsilon)
1444 distortion[OpacityChannel]=(-10.0*
1445 MagickLog10(distortion[OpacityChannel]));
1447 if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
1449 if (fabs(distortion[BlackChannel]) >= MagickEpsilon)
1450 distortion[BlackChannel]=(-10.0*MagickLog10(distortion[BlackChannel]));
1452 if (fabs(distortion[CompositeChannels]) >= MagickEpsilon)
1453 distortion[CompositeChannels]=(-10.0*
1454 MagickLog10(distortion[CompositeChannels]));
1458static MagickBooleanType GetPerceptualHashDistortion(
const Image *image,
1459 const Image *reconstruct_image,
const ChannelType channel,
double *distortion,
1475 image_phash=GetImageChannelPerceptualHash(image,exception);
1477 return(MagickFalse);
1478 reconstruct_phash=GetImageChannelPerceptualHash(reconstruct_image,exception);
1482 return(MagickFalse);
1484 for (i=0; i < MaximumNumberOfImageMoments; i++)
1489 if ((channel & RedChannel) != 0)
1491 difference=reconstruct_phash[RedChannel].P[i]-
1492 image_phash[RedChannel].P[i];
1493 distortion[RedChannel]+=difference*difference;
1494 distortion[CompositeChannels]+=difference*difference;
1496 if ((channel & GreenChannel) != 0)
1498 difference=reconstruct_phash[GreenChannel].P[i]-
1499 image_phash[GreenChannel].P[i];
1500 distortion[GreenChannel]+=difference*difference;
1501 distortion[CompositeChannels]+=difference*difference;
1503 if ((channel & BlueChannel) != 0)
1505 difference=reconstruct_phash[BlueChannel].P[i]-
1506 image_phash[BlueChannel].P[i];
1507 distortion[BlueChannel]+=difference*difference;
1508 distortion[CompositeChannels]+=difference*difference;
1510 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse) &&
1511 (reconstruct_image->matte != MagickFalse))
1513 difference=reconstruct_phash[OpacityChannel].P[i]-
1514 image_phash[OpacityChannel].P[i];
1515 distortion[OpacityChannel]+=difference*difference;
1516 distortion[CompositeChannels]+=difference*difference;
1518 if (((channel & IndexChannel) != 0) &&
1519 (image->colorspace == CMYKColorspace) &&
1520 (reconstruct_image->colorspace == CMYKColorspace))
1522 difference=reconstruct_phash[IndexChannel].P[i]-
1523 image_phash[IndexChannel].P[i];
1524 distortion[IndexChannel]+=difference*difference;
1525 distortion[CompositeChannels]+=difference*difference;
1531 for (i=0; i < MaximumNumberOfImageMoments; i++)
1536 if ((channel & RedChannel) != 0)
1538 difference=reconstruct_phash[RedChannel].Q[i]-
1539 image_phash[RedChannel].Q[i];
1540 distortion[RedChannel]+=difference*difference;
1541 distortion[CompositeChannels]+=difference*difference;
1543 if ((channel & GreenChannel) != 0)
1545 difference=reconstruct_phash[GreenChannel].Q[i]-
1546 image_phash[GreenChannel].Q[i];
1547 distortion[GreenChannel]+=difference*difference;
1548 distortion[CompositeChannels]+=difference*difference;
1550 if ((channel & BlueChannel) != 0)
1552 difference=reconstruct_phash[BlueChannel].Q[i]-
1553 image_phash[BlueChannel].Q[i];
1554 distortion[BlueChannel]+=difference*difference;
1555 distortion[CompositeChannels]+=difference*difference;
1557 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse) &&
1558 (reconstruct_image->matte != MagickFalse))
1560 difference=reconstruct_phash[OpacityChannel].Q[i]-
1561 image_phash[OpacityChannel].Q[i];
1562 distortion[OpacityChannel]+=difference*difference;
1563 distortion[CompositeChannels]+=difference*difference;
1565 if (((channel & IndexChannel) != 0) &&
1566 (image->colorspace == CMYKColorspace) &&
1567 (reconstruct_image->colorspace == CMYKColorspace))
1569 difference=reconstruct_phash[IndexChannel].Q[i]-
1570 image_phash[IndexChannel].Q[i];
1571 distortion[IndexChannel]+=difference*difference;
1572 distortion[CompositeChannels]+=difference*difference;
1584static MagickBooleanType GetRootMeanSquaredDistortion(
const Image *image,
1585 const Image *reconstruct_image,
const ChannelType channel,
double *distortion,
1591 status=GetMeanSquaredDistortion(image,reconstruct_image,channel,distortion,
1593 if ((channel & RedChannel) != 0)
1594 distortion[RedChannel]=sqrt(distortion[RedChannel]);
1595 if ((channel & GreenChannel) != 0)
1596 distortion[GreenChannel]=sqrt(distortion[GreenChannel]);
1597 if ((channel & BlueChannel) != 0)
1598 distortion[BlueChannel]=sqrt(distortion[BlueChannel]);
1599 if (((channel & OpacityChannel) != 0) &&
1600 (image->matte != MagickFalse))
1601 distortion[OpacityChannel]=sqrt(distortion[OpacityChannel]);
1602 if (((channel & IndexChannel) != 0) &&
1603 (image->colorspace == CMYKColorspace))
1604 distortion[BlackChannel]=sqrt(distortion[BlackChannel]);
1605 distortion[CompositeChannels]=sqrt(distortion[CompositeChannels]);
1609MagickExport MagickBooleanType GetImageChannelDistortion(
Image *image,
1610 const Image *reconstruct_image,
const ChannelType channel,
1611 const MetricType metric,
double *distortion,
ExceptionInfo *exception)
1614 *channel_distortion;
1622 assert(image != (
Image *) NULL);
1623 assert(image->signature == MagickCoreSignature);
1624 assert(reconstruct_image != (
const Image *) NULL);
1625 assert(reconstruct_image->signature == MagickCoreSignature);
1626 assert(distortion != (
double *) NULL);
1627 if (IsEventLogging() != MagickFalse)
1628 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
1630 if (metric != PerceptualHashErrorMetric)
1631 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1632 ThrowBinaryException(ImageError,
"ImageMorphologyDiffers",image->filename);
1636 length=CompositeChannels+1UL;
1637 channel_distortion=(
double *) AcquireQuantumMemory(length,
1638 sizeof(*channel_distortion));
1639 if (channel_distortion == (
double *) NULL)
1640 ThrowFatalException(ResourceLimitFatalError,
"MemoryAllocationFailed");
1641 (void) memset(channel_distortion,0,length*
sizeof(*channel_distortion));
1644 case AbsoluteErrorMetric:
1646 status=GetAbsoluteDistortion(image,reconstruct_image,channel,
1647 channel_distortion,exception);
1650 case FuzzErrorMetric:
1652 status=GetFuzzDistortion(image,reconstruct_image,channel,
1653 channel_distortion,exception);
1656 case MeanAbsoluteErrorMetric:
1658 status=GetMeanAbsoluteDistortion(image,reconstruct_image,channel,
1659 channel_distortion,exception);
1662 case MeanErrorPerPixelMetric:
1664 status=GetMeanErrorPerPixel(image,reconstruct_image,channel,
1665 channel_distortion,exception);
1668 case MeanSquaredErrorMetric:
1670 status=GetMeanSquaredDistortion(image,reconstruct_image,channel,
1671 channel_distortion,exception);
1674 case NormalizedCrossCorrelationErrorMetric:
1677 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1678 channel,channel_distortion,exception);
1681 case PeakAbsoluteErrorMetric:
1683 status=GetPeakAbsoluteDistortion(image,reconstruct_image,channel,
1684 channel_distortion,exception);
1687 case PeakSignalToNoiseRatioMetric:
1689 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,channel,
1690 channel_distortion,exception);
1693 case PerceptualHashErrorMetric:
1695 status=GetPerceptualHashDistortion(image,reconstruct_image,channel,
1696 channel_distortion,exception);
1699 case RootMeanSquaredErrorMetric:
1701 status=GetRootMeanSquaredDistortion(image,reconstruct_image,channel,
1702 channel_distortion,exception);
1706 *distortion=channel_distortion[CompositeChannels];
1707 channel_distortion=(
double *) RelinquishMagickMemory(channel_distortion);
1708 (void) FormatImageProperty(image,
"distortion",
"%.*g",GetMagickPrecision(),
1745MagickExport
double *GetImageChannelDistortions(
Image *image,
1746 const Image *reconstruct_image,
const MetricType metric,
1750 *channel_distortion;
1758 assert(image != (
Image *) NULL);
1759 assert(image->signature == MagickCoreSignature);
1760 assert(reconstruct_image != (
const Image *) NULL);
1761 assert(reconstruct_image->signature == MagickCoreSignature);
1762 if (IsEventLogging() != MagickFalse)
1763 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
1764 if (metric != PerceptualHashErrorMetric)
1765 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1767 (void) ThrowMagickException(&image->exception,GetMagickModule(),
1768 ImageError,
"ImageMorphologyDiffers",
"`%s'",image->filename);
1769 return((
double *) NULL);
1774 length=CompositeChannels+1UL;
1775 channel_distortion=(
double *) AcquireQuantumMemory(length,
1776 sizeof(*channel_distortion));
1777 if (channel_distortion == (
double *) NULL)
1778 ThrowFatalException(ResourceLimitFatalError,
"MemoryAllocationFailed");
1779 (void) memset(channel_distortion,0,length*
1780 sizeof(*channel_distortion));
1784 case AbsoluteErrorMetric:
1786 status=GetAbsoluteDistortion(image,reconstruct_image,CompositeChannels,
1787 channel_distortion,exception);
1790 case FuzzErrorMetric:
1792 status=GetFuzzDistortion(image,reconstruct_image,CompositeChannels,
1793 channel_distortion,exception);
1796 case MeanAbsoluteErrorMetric:
1798 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1799 CompositeChannels,channel_distortion,exception);
1802 case MeanErrorPerPixelMetric:
1804 status=GetMeanErrorPerPixel(image,reconstruct_image,CompositeChannels,
1805 channel_distortion,exception);
1808 case MeanSquaredErrorMetric:
1810 status=GetMeanSquaredDistortion(image,reconstruct_image,CompositeChannels,
1811 channel_distortion,exception);
1814 case NormalizedCrossCorrelationErrorMetric:
1817 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1818 CompositeChannels,channel_distortion,exception);
1821 case PeakAbsoluteErrorMetric:
1823 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1824 CompositeChannels,channel_distortion,exception);
1827 case PeakSignalToNoiseRatioMetric:
1829 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1830 CompositeChannels,channel_distortion,exception);
1833 case PerceptualHashErrorMetric:
1835 status=GetPerceptualHashDistortion(image,reconstruct_image,
1836 CompositeChannels,channel_distortion,exception);
1839 case RootMeanSquaredErrorMetric:
1841 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1842 CompositeChannels,channel_distortion,exception);
1846 if (status == MagickFalse)
1848 channel_distortion=(
double *) RelinquishMagickMemory(channel_distortion);
1849 return((
double *) NULL);
1851 return(channel_distortion);
1901MagickExport MagickBooleanType IsImagesEqual(
Image *image,
1902 const Image *reconstruct_image)
1919 mean_error_per_pixel;
1928 assert(image != (
Image *) NULL);
1929 assert(image->signature == MagickCoreSignature);
1930 assert(reconstruct_image != (
const Image *) NULL);
1931 assert(reconstruct_image->signature == MagickCoreSignature);
1932 exception=(&image->exception);
1933 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1934 ThrowBinaryException(ImageError,
"ImageMorphologyDiffers",image->filename);
1937 mean_error_per_pixel=0.0;
1939 rows=MagickMax(image->rows,reconstruct_image->rows);
1940 columns=MagickMax(image->columns,reconstruct_image->columns);
1941 image_view=AcquireVirtualCacheView(image,exception);
1942 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1943 for (y=0; y < (ssize_t) rows; y++)
1946 *magick_restrict indexes,
1947 *magick_restrict reconstruct_indexes;
1956 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1957 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1960 indexes=GetCacheViewVirtualIndexQueue(image_view);
1961 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1962 for (x=0; x < (ssize_t) columns; x++)
1967 distance=fabs((
double) GetPixelRed(p)-(
double) GetPixelRed(q));
1968 mean_error_per_pixel+=distance;
1969 mean_error+=distance*distance;
1970 if (distance > maximum_error)
1971 maximum_error=distance;
1973 distance=fabs((
double) GetPixelGreen(p)-(
double) GetPixelGreen(q));
1974 mean_error_per_pixel+=distance;
1975 mean_error+=distance*distance;
1976 if (distance > maximum_error)
1977 maximum_error=distance;
1979 distance=fabs((
double) GetPixelBlue(p)-(
double) GetPixelBlue(q));
1980 mean_error_per_pixel+=distance;
1981 mean_error+=distance*distance;
1982 if (distance > maximum_error)
1983 maximum_error=distance;
1985 if (image->matte != MagickFalse)
1987 distance=fabs((
double) GetPixelOpacity(p)-(
double)
1988 GetPixelOpacity(q));
1989 mean_error_per_pixel+=distance;
1990 mean_error+=distance*distance;
1991 if (distance > maximum_error)
1992 maximum_error=distance;
1995 if ((image->colorspace == CMYKColorspace) &&
1996 (reconstruct_image->colorspace == CMYKColorspace))
1998 distance=fabs((
double) GetPixelIndex(indexes+x)-(
double)
1999 GetPixelIndex(reconstruct_indexes+x));
2000 mean_error_per_pixel+=distance;
2001 mean_error+=distance*distance;
2002 if (distance > maximum_error)
2003 maximum_error=distance;
2010 reconstruct_view=DestroyCacheView(reconstruct_view);
2011 image_view=DestroyCacheView(image_view);
2012 gamma=PerceptibleReciprocal(area);
2013 image->error.mean_error_per_pixel=gamma*mean_error_per_pixel;
2014 image->error.normalized_mean_error=gamma*QuantumScale*QuantumScale*mean_error;
2015 image->error.normalized_maximum_error=QuantumScale*maximum_error;
2016 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
2055static double GetSimilarityMetric(
const Image *image,
const Image *reference,
2056 const MetricType metric,
const ssize_t x_offset,
const ssize_t y_offset,
2071 SetGeometry(reference,&geometry);
2072 geometry.x=x_offset;
2073 geometry.y=y_offset;
2074 similarity_image=CropImage(image,&geometry,exception);
2075 if (similarity_image == (
Image *) NULL)
2078 status=GetImageDistortion(similarity_image,reference,metric,&distortion,
2081 similarity_image=DestroyImage(similarity_image);
2085MagickExport
Image *SimilarityImage(
Image *image,
const Image *reference,
2091 similarity_image=SimilarityMetricImage(image,reference,
2092 RootMeanSquaredErrorMetric,offset,similarity_metric,exception);
2093 return(similarity_image);
2096MagickExport
Image *SimilarityMetricImage(
Image *image,
const Image *reference,
2097 const MetricType metric,
RectangleInfo *offset,
double *similarity_metric,
2100#define SimilarityImageTag "Similarity/Image"
2109 similarity_threshold;
2112 *similarity_image = (
Image *) NULL;
2123 assert(image != (
const Image *) NULL);
2124 assert(image->signature == MagickCoreSignature);
2126 assert(exception->signature == MagickCoreSignature);
2128 if (IsEventLogging() != MagickFalse)
2129 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
2130 SetGeometry(reference,offset);
2131 *similarity_metric=MagickMaximumValue;
2132 if (ValidateImageMorphology(image,reference) == MagickFalse)
2133 ThrowImageException(ImageError,
"ImageMorphologyDiffers");
2134 if ((image->columns >= reference->columns) &&
2135 (image->rows >= reference->rows))
2136 similarity_image=CloneImage(image,image->columns-reference->columns+1,
2137 image->rows-reference->rows+1,MagickTrue,exception);
2138 if (similarity_image == (
Image *) NULL)
2139 return((
Image *) NULL);
2140 if (SetImageStorageClass(similarity_image,DirectClass) == MagickFalse)
2142 InheritException(exception,&similarity_image->exception);
2143 similarity_image=DestroyImage(similarity_image);
2144 return((
Image *) NULL);
2146 (void) SetImageAlphaChannel(similarity_image,DeactivateAlphaChannel);
2150 similarity_threshold=(-1.0);
2151 artifact=GetImageArtifact(image,
"compare:similarity-threshold");
2152 if (artifact != (
const char *) NULL)
2153 similarity_threshold=StringToDouble(artifact,(
char **) NULL);
2156 similarity_view=AcquireVirtualCacheView(similarity_image,exception);
2157#if defined(MAGICKCORE_OPENMP_SUPPORT)
2158 #pragma omp parallel for schedule(static) \
2159 shared(progress,status,similarity_metric) \
2160 magick_number_threads(image,image,image->rows-reference->rows+1,1)
2162 for (y=0; y < (ssize_t) (image->rows-reference->rows+1); y++)
2173 if (status == MagickFalse)
2175#if defined(MAGICKCORE_OPENMP_SUPPORT)
2176 #pragma omp flush(similarity_metric)
2178 if (*similarity_metric <= similarity_threshold)
2180 q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
2187 for (x=0; x < (ssize_t) (image->columns-reference->columns+1); x++)
2189#if defined(MAGICKCORE_OPENMP_SUPPORT)
2190 #pragma omp flush(similarity_metric)
2192 if (*similarity_metric <= similarity_threshold)
2194 similarity=GetSimilarityMetric(image,reference,metric,x,y,exception);
2195 if (metric == PeakSignalToNoiseRatioMetric)
2197 if ((metric == NormalizedCrossCorrelationErrorMetric) ||
2198 (metric == UndefinedErrorMetric))
2199 similarity=1.0-similarity;
2200#if defined(MAGICKCORE_OPENMP_SUPPORT)
2201 #pragma omp critical (MagickCore_SimilarityImage)
2203 if (similarity < *similarity_metric)
2205 *similarity_metric=similarity;
2209 if (metric == PerceptualHashErrorMetric)
2210 similarity=MagickMin(0.01*similarity,1.0);
2211 SetPixelRed(q,ClampToQuantum((
double) QuantumRange-(
double) QuantumRange*
2213 SetPixelGreen(q,GetPixelRed(q));
2214 SetPixelBlue(q,GetPixelRed(q));
2217 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
2219 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2224#if defined(MAGICKCORE_OPENMP_SUPPORT)
2228 proceed=SetImageProgress(image,SimilarityImageTag,progress,image->rows);
2229 if (proceed == MagickFalse)
2233 similarity_view=DestroyCacheView(similarity_view);
2234 if (status == MagickFalse)
2235 similarity_image=DestroyImage(similarity_image);
2236 return(similarity_image);