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 inline MagickBooleanType ValidateImageMorphology(
144 const Image *magick_restrict image,
145 const Image *magick_restrict reconstruct_image)
150 if (GetNumberChannels(image,DefaultChannels) !=
151 GetNumberChannels(reconstruct_image,DefaultChannels))
156MagickExport
Image *CompareImageChannels(
Image *image,
157 const Image *reconstruct_image,
const ChannelType channel,
158 const MetricType metric,
double *distortion,
ExceptionInfo *exception)
191 assert(image != (
Image *) NULL);
192 assert(image->signature == MagickCoreSignature);
193 assert(reconstruct_image != (
const Image *) NULL);
194 assert(reconstruct_image->signature == MagickCoreSignature);
195 assert(distortion != (
double *) NULL);
196 if (IsEventLogging() != MagickFalse)
197 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
199 if (metric != PerceptualHashErrorMetric)
200 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
201 ThrowImageException(ImageError,
"ImageMorphologyDiffers");
202 status=GetImageChannelDistortion(image,reconstruct_image,channel,metric,
203 distortion,exception);
204 if (status == MagickFalse)
205 return((
Image *) NULL);
206 clone_image=CloneImage(image,0,0,MagickTrue,exception);
207 if (clone_image == (
Image *) NULL)
208 return((
Image *) NULL);
209 (void) SetImageMask(clone_image,(
Image *) NULL);
210 difference_image=CloneImage(clone_image,0,0,MagickTrue,exception);
211 clone_image=DestroyImage(clone_image);
212 if (difference_image == (
Image *) NULL)
213 return((
Image *) NULL);
214 (void) SetImageAlphaChannel(difference_image,OpaqueAlphaChannel);
215 rows=MagickMax(image->rows,reconstruct_image->rows);
216 columns=MagickMax(image->columns,reconstruct_image->columns);
217 highlight_image=CloneImage(image,columns,rows,MagickTrue,exception);
218 if (highlight_image == (
Image *) NULL)
220 difference_image=DestroyImage(difference_image);
221 return((
Image *) NULL);
223 if (SetImageStorageClass(highlight_image,DirectClass) == MagickFalse)
225 InheritException(exception,&highlight_image->exception);
226 difference_image=DestroyImage(difference_image);
227 highlight_image=DestroyImage(highlight_image);
228 return((
Image *) NULL);
230 (void) SetImageMask(highlight_image,(
Image *) NULL);
231 (void) SetImageAlphaChannel(highlight_image,OpaqueAlphaChannel);
232 (void) QueryMagickColor(
"#f1001ecc",&highlight,exception);
233 artifact=GetImageArtifact(image,
"compare:highlight-color");
234 if (artifact != (
const char *) NULL)
235 (void) QueryMagickColor(artifact,&highlight,exception);
236 (void) QueryMagickColor(
"#ffffffcc",&lowlight,exception);
237 artifact=GetImageArtifact(image,
"compare:lowlight-color");
238 if (artifact != (
const char *) NULL)
239 (void) QueryMagickColor(artifact,&lowlight,exception);
240 if (highlight_image->colorspace == CMYKColorspace)
242 ConvertRGBToCMYK(&highlight);
243 ConvertRGBToCMYK(&lowlight);
249 fuzz=GetFuzzyColorDistance(image,reconstruct_image);
250 GetMagickPixelPacket(image,&zero);
251 image_view=AcquireVirtualCacheView(image,exception);
252 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
253 highlight_view=AcquireAuthenticCacheView(highlight_image,exception);
254#if defined(MAGICKCORE_OPENMP_SUPPORT)
255 #pragma omp parallel for schedule(static) shared(status) \
256 magick_number_threads(image,highlight_image,rows,1)
258 for (y=0; y < (ssize_t) rows; y++)
268 *magick_restrict indexes,
269 *magick_restrict reconstruct_indexes;
276 *magick_restrict highlight_indexes;
284 if (status == MagickFalse)
286 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
287 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
288 r=QueueCacheViewAuthenticPixels(highlight_view,0,y,columns,1,exception);
295 indexes=GetCacheViewVirtualIndexQueue(image_view);
296 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
297 highlight_indexes=GetCacheViewAuthenticIndexQueue(highlight_view);
299 reconstruct_pixel=zero;
300 for (x=0; x < (ssize_t) columns; x++)
305 SetMagickPixelPacket(image,p,indexes == (IndexPacket *) NULL ? NULL :
307 SetMagickPixelPacket(reconstruct_image,q,reconstruct_indexes ==
308 (IndexPacket *) NULL ? NULL : reconstruct_indexes+x,&reconstruct_pixel);
309 difference=MagickFalse;
310 if (channel == CompositeChannels)
312 if (IsMagickColorSimilar(&pixel,&reconstruct_pixel) == MagickFalse)
313 difference=MagickTrue;
323 Sa=QuantumScale*(image->matte != MagickFalse ? (double)
324 GetPixelAlpha(p) : ((double) QuantumRange-(double) OpaqueOpacity));
325 Da=QuantumScale*(image->matte != MagickFalse ? (double)
326 GetPixelAlpha(q) : ((double) QuantumRange-(double) OpaqueOpacity));
327 if ((channel & RedChannel) != 0)
329 pixel=Sa*(double) GetPixelRed(p)-Da*(double) GetPixelRed(q);
330 distance=pixel*pixel;
331 if (distance >= fuzz)
332 difference=MagickTrue;
334 if ((channel & GreenChannel) != 0)
336 pixel=Sa*(double) GetPixelGreen(p)-Da*(double) GetPixelGreen(q);
337 distance=pixel*pixel;
338 if (distance >= fuzz)
339 difference=MagickTrue;
341 if ((channel & BlueChannel) != 0)
343 pixel=Sa*(double) GetPixelBlue(p)-Da*(double) GetPixelBlue(q);
344 distance=pixel*pixel;
345 if (distance >= fuzz)
346 difference=MagickTrue;
348 if (((channel & OpacityChannel) != 0) &&
349 (image->matte != MagickFalse))
351 pixel=(double) GetPixelOpacity(p)-(double) GetPixelOpacity(q);
352 distance=pixel*pixel;
353 if (distance >= fuzz)
354 difference=MagickTrue;
356 if (((channel & IndexChannel) != 0) &&
357 (image->colorspace == CMYKColorspace))
359 pixel=Sa*(double) indexes[x]-Da*(double) reconstruct_indexes[x];
360 distance=pixel*pixel;
361 if (distance >= fuzz)
362 difference=MagickTrue;
365 if (difference != MagickFalse)
366 SetPixelPacket(highlight_image,&highlight,r,highlight_indexes ==
367 (IndexPacket *) NULL ? NULL : highlight_indexes+x);
369 SetPixelPacket(highlight_image,&lowlight,r,highlight_indexes ==
370 (IndexPacket *) NULL ? NULL : highlight_indexes+x);
375 sync=SyncCacheViewAuthenticPixels(highlight_view,exception);
376 if (sync == MagickFalse)
379 highlight_view=DestroyCacheView(highlight_view);
380 reconstruct_view=DestroyCacheView(reconstruct_view);
381 image_view=DestroyCacheView(image_view);
382 (void) CompositeImage(difference_image,image->compose,highlight_image,0,0);
383 highlight_image=DestroyImage(highlight_image);
384 if (status == MagickFalse)
385 difference_image=DestroyImage(difference_image);
386 return(difference_image);
425MagickExport MagickBooleanType GetImageDistortion(
Image *image,
426 const Image *reconstruct_image,
const MetricType metric,
double *distortion,
432 status=GetImageChannelDistortion(image,reconstruct_image,CompositeChannels,
433 metric,distortion,exception);
437static MagickBooleanType GetAbsoluteDistortion(
const Image *image,
438 const Image *reconstruct_image,
const ChannelType channel,
double *distortion,
462 fuzz=GetFuzzyColorDistance(image,reconstruct_image);
463 rows=MagickMax(image->rows,reconstruct_image->rows);
464 columns=MagickMax(image->columns,reconstruct_image->columns);
465 image_view=AcquireVirtualCacheView(image,exception);
466 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
467#if defined(MAGICKCORE_OPENMP_SUPPORT)
468 #pragma omp parallel for schedule(static) shared(status) \
469 magick_number_threads(image,image,rows,1)
471 for (y=0; y < (ssize_t) rows; y++)
474 channel_distortion[CompositeChannels+1];
477 *magick_restrict indexes,
478 *magick_restrict reconstruct_indexes;
488 if (status == MagickFalse)
490 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
491 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
497 indexes=GetCacheViewVirtualIndexQueue(image_view);
498 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
499 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
500 for (x=0; x < (ssize_t) columns; x++)
511 difference=MagickFalse;
512 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
513 ((double) QuantumRange-(double) OpaqueOpacity));
514 Da=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(q) :
515 ((double) QuantumRange-(double) OpaqueOpacity));
516 if ((channel & RedChannel) != 0)
518 pixel=Sa*(double) GetPixelRed(p)-Da*(double) GetPixelRed(q);
519 distance=pixel*pixel;
520 if (distance >= fuzz)
522 channel_distortion[RedChannel]++;
523 difference=MagickTrue;
526 if ((channel & GreenChannel) != 0)
528 pixel=Sa*(double) GetPixelGreen(p)-Da*(double) GetPixelGreen(q);
529 distance=pixel*pixel;
530 if (distance >= fuzz)
532 channel_distortion[GreenChannel]++;
533 difference=MagickTrue;
536 if ((channel & BlueChannel) != 0)
538 pixel=Sa*(double) GetPixelBlue(p)-Da*(double) GetPixelBlue(q);
539 distance=pixel*pixel;
540 if (distance >= fuzz)
542 channel_distortion[BlueChannel]++;
543 difference=MagickTrue;
546 if (((channel & OpacityChannel) != 0) &&
547 (image->matte != MagickFalse))
549 pixel=(double) GetPixelOpacity(p)-(double) GetPixelOpacity(q);
550 distance=pixel*pixel;
551 if (distance >= fuzz)
553 channel_distortion[OpacityChannel]++;
554 difference=MagickTrue;
557 if (((channel & IndexChannel) != 0) &&
558 (image->colorspace == CMYKColorspace))
560 pixel=Sa*(double) indexes[x]-Da*(
double) reconstruct_indexes[x];
561 distance=pixel*pixel;
562 if (distance >= fuzz)
564 channel_distortion[BlackChannel]++;
565 difference=MagickTrue;
568 if (difference != MagickFalse)
569 channel_distortion[CompositeChannels]++;
573#if defined(MAGICKCORE_OPENMP_SUPPORT)
574 #pragma omp critical (MagickCore_GetAbsoluteDistortion)
576 for (i=0; i <= (ssize_t) CompositeChannels; i++)
577 distortion[i]+=channel_distortion[i];
579 reconstruct_view=DestroyCacheView(reconstruct_view);
580 image_view=DestroyCacheView(image_view);
584static MagickBooleanType GetFuzzDistortion(
const Image *image,
585 const Image *reconstruct_image,
const ChannelType channel,
606 rows=MagickMax(image->rows,reconstruct_image->rows);
607 columns=MagickMax(image->columns,reconstruct_image->columns);
608 image_view=AcquireVirtualCacheView(image,exception);
609 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
610#if defined(MAGICKCORE_OPENMP_SUPPORT)
611 #pragma omp parallel for schedule(static) shared(status) \
612 magick_number_threads(image,image,rows,1)
614 for (y=0; y < (ssize_t) rows; y++)
617 channel_distortion[CompositeChannels+1];
620 *magick_restrict indexes,
621 *magick_restrict reconstruct_indexes;
631 if (status == MagickFalse)
633 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
634 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
640 indexes=GetCacheViewVirtualIndexQueue(image_view);
641 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
642 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
643 for (x=0; x < (ssize_t) columns; x++)
650 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
651 ((double) QuantumRange-(double) OpaqueOpacity));
652 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
653 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
655 if ((channel & RedChannel) != 0)
657 distance=QuantumScale*(Sa*(double) GetPixelRed(p)-Da*(double)
659 channel_distortion[RedChannel]+=distance*distance;
660 channel_distortion[CompositeChannels]+=distance*distance;
662 if ((channel & GreenChannel) != 0)
664 distance=QuantumScale*(Sa*(double) GetPixelGreen(p)-Da*(double)
666 channel_distortion[GreenChannel]+=distance*distance;
667 channel_distortion[CompositeChannels]+=distance*distance;
669 if ((channel & BlueChannel) != 0)
671 distance=QuantumScale*(Sa*(double) GetPixelBlue(p)-Da*(double)
673 channel_distortion[BlueChannel]+=distance*distance;
674 channel_distortion[CompositeChannels]+=distance*distance;
676 if (((channel & OpacityChannel) != 0) && ((image->matte != MagickFalse) ||
677 (reconstruct_image->matte != MagickFalse)))
679 distance=QuantumScale*((image->matte != MagickFalse ? (double)
680 GetPixelOpacity(p) : (double) OpaqueOpacity)-
681 (reconstruct_image->matte != MagickFalse ?
682 (double) GetPixelOpacity(q): (double) OpaqueOpacity));
683 channel_distortion[OpacityChannel]+=distance*distance;
684 channel_distortion[CompositeChannels]+=distance*distance;
686 if (((channel & IndexChannel) != 0) &&
687 (image->colorspace == CMYKColorspace) &&
688 (reconstruct_image->colorspace == CMYKColorspace))
690 distance=QuantumScale*(Sa*(double) GetPixelIndex(indexes+x)-
691 Da*(double) GetPixelIndex(reconstruct_indexes+x));
692 channel_distortion[BlackChannel]+=distance*distance;
693 channel_distortion[CompositeChannels]+=distance*distance;
698#if defined(MAGICKCORE_OPENMP_SUPPORT)
699 #pragma omp critical (MagickCore_GetFuzzDistortion)
701 for (i=0; i <= (ssize_t) CompositeChannels; i++)
702 distortion[i]+=channel_distortion[i];
704 reconstruct_view=DestroyCacheView(reconstruct_view);
705 image_view=DestroyCacheView(image_view);
706 for (i=0; i <= (ssize_t) CompositeChannels; i++)
707 distortion[i]/=((
double) columns*rows);
708 distortion[CompositeChannels]/=(double) GetNumberChannels(image,channel);
709 distortion[CompositeChannels]=sqrt(distortion[CompositeChannels]);
713static MagickBooleanType GetMeanAbsoluteDistortion(
const Image *image,
714 const Image *reconstruct_image,
const ChannelType channel,
735 rows=MagickMax(image->rows,reconstruct_image->rows);
736 columns=MagickMax(image->columns,reconstruct_image->columns);
737 image_view=AcquireVirtualCacheView(image,exception);
738 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
739#if defined(MAGICKCORE_OPENMP_SUPPORT)
740 #pragma omp parallel for schedule(static) shared(status) \
741 magick_number_threads(image,image,rows,1)
743 for (y=0; y < (ssize_t) rows; y++)
746 channel_distortion[CompositeChannels+1];
749 *magick_restrict indexes,
750 *magick_restrict reconstruct_indexes;
760 if (status == MagickFalse)
762 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
763 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
769 indexes=GetCacheViewVirtualIndexQueue(image_view);
770 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
771 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
772 for (x=0; x < (ssize_t) columns; x++)
779 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
780 ((double) QuantumRange-(double) OpaqueOpacity));
781 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
782 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
784 if ((channel & RedChannel) != 0)
786 distance=QuantumScale*fabs(Sa*(
double) GetPixelRed(p)-Da*
787 (
double) GetPixelRed(q));
788 channel_distortion[RedChannel]+=distance;
789 channel_distortion[CompositeChannels]+=distance;
791 if ((channel & GreenChannel) != 0)
793 distance=QuantumScale*fabs(Sa*(
double) GetPixelGreen(p)-Da*
794 (
double) GetPixelGreen(q));
795 channel_distortion[GreenChannel]+=distance;
796 channel_distortion[CompositeChannels]+=distance;
798 if ((channel & BlueChannel) != 0)
800 distance=QuantumScale*fabs(Sa*(
double) GetPixelBlue(p)-Da*
801 (
double) GetPixelBlue(q));
802 channel_distortion[BlueChannel]+=distance;
803 channel_distortion[CompositeChannels]+=distance;
805 if (((channel & OpacityChannel) != 0) &&
806 (image->matte != MagickFalse))
808 distance=QuantumScale*fabs((double) GetPixelOpacity(p)-(double)
810 channel_distortion[OpacityChannel]+=distance;
811 channel_distortion[CompositeChannels]+=distance;
813 if (((channel & IndexChannel) != 0) &&
814 (image->colorspace == CMYKColorspace))
816 distance=QuantumScale*fabs(Sa*(double) GetPixelIndex(indexes+x)-Da*
817 (double) GetPixelIndex(reconstruct_indexes+x));
818 channel_distortion[BlackChannel]+=distance;
819 channel_distortion[CompositeChannels]+=distance;
824#if defined(MAGICKCORE_OPENMP_SUPPORT)
825 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
827 for (i=0; i <= (ssize_t) CompositeChannels; i++)
828 distortion[i]+=channel_distortion[i];
830 reconstruct_view=DestroyCacheView(reconstruct_view);
831 image_view=DestroyCacheView(image_view);
832 for (i=0; i <= (ssize_t) CompositeChannels; i++)
833 distortion[i]/=((
double) columns*rows);
834 distortion[CompositeChannels]/=(double) GetNumberChannels(image,channel);
838static MagickBooleanType GetMeanErrorPerPixel(
Image *image,
839 const Image *reconstruct_image,
const ChannelType channel,
double *distortion,
866 rows=MagickMax(image->rows,reconstruct_image->rows);
867 columns=MagickMax(image->columns,reconstruct_image->columns);
868 image_view=AcquireVirtualCacheView(image,exception);
869 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
870 for (y=0; y < (ssize_t) rows; y++)
873 *magick_restrict indexes,
874 *magick_restrict reconstruct_indexes;
883 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
884 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
890 indexes=GetCacheViewVirtualIndexQueue(image_view);
891 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
892 for (x=0; x < (ssize_t) columns; x++)
899 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
900 ((double) QuantumRange-(double) OpaqueOpacity));
901 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
902 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
904 if ((channel & RedChannel) != 0)
906 distance=fabs(Sa*(
double) GetPixelRed(p)-Da*(
double) GetPixelRed(q));
907 distortion[RedChannel]+=distance;
908 distortion[CompositeChannels]+=distance;
909 mean_error+=distance*distance;
910 if (distance > maximum_error)
911 maximum_error=distance;
914 if ((channel & GreenChannel) != 0)
916 distance=fabs(Sa*(
double) GetPixelGreen(p)-Da*(
double)
918 distortion[GreenChannel]+=distance;
919 distortion[CompositeChannels]+=distance;
920 mean_error+=distance*distance;
921 if (distance > maximum_error)
922 maximum_error=distance;
925 if ((channel & BlueChannel) != 0)
927 distance=fabs(Sa*(
double) GetPixelBlue(p)-Da*(
double)
929 distortion[BlueChannel]+=distance;
930 distortion[CompositeChannels]+=distance;
931 mean_error+=distance*distance;
932 if (distance > maximum_error)
933 maximum_error=distance;
936 if (((channel & OpacityChannel) != 0) &&
937 (image->matte != MagickFalse))
939 distance=fabs((double) GetPixelOpacity(p)-
940 (double) GetPixelOpacity(q));
941 distortion[OpacityChannel]+=distance;
942 distortion[CompositeChannels]+=distance;
943 mean_error+=distance*distance;
944 if (distance > maximum_error)
945 maximum_error=distance;
948 if (((channel & IndexChannel) != 0) &&
949 (image->colorspace == CMYKColorspace) &&
950 (reconstruct_image->colorspace == CMYKColorspace))
952 distance=fabs(Sa*(double) GetPixelIndex(indexes+x)-Da*
953 (double) GetPixelIndex(reconstruct_indexes+x));
954 distortion[BlackChannel]+=distance;
955 distortion[CompositeChannels]+=distance;
956 mean_error+=distance*distance;
957 if (distance > maximum_error)
958 maximum_error=distance;
965 reconstruct_view=DestroyCacheView(reconstruct_view);
966 image_view=DestroyCacheView(image_view);
967 gamma=PerceptibleReciprocal(area);
968 image->error.mean_error_per_pixel=gamma*distortion[CompositeChannels];
969 image->error.normalized_mean_error=gamma*QuantumScale*QuantumScale*mean_error;
970 image->error.normalized_maximum_error=QuantumScale*maximum_error;
974static MagickBooleanType GetMeanSquaredDistortion(
const Image *image,
975 const Image *reconstruct_image,
const ChannelType channel,
996 rows=MagickMax(image->rows,reconstruct_image->rows);
997 columns=MagickMax(image->columns,reconstruct_image->columns);
998 image_view=AcquireVirtualCacheView(image,exception);
999 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1000#if defined(MAGICKCORE_OPENMP_SUPPORT)
1001 #pragma omp parallel for schedule(static) shared(status) \
1002 magick_number_threads(image,image,rows,1)
1004 for (y=0; y < (ssize_t) rows; y++)
1007 channel_distortion[CompositeChannels+1];
1010 *magick_restrict indexes,
1011 *magick_restrict reconstruct_indexes;
1021 if (status == MagickFalse)
1023 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1024 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1030 indexes=GetCacheViewVirtualIndexQueue(image_view);
1031 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1032 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
1033 for (x=0; x < (ssize_t) columns; x++)
1040 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
1041 ((double) QuantumRange-(double) OpaqueOpacity));
1042 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1043 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
1045 if ((channel & RedChannel) != 0)
1047 distance=QuantumScale*(Sa*(double) GetPixelRed(p)-Da*(double)
1049 channel_distortion[RedChannel]+=distance*distance;
1050 channel_distortion[CompositeChannels]+=distance*distance;
1052 if ((channel & GreenChannel) != 0)
1054 distance=QuantumScale*(Sa*(double) GetPixelGreen(p)-Da*(double)
1056 channel_distortion[GreenChannel]+=distance*distance;
1057 channel_distortion[CompositeChannels]+=distance*distance;
1059 if ((channel & BlueChannel) != 0)
1061 distance=QuantumScale*(Sa*(double) GetPixelBlue(p)-Da*(double)
1063 channel_distortion[BlueChannel]+=distance*distance;
1064 channel_distortion[CompositeChannels]+=distance*distance;
1066 if (((channel & OpacityChannel) != 0) &&
1067 (image->matte != MagickFalse))
1069 distance=QuantumScale*((double) GetPixelOpacity(p)-(double)
1070 GetPixelOpacity(q));
1071 channel_distortion[OpacityChannel]+=distance*distance;
1072 channel_distortion[CompositeChannels]+=distance*distance;
1074 if (((channel & IndexChannel) != 0) &&
1075 (image->colorspace == CMYKColorspace) &&
1076 (reconstruct_image->colorspace == CMYKColorspace))
1078 distance=QuantumScale*(Sa*(double) GetPixelIndex(indexes+x)-Da*
1079 (double) GetPixelIndex(reconstruct_indexes+x));
1080 channel_distortion[BlackChannel]+=distance*distance;
1081 channel_distortion[CompositeChannels]+=distance*distance;
1086#if defined(MAGICKCORE_OPENMP_SUPPORT)
1087 #pragma omp critical (MagickCore_GetMeanSquaredError)
1089 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1090 distortion[i]+=channel_distortion[i];
1092 reconstruct_view=DestroyCacheView(reconstruct_view);
1093 image_view=DestroyCacheView(image_view);
1094 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1095 distortion[i]/=((
double) columns*rows);
1096 distortion[CompositeChannels]/=(double) GetNumberChannels(image,channel);
1100static MagickBooleanType GetNormalizedCrossCorrelationDistortion(
1101 const Image *image,
const Image *reconstruct_image,
const ChannelType channel,
1104#define SimilarityImageTag "Similarity/Image"
1112 *reconstruct_statistics;
1115 alpha_variance[CompositeChannels+1],
1116 beta_variance[CompositeChannels+1];
1137 image_statistics=GetImageChannelStatistics(image,exception);
1138 reconstruct_statistics=GetImageChannelStatistics(reconstruct_image,exception);
1147 reconstruct_statistics);
1148 return(MagickFalse);
1150 (void) memset(distortion,0,(CompositeChannels+1)*
sizeof(*distortion));
1151 (void) memset(alpha_variance,0,(CompositeChannels+1)*
sizeof(*alpha_variance));
1152 (void) memset(beta_variance,0,(CompositeChannels+1)*
sizeof(*beta_variance));
1155 rows=MagickMax(image->rows,reconstruct_image->rows);
1156 columns=MagickMax(image->columns,reconstruct_image->columns);
1157 image_view=AcquireVirtualCacheView(image,exception);
1158 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1159 for (y=0; y < (ssize_t) rows; y++)
1162 *magick_restrict indexes,
1163 *magick_restrict reconstruct_indexes;
1172 if (status == MagickFalse)
1174 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1175 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1181 indexes=GetCacheViewVirtualIndexQueue(image_view);
1182 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1183 for (x=0; x < (ssize_t) columns; x++)
1191 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
1192 (double) QuantumRange);
1193 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1194 (double) GetPixelAlpha(q) : (double) QuantumRange);
1195 if ((channel & RedChannel) != 0)
1197 alpha=QuantumScale*(Sa*(double) GetPixelRed(p)-
1198 image_statistics[RedChannel].mean);
1199 beta=QuantumScale*(Da*(double) GetPixelRed(q)-
1200 reconstruct_statistics[RedChannel].mean);
1201 distortion[RedChannel]+=alpha*beta;
1202 alpha_variance[RedChannel]+=alpha*alpha;
1203 beta_variance[RedChannel]+=beta*beta;
1205 if ((channel & GreenChannel) != 0)
1207 alpha=QuantumScale*(Sa*(double) GetPixelGreen(p)-
1208 image_statistics[GreenChannel].mean);
1209 beta=QuantumScale*(Da*(double) GetPixelGreen(q)-
1210 reconstruct_statistics[GreenChannel].mean);
1211 distortion[GreenChannel]+=alpha*beta;
1212 alpha_variance[GreenChannel]+=alpha*alpha;
1213 beta_variance[GreenChannel]+=beta*beta;
1215 if ((channel & BlueChannel) != 0)
1217 alpha=QuantumScale*(Sa*(double) GetPixelBlue(p)-
1218 image_statistics[BlueChannel].mean);
1219 beta=QuantumScale*(Da*(double) GetPixelBlue(q)-
1220 reconstruct_statistics[BlueChannel].mean);
1221 distortion[BlueChannel]+=alpha*beta;
1222 alpha_variance[BlueChannel]+=alpha*alpha;
1223 beta_variance[BlueChannel]+=beta*beta;
1225 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
1227 alpha=QuantumScale*((double) GetPixelAlpha(p)-
1228 image_statistics[AlphaChannel].mean);
1229 beta=QuantumScale*((double) GetPixelAlpha(q)-
1230 reconstruct_statistics[AlphaChannel].mean);
1231 distortion[OpacityChannel]+=alpha*beta;
1232 alpha_variance[OpacityChannel]+=alpha*alpha;
1233 beta_variance[OpacityChannel]+=beta*beta;
1235 if (((channel & IndexChannel) != 0) &&
1236 (image->colorspace == CMYKColorspace) &&
1237 (reconstruct_image->colorspace == CMYKColorspace))
1239 alpha=QuantumScale*(Sa*(double) GetPixelIndex(indexes+x)-
1240 image_statistics[BlackChannel].mean);
1241 beta=QuantumScale*(Da*(double) GetPixelIndex(reconstruct_indexes+x)-
1242 reconstruct_statistics[BlackChannel].mean);
1243 distortion[BlackChannel]+=alpha*beta;
1244 alpha_variance[BlackChannel]+=alpha*alpha;
1245 beta_variance[BlackChannel]+=beta*beta;
1250 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1255#if defined(MAGICKCORE_OPENMP_SUPPORT)
1259 proceed=SetImageProgress(image,SimilarityImageTag,progress,rows);
1260 if (proceed == MagickFalse)
1264 reconstruct_view=DestroyCacheView(reconstruct_view);
1265 image_view=DestroyCacheView(image_view);
1269 for (i=0; i < (ssize_t) CompositeChannels; i++)
1271 distortion[i]/=sqrt(alpha_variance[i]*beta_variance[i]);
1272 if (fabs(distortion[i]) >= MagickEpsilon)
1273 distortion[CompositeChannels]+=distortion[i];
1275 distortion[CompositeChannels]=distortion[CompositeChannels]/
1276 GetNumberChannels(image,channel);
1281 reconstruct_statistics);
1287static MagickBooleanType GetPeakAbsoluteDistortion(
const Image *image,
1288 const Image *reconstruct_image,
const ChannelType channel,
1306 (void) memset(distortion,0,(CompositeChannels+1)*
sizeof(*distortion));
1307 rows=MagickMax(image->rows,reconstruct_image->rows);
1308 columns=MagickMax(image->columns,reconstruct_image->columns);
1309 image_view=AcquireVirtualCacheView(image,exception);
1310 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1311#if defined(MAGICKCORE_OPENMP_SUPPORT)
1312 #pragma omp parallel for schedule(static) shared(status) \
1313 magick_number_threads(image,image,rows,1)
1315 for (y=0; y < (ssize_t) rows; y++)
1318 channel_distortion[CompositeChannels+1];
1321 *magick_restrict indexes,
1322 *magick_restrict reconstruct_indexes;
1332 if (status == MagickFalse)
1334 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1335 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1341 indexes=GetCacheViewVirtualIndexQueue(image_view);
1342 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1343 (void) memset(channel_distortion,0,(CompositeChannels+1)*
1344 sizeof(*channel_distortion));
1345 for (x=0; x < (ssize_t) columns; x++)
1352 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
1353 ((double) QuantumRange-(double) OpaqueOpacity));
1354 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1355 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
1357 if ((channel & RedChannel) != 0)
1359 distance=QuantumScale*fabs(Sa*(
double) GetPixelRed(p)-Da*
1360 (
double) GetPixelRed(q));
1361 if (distance > channel_distortion[RedChannel])
1362 channel_distortion[RedChannel]=distance;
1363 if (distance > channel_distortion[CompositeChannels])
1364 channel_distortion[CompositeChannels]=distance;
1366 if ((channel & GreenChannel) != 0)
1368 distance=QuantumScale*fabs(Sa*(
double) GetPixelGreen(p)-Da*
1369 (
double) GetPixelGreen(q));
1370 if (distance > channel_distortion[GreenChannel])
1371 channel_distortion[GreenChannel]=distance;
1372 if (distance > channel_distortion[CompositeChannels])
1373 channel_distortion[CompositeChannels]=distance;
1375 if ((channel & BlueChannel) != 0)
1377 distance=QuantumScale*fabs(Sa*(
double) GetPixelBlue(p)-Da*
1378 (
double) GetPixelBlue(q));
1379 if (distance > channel_distortion[BlueChannel])
1380 channel_distortion[BlueChannel]=distance;
1381 if (distance > channel_distortion[CompositeChannels])
1382 channel_distortion[CompositeChannels]=distance;
1384 if (((channel & OpacityChannel) != 0) &&
1385 (image->matte != MagickFalse))
1387 distance=QuantumScale*fabs((double) GetPixelOpacity(p)-(double)
1388 GetPixelOpacity(q));
1389 if (distance > channel_distortion[OpacityChannel])
1390 channel_distortion[OpacityChannel]=distance;
1391 if (distance > channel_distortion[CompositeChannels])
1392 channel_distortion[CompositeChannels]=distance;
1394 if (((channel & IndexChannel) != 0) &&
1395 (image->colorspace == CMYKColorspace) &&
1396 (reconstruct_image->colorspace == CMYKColorspace))
1398 distance=QuantumScale*fabs(Sa*(double) GetPixelIndex(indexes+x)-Da*
1399 (double) GetPixelIndex(reconstruct_indexes+x));
1400 if (distance > channel_distortion[BlackChannel])
1401 channel_distortion[BlackChannel]=distance;
1402 if (distance > channel_distortion[CompositeChannels])
1403 channel_distortion[CompositeChannels]=distance;
1408#if defined(MAGICKCORE_OPENMP_SUPPORT)
1409 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
1411 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1412 if (channel_distortion[i] > distortion[i])
1413 distortion[i]=channel_distortion[i];
1415 reconstruct_view=DestroyCacheView(reconstruct_view);
1416 image_view=DestroyCacheView(image_view);
1420static MagickBooleanType GetPeakSignalToNoiseRatio(
const Image *image,
1421 const Image *reconstruct_image,
const ChannelType channel,
1427 status=GetMeanSquaredDistortion(image,reconstruct_image,channel,distortion,
1429 if ((channel & RedChannel) != 0)
1431 if ((fabs(distortion[RedChannel]) < MagickEpsilon) ||
1432 (fabs(distortion[RedChannel]) >= 1.0))
1433 distortion[RedChannel]=fabs(distortion[RedChannel]) < MagickEpsilon ?
1436 distortion[RedChannel]=(-10.0*MagickLog10(PerceptibleReciprocal(
1437 distortion[RedChannel])))/48.1647;
1439 if ((channel & GreenChannel) != 0)
1441 if ((fabs(distortion[GreenChannel]) < MagickEpsilon) ||
1442 (fabs(distortion[GreenChannel]) >= 1.0))
1443 distortion[GreenChannel]=fabs(distortion[GreenChannel]) <
1444 MagickEpsilon ? 0.0 : 1.0;
1446 distortion[GreenChannel]=(-10.0*MagickLog10(PerceptibleReciprocal(
1447 distortion[GreenChannel])))/48.1647;
1449 if ((channel & BlueChannel) != 0)
1451 if ((fabs(distortion[BlueChannel]) < MagickEpsilon) ||
1452 (fabs(distortion[BlueChannel]) >= 1.0))
1453 distortion[BlueChannel]=fabs(distortion[BlueChannel]) < MagickEpsilon ?
1456 distortion[BlueChannel]=(-10.0*MagickLog10(PerceptibleReciprocal(
1457 distortion[BlueChannel])))/48.1647;
1459 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
1461 if ((fabs(distortion[OpacityChannel]) < MagickEpsilon) ||
1462 (fabs(distortion[OpacityChannel]) >= 1.0))
1463 distortion[OpacityChannel]=fabs(distortion[OpacityChannel]) <
1464 MagickEpsilon ? 0.0 : 1.0;
1466 distortion[OpacityChannel]=(-10.0*MagickLog10(PerceptibleReciprocal(
1467 distortion[OpacityChannel])))/48.1647;
1469 if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
1471 if ((fabs(distortion[BlackChannel]) < MagickEpsilon) ||
1472 (fabs(distortion[BlackChannel]) >= 1.0))
1473 distortion[BlackChannel]=fabs(distortion[BlackChannel]) <
1474 MagickEpsilon ? 0.0 : 1.0;
1476 distortion[BlackChannel]=(-10.0*MagickLog10(PerceptibleReciprocal(
1477 distortion[BlackChannel])))/48.1647;
1482static MagickBooleanType GetPerceptualHashDistortion(
const Image *image,
1483 const Image *reconstruct_image,
const ChannelType channel,
double *distortion,
1499 image_phash=GetImageChannelPerceptualHash(image,exception);
1501 return(MagickFalse);
1502 reconstruct_phash=GetImageChannelPerceptualHash(reconstruct_image,exception);
1506 return(MagickFalse);
1508 for (i=0; i < MaximumNumberOfImageMoments; i++)
1513 if ((channel & RedChannel) != 0)
1515 difference=reconstruct_phash[RedChannel].P[i]-
1516 image_phash[RedChannel].P[i];
1517 distortion[RedChannel]+=difference*difference;
1518 distortion[CompositeChannels]+=difference*difference;
1520 if ((channel & GreenChannel) != 0)
1522 difference=reconstruct_phash[GreenChannel].P[i]-
1523 image_phash[GreenChannel].P[i];
1524 distortion[GreenChannel]+=difference*difference;
1525 distortion[CompositeChannels]+=difference*difference;
1527 if ((channel & BlueChannel) != 0)
1529 difference=reconstruct_phash[BlueChannel].P[i]-
1530 image_phash[BlueChannel].P[i];
1531 distortion[BlueChannel]+=difference*difference;
1532 distortion[CompositeChannels]+=difference*difference;
1534 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse) &&
1535 (reconstruct_image->matte != MagickFalse))
1537 difference=reconstruct_phash[OpacityChannel].P[i]-
1538 image_phash[OpacityChannel].P[i];
1539 distortion[OpacityChannel]+=difference*difference;
1540 distortion[CompositeChannels]+=difference*difference;
1542 if (((channel & IndexChannel) != 0) &&
1543 (image->colorspace == CMYKColorspace) &&
1544 (reconstruct_image->colorspace == CMYKColorspace))
1546 difference=reconstruct_phash[IndexChannel].P[i]-
1547 image_phash[IndexChannel].P[i];
1548 distortion[IndexChannel]+=difference*difference;
1549 distortion[CompositeChannels]+=difference*difference;
1555 for (i=0; i < MaximumNumberOfImageMoments; i++)
1560 if ((channel & RedChannel) != 0)
1562 difference=reconstruct_phash[RedChannel].Q[i]-
1563 image_phash[RedChannel].Q[i];
1564 distortion[RedChannel]+=difference*difference;
1565 distortion[CompositeChannels]+=difference*difference;
1567 if ((channel & GreenChannel) != 0)
1569 difference=reconstruct_phash[GreenChannel].Q[i]-
1570 image_phash[GreenChannel].Q[i];
1571 distortion[GreenChannel]+=difference*difference;
1572 distortion[CompositeChannels]+=difference*difference;
1574 if ((channel & BlueChannel) != 0)
1576 difference=reconstruct_phash[BlueChannel].Q[i]-
1577 image_phash[BlueChannel].Q[i];
1578 distortion[BlueChannel]+=difference*difference;
1579 distortion[CompositeChannels]+=difference*difference;
1581 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse) &&
1582 (reconstruct_image->matte != MagickFalse))
1584 difference=reconstruct_phash[OpacityChannel].Q[i]-
1585 image_phash[OpacityChannel].Q[i];
1586 distortion[OpacityChannel]+=difference*difference;
1587 distortion[CompositeChannels]+=difference*difference;
1589 if (((channel & IndexChannel) != 0) &&
1590 (image->colorspace == CMYKColorspace) &&
1591 (reconstruct_image->colorspace == CMYKColorspace))
1593 difference=reconstruct_phash[IndexChannel].Q[i]-
1594 image_phash[IndexChannel].Q[i];
1595 distortion[IndexChannel]+=difference*difference;
1596 distortion[CompositeChannels]+=difference*difference;
1608static MagickBooleanType GetRootMeanSquaredDistortion(
const Image *image,
1609 const Image *reconstruct_image,
const ChannelType channel,
double *distortion,
1615 status=GetMeanSquaredDistortion(image,reconstruct_image,channel,distortion,
1617 if ((channel & RedChannel) != 0)
1618 distortion[RedChannel]=sqrt(distortion[RedChannel]);
1619 if ((channel & GreenChannel) != 0)
1620 distortion[GreenChannel]=sqrt(distortion[GreenChannel]);
1621 if ((channel & BlueChannel) != 0)
1622 distortion[BlueChannel]=sqrt(distortion[BlueChannel]);
1623 if (((channel & OpacityChannel) != 0) &&
1624 (image->matte != MagickFalse))
1625 distortion[OpacityChannel]=sqrt(distortion[OpacityChannel]);
1626 if (((channel & IndexChannel) != 0) &&
1627 (image->colorspace == CMYKColorspace))
1628 distortion[BlackChannel]=sqrt(distortion[BlackChannel]);
1629 distortion[CompositeChannels]=sqrt(distortion[CompositeChannels]);
1633MagickExport MagickBooleanType GetImageChannelDistortion(
Image *image,
1634 const Image *reconstruct_image,
const ChannelType channel,
1635 const MetricType metric,
double *distortion,
ExceptionInfo *exception)
1638 *channel_distortion;
1646 assert(image != (
Image *) NULL);
1647 assert(image->signature == MagickCoreSignature);
1648 assert(reconstruct_image != (
const Image *) NULL);
1649 assert(reconstruct_image->signature == MagickCoreSignature);
1650 assert(distortion != (
double *) NULL);
1651 if (IsEventLogging() != MagickFalse)
1652 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
1654 if (metric != PerceptualHashErrorMetric)
1655 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1656 ThrowBinaryException(ImageError,
"ImageMorphologyDiffers",image->filename);
1660 length=CompositeChannels+1UL;
1661 channel_distortion=(
double *) AcquireQuantumMemory(length,
1662 sizeof(*channel_distortion));
1663 if (channel_distortion == (
double *) NULL)
1664 ThrowFatalException(ResourceLimitFatalError,
"MemoryAllocationFailed");
1665 (void) memset(channel_distortion,0,length*
sizeof(*channel_distortion));
1668 case AbsoluteErrorMetric:
1670 status=GetAbsoluteDistortion(image,reconstruct_image,channel,
1671 channel_distortion,exception);
1674 case FuzzErrorMetric:
1676 status=GetFuzzDistortion(image,reconstruct_image,channel,
1677 channel_distortion,exception);
1680 case MeanAbsoluteErrorMetric:
1682 status=GetMeanAbsoluteDistortion(image,reconstruct_image,channel,
1683 channel_distortion,exception);
1686 case MeanErrorPerPixelMetric:
1688 status=GetMeanErrorPerPixel(image,reconstruct_image,channel,
1689 channel_distortion,exception);
1692 case MeanSquaredErrorMetric:
1694 status=GetMeanSquaredDistortion(image,reconstruct_image,channel,
1695 channel_distortion,exception);
1698 case NormalizedCrossCorrelationErrorMetric:
1701 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1702 channel,channel_distortion,exception);
1705 case PeakAbsoluteErrorMetric:
1707 status=GetPeakAbsoluteDistortion(image,reconstruct_image,channel,
1708 channel_distortion,exception);
1711 case PeakSignalToNoiseRatioMetric:
1713 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,channel,
1714 channel_distortion,exception);
1717 case PerceptualHashErrorMetric:
1719 status=GetPerceptualHashDistortion(image,reconstruct_image,channel,
1720 channel_distortion,exception);
1723 case RootMeanSquaredErrorMetric:
1725 status=GetRootMeanSquaredDistortion(image,reconstruct_image,channel,
1726 channel_distortion,exception);
1730 *distortion=channel_distortion[CompositeChannels];
1731 channel_distortion=(
double *) RelinquishMagickMemory(channel_distortion);
1732 (void) FormatImageProperty(image,
"distortion",
"%.*g",GetMagickPrecision(),
1769MagickExport
double *GetImageChannelDistortions(
Image *image,
1770 const Image *reconstruct_image,
const MetricType metric,
1774 *channel_distortion;
1782 assert(image != (
Image *) NULL);
1783 assert(image->signature == MagickCoreSignature);
1784 assert(reconstruct_image != (
const Image *) NULL);
1785 assert(reconstruct_image->signature == MagickCoreSignature);
1786 if (IsEventLogging() != MagickFalse)
1787 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
1788 if (metric != PerceptualHashErrorMetric)
1789 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1791 (void) ThrowMagickException(&image->exception,GetMagickModule(),
1792 ImageError,
"ImageMorphologyDiffers",
"`%s'",image->filename);
1793 return((
double *) NULL);
1798 length=CompositeChannels+1UL;
1799 channel_distortion=(
double *) AcquireQuantumMemory(length,
1800 sizeof(*channel_distortion));
1801 if (channel_distortion == (
double *) NULL)
1802 ThrowFatalException(ResourceLimitFatalError,
"MemoryAllocationFailed");
1803 (void) memset(channel_distortion,0,length*
1804 sizeof(*channel_distortion));
1808 case AbsoluteErrorMetric:
1810 status=GetAbsoluteDistortion(image,reconstruct_image,CompositeChannels,
1811 channel_distortion,exception);
1814 case FuzzErrorMetric:
1816 status=GetFuzzDistortion(image,reconstruct_image,CompositeChannels,
1817 channel_distortion,exception);
1820 case MeanAbsoluteErrorMetric:
1822 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1823 CompositeChannels,channel_distortion,exception);
1826 case MeanErrorPerPixelMetric:
1828 status=GetMeanErrorPerPixel(image,reconstruct_image,CompositeChannels,
1829 channel_distortion,exception);
1832 case MeanSquaredErrorMetric:
1834 status=GetMeanSquaredDistortion(image,reconstruct_image,CompositeChannels,
1835 channel_distortion,exception);
1838 case NormalizedCrossCorrelationErrorMetric:
1841 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1842 CompositeChannels,channel_distortion,exception);
1845 case PeakAbsoluteErrorMetric:
1847 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1848 CompositeChannels,channel_distortion,exception);
1851 case PeakSignalToNoiseRatioMetric:
1853 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1854 CompositeChannels,channel_distortion,exception);
1857 case PerceptualHashErrorMetric:
1859 status=GetPerceptualHashDistortion(image,reconstruct_image,
1860 CompositeChannels,channel_distortion,exception);
1863 case RootMeanSquaredErrorMetric:
1865 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1866 CompositeChannels,channel_distortion,exception);
1870 if (status == MagickFalse)
1872 channel_distortion=(
double *) RelinquishMagickMemory(channel_distortion);
1873 return((
double *) NULL);
1875 return(channel_distortion);
1925MagickExport MagickBooleanType IsImagesEqual(
Image *image,
1926 const Image *reconstruct_image)
1943 mean_error_per_pixel;
1952 assert(image != (
Image *) NULL);
1953 assert(image->signature == MagickCoreSignature);
1954 assert(reconstruct_image != (
const Image *) NULL);
1955 assert(reconstruct_image->signature == MagickCoreSignature);
1956 exception=(&image->exception);
1957 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1958 ThrowBinaryException(ImageError,
"ImageMorphologyDiffers",image->filename);
1961 mean_error_per_pixel=0.0;
1963 rows=MagickMax(image->rows,reconstruct_image->rows);
1964 columns=MagickMax(image->columns,reconstruct_image->columns);
1965 image_view=AcquireVirtualCacheView(image,exception);
1966 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1967 for (y=0; y < (ssize_t) rows; y++)
1970 *magick_restrict indexes,
1971 *magick_restrict reconstruct_indexes;
1980 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1981 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1984 indexes=GetCacheViewVirtualIndexQueue(image_view);
1985 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1986 for (x=0; x < (ssize_t) columns; x++)
1991 distance=fabs((
double) GetPixelRed(p)-(
double) GetPixelRed(q));
1992 mean_error_per_pixel+=distance;
1993 mean_error+=distance*distance;
1994 if (distance > maximum_error)
1995 maximum_error=distance;
1997 distance=fabs((
double) GetPixelGreen(p)-(
double) GetPixelGreen(q));
1998 mean_error_per_pixel+=distance;
1999 mean_error+=distance*distance;
2000 if (distance > maximum_error)
2001 maximum_error=distance;
2003 distance=fabs((
double) GetPixelBlue(p)-(
double) GetPixelBlue(q));
2004 mean_error_per_pixel+=distance;
2005 mean_error+=distance*distance;
2006 if (distance > maximum_error)
2007 maximum_error=distance;
2009 if (image->matte != MagickFalse)
2011 distance=fabs((
double) GetPixelOpacity(p)-(
double)
2012 GetPixelOpacity(q));
2013 mean_error_per_pixel+=distance;
2014 mean_error+=distance*distance;
2015 if (distance > maximum_error)
2016 maximum_error=distance;
2019 if ((image->colorspace == CMYKColorspace) &&
2020 (reconstruct_image->colorspace == CMYKColorspace))
2022 distance=fabs((
double) GetPixelIndex(indexes+x)-(
double)
2023 GetPixelIndex(reconstruct_indexes+x));
2024 mean_error_per_pixel+=distance;
2025 mean_error+=distance*distance;
2026 if (distance > maximum_error)
2027 maximum_error=distance;
2034 reconstruct_view=DestroyCacheView(reconstruct_view);
2035 image_view=DestroyCacheView(image_view);
2036 gamma=PerceptibleReciprocal(area);
2037 image->error.mean_error_per_pixel=gamma*mean_error_per_pixel;
2038 image->error.normalized_mean_error=gamma*QuantumScale*QuantumScale*mean_error;
2039 image->error.normalized_maximum_error=QuantumScale*maximum_error;
2040 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
2079static double GetSimilarityMetric(
const Image *image,
const Image *reference,
2080 const MetricType metric,
const ssize_t x_offset,
const ssize_t y_offset,
2095 SetGeometry(reference,&geometry);
2096 geometry.x=x_offset;
2097 geometry.y=y_offset;
2098 similarity_image=CropImage(image,&geometry,exception);
2099 if (similarity_image == (
Image *) NULL)
2102 status=GetImageDistortion(similarity_image,reference,metric,&distortion,
2105 similarity_image=DestroyImage(similarity_image);
2109MagickExport
Image *SimilarityImage(
Image *image,
const Image *reference,
2115 similarity_image=SimilarityMetricImage(image,reference,
2116 RootMeanSquaredErrorMetric,offset,similarity_metric,exception);
2117 return(similarity_image);
2120MagickExport
Image *SimilarityMetricImage(
Image *image,
const Image *reference,
2121 const MetricType metric,
RectangleInfo *offset,
double *similarity_metric,
2124#define SimilarityImageTag "Similarity/Image"
2133 similarity_threshold;
2136 *similarity_image = (
Image *) NULL;
2150 assert(image != (
const Image *) NULL);
2151 assert(image->signature == MagickCoreSignature);
2153 assert(exception->signature == MagickCoreSignature);
2155 if (IsEventLogging() != MagickFalse)
2156 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
2157 SetGeometry(reference,offset);
2158 *similarity_metric=MagickMaximumValue;
2159 if (ValidateImageMorphology(image,reference) == MagickFalse)
2160 ThrowImageException(ImageError,
"ImageMorphologyDiffers");
2161 if ((image->columns < reference->columns) || (image->rows < reference->rows))
2163 (void) ThrowMagickException(&image->exception,GetMagickModule(),
2164 OptionWarning,
"GeometryDoesNotContainImage",
"`%s'",image->filename);
2165 return((
Image *) NULL);
2167 similarity_image=CloneImage(image,image->columns,image->rows,MagickTrue,
2169 if (similarity_image == (
Image *) NULL)
2170 return((
Image *) NULL);
2171 similarity_image->depth=MAGICKCORE_QUANTUM_DEPTH;
2172 similarity_image->matte=MagickFalse;
2173 status=SetImageStorageClass(similarity_image,DirectClass);
2174 if (status == MagickFalse)
2176 InheritException(exception,&similarity_image->exception);
2177 return(DestroyImage(similarity_image));
2182 similarity_threshold=(-1.0);
2183 artifact=GetImageArtifact(image,
"compare:similarity-threshold");
2184 if (artifact != (
const char *) NULL)
2185 similarity_threshold=StringToDouble(artifact,(
char **) NULL);
2188 similarity_view=AcquireVirtualCacheView(similarity_image,exception);
2189 rows=similarity_image->rows;
2190#if defined(MAGICKCORE_OPENMP_SUPPORT)
2191 #pragma omp parallel for schedule(static,1) \
2192 shared(progress,status,similarity_metric) \
2193 magick_number_threads(similarity_image,similarity_image,rows << 3,1)
2195 for (y=0; y < (ssize_t) rows; y++)
2206 if (status == MagickFalse)
2208#if defined(MAGICKCORE_OPENMP_SUPPORT)
2209 #pragma omp flush(similarity_metric)
2211 if (*similarity_metric <= similarity_threshold)
2213 q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
2220 for (x=0; x < (ssize_t) similarity_image->columns; x++)
2222#if defined(MAGICKCORE_OPENMP_SUPPORT)
2223 #pragma omp flush(similarity_metric)
2225 if (*similarity_metric <= similarity_threshold)
2227 similarity=GetSimilarityMetric(image,reference,metric,x,y,exception);
2228 if ((metric == NormalizedCrossCorrelationErrorMetric) ||
2229 (metric == UndefinedErrorMetric))
2230 similarity=1.0-similarity;
2231#if defined(MAGICKCORE_OPENMP_SUPPORT)
2232 #pragma omp critical (MagickCore_SimilarityImage)
2234 if (similarity < *similarity_metric)
2236 *similarity_metric=similarity;
2240 if (metric == PerceptualHashErrorMetric)
2241 similarity=MagickMin(0.01*similarity,1.0);
2244 case AbsoluteErrorMetric:
2245 case FuzzErrorMetric:
2246 case MeanAbsoluteErrorMetric:
2247 case MeanErrorPerPixelMetric:
2248 case MeanSquaredErrorMetric:
2249 case NormalizedCrossCorrelationErrorMetric:
2250 case PeakAbsoluteErrorMetric:
2251 case PerceptualHashErrorMetric:
2252 case RootMeanSquaredErrorMetric:
2254 SetPixelRed(q,ClampToQuantum((
double) QuantumRange-QuantumRange*
2260 SetPixelRed(q,ClampToQuantum((
double) QuantumRange*similarity));
2264 SetPixelGreen(q,GetPixelRed(q));
2265 SetPixelBlue(q,GetPixelRed(q));
2268 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
2270 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2275#if defined(MAGICKCORE_OPENMP_SUPPORT)
2279 proceed=SetImageProgress(image,SimilarityImageTag,progress,image->rows);
2280 if (proceed == MagickFalse)
2284 similarity_view=DestroyCacheView(similarity_view);
2285 (void) SetImageType(similarity_image,GrayscaleType);
2286 if (status == MagickFalse)
2287 similarity_image=DestroyImage(similarity_image);
2288 return(similarity_image);