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/compare-private.h" 55 #include "magick/composite-private.h" 56 #include "magick/constitute.h" 57 #include "magick/exception-private.h" 58 #include "magick/geometry.h" 59 #include "magick/image-private.h" 60 #include "magick/list.h" 61 #include "magick/log.h" 62 #include "magick/memory_.h" 63 #include "magick/monitor.h" 64 #include "magick/monitor-private.h" 65 #include "magick/option.h" 66 #include "magick/pixel-private.h" 67 #include "magick/property.h" 68 #include "magick/resource_.h" 69 #include "magick/statistic-private.h" 70 #include "magick/string_.h" 71 #include "magick/string-private.h" 72 #include "magick/statistic.h" 73 #include "magick/thread-private.h" 74 #include "magick/transform.h" 75 #include "magick/utility.h" 76 #include "magick/version.h" 114 MagickExport
Image *CompareImages(
Image *image,
const Image *reconstruct_image,
115 const MetricType metric,
double *distortion,
ExceptionInfo *exception)
120 highlight_image=CompareImageChannels(image,reconstruct_image,
121 CompositeChannels,metric,distortion,exception);
122 return(highlight_image);
125 static inline MagickBooleanType ValidateImageMorphology(
126 const Image *magick_restrict image,
127 const Image *magick_restrict reconstruct_image)
132 if (GetNumberChannels(image,DefaultChannels) !=
133 GetNumberChannels(reconstruct_image,DefaultChannels))
138 MagickExport
Image *CompareImageChannels(
Image *image,
139 const Image *reconstruct_image,
const ChannelType channel,
140 const MetricType metric,
double *distortion,
ExceptionInfo *exception)
170 assert(image != (
Image *) NULL);
171 assert(image->signature == MagickCoreSignature);
172 assert(reconstruct_image != (
const Image *) NULL);
173 assert(reconstruct_image->signature == MagickCoreSignature);
174 assert(distortion != (
double *) NULL);
175 if (IsEventLogging() != MagickFalse)
176 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
178 if (metric != PerceptualHashErrorMetric)
179 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
180 ThrowImageException(ImageError,
"ImageMorphologyDiffers");
181 status=GetImageChannelDistortion(image,reconstruct_image,channel,metric,
182 distortion,exception);
183 if (status == MagickFalse)
184 return((
Image *) NULL);
185 clone_image=CloneImage(image,0,0,MagickTrue,exception);
186 if (clone_image == (
Image *) NULL)
187 return((
Image *) NULL);
188 (void) SetImageMask(clone_image,(
Image *) NULL);
189 difference_image=CloneImage(clone_image,0,0,MagickTrue,exception);
190 clone_image=DestroyImage(clone_image);
191 if (difference_image == (
Image *) NULL)
192 return((
Image *) NULL);
193 (void) SetImageAlphaChannel(difference_image,OpaqueAlphaChannel);
194 SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
195 highlight_image=CloneImage(image,columns,rows,MagickTrue,exception);
196 if (highlight_image == (
Image *) NULL)
198 difference_image=DestroyImage(difference_image);
199 return((
Image *) NULL);
201 if (SetImageStorageClass(highlight_image,DirectClass) == MagickFalse)
203 InheritException(exception,&highlight_image->exception);
204 difference_image=DestroyImage(difference_image);
205 highlight_image=DestroyImage(highlight_image);
206 return((
Image *) NULL);
208 (void) SetImageMask(highlight_image,(
Image *) NULL);
209 (void) SetImageAlphaChannel(highlight_image,OpaqueAlphaChannel);
210 (void) QueryMagickColor(
"#f1001ecc",&highlight,exception);
211 artifact=GetImageArtifact(image,
"compare:highlight-color");
212 if (artifact != (
const char *) NULL)
213 (void) QueryMagickColor(artifact,&highlight,exception);
214 (void) QueryMagickColor(
"#ffffffcc",&lowlight,exception);
215 artifact=GetImageArtifact(image,
"compare:lowlight-color");
216 if (artifact != (
const char *) NULL)
217 (void) QueryMagickColor(artifact,&lowlight,exception);
218 if (highlight_image->colorspace == CMYKColorspace)
220 ConvertRGBToCMYK(&highlight);
221 ConvertRGBToCMYK(&lowlight);
226 GetMagickPixelPacket(image,&zero);
227 image_view=AcquireVirtualCacheView(image,exception);
228 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
229 highlight_view=AcquireAuthenticCacheView(highlight_image,exception);
230 #if defined(MAGICKCORE_OPENMP_SUPPORT) 231 #pragma omp parallel for schedule(static) shared(status) \ 232 magick_number_threads(image,highlight_image,rows,1) 234 for (y=0; y < (ssize_t) rows; y++)
244 *magick_restrict indexes,
245 *magick_restrict reconstruct_indexes;
252 *magick_restrict highlight_indexes;
260 if (status == MagickFalse)
262 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
263 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
264 r=QueueCacheViewAuthenticPixels(highlight_view,0,y,columns,1,exception);
271 indexes=GetCacheViewVirtualIndexQueue(image_view);
272 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
273 highlight_indexes=GetCacheViewAuthenticIndexQueue(highlight_view);
275 reconstruct_pixel=zero;
276 for (x=0; x < (ssize_t) columns; x++)
278 SetMagickPixelPacket(image,p,indexes == (IndexPacket *) NULL ? NULL :
280 SetMagickPixelPacket(reconstruct_image,q,reconstruct_indexes ==
281 (IndexPacket *) NULL ? NULL : reconstruct_indexes+x,&reconstruct_pixel);
282 if (IsMagickColorSimilar(&pixel,&reconstruct_pixel) == MagickFalse)
283 SetPixelPacket(highlight_image,&highlight,r,highlight_indexes ==
284 (IndexPacket *) NULL ? NULL : highlight_indexes+x);
286 SetPixelPacket(highlight_image,&lowlight,r,highlight_indexes ==
287 (IndexPacket *) NULL ? NULL : highlight_indexes+x);
292 sync=SyncCacheViewAuthenticPixels(highlight_view,exception);
293 if (sync == MagickFalse)
296 highlight_view=DestroyCacheView(highlight_view);
297 reconstruct_view=DestroyCacheView(reconstruct_view);
298 image_view=DestroyCacheView(image_view);
299 (void) CompositeImage(difference_image,image->compose,highlight_image,0,0);
300 highlight_image=DestroyImage(highlight_image);
301 if (status == MagickFalse)
302 difference_image=DestroyImage(difference_image);
303 return(difference_image);
342 MagickExport MagickBooleanType GetImageDistortion(
Image *image,
343 const Image *reconstruct_image,
const MetricType metric,
double *distortion,
349 status=GetImageChannelDistortion(image,reconstruct_image,CompositeChannels,
350 metric,distortion,exception);
354 static MagickBooleanType GetAESimilarity(
const Image *image,
355 const Image *reconstruct_image,
const ChannelType channel,
double *similarity,
380 fuzz=GetFuzzyColorDistance(image,reconstruct_image);
381 SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
382 image_view=AcquireVirtualCacheView(image,exception);
383 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
384 #if defined(MAGICKCORE_OPENMP_SUPPORT) 385 #pragma omp parallel for schedule(static) shared(similarity,status) \ 386 magick_number_threads(image,image,rows,1) 388 for (y=0; y < (ssize_t) rows; y++)
391 *magick_restrict indexes,
392 *magick_restrict reconstruct_indexes;
399 channel_similarity[CompositeChannels+1] = { 0.0 };
405 if (status == MagickFalse)
407 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
408 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
414 indexes=GetCacheViewVirtualIndexQueue(image_view);
415 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
416 (void) memset(channel_similarity,0,
sizeof(channel_similarity));
417 for (x=0; x < (ssize_t) columns; x++)
427 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
428 ((double) QuantumRange-(double) OpaqueOpacity));
429 Da=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(q) :
430 ((double) QuantumRange-(double) OpaqueOpacity));
431 if ((channel & RedChannel) != 0)
433 error=Sa*(double) GetPixelRed(p)-Da*(double)
435 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
437 channel_similarity[RedChannel]++;
441 if ((channel & GreenChannel) != 0)
443 error=Sa*(double) GetPixelGreen(p)-Da*(double)
445 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
447 channel_similarity[GreenChannel]++;
451 if ((channel & BlueChannel) != 0)
453 error=Sa*(double) GetPixelBlue(p)-Da*(double)
455 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
457 channel_similarity[BlueChannel]++;
461 if (((channel & OpacityChannel) != 0) &&
462 (image->matte != MagickFalse))
464 error=(double) GetPixelOpacity(p)-(double) GetPixelOpacity(q);
465 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
467 channel_similarity[OpacityChannel]++;
471 if (((channel & IndexChannel) != 0) &&
472 (image->colorspace == CMYKColorspace))
474 error=Sa*(double) indexes[x]-Da*(
double) reconstruct_indexes[x];
475 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
477 channel_similarity[IndexChannel]++;
482 channel_similarity[CompositeChannels]++;
486 #if defined(MAGICKCORE_OPENMP_SUPPORT) 487 #pragma omp critical (MagickCore_GetAESimilarity) 489 for (i=0; i <= (ssize_t) CompositeChannels; i++)
490 similarity[i]+=channel_similarity[i];
492 reconstruct_view=DestroyCacheView(reconstruct_view);
493 image_view=DestroyCacheView(image_view);
494 area=MagickSafeReciprocal((
double) columns*rows);
495 for (j=0; j <= CompositeChannels; j++)
500 static MagickBooleanType GetFUZZSimilarity(
const Image *image,
501 const Image *reconstruct_image,
const ChannelType channel,
523 fuzz=GetFuzzyColorDistance(image,reconstruct_image);
524 SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
525 image_view=AcquireVirtualCacheView(image,exception);
526 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
527 #if defined(MAGICKCORE_OPENMP_SUPPORT) 528 #pragma omp parallel for schedule(static) shared(status) \ 529 magick_number_threads(image,image,rows,1) 531 for (y=0; y < (ssize_t) rows; y++)
535 channel_similarity[CompositeChannels+1] = { 0.0 };
538 *magick_restrict indexes,
539 *magick_restrict reconstruct_indexes;
549 if (status == MagickFalse)
551 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
552 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
558 indexes=GetCacheViewVirtualIndexQueue(image_view);
559 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
560 for (x=0; x < (ssize_t) columns; x++)
567 Sa=QuantumScale*(image->matte != MagickFalse ? (double)
568 GetPixelAlpha(p) : ((double) QuantumRange-(double) OpaqueOpacity));
569 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
570 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
572 if ((channel & RedChannel) != 0)
574 error=QuantumScale*(Sa*GetPixelRed(p)-Da*GetPixelRed(q));
575 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
577 channel_similarity[RedChannel]+=error*error;
578 channel_similarity[CompositeChannels]+=error*error;
582 if ((channel & GreenChannel) != 0)
584 error=QuantumScale*(Sa*GetPixelGreen(p)-Da*GetPixelGreen(q));
585 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
587 channel_similarity[GreenChannel]+=error*error;
588 channel_similarity[CompositeChannels]+=error*error;
592 if ((channel & BlueChannel) != 0)
594 error=QuantumScale*(Sa*GetPixelBlue(p)-Da*GetPixelBlue(q));
595 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
597 channel_similarity[BlueChannel]+=error*error;
598 channel_similarity[CompositeChannels]+=error*error;
602 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
604 error=QuantumScale*((double) GetPixelOpacity(p)-GetPixelOpacity(q));
605 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
607 channel_similarity[OpacityChannel]+=error*error;
608 channel_similarity[CompositeChannels]+=error*error;
612 if (((channel & IndexChannel) != 0) &&
613 (image->colorspace == CMYKColorspace))
615 error=QuantumScale*(Sa*GetPixelIndex(indexes+x)-Da*
616 GetPixelIndex(reconstruct_indexes+x));
617 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
619 channel_similarity[BlackChannel]+=error*error;
620 channel_similarity[CompositeChannels]+=error*error;
627 #if defined(MAGICKCORE_OPENMP_SUPPORT) 628 #pragma omp critical (MagickCore_GetMeanAbsoluteError) 632 for (i=0; i <= (ssize_t) CompositeChannels; i++)
633 similarity[i]+=channel_similarity[i];
636 reconstruct_view=DestroyCacheView(reconstruct_view);
637 image_view=DestroyCacheView(image_view);
638 area=MagickSafeReciprocal(area);
639 for (i=0; i <= (ssize_t) CompositeChannels; i++)
644 static MagickBooleanType GetPDCSimilarity(
const Image *image,
645 const Image *reconstruct_image,
const ChannelType channel,
double *similarity,
670 fuzz=GetFuzzyColorDistance(image,reconstruct_image);
671 SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
672 image_view=AcquireVirtualCacheView(image,exception);
673 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
674 #if defined(MAGICKCORE_OPENMP_SUPPORT) 675 #pragma omp parallel for schedule(static) shared(similarity,status) \ 676 magick_number_threads(image,image,rows,1) 678 for (y=0; y < (ssize_t) rows; y++)
681 *magick_restrict indexes,
682 *magick_restrict reconstruct_indexes;
689 channel_similarity[CompositeChannels+1] = { 0.0 };
695 if (status == MagickFalse)
697 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
698 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
704 indexes=GetCacheViewVirtualIndexQueue(image_view);
705 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
706 (void) memset(channel_similarity,0,
sizeof(channel_similarity));
707 for (x=0; x < (ssize_t) columns; x++)
717 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
718 ((double) QuantumRange-(double) OpaqueOpacity));
719 Da=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(q) :
720 ((double) QuantumRange-(double) OpaqueOpacity));
721 if ((channel & RedChannel) != 0)
723 error=Sa*(double) GetPixelRed(p)-Da*(double)
725 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
727 channel_similarity[RedChannel]++;
731 if ((channel & GreenChannel) != 0)
733 error=Sa*(double) GetPixelGreen(p)-Da*(double)
735 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
737 channel_similarity[GreenChannel]++;
741 if ((channel & BlueChannel) != 0)
743 error=Sa*(double) GetPixelBlue(p)-Da*(double)
745 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
747 channel_similarity[BlueChannel]++;
751 if (((channel & OpacityChannel) != 0) &&
752 (image->matte != MagickFalse))
754 error=(double) GetPixelOpacity(p)-(double) GetPixelOpacity(q);
755 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
757 channel_similarity[OpacityChannel]++;
761 if (((channel & IndexChannel) != 0) &&
762 (image->colorspace == CMYKColorspace))
764 error=Sa*(double) indexes[x]-Da*(
double) reconstruct_indexes[x];
765 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
767 channel_similarity[IndexChannel]++;
772 channel_similarity[CompositeChannels]++;
776 #if defined(MAGICKCORE_OPENMP_SUPPORT) 777 #pragma omp critical (MagickCore_GetAESimilarity) 779 for (i=0; i <= (ssize_t) CompositeChannels; i++)
780 similarity[i]+=channel_similarity[i];
782 reconstruct_view=DestroyCacheView(reconstruct_view);
783 image_view=DestroyCacheView(image_view);
784 area=MagickSafeReciprocal((
double) columns*rows);
785 for (j=0; j <= CompositeChannels; j++)
790 static MagickBooleanType GetMAESimilarity(
const Image *image,
791 const Image *reconstruct_image,
const ChannelType channel,
810 SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
811 image_view=AcquireVirtualCacheView(image,exception);
812 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
813 #if defined(MAGICKCORE_OPENMP_SUPPORT) 814 #pragma omp parallel for schedule(static) shared(status) \ 815 magick_number_threads(image,image,rows,1) 817 for (y=0; y < (ssize_t) rows; y++)
820 channel_similarity[CompositeChannels+1];
823 *magick_restrict indexes,
824 *magick_restrict reconstruct_indexes;
834 if (status == MagickFalse)
836 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
837 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
843 indexes=GetCacheViewVirtualIndexQueue(image_view);
844 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
845 (void) memset(channel_similarity,0,
sizeof(channel_similarity));
846 for (x=0; x < (ssize_t) columns; x++)
853 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
854 ((double) QuantumRange-(double) OpaqueOpacity));
855 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
856 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
858 if ((channel & RedChannel) != 0)
860 distance=QuantumScale*fabs(Sa*(
double) GetPixelRed(p)-Da*
861 (
double) GetPixelRed(q));
862 channel_similarity[RedChannel]+=distance;
863 channel_similarity[CompositeChannels]+=distance;
865 if ((channel & GreenChannel) != 0)
867 distance=QuantumScale*fabs(Sa*(
double) GetPixelGreen(p)-Da*
868 (
double) GetPixelGreen(q));
869 channel_similarity[GreenChannel]+=distance;
870 channel_similarity[CompositeChannels]+=distance;
872 if ((channel & BlueChannel) != 0)
874 distance=QuantumScale*fabs(Sa*(
double) GetPixelBlue(p)-Da*
875 (
double) GetPixelBlue(q));
876 channel_similarity[BlueChannel]+=distance;
877 channel_similarity[CompositeChannels]+=distance;
879 if (((channel & OpacityChannel) != 0) &&
880 (image->matte != MagickFalse))
882 distance=QuantumScale*fabs((
double) GetPixelOpacity(p)-(
double)
884 channel_similarity[OpacityChannel]+=distance;
885 channel_similarity[CompositeChannels]+=distance;
887 if (((channel & IndexChannel) != 0) &&
888 (image->colorspace == CMYKColorspace))
890 distance=QuantumScale*fabs(Sa*(
double) GetPixelIndex(indexes+x)-Da*
891 (
double) GetPixelIndex(reconstruct_indexes+x));
892 channel_similarity[BlackChannel]+=distance;
893 channel_similarity[CompositeChannels]+=distance;
898 #if defined(MAGICKCORE_OPENMP_SUPPORT) 899 #pragma omp critical (MagickCore_GetMeanAbsoluteError) 901 for (i=0; i <= (ssize_t) CompositeChannels; i++)
902 similarity[i]+=channel_similarity[i];
904 reconstruct_view=DestroyCacheView(reconstruct_view);
905 image_view=DestroyCacheView(image_view);
906 for (i=0; i <= (ssize_t) CompositeChannels; i++)
907 similarity[i]/=((
double) columns*rows);
908 similarity[CompositeChannels]/=(double) GetNumberChannels(image,channel);
912 static MagickBooleanType GetMEPPSimilarity(
Image *image,
913 const Image *reconstruct_image,
const ChannelType channel,
double *similarity,
921 maximum_error = -MagickMaximumValue,
936 SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
937 image_view=AcquireVirtualCacheView(image,exception);
938 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
939 #if defined(MAGICKCORE_OPENMP_SUPPORT) 940 #pragma omp parallel for schedule(static) shared(maximum_error,status) \ 941 magick_number_threads(image,image,rows,1) 943 for (y=0; y < (ssize_t) rows; y++)
946 channel_similarity[CompositeChannels+1] = { 0.0 },
947 local_maximum = maximum_error,
948 local_mean_error = 0.0;
951 *magick_restrict indexes,
952 *magick_restrict reconstruct_indexes;
962 if (status == MagickFalse)
964 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
965 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
971 indexes=GetCacheViewVirtualIndexQueue(image_view);
972 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
973 (void) memset(channel_similarity,0,
sizeof(channel_similarity));
974 for (x=0; x < (ssize_t) columns; x++)
981 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
982 ((double) QuantumRange-(double) OpaqueOpacity));
983 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
984 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
986 if ((channel & RedChannel) != 0)
988 distance=QuantumScale*fabs(Sa*(
double) GetPixelRed(p)-Da*
989 (
double) GetPixelRed(q));
990 channel_similarity[RedChannel]+=distance;
991 channel_similarity[CompositeChannels]+=distance;
992 local_mean_error+=distance*distance;
993 if (distance > local_maximum)
994 local_maximum=distance;
996 if ((channel & GreenChannel) != 0)
998 distance=QuantumScale*fabs(Sa*(
double) GetPixelGreen(p)-Da*
999 (
double) GetPixelGreen(q));
1000 channel_similarity[GreenChannel]+=distance;
1001 channel_similarity[CompositeChannels]+=distance;
1002 local_mean_error+=distance*distance;
1003 if (distance > local_maximum)
1004 local_maximum=distance;
1006 if ((channel & BlueChannel) != 0)
1008 distance=QuantumScale*fabs(Sa*(
double) GetPixelBlue(p)-Da*
1009 (
double) GetPixelBlue(q));
1010 channel_similarity[BlueChannel]+=distance;
1011 channel_similarity[CompositeChannels]+=distance;
1012 local_mean_error+=distance*distance;
1013 if (distance > local_maximum)
1014 local_maximum=distance;
1016 if (((channel & OpacityChannel) != 0) &&
1017 (image->matte != MagickFalse))
1019 distance=QuantumScale*fabs((
double) GetPixelOpacity(p)-(
double)
1020 GetPixelOpacity(q));
1021 channel_similarity[OpacityChannel]+=distance;
1022 channel_similarity[CompositeChannels]+=distance;
1023 local_mean_error+=distance*distance;
1024 if (distance > local_maximum)
1025 local_maximum=distance;
1027 if (((channel & IndexChannel) != 0) &&
1028 (image->colorspace == CMYKColorspace))
1030 distance=QuantumScale*fabs(Sa*(
double) GetPixelIndex(indexes+x)-Da*
1031 (
double) GetPixelIndex(reconstruct_indexes+x));
1032 channel_similarity[BlackChannel]+=distance;
1033 channel_similarity[CompositeChannels]+=distance;
1034 local_mean_error+=distance*distance;
1035 if (distance > local_maximum)
1036 local_maximum=distance;
1041 #if defined(MAGICKCORE_OPENMP_SUPPORT) 1042 #pragma omp critical (MagickCore_GetMeanAbsoluteError) 1045 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1046 similarity[i]+=channel_similarity[i];
1047 mean_error+=local_mean_error;
1048 if (local_maximum > maximum_error)
1049 maximum_error=local_maximum;
1052 reconstruct_view=DestroyCacheView(reconstruct_view);
1053 image_view=DestroyCacheView(image_view);
1054 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1055 similarity[i]/=((
double) columns*rows);
1056 similarity[CompositeChannels]/=(double) GetNumberChannels(image,channel);
1057 image->error.mean_error_per_pixel=QuantumRange*similarity[CompositeChannels];
1058 image->error.normalized_mean_error=mean_error/((double) columns*rows);
1059 image->error.normalized_maximum_error=maximum_error;
1063 static MagickBooleanType GetMSESimilarity(
const Image *image,
1064 const Image *reconstruct_image,
const ChannelType channel,
1086 SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
1087 image_view=AcquireVirtualCacheView(image,exception);
1088 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1089 #if defined(MAGICKCORE_OPENMP_SUPPORT) 1090 #pragma omp parallel for schedule(static) shared(similarity,status) \ 1091 magick_number_threads(image,image,rows,1) 1093 for (y=0; y < (ssize_t) rows; y++)
1096 channel_similarity[CompositeChannels+1] = { 0.0 };
1099 *magick_restrict indexes,
1100 *magick_restrict reconstruct_indexes;
1110 if (status == MagickFalse)
1112 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1113 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1119 indexes=GetCacheViewVirtualIndexQueue(image_view);
1120 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1121 for (x=0; x < (ssize_t) columns; x++)
1128 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
1129 ((double) QuantumRange-(double) OpaqueOpacity));
1130 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1131 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
1133 if ((channel & RedChannel) != 0)
1135 distance=QuantumScale*(Sa*(double) GetPixelRed(p)-Da*(double)
1137 channel_similarity[RedChannel]+=distance*distance;
1138 channel_similarity[CompositeChannels]+=distance*distance;
1140 if ((channel & GreenChannel) != 0)
1142 distance=QuantumScale*(Sa*(double) GetPixelGreen(p)-Da*(double)
1144 channel_similarity[GreenChannel]+=distance*distance;
1145 channel_similarity[CompositeChannels]+=distance*distance;
1147 if ((channel & BlueChannel) != 0)
1149 distance=QuantumScale*(Sa*(double) GetPixelBlue(p)-Da*(double)
1151 channel_similarity[BlueChannel]+=distance*distance;
1152 channel_similarity[CompositeChannels]+=distance*distance;
1154 if (((channel & OpacityChannel) != 0) &&
1155 (image->matte != MagickFalse))
1157 distance=QuantumScale*((double) GetPixelOpacity(p)-(double)
1158 GetPixelOpacity(q));
1159 channel_similarity[OpacityChannel]+=distance*distance;
1160 channel_similarity[CompositeChannels]+=distance*distance;
1162 if (((channel & IndexChannel) != 0) &&
1163 (image->colorspace == CMYKColorspace) &&
1164 (reconstruct_image->colorspace == CMYKColorspace))
1166 distance=QuantumScale*(Sa*(double) GetPixelIndex(indexes+x)-Da*
1167 (double) GetPixelIndex(reconstruct_indexes+x));
1168 channel_similarity[BlackChannel]+=distance*distance;
1169 channel_similarity[CompositeChannels]+=distance*distance;
1174 #if defined(MAGICKCORE_OPENMP_SUPPORT) 1175 #pragma omp critical (MagickCore_GetMeanSquaredError) 1177 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1178 similarity[i]+=channel_similarity[i];
1180 reconstruct_view=DestroyCacheView(reconstruct_view);
1181 image_view=DestroyCacheView(image_view);
1182 area=MagickSafeReciprocal((
double) columns*rows);
1183 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1184 similarity[i]*=area;
1185 similarity[CompositeChannels]/=(double) GetNumberChannels(image,channel);
1189 static MagickBooleanType GetNCCSimilarity(
const Image *image,
1190 const Image *reconstruct_image,
const ChannelType channel,
double *similarity,
1193 #define SimilarityImageTag "Similarity/Image" 1201 *reconstruct_statistics;
1204 alpha_variance[CompositeChannels+1] = { 0.0 },
1205 beta_variance[CompositeChannels+1] = { 0.0 };
1224 image_statistics=GetImageChannelStatistics(image,exception);
1225 reconstruct_statistics=GetImageChannelStatistics(reconstruct_image,exception);
1234 reconstruct_statistics);
1235 return(MagickFalse);
1237 (void) memset(similarity,0,(CompositeChannels+1)*
sizeof(*similarity));
1240 SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
1241 image_view=AcquireVirtualCacheView(image,exception);
1242 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1243 #if defined(MAGICKCORE_OPENMP_SUPPORT) 1244 #pragma omp parallel for schedule(static) shared(status) \ 1245 magick_number_threads(image,image,rows,1) 1247 for (y=0; y < (ssize_t) rows; y++)
1250 *magick_restrict indexes,
1251 *magick_restrict reconstruct_indexes;
1258 channel_alpha_variance[CompositeChannels+1] = { 0.0 },
1259 channel_beta_variance[CompositeChannels+1] = { 0.0 },
1260 channel_similarity[CompositeChannels+1] = { 0.0 };
1265 if (status == MagickFalse)
1267 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1268 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1274 indexes=GetCacheViewVirtualIndexQueue(image_view);
1275 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1276 for (x=0; x < (ssize_t) columns; x++)
1284 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
1285 (double) QuantumRange);
1286 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1287 (double) GetPixelAlpha(q) : (double) QuantumRange);
1288 if ((channel & RedChannel) != 0)
1290 alpha=QuantumScale*(Sa*(double) GetPixelRed(p)-
1291 image_statistics[RedChannel].mean);
1292 beta=QuantumScale*(Da*(double) GetPixelRed(q)-
1293 reconstruct_statistics[RedChannel].mean);
1294 channel_similarity[RedChannel]+=alpha*beta;
1295 channel_similarity[CompositeChannels]+=alpha*beta;
1296 channel_alpha_variance[RedChannel]+=alpha*alpha;
1297 channel_alpha_variance[CompositeChannels]+=alpha*alpha;
1298 channel_beta_variance[RedChannel]+=beta*beta;
1299 channel_beta_variance[CompositeChannels]+=beta*beta;
1301 if ((channel & GreenChannel) != 0)
1303 alpha=QuantumScale*(Sa*(double) GetPixelGreen(p)-
1304 image_statistics[GreenChannel].mean);
1305 beta=QuantumScale*(Da*(double) GetPixelGreen(q)-
1306 reconstruct_statistics[GreenChannel].mean);
1307 channel_similarity[GreenChannel]+=alpha*beta;
1308 channel_similarity[CompositeChannels]+=alpha*beta;
1309 channel_alpha_variance[GreenChannel]+=alpha*alpha;
1310 channel_alpha_variance[CompositeChannels]+=alpha*alpha;
1311 channel_beta_variance[GreenChannel]+=beta*beta;
1312 channel_beta_variance[CompositeChannels]+=beta*beta;
1314 if ((channel & BlueChannel) != 0)
1316 alpha=QuantumScale*(Sa*(double) GetPixelBlue(p)-
1317 image_statistics[BlueChannel].mean);
1318 beta=QuantumScale*(Da*(double) GetPixelBlue(q)-
1319 reconstruct_statistics[BlueChannel].mean);
1320 channel_similarity[BlueChannel]+=alpha*beta;
1321 channel_alpha_variance[BlueChannel]+=alpha*alpha;
1322 channel_beta_variance[BlueChannel]+=beta*beta;
1324 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
1326 alpha=QuantumScale*((double) GetPixelAlpha(p)-
1327 image_statistics[AlphaChannel].mean);
1328 beta=QuantumScale*((double) GetPixelAlpha(q)-
1329 reconstruct_statistics[AlphaChannel].mean);
1330 channel_similarity[OpacityChannel]+=alpha*beta;
1331 channel_similarity[CompositeChannels]+=alpha*beta;
1332 channel_alpha_variance[OpacityChannel]+=alpha*alpha;
1333 channel_alpha_variance[CompositeChannels]+=alpha*alpha;
1334 channel_beta_variance[OpacityChannel]+=beta*beta;
1335 channel_beta_variance[CompositeChannels]+=beta*beta;
1337 if (((channel & IndexChannel) != 0) &&
1338 (image->colorspace == CMYKColorspace) &&
1339 (reconstruct_image->colorspace == CMYKColorspace))
1341 alpha=QuantumScale*(Sa*(double) GetPixelIndex(indexes+x)-
1342 image_statistics[BlackChannel].mean);
1343 beta=QuantumScale*(Da*(double) GetPixelIndex(reconstruct_indexes+
1344 x)-reconstruct_statistics[BlackChannel].mean);
1345 channel_similarity[BlackChannel]+=alpha*beta;
1346 channel_similarity[CompositeChannels]+=alpha*beta;
1347 channel_alpha_variance[BlackChannel]+=alpha*alpha;
1348 channel_alpha_variance[CompositeChannels]+=alpha*alpha;
1349 channel_beta_variance[BlackChannel]+=beta*beta;
1350 channel_beta_variance[CompositeChannels]+=beta*beta;
1355 #if defined(MAGICKCORE_OPENMP_SUPPORT) 1356 #pragma omp critical (GetNCCSimilarity) 1362 for (j=0; j <= (ssize_t) CompositeChannels; j++)
1364 similarity[j]+=channel_similarity[j];
1365 alpha_variance[j]+=channel_alpha_variance[j];
1366 beta_variance[j]+=channel_beta_variance[j];
1369 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1374 #if defined(MAGICKCORE_OPENMP_SUPPORT) 1378 proceed=SetImageProgress(image,SimilarityImageTag,progress,rows);
1379 if (proceed == MagickFalse)
1383 reconstruct_view=DestroyCacheView(reconstruct_view);
1384 image_view=DestroyCacheView(image_view);
1388 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1389 similarity[i]*=MagickSafeReciprocal(sqrt(alpha_variance[i])*
1390 sqrt(beta_variance[i]));
1395 reconstruct_statistics);
1401 static MagickBooleanType GetPASimilarity(
const Image *image,
1402 const Image *reconstruct_image,
const ChannelType channel,
1420 (void) memset(similarity,0,(CompositeChannels+1)*
sizeof(*similarity));
1421 SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
1422 image_view=AcquireVirtualCacheView(image,exception);
1423 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1424 #if defined(MAGICKCORE_OPENMP_SUPPORT) 1425 #pragma omp parallel for schedule(static) shared(status) \ 1426 magick_number_threads(image,image,rows,1) 1428 for (y=0; y < (ssize_t) rows; y++)
1431 channel_similarity[CompositeChannels+1];
1434 *magick_restrict indexes,
1435 *magick_restrict reconstruct_indexes;
1445 if (status == MagickFalse)
1447 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1448 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1454 indexes=GetCacheViewVirtualIndexQueue(image_view);
1455 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1456 (void) memset(channel_similarity,0,(CompositeChannels+1)*
1457 sizeof(*channel_similarity));
1458 for (x=0; x < (ssize_t) columns; x++)
1465 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
1466 ((double) QuantumRange-(double) OpaqueOpacity));
1467 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1468 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
1470 if ((channel & RedChannel) != 0)
1472 distance=QuantumScale*fabs(Sa*(
double) GetPixelRed(p)-Da*
1473 (
double) GetPixelRed(q));
1474 if (distance > channel_similarity[RedChannel])
1475 channel_similarity[RedChannel]=distance;
1476 if (distance > channel_similarity[CompositeChannels])
1477 channel_similarity[CompositeChannels]=distance;
1479 if ((channel & GreenChannel) != 0)
1481 distance=QuantumScale*fabs(Sa*(
double) GetPixelGreen(p)-Da*
1482 (
double) GetPixelGreen(q));
1483 if (distance > channel_similarity[GreenChannel])
1484 channel_similarity[GreenChannel]=distance;
1485 if (distance > channel_similarity[CompositeChannels])
1486 channel_similarity[CompositeChannels]=distance;
1488 if ((channel & BlueChannel) != 0)
1490 distance=QuantumScale*fabs(Sa*(
double) GetPixelBlue(p)-Da*
1491 (
double) GetPixelBlue(q));
1492 if (distance > channel_similarity[BlueChannel])
1493 channel_similarity[BlueChannel]=distance;
1494 if (distance > channel_similarity[CompositeChannels])
1495 channel_similarity[CompositeChannels]=distance;
1497 if (((channel & OpacityChannel) != 0) &&
1498 (image->matte != MagickFalse))
1500 distance=QuantumScale*fabs((
double) GetPixelOpacity(p)-(
double)
1501 GetPixelOpacity(q));
1502 if (distance > channel_similarity[OpacityChannel])
1503 channel_similarity[OpacityChannel]=distance;
1504 if (distance > channel_similarity[CompositeChannels])
1505 channel_similarity[CompositeChannels]=distance;
1507 if (((channel & IndexChannel) != 0) &&
1508 (image->colorspace == CMYKColorspace) &&
1509 (reconstruct_image->colorspace == CMYKColorspace))
1511 distance=QuantumScale*fabs(Sa*(
double) GetPixelIndex(indexes+x)-Da*
1512 (
double) GetPixelIndex(reconstruct_indexes+x));
1513 if (distance > channel_similarity[BlackChannel])
1514 channel_similarity[BlackChannel]=distance;
1515 if (distance > channel_similarity[CompositeChannels])
1516 channel_similarity[CompositeChannels]=distance;
1521 #if defined(MAGICKCORE_OPENMP_SUPPORT) 1522 #pragma omp critical (MagickCore_GetPeakAbsoluteError) 1524 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1525 if (channel_similarity[i] > similarity[i])
1526 similarity[i]=channel_similarity[i];
1528 reconstruct_view=DestroyCacheView(reconstruct_view);
1529 image_view=DestroyCacheView(image_view);
1533 static MagickBooleanType GetPSNRSimilarity(
const Image *image,
1534 const Image *reconstruct_image,
const ChannelType channel,
1540 status=GetMSESimilarity(image,reconstruct_image,channel,similarity,
1542 if ((channel & RedChannel) != 0)
1543 similarity[RedChannel]=10.0*MagickSafeLog10(MagickSafeReciprocal(
1544 similarity[RedChannel]))/MagickSafePSNRRecipicol(10.0);
1545 if ((channel & GreenChannel) != 0)
1546 similarity[GreenChannel]=10.0*MagickSafeLog10(MagickSafeReciprocal(
1547 similarity[GreenChannel]))/MagickSafePSNRRecipicol(10.0);
1548 if ((channel & BlueChannel) != 0)
1549 similarity[BlueChannel]=10.0*MagickSafeLog10(MagickSafeReciprocal(
1550 similarity[BlueChannel]))/MagickSafePSNRRecipicol(10.0);
1551 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
1552 similarity[OpacityChannel]=10.0*MagickSafeLog10(MagickSafeReciprocal(
1553 similarity[OpacityChannel]))/MagickSafePSNRRecipicol(10.0);
1554 if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
1555 similarity[BlackChannel]=10.0*MagickSafeLog10(MagickSafeReciprocal(
1556 similarity[BlackChannel]))/MagickSafePSNRRecipicol(10.0);
1557 similarity[CompositeChannels]=10.0*MagickSafeLog10(MagickSafeReciprocal(
1558 similarity[CompositeChannels]))/MagickSafePSNRRecipicol(10.0);
1562 static MagickBooleanType GetPHASHSimilarity(
const Image *image,
1563 const Image *reconstruct_image,
const ChannelType channel,
double *similarity,
1580 image_phash=GetImageChannelPerceptualHash(image,exception);
1582 return(MagickFalse);
1583 reconstruct_phash=GetImageChannelPerceptualHash(reconstruct_image,exception);
1587 return(MagickFalse);
1589 for (i=0; i < MaximumNumberOfImageMoments; i++)
1594 if ((channel & RedChannel) != 0)
1596 error=reconstruct_phash[RedChannel].P[i]-image_phash[RedChannel].P[i];
1597 if (IsNaN(error) != 0)
1599 difference=error*error;
1600 similarity[RedChannel]+=difference;
1601 similarity[CompositeChannels]+=difference;
1603 if ((channel & GreenChannel) != 0)
1605 error=reconstruct_phash[GreenChannel].P[i]-
1606 image_phash[GreenChannel].P[i];
1607 if (IsNaN(error) != 0)
1609 difference=error*error;
1610 similarity[GreenChannel]+=difference;
1611 similarity[CompositeChannels]+=difference;
1613 if ((channel & BlueChannel) != 0)
1615 error=reconstruct_phash[BlueChannel].P[i]-image_phash[BlueChannel].P[i];
1616 if (IsNaN(error) != 0)
1618 difference=error*error;
1619 similarity[BlueChannel]+=difference;
1620 similarity[CompositeChannels]+=difference;
1622 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse) &&
1623 (reconstruct_image->matte != MagickFalse))
1625 error=reconstruct_phash[OpacityChannel].P[i]-
1626 image_phash[OpacityChannel].P[i];
1627 if (IsNaN(error) != 0)
1629 difference=error*error;
1630 similarity[OpacityChannel]+=difference;
1631 similarity[CompositeChannels]+=difference;
1633 if (((channel & IndexChannel) != 0) &&
1634 (image->colorspace == CMYKColorspace) &&
1635 (reconstruct_image->colorspace == CMYKColorspace))
1637 error=reconstruct_phash[IndexChannel].P[i]-
1638 image_phash[IndexChannel].P[i];
1639 if (IsNaN(error) != 0)
1641 difference=error*error;
1642 similarity[IndexChannel]+=difference;
1643 similarity[CompositeChannels]+=difference;
1649 for (i=0; i < MaximumNumberOfImageMoments; i++)
1654 if ((channel & RedChannel) != 0)
1656 error=reconstruct_phash[RedChannel].Q[i]-image_phash[RedChannel].Q[i];
1657 if (IsNaN(error) != 0)
1659 difference=error*error;
1660 similarity[RedChannel]+=difference;
1661 similarity[CompositeChannels]+=difference;
1663 if ((channel & GreenChannel) != 0)
1665 error=reconstruct_phash[GreenChannel].Q[i]-
1666 image_phash[GreenChannel].Q[i];
1667 if (IsNaN(error) != 0)
1669 difference=error*error;
1670 similarity[GreenChannel]+=difference;
1671 similarity[CompositeChannels]+=difference;
1673 if ((channel & BlueChannel) != 0)
1675 error=reconstruct_phash[BlueChannel].Q[i]-image_phash[BlueChannel].Q[i];
1676 if (IsNaN(error) != 0)
1678 difference=error*error;
1679 similarity[BlueChannel]+=difference;
1680 similarity[CompositeChannels]+=difference;
1682 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse) &&
1683 (reconstruct_image->matte != MagickFalse))
1685 error=reconstruct_phash[OpacityChannel].Q[i]-
1686 image_phash[OpacityChannel].Q[i];
1687 if (IsNaN(error) != 0)
1689 difference=error*error;
1690 similarity[OpacityChannel]+=difference;
1691 similarity[CompositeChannels]+=difference;
1693 if (((channel & IndexChannel) != 0) &&
1694 (image->colorspace == CMYKColorspace) &&
1695 (reconstruct_image->colorspace == CMYKColorspace))
1697 error=reconstruct_phash[IndexChannel].Q[i]-
1698 image_phash[IndexChannel].Q[i];
1699 if (IsNaN(error) != 0)
1701 difference=error*error;
1702 similarity[IndexChannel]+=difference;
1703 similarity[CompositeChannels]+=difference;
1706 similarity[CompositeChannels]/=(double) GetNumberChannels(image,channel);
1716 static MagickBooleanType GetRMSESimilarity(
const Image *image,
1717 const Image *reconstruct_image,
const ChannelType channel,
double *similarity,
1720 #define RMSESquareRoot(x) sqrt((x) < 0.0 ? 0.0 : (x)) 1725 status=GetMSESimilarity(image,reconstruct_image,channel,similarity,
1727 if ((channel & RedChannel) != 0)
1728 similarity[RedChannel]=RMSESquareRoot(similarity[RedChannel]);
1729 if ((channel & GreenChannel) != 0)
1730 similarity[GreenChannel]=RMSESquareRoot(similarity[GreenChannel]);
1731 if ((channel & BlueChannel) != 0)
1732 similarity[BlueChannel]=RMSESquareRoot(similarity[BlueChannel]);
1733 if (((channel & OpacityChannel) != 0) &&
1734 (image->matte != MagickFalse))
1735 similarity[OpacityChannel]=RMSESquareRoot(similarity[OpacityChannel]);
1736 if (((channel & IndexChannel) != 0) &&
1737 (image->colorspace == CMYKColorspace))
1738 similarity[BlackChannel]=RMSESquareRoot(similarity[BlackChannel]);
1739 similarity[CompositeChannels]=RMSESquareRoot(similarity[CompositeChannels]);
1743 MagickExport MagickBooleanType GetImageChannelDistortion(
Image *image,
1744 const Image *reconstruct_image,
const ChannelType channel,
1745 const MetricType metric,
double *distortion,
ExceptionInfo *exception)
1748 *channel_similarity;
1756 assert(image != (
Image *) NULL);
1757 assert(image->signature == MagickCoreSignature);
1758 assert(reconstruct_image != (
const Image *) NULL);
1759 assert(reconstruct_image->signature == MagickCoreSignature);
1760 assert(distortion != (
double *) NULL);
1761 if (IsEventLogging() != MagickFalse)
1762 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
1764 if (metric != PerceptualHashErrorMetric)
1765 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1766 ThrowBinaryException(ImageError,
"ImageMorphologyDiffers",image->filename);
1770 length=CompositeChannels+1UL;
1771 channel_similarity=(
double *) AcquireQuantumMemory(length,
1772 sizeof(*channel_similarity));
1773 if (channel_similarity == (
double *) NULL)
1774 ThrowFatalException(ResourceLimitFatalError,
"MemoryAllocationFailed");
1775 (void) memset(channel_similarity,0,length*
sizeof(*channel_similarity));
1778 case AbsoluteErrorMetric:
1780 status=GetAESimilarity(image,reconstruct_image,channel,
1781 channel_similarity,exception);
1784 case FuzzErrorMetric:
1786 status=GetFUZZSimilarity(image,reconstruct_image,channel,
1787 channel_similarity,exception);
1790 case MeanAbsoluteErrorMetric:
1792 status=GetMAESimilarity(image,reconstruct_image,channel,
1793 channel_similarity,exception);
1796 case MeanErrorPerPixelMetric:
1798 status=GetMEPPSimilarity(image,reconstruct_image,channel,
1799 channel_similarity,exception);
1802 case MeanSquaredErrorMetric:
1804 status=GetMSESimilarity(image,reconstruct_image,channel,
1805 channel_similarity,exception);
1808 case NormalizedCrossCorrelationErrorMetric:
1810 status=GetNCCSimilarity(image,reconstruct_image,channel,
1811 channel_similarity,exception);
1814 case PeakAbsoluteErrorMetric:
1816 status=GetPASimilarity(image,reconstruct_image,channel,
1817 channel_similarity,exception);
1820 case PeakSignalToNoiseRatioMetric:
1822 status=GetPSNRSimilarity(image,reconstruct_image,channel,
1823 channel_similarity,exception);
1826 case PerceptualHashErrorMetric:
1828 status=GetPHASHSimilarity(image,reconstruct_image,channel,
1829 channel_similarity,exception);
1832 case PixelDifferenceCountErrorMetric:
1834 status=GetPDCSimilarity(image,reconstruct_image,channel,
1835 channel_similarity,exception);
1838 case RootMeanSquaredErrorMetric:
1839 case UndefinedErrorMetric:
1842 status=GetRMSESimilarity(image,reconstruct_image,channel,
1843 channel_similarity,exception);
1847 *distortion=channel_similarity[CompositeChannels];
1850 case NormalizedCrossCorrelationErrorMetric:
1852 *distortion=(1.0-(*distortion))/2.0;
1857 if (fabs(*distortion) < MagickEpsilon)
1859 channel_similarity=(
double *) RelinquishMagickMemory(channel_similarity);
1860 (void) FormatImageProperty(image,
"distortion",
"%.*g",GetMagickPrecision(),
1897 MagickExport
double *GetImageChannelDistortions(
Image *image,
1898 const Image *reconstruct_image,
const MetricType metric,
1914 assert(image != (
Image *) NULL);
1915 assert(image->signature == MagickCoreSignature);
1916 assert(reconstruct_image != (
const Image *) NULL);
1917 assert(reconstruct_image->signature == MagickCoreSignature);
1918 if (IsEventLogging() != MagickFalse)
1919 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
1920 if (metric != PerceptualHashErrorMetric)
1921 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1923 (void) ThrowMagickException(&image->exception,GetMagickModule(),
1924 ImageError,
"ImageMorphologyDiffers",
"`%s'",image->filename);
1925 return((
double *) NULL);
1930 length=CompositeChannels+1UL;
1931 similarity=(
double *) AcquireQuantumMemory(length,
1932 sizeof(*similarity));
1933 if (similarity == (
double *) NULL)
1934 ThrowFatalException(ResourceLimitFatalError,
"MemoryAllocationFailed");
1935 (void) memset(similarity,0,length*
sizeof(*similarity));
1939 case AbsoluteErrorMetric:
1941 status=GetAESimilarity(image,reconstruct_image,CompositeChannels,
1942 similarity,exception);
1945 case FuzzErrorMetric:
1947 status=GetFUZZSimilarity(image,reconstruct_image,CompositeChannels,
1948 similarity,exception);
1951 case MeanAbsoluteErrorMetric:
1953 status=GetMAESimilarity(image,reconstruct_image,CompositeChannels,
1954 similarity,exception);
1957 case MeanErrorPerPixelMetric:
1959 status=GetMEPPSimilarity(image,reconstruct_image,CompositeChannels,
1960 similarity,exception);
1963 case MeanSquaredErrorMetric:
1965 status=GetMSESimilarity(image,reconstruct_image,CompositeChannels,
1966 similarity,exception);
1969 case NormalizedCrossCorrelationErrorMetric:
1971 status=GetNCCSimilarity(image,reconstruct_image,CompositeChannels,
1972 similarity,exception);
1975 case PeakAbsoluteErrorMetric:
1977 status=GetPASimilarity(image,reconstruct_image,CompositeChannels,
1978 similarity,exception);
1981 case PeakSignalToNoiseRatioMetric:
1983 status=GetPSNRSimilarity(image,reconstruct_image,CompositeChannels,
1984 similarity,exception);
1987 case PerceptualHashErrorMetric:
1989 status=GetPHASHSimilarity(image,reconstruct_image,CompositeChannels,
1990 similarity,exception);
1993 case PixelDifferenceCountErrorMetric:
1995 status=GetPDCSimilarity(image,reconstruct_image,CompositeChannels,
1996 similarity,exception);
1999 case RootMeanSquaredErrorMetric:
2000 case UndefinedErrorMetric:
2003 status=GetRMSESimilarity(image,reconstruct_image,CompositeChannels,
2004 similarity,exception);
2008 if (status == MagickFalse)
2010 similarity=(
double *) RelinquishMagickMemory(similarity);
2011 return((
double *) NULL);
2013 distortion=similarity;
2016 case NormalizedCrossCorrelationErrorMetric:
2018 for (i=0; i <= (ssize_t) CompositeChannels; i++)
2019 distortion[i]=(1.0-distortion[i])/2.0;
2024 for (i=0; i <= (ssize_t) CompositeChannels; i++)
2025 if (fabs(distortion[i]) < MagickEpsilon)
2077 MagickExport MagickBooleanType IsImagesEqual(
Image *image,
2078 const Image *reconstruct_image)
2095 mean_error_per_pixel;
2104 assert(image != (
Image *) NULL);
2105 assert(image->signature == MagickCoreSignature);
2106 assert(reconstruct_image != (
const Image *) NULL);
2107 assert(reconstruct_image->signature == MagickCoreSignature);
2108 exception=(&image->exception);
2109 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
2110 ThrowBinaryException(ImageError,
"ImageMorphologyDiffers",image->filename);
2113 mean_error_per_pixel=0.0;
2115 SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
2116 image_view=AcquireVirtualCacheView(image,exception);
2117 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
2118 for (y=0; y < (ssize_t) rows; y++)
2121 *magick_restrict indexes,
2122 *magick_restrict reconstruct_indexes;
2131 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
2132 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
2135 indexes=GetCacheViewVirtualIndexQueue(image_view);
2136 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
2137 for (x=0; x < (ssize_t) columns; x++)
2142 distance=fabs((
double) GetPixelRed(p)-(
double) GetPixelRed(q));
2143 mean_error_per_pixel+=distance;
2144 mean_error+=distance*distance;
2145 if (distance > maximum_error)
2146 maximum_error=distance;
2148 distance=fabs((
double) GetPixelGreen(p)-(
double) GetPixelGreen(q));
2149 mean_error_per_pixel+=distance;
2150 mean_error+=distance*distance;
2151 if (distance > maximum_error)
2152 maximum_error=distance;
2154 distance=fabs((
double) GetPixelBlue(p)-(
double) GetPixelBlue(q));
2155 mean_error_per_pixel+=distance;
2156 mean_error+=distance*distance;
2157 if (distance > maximum_error)
2158 maximum_error=distance;
2160 if (image->matte != MagickFalse)
2162 distance=fabs((
double) GetPixelOpacity(p)-(
double)
2163 GetPixelOpacity(q));
2164 mean_error_per_pixel+=distance;
2165 mean_error+=distance*distance;
2166 if (distance > maximum_error)
2167 maximum_error=distance;
2170 if ((image->colorspace == CMYKColorspace) &&
2171 (reconstruct_image->colorspace == CMYKColorspace))
2173 distance=fabs((
double) GetPixelIndex(indexes+x)-(
double)
2174 GetPixelIndex(reconstruct_indexes+x));
2175 mean_error_per_pixel+=distance;
2176 mean_error+=distance*distance;
2177 if (distance > maximum_error)
2178 maximum_error=distance;
2185 reconstruct_view=DestroyCacheView(reconstruct_view);
2186 image_view=DestroyCacheView(image_view);
2187 gamma=MagickSafeReciprocal(area);
2188 image->error.mean_error_per_pixel=gamma*mean_error_per_pixel;
2189 image->error.normalized_mean_error=gamma*QuantumScale*QuantumScale*mean_error;
2190 image->error.normalized_maximum_error=QuantumScale*maximum_error;
2191 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
2230 static double GetSimilarityMetric(
const Image *image,
2231 const Image *reconstruct_image,
const MetricType metric,
2232 const ssize_t x_offset,
const ssize_t y_offset,
ExceptionInfo *exception)
2235 *channel_similarity,
2239 *sans_exception = AcquireExceptionInfo();
2245 status = MagickTrue;
2251 length = CompositeChannels+1UL;
2253 SetGeometry(reconstruct_image,&geometry);
2254 geometry.x=x_offset;
2255 geometry.y=y_offset;
2256 similarity_image=CropImage(image,&geometry,sans_exception);
2257 sans_exception=DestroyExceptionInfo(sans_exception);
2258 if (similarity_image == (
Image *) NULL)
2263 channel_similarity=(
double *) AcquireQuantumMemory(length,
2264 sizeof(*channel_similarity));
2265 if (channel_similarity == (
double *) NULL)
2266 ThrowFatalException(ResourceLimitFatalError,
"MemoryAllocationFailed");
2267 (void) memset(channel_similarity,0,length*
sizeof(*channel_similarity));
2270 case AbsoluteErrorMetric:
2272 status=GetAESimilarity(similarity_image,reconstruct_image,
2273 CompositeChannels,channel_similarity,exception);
2276 case FuzzErrorMetric:
2278 status=GetFUZZSimilarity(similarity_image,reconstruct_image,
2279 CompositeChannels,channel_similarity,exception);
2282 case MeanAbsoluteErrorMetric:
2284 status=GetMAESimilarity(similarity_image,reconstruct_image,
2285 CompositeChannels,channel_similarity,exception);
2288 case MeanErrorPerPixelMetric:
2290 status=GetMEPPSimilarity(similarity_image,reconstruct_image,
2291 CompositeChannels,channel_similarity,exception);
2294 case MeanSquaredErrorMetric:
2296 status=GetMSESimilarity(similarity_image,reconstruct_image,
2297 CompositeChannels,channel_similarity,exception);
2300 case NormalizedCrossCorrelationErrorMetric:
2302 status=GetNCCSimilarity(similarity_image,reconstruct_image,
2303 CompositeChannels,channel_similarity,exception);
2306 case PeakAbsoluteErrorMetric:
2308 status=GetPASimilarity(similarity_image,reconstruct_image,
2309 CompositeChannels,channel_similarity,exception);
2312 case PeakSignalToNoiseRatioMetric:
2314 status=GetPSNRSimilarity(similarity_image,reconstruct_image,
2315 CompositeChannels,channel_similarity,exception);
2318 case PerceptualHashErrorMetric:
2320 status=GetPHASHSimilarity(similarity_image,reconstruct_image,
2321 CompositeChannels,channel_similarity,exception);
2324 case PixelDifferenceCountErrorMetric:
2326 status=GetPDCSimilarity(similarity_image,reconstruct_image,
2327 CompositeChannels,channel_similarity,exception);
2330 case RootMeanSquaredErrorMetric:
2331 case UndefinedErrorMetric:
2334 status=GetRMSESimilarity(similarity_image,reconstruct_image,
2335 CompositeChannels,channel_similarity,exception);
2339 similarity_image=DestroyImage(similarity_image);
2340 similarity=channel_similarity[CompositeChannels];
2341 channel_similarity=(
double *) RelinquishMagickMemory(channel_similarity);
2342 if (status == MagickFalse)
2347 MagickExport
Image *SimilarityImage(
Image *image,
const Image *reference,
2353 similarity_image=SimilarityMetricImage(image,reference,
2354 RootMeanSquaredErrorMetric,offset,similarity_metric,exception);
2355 return(similarity_image);
2358 MagickExport
Image *SimilarityMetricImage(
Image *image,
const Image *reconstruct,
2359 const MetricType metric,
RectangleInfo *offset,
double *similarity_metric,
2362 #define SimilarityImageTag "Similarity/Image" 2381 similarity_threshold;
2384 *similarity_image = (
Image *) NULL;
2393 similarity_info = { 0 };
2398 assert(image != (
const Image *) NULL);
2399 assert(image->signature == MagickCoreSignature);
2401 assert(exception->signature == MagickCoreSignature);
2403 if (IsEventLogging() != MagickFalse)
2404 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
2405 SetGeometry(reconstruct,offset);
2406 *similarity_metric=0.0;
2409 if (ValidateImageMorphology(image,reconstruct) == MagickFalse)
2410 ThrowImageException(ImageError,
"ImageMorphologyDiffers");
2411 if ((image->columns < reconstruct->columns) ||
2412 (image->rows < reconstruct->rows))
2414 (void) ThrowMagickException(&image->exception,GetMagickModule(),
2415 OptionWarning,
"GeometryDoesNotContainImage",
"`%s'",image->filename);
2416 return((
Image *) NULL);
2418 similarity_image=CloneImage(image,image->columns-reconstruct->columns+1,
2419 image->rows-reconstruct->rows+1,MagickTrue,exception);
2420 if (similarity_image == (
Image *) NULL)
2421 return((
Image *) NULL);
2422 similarity_image->depth=32;
2423 similarity_image->colorspace=GRAYColorspace;
2424 similarity_image->matte=MagickFalse;
2425 status=SetImageStorageClass(similarity_image,DirectClass);
2426 if (status == MagickFalse)
2428 InheritException(exception,&similarity_image->exception);
2429 return(DestroyImage(similarity_image));
2434 similarity_threshold=DefaultSimilarityThreshold;
2435 artifact=GetImageArtifact(image,
"compare:similarity-threshold");
2436 if (artifact != (
const char *) NULL)
2437 similarity_threshold=StringToDouble(artifact,(
char **) NULL);
2439 similarity_info.similarity=GetSimilarityMetric(image,reconstruct,metric,
2440 similarity_info.x,similarity_info.y,exception);
2442 similarity_view=AcquireVirtualCacheView(similarity_image,exception);
2443 #if defined(MAGICKCORE_OPENMP_SUPPORT) 2444 #pragma omp parallel for schedule(static) shared(status,similarity_info) \ 2445 magick_number_threads(image,reconstruct,similarity_image->rows << 2,1) 2447 for (y=0; y < (ssize_t) similarity_image->rows; y++)
2453 threshold_trigger = MagickFalse;
2459 channel_info = similarity_info;
2464 if (status == MagickFalse)
2466 if (threshold_trigger != MagickFalse)
2468 q=QueueCacheViewAuthenticPixels(similarity_view,0,y,
2469 similarity_image->columns,1,exception);
2475 for (x=0; x < (ssize_t) similarity_image->columns; x++)
2478 update = MagickFalse;
2480 similarity=GetSimilarityMetric(image,reconstruct,metric,x,y,exception);
2483 case NormalizedCrossCorrelationErrorMetric:
2484 case PeakSignalToNoiseRatioMetric:
2486 if (similarity > channel_info.similarity)
2492 if (similarity < channel_info.similarity)
2497 if (update != MagickFalse)
2499 channel_info.similarity=similarity;
2505 case NormalizedCrossCorrelationErrorMetric:
2506 case PeakSignalToNoiseRatioMetric:
2508 SetPixelRed(q,ClampToQuantum((
double) QuantumRange*similarity));
2513 SetPixelRed(q,ClampToQuantum((
double) QuantumRange*(1.0-similarity)));
2517 SetPixelGreen(q,GetPixelRed(q));
2518 SetPixelBlue(q,GetPixelRed(q));
2521 #if defined(MAGICKCORE_OPENMP_SUPPORT) 2522 #pragma omp critical (MagickCore_SimilarityMetricImage) 2526 case NormalizedCrossCorrelationErrorMetric:
2527 case PeakSignalToNoiseRatioMetric:
2529 if (similarity_threshold != DefaultSimilarityThreshold)
2530 if (channel_info.similarity >= similarity_threshold)
2531 threshold_trigger=MagickTrue;
2532 if (channel_info.similarity >= similarity_info.similarity)
2533 similarity_info=channel_info;
2538 if (similarity_threshold != DefaultSimilarityThreshold)
2539 if (channel_info.similarity < similarity_threshold)
2540 threshold_trigger=MagickTrue;
2541 if (channel_info.similarity < similarity_info.similarity)
2542 similarity_info=channel_info;
2546 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
2548 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2554 proceed=SetImageProgress(image,SimilarityImageTag,progress,image->rows);
2555 if (proceed == MagickFalse)
2559 similarity_view=DestroyCacheView(similarity_view);
2560 if (status == MagickFalse)
2561 similarity_image=DestroyImage(similarity_image);
2562 *similarity_metric=similarity_info.similarity;
2563 if (fabs(*similarity_metric) < MagickEpsilon)
2564 *similarity_metric=0.0;
2565 offset->x=similarity_info.x;
2566 offset->y=similarity_info.y;
2567 (void) FormatImageProperty((
Image *) image,
"similarity",
"%.*g",
2568 GetMagickPrecision(),*similarity_metric);
2569 (void) FormatImageProperty((
Image *) image,
"similarity.offset.x",
"%.*g",
2570 GetMagickPrecision(),(double) offset->x);
2571 (void) FormatImageProperty((
Image *) image,
"similarity.offset.y",
"%.*g",
2572 GetMagickPrecision(),(double) offset->y);
2573 return(similarity_image);