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/statistic-private.h"
68 #include "magick/string_.h"
69 #include "magick/string-private.h"
70 #include "magick/statistic.h"
71 #include "magick/thread-private.h"
72 #include "magick/transform.h"
73 #include "magick/utility.h"
74 #include "magick/version.h"
112 MagickExport
Image *CompareImages(
Image *image,
const Image *reconstruct_image,
113 const MetricType metric,
double *distortion,
ExceptionInfo *exception)
118 highlight_image=CompareImageChannels(image,reconstruct_image,
119 CompositeChannels,metric,distortion,exception);
120 return(highlight_image);
123 static size_t GetNumberChannels(
const Image *image,
const ChannelType channel)
129 if ((channel & RedChannel) != 0)
131 if ((channel & GreenChannel) != 0)
133 if ((channel & BlueChannel) != 0)
135 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
137 if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
139 return(channels == 0 ? 1UL : channels);
142 static inline MagickBooleanType ValidateImageMorphology(
143 const Image *magick_restrict image,
144 const Image *magick_restrict reconstruct_image)
149 if (GetNumberChannels(image,DefaultChannels) !=
150 GetNumberChannels(reconstruct_image,DefaultChannels))
155 MagickExport
Image *CompareImageChannels(
Image *image,
156 const Image *reconstruct_image,
const ChannelType channel,
157 const MetricType metric,
double *distortion,
ExceptionInfo *exception)
190 assert(image != (
Image *) NULL);
191 assert(image->signature == MagickCoreSignature);
192 assert(reconstruct_image != (
const Image *) NULL);
193 assert(reconstruct_image->signature == MagickCoreSignature);
194 assert(distortion != (
double *) NULL);
195 if (IsEventLogging() != MagickFalse)
196 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
198 if (metric != PerceptualHashErrorMetric)
199 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
200 ThrowImageException(ImageError,
"ImageMorphologyDiffers");
201 status=GetImageChannelDistortion(image,reconstruct_image,channel,metric,
202 distortion,exception);
203 if (status == MagickFalse)
204 return((
Image *) NULL);
205 clone_image=CloneImage(image,0,0,MagickTrue,exception);
206 if (clone_image == (
Image *) NULL)
207 return((
Image *) NULL);
208 (void) SetImageMask(clone_image,(
Image *) NULL);
209 difference_image=CloneImage(clone_image,0,0,MagickTrue,exception);
210 clone_image=DestroyImage(clone_image);
211 if (difference_image == (
Image *) NULL)
212 return((
Image *) NULL);
213 (void) SetImageAlphaChannel(difference_image,OpaqueAlphaChannel);
214 rows=MagickMax(image->rows,reconstruct_image->rows);
215 columns=MagickMax(image->columns,reconstruct_image->columns);
216 highlight_image=CloneImage(image,columns,rows,MagickTrue,exception);
217 if (highlight_image == (
Image *) NULL)
219 difference_image=DestroyImage(difference_image);
220 return((
Image *) NULL);
222 if (SetImageStorageClass(highlight_image,DirectClass) == MagickFalse)
224 InheritException(exception,&highlight_image->exception);
225 difference_image=DestroyImage(difference_image);
226 highlight_image=DestroyImage(highlight_image);
227 return((
Image *) NULL);
229 (void) SetImageMask(highlight_image,(
Image *) NULL);
230 (void) SetImageAlphaChannel(highlight_image,OpaqueAlphaChannel);
231 (void) QueryMagickColor(
"#f1001ecc",&highlight,exception);
232 artifact=GetImageArtifact(image,
"compare:highlight-color");
233 if (artifact != (
const char *) NULL)
234 (void) QueryMagickColor(artifact,&highlight,exception);
235 (void) QueryMagickColor(
"#ffffffcc",&lowlight,exception);
236 artifact=GetImageArtifact(image,
"compare:lowlight-color");
237 if (artifact != (
const char *) NULL)
238 (void) QueryMagickColor(artifact,&lowlight,exception);
239 if (highlight_image->colorspace == CMYKColorspace)
241 ConvertRGBToCMYK(&highlight);
242 ConvertRGBToCMYK(&lowlight);
248 fuzz=GetFuzzyColorDistance(image,reconstruct_image);
249 GetMagickPixelPacket(image,&zero);
250 image_view=AcquireVirtualCacheView(image,exception);
251 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
252 highlight_view=AcquireAuthenticCacheView(highlight_image,exception);
253 #if defined(MAGICKCORE_OPENMP_SUPPORT)
254 #pragma omp parallel for schedule(static) shared(status) \
255 magick_number_threads(image,highlight_image,rows,1)
257 for (y=0; y < (ssize_t) rows; y++)
267 *magick_restrict indexes,
268 *magick_restrict reconstruct_indexes;
275 *magick_restrict highlight_indexes;
283 if (status == MagickFalse)
285 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
286 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
287 r=QueueCacheViewAuthenticPixels(highlight_view,0,y,columns,1,exception);
294 indexes=GetCacheViewVirtualIndexQueue(image_view);
295 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
296 highlight_indexes=GetCacheViewAuthenticIndexQueue(highlight_view);
298 reconstruct_pixel=zero;
299 for (x=0; x < (ssize_t) columns; x++)
304 SetMagickPixelPacket(image,p,indexes == (IndexPacket *) NULL ? NULL :
306 SetMagickPixelPacket(reconstruct_image,q,reconstruct_indexes ==
307 (IndexPacket *) NULL ? NULL : reconstruct_indexes+x,&reconstruct_pixel);
308 difference=MagickFalse;
309 if (channel == CompositeChannels)
311 if (IsMagickColorSimilar(&pixel,&reconstruct_pixel) == MagickFalse)
312 difference=MagickTrue;
322 Sa=QuantumScale*(image->matte != MagickFalse ? GetPixelAlpha(p) :
323 (QuantumRange-OpaqueOpacity));
324 Da=QuantumScale*(image->matte != MagickFalse ? GetPixelAlpha(q) :
325 (QuantumRange-OpaqueOpacity));
326 if ((channel & RedChannel) != 0)
328 pixel=Sa*GetPixelRed(p)-Da*GetPixelRed(q);
329 distance=pixel*pixel;
330 if (distance >= fuzz)
331 difference=MagickTrue;
333 if ((channel & GreenChannel) != 0)
335 pixel=Sa*GetPixelGreen(p)-Da*GetPixelGreen(q);
336 distance=pixel*pixel;
337 if (distance >= fuzz)
338 difference=MagickTrue;
340 if ((channel & BlueChannel) != 0)
342 pixel=Sa*GetPixelBlue(p)-Da*GetPixelBlue(q);
343 distance=pixel*pixel;
344 if (distance >= fuzz)
345 difference=MagickTrue;
347 if (((channel & OpacityChannel) != 0) &&
348 (image->matte != MagickFalse))
350 pixel=(double) GetPixelOpacity(p)-GetPixelOpacity(q);
351 distance=pixel*pixel;
352 if (distance >= fuzz)
353 difference=MagickTrue;
355 if (((channel & IndexChannel) != 0) &&
356 (image->colorspace == CMYKColorspace))
358 pixel=Sa*indexes[x]-Da*reconstruct_indexes[x];
359 distance=pixel*pixel;
360 if (distance >= fuzz)
361 difference=MagickTrue;
364 if (difference != MagickFalse)
365 SetPixelPacket(highlight_image,&highlight,r,highlight_indexes ==
366 (IndexPacket *) NULL ? NULL : highlight_indexes+x);
368 SetPixelPacket(highlight_image,&lowlight,r,highlight_indexes ==
369 (IndexPacket *) NULL ? NULL : highlight_indexes+x);
374 sync=SyncCacheViewAuthenticPixels(highlight_view,exception);
375 if (sync == MagickFalse)
378 highlight_view=DestroyCacheView(highlight_view);
379 reconstruct_view=DestroyCacheView(reconstruct_view);
380 image_view=DestroyCacheView(image_view);
381 (void) CompositeImage(difference_image,image->compose,highlight_image,0,0);
382 highlight_image=DestroyImage(highlight_image);
383 if (status == MagickFalse)
384 difference_image=DestroyImage(difference_image);
385 return(difference_image);
424 MagickExport MagickBooleanType GetImageDistortion(
Image *image,
425 const Image *reconstruct_image,
const MetricType metric,
double *distortion,
431 status=GetImageChannelDistortion(image,reconstruct_image,CompositeChannels,
432 metric,distortion,exception);
436 static MagickBooleanType GetAbsoluteDistortion(
const Image *image,
437 const Image *reconstruct_image,
const ChannelType channel,
double *distortion,
461 fuzz=GetFuzzyColorDistance(image,reconstruct_image);
462 rows=MagickMax(image->rows,reconstruct_image->rows);
463 columns=MagickMax(image->columns,reconstruct_image->columns);
464 image_view=AcquireVirtualCacheView(image,exception);
465 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
466 #if defined(MAGICKCORE_OPENMP_SUPPORT)
467 #pragma omp parallel for schedule(static) shared(status) \
468 magick_number_threads(image,image,rows,1)
470 for (y=0; y < (ssize_t) rows; y++)
473 channel_distortion[CompositeChannels+1];
476 *magick_restrict indexes,
477 *magick_restrict reconstruct_indexes;
487 if (status == MagickFalse)
489 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
490 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
496 indexes=GetCacheViewVirtualIndexQueue(image_view);
497 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
498 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
499 for (x=0; x < (ssize_t) columns; x++)
510 difference=MagickFalse;
511 Sa=QuantumScale*(image->matte != MagickFalse ? GetPixelAlpha(p) :
512 (QuantumRange-OpaqueOpacity));
513 Da=QuantumScale*(image->matte != MagickFalse ? GetPixelAlpha(q) :
514 (QuantumRange-OpaqueOpacity));
515 if ((channel & RedChannel) != 0)
517 pixel=Sa*GetPixelRed(p)-Da*GetPixelRed(q);
518 distance=pixel*pixel;
519 if (distance >= fuzz)
521 channel_distortion[RedChannel]++;
522 difference=MagickTrue;
525 if ((channel & GreenChannel) != 0)
527 pixel=Sa*GetPixelGreen(p)-Da*GetPixelGreen(q);
528 distance=pixel*pixel;
529 if (distance >= fuzz)
531 channel_distortion[GreenChannel]++;
532 difference=MagickTrue;
535 if ((channel & BlueChannel) != 0)
537 pixel=Sa*GetPixelBlue(p)-Da*GetPixelBlue(q);
538 distance=pixel*pixel;
539 if (distance >= fuzz)
541 channel_distortion[BlueChannel]++;
542 difference=MagickTrue;
545 if (((channel & OpacityChannel) != 0) &&
546 (image->matte != MagickFalse))
548 pixel=(double) GetPixelOpacity(p)-GetPixelOpacity(q);
549 distance=pixel*pixel;
550 if (distance >= fuzz)
552 channel_distortion[OpacityChannel]++;
553 difference=MagickTrue;
556 if (((channel & IndexChannel) != 0) &&
557 (image->colorspace == CMYKColorspace))
559 pixel=Sa*indexes[x]-Da*reconstruct_indexes[x];
560 distance=pixel*pixel;
561 if (distance >= fuzz)
563 channel_distortion[BlackChannel]++;
564 difference=MagickTrue;
567 if (difference != MagickFalse)
568 channel_distortion[CompositeChannels]++;
572 #if defined(MAGICKCORE_OPENMP_SUPPORT)
573 #pragma omp critical (MagickCore_GetAbsoluteDistortion)
575 for (i=0; i <= (ssize_t) CompositeChannels; i++)
576 distortion[i]+=channel_distortion[i];
578 reconstruct_view=DestroyCacheView(reconstruct_view);
579 image_view=DestroyCacheView(image_view);
583 static MagickBooleanType GetFuzzDistortion(
const Image *image,
584 const Image *reconstruct_image,
const ChannelType channel,
605 rows=MagickMax(image->rows,reconstruct_image->rows);
606 columns=MagickMax(image->columns,reconstruct_image->columns);
607 image_view=AcquireVirtualCacheView(image,exception);
608 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
609 #if defined(MAGICKCORE_OPENMP_SUPPORT)
610 #pragma omp parallel for schedule(static) shared(status) \
611 magick_number_threads(image,image,rows,1)
613 for (y=0; y < (ssize_t) rows; y++)
616 channel_distortion[CompositeChannels+1];
619 *magick_restrict indexes,
620 *magick_restrict reconstruct_indexes;
630 if (status == MagickFalse)
632 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
633 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
639 indexes=GetCacheViewVirtualIndexQueue(image_view);
640 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
641 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
642 for (x=0; x < (ssize_t) columns; x++)
649 Sa=QuantumScale*(image->matte != MagickFalse ? GetPixelAlpha(p) :
650 (QuantumRange-OpaqueOpacity));
651 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
652 GetPixelAlpha(q) : (QuantumRange-OpaqueOpacity));
653 if ((channel & RedChannel) != 0)
655 distance=QuantumScale*(Sa*GetPixelRed(p)-Da*GetPixelRed(q));
656 channel_distortion[RedChannel]+=distance*distance;
657 channel_distortion[CompositeChannels]+=distance*distance;
659 if ((channel & GreenChannel) != 0)
661 distance=QuantumScale*(Sa*GetPixelGreen(p)-Da*GetPixelGreen(q));
662 channel_distortion[GreenChannel]+=distance*distance;
663 channel_distortion[CompositeChannels]+=distance*distance;
665 if ((channel & BlueChannel) != 0)
667 distance=QuantumScale*(Sa*GetPixelBlue(p)-Da*GetPixelBlue(q));
668 channel_distortion[BlueChannel]+=distance*distance;
669 channel_distortion[CompositeChannels]+=distance*distance;
671 if (((channel & OpacityChannel) != 0) && ((image->matte != MagickFalse) ||
672 (reconstruct_image->matte != MagickFalse)))
674 distance=QuantumScale*((image->matte != MagickFalse ?
675 GetPixelOpacity(p) : OpaqueOpacity)-
676 (reconstruct_image->matte != MagickFalse ?
677 GetPixelOpacity(q): OpaqueOpacity));
678 channel_distortion[OpacityChannel]+=distance*distance;
679 channel_distortion[CompositeChannels]+=distance*distance;
681 if (((channel & IndexChannel) != 0) &&
682 (image->colorspace == CMYKColorspace) &&
683 (reconstruct_image->colorspace == CMYKColorspace))
685 distance=QuantumScale*(Sa*GetPixelIndex(indexes+x)-
686 Da*GetPixelIndex(reconstruct_indexes+x));
687 channel_distortion[BlackChannel]+=distance*distance;
688 channel_distortion[CompositeChannels]+=distance*distance;
693 #if defined(MAGICKCORE_OPENMP_SUPPORT)
694 #pragma omp critical (MagickCore_GetFuzzDistortion)
696 for (i=0; i <= (ssize_t) CompositeChannels; i++)
697 distortion[i]+=channel_distortion[i];
699 reconstruct_view=DestroyCacheView(reconstruct_view);
700 image_view=DestroyCacheView(image_view);
701 for (i=0; i <= (ssize_t) CompositeChannels; i++)
702 distortion[i]/=((
double) columns*rows);
703 distortion[CompositeChannels]/=(double) GetNumberChannels(image,channel);
704 distortion[CompositeChannels]=sqrt(distortion[CompositeChannels]);
708 static MagickBooleanType GetMeanAbsoluteDistortion(
const Image *image,
709 const Image *reconstruct_image,
const ChannelType channel,
730 rows=MagickMax(image->rows,reconstruct_image->rows);
731 columns=MagickMax(image->columns,reconstruct_image->columns);
732 image_view=AcquireVirtualCacheView(image,exception);
733 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
734 #if defined(MAGICKCORE_OPENMP_SUPPORT)
735 #pragma omp parallel for schedule(static) shared(status) \
736 magick_number_threads(image,image,rows,1)
738 for (y=0; y < (ssize_t) rows; y++)
741 channel_distortion[CompositeChannels+1];
744 *magick_restrict indexes,
745 *magick_restrict reconstruct_indexes;
755 if (status == MagickFalse)
757 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
758 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
764 indexes=GetCacheViewVirtualIndexQueue(image_view);
765 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
766 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
767 for (x=0; x < (ssize_t) columns; x++)
774 Sa=QuantumScale*(image->matte != MagickFalse ? GetPixelAlpha(p) :
775 (QuantumRange-OpaqueOpacity));
776 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
777 GetPixelAlpha(q) : (QuantumRange-OpaqueOpacity));
778 if ((channel & RedChannel) != 0)
780 distance=QuantumScale*fabs((
double) (Sa*GetPixelRed(p)-Da*
782 channel_distortion[RedChannel]+=distance;
783 channel_distortion[CompositeChannels]+=distance;
785 if ((channel & GreenChannel) != 0)
787 distance=QuantumScale*fabs((
double) (Sa*GetPixelGreen(p)-Da*
789 channel_distortion[GreenChannel]+=distance;
790 channel_distortion[CompositeChannels]+=distance;
792 if ((channel & BlueChannel) != 0)
794 distance=QuantumScale*fabs((
double) (Sa*GetPixelBlue(p)-Da*
796 channel_distortion[BlueChannel]+=distance;
797 channel_distortion[CompositeChannels]+=distance;
799 if (((channel & OpacityChannel) != 0) &&
800 (image->matte != MagickFalse))
802 distance=QuantumScale*fabs((
double) (GetPixelOpacity(p)-(
double)
803 GetPixelOpacity(q)));
804 channel_distortion[OpacityChannel]+=distance;
805 channel_distortion[CompositeChannels]+=distance;
807 if (((channel & IndexChannel) != 0) &&
808 (image->colorspace == CMYKColorspace))
810 distance=QuantumScale*fabs((
double) (Sa*GetPixelIndex(indexes+x)-Da*
811 GetPixelIndex(reconstruct_indexes+x)));
812 channel_distortion[BlackChannel]+=distance;
813 channel_distortion[CompositeChannels]+=distance;
818 #if defined(MAGICKCORE_OPENMP_SUPPORT)
819 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
821 for (i=0; i <= (ssize_t) CompositeChannels; i++)
822 distortion[i]+=channel_distortion[i];
824 reconstruct_view=DestroyCacheView(reconstruct_view);
825 image_view=DestroyCacheView(image_view);
826 for (i=0; i <= (ssize_t) CompositeChannels; i++)
827 distortion[i]/=((
double) columns*rows);
828 distortion[CompositeChannels]/=(double) GetNumberChannels(image,channel);
832 static MagickBooleanType GetMeanErrorPerPixel(
Image *image,
833 const Image *reconstruct_image,
const ChannelType channel,
double *distortion,
860 rows=MagickMax(image->rows,reconstruct_image->rows);
861 columns=MagickMax(image->columns,reconstruct_image->columns);
862 image_view=AcquireVirtualCacheView(image,exception);
863 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
864 for (y=0; y < (ssize_t) rows; y++)
867 *magick_restrict indexes,
868 *magick_restrict reconstruct_indexes;
877 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
878 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
884 indexes=GetCacheViewVirtualIndexQueue(image_view);
885 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
886 for (x=0; x < (ssize_t) columns; x++)
893 Sa=QuantumScale*(image->matte != MagickFalse ? GetPixelAlpha(p) :
894 (QuantumRange-OpaqueOpacity));
895 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
896 GetPixelAlpha(q) : (QuantumRange-OpaqueOpacity));
897 if ((channel & RedChannel) != 0)
899 distance=fabs((
double) (Sa*GetPixelRed(p)-Da*GetPixelRed(q)));
900 distortion[RedChannel]+=distance;
901 distortion[CompositeChannels]+=distance;
902 mean_error+=distance*distance;
903 if (distance > maximum_error)
904 maximum_error=distance;
907 if ((channel & GreenChannel) != 0)
909 distance=fabs((
double) (Sa*GetPixelGreen(p)-Da*GetPixelGreen(q)));
910 distortion[GreenChannel]+=distance;
911 distortion[CompositeChannels]+=distance;
912 mean_error+=distance*distance;
913 if (distance > maximum_error)
914 maximum_error=distance;
917 if ((channel & BlueChannel) != 0)
919 distance=fabs((
double) (Sa*GetPixelBlue(p)-Da*GetPixelBlue(q)));
920 distortion[BlueChannel]+=distance;
921 distortion[CompositeChannels]+=distance;
922 mean_error+=distance*distance;
923 if (distance > maximum_error)
924 maximum_error=distance;
927 if (((channel & OpacityChannel) != 0) &&
928 (image->matte != MagickFalse))
930 distance=fabs((
double) (GetPixelOpacity(p)-
931 (
double) GetPixelOpacity(q)));
932 distortion[OpacityChannel]+=distance;
933 distortion[CompositeChannels]+=distance;
934 mean_error+=distance*distance;
935 if (distance > maximum_error)
936 maximum_error=distance;
939 if (((channel & IndexChannel) != 0) &&
940 (image->colorspace == CMYKColorspace) &&
941 (reconstruct_image->colorspace == CMYKColorspace))
943 distance=fabs((
double) (Sa*GetPixelIndex(indexes+x)-Da*
944 GetPixelIndex(reconstruct_indexes+x)));
945 distortion[BlackChannel]+=distance;
946 distortion[CompositeChannels]+=distance;
947 mean_error+=distance*distance;
948 if (distance > maximum_error)
949 maximum_error=distance;
956 reconstruct_view=DestroyCacheView(reconstruct_view);
957 image_view=DestroyCacheView(image_view);
958 gamma=PerceptibleReciprocal(area);
959 image->error.mean_error_per_pixel=gamma*distortion[CompositeChannels];
960 image->error.normalized_mean_error=gamma*QuantumScale*QuantumScale*mean_error;
961 image->error.normalized_maximum_error=QuantumScale*maximum_error;
965 static MagickBooleanType GetMeanSquaredDistortion(
const Image *image,
966 const Image *reconstruct_image,
const ChannelType channel,
987 rows=MagickMax(image->rows,reconstruct_image->rows);
988 columns=MagickMax(image->columns,reconstruct_image->columns);
989 image_view=AcquireVirtualCacheView(image,exception);
990 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
991 #if defined(MAGICKCORE_OPENMP_SUPPORT)
992 #pragma omp parallel for schedule(static) shared(status) \
993 magick_number_threads(image,image,rows,1)
995 for (y=0; y < (ssize_t) rows; y++)
998 channel_distortion[CompositeChannels+1];
1001 *magick_restrict indexes,
1002 *magick_restrict reconstruct_indexes;
1012 if (status == MagickFalse)
1014 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1015 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1021 indexes=GetCacheViewVirtualIndexQueue(image_view);
1022 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1023 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
1024 for (x=0; x < (ssize_t) columns; x++)
1031 Sa=QuantumScale*(image->matte != MagickFalse ? GetPixelAlpha(p) :
1032 (QuantumRange-OpaqueOpacity));
1033 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1034 GetPixelAlpha(q) : (QuantumRange-OpaqueOpacity));
1035 if ((channel & RedChannel) != 0)
1037 distance=QuantumScale*(Sa*GetPixelRed(p)-Da*GetPixelRed(q));
1038 channel_distortion[RedChannel]+=distance*distance;
1039 channel_distortion[CompositeChannels]+=distance*distance;
1041 if ((channel & GreenChannel) != 0)
1043 distance=QuantumScale*(Sa*GetPixelGreen(p)-Da*GetPixelGreen(q));
1044 channel_distortion[GreenChannel]+=distance*distance;
1045 channel_distortion[CompositeChannels]+=distance*distance;
1047 if ((channel & BlueChannel) != 0)
1049 distance=QuantumScale*(Sa*GetPixelBlue(p)-Da*GetPixelBlue(q));
1050 channel_distortion[BlueChannel]+=distance*distance;
1051 channel_distortion[CompositeChannels]+=distance*distance;
1053 if (((channel & OpacityChannel) != 0) &&
1054 (image->matte != MagickFalse))
1056 distance=QuantumScale*(GetPixelOpacity(p)-(MagickRealType)
1057 GetPixelOpacity(q));
1058 channel_distortion[OpacityChannel]+=distance*distance;
1059 channel_distortion[CompositeChannels]+=distance*distance;
1061 if (((channel & IndexChannel) != 0) &&
1062 (image->colorspace == CMYKColorspace) &&
1063 (reconstruct_image->colorspace == CMYKColorspace))
1065 distance=QuantumScale*(Sa*GetPixelIndex(indexes+x)-Da*
1066 GetPixelIndex(reconstruct_indexes+x));
1067 channel_distortion[BlackChannel]+=distance*distance;
1068 channel_distortion[CompositeChannels]+=distance*distance;
1073 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1074 #pragma omp critical (MagickCore_GetMeanSquaredError)
1076 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1077 distortion[i]+=channel_distortion[i];
1079 reconstruct_view=DestroyCacheView(reconstruct_view);
1080 image_view=DestroyCacheView(image_view);
1081 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1082 distortion[i]/=((
double) columns*rows);
1083 distortion[CompositeChannels]/=(double) GetNumberChannels(image,channel);
1087 static MagickBooleanType GetNormalizedCrossCorrelationDistortion(
1088 const Image *image,
const Image *reconstruct_image,
const ChannelType channel,
1091 #define SimilarityImageTag "Similarity/Image"
1099 *reconstruct_statistics;
1102 alpha_variance[CompositeChannels+1],
1103 beta_variance[CompositeChannels+1];
1124 image_statistics=GetImageChannelStatistics(image,exception);
1125 reconstruct_statistics=GetImageChannelStatistics(reconstruct_image,exception);
1134 reconstruct_statistics);
1135 return(MagickFalse);
1137 (void) memset(distortion,0,(CompositeChannels+1)*
sizeof(*distortion));
1138 (void) memset(alpha_variance,0,(CompositeChannels+1)*
sizeof(*alpha_variance));
1139 (void) memset(beta_variance,0,(CompositeChannels+1)*
sizeof(*beta_variance));
1142 rows=MagickMax(image->rows,reconstruct_image->rows);
1143 columns=MagickMax(image->columns,reconstruct_image->columns);
1144 image_view=AcquireVirtualCacheView(image,exception);
1145 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1146 for (y=0; y < (ssize_t) rows; y++)
1149 *magick_restrict indexes,
1150 *magick_restrict reconstruct_indexes;
1159 if (status == MagickFalse)
1161 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1162 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1168 indexes=GetCacheViewVirtualIndexQueue(image_view);
1169 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1170 for (x=0; x < (ssize_t) columns; x++)
1178 Sa=QuantumScale*(image->matte != MagickFalse ? GetPixelAlpha(p) :
1180 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1181 GetPixelAlpha(q) : QuantumRange);
1182 if ((channel & RedChannel) != 0)
1184 alpha=QuantumScale*(Sa*GetPixelRed(p)-
1185 image_statistics[RedChannel].mean);
1186 beta=QuantumScale*(Da*GetPixelRed(q)-
1187 reconstruct_statistics[RedChannel].mean);
1188 distortion[RedChannel]+=alpha*beta;
1189 alpha_variance[RedChannel]+=alpha*alpha;
1190 beta_variance[RedChannel]+=beta*beta;
1192 if ((channel & GreenChannel) != 0)
1194 alpha=QuantumScale*(Sa*GetPixelGreen(p)-
1195 image_statistics[GreenChannel].mean);
1196 beta=QuantumScale*(Da*GetPixelGreen(q)-
1197 reconstruct_statistics[GreenChannel].mean);
1198 distortion[GreenChannel]+=alpha*beta;
1199 alpha_variance[GreenChannel]+=alpha*alpha;
1200 beta_variance[GreenChannel]+=beta*beta;
1202 if ((channel & BlueChannel) != 0)
1204 alpha=QuantumScale*(Sa*GetPixelBlue(p)-
1205 image_statistics[BlueChannel].mean);
1206 beta=QuantumScale*(Da*GetPixelBlue(q)-
1207 reconstruct_statistics[BlueChannel].mean);
1208 distortion[BlueChannel]+=alpha*beta;
1209 alpha_variance[BlueChannel]+=alpha*alpha;
1210 beta_variance[BlueChannel]+=beta*beta;
1212 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
1214 alpha=QuantumScale*(GetPixelAlpha(p)-
1215 image_statistics[AlphaChannel].mean);
1216 beta=QuantumScale*(GetPixelAlpha(q)-
1217 reconstruct_statistics[AlphaChannel].mean);
1218 distortion[OpacityChannel]+=alpha*beta;
1219 alpha_variance[OpacityChannel]+=alpha*alpha;
1220 beta_variance[OpacityChannel]+=beta*beta;
1222 if (((channel & IndexChannel) != 0) &&
1223 (image->colorspace == CMYKColorspace) &&
1224 (reconstruct_image->colorspace == CMYKColorspace))
1226 alpha=QuantumScale*(Sa*GetPixelIndex(indexes+x)-
1227 image_statistics[BlackChannel].mean);
1228 beta=QuantumScale*(Da*GetPixelIndex(reconstruct_indexes+x)-
1229 reconstruct_statistics[BlackChannel].mean);
1230 distortion[BlackChannel]+=alpha*beta;
1231 alpha_variance[BlackChannel]+=alpha*alpha;
1232 beta_variance[BlackChannel]+=beta*beta;
1237 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1242 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1246 proceed=SetImageProgress(image,SimilarityImageTag,progress,rows);
1247 if (proceed == MagickFalse)
1251 reconstruct_view=DestroyCacheView(reconstruct_view);
1252 image_view=DestroyCacheView(image_view);
1256 for (i=0; i < (ssize_t) CompositeChannels; i++)
1258 distortion[i]/=sqrt(alpha_variance[i]*beta_variance[i]);
1259 if (fabs(distortion[i]) > MagickEpsilon)
1260 distortion[CompositeChannels]+=distortion[i];
1262 distortion[CompositeChannels]=distortion[CompositeChannels]/
1263 GetNumberChannels(image,channel);
1268 reconstruct_statistics);
1274 static MagickBooleanType GetPeakAbsoluteDistortion(
const Image *image,
1275 const Image *reconstruct_image,
const ChannelType channel,
1293 rows=MagickMax(image->rows,reconstruct_image->rows);
1294 columns=MagickMax(image->columns,reconstruct_image->columns);
1295 image_view=AcquireVirtualCacheView(image,exception);
1296 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1297 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1298 #pragma omp parallel for schedule(static) shared(status) \
1299 magick_number_threads(image,image,rows,1)
1301 for (y=0; y < (ssize_t) rows; y++)
1304 channel_distortion[CompositeChannels+1];
1307 *magick_restrict indexes,
1308 *magick_restrict reconstruct_indexes;
1318 if (status == MagickFalse)
1320 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1321 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1327 indexes=GetCacheViewVirtualIndexQueue(image_view);
1328 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1329 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
1330 for (x=0; x < (ssize_t) columns; x++)
1337 Sa=QuantumScale*(image->matte != MagickFalse ? GetPixelAlpha(p) :
1338 (QuantumRange-OpaqueOpacity));
1339 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1340 GetPixelAlpha(q) : (QuantumRange-OpaqueOpacity));
1341 if ((channel & RedChannel) != 0)
1343 distance=QuantumScale*fabs((
double) (Sa*GetPixelRed(p)-Da*
1345 if (distance > channel_distortion[RedChannel])
1346 channel_distortion[RedChannel]=distance;
1347 if (distance > channel_distortion[CompositeChannels])
1348 channel_distortion[CompositeChannels]=distance;
1350 if ((channel & GreenChannel) != 0)
1352 distance=QuantumScale*fabs((
double) (Sa*GetPixelGreen(p)-Da*
1354 if (distance > channel_distortion[GreenChannel])
1355 channel_distortion[GreenChannel]=distance;
1356 if (distance > channel_distortion[CompositeChannels])
1357 channel_distortion[CompositeChannels]=distance;
1359 if ((channel & BlueChannel) != 0)
1361 distance=QuantumScale*fabs((
double) (Sa*GetPixelBlue(p)-Da*
1363 if (distance > channel_distortion[BlueChannel])
1364 channel_distortion[BlueChannel]=distance;
1365 if (distance > channel_distortion[CompositeChannels])
1366 channel_distortion[CompositeChannels]=distance;
1368 if (((channel & OpacityChannel) != 0) &&
1369 (image->matte != MagickFalse))
1371 distance=QuantumScale*fabs((
double) (GetPixelOpacity(p)-(
double)
1372 GetPixelOpacity(q)));
1373 if (distance > channel_distortion[OpacityChannel])
1374 channel_distortion[OpacityChannel]=distance;
1375 if (distance > channel_distortion[CompositeChannels])
1376 channel_distortion[CompositeChannels]=distance;
1378 if (((channel & IndexChannel) != 0) &&
1379 (image->colorspace == CMYKColorspace) &&
1380 (reconstruct_image->colorspace == CMYKColorspace))
1382 distance=QuantumScale*fabs((
double) (Sa*GetPixelIndex(indexes+x)-Da*
1383 GetPixelIndex(reconstruct_indexes+x)));
1384 if (distance > channel_distortion[BlackChannel])
1385 channel_distortion[BlackChannel]=distance;
1386 if (distance > channel_distortion[CompositeChannels])
1387 channel_distortion[CompositeChannels]=distance;
1392 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1393 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
1395 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1396 if (channel_distortion[i] > distortion[i])
1397 distortion[i]=channel_distortion[i];
1399 reconstruct_view=DestroyCacheView(reconstruct_view);
1400 image_view=DestroyCacheView(image_view);
1404 static MagickBooleanType GetPeakSignalToNoiseRatio(
const Image *image,
1405 const Image *reconstruct_image,
const ChannelType channel,
1411 status=GetMeanSquaredDistortion(image,reconstruct_image,channel,distortion,
1413 if ((channel & RedChannel) != 0)
1415 if (fabs(distortion[RedChannel]) >= MagickEpsilon)
1416 distortion[RedChannel]=(-10.0*MagickLog10(distortion[RedChannel]));
1418 if ((channel & GreenChannel) != 0)
1420 if (fabs(distortion[GreenChannel]) >= MagickEpsilon)
1421 distortion[GreenChannel]=(-10.0*MagickLog10(distortion[GreenChannel]));
1423 if ((channel & BlueChannel) != 0)
1425 if (fabs(distortion[BlueChannel]) >= MagickEpsilon)
1426 distortion[BlueChannel]=(-10.0*MagickLog10(distortion[BlueChannel]));
1428 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
1430 if (fabs(distortion[OpacityChannel]) >= MagickEpsilon)
1431 distortion[OpacityChannel]=(-10.0*
1432 MagickLog10(distortion[OpacityChannel]));
1434 if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
1436 if (fabs(distortion[BlackChannel]) >= MagickEpsilon)
1437 distortion[BlackChannel]=(-10.0*MagickLog10(distortion[BlackChannel]));
1439 if (fabs(distortion[CompositeChannels]) >= MagickEpsilon)
1440 distortion[CompositeChannels]=(-10.0*
1441 MagickLog10(distortion[CompositeChannels]));
1445 static MagickBooleanType GetPerceptualHashDistortion(
const Image *image,
1446 const Image *reconstruct_image,
const ChannelType channel,
double *distortion,
1462 image_phash=GetImageChannelPerceptualHash(image,exception);
1464 return(MagickFalse);
1465 reconstruct_phash=GetImageChannelPerceptualHash(reconstruct_image,exception);
1469 return(MagickFalse);
1471 for (i=0; i < MaximumNumberOfImageMoments; i++)
1476 if ((channel & RedChannel) != 0)
1478 difference=reconstruct_phash[RedChannel].P[i]-
1479 image_phash[RedChannel].P[i];
1480 distortion[RedChannel]+=difference*difference;
1481 distortion[CompositeChannels]+=difference*difference;
1483 if ((channel & GreenChannel) != 0)
1485 difference=reconstruct_phash[GreenChannel].P[i]-
1486 image_phash[GreenChannel].P[i];
1487 distortion[GreenChannel]+=difference*difference;
1488 distortion[CompositeChannels]+=difference*difference;
1490 if ((channel & BlueChannel) != 0)
1492 difference=reconstruct_phash[BlueChannel].P[i]-
1493 image_phash[BlueChannel].P[i];
1494 distortion[BlueChannel]+=difference*difference;
1495 distortion[CompositeChannels]+=difference*difference;
1497 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse) &&
1498 (reconstruct_image->matte != MagickFalse))
1500 difference=reconstruct_phash[OpacityChannel].P[i]-
1501 image_phash[OpacityChannel].P[i];
1502 distortion[OpacityChannel]+=difference*difference;
1503 distortion[CompositeChannels]+=difference*difference;
1505 if (((channel & IndexChannel) != 0) &&
1506 (image->colorspace == CMYKColorspace) &&
1507 (reconstruct_image->colorspace == CMYKColorspace))
1509 difference=reconstruct_phash[IndexChannel].P[i]-
1510 image_phash[IndexChannel].P[i];
1511 distortion[IndexChannel]+=difference*difference;
1512 distortion[CompositeChannels]+=difference*difference;
1518 for (i=0; i < MaximumNumberOfImageMoments; i++)
1523 if ((channel & RedChannel) != 0)
1525 difference=reconstruct_phash[RedChannel].Q[i]-
1526 image_phash[RedChannel].Q[i];
1527 distortion[RedChannel]+=difference*difference;
1528 distortion[CompositeChannels]+=difference*difference;
1530 if ((channel & GreenChannel) != 0)
1532 difference=reconstruct_phash[GreenChannel].Q[i]-
1533 image_phash[GreenChannel].Q[i];
1534 distortion[GreenChannel]+=difference*difference;
1535 distortion[CompositeChannels]+=difference*difference;
1537 if ((channel & BlueChannel) != 0)
1539 difference=reconstruct_phash[BlueChannel].Q[i]-
1540 image_phash[BlueChannel].Q[i];
1541 distortion[BlueChannel]+=difference*difference;
1542 distortion[CompositeChannels]+=difference*difference;
1544 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse) &&
1545 (reconstruct_image->matte != MagickFalse))
1547 difference=reconstruct_phash[OpacityChannel].Q[i]-
1548 image_phash[OpacityChannel].Q[i];
1549 distortion[OpacityChannel]+=difference*difference;
1550 distortion[CompositeChannels]+=difference*difference;
1552 if (((channel & IndexChannel) != 0) &&
1553 (image->colorspace == CMYKColorspace) &&
1554 (reconstruct_image->colorspace == CMYKColorspace))
1556 difference=reconstruct_phash[IndexChannel].Q[i]-
1557 image_phash[IndexChannel].Q[i];
1558 distortion[IndexChannel]+=difference*difference;
1559 distortion[CompositeChannels]+=difference*difference;
1571 static MagickBooleanType GetRootMeanSquaredDistortion(
const Image *image,
1572 const Image *reconstruct_image,
const ChannelType channel,
double *distortion,
1578 status=GetMeanSquaredDistortion(image,reconstruct_image,channel,distortion,
1580 if ((channel & RedChannel) != 0)
1581 distortion[RedChannel]=sqrt(distortion[RedChannel]);
1582 if ((channel & GreenChannel) != 0)
1583 distortion[GreenChannel]=sqrt(distortion[GreenChannel]);
1584 if ((channel & BlueChannel) != 0)
1585 distortion[BlueChannel]=sqrt(distortion[BlueChannel]);
1586 if (((channel & OpacityChannel) != 0) &&
1587 (image->matte != MagickFalse))
1588 distortion[OpacityChannel]=sqrt(distortion[OpacityChannel]);
1589 if (((channel & IndexChannel) != 0) &&
1590 (image->colorspace == CMYKColorspace))
1591 distortion[BlackChannel]=sqrt(distortion[BlackChannel]);
1592 distortion[CompositeChannels]=sqrt(distortion[CompositeChannels]);
1596 MagickExport MagickBooleanType GetImageChannelDistortion(
Image *image,
1597 const Image *reconstruct_image,
const ChannelType channel,
1598 const MetricType metric,
double *distortion,
ExceptionInfo *exception)
1601 *channel_distortion;
1609 assert(image != (
Image *) NULL);
1610 assert(image->signature == MagickCoreSignature);
1611 assert(reconstruct_image != (
const Image *) NULL);
1612 assert(reconstruct_image->signature == MagickCoreSignature);
1613 assert(distortion != (
double *) NULL);
1614 if (IsEventLogging() != MagickFalse)
1615 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
1617 if (metric != PerceptualHashErrorMetric)
1618 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1619 ThrowBinaryException(ImageError,
"ImageMorphologyDiffers",image->filename);
1623 length=CompositeChannels+1UL;
1624 channel_distortion=(
double *) AcquireQuantumMemory(length,
1625 sizeof(*channel_distortion));
1626 if (channel_distortion == (
double *) NULL)
1627 ThrowFatalException(ResourceLimitFatalError,
"MemoryAllocationFailed");
1628 (void) memset(channel_distortion,0,length*
sizeof(*channel_distortion));
1631 case AbsoluteErrorMetric:
1633 status=GetAbsoluteDistortion(image,reconstruct_image,channel,
1634 channel_distortion,exception);
1637 case FuzzErrorMetric:
1639 status=GetFuzzDistortion(image,reconstruct_image,channel,
1640 channel_distortion,exception);
1643 case MeanAbsoluteErrorMetric:
1645 status=GetMeanAbsoluteDistortion(image,reconstruct_image,channel,
1646 channel_distortion,exception);
1649 case MeanErrorPerPixelMetric:
1651 status=GetMeanErrorPerPixel(image,reconstruct_image,channel,
1652 channel_distortion,exception);
1655 case MeanSquaredErrorMetric:
1657 status=GetMeanSquaredDistortion(image,reconstruct_image,channel,
1658 channel_distortion,exception);
1661 case NormalizedCrossCorrelationErrorMetric:
1664 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1665 channel,channel_distortion,exception);
1668 case PeakAbsoluteErrorMetric:
1670 status=GetPeakAbsoluteDistortion(image,reconstruct_image,channel,
1671 channel_distortion,exception);
1674 case PeakSignalToNoiseRatioMetric:
1676 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,channel,
1677 channel_distortion,exception);
1680 case PerceptualHashErrorMetric:
1682 status=GetPerceptualHashDistortion(image,reconstruct_image,channel,
1683 channel_distortion,exception);
1686 case RootMeanSquaredErrorMetric:
1688 status=GetRootMeanSquaredDistortion(image,reconstruct_image,channel,
1689 channel_distortion,exception);
1693 *distortion=channel_distortion[CompositeChannels];
1694 channel_distortion=(
double *) RelinquishMagickMemory(channel_distortion);
1695 (void) FormatImageProperty(image,
"distortion",
"%.*g",GetMagickPrecision(),
1732 MagickExport
double *GetImageChannelDistortions(
Image *image,
1733 const Image *reconstruct_image,
const MetricType metric,
1737 *channel_distortion;
1745 assert(image != (
Image *) NULL);
1746 assert(image->signature == MagickCoreSignature);
1747 assert(reconstruct_image != (
const Image *) NULL);
1748 assert(reconstruct_image->signature == MagickCoreSignature);
1749 if (IsEventLogging() != MagickFalse)
1750 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
1751 if (metric != PerceptualHashErrorMetric)
1752 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1754 (void) ThrowMagickException(&image->exception,GetMagickModule(),
1755 ImageError,
"ImageMorphologyDiffers",
"`%s'",image->filename);
1756 return((
double *) NULL);
1761 length=CompositeChannels+1UL;
1762 channel_distortion=(
double *) AcquireQuantumMemory(length,
1763 sizeof(*channel_distortion));
1764 if (channel_distortion == (
double *) NULL)
1765 ThrowFatalException(ResourceLimitFatalError,
"MemoryAllocationFailed");
1766 (void) memset(channel_distortion,0,length*
1767 sizeof(*channel_distortion));
1771 case AbsoluteErrorMetric:
1773 status=GetAbsoluteDistortion(image,reconstruct_image,CompositeChannels,
1774 channel_distortion,exception);
1777 case FuzzErrorMetric:
1779 status=GetFuzzDistortion(image,reconstruct_image,CompositeChannels,
1780 channel_distortion,exception);
1783 case MeanAbsoluteErrorMetric:
1785 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1786 CompositeChannels,channel_distortion,exception);
1789 case MeanErrorPerPixelMetric:
1791 status=GetMeanErrorPerPixel(image,reconstruct_image,CompositeChannels,
1792 channel_distortion,exception);
1795 case MeanSquaredErrorMetric:
1797 status=GetMeanSquaredDistortion(image,reconstruct_image,CompositeChannels,
1798 channel_distortion,exception);
1801 case NormalizedCrossCorrelationErrorMetric:
1804 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1805 CompositeChannels,channel_distortion,exception);
1808 case PeakAbsoluteErrorMetric:
1810 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1811 CompositeChannels,channel_distortion,exception);
1814 case PeakSignalToNoiseRatioMetric:
1816 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1817 CompositeChannels,channel_distortion,exception);
1820 case PerceptualHashErrorMetric:
1822 status=GetPerceptualHashDistortion(image,reconstruct_image,
1823 CompositeChannels,channel_distortion,exception);
1826 case RootMeanSquaredErrorMetric:
1828 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1829 CompositeChannels,channel_distortion,exception);
1833 if (status == MagickFalse)
1835 channel_distortion=(
double *) RelinquishMagickMemory(channel_distortion);
1836 return((
double *) NULL);
1838 return(channel_distortion);
1888 MagickExport MagickBooleanType IsImagesEqual(
Image *image,
1889 const Image *reconstruct_image)
1906 mean_error_per_pixel;
1915 assert(image != (
Image *) NULL);
1916 assert(image->signature == MagickCoreSignature);
1917 assert(reconstruct_image != (
const Image *) NULL);
1918 assert(reconstruct_image->signature == MagickCoreSignature);
1919 exception=(&image->exception);
1920 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1921 ThrowBinaryException(ImageError,
"ImageMorphologyDiffers",image->filename);
1924 mean_error_per_pixel=0.0;
1926 rows=MagickMax(image->rows,reconstruct_image->rows);
1927 columns=MagickMax(image->columns,reconstruct_image->columns);
1928 image_view=AcquireVirtualCacheView(image,exception);
1929 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1930 for (y=0; y < (ssize_t) rows; y++)
1933 *magick_restrict indexes,
1934 *magick_restrict reconstruct_indexes;
1943 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1944 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1947 indexes=GetCacheViewVirtualIndexQueue(image_view);
1948 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1949 for (x=0; x < (ssize_t) columns; x++)
1954 distance=fabs((
double) (GetPixelRed(p)-(
double) GetPixelRed(q)));
1955 mean_error_per_pixel+=distance;
1956 mean_error+=distance*distance;
1957 if (distance > maximum_error)
1958 maximum_error=distance;
1960 distance=fabs((
double) (GetPixelGreen(p)-(
double) GetPixelGreen(q)));
1961 mean_error_per_pixel+=distance;
1962 mean_error+=distance*distance;
1963 if (distance > maximum_error)
1964 maximum_error=distance;
1966 distance=fabs((
double) (GetPixelBlue(p)-(
double) GetPixelBlue(q)));
1967 mean_error_per_pixel+=distance;
1968 mean_error+=distance*distance;
1969 if (distance > maximum_error)
1970 maximum_error=distance;
1972 if (image->matte != MagickFalse)
1974 distance=fabs((
double) (GetPixelOpacity(p)-(
double)
1975 GetPixelOpacity(q)));
1976 mean_error_per_pixel+=distance;
1977 mean_error+=distance*distance;
1978 if (distance > maximum_error)
1979 maximum_error=distance;
1982 if ((image->colorspace == CMYKColorspace) &&
1983 (reconstruct_image->colorspace == CMYKColorspace))
1985 distance=fabs((
double) (GetPixelIndex(indexes+x)-(
double)
1986 GetPixelIndex(reconstruct_indexes+x)));
1987 mean_error_per_pixel+=distance;
1988 mean_error+=distance*distance;
1989 if (distance > maximum_error)
1990 maximum_error=distance;
1997 reconstruct_view=DestroyCacheView(reconstruct_view);
1998 image_view=DestroyCacheView(image_view);
1999 gamma=PerceptibleReciprocal(area);
2000 image->error.mean_error_per_pixel=gamma*mean_error_per_pixel;
2001 image->error.normalized_mean_error=gamma*QuantumScale*QuantumScale*mean_error;
2002 image->error.normalized_maximum_error=QuantumScale*maximum_error;
2003 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
2042 static double GetSimilarityMetric(
const Image *image,
const Image *reference,
2043 const MetricType metric,
const ssize_t x_offset,
const ssize_t y_offset,
2058 SetGeometry(reference,&geometry);
2059 geometry.x=x_offset;
2060 geometry.y=y_offset;
2061 similarity_image=CropImage(image,&geometry,exception);
2062 if (similarity_image == (
Image *) NULL)
2065 status=GetImageDistortion(similarity_image,reference,metric,&distortion,
2068 similarity_image=DestroyImage(similarity_image);
2072 MagickExport
Image *SimilarityImage(
Image *image,
const Image *reference,
2078 similarity_image=SimilarityMetricImage(image,reference,
2079 RootMeanSquaredErrorMetric,offset,similarity_metric,exception);
2080 return(similarity_image);
2083 MagickExport
Image *SimilarityMetricImage(
Image *image,
const Image *reference,
2084 const MetricType metric,
RectangleInfo *offset,
double *similarity_metric,
2087 #define SimilarityImageTag "Similarity/Image"
2096 similarity_threshold;
2110 assert(image != (
const Image *) NULL);
2111 assert(image->signature == MagickCoreSignature);
2113 assert(exception->signature == MagickCoreSignature);
2115 if (IsEventLogging() != MagickFalse)
2116 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
2117 SetGeometry(reference,offset);
2118 *similarity_metric=MagickMaximumValue;
2119 if (ValidateImageMorphology(image,reference) == MagickFalse)
2120 ThrowImageException(ImageError,
"ImageMorphologyDiffers");
2121 similarity_image=CloneImage(image,image->columns-reference->columns+1,
2122 image->rows-reference->rows+1,MagickTrue,exception);
2123 if (similarity_image == (
Image *) NULL)
2124 return((
Image *) NULL);
2125 if (SetImageStorageClass(similarity_image,DirectClass) == MagickFalse)
2127 InheritException(exception,&similarity_image->exception);
2128 similarity_image=DestroyImage(similarity_image);
2129 return((
Image *) NULL);
2131 (void) SetImageAlphaChannel(similarity_image,DeactivateAlphaChannel);
2135 similarity_threshold=(-1.0);
2136 artifact=GetImageArtifact(image,
"compare:similarity-threshold");
2137 if (artifact != (
const char *) NULL)
2138 similarity_threshold=StringToDouble(artifact,(
char **) NULL);
2141 similarity_view=AcquireVirtualCacheView(similarity_image,exception);
2142 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2143 #pragma omp parallel for schedule(static) \
2144 shared(progress,status,similarity_metric) \
2145 magick_number_threads(image,image,image->rows-reference->rows+1,1)
2147 for (y=0; y < (ssize_t) (image->rows-reference->rows+1); y++)
2158 if (status == MagickFalse)
2160 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2161 #pragma omp flush(similarity_metric)
2163 if (*similarity_metric <= similarity_threshold)
2165 q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
2172 for (x=0; x < (ssize_t) (image->columns-reference->columns+1); x++)
2174 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2175 #pragma omp flush(similarity_metric)
2177 if (*similarity_metric <= similarity_threshold)
2179 similarity=GetSimilarityMetric(image,reference,metric,x,y,exception);
2180 if (metric == PeakSignalToNoiseRatioMetric)
2182 if ((metric == NormalizedCrossCorrelationErrorMetric) ||
2183 (metric == UndefinedErrorMetric))
2184 similarity=1.0-similarity;
2185 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2186 #pragma omp critical (MagickCore_SimilarityImage)
2188 if (similarity < *similarity_metric)
2190 *similarity_metric=similarity;
2194 if (metric == PerceptualHashErrorMetric)
2195 similarity=MagickMin(0.01*similarity,1.0);
2196 SetPixelRed(q,ClampToQuantum(QuantumRange-QuantumRange*similarity));
2197 SetPixelGreen(q,GetPixelRed(q));
2198 SetPixelBlue(q,GetPixelRed(q));
2201 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
2203 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2208 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2212 proceed=SetImageProgress(image,SimilarityImageTag,progress,image->rows);
2213 if (proceed == MagickFalse)
2217 similarity_view=DestroyCacheView(similarity_view);
2218 if (status == MagickFalse)
2219 similarity_image=DestroyImage(similarity_image);
2220 return(similarity_image);