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;
348 difference=MagickTrue;
350 if ((channel & GreenChannel) != 0)
352 pixel=Sa*(double) GetPixelGreen(p)-Da*(double) GetPixelGreen(q);
353 distance=pixel*pixel;
355 difference=MagickTrue;
357 if ((channel & BlueChannel) != 0)
359 pixel=Sa*(double) GetPixelBlue(p)-Da*(double) GetPixelBlue(q);
360 distance=pixel*pixel;
362 difference=MagickTrue;
364 if (((channel & OpacityChannel) != 0) &&
365 (image->matte != MagickFalse))
367 pixel=(double) GetPixelOpacity(p)-(double) GetPixelOpacity(q);
368 distance=pixel*pixel;
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;
378 difference=MagickTrue;
381 if (difference != MagickFalse)
382 SetPixelPacket(highlight_image,&highlight,r,highlight_indexes ==
383 (IndexPacket *) NULL ? NULL : highlight_indexes+x);
385 SetPixelPacket(highlight_image,&lowlight,r,highlight_indexes ==
386 (IndexPacket *) NULL ? NULL : highlight_indexes+x);
391 sync=SyncCacheViewAuthenticPixels(highlight_view,exception);
392 if (sync == MagickFalse)
395 highlight_view=DestroyCacheView(highlight_view);
396 reconstruct_view=DestroyCacheView(reconstruct_view);
397 image_view=DestroyCacheView(image_view);
398 (void) CompositeImage(difference_image,image->compose,highlight_image,0,0);
399 highlight_image=DestroyImage(highlight_image);
400 if (status == MagickFalse)
401 difference_image=DestroyImage(difference_image);
402 return(difference_image);
441MagickExport MagickBooleanType GetImageDistortion(Image *image,
442 const Image *reconstruct_image,
const MetricType metric,
double *distortion,
443 ExceptionInfo *exception)
448 status=GetImageChannelDistortion(image,reconstruct_image,CompositeChannels,
449 metric,distortion,exception);
453static MagickBooleanType GetAbsoluteDistortion(
const Image *image,
454 const Image *reconstruct_image,
const ChannelType channel,
double *distortion,
455 ExceptionInfo *exception)
478 fuzz=GetFuzzyColorDistance(image,reconstruct_image);
479 SetImageDistortionBounds(image,reconstruct_image,&columns,&rows);
480 image_view=AcquireVirtualCacheView(image,exception);
481 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
482#if defined(MAGICKCORE_OPENMP_SUPPORT)
483 #pragma omp parallel for schedule(static) shared(status) \
484 magick_number_threads(image,image,rows,1)
486 for (y=0; y < (ssize_t) rows; y++)
489 *magick_restrict indexes,
490 *magick_restrict reconstruct_indexes;
497 channel_distortion[CompositeChannels+1];
503 if (status == MagickFalse)
505 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
506 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
507 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
512 indexes=GetCacheViewVirtualIndexQueue(image_view);
513 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
514 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
515 for (x=0; x < (ssize_t) columns; x++)
525 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
526 ((double) QuantumRange-(double) OpaqueOpacity));
527 Da=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(q) :
528 ((double) QuantumRange-(double) OpaqueOpacity));
529 if ((channel & RedChannel) != 0)
531 delta=Sa*(double) GetPixelRed(p)-Da*(double)
533 if ((delta*delta) > fuzz)
535 channel_distortion[RedChannel]++;
539 if ((channel & GreenChannel) != 0)
541 delta=Sa*(double) GetPixelGreen(p)-Da*(double)
543 if ((delta*delta) > fuzz)
545 channel_distortion[RedChannel]++;
549 if ((channel & BlueChannel) != 0)
551 delta=Sa*(double) GetPixelBlue(p)-Da*(double)
553 if ((delta*delta) > fuzz)
555 channel_distortion[RedChannel]++;
559 if (((channel & OpacityChannel) != 0) &&
560 (image->matte != MagickFalse))
562 delta=(double) GetPixelOpacity(p)-(double)
564 if ((delta*delta) > fuzz)
566 channel_distortion[RedChannel]++;
570 if (((channel & IndexChannel) != 0) &&
571 (image->colorspace == CMYKColorspace))
573 delta=Sa*(double) indexes[x]-Da*(
double)
574 reconstruct_indexes[x];
575 if ((delta*delta) > fuzz)
577 channel_distortion[RedChannel]++;
582 channel_distortion[CompositeChannels]++;
586#if defined(MAGICKCORE_OPENMP_SUPPORT)
587 #pragma omp critical (MagickCore_GetAbsoluteDistortion)
589 for (i=0; i <= (ssize_t) CompositeChannels; i++)
590 distortion[i]+=channel_distortion[i];
592 reconstruct_view=DestroyCacheView(reconstruct_view);
593 image_view=DestroyCacheView(image_view);
594 distortion[CompositeChannels]/=((double) image->columns*image->rows);
598static MagickBooleanType GetFuzzDistortion(
const Image *image,
599 const Image *reconstruct_image,
const ChannelType channel,
600 double *distortion,ExceptionInfo *exception)
620 SetImageDistortionBounds(image,reconstruct_image,&columns,&rows);
621 image_view=AcquireVirtualCacheView(image,exception);
622 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
623#if defined(MAGICKCORE_OPENMP_SUPPORT)
624 #pragma omp parallel for schedule(static) shared(status) \
625 magick_number_threads(image,image,rows,1)
627 for (y=0; y < (ssize_t) rows; y++)
630 channel_distortion[CompositeChannels+1];
633 *magick_restrict indexes,
634 *magick_restrict reconstruct_indexes;
644 if (status == MagickFalse)
646 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
647 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
648 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
653 indexes=GetCacheViewVirtualIndexQueue(image_view);
654 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
655 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
656 for (x=0; x < (ssize_t) columns; x++)
663 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
664 ((double) QuantumRange-(double) OpaqueOpacity));
665 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
666 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
668 if ((channel & RedChannel) != 0)
670 distance=QuantumScale*(Sa*(double) GetPixelRed(p)-Da*(double)
672 channel_distortion[RedChannel]+=distance*distance;
673 channel_distortion[CompositeChannels]+=distance*distance;
675 if ((channel & GreenChannel) != 0)
677 distance=QuantumScale*(Sa*(double) GetPixelGreen(p)-Da*(double)
679 channel_distortion[GreenChannel]+=distance*distance;
680 channel_distortion[CompositeChannels]+=distance*distance;
682 if ((channel & BlueChannel) != 0)
684 distance=QuantumScale*(Sa*(double) GetPixelBlue(p)-Da*(double)
686 channel_distortion[BlueChannel]+=distance*distance;
687 channel_distortion[CompositeChannels]+=distance*distance;
689 if (((channel & OpacityChannel) != 0) && ((image->matte != MagickFalse) ||
690 (reconstruct_image->matte != MagickFalse)))
692 distance=QuantumScale*((image->matte != MagickFalse ? (double)
693 GetPixelOpacity(p) : (double) OpaqueOpacity)-
694 (reconstruct_image->matte != MagickFalse ?
695 (double) GetPixelOpacity(q): (double) OpaqueOpacity));
696 channel_distortion[OpacityChannel]+=distance*distance;
697 channel_distortion[CompositeChannels]+=distance*distance;
699 if (((channel & IndexChannel) != 0) &&
700 (image->colorspace == CMYKColorspace) &&
701 (reconstruct_image->colorspace == CMYKColorspace))
703 distance=QuantumScale*(Sa*(double) GetPixelIndex(indexes+x)-
704 Da*(double) GetPixelIndex(reconstruct_indexes+x));
705 channel_distortion[BlackChannel]+=distance*distance;
706 channel_distortion[CompositeChannels]+=distance*distance;
711#if defined(MAGICKCORE_OPENMP_SUPPORT)
712 #pragma omp critical (MagickCore_GetFuzzDistortion)
714 for (i=0; i <= (ssize_t) CompositeChannels; i++)
715 distortion[i]+=channel_distortion[i];
717 reconstruct_view=DestroyCacheView(reconstruct_view);
718 image_view=DestroyCacheView(image_view);
719 distortion[CompositeChannels]/=(double) GetNumberChannels(image,channel);
720 for (i=0; i <= (ssize_t) CompositeChannels; i++)
721 distortion[i]/=((
double) columns*rows);
722 distortion[CompositeChannels]=sqrt(distortion[CompositeChannels]);
726static MagickBooleanType GetMeanAbsoluteDistortion(
const Image *image,
727 const Image *reconstruct_image,
const ChannelType channel,
728 double *distortion,ExceptionInfo *exception)
746 SetImageDistortionBounds(image,reconstruct_image,&columns,&rows);
747 image_view=AcquireVirtualCacheView(image,exception);
748 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
749#if defined(MAGICKCORE_OPENMP_SUPPORT)
750 #pragma omp parallel for schedule(static) shared(status) \
751 magick_number_threads(image,image,rows,1)
753 for (y=0; y < (ssize_t) rows; y++)
756 channel_distortion[CompositeChannels+1];
759 *magick_restrict indexes,
760 *magick_restrict reconstruct_indexes;
770 if (status == MagickFalse)
772 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
773 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
774 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
779 indexes=GetCacheViewVirtualIndexQueue(image_view);
780 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
781 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
782 for (x=0; x < (ssize_t) columns; x++)
789 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
790 ((double) QuantumRange-(double) OpaqueOpacity));
791 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
792 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
794 if ((channel & RedChannel) != 0)
796 distance=QuantumScale*fabs(Sa*(
double) GetPixelRed(p)-Da*
797 (
double) GetPixelRed(q));
798 channel_distortion[RedChannel]+=distance;
799 channel_distortion[CompositeChannels]+=distance;
801 if ((channel & GreenChannel) != 0)
803 distance=QuantumScale*fabs(Sa*(
double) GetPixelGreen(p)-Da*
804 (
double) GetPixelGreen(q));
805 channel_distortion[GreenChannel]+=distance;
806 channel_distortion[CompositeChannels]+=distance;
808 if ((channel & BlueChannel) != 0)
810 distance=QuantumScale*fabs(Sa*(
double) GetPixelBlue(p)-Da*
811 (
double) GetPixelBlue(q));
812 channel_distortion[BlueChannel]+=distance;
813 channel_distortion[CompositeChannels]+=distance;
815 if (((channel & OpacityChannel) != 0) &&
816 (image->matte != MagickFalse))
818 distance=QuantumScale*fabs((double) GetPixelOpacity(p)-(double)
820 channel_distortion[OpacityChannel]+=distance;
821 channel_distortion[CompositeChannels]+=distance;
823 if (((channel & IndexChannel) != 0) &&
824 (image->colorspace == CMYKColorspace))
826 distance=QuantumScale*fabs(Sa*(double) GetPixelIndex(indexes+x)-Da*
827 (double) GetPixelIndex(reconstruct_indexes+x));
828 channel_distortion[BlackChannel]+=distance;
829 channel_distortion[CompositeChannels]+=distance;
834#if defined(MAGICKCORE_OPENMP_SUPPORT)
835 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
837 for (i=0; i <= (ssize_t) CompositeChannels; i++)
838 distortion[i]+=channel_distortion[i];
840 reconstruct_view=DestroyCacheView(reconstruct_view);
841 image_view=DestroyCacheView(image_view);
842 for (i=0; i <= (ssize_t) CompositeChannels; i++)
843 distortion[i]/=((
double) columns*rows);
844 distortion[CompositeChannels]/=(double) GetNumberChannels(image,channel);
848static MagickBooleanType GetMeanErrorPerPixel(Image *image,
849 const Image *reconstruct_image,
const ChannelType channel,
double *distortion,
850 ExceptionInfo *exception)
857 maximum_error = MagickMinimumValue,
872 SetImageDistortionBounds(image,reconstruct_image,&columns,&rows);
873 image_view=AcquireVirtualCacheView(image,exception);
874 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
875#if defined(MAGICKCORE_OPENMP_SUPPORT)
876 #pragma omp parallel for schedule(static) shared(status) \
877 magick_number_threads(image,image,rows,1)
879 for (y=0; y < (ssize_t) rows; y++)
882 channel_distortion[CompositeChannels+1],
883 local_maximum = MinimumValue,
884 local_mean_error = 0.0;
887 *magick_restrict indexes,
888 *magick_restrict reconstruct_indexes;
898 if (status == MagickFalse)
900 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
901 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
902 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
907 indexes=GetCacheViewVirtualIndexQueue(image_view);
908 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
909 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
910 for (x=0; x < (ssize_t) columns; x++)
917 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
918 ((double) QuantumRange-(double) OpaqueOpacity));
919 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
920 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
922 if ((channel & RedChannel) != 0)
924 distance=QuantumScale*fabs(Sa*(
double) GetPixelRed(p)-Da*
925 (
double) GetPixelRed(q));
926 channel_distortion[RedChannel]+=distance;
927 channel_distortion[CompositeChannels]+=distance;
928 local_mean_error+=distance*distance;
929 if (distance > local_maximum)
930 local_maximum=distance;
932 if ((channel & GreenChannel) != 0)
934 distance=QuantumScale*fabs(Sa*(
double) GetPixelGreen(p)-Da*
935 (
double) GetPixelGreen(q));
936 channel_distortion[GreenChannel]+=distance;
937 channel_distortion[CompositeChannels]+=distance;
938 local_mean_error+=distance*distance;
939 if (distance > local_maximum)
940 local_maximum=distance;
942 if ((channel & BlueChannel) != 0)
944 distance=QuantumScale*fabs(Sa*(
double) GetPixelBlue(p)-Da*
945 (
double) GetPixelBlue(q));
946 channel_distortion[BlueChannel]+=distance;
947 channel_distortion[CompositeChannels]+=distance;
948 local_mean_error+=distance*distance;
949 if (distance > local_maximum)
950 local_maximum=distance;
952 if (((channel & OpacityChannel) != 0) &&
953 (image->matte != MagickFalse))
955 distance=QuantumScale*fabs((double) GetPixelOpacity(p)-(double)
957 channel_distortion[OpacityChannel]+=distance;
958 channel_distortion[CompositeChannels]+=distance;
959 local_mean_error+=distance*distance;
960 if (distance > local_maximum)
961 local_maximum=distance;
963 if (((channel & IndexChannel) != 0) &&
964 (image->colorspace == CMYKColorspace))
966 distance=QuantumScale*fabs(Sa*(double) GetPixelIndex(indexes+x)-Da*
967 (double) GetPixelIndex(reconstruct_indexes+x));
968 channel_distortion[BlackChannel]+=distance;
969 channel_distortion[CompositeChannels]+=distance;
970 local_mean_error+=distance*distance;
971 if (distance > local_maximum)
972 local_maximum=distance;
977#if defined(MAGICKCORE_OPENMP_SUPPORT)
978 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
981 for (i=0; i <= (ssize_t) CompositeChannels; i++)
982 distortion[i]+=channel_distortion[i];
983 mean_error+=local_mean_error;
984 if (local_maximum > maximum_error)
985 maximum_error=local_maximum;
988 reconstruct_view=DestroyCacheView(reconstruct_view);
989 image_view=DestroyCacheView(image_view);
990 for (i=0; i <= (ssize_t) CompositeChannels; i++)
991 distortion[i]/=((
double) columns*rows);
992 distortion[CompositeChannels]/=(double) GetNumberChannels(image,channel);
993 image->error.mean_error_per_pixel=QuantumRange*distortion[CompositeChannels];
994 image->error.normalized_mean_error=mean_error;
995 image->error.normalized_maximum_error=maximum_error;
999static MagickBooleanType GetMeanSquaredDistortion(
const Image *image,
1000 const Image *reconstruct_image,
const ChannelType channel,
1001 double *distortion,ExceptionInfo *exception)
1022 SetImageDistortionBounds(image,reconstruct_image,&columns,&rows);
1023 image_view=AcquireVirtualCacheView(image,exception);
1024 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1025#if defined(MAGICKCORE_OPENMP_SUPPORT)
1026 #pragma omp parallel for schedule(static) shared(status) \
1027 magick_number_threads(image,image,rows,1)
1029 for (y=0; y < (ssize_t) rows; y++)
1032 channel_distortion[CompositeChannels+1];
1035 *magick_restrict indexes,
1036 *magick_restrict reconstruct_indexes;
1046 if (status == MagickFalse)
1048 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1049 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1050 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
1055 indexes=GetCacheViewVirtualIndexQueue(image_view);
1056 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1057 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
1058 for (x=0; x < (ssize_t) columns; x++)
1065 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
1066 ((double) QuantumRange-(double) OpaqueOpacity));
1067 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1068 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
1070 if ((channel & RedChannel) != 0)
1072 distance=QuantumScale*(Sa*(double) GetPixelRed(p)-Da*(double)
1074 channel_distortion[RedChannel]+=distance*distance;
1075 channel_distortion[CompositeChannels]+=distance*distance;
1077 if ((channel & GreenChannel) != 0)
1079 distance=QuantumScale*(Sa*(double) GetPixelGreen(p)-Da*(double)
1081 channel_distortion[GreenChannel]+=distance*distance;
1082 channel_distortion[CompositeChannels]+=distance*distance;
1084 if ((channel & BlueChannel) != 0)
1086 distance=QuantumScale*(Sa*(double) GetPixelBlue(p)-Da*(double)
1088 channel_distortion[BlueChannel]+=distance*distance;
1089 channel_distortion[CompositeChannels]+=distance*distance;
1091 if (((channel & OpacityChannel) != 0) &&
1092 (image->matte != MagickFalse))
1094 distance=QuantumScale*((double) GetPixelOpacity(p)-(double)
1095 GetPixelOpacity(q));
1096 channel_distortion[OpacityChannel]+=distance*distance;
1097 channel_distortion[CompositeChannels]+=distance*distance;
1099 if (((channel & IndexChannel) != 0) &&
1100 (image->colorspace == CMYKColorspace) &&
1101 (reconstruct_image->colorspace == CMYKColorspace))
1103 distance=QuantumScale*(Sa*(double) GetPixelIndex(indexes+x)-Da*
1104 (double) GetPixelIndex(reconstruct_indexes+x));
1105 channel_distortion[BlackChannel]+=distance*distance;
1106 channel_distortion[CompositeChannels]+=distance*distance;
1111#if defined(MAGICKCORE_OPENMP_SUPPORT)
1112 #pragma omp critical (MagickCore_GetMeanSquaredError)
1114 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1115 distortion[i]+=channel_distortion[i];
1117 reconstruct_view=DestroyCacheView(reconstruct_view);
1118 image_view=DestroyCacheView(image_view);
1119 area=PerceptibleReciprocal((
double) columns*rows);
1120 distortion[CompositeChannels]/=(double) GetNumberChannels(image,channel);
1121 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1122 distortion[i]*=area;
1126static MagickBooleanType GetNormalizedCrossCorrelationDistortion(
1127 const Image *image,
const Image *reconstruct_image,
const ChannelType channel,
1128 double *distortion,ExceptionInfo *exception)
1130#define SimilarityImageTag "Similarity/Image"
1138 *reconstruct_statistics;
1141 alpha_variance[CompositeChannels+1] = { 0.0 },
1142 beta_variance[CompositeChannels+1] = { 0.0 };
1161 image_statistics=GetImageChannelStatistics(image,exception);
1162 reconstruct_statistics=GetImageChannelStatistics(reconstruct_image,exception);
1163 if ((image_statistics == (ChannelStatistics *) NULL) ||
1164 (reconstruct_statistics == (ChannelStatistics *) NULL))
1166 if (image_statistics != (ChannelStatistics *) NULL)
1167 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1169 if (reconstruct_statistics != (ChannelStatistics *) NULL)
1170 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1171 reconstruct_statistics);
1172 return(MagickFalse);
1174 (void) memset(distortion,0,(CompositeChannels+1)*
sizeof(*distortion));
1175 (void) memset(alpha_variance,0,(CompositeChannels+1)*
sizeof(*alpha_variance));
1176 (void) memset(beta_variance,0,(CompositeChannels+1)*
sizeof(*beta_variance));
1179 SetImageDistortionBounds(image,reconstruct_image,&columns,&rows);
1180 image_view=AcquireVirtualCacheView(image,exception);
1181 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1182#if defined(MAGICKCORE_OPENMP_SUPPORT)
1183 #pragma omp parallel for schedule(static) shared(status) \
1184 magick_number_threads(image,image,rows,1)
1186 for (y=0; y < (ssize_t) rows; y++)
1189 *magick_restrict indexes,
1190 *magick_restrict reconstruct_indexes;
1197 channel_alpha_variance[CompositeChannels+1] = { 0.0 },
1198 channel_beta_variance[CompositeChannels+1] = { 0.0 },
1199 channel_distortion[CompositeChannels+1] = { 0.0 };
1204 if (status == MagickFalse)
1206 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1207 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1208 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
1213 indexes=GetCacheViewVirtualIndexQueue(image_view);
1214 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1215 for (x=0; x < (ssize_t) columns; x++)
1223 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
1224 (double) QuantumRange);
1225 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1226 (double) GetPixelAlpha(q) : (double) QuantumRange);
1227 if ((channel & RedChannel) != 0)
1229 alpha=QuantumScale*fabs(Sa*(
double) GetPixelRed(p)-
1230 image_statistics[RedChannel].mean);
1231 beta=QuantumScale*fabs(Da*(
double) GetPixelRed(q)-
1232 reconstruct_statistics[RedChannel].mean);
1233 channel_distortion[RedChannel]+=alpha*beta;
1234 channel_alpha_variance[RedChannel]+=alpha*alpha;
1235 channel_beta_variance[RedChannel]+=beta*beta;
1237 if ((channel & GreenChannel) != 0)
1239 alpha=QuantumScale*fabs(Sa*(
double) GetPixelGreen(p)-
1240 image_statistics[GreenChannel].mean);
1241 beta=QuantumScale*fabs(Da*(
double) GetPixelGreen(q)-
1242 reconstruct_statistics[GreenChannel].mean);
1243 channel_distortion[GreenChannel]+=alpha*beta;
1244 channel_alpha_variance[GreenChannel]+=alpha*alpha;
1245 channel_beta_variance[GreenChannel]+=beta*beta;
1247 if ((channel & BlueChannel) != 0)
1249 alpha=QuantumScale*fabs(Sa*(
double) GetPixelBlue(p)-
1250 image_statistics[BlueChannel].mean);
1251 beta=QuantumScale*fabs(Da*(
double) GetPixelBlue(q)-
1252 reconstruct_statistics[BlueChannel].mean);
1253 channel_distortion[BlueChannel]+=alpha*beta;
1254 channel_alpha_variance[BlueChannel]+=alpha*alpha;
1255 channel_beta_variance[BlueChannel]+=beta*beta;
1257 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
1259 alpha=QuantumScale*fabs((double) GetPixelAlpha(p)-
1260 image_statistics[AlphaChannel].mean);
1261 beta=QuantumScale*fabs((double) GetPixelAlpha(q)-
1262 reconstruct_statistics[AlphaChannel].mean);
1263 channel_distortion[OpacityChannel]+=alpha*beta;
1264 channel_alpha_variance[OpacityChannel]+=alpha*alpha;
1265 channel_beta_variance[OpacityChannel]+=beta*beta;
1267 if (((channel & IndexChannel) != 0) &&
1268 (image->colorspace == CMYKColorspace) &&
1269 (reconstruct_image->colorspace == CMYKColorspace))
1271 alpha=QuantumScale*fabs(Sa*(double) GetPixelIndex(indexes+x)-
1272 image_statistics[BlackChannel].mean);
1273 beta=QuantumScale*fabs(Da*(double) GetPixelIndex(reconstruct_indexes+
1274 x)-reconstruct_statistics[BlackChannel].mean);
1275 channel_distortion[BlackChannel]+=alpha*beta;
1276 channel_alpha_variance[BlackChannel]+=alpha*alpha;
1277 channel_beta_variance[BlackChannel]+=beta*beta;
1282#if defined(MAGICKCORE_OPENMP_SUPPORT)
1283 #pragma omp critical (GetNormalizedCrossCorrelationDistortion)
1289 for (j=0; j < (ssize_t) GetNumberChannels(image,channel); j++)
1291 distortion[j]+=channel_distortion[j];
1292 alpha_variance[j]+=channel_alpha_variance[j];
1293 beta_variance[j]+=channel_beta_variance[j];
1296 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1301#if defined(MAGICKCORE_OPENMP_SUPPORT)
1305 proceed=SetImageProgress(image,SimilarityImageTag,progress,rows);
1306 if (proceed == MagickFalse)
1310 reconstruct_view=DestroyCacheView(reconstruct_view);
1311 image_view=DestroyCacheView(image_view);
1315 for (i=0; i < (ssize_t) GetNumberChannels(image,channel); i++)
1317 distortion[i]/=((double) image->columns*image->rows);
1318 alpha_variance[i]/=((double) image->columns*image->rows);
1319 beta_variance[i]/=((double) image->columns*image->rows);
1320 distortion[i]/=sqrt(alpha_variance[i]*beta_variance[i]);
1321 distortion[CompositeChannels]+=distortion[i];
1326 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1327 reconstruct_statistics);
1328 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1333static MagickBooleanType GetPeakAbsoluteDistortion(
const Image *image,
1334 const Image *reconstruct_image,
const ChannelType channel,
1335 double *distortion,ExceptionInfo *exception)
1352 (void) memset(distortion,0,(CompositeChannels+1)*
sizeof(*distortion));
1353 SetImageDistortionBounds(image,reconstruct_image,&columns,&rows);
1354 image_view=AcquireVirtualCacheView(image,exception);
1355 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1356#if defined(MAGICKCORE_OPENMP_SUPPORT)
1357 #pragma omp parallel for schedule(static) shared(status) \
1358 magick_number_threads(image,image,rows,1)
1360 for (y=0; y < (ssize_t) rows; y++)
1363 channel_distortion[CompositeChannels+1];
1366 *magick_restrict indexes,
1367 *magick_restrict reconstruct_indexes;
1377 if (status == MagickFalse)
1379 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1380 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1381 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
1386 indexes=GetCacheViewVirtualIndexQueue(image_view);
1387 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1388 (void) memset(channel_distortion,0,(CompositeChannels+1)*
1389 sizeof(*channel_distortion));
1390 for (x=0; x < (ssize_t) columns; x++)
1397 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
1398 ((double) QuantumRange-(double) OpaqueOpacity));
1399 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1400 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
1402 if ((channel & RedChannel) != 0)
1404 distance=QuantumScale*fabs(Sa*(
double) GetPixelRed(p)-Da*
1405 (
double) GetPixelRed(q));
1406 if (distance > channel_distortion[RedChannel])
1407 channel_distortion[RedChannel]=distance;
1408 if (distance > channel_distortion[CompositeChannels])
1409 channel_distortion[CompositeChannels]=distance;
1411 if ((channel & GreenChannel) != 0)
1413 distance=QuantumScale*fabs(Sa*(
double) GetPixelGreen(p)-Da*
1414 (
double) GetPixelGreen(q));
1415 if (distance > channel_distortion[GreenChannel])
1416 channel_distortion[GreenChannel]=distance;
1417 if (distance > channel_distortion[CompositeChannels])
1418 channel_distortion[CompositeChannels]=distance;
1420 if ((channel & BlueChannel) != 0)
1422 distance=QuantumScale*fabs(Sa*(
double) GetPixelBlue(p)-Da*
1423 (
double) GetPixelBlue(q));
1424 if (distance > channel_distortion[BlueChannel])
1425 channel_distortion[BlueChannel]=distance;
1426 if (distance > channel_distortion[CompositeChannels])
1427 channel_distortion[CompositeChannels]=distance;
1429 if (((channel & OpacityChannel) != 0) &&
1430 (image->matte != MagickFalse))
1432 distance=QuantumScale*fabs((double) GetPixelOpacity(p)-(double)
1433 GetPixelOpacity(q));
1434 if (distance > channel_distortion[OpacityChannel])
1435 channel_distortion[OpacityChannel]=distance;
1436 if (distance > channel_distortion[CompositeChannels])
1437 channel_distortion[CompositeChannels]=distance;
1439 if (((channel & IndexChannel) != 0) &&
1440 (image->colorspace == CMYKColorspace) &&
1441 (reconstruct_image->colorspace == CMYKColorspace))
1443 distance=QuantumScale*fabs(Sa*(double) GetPixelIndex(indexes+x)-Da*
1444 (double) GetPixelIndex(reconstruct_indexes+x));
1445 if (distance > channel_distortion[BlackChannel])
1446 channel_distortion[BlackChannel]=distance;
1447 if (distance > channel_distortion[CompositeChannels])
1448 channel_distortion[CompositeChannels]=distance;
1453#if defined(MAGICKCORE_OPENMP_SUPPORT)
1454 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
1456 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1457 if (channel_distortion[i] > distortion[i])
1458 distortion[i]=channel_distortion[i];
1460 reconstruct_view=DestroyCacheView(reconstruct_view);
1461 image_view=DestroyCacheView(image_view);
1465static MagickBooleanType GetPeakSignalToNoiseRatio(
const Image *image,
1466 const Image *reconstruct_image,
const ChannelType channel,
1467 double *distortion,ExceptionInfo *exception)
1472 status=GetMeanSquaredDistortion(image,reconstruct_image,channel,distortion,
1474 if ((channel & RedChannel) != 0)
1475 distortion[RedChannel]=10.0*PerceptibleLog10(PerceptibleReciprocal(
1476 distortion[RedChannel]))/MagickPSNRDistortion;
1477 if ((channel & GreenChannel) != 0)
1478 distortion[GreenChannel]=10.0*PerceptibleLog10(PerceptibleReciprocal(
1479 distortion[GreenChannel]))/MagickPSNRDistortion;
1480 if ((channel & BlueChannel) != 0)
1481 distortion[BlueChannel]=10.0*PerceptibleLog10(PerceptibleReciprocal(
1482 distortion[BlueChannel]))/MagickPSNRDistortion;
1483 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
1484 distortion[OpacityChannel]=10.0*PerceptibleLog10(PerceptibleReciprocal(
1485 distortion[OpacityChannel]))/MagickPSNRDistortion;
1486 if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
1487 distortion[BlackChannel]=10.0*PerceptibleLog10(PerceptibleReciprocal(
1488 distortion[BlackChannel]))/MagickPSNRDistortion;
1489 distortion[CompositeChannels]=10.0*PerceptibleLog10(PerceptibleReciprocal(
1490 distortion[CompositeChannels]))/MagickPSNRDistortion;
1494static MagickBooleanType GetPerceptualHashDistortion(
const Image *image,
1495 const Image *reconstruct_image,
const ChannelType channel,
double *distortion,
1496 ExceptionInfo *exception)
1498#define PHASHNormalizationFactor 389.373723242
1500 ChannelPerceptualHash
1513 image_phash=GetImageChannelPerceptualHash(image,exception);
1514 if (image_phash == (ChannelPerceptualHash *) NULL)
1515 return(MagickFalse);
1516 reconstruct_phash=GetImageChannelPerceptualHash(reconstruct_image,exception);
1517 if (reconstruct_phash == (ChannelPerceptualHash *) NULL)
1519 image_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(image_phash);
1520 return(MagickFalse);
1522 for (i=0; i < MaximumNumberOfImageMoments; i++)
1527 if ((channel & RedChannel) != 0)
1529 difference=reconstruct_phash[RedChannel].P[i]-
1530 image_phash[RedChannel].P[i];
1531 if (IsNaN(difference) != 0)
1533 distortion[RedChannel]+=difference*difference;
1534 distortion[CompositeChannels]+=QuantumScale*difference*difference;
1536 if ((channel & GreenChannel) != 0)
1538 difference=reconstruct_phash[GreenChannel].P[i]-
1539 image_phash[GreenChannel].P[i];
1540 if (IsNaN(difference) != 0)
1542 distortion[GreenChannel]+=difference*difference;
1543 distortion[CompositeChannels]+=QuantumScale*difference*difference;
1545 if ((channel & BlueChannel) != 0)
1547 difference=reconstruct_phash[BlueChannel].P[i]-
1548 image_phash[BlueChannel].P[i];
1549 if (IsNaN(difference) != 0)
1551 distortion[BlueChannel]+=difference*difference;
1552 distortion[CompositeChannels]+=QuantumScale*difference*difference;
1554 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse) &&
1555 (reconstruct_image->matte != MagickFalse))
1557 difference=reconstruct_phash[OpacityChannel].P[i]-
1558 image_phash[OpacityChannel].P[i];
1559 if (IsNaN(difference) != 0)
1561 distortion[OpacityChannel]+=difference*difference;
1562 distortion[CompositeChannels]+=QuantumScale*difference*difference;
1564 if (((channel & IndexChannel) != 0) &&
1565 (image->colorspace == CMYKColorspace) &&
1566 (reconstruct_image->colorspace == CMYKColorspace))
1568 difference=reconstruct_phash[IndexChannel].P[i]-
1569 image_phash[IndexChannel].P[i];
1570 if (IsNaN(difference) != 0)
1572 distortion[IndexChannel]+=difference*difference;
1573 distortion[CompositeChannels]+=QuantumScale*difference*difference;
1579 for (i=0; i < MaximumNumberOfImageMoments; i++)
1584 if ((channel & RedChannel) != 0)
1586 difference=reconstruct_phash[RedChannel].Q[i]-
1587 image_phash[RedChannel].Q[i];
1588 if (IsNaN(difference) != 0)
1590 distortion[RedChannel]+=difference*difference;
1591 distortion[CompositeChannels]+=difference*difference;
1593 if ((channel & GreenChannel) != 0)
1595 difference=reconstruct_phash[GreenChannel].Q[i]-
1596 image_phash[GreenChannel].Q[i];
1597 if (IsNaN(difference) != 0)
1599 distortion[GreenChannel]+=difference*difference;
1600 distortion[CompositeChannels]+=difference*difference;
1602 if ((channel & BlueChannel) != 0)
1604 difference=reconstruct_phash[BlueChannel].Q[i]-
1605 image_phash[BlueChannel].Q[i];
1606 distortion[BlueChannel]+=difference*difference;
1607 distortion[CompositeChannels]+=difference*difference;
1609 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse) &&
1610 (reconstruct_image->matte != MagickFalse))
1612 difference=reconstruct_phash[OpacityChannel].Q[i]-
1613 image_phash[OpacityChannel].Q[i];
1614 if (IsNaN(difference) != 0)
1616 distortion[OpacityChannel]+=difference*difference;
1617 distortion[CompositeChannels]+=difference*difference;
1619 if (((channel & IndexChannel) != 0) &&
1620 (image->colorspace == CMYKColorspace) &&
1621 (reconstruct_image->colorspace == CMYKColorspace))
1623 difference=reconstruct_phash[IndexChannel].Q[i]-
1624 image_phash[IndexChannel].Q[i];
1625 if (IsNaN(difference) != 0)
1627 distortion[IndexChannel]+=difference*difference;
1628 distortion[CompositeChannels]+=difference*difference;
1631 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1632 distortion[i]/=PHASHNormalizationFactor;
1636 reconstruct_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(
1638 image_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(image_phash);
1642static MagickBooleanType GetRootMeanSquaredDistortion(
const Image *image,
1643 const Image *reconstruct_image,
const ChannelType channel,
double *distortion,
1644 ExceptionInfo *exception)
1649 status=GetMeanSquaredDistortion(image,reconstruct_image,channel,distortion,
1651 if ((channel & RedChannel) != 0)
1652 distortion[RedChannel]=sqrt(distortion[RedChannel]);
1653 if ((channel & GreenChannel) != 0)
1654 distortion[GreenChannel]=sqrt(distortion[GreenChannel]);
1655 if ((channel & BlueChannel) != 0)
1656 distortion[BlueChannel]=sqrt(distortion[BlueChannel]);
1657 if (((channel & OpacityChannel) != 0) &&
1658 (image->matte != MagickFalse))
1659 distortion[OpacityChannel]=sqrt(distortion[OpacityChannel]);
1660 if (((channel & IndexChannel) != 0) &&
1661 (image->colorspace == CMYKColorspace))
1662 distortion[BlackChannel]=sqrt(distortion[BlackChannel]);
1663 distortion[CompositeChannels]=sqrt(distortion[CompositeChannels]);
1667MagickExport MagickBooleanType GetImageChannelDistortion(Image *image,
1668 const Image *reconstruct_image,
const ChannelType channel,
1669 const MetricType metric,
double *distortion,ExceptionInfo *exception)
1672 *channel_distortion;
1680 assert(image != (Image *) NULL);
1681 assert(image->signature == MagickCoreSignature);
1682 assert(reconstruct_image != (
const Image *) NULL);
1683 assert(reconstruct_image->signature == MagickCoreSignature);
1684 assert(distortion != (
double *) NULL);
1685 if (IsEventLogging() != MagickFalse)
1686 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
1688 if (metric != PerceptualHashErrorMetric)
1689 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1690 ThrowBinaryException(ImageError,
"ImageMorphologyDiffers",image->filename);
1694 length=CompositeChannels+1UL;
1695 channel_distortion=(
double *) AcquireQuantumMemory(length,
1696 sizeof(*channel_distortion));
1697 if (channel_distortion == (
double *) NULL)
1698 ThrowFatalException(ResourceLimitFatalError,
"MemoryAllocationFailed");
1699 (void) memset(channel_distortion,0,length*
sizeof(*channel_distortion));
1702 case AbsoluteErrorMetric:
1704 status=GetAbsoluteDistortion(image,reconstruct_image,channel,
1705 channel_distortion,exception);
1708 case FuzzErrorMetric:
1710 status=GetFuzzDistortion(image,reconstruct_image,channel,
1711 channel_distortion,exception);
1714 case MeanAbsoluteErrorMetric:
1716 status=GetMeanAbsoluteDistortion(image,reconstruct_image,channel,
1717 channel_distortion,exception);
1720 case MeanErrorPerPixelMetric:
1722 status=GetMeanErrorPerPixel(image,reconstruct_image,channel,
1723 channel_distortion,exception);
1726 case MeanSquaredErrorMetric:
1728 status=GetMeanSquaredDistortion(image,reconstruct_image,channel,
1729 channel_distortion,exception);
1732 case NormalizedCrossCorrelationErrorMetric:
1735 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1736 channel,channel_distortion,exception);
1739 case PeakAbsoluteErrorMetric:
1741 status=GetPeakAbsoluteDistortion(image,reconstruct_image,channel,
1742 channel_distortion,exception);
1745 case PeakSignalToNoiseRatioMetric:
1747 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,channel,
1748 channel_distortion,exception);
1751 case PerceptualHashErrorMetric:
1753 status=GetPerceptualHashDistortion(image,reconstruct_image,channel,
1754 channel_distortion,exception);
1757 case RootMeanSquaredErrorMetric:
1759 status=GetRootMeanSquaredDistortion(image,reconstruct_image,channel,
1760 channel_distortion,exception);
1764 *distortion=channel_distortion[CompositeChannels];
1765 channel_distortion=(
double *) RelinquishMagickMemory(channel_distortion);
1766 (void) FormatImageProperty(image,
"distortion",
"%.*g",GetMagickPrecision(),
1803MagickExport
double *GetImageChannelDistortions(Image *image,
1804 const Image *reconstruct_image,
const MetricType metric,
1805 ExceptionInfo *exception)
1808 *channel_distortion;
1816 assert(image != (Image *) NULL);
1817 assert(image->signature == MagickCoreSignature);
1818 assert(reconstruct_image != (
const Image *) NULL);
1819 assert(reconstruct_image->signature == MagickCoreSignature);
1820 if (IsEventLogging() != MagickFalse)
1821 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
1822 if (metric != PerceptualHashErrorMetric)
1823 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1825 (void) ThrowMagickException(&image->exception,GetMagickModule(),
1826 ImageError,
"ImageMorphologyDiffers",
"`%s'",image->filename);
1827 return((
double *) NULL);
1832 length=CompositeChannels+1UL;
1833 channel_distortion=(
double *) AcquireQuantumMemory(length,
1834 sizeof(*channel_distortion));
1835 if (channel_distortion == (
double *) NULL)
1836 ThrowFatalException(ResourceLimitFatalError,
"MemoryAllocationFailed");
1837 (void) memset(channel_distortion,0,length*
1838 sizeof(*channel_distortion));
1842 case AbsoluteErrorMetric:
1844 status=GetAbsoluteDistortion(image,reconstruct_image,CompositeChannels,
1845 channel_distortion,exception);
1848 case FuzzErrorMetric:
1850 status=GetFuzzDistortion(image,reconstruct_image,CompositeChannels,
1851 channel_distortion,exception);
1854 case MeanAbsoluteErrorMetric:
1856 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1857 CompositeChannels,channel_distortion,exception);
1860 case MeanErrorPerPixelMetric:
1862 status=GetMeanErrorPerPixel(image,reconstruct_image,CompositeChannels,
1863 channel_distortion,exception);
1866 case MeanSquaredErrorMetric:
1868 status=GetMeanSquaredDistortion(image,reconstruct_image,CompositeChannels,
1869 channel_distortion,exception);
1872 case NormalizedCrossCorrelationErrorMetric:
1875 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1876 CompositeChannels,channel_distortion,exception);
1879 case PeakAbsoluteErrorMetric:
1881 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1882 CompositeChannels,channel_distortion,exception);
1885 case PeakSignalToNoiseRatioMetric:
1887 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1888 CompositeChannels,channel_distortion,exception);
1891 case PerceptualHashErrorMetric:
1893 status=GetPerceptualHashDistortion(image,reconstruct_image,
1894 CompositeChannels,channel_distortion,exception);
1897 case RootMeanSquaredErrorMetric:
1899 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1900 CompositeChannels,channel_distortion,exception);
1904 if (status == MagickFalse)
1906 channel_distortion=(
double *) RelinquishMagickMemory(channel_distortion);
1907 return((
double *) NULL);
1909 return(channel_distortion);
1959MagickExport MagickBooleanType IsImagesEqual(Image *image,
1960 const Image *reconstruct_image)
1977 mean_error_per_pixel;
1986 assert(image != (Image *) NULL);
1987 assert(image->signature == MagickCoreSignature);
1988 assert(reconstruct_image != (
const Image *) NULL);
1989 assert(reconstruct_image->signature == MagickCoreSignature);
1990 exception=(&image->exception);
1991 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1992 ThrowBinaryException(ImageError,
"ImageMorphologyDiffers",image->filename);
1995 mean_error_per_pixel=0.0;
1997 SetImageDistortionBounds(image,reconstruct_image,&columns,&rows);
1998 image_view=AcquireVirtualCacheView(image,exception);
1999 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
2000 for (y=0; y < (ssize_t) rows; y++)
2003 *magick_restrict indexes,
2004 *magick_restrict reconstruct_indexes;
2013 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
2014 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
2015 if ((p == (
const PixelPacket *) NULL) || (q == (
const PixelPacket *) NULL))
2017 indexes=GetCacheViewVirtualIndexQueue(image_view);
2018 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
2019 for (x=0; x < (ssize_t) columns; x++)
2024 distance=fabs((
double) GetPixelRed(p)-(
double) GetPixelRed(q));
2025 mean_error_per_pixel+=distance;
2026 mean_error+=distance*distance;
2027 if (distance > maximum_error)
2028 maximum_error=distance;
2030 distance=fabs((
double) GetPixelGreen(p)-(
double) GetPixelGreen(q));
2031 mean_error_per_pixel+=distance;
2032 mean_error+=distance*distance;
2033 if (distance > maximum_error)
2034 maximum_error=distance;
2036 distance=fabs((
double) GetPixelBlue(p)-(
double) GetPixelBlue(q));
2037 mean_error_per_pixel+=distance;
2038 mean_error+=distance*distance;
2039 if (distance > maximum_error)
2040 maximum_error=distance;
2042 if (image->matte != MagickFalse)
2044 distance=fabs((
double) GetPixelOpacity(p)-(
double)
2045 GetPixelOpacity(q));
2046 mean_error_per_pixel+=distance;
2047 mean_error+=distance*distance;
2048 if (distance > maximum_error)
2049 maximum_error=distance;
2052 if ((image->colorspace == CMYKColorspace) &&
2053 (reconstruct_image->colorspace == CMYKColorspace))
2055 distance=fabs((
double) GetPixelIndex(indexes+x)-(
double)
2056 GetPixelIndex(reconstruct_indexes+x));
2057 mean_error_per_pixel+=distance;
2058 mean_error+=distance*distance;
2059 if (distance > maximum_error)
2060 maximum_error=distance;
2067 reconstruct_view=DestroyCacheView(reconstruct_view);
2068 image_view=DestroyCacheView(image_view);
2069 gamma=PerceptibleReciprocal(area);
2070 image->error.mean_error_per_pixel=gamma*mean_error_per_pixel;
2071 image->error.normalized_mean_error=gamma*QuantumScale*QuantumScale*mean_error;
2072 image->error.normalized_maximum_error=QuantumScale*maximum_error;
2073 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
2112static double GetSimilarityMetric(
const Image *image,
const Image *reference,
2113 const MetricType metric,
const ssize_t x_offset,
const ssize_t y_offset,
2114 ExceptionInfo *exception)
2128 SetGeometry(reference,&geometry);
2129 geometry.x=x_offset;
2130 geometry.y=y_offset;
2131 similarity_image=CropImage(image,&geometry,exception);
2132 if (similarity_image == (Image *) NULL)
2135 status=GetImageDistortion(similarity_image,reference,metric,&distortion,
2138 similarity_image=DestroyImage(similarity_image);
2142MagickExport Image *SimilarityImage(Image *image,
const Image *reference,
2143 RectangleInfo *offset,
double *similarity_metric,ExceptionInfo *exception)
2148 similarity_image=SimilarityMetricImage(image,reference,
2149 RootMeanSquaredErrorMetric,offset,similarity_metric,exception);
2150 return(similarity_image);
2153MagickExport Image *SimilarityMetricImage(Image *image,
const Image *reference,
2154 const MetricType metric,RectangleInfo *offset,
double *similarity_metric,
2155 ExceptionInfo *exception)
2157#define SimilarityImageTag "Similarity/Image"
2176 similarity_threshold;
2179 *similarity_image = (Image *) NULL;
2188 similarity_info = { MagickMaximumValue, 0, 0 };
2196 assert(image != (
const Image *) NULL);
2197 assert(image->signature == MagickCoreSignature);
2198 assert(exception != (ExceptionInfo *) NULL);
2199 assert(exception->signature == MagickCoreSignature);
2200 assert(offset != (RectangleInfo *) NULL);
2201 if (IsEventLogging() != MagickFalse)
2202 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
2203 SetGeometry(reference,offset);
2204 *similarity_metric=MagickMaximumValue;
2205 if (ValidateImageMorphology(image,reference) == MagickFalse)
2206 ThrowImageException(ImageError,
"ImageMorphologyDiffers");
2207 if ((image->columns < reference->columns) || (image->rows < reference->rows))
2209 (void) ThrowMagickException(&image->exception,GetMagickModule(),
2210 OptionWarning,
"GeometryDoesNotContainImage",
"`%s'",image->filename);
2211 return((Image *) NULL);
2213 if (metric == PeakAbsoluteErrorMetric)
2215 (void) ThrowMagickException(&image->exception,GetMagickModule(),
2216 OptionError,
"InvalidUseOfOption",
"`%s'",image->filename);
2217 return((Image *) NULL);
2219 similarity_image=CloneImage(image,image->columns,image->rows,MagickTrue,
2221 if (similarity_image == (Image *) NULL)
2222 return((Image *) NULL);
2223 similarity_image->depth=32;
2224 similarity_image->colorspace=GRAYColorspace;
2225 similarity_image->matte=MagickFalse;
2226 status=SetImageStorageClass(similarity_image,DirectClass);
2227 if (status == MagickFalse)
2229 InheritException(exception,&similarity_image->exception);
2230 return(DestroyImage(similarity_image));
2235 similarity_threshold=(-1.0);
2236 artifact=GetImageArtifact(image,
"compare:similarity-threshold");
2237 if (artifact != (
const char *) NULL)
2238 similarity_threshold=StringToDouble(artifact,(
char **) NULL);
2241 similarity_view=AcquireVirtualCacheView(similarity_image,exception);
2242 rows=similarity_image->rows;
2243#if defined(MAGICKCORE_OPENMP_SUPPORT)
2244 #pragma omp parallel for schedule(static) shared(status) \
2245 magick_number_threads(image,reference,rows << 2,1)
2247 for (y=0; y < (ssize_t) rows; y++)
2259 channel_similarity = { MagickMaximumValue, 0, 0 };
2261 if (status == MagickFalse)
2263 if (similarity_info.similarity <= similarity_threshold)
2265 q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
2267 if (q == (
const PixelPacket *) NULL)
2272 for (x=0; x < (ssize_t) similarity_image->columns; x++)
2274 if (similarity_info.similarity <= similarity_threshold)
2276 similarity=GetSimilarityMetric(image,reference,metric,x,y,exception);
2279 case NormalizedCrossCorrelationErrorMetric:
2280 case PeakSignalToNoiseRatioMetric:
2281 case UndefinedErrorMetric:
2283 similarity=1.0-similarity;
2289 if (similarity < channel_similarity.similarity)
2291 channel_similarity.similarity=similarity;
2292 channel_similarity.x=x;
2293 channel_similarity.y=y;
2297 case AbsoluteErrorMetric:
2298 case FuzzErrorMetric:
2299 case MeanAbsoluteErrorMetric:
2300 case MeanErrorPerPixelMetric:
2301 case MeanSquaredErrorMetric:
2302 case NormalizedCrossCorrelationErrorMetric:
2303 case PeakAbsoluteErrorMetric:
2304 case PeakSignalToNoiseRatioMetric:
2305 case PerceptualHashErrorMetric:
2306 case RootMeanSquaredErrorMetric:
2308 SetPixelRed(q,ClampToQuantum((
double) QuantumRange-QuantumRange*
2314 SetPixelRed(q,ClampToQuantum((
double) QuantumRange*similarity));
2318 SetPixelGreen(q,GetPixelRed(q));
2319 SetPixelBlue(q,GetPixelRed(q));
2322#if defined(MAGICKCORE_OPENMP_SUPPORT)
2323 #pragma omp critical (MagickCore_SimilarityMetricImage)
2325 if (channel_similarity.similarity < similarity_info.similarity)
2326 similarity_info=channel_similarity;
2327 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
2329 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2335 proceed=SetImageProgress(image,SimilarityImageTag,progress,image->rows);
2336 if (proceed == MagickFalse)
2340 similarity_view=DestroyCacheView(similarity_view);
2341 *similarity_metric=similarity_info.similarity;
2342 offset->x=similarity_info.x;
2343 offset->y=similarity_info.y;
2344 if (status == MagickFalse)
2345 similarity_image=DestroyImage(similarity_image);
2346 return(similarity_image);