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,
378 fuzz=GetFuzzyColorDistance(image,reconstruct_image);
379 SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
380 image_view=AcquireVirtualCacheView(image,exception);
381 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
382 #if defined(MAGICKCORE_OPENMP_SUPPORT) 383 #pragma omp parallel for schedule(static) shared(similarity,status) \ 384 magick_number_threads(image,image,rows,1) 386 for (y=0; y < (ssize_t) rows; y++)
389 *magick_restrict indexes,
390 *magick_restrict reconstruct_indexes;
397 channel_similarity[CompositeChannels+1] = { 0.0 };
403 if (status == MagickFalse)
405 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
406 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
412 indexes=GetCacheViewVirtualIndexQueue(image_view);
413 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
414 (void) memset(channel_similarity,0,
sizeof(channel_similarity));
415 for (x=0; x < (ssize_t) columns; x++)
422 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
423 ((double) QuantumRange-(double) OpaqueOpacity));
424 Da=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(q) :
425 ((double) QuantumRange-(double) OpaqueOpacity));
426 if ((channel & RedChannel) != 0)
428 error=Sa*(double) GetPixelRed(p)-Da*(double)
430 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
432 channel_similarity[RedChannel]+=fabs(error);
433 channel_similarity[CompositeChannels]+=fabs(error);
436 if ((channel & GreenChannel) != 0)
438 error=Sa*(double) GetPixelGreen(p)-Da*(double)
440 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
442 channel_similarity[GreenChannel]+=fabs(error);
443 channel_similarity[CompositeChannels]+=fabs(error);
446 if ((channel & BlueChannel) != 0)
448 error=Sa*(double) GetPixelBlue(p)-Da*(double)
450 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
452 channel_similarity[BlueChannel]+=fabs(error);
453 channel_similarity[CompositeChannels]+=fabs(error);
456 if (((channel & OpacityChannel) != 0) &&
457 (image->matte != MagickFalse))
459 error=(double) GetPixelOpacity(p)-(double) GetPixelOpacity(q);
460 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
462 channel_similarity[OpacityChannel]+=fabs(error);
463 channel_similarity[CompositeChannels]+=fabs(error);
466 if (((channel & IndexChannel) != 0) &&
467 (image->colorspace == CMYKColorspace))
469 error=Sa*(double) indexes[x]-Da*(
double) reconstruct_indexes[x];
470 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
472 channel_similarity[IndexChannel]+=fabs(error);
473 channel_similarity[CompositeChannels]+=fabs(error);
479 #if defined(MAGICKCORE_OPENMP_SUPPORT) 480 #pragma omp critical (MagickCore_GetAESimilarity) 482 for (i=0; i <= (ssize_t) CompositeChannels; i++)
483 similarity[i]+=channel_similarity[i];
485 similarity[CompositeChannels]/=(double) GetNumberChannels(image,channel);
486 reconstruct_view=DestroyCacheView(reconstruct_view);
487 image_view=DestroyCacheView(image_view);
491 static MagickBooleanType GetFUZZSimilarity(
const Image *image,
492 const Image *reconstruct_image,
const ChannelType channel,
514 fuzz=GetFuzzyColorDistance(image,reconstruct_image);
515 SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
516 image_view=AcquireVirtualCacheView(image,exception);
517 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
518 #if defined(MAGICKCORE_OPENMP_SUPPORT) 519 #pragma omp parallel for schedule(static) shared(status) \ 520 magick_number_threads(image,image,rows,1) 522 for (y=0; y < (ssize_t) rows; y++)
526 channel_similarity[CompositeChannels+1] = { 0.0 };
529 *magick_restrict indexes,
530 *magick_restrict reconstruct_indexes;
540 if (status == MagickFalse)
542 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
543 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
549 indexes=GetCacheViewVirtualIndexQueue(image_view);
550 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
551 for (x=0; x < (ssize_t) columns; x++)
558 Sa=QuantumScale*(image->matte != MagickFalse ? (double)
559 GetPixelAlpha(p) : ((double) QuantumRange-(double) OpaqueOpacity));
560 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
561 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
563 if ((channel & RedChannel) != 0)
565 error=QuantumScale*(Sa*GetPixelRed(p)-Da*GetPixelRed(q));
566 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
568 channel_similarity[RedChannel]+=error*error;
569 channel_similarity[CompositeChannels]+=error*error;
573 if ((channel & GreenChannel) != 0)
575 error=QuantumScale*(Sa*GetPixelGreen(p)-Da*GetPixelGreen(q));
576 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
578 channel_similarity[GreenChannel]+=error*error;
579 channel_similarity[CompositeChannels]+=error*error;
583 if ((channel & BlueChannel) != 0)
585 error=QuantumScale*(Sa*GetPixelBlue(p)-Da*GetPixelBlue(q));
586 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
588 channel_similarity[BlueChannel]+=error*error;
589 channel_similarity[CompositeChannels]+=error*error;
593 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
595 error=QuantumScale*((double) GetPixelOpacity(p)-GetPixelOpacity(q));
596 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
598 channel_similarity[OpacityChannel]+=error*error;
599 channel_similarity[CompositeChannels]+=error*error;
603 if (((channel & IndexChannel) != 0) &&
604 (image->colorspace == CMYKColorspace))
606 error=QuantumScale*(Sa*GetPixelIndex(indexes+x)-Da*
607 GetPixelIndex(reconstruct_indexes+x));
608 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
610 channel_similarity[BlackChannel]+=error*error;
611 channel_similarity[CompositeChannels]+=error*error;
618 #if defined(MAGICKCORE_OPENMP_SUPPORT) 619 #pragma omp critical (MagickCore_GetMeanAbsoluteError) 623 for (i=0; i <= (ssize_t) CompositeChannels; i++)
624 similarity[i]+=channel_similarity[i];
627 reconstruct_view=DestroyCacheView(reconstruct_view);
628 image_view=DestroyCacheView(image_view);
629 area=MagickSafeReciprocal(area);
630 for (i=0; i <= (ssize_t) CompositeChannels; i++)
635 static MagickBooleanType GetPDCSimilarity(
const Image *image,
636 const Image *reconstruct_image,
const ChannelType channel,
double *similarity,
661 fuzz=GetFuzzyColorDistance(image,reconstruct_image);
662 SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
663 image_view=AcquireVirtualCacheView(image,exception);
664 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
665 #if defined(MAGICKCORE_OPENMP_SUPPORT) 666 #pragma omp parallel for schedule(static) shared(similarity,status) \ 667 magick_number_threads(image,image,rows,1) 669 for (y=0; y < (ssize_t) rows; y++)
672 *magick_restrict indexes,
673 *magick_restrict reconstruct_indexes;
680 channel_similarity[CompositeChannels+1] = { 0.0 };
686 if (status == MagickFalse)
688 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
689 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
695 indexes=GetCacheViewVirtualIndexQueue(image_view);
696 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
697 (void) memset(channel_similarity,0,
sizeof(channel_similarity));
698 for (x=0; x < (ssize_t) columns; x++)
708 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
709 ((double) QuantumRange-(double) OpaqueOpacity));
710 Da=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(q) :
711 ((double) QuantumRange-(double) OpaqueOpacity));
712 if ((channel & RedChannel) != 0)
714 error=Sa*(double) GetPixelRed(p)-Da*(double)
716 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
718 channel_similarity[RedChannel]++;
722 if ((channel & GreenChannel) != 0)
724 error=Sa*(double) GetPixelGreen(p)-Da*(double)
726 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
728 channel_similarity[GreenChannel]++;
732 if ((channel & BlueChannel) != 0)
734 error=Sa*(double) GetPixelBlue(p)-Da*(double)
736 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
738 channel_similarity[BlueChannel]++;
742 if (((channel & OpacityChannel) != 0) &&
743 (image->matte != MagickFalse))
745 error=(double) GetPixelOpacity(p)-(double) GetPixelOpacity(q);
746 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
748 channel_similarity[OpacityChannel]++;
752 if (((channel & IndexChannel) != 0) &&
753 (image->colorspace == CMYKColorspace))
755 error=Sa*(double) indexes[x]-Da*(
double) reconstruct_indexes[x];
756 if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
758 channel_similarity[IndexChannel]++;
763 channel_similarity[CompositeChannels]++;
767 #if defined(MAGICKCORE_OPENMP_SUPPORT) 768 #pragma omp critical (MagickCore_GetAESimilarity) 770 for (i=0; i <= (ssize_t) CompositeChannels; i++)
771 similarity[i]+=channel_similarity[i];
773 reconstruct_view=DestroyCacheView(reconstruct_view);
774 image_view=DestroyCacheView(image_view);
775 area=MagickSafeReciprocal((
double) columns*rows);
776 for (j=0; j <= CompositeChannels; j++)
781 static MagickBooleanType GetMAESimilarity(
const Image *image,
782 const Image *reconstruct_image,
const ChannelType channel,
801 SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
802 image_view=AcquireVirtualCacheView(image,exception);
803 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
804 #if defined(MAGICKCORE_OPENMP_SUPPORT) 805 #pragma omp parallel for schedule(static) shared(status) \ 806 magick_number_threads(image,image,rows,1) 808 for (y=0; y < (ssize_t) rows; y++)
811 channel_similarity[CompositeChannels+1];
814 *magick_restrict indexes,
815 *magick_restrict reconstruct_indexes;
825 if (status == MagickFalse)
827 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
828 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
834 indexes=GetCacheViewVirtualIndexQueue(image_view);
835 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
836 (void) memset(channel_similarity,0,
sizeof(channel_similarity));
837 for (x=0; x < (ssize_t) columns; x++)
844 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
845 ((double) QuantumRange-(double) OpaqueOpacity));
846 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
847 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
849 if ((channel & RedChannel) != 0)
851 distance=QuantumScale*fabs(Sa*(
double) GetPixelRed(p)-Da*
852 (
double) GetPixelRed(q));
853 channel_similarity[RedChannel]+=distance;
854 channel_similarity[CompositeChannels]+=distance;
856 if ((channel & GreenChannel) != 0)
858 distance=QuantumScale*fabs(Sa*(
double) GetPixelGreen(p)-Da*
859 (
double) GetPixelGreen(q));
860 channel_similarity[GreenChannel]+=distance;
861 channel_similarity[CompositeChannels]+=distance;
863 if ((channel & BlueChannel) != 0)
865 distance=QuantumScale*fabs(Sa*(
double) GetPixelBlue(p)-Da*
866 (
double) GetPixelBlue(q));
867 channel_similarity[BlueChannel]+=distance;
868 channel_similarity[CompositeChannels]+=distance;
870 if (((channel & OpacityChannel) != 0) &&
871 (image->matte != MagickFalse))
873 distance=QuantumScale*fabs((
double) GetPixelOpacity(p)-(
double)
875 channel_similarity[OpacityChannel]+=distance;
876 channel_similarity[CompositeChannels]+=distance;
878 if (((channel & IndexChannel) != 0) &&
879 (image->colorspace == CMYKColorspace))
881 distance=QuantumScale*fabs(Sa*(
double) GetPixelIndex(indexes+x)-Da*
882 (
double) GetPixelIndex(reconstruct_indexes+x));
883 channel_similarity[BlackChannel]+=distance;
884 channel_similarity[CompositeChannels]+=distance;
889 #if defined(MAGICKCORE_OPENMP_SUPPORT) 890 #pragma omp critical (MagickCore_GetMeanAbsoluteError) 892 for (i=0; i <= (ssize_t) CompositeChannels; i++)
893 similarity[i]+=channel_similarity[i];
895 reconstruct_view=DestroyCacheView(reconstruct_view);
896 image_view=DestroyCacheView(image_view);
897 for (i=0; i <= (ssize_t) CompositeChannels; i++)
898 similarity[i]/=((
double) columns*rows);
899 similarity[CompositeChannels]/=(double) GetNumberChannels(image,channel);
903 static MagickBooleanType GetMEPPSimilarity(
Image *image,
904 const Image *reconstruct_image,
const ChannelType channel,
double *similarity,
912 maximum_error = -MagickMaximumValue,
927 SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
928 image_view=AcquireVirtualCacheView(image,exception);
929 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
930 #if defined(MAGICKCORE_OPENMP_SUPPORT) 931 #pragma omp parallel for schedule(static) shared(maximum_error,status) \ 932 magick_number_threads(image,image,rows,1) 934 for (y=0; y < (ssize_t) rows; y++)
937 channel_similarity[CompositeChannels+1] = { 0.0 },
938 local_maximum = maximum_error,
939 local_mean_error = 0.0;
942 *magick_restrict indexes,
943 *magick_restrict reconstruct_indexes;
953 if (status == MagickFalse)
955 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
956 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
962 indexes=GetCacheViewVirtualIndexQueue(image_view);
963 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
964 (void) memset(channel_similarity,0,
sizeof(channel_similarity));
965 for (x=0; x < (ssize_t) columns; x++)
972 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
973 ((double) QuantumRange-(double) OpaqueOpacity));
974 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
975 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
977 if ((channel & RedChannel) != 0)
979 distance=QuantumScale*fabs(Sa*(
double) GetPixelRed(p)-Da*
980 (
double) GetPixelRed(q));
981 channel_similarity[RedChannel]+=distance;
982 channel_similarity[CompositeChannels]+=distance;
983 local_mean_error+=distance*distance;
984 if (distance > local_maximum)
985 local_maximum=distance;
987 if ((channel & GreenChannel) != 0)
989 distance=QuantumScale*fabs(Sa*(
double) GetPixelGreen(p)-Da*
990 (
double) GetPixelGreen(q));
991 channel_similarity[GreenChannel]+=distance;
992 channel_similarity[CompositeChannels]+=distance;
993 local_mean_error+=distance*distance;
994 if (distance > local_maximum)
995 local_maximum=distance;
997 if ((channel & BlueChannel) != 0)
999 distance=QuantumScale*fabs(Sa*(
double) GetPixelBlue(p)-Da*
1000 (
double) GetPixelBlue(q));
1001 channel_similarity[BlueChannel]+=distance;
1002 channel_similarity[CompositeChannels]+=distance;
1003 local_mean_error+=distance*distance;
1004 if (distance > local_maximum)
1005 local_maximum=distance;
1007 if (((channel & OpacityChannel) != 0) &&
1008 (image->matte != MagickFalse))
1010 distance=QuantumScale*fabs((
double) GetPixelOpacity(p)-(
double)
1011 GetPixelOpacity(q));
1012 channel_similarity[OpacityChannel]+=distance;
1013 channel_similarity[CompositeChannels]+=distance;
1014 local_mean_error+=distance*distance;
1015 if (distance > local_maximum)
1016 local_maximum=distance;
1018 if (((channel & IndexChannel) != 0) &&
1019 (image->colorspace == CMYKColorspace))
1021 distance=QuantumScale*fabs(Sa*(
double) GetPixelIndex(indexes+x)-Da*
1022 (
double) GetPixelIndex(reconstruct_indexes+x));
1023 channel_similarity[BlackChannel]+=distance;
1024 channel_similarity[CompositeChannels]+=distance;
1025 local_mean_error+=distance*distance;
1026 if (distance > local_maximum)
1027 local_maximum=distance;
1032 #if defined(MAGICKCORE_OPENMP_SUPPORT) 1033 #pragma omp critical (MagickCore_GetMeanAbsoluteError) 1036 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1037 similarity[i]+=channel_similarity[i];
1038 mean_error+=local_mean_error;
1039 if (local_maximum > maximum_error)
1040 maximum_error=local_maximum;
1043 reconstruct_view=DestroyCacheView(reconstruct_view);
1044 image_view=DestroyCacheView(image_view);
1045 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1046 similarity[i]/=((
double) columns*rows);
1047 similarity[CompositeChannels]/=(double) GetNumberChannels(image,channel);
1048 image->error.mean_error_per_pixel=QuantumRange*similarity[CompositeChannels];
1049 image->error.normalized_mean_error=mean_error/((double) columns*rows);
1050 image->error.normalized_maximum_error=maximum_error;
1054 static MagickBooleanType GetMSESimilarity(
const Image *image,
1055 const Image *reconstruct_image,
const ChannelType channel,
1077 SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
1078 image_view=AcquireVirtualCacheView(image,exception);
1079 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1080 #if defined(MAGICKCORE_OPENMP_SUPPORT) 1081 #pragma omp parallel for schedule(static) shared(similarity,status) \ 1082 magick_number_threads(image,image,rows,1) 1084 for (y=0; y < (ssize_t) rows; y++)
1087 channel_similarity[CompositeChannels+1] = { 0.0 };
1090 *magick_restrict indexes,
1091 *magick_restrict reconstruct_indexes;
1101 if (status == MagickFalse)
1103 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1104 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1110 indexes=GetCacheViewVirtualIndexQueue(image_view);
1111 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1112 for (x=0; x < (ssize_t) columns; x++)
1119 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
1120 ((double) QuantumRange-(double) OpaqueOpacity));
1121 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1122 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
1124 if ((channel & RedChannel) != 0)
1126 distance=QuantumScale*(Sa*(double) GetPixelRed(p)-Da*(double)
1128 channel_similarity[RedChannel]+=distance*distance;
1129 channel_similarity[CompositeChannels]+=distance*distance;
1131 if ((channel & GreenChannel) != 0)
1133 distance=QuantumScale*(Sa*(double) GetPixelGreen(p)-Da*(double)
1135 channel_similarity[GreenChannel]+=distance*distance;
1136 channel_similarity[CompositeChannels]+=distance*distance;
1138 if ((channel & BlueChannel) != 0)
1140 distance=QuantumScale*(Sa*(double) GetPixelBlue(p)-Da*(double)
1142 channel_similarity[BlueChannel]+=distance*distance;
1143 channel_similarity[CompositeChannels]+=distance*distance;
1145 if (((channel & OpacityChannel) != 0) &&
1146 (image->matte != MagickFalse))
1148 distance=QuantumScale*((double) GetPixelOpacity(p)-(double)
1149 GetPixelOpacity(q));
1150 channel_similarity[OpacityChannel]+=distance*distance;
1151 channel_similarity[CompositeChannels]+=distance*distance;
1153 if (((channel & IndexChannel) != 0) &&
1154 (image->colorspace == CMYKColorspace) &&
1155 (reconstruct_image->colorspace == CMYKColorspace))
1157 distance=QuantumScale*(Sa*(double) GetPixelIndex(indexes+x)-Da*
1158 (double) GetPixelIndex(reconstruct_indexes+x));
1159 channel_similarity[BlackChannel]+=distance*distance;
1160 channel_similarity[CompositeChannels]+=distance*distance;
1165 #if defined(MAGICKCORE_OPENMP_SUPPORT) 1166 #pragma omp critical (MagickCore_GetMeanSquaredError) 1168 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1169 similarity[i]+=channel_similarity[i];
1171 reconstruct_view=DestroyCacheView(reconstruct_view);
1172 image_view=DestroyCacheView(image_view);
1173 area=MagickSafeReciprocal((
double) columns*rows);
1174 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1175 similarity[i]*=area;
1176 similarity[CompositeChannels]/=(double) GetNumberChannels(image,channel);
1180 static MagickBooleanType GetNCCSimilarity(
const Image *image,
1181 const Image *reconstruct_image,
const ChannelType channel,
double *similarity,
1184 #define SimilarityImageTag "Similarity/Image" 1192 *reconstruct_statistics;
1195 alpha_variance[CompositeChannels+1] = { 0.0 },
1196 beta_variance[CompositeChannels+1] = { 0.0 };
1215 image_statistics=GetImageChannelStatistics(image,exception);
1216 reconstruct_statistics=GetImageChannelStatistics(reconstruct_image,exception);
1225 reconstruct_statistics);
1226 return(MagickFalse);
1228 (void) memset(similarity,0,(CompositeChannels+1)*
sizeof(*similarity));
1231 SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
1232 image_view=AcquireVirtualCacheView(image,exception);
1233 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1234 #if defined(MAGICKCORE_OPENMP_SUPPORT) 1235 #pragma omp parallel for schedule(static) shared(status) \ 1236 magick_number_threads(image,image,rows,1) 1238 for (y=0; y < (ssize_t) rows; y++)
1241 *magick_restrict indexes,
1242 *magick_restrict reconstruct_indexes;
1249 channel_alpha_variance[CompositeChannels+1] = { 0.0 },
1250 channel_beta_variance[CompositeChannels+1] = { 0.0 },
1251 channel_similarity[CompositeChannels+1] = { 0.0 };
1256 if (status == MagickFalse)
1258 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1259 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1265 indexes=GetCacheViewVirtualIndexQueue(image_view);
1266 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1267 for (x=0; x < (ssize_t) columns; x++)
1275 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
1276 (double) QuantumRange);
1277 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1278 (double) GetPixelAlpha(q) : (double) QuantumRange);
1279 if ((channel & RedChannel) != 0)
1281 alpha=QuantumScale*(Sa*(double) GetPixelRed(p)-
1282 image_statistics[RedChannel].mean);
1283 beta=QuantumScale*(Da*(double) GetPixelRed(q)-
1284 reconstruct_statistics[RedChannel].mean);
1285 channel_similarity[RedChannel]+=alpha*beta;
1286 channel_similarity[CompositeChannels]+=alpha*beta;
1287 channel_alpha_variance[RedChannel]+=alpha*alpha;
1288 channel_alpha_variance[CompositeChannels]+=alpha*alpha;
1289 channel_beta_variance[RedChannel]+=beta*beta;
1290 channel_beta_variance[CompositeChannels]+=beta*beta;
1292 if ((channel & GreenChannel) != 0)
1294 alpha=QuantumScale*(Sa*(double) GetPixelGreen(p)-
1295 image_statistics[GreenChannel].mean);
1296 beta=QuantumScale*(Da*(double) GetPixelGreen(q)-
1297 reconstruct_statistics[GreenChannel].mean);
1298 channel_similarity[GreenChannel]+=alpha*beta;
1299 channel_similarity[CompositeChannels]+=alpha*beta;
1300 channel_alpha_variance[GreenChannel]+=alpha*alpha;
1301 channel_alpha_variance[CompositeChannels]+=alpha*alpha;
1302 channel_beta_variance[GreenChannel]+=beta*beta;
1303 channel_beta_variance[CompositeChannels]+=beta*beta;
1305 if ((channel & BlueChannel) != 0)
1307 alpha=QuantumScale*(Sa*(double) GetPixelBlue(p)-
1308 image_statistics[BlueChannel].mean);
1309 beta=QuantumScale*(Da*(double) GetPixelBlue(q)-
1310 reconstruct_statistics[BlueChannel].mean);
1311 channel_similarity[BlueChannel]+=alpha*beta;
1312 channel_alpha_variance[BlueChannel]+=alpha*alpha;
1313 channel_beta_variance[BlueChannel]+=beta*beta;
1315 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
1317 alpha=QuantumScale*((double) GetPixelAlpha(p)-
1318 image_statistics[AlphaChannel].mean);
1319 beta=QuantumScale*((double) GetPixelAlpha(q)-
1320 reconstruct_statistics[AlphaChannel].mean);
1321 channel_similarity[OpacityChannel]+=alpha*beta;
1322 channel_similarity[CompositeChannels]+=alpha*beta;
1323 channel_alpha_variance[OpacityChannel]+=alpha*alpha;
1324 channel_alpha_variance[CompositeChannels]+=alpha*alpha;
1325 channel_beta_variance[OpacityChannel]+=beta*beta;
1326 channel_beta_variance[CompositeChannels]+=beta*beta;
1328 if (((channel & IndexChannel) != 0) &&
1329 (image->colorspace == CMYKColorspace) &&
1330 (reconstruct_image->colorspace == CMYKColorspace))
1332 alpha=QuantumScale*(Sa*(double) GetPixelIndex(indexes+x)-
1333 image_statistics[BlackChannel].mean);
1334 beta=QuantumScale*(Da*(double) GetPixelIndex(reconstruct_indexes+
1335 x)-reconstruct_statistics[BlackChannel].mean);
1336 channel_similarity[BlackChannel]+=alpha*beta;
1337 channel_similarity[CompositeChannels]+=alpha*beta;
1338 channel_alpha_variance[BlackChannel]+=alpha*alpha;
1339 channel_alpha_variance[CompositeChannels]+=alpha*alpha;
1340 channel_beta_variance[BlackChannel]+=beta*beta;
1341 channel_beta_variance[CompositeChannels]+=beta*beta;
1346 #if defined(MAGICKCORE_OPENMP_SUPPORT) 1347 #pragma omp critical (GetNCCSimilarity) 1353 for (j=0; j <= (ssize_t) CompositeChannels; j++)
1355 similarity[j]+=channel_similarity[j];
1356 alpha_variance[j]+=channel_alpha_variance[j];
1357 beta_variance[j]+=channel_beta_variance[j];
1360 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1365 #if defined(MAGICKCORE_OPENMP_SUPPORT) 1369 proceed=SetImageProgress(image,SimilarityImageTag,progress,rows);
1370 if (proceed == MagickFalse)
1374 reconstruct_view=DestroyCacheView(reconstruct_view);
1375 image_view=DestroyCacheView(image_view);
1379 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1380 similarity[i]*=MagickSafeReciprocal(sqrt(alpha_variance[i])*
1381 sqrt(beta_variance[i]));
1386 reconstruct_statistics);
1392 static MagickBooleanType GetPASimilarity(
const Image *image,
1393 const Image *reconstruct_image,
const ChannelType channel,
1411 (void) memset(similarity,0,(CompositeChannels+1)*
sizeof(*similarity));
1412 SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
1413 image_view=AcquireVirtualCacheView(image,exception);
1414 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1415 #if defined(MAGICKCORE_OPENMP_SUPPORT) 1416 #pragma omp parallel for schedule(static) shared(status) \ 1417 magick_number_threads(image,image,rows,1) 1419 for (y=0; y < (ssize_t) rows; y++)
1422 channel_similarity[CompositeChannels+1];
1425 *magick_restrict indexes,
1426 *magick_restrict reconstruct_indexes;
1436 if (status == MagickFalse)
1438 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1439 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1445 indexes=GetCacheViewVirtualIndexQueue(image_view);
1446 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1447 (void) memset(channel_similarity,0,(CompositeChannels+1)*
1448 sizeof(*channel_similarity));
1449 for (x=0; x < (ssize_t) columns; x++)
1456 Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
1457 ((double) QuantumRange-(double) OpaqueOpacity));
1458 Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1459 (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
1461 if ((channel & RedChannel) != 0)
1463 distance=QuantumScale*fabs(Sa*(
double) GetPixelRed(p)-Da*
1464 (
double) GetPixelRed(q));
1465 if (distance > channel_similarity[RedChannel])
1466 channel_similarity[RedChannel]=distance;
1467 if (distance > channel_similarity[CompositeChannels])
1468 channel_similarity[CompositeChannels]=distance;
1470 if ((channel & GreenChannel) != 0)
1472 distance=QuantumScale*fabs(Sa*(
double) GetPixelGreen(p)-Da*
1473 (
double) GetPixelGreen(q));
1474 if (distance > channel_similarity[GreenChannel])
1475 channel_similarity[GreenChannel]=distance;
1476 if (distance > channel_similarity[CompositeChannels])
1477 channel_similarity[CompositeChannels]=distance;
1479 if ((channel & BlueChannel) != 0)
1481 distance=QuantumScale*fabs(Sa*(
double) GetPixelBlue(p)-Da*
1482 (
double) GetPixelBlue(q));
1483 if (distance > channel_similarity[BlueChannel])
1484 channel_similarity[BlueChannel]=distance;
1485 if (distance > channel_similarity[CompositeChannels])
1486 channel_similarity[CompositeChannels]=distance;
1488 if (((channel & OpacityChannel) != 0) &&
1489 (image->matte != MagickFalse))
1491 distance=QuantumScale*fabs((
double) GetPixelOpacity(p)-(
double)
1492 GetPixelOpacity(q));
1493 if (distance > channel_similarity[OpacityChannel])
1494 channel_similarity[OpacityChannel]=distance;
1495 if (distance > channel_similarity[CompositeChannels])
1496 channel_similarity[CompositeChannels]=distance;
1498 if (((channel & IndexChannel) != 0) &&
1499 (image->colorspace == CMYKColorspace) &&
1500 (reconstruct_image->colorspace == CMYKColorspace))
1502 distance=QuantumScale*fabs(Sa*(
double) GetPixelIndex(indexes+x)-Da*
1503 (
double) GetPixelIndex(reconstruct_indexes+x));
1504 if (distance > channel_similarity[BlackChannel])
1505 channel_similarity[BlackChannel]=distance;
1506 if (distance > channel_similarity[CompositeChannels])
1507 channel_similarity[CompositeChannels]=distance;
1512 #if defined(MAGICKCORE_OPENMP_SUPPORT) 1513 #pragma omp critical (MagickCore_GetPeakAbsoluteError) 1515 for (i=0; i <= (ssize_t) CompositeChannels; i++)
1516 if (channel_similarity[i] > similarity[i])
1517 similarity[i]=channel_similarity[i];
1519 reconstruct_view=DestroyCacheView(reconstruct_view);
1520 image_view=DestroyCacheView(image_view);
1524 static MagickBooleanType GetPSNRSimilarity(
const Image *image,
1525 const Image *reconstruct_image,
const ChannelType channel,
1531 status=GetMSESimilarity(image,reconstruct_image,channel,similarity,
1533 if ((channel & RedChannel) != 0)
1534 similarity[RedChannel]=10.0*MagickSafeLog10(MagickSafeReciprocal(
1535 similarity[RedChannel]))/MagickSafePSNRRecipicol(10.0);
1536 if ((channel & GreenChannel) != 0)
1537 similarity[GreenChannel]=10.0*MagickSafeLog10(MagickSafeReciprocal(
1538 similarity[GreenChannel]))/MagickSafePSNRRecipicol(10.0);
1539 if ((channel & BlueChannel) != 0)
1540 similarity[BlueChannel]=10.0*MagickSafeLog10(MagickSafeReciprocal(
1541 similarity[BlueChannel]))/MagickSafePSNRRecipicol(10.0);
1542 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
1543 similarity[OpacityChannel]=10.0*MagickSafeLog10(MagickSafeReciprocal(
1544 similarity[OpacityChannel]))/MagickSafePSNRRecipicol(10.0);
1545 if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
1546 similarity[BlackChannel]=10.0*MagickSafeLog10(MagickSafeReciprocal(
1547 similarity[BlackChannel]))/MagickSafePSNRRecipicol(10.0);
1548 similarity[CompositeChannels]=10.0*MagickSafeLog10(MagickSafeReciprocal(
1549 similarity[CompositeChannels]))/MagickSafePSNRRecipicol(10.0);
1553 static MagickBooleanType GetPHASHSimilarity(
const Image *image,
1554 const Image *reconstruct_image,
const ChannelType channel,
double *similarity,
1571 image_phash=GetImageChannelPerceptualHash(image,exception);
1573 return(MagickFalse);
1574 reconstruct_phash=GetImageChannelPerceptualHash(reconstruct_image,exception);
1578 return(MagickFalse);
1580 for (i=0; i < MaximumNumberOfImageMoments; i++)
1585 if ((channel & RedChannel) != 0)
1587 error=reconstruct_phash[RedChannel].P[i]-image_phash[RedChannel].P[i];
1588 if (IsNaN(error) != 0)
1590 difference=error*error;
1591 similarity[RedChannel]+=difference;
1592 similarity[CompositeChannels]+=difference;
1594 if ((channel & GreenChannel) != 0)
1596 error=reconstruct_phash[GreenChannel].P[i]-
1597 image_phash[GreenChannel].P[i];
1598 if (IsNaN(error) != 0)
1600 difference=error*error;
1601 similarity[GreenChannel]+=difference;
1602 similarity[CompositeChannels]+=difference;
1604 if ((channel & BlueChannel) != 0)
1606 error=reconstruct_phash[BlueChannel].P[i]-image_phash[BlueChannel].P[i];
1607 if (IsNaN(error) != 0)
1609 difference=error*error;
1610 similarity[BlueChannel]+=difference;
1611 similarity[CompositeChannels]+=difference;
1613 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse) &&
1614 (reconstruct_image->matte != MagickFalse))
1616 error=reconstruct_phash[OpacityChannel].P[i]-
1617 image_phash[OpacityChannel].P[i];
1618 if (IsNaN(error) != 0)
1620 difference=error*error;
1621 similarity[OpacityChannel]+=difference;
1622 similarity[CompositeChannels]+=difference;
1624 if (((channel & IndexChannel) != 0) &&
1625 (image->colorspace == CMYKColorspace) &&
1626 (reconstruct_image->colorspace == CMYKColorspace))
1628 error=reconstruct_phash[IndexChannel].P[i]-
1629 image_phash[IndexChannel].P[i];
1630 if (IsNaN(error) != 0)
1632 difference=error*error;
1633 similarity[IndexChannel]+=difference;
1634 similarity[CompositeChannels]+=difference;
1640 for (i=0; i < MaximumNumberOfImageMoments; i++)
1645 if ((channel & RedChannel) != 0)
1647 error=reconstruct_phash[RedChannel].Q[i]-image_phash[RedChannel].Q[i];
1648 if (IsNaN(error) != 0)
1650 difference=error*error;
1651 similarity[RedChannel]+=difference;
1652 similarity[CompositeChannels]+=difference;
1654 if ((channel & GreenChannel) != 0)
1656 error=reconstruct_phash[GreenChannel].Q[i]-
1657 image_phash[GreenChannel].Q[i];
1658 if (IsNaN(error) != 0)
1660 difference=error*error;
1661 similarity[GreenChannel]+=difference;
1662 similarity[CompositeChannels]+=difference;
1664 if ((channel & BlueChannel) != 0)
1666 error=reconstruct_phash[BlueChannel].Q[i]-image_phash[BlueChannel].Q[i];
1667 if (IsNaN(error) != 0)
1669 difference=error*error;
1670 similarity[BlueChannel]+=difference;
1671 similarity[CompositeChannels]+=difference;
1673 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse) &&
1674 (reconstruct_image->matte != MagickFalse))
1676 error=reconstruct_phash[OpacityChannel].Q[i]-
1677 image_phash[OpacityChannel].Q[i];
1678 if (IsNaN(error) != 0)
1680 difference=error*error;
1681 similarity[OpacityChannel]+=difference;
1682 similarity[CompositeChannels]+=difference;
1684 if (((channel & IndexChannel) != 0) &&
1685 (image->colorspace == CMYKColorspace) &&
1686 (reconstruct_image->colorspace == CMYKColorspace))
1688 error=reconstruct_phash[IndexChannel].Q[i]-
1689 image_phash[IndexChannel].Q[i];
1690 if (IsNaN(error) != 0)
1692 difference=error*error;
1693 similarity[IndexChannel]+=difference;
1694 similarity[CompositeChannels]+=difference;
1697 similarity[CompositeChannels]/=(double) GetNumberChannels(image,channel);
1707 static MagickBooleanType GetRMSESimilarity(
const Image *image,
1708 const Image *reconstruct_image,
const ChannelType channel,
double *similarity,
1711 #define RMSESquareRoot(x) sqrt((x) < 0.0 ? 0.0 : (x)) 1716 status=GetMSESimilarity(image,reconstruct_image,channel,similarity,
1718 if ((channel & RedChannel) != 0)
1719 similarity[RedChannel]=RMSESquareRoot(similarity[RedChannel]);
1720 if ((channel & GreenChannel) != 0)
1721 similarity[GreenChannel]=RMSESquareRoot(similarity[GreenChannel]);
1722 if ((channel & BlueChannel) != 0)
1723 similarity[BlueChannel]=RMSESquareRoot(similarity[BlueChannel]);
1724 if (((channel & OpacityChannel) != 0) &&
1725 (image->matte != MagickFalse))
1726 similarity[OpacityChannel]=RMSESquareRoot(similarity[OpacityChannel]);
1727 if (((channel & IndexChannel) != 0) &&
1728 (image->colorspace == CMYKColorspace))
1729 similarity[BlackChannel]=RMSESquareRoot(similarity[BlackChannel]);
1730 similarity[CompositeChannels]=RMSESquareRoot(similarity[CompositeChannels]);
1734 MagickExport MagickBooleanType GetImageChannelDistortion(
Image *image,
1735 const Image *reconstruct_image,
const ChannelType channel,
1736 const MetricType metric,
double *distortion,
ExceptionInfo *exception)
1739 *channel_similarity;
1747 assert(image != (
Image *) NULL);
1748 assert(image->signature == MagickCoreSignature);
1749 assert(reconstruct_image != (
const Image *) NULL);
1750 assert(reconstruct_image->signature == MagickCoreSignature);
1751 assert(distortion != (
double *) NULL);
1752 if (IsEventLogging() != MagickFalse)
1753 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
1755 if (metric != PerceptualHashErrorMetric)
1756 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1757 ThrowBinaryException(ImageError,
"ImageMorphologyDiffers",image->filename);
1761 length=CompositeChannels+1UL;
1762 channel_similarity=(
double *) AcquireQuantumMemory(length,
1763 sizeof(*channel_similarity));
1764 if (channel_similarity == (
double *) NULL)
1765 ThrowFatalException(ResourceLimitFatalError,
"MemoryAllocationFailed");
1766 (void) memset(channel_similarity,0,length*
sizeof(*channel_similarity));
1769 case AbsoluteErrorMetric:
1771 status=GetAESimilarity(image,reconstruct_image,channel,
1772 channel_similarity,exception);
1775 case FuzzErrorMetric:
1777 status=GetFUZZSimilarity(image,reconstruct_image,channel,
1778 channel_similarity,exception);
1781 case MeanAbsoluteErrorMetric:
1783 status=GetMAESimilarity(image,reconstruct_image,channel,
1784 channel_similarity,exception);
1787 case MeanErrorPerPixelMetric:
1789 status=GetMEPPSimilarity(image,reconstruct_image,channel,
1790 channel_similarity,exception);
1793 case MeanSquaredErrorMetric:
1795 status=GetMSESimilarity(image,reconstruct_image,channel,
1796 channel_similarity,exception);
1799 case NormalizedCrossCorrelationErrorMetric:
1801 status=GetNCCSimilarity(image,reconstruct_image,channel,
1802 channel_similarity,exception);
1805 case PeakAbsoluteErrorMetric:
1807 status=GetPASimilarity(image,reconstruct_image,channel,
1808 channel_similarity,exception);
1811 case PeakSignalToNoiseRatioMetric:
1813 status=GetPSNRSimilarity(image,reconstruct_image,channel,
1814 channel_similarity,exception);
1817 case PerceptualHashErrorMetric:
1819 status=GetPHASHSimilarity(image,reconstruct_image,channel,
1820 channel_similarity,exception);
1823 case PixelDifferenceCountErrorMetric:
1825 status=GetPDCSimilarity(image,reconstruct_image,channel,
1826 channel_similarity,exception);
1829 case RootMeanSquaredErrorMetric:
1830 case UndefinedErrorMetric:
1833 status=GetRMSESimilarity(image,reconstruct_image,channel,
1834 channel_similarity,exception);
1838 *distortion=channel_similarity[CompositeChannels];
1841 case NormalizedCrossCorrelationErrorMetric:
1843 *distortion=(1.0-(*distortion))/2.0;
1848 if (fabs(*distortion) < MagickEpsilon)
1850 channel_similarity=(
double *) RelinquishMagickMemory(channel_similarity);
1851 (void) FormatImageProperty(image,
"distortion",
"%.*g",GetMagickPrecision(),
1888 MagickExport
double *GetImageChannelDistortions(
Image *image,
1889 const Image *reconstruct_image,
const MetricType metric,
1905 assert(image != (
Image *) NULL);
1906 assert(image->signature == MagickCoreSignature);
1907 assert(reconstruct_image != (
const Image *) NULL);
1908 assert(reconstruct_image->signature == MagickCoreSignature);
1909 if (IsEventLogging() != MagickFalse)
1910 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
1911 if (metric != PerceptualHashErrorMetric)
1912 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1914 (void) ThrowMagickException(&image->exception,GetMagickModule(),
1915 ImageError,
"ImageMorphologyDiffers",
"`%s'",image->filename);
1916 return((
double *) NULL);
1921 length=CompositeChannels+1UL;
1922 similarity=(
double *) AcquireQuantumMemory(length,
1923 sizeof(*similarity));
1924 if (similarity == (
double *) NULL)
1925 ThrowFatalException(ResourceLimitFatalError,
"MemoryAllocationFailed");
1926 (void) memset(similarity,0,length*
sizeof(*similarity));
1930 case AbsoluteErrorMetric:
1932 status=GetAESimilarity(image,reconstruct_image,CompositeChannels,
1933 similarity,exception);
1936 case FuzzErrorMetric:
1938 status=GetFUZZSimilarity(image,reconstruct_image,CompositeChannels,
1939 similarity,exception);
1942 case MeanAbsoluteErrorMetric:
1944 status=GetMAESimilarity(image,reconstruct_image,CompositeChannels,
1945 similarity,exception);
1948 case MeanErrorPerPixelMetric:
1950 status=GetMEPPSimilarity(image,reconstruct_image,CompositeChannels,
1951 similarity,exception);
1954 case MeanSquaredErrorMetric:
1956 status=GetMSESimilarity(image,reconstruct_image,CompositeChannels,
1957 similarity,exception);
1960 case NormalizedCrossCorrelationErrorMetric:
1962 status=GetNCCSimilarity(image,reconstruct_image,CompositeChannels,
1963 similarity,exception);
1966 case PeakAbsoluteErrorMetric:
1968 status=GetPASimilarity(image,reconstruct_image,CompositeChannels,
1969 similarity,exception);
1972 case PeakSignalToNoiseRatioMetric:
1974 status=GetPSNRSimilarity(image,reconstruct_image,CompositeChannels,
1975 similarity,exception);
1978 case PerceptualHashErrorMetric:
1980 status=GetPHASHSimilarity(image,reconstruct_image,CompositeChannels,
1981 similarity,exception);
1984 case PixelDifferenceCountErrorMetric:
1986 status=GetPDCSimilarity(image,reconstruct_image,CompositeChannels,
1987 similarity,exception);
1990 case RootMeanSquaredErrorMetric:
1991 case UndefinedErrorMetric:
1994 status=GetRMSESimilarity(image,reconstruct_image,CompositeChannels,
1995 similarity,exception);
1999 if (status == MagickFalse)
2001 similarity=(
double *) RelinquishMagickMemory(similarity);
2002 return((
double *) NULL);
2004 distortion=similarity;
2007 case NormalizedCrossCorrelationErrorMetric:
2009 for (i=0; i <= (ssize_t) CompositeChannels; i++)
2010 distortion[i]=(1.0-distortion[i])/2.0;
2015 for (i=0; i <= (ssize_t) CompositeChannels; i++)
2016 if (fabs(distortion[i]) < MagickEpsilon)
2068 MagickExport MagickBooleanType IsImagesEqual(
Image *image,
2069 const Image *reconstruct_image)
2086 mean_error_per_pixel;
2095 assert(image != (
Image *) NULL);
2096 assert(image->signature == MagickCoreSignature);
2097 assert(reconstruct_image != (
const Image *) NULL);
2098 assert(reconstruct_image->signature == MagickCoreSignature);
2099 exception=(&image->exception);
2100 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
2101 ThrowBinaryException(ImageError,
"ImageMorphologyDiffers",image->filename);
2104 mean_error_per_pixel=0.0;
2106 SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
2107 image_view=AcquireVirtualCacheView(image,exception);
2108 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
2109 for (y=0; y < (ssize_t) rows; y++)
2112 *magick_restrict indexes,
2113 *magick_restrict reconstruct_indexes;
2122 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
2123 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
2126 indexes=GetCacheViewVirtualIndexQueue(image_view);
2127 reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
2128 for (x=0; x < (ssize_t) columns; x++)
2133 distance=fabs((
double) GetPixelRed(p)-(
double) GetPixelRed(q));
2134 mean_error_per_pixel+=distance;
2135 mean_error+=distance*distance;
2136 if (distance > maximum_error)
2137 maximum_error=distance;
2139 distance=fabs((
double) GetPixelGreen(p)-(
double) GetPixelGreen(q));
2140 mean_error_per_pixel+=distance;
2141 mean_error+=distance*distance;
2142 if (distance > maximum_error)
2143 maximum_error=distance;
2145 distance=fabs((
double) GetPixelBlue(p)-(
double) GetPixelBlue(q));
2146 mean_error_per_pixel+=distance;
2147 mean_error+=distance*distance;
2148 if (distance > maximum_error)
2149 maximum_error=distance;
2151 if (image->matte != MagickFalse)
2153 distance=fabs((
double) GetPixelOpacity(p)-(
double)
2154 GetPixelOpacity(q));
2155 mean_error_per_pixel+=distance;
2156 mean_error+=distance*distance;
2157 if (distance > maximum_error)
2158 maximum_error=distance;
2161 if ((image->colorspace == CMYKColorspace) &&
2162 (reconstruct_image->colorspace == CMYKColorspace))
2164 distance=fabs((
double) GetPixelIndex(indexes+x)-(
double)
2165 GetPixelIndex(reconstruct_indexes+x));
2166 mean_error_per_pixel+=distance;
2167 mean_error+=distance*distance;
2168 if (distance > maximum_error)
2169 maximum_error=distance;
2176 reconstruct_view=DestroyCacheView(reconstruct_view);
2177 image_view=DestroyCacheView(image_view);
2178 gamma=MagickSafeReciprocal(area);
2179 image->error.mean_error_per_pixel=gamma*mean_error_per_pixel;
2180 image->error.normalized_mean_error=gamma*QuantumScale*QuantumScale*mean_error;
2181 image->error.normalized_maximum_error=QuantumScale*maximum_error;
2182 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
2221 static double GetSimilarityMetric(
const Image *image,
2222 const Image *reconstruct_image,
const MetricType metric,
2223 const ssize_t x_offset,
const ssize_t y_offset,
ExceptionInfo *exception)
2226 *channel_similarity,
2230 *sans_exception = AcquireExceptionInfo();
2236 status = MagickTrue;
2242 length = CompositeChannels+1UL;
2244 SetGeometry(reconstruct_image,&geometry);
2245 geometry.x=x_offset;
2246 geometry.y=y_offset;
2247 similarity_image=CropImage(image,&geometry,sans_exception);
2248 sans_exception=DestroyExceptionInfo(sans_exception);
2249 if (similarity_image == (
Image *) NULL)
2254 channel_similarity=(
double *) AcquireQuantumMemory(length,
2255 sizeof(*channel_similarity));
2256 if (channel_similarity == (
double *) NULL)
2257 ThrowFatalException(ResourceLimitFatalError,
"MemoryAllocationFailed");
2258 (void) memset(channel_similarity,0,length*
sizeof(*channel_similarity));
2261 case AbsoluteErrorMetric:
2263 status=GetAESimilarity(similarity_image,reconstruct_image,
2264 CompositeChannels,channel_similarity,exception);
2267 case FuzzErrorMetric:
2269 status=GetFUZZSimilarity(similarity_image,reconstruct_image,
2270 CompositeChannels,channel_similarity,exception);
2273 case MeanAbsoluteErrorMetric:
2275 status=GetMAESimilarity(similarity_image,reconstruct_image,
2276 CompositeChannels,channel_similarity,exception);
2279 case MeanErrorPerPixelMetric:
2281 status=GetMEPPSimilarity(similarity_image,reconstruct_image,
2282 CompositeChannels,channel_similarity,exception);
2285 case MeanSquaredErrorMetric:
2287 status=GetMSESimilarity(similarity_image,reconstruct_image,
2288 CompositeChannels,channel_similarity,exception);
2291 case NormalizedCrossCorrelationErrorMetric:
2293 status=GetNCCSimilarity(similarity_image,reconstruct_image,
2294 CompositeChannels,channel_similarity,exception);
2297 case PeakAbsoluteErrorMetric:
2299 status=GetPASimilarity(similarity_image,reconstruct_image,
2300 CompositeChannels,channel_similarity,exception);
2303 case PeakSignalToNoiseRatioMetric:
2305 status=GetPSNRSimilarity(similarity_image,reconstruct_image,
2306 CompositeChannels,channel_similarity,exception);
2309 case PerceptualHashErrorMetric:
2311 status=GetPHASHSimilarity(similarity_image,reconstruct_image,
2312 CompositeChannels,channel_similarity,exception);
2315 case PixelDifferenceCountErrorMetric:
2317 status=GetPDCSimilarity(similarity_image,reconstruct_image,
2318 CompositeChannels,channel_similarity,exception);
2321 case RootMeanSquaredErrorMetric:
2322 case UndefinedErrorMetric:
2325 status=GetRMSESimilarity(similarity_image,reconstruct_image,
2326 CompositeChannels,channel_similarity,exception);
2330 similarity_image=DestroyImage(similarity_image);
2331 similarity=channel_similarity[CompositeChannels];
2332 channel_similarity=(
double *) RelinquishMagickMemory(channel_similarity);
2333 if (status == MagickFalse)
2338 MagickExport
Image *SimilarityImage(
Image *image,
const Image *reference,
2344 similarity_image=SimilarityMetricImage(image,reference,
2345 RootMeanSquaredErrorMetric,offset,similarity_metric,exception);
2346 return(similarity_image);
2349 MagickExport
Image *SimilarityMetricImage(
Image *image,
const Image *reconstruct,
2350 const MetricType metric,
RectangleInfo *offset,
double *similarity_metric,
2353 #define SimilarityImageTag "Similarity/Image" 2372 similarity_threshold;
2375 *similarity_image = (
Image *) NULL;
2384 similarity_info = { 0 };
2389 assert(image != (
const Image *) NULL);
2390 assert(image->signature == MagickCoreSignature);
2392 assert(exception->signature == MagickCoreSignature);
2394 if (IsEventLogging() != MagickFalse)
2395 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
2396 SetGeometry(reconstruct,offset);
2397 *similarity_metric=0.0;
2400 if (ValidateImageMorphology(image,reconstruct) == MagickFalse)
2401 ThrowImageException(ImageError,
"ImageMorphologyDiffers");
2402 if ((image->columns < reconstruct->columns) ||
2403 (image->rows < reconstruct->rows))
2405 (void) ThrowMagickException(&image->exception,GetMagickModule(),
2406 OptionWarning,
"GeometryDoesNotContainImage",
"`%s'",image->filename);
2407 return((
Image *) NULL);
2409 similarity_image=CloneImage(image,image->columns-reconstruct->columns+1,
2410 image->rows-reconstruct->rows+1,MagickTrue,exception);
2411 if (similarity_image == (
Image *) NULL)
2412 return((
Image *) NULL);
2413 similarity_image->depth=32;
2414 similarity_image->colorspace=GRAYColorspace;
2415 similarity_image->matte=MagickFalse;
2416 status=SetImageStorageClass(similarity_image,DirectClass);
2417 if (status == MagickFalse)
2419 InheritException(exception,&similarity_image->exception);
2420 return(DestroyImage(similarity_image));
2425 similarity_threshold=DefaultSimilarityThreshold;
2426 artifact=GetImageArtifact(image,
"compare:similarity-threshold");
2427 if (artifact != (
const char *) NULL)
2428 similarity_threshold=StringToDouble(artifact,(
char **) NULL);
2430 similarity_info.similarity=GetSimilarityMetric(image,reconstruct,metric,
2431 similarity_info.x,similarity_info.y,exception);
2433 similarity_view=AcquireVirtualCacheView(similarity_image,exception);
2434 #if defined(MAGICKCORE_OPENMP_SUPPORT) 2435 #pragma omp parallel for schedule(static) shared(status,similarity_info) \ 2436 magick_number_threads(image,reconstruct,similarity_image->rows << 2,1) 2438 for (y=0; y < (ssize_t) similarity_image->rows; y++)
2444 threshold_trigger = MagickFalse;
2450 channel_info = similarity_info;
2455 if (status == MagickFalse)
2457 if (threshold_trigger != MagickFalse)
2459 q=QueueCacheViewAuthenticPixels(similarity_view,0,y,
2460 similarity_image->columns,1,exception);
2466 for (x=0; x < (ssize_t) similarity_image->columns; x++)
2469 update = MagickFalse;
2471 similarity=GetSimilarityMetric(image,reconstruct,metric,x,y,exception);
2474 case NormalizedCrossCorrelationErrorMetric:
2475 case PeakSignalToNoiseRatioMetric:
2477 if (similarity > channel_info.similarity)
2483 if (similarity < channel_info.similarity)
2488 if (update != MagickFalse)
2490 channel_info.similarity=similarity;
2496 case NormalizedCrossCorrelationErrorMetric:
2497 case PeakSignalToNoiseRatioMetric:
2499 SetPixelRed(q,ClampToQuantum((
double) QuantumRange*similarity));
2504 SetPixelRed(q,ClampToQuantum((
double) QuantumRange*(1.0-similarity)));
2508 SetPixelGreen(q,GetPixelRed(q));
2509 SetPixelBlue(q,GetPixelRed(q));
2512 #if defined(MAGICKCORE_OPENMP_SUPPORT) 2513 #pragma omp critical (MagickCore_SimilarityMetricImage) 2517 case NormalizedCrossCorrelationErrorMetric:
2518 case PeakSignalToNoiseRatioMetric:
2520 if (similarity_threshold != DefaultSimilarityThreshold)
2521 if (channel_info.similarity >= similarity_threshold)
2522 threshold_trigger=MagickTrue;
2523 if (channel_info.similarity >= similarity_info.similarity)
2524 similarity_info=channel_info;
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;
2537 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
2539 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2545 proceed=SetImageProgress(image,SimilarityImageTag,progress,image->rows);
2546 if (proceed == MagickFalse)
2550 similarity_view=DestroyCacheView(similarity_view);
2551 if (status == MagickFalse)
2552 similarity_image=DestroyImage(similarity_image);
2553 *similarity_metric=similarity_info.similarity;
2554 if (fabs(*similarity_metric) < MagickEpsilon)
2555 *similarity_metric=0.0;
2556 offset->x=similarity_info.x;
2557 offset->y=similarity_info.y;
2558 (void) FormatImageProperty((
Image *) image,
"similarity",
"%.*g",
2559 GetMagickPrecision(),*similarity_metric);
2560 (void) FormatImageProperty((
Image *) image,
"similarity.offset.x",
"%.*g",
2561 GetMagickPrecision(),(double) offset->x);
2562 (void) FormatImageProperty((
Image *) image,
"similarity.offset.y",
"%.*g",
2563 GetMagickPrecision(),(double) offset->y);
2564 return(similarity_image);