43 #include "magick/studio.h" 44 #include "magick/artifact.h" 45 #include "magick/attribute.h" 46 #include "magick/cache-view.h" 47 #include "magick/channel.h" 48 #include "magick/client.h" 49 #include "magick/color.h" 50 #include "magick/color-private.h" 51 #include "magick/colorspace.h" 52 #include "magick/colorspace-private.h" 53 #include "magick/compare.h" 54 #include "magick/composite-private.h" 55 #include "magick/constitute.h" 56 #include "magick/exception-private.h" 57 #include "magick/geometry.h" 58 #include "magick/image-private.h" 59 #include "magick/list.h" 60 #include "magick/log.h" 61 #include "magick/memory_.h" 62 #include "magick/monitor.h" 63 #include "magick/monitor-private.h" 64 #include "magick/option.h" 65 #include "magick/pixel-private.h" 66 #include "magick/property.h" 67 #include "magick/resource_.h" 68 #include "magick/statistic-private.h" 69 #include "magick/string_.h" 70 #include "magick/string-private.h" 71 #include "magick/statistic.h" 72 #include "magick/thread-private.h" 73 #include "magick/transform.h" 74 #include "magick/utility.h" 75 #include "magick/version.h" 113 MagickExport
Image *CompareImages(
Image *image,
const Image *reconstruct_image,
114 const MetricType metric,
double *distortion,
ExceptionInfo *exception)
119 highlight_image=CompareImageChannels(image,reconstruct_image,
120 CompositeChannels,metric,distortion,exception);
121 return(highlight_image);
124 static size_t GetNumberChannels(
const Image *image,
const ChannelType channel)
130 if ((channel & RedChannel) != 0)
132 if ((channel & GreenChannel) != 0)
134 if ((channel & BlueChannel) != 0)
136 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
138 if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
140 return(channels == 0 ? 1UL : channels);
143 static inline MagickBooleanType ValidateImageMorphology(
144 const Image *magick_restrict image,
145 const Image *magick_restrict reconstruct_image)
150 if (GetNumberChannels(image,DefaultChannels) !=
151 GetNumberChannels(reconstruct_image,DefaultChannels))
156 MagickExport
Image *CompareImageChannels(
Image *image,
157 const Image *reconstruct_image,
const ChannelType channel,
158 const MetricType metric,
double *distortion,
ExceptionInfo *exception)
191 assert(image != (
Image *) NULL);
192 assert(image->signature == MagickCoreSignature);
193 assert(reconstruct_image != (
const Image *) NULL);
194 assert(reconstruct_image->signature == MagickCoreSignature);
195 assert(distortion != (
double *) NULL);
196 if (IsEventLogging() != MagickFalse)
197 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
199 if (metric != PerceptualHashErrorMetric)
200 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
201 ThrowImageException(ImageError,
"ImageMorphologyDiffers");
202 status=GetImageChannelDistortion(image,reconstruct_image,channel,metric,
203 distortion,exception);
204 if (status == MagickFalse)
205 return((
Image *) NULL);
206 clone_image=CloneImage(image,0,0,MagickTrue,exception);
207 if (clone_image == (
Image *) NULL)
208 return((
Image *) NULL);
209 (void) SetImageMask(clone_image,(
Image *) NULL);
210 difference_image=CloneImage(clone_image,0,0,MagickTrue,exception);
211 clone_image=DestroyImage(clone_image);
212 if (difference_image == (
Image *) NULL)
213 return((
Image *) NULL);
214 (void) SetImageAlphaChannel(difference_image,OpaqueAlphaChannel);
215 rows=MagickMax(image->rows,reconstruct_image->rows);
216 columns=MagickMax(image->columns,reconstruct_image->columns);
217 highlight_image=CloneImage(image,columns,rows,MagickTrue,exception);
218 if (highlight_image == (
Image *) NULL)
220 difference_image=DestroyImage(difference_image);
221 return((
Image *) NULL);
223 if (SetImageStorageClass(highlight_image,DirectClass) == MagickFalse)
225 InheritException(exception,&highlight_image->exception);
226 difference_image=DestroyImage(difference_image);
227 highlight_image=DestroyImage(highlight_image);
228 return((
Image *) NULL);
230 (void) SetImageMask(highlight_image,(
Image *) NULL);
231 (void) SetImageAlphaChannel(highlight_image,OpaqueAlphaChannel);
232 (void) QueryMagickColor(
"#f1001ecc",&highlight,exception);
233 artifact=GetImageArtifact(image,
"compare:highlight-color");
234 if (artifact != (
const char *) NULL)
235 (void) QueryMagickColor(artifact,&highlight,exception);
236 (void) QueryMagickColor(
"#ffffffcc",&lowlight,exception);
237 artifact=GetImageArtifact(image,
"compare:lowlight-color");
238 if (artifact != (
const char *) NULL)
239 (void) QueryMagickColor(artifact,&lowlight,exception);
240 if (highlight_image->colorspace == CMYKColorspace)
242 ConvertRGBToCMYK(&highlight);
243 ConvertRGBToCMYK(&lowlight);
249 fuzz=GetFuzzyColorDistance(image,reconstruct_image);
250 GetMagickPixelPacket(image,&zero);
251 image_view=AcquireVirtualCacheView(image,exception);
252 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
253 highlight_view=AcquireAuthenticCacheView(highlight_image,exception);
254 #if defined(MAGICKCORE_OPENMP_SUPPORT) 255 #pragma omp parallel for schedule(static) shared(status) \ 256 magick_number_threads(image,highlight_image,rows,1) 258 for (y=0; y < (ssize_t) rows; y++)
268 *magick_restrict indexes,
269 *magick_restrict reconstruct_indexes;
276 *magick_restrict highlight_indexes;
284 if (status == MagickFalse)
286 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
287 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
288 r=QueueCacheViewAuthenticPixels(highlight_view,0,y,columns,1,exception);
295 indexes=GetCacheViewVirtualIndexQueue(image_view);
296 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
297 highlight_indexes=GetCacheViewAuthenticIndexQueue(highlight_view);
299 reconstruct_pixel=zero;
300 for (x=0; x < (ssize_t) columns; x++)
305 SetMagickPixelPacket(image,p,indexes == (IndexPacket *) NULL ? NULL :
307 SetMagickPixelPacket(reconstruct_image,q,reconstruct_indexes ==
308 (IndexPacket *) NULL ? NULL : reconstruct_indexes+x,&reconstruct_pixel);
309 difference=MagickFalse;
310 if (channel == CompositeChannels)
312 if (IsMagickColorSimilar(&pixel,&reconstruct_pixel) == MagickFalse)
313 difference=MagickTrue;
323 Sa=QuantumScale*(image->matte != MagickFalse ? (double)
324 GetPixelAlpha(p) : ((double) QuantumRange-(double) OpaqueOpacity));
325 Da=QuantumScale*(image->matte != MagickFalse ? (double)
326 GetPixelAlpha(q) : ((double) QuantumRange-(double) OpaqueOpacity));
327 if ((channel & RedChannel) != 0)
329 pixel=Sa*(double) GetPixelRed(p)-Da*(double) GetPixelRed(q);
330 distance=pixel*pixel;
331 if (distance >= fuzz)
332 difference=MagickTrue;
334 if ((channel & GreenChannel) != 0)
336 pixel=Sa*(double) GetPixelGreen(p)-Da*(double) GetPixelGreen(q);
337 distance=pixel*pixel;
338 if (distance >= fuzz)
339 difference=MagickTrue;
341 if ((channel & BlueChannel) != 0)
343 pixel=Sa*(double) GetPixelBlue(p)-Da*(double) GetPixelBlue(q);
344 distance=pixel*pixel;
345 if (distance >= fuzz)
346 difference=MagickTrue;
348 if (((channel & OpacityChannel) != 0) &&
349 (image->matte != MagickFalse))
351 pixel=(double) GetPixelOpacity(p)-(double) GetPixelOpacity(q);
352 distance=pixel*pixel;
353 if (distance >= fuzz)
354 difference=MagickTrue;
356 if (((channel & IndexChannel) != 0) &&
357 (image->colorspace == CMYKColorspace))
359 pixel=Sa*(double) indexes[x]-Da*(
double) reconstruct_indexes[x];
360 distance=pixel*pixel;
361 if (distance >= fuzz)
362 difference=MagickTrue;
365 if (difference != MagickFalse)
366 SetPixelPacket(highlight_image,&highlight,r,highlight_indexes ==
367 (IndexPacket *) NULL ? NULL : highlight_indexes+x);
369 SetPixelPacket(highlight_image,&lowlight,r,highlight_indexes ==
370 (IndexPacket *) NULL ? NULL : highlight_indexes+x);
375 sync=SyncCacheViewAuthenticPixels(highlight_view,exception);
376 if (sync == MagickFalse)
379 highlight_view=DestroyCacheView(highlight_view);
380 reconstruct_view=DestroyCacheView(reconstruct_view);
381 image_view=DestroyCacheView(image_view);
382 (void) CompositeImage(difference_image,image->compose,highlight_image,0,0);
383 highlight_image=DestroyImage(highlight_image);
384 if (status == MagickFalse)
385 difference_image=DestroyImage(difference_image);
386 return(difference_image);
425 MagickExport MagickBooleanType GetImageDistortion(
Image *image,
426 const Image *reconstruct_image,
const MetricType metric,
double *distortion,
432 status=GetImageChannelDistortion(image,reconstruct_image,CompositeChannels,
433 metric,distortion,exception);
437 static MagickBooleanType GetAbsoluteDistortion(
const Image *image,
438 const Image *reconstruct_image,
const ChannelType channel,
double *distortion,
462 fuzz=GetFuzzyColorDistance(image,reconstruct_image);
463 rows=MagickMax(image->rows,reconstruct_image->rows);
464 columns=MagickMax(image->columns,reconstruct_image->columns);
465 image_view=AcquireVirtualCacheView(image,exception);
466 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
467 #if defined(MAGICKCORE_OPENMP_SUPPORT) 468 #pragma omp parallel for schedule(static) shared(status) \ 469 magick_number_threads(image,image,rows,1) 471 for (y=0; y < (ssize_t) rows; y++)
474 channel_distortion[CompositeChannels+1];
477 *magick_restrict indexes,
478 *magick_restrict reconstruct_indexes;
488 if (status == MagickFalse)
490 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
491 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
497 indexes=GetCacheViewVirtualIndexQueue(image_view);
498 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
499 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
500 for (x=0; x < (ssize_t) columns; x++)
511 difference=MagickFalse;
512 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
513 ((double) QuantumRange-(double) OpaqueOpacity));
514 Da=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(q) :
515 ((double) QuantumRange-(double) OpaqueOpacity));
516 if ((channel & RedChannel) != 0)
518 pixel=Sa*(double) GetPixelRed(p)-Da*(double) GetPixelRed(q);
519 distance=pixel*pixel;
520 if (distance >= fuzz)
522 channel_distortion[RedChannel]++;
523 difference=MagickTrue;
526 if ((channel & GreenChannel) != 0)
528 pixel=Sa*(double) GetPixelGreen(p)-Da*(double) GetPixelGreen(q);
529 distance=pixel*pixel;
530 if (distance >= fuzz)
532 channel_distortion[GreenChannel]++;
533 difference=MagickTrue;
536 if ((channel & BlueChannel) != 0)
538 pixel=Sa*(double) GetPixelBlue(p)-Da*(double) GetPixelBlue(q);
539 distance=pixel*pixel;
540 if (distance >= fuzz)
542 channel_distortion[BlueChannel]++;
543 difference=MagickTrue;
546 if (((channel & OpacityChannel) != 0) &&
547 (image->matte != MagickFalse))
549 pixel=(double) GetPixelOpacity(p)-(double) GetPixelOpacity(q);
550 distance=pixel*pixel;
551 if (distance >= fuzz)
553 channel_distortion[OpacityChannel]++;
554 difference=MagickTrue;
557 if (((channel & IndexChannel) != 0) &&
558 (image->colorspace == CMYKColorspace))
560 pixel=Sa*(double) indexes[x]-Da*(
double) reconstruct_indexes[x];
561 distance=pixel*pixel;
562 if (distance >= fuzz)
564 channel_distortion[BlackChannel]++;
565 difference=MagickTrue;
568 if (difference != MagickFalse)
569 channel_distortion[CompositeChannels]++;
573 #if defined(MAGICKCORE_OPENMP_SUPPORT) 574 #pragma omp critical (MagickCore_GetAbsoluteDistortion) 576 for (i=0; i <= (ssize_t) CompositeChannels; i++)
577 distortion[i]+=channel_distortion[i];
579 reconstruct_view=DestroyCacheView(reconstruct_view);
580 image_view=DestroyCacheView(image_view);
584 static MagickBooleanType GetFuzzDistortion(
const Image *image,
585 const Image *reconstruct_image,
const ChannelType channel,
606 rows=MagickMax(image->rows,reconstruct_image->rows);
607 columns=MagickMax(image->columns,reconstruct_image->columns);
608 image_view=AcquireVirtualCacheView(image,exception);
609 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
610 #if defined(MAGICKCORE_OPENMP_SUPPORT) 611 #pragma omp parallel for schedule(static) shared(status) \ 612 magick_number_threads(image,image,rows,1) 614 for (y=0; y < (ssize_t) rows; y++)
617 channel_distortion[CompositeChannels+1];
620 *magick_restrict indexes,
621 *magick_restrict reconstruct_indexes;
631 if (status == MagickFalse)
633 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
634 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
640 indexes=GetCacheViewVirtualIndexQueue(image_view);
641 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
642 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
643 for (x=0; x < (ssize_t) columns; x++)
650 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
651 ((double) QuantumRange-(double) OpaqueOpacity));
652 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
653 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
655 if ((channel & RedChannel) != 0)
657 distance=QuantumScale*(Sa*(double) GetPixelRed(p)-Da*(double)
659 channel_distortion[RedChannel]+=distance*distance;
660 channel_distortion[CompositeChannels]+=distance*distance;
662 if ((channel & GreenChannel) != 0)
664 distance=QuantumScale*(Sa*(double) GetPixelGreen(p)-Da*(double)
666 channel_distortion[GreenChannel]+=distance*distance;
667 channel_distortion[CompositeChannels]+=distance*distance;
669 if ((channel & BlueChannel) != 0)
671 distance=QuantumScale*(Sa*(double) GetPixelBlue(p)-Da*(double)
673 channel_distortion[BlueChannel]+=distance*distance;
674 channel_distortion[CompositeChannels]+=distance*distance;
676 if (((channel & OpacityChannel) != 0) && ((image->matte != MagickFalse) ||
677 (reconstruct_image->matte != MagickFalse)))
679 distance=QuantumScale*((image->matte != MagickFalse ? (double)
680 GetPixelOpacity(p) : (double) OpaqueOpacity)-
681 (reconstruct_image->matte != MagickFalse ?
682 (double) GetPixelOpacity(q): (double) OpaqueOpacity));
683 channel_distortion[OpacityChannel]+=distance*distance;
684 channel_distortion[CompositeChannels]+=distance*distance;
686 if (((channel & IndexChannel) != 0) &&
687 (image->colorspace == CMYKColorspace) &&
688 (reconstruct_image->colorspace == CMYKColorspace))
690 distance=QuantumScale*(Sa*(double) GetPixelIndex(indexes+x)-
691 Da*(double) GetPixelIndex(reconstruct_indexes+x));
692 channel_distortion[BlackChannel]+=distance*distance;
693 channel_distortion[CompositeChannels]+=distance*distance;
698 #if defined(MAGICKCORE_OPENMP_SUPPORT) 699 #pragma omp critical (MagickCore_GetFuzzDistortion) 701 for (i=0; i <= (ssize_t) CompositeChannels; i++)
702 distortion[i]+=channel_distortion[i];
704 reconstruct_view=DestroyCacheView(reconstruct_view);
705 image_view=DestroyCacheView(image_view);
706 for (i=0; i <= (ssize_t) CompositeChannels; i++)
707 distortion[i]/=((
double) columns*rows);
708 distortion[CompositeChannels]/=(double) GetNumberChannels(image,channel);
709 distortion[CompositeChannels]=sqrt(distortion[CompositeChannels]);
713 static MagickBooleanType GetMeanAbsoluteDistortion(
const Image *image,
714 const Image *reconstruct_image,
const ChannelType channel,
735 rows=MagickMax(image->rows,reconstruct_image->rows);
736 columns=MagickMax(image->columns,reconstruct_image->columns);
737 image_view=AcquireVirtualCacheView(image,exception);
738 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
739 #if defined(MAGICKCORE_OPENMP_SUPPORT) 740 #pragma omp parallel for schedule(static) shared(status) \ 741 magick_number_threads(image,image,rows,1) 743 for (y=0; y < (ssize_t) rows; y++)
746 channel_distortion[CompositeChannels+1];
749 *magick_restrict indexes,
750 *magick_restrict reconstruct_indexes;
760 if (status == MagickFalse)
762 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
763 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
769 indexes=GetCacheViewVirtualIndexQueue(image_view);
770 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
771 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
772 for (x=0; x < (ssize_t) columns; x++)
779 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
780 ((double) QuantumRange-(double) OpaqueOpacity));
781 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
782 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
784 if ((channel & RedChannel) != 0)
786 distance=QuantumScale*fabs(Sa*(
double) GetPixelRed(p)-Da*
787 (
double) GetPixelRed(q));
788 channel_distortion[RedChannel]+=distance;
789 channel_distortion[CompositeChannels]+=distance;
791 if ((channel & GreenChannel) != 0)
793 distance=QuantumScale*fabs(Sa*(
double) GetPixelGreen(p)-Da*
794 (
double) GetPixelGreen(q));
795 channel_distortion[GreenChannel]+=distance;
796 channel_distortion[CompositeChannels]+=distance;
798 if ((channel & BlueChannel) != 0)
800 distance=QuantumScale*fabs(Sa*(
double) GetPixelBlue(p)-Da*
801 (
double) GetPixelBlue(q));
802 channel_distortion[BlueChannel]+=distance;
803 channel_distortion[CompositeChannels]+=distance;
805 if (((channel & OpacityChannel) != 0) &&
806 (image->matte != MagickFalse))
808 distance=QuantumScale*fabs((
double) GetPixelOpacity(p)-(
double)
810 channel_distortion[OpacityChannel]+=distance;
811 channel_distortion[CompositeChannels]+=distance;
813 if (((channel & IndexChannel) != 0) &&
814 (image->colorspace == CMYKColorspace))
816 distance=QuantumScale*fabs(Sa*(
double) GetPixelIndex(indexes+x)-Da*
817 (
double) GetPixelIndex(reconstruct_indexes+x));
818 channel_distortion[BlackChannel]+=distance;
819 channel_distortion[CompositeChannels]+=distance;
824 #if defined(MAGICKCORE_OPENMP_SUPPORT) 825 #pragma omp critical (MagickCore_GetMeanAbsoluteError) 827 for (i=0; i <= (ssize_t) CompositeChannels; i++)
828 distortion[i]+=channel_distortion[i];
830 reconstruct_view=DestroyCacheView(reconstruct_view);
831 image_view=DestroyCacheView(image_view);
832 for (i=0; i <= (ssize_t) CompositeChannels; i++)
833 distortion[i]/=((
double) columns*rows);
834 distortion[CompositeChannels]/=(double) GetNumberChannels(image,channel);
838 static MagickBooleanType GetMeanErrorPerPixel(
Image *image,
839 const Image *reconstruct_image,
const ChannelType channel,
double *distortion,
866 rows=MagickMax(image->rows,reconstruct_image->rows);
867 columns=MagickMax(image->columns,reconstruct_image->columns);
868 image_view=AcquireVirtualCacheView(image,exception);
869 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
870 for (y=0; y < (ssize_t) rows; y++)
873 *magick_restrict indexes,
874 *magick_restrict reconstruct_indexes;
883 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
884 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
890 indexes=GetCacheViewVirtualIndexQueue(image_view);
891 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
892 for (x=0; x < (ssize_t) columns; x++)
899 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
900 ((double) QuantumRange-(double) OpaqueOpacity));
901 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
902 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
904 if ((channel & RedChannel) != 0)
906 distance=fabs(Sa*(
double) GetPixelRed(p)-Da*(
double) GetPixelRed(q));
907 distortion[RedChannel]+=distance;
908 distortion[CompositeChannels]+=distance;
909 mean_error+=distance*distance;
910 if (distance > maximum_error)
911 maximum_error=distance;
914 if ((channel & GreenChannel) != 0)
916 distance=fabs(Sa*(
double) GetPixelGreen(p)-Da*(
double)
918 distortion[GreenChannel]+=distance;
919 distortion[CompositeChannels]+=distance;
920 mean_error+=distance*distance;
921 if (distance > maximum_error)
922 maximum_error=distance;
925 if ((channel & BlueChannel) != 0)
927 distance=fabs(Sa*(
double) GetPixelBlue(p)-Da*(
double)
929 distortion[BlueChannel]+=distance;
930 distortion[CompositeChannels]+=distance;
931 mean_error+=distance*distance;
932 if (distance > maximum_error)
933 maximum_error=distance;
936 if (((channel & OpacityChannel) != 0) &&
937 (image->matte != MagickFalse))
939 distance=fabs((
double) GetPixelOpacity(p)-
940 (
double) GetPixelOpacity(q));
941 distortion[OpacityChannel]+=distance;
942 distortion[CompositeChannels]+=distance;
943 mean_error+=distance*distance;
944 if (distance > maximum_error)
945 maximum_error=distance;
948 if (((channel & IndexChannel) != 0) &&
949 (image->colorspace == CMYKColorspace) &&
950 (reconstruct_image->colorspace == CMYKColorspace))
952 distance=fabs(Sa*(
double) GetPixelIndex(indexes+x)-Da*
953 (
double) GetPixelIndex(reconstruct_indexes+x));
954 distortion[BlackChannel]+=distance;
955 distortion[CompositeChannels]+=distance;
956 mean_error+=distance*distance;
957 if (distance > maximum_error)
958 maximum_error=distance;
965 reconstruct_view=DestroyCacheView(reconstruct_view);
966 image_view=DestroyCacheView(image_view);
967 gamma=PerceptibleReciprocal(area);
968 image->error.mean_error_per_pixel=gamma*distortion[CompositeChannels];
969 image->error.normalized_mean_error=gamma*QuantumScale*QuantumScale*mean_error;
970 image->error.normalized_maximum_error=QuantumScale*maximum_error;
974 static MagickBooleanType GetMeanSquaredDistortion(
const Image *image,
975 const Image *reconstruct_image,
const ChannelType channel,
996 rows=MagickMax(image->rows,reconstruct_image->rows);
997 columns=MagickMax(image->columns,reconstruct_image->columns);
998 image_view=AcquireVirtualCacheView(image,exception);
999 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1000 #if defined(MAGICKCORE_OPENMP_SUPPORT) 1001 #pragma omp parallel for schedule(static) shared(status) \ 1002 magick_number_threads(image,image,rows,1) 1004 for (y=0; y < (ssize_t) rows; y++)
1007 channel_distortion[CompositeChannels+1];
1010 *magick_restrict indexes,
1011 *magick_restrict reconstruct_indexes;
1021 if (status == MagickFalse)
1023 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1024 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1030 indexes=GetCacheViewVirtualIndexQueue(image_view);
1031 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1032 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
1033 for (x=0; x < (ssize_t) columns; x++)
1040 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
1041 ((double) QuantumRange-(double) OpaqueOpacity));
1042 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1043 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
1045 if ((channel & RedChannel) != 0)
1047 distance=QuantumScale*(Sa*(double) GetPixelRed(p)-Da*(double)
1049 channel_distortion[RedChannel]+=distance*distance;
1050 channel_distortion[CompositeChannels]+=distance*distance;
1052 if ((channel & GreenChannel) != 0)
1054 distance=QuantumScale*(Sa*(double) GetPixelGreen(p)-Da*(double)
1056 channel_distortion[GreenChannel]+=distance*distance;
1057 channel_distortion[CompositeChannels]+=distance*distance;
1059 if ((channel & BlueChannel) != 0)
1061 distance=QuantumScale*(Sa*(double) GetPixelBlue(p)-Da*(double)
1063 channel_distortion[BlueChannel]+=distance*distance;
1064 channel_distortion[CompositeChannels]+=distance*distance;
1066 if (((channel & OpacityChannel) != 0) &&
1067 (image->matte != MagickFalse))
1069 distance=QuantumScale*((double) GetPixelOpacity(p)-(double)
1070 GetPixelOpacity(q));
1071 channel_distortion[OpacityChannel]+=distance*distance;
1072 channel_distortion[CompositeChannels]+=distance*distance;
1074 if (((channel & IndexChannel) != 0) &&
1075 (image->colorspace == CMYKColorspace) &&
1076 (reconstruct_image->colorspace == CMYKColorspace))
1078 distance=QuantumScale*(Sa*(double) GetPixelIndex(indexes+x)-Da*
1079 (double) GetPixelIndex(reconstruct_indexes+x));
1080 channel_distortion[BlackChannel]+=distance*distance;
1081 channel_distortion[CompositeChannels]+=distance*distance;
1086 #if defined(MAGICKCORE_OPENMP_SUPPORT) 1087 #pragma omp critical (MagickCore_GetMeanSquaredError) 1089 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1090 distortion[i]+=channel_distortion[i];
1092 reconstruct_view=DestroyCacheView(reconstruct_view);
1093 image_view=DestroyCacheView(image_view);
1094 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1095 distortion[i]/=((
double) columns*rows);
1096 distortion[CompositeChannels]/=(double) GetNumberChannels(image,channel);
1100 static MagickBooleanType GetNormalizedCrossCorrelationDistortion(
1101 const Image *image,
const Image *reconstruct_image,
const ChannelType channel,
1104 #define SimilarityImageTag "Similarity/Image" 1112 *reconstruct_statistics;
1115 alpha_variance[CompositeChannels+1],
1116 beta_variance[CompositeChannels+1];
1137 image_statistics=GetImageChannelStatistics(image,exception);
1138 reconstruct_statistics=GetImageChannelStatistics(reconstruct_image,exception);
1147 reconstruct_statistics);
1148 return(MagickFalse);
1150 (void) memset(distortion,0,(CompositeChannels+1)*
sizeof(*distortion));
1151 (void) memset(alpha_variance,0,(CompositeChannels+1)*
sizeof(*alpha_variance));
1152 (void) memset(beta_variance,0,(CompositeChannels+1)*
sizeof(*beta_variance));
1155 rows=MagickMax(image->rows,reconstruct_image->rows);
1156 columns=MagickMax(image->columns,reconstruct_image->columns);
1157 image_view=AcquireVirtualCacheView(image,exception);
1158 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1159 for (y=0; y < (ssize_t) rows; y++)
1162 *magick_restrict indexes,
1163 *magick_restrict reconstruct_indexes;
1172 if (status == MagickFalse)
1174 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1175 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1181 indexes=GetCacheViewVirtualIndexQueue(image_view);
1182 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1183 for (x=0; x < (ssize_t) columns; x++)
1191 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
1192 (double) QuantumRange);
1193 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1194 (double) GetPixelAlpha(q) : (double) QuantumRange);
1195 if ((channel & RedChannel) != 0)
1197 alpha=QuantumScale*(Sa*(double) GetPixelRed(p)-
1198 image_statistics[RedChannel].mean);
1199 beta=QuantumScale*(Da*(double) GetPixelRed(q)-
1200 reconstruct_statistics[RedChannel].mean);
1201 distortion[RedChannel]+=alpha*beta;
1202 alpha_variance[RedChannel]+=alpha*alpha;
1203 beta_variance[RedChannel]+=beta*beta;
1205 if ((channel & GreenChannel) != 0)
1207 alpha=QuantumScale*(Sa*(double) GetPixelGreen(p)-
1208 image_statistics[GreenChannel].mean);
1209 beta=QuantumScale*(Da*(double) GetPixelGreen(q)-
1210 reconstruct_statistics[GreenChannel].mean);
1211 distortion[GreenChannel]+=alpha*beta;
1212 alpha_variance[GreenChannel]+=alpha*alpha;
1213 beta_variance[GreenChannel]+=beta*beta;
1215 if ((channel & BlueChannel) != 0)
1217 alpha=QuantumScale*(Sa*(double) GetPixelBlue(p)-
1218 image_statistics[BlueChannel].mean);
1219 beta=QuantumScale*(Da*(double) GetPixelBlue(q)-
1220 reconstruct_statistics[BlueChannel].mean);
1221 distortion[BlueChannel]+=alpha*beta;
1222 alpha_variance[BlueChannel]+=alpha*alpha;
1223 beta_variance[BlueChannel]+=beta*beta;
1225 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
1227 alpha=QuantumScale*((double) GetPixelAlpha(p)-
1228 image_statistics[AlphaChannel].mean);
1229 beta=QuantumScale*((double) GetPixelAlpha(q)-
1230 reconstruct_statistics[AlphaChannel].mean);
1231 distortion[OpacityChannel]+=alpha*beta;
1232 alpha_variance[OpacityChannel]+=alpha*alpha;
1233 beta_variance[OpacityChannel]+=beta*beta;
1235 if (((channel & IndexChannel) != 0) &&
1236 (image->colorspace == CMYKColorspace) &&
1237 (reconstruct_image->colorspace == CMYKColorspace))
1239 alpha=QuantumScale*(Sa*(double) GetPixelIndex(indexes+x)-
1240 image_statistics[BlackChannel].mean);
1241 beta=QuantumScale*(Da*(double) GetPixelIndex(reconstruct_indexes+x)-
1242 reconstruct_statistics[BlackChannel].mean);
1243 distortion[BlackChannel]+=alpha*beta;
1244 alpha_variance[BlackChannel]+=alpha*alpha;
1245 beta_variance[BlackChannel]+=beta*beta;
1250 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1255 #if defined(MAGICKCORE_OPENMP_SUPPORT) 1259 proceed=SetImageProgress(image,SimilarityImageTag,progress,rows);
1260 if (proceed == MagickFalse)
1264 reconstruct_view=DestroyCacheView(reconstruct_view);
1265 image_view=DestroyCacheView(image_view);
1269 for (i=0; i < (ssize_t) CompositeChannels; i++)
1271 distortion[i]/=sqrt(alpha_variance[i]*beta_variance[i]);
1272 if (fabs(distortion[i]) > MagickEpsilon)
1273 distortion[CompositeChannels]+=distortion[i];
1275 distortion[CompositeChannels]=distortion[CompositeChannels]/
1276 GetNumberChannels(image,channel);
1281 reconstruct_statistics);
1287 static MagickBooleanType GetPeakAbsoluteDistortion(
const Image *image,
1288 const Image *reconstruct_image,
const ChannelType channel,
1306 rows=MagickMax(image->rows,reconstruct_image->rows);
1307 columns=MagickMax(image->columns,reconstruct_image->columns);
1308 image_view=AcquireVirtualCacheView(image,exception);
1309 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1310 #if defined(MAGICKCORE_OPENMP_SUPPORT) 1311 #pragma omp parallel for schedule(static) shared(status) \ 1312 magick_number_threads(image,image,rows,1) 1314 for (y=0; y < (ssize_t) rows; y++)
1317 channel_distortion[CompositeChannels+1];
1320 *magick_restrict indexes,
1321 *magick_restrict reconstruct_indexes;
1331 if (status == MagickFalse)
1333 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1334 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1340 indexes=GetCacheViewVirtualIndexQueue(image_view);
1341 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1342 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
1343 for (x=0; x < (ssize_t) columns; x++)
1350 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
1351 ((double) QuantumRange-(double) OpaqueOpacity));
1352 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1353 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
1355 if ((channel & RedChannel) != 0)
1357 distance=QuantumScale*fabs(Sa*(
double) GetPixelRed(p)-Da*
1358 (
double) GetPixelRed(q));
1359 if (distance > channel_distortion[RedChannel])
1360 channel_distortion[RedChannel]=distance;
1361 if (distance > channel_distortion[CompositeChannels])
1362 channel_distortion[CompositeChannels]=distance;
1364 if ((channel & GreenChannel) != 0)
1366 distance=QuantumScale*fabs(Sa*(
double) GetPixelGreen(p)-Da*
1367 (
double) GetPixelGreen(q));
1368 if (distance > channel_distortion[GreenChannel])
1369 channel_distortion[GreenChannel]=distance;
1370 if (distance > channel_distortion[CompositeChannels])
1371 channel_distortion[CompositeChannels]=distance;
1373 if ((channel & BlueChannel) != 0)
1375 distance=QuantumScale*fabs(Sa*(
double) GetPixelBlue(p)-Da*
1376 (
double) GetPixelBlue(q));
1377 if (distance > channel_distortion[BlueChannel])
1378 channel_distortion[BlueChannel]=distance;
1379 if (distance > channel_distortion[CompositeChannels])
1380 channel_distortion[CompositeChannels]=distance;
1382 if (((channel & OpacityChannel) != 0) &&
1383 (image->matte != MagickFalse))
1385 distance=QuantumScale*fabs((
double) GetPixelOpacity(p)-(
double)
1386 GetPixelOpacity(q));
1387 if (distance > channel_distortion[OpacityChannel])
1388 channel_distortion[OpacityChannel]=distance;
1389 if (distance > channel_distortion[CompositeChannels])
1390 channel_distortion[CompositeChannels]=distance;
1392 if (((channel & IndexChannel) != 0) &&
1393 (image->colorspace == CMYKColorspace) &&
1394 (reconstruct_image->colorspace == CMYKColorspace))
1396 distance=QuantumScale*fabs(Sa*(
double) GetPixelIndex(indexes+x)-Da*
1397 (
double) GetPixelIndex(reconstruct_indexes+x));
1398 if (distance > channel_distortion[BlackChannel])
1399 channel_distortion[BlackChannel]=distance;
1400 if (distance > channel_distortion[CompositeChannels])
1401 channel_distortion[CompositeChannels]=distance;
1406 #if defined(MAGICKCORE_OPENMP_SUPPORT) 1407 #pragma omp critical (MagickCore_GetPeakAbsoluteError) 1409 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1410 if (channel_distortion[i] > distortion[i])
1411 distortion[i]=channel_distortion[i];
1413 reconstruct_view=DestroyCacheView(reconstruct_view);
1414 image_view=DestroyCacheView(image_view);
1418 static MagickBooleanType GetPeakSignalToNoiseRatio(
const Image *image,
1419 const Image *reconstruct_image,
const ChannelType channel,
1428 status=GetMeanSquaredDistortion(image,reconstruct_image,channel,distortion,
1430 if ((channel & RedChannel) != 0)
1432 if (fabs(distortion[RedChannel]) >= MagickEpsilon)
1433 distortion[RedChannel]=(-10.0*MagickLog10(distortion[RedChannel]))/
1436 if ((channel & GreenChannel) != 0)
1438 if (fabs(distortion[GreenChannel]) >= MagickEpsilon)
1439 distortion[GreenChannel]=(-10.0*MagickLog10(distortion[GreenChannel]))/
1442 if ((channel & BlueChannel) != 0)
1444 if (fabs(distortion[BlueChannel]) >= MagickEpsilon)
1445 distortion[BlueChannel]=(-10.0*MagickLog10(distortion[BlueChannel]))/
1448 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
1450 if (fabs(distortion[OpacityChannel]) >= MagickEpsilon)
1451 distortion[OpacityChannel]=(-10.0*
1452 MagickLog10(distortion[OpacityChannel]))/48.1647;
1454 if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
1456 if (fabs(distortion[BlackChannel]) >= MagickEpsilon)
1457 distortion[BlackChannel]=(-10.0*MagickLog10(distortion[BlackChannel]))/
1460 distortion[CompositeChannels]=0.0;
1461 for (i=0; i < (ssize_t) CompositeChannels; i++)
1462 if (fabs(distortion[i]) >= MagickEpsilon)
1463 distortion[CompositeChannels]+=distortion[i];
1464 distortion[CompositeChannels]/=(double) GetNumberChannels(image,channel);
1468 static MagickBooleanType GetPerceptualHashDistortion(
const Image *image,
1469 const Image *reconstruct_image,
const ChannelType channel,
double *distortion,
1485 image_phash=GetImageChannelPerceptualHash(image,exception);
1487 return(MagickFalse);
1488 reconstruct_phash=GetImageChannelPerceptualHash(reconstruct_image,exception);
1492 return(MagickFalse);
1494 for (i=0; i < MaximumNumberOfImageMoments; i++)
1499 if ((channel & RedChannel) != 0)
1501 difference=reconstruct_phash[RedChannel].P[i]-
1502 image_phash[RedChannel].P[i];
1503 distortion[RedChannel]+=difference*difference;
1504 distortion[CompositeChannels]+=difference*difference;
1506 if ((channel & GreenChannel) != 0)
1508 difference=reconstruct_phash[GreenChannel].P[i]-
1509 image_phash[GreenChannel].P[i];
1510 distortion[GreenChannel]+=difference*difference;
1511 distortion[CompositeChannels]+=difference*difference;
1513 if ((channel & BlueChannel) != 0)
1515 difference=reconstruct_phash[BlueChannel].P[i]-
1516 image_phash[BlueChannel].P[i];
1517 distortion[BlueChannel]+=difference*difference;
1518 distortion[CompositeChannels]+=difference*difference;
1520 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse) &&
1521 (reconstruct_image->matte != MagickFalse))
1523 difference=reconstruct_phash[OpacityChannel].P[i]-
1524 image_phash[OpacityChannel].P[i];
1525 distortion[OpacityChannel]+=difference*difference;
1526 distortion[CompositeChannels]+=difference*difference;
1528 if (((channel & IndexChannel) != 0) &&
1529 (image->colorspace == CMYKColorspace) &&
1530 (reconstruct_image->colorspace == CMYKColorspace))
1532 difference=reconstruct_phash[IndexChannel].P[i]-
1533 image_phash[IndexChannel].P[i];
1534 distortion[IndexChannel]+=difference*difference;
1535 distortion[CompositeChannels]+=difference*difference;
1541 for (i=0; i < MaximumNumberOfImageMoments; i++)
1546 if ((channel & RedChannel) != 0)
1548 difference=reconstruct_phash[RedChannel].Q[i]-
1549 image_phash[RedChannel].Q[i];
1550 distortion[RedChannel]+=difference*difference;
1551 distortion[CompositeChannels]+=difference*difference;
1553 if ((channel & GreenChannel) != 0)
1555 difference=reconstruct_phash[GreenChannel].Q[i]-
1556 image_phash[GreenChannel].Q[i];
1557 distortion[GreenChannel]+=difference*difference;
1558 distortion[CompositeChannels]+=difference*difference;
1560 if ((channel & BlueChannel) != 0)
1562 difference=reconstruct_phash[BlueChannel].Q[i]-
1563 image_phash[BlueChannel].Q[i];
1564 distortion[BlueChannel]+=difference*difference;
1565 distortion[CompositeChannels]+=difference*difference;
1567 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse) &&
1568 (reconstruct_image->matte != MagickFalse))
1570 difference=reconstruct_phash[OpacityChannel].Q[i]-
1571 image_phash[OpacityChannel].Q[i];
1572 distortion[OpacityChannel]+=difference*difference;
1573 distortion[CompositeChannels]+=difference*difference;
1575 if (((channel & IndexChannel) != 0) &&
1576 (image->colorspace == CMYKColorspace) &&
1577 (reconstruct_image->colorspace == CMYKColorspace))
1579 difference=reconstruct_phash[IndexChannel].Q[i]-
1580 image_phash[IndexChannel].Q[i];
1581 distortion[IndexChannel]+=difference*difference;
1582 distortion[CompositeChannels]+=difference*difference;
1594 static MagickBooleanType GetRootMeanSquaredDistortion(
const Image *image,
1595 const Image *reconstruct_image,
const ChannelType channel,
double *distortion,
1601 status=GetMeanSquaredDistortion(image,reconstruct_image,channel,distortion,
1603 if ((channel & RedChannel) != 0)
1604 distortion[RedChannel]=sqrt(distortion[RedChannel]);
1605 if ((channel & GreenChannel) != 0)
1606 distortion[GreenChannel]=sqrt(distortion[GreenChannel]);
1607 if ((channel & BlueChannel) != 0)
1608 distortion[BlueChannel]=sqrt(distortion[BlueChannel]);
1609 if (((channel & OpacityChannel) != 0) &&
1610 (image->matte != MagickFalse))
1611 distortion[OpacityChannel]=sqrt(distortion[OpacityChannel]);
1612 if (((channel & IndexChannel) != 0) &&
1613 (image->colorspace == CMYKColorspace))
1614 distortion[BlackChannel]=sqrt(distortion[BlackChannel]);
1615 distortion[CompositeChannels]=sqrt(distortion[CompositeChannels]);
1619 MagickExport MagickBooleanType GetImageChannelDistortion(
Image *image,
1620 const Image *reconstruct_image,
const ChannelType channel,
1621 const MetricType metric,
double *distortion,
ExceptionInfo *exception)
1624 *channel_distortion;
1632 assert(image != (
Image *) NULL);
1633 assert(image->signature == MagickCoreSignature);
1634 assert(reconstruct_image != (
const Image *) NULL);
1635 assert(reconstruct_image->signature == MagickCoreSignature);
1636 assert(distortion != (
double *) NULL);
1637 if (IsEventLogging() != MagickFalse)
1638 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
1640 if (metric != PerceptualHashErrorMetric)
1641 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1642 ThrowBinaryException(ImageError,
"ImageMorphologyDiffers",image->filename);
1646 length=CompositeChannels+1UL;
1647 channel_distortion=(
double *) AcquireQuantumMemory(length,
1648 sizeof(*channel_distortion));
1649 if (channel_distortion == (
double *) NULL)
1650 ThrowFatalException(ResourceLimitFatalError,
"MemoryAllocationFailed");
1651 (void) memset(channel_distortion,0,length*
sizeof(*channel_distortion));
1654 case AbsoluteErrorMetric:
1656 status=GetAbsoluteDistortion(image,reconstruct_image,channel,
1657 channel_distortion,exception);
1660 case FuzzErrorMetric:
1662 status=GetFuzzDistortion(image,reconstruct_image,channel,
1663 channel_distortion,exception);
1666 case MeanAbsoluteErrorMetric:
1668 status=GetMeanAbsoluteDistortion(image,reconstruct_image,channel,
1669 channel_distortion,exception);
1672 case MeanErrorPerPixelMetric:
1674 status=GetMeanErrorPerPixel(image,reconstruct_image,channel,
1675 channel_distortion,exception);
1678 case MeanSquaredErrorMetric:
1680 status=GetMeanSquaredDistortion(image,reconstruct_image,channel,
1681 channel_distortion,exception);
1684 case NormalizedCrossCorrelationErrorMetric:
1687 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1688 channel,channel_distortion,exception);
1691 case PeakAbsoluteErrorMetric:
1693 status=GetPeakAbsoluteDistortion(image,reconstruct_image,channel,
1694 channel_distortion,exception);
1697 case PeakSignalToNoiseRatioMetric:
1699 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,channel,
1700 channel_distortion,exception);
1703 case PerceptualHashErrorMetric:
1705 status=GetPerceptualHashDistortion(image,reconstruct_image,channel,
1706 channel_distortion,exception);
1709 case RootMeanSquaredErrorMetric:
1711 status=GetRootMeanSquaredDistortion(image,reconstruct_image,channel,
1712 channel_distortion,exception);
1716 *distortion=channel_distortion[CompositeChannels];
1717 channel_distortion=(
double *) RelinquishMagickMemory(channel_distortion);
1718 (void) FormatImageProperty(image,
"distortion",
"%.*g",GetMagickPrecision(),
1755 MagickExport
double *GetImageChannelDistortions(
Image *image,
1756 const Image *reconstruct_image,
const MetricType metric,
1760 *channel_distortion;
1768 assert(image != (
Image *) NULL);
1769 assert(image->signature == MagickCoreSignature);
1770 assert(reconstruct_image != (
const Image *) NULL);
1771 assert(reconstruct_image->signature == MagickCoreSignature);
1772 if (IsEventLogging() != MagickFalse)
1773 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
1774 if (metric != PerceptualHashErrorMetric)
1775 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1777 (void) ThrowMagickException(&image->exception,GetMagickModule(),
1778 ImageError,
"ImageMorphologyDiffers",
"`%s'",image->filename);
1779 return((
double *) NULL);
1784 length=CompositeChannels+1UL;
1785 channel_distortion=(
double *) AcquireQuantumMemory(length,
1786 sizeof(*channel_distortion));
1787 if (channel_distortion == (
double *) NULL)
1788 ThrowFatalException(ResourceLimitFatalError,
"MemoryAllocationFailed");
1789 (void) memset(channel_distortion,0,length*
1790 sizeof(*channel_distortion));
1794 case AbsoluteErrorMetric:
1796 status=GetAbsoluteDistortion(image,reconstruct_image,CompositeChannels,
1797 channel_distortion,exception);
1800 case FuzzErrorMetric:
1802 status=GetFuzzDistortion(image,reconstruct_image,CompositeChannels,
1803 channel_distortion,exception);
1806 case MeanAbsoluteErrorMetric:
1808 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1809 CompositeChannels,channel_distortion,exception);
1812 case MeanErrorPerPixelMetric:
1814 status=GetMeanErrorPerPixel(image,reconstruct_image,CompositeChannels,
1815 channel_distortion,exception);
1818 case MeanSquaredErrorMetric:
1820 status=GetMeanSquaredDistortion(image,reconstruct_image,CompositeChannels,
1821 channel_distortion,exception);
1824 case NormalizedCrossCorrelationErrorMetric:
1827 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1828 CompositeChannels,channel_distortion,exception);
1831 case PeakAbsoluteErrorMetric:
1833 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1834 CompositeChannels,channel_distortion,exception);
1837 case PeakSignalToNoiseRatioMetric:
1839 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1840 CompositeChannels,channel_distortion,exception);
1843 case PerceptualHashErrorMetric:
1845 status=GetPerceptualHashDistortion(image,reconstruct_image,
1846 CompositeChannels,channel_distortion,exception);
1849 case RootMeanSquaredErrorMetric:
1851 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1852 CompositeChannels,channel_distortion,exception);
1856 if (status == MagickFalse)
1858 channel_distortion=(
double *) RelinquishMagickMemory(channel_distortion);
1859 return((
double *) NULL);
1861 return(channel_distortion);
1911 MagickExport MagickBooleanType IsImagesEqual(
Image *image,
1912 const Image *reconstruct_image)
1929 mean_error_per_pixel;
1938 assert(image != (
Image *) NULL);
1939 assert(image->signature == MagickCoreSignature);
1940 assert(reconstruct_image != (
const Image *) NULL);
1941 assert(reconstruct_image->signature == MagickCoreSignature);
1942 exception=(&image->exception);
1943 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1944 ThrowBinaryException(ImageError,
"ImageMorphologyDiffers",image->filename);
1947 mean_error_per_pixel=0.0;
1949 rows=MagickMax(image->rows,reconstruct_image->rows);
1950 columns=MagickMax(image->columns,reconstruct_image->columns);
1951 image_view=AcquireVirtualCacheView(image,exception);
1952 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1953 for (y=0; y < (ssize_t) rows; y++)
1956 *magick_restrict indexes,
1957 *magick_restrict reconstruct_indexes;
1966 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1967 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1970 indexes=GetCacheViewVirtualIndexQueue(image_view);
1971 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1972 for (x=0; x < (ssize_t) columns; x++)
1977 distance=fabs((
double) GetPixelRed(p)-(
double) GetPixelRed(q));
1978 mean_error_per_pixel+=distance;
1979 mean_error+=distance*distance;
1980 if (distance > maximum_error)
1981 maximum_error=distance;
1983 distance=fabs((
double) GetPixelGreen(p)-(
double) GetPixelGreen(q));
1984 mean_error_per_pixel+=distance;
1985 mean_error+=distance*distance;
1986 if (distance > maximum_error)
1987 maximum_error=distance;
1989 distance=fabs((
double) GetPixelBlue(p)-(
double) GetPixelBlue(q));
1990 mean_error_per_pixel+=distance;
1991 mean_error+=distance*distance;
1992 if (distance > maximum_error)
1993 maximum_error=distance;
1995 if (image->matte != MagickFalse)
1997 distance=fabs((
double) GetPixelOpacity(p)-(
double)
1998 GetPixelOpacity(q));
1999 mean_error_per_pixel+=distance;
2000 mean_error+=distance*distance;
2001 if (distance > maximum_error)
2002 maximum_error=distance;
2005 if ((image->colorspace == CMYKColorspace) &&
2006 (reconstruct_image->colorspace == CMYKColorspace))
2008 distance=fabs((
double) GetPixelIndex(indexes+x)-(
double)
2009 GetPixelIndex(reconstruct_indexes+x));
2010 mean_error_per_pixel+=distance;
2011 mean_error+=distance*distance;
2012 if (distance > maximum_error)
2013 maximum_error=distance;
2020 reconstruct_view=DestroyCacheView(reconstruct_view);
2021 image_view=DestroyCacheView(image_view);
2022 gamma=PerceptibleReciprocal(area);
2023 image->error.mean_error_per_pixel=gamma*mean_error_per_pixel;
2024 image->error.normalized_mean_error=gamma*QuantumScale*QuantumScale*mean_error;
2025 image->error.normalized_maximum_error=QuantumScale*maximum_error;
2026 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
2065 static double GetSimilarityMetric(
const Image *image,
const Image *reference,
2066 const MetricType metric,
const ssize_t x_offset,
const ssize_t y_offset,
2081 SetGeometry(reference,&geometry);
2082 geometry.x=x_offset;
2083 geometry.y=y_offset;
2084 similarity_image=CropImage(image,&geometry,exception);
2085 if (similarity_image == (
Image *) NULL)
2088 status=GetImageDistortion(similarity_image,reference,metric,&distortion,
2091 similarity_image=DestroyImage(similarity_image);
2095 MagickExport
Image *SimilarityImage(
Image *image,
const Image *reference,
2101 similarity_image=SimilarityMetricImage(image,reference,
2102 RootMeanSquaredErrorMetric,offset,similarity_metric,exception);
2103 return(similarity_image);
2106 MagickExport
Image *SimilarityMetricImage(
Image *image,
const Image *reference,
2107 const MetricType metric,
RectangleInfo *offset,
double *similarity_metric,
2110 #define SimilarityImageTag "Similarity/Image" 2119 similarity_threshold;
2122 *similarity_image = (
Image *) NULL;
2136 assert(image != (
const Image *) NULL);
2137 assert(image->signature == MagickCoreSignature);
2139 assert(exception->signature == MagickCoreSignature);
2141 if (IsEventLogging() != MagickFalse)
2142 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
2143 SetGeometry(reference,offset);
2144 *similarity_metric=MagickMaximumValue;
2145 if (ValidateImageMorphology(image,reference) == MagickFalse)
2146 ThrowImageException(ImageError,
"ImageMorphologyDiffers");
2147 if ((image->columns < reference->columns) || (image->rows < reference->rows))
2149 (void) ThrowMagickException(&image->exception,GetMagickModule(),
2150 OptionWarning,
"GeometryDoesNotContainImage",
"`%s'",image->filename);
2151 return((
Image *) NULL);
2153 similarity_image=CloneImage(image,image->columns,image->rows,MagickTrue,
2155 if (similarity_image == (
Image *) NULL)
2156 return((
Image *) NULL);
2157 similarity_image->depth=MAGICKCORE_QUANTUM_DEPTH;
2158 similarity_image->matte=MagickFalse;
2159 similarity_image->type=GrayscaleType;
2160 status=SetImageStorageClass(similarity_image,DirectClass);
2161 if (status == MagickFalse)
2163 InheritException(exception,&similarity_image->exception);
2164 return(DestroyImage(similarity_image));
2169 similarity_threshold=(-1.0);
2170 artifact=GetImageArtifact(image,
"compare:similarity-threshold");
2171 if (artifact != (
const char *) NULL)
2172 similarity_threshold=StringToDouble(artifact,(
char **) NULL);
2175 similarity_view=AcquireVirtualCacheView(similarity_image,exception);
2176 rows=similarity_image->rows;
2177 #if defined(MAGICKCORE_OPENMP_SUPPORT) 2178 #pragma omp parallel for schedule(static,1) \ 2179 shared(progress,status,similarity_metric) \ 2180 magick_number_threads(similarity_image,similarity_image,rows << 3,1) 2182 for (y=0; y < (ssize_t) rows; y++)
2193 if (status == MagickFalse)
2195 #if defined(MAGICKCORE_OPENMP_SUPPORT) 2196 #pragma omp flush(similarity_metric) 2198 if (*similarity_metric <= similarity_threshold)
2200 q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
2207 for (x=0; x < (ssize_t) similarity_image->columns; x++)
2209 #if defined(MAGICKCORE_OPENMP_SUPPORT) 2210 #pragma omp flush(similarity_metric) 2212 if (*similarity_metric <= similarity_threshold)
2214 similarity=GetSimilarityMetric(image,reference,metric,x,y,exception);
2215 if ((metric == NormalizedCrossCorrelationErrorMetric) ||
2216 (metric == UndefinedErrorMetric))
2217 similarity=1.0-similarity;
2218 #if defined(MAGICKCORE_OPENMP_SUPPORT) 2219 #pragma omp critical (MagickCore_SimilarityImage) 2221 if (similarity < *similarity_metric)
2223 *similarity_metric=similarity;
2227 if (metric == PerceptualHashErrorMetric)
2228 similarity=MagickMin(0.01*similarity,1.0);
2229 if ((metric == MeanSquaredErrorMetric) ||
2230 (metric == NormalizedCrossCorrelationErrorMetric) ||
2231 (metric == RootMeanSquaredErrorMetric))
2232 SetPixelRed(q,ClampToQuantum((
double) QuantumRange-QuantumRange*
2235 SetPixelRed(q,ClampToQuantum((
double) QuantumRange*similarity));
2236 SetPixelGreen(q,GetPixelRed(q));
2237 SetPixelBlue(q,GetPixelRed(q));
2240 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
2242 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2247 #if defined(MAGICKCORE_OPENMP_SUPPORT) 2251 proceed=SetImageProgress(image,SimilarityImageTag,progress,image->rows);
2252 if (proceed == MagickFalse)
2256 similarity_view=DestroyCacheView(similarity_view);
2257 if (status == MagickFalse)
2258 similarity_image=DestroyImage(similarity_image);
2259 return(similarity_image);