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 gamma=PerceptibleReciprocal(gamma);
1231 distortion[i]=QuantumRange*gamma*distortion[i];
1233 distortion[CompositeChannels]=0.0;
1234 if ((channel & RedChannel) != 0)
1235 distortion[CompositeChannels]+=distortion[RedChannel]*
1236 distortion[RedChannel];
1237 if ((channel & GreenChannel) != 0)
1238 distortion[CompositeChannels]+=distortion[GreenChannel]*
1239 distortion[GreenChannel];
1240 if ((channel & BlueChannel) != 0)
1241 distortion[CompositeChannels]+=distortion[BlueChannel]*
1242 distortion[BlueChannel];
1243 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
1244 distortion[CompositeChannels]+=distortion[OpacityChannel]*
1245 distortion[OpacityChannel];
1246 if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
1247 distortion[CompositeChannels]+=distortion[BlackChannel]*
1248 distortion[BlackChannel];
1249 distortion[CompositeChannels]=sqrt(distortion[CompositeChannels]/
1250 GetNumberChannels(image,channel));
1255 reconstruct_statistics);
1261 static MagickBooleanType GetPeakAbsoluteDistortion(
const Image *image,
1262 const Image *reconstruct_image,
const ChannelType channel,
1280 rows=MagickMax(image->rows,reconstruct_image->rows);
1281 columns=MagickMax(image->columns,reconstruct_image->columns);
1282 image_view=AcquireVirtualCacheView(image,exception);
1283 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1284 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1285 #pragma omp parallel for schedule(static) shared(status) \
1286 magick_number_threads(image,image,rows,1)
1288 for (y=0; y < (ssize_t) rows; y++)
1291 channel_distortion[CompositeChannels+1];
1294 *magick_restrict indexes,
1295 *magick_restrict reconstruct_indexes;
1305 if (status == MagickFalse)
1307 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1308 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1314 indexes=GetCacheViewVirtualIndexQueue(image_view);
1315 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1316 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
1317 for (x=0; x < (ssize_t) columns; x++)
1324 Sa=QuantumScale*(image->matte != MagickFalse ? GetPixelAlpha(p) :
1325 (QuantumRange-OpaqueOpacity));
1326 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1327 GetPixelAlpha(q) : (QuantumRange-OpaqueOpacity));
1328 if ((channel & RedChannel) != 0)
1330 distance=QuantumScale*fabs((
double) (Sa*GetPixelRed(p)-Da*
1332 if (distance > channel_distortion[RedChannel])
1333 channel_distortion[RedChannel]=distance;
1334 if (distance > channel_distortion[CompositeChannels])
1335 channel_distortion[CompositeChannels]=distance;
1337 if ((channel & GreenChannel) != 0)
1339 distance=QuantumScale*fabs((
double) (Sa*GetPixelGreen(p)-Da*
1341 if (distance > channel_distortion[GreenChannel])
1342 channel_distortion[GreenChannel]=distance;
1343 if (distance > channel_distortion[CompositeChannels])
1344 channel_distortion[CompositeChannels]=distance;
1346 if ((channel & BlueChannel) != 0)
1348 distance=QuantumScale*fabs((
double) (Sa*GetPixelBlue(p)-Da*
1350 if (distance > channel_distortion[BlueChannel])
1351 channel_distortion[BlueChannel]=distance;
1352 if (distance > channel_distortion[CompositeChannels])
1353 channel_distortion[CompositeChannels]=distance;
1355 if (((channel & OpacityChannel) != 0) &&
1356 (image->matte != MagickFalse))
1358 distance=QuantumScale*fabs((
double) (GetPixelOpacity(p)-(
double)
1359 GetPixelOpacity(q)));
1360 if (distance > channel_distortion[OpacityChannel])
1361 channel_distortion[OpacityChannel]=distance;
1362 if (distance > channel_distortion[CompositeChannels])
1363 channel_distortion[CompositeChannels]=distance;
1365 if (((channel & IndexChannel) != 0) &&
1366 (image->colorspace == CMYKColorspace) &&
1367 (reconstruct_image->colorspace == CMYKColorspace))
1369 distance=QuantumScale*fabs((
double) (Sa*GetPixelIndex(indexes+x)-Da*
1370 GetPixelIndex(reconstruct_indexes+x)));
1371 if (distance > channel_distortion[BlackChannel])
1372 channel_distortion[BlackChannel]=distance;
1373 if (distance > channel_distortion[CompositeChannels])
1374 channel_distortion[CompositeChannels]=distance;
1379 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1380 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
1382 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1383 if (channel_distortion[i] > distortion[i])
1384 distortion[i]=channel_distortion[i];
1386 reconstruct_view=DestroyCacheView(reconstruct_view);
1387 image_view=DestroyCacheView(image_view);
1391 static inline double MagickLog10(
const double x)
1393 #define Log10Epsilon (1.0e-11)
1395 if (fabs(x) < Log10Epsilon)
1396 return(log10(Log10Epsilon));
1397 return(log10(fabs(x)));
1400 static MagickBooleanType GetPeakSignalToNoiseRatio(
const Image *image,
1401 const Image *reconstruct_image,
const ChannelType channel,
1407 status=GetMeanSquaredDistortion(image,reconstruct_image,channel,distortion,
1409 if ((channel & RedChannel) != 0)
1411 if (fabs(distortion[RedChannel]) < MagickEpsilon)
1412 distortion[RedChannel]=INFINITY;
1414 distortion[RedChannel]=10.0*MagickLog10(1.0)-10.0*
1415 MagickLog10(distortion[RedChannel]);
1417 if ((channel & GreenChannel) != 0)
1419 if (fabs(distortion[GreenChannel]) < MagickEpsilon)
1420 distortion[GreenChannel]=INFINITY;
1422 distortion[GreenChannel]=10.0*MagickLog10(1.0)-10.0*
1423 MagickLog10(distortion[GreenChannel]);
1425 if ((channel & BlueChannel) != 0)
1427 if (fabs(distortion[BlueChannel]) < MagickEpsilon)
1428 distortion[BlueChannel]=INFINITY;
1430 distortion[BlueChannel]=10.0*MagickLog10(1.0)-10.0*
1431 MagickLog10(distortion[BlueChannel]);
1433 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
1435 if (fabs(distortion[OpacityChannel]) < MagickEpsilon)
1436 distortion[OpacityChannel]=INFINITY;
1438 distortion[OpacityChannel]=10.0*MagickLog10(1.0)-10.0*
1439 MagickLog10(distortion[OpacityChannel]);
1441 if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
1443 if (fabs(distortion[BlackChannel]) < MagickEpsilon)
1444 distortion[BlackChannel]=INFINITY;
1446 distortion[BlackChannel]=10.0*MagickLog10(1.0)-10.0*
1447 MagickLog10(distortion[BlackChannel]);
1449 if (fabs(distortion[CompositeChannels]) < MagickEpsilon)
1450 distortion[CompositeChannels]=INFINITY;
1452 distortion[CompositeChannels]=10.0*MagickLog10(1.0)-10.0*
1453 MagickLog10(distortion[CompositeChannels]);
1457 static MagickBooleanType GetPerceptualHashDistortion(
const Image *image,
1458 const Image *reconstruct_image,
const ChannelType channel,
double *distortion,
1474 image_phash=GetImageChannelPerceptualHash(image,exception);
1476 return(MagickFalse);
1477 reconstruct_phash=GetImageChannelPerceptualHash(reconstruct_image,exception);
1481 return(MagickFalse);
1483 for (i=0; i < MaximumNumberOfImageMoments; i++)
1488 if ((channel & RedChannel) != 0)
1490 difference=reconstruct_phash[RedChannel].P[i]-
1491 image_phash[RedChannel].P[i];
1492 distortion[RedChannel]+=difference*difference;
1493 distortion[CompositeChannels]+=difference*difference;
1495 if ((channel & GreenChannel) != 0)
1497 difference=reconstruct_phash[GreenChannel].P[i]-
1498 image_phash[GreenChannel].P[i];
1499 distortion[GreenChannel]+=difference*difference;
1500 distortion[CompositeChannels]+=difference*difference;
1502 if ((channel & BlueChannel) != 0)
1504 difference=reconstruct_phash[BlueChannel].P[i]-
1505 image_phash[BlueChannel].P[i];
1506 distortion[BlueChannel]+=difference*difference;
1507 distortion[CompositeChannels]+=difference*difference;
1509 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse) &&
1510 (reconstruct_image->matte != MagickFalse))
1512 difference=reconstruct_phash[OpacityChannel].P[i]-
1513 image_phash[OpacityChannel].P[i];
1514 distortion[OpacityChannel]+=difference*difference;
1515 distortion[CompositeChannels]+=difference*difference;
1517 if (((channel & IndexChannel) != 0) &&
1518 (image->colorspace == CMYKColorspace) &&
1519 (reconstruct_image->colorspace == CMYKColorspace))
1521 difference=reconstruct_phash[IndexChannel].P[i]-
1522 image_phash[IndexChannel].P[i];
1523 distortion[IndexChannel]+=difference*difference;
1524 distortion[CompositeChannels]+=difference*difference;
1530 for (i=0; i < MaximumNumberOfImageMoments; i++)
1535 if ((channel & RedChannel) != 0)
1537 difference=reconstruct_phash[RedChannel].Q[i]-
1538 image_phash[RedChannel].Q[i];
1539 distortion[RedChannel]+=difference*difference;
1540 distortion[CompositeChannels]+=difference*difference;
1542 if ((channel & GreenChannel) != 0)
1544 difference=reconstruct_phash[GreenChannel].Q[i]-
1545 image_phash[GreenChannel].Q[i];
1546 distortion[GreenChannel]+=difference*difference;
1547 distortion[CompositeChannels]+=difference*difference;
1549 if ((channel & BlueChannel) != 0)
1551 difference=reconstruct_phash[BlueChannel].Q[i]-
1552 image_phash[BlueChannel].Q[i];
1553 distortion[BlueChannel]+=difference*difference;
1554 distortion[CompositeChannels]+=difference*difference;
1556 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse) &&
1557 (reconstruct_image->matte != MagickFalse))
1559 difference=reconstruct_phash[OpacityChannel].Q[i]-
1560 image_phash[OpacityChannel].Q[i];
1561 distortion[OpacityChannel]+=difference*difference;
1562 distortion[CompositeChannels]+=difference*difference;
1564 if (((channel & IndexChannel) != 0) &&
1565 (image->colorspace == CMYKColorspace) &&
1566 (reconstruct_image->colorspace == CMYKColorspace))
1568 difference=reconstruct_phash[IndexChannel].Q[i]-
1569 image_phash[IndexChannel].Q[i];
1570 distortion[IndexChannel]+=difference*difference;
1571 distortion[CompositeChannels]+=difference*difference;
1583 static MagickBooleanType GetRootMeanSquaredDistortion(
const Image *image,
1584 const Image *reconstruct_image,
const ChannelType channel,
double *distortion,
1590 status=GetMeanSquaredDistortion(image,reconstruct_image,channel,distortion,
1592 if ((channel & RedChannel) != 0)
1593 distortion[RedChannel]=sqrt(distortion[RedChannel]);
1594 if ((channel & GreenChannel) != 0)
1595 distortion[GreenChannel]=sqrt(distortion[GreenChannel]);
1596 if ((channel & BlueChannel) != 0)
1597 distortion[BlueChannel]=sqrt(distortion[BlueChannel]);
1598 if (((channel & OpacityChannel) != 0) &&
1599 (image->matte != MagickFalse))
1600 distortion[OpacityChannel]=sqrt(distortion[OpacityChannel]);
1601 if (((channel & IndexChannel) != 0) &&
1602 (image->colorspace == CMYKColorspace))
1603 distortion[BlackChannel]=sqrt(distortion[BlackChannel]);
1604 distortion[CompositeChannels]=sqrt(distortion[CompositeChannels]);
1608 MagickExport MagickBooleanType GetImageChannelDistortion(
Image *image,
1609 const Image *reconstruct_image,
const ChannelType channel,
1610 const MetricType metric,
double *distortion,
ExceptionInfo *exception)
1613 *channel_distortion;
1621 assert(image != (
Image *) NULL);
1622 assert(image->signature == MagickCoreSignature);
1623 assert(reconstruct_image != (
const Image *) NULL);
1624 assert(reconstruct_image->signature == MagickCoreSignature);
1625 assert(distortion != (
double *) NULL);
1626 if (IsEventLogging() != MagickFalse)
1627 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
1629 if (metric != PerceptualHashErrorMetric)
1630 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1631 ThrowBinaryException(ImageError,
"ImageMorphologyDiffers",image->filename);
1635 length=CompositeChannels+1UL;
1636 channel_distortion=(
double *) AcquireQuantumMemory(length,
1637 sizeof(*channel_distortion));
1638 if (channel_distortion == (
double *) NULL)
1639 ThrowFatalException(ResourceLimitFatalError,
"MemoryAllocationFailed");
1640 (void) memset(channel_distortion,0,length*
sizeof(*channel_distortion));
1643 case AbsoluteErrorMetric:
1645 status=GetAbsoluteDistortion(image,reconstruct_image,channel,
1646 channel_distortion,exception);
1649 case FuzzErrorMetric:
1651 status=GetFuzzDistortion(image,reconstruct_image,channel,
1652 channel_distortion,exception);
1655 case MeanAbsoluteErrorMetric:
1657 status=GetMeanAbsoluteDistortion(image,reconstruct_image,channel,
1658 channel_distortion,exception);
1661 case MeanErrorPerPixelMetric:
1663 status=GetMeanErrorPerPixel(image,reconstruct_image,channel,
1664 channel_distortion,exception);
1667 case MeanSquaredErrorMetric:
1669 status=GetMeanSquaredDistortion(image,reconstruct_image,channel,
1670 channel_distortion,exception);
1673 case NormalizedCrossCorrelationErrorMetric:
1676 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1677 channel,channel_distortion,exception);
1680 case PeakAbsoluteErrorMetric:
1682 status=GetPeakAbsoluteDistortion(image,reconstruct_image,channel,
1683 channel_distortion,exception);
1686 case PeakSignalToNoiseRatioMetric:
1688 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,channel,
1689 channel_distortion,exception);
1692 case PerceptualHashErrorMetric:
1694 status=GetPerceptualHashDistortion(image,reconstruct_image,channel,
1695 channel_distortion,exception);
1698 case RootMeanSquaredErrorMetric:
1700 status=GetRootMeanSquaredDistortion(image,reconstruct_image,channel,
1701 channel_distortion,exception);
1705 *distortion=channel_distortion[CompositeChannels];
1706 channel_distortion=(
double *) RelinquishMagickMemory(channel_distortion);
1707 (void) FormatImageProperty(image,
"distortion",
"%.*g",GetMagickPrecision(),
1744 MagickExport
double *GetImageChannelDistortions(
Image *image,
1745 const Image *reconstruct_image,
const MetricType metric,
1749 *channel_distortion;
1757 assert(image != (
Image *) NULL);
1758 assert(image->signature == MagickCoreSignature);
1759 assert(reconstruct_image != (
const Image *) NULL);
1760 assert(reconstruct_image->signature == MagickCoreSignature);
1761 if (IsEventLogging() != MagickFalse)
1762 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
1763 if (metric != PerceptualHashErrorMetric)
1764 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1766 (void) ThrowMagickException(&image->exception,GetMagickModule(),
1767 ImageError,
"ImageMorphologyDiffers",
"`%s'",image->filename);
1768 return((
double *) NULL);
1773 length=CompositeChannels+1UL;
1774 channel_distortion=(
double *) AcquireQuantumMemory(length,
1775 sizeof(*channel_distortion));
1776 if (channel_distortion == (
double *) NULL)
1777 ThrowFatalException(ResourceLimitFatalError,
"MemoryAllocationFailed");
1778 (void) memset(channel_distortion,0,length*
1779 sizeof(*channel_distortion));
1783 case AbsoluteErrorMetric:
1785 status=GetAbsoluteDistortion(image,reconstruct_image,CompositeChannels,
1786 channel_distortion,exception);
1789 case FuzzErrorMetric:
1791 status=GetFuzzDistortion(image,reconstruct_image,CompositeChannels,
1792 channel_distortion,exception);
1795 case MeanAbsoluteErrorMetric:
1797 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1798 CompositeChannels,channel_distortion,exception);
1801 case MeanErrorPerPixelMetric:
1803 status=GetMeanErrorPerPixel(image,reconstruct_image,CompositeChannels,
1804 channel_distortion,exception);
1807 case MeanSquaredErrorMetric:
1809 status=GetMeanSquaredDistortion(image,reconstruct_image,CompositeChannels,
1810 channel_distortion,exception);
1813 case NormalizedCrossCorrelationErrorMetric:
1816 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1817 CompositeChannels,channel_distortion,exception);
1820 case PeakAbsoluteErrorMetric:
1822 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1823 CompositeChannels,channel_distortion,exception);
1826 case PeakSignalToNoiseRatioMetric:
1828 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1829 CompositeChannels,channel_distortion,exception);
1832 case PerceptualHashErrorMetric:
1834 status=GetPerceptualHashDistortion(image,reconstruct_image,
1835 CompositeChannels,channel_distortion,exception);
1838 case RootMeanSquaredErrorMetric:
1840 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1841 CompositeChannels,channel_distortion,exception);
1845 if (status == MagickFalse)
1847 channel_distortion=(
double *) RelinquishMagickMemory(channel_distortion);
1848 return((
double *) NULL);
1850 return(channel_distortion);
1900 MagickExport MagickBooleanType IsImagesEqual(
Image *image,
1901 const Image *reconstruct_image)
1918 mean_error_per_pixel;
1927 assert(image != (
Image *) NULL);
1928 assert(image->signature == MagickCoreSignature);
1929 assert(reconstruct_image != (
const Image *) NULL);
1930 assert(reconstruct_image->signature == MagickCoreSignature);
1931 exception=(&image->exception);
1932 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1933 ThrowBinaryException(ImageError,
"ImageMorphologyDiffers",image->filename);
1936 mean_error_per_pixel=0.0;
1938 rows=MagickMax(image->rows,reconstruct_image->rows);
1939 columns=MagickMax(image->columns,reconstruct_image->columns);
1940 image_view=AcquireVirtualCacheView(image,exception);
1941 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1942 for (y=0; y < (ssize_t) rows; y++)
1945 *magick_restrict indexes,
1946 *magick_restrict reconstruct_indexes;
1955 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1956 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1959 indexes=GetCacheViewVirtualIndexQueue(image_view);
1960 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1961 for (x=0; x < (ssize_t) columns; x++)
1966 distance=fabs((
double) (GetPixelRed(p)-(
double) GetPixelRed(q)));
1967 mean_error_per_pixel+=distance;
1968 mean_error+=distance*distance;
1969 if (distance > maximum_error)
1970 maximum_error=distance;
1972 distance=fabs((
double) (GetPixelGreen(p)-(
double) GetPixelGreen(q)));
1973 mean_error_per_pixel+=distance;
1974 mean_error+=distance*distance;
1975 if (distance > maximum_error)
1976 maximum_error=distance;
1978 distance=fabs((
double) (GetPixelBlue(p)-(
double) GetPixelBlue(q)));
1979 mean_error_per_pixel+=distance;
1980 mean_error+=distance*distance;
1981 if (distance > maximum_error)
1982 maximum_error=distance;
1984 if (image->matte != MagickFalse)
1986 distance=fabs((
double) (GetPixelOpacity(p)-(
double)
1987 GetPixelOpacity(q)));
1988 mean_error_per_pixel+=distance;
1989 mean_error+=distance*distance;
1990 if (distance > maximum_error)
1991 maximum_error=distance;
1994 if ((image->colorspace == CMYKColorspace) &&
1995 (reconstruct_image->colorspace == CMYKColorspace))
1997 distance=fabs((
double) (GetPixelIndex(indexes+x)-(
double)
1998 GetPixelIndex(reconstruct_indexes+x)));
1999 mean_error_per_pixel+=distance;
2000 mean_error+=distance*distance;
2001 if (distance > maximum_error)
2002 maximum_error=distance;
2009 reconstruct_view=DestroyCacheView(reconstruct_view);
2010 image_view=DestroyCacheView(image_view);
2011 gamma=PerceptibleReciprocal(area);
2012 image->error.mean_error_per_pixel=gamma*mean_error_per_pixel;
2013 image->error.normalized_mean_error=gamma*QuantumScale*QuantumScale*mean_error;
2014 image->error.normalized_maximum_error=QuantumScale*maximum_error;
2015 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
2054 static double GetSimilarityMetric(
const Image *image,
const Image *reference,
2055 const MetricType metric,
const ssize_t x_offset,
const ssize_t y_offset,
2070 SetGeometry(reference,&geometry);
2071 geometry.x=x_offset;
2072 geometry.y=y_offset;
2073 similarity_image=CropImage(image,&geometry,exception);
2074 if (similarity_image == (
Image *) NULL)
2077 status=GetImageDistortion(similarity_image,reference,metric,&distortion,
2080 similarity_image=DestroyImage(similarity_image);
2084 MagickExport
Image *SimilarityImage(
Image *image,
const Image *reference,
2090 similarity_image=SimilarityMetricImage(image,reference,
2091 RootMeanSquaredErrorMetric,offset,similarity_metric,exception);
2092 return(similarity_image);
2095 MagickExport
Image *SimilarityMetricImage(
Image *image,
const Image *reference,
2096 const MetricType metric,
RectangleInfo *offset,
double *similarity_metric,
2099 #define SimilarityImageTag "Similarity/Image"
2108 similarity_threshold;
2122 assert(image != (
const Image *) NULL);
2123 assert(image->signature == MagickCoreSignature);
2125 assert(exception->signature == MagickCoreSignature);
2127 if (IsEventLogging() != MagickFalse)
2128 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
2129 SetGeometry(reference,offset);
2130 *similarity_metric=MagickMaximumValue;
2131 if (ValidateImageMorphology(image,reference) == MagickFalse)
2132 ThrowImageException(ImageError,
"ImageMorphologyDiffers");
2133 similarity_image=CloneImage(image,image->columns-reference->columns+1,
2134 image->rows-reference->rows+1,MagickTrue,exception);
2135 if (similarity_image == (
Image *) NULL)
2136 return((
Image *) NULL);
2137 if (SetImageStorageClass(similarity_image,DirectClass) == MagickFalse)
2139 InheritException(exception,&similarity_image->exception);
2140 similarity_image=DestroyImage(similarity_image);
2141 return((
Image *) NULL);
2143 (void) SetImageAlphaChannel(similarity_image,DeactivateAlphaChannel);
2147 similarity_threshold=(-1.0);
2148 artifact=GetImageArtifact(image,
"compare:similarity-threshold");
2149 if (artifact != (
const char *) NULL)
2150 similarity_threshold=StringToDouble(artifact,(
char **) NULL);
2153 similarity_view=AcquireVirtualCacheView(similarity_image,exception);
2154 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2155 #pragma omp parallel for schedule(static) \
2156 shared(progress,status,similarity_metric) \
2157 magick_number_threads(image,image,image->rows-reference->rows+1,1)
2159 for (y=0; y < (ssize_t) (image->rows-reference->rows+1); y++)
2170 if (status == MagickFalse)
2172 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2173 #pragma omp flush(similarity_metric)
2175 if (*similarity_metric <= similarity_threshold)
2177 q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
2184 for (x=0; x < (ssize_t) (image->columns-reference->columns+1); x++)
2186 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2187 #pragma omp flush(similarity_metric)
2189 if (*similarity_metric <= similarity_threshold)
2191 similarity=GetSimilarityMetric(image,reference,metric,x,y,exception);
2192 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2193 #pragma omp critical (MagickCore_SimilarityImage)
2195 if ((metric == NormalizedCrossCorrelationErrorMetric) ||
2196 (metric == UndefinedErrorMetric))
2197 similarity=1.0-similarity;
2198 if (similarity < *similarity_metric)
2200 *similarity_metric=similarity;
2204 if (metric == PerceptualHashErrorMetric)
2205 similarity=MagickMin(0.01*similarity,1.0);
2206 SetPixelRed(q,ClampToQuantum(QuantumRange-QuantumRange*similarity));
2207 SetPixelGreen(q,GetPixelRed(q));
2208 SetPixelBlue(q,GetPixelRed(q));
2211 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
2213 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2218 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2222 proceed=SetImageProgress(image,SimilarityImageTag,progress,image->rows);
2223 if (proceed == MagickFalse)
2227 similarity_view=DestroyCacheView(similarity_view);
2228 if (status == MagickFalse)
2229 similarity_image=DestroyImage(similarity_image);
2230 return(similarity_image);