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)
464 fuzz=GetFuzzyColorDistance(image,reconstruct_image);
465 SetImageDistortionBounds(image,reconstruct_image,&columns,&rows);
466 image_view=AcquireVirtualCacheView(image,exception);
467 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
468#if defined(MAGICKCORE_OPENMP_SUPPORT)
469 #pragma omp parallel for schedule(static) shared(status) \
470 magick_number_threads(image,image,rows,1)
472 for (y=0; y < (ssize_t) rows; y++)
475 *magick_restrict indexes,
476 *magick_restrict reconstruct_indexes;
483 channel_distortion[CompositeChannels+1];
489 if (status == MagickFalse)
491 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
492 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
493 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
498 indexes=GetCacheViewVirtualIndexQueue(image_view);
499 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
500 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
501 for (x=0; x < (ssize_t) columns; x++)
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 delta=Sa*(double) GetPixelRed(p)-Da*(double)
519 if ((delta*delta) > fuzz)
521 channel_distortion[RedChannel]++;
525 if ((channel & GreenChannel) != 0)
527 delta=Sa*(double) GetPixelGreen(p)-Da*(double)
529 if ((delta*delta) > fuzz)
531 channel_distortion[GreenChannel]++;
535 if ((channel & BlueChannel) != 0)
537 delta=Sa*(double) GetPixelBlue(p)-Da*(double)
539 if ((delta*delta) > fuzz)
541 channel_distortion[BlueChannel]++;
545 if (((channel & OpacityChannel) != 0) &&
546 (image->matte != MagickFalse))
548 delta=(double) GetPixelOpacity(p)-(double)
550 if ((delta*delta) > fuzz)
552 channel_distortion[OpacityChannel]++;
556 if (((channel & IndexChannel) != 0) &&
557 (image->colorspace == CMYKColorspace))
559 delta=Sa*(double) indexes[x]-Da*(
double)
560 reconstruct_indexes[x];
561 if ((delta*delta) > fuzz)
563 channel_distortion[IndexChannel]++;
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);
580 area=PerceptibleReciprocal((
double) columns*rows);
581 for (j=0; j <= CompositeChannels; j++)
586static MagickBooleanType GetFuzzDistortion(
const Image *image,
587 const Image *reconstruct_image,
const ChannelType channel,
588 double *distortion,ExceptionInfo *exception)
608 SetImageDistortionBounds(image,reconstruct_image,&columns,&rows);
609 image_view=AcquireVirtualCacheView(image,exception);
610 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
611#if defined(MAGICKCORE_OPENMP_SUPPORT)
612 #pragma omp parallel for schedule(static) shared(status) \
613 magick_number_threads(image,image,rows,1)
615 for (y=0; y < (ssize_t) rows; y++)
618 channel_distortion[CompositeChannels+1];
621 *magick_restrict indexes,
622 *magick_restrict reconstruct_indexes;
632 if (status == MagickFalse)
634 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
635 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
636 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
641 indexes=GetCacheViewVirtualIndexQueue(image_view);
642 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
643 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
644 for (x=0; x < (ssize_t) columns; x++)
651 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
652 ((double) QuantumRange-(double) OpaqueOpacity));
653 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
654 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
656 if ((channel & RedChannel) != 0)
658 distance=QuantumScale*(Sa*(double) GetPixelRed(p)-Da*(double)
660 channel_distortion[RedChannel]+=distance*distance;
661 channel_distortion[CompositeChannels]+=distance*distance;
663 if ((channel & GreenChannel) != 0)
665 distance=QuantumScale*(Sa*(double) GetPixelGreen(p)-Da*(double)
667 channel_distortion[GreenChannel]+=distance*distance;
668 channel_distortion[CompositeChannels]+=distance*distance;
670 if ((channel & BlueChannel) != 0)
672 distance=QuantumScale*(Sa*(double) GetPixelBlue(p)-Da*(double)
674 channel_distortion[BlueChannel]+=distance*distance;
675 channel_distortion[CompositeChannels]+=distance*distance;
677 if (((channel & OpacityChannel) != 0) && ((image->matte != MagickFalse) ||
678 (reconstruct_image->matte != MagickFalse)))
680 distance=QuantumScale*((image->matte != MagickFalse ? (double)
681 GetPixelOpacity(p) : (double) OpaqueOpacity)-
682 (reconstruct_image->matte != MagickFalse ?
683 (double) GetPixelOpacity(q): (double) OpaqueOpacity));
684 channel_distortion[OpacityChannel]+=distance*distance;
685 channel_distortion[CompositeChannels]+=distance*distance;
687 if (((channel & IndexChannel) != 0) &&
688 (image->colorspace == CMYKColorspace) &&
689 (reconstruct_image->colorspace == CMYKColorspace))
691 distance=QuantumScale*(Sa*(double) GetPixelIndex(indexes+x)-
692 Da*(double) GetPixelIndex(reconstruct_indexes+x));
693 channel_distortion[BlackChannel]+=distance*distance;
694 channel_distortion[CompositeChannels]+=distance*distance;
699#if defined(MAGICKCORE_OPENMP_SUPPORT)
700 #pragma omp critical (MagickCore_GetFuzzDistortion)
702 for (i=0; i <= (ssize_t) CompositeChannels; i++)
703 distortion[i]+=channel_distortion[i];
705 reconstruct_view=DestroyCacheView(reconstruct_view);
706 image_view=DestroyCacheView(image_view);
707 distortion[CompositeChannels]/=(double) GetNumberChannels(image,channel);
708 for (i=0; i <= (ssize_t) CompositeChannels; i++)
709 distortion[i]/=((
double) columns*rows);
710 distortion[CompositeChannels]=sqrt(distortion[CompositeChannels]);
714static MagickBooleanType GetMeanAbsoluteDistortion(
const Image *image,
715 const Image *reconstruct_image,
const ChannelType channel,
716 double *distortion,ExceptionInfo *exception)
734 SetImageDistortionBounds(image,reconstruct_image,&columns,&rows);
735 image_view=AcquireVirtualCacheView(image,exception);
736 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
737#if defined(MAGICKCORE_OPENMP_SUPPORT)
738 #pragma omp parallel for schedule(static) shared(status) \
739 magick_number_threads(image,image,rows,1)
741 for (y=0; y < (ssize_t) rows; y++)
744 channel_distortion[CompositeChannels+1];
747 *magick_restrict indexes,
748 *magick_restrict reconstruct_indexes;
758 if (status == MagickFalse)
760 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
761 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
762 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
767 indexes=GetCacheViewVirtualIndexQueue(image_view);
768 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
769 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
770 for (x=0; x < (ssize_t) columns; x++)
777 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
778 ((double) QuantumRange-(double) OpaqueOpacity));
779 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
780 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
782 if ((channel & RedChannel) != 0)
784 distance=QuantumScale*fabs(Sa*(
double) GetPixelRed(p)-Da*
785 (
double) GetPixelRed(q));
786 channel_distortion[RedChannel]+=distance;
787 channel_distortion[CompositeChannels]+=distance;
789 if ((channel & GreenChannel) != 0)
791 distance=QuantumScale*fabs(Sa*(
double) GetPixelGreen(p)-Da*
792 (
double) GetPixelGreen(q));
793 channel_distortion[GreenChannel]+=distance;
794 channel_distortion[CompositeChannels]+=distance;
796 if ((channel & BlueChannel) != 0)
798 distance=QuantumScale*fabs(Sa*(
double) GetPixelBlue(p)-Da*
799 (
double) GetPixelBlue(q));
800 channel_distortion[BlueChannel]+=distance;
801 channel_distortion[CompositeChannels]+=distance;
803 if (((channel & OpacityChannel) != 0) &&
804 (image->matte != MagickFalse))
806 distance=QuantumScale*fabs((double) GetPixelOpacity(p)-(double)
808 channel_distortion[OpacityChannel]+=distance;
809 channel_distortion[CompositeChannels]+=distance;
811 if (((channel & IndexChannel) != 0) &&
812 (image->colorspace == CMYKColorspace))
814 distance=QuantumScale*fabs(Sa*(double) GetPixelIndex(indexes+x)-Da*
815 (double) GetPixelIndex(reconstruct_indexes+x));
816 channel_distortion[BlackChannel]+=distance;
817 channel_distortion[CompositeChannels]+=distance;
822#if defined(MAGICKCORE_OPENMP_SUPPORT)
823 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
825 for (i=0; i <= (ssize_t) CompositeChannels; i++)
826 distortion[i]+=channel_distortion[i];
828 reconstruct_view=DestroyCacheView(reconstruct_view);
829 image_view=DestroyCacheView(image_view);
830 for (i=0; i <= (ssize_t) CompositeChannels; i++)
831 distortion[i]/=((
double) columns*rows);
832 distortion[CompositeChannels]/=(double) GetNumberChannels(image,channel);
836static MagickBooleanType GetMeanErrorPerPixel(Image *image,
837 const Image *reconstruct_image,
const ChannelType channel,
double *distortion,
838 ExceptionInfo *exception)
845 maximum_error = MagickMinimumValue,
860 SetImageDistortionBounds(image,reconstruct_image,&columns,&rows);
861 image_view=AcquireVirtualCacheView(image,exception);
862 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
863#if defined(MAGICKCORE_OPENMP_SUPPORT)
864 #pragma omp parallel for schedule(static) shared(status) \
865 magick_number_threads(image,image,rows,1)
867 for (y=0; y < (ssize_t) rows; y++)
870 channel_distortion[CompositeChannels+1],
871 local_maximum = MinimumValue,
872 local_mean_error = 0.0;
875 *magick_restrict indexes,
876 *magick_restrict reconstruct_indexes;
886 if (status == MagickFalse)
888 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
889 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
890 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
895 indexes=GetCacheViewVirtualIndexQueue(image_view);
896 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
897 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
898 for (x=0; x < (ssize_t) columns; x++)
905 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
906 ((double) QuantumRange-(double) OpaqueOpacity));
907 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
908 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
910 if ((channel & RedChannel) != 0)
912 distance=QuantumScale*fabs(Sa*(
double) GetPixelRed(p)-Da*
913 (
double) GetPixelRed(q));
914 channel_distortion[RedChannel]+=distance;
915 channel_distortion[CompositeChannels]+=distance;
916 local_mean_error+=distance*distance;
917 if (distance > local_maximum)
918 local_maximum=distance;
920 if ((channel & GreenChannel) != 0)
922 distance=QuantumScale*fabs(Sa*(
double) GetPixelGreen(p)-Da*
923 (
double) GetPixelGreen(q));
924 channel_distortion[GreenChannel]+=distance;
925 channel_distortion[CompositeChannels]+=distance;
926 local_mean_error+=distance*distance;
927 if (distance > local_maximum)
928 local_maximum=distance;
930 if ((channel & BlueChannel) != 0)
932 distance=QuantumScale*fabs(Sa*(
double) GetPixelBlue(p)-Da*
933 (
double) GetPixelBlue(q));
934 channel_distortion[BlueChannel]+=distance;
935 channel_distortion[CompositeChannels]+=distance;
936 local_mean_error+=distance*distance;
937 if (distance > local_maximum)
938 local_maximum=distance;
940 if (((channel & OpacityChannel) != 0) &&
941 (image->matte != MagickFalse))
943 distance=QuantumScale*fabs((double) GetPixelOpacity(p)-(double)
945 channel_distortion[OpacityChannel]+=distance;
946 channel_distortion[CompositeChannels]+=distance;
947 local_mean_error+=distance*distance;
948 if (distance > local_maximum)
949 local_maximum=distance;
951 if (((channel & IndexChannel) != 0) &&
952 (image->colorspace == CMYKColorspace))
954 distance=QuantumScale*fabs(Sa*(double) GetPixelIndex(indexes+x)-Da*
955 (double) GetPixelIndex(reconstruct_indexes+x));
956 channel_distortion[BlackChannel]+=distance;
957 channel_distortion[CompositeChannels]+=distance;
958 local_mean_error+=distance*distance;
959 if (distance > local_maximum)
960 local_maximum=distance;
965#if defined(MAGICKCORE_OPENMP_SUPPORT)
966 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
969 for (i=0; i <= (ssize_t) CompositeChannels; i++)
970 distortion[i]+=channel_distortion[i];
971 mean_error+=local_mean_error;
972 if (local_maximum > maximum_error)
973 maximum_error=local_maximum;
976 reconstruct_view=DestroyCacheView(reconstruct_view);
977 image_view=DestroyCacheView(image_view);
978 for (i=0; i <= (ssize_t) CompositeChannels; i++)
979 distortion[i]/=((
double) columns*rows);
980 distortion[CompositeChannels]/=(double) GetNumberChannels(image,channel);
981 image->error.mean_error_per_pixel=QuantumRange*distortion[CompositeChannels];
982 image->error.normalized_mean_error=mean_error;
983 image->error.normalized_maximum_error=maximum_error;
987static MagickBooleanType GetMeanSquaredDistortion(
const Image *image,
988 const Image *reconstruct_image,
const ChannelType channel,
989 double *distortion,ExceptionInfo *exception)
1010 SetImageDistortionBounds(image,reconstruct_image,&columns,&rows);
1011 image_view=AcquireVirtualCacheView(image,exception);
1012 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1013#if defined(MAGICKCORE_OPENMP_SUPPORT)
1014 #pragma omp parallel for schedule(static) shared(status) \
1015 magick_number_threads(image,image,rows,1)
1017 for (y=0; y < (ssize_t) rows; y++)
1020 channel_distortion[CompositeChannels+1];
1023 *magick_restrict indexes,
1024 *magick_restrict reconstruct_indexes;
1034 if (status == MagickFalse)
1036 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1037 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1038 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
1043 indexes=GetCacheViewVirtualIndexQueue(image_view);
1044 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1045 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
1046 for (x=0; x < (ssize_t) columns; x++)
1053 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
1054 ((double) QuantumRange-(double) OpaqueOpacity));
1055 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1056 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
1058 if ((channel & RedChannel) != 0)
1060 distance=QuantumScale*(Sa*(double) GetPixelRed(p)-Da*(double)
1062 channel_distortion[RedChannel]+=distance*distance;
1063 channel_distortion[CompositeChannels]+=distance*distance;
1065 if ((channel & GreenChannel) != 0)
1067 distance=QuantumScale*(Sa*(double) GetPixelGreen(p)-Da*(double)
1069 channel_distortion[GreenChannel]+=distance*distance;
1070 channel_distortion[CompositeChannels]+=distance*distance;
1072 if ((channel & BlueChannel) != 0)
1074 distance=QuantumScale*(Sa*(double) GetPixelBlue(p)-Da*(double)
1076 channel_distortion[BlueChannel]+=distance*distance;
1077 channel_distortion[CompositeChannels]+=distance*distance;
1079 if (((channel & OpacityChannel) != 0) &&
1080 (image->matte != MagickFalse))
1082 distance=QuantumScale*((double) GetPixelOpacity(p)-(double)
1083 GetPixelOpacity(q));
1084 channel_distortion[OpacityChannel]+=distance*distance;
1085 channel_distortion[CompositeChannels]+=distance*distance;
1087 if (((channel & IndexChannel) != 0) &&
1088 (image->colorspace == CMYKColorspace) &&
1089 (reconstruct_image->colorspace == CMYKColorspace))
1091 distance=QuantumScale*(Sa*(double) GetPixelIndex(indexes+x)-Da*
1092 (double) GetPixelIndex(reconstruct_indexes+x));
1093 channel_distortion[BlackChannel]+=distance*distance;
1094 channel_distortion[CompositeChannels]+=distance*distance;
1099#if defined(MAGICKCORE_OPENMP_SUPPORT)
1100 #pragma omp critical (MagickCore_GetMeanSquaredError)
1102 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1103 distortion[i]+=channel_distortion[i];
1105 reconstruct_view=DestroyCacheView(reconstruct_view);
1106 image_view=DestroyCacheView(image_view);
1107 area=PerceptibleReciprocal((
double) columns*rows);
1108 distortion[CompositeChannels]/=(double) GetNumberChannels(image,channel);
1109 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1110 distortion[i]*=area;
1114static MagickBooleanType GetNormalizedCrossCorrelationDistortion(
1115 const Image *image,
const Image *reconstruct_image,
const ChannelType channel,
1116 double *distortion,ExceptionInfo *exception)
1118#define SimilarityImageTag "Similarity/Image"
1126 *reconstruct_statistics;
1129 alpha_variance[CompositeChannels+1] = { 0.0 },
1131 beta_variance[CompositeChannels+1] = { 0.0 };
1150 image_statistics=GetImageChannelStatistics(image,exception);
1151 reconstruct_statistics=GetImageChannelStatistics(reconstruct_image,exception);
1152 if ((image_statistics == (ChannelStatistics *) NULL) ||
1153 (reconstruct_statistics == (ChannelStatistics *) NULL))
1155 if (image_statistics != (ChannelStatistics *) NULL)
1156 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1158 if (reconstruct_statistics != (ChannelStatistics *) NULL)
1159 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1160 reconstruct_statistics);
1161 return(MagickFalse);
1163 (void) memset(distortion,0,(CompositeChannels+1)*
sizeof(*distortion));
1166 SetImageDistortionBounds(image,reconstruct_image,&columns,&rows);
1167 image_view=AcquireVirtualCacheView(image,exception);
1168 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1169#if defined(MAGICKCORE_OPENMP_SUPPORT)
1170 #pragma omp parallel for schedule(static) shared(status) \
1171 magick_number_threads(image,image,rows,1)
1173 for (y=0; y < (ssize_t) rows; y++)
1176 *magick_restrict indexes,
1177 *magick_restrict reconstruct_indexes;
1184 channel_alpha_variance[CompositeChannels+1] = { 0.0 },
1185 channel_beta_variance[CompositeChannels+1] = { 0.0 },
1186 channel_distortion[CompositeChannels+1] = { 0.0 };
1191 if (status == MagickFalse)
1193 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1194 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1195 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
1200 indexes=GetCacheViewVirtualIndexQueue(image_view);
1201 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1202 for (x=0; x < (ssize_t) columns; x++)
1210 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
1211 (double) QuantumRange);
1212 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1213 (double) GetPixelAlpha(q) : (double) QuantumRange);
1214 if ((channel & RedChannel) != 0)
1216 alpha=QuantumScale*(Sa*(double) GetPixelRed(p)-
1217 image_statistics[RedChannel].mean);
1218 beta=QuantumScale*(Da*(double) GetPixelRed(q)-
1219 reconstruct_statistics[RedChannel].mean);
1220 channel_distortion[RedChannel]+=alpha*beta;
1221 channel_alpha_variance[RedChannel]+=alpha*alpha;
1222 channel_beta_variance[RedChannel]+=beta*beta;
1224 if ((channel & GreenChannel) != 0)
1226 alpha=QuantumScale*(Sa*(double) GetPixelGreen(p)-
1227 image_statistics[GreenChannel].mean);
1228 beta=QuantumScale*(Da*(double) GetPixelGreen(q)-
1229 reconstruct_statistics[GreenChannel].mean);
1230 channel_distortion[GreenChannel]+=alpha*beta;
1231 channel_alpha_variance[GreenChannel]+=alpha*alpha;
1232 channel_beta_variance[GreenChannel]+=beta*beta;
1234 if ((channel & BlueChannel) != 0)
1236 alpha=QuantumScale*(Sa*(double) GetPixelBlue(p)-
1237 image_statistics[BlueChannel].mean);
1238 beta=QuantumScale*(Da*(double) GetPixelBlue(q)-
1239 reconstruct_statistics[BlueChannel].mean);
1240 channel_distortion[BlueChannel]+=alpha*beta;
1241 channel_alpha_variance[BlueChannel]+=alpha*alpha;
1242 channel_beta_variance[BlueChannel]+=beta*beta;
1244 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
1246 alpha=QuantumScale*((double) GetPixelAlpha(p)-
1247 image_statistics[AlphaChannel].mean);
1248 beta=QuantumScale*((double) GetPixelAlpha(q)-
1249 reconstruct_statistics[AlphaChannel].mean);
1250 channel_distortion[OpacityChannel]+=alpha*beta;
1251 channel_alpha_variance[OpacityChannel]+=alpha*alpha;
1252 channel_beta_variance[OpacityChannel]+=beta*beta;
1254 if (((channel & IndexChannel) != 0) &&
1255 (image->colorspace == CMYKColorspace) &&
1256 (reconstruct_image->colorspace == CMYKColorspace))
1258 alpha=QuantumScale*(Sa*(double) GetPixelIndex(indexes+x)-
1259 image_statistics[BlackChannel].mean);
1260 beta=QuantumScale*(Da*(double) GetPixelIndex(reconstruct_indexes+
1261 x)-reconstruct_statistics[BlackChannel].mean);
1262 channel_distortion[BlackChannel]+=alpha*beta;
1263 channel_alpha_variance[BlackChannel]+=alpha*alpha;
1264 channel_beta_variance[BlackChannel]+=beta*beta;
1269#if defined(MAGICKCORE_OPENMP_SUPPORT)
1270 #pragma omp critical (GetNormalizedCrossCorrelationDistortion)
1276 for (j=0; j < (ssize_t) CompositeChannels; j++)
1278 distortion[j]+=channel_distortion[j];
1279 alpha_variance[j]+=channel_alpha_variance[j];
1280 beta_variance[j]+=channel_beta_variance[j];
1283 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1288#if defined(MAGICKCORE_OPENMP_SUPPORT)
1292 proceed=SetImageProgress(image,SimilarityImageTag,progress,rows);
1293 if (proceed == MagickFalse)
1297 reconstruct_view=DestroyCacheView(reconstruct_view);
1298 image_view=DestroyCacheView(image_view);
1302 area=PerceptibleReciprocal((
double) columns*rows);
1303 for (i=0; i < (ssize_t) CompositeChannels; i++)
1305 distortion[i]*=area;
1306 alpha_variance[i]*=area;
1307 beta_variance[i]*=area;
1308 distortion[i]*=PerceptibleReciprocal(sqrt(alpha_variance[i]*
1310 distortion[i]=1.0-distortion[i];
1311 if (fabs(distortion[i]) < MagickEpsilon)
1313 distortion[CompositeChannels]+=distortion[i];
1315 distortion[CompositeChannels]/=(double) GetNumberChannels(image,channel);
1319 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1320 reconstruct_statistics);
1321 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1326static MagickBooleanType GetPeakAbsoluteDistortion(
const Image *image,
1327 const Image *reconstruct_image,
const ChannelType channel,
1328 double *distortion,ExceptionInfo *exception)
1345 (void) memset(distortion,0,(CompositeChannels+1)*
sizeof(*distortion));
1346 SetImageDistortionBounds(image,reconstruct_image,&columns,&rows);
1347 image_view=AcquireVirtualCacheView(image,exception);
1348 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1349#if defined(MAGICKCORE_OPENMP_SUPPORT)
1350 #pragma omp parallel for schedule(static) shared(status) \
1351 magick_number_threads(image,image,rows,1)
1353 for (y=0; y < (ssize_t) rows; y++)
1356 channel_distortion[CompositeChannels+1];
1359 *magick_restrict indexes,
1360 *magick_restrict reconstruct_indexes;
1370 if (status == MagickFalse)
1372 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1373 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1374 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
1379 indexes=GetCacheViewVirtualIndexQueue(image_view);
1380 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1381 (void) memset(channel_distortion,0,(CompositeChannels+1)*
1382 sizeof(*channel_distortion));
1383 for (x=0; x < (ssize_t) columns; x++)
1390 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
1391 ((double) QuantumRange-(double) OpaqueOpacity));
1392 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1393 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
1395 if ((channel & RedChannel) != 0)
1397 distance=QuantumScale*fabs(Sa*(
double) GetPixelRed(p)-Da*
1398 (
double) GetPixelRed(q));
1399 if (distance > channel_distortion[RedChannel])
1400 channel_distortion[RedChannel]=distance;
1401 if (distance > channel_distortion[CompositeChannels])
1402 channel_distortion[CompositeChannels]=distance;
1404 if ((channel & GreenChannel) != 0)
1406 distance=QuantumScale*fabs(Sa*(
double) GetPixelGreen(p)-Da*
1407 (
double) GetPixelGreen(q));
1408 if (distance > channel_distortion[GreenChannel])
1409 channel_distortion[GreenChannel]=distance;
1410 if (distance > channel_distortion[CompositeChannels])
1411 channel_distortion[CompositeChannels]=distance;
1413 if ((channel & BlueChannel) != 0)
1415 distance=QuantumScale*fabs(Sa*(
double) GetPixelBlue(p)-Da*
1416 (
double) GetPixelBlue(q));
1417 if (distance > channel_distortion[BlueChannel])
1418 channel_distortion[BlueChannel]=distance;
1419 if (distance > channel_distortion[CompositeChannels])
1420 channel_distortion[CompositeChannels]=distance;
1422 if (((channel & OpacityChannel) != 0) &&
1423 (image->matte != MagickFalse))
1425 distance=QuantumScale*fabs((double) GetPixelOpacity(p)-(double)
1426 GetPixelOpacity(q));
1427 if (distance > channel_distortion[OpacityChannel])
1428 channel_distortion[OpacityChannel]=distance;
1429 if (distance > channel_distortion[CompositeChannels])
1430 channel_distortion[CompositeChannels]=distance;
1432 if (((channel & IndexChannel) != 0) &&
1433 (image->colorspace == CMYKColorspace) &&
1434 (reconstruct_image->colorspace == CMYKColorspace))
1436 distance=QuantumScale*fabs(Sa*(double) GetPixelIndex(indexes+x)-Da*
1437 (double) GetPixelIndex(reconstruct_indexes+x));
1438 if (distance > channel_distortion[BlackChannel])
1439 channel_distortion[BlackChannel]=distance;
1440 if (distance > channel_distortion[CompositeChannels])
1441 channel_distortion[CompositeChannels]=distance;
1446#if defined(MAGICKCORE_OPENMP_SUPPORT)
1447 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
1449 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1450 if (channel_distortion[i] > distortion[i])
1451 distortion[i]=channel_distortion[i];
1453 reconstruct_view=DestroyCacheView(reconstruct_view);
1454 image_view=DestroyCacheView(image_view);
1458static MagickBooleanType GetPeakSignalToNoiseRatio(
const Image *image,
1459 const Image *reconstruct_image,
const ChannelType channel,
1460 double *distortion,ExceptionInfo *exception)
1465 status=GetMeanSquaredDistortion(image,reconstruct_image,channel,distortion,
1467 if ((channel & RedChannel) != 0)
1468 distortion[RedChannel]=10.0*PerceptibleLog10(PerceptibleReciprocal(
1469 distortion[RedChannel]))/MagickPSNRDistortion;
1470 if ((channel & GreenChannel) != 0)
1471 distortion[GreenChannel]=10.0*PerceptibleLog10(PerceptibleReciprocal(
1472 distortion[GreenChannel]))/MagickPSNRDistortion;
1473 if ((channel & BlueChannel) != 0)
1474 distortion[BlueChannel]=10.0*PerceptibleLog10(PerceptibleReciprocal(
1475 distortion[BlueChannel]))/MagickPSNRDistortion;
1476 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
1477 distortion[OpacityChannel]=10.0*PerceptibleLog10(PerceptibleReciprocal(
1478 distortion[OpacityChannel]))/MagickPSNRDistortion;
1479 if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
1480 distortion[BlackChannel]=10.0*PerceptibleLog10(PerceptibleReciprocal(
1481 distortion[BlackChannel]))/MagickPSNRDistortion;
1482 distortion[CompositeChannels]=10.0*PerceptibleLog10(PerceptibleReciprocal(
1483 distortion[CompositeChannels]))/MagickPSNRDistortion;
1487static MagickBooleanType GetPerceptualHashDistortion(
const Image *image,
1488 const Image *reconstruct_image,
const ChannelType channel,
double *distortion,
1489 ExceptionInfo *exception)
1491#define PHASHNormalizationFactor 389.373723242
1493 ChannelPerceptualHash
1506 image_phash=GetImageChannelPerceptualHash(image,exception);
1507 if (image_phash == (ChannelPerceptualHash *) NULL)
1508 return(MagickFalse);
1509 reconstruct_phash=GetImageChannelPerceptualHash(reconstruct_image,exception);
1510 if (reconstruct_phash == (ChannelPerceptualHash *) NULL)
1512 image_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(image_phash);
1513 return(MagickFalse);
1515 for (i=0; i < MaximumNumberOfImageMoments; i++)
1520 if ((channel & RedChannel) != 0)
1522 difference=reconstruct_phash[RedChannel].P[i]-
1523 image_phash[RedChannel].P[i];
1524 if (IsNaN(difference) != 0)
1526 distortion[RedChannel]+=difference*difference;
1527 distortion[CompositeChannels]+=QuantumScale*difference*difference;
1529 if ((channel & GreenChannel) != 0)
1531 difference=reconstruct_phash[GreenChannel].P[i]-
1532 image_phash[GreenChannel].P[i];
1533 if (IsNaN(difference) != 0)
1535 distortion[GreenChannel]+=difference*difference;
1536 distortion[CompositeChannels]+=QuantumScale*difference*difference;
1538 if ((channel & BlueChannel) != 0)
1540 difference=reconstruct_phash[BlueChannel].P[i]-
1541 image_phash[BlueChannel].P[i];
1542 if (IsNaN(difference) != 0)
1544 distortion[BlueChannel]+=difference*difference;
1545 distortion[CompositeChannels]+=QuantumScale*difference*difference;
1547 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse) &&
1548 (reconstruct_image->matte != MagickFalse))
1550 difference=reconstruct_phash[OpacityChannel].P[i]-
1551 image_phash[OpacityChannel].P[i];
1552 if (IsNaN(difference) != 0)
1554 distortion[OpacityChannel]+=difference*difference;
1555 distortion[CompositeChannels]+=QuantumScale*difference*difference;
1557 if (((channel & IndexChannel) != 0) &&
1558 (image->colorspace == CMYKColorspace) &&
1559 (reconstruct_image->colorspace == CMYKColorspace))
1561 difference=reconstruct_phash[IndexChannel].P[i]-
1562 image_phash[IndexChannel].P[i];
1563 if (IsNaN(difference) != 0)
1565 distortion[IndexChannel]+=difference*difference;
1566 distortion[CompositeChannels]+=QuantumScale*difference*difference;
1572 for (i=0; i < MaximumNumberOfImageMoments; i++)
1577 if ((channel & RedChannel) != 0)
1579 difference=reconstruct_phash[RedChannel].Q[i]-
1580 image_phash[RedChannel].Q[i];
1581 if (IsNaN(difference) != 0)
1583 distortion[RedChannel]+=difference*difference;
1584 distortion[CompositeChannels]+=difference*difference;
1586 if ((channel & GreenChannel) != 0)
1588 difference=reconstruct_phash[GreenChannel].Q[i]-
1589 image_phash[GreenChannel].Q[i];
1590 if (IsNaN(difference) != 0)
1592 distortion[GreenChannel]+=difference*difference;
1593 distortion[CompositeChannels]+=difference*difference;
1595 if ((channel & BlueChannel) != 0)
1597 difference=reconstruct_phash[BlueChannel].Q[i]-
1598 image_phash[BlueChannel].Q[i];
1599 distortion[BlueChannel]+=difference*difference;
1600 distortion[CompositeChannels]+=difference*difference;
1602 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse) &&
1603 (reconstruct_image->matte != MagickFalse))
1605 difference=reconstruct_phash[OpacityChannel].Q[i]-
1606 image_phash[OpacityChannel].Q[i];
1607 if (IsNaN(difference) != 0)
1609 distortion[OpacityChannel]+=difference*difference;
1610 distortion[CompositeChannels]+=difference*difference;
1612 if (((channel & IndexChannel) != 0) &&
1613 (image->colorspace == CMYKColorspace) &&
1614 (reconstruct_image->colorspace == CMYKColorspace))
1616 difference=reconstruct_phash[IndexChannel].Q[i]-
1617 image_phash[IndexChannel].Q[i];
1618 if (IsNaN(difference) != 0)
1620 distortion[IndexChannel]+=difference*difference;
1621 distortion[CompositeChannels]+=difference*difference;
1624 distortion[CompositeChannels]/=(double) GetNumberChannels(image,channel);
1625 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1626 distortion[i]/=PHASHNormalizationFactor;
1630 reconstruct_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(
1632 image_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(image_phash);
1636static MagickBooleanType GetRootMeanSquaredDistortion(
const Image *image,
1637 const Image *reconstruct_image,
const ChannelType channel,
double *distortion,
1638 ExceptionInfo *exception)
1643 status=GetMeanSquaredDistortion(image,reconstruct_image,channel,distortion,
1645 if ((channel & RedChannel) != 0)
1646 distortion[RedChannel]=sqrt(distortion[RedChannel]);
1647 if ((channel & GreenChannel) != 0)
1648 distortion[GreenChannel]=sqrt(distortion[GreenChannel]);
1649 if ((channel & BlueChannel) != 0)
1650 distortion[BlueChannel]=sqrt(distortion[BlueChannel]);
1651 if (((channel & OpacityChannel) != 0) &&
1652 (image->matte != MagickFalse))
1653 distortion[OpacityChannel]=sqrt(distortion[OpacityChannel]);
1654 if (((channel & IndexChannel) != 0) &&
1655 (image->colorspace == CMYKColorspace))
1656 distortion[BlackChannel]=sqrt(distortion[BlackChannel]);
1657 distortion[CompositeChannels]=sqrt(distortion[CompositeChannels]);
1661MagickExport MagickBooleanType GetImageChannelDistortion(Image *image,
1662 const Image *reconstruct_image,
const ChannelType channel,
1663 const MetricType metric,
double *distortion,ExceptionInfo *exception)
1666 *channel_distortion;
1674 assert(image != (Image *) NULL);
1675 assert(image->signature == MagickCoreSignature);
1676 assert(reconstruct_image != (
const Image *) NULL);
1677 assert(reconstruct_image->signature == MagickCoreSignature);
1678 assert(distortion != (
double *) NULL);
1679 if (IsEventLogging() != MagickFalse)
1680 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
1682 if (metric != PerceptualHashErrorMetric)
1683 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1684 ThrowBinaryException(ImageError,
"ImageMorphologyDiffers",image->filename);
1688 length=CompositeChannels+1UL;
1689 channel_distortion=(
double *) AcquireQuantumMemory(length,
1690 sizeof(*channel_distortion));
1691 if (channel_distortion == (
double *) NULL)
1692 ThrowFatalException(ResourceLimitFatalError,
"MemoryAllocationFailed");
1693 (void) memset(channel_distortion,0,length*
sizeof(*channel_distortion));
1696 case AbsoluteErrorMetric:
1698 status=GetAbsoluteDistortion(image,reconstruct_image,channel,
1699 channel_distortion,exception);
1702 case FuzzErrorMetric:
1704 status=GetFuzzDistortion(image,reconstruct_image,channel,
1705 channel_distortion,exception);
1708 case MeanAbsoluteErrorMetric:
1710 status=GetMeanAbsoluteDistortion(image,reconstruct_image,channel,
1711 channel_distortion,exception);
1714 case MeanErrorPerPixelMetric:
1716 status=GetMeanErrorPerPixel(image,reconstruct_image,channel,
1717 channel_distortion,exception);
1720 case MeanSquaredErrorMetric:
1722 status=GetMeanSquaredDistortion(image,reconstruct_image,channel,
1723 channel_distortion,exception);
1726 case NormalizedCrossCorrelationErrorMetric:
1728 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1729 channel,channel_distortion,exception);
1732 case PeakAbsoluteErrorMetric:
1734 status=GetPeakAbsoluteDistortion(image,reconstruct_image,channel,
1735 channel_distortion,exception);
1738 case PeakSignalToNoiseRatioMetric:
1740 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,channel,
1741 channel_distortion,exception);
1744 case PerceptualHashErrorMetric:
1746 status=GetPerceptualHashDistortion(image,reconstruct_image,channel,
1747 channel_distortion,exception);
1750 case RootMeanSquaredErrorMetric:
1751 case UndefinedErrorMetric:
1754 status=GetRootMeanSquaredDistortion(image,reconstruct_image,channel,
1755 channel_distortion,exception);
1759 *distortion=channel_distortion[CompositeChannels];
1760 channel_distortion=(
double *) RelinquishMagickMemory(channel_distortion);
1761 (void) FormatImageProperty(image,
"distortion",
"%.*g",GetMagickPrecision(),
1798MagickExport
double *GetImageChannelDistortions(Image *image,
1799 const Image *reconstruct_image,
const MetricType metric,
1800 ExceptionInfo *exception)
1803 *channel_distortion;
1811 assert(image != (Image *) NULL);
1812 assert(image->signature == MagickCoreSignature);
1813 assert(reconstruct_image != (
const Image *) NULL);
1814 assert(reconstruct_image->signature == MagickCoreSignature);
1815 if (IsEventLogging() != MagickFalse)
1816 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
1817 if (metric != PerceptualHashErrorMetric)
1818 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1820 (void) ThrowMagickException(&image->exception,GetMagickModule(),
1821 ImageError,
"ImageMorphologyDiffers",
"`%s'",image->filename);
1822 return((
double *) NULL);
1827 length=CompositeChannels+1UL;
1828 channel_distortion=(
double *) AcquireQuantumMemory(length,
1829 sizeof(*channel_distortion));
1830 if (channel_distortion == (
double *) NULL)
1831 ThrowFatalException(ResourceLimitFatalError,
"MemoryAllocationFailed");
1832 (void) memset(channel_distortion,0,length*
1833 sizeof(*channel_distortion));
1837 case AbsoluteErrorMetric:
1839 status=GetAbsoluteDistortion(image,reconstruct_image,CompositeChannels,
1840 channel_distortion,exception);
1843 case FuzzErrorMetric:
1845 status=GetFuzzDistortion(image,reconstruct_image,CompositeChannels,
1846 channel_distortion,exception);
1849 case MeanAbsoluteErrorMetric:
1851 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1852 CompositeChannels,channel_distortion,exception);
1855 case MeanErrorPerPixelMetric:
1857 status=GetMeanErrorPerPixel(image,reconstruct_image,CompositeChannels,
1858 channel_distortion,exception);
1861 case MeanSquaredErrorMetric:
1863 status=GetMeanSquaredDistortion(image,reconstruct_image,CompositeChannels,
1864 channel_distortion,exception);
1867 case NormalizedCrossCorrelationErrorMetric:
1869 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1870 CompositeChannels,channel_distortion,exception);
1873 case PeakAbsoluteErrorMetric:
1875 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1876 CompositeChannels,channel_distortion,exception);
1879 case PeakSignalToNoiseRatioMetric:
1881 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1882 CompositeChannels,channel_distortion,exception);
1885 case PerceptualHashErrorMetric:
1887 status=GetPerceptualHashDistortion(image,reconstruct_image,
1888 CompositeChannels,channel_distortion,exception);
1891 case RootMeanSquaredErrorMetric:
1892 case UndefinedErrorMetric:
1895 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1896 CompositeChannels,channel_distortion,exception);
1900 if (status == MagickFalse)
1902 channel_distortion=(
double *) RelinquishMagickMemory(channel_distortion);
1903 return((
double *) NULL);
1905 return(channel_distortion);
1955MagickExport MagickBooleanType IsImagesEqual(Image *image,
1956 const Image *reconstruct_image)
1973 mean_error_per_pixel;
1982 assert(image != (Image *) NULL);
1983 assert(image->signature == MagickCoreSignature);
1984 assert(reconstruct_image != (
const Image *) NULL);
1985 assert(reconstruct_image->signature == MagickCoreSignature);
1986 exception=(&image->exception);
1987 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1988 ThrowBinaryException(ImageError,
"ImageMorphologyDiffers",image->filename);
1991 mean_error_per_pixel=0.0;
1993 SetImageDistortionBounds(image,reconstruct_image,&columns,&rows);
1994 image_view=AcquireVirtualCacheView(image,exception);
1995 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1996 for (y=0; y < (ssize_t) rows; y++)
1999 *magick_restrict indexes,
2000 *magick_restrict reconstruct_indexes;
2009 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
2010 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
2011 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
2013 indexes=GetCacheViewVirtualIndexQueue(image_view);
2014 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
2015 for (x=0; x < (ssize_t) columns; x++)
2020 distance=fabs((
double) GetPixelRed(p)-(
double) GetPixelRed(q));
2021 mean_error_per_pixel+=distance;
2022 mean_error+=distance*distance;
2023 if (distance > maximum_error)
2024 maximum_error=distance;
2026 distance=fabs((
double) GetPixelGreen(p)-(
double) GetPixelGreen(q));
2027 mean_error_per_pixel+=distance;
2028 mean_error+=distance*distance;
2029 if (distance > maximum_error)
2030 maximum_error=distance;
2032 distance=fabs((
double) GetPixelBlue(p)-(
double) GetPixelBlue(q));
2033 mean_error_per_pixel+=distance;
2034 mean_error+=distance*distance;
2035 if (distance > maximum_error)
2036 maximum_error=distance;
2038 if (image->matte != MagickFalse)
2040 distance=fabs((
double) GetPixelOpacity(p)-(
double)
2041 GetPixelOpacity(q));
2042 mean_error_per_pixel+=distance;
2043 mean_error+=distance*distance;
2044 if (distance > maximum_error)
2045 maximum_error=distance;
2048 if ((image->colorspace == CMYKColorspace) &&
2049 (reconstruct_image->colorspace == CMYKColorspace))
2051 distance=fabs((
double) GetPixelIndex(indexes+x)-(
double)
2052 GetPixelIndex(reconstruct_indexes+x));
2053 mean_error_per_pixel+=distance;
2054 mean_error+=distance*distance;
2055 if (distance > maximum_error)
2056 maximum_error=distance;
2063 reconstruct_view=DestroyCacheView(reconstruct_view);
2064 image_view=DestroyCacheView(image_view);
2065 gamma=PerceptibleReciprocal(area);
2066 image->error.mean_error_per_pixel=gamma*mean_error_per_pixel;
2067 image->error.normalized_mean_error=gamma*QuantumScale*QuantumScale*mean_error;
2068 image->error.normalized_maximum_error=QuantumScale*maximum_error;
2069 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
2108static double GetSimilarityMetric(
const Image *image,
const Image *reference,
2109 const MetricType metric,
const ssize_t x_offset,
const ssize_t y_offset,
2110 ExceptionInfo *exception)
2124 SetGeometry(reference,&geometry);
2125 geometry.x=x_offset;
2126 geometry.y=y_offset;
2127 similarity_image=CropImage(image,&geometry,exception);
2128 if (similarity_image == (Image *) NULL)
2131 status=GetImageDistortion(similarity_image,reference,metric,&distortion,
2134 similarity_image=DestroyImage(similarity_image);
2138MagickExport Image *SimilarityImage(Image *image,
const Image *reference,
2139 RectangleInfo *offset,
double *similarity_metric,ExceptionInfo *exception)
2144 similarity_image=SimilarityMetricImage(image,reference,
2145 RootMeanSquaredErrorMetric,offset,similarity_metric,exception);
2146 return(similarity_image);
2149MagickExport Image *SimilarityMetricImage(Image *image,
const Image *reconstruct,
2150 const MetricType metric,RectangleInfo *offset,
double *similarity_metric,
2151 ExceptionInfo *exception)
2153#define SimilarityImageTag "Similarity/Image"
2172 similarity_threshold;
2175 *similarity_image = (Image *) NULL;
2184 similarity_info = { 0 };
2189 assert(image != (
const Image *) NULL);
2190 assert(image->signature == MagickCoreSignature);
2191 assert(exception != (ExceptionInfo *) NULL);
2192 assert(exception->signature == MagickCoreSignature);
2193 assert(offset != (RectangleInfo *) NULL);
2194 if (IsEventLogging() != MagickFalse)
2195 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
2196 SetGeometry(reconstruct,offset);
2197 *similarity_metric=0.0;
2200 if (ValidateImageMorphology(image,reconstruct) == MagickFalse)
2201 ThrowImageException(ImageError,
"ImageMorphologyDiffers");
2202 if ((image->columns < reconstruct->columns) ||
2203 (image->rows < reconstruct->rows))
2205 (void) ThrowMagickException(&image->exception,GetMagickModule(),
2206 OptionWarning,
"GeometryDoesNotContainImage",
"`%s'",image->filename);
2207 return((Image *) NULL);
2209 similarity_image=CloneImage(image,image->columns,image->rows,MagickTrue,
2211 if (similarity_image == (Image *) NULL)
2212 return((Image *) NULL);
2213 similarity_image->depth=32;
2214 similarity_image->colorspace=GRAYColorspace;
2215 similarity_image->matte=MagickFalse;
2216 status=SetImageStorageClass(similarity_image,DirectClass);
2217 if (status == MagickFalse)
2219 InheritException(exception,&similarity_image->exception);
2220 return(DestroyImage(similarity_image));
2225 similarity_threshold=DefaultSimilarityThreshold;
2226 artifact=GetImageArtifact(image,
"compare:similarity-threshold");
2227 if (artifact != (
const char *) NULL)
2228 similarity_threshold=StringToDouble(artifact,(
char **) NULL);
2230 similarity_info.similarity=GetSimilarityMetric(image,reconstruct,metric,
2231 similarity_info.x,similarity_info.y,exception);
2233 similarity_view=AcquireVirtualCacheView(similarity_image,exception);
2234#if defined(MAGICKCORE_OPENMP_SUPPORT)
2235 #pragma omp parallel for schedule(static) shared(status,similarity_info) \
2236 magick_number_threads(image,reconstruct,similarity_image->rows << 2,1)
2238 for (y=0; y < (ssize_t) similarity_image->rows; y++)
2244 threshold_trigger = MagickFalse;
2250 channel_info = similarity_info;
2255 if (status == MagickFalse)
2257 if (threshold_trigger != MagickFalse)
2259 q=QueueCacheViewAuthenticPixels(similarity_view,0,y,
2260 similarity_image->columns,1,exception);
2261 if (q == (PixelPacket *) NULL)
2266 for (x=0; x < (ssize_t) similarity_image->columns; x++)
2269 update = MagickFalse;
2271 similarity=GetSimilarityMetric(image,reconstruct,metric,x,y,exception);
2274 case NormalizedCrossCorrelationErrorMetric:
2275 case PeakSignalToNoiseRatioMetric:
2277 if (similarity > channel_info.similarity)
2283 if (similarity < channel_info.similarity)
2288 if (update != MagickFalse)
2290 channel_info.similarity=similarity;
2296 case NormalizedCrossCorrelationErrorMetric:
2297 case PeakSignalToNoiseRatioMetric:
2299 SetPixelRed(q,ClampToQuantum((
double) QuantumRange*similarity));
2304 SetPixelRed(q,ClampToQuantum((
double) QuantumRange*(1.0-similarity)));
2308 SetPixelGreen(q,GetPixelRed(q));
2309 SetPixelBlue(q,GetPixelRed(q));
2312#if defined(MAGICKCORE_OPENMP_SUPPORT)
2313 #pragma omp critical (MagickCore_SimilarityMetricImage)
2317 case NormalizedCrossCorrelationErrorMetric:
2318 case PeakSignalToNoiseRatioMetric:
2320 if (similarity_threshold != DefaultSimilarityThreshold)
2321 if (channel_info.similarity >= similarity_threshold)
2322 threshold_trigger=MagickTrue;
2323 if (channel_info.similarity >= similarity_info.similarity)
2324 similarity_info=channel_info;
2329 if (similarity_threshold != DefaultSimilarityThreshold)
2330 if (channel_info.similarity < similarity_threshold)
2331 threshold_trigger=MagickTrue;
2332 if (channel_info.similarity < similarity_info.similarity)
2333 similarity_info=channel_info;
2337 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
2339 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2345 proceed=SetImageProgress(image,SimilarityImageTag,progress,image->rows);
2346 if (proceed == MagickFalse)
2350 similarity_view=DestroyCacheView(similarity_view);
2351 *similarity_metric=similarity_info.similarity;
2352 offset->x=similarity_info.x;
2353 offset->y=similarity_info.y;
2354 if (status == MagickFalse)
2355 similarity_image=DestroyImage(similarity_image);
2356 return(similarity_image);