43#include "magick/studio.h"
44#include "magick/artifact.h"
45#include "magick/attribute.h"
46#include "magick/cache-view.h"
47#include "magick/channel.h"
48#include "magick/client.h"
49#include "magick/color.h"
50#include "magick/color-private.h"
51#include "magick/colorspace.h"
52#include "magick/colorspace-private.h"
53#include "magick/compare.h"
54#include "magick/composite-private.h"
55#include "magick/constitute.h"
56#include "magick/exception-private.h"
57#include "magick/geometry.h"
58#include "magick/image-private.h"
59#include "magick/list.h"
60#include "magick/log.h"
61#include "magick/memory_.h"
62#include "magick/monitor.h"
63#include "magick/monitor-private.h"
64#include "magick/option.h"
65#include "magick/pixel-private.h"
66#include "magick/property.h"
67#include "magick/resource_.h"
68#include "magick/statistic-private.h"
69#include "magick/string_.h"
70#include "magick/string-private.h"
71#include "magick/statistic.h"
72#include "magick/thread-private.h"
73#include "magick/transform.h"
74#include "magick/utility.h"
75#include "magick/version.h"
113MagickExport Image *CompareImages(Image *image,
const Image *reconstruct_image,
114 const MetricType metric,
double *distortion,ExceptionInfo *exception)
119 highlight_image=CompareImageChannels(image,reconstruct_image,
120 CompositeChannels,metric,distortion,exception);
121 return(highlight_image);
124static size_t GetNumberChannels(
const Image *image,
const ChannelType channel)
130 if ((channel & RedChannel) != 0)
132 if ((channel & GreenChannel) != 0)
134 if ((channel & BlueChannel) != 0)
136 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
138 if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
140 return(channels == 0 ? 1UL : channels);
143static void SetImageDistortionBounds(
const Image *image,
144 const Image *reconstruct_image,
size_t *columns,
size_t *rows)
149 *columns=MagickMax(image->columns,reconstruct_image->columns);
150 *rows=MagickMax(image->rows,reconstruct_image->rows);
151 artifact=GetImageArtifact(image,
"compare:virtual-pixels");
152 if ((artifact != (
const char *) NULL) &&
153 (IsStringTrue(artifact) == MagickFalse))
155 *columns=MagickMin(image->columns,reconstruct_image->columns);
156 *rows=MagickMin(image->rows,reconstruct_image->rows);
160static inline MagickBooleanType ValidateImageMorphology(
161 const Image *magick_restrict image,
162 const Image *magick_restrict reconstruct_image)
167 if (GetNumberChannels(image,DefaultChannels) !=
168 GetNumberChannels(reconstruct_image,DefaultChannels))
173MagickExport Image *CompareImageChannels(Image *image,
174 const Image *reconstruct_image,
const ChannelType channel,
175 const MetricType metric,
double *distortion,ExceptionInfo *exception)
208 assert(image != (Image *) NULL);
209 assert(image->signature == MagickCoreSignature);
210 assert(reconstruct_image != (
const Image *) NULL);
211 assert(reconstruct_image->signature == MagickCoreSignature);
212 assert(distortion != (
double *) NULL);
213 if (IsEventLogging() != MagickFalse)
214 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
216 if (metric != PerceptualHashErrorMetric)
217 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
218 ThrowImageException(ImageError,
"ImageMorphologyDiffers");
219 status=GetImageChannelDistortion(image,reconstruct_image,channel,metric,
220 distortion,exception);
221 if (status == MagickFalse)
222 return((Image *) NULL);
223 clone_image=CloneImage(image,0,0,MagickTrue,exception);
224 if (clone_image == (Image *) NULL)
225 return((Image *) NULL);
226 (void) SetImageMask(clone_image,(Image *) NULL);
227 difference_image=CloneImage(clone_image,0,0,MagickTrue,exception);
228 clone_image=DestroyImage(clone_image);
229 if (difference_image == (Image *) NULL)
230 return((Image *) NULL);
231 (void) SetImageAlphaChannel(difference_image,OpaqueAlphaChannel);
232 SetImageDistortionBounds(image,reconstruct_image,&columns,&rows);
233 highlight_image=CloneImage(image,columns,rows,MagickTrue,exception);
234 if (highlight_image == (Image *) NULL)
236 difference_image=DestroyImage(difference_image);
237 return((Image *) NULL);
239 if (SetImageStorageClass(highlight_image,DirectClass) == MagickFalse)
241 InheritException(exception,&highlight_image->exception);
242 difference_image=DestroyImage(difference_image);
243 highlight_image=DestroyImage(highlight_image);
244 return((Image *) NULL);
246 (void) SetImageMask(highlight_image,(Image *) NULL);
247 (void) SetImageAlphaChannel(highlight_image,OpaqueAlphaChannel);
248 (void) QueryMagickColor(
"#f1001ecc",&highlight,exception);
249 artifact=GetImageArtifact(image,
"compare:highlight-color");
250 if (artifact != (
const char *) NULL)
251 (void) QueryMagickColor(artifact,&highlight,exception);
252 (void) QueryMagickColor(
"#ffffffcc",&lowlight,exception);
253 artifact=GetImageArtifact(image,
"compare:lowlight-color");
254 if (artifact != (
const char *) NULL)
255 (void) QueryMagickColor(artifact,&lowlight,exception);
256 if (highlight_image->colorspace == CMYKColorspace)
258 ConvertRGBToCMYK(&highlight);
259 ConvertRGBToCMYK(&lowlight);
265 fuzz=GetFuzzyColorDistance(image,reconstruct_image);
266 GetMagickPixelPacket(image,&zero);
267 image_view=AcquireVirtualCacheView(image,exception);
268 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
269 highlight_view=AcquireAuthenticCacheView(highlight_image,exception);
270#if defined(MAGICKCORE_OPENMP_SUPPORT)
271 #pragma omp parallel for schedule(static) shared(status) \
272 magick_number_threads(image,highlight_image,rows,1)
274 for (y=0; y < (ssize_t) rows; y++)
284 *magick_restrict indexes,
285 *magick_restrict reconstruct_indexes;
292 *magick_restrict highlight_indexes;
300 if (status == MagickFalse)
302 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
303 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
304 r=QueueCacheViewAuthenticPixels(highlight_view,0,y,columns,1,exception);
305 if ((p == (
const PixelPacket *) NULL) ||
306 (q == (
const PixelPacket *) NULL) || (r == (PixelPacket *) NULL))
311 indexes=GetCacheViewVirtualIndexQueue(image_view);
312 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
313 highlight_indexes=GetCacheViewAuthenticIndexQueue(highlight_view);
315 reconstruct_pixel=zero;
316 for (x=0; x < (ssize_t) columns; x++)
321 SetMagickPixelPacket(image,p,indexes == (IndexPacket *) NULL ? NULL :
323 SetMagickPixelPacket(reconstruct_image,q,reconstruct_indexes ==
324 (IndexPacket *) NULL ? NULL : reconstruct_indexes+x,&reconstruct_pixel);
325 difference=MagickFalse;
326 if (channel == CompositeChannels)
328 if (IsMagickColorSimilar(&pixel,&reconstruct_pixel) == MagickFalse)
329 difference=MagickTrue;
339 Sa=QuantumScale*(image->matte != MagickFalse ? (double)
340 GetPixelAlpha(p) : ((double) QuantumRange-(double) OpaqueOpacity));
341 Da=QuantumScale*(image->matte != MagickFalse ? (double)
342 GetPixelAlpha(q) : ((double) QuantumRange-(double) OpaqueOpacity));
343 if ((channel & RedChannel) != 0)
345 pixel=Sa*(double) GetPixelRed(p)-Da*(double) GetPixelRed(q);
346 distance=pixel*pixel;
347 if (distance >= fuzz)
348 difference=MagickTrue;
350 if ((channel & GreenChannel) != 0)
352 pixel=Sa*(double) GetPixelGreen(p)-Da*(double) GetPixelGreen(q);
353 distance=pixel*pixel;
354 if (distance >= fuzz)
355 difference=MagickTrue;
357 if ((channel & BlueChannel) != 0)
359 pixel=Sa*(double) GetPixelBlue(p)-Da*(double) GetPixelBlue(q);
360 distance=pixel*pixel;
361 if (distance >= fuzz)
362 difference=MagickTrue;
364 if (((channel & OpacityChannel) != 0) &&
365 (image->matte != MagickFalse))
367 pixel=(double) GetPixelOpacity(p)-(double) GetPixelOpacity(q);
368 distance=pixel*pixel;
369 if (distance >= fuzz)
370 difference=MagickTrue;
372 if (((channel & IndexChannel) != 0) &&
373 (image->colorspace == CMYKColorspace))
375 pixel=Sa*(double) indexes[x]-Da*(double) reconstruct_indexes[x];
376 distance=pixel*pixel;
377 if (distance >= fuzz)
378 difference=MagickTrue;
381 if (difference != MagickFalse)
382 SetPixelPacket(highlight_image,&highlight,r,highlight_indexes ==
383 (IndexPacket *) NULL ? NULL : highlight_indexes+x);
385 SetPixelPacket(highlight_image,&lowlight,r,highlight_indexes ==
386 (IndexPacket *) NULL ? NULL : highlight_indexes+x);
391 sync=SyncCacheViewAuthenticPixels(highlight_view,exception);
392 if (sync == MagickFalse)
395 highlight_view=DestroyCacheView(highlight_view);
396 reconstruct_view=DestroyCacheView(reconstruct_view);
397 image_view=DestroyCacheView(image_view);
398 (void) CompositeImage(difference_image,image->compose,highlight_image,0,0);
399 highlight_image=DestroyImage(highlight_image);
400 if (status == MagickFalse)
401 difference_image=DestroyImage(difference_image);
402 return(difference_image);
441MagickExport MagickBooleanType GetImageDistortion(Image *image,
442 const Image *reconstruct_image,
const MetricType metric,
double *distortion,
443 ExceptionInfo *exception)
448 status=GetImageChannelDistortion(image,reconstruct_image,CompositeChannels,
449 metric,distortion,exception);
453static MagickBooleanType GetAbsoluteDistortion(
const Image *image,
454 const Image *reconstruct_image,
const ChannelType channel,
double *distortion,
455 ExceptionInfo *exception)
476 SetImageDistortionBounds(image,reconstruct_image,&columns,&rows);
477 image_view=AcquireVirtualCacheView(image,exception);
478 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
479#if defined(MAGICKCORE_OPENMP_SUPPORT)
480 #pragma omp parallel for schedule(static) shared(status) \
481 magick_number_threads(image,image,rows,1)
483 for (y=0; y < (ssize_t) rows; y++)
486 channel_distortion[CompositeChannels+1];
489 *magick_restrict indexes,
490 *magick_restrict reconstruct_indexes;
500 if (status == MagickFalse)
502 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
503 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
504 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
509 indexes=GetCacheViewVirtualIndexQueue(image_view);
510 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
511 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
512 for (x=0; x < (ssize_t) columns; x++)
519 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
520 ((double) QuantumRange-(double) OpaqueOpacity));
521 Da=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(q) :
522 ((double) QuantumRange-(double) OpaqueOpacity));
523 if ((channel & RedChannel) != 0)
525 delta=QuantumScale*(Sa*(double) GetPixelRed(p)-Da*(double)
527 channel_distortion[RedChannel]+=fabs(delta);
528 channel_distortion[CompositeChannels]+=fabs(delta);
530 if ((channel & GreenChannel) != 0)
532 delta=QuantumScale*(Sa*(double) GetPixelGreen(p)-Da*(double)
534 channel_distortion[RedChannel]+=fabs(delta);
535 channel_distortion[CompositeChannels]+=fabs(delta);
537 if ((channel & BlueChannel) != 0)
539 delta=QuantumScale*(Sa*(double) GetPixelBlue(p)-Da*(double)
541 channel_distortion[RedChannel]+=fabs(delta);
542 channel_distortion[CompositeChannels]+=fabs(delta);
544 if (((channel & OpacityChannel) != 0) &&
545 (image->matte != MagickFalse))
547 delta=QuantumScale*((double) GetPixelOpacity(p)-(double)
549 channel_distortion[RedChannel]+=fabs(delta);
550 channel_distortion[CompositeChannels]+=fabs(delta);
552 if (((channel & IndexChannel) != 0) &&
553 (image->colorspace == CMYKColorspace))
555 delta=QuantumScale*(Sa*(double) indexes[x]-Da*(double)
556 reconstruct_indexes[x]);
557 channel_distortion[RedChannel]+=fabs(delta);
558 channel_distortion[CompositeChannels]+=fabs(delta);
563#if defined(MAGICKCORE_OPENMP_SUPPORT)
564 #pragma omp critical (MagickCore_GetAbsoluteDistortion)
566 for (i=0; i <= (ssize_t) CompositeChannels; i++)
567 distortion[i]+=channel_distortion[i];
569 reconstruct_view=DestroyCacheView(reconstruct_view);
570 image_view=DestroyCacheView(image_view);
571 distortion[CompositeChannels]/=(double) GetNumberChannels(image,channel);
572 for (i=0; i <= (ssize_t) CompositeChannels; i++)
573 distortion[i]/=((
double) columns*rows);
577static MagickBooleanType GetFuzzDistortion(
const Image *image,
578 const Image *reconstruct_image,
const ChannelType channel,
579 double *distortion,ExceptionInfo *exception)
599 SetImageDistortionBounds(image,reconstruct_image,&columns,&rows);
600 image_view=AcquireVirtualCacheView(image,exception);
601 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
602#if defined(MAGICKCORE_OPENMP_SUPPORT)
603 #pragma omp parallel for schedule(static) shared(status) \
604 magick_number_threads(image,image,rows,1)
606 for (y=0; y < (ssize_t) rows; y++)
609 channel_distortion[CompositeChannels+1];
612 *magick_restrict indexes,
613 *magick_restrict reconstruct_indexes;
623 if (status == MagickFalse)
625 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
626 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
627 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
632 indexes=GetCacheViewVirtualIndexQueue(image_view);
633 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
634 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
635 for (x=0; x < (ssize_t) columns; x++)
642 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
643 ((double) QuantumRange-(double) OpaqueOpacity));
644 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
645 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
647 if ((channel & RedChannel) != 0)
649 distance=QuantumScale*(Sa*(double) GetPixelRed(p)-Da*(double)
651 channel_distortion[RedChannel]+=distance*distance;
652 channel_distortion[CompositeChannels]+=distance*distance;
654 if ((channel & GreenChannel) != 0)
656 distance=QuantumScale*(Sa*(double) GetPixelGreen(p)-Da*(double)
658 channel_distortion[GreenChannel]+=distance*distance;
659 channel_distortion[CompositeChannels]+=distance*distance;
661 if ((channel & BlueChannel) != 0)
663 distance=QuantumScale*(Sa*(double) GetPixelBlue(p)-Da*(double)
665 channel_distortion[BlueChannel]+=distance*distance;
666 channel_distortion[CompositeChannels]+=distance*distance;
668 if (((channel & OpacityChannel) != 0) && ((image->matte != MagickFalse) ||
669 (reconstruct_image->matte != MagickFalse)))
671 distance=QuantumScale*((image->matte != MagickFalse ? (double)
672 GetPixelOpacity(p) : (double) OpaqueOpacity)-
673 (reconstruct_image->matte != MagickFalse ?
674 (double) GetPixelOpacity(q): (double) OpaqueOpacity));
675 channel_distortion[OpacityChannel]+=distance*distance;
676 channel_distortion[CompositeChannels]+=distance*distance;
678 if (((channel & IndexChannel) != 0) &&
679 (image->colorspace == CMYKColorspace) &&
680 (reconstruct_image->colorspace == CMYKColorspace))
682 distance=QuantumScale*(Sa*(double) GetPixelIndex(indexes+x)-
683 Da*(double) GetPixelIndex(reconstruct_indexes+x));
684 channel_distortion[BlackChannel]+=distance*distance;
685 channel_distortion[CompositeChannels]+=distance*distance;
690#if defined(MAGICKCORE_OPENMP_SUPPORT)
691 #pragma omp critical (MagickCore_GetFuzzDistortion)
693 for (i=0; i <= (ssize_t) CompositeChannels; i++)
694 distortion[i]+=channel_distortion[i];
696 reconstruct_view=DestroyCacheView(reconstruct_view);
697 image_view=DestroyCacheView(image_view);
698 distortion[CompositeChannels]/=(double) GetNumberChannels(image,channel);
699 for (i=0; i <= (ssize_t) CompositeChannels; i++)
700 distortion[i]/=((
double) columns*rows);
701 distortion[CompositeChannels]=sqrt(distortion[CompositeChannels]);
705static MagickBooleanType GetMeanAbsoluteDistortion(
const Image *image,
706 const Image *reconstruct_image,
const ChannelType channel,
707 double *distortion,ExceptionInfo *exception)
725 SetImageDistortionBounds(image,reconstruct_image,&columns,&rows);
726 image_view=AcquireVirtualCacheView(image,exception);
727 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
728#if defined(MAGICKCORE_OPENMP_SUPPORT)
729 #pragma omp parallel for schedule(static) shared(status) \
730 magick_number_threads(image,image,rows,1)
732 for (y=0; y < (ssize_t) rows; y++)
735 channel_distortion[CompositeChannels+1];
738 *magick_restrict indexes,
739 *magick_restrict reconstruct_indexes;
749 if (status == MagickFalse)
751 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
752 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
753 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
758 indexes=GetCacheViewVirtualIndexQueue(image_view);
759 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
760 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
761 for (x=0; x < (ssize_t) columns; x++)
768 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
769 ((double) QuantumRange-(double) OpaqueOpacity));
770 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
771 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
773 if ((channel & RedChannel) != 0)
775 distance=QuantumScale*fabs(Sa*(
double) GetPixelRed(p)-Da*
776 (
double) GetPixelRed(q));
777 channel_distortion[RedChannel]+=distance;
778 channel_distortion[CompositeChannels]+=distance;
780 if ((channel & GreenChannel) != 0)
782 distance=QuantumScale*fabs(Sa*(
double) GetPixelGreen(p)-Da*
783 (
double) GetPixelGreen(q));
784 channel_distortion[GreenChannel]+=distance;
785 channel_distortion[CompositeChannels]+=distance;
787 if ((channel & BlueChannel) != 0)
789 distance=QuantumScale*fabs(Sa*(
double) GetPixelBlue(p)-Da*
790 (
double) GetPixelBlue(q));
791 channel_distortion[BlueChannel]+=distance;
792 channel_distortion[CompositeChannels]+=distance;
794 if (((channel & OpacityChannel) != 0) &&
795 (image->matte != MagickFalse))
797 distance=QuantumScale*fabs((double) GetPixelOpacity(p)-(double)
799 channel_distortion[OpacityChannel]+=distance;
800 channel_distortion[CompositeChannels]+=distance;
802 if (((channel & IndexChannel) != 0) &&
803 (image->colorspace == CMYKColorspace))
805 distance=QuantumScale*fabs(Sa*(double) GetPixelIndex(indexes+x)-Da*
806 (double) GetPixelIndex(reconstruct_indexes+x));
807 channel_distortion[BlackChannel]+=distance;
808 channel_distortion[CompositeChannels]+=distance;
813#if defined(MAGICKCORE_OPENMP_SUPPORT)
814 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
816 for (i=0; i <= (ssize_t) CompositeChannels; i++)
817 distortion[i]+=channel_distortion[i];
819 reconstruct_view=DestroyCacheView(reconstruct_view);
820 image_view=DestroyCacheView(image_view);
821 for (i=0; i <= (ssize_t) CompositeChannels; i++)
822 distortion[i]/=((
double) columns*rows);
823 distortion[CompositeChannels]/=(double) GetNumberChannels(image,channel);
827static MagickBooleanType GetMeanErrorPerPixel(Image *image,
828 const Image *reconstruct_image,
const ChannelType channel,
double *distortion,
829 ExceptionInfo *exception)
836 maximum_error = MagickMinimumValue;
850 SetImageDistortionBounds(image,reconstruct_image,&columns,&rows);
851 image_view=AcquireVirtualCacheView(image,exception);
852 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
853#if defined(MAGICKCORE_OPENMP_SUPPORT)
854 #pragma omp parallel for schedule(static) shared(status) \
855 magick_number_threads(image,image,rows,1)
857 for (y=0; y < (ssize_t) rows; y++)
860 channel_distortion[CompositeChannels+1],
861 local_maximum = MinimumValue;
864 *magick_restrict indexes,
865 *magick_restrict reconstruct_indexes;
875 if (status == MagickFalse)
877 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
878 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
879 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
884 indexes=GetCacheViewVirtualIndexQueue(image_view);
885 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
886 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
887 for (x=0; x < (ssize_t) columns; x++)
894 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
895 ((double) QuantumRange-(double) OpaqueOpacity));
896 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
897 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
899 if ((channel & RedChannel) != 0)
901 distance=QuantumScale*fabs(Sa*(
double) GetPixelRed(p)-Da*
902 (
double) GetPixelRed(q));
903 channel_distortion[RedChannel]+=distance;
904 channel_distortion[CompositeChannels]+=distance;
905 if (distance > local_maximum)
906 local_maximum=distance;
908 if ((channel & GreenChannel) != 0)
910 distance=QuantumScale*fabs(Sa*(
double) GetPixelGreen(p)-Da*
911 (
double) GetPixelGreen(q));
912 channel_distortion[GreenChannel]+=distance;
913 channel_distortion[CompositeChannels]+=distance;
914 if (distance > local_maximum)
915 local_maximum=distance;
917 if ((channel & BlueChannel) != 0)
919 distance=QuantumScale*fabs(Sa*(
double) GetPixelBlue(p)-Da*
920 (
double) GetPixelBlue(q));
921 channel_distortion[BlueChannel]+=distance;
922 channel_distortion[CompositeChannels]+=distance;
923 if (distance > local_maximum)
924 local_maximum=distance;
926 if (((channel & OpacityChannel) != 0) &&
927 (image->matte != MagickFalse))
929 distance=QuantumScale*fabs((double) GetPixelOpacity(p)-(double)
931 channel_distortion[OpacityChannel]+=distance;
932 channel_distortion[CompositeChannels]+=distance;
933 if (distance > local_maximum)
934 local_maximum=distance;
936 if (((channel & IndexChannel) != 0) &&
937 (image->colorspace == CMYKColorspace))
939 distance=QuantumScale*fabs(Sa*(double) GetPixelIndex(indexes+x)-Da*
940 (double) GetPixelIndex(reconstruct_indexes+x));
941 channel_distortion[BlackChannel]+=distance;
942 channel_distortion[CompositeChannels]+=distance;
943 if (distance > local_maximum)
944 local_maximum=distance;
949#if defined(MAGICKCORE_OPENMP_SUPPORT)
950 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
953 for (i=0; i <= (ssize_t) CompositeChannels; i++)
954 distortion[i]+=channel_distortion[i];
955 if (local_maximum > maximum_error)
956 maximum_error=local_maximum;
959 reconstruct_view=DestroyCacheView(reconstruct_view);
960 image_view=DestroyCacheView(image_view);
961 distortion[CompositeChannels]/=(double) GetNumberChannels(image,channel);
962 for (i=0; i <= (ssize_t) CompositeChannels; i++)
963 distortion[i]/=((
double) columns*rows);
964 image->error.mean_error_per_pixel=distortion[CompositeChannels];
965 image->error.normalized_mean_error=distortion[CompositeChannels];
966 image->error.normalized_maximum_error=QuantumScale*maximum_error;
970static MagickBooleanType GetMeanSquaredDistortion(
const Image *image,
971 const Image *reconstruct_image,
const ChannelType channel,
972 double *distortion,ExceptionInfo *exception)
993 SetImageDistortionBounds(image,reconstruct_image,&columns,&rows);
994 image_view=AcquireVirtualCacheView(image,exception);
995 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
996#if defined(MAGICKCORE_OPENMP_SUPPORT)
997 #pragma omp parallel for schedule(static) shared(status) \
998 magick_number_threads(image,image,rows,1)
1000 for (y=0; y < (ssize_t) rows; y++)
1003 channel_distortion[CompositeChannels+1];
1006 *magick_restrict indexes,
1007 *magick_restrict reconstruct_indexes;
1017 if (status == MagickFalse)
1019 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1020 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1021 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
1026 indexes=GetCacheViewVirtualIndexQueue(image_view);
1027 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1028 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
1029 for (x=0; x < (ssize_t) columns; x++)
1036 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
1037 ((double) QuantumRange-(double) OpaqueOpacity));
1038 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1039 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
1041 if ((channel & RedChannel) != 0)
1043 distance=QuantumScale*(Sa*(double) GetPixelRed(p)-Da*(double)
1045 channel_distortion[RedChannel]+=distance*distance;
1046 channel_distortion[CompositeChannels]+=distance*distance;
1048 if ((channel & GreenChannel) != 0)
1050 distance=QuantumScale*(Sa*(double) GetPixelGreen(p)-Da*(double)
1052 channel_distortion[GreenChannel]+=distance*distance;
1053 channel_distortion[CompositeChannels]+=distance*distance;
1055 if ((channel & BlueChannel) != 0)
1057 distance=QuantumScale*(Sa*(double) GetPixelBlue(p)-Da*(double)
1059 channel_distortion[BlueChannel]+=distance*distance;
1060 channel_distortion[CompositeChannels]+=distance*distance;
1062 if (((channel & OpacityChannel) != 0) &&
1063 (image->matte != MagickFalse))
1065 distance=QuantumScale*((double) GetPixelOpacity(p)-(double)
1066 GetPixelOpacity(q));
1067 channel_distortion[OpacityChannel]+=distance*distance;
1068 channel_distortion[CompositeChannels]+=distance*distance;
1070 if (((channel & IndexChannel) != 0) &&
1071 (image->colorspace == CMYKColorspace) &&
1072 (reconstruct_image->colorspace == CMYKColorspace))
1074 distance=QuantumScale*(Sa*(double) GetPixelIndex(indexes+x)-Da*
1075 (double) GetPixelIndex(reconstruct_indexes+x));
1076 channel_distortion[BlackChannel]+=distance*distance;
1077 channel_distortion[CompositeChannels]+=distance*distance;
1082#if defined(MAGICKCORE_OPENMP_SUPPORT)
1083 #pragma omp critical (MagickCore_GetMeanSquaredError)
1085 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1086 distortion[i]+=channel_distortion[i];
1088 reconstruct_view=DestroyCacheView(reconstruct_view);
1089 image_view=DestroyCacheView(image_view);
1090 area=PerceptibleReciprocal((
double) columns*rows);
1091 distortion[CompositeChannels]/=(double) GetNumberChannels(image,channel);
1092 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1093 distortion[i]*=area;
1097static MagickBooleanType GetNormalizedCrossCorrelationDistortion(
1098 const Image *image,
const Image *reconstruct_image,
const ChannelType channel,
1099 double *distortion,ExceptionInfo *exception)
1101#define SimilarityImageTag "Similarity/Image"
1109 *reconstruct_statistics;
1112 alpha_variance[CompositeChannels+1],
1113 beta_variance[CompositeChannels+1];
1134 image_statistics=GetImageChannelStatistics(image,exception);
1135 reconstruct_statistics=GetImageChannelStatistics(reconstruct_image,exception);
1136 if ((image_statistics == (ChannelStatistics *) NULL) ||
1137 (reconstruct_statistics == (ChannelStatistics *) NULL))
1139 if (image_statistics != (ChannelStatistics *) NULL)
1140 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1142 if (reconstruct_statistics != (ChannelStatistics *) NULL)
1143 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1144 reconstruct_statistics);
1145 return(MagickFalse);
1147 (void) memset(distortion,0,(CompositeChannels+1)*
sizeof(*distortion));
1148 (void) memset(alpha_variance,0,(CompositeChannels+1)*
sizeof(*alpha_variance));
1149 (void) memset(beta_variance,0,(CompositeChannels+1)*
sizeof(*beta_variance));
1152 SetImageDistortionBounds(image,reconstruct_image,&columns,&rows);
1153 image_view=AcquireVirtualCacheView(image,exception);
1154 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1155 for (y=0; y < (ssize_t) rows; y++)
1158 *magick_restrict indexes,
1159 *magick_restrict reconstruct_indexes;
1168 if (status == MagickFalse)
1170 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1171 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1172 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
1177 indexes=GetCacheViewVirtualIndexQueue(image_view);
1178 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1179 for (x=0; x < (ssize_t) columns; x++)
1187 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
1188 (double) QuantumRange);
1189 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1190 (double) GetPixelAlpha(q) : (double) QuantumRange);
1191 if ((channel & RedChannel) != 0)
1193 alpha=QuantumScale*fabs(Sa*(
double) GetPixelRed(p)-
1194 image_statistics[RedChannel].mean);
1195 beta=QuantumScale*fabs(Da*(
double) GetPixelRed(q)-
1196 reconstruct_statistics[RedChannel].mean);
1197 distortion[RedChannel]+=alpha*beta;
1198 alpha_variance[RedChannel]+=alpha*alpha;
1199 beta_variance[RedChannel]+=beta*beta;
1201 if ((channel & GreenChannel) != 0)
1203 alpha=QuantumScale*fabs(Sa*(
double) GetPixelGreen(p)-
1204 image_statistics[GreenChannel].mean);
1205 beta=QuantumScale*fabs(Da*(
double) GetPixelGreen(q)-
1206 reconstruct_statistics[GreenChannel].mean);
1207 distortion[GreenChannel]+=alpha*beta;
1208 alpha_variance[GreenChannel]+=alpha*alpha;
1209 beta_variance[GreenChannel]+=beta*beta;
1211 if ((channel & BlueChannel) != 0)
1213 alpha=QuantumScale*fabs(Sa*(
double) GetPixelBlue(p)-
1214 image_statistics[BlueChannel].mean);
1215 beta=QuantumScale*fabs(Da*(
double) GetPixelBlue(q)-
1216 reconstruct_statistics[BlueChannel].mean);
1217 distortion[BlueChannel]+=alpha*beta;
1218 alpha_variance[BlueChannel]+=alpha*alpha;
1219 beta_variance[BlueChannel]+=beta*beta;
1221 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
1223 alpha=QuantumScale*fabs((double) GetPixelAlpha(p)-
1224 image_statistics[AlphaChannel].mean);
1225 beta=QuantumScale*fabs((double) GetPixelAlpha(q)-
1226 reconstruct_statistics[AlphaChannel].mean);
1227 distortion[OpacityChannel]+=alpha*beta;
1228 alpha_variance[OpacityChannel]+=alpha*alpha;
1229 beta_variance[OpacityChannel]+=beta*beta;
1231 if (((channel & IndexChannel) != 0) &&
1232 (image->colorspace == CMYKColorspace) &&
1233 (reconstruct_image->colorspace == CMYKColorspace))
1235 alpha=QuantumScale*fabs(Sa*(double) GetPixelIndex(indexes+x)-
1236 image_statistics[BlackChannel].mean);
1237 beta=QuantumScale*fabs(Da*(double) GetPixelIndex(reconstruct_indexes+
1238 x)-reconstruct_statistics[BlackChannel].mean);
1239 distortion[BlackChannel]+=alpha*beta;
1240 alpha_variance[BlackChannel]+=alpha*alpha;
1241 beta_variance[BlackChannel]+=beta*beta;
1246 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1251#if defined(MAGICKCORE_OPENMP_SUPPORT)
1255 proceed=SetImageProgress(image,SimilarityImageTag,progress,rows);
1256 if (proceed == MagickFalse)
1260 reconstruct_view=DestroyCacheView(reconstruct_view);
1261 image_view=DestroyCacheView(image_view);
1265 for (i=0; i < (ssize_t) CompositeChannels; i++)
1267 distortion[i]/=sqrt(alpha_variance[i]*beta_variance[i]);
1268 if (fabs(distortion[i]) >= MagickEpsilon)
1269 distortion[CompositeChannels]+=distortion[i];
1271 distortion[CompositeChannels]=distortion[CompositeChannels]/
1272 GetNumberChannels(image,channel);
1276 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1277 reconstruct_statistics);
1278 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1283static MagickBooleanType GetPeakAbsoluteDistortion(
const Image *image,
1284 const Image *reconstruct_image,
const ChannelType channel,
1285 double *distortion,ExceptionInfo *exception)
1302 (void) memset(distortion,0,(CompositeChannels+1)*
sizeof(*distortion));
1303 SetImageDistortionBounds(image,reconstruct_image,&columns,&rows);
1304 image_view=AcquireVirtualCacheView(image,exception);
1305 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1306#if defined(MAGICKCORE_OPENMP_SUPPORT)
1307 #pragma omp parallel for schedule(static) shared(status) \
1308 magick_number_threads(image,image,rows,1)
1310 for (y=0; y < (ssize_t) rows; y++)
1313 channel_distortion[CompositeChannels+1];
1316 *magick_restrict indexes,
1317 *magick_restrict reconstruct_indexes;
1327 if (status == MagickFalse)
1329 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1330 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1331 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
1336 indexes=GetCacheViewVirtualIndexQueue(image_view);
1337 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1338 (void) memset(channel_distortion,0,(CompositeChannels+1)*
1339 sizeof(*channel_distortion));
1340 for (x=0; x < (ssize_t) columns; x++)
1347 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
1348 ((double) QuantumRange-(double) OpaqueOpacity));
1349 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1350 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
1352 if ((channel & RedChannel) != 0)
1354 distance=QuantumScale*fabs(Sa*(
double) GetPixelRed(p)-Da*
1355 (
double) GetPixelRed(q));
1356 if (distance > channel_distortion[RedChannel])
1357 channel_distortion[RedChannel]=distance;
1358 if (distance > channel_distortion[CompositeChannels])
1359 channel_distortion[CompositeChannels]=distance;
1361 if ((channel & GreenChannel) != 0)
1363 distance=QuantumScale*fabs(Sa*(
double) GetPixelGreen(p)-Da*
1364 (
double) GetPixelGreen(q));
1365 if (distance > channel_distortion[GreenChannel])
1366 channel_distortion[GreenChannel]=distance;
1367 if (distance > channel_distortion[CompositeChannels])
1368 channel_distortion[CompositeChannels]=distance;
1370 if ((channel & BlueChannel) != 0)
1372 distance=QuantumScale*fabs(Sa*(
double) GetPixelBlue(p)-Da*
1373 (
double) GetPixelBlue(q));
1374 if (distance > channel_distortion[BlueChannel])
1375 channel_distortion[BlueChannel]=distance;
1376 if (distance > channel_distortion[CompositeChannels])
1377 channel_distortion[CompositeChannels]=distance;
1379 if (((channel & OpacityChannel) != 0) &&
1380 (image->matte != MagickFalse))
1382 distance=QuantumScale*fabs((double) GetPixelOpacity(p)-(double)
1383 GetPixelOpacity(q));
1384 if (distance > channel_distortion[OpacityChannel])
1385 channel_distortion[OpacityChannel]=distance;
1386 if (distance > channel_distortion[CompositeChannels])
1387 channel_distortion[CompositeChannels]=distance;
1389 if (((channel & IndexChannel) != 0) &&
1390 (image->colorspace == CMYKColorspace) &&
1391 (reconstruct_image->colorspace == CMYKColorspace))
1393 distance=QuantumScale*fabs(Sa*(double) GetPixelIndex(indexes+x)-Da*
1394 (double) GetPixelIndex(reconstruct_indexes+x));
1395 if (distance > channel_distortion[BlackChannel])
1396 channel_distortion[BlackChannel]=distance;
1397 if (distance > channel_distortion[CompositeChannels])
1398 channel_distortion[CompositeChannels]=distance;
1403#if defined(MAGICKCORE_OPENMP_SUPPORT)
1404 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
1406 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1407 if (channel_distortion[i] > distortion[i])
1408 distortion[i]=channel_distortion[i];
1410 reconstruct_view=DestroyCacheView(reconstruct_view);
1411 image_view=DestroyCacheView(image_view);
1415static MagickBooleanType GetPeakSignalToNoiseRatio(
const Image *image,
1416 const Image *reconstruct_image,
const ChannelType channel,
1417 double *distortion,ExceptionInfo *exception)
1422 status=GetMeanSquaredDistortion(image,reconstruct_image,channel,distortion,
1424 if ((channel & RedChannel) != 0)
1426 if (distortion[RedChannel] < MagickEpsilon)
1427 distortion[RedChannel]=1.0;
1429 if (distortion[RedChannel] >= 1.0)
1430 distortion[RedChannel]=0.0;
1432 distortion[RedChannel]=10.0*log10(PerceptibleReciprocal(
1433 distortion[RedChannel]))/MagickPSNRDistortion;
1435 if ((channel & GreenChannel) != 0)
1437 if (distortion[GreenChannel] < MagickEpsilon)
1438 distortion[GreenChannel]=1.0;
1440 if (distortion[GreenChannel] >= 1.0)
1441 distortion[GreenChannel]=0.0;
1443 distortion[GreenChannel]=10.0*log10(PerceptibleReciprocal(
1444 distortion[GreenChannel]))/MagickPSNRDistortion;
1446 if ((channel & BlueChannel) != 0)
1448 if (distortion[BlueChannel] < MagickEpsilon)
1449 distortion[BlueChannel]=1.0;
1451 if (distortion[BlueChannel] >= 1.0)
1452 distortion[BlueChannel]=0.0;
1454 distortion[BlueChannel]=10.0*log10(PerceptibleReciprocal(
1455 distortion[BlueChannel]))/MagickPSNRDistortion;
1457 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
1459 if (distortion[OpacityChannel] < MagickEpsilon)
1460 distortion[OpacityChannel]=1.0;
1462 if (distortion[OpacityChannel] >= 1.0)
1463 distortion[OpacityChannel]=0.0;
1465 distortion[OpacityChannel]=10.0*log10(PerceptibleReciprocal(
1466 distortion[OpacityChannel]))/MagickPSNRDistortion;
1468 if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
1470 if (distortion[BlackChannel] < MagickEpsilon)
1471 distortion[BlackChannel]=1.0;
1473 if (distortion[BlackChannel] >= 1.0)
1474 distortion[BlackChannel]=0.0;
1476 distortion[BlackChannel]=10.0*log10(PerceptibleReciprocal(
1477 distortion[BlackChannel]))/MagickPSNRDistortion;
1479 if (distortion[CompositeChannels] < MagickEpsilon)
1480 distortion[CompositeChannels]=1.0;
1482 if (distortion[CompositeChannels] >= 1.0)
1483 distortion[CompositeChannels]=0.0;
1485 distortion[CompositeChannels]=10.0*log10(PerceptibleReciprocal(
1486 distortion[CompositeChannels]))/MagickPSNRDistortion;
1490static MagickBooleanType GetPerceptualHashDistortion(
const Image *image,
1491 const Image *reconstruct_image,
const ChannelType channel,
double *distortion,
1492 ExceptionInfo *exception)
1494 ChannelPerceptualHash
1507 image_phash=GetImageChannelPerceptualHash(image,exception);
1508 if (image_phash == (ChannelPerceptualHash *) NULL)
1509 return(MagickFalse);
1510 reconstruct_phash=GetImageChannelPerceptualHash(reconstruct_image,exception);
1511 if (reconstruct_phash == (ChannelPerceptualHash *) NULL)
1513 image_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(image_phash);
1514 return(MagickFalse);
1516 for (i=0; i < MaximumNumberOfImageMoments; i++)
1521 if ((channel & RedChannel) != 0)
1523 difference=reconstruct_phash[RedChannel].P[i]-
1524 image_phash[RedChannel].P[i];
1525 if (IsNaN(difference) != 0)
1527 distortion[RedChannel]+=difference*difference;
1528 distortion[CompositeChannels]+=difference*difference;
1530 if ((channel & GreenChannel) != 0)
1532 difference=reconstruct_phash[GreenChannel].P[i]-
1533 image_phash[GreenChannel].P[i];
1534 if (IsNaN(difference) != 0)
1536 distortion[GreenChannel]+=difference*difference;
1537 distortion[CompositeChannels]+=difference*difference;
1539 if ((channel & BlueChannel) != 0)
1541 difference=reconstruct_phash[BlueChannel].P[i]-
1542 image_phash[BlueChannel].P[i];
1543 if (IsNaN(difference) != 0)
1545 distortion[BlueChannel]+=difference*difference;
1546 distortion[CompositeChannels]+=difference*difference;
1548 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse) &&
1549 (reconstruct_image->matte != MagickFalse))
1551 difference=reconstruct_phash[OpacityChannel].P[i]-
1552 image_phash[OpacityChannel].P[i];
1553 if (IsNaN(difference) != 0)
1555 distortion[OpacityChannel]+=difference*difference;
1556 distortion[CompositeChannels]+=difference*difference;
1558 if (((channel & IndexChannel) != 0) &&
1559 (image->colorspace == CMYKColorspace) &&
1560 (reconstruct_image->colorspace == CMYKColorspace))
1562 difference=reconstruct_phash[IndexChannel].P[i]-
1563 image_phash[IndexChannel].P[i];
1564 if (IsNaN(difference) != 0)
1566 distortion[IndexChannel]+=difference*difference;
1567 distortion[CompositeChannels]+=difference*difference;
1573 for (i=0; i < MaximumNumberOfImageMoments; i++)
1578 if ((channel & RedChannel) != 0)
1580 difference=reconstruct_phash[RedChannel].Q[i]-
1581 image_phash[RedChannel].Q[i];
1582 if (IsNaN(difference) != 0)
1584 distortion[RedChannel]+=difference*difference;
1585 distortion[CompositeChannels]+=difference*difference;
1587 if ((channel & GreenChannel) != 0)
1589 difference=reconstruct_phash[GreenChannel].Q[i]-
1590 image_phash[GreenChannel].Q[i];
1591 if (IsNaN(difference) != 0)
1593 distortion[GreenChannel]+=difference*difference;
1594 distortion[CompositeChannels]+=difference*difference;
1596 if ((channel & BlueChannel) != 0)
1598 difference=reconstruct_phash[BlueChannel].Q[i]-
1599 image_phash[BlueChannel].Q[i];
1600 distortion[BlueChannel]+=difference*difference;
1601 distortion[CompositeChannels]+=difference*difference;
1603 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse) &&
1604 (reconstruct_image->matte != MagickFalse))
1606 difference=reconstruct_phash[OpacityChannel].Q[i]-
1607 image_phash[OpacityChannel].Q[i];
1608 if (IsNaN(difference) != 0)
1610 distortion[OpacityChannel]+=difference*difference;
1611 distortion[CompositeChannels]+=difference*difference;
1613 if (((channel & IndexChannel) != 0) &&
1614 (image->colorspace == CMYKColorspace) &&
1615 (reconstruct_image->colorspace == CMYKColorspace))
1617 difference=reconstruct_phash[IndexChannel].Q[i]-
1618 image_phash[IndexChannel].Q[i];
1619 if (IsNaN(difference) != 0)
1621 distortion[IndexChannel]+=difference*difference;
1622 distortion[CompositeChannels]+=difference*difference;
1628 reconstruct_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(
1630 image_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(image_phash);
1634static MagickBooleanType GetRootMeanSquaredDistortion(
const Image *image,
1635 const Image *reconstruct_image,
const ChannelType channel,
double *distortion,
1636 ExceptionInfo *exception)
1641 status=GetMeanSquaredDistortion(image,reconstruct_image,channel,distortion,
1643 if ((channel & RedChannel) != 0)
1644 distortion[RedChannel]=sqrt(distortion[RedChannel]);
1645 if ((channel & GreenChannel) != 0)
1646 distortion[GreenChannel]=sqrt(distortion[GreenChannel]);
1647 if ((channel & BlueChannel) != 0)
1648 distortion[BlueChannel]=sqrt(distortion[BlueChannel]);
1649 if (((channel & OpacityChannel) != 0) &&
1650 (image->matte != MagickFalse))
1651 distortion[OpacityChannel]=sqrt(distortion[OpacityChannel]);
1652 if (((channel & IndexChannel) != 0) &&
1653 (image->colorspace == CMYKColorspace))
1654 distortion[BlackChannel]=sqrt(distortion[BlackChannel]);
1655 distortion[CompositeChannels]=sqrt(distortion[CompositeChannels]);
1659MagickExport MagickBooleanType GetImageChannelDistortion(Image *image,
1660 const Image *reconstruct_image,
const ChannelType channel,
1661 const MetricType metric,
double *distortion,ExceptionInfo *exception)
1664 *channel_distortion;
1672 assert(image != (Image *) NULL);
1673 assert(image->signature == MagickCoreSignature);
1674 assert(reconstruct_image != (
const Image *) NULL);
1675 assert(reconstruct_image->signature == MagickCoreSignature);
1676 assert(distortion != (
double *) NULL);
1677 if (IsEventLogging() != MagickFalse)
1678 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
1680 if (metric != PerceptualHashErrorMetric)
1681 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1682 ThrowBinaryException(ImageError,
"ImageMorphologyDiffers",image->filename);
1686 length=CompositeChannels+1UL;
1687 channel_distortion=(
double *) AcquireQuantumMemory(length,
1688 sizeof(*channel_distortion));
1689 if (channel_distortion == (
double *) NULL)
1690 ThrowFatalException(ResourceLimitFatalError,
"MemoryAllocationFailed");
1691 (void) memset(channel_distortion,0,length*
sizeof(*channel_distortion));
1694 case AbsoluteErrorMetric:
1696 status=GetAbsoluteDistortion(image,reconstruct_image,channel,
1697 channel_distortion,exception);
1700 case FuzzErrorMetric:
1702 status=GetFuzzDistortion(image,reconstruct_image,channel,
1703 channel_distortion,exception);
1706 case MeanAbsoluteErrorMetric:
1708 status=GetMeanAbsoluteDistortion(image,reconstruct_image,channel,
1709 channel_distortion,exception);
1712 case MeanErrorPerPixelMetric:
1714 status=GetMeanErrorPerPixel(image,reconstruct_image,channel,
1715 channel_distortion,exception);
1718 case MeanSquaredErrorMetric:
1720 status=GetMeanSquaredDistortion(image,reconstruct_image,channel,
1721 channel_distortion,exception);
1724 case NormalizedCrossCorrelationErrorMetric:
1727 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1728 channel,channel_distortion,exception);
1731 case PeakAbsoluteErrorMetric:
1733 status=GetPeakAbsoluteDistortion(image,reconstruct_image,channel,
1734 channel_distortion,exception);
1737 case PeakSignalToNoiseRatioMetric:
1739 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,channel,
1740 channel_distortion,exception);
1743 case PerceptualHashErrorMetric:
1745 status=GetPerceptualHashDistortion(image,reconstruct_image,channel,
1746 channel_distortion,exception);
1749 case RootMeanSquaredErrorMetric:
1751 status=GetRootMeanSquaredDistortion(image,reconstruct_image,channel,
1752 channel_distortion,exception);
1756 *distortion=channel_distortion[CompositeChannels];
1757 channel_distortion=(
double *) RelinquishMagickMemory(channel_distortion);
1758 (void) FormatImageProperty(image,
"distortion",
"%.*g",GetMagickPrecision(),
1795MagickExport
double *GetImageChannelDistortions(Image *image,
1796 const Image *reconstruct_image,
const MetricType metric,
1797 ExceptionInfo *exception)
1800 *channel_distortion;
1808 assert(image != (Image *) NULL);
1809 assert(image->signature == MagickCoreSignature);
1810 assert(reconstruct_image != (
const Image *) NULL);
1811 assert(reconstruct_image->signature == MagickCoreSignature);
1812 if (IsEventLogging() != MagickFalse)
1813 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
1814 if (metric != PerceptualHashErrorMetric)
1815 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1817 (void) ThrowMagickException(&image->exception,GetMagickModule(),
1818 ImageError,
"ImageMorphologyDiffers",
"`%s'",image->filename);
1819 return((
double *) NULL);
1824 length=CompositeChannels+1UL;
1825 channel_distortion=(
double *) AcquireQuantumMemory(length,
1826 sizeof(*channel_distortion));
1827 if (channel_distortion == (
double *) NULL)
1828 ThrowFatalException(ResourceLimitFatalError,
"MemoryAllocationFailed");
1829 (void) memset(channel_distortion,0,length*
1830 sizeof(*channel_distortion));
1834 case AbsoluteErrorMetric:
1836 status=GetAbsoluteDistortion(image,reconstruct_image,CompositeChannels,
1837 channel_distortion,exception);
1840 case FuzzErrorMetric:
1842 status=GetFuzzDistortion(image,reconstruct_image,CompositeChannels,
1843 channel_distortion,exception);
1846 case MeanAbsoluteErrorMetric:
1848 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1849 CompositeChannels,channel_distortion,exception);
1852 case MeanErrorPerPixelMetric:
1854 status=GetMeanErrorPerPixel(image,reconstruct_image,CompositeChannels,
1855 channel_distortion,exception);
1858 case MeanSquaredErrorMetric:
1860 status=GetMeanSquaredDistortion(image,reconstruct_image,CompositeChannels,
1861 channel_distortion,exception);
1864 case NormalizedCrossCorrelationErrorMetric:
1867 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1868 CompositeChannels,channel_distortion,exception);
1871 case PeakAbsoluteErrorMetric:
1873 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1874 CompositeChannels,channel_distortion,exception);
1877 case PeakSignalToNoiseRatioMetric:
1879 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1880 CompositeChannels,channel_distortion,exception);
1883 case PerceptualHashErrorMetric:
1885 status=GetPerceptualHashDistortion(image,reconstruct_image,
1886 CompositeChannels,channel_distortion,exception);
1889 case RootMeanSquaredErrorMetric:
1891 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1892 CompositeChannels,channel_distortion,exception);
1896 if (status == MagickFalse)
1898 channel_distortion=(
double *) RelinquishMagickMemory(channel_distortion);
1899 return((
double *) NULL);
1901 return(channel_distortion);
1951MagickExport MagickBooleanType IsImagesEqual(Image *image,
1952 const Image *reconstruct_image)
1969 mean_error_per_pixel;
1978 assert(image != (Image *) NULL);
1979 assert(image->signature == MagickCoreSignature);
1980 assert(reconstruct_image != (
const Image *) NULL);
1981 assert(reconstruct_image->signature == MagickCoreSignature);
1982 exception=(&image->exception);
1983 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1984 ThrowBinaryException(ImageError,
"ImageMorphologyDiffers",image->filename);
1987 mean_error_per_pixel=0.0;
1989 SetImageDistortionBounds(image,reconstruct_image,&columns,&rows);
1990 image_view=AcquireVirtualCacheView(image,exception);
1991 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1992 for (y=0; y < (ssize_t) rows; y++)
1995 *magick_restrict indexes,
1996 *magick_restrict reconstruct_indexes;
2005 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
2006 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
2007 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
2009 indexes=GetCacheViewVirtualIndexQueue(image_view);
2010 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
2011 for (x=0; x < (ssize_t) columns; x++)
2016 distance=fabs((
double) GetPixelRed(p)-(
double) GetPixelRed(q));
2017 mean_error_per_pixel+=distance;
2018 mean_error+=distance*distance;
2019 if (distance > maximum_error)
2020 maximum_error=distance;
2022 distance=fabs((
double) GetPixelGreen(p)-(
double) GetPixelGreen(q));
2023 mean_error_per_pixel+=distance;
2024 mean_error+=distance*distance;
2025 if (distance > maximum_error)
2026 maximum_error=distance;
2028 distance=fabs((
double) GetPixelBlue(p)-(
double) GetPixelBlue(q));
2029 mean_error_per_pixel+=distance;
2030 mean_error+=distance*distance;
2031 if (distance > maximum_error)
2032 maximum_error=distance;
2034 if (image->matte != MagickFalse)
2036 distance=fabs((
double) GetPixelOpacity(p)-(
double)
2037 GetPixelOpacity(q));
2038 mean_error_per_pixel+=distance;
2039 mean_error+=distance*distance;
2040 if (distance > maximum_error)
2041 maximum_error=distance;
2044 if ((image->colorspace == CMYKColorspace) &&
2045 (reconstruct_image->colorspace == CMYKColorspace))
2047 distance=fabs((
double) GetPixelIndex(indexes+x)-(
double)
2048 GetPixelIndex(reconstruct_indexes+x));
2049 mean_error_per_pixel+=distance;
2050 mean_error+=distance*distance;
2051 if (distance > maximum_error)
2052 maximum_error=distance;
2059 reconstruct_view=DestroyCacheView(reconstruct_view);
2060 image_view=DestroyCacheView(image_view);
2061 gamma=PerceptibleReciprocal(area);
2062 image->error.mean_error_per_pixel=gamma*mean_error_per_pixel;
2063 image->error.normalized_mean_error=gamma*QuantumScale*QuantumScale*mean_error;
2064 image->error.normalized_maximum_error=QuantumScale*maximum_error;
2065 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
2104static double GetSimilarityMetric(
const Image *image,
const Image *reference,
2105 const MetricType metric,
const ssize_t x_offset,
const ssize_t y_offset,
2106 ExceptionInfo *exception)
2120 SetGeometry(reference,&geometry);
2121 geometry.x=x_offset;
2122 geometry.y=y_offset;
2123 similarity_image=CropImage(image,&geometry,exception);
2124 if (similarity_image == (Image *) NULL)
2127 status=GetImageDistortion(similarity_image,reference,metric,&distortion,
2130 similarity_image=DestroyImage(similarity_image);
2134MagickExport Image *SimilarityImage(Image *image,
const Image *reference,
2135 RectangleInfo *offset,
double *similarity_metric,ExceptionInfo *exception)
2140 similarity_image=SimilarityMetricImage(image,reference,
2141 RootMeanSquaredErrorMetric,offset,similarity_metric,exception);
2142 return(similarity_image);
2145MagickExport Image *SimilarityMetricImage(Image *image,
const Image *reference,
2146 const MetricType metric,RectangleInfo *offset,
double *similarity_metric,
2147 ExceptionInfo *exception)
2149#define SimilarityImageTag "Similarity/Image"
2158 similarity_threshold;
2161 *similarity_image = (Image *) NULL;
2175 assert(image != (
const Image *) NULL);
2176 assert(image->signature == MagickCoreSignature);
2177 assert(exception != (ExceptionInfo *) NULL);
2178 assert(exception->signature == MagickCoreSignature);
2179 assert(offset != (RectangleInfo *) NULL);
2180 if (IsEventLogging() != MagickFalse)
2181 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
2182 SetGeometry(reference,offset);
2183 *similarity_metric=MagickMaximumValue;
2184 if (ValidateImageMorphology(image,reference) == MagickFalse)
2185 ThrowImageException(ImageError,
"ImageMorphologyDiffers");
2186 if ((image->columns < reference->columns) || (image->rows < reference->rows))
2188 (void) ThrowMagickException(&image->exception,GetMagickModule(),
2189 OptionWarning,
"GeometryDoesNotContainImage",
"`%s'",image->filename);
2190 return((Image *) NULL);
2192 if (metric == PeakAbsoluteErrorMetric)
2194 (void) ThrowMagickException(&image->exception,GetMagickModule(),
2195 OptionError,
"InvalidUseOfOption",
"`%s'",image->filename);
2196 return((Image *) NULL);
2198 similarity_image=CloneImage(image,image->columns,image->rows,MagickTrue,
2200 if (similarity_image == (Image *) NULL)
2201 return((Image *) NULL);
2202 similarity_image->depth=MAGICKCORE_QUANTUM_DEPTH;
2203 similarity_image->matte=MagickFalse;
2204 status=SetImageStorageClass(similarity_image,DirectClass);
2205 if (status == MagickFalse)
2207 InheritException(exception,&similarity_image->exception);
2208 return(DestroyImage(similarity_image));
2213 similarity_threshold=(-1.0);
2214 artifact=GetImageArtifact(image,
"compare:similarity-threshold");
2215 if (artifact != (
const char *) NULL)
2216 similarity_threshold=StringToDouble(artifact,(
char **) NULL);
2219 similarity_view=AcquireVirtualCacheView(similarity_image,exception);
2220 rows=similarity_image->rows;
2221 for (y=0; y < (ssize_t) rows; y++)
2232 if (status == MagickFalse)
2234 if (*similarity_metric <= similarity_threshold)
2236 q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
2238 if (q == (
const PixelPacket *) NULL)
2243 for (x=0; x < (ssize_t) similarity_image->columns; x++)
2245 if (*similarity_metric <= similarity_threshold)
2247 similarity=GetSimilarityMetric(image,reference,metric,x,y,exception);
2250 case NormalizedCrossCorrelationErrorMetric:
2251 case PeakSignalToNoiseRatioMetric:
2252 case UndefinedErrorMetric:
2254 similarity=1.0-similarity;
2257 case PerceptualHashErrorMetric:
2259 similarity=MagickMin(0.01*similarity,1.0);
2265 if (similarity < *similarity_metric)
2267 *similarity_metric=similarity;
2271 if ((metric == PeakSignalToNoiseRatioMetric) &&
2272 (fabs(similarity) < MagickEpsilon))
2273 similarity=1.0-similarity;
2276 case AbsoluteErrorMetric:
2277 case FuzzErrorMetric:
2278 case MeanAbsoluteErrorMetric:
2279 case MeanErrorPerPixelMetric:
2280 case MeanSquaredErrorMetric:
2281 case NormalizedCrossCorrelationErrorMetric:
2282 case PeakAbsoluteErrorMetric:
2283 case PeakSignalToNoiseRatioMetric:
2284 case PerceptualHashErrorMetric:
2285 case RootMeanSquaredErrorMetric:
2287 SetPixelRed(q,ClampToQuantum((
double) QuantumRange-QuantumRange*
2293 SetPixelRed(q,ClampToQuantum((
double) QuantumRange*similarity));
2297 SetPixelGreen(q,GetPixelRed(q));
2298 SetPixelBlue(q,GetPixelRed(q));
2301 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
2303 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2309 proceed=SetImageProgress(image,SimilarityImageTag,progress,image->rows);
2310 if (proceed == MagickFalse)
2314 similarity_view=DestroyCacheView(similarity_view);
2315 (void) SetImageType(similarity_image,GrayscaleType);
2316 if (status == MagickFalse)
2317 similarity_image=DestroyImage(similarity_image);
2318 return(similarity_image);