43 #include "magick/studio.h"
44 #include "magick/artifact.h"
45 #include "magick/cache-view.h"
46 #include "magick/channel.h"
47 #include "magick/client.h"
48 #include "magick/color.h"
49 #include "magick/color-private.h"
50 #include "magick/colorspace.h"
51 #include "magick/colorspace-private.h"
52 #include "magick/compare.h"
53 #include "magick/composite-private.h"
54 #include "magick/constitute.h"
55 #include "magick/exception-private.h"
56 #include "magick/geometry.h"
57 #include "magick/image-private.h"
58 #include "magick/list.h"
59 #include "magick/log.h"
60 #include "magick/memory_.h"
61 #include "magick/monitor.h"
62 #include "magick/monitor-private.h"
63 #include "magick/option.h"
64 #include "magick/pixel-private.h"
65 #include "magick/property.h"
66 #include "magick/resource_.h"
67 #include "magick/string_.h"
68 #include "magick/string-private.h"
69 #include "magick/statistic.h"
70 #include "magick/thread-private.h"
71 #include "magick/transform.h"
72 #include "magick/utility.h"
73 #include "magick/version.h"
111 MagickExport
Image *CompareImages(
Image *image,
const Image *reconstruct_image,
112 const MetricType metric,
double *distortion,
ExceptionInfo *exception)
117 highlight_image=CompareImageChannels(image,reconstruct_image,
118 CompositeChannels,metric,distortion,exception);
119 return(highlight_image);
122 static size_t GetNumberChannels(
const Image *image,
const ChannelType channel)
128 if ((channel & RedChannel) != 0)
130 if ((channel & GreenChannel) != 0)
132 if ((channel & BlueChannel) != 0)
134 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
136 if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
138 return(channels == 0 ? 1UL : channels);
141 static inline MagickBooleanType ValidateImageMorphology(
142 const Image *magick_restrict image,
143 const Image *magick_restrict reconstruct_image)
148 if (GetNumberChannels(image,DefaultChannels) !=
149 GetNumberChannels(reconstruct_image,DefaultChannels))
154 MagickExport
Image *CompareImageChannels(
Image *image,
155 const Image *reconstruct_image,
const ChannelType channel,
156 const MetricType metric,
double *distortion,
ExceptionInfo *exception)
189 assert(image != (
Image *) NULL);
190 assert(image->signature == MagickCoreSignature);
191 assert(reconstruct_image != (
const Image *) NULL);
192 assert(reconstruct_image->signature == MagickCoreSignature);
193 assert(distortion != (
double *) NULL);
194 if (IsEventLogging() != MagickFalse)
195 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
197 if (metric != PerceptualHashErrorMetric)
198 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
199 ThrowImageException(ImageError,
"ImageMorphologyDiffers");
200 status=GetImageChannelDistortion(image,reconstruct_image,channel,metric,
201 distortion,exception);
202 if (status == MagickFalse)
203 return((
Image *) NULL);
204 clone_image=CloneImage(image,0,0,MagickTrue,exception);
205 if (clone_image == (
Image *) NULL)
206 return((
Image *) NULL);
207 (void) SetImageMask(clone_image,(
Image *) NULL);
208 difference_image=CloneImage(clone_image,0,0,MagickTrue,exception);
209 clone_image=DestroyImage(clone_image);
210 if (difference_image == (
Image *) NULL)
211 return((
Image *) NULL);
212 (void) SetImageAlphaChannel(difference_image,OpaqueAlphaChannel);
213 rows=MagickMax(image->rows,reconstruct_image->rows);
214 columns=MagickMax(image->columns,reconstruct_image->columns);
215 highlight_image=CloneImage(image,columns,rows,MagickTrue,exception);
216 if (highlight_image == (
Image *) NULL)
218 difference_image=DestroyImage(difference_image);
219 return((
Image *) NULL);
221 if (SetImageStorageClass(highlight_image,DirectClass) == MagickFalse)
223 InheritException(exception,&highlight_image->exception);
224 difference_image=DestroyImage(difference_image);
225 highlight_image=DestroyImage(highlight_image);
226 return((
Image *) NULL);
228 (void) SetImageMask(highlight_image,(
Image *) NULL);
229 (void) SetImageAlphaChannel(highlight_image,OpaqueAlphaChannel);
230 (void) QueryMagickColor(
"#f1001ecc",&highlight,exception);
231 artifact=GetImageArtifact(image,
"compare:highlight-color");
232 if (artifact != (
const char *) NULL)
233 (void) QueryMagickColor(artifact,&highlight,exception);
234 (void) QueryMagickColor(
"#ffffffcc",&lowlight,exception);
235 artifact=GetImageArtifact(image,
"compare:lowlight-color");
236 if (artifact != (
const char *) NULL)
237 (void) QueryMagickColor(artifact,&lowlight,exception);
238 if (highlight_image->colorspace == CMYKColorspace)
240 ConvertRGBToCMYK(&highlight);
241 ConvertRGBToCMYK(&lowlight);
247 fuzz=GetFuzzyColorDistance(image,reconstruct_image);
248 GetMagickPixelPacket(image,&zero);
249 image_view=AcquireVirtualCacheView(image,exception);
250 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
251 highlight_view=AcquireAuthenticCacheView(highlight_image,exception);
252 #if defined(MAGICKCORE_OPENMP_SUPPORT)
253 #pragma omp parallel for schedule(static) shared(status) \
254 magick_number_threads(image,highlight_image,rows,1)
256 for (y=0; y < (ssize_t) rows; y++)
266 *magick_restrict indexes,
267 *magick_restrict reconstruct_indexes;
274 *magick_restrict highlight_indexes;
282 if (status == MagickFalse)
284 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
285 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
286 r=QueueCacheViewAuthenticPixels(highlight_view,0,y,columns,1,exception);
293 indexes=GetCacheViewVirtualIndexQueue(image_view);
294 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
295 highlight_indexes=GetCacheViewAuthenticIndexQueue(highlight_view);
297 reconstruct_pixel=zero;
298 for (x=0; x < (ssize_t) columns; x++)
303 SetMagickPixelPacket(image,p,indexes == (IndexPacket *) NULL ? NULL :
305 SetMagickPixelPacket(reconstruct_image,q,reconstruct_indexes ==
306 (IndexPacket *) NULL ? NULL : reconstruct_indexes+x,&reconstruct_pixel);
307 difference=MagickFalse;
308 if (channel == CompositeChannels)
310 if (IsMagickColorSimilar(&pixel,&reconstruct_pixel) == MagickFalse)
311 difference=MagickTrue;
321 Sa=QuantumScale*(image->matte != MagickFalse ? GetPixelAlpha(p) :
322 (QuantumRange-OpaqueOpacity));
323 Da=QuantumScale*(image->matte != MagickFalse ? GetPixelAlpha(q) :
324 (QuantumRange-OpaqueOpacity));
325 if ((channel & RedChannel) != 0)
327 pixel=Sa*GetPixelRed(p)-Da*GetPixelRed(q);
328 distance=pixel*pixel;
329 if (distance >= fuzz)
330 difference=MagickTrue;
332 if ((channel & GreenChannel) != 0)
334 pixel=Sa*GetPixelGreen(p)-Da*GetPixelGreen(q);
335 distance=pixel*pixel;
336 if (distance >= fuzz)
337 difference=MagickTrue;
339 if ((channel & BlueChannel) != 0)
341 pixel=Sa*GetPixelBlue(p)-Da*GetPixelBlue(q);
342 distance=pixel*pixel;
343 if (distance >= fuzz)
344 difference=MagickTrue;
346 if (((channel & OpacityChannel) != 0) &&
347 (image->matte != MagickFalse))
349 pixel=(double) GetPixelOpacity(p)-GetPixelOpacity(q);
350 distance=pixel*pixel;
351 if (distance >= fuzz)
352 difference=MagickTrue;
354 if (((channel & IndexChannel) != 0) &&
355 (image->colorspace == CMYKColorspace))
357 pixel=Sa*indexes[x]-Da*reconstruct_indexes[x];
358 distance=pixel*pixel;
359 if (distance >= fuzz)
360 difference=MagickTrue;
363 if (difference != MagickFalse)
364 SetPixelPacket(highlight_image,&highlight,r,highlight_indexes ==
365 (IndexPacket *) NULL ? NULL : highlight_indexes+x);
367 SetPixelPacket(highlight_image,&lowlight,r,highlight_indexes ==
368 (IndexPacket *) NULL ? NULL : highlight_indexes+x);
373 sync=SyncCacheViewAuthenticPixels(highlight_view,exception);
374 if (sync == MagickFalse)
377 highlight_view=DestroyCacheView(highlight_view);
378 reconstruct_view=DestroyCacheView(reconstruct_view);
379 image_view=DestroyCacheView(image_view);
380 (void) CompositeImage(difference_image,image->compose,highlight_image,0,0);
381 highlight_image=DestroyImage(highlight_image);
382 if (status == MagickFalse)
383 difference_image=DestroyImage(difference_image);
384 return(difference_image);
423 MagickExport MagickBooleanType GetImageDistortion(
Image *image,
424 const Image *reconstruct_image,
const MetricType metric,
double *distortion,
430 status=GetImageChannelDistortion(image,reconstruct_image,CompositeChannels,
431 metric,distortion,exception);
435 static MagickBooleanType GetAbsoluteDistortion(
const Image *image,
436 const Image *reconstruct_image,
const ChannelType channel,
double *distortion,
460 fuzz=GetFuzzyColorDistance(image,reconstruct_image);
461 rows=MagickMax(image->rows,reconstruct_image->rows);
462 columns=MagickMax(image->columns,reconstruct_image->columns);
463 image_view=AcquireVirtualCacheView(image,exception);
464 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
465 #if defined(MAGICKCORE_OPENMP_SUPPORT)
466 #pragma omp parallel for schedule(static) shared(status) \
467 magick_number_threads(image,image,rows,1)
469 for (y=0; y < (ssize_t) rows; y++)
472 channel_distortion[CompositeChannels+1];
475 *magick_restrict indexes,
476 *magick_restrict reconstruct_indexes;
486 if (status == MagickFalse)
488 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
489 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
495 indexes=GetCacheViewVirtualIndexQueue(image_view);
496 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
497 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
498 for (x=0; x < (ssize_t) columns; x++)
509 difference=MagickFalse;
510 Sa=QuantumScale*(image->matte != MagickFalse ? GetPixelAlpha(p) :
511 (QuantumRange-OpaqueOpacity));
512 Da=QuantumScale*(image->matte != MagickFalse ? GetPixelAlpha(q) :
513 (QuantumRange-OpaqueOpacity));
514 if ((channel & RedChannel) != 0)
516 pixel=Sa*GetPixelRed(p)-Da*GetPixelRed(q);
517 distance=pixel*pixel;
518 if (distance >= fuzz)
520 channel_distortion[RedChannel]++;
521 difference=MagickTrue;
524 if ((channel & GreenChannel) != 0)
526 pixel=Sa*GetPixelGreen(p)-Da*GetPixelGreen(q);
527 distance=pixel*pixel;
528 if (distance >= fuzz)
530 channel_distortion[GreenChannel]++;
531 difference=MagickTrue;
534 if ((channel & BlueChannel) != 0)
536 pixel=Sa*GetPixelBlue(p)-Da*GetPixelBlue(q);
537 distance=pixel*pixel;
538 if (distance >= fuzz)
540 channel_distortion[BlueChannel]++;
541 difference=MagickTrue;
544 if (((channel & OpacityChannel) != 0) &&
545 (image->matte != MagickFalse))
547 pixel=(double) GetPixelOpacity(p)-GetPixelOpacity(q);
548 distance=pixel*pixel;
549 if (distance >= fuzz)
551 channel_distortion[OpacityChannel]++;
552 difference=MagickTrue;
555 if (((channel & IndexChannel) != 0) &&
556 (image->colorspace == CMYKColorspace))
558 pixel=Sa*indexes[x]-Da*reconstruct_indexes[x];
559 distance=pixel*pixel;
560 if (distance >= fuzz)
562 channel_distortion[BlackChannel]++;
563 difference=MagickTrue;
566 if (difference != MagickFalse)
567 channel_distortion[CompositeChannels]++;
571 #if defined(MAGICKCORE_OPENMP_SUPPORT)
572 #pragma omp critical (MagickCore_GetAbsoluteDistortion)
574 for (i=0; i <= (ssize_t) CompositeChannels; i++)
575 distortion[i]+=channel_distortion[i];
577 reconstruct_view=DestroyCacheView(reconstruct_view);
578 image_view=DestroyCacheView(image_view);
582 static MagickBooleanType GetFuzzDistortion(
const Image *image,
583 const Image *reconstruct_image,
const ChannelType channel,
604 rows=MagickMax(image->rows,reconstruct_image->rows);
605 columns=MagickMax(image->columns,reconstruct_image->columns);
606 image_view=AcquireVirtualCacheView(image,exception);
607 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
608 #if defined(MAGICKCORE_OPENMP_SUPPORT)
609 #pragma omp parallel for schedule(static) shared(status) \
610 magick_number_threads(image,image,rows,1)
612 for (y=0; y < (ssize_t) rows; y++)
615 channel_distortion[CompositeChannels+1];
618 *magick_restrict indexes,
619 *magick_restrict reconstruct_indexes;
629 if (status == MagickFalse)
631 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
632 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
638 indexes=GetCacheViewVirtualIndexQueue(image_view);
639 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
640 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
641 for (x=0; x < (ssize_t) columns; x++)
648 Sa=QuantumScale*(image->matte != MagickFalse ? GetPixelAlpha(p) :
649 (QuantumRange-OpaqueOpacity));
650 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
651 GetPixelAlpha(q) : (QuantumRange-OpaqueOpacity));
652 if ((channel & RedChannel) != 0)
654 distance=QuantumScale*(Sa*GetPixelRed(p)-Da*GetPixelRed(q));
655 channel_distortion[RedChannel]+=distance*distance;
656 channel_distortion[CompositeChannels]+=distance*distance;
658 if ((channel & GreenChannel) != 0)
660 distance=QuantumScale*(Sa*GetPixelGreen(p)-Da*GetPixelGreen(q));
661 channel_distortion[GreenChannel]+=distance*distance;
662 channel_distortion[CompositeChannels]+=distance*distance;
664 if ((channel & BlueChannel) != 0)
666 distance=QuantumScale*(Sa*GetPixelBlue(p)-Da*GetPixelBlue(q));
667 channel_distortion[BlueChannel]+=distance*distance;
668 channel_distortion[CompositeChannels]+=distance*distance;
670 if (((channel & OpacityChannel) != 0) && ((image->matte != MagickFalse) ||
671 (reconstruct_image->matte != MagickFalse)))
673 distance=QuantumScale*((image->matte != MagickFalse ?
674 GetPixelOpacity(p) : OpaqueOpacity)-
675 (reconstruct_image->matte != MagickFalse ?
676 GetPixelOpacity(q): OpaqueOpacity));
677 channel_distortion[OpacityChannel]+=distance*distance;
678 channel_distortion[CompositeChannels]+=distance*distance;
680 if (((channel & IndexChannel) != 0) &&
681 (image->colorspace == CMYKColorspace) &&
682 (reconstruct_image->colorspace == CMYKColorspace))
684 distance=QuantumScale*(Sa*GetPixelIndex(indexes+x)-
685 Da*GetPixelIndex(reconstruct_indexes+x));
686 channel_distortion[BlackChannel]+=distance*distance;
687 channel_distortion[CompositeChannels]+=distance*distance;
692 #if defined(MAGICKCORE_OPENMP_SUPPORT)
693 #pragma omp critical (MagickCore_GetFuzzDistortion)
695 for (i=0; i <= (ssize_t) CompositeChannels; i++)
696 distortion[i]+=channel_distortion[i];
698 reconstruct_view=DestroyCacheView(reconstruct_view);
699 image_view=DestroyCacheView(image_view);
700 for (i=0; i <= (ssize_t) CompositeChannels; i++)
701 distortion[i]/=((
double) columns*rows);
702 distortion[CompositeChannels]/=(double) GetNumberChannels(image,channel);
703 distortion[CompositeChannels]=sqrt(distortion[CompositeChannels]);
707 static MagickBooleanType GetMeanAbsoluteDistortion(
const Image *image,
708 const Image *reconstruct_image,
const ChannelType channel,
729 rows=MagickMax(image->rows,reconstruct_image->rows);
730 columns=MagickMax(image->columns,reconstruct_image->columns);
731 image_view=AcquireVirtualCacheView(image,exception);
732 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
733 #if defined(MAGICKCORE_OPENMP_SUPPORT)
734 #pragma omp parallel for schedule(static) shared(status) \
735 magick_number_threads(image,image,rows,1)
737 for (y=0; y < (ssize_t) rows; y++)
740 channel_distortion[CompositeChannels+1];
743 *magick_restrict indexes,
744 *magick_restrict reconstruct_indexes;
754 if (status == MagickFalse)
756 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
757 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
763 indexes=GetCacheViewVirtualIndexQueue(image_view);
764 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
765 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
766 for (x=0; x < (ssize_t) columns; x++)
773 Sa=QuantumScale*(image->matte != MagickFalse ? GetPixelAlpha(p) :
774 (QuantumRange-OpaqueOpacity));
775 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
776 GetPixelAlpha(q) : (QuantumRange-OpaqueOpacity));
777 if ((channel & RedChannel) != 0)
779 distance=QuantumScale*fabs((
double) (Sa*GetPixelRed(p)-Da*
781 channel_distortion[RedChannel]+=distance;
782 channel_distortion[CompositeChannels]+=distance;
784 if ((channel & GreenChannel) != 0)
786 distance=QuantumScale*fabs((
double) (Sa*GetPixelGreen(p)-Da*
788 channel_distortion[GreenChannel]+=distance;
789 channel_distortion[CompositeChannels]+=distance;
791 if ((channel & BlueChannel) != 0)
793 distance=QuantumScale*fabs((
double) (Sa*GetPixelBlue(p)-Da*
795 channel_distortion[BlueChannel]+=distance;
796 channel_distortion[CompositeChannels]+=distance;
798 if (((channel & OpacityChannel) != 0) &&
799 (image->matte != MagickFalse))
801 distance=QuantumScale*fabs((
double) (GetPixelOpacity(p)-(
double)
802 GetPixelOpacity(q)));
803 channel_distortion[OpacityChannel]+=distance;
804 channel_distortion[CompositeChannels]+=distance;
806 if (((channel & IndexChannel) != 0) &&
807 (image->colorspace == CMYKColorspace))
809 distance=QuantumScale*fabs((
double) (Sa*GetPixelIndex(indexes+x)-Da*
810 GetPixelIndex(reconstruct_indexes+x)));
811 channel_distortion[BlackChannel]+=distance;
812 channel_distortion[CompositeChannels]+=distance;
817 #if defined(MAGICKCORE_OPENMP_SUPPORT)
818 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
820 for (i=0; i <= (ssize_t) CompositeChannels; i++)
821 distortion[i]+=channel_distortion[i];
823 reconstruct_view=DestroyCacheView(reconstruct_view);
824 image_view=DestroyCacheView(image_view);
825 for (i=0; i <= (ssize_t) CompositeChannels; i++)
826 distortion[i]/=((
double) columns*rows);
827 distortion[CompositeChannels]/=(double) GetNumberChannels(image,channel);
831 static MagickBooleanType GetMeanErrorPerPixel(
Image *image,
832 const Image *reconstruct_image,
const ChannelType channel,
double *distortion,
859 rows=MagickMax(image->rows,reconstruct_image->rows);
860 columns=MagickMax(image->columns,reconstruct_image->columns);
861 image_view=AcquireVirtualCacheView(image,exception);
862 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
863 for (y=0; y < (ssize_t) rows; y++)
866 *magick_restrict indexes,
867 *magick_restrict reconstruct_indexes;
876 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
877 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
883 indexes=GetCacheViewVirtualIndexQueue(image_view);
884 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
885 for (x=0; x < (ssize_t) columns; x++)
892 Sa=QuantumScale*(image->matte != MagickFalse ? GetPixelAlpha(p) :
893 (QuantumRange-OpaqueOpacity));
894 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
895 GetPixelAlpha(q) : (QuantumRange-OpaqueOpacity));
896 if ((channel & RedChannel) != 0)
898 distance=fabs((
double) (Sa*GetPixelRed(p)-Da*GetPixelRed(q)));
899 distortion[RedChannel]+=distance;
900 distortion[CompositeChannels]+=distance;
901 mean_error+=distance*distance;
902 if (distance > maximum_error)
903 maximum_error=distance;
906 if ((channel & GreenChannel) != 0)
908 distance=fabs((
double) (Sa*GetPixelGreen(p)-Da*GetPixelGreen(q)));
909 distortion[GreenChannel]+=distance;
910 distortion[CompositeChannels]+=distance;
911 mean_error+=distance*distance;
912 if (distance > maximum_error)
913 maximum_error=distance;
916 if ((channel & BlueChannel) != 0)
918 distance=fabs((
double) (Sa*GetPixelBlue(p)-Da*GetPixelBlue(q)));
919 distortion[BlueChannel]+=distance;
920 distortion[CompositeChannels]+=distance;
921 mean_error+=distance*distance;
922 if (distance > maximum_error)
923 maximum_error=distance;
926 if (((channel & OpacityChannel) != 0) &&
927 (image->matte != MagickFalse))
929 distance=fabs((
double) (GetPixelOpacity(p)-
930 (
double) GetPixelOpacity(q)));
931 distortion[OpacityChannel]+=distance;
932 distortion[CompositeChannels]+=distance;
933 mean_error+=distance*distance;
934 if (distance > maximum_error)
935 maximum_error=distance;
938 if (((channel & IndexChannel) != 0) &&
939 (image->colorspace == CMYKColorspace) &&
940 (reconstruct_image->colorspace == CMYKColorspace))
942 distance=fabs((
double) (Sa*GetPixelIndex(indexes+x)-Da*
943 GetPixelIndex(reconstruct_indexes+x)));
944 distortion[BlackChannel]+=distance;
945 distortion[CompositeChannels]+=distance;
946 mean_error+=distance*distance;
947 if (distance > maximum_error)
948 maximum_error=distance;
955 reconstruct_view=DestroyCacheView(reconstruct_view);
956 image_view=DestroyCacheView(image_view);
957 gamma=PerceptibleReciprocal(area);
958 image->error.mean_error_per_pixel=gamma*distortion[CompositeChannels];
959 image->error.normalized_mean_error=gamma*QuantumScale*QuantumScale*mean_error;
960 image->error.normalized_maximum_error=QuantumScale*maximum_error;
964 static MagickBooleanType GetMeanSquaredDistortion(
const Image *image,
965 const Image *reconstruct_image,
const ChannelType channel,
986 rows=MagickMax(image->rows,reconstruct_image->rows);
987 columns=MagickMax(image->columns,reconstruct_image->columns);
988 image_view=AcquireVirtualCacheView(image,exception);
989 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
990 #if defined(MAGICKCORE_OPENMP_SUPPORT)
991 #pragma omp parallel for schedule(static) shared(status) \
992 magick_number_threads(image,image,rows,1)
994 for (y=0; y < (ssize_t) rows; y++)
997 channel_distortion[CompositeChannels+1];
1000 *magick_restrict indexes,
1001 *magick_restrict reconstruct_indexes;
1011 if (status == MagickFalse)
1013 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1014 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1020 indexes=GetCacheViewVirtualIndexQueue(image_view);
1021 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1022 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
1023 for (x=0; x < (ssize_t) columns; x++)
1030 Sa=QuantumScale*(image->matte != MagickFalse ? GetPixelAlpha(p) :
1031 (QuantumRange-OpaqueOpacity));
1032 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1033 GetPixelAlpha(q) : (QuantumRange-OpaqueOpacity));
1034 if ((channel & RedChannel) != 0)
1036 distance=QuantumScale*(Sa*GetPixelRed(p)-Da*GetPixelRed(q));
1037 channel_distortion[RedChannel]+=distance*distance;
1038 channel_distortion[CompositeChannels]+=distance*distance;
1040 if ((channel & GreenChannel) != 0)
1042 distance=QuantumScale*(Sa*GetPixelGreen(p)-Da*GetPixelGreen(q));
1043 channel_distortion[GreenChannel]+=distance*distance;
1044 channel_distortion[CompositeChannels]+=distance*distance;
1046 if ((channel & BlueChannel) != 0)
1048 distance=QuantumScale*(Sa*GetPixelBlue(p)-Da*GetPixelBlue(q));
1049 channel_distortion[BlueChannel]+=distance*distance;
1050 channel_distortion[CompositeChannels]+=distance*distance;
1052 if (((channel & OpacityChannel) != 0) &&
1053 (image->matte != MagickFalse))
1055 distance=QuantumScale*(GetPixelOpacity(p)-(MagickRealType)
1056 GetPixelOpacity(q));
1057 channel_distortion[OpacityChannel]+=distance*distance;
1058 channel_distortion[CompositeChannels]+=distance*distance;
1060 if (((channel & IndexChannel) != 0) &&
1061 (image->colorspace == CMYKColorspace) &&
1062 (reconstruct_image->colorspace == CMYKColorspace))
1064 distance=QuantumScale*(Sa*GetPixelIndex(indexes+x)-Da*
1065 GetPixelIndex(reconstruct_indexes+x));
1066 channel_distortion[BlackChannel]+=distance*distance;
1067 channel_distortion[CompositeChannels]+=distance*distance;
1072 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1073 #pragma omp critical (MagickCore_GetMeanSquaredError)
1075 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1076 distortion[i]+=channel_distortion[i];
1078 reconstruct_view=DestroyCacheView(reconstruct_view);
1079 image_view=DestroyCacheView(image_view);
1080 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1081 distortion[i]/=((
double) columns*rows);
1082 distortion[CompositeChannels]/=(double) GetNumberChannels(image,channel);
1086 static MagickBooleanType GetNormalizedCrossCorrelationDistortion(
1087 const Image *image,
const Image *reconstruct_image,
const ChannelType channel,
1090 #define SimilarityImageTag "Similarity/Image"
1098 *reconstruct_statistics;
1101 alpha_variance[CompositeChannels+1],
1102 beta_variance[CompositeChannels+1];
1123 image_statistics=GetImageChannelStatistics(image,exception);
1124 reconstruct_statistics=GetImageChannelStatistics(reconstruct_image,exception);
1133 reconstruct_statistics);
1134 return(MagickFalse);
1136 (void) memset(distortion,0,(CompositeChannels+1)*
sizeof(*distortion));
1137 (void) memset(alpha_variance,0,(CompositeChannels+1)*
sizeof(*alpha_variance));
1138 (void) memset(beta_variance,0,(CompositeChannels+1)*
sizeof(*beta_variance));
1141 rows=MagickMax(image->rows,reconstruct_image->rows);
1142 columns=MagickMax(image->columns,reconstruct_image->columns);
1143 image_view=AcquireVirtualCacheView(image,exception);
1144 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1145 for (y=0; y < (ssize_t) rows; y++)
1148 *magick_restrict indexes,
1149 *magick_restrict reconstruct_indexes;
1158 if (status == MagickFalse)
1160 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1161 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1167 indexes=GetCacheViewVirtualIndexQueue(image_view);
1168 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1169 for (x=0; x < (ssize_t) columns; x++)
1177 Sa=QuantumScale*(image->matte != MagickFalse ? GetPixelAlpha(p) :
1179 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1180 GetPixelAlpha(q) : QuantumRange);
1181 if ((channel & RedChannel) != 0)
1183 alpha=QuantumScale*(Sa*GetPixelRed(p)-
1184 image_statistics[RedChannel].mean);
1185 beta=QuantumScale*(Da*GetPixelRed(q)-
1186 reconstruct_statistics[RedChannel].mean);
1187 distortion[RedChannel]+=alpha*beta;
1188 alpha_variance[RedChannel]+=alpha*alpha;
1189 beta_variance[RedChannel]+=beta*beta;
1191 if ((channel & GreenChannel) != 0)
1193 alpha=QuantumScale*(Sa*GetPixelGreen(p)-
1194 image_statistics[GreenChannel].mean);
1195 beta=QuantumScale*(Da*GetPixelGreen(q)-
1196 reconstruct_statistics[GreenChannel].mean);
1197 distortion[GreenChannel]+=alpha*beta;
1198 alpha_variance[GreenChannel]+=alpha*alpha;
1199 beta_variance[GreenChannel]+=beta*beta;
1201 if ((channel & BlueChannel) != 0)
1203 alpha=QuantumScale*(Sa*GetPixelBlue(p)-
1204 image_statistics[BlueChannel].mean);
1205 beta=QuantumScale*(Da*GetPixelBlue(q)-
1206 reconstruct_statistics[BlueChannel].mean);
1207 distortion[BlueChannel]+=alpha*beta;
1208 alpha_variance[BlueChannel]+=alpha*alpha;
1209 beta_variance[BlueChannel]+=beta*beta;
1211 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
1213 alpha=QuantumScale*(GetPixelAlpha(p)-
1214 image_statistics[AlphaChannel].mean);
1215 beta=QuantumScale*(GetPixelAlpha(q)-
1216 reconstruct_statistics[AlphaChannel].mean);
1217 distortion[OpacityChannel]+=alpha*beta;
1218 alpha_variance[OpacityChannel]+=alpha*alpha;
1219 beta_variance[OpacityChannel]+=beta*beta;
1221 if (((channel & IndexChannel) != 0) &&
1222 (image->colorspace == CMYKColorspace) &&
1223 (reconstruct_image->colorspace == CMYKColorspace))
1225 alpha=QuantumScale*(Sa*GetPixelIndex(indexes+x)-
1226 image_statistics[BlackChannel].mean);
1227 beta=QuantumScale*(Da*GetPixelIndex(reconstruct_indexes+x)-
1228 reconstruct_statistics[BlackChannel].mean);
1229 distortion[BlackChannel]+=alpha*beta;
1230 alpha_variance[BlackChannel]+=alpha*alpha;
1231 beta_variance[BlackChannel]+=beta*beta;
1236 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1241 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1245 proceed=SetImageProgress(image,SimilarityImageTag,progress,rows);
1246 if (proceed == MagickFalse)
1250 reconstruct_view=DestroyCacheView(reconstruct_view);
1251 image_view=DestroyCacheView(image_view);
1255 for (i=0; i < (ssize_t) CompositeChannels; i++)
1257 distortion[i]/=sqrt(alpha_variance[i]*beta_variance[i]);
1258 if (fabs(distortion[i]) > MagickEpsilon)
1259 distortion[CompositeChannels]+=distortion[i];
1261 distortion[CompositeChannels]=distortion[CompositeChannels]/
1262 GetNumberChannels(image,channel);
1267 reconstruct_statistics);
1273 static MagickBooleanType GetPeakAbsoluteDistortion(
const Image *image,
1274 const Image *reconstruct_image,
const ChannelType channel,
1292 rows=MagickMax(image->rows,reconstruct_image->rows);
1293 columns=MagickMax(image->columns,reconstruct_image->columns);
1294 image_view=AcquireVirtualCacheView(image,exception);
1295 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1296 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1297 #pragma omp parallel for schedule(static) shared(status) \
1298 magick_number_threads(image,image,rows,1)
1300 for (y=0; y < (ssize_t) rows; y++)
1303 channel_distortion[CompositeChannels+1];
1306 *magick_restrict indexes,
1307 *magick_restrict reconstruct_indexes;
1317 if (status == MagickFalse)
1319 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1320 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1326 indexes=GetCacheViewVirtualIndexQueue(image_view);
1327 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1328 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
1329 for (x=0; x < (ssize_t) columns; x++)
1336 Sa=QuantumScale*(image->matte != MagickFalse ? GetPixelAlpha(p) :
1337 (QuantumRange-OpaqueOpacity));
1338 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1339 GetPixelAlpha(q) : (QuantumRange-OpaqueOpacity));
1340 if ((channel & RedChannel) != 0)
1342 distance=QuantumScale*fabs((
double) (Sa*GetPixelRed(p)-Da*
1344 if (distance > channel_distortion[RedChannel])
1345 channel_distortion[RedChannel]=distance;
1346 if (distance > channel_distortion[CompositeChannels])
1347 channel_distortion[CompositeChannels]=distance;
1349 if ((channel & GreenChannel) != 0)
1351 distance=QuantumScale*fabs((
double) (Sa*GetPixelGreen(p)-Da*
1353 if (distance > channel_distortion[GreenChannel])
1354 channel_distortion[GreenChannel]=distance;
1355 if (distance > channel_distortion[CompositeChannels])
1356 channel_distortion[CompositeChannels]=distance;
1358 if ((channel & BlueChannel) != 0)
1360 distance=QuantumScale*fabs((
double) (Sa*GetPixelBlue(p)-Da*
1362 if (distance > channel_distortion[BlueChannel])
1363 channel_distortion[BlueChannel]=distance;
1364 if (distance > channel_distortion[CompositeChannels])
1365 channel_distortion[CompositeChannels]=distance;
1367 if (((channel & OpacityChannel) != 0) &&
1368 (image->matte != MagickFalse))
1370 distance=QuantumScale*fabs((
double) (GetPixelOpacity(p)-(
double)
1371 GetPixelOpacity(q)));
1372 if (distance > channel_distortion[OpacityChannel])
1373 channel_distortion[OpacityChannel]=distance;
1374 if (distance > channel_distortion[CompositeChannels])
1375 channel_distortion[CompositeChannels]=distance;
1377 if (((channel & IndexChannel) != 0) &&
1378 (image->colorspace == CMYKColorspace) &&
1379 (reconstruct_image->colorspace == CMYKColorspace))
1381 distance=QuantumScale*fabs((
double) (Sa*GetPixelIndex(indexes+x)-Da*
1382 GetPixelIndex(reconstruct_indexes+x)));
1383 if (distance > channel_distortion[BlackChannel])
1384 channel_distortion[BlackChannel]=distance;
1385 if (distance > channel_distortion[CompositeChannels])
1386 channel_distortion[CompositeChannels]=distance;
1391 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1392 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
1394 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1395 if (channel_distortion[i] > distortion[i])
1396 distortion[i]=channel_distortion[i];
1398 reconstruct_view=DestroyCacheView(reconstruct_view);
1399 image_view=DestroyCacheView(image_view);
1403 static inline double MagickLog10(
const double x)
1405 #define Log10Epsilon (1.0e-11)
1407 if (fabs(x) < Log10Epsilon)
1408 return(log10(Log10Epsilon));
1409 return(log10(fabs(x)));
1412 static MagickBooleanType GetPeakSignalToNoiseRatio(
const Image *image,
1413 const Image *reconstruct_image,
const ChannelType channel,
1419 status=GetMeanSquaredDistortion(image,reconstruct_image,channel,distortion,
1421 if ((channel & RedChannel) != 0)
1423 if (fabs(distortion[RedChannel]) < MagickEpsilon)
1424 distortion[RedChannel]=INFINITY;
1426 distortion[RedChannel]=10.0*MagickLog10(1.0)-10.0*
1427 MagickLog10(distortion[RedChannel]);
1429 if ((channel & GreenChannel) != 0)
1431 if (fabs(distortion[GreenChannel]) < MagickEpsilon)
1432 distortion[GreenChannel]=INFINITY;
1434 distortion[GreenChannel]=10.0*MagickLog10(1.0)-10.0*
1435 MagickLog10(distortion[GreenChannel]);
1437 if ((channel & BlueChannel) != 0)
1439 if (fabs(distortion[BlueChannel]) < MagickEpsilon)
1440 distortion[BlueChannel]=INFINITY;
1442 distortion[BlueChannel]=10.0*MagickLog10(1.0)-10.0*
1443 MagickLog10(distortion[BlueChannel]);
1445 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
1447 if (fabs(distortion[OpacityChannel]) < MagickEpsilon)
1448 distortion[OpacityChannel]=INFINITY;
1450 distortion[OpacityChannel]=10.0*MagickLog10(1.0)-10.0*
1451 MagickLog10(distortion[OpacityChannel]);
1453 if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
1455 if (fabs(distortion[BlackChannel]) < MagickEpsilon)
1456 distortion[BlackChannel]=INFINITY;
1458 distortion[BlackChannel]=10.0*MagickLog10(1.0)-10.0*
1459 MagickLog10(distortion[BlackChannel]);
1461 if (fabs(distortion[CompositeChannels]) < MagickEpsilon)
1462 distortion[CompositeChannels]=INFINITY;
1464 distortion[CompositeChannels]=10.0*MagickLog10(1.0)-10.0*
1465 MagickLog10(distortion[CompositeChannels]);
1469 static MagickBooleanType GetPerceptualHashDistortion(
const Image *image,
1470 const Image *reconstruct_image,
const ChannelType channel,
double *distortion,
1486 image_phash=GetImageChannelPerceptualHash(image,exception);
1488 return(MagickFalse);
1489 reconstruct_phash=GetImageChannelPerceptualHash(reconstruct_image,exception);
1493 return(MagickFalse);
1495 for (i=0; i < MaximumNumberOfImageMoments; i++)
1500 if ((channel & RedChannel) != 0)
1502 difference=reconstruct_phash[RedChannel].P[i]-
1503 image_phash[RedChannel].P[i];
1504 distortion[RedChannel]+=difference*difference;
1505 distortion[CompositeChannels]+=difference*difference;
1507 if ((channel & GreenChannel) != 0)
1509 difference=reconstruct_phash[GreenChannel].P[i]-
1510 image_phash[GreenChannel].P[i];
1511 distortion[GreenChannel]+=difference*difference;
1512 distortion[CompositeChannels]+=difference*difference;
1514 if ((channel & BlueChannel) != 0)
1516 difference=reconstruct_phash[BlueChannel].P[i]-
1517 image_phash[BlueChannel].P[i];
1518 distortion[BlueChannel]+=difference*difference;
1519 distortion[CompositeChannels]+=difference*difference;
1521 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse) &&
1522 (reconstruct_image->matte != MagickFalse))
1524 difference=reconstruct_phash[OpacityChannel].P[i]-
1525 image_phash[OpacityChannel].P[i];
1526 distortion[OpacityChannel]+=difference*difference;
1527 distortion[CompositeChannels]+=difference*difference;
1529 if (((channel & IndexChannel) != 0) &&
1530 (image->colorspace == CMYKColorspace) &&
1531 (reconstruct_image->colorspace == CMYKColorspace))
1533 difference=reconstruct_phash[IndexChannel].P[i]-
1534 image_phash[IndexChannel].P[i];
1535 distortion[IndexChannel]+=difference*difference;
1536 distortion[CompositeChannels]+=difference*difference;
1542 for (i=0; i < MaximumNumberOfImageMoments; i++)
1547 if ((channel & RedChannel) != 0)
1549 difference=reconstruct_phash[RedChannel].Q[i]-
1550 image_phash[RedChannel].Q[i];
1551 distortion[RedChannel]+=difference*difference;
1552 distortion[CompositeChannels]+=difference*difference;
1554 if ((channel & GreenChannel) != 0)
1556 difference=reconstruct_phash[GreenChannel].Q[i]-
1557 image_phash[GreenChannel].Q[i];
1558 distortion[GreenChannel]+=difference*difference;
1559 distortion[CompositeChannels]+=difference*difference;
1561 if ((channel & BlueChannel) != 0)
1563 difference=reconstruct_phash[BlueChannel].Q[i]-
1564 image_phash[BlueChannel].Q[i];
1565 distortion[BlueChannel]+=difference*difference;
1566 distortion[CompositeChannels]+=difference*difference;
1568 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse) &&
1569 (reconstruct_image->matte != MagickFalse))
1571 difference=reconstruct_phash[OpacityChannel].Q[i]-
1572 image_phash[OpacityChannel].Q[i];
1573 distortion[OpacityChannel]+=difference*difference;
1574 distortion[CompositeChannels]+=difference*difference;
1576 if (((channel & IndexChannel) != 0) &&
1577 (image->colorspace == CMYKColorspace) &&
1578 (reconstruct_image->colorspace == CMYKColorspace))
1580 difference=reconstruct_phash[IndexChannel].Q[i]-
1581 image_phash[IndexChannel].Q[i];
1582 distortion[IndexChannel]+=difference*difference;
1583 distortion[CompositeChannels]+=difference*difference;
1595 static MagickBooleanType GetRootMeanSquaredDistortion(
const Image *image,
1596 const Image *reconstruct_image,
const ChannelType channel,
double *distortion,
1602 status=GetMeanSquaredDistortion(image,reconstruct_image,channel,distortion,
1604 if ((channel & RedChannel) != 0)
1605 distortion[RedChannel]=sqrt(distortion[RedChannel]);
1606 if ((channel & GreenChannel) != 0)
1607 distortion[GreenChannel]=sqrt(distortion[GreenChannel]);
1608 if ((channel & BlueChannel) != 0)
1609 distortion[BlueChannel]=sqrt(distortion[BlueChannel]);
1610 if (((channel & OpacityChannel) != 0) &&
1611 (image->matte != MagickFalse))
1612 distortion[OpacityChannel]=sqrt(distortion[OpacityChannel]);
1613 if (((channel & IndexChannel) != 0) &&
1614 (image->colorspace == CMYKColorspace))
1615 distortion[BlackChannel]=sqrt(distortion[BlackChannel]);
1616 distortion[CompositeChannels]=sqrt(distortion[CompositeChannels]);
1620 MagickExport MagickBooleanType GetImageChannelDistortion(
Image *image,
1621 const Image *reconstruct_image,
const ChannelType channel,
1622 const MetricType metric,
double *distortion,
ExceptionInfo *exception)
1625 *channel_distortion;
1633 assert(image != (
Image *) NULL);
1634 assert(image->signature == MagickCoreSignature);
1635 assert(reconstruct_image != (
const Image *) NULL);
1636 assert(reconstruct_image->signature == MagickCoreSignature);
1637 assert(distortion != (
double *) NULL);
1638 if (IsEventLogging() != MagickFalse)
1639 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
1641 if (metric != PerceptualHashErrorMetric)
1642 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1643 ThrowBinaryException(ImageError,
"ImageMorphologyDiffers",image->filename);
1647 length=CompositeChannels+1UL;
1648 channel_distortion=(
double *) AcquireQuantumMemory(length,
1649 sizeof(*channel_distortion));
1650 if (channel_distortion == (
double *) NULL)
1651 ThrowFatalException(ResourceLimitFatalError,
"MemoryAllocationFailed");
1652 (void) memset(channel_distortion,0,length*
sizeof(*channel_distortion));
1655 case AbsoluteErrorMetric:
1657 status=GetAbsoluteDistortion(image,reconstruct_image,channel,
1658 channel_distortion,exception);
1661 case FuzzErrorMetric:
1663 status=GetFuzzDistortion(image,reconstruct_image,channel,
1664 channel_distortion,exception);
1667 case MeanAbsoluteErrorMetric:
1669 status=GetMeanAbsoluteDistortion(image,reconstruct_image,channel,
1670 channel_distortion,exception);
1673 case MeanErrorPerPixelMetric:
1675 status=GetMeanErrorPerPixel(image,reconstruct_image,channel,
1676 channel_distortion,exception);
1679 case MeanSquaredErrorMetric:
1681 status=GetMeanSquaredDistortion(image,reconstruct_image,channel,
1682 channel_distortion,exception);
1685 case NormalizedCrossCorrelationErrorMetric:
1688 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1689 channel,channel_distortion,exception);
1692 case PeakAbsoluteErrorMetric:
1694 status=GetPeakAbsoluteDistortion(image,reconstruct_image,channel,
1695 channel_distortion,exception);
1698 case PeakSignalToNoiseRatioMetric:
1700 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,channel,
1701 channel_distortion,exception);
1704 case PerceptualHashErrorMetric:
1706 status=GetPerceptualHashDistortion(image,reconstruct_image,channel,
1707 channel_distortion,exception);
1710 case RootMeanSquaredErrorMetric:
1712 status=GetRootMeanSquaredDistortion(image,reconstruct_image,channel,
1713 channel_distortion,exception);
1717 *distortion=channel_distortion[CompositeChannels];
1718 channel_distortion=(
double *) RelinquishMagickMemory(channel_distortion);
1719 (void) FormatImageProperty(image,
"distortion",
"%.*g",GetMagickPrecision(),
1756 MagickExport
double *GetImageChannelDistortions(
Image *image,
1757 const Image *reconstruct_image,
const MetricType metric,
1761 *channel_distortion;
1769 assert(image != (
Image *) NULL);
1770 assert(image->signature == MagickCoreSignature);
1771 assert(reconstruct_image != (
const Image *) NULL);
1772 assert(reconstruct_image->signature == MagickCoreSignature);
1773 if (IsEventLogging() != MagickFalse)
1774 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
1775 if (metric != PerceptualHashErrorMetric)
1776 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1778 (void) ThrowMagickException(&image->exception,GetMagickModule(),
1779 ImageError,
"ImageMorphologyDiffers",
"`%s'",image->filename);
1780 return((
double *) NULL);
1785 length=CompositeChannels+1UL;
1786 channel_distortion=(
double *) AcquireQuantumMemory(length,
1787 sizeof(*channel_distortion));
1788 if (channel_distortion == (
double *) NULL)
1789 ThrowFatalException(ResourceLimitFatalError,
"MemoryAllocationFailed");
1790 (void) memset(channel_distortion,0,length*
1791 sizeof(*channel_distortion));
1795 case AbsoluteErrorMetric:
1797 status=GetAbsoluteDistortion(image,reconstruct_image,CompositeChannels,
1798 channel_distortion,exception);
1801 case FuzzErrorMetric:
1803 status=GetFuzzDistortion(image,reconstruct_image,CompositeChannels,
1804 channel_distortion,exception);
1807 case MeanAbsoluteErrorMetric:
1809 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1810 CompositeChannels,channel_distortion,exception);
1813 case MeanErrorPerPixelMetric:
1815 status=GetMeanErrorPerPixel(image,reconstruct_image,CompositeChannels,
1816 channel_distortion,exception);
1819 case MeanSquaredErrorMetric:
1821 status=GetMeanSquaredDistortion(image,reconstruct_image,CompositeChannels,
1822 channel_distortion,exception);
1825 case NormalizedCrossCorrelationErrorMetric:
1828 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1829 CompositeChannels,channel_distortion,exception);
1832 case PeakAbsoluteErrorMetric:
1834 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1835 CompositeChannels,channel_distortion,exception);
1838 case PeakSignalToNoiseRatioMetric:
1840 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1841 CompositeChannels,channel_distortion,exception);
1844 case PerceptualHashErrorMetric:
1846 status=GetPerceptualHashDistortion(image,reconstruct_image,
1847 CompositeChannels,channel_distortion,exception);
1850 case RootMeanSquaredErrorMetric:
1852 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1853 CompositeChannels,channel_distortion,exception);
1857 if (status == MagickFalse)
1859 channel_distortion=(
double *) RelinquishMagickMemory(channel_distortion);
1860 return((
double *) NULL);
1862 return(channel_distortion);
1912 MagickExport MagickBooleanType IsImagesEqual(
Image *image,
1913 const Image *reconstruct_image)
1930 mean_error_per_pixel;
1939 assert(image != (
Image *) NULL);
1940 assert(image->signature == MagickCoreSignature);
1941 assert(reconstruct_image != (
const Image *) NULL);
1942 assert(reconstruct_image->signature == MagickCoreSignature);
1943 exception=(&image->exception);
1944 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1945 ThrowBinaryException(ImageError,
"ImageMorphologyDiffers",image->filename);
1948 mean_error_per_pixel=0.0;
1950 rows=MagickMax(image->rows,reconstruct_image->rows);
1951 columns=MagickMax(image->columns,reconstruct_image->columns);
1952 image_view=AcquireVirtualCacheView(image,exception);
1953 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1954 for (y=0; y < (ssize_t) rows; y++)
1957 *magick_restrict indexes,
1958 *magick_restrict reconstruct_indexes;
1967 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1968 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1971 indexes=GetCacheViewVirtualIndexQueue(image_view);
1972 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1973 for (x=0; x < (ssize_t) columns; x++)
1978 distance=fabs((
double) (GetPixelRed(p)-(
double) GetPixelRed(q)));
1979 mean_error_per_pixel+=distance;
1980 mean_error+=distance*distance;
1981 if (distance > maximum_error)
1982 maximum_error=distance;
1984 distance=fabs((
double) (GetPixelGreen(p)-(
double) GetPixelGreen(q)));
1985 mean_error_per_pixel+=distance;
1986 mean_error+=distance*distance;
1987 if (distance > maximum_error)
1988 maximum_error=distance;
1990 distance=fabs((
double) (GetPixelBlue(p)-(
double) GetPixelBlue(q)));
1991 mean_error_per_pixel+=distance;
1992 mean_error+=distance*distance;
1993 if (distance > maximum_error)
1994 maximum_error=distance;
1996 if (image->matte != MagickFalse)
1998 distance=fabs((
double) (GetPixelOpacity(p)-(
double)
1999 GetPixelOpacity(q)));
2000 mean_error_per_pixel+=distance;
2001 mean_error+=distance*distance;
2002 if (distance > maximum_error)
2003 maximum_error=distance;
2006 if ((image->colorspace == CMYKColorspace) &&
2007 (reconstruct_image->colorspace == CMYKColorspace))
2009 distance=fabs((
double) (GetPixelIndex(indexes+x)-(
double)
2010 GetPixelIndex(reconstruct_indexes+x)));
2011 mean_error_per_pixel+=distance;
2012 mean_error+=distance*distance;
2013 if (distance > maximum_error)
2014 maximum_error=distance;
2021 reconstruct_view=DestroyCacheView(reconstruct_view);
2022 image_view=DestroyCacheView(image_view);
2023 gamma=PerceptibleReciprocal(area);
2024 image->error.mean_error_per_pixel=gamma*mean_error_per_pixel;
2025 image->error.normalized_mean_error=gamma*QuantumScale*QuantumScale*mean_error;
2026 image->error.normalized_maximum_error=QuantumScale*maximum_error;
2027 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
2066 static double GetSimilarityMetric(
const Image *image,
const Image *reference,
2067 const MetricType metric,
const ssize_t x_offset,
const ssize_t y_offset,
2082 SetGeometry(reference,&geometry);
2083 geometry.x=x_offset;
2084 geometry.y=y_offset;
2085 similarity_image=CropImage(image,&geometry,exception);
2086 if (similarity_image == (
Image *) NULL)
2089 status=GetImageDistortion(similarity_image,reference,metric,&distortion,
2092 similarity_image=DestroyImage(similarity_image);
2096 MagickExport
Image *SimilarityImage(
Image *image,
const Image *reference,
2102 similarity_image=SimilarityMetricImage(image,reference,
2103 RootMeanSquaredErrorMetric,offset,similarity_metric,exception);
2104 return(similarity_image);
2107 MagickExport
Image *SimilarityMetricImage(
Image *image,
const Image *reference,
2108 const MetricType metric,
RectangleInfo *offset,
double *similarity_metric,
2111 #define SimilarityImageTag "Similarity/Image"
2120 similarity_threshold;
2134 assert(image != (
const Image *) NULL);
2135 assert(image->signature == MagickCoreSignature);
2137 assert(exception->signature == MagickCoreSignature);
2139 if (IsEventLogging() != MagickFalse)
2140 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
2141 SetGeometry(reference,offset);
2142 *similarity_metric=MagickMaximumValue;
2143 if (ValidateImageMorphology(image,reference) == MagickFalse)
2144 ThrowImageException(ImageError,
"ImageMorphologyDiffers");
2145 similarity_image=CloneImage(image,image->columns-reference->columns+1,
2146 image->rows-reference->rows+1,MagickTrue,exception);
2147 if (similarity_image == (
Image *) NULL)
2148 return((
Image *) NULL);
2149 if (SetImageStorageClass(similarity_image,DirectClass) == MagickFalse)
2151 InheritException(exception,&similarity_image->exception);
2152 similarity_image=DestroyImage(similarity_image);
2153 return((
Image *) NULL);
2155 (void) SetImageAlphaChannel(similarity_image,DeactivateAlphaChannel);
2159 similarity_threshold=(-1.0);
2160 artifact=GetImageArtifact(image,
"compare:similarity-threshold");
2161 if (artifact != (
const char *) NULL)
2162 similarity_threshold=StringToDouble(artifact,(
char **) NULL);
2165 similarity_view=AcquireVirtualCacheView(similarity_image,exception);
2166 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2167 #pragma omp parallel for schedule(static) \
2168 shared(progress,status,similarity_metric) \
2169 magick_number_threads(image,image,image->rows-reference->rows+1,1)
2171 for (y=0; y < (ssize_t) (image->rows-reference->rows+1); y++)
2182 if (status == MagickFalse)
2184 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2185 #pragma omp flush(similarity_metric)
2187 if (*similarity_metric <= similarity_threshold)
2189 q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
2196 for (x=0; x < (ssize_t) (image->columns-reference->columns+1); x++)
2198 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2199 #pragma omp flush(similarity_metric)
2201 if (*similarity_metric <= similarity_threshold)
2203 similarity=GetSimilarityMetric(image,reference,metric,x,y,exception);
2204 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2205 #pragma omp critical (MagickCore_SimilarityImage)
2207 if ((metric == NormalizedCrossCorrelationErrorMetric) ||
2208 (metric == UndefinedErrorMetric))
2209 similarity=1.0-similarity;
2210 if (similarity < *similarity_metric)
2212 *similarity_metric=similarity;
2216 if (metric == PerceptualHashErrorMetric)
2217 similarity=MagickMin(0.01*similarity,1.0);
2218 SetPixelRed(q,ClampToQuantum(QuantumRange-QuantumRange*similarity));
2219 SetPixelGreen(q,GetPixelRed(q));
2220 SetPixelBlue(q,GetPixelRed(q));
2223 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
2225 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2230 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2234 proceed=SetImageProgress(image,SimilarityImageTag,progress,image->rows);
2235 if (proceed == MagickFalse)
2239 similarity_view=DestroyCacheView(similarity_view);
2240 if (status == MagickFalse)
2241 similarity_image=DestroyImage(similarity_image);
2242 return(similarity_image);