43#include "magick/studio.h"
44#include "magick/artifact.h"
45#include "magick/attribute.h"
46#include "magick/cache-view.h"
47#include "magick/channel.h"
48#include "magick/client.h"
49#include "magick/color.h"
50#include "magick/color-private.h"
51#include "magick/colorspace.h"
52#include "magick/colorspace-private.h"
53#include "magick/compare.h"
54#include "magick/compare-private.h"
55#include "magick/composite-private.h"
56#include "magick/constitute.h"
57#include "magick/exception-private.h"
58#include "magick/geometry.h"
59#include "magick/image-private.h"
60#include "magick/list.h"
61#include "magick/log.h"
62#include "magick/memory_.h"
63#include "magick/monitor.h"
64#include "magick/monitor-private.h"
65#include "magick/option.h"
66#include "magick/pixel-private.h"
67#include "magick/property.h"
68#include "magick/resource_.h"
69#include "magick/statistic-private.h"
70#include "magick/string_.h"
71#include "magick/string-private.h"
72#include "magick/statistic.h"
73#include "magick/thread-private.h"
74#include "magick/transform.h"
75#include "magick/utility.h"
76#include "magick/version.h"
114MagickExport Image *CompareImages(Image *image,
const Image *reconstruct_image,
115 const MetricType metric,
double *distortion,ExceptionInfo *exception)
120 highlight_image=CompareImageChannels(image,reconstruct_image,
121 CompositeChannels,metric,distortion,exception);
122 return(highlight_image);
125static size_t GetNumberChannels(
const Image *image,
const ChannelType channel)
131 if ((channel & RedChannel) != 0)
133 if ((channel & GreenChannel) != 0)
135 if ((channel & BlueChannel) != 0)
137 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
139 if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
141 return(channels == 0 ? 1UL : channels);
144static inline MagickBooleanType ValidateImageMorphology(
145 const Image *magick_restrict image,
146 const Image *magick_restrict reconstruct_image)
151 if (GetNumberChannels(image,DefaultChannels) !=
152 GetNumberChannels(reconstruct_image,DefaultChannels))
157MagickExport Image *CompareImageChannels(Image *image,
158 const Image *reconstruct_image,
const ChannelType channel,
159 const MetricType metric,
double *distortion,ExceptionInfo *exception)
192 assert(image != (Image *) NULL);
193 assert(image->signature == MagickCoreSignature);
194 assert(reconstruct_image != (
const Image *) NULL);
195 assert(reconstruct_image->signature == MagickCoreSignature);
196 assert(distortion != (
double *) NULL);
197 if (IsEventLogging() != MagickFalse)
198 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
200 if (metric != PerceptualHashErrorMetric)
201 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
202 ThrowImageException(ImageError,
"ImageMorphologyDiffers");
203 status=GetImageChannelDistortion(image,reconstruct_image,channel,metric,
204 distortion,exception);
205 if (status == MagickFalse)
206 return((Image *) NULL);
207 clone_image=CloneImage(image,0,0,MagickTrue,exception);
208 if (clone_image == (Image *) NULL)
209 return((Image *) NULL);
210 (void) SetImageMask(clone_image,(Image *) NULL);
211 difference_image=CloneImage(clone_image,0,0,MagickTrue,exception);
212 clone_image=DestroyImage(clone_image);
213 if (difference_image == (Image *) NULL)
214 return((Image *) NULL);
215 (void) SetImageAlphaChannel(difference_image,OpaqueAlphaChannel);
216 SetImageDistortionBounds(image,reconstruct_image,&columns,&rows);
217 highlight_image=CloneImage(image,columns,rows,MagickTrue,exception);
218 if (highlight_image == (Image *) NULL)
220 difference_image=DestroyImage(difference_image);
221 return((Image *) NULL);
223 if (SetImageStorageClass(highlight_image,DirectClass) == MagickFalse)
225 InheritException(exception,&highlight_image->exception);
226 difference_image=DestroyImage(difference_image);
227 highlight_image=DestroyImage(highlight_image);
228 return((Image *) NULL);
230 (void) SetImageMask(highlight_image,(Image *) NULL);
231 (void) SetImageAlphaChannel(highlight_image,OpaqueAlphaChannel);
232 (void) QueryMagickColor(
"#f1001ecc",&highlight,exception);
233 artifact=GetImageArtifact(image,
"compare:highlight-color");
234 if (artifact != (
const char *) NULL)
235 (void) QueryMagickColor(artifact,&highlight,exception);
236 (void) QueryMagickColor(
"#ffffffcc",&lowlight,exception);
237 artifact=GetImageArtifact(image,
"compare:lowlight-color");
238 if (artifact != (
const char *) NULL)
239 (void) QueryMagickColor(artifact,&lowlight,exception);
240 if (highlight_image->colorspace == CMYKColorspace)
242 ConvertRGBToCMYK(&highlight);
243 ConvertRGBToCMYK(&lowlight);
249 fuzz=GetFuzzyColorDistance(image,reconstruct_image);
250 GetMagickPixelPacket(image,&zero);
251 image_view=AcquireVirtualCacheView(image,exception);
252 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
253 highlight_view=AcquireAuthenticCacheView(highlight_image,exception);
254#if defined(MAGICKCORE_OPENMP_SUPPORT)
255 #pragma omp parallel for schedule(static) shared(status) \
256 magick_number_threads(image,highlight_image,rows,1)
258 for (y=0; y < (ssize_t) rows; y++)
268 *magick_restrict indexes,
269 *magick_restrict reconstruct_indexes;
276 *magick_restrict highlight_indexes;
284 if (status == MagickFalse)
286 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
287 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
288 r=QueueCacheViewAuthenticPixels(highlight_view,0,y,columns,1,exception);
289 if ((p == (
const PixelPacket *) NULL) ||
290 (q == (
const PixelPacket *) NULL) || (r == (PixelPacket *) NULL))
295 indexes=GetCacheViewVirtualIndexQueue(image_view);
296 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
297 highlight_indexes=GetCacheViewAuthenticIndexQueue(highlight_view);
299 reconstruct_pixel=zero;
300 for (x=0; x < (ssize_t) columns; x++)
305 SetMagickPixelPacket(image,p,indexes == (IndexPacket *) NULL ? NULL :
307 SetMagickPixelPacket(reconstruct_image,q,reconstruct_indexes ==
308 (IndexPacket *) NULL ? NULL : reconstruct_indexes+x,&reconstruct_pixel);
309 difference=MagickFalse;
310 if (channel == CompositeChannels)
312 if (IsMagickColorSimilar(&pixel,&reconstruct_pixel) == MagickFalse)
313 difference=MagickTrue;
323 Sa=QuantumScale*(image->matte != MagickFalse ? (double)
324 GetPixelAlpha(p) : ((double) QuantumRange-(double) OpaqueOpacity));
325 Da=QuantumScale*(image->matte != MagickFalse ? (double)
326 GetPixelAlpha(q) : ((double) QuantumRange-(double) OpaqueOpacity));
327 if ((channel & RedChannel) != 0)
329 pixel=Sa*(double) GetPixelRed(p)-Da*(double) GetPixelRed(q);
330 distance=pixel*pixel;
332 difference=MagickTrue;
334 if ((channel & GreenChannel) != 0)
336 pixel=Sa*(double) GetPixelGreen(p)-Da*(double) GetPixelGreen(q);
337 distance=pixel*pixel;
339 difference=MagickTrue;
341 if ((channel & BlueChannel) != 0)
343 pixel=Sa*(double) GetPixelBlue(p)-Da*(double) GetPixelBlue(q);
344 distance=pixel*pixel;
346 difference=MagickTrue;
348 if (((channel & OpacityChannel) != 0) &&
349 (image->matte != MagickFalse))
351 pixel=(double) GetPixelOpacity(p)-(double) GetPixelOpacity(q);
352 distance=pixel*pixel;
354 difference=MagickTrue;
356 if (((channel & IndexChannel) != 0) &&
357 (image->colorspace == CMYKColorspace))
359 pixel=Sa*(double) indexes[x]-Da*(double) reconstruct_indexes[x];
360 distance=pixel*pixel;
362 difference=MagickTrue;
365 if (difference != MagickFalse)
366 SetPixelPacket(highlight_image,&highlight,r,highlight_indexes ==
367 (IndexPacket *) NULL ? NULL : highlight_indexes+x);
369 SetPixelPacket(highlight_image,&lowlight,r,highlight_indexes ==
370 (IndexPacket *) NULL ? NULL : highlight_indexes+x);
375 sync=SyncCacheViewAuthenticPixels(highlight_view,exception);
376 if (sync == MagickFalse)
379 highlight_view=DestroyCacheView(highlight_view);
380 reconstruct_view=DestroyCacheView(reconstruct_view);
381 image_view=DestroyCacheView(image_view);
382 (void) CompositeImage(difference_image,image->compose,highlight_image,0,0);
383 highlight_image=DestroyImage(highlight_image);
384 if (status == MagickFalse)
385 difference_image=DestroyImage(difference_image);
386 return(difference_image);
425MagickExport MagickBooleanType GetImageDistortion(Image *image,
426 const Image *reconstruct_image,
const MetricType metric,
double *distortion,
427 ExceptionInfo *exception)
432 status=GetImageChannelDistortion(image,reconstruct_image,CompositeChannels,
433 metric,distortion,exception);
437static MagickBooleanType GetAbsoluteDistortion(
const Image *image,
438 const Image *reconstruct_image,
const ChannelType channel,
double *distortion,
439 ExceptionInfo *exception)
463 SetImageDistortionBounds(image,reconstruct_image,&columns,&rows);
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(distortion,status) \
468 magick_number_threads(image,image,rows,1)
470 for (y=0; y < (ssize_t) rows; y++)
473 *magick_restrict indexes,
474 *magick_restrict reconstruct_indexes;
481 channel_distortion[CompositeChannels+1] = { 0.0 };
487 if (status == MagickFalse)
489 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
490 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
491 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
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++)
509 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
510 ((double) QuantumRange-(double) OpaqueOpacity));
511 Da=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(q) :
512 ((double) QuantumRange-(double) OpaqueOpacity));
513 if ((channel & RedChannel) != 0)
515 delta=Sa*(double) GetPixelRed(p)-Da*(double)
517 if ((delta*delta) >= MagickEpsilon)
519 channel_distortion[RedChannel]++;
523 if ((channel & GreenChannel) != 0)
525 delta=Sa*(double) GetPixelGreen(p)-Da*(double)
527 if ((delta*delta) >= MagickEpsilon)
529 channel_distortion[GreenChannel]++;
533 if ((channel & BlueChannel) != 0)
535 delta=Sa*(double) GetPixelBlue(p)-Da*(double)
537 if ((delta*delta) >= MagickEpsilon)
539 channel_distortion[BlueChannel]++;
543 if (((channel & OpacityChannel) != 0) &&
544 (image->matte != MagickFalse))
546 delta=(double) GetPixelOpacity(p)-(double)
548 if ((delta*delta) >= MagickEpsilon)
550 channel_distortion[OpacityChannel]++;
554 if (((channel & IndexChannel) != 0) &&
555 (image->colorspace == CMYKColorspace))
557 delta=Sa*(double) indexes[x]-Da*(
double)
558 reconstruct_indexes[x];
559 if ((delta*delta) >= MagickEpsilon)
561 channel_distortion[IndexChannel]++;
566 channel_distortion[CompositeChannels]++;
570#if defined(MAGICKCORE_OPENMP_SUPPORT)
571 #pragma omp critical (MagickCore_GetAbsoluteDistortion)
573 for (i=0; i <= (ssize_t) CompositeChannels; i++)
574 distortion[i]+=channel_distortion[i];
576 reconstruct_view=DestroyCacheView(reconstruct_view);
577 image_view=DestroyCacheView(image_view);
578 area=MagickSafeReciprocal((
double) columns*rows);
579 for (j=0; j <= CompositeChannels; j++)
584static MagickBooleanType GetFuzzDistortion(
const Image *image,
585 const Image *reconstruct_image,
const ChannelType channel,
double *distortion,
586 ExceptionInfo *exception)
611 fuzz=GetFuzzyColorDistance(image,reconstruct_image);
612 SetImageDistortionBounds(image,reconstruct_image,&columns,&rows);
613 image_view=AcquireVirtualCacheView(image,exception);
614 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
615#if defined(MAGICKCORE_OPENMP_SUPPORT)
616 #pragma omp parallel for schedule(static) shared(distortion,status) \
617 magick_number_threads(image,image,rows,1)
619 for (y=0; y < (ssize_t) rows; y++)
622 *magick_restrict indexes,
623 *magick_restrict reconstruct_indexes;
630 channel_distortion[CompositeChannels+1] = { 0.0 };
636 if (status == MagickFalse)
638 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
639 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
640 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
645 indexes=GetCacheViewVirtualIndexQueue(image_view);
646 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
647 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
648 for (x=0; x < (ssize_t) columns; x++)
658 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
659 ((double) QuantumRange-(double) OpaqueOpacity));
660 Da=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(q) :
661 ((double) QuantumRange-(double) OpaqueOpacity));
662 if ((channel & RedChannel) != 0)
664 delta=Sa*(double) GetPixelRed(p)-Da*(double)
666 if ((delta*delta) > fuzz)
668 channel_distortion[RedChannel]++;
672 if ((channel & GreenChannel) != 0)
674 delta=Sa*(double) GetPixelGreen(p)-Da*(double)
676 if ((delta*delta) > fuzz)
678 channel_distortion[GreenChannel]++;
682 if ((channel & BlueChannel) != 0)
684 delta=Sa*(double) GetPixelBlue(p)-Da*(double)
686 if ((delta*delta) > fuzz)
688 channel_distortion[BlueChannel]++;
692 if (((channel & OpacityChannel) != 0) &&
693 (image->matte != MagickFalse))
695 delta=(double) GetPixelOpacity(p)-(double)
697 if ((delta*delta) > fuzz)
699 channel_distortion[OpacityChannel]++;
703 if (((channel & IndexChannel) != 0) &&
704 (image->colorspace == CMYKColorspace))
706 delta=Sa*(double) indexes[x]-Da*(
double)
707 reconstruct_indexes[x];
708 if ((delta*delta) > fuzz)
710 channel_distortion[IndexChannel]++;
715 channel_distortion[CompositeChannels]++;
719#if defined(MAGICKCORE_OPENMP_SUPPORT)
720 #pragma omp critical (MagickCore_GetAbsoluteDistortion)
722 for (i=0; i <= (ssize_t) CompositeChannels; i++)
723 distortion[i]+=channel_distortion[i];
725 reconstruct_view=DestroyCacheView(reconstruct_view);
726 image_view=DestroyCacheView(image_view);
727 area=MagickSafeReciprocal((
double) columns*rows);
728 for (j=0; j <= CompositeChannels; j++)
735static MagickBooleanType GetMeanAbsoluteDistortion(
const Image *image,
736 const Image *reconstruct_image,
const ChannelType channel,
737 double *distortion,ExceptionInfo *exception)
755 SetImageDistortionBounds(image,reconstruct_image,&columns,&rows);
756 image_view=AcquireVirtualCacheView(image,exception);
757 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
758#if defined(MAGICKCORE_OPENMP_SUPPORT)
759 #pragma omp parallel for schedule(static) shared(status) \
760 magick_number_threads(image,image,rows,1)
762 for (y=0; y < (ssize_t) rows; y++)
765 channel_distortion[CompositeChannels+1];
768 *magick_restrict indexes,
769 *magick_restrict reconstruct_indexes;
779 if (status == MagickFalse)
781 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
782 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
783 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
788 indexes=GetCacheViewVirtualIndexQueue(image_view);
789 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
790 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
791 for (x=0; x < (ssize_t) columns; x++)
798 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
799 ((double) QuantumRange-(double) OpaqueOpacity));
800 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
801 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
803 if ((channel & RedChannel) != 0)
805 distance=QuantumScale*fabs(Sa*(
double) GetPixelRed(p)-Da*
806 (
double) GetPixelRed(q));
807 channel_distortion[RedChannel]+=distance;
808 channel_distortion[CompositeChannels]+=distance;
810 if ((channel & GreenChannel) != 0)
812 distance=QuantumScale*fabs(Sa*(
double) GetPixelGreen(p)-Da*
813 (
double) GetPixelGreen(q));
814 channel_distortion[GreenChannel]+=distance;
815 channel_distortion[CompositeChannels]+=distance;
817 if ((channel & BlueChannel) != 0)
819 distance=QuantumScale*fabs(Sa*(
double) GetPixelBlue(p)-Da*
820 (
double) GetPixelBlue(q));
821 channel_distortion[BlueChannel]+=distance;
822 channel_distortion[CompositeChannels]+=distance;
824 if (((channel & OpacityChannel) != 0) &&
825 (image->matte != MagickFalse))
827 distance=QuantumScale*fabs((double) GetPixelOpacity(p)-(double)
829 channel_distortion[OpacityChannel]+=distance;
830 channel_distortion[CompositeChannels]+=distance;
832 if (((channel & IndexChannel) != 0) &&
833 (image->colorspace == CMYKColorspace))
835 distance=QuantumScale*fabs(Sa*(double) GetPixelIndex(indexes+x)-Da*
836 (double) GetPixelIndex(reconstruct_indexes+x));
837 channel_distortion[BlackChannel]+=distance;
838 channel_distortion[CompositeChannels]+=distance;
843#if defined(MAGICKCORE_OPENMP_SUPPORT)
844 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
846 for (i=0; i <= (ssize_t) CompositeChannels; i++)
847 distortion[i]+=channel_distortion[i];
849 reconstruct_view=DestroyCacheView(reconstruct_view);
850 image_view=DestroyCacheView(image_view);
851 for (i=0; i <= (ssize_t) CompositeChannels; i++)
852 distortion[i]/=((
double) columns*rows);
853 distortion[CompositeChannels]/=(double) GetNumberChannels(image,channel);
857static MagickBooleanType GetMeanErrorPerPixel(Image *image,
858 const Image *reconstruct_image,
const ChannelType channel,
double *distortion,
859 ExceptionInfo *exception)
866 maximum_error = -MagickMaximumValue,
881 SetImageDistortionBounds(image,reconstruct_image,&columns,&rows);
882 image_view=AcquireVirtualCacheView(image,exception);
883 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
884#if defined(MAGICKCORE_OPENMP_SUPPORT)
885 #pragma omp parallel for schedule(static) shared(maximum_error,status) \
886 magick_number_threads(image,image,rows,1)
888 for (y=0; y < (ssize_t) rows; y++)
891 channel_distortion[CompositeChannels+1] = { 0.0 },
892 local_maximum = maximum_error,
893 local_mean_error = 0.0;
896 *magick_restrict indexes,
897 *magick_restrict reconstruct_indexes;
907 if (status == MagickFalse)
909 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
910 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
911 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
916 indexes=GetCacheViewVirtualIndexQueue(image_view);
917 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
918 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
919 for (x=0; x < (ssize_t) columns; x++)
926 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
927 ((double) QuantumRange-(double) OpaqueOpacity));
928 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
929 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
931 if ((channel & RedChannel) != 0)
933 distance=QuantumScale*fabs(Sa*(
double) GetPixelRed(p)-Da*
934 (
double) GetPixelRed(q));
935 channel_distortion[RedChannel]+=distance;
936 channel_distortion[CompositeChannels]+=distance;
937 local_mean_error+=distance*distance;
938 if (distance > local_maximum)
939 local_maximum=distance;
941 if ((channel & GreenChannel) != 0)
943 distance=QuantumScale*fabs(Sa*(
double) GetPixelGreen(p)-Da*
944 (
double) GetPixelGreen(q));
945 channel_distortion[GreenChannel]+=distance;
946 channel_distortion[CompositeChannels]+=distance;
947 local_mean_error+=distance*distance;
948 if (distance > local_maximum)
949 local_maximum=distance;
951 if ((channel & BlueChannel) != 0)
953 distance=QuantumScale*fabs(Sa*(
double) GetPixelBlue(p)-Da*
954 (
double) GetPixelBlue(q));
955 channel_distortion[BlueChannel]+=distance;
956 channel_distortion[CompositeChannels]+=distance;
957 local_mean_error+=distance*distance;
958 if (distance > local_maximum)
959 local_maximum=distance;
961 if (((channel & OpacityChannel) != 0) &&
962 (image->matte != MagickFalse))
964 distance=QuantumScale*fabs((double) GetPixelOpacity(p)-(double)
966 channel_distortion[OpacityChannel]+=distance;
967 channel_distortion[CompositeChannels]+=distance;
968 local_mean_error+=distance*distance;
969 if (distance > local_maximum)
970 local_maximum=distance;
972 if (((channel & IndexChannel) != 0) &&
973 (image->colorspace == CMYKColorspace))
975 distance=QuantumScale*fabs(Sa*(double) GetPixelIndex(indexes+x)-Da*
976 (double) GetPixelIndex(reconstruct_indexes+x));
977 channel_distortion[BlackChannel]+=distance;
978 channel_distortion[CompositeChannels]+=distance;
979 local_mean_error+=distance*distance;
980 if (distance > local_maximum)
981 local_maximum=distance;
986#if defined(MAGICKCORE_OPENMP_SUPPORT)
987 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
990 for (i=0; i <= (ssize_t) CompositeChannels; i++)
991 distortion[i]+=channel_distortion[i];
992 mean_error+=local_mean_error;
993 if (local_maximum > maximum_error)
994 maximum_error=local_maximum;
997 reconstruct_view=DestroyCacheView(reconstruct_view);
998 image_view=DestroyCacheView(image_view);
999 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1000 distortion[i]/=((
double) columns*rows);
1001 distortion[CompositeChannels]/=(double) GetNumberChannels(image,channel);
1002 image->error.mean_error_per_pixel=QuantumRange*distortion[CompositeChannels];
1003 image->error.normalized_mean_error=mean_error/((double) columns*rows);
1004 image->error.normalized_maximum_error=maximum_error;
1008static MagickBooleanType GetMeanSquaredDistortion(
const Image *image,
1009 const Image *reconstruct_image,
const ChannelType channel,
1010 double *distortion,ExceptionInfo *exception)
1031 SetImageDistortionBounds(image,reconstruct_image,&columns,&rows);
1032 image_view=AcquireVirtualCacheView(image,exception);
1033 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1034#if defined(MAGICKCORE_OPENMP_SUPPORT)
1035 #pragma omp parallel for schedule(static) shared(distortion,status) \
1036 magick_number_threads(image,image,rows,1)
1038 for (y=0; y < (ssize_t) rows; y++)
1041 channel_distortion[CompositeChannels+1] = { 0.0 };
1044 *magick_restrict indexes,
1045 *magick_restrict reconstruct_indexes;
1055 if (status == MagickFalse)
1057 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1058 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1059 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
1064 indexes=GetCacheViewVirtualIndexQueue(image_view);
1065 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1066 for (x=0; x < (ssize_t) columns; x++)
1073 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
1074 ((double) QuantumRange-(double) OpaqueOpacity));
1075 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1076 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
1078 if ((channel & RedChannel) != 0)
1080 distance=QuantumScale*(Sa*(double) GetPixelRed(p)-Da*(double)
1082 channel_distortion[RedChannel]+=distance*distance;
1083 channel_distortion[CompositeChannels]+=distance*distance;
1085 if ((channel & GreenChannel) != 0)
1087 distance=QuantumScale*(Sa*(double) GetPixelGreen(p)-Da*(double)
1089 channel_distortion[GreenChannel]+=distance*distance;
1090 channel_distortion[CompositeChannels]+=distance*distance;
1092 if ((channel & BlueChannel) != 0)
1094 distance=QuantumScale*(Sa*(double) GetPixelBlue(p)-Da*(double)
1096 channel_distortion[BlueChannel]+=distance*distance;
1097 channel_distortion[CompositeChannels]+=distance*distance;
1099 if (((channel & OpacityChannel) != 0) &&
1100 (image->matte != MagickFalse))
1102 distance=QuantumScale*((double) GetPixelOpacity(p)-(double)
1103 GetPixelOpacity(q));
1104 channel_distortion[OpacityChannel]+=distance*distance;
1105 channel_distortion[CompositeChannels]+=distance*distance;
1107 if (((channel & IndexChannel) != 0) &&
1108 (image->colorspace == CMYKColorspace) &&
1109 (reconstruct_image->colorspace == CMYKColorspace))
1111 distance=QuantumScale*(Sa*(double) GetPixelIndex(indexes+x)-Da*
1112 (double) GetPixelIndex(reconstruct_indexes+x));
1113 channel_distortion[BlackChannel]+=distance*distance;
1114 channel_distortion[CompositeChannels]+=distance*distance;
1119#if defined(MAGICKCORE_OPENMP_SUPPORT)
1120 #pragma omp critical (MagickCore_GetMeanSquaredError)
1122 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1123 distortion[i]+=channel_distortion[i];
1125 reconstruct_view=DestroyCacheView(reconstruct_view);
1126 image_view=DestroyCacheView(image_view);
1127 area=MagickSafeReciprocal((
double) columns*rows);
1128 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1129 distortion[i]*=area;
1130 distortion[CompositeChannels]/=(double) GetNumberChannels(image,channel);
1134static MagickBooleanType GetNormalizedCrossCorrelationDistortion(
1135 const Image *image,
const Image *reconstruct_image,
const ChannelType channel,
1136 double *distortion,ExceptionInfo *exception)
1138#define SimilarityImageTag "Similarity/Image"
1146 *reconstruct_statistics;
1149 alpha_variance[CompositeChannels+1] = { 0.0 },
1151 beta_variance[CompositeChannels+1] = { 0.0 };
1170 image_statistics=GetImageChannelStatistics(image,exception);
1171 reconstruct_statistics=GetImageChannelStatistics(reconstruct_image,exception);
1172 if ((image_statistics == (ChannelStatistics *) NULL) ||
1173 (reconstruct_statistics == (ChannelStatistics *) NULL))
1175 if (image_statistics != (ChannelStatistics *) NULL)
1176 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1178 if (reconstruct_statistics != (ChannelStatistics *) NULL)
1179 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1180 reconstruct_statistics);
1181 return(MagickFalse);
1183 (void) memset(distortion,0,(CompositeChannels+1)*
sizeof(*distortion));
1186 SetImageDistortionBounds(image,reconstruct_image,&columns,&rows);
1187 image_view=AcquireVirtualCacheView(image,exception);
1188 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1189#if defined(MAGICKCORE_OPENMP_SUPPORT)
1190 #pragma omp parallel for schedule(static) shared(status) \
1191 magick_number_threads(image,image,rows,1)
1193 for (y=0; y < (ssize_t) rows; y++)
1196 *magick_restrict indexes,
1197 *magick_restrict reconstruct_indexes;
1204 channel_alpha_variance[CompositeChannels+1] = { 0.0 },
1205 channel_beta_variance[CompositeChannels+1] = { 0.0 },
1206 channel_distortion[CompositeChannels+1] = { 0.0 };
1211 if (status == MagickFalse)
1213 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1214 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1215 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
1220 indexes=GetCacheViewVirtualIndexQueue(image_view);
1221 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1222 for (x=0; x < (ssize_t) columns; x++)
1230 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
1231 (double) QuantumRange);
1232 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1233 (double) GetPixelAlpha(q) : (double) QuantumRange);
1234 if ((channel & RedChannel) != 0)
1236 alpha=QuantumScale*(Sa*(double) GetPixelRed(p)-
1237 image_statistics[RedChannel].mean);
1238 beta=QuantumScale*(Da*(double) GetPixelRed(q)-
1239 reconstruct_statistics[RedChannel].mean);
1240 channel_distortion[RedChannel]+=alpha*beta;
1241 channel_alpha_variance[RedChannel]+=alpha*alpha;
1242 channel_beta_variance[RedChannel]+=beta*beta;
1244 if ((channel & GreenChannel) != 0)
1246 alpha=QuantumScale*(Sa*(double) GetPixelGreen(p)-
1247 image_statistics[GreenChannel].mean);
1248 beta=QuantumScale*(Da*(double) GetPixelGreen(q)-
1249 reconstruct_statistics[GreenChannel].mean);
1250 channel_distortion[GreenChannel]+=alpha*beta;
1251 channel_alpha_variance[GreenChannel]+=alpha*alpha;
1252 channel_beta_variance[GreenChannel]+=beta*beta;
1254 if ((channel & BlueChannel) != 0)
1256 alpha=QuantumScale*(Sa*(double) GetPixelBlue(p)-
1257 image_statistics[BlueChannel].mean);
1258 beta=QuantumScale*(Da*(double) GetPixelBlue(q)-
1259 reconstruct_statistics[BlueChannel].mean);
1260 channel_distortion[BlueChannel]+=alpha*beta;
1261 channel_alpha_variance[BlueChannel]+=alpha*alpha;
1262 channel_beta_variance[BlueChannel]+=beta*beta;
1264 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
1266 alpha=QuantumScale*((double) GetPixelAlpha(p)-
1267 image_statistics[AlphaChannel].mean);
1268 beta=QuantumScale*((double) GetPixelAlpha(q)-
1269 reconstruct_statistics[AlphaChannel].mean);
1270 channel_distortion[OpacityChannel]+=alpha*beta;
1271 channel_alpha_variance[OpacityChannel]+=alpha*alpha;
1272 channel_beta_variance[OpacityChannel]+=beta*beta;
1274 if (((channel & IndexChannel) != 0) &&
1275 (image->colorspace == CMYKColorspace) &&
1276 (reconstruct_image->colorspace == CMYKColorspace))
1278 alpha=QuantumScale*(Sa*(double) GetPixelIndex(indexes+x)-
1279 image_statistics[BlackChannel].mean);
1280 beta=QuantumScale*(Da*(double) GetPixelIndex(reconstruct_indexes+
1281 x)-reconstruct_statistics[BlackChannel].mean);
1282 channel_distortion[BlackChannel]+=alpha*beta;
1283 channel_alpha_variance[BlackChannel]+=alpha*alpha;
1284 channel_beta_variance[BlackChannel]+=beta*beta;
1289#if defined(MAGICKCORE_OPENMP_SUPPORT)
1290 #pragma omp critical (GetNormalizedCrossCorrelationDistortion)
1296 for (j=0; j < (ssize_t) CompositeChannels; j++)
1298 distortion[j]+=channel_distortion[j];
1299 alpha_variance[j]+=channel_alpha_variance[j];
1300 beta_variance[j]+=channel_beta_variance[j];
1303 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1308#if defined(MAGICKCORE_OPENMP_SUPPORT)
1312 proceed=SetImageProgress(image,SimilarityImageTag,progress,rows);
1313 if (proceed == MagickFalse)
1317 reconstruct_view=DestroyCacheView(reconstruct_view);
1318 image_view=DestroyCacheView(image_view);
1322 area=MagickSafeReciprocal((
double) columns*rows);
1323 for (i=0; i < (ssize_t) CompositeChannels; i++)
1325 distortion[i]*=area;
1326 alpha_variance[i]*=area;
1327 beta_variance[i]*=area;
1328 distortion[i]*=MagickSafeReciprocal(sqrt(alpha_variance[i]*
1330 distortion[CompositeChannels]+=distortion[i];
1332 distortion[CompositeChannels]/=(double) GetNumberChannels(image,channel);
1336 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1337 reconstruct_statistics);
1338 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1343static MagickBooleanType GetPeakAbsoluteDistortion(
const Image *image,
1344 const Image *reconstruct_image,
const ChannelType channel,
1345 double *distortion,ExceptionInfo *exception)
1362 (void) memset(distortion,0,(CompositeChannels+1)*
sizeof(*distortion));
1363 SetImageDistortionBounds(image,reconstruct_image,&columns,&rows);
1364 image_view=AcquireVirtualCacheView(image,exception);
1365 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1366#if defined(MAGICKCORE_OPENMP_SUPPORT)
1367 #pragma omp parallel for schedule(static) shared(status) \
1368 magick_number_threads(image,image,rows,1)
1370 for (y=0; y < (ssize_t) rows; y++)
1373 channel_distortion[CompositeChannels+1];
1376 *magick_restrict indexes,
1377 *magick_restrict reconstruct_indexes;
1387 if (status == MagickFalse)
1389 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1390 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1391 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
1396 indexes=GetCacheViewVirtualIndexQueue(image_view);
1397 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1398 (void) memset(channel_distortion,0,(CompositeChannels+1)*
1399 sizeof(*channel_distortion));
1400 for (x=0; x < (ssize_t) columns; x++)
1407 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
1408 ((double) QuantumRange-(double) OpaqueOpacity));
1409 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1410 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
1412 if ((channel & RedChannel) != 0)
1414 distance=QuantumScale*fabs(Sa*(
double) GetPixelRed(p)-Da*
1415 (
double) GetPixelRed(q));
1416 if (distance > channel_distortion[RedChannel])
1417 channel_distortion[RedChannel]=distance;
1418 if (distance > channel_distortion[CompositeChannels])
1419 channel_distortion[CompositeChannels]=distance;
1421 if ((channel & GreenChannel) != 0)
1423 distance=QuantumScale*fabs(Sa*(
double) GetPixelGreen(p)-Da*
1424 (
double) GetPixelGreen(q));
1425 if (distance > channel_distortion[GreenChannel])
1426 channel_distortion[GreenChannel]=distance;
1427 if (distance > channel_distortion[CompositeChannels])
1428 channel_distortion[CompositeChannels]=distance;
1430 if ((channel & BlueChannel) != 0)
1432 distance=QuantumScale*fabs(Sa*(
double) GetPixelBlue(p)-Da*
1433 (
double) GetPixelBlue(q));
1434 if (distance > channel_distortion[BlueChannel])
1435 channel_distortion[BlueChannel]=distance;
1436 if (distance > channel_distortion[CompositeChannels])
1437 channel_distortion[CompositeChannels]=distance;
1439 if (((channel & OpacityChannel) != 0) &&
1440 (image->matte != MagickFalse))
1442 distance=QuantumScale*fabs((double) GetPixelOpacity(p)-(double)
1443 GetPixelOpacity(q));
1444 if (distance > channel_distortion[OpacityChannel])
1445 channel_distortion[OpacityChannel]=distance;
1446 if (distance > channel_distortion[CompositeChannels])
1447 channel_distortion[CompositeChannels]=distance;
1449 if (((channel & IndexChannel) != 0) &&
1450 (image->colorspace == CMYKColorspace) &&
1451 (reconstruct_image->colorspace == CMYKColorspace))
1453 distance=QuantumScale*fabs(Sa*(double) GetPixelIndex(indexes+x)-Da*
1454 (double) GetPixelIndex(reconstruct_indexes+x));
1455 if (distance > channel_distortion[BlackChannel])
1456 channel_distortion[BlackChannel]=distance;
1457 if (distance > channel_distortion[CompositeChannels])
1458 channel_distortion[CompositeChannels]=distance;
1463#if defined(MAGICKCORE_OPENMP_SUPPORT)
1464 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
1466 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1467 if (channel_distortion[i] > distortion[i])
1468 distortion[i]=channel_distortion[i];
1470 reconstruct_view=DestroyCacheView(reconstruct_view);
1471 image_view=DestroyCacheView(image_view);
1475static MagickBooleanType GetPeakSignalToNoiseRatio(
const Image *image,
1476 const Image *reconstruct_image,
const ChannelType channel,
1477 double *distortion,ExceptionInfo *exception)
1482 status=GetMeanSquaredDistortion(image,reconstruct_image,channel,distortion,
1484 if ((channel & RedChannel) != 0)
1485 distortion[RedChannel]=10.0*MagickSafeLog10(MagickSafeReciprocal(
1486 distortion[RedChannel]))/MagickPSNRDistortion;
1487 if ((channel & GreenChannel) != 0)
1488 distortion[GreenChannel]=10.0*MagickSafeLog10(MagickSafeReciprocal(
1489 distortion[GreenChannel]))/MagickPSNRDistortion;
1490 if ((channel & BlueChannel) != 0)
1491 distortion[BlueChannel]=10.0*MagickSafeLog10(MagickSafeReciprocal(
1492 distortion[BlueChannel]))/MagickPSNRDistortion;
1493 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
1494 distortion[OpacityChannel]=10.0*MagickSafeLog10(MagickSafeReciprocal(
1495 distortion[OpacityChannel]))/MagickPSNRDistortion;
1496 if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
1497 distortion[BlackChannel]=10.0*MagickSafeLog10(MagickSafeReciprocal(
1498 distortion[BlackChannel]))/MagickPSNRDistortion;
1499 distortion[CompositeChannels]=10.0*MagickSafeLog10(MagickSafeReciprocal(
1500 distortion[CompositeChannels]))/MagickPSNRDistortion;
1504static MagickBooleanType GetPerceptualHashDistortion(
const Image *image,
1505 const Image *reconstruct_image,
const ChannelType channel,
double *distortion,
1506 ExceptionInfo *exception)
1508#define PHASHNormalizationFactor 389.373723242
1510 ChannelPerceptualHash
1523 image_phash=GetImageChannelPerceptualHash(image,exception);
1524 if (image_phash == (ChannelPerceptualHash *) NULL)
1525 return(MagickFalse);
1526 reconstruct_phash=GetImageChannelPerceptualHash(reconstruct_image,exception);
1527 if (reconstruct_phash == (ChannelPerceptualHash *) NULL)
1529 image_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(image_phash);
1530 return(MagickFalse);
1532 for (i=0; i < MaximumNumberOfImageMoments; i++)
1537 if ((channel & RedChannel) != 0)
1539 difference=reconstruct_phash[RedChannel].P[i]-
1540 image_phash[RedChannel].P[i];
1541 if (IsNaN(difference) != 0)
1543 distortion[RedChannel]+=difference*difference;
1544 distortion[CompositeChannels]+=QuantumScale*difference*difference;
1546 if ((channel & GreenChannel) != 0)
1548 difference=reconstruct_phash[GreenChannel].P[i]-
1549 image_phash[GreenChannel].P[i];
1550 if (IsNaN(difference) != 0)
1552 distortion[GreenChannel]+=difference*difference;
1553 distortion[CompositeChannels]+=QuantumScale*difference*difference;
1555 if ((channel & BlueChannel) != 0)
1557 difference=reconstruct_phash[BlueChannel].P[i]-
1558 image_phash[BlueChannel].P[i];
1559 if (IsNaN(difference) != 0)
1561 distortion[BlueChannel]+=difference*difference;
1562 distortion[CompositeChannels]+=QuantumScale*difference*difference;
1564 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse) &&
1565 (reconstruct_image->matte != MagickFalse))
1567 difference=reconstruct_phash[OpacityChannel].P[i]-
1568 image_phash[OpacityChannel].P[i];
1569 if (IsNaN(difference) != 0)
1571 distortion[OpacityChannel]+=difference*difference;
1572 distortion[CompositeChannels]+=QuantumScale*difference*difference;
1574 if (((channel & IndexChannel) != 0) &&
1575 (image->colorspace == CMYKColorspace) &&
1576 (reconstruct_image->colorspace == CMYKColorspace))
1578 difference=reconstruct_phash[IndexChannel].P[i]-
1579 image_phash[IndexChannel].P[i];
1580 if (IsNaN(difference) != 0)
1582 distortion[IndexChannel]+=difference*difference;
1583 distortion[CompositeChannels]+=QuantumScale*difference*difference;
1589 for (i=0; i < MaximumNumberOfImageMoments; i++)
1594 if ((channel & RedChannel) != 0)
1596 difference=reconstruct_phash[RedChannel].Q[i]-
1597 image_phash[RedChannel].Q[i];
1598 if (IsNaN(difference) != 0)
1600 distortion[RedChannel]+=difference*difference;
1601 distortion[CompositeChannels]+=difference*difference;
1603 if ((channel & GreenChannel) != 0)
1605 difference=reconstruct_phash[GreenChannel].Q[i]-
1606 image_phash[GreenChannel].Q[i];
1607 if (IsNaN(difference) != 0)
1609 distortion[GreenChannel]+=difference*difference;
1610 distortion[CompositeChannels]+=difference*difference;
1612 if ((channel & BlueChannel) != 0)
1614 difference=reconstruct_phash[BlueChannel].Q[i]-
1615 image_phash[BlueChannel].Q[i];
1616 distortion[BlueChannel]+=difference*difference;
1617 distortion[CompositeChannels]+=difference*difference;
1619 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse) &&
1620 (reconstruct_image->matte != MagickFalse))
1622 difference=reconstruct_phash[OpacityChannel].Q[i]-
1623 image_phash[OpacityChannel].Q[i];
1624 if (IsNaN(difference) != 0)
1626 distortion[OpacityChannel]+=difference*difference;
1627 distortion[CompositeChannels]+=difference*difference;
1629 if (((channel & IndexChannel) != 0) &&
1630 (image->colorspace == CMYKColorspace) &&
1631 (reconstruct_image->colorspace == CMYKColorspace))
1633 difference=reconstruct_phash[IndexChannel].Q[i]-
1634 image_phash[IndexChannel].Q[i];
1635 if (IsNaN(difference) != 0)
1637 distortion[IndexChannel]+=difference*difference;
1638 distortion[CompositeChannels]+=difference*difference;
1641 distortion[CompositeChannels]/=(double) GetNumberChannels(image,channel);
1642 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1643 distortion[i]/=PHASHNormalizationFactor;
1647 reconstruct_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(
1649 image_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(image_phash);
1653static MagickBooleanType GetRootMeanSquaredDistortion(
const Image *image,
1654 const Image *reconstruct_image,
const ChannelType channel,
double *distortion,
1655 ExceptionInfo *exception)
1657#define RMSESquareRoot(x) sqrt((x) < 0.0 ? 0.0 : (x))
1662 status=GetMeanSquaredDistortion(image,reconstruct_image,channel,distortion,
1664 if ((channel & RedChannel) != 0)
1665 distortion[RedChannel]=RMSESquareRoot(distortion[RedChannel]);
1666 if ((channel & GreenChannel) != 0)
1667 distortion[GreenChannel]=RMSESquareRoot(distortion[GreenChannel]);
1668 if ((channel & BlueChannel) != 0)
1669 distortion[BlueChannel]=RMSESquareRoot(distortion[BlueChannel]);
1670 if (((channel & OpacityChannel) != 0) &&
1671 (image->matte != MagickFalse))
1672 distortion[OpacityChannel]=RMSESquareRoot(distortion[OpacityChannel]);
1673 if (((channel & IndexChannel) != 0) &&
1674 (image->colorspace == CMYKColorspace))
1675 distortion[BlackChannel]=RMSESquareRoot(distortion[BlackChannel]);
1676 distortion[CompositeChannels]=RMSESquareRoot(distortion[CompositeChannels]);
1680MagickExport MagickBooleanType GetImageChannelDistortion(Image *image,
1681 const Image *reconstruct_image,
const ChannelType channel,
1682 const MetricType metric,
double *distortion,ExceptionInfo *exception)
1685 *channel_distortion;
1696 assert(image != (Image *) NULL);
1697 assert(image->signature == MagickCoreSignature);
1698 assert(reconstruct_image != (
const Image *) NULL);
1699 assert(reconstruct_image->signature == MagickCoreSignature);
1700 assert(distortion != (
double *) NULL);
1701 if (IsEventLogging() != MagickFalse)
1702 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
1704 if (metric != PerceptualHashErrorMetric)
1705 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1706 ThrowBinaryException(ImageError,
"ImageMorphologyDiffers",image->filename);
1710 length=CompositeChannels+1UL;
1711 channel_distortion=(
double *) AcquireQuantumMemory(length,
1712 sizeof(*channel_distortion));
1713 if (channel_distortion == (
double *) NULL)
1714 ThrowFatalException(ResourceLimitFatalError,
"MemoryAllocationFailed");
1715 (void) memset(channel_distortion,0,length*
sizeof(*channel_distortion));
1718 case AbsoluteErrorMetric:
1720 status=GetAbsoluteDistortion(image,reconstruct_image,channel,
1721 channel_distortion,exception);
1724 case FuzzErrorMetric:
1726 status=GetFuzzDistortion(image,reconstruct_image,channel,
1727 channel_distortion,exception);
1730 case MeanAbsoluteErrorMetric:
1732 status=GetMeanAbsoluteDistortion(image,reconstruct_image,channel,
1733 channel_distortion,exception);
1736 case MeanErrorPerPixelMetric:
1738 status=GetMeanErrorPerPixel(image,reconstruct_image,channel,
1739 channel_distortion,exception);
1742 case MeanSquaredErrorMetric:
1744 status=GetMeanSquaredDistortion(image,reconstruct_image,channel,
1745 channel_distortion,exception);
1748 case NormalizedCrossCorrelationErrorMetric:
1750 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1751 channel,channel_distortion,exception);
1752 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1753 channel_distortion[i]=1.0-channel_distortion[i];
1756 case PeakAbsoluteErrorMetric:
1758 status=GetPeakAbsoluteDistortion(image,reconstruct_image,channel,
1759 channel_distortion,exception);
1762 case PeakSignalToNoiseRatioMetric:
1764 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,channel,
1765 channel_distortion,exception);
1768 case PerceptualHashErrorMetric:
1770 status=GetPerceptualHashDistortion(image,reconstruct_image,channel,
1771 channel_distortion,exception);
1774 case RootMeanSquaredErrorMetric:
1775 case UndefinedErrorMetric:
1778 status=GetRootMeanSquaredDistortion(image,reconstruct_image,channel,
1779 channel_distortion,exception);
1783 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1784 channel_distortion[i]=MagickMin(MagickMax(channel_distortion[i],0.0),1.0);
1785 *distortion=channel_distortion[CompositeChannels];
1786 channel_distortion=(
double *) RelinquishMagickMemory(channel_distortion);
1787 (void) FormatImageProperty(image,
"distortion",
"%.*g",GetMagickPrecision(),
1824MagickExport
double *GetImageChannelDistortions(Image *image,
1825 const Image *reconstruct_image,
const MetricType metric,
1826 ExceptionInfo *exception)
1829 *channel_distortion;
1840 assert(image != (Image *) NULL);
1841 assert(image->signature == MagickCoreSignature);
1842 assert(reconstruct_image != (
const Image *) NULL);
1843 assert(reconstruct_image->signature == MagickCoreSignature);
1844 if (IsEventLogging() != MagickFalse)
1845 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
1846 if (metric != PerceptualHashErrorMetric)
1847 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1849 (void) ThrowMagickException(&image->exception,GetMagickModule(),
1850 ImageError,
"ImageMorphologyDiffers",
"`%s'",image->filename);
1851 return((
double *) NULL);
1856 length=CompositeChannels+1UL;
1857 channel_distortion=(
double *) AcquireQuantumMemory(length,
1858 sizeof(*channel_distortion));
1859 if (channel_distortion == (
double *) NULL)
1860 ThrowFatalException(ResourceLimitFatalError,
"MemoryAllocationFailed");
1861 (void) memset(channel_distortion,0,length*
1862 sizeof(*channel_distortion));
1866 case AbsoluteErrorMetric:
1868 status=GetAbsoluteDistortion(image,reconstruct_image,CompositeChannels,
1869 channel_distortion,exception);
1872 case FuzzErrorMetric:
1874 status=GetFuzzDistortion(image,reconstruct_image,CompositeChannels,
1875 channel_distortion,exception);
1878 case MeanAbsoluteErrorMetric:
1880 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1881 CompositeChannels,channel_distortion,exception);
1884 case MeanErrorPerPixelMetric:
1886 status=GetMeanErrorPerPixel(image,reconstruct_image,CompositeChannels,
1887 channel_distortion,exception);
1890 case MeanSquaredErrorMetric:
1892 status=GetMeanSquaredDistortion(image,reconstruct_image,CompositeChannels,
1893 channel_distortion,exception);
1896 case NormalizedCrossCorrelationErrorMetric:
1898 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1899 CompositeChannels,channel_distortion,exception);
1900 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1901 channel_distortion[i]=1.0-channel_distortion[i];
1904 case PeakAbsoluteErrorMetric:
1906 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1907 CompositeChannels,channel_distortion,exception);
1910 case PeakSignalToNoiseRatioMetric:
1912 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1913 CompositeChannels,channel_distortion,exception);
1916 case PerceptualHashErrorMetric:
1918 status=GetPerceptualHashDistortion(image,reconstruct_image,
1919 CompositeChannels,channel_distortion,exception);
1922 case RootMeanSquaredErrorMetric:
1923 case UndefinedErrorMetric:
1926 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1927 CompositeChannels,channel_distortion,exception);
1931 if (status == MagickFalse)
1933 channel_distortion=(
double *) RelinquishMagickMemory(channel_distortion);
1934 return((
double *) NULL);
1936 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1937 channel_distortion[i]=MagickMin(MagickMax(channel_distortion[i],0.0),1.0);
1938 return(channel_distortion);
1988MagickExport MagickBooleanType IsImagesEqual(Image *image,
1989 const Image *reconstruct_image)
2006 mean_error_per_pixel;
2015 assert(image != (Image *) NULL);
2016 assert(image->signature == MagickCoreSignature);
2017 assert(reconstruct_image != (
const Image *) NULL);
2018 assert(reconstruct_image->signature == MagickCoreSignature);
2019 exception=(&image->exception);
2020 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
2021 ThrowBinaryException(ImageError,
"ImageMorphologyDiffers",image->filename);
2024 mean_error_per_pixel=0.0;
2026 SetImageDistortionBounds(image,reconstruct_image,&columns,&rows);
2027 image_view=AcquireVirtualCacheView(image,exception);
2028 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
2029 for (y=0; y < (ssize_t) rows; y++)
2032 *magick_restrict indexes,
2033 *magick_restrict reconstruct_indexes;
2042 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
2043 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
2044 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
2046 indexes=GetCacheViewVirtualIndexQueue(image_view);
2047 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
2048 for (x=0; x < (ssize_t) columns; x++)
2053 distance=fabs((
double) GetPixelRed(p)-(
double) GetPixelRed(q));
2054 mean_error_per_pixel+=distance;
2055 mean_error+=distance*distance;
2056 if (distance > maximum_error)
2057 maximum_error=distance;
2059 distance=fabs((
double) GetPixelGreen(p)-(
double) GetPixelGreen(q));
2060 mean_error_per_pixel+=distance;
2061 mean_error+=distance*distance;
2062 if (distance > maximum_error)
2063 maximum_error=distance;
2065 distance=fabs((
double) GetPixelBlue(p)-(
double) GetPixelBlue(q));
2066 mean_error_per_pixel+=distance;
2067 mean_error+=distance*distance;
2068 if (distance > maximum_error)
2069 maximum_error=distance;
2071 if (image->matte != MagickFalse)
2073 distance=fabs((
double) GetPixelOpacity(p)-(
double)
2074 GetPixelOpacity(q));
2075 mean_error_per_pixel+=distance;
2076 mean_error+=distance*distance;
2077 if (distance > maximum_error)
2078 maximum_error=distance;
2081 if ((image->colorspace == CMYKColorspace) &&
2082 (reconstruct_image->colorspace == CMYKColorspace))
2084 distance=fabs((
double) GetPixelIndex(indexes+x)-(
double)
2085 GetPixelIndex(reconstruct_indexes+x));
2086 mean_error_per_pixel+=distance;
2087 mean_error+=distance*distance;
2088 if (distance > maximum_error)
2089 maximum_error=distance;
2096 reconstruct_view=DestroyCacheView(reconstruct_view);
2097 image_view=DestroyCacheView(image_view);
2098 gamma=MagickSafeReciprocal(area);
2099 image->error.mean_error_per_pixel=gamma*mean_error_per_pixel;
2100 image->error.normalized_mean_error=gamma*QuantumScale*QuantumScale*mean_error;
2101 image->error.normalized_maximum_error=QuantumScale*maximum_error;
2102 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
2141static double GetSimilarityMetric(
const Image *image,
const Image *reference,
2142 const MetricType metric,
const ssize_t x_offset,
const ssize_t y_offset,
2143 ExceptionInfo *exception)
2157 SetGeometry(reference,&geometry);
2158 geometry.x=x_offset;
2159 geometry.y=y_offset;
2160 similarity_image=CropImage(image,&geometry,exception);
2161 if (similarity_image == (Image *) NULL)
2164 status=GetImageDistortion(similarity_image,reference,metric,&distortion,
2167 similarity_image=DestroyImage(similarity_image);
2171MagickExport Image *SimilarityImage(Image *image,
const Image *reference,
2172 RectangleInfo *offset,
double *similarity_metric,ExceptionInfo *exception)
2177 similarity_image=SimilarityMetricImage(image,reference,
2178 RootMeanSquaredErrorMetric,offset,similarity_metric,exception);
2179 return(similarity_image);
2182MagickExport Image *SimilarityMetricImage(Image *image,
const Image *reconstruct,
2183 const MetricType metric,RectangleInfo *offset,
double *similarity_metric,
2184 ExceptionInfo *exception)
2186#define SimilarityImageTag "Similarity/Image"
2205 similarity_threshold;
2208 *similarity_image = (Image *) NULL;
2217 similarity_info = { 0 };
2222 assert(image != (
const Image *) NULL);
2223 assert(image->signature == MagickCoreSignature);
2224 assert(exception != (ExceptionInfo *) NULL);
2225 assert(exception->signature == MagickCoreSignature);
2226 assert(offset != (RectangleInfo *) NULL);
2227 if (IsEventLogging() != MagickFalse)
2228 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
2229 SetGeometry(reconstruct,offset);
2230 *similarity_metric=0.0;
2233 if (ValidateImageMorphology(image,reconstruct) == MagickFalse)
2234 ThrowImageException(ImageError,
"ImageMorphologyDiffers");
2235 if ((image->columns < reconstruct->columns) ||
2236 (image->rows < reconstruct->rows))
2238 (void) ThrowMagickException(&image->exception,GetMagickModule(),
2239 OptionWarning,
"GeometryDoesNotContainImage",
"`%s'",image->filename);
2240 return((Image *) NULL);
2242 similarity_image=CloneImage(image,image->columns,image->rows,MagickTrue,
2244 if (similarity_image == (Image *) NULL)
2245 return((Image *) NULL);
2246 similarity_image->depth=32;
2247 similarity_image->colorspace=GRAYColorspace;
2248 similarity_image->matte=MagickFalse;
2249 status=SetImageStorageClass(similarity_image,DirectClass);
2250 if (status == MagickFalse)
2252 InheritException(exception,&similarity_image->exception);
2253 return(DestroyImage(similarity_image));
2258 similarity_threshold=DefaultSimilarityThreshold;
2259 artifact=GetImageArtifact(image,
"compare:similarity-threshold");
2260 if (artifact != (
const char *) NULL)
2261 similarity_threshold=StringToDouble(artifact,(
char **) NULL);
2263 similarity_info.similarity=GetSimilarityMetric(image,reconstruct,metric,
2264 similarity_info.x,similarity_info.y,exception);
2266 similarity_view=AcquireVirtualCacheView(similarity_image,exception);
2267#if defined(MAGICKCORE_OPENMP_SUPPORT)
2268 #pragma omp parallel for schedule(static) shared(status,similarity_info) \
2269 magick_number_threads(image,reconstruct,similarity_image->rows << 2,1)
2271 for (y=0; y < (ssize_t) similarity_image->rows; y++)
2277 threshold_trigger = MagickFalse;
2283 channel_info = similarity_info;
2288 if (status == MagickFalse)
2290 if (threshold_trigger != MagickFalse)
2292 q=QueueCacheViewAuthenticPixels(similarity_view,0,y,
2293 similarity_image->columns,1,exception);
2294 if (q == (PixelPacket *) NULL)
2299 for (x=0; x < (ssize_t) similarity_image->columns; x++)
2302 update = MagickFalse;
2304 similarity=GetSimilarityMetric(image,reconstruct,metric,x,y,exception);
2307 case PeakSignalToNoiseRatioMetric:
2309 if (similarity > channel_info.similarity)
2315 if (similarity < channel_info.similarity)
2320 if (update != MagickFalse)
2322 channel_info.similarity=similarity;
2328 case PeakSignalToNoiseRatioMetric:
2330 SetPixelRed(q,ClampToQuantum((
double) QuantumRange*similarity));
2335 SetPixelRed(q,ClampToQuantum((
double) QuantumRange*(1.0-similarity)));
2339 SetPixelGreen(q,GetPixelRed(q));
2340 SetPixelBlue(q,GetPixelRed(q));
2343#if defined(MAGICKCORE_OPENMP_SUPPORT)
2344 #pragma omp critical (MagickCore_SimilarityMetricImage)
2348 case PeakSignalToNoiseRatioMetric:
2350 if (similarity_threshold != DefaultSimilarityThreshold)
2351 if (channel_info.similarity >= similarity_threshold)
2352 threshold_trigger=MagickTrue;
2353 if (channel_info.similarity >= similarity_info.similarity)
2354 similarity_info=channel_info;
2359 if (similarity_threshold != DefaultSimilarityThreshold)
2360 if (channel_info.similarity < similarity_threshold)
2361 threshold_trigger=MagickTrue;
2362 if (channel_info.similarity < similarity_info.similarity)
2363 similarity_info=channel_info;
2367 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
2369 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2375 proceed=SetImageProgress(image,SimilarityImageTag,progress,image->rows);
2376 if (proceed == MagickFalse)
2380 similarity_view=DestroyCacheView(similarity_view);
2383 case NormalizedCrossCorrelationErrorMetric:
2385 similarity_info.similarity=1.0-similarity_info.similarity;
2391 if (status == MagickFalse)
2392 similarity_image=DestroyImage(similarity_image);
2393 *similarity_metric=similarity_info.similarity;
2394 offset->x=similarity_info.x;
2395 offset->y=similarity_info.y;
2396 return(similarity_image);