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;
1122 image_statistics=GetImageChannelStatistics(image,exception);
1123 reconstruct_statistics=GetImageChannelStatistics(reconstruct_image,exception);
1132 reconstruct_statistics);
1133 return(MagickFalse);
1137 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1139 rows=MagickMax(image->rows,reconstruct_image->rows);
1140 columns=MagickMax(image->columns,reconstruct_image->columns);
1141 area=1.0/((MagickRealType) columns*rows);
1142 image_view=AcquireVirtualCacheView(image,exception);
1143 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1144 for (y=0; y < (ssize_t) rows; y++)
1147 *magick_restrict indexes,
1148 *magick_restrict reconstruct_indexes;
1157 if (status == MagickFalse)
1159 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1160 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1166 indexes=GetCacheViewVirtualIndexQueue(image_view);
1167 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1168 for (x=0; x < (ssize_t) columns; x++)
1174 Sa=QuantumScale*(image->matte != MagickFalse ? GetPixelAlpha(p) :
1175 (QuantumRange-OpaqueOpacity));
1176 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1177 GetPixelAlpha(q) : (QuantumRange-OpaqueOpacity));
1178 if ((channel & RedChannel) != 0)
1179 distortion[RedChannel]+=area*QuantumScale*(Sa*GetPixelRed(p)-
1180 image_statistics[RedChannel].mean)*(Da*GetPixelRed(q)-
1181 reconstruct_statistics[RedChannel].mean);
1182 if ((channel & GreenChannel) != 0)
1183 distortion[GreenChannel]+=area*QuantumScale*(Sa*GetPixelGreen(p)-
1184 image_statistics[GreenChannel].mean)*(Da*GetPixelGreen(q)-
1185 reconstruct_statistics[GreenChannel].mean);
1186 if ((channel & BlueChannel) != 0)
1187 distortion[BlueChannel]+=area*QuantumScale*(Sa*GetPixelBlue(p)-
1188 image_statistics[BlueChannel].mean)*(Da*GetPixelBlue(q)-
1189 reconstruct_statistics[BlueChannel].mean);
1190 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
1191 distortion[OpacityChannel]+=area*QuantumScale*(
1192 GetPixelOpacity(p)-image_statistics[OpacityChannel].mean)*
1193 (GetPixelOpacity(q)-reconstruct_statistics[OpacityChannel].mean);
1194 if (((channel & IndexChannel) != 0) &&
1195 (image->colorspace == CMYKColorspace) &&
1196 (reconstruct_image->colorspace == CMYKColorspace))
1197 distortion[BlackChannel]+=area*QuantumScale*(Sa*
1198 GetPixelIndex(indexes+x)-image_statistics[BlackChannel].mean)*(Da*
1199 GetPixelIndex(reconstruct_indexes+x)-
1200 reconstruct_statistics[BlackChannel].mean);
1204 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1209 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1213 proceed=SetImageProgress(image,SimilarityImageTag,progress,rows);
1214 if (proceed == MagickFalse)
1218 reconstruct_view=DestroyCacheView(reconstruct_view);
1219 image_view=DestroyCacheView(image_view);
1223 for (i=0; i < (ssize_t) CompositeChannels; i++)
1228 gamma=image_statistics[i].standard_deviation*
1229 reconstruct_statistics[i].standard_deviation;
1230 if (fabs(gamma) >= MagickEpsilon)
1232 gamma=PerceptibleReciprocal(gamma);
1233 distortion[i]=QuantumRange*gamma*distortion[i];
1236 distortion[CompositeChannels]=0.0;
1237 if ((channel & RedChannel) != 0)
1238 distortion[CompositeChannels]+=distortion[RedChannel]*
1239 distortion[RedChannel];
1240 if ((channel & GreenChannel) != 0)
1241 distortion[CompositeChannels]+=distortion[GreenChannel]*
1242 distortion[GreenChannel];
1243 if ((channel & BlueChannel) != 0)
1244 distortion[CompositeChannels]+=distortion[BlueChannel]*
1245 distortion[BlueChannel];
1246 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
1247 distortion[CompositeChannels]+=distortion[OpacityChannel]*
1248 distortion[OpacityChannel];
1249 if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
1250 distortion[CompositeChannels]+=distortion[BlackChannel]*
1251 distortion[BlackChannel];
1252 distortion[CompositeChannels]=sqrt(distortion[CompositeChannels]/
1253 GetNumberChannels(image,channel));
1258 reconstruct_statistics);
1264 static MagickBooleanType GetPeakAbsoluteDistortion(
const Image *image,
1265 const Image *reconstruct_image,
const ChannelType channel,
1283 rows=MagickMax(image->rows,reconstruct_image->rows);
1284 columns=MagickMax(image->columns,reconstruct_image->columns);
1285 image_view=AcquireVirtualCacheView(image,exception);
1286 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1287 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1288 #pragma omp parallel for schedule(static) shared(status) \
1289 magick_number_threads(image,image,rows,1)
1291 for (y=0; y < (ssize_t) rows; y++)
1294 channel_distortion[CompositeChannels+1];
1297 *magick_restrict indexes,
1298 *magick_restrict reconstruct_indexes;
1308 if (status == MagickFalse)
1310 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1311 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1317 indexes=GetCacheViewVirtualIndexQueue(image_view);
1318 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1319 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
1320 for (x=0; x < (ssize_t) columns; x++)
1327 Sa=QuantumScale*(image->matte != MagickFalse ? GetPixelAlpha(p) :
1328 (QuantumRange-OpaqueOpacity));
1329 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1330 GetPixelAlpha(q) : (QuantumRange-OpaqueOpacity));
1331 if ((channel & RedChannel) != 0)
1333 distance=QuantumScale*fabs((
double) (Sa*GetPixelRed(p)-Da*
1335 if (distance > channel_distortion[RedChannel])
1336 channel_distortion[RedChannel]=distance;
1337 if (distance > channel_distortion[CompositeChannels])
1338 channel_distortion[CompositeChannels]=distance;
1340 if ((channel & GreenChannel) != 0)
1342 distance=QuantumScale*fabs((
double) (Sa*GetPixelGreen(p)-Da*
1344 if (distance > channel_distortion[GreenChannel])
1345 channel_distortion[GreenChannel]=distance;
1346 if (distance > channel_distortion[CompositeChannels])
1347 channel_distortion[CompositeChannels]=distance;
1349 if ((channel & BlueChannel) != 0)
1351 distance=QuantumScale*fabs((
double) (Sa*GetPixelBlue(p)-Da*
1353 if (distance > channel_distortion[BlueChannel])
1354 channel_distortion[BlueChannel]=distance;
1355 if (distance > channel_distortion[CompositeChannels])
1356 channel_distortion[CompositeChannels]=distance;
1358 if (((channel & OpacityChannel) != 0) &&
1359 (image->matte != MagickFalse))
1361 distance=QuantumScale*fabs((
double) (GetPixelOpacity(p)-(
double)
1362 GetPixelOpacity(q)));
1363 if (distance > channel_distortion[OpacityChannel])
1364 channel_distortion[OpacityChannel]=distance;
1365 if (distance > channel_distortion[CompositeChannels])
1366 channel_distortion[CompositeChannels]=distance;
1368 if (((channel & IndexChannel) != 0) &&
1369 (image->colorspace == CMYKColorspace) &&
1370 (reconstruct_image->colorspace == CMYKColorspace))
1372 distance=QuantumScale*fabs((
double) (Sa*GetPixelIndex(indexes+x)-Da*
1373 GetPixelIndex(reconstruct_indexes+x)));
1374 if (distance > channel_distortion[BlackChannel])
1375 channel_distortion[BlackChannel]=distance;
1376 if (distance > channel_distortion[CompositeChannels])
1377 channel_distortion[CompositeChannels]=distance;
1382 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1383 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
1385 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1386 if (channel_distortion[i] > distortion[i])
1387 distortion[i]=channel_distortion[i];
1389 reconstruct_view=DestroyCacheView(reconstruct_view);
1390 image_view=DestroyCacheView(image_view);
1394 static inline double MagickLog10(
const double x)
1396 #define Log10Epsilon (1.0e-11)
1398 if (fabs(x) < Log10Epsilon)
1399 return(log10(Log10Epsilon));
1400 return(log10(fabs(x)));
1403 static MagickBooleanType GetPeakSignalToNoiseRatio(
const Image *image,
1404 const Image *reconstruct_image,
const ChannelType channel,
1410 status=GetMeanSquaredDistortion(image,reconstruct_image,channel,distortion,
1412 if ((channel & RedChannel) != 0)
1414 if (fabs(distortion[RedChannel]) < MagickEpsilon)
1415 distortion[RedChannel]=INFINITY;
1417 distortion[RedChannel]=10.0*MagickLog10(1.0)-10.0*
1418 MagickLog10(distortion[RedChannel]);
1420 if ((channel & GreenChannel) != 0)
1422 if (fabs(distortion[GreenChannel]) < MagickEpsilon)
1423 distortion[GreenChannel]=INFINITY;
1425 distortion[GreenChannel]=10.0*MagickLog10(1.0)-10.0*
1426 MagickLog10(distortion[GreenChannel]);
1428 if ((channel & BlueChannel) != 0)
1430 if (fabs(distortion[BlueChannel]) < MagickEpsilon)
1431 distortion[BlueChannel]=INFINITY;
1433 distortion[BlueChannel]=10.0*MagickLog10(1.0)-10.0*
1434 MagickLog10(distortion[BlueChannel]);
1436 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
1438 if (fabs(distortion[OpacityChannel]) < MagickEpsilon)
1439 distortion[OpacityChannel]=INFINITY;
1441 distortion[OpacityChannel]=10.0*MagickLog10(1.0)-10.0*
1442 MagickLog10(distortion[OpacityChannel]);
1444 if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
1446 if (fabs(distortion[BlackChannel]) < MagickEpsilon)
1447 distortion[BlackChannel]=INFINITY;
1449 distortion[BlackChannel]=10.0*MagickLog10(1.0)-10.0*
1450 MagickLog10(distortion[BlackChannel]);
1452 if (fabs(distortion[CompositeChannels]) < MagickEpsilon)
1453 distortion[CompositeChannels]=INFINITY;
1455 distortion[CompositeChannels]=10.0*MagickLog10(1.0)-10.0*
1456 MagickLog10(distortion[CompositeChannels]);
1460 static MagickBooleanType GetPerceptualHashDistortion(
const Image *image,
1461 const Image *reconstruct_image,
const ChannelType channel,
double *distortion,
1477 image_phash=GetImageChannelPerceptualHash(image,exception);
1479 return(MagickFalse);
1480 reconstruct_phash=GetImageChannelPerceptualHash(reconstruct_image,exception);
1484 return(MagickFalse);
1486 for (i=0; i < MaximumNumberOfImageMoments; i++)
1491 if ((channel & RedChannel) != 0)
1493 difference=reconstruct_phash[RedChannel].P[i]-
1494 image_phash[RedChannel].P[i];
1495 distortion[RedChannel]+=difference*difference;
1496 distortion[CompositeChannels]+=difference*difference;
1498 if ((channel & GreenChannel) != 0)
1500 difference=reconstruct_phash[GreenChannel].P[i]-
1501 image_phash[GreenChannel].P[i];
1502 distortion[GreenChannel]+=difference*difference;
1503 distortion[CompositeChannels]+=difference*difference;
1505 if ((channel & BlueChannel) != 0)
1507 difference=reconstruct_phash[BlueChannel].P[i]-
1508 image_phash[BlueChannel].P[i];
1509 distortion[BlueChannel]+=difference*difference;
1510 distortion[CompositeChannels]+=difference*difference;
1512 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse) &&
1513 (reconstruct_image->matte != MagickFalse))
1515 difference=reconstruct_phash[OpacityChannel].P[i]-
1516 image_phash[OpacityChannel].P[i];
1517 distortion[OpacityChannel]+=difference*difference;
1518 distortion[CompositeChannels]+=difference*difference;
1520 if (((channel & IndexChannel) != 0) &&
1521 (image->colorspace == CMYKColorspace) &&
1522 (reconstruct_image->colorspace == CMYKColorspace))
1524 difference=reconstruct_phash[IndexChannel].P[i]-
1525 image_phash[IndexChannel].P[i];
1526 distortion[IndexChannel]+=difference*difference;
1527 distortion[CompositeChannels]+=difference*difference;
1533 for (i=0; i < MaximumNumberOfImageMoments; i++)
1538 if ((channel & RedChannel) != 0)
1540 difference=reconstruct_phash[RedChannel].Q[i]-
1541 image_phash[RedChannel].Q[i];
1542 distortion[RedChannel]+=difference*difference;
1543 distortion[CompositeChannels]+=difference*difference;
1545 if ((channel & GreenChannel) != 0)
1547 difference=reconstruct_phash[GreenChannel].Q[i]-
1548 image_phash[GreenChannel].Q[i];
1549 distortion[GreenChannel]+=difference*difference;
1550 distortion[CompositeChannels]+=difference*difference;
1552 if ((channel & BlueChannel) != 0)
1554 difference=reconstruct_phash[BlueChannel].Q[i]-
1555 image_phash[BlueChannel].Q[i];
1556 distortion[BlueChannel]+=difference*difference;
1557 distortion[CompositeChannels]+=difference*difference;
1559 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse) &&
1560 (reconstruct_image->matte != MagickFalse))
1562 difference=reconstruct_phash[OpacityChannel].Q[i]-
1563 image_phash[OpacityChannel].Q[i];
1564 distortion[OpacityChannel]+=difference*difference;
1565 distortion[CompositeChannels]+=difference*difference;
1567 if (((channel & IndexChannel) != 0) &&
1568 (image->colorspace == CMYKColorspace) &&
1569 (reconstruct_image->colorspace == CMYKColorspace))
1571 difference=reconstruct_phash[IndexChannel].Q[i]-
1572 image_phash[IndexChannel].Q[i];
1573 distortion[IndexChannel]+=difference*difference;
1574 distortion[CompositeChannels]+=difference*difference;
1586 static MagickBooleanType GetRootMeanSquaredDistortion(
const Image *image,
1587 const Image *reconstruct_image,
const ChannelType channel,
double *distortion,
1593 status=GetMeanSquaredDistortion(image,reconstruct_image,channel,distortion,
1595 if ((channel & RedChannel) != 0)
1596 distortion[RedChannel]=sqrt(distortion[RedChannel]);
1597 if ((channel & GreenChannel) != 0)
1598 distortion[GreenChannel]=sqrt(distortion[GreenChannel]);
1599 if ((channel & BlueChannel) != 0)
1600 distortion[BlueChannel]=sqrt(distortion[BlueChannel]);
1601 if (((channel & OpacityChannel) != 0) &&
1602 (image->matte != MagickFalse))
1603 distortion[OpacityChannel]=sqrt(distortion[OpacityChannel]);
1604 if (((channel & IndexChannel) != 0) &&
1605 (image->colorspace == CMYKColorspace))
1606 distortion[BlackChannel]=sqrt(distortion[BlackChannel]);
1607 distortion[CompositeChannels]=sqrt(distortion[CompositeChannels]);
1611 MagickExport MagickBooleanType GetImageChannelDistortion(
Image *image,
1612 const Image *reconstruct_image,
const ChannelType channel,
1613 const MetricType metric,
double *distortion,
ExceptionInfo *exception)
1616 *channel_distortion;
1624 assert(image != (
Image *) NULL);
1625 assert(image->signature == MagickCoreSignature);
1626 assert(reconstruct_image != (
const Image *) NULL);
1627 assert(reconstruct_image->signature == MagickCoreSignature);
1628 assert(distortion != (
double *) NULL);
1629 if (IsEventLogging() != MagickFalse)
1630 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
1632 if (metric != PerceptualHashErrorMetric)
1633 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1634 ThrowBinaryException(ImageError,
"ImageMorphologyDiffers",image->filename);
1638 length=CompositeChannels+1UL;
1639 channel_distortion=(
double *) AcquireQuantumMemory(length,
1640 sizeof(*channel_distortion));
1641 if (channel_distortion == (
double *) NULL)
1642 ThrowFatalException(ResourceLimitFatalError,
"MemoryAllocationFailed");
1643 (void) memset(channel_distortion,0,length*
sizeof(*channel_distortion));
1646 case AbsoluteErrorMetric:
1648 status=GetAbsoluteDistortion(image,reconstruct_image,channel,
1649 channel_distortion,exception);
1652 case FuzzErrorMetric:
1654 status=GetFuzzDistortion(image,reconstruct_image,channel,
1655 channel_distortion,exception);
1658 case MeanAbsoluteErrorMetric:
1660 status=GetMeanAbsoluteDistortion(image,reconstruct_image,channel,
1661 channel_distortion,exception);
1664 case MeanErrorPerPixelMetric:
1666 status=GetMeanErrorPerPixel(image,reconstruct_image,channel,
1667 channel_distortion,exception);
1670 case MeanSquaredErrorMetric:
1672 status=GetMeanSquaredDistortion(image,reconstruct_image,channel,
1673 channel_distortion,exception);
1676 case NormalizedCrossCorrelationErrorMetric:
1679 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1680 channel,channel_distortion,exception);
1683 case PeakAbsoluteErrorMetric:
1685 status=GetPeakAbsoluteDistortion(image,reconstruct_image,channel,
1686 channel_distortion,exception);
1689 case PeakSignalToNoiseRatioMetric:
1691 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,channel,
1692 channel_distortion,exception);
1695 case PerceptualHashErrorMetric:
1697 status=GetPerceptualHashDistortion(image,reconstruct_image,channel,
1698 channel_distortion,exception);
1701 case RootMeanSquaredErrorMetric:
1703 status=GetRootMeanSquaredDistortion(image,reconstruct_image,channel,
1704 channel_distortion,exception);
1708 *distortion=channel_distortion[CompositeChannels];
1709 channel_distortion=(
double *) RelinquishMagickMemory(channel_distortion);
1710 (void) FormatImageProperty(image,
"distortion",
"%.*g",GetMagickPrecision(),
1747 MagickExport
double *GetImageChannelDistortions(
Image *image,
1748 const Image *reconstruct_image,
const MetricType metric,
1752 *channel_distortion;
1760 assert(image != (
Image *) NULL);
1761 assert(image->signature == MagickCoreSignature);
1762 assert(reconstruct_image != (
const Image *) NULL);
1763 assert(reconstruct_image->signature == MagickCoreSignature);
1764 if (IsEventLogging() != MagickFalse)
1765 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
1766 if (metric != PerceptualHashErrorMetric)
1767 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1769 (void) ThrowMagickException(&image->exception,GetMagickModule(),
1770 ImageError,
"ImageMorphologyDiffers",
"`%s'",image->filename);
1771 return((
double *) NULL);
1776 length=CompositeChannels+1UL;
1777 channel_distortion=(
double *) AcquireQuantumMemory(length,
1778 sizeof(*channel_distortion));
1779 if (channel_distortion == (
double *) NULL)
1780 ThrowFatalException(ResourceLimitFatalError,
"MemoryAllocationFailed");
1781 (void) memset(channel_distortion,0,length*
1782 sizeof(*channel_distortion));
1786 case AbsoluteErrorMetric:
1788 status=GetAbsoluteDistortion(image,reconstruct_image,CompositeChannels,
1789 channel_distortion,exception);
1792 case FuzzErrorMetric:
1794 status=GetFuzzDistortion(image,reconstruct_image,CompositeChannels,
1795 channel_distortion,exception);
1798 case MeanAbsoluteErrorMetric:
1800 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1801 CompositeChannels,channel_distortion,exception);
1804 case MeanErrorPerPixelMetric:
1806 status=GetMeanErrorPerPixel(image,reconstruct_image,CompositeChannels,
1807 channel_distortion,exception);
1810 case MeanSquaredErrorMetric:
1812 status=GetMeanSquaredDistortion(image,reconstruct_image,CompositeChannels,
1813 channel_distortion,exception);
1816 case NormalizedCrossCorrelationErrorMetric:
1819 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1820 CompositeChannels,channel_distortion,exception);
1823 case PeakAbsoluteErrorMetric:
1825 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1826 CompositeChannels,channel_distortion,exception);
1829 case PeakSignalToNoiseRatioMetric:
1831 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1832 CompositeChannels,channel_distortion,exception);
1835 case PerceptualHashErrorMetric:
1837 status=GetPerceptualHashDistortion(image,reconstruct_image,
1838 CompositeChannels,channel_distortion,exception);
1841 case RootMeanSquaredErrorMetric:
1843 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1844 CompositeChannels,channel_distortion,exception);
1848 if (status == MagickFalse)
1850 channel_distortion=(
double *) RelinquishMagickMemory(channel_distortion);
1851 return((
double *) NULL);
1853 return(channel_distortion);
1903 MagickExport MagickBooleanType IsImagesEqual(
Image *image,
1904 const Image *reconstruct_image)
1921 mean_error_per_pixel;
1930 assert(image != (
Image *) NULL);
1931 assert(image->signature == MagickCoreSignature);
1932 assert(reconstruct_image != (
const Image *) NULL);
1933 assert(reconstruct_image->signature == MagickCoreSignature);
1934 exception=(&image->exception);
1935 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1936 ThrowBinaryException(ImageError,
"ImageMorphologyDiffers",image->filename);
1939 mean_error_per_pixel=0.0;
1941 rows=MagickMax(image->rows,reconstruct_image->rows);
1942 columns=MagickMax(image->columns,reconstruct_image->columns);
1943 image_view=AcquireVirtualCacheView(image,exception);
1944 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1945 for (y=0; y < (ssize_t) rows; y++)
1948 *magick_restrict indexes,
1949 *magick_restrict reconstruct_indexes;
1958 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1959 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1962 indexes=GetCacheViewVirtualIndexQueue(image_view);
1963 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1964 for (x=0; x < (ssize_t) columns; x++)
1969 distance=fabs((
double) (GetPixelRed(p)-(
double) GetPixelRed(q)));
1970 mean_error_per_pixel+=distance;
1971 mean_error+=distance*distance;
1972 if (distance > maximum_error)
1973 maximum_error=distance;
1975 distance=fabs((
double) (GetPixelGreen(p)-(
double) GetPixelGreen(q)));
1976 mean_error_per_pixel+=distance;
1977 mean_error+=distance*distance;
1978 if (distance > maximum_error)
1979 maximum_error=distance;
1981 distance=fabs((
double) (GetPixelBlue(p)-(
double) GetPixelBlue(q)));
1982 mean_error_per_pixel+=distance;
1983 mean_error+=distance*distance;
1984 if (distance > maximum_error)
1985 maximum_error=distance;
1987 if (image->matte != MagickFalse)
1989 distance=fabs((
double) (GetPixelOpacity(p)-(
double)
1990 GetPixelOpacity(q)));
1991 mean_error_per_pixel+=distance;
1992 mean_error+=distance*distance;
1993 if (distance > maximum_error)
1994 maximum_error=distance;
1997 if ((image->colorspace == CMYKColorspace) &&
1998 (reconstruct_image->colorspace == CMYKColorspace))
2000 distance=fabs((
double) (GetPixelIndex(indexes+x)-(
double)
2001 GetPixelIndex(reconstruct_indexes+x)));
2002 mean_error_per_pixel+=distance;
2003 mean_error+=distance*distance;
2004 if (distance > maximum_error)
2005 maximum_error=distance;
2012 reconstruct_view=DestroyCacheView(reconstruct_view);
2013 image_view=DestroyCacheView(image_view);
2014 gamma=PerceptibleReciprocal(area);
2015 image->error.mean_error_per_pixel=gamma*mean_error_per_pixel;
2016 image->error.normalized_mean_error=gamma*QuantumScale*QuantumScale*mean_error;
2017 image->error.normalized_maximum_error=QuantumScale*maximum_error;
2018 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
2057 static double GetSimilarityMetric(
const Image *image,
const Image *reference,
2058 const MetricType metric,
const ssize_t x_offset,
const ssize_t y_offset,
2073 SetGeometry(reference,&geometry);
2074 geometry.x=x_offset;
2075 geometry.y=y_offset;
2076 similarity_image=CropImage(image,&geometry,exception);
2077 if (similarity_image == (
Image *) NULL)
2080 status=GetImageDistortion(similarity_image,reference,metric,&distortion,
2083 similarity_image=DestroyImage(similarity_image);
2087 MagickExport
Image *SimilarityImage(
Image *image,
const Image *reference,
2093 similarity_image=SimilarityMetricImage(image,reference,
2094 RootMeanSquaredErrorMetric,offset,similarity_metric,exception);
2095 return(similarity_image);
2098 MagickExport
Image *SimilarityMetricImage(
Image *image,
const Image *reference,
2099 const MetricType metric,
RectangleInfo *offset,
double *similarity_metric,
2102 #define SimilarityImageTag "Similarity/Image"
2111 similarity_threshold;
2125 assert(image != (
const Image *) NULL);
2126 assert(image->signature == MagickCoreSignature);
2128 assert(exception->signature == MagickCoreSignature);
2130 if (IsEventLogging() != MagickFalse)
2131 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
2132 SetGeometry(reference,offset);
2133 *similarity_metric=MagickMaximumValue;
2134 if (ValidateImageMorphology(image,reference) == MagickFalse)
2135 ThrowImageException(ImageError,
"ImageMorphologyDiffers");
2136 similarity_image=CloneImage(image,image->columns-reference->columns+1,
2137 image->rows-reference->rows+1,MagickTrue,exception);
2138 if (similarity_image == (
Image *) NULL)
2139 return((
Image *) NULL);
2140 if (SetImageStorageClass(similarity_image,DirectClass) == MagickFalse)
2142 InheritException(exception,&similarity_image->exception);
2143 similarity_image=DestroyImage(similarity_image);
2144 return((
Image *) NULL);
2146 (void) SetImageAlphaChannel(similarity_image,DeactivateAlphaChannel);
2150 similarity_threshold=(-1.0);
2151 artifact=GetImageArtifact(image,
"compare:similarity-threshold");
2152 if (artifact != (
const char *) NULL)
2153 similarity_threshold=StringToDouble(artifact,(
char **) NULL);
2156 similarity_view=AcquireVirtualCacheView(similarity_image,exception);
2157 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2158 #pragma omp parallel for schedule(static) \
2159 shared(progress,status,similarity_metric) \
2160 magick_number_threads(image,image,image->rows-reference->rows+1,1)
2162 for (y=0; y < (ssize_t) (image->rows-reference->rows+1); y++)
2173 if (status == MagickFalse)
2175 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2176 #pragma omp flush(similarity_metric)
2178 if (*similarity_metric <= similarity_threshold)
2180 q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
2187 for (x=0; x < (ssize_t) (image->columns-reference->columns+1); x++)
2189 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2190 #pragma omp flush(similarity_metric)
2192 if (*similarity_metric <= similarity_threshold)
2194 similarity=GetSimilarityMetric(image,reference,metric,x,y,exception);
2195 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2196 #pragma omp critical (MagickCore_SimilarityImage)
2198 if ((metric == NormalizedCrossCorrelationErrorMetric) ||
2199 (metric == UndefinedErrorMetric))
2200 similarity=1.0-similarity;
2201 if (similarity < *similarity_metric)
2203 *similarity_metric=similarity;
2207 if (metric == PerceptualHashErrorMetric)
2208 similarity=MagickMin(0.01*similarity,1.0);
2209 SetPixelRed(q,ClampToQuantum(QuantumRange-QuantumRange*similarity));
2210 SetPixelGreen(q,GetPixelRed(q));
2211 SetPixelBlue(q,GetPixelRed(q));
2214 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
2216 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2221 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2225 proceed=SetImageProgress(image,SimilarityImageTag,progress,image->rows);
2226 if (proceed == MagickFalse)
2230 similarity_view=DestroyCacheView(similarity_view);
2231 if (status == MagickFalse)
2232 similarity_image=DestroyImage(similarity_image);
2233 return(similarity_image);