43#include "MagickCore/studio.h"
44#include "MagickCore/artifact.h"
45#include "MagickCore/attribute.h"
46#include "MagickCore/cache-view.h"
47#include "MagickCore/channel.h"
48#include "MagickCore/client.h"
49#include "MagickCore/color.h"
50#include "MagickCore/color-private.h"
51#include "MagickCore/colorspace.h"
52#include "MagickCore/colorspace-private.h"
53#include "MagickCore/compare.h"
54#include "MagickCore/composite-private.h"
55#include "MagickCore/constitute.h"
56#include "MagickCore/exception-private.h"
57#include "MagickCore/enhance.h"
58#include "MagickCore/fourier.h"
59#include "MagickCore/geometry.h"
60#include "MagickCore/image-private.h"
61#include "MagickCore/list.h"
62#include "MagickCore/log.h"
63#include "MagickCore/memory_.h"
64#include "MagickCore/monitor.h"
65#include "MagickCore/monitor-private.h"
66#include "MagickCore/option.h"
67#include "MagickCore/pixel-accessor.h"
68#include "MagickCore/property.h"
69#include "MagickCore/registry.h"
70#include "MagickCore/resource_.h"
71#include "MagickCore/string_.h"
72#include "MagickCore/statistic.h"
73#include "MagickCore/statistic-private.h"
74#include "MagickCore/string-private.h"
75#include "MagickCore/thread-private.h"
76#include "MagickCore/transform.h"
77#include "MagickCore/utility.h"
78#include "MagickCore/version.h"
113MagickExport
Image *CompareImages(
Image *image,
const Image *reconstruct_image,
114 const MetricType metric,
double *distortion,
ExceptionInfo *exception)
150 assert(image != (
Image *) NULL);
151 assert(image->signature == MagickCoreSignature);
152 assert(reconstruct_image != (
const Image *) NULL);
153 assert(reconstruct_image->signature == MagickCoreSignature);
154 assert(distortion != (
double *) NULL);
155 if (IsEventLogging() != MagickFalse)
156 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
158 status=GetImageDistortion(image,reconstruct_image,metric,distortion,
160 if (status == MagickFalse)
161 return((
Image *) NULL);
162 columns=MagickMax(image->columns,reconstruct_image->columns);
163 rows=MagickMax(image->rows,reconstruct_image->rows);
164 SetGeometry(image,&geometry);
165 geometry.width=columns;
166 geometry.height=rows;
167 clone_image=CloneImage(image,0,0,MagickTrue,exception);
168 if (clone_image == (
Image *) NULL)
169 return((
Image *) NULL);
170 (void) SetImageMask(clone_image,ReadPixelMask,(
Image *) NULL,exception);
171 difference_image=ExtentImage(clone_image,&geometry,exception);
172 clone_image=DestroyImage(clone_image);
173 if (difference_image == (
Image *) NULL)
174 return((
Image *) NULL);
175 (void) SetImageAlphaChannel(difference_image,OpaqueAlphaChannel,exception);
176 highlight_image=CloneImage(image,columns,rows,MagickTrue,exception);
177 if (highlight_image == (
Image *) NULL)
179 difference_image=DestroyImage(difference_image);
180 return((
Image *) NULL);
182 status=SetImageStorageClass(highlight_image,DirectClass,exception);
183 if (status == MagickFalse)
185 difference_image=DestroyImage(difference_image);
186 highlight_image=DestroyImage(highlight_image);
187 return((
Image *) NULL);
189 (void) SetImageMask(highlight_image,ReadPixelMask,(
Image *) NULL,exception);
190 (void) SetImageAlphaChannel(highlight_image,OpaqueAlphaChannel,exception);
191 (void) QueryColorCompliance(
"#f1001ecc",AllCompliance,&highlight,exception);
192 artifact=GetImageArtifact(image,
"compare:highlight-color");
193 if (artifact != (
const char *) NULL)
194 (void) QueryColorCompliance(artifact,AllCompliance,&highlight,exception);
195 (void) QueryColorCompliance(
"#ffffffcc",AllCompliance,&lowlight,exception);
196 artifact=GetImageArtifact(image,
"compare:lowlight-color");
197 if (artifact != (
const char *) NULL)
198 (void) QueryColorCompliance(artifact,AllCompliance,&lowlight,exception);
199 (void) QueryColorCompliance(
"#888888cc",AllCompliance,&masklight,exception);
200 artifact=GetImageArtifact(image,
"compare:masklight-color");
201 if (artifact != (
const char *) NULL)
202 (void) QueryColorCompliance(artifact,AllCompliance,&masklight,exception);
207 fuzz=GetFuzzyColorDistance(image,reconstruct_image);
208 image_view=AcquireVirtualCacheView(image,exception);
209 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
210 highlight_view=AcquireAuthenticCacheView(highlight_image,exception);
211#if defined(MAGICKCORE_OPENMP_SUPPORT)
212 #pragma omp parallel for schedule(static) shared(status) \
213 magick_number_threads(image,highlight_image,rows,1)
215 for (y=0; y < (ssize_t) rows; y++)
230 if (status == MagickFalse)
232 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
233 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
234 r=QueueCacheViewAuthenticPixels(highlight_view,0,y,columns,1,exception);
235 if ((p == (
const Quantum *) NULL) || (q == (
const Quantum *) NULL) ||
236 (r == (Quantum *) NULL))
241 for (x=0; x < (ssize_t) columns; x++)
253 if ((GetPixelReadMask(image,p) <= (QuantumRange/2)) ||
254 (GetPixelReadMask(reconstruct_image,q) <= (QuantumRange/2)))
256 SetPixelViaPixelInfo(highlight_image,&masklight,r);
257 p+=(ptrdiff_t) GetPixelChannels(image);
258 q+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
259 r+=(ptrdiff_t) GetPixelChannels(highlight_image);
262 difference=MagickFalse;
263 Sa=QuantumScale*(double) GetPixelAlpha(image,p);
264 Da=QuantumScale*(double) GetPixelAlpha(reconstruct_image,q);
265 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
271 PixelChannel channel = GetPixelChannelChannel(image,i);
272 PixelTrait traits = GetPixelChannelTraits(image,channel);
273 PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
275 if ((traits == UndefinedPixelTrait) ||
276 (reconstruct_traits == UndefinedPixelTrait) ||
277 ((reconstruct_traits & UpdatePixelTrait) == 0))
279 if (channel == AlphaPixelChannel)
280 pixel=(double) p[i]-(
double) GetPixelChannel(reconstruct_image,
283 pixel=Sa*(double) p[i]-Da*(
double)
284 GetPixelChannel(reconstruct_image,channel,q);
285 distance=pixel*pixel;
286 if (distance >= fuzz)
288 difference=MagickTrue;
292 if (difference == MagickFalse)
293 SetPixelViaPixelInfo(highlight_image,&lowlight,r);
295 SetPixelViaPixelInfo(highlight_image,&highlight,r);
296 p+=(ptrdiff_t) GetPixelChannels(image);
297 q+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
298 r+=(ptrdiff_t) GetPixelChannels(highlight_image);
300 sync=SyncCacheViewAuthenticPixels(highlight_view,exception);
301 if (sync == MagickFalse)
304 highlight_view=DestroyCacheView(highlight_view);
305 reconstruct_view=DestroyCacheView(reconstruct_view);
306 image_view=DestroyCacheView(image_view);
307 (void) CompositeImage(difference_image,highlight_image,image->compose,
308 MagickTrue,0,0,exception);
309 highlight_image=DestroyImage(highlight_image);
310 if (status == MagickFalse)
311 difference_image=DestroyImage(difference_image);
312 return(difference_image);
349static MagickBooleanType GetAbsoluteDistortion(
const Image *image,
373 fuzz=GetFuzzyColorDistance(image,reconstruct_image);
374 rows=MagickMax(image->rows,reconstruct_image->rows);
375 columns=MagickMax(image->columns,reconstruct_image->columns);
376 image_view=AcquireVirtualCacheView(image,exception);
377 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
378#if defined(MAGICKCORE_OPENMP_SUPPORT)
379 #pragma omp parallel for schedule(static) shared(status) \
380 magick_number_threads(image,image,rows,1)
382 for (y=0; y < (ssize_t) rows; y++)
385 channel_distortion[MaxPixelChannels+1];
395 if (status == MagickFalse)
397 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
398 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
399 if ((p == (
const Quantum *) NULL) || (q == (
const Quantum *) NULL))
404 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
405 for (x=0; x < (ssize_t) columns; x++)
417 if ((GetPixelReadMask(image,p) <= (QuantumRange/2)) ||
418 (GetPixelReadMask(reconstruct_image,q) <= (QuantumRange/2)))
420 p+=(ptrdiff_t) GetPixelChannels(image);
421 q+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
424 difference=MagickFalse;
425 Sa=QuantumScale*(double) GetPixelAlpha(image,p);
426 Da=QuantumScale*(double) GetPixelAlpha(reconstruct_image,q);
427 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
433 PixelChannel channel = GetPixelChannelChannel(image,i);
434 PixelTrait traits = GetPixelChannelTraits(image,channel);
435 PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
437 if ((traits == UndefinedPixelTrait) ||
438 (reconstruct_traits == UndefinedPixelTrait) ||
439 ((reconstruct_traits & UpdatePixelTrait) == 0))
441 if (channel == AlphaPixelChannel)
442 pixel=(double) p[i]-(
double) GetPixelChannel(reconstruct_image,
445 pixel=Sa*(double) p[i]-Da*(
double) GetPixelChannel(reconstruct_image,
447 distance=pixel*pixel;
448 if (distance >= fuzz)
450 channel_distortion[i]++;
451 difference=MagickTrue;
454 if (difference != MagickFalse)
455 channel_distortion[CompositePixelChannel]++;
456 p+=(ptrdiff_t) GetPixelChannels(image);
457 q+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
459#if defined(MAGICKCORE_OPENMP_SUPPORT)
460 #pragma omp critical (MagickCore_GetAbsoluteDistortion)
462 for (j=0; j <= MaxPixelChannels; j++)
463 distortion[j]+=channel_distortion[j];
465 reconstruct_view=DestroyCacheView(reconstruct_view);
466 image_view=DestroyCacheView(image_view);
470static MagickBooleanType GetFuzzDistortion(
const Image *image,
494 rows=MagickMax(image->rows,reconstruct_image->rows);
495 columns=MagickMax(image->columns,reconstruct_image->columns);
497 image_view=AcquireVirtualCacheView(image,exception);
498 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
499#if defined(MAGICKCORE_OPENMP_SUPPORT)
500 #pragma omp parallel for schedule(static) shared(status) \
501 magick_number_threads(image,image,rows,1)
503 for (y=0; y < (ssize_t) rows; y++)
506 channel_distortion[MaxPixelChannels+1];
518 if (status == MagickFalse)
520 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
521 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
522 if ((p == (
const Quantum *) NULL) || (q == (Quantum *) NULL))
527 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
528 for (x=0; x < (ssize_t) columns; x++)
537 if ((GetPixelReadMask(image,p) <= (QuantumRange/2)) ||
538 (GetPixelReadMask(reconstruct_image,q) <= (QuantumRange/2)))
540 p+=(ptrdiff_t) GetPixelChannels(image);
541 q+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
544 Sa=QuantumScale*(double) GetPixelAlpha(image,p);
545 Da=QuantumScale*(double) GetPixelAlpha(reconstruct_image,q);
546 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
551 PixelChannel channel = GetPixelChannelChannel(image,i);
552 PixelTrait traits = GetPixelChannelTraits(image,channel);
553 PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
555 if ((traits == UndefinedPixelTrait) ||
556 (reconstruct_traits == UndefinedPixelTrait) ||
557 ((reconstruct_traits & UpdatePixelTrait) == 0))
559 if (channel == AlphaPixelChannel)
560 distance=QuantumScale*((double) p[i]-(double) GetPixelChannel(
561 reconstruct_image,channel,q));
563 distance=QuantumScale*(Sa*(double) p[i]-Da*(double) GetPixelChannel(
564 reconstruct_image,channel,q));
565 channel_distortion[i]+=distance*distance;
566 channel_distortion[CompositePixelChannel]+=distance*distance;
569 p+=(ptrdiff_t) GetPixelChannels(image);
570 q+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
572#if defined(MAGICKCORE_OPENMP_SUPPORT)
573 #pragma omp critical (MagickCore_GetFuzzDistortion)
577 for (j=0; j <= MaxPixelChannels; j++)
578 distortion[j]+=channel_distortion[j];
581 reconstruct_view=DestroyCacheView(reconstruct_view);
582 image_view=DestroyCacheView(image_view);
583 area=PerceptibleReciprocal(area);
584 for (j=0; j <= MaxPixelChannels; j++)
586 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
587 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]);
591static MagickBooleanType GetMeanAbsoluteDistortion(
const Image *image,
615 rows=MagickMax(image->rows,reconstruct_image->rows);
616 columns=MagickMax(image->columns,reconstruct_image->columns);
618 image_view=AcquireVirtualCacheView(image,exception);
619 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
620#if defined(MAGICKCORE_OPENMP_SUPPORT)
621 #pragma omp parallel for schedule(static) shared(status) \
622 magick_number_threads(image,image,rows,1)
624 for (y=0; y < (ssize_t) rows; y++)
627 channel_distortion[MaxPixelChannels+1];
639 if (status == MagickFalse)
641 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
642 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
643 if ((p == (
const Quantum *) NULL) || (q == (
const Quantum *) NULL))
648 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
649 for (x=0; x < (ssize_t) columns; x++)
658 if ((GetPixelReadMask(image,p) <= (QuantumRange/2)) ||
659 (GetPixelReadMask(reconstruct_image,q) <= (QuantumRange/2)))
661 p+=(ptrdiff_t) GetPixelChannels(image);
662 q+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
665 Sa=QuantumScale*(double) GetPixelAlpha(image,p);
666 Da=QuantumScale*(double) GetPixelAlpha(reconstruct_image,q);
667 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
672 PixelChannel channel = GetPixelChannelChannel(image,i);
673 PixelTrait traits = GetPixelChannelTraits(image,channel);
674 PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
676 if ((traits == UndefinedPixelTrait) ||
677 (reconstruct_traits == UndefinedPixelTrait) ||
678 ((reconstruct_traits & UpdatePixelTrait) == 0))
680 if (channel == AlphaPixelChannel)
681 distance=QuantumScale*fabs((
double) p[i]-(
double)
682 GetPixelChannel(reconstruct_image,channel,q));
684 distance=QuantumScale*fabs(Sa*(
double) p[i]-Da*(
double)
685 GetPixelChannel(reconstruct_image,channel,q));
686 channel_distortion[i]+=distance;
687 channel_distortion[CompositePixelChannel]+=distance;
690 p+=(ptrdiff_t) GetPixelChannels(image);
691 q+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
693#if defined(MAGICKCORE_OPENMP_SUPPORT)
694 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
698 for (j=0; j <= MaxPixelChannels; j++)
699 distortion[j]+=channel_distortion[j];
702 reconstruct_view=DestroyCacheView(reconstruct_view);
703 image_view=DestroyCacheView(image_view);
704 area=PerceptibleReciprocal(area);
705 for (j=0; j <= MaxPixelChannels; j++)
707 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
711static MagickBooleanType GetMeanErrorPerPixel(
Image *image,
737 rows=MagickMax(image->rows,reconstruct_image->rows);
738 columns=MagickMax(image->columns,reconstruct_image->columns);
739 image_view=AcquireVirtualCacheView(image,exception);
740 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
741 for (y=0; y < (ssize_t) rows; y++)
750 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
751 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
752 if ((p == (
const Quantum *) NULL) || (q == (
const Quantum *) NULL))
757 for (x=0; x < (ssize_t) columns; x++)
766 if ((GetPixelReadMask(image,p) <= (QuantumRange/2)) ||
767 (GetPixelReadMask(reconstruct_image,q) <= (QuantumRange/2)))
769 p+=(ptrdiff_t) GetPixelChannels(image);
770 q+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
773 Sa=QuantumScale*(double) GetPixelAlpha(image,p);
774 Da=QuantumScale*(double) GetPixelAlpha(reconstruct_image,q);
775 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
780 PixelChannel channel = GetPixelChannelChannel(image,i);
781 PixelTrait traits = GetPixelChannelTraits(image,channel);
782 PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
784 if ((traits == UndefinedPixelTrait) ||
785 (reconstruct_traits == UndefinedPixelTrait) ||
786 ((reconstruct_traits & UpdatePixelTrait) == 0))
788 if (channel == AlphaPixelChannel)
789 distance=fabs((
double) p[i]-(
double)
790 GetPixelChannel(reconstruct_image,channel,q));
792 distance=fabs(Sa*(
double) p[i]-Da*(
double)
793 GetPixelChannel(reconstruct_image,channel,q));
794 distortion[i]+=distance;
795 distortion[CompositePixelChannel]+=distance;
796 mean_error+=distance*distance;
797 if (distance > maximum_error)
798 maximum_error=distance;
801 p+=(ptrdiff_t) GetPixelChannels(image);
802 q+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
805 reconstruct_view=DestroyCacheView(reconstruct_view);
806 image_view=DestroyCacheView(image_view);
807 area=PerceptibleReciprocal(area);
808 image->error.mean_error_per_pixel=area*distortion[CompositePixelChannel];
809 image->error.normalized_mean_error=area*QuantumScale*QuantumScale*mean_error;
810 image->error.normalized_maximum_error=QuantumScale*maximum_error;
814static MagickBooleanType GetMeanSquaredDistortion(
const Image *image,
836 rows=MagickMax(image->rows,reconstruct_image->rows);
837 columns=MagickMax(image->columns,reconstruct_image->columns);
839 image_view=AcquireVirtualCacheView(image,exception);
840 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
841#if defined(MAGICKCORE_OPENMP_SUPPORT)
842 #pragma omp parallel for schedule(static) shared(status) \
843 magick_number_threads(image,image,rows,1)
845 for (y=0; y < (ssize_t) rows; y++)
852 channel_distortion[MaxPixelChannels+1];
860 if (status == MagickFalse)
862 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
863 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
864 if ((p == (
const Quantum *) NULL) || (q == (
const Quantum *) NULL))
869 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
870 for (x=0; x < (ssize_t) columns; x++)
879 if ((GetPixelReadMask(image,p) <= (QuantumRange/2)) ||
880 (GetPixelReadMask(reconstruct_image,q) <= (QuantumRange/2)))
882 p+=(ptrdiff_t) GetPixelChannels(image);
883 q+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
886 Sa=QuantumScale*(double) GetPixelAlpha(image,p);
887 Da=QuantumScale*(double) GetPixelAlpha(reconstruct_image,q);
888 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
893 PixelChannel channel = GetPixelChannelChannel(image,i);
894 PixelTrait traits = GetPixelChannelTraits(image,channel);
895 PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
897 if ((traits == UndefinedPixelTrait) ||
898 (reconstruct_traits == UndefinedPixelTrait) ||
899 ((reconstruct_traits & UpdatePixelTrait) == 0))
901 if (channel == AlphaPixelChannel)
902 distance=QuantumScale*((double) p[i]-(double) GetPixelChannel(
903 reconstruct_image,channel,q));
905 distance=QuantumScale*(Sa*(double) p[i]-Da*(double) GetPixelChannel(
906 reconstruct_image,channel,q));
907 channel_distortion[i]+=distance*distance;
908 channel_distortion[CompositePixelChannel]+=distance*distance;
911 p+=(ptrdiff_t) GetPixelChannels(image);
912 q+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
914#if defined(MAGICKCORE_OPENMP_SUPPORT)
915 #pragma omp critical (MagickCore_GetMeanSquaredError)
919 for (j=0; j <= MaxPixelChannels; j++)
920 distortion[j]+=channel_distortion[j];
923 reconstruct_view=DestroyCacheView(reconstruct_view);
924 image_view=DestroyCacheView(image_view);
925 area=PerceptibleReciprocal(area);
926 for (j=0; j <= MaxPixelChannels; j++)
928 distortion[CompositePixelChannel]/=GetImageChannels(image);
932static MagickBooleanType GetNormalizedCrossCorrelationDistortion(
933 const Image *image,
const Image *reconstruct_image,
double *distortion,
936#define SimilarityImageTag "Similarity/Image"
944 *reconstruct_statistics;
947 alpha_variance[MaxPixelChannels+1],
948 beta_variance[MaxPixelChannels+1];
967 image_statistics=GetImageStatistics(image,exception);
968 reconstruct_statistics=GetImageStatistics(reconstruct_image,exception);
977 reconstruct_statistics);
980 (void) memset(distortion,0,(MaxPixelChannels+1)*
sizeof(*distortion));
981 (void) memset(alpha_variance,0,(MaxPixelChannels+1)*
sizeof(*alpha_variance));
982 (void) memset(beta_variance,0,(MaxPixelChannels+1)*
sizeof(*beta_variance));
985 columns=MagickMax(image->columns,reconstruct_image->columns);
986 rows=MagickMax(image->rows,reconstruct_image->rows);
987 image_view=AcquireVirtualCacheView(image,exception);
988 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
989 for (y=0; y < (ssize_t) rows; y++)
998 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
999 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1000 if ((p == (
const Quantum *) NULL) || (q == (
const Quantum *) NULL))
1005 for (x=0; x < (ssize_t) columns; x++)
1011 if ((GetPixelReadMask(image,p) <= (QuantumRange/2)) ||
1012 (GetPixelReadMask(reconstruct_image,q) <= (QuantumRange/2)))
1014 p+=(ptrdiff_t) GetPixelChannels(image);
1015 q+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
1018 Sa=QuantumScale*(double) GetPixelAlpha(image,p);
1019 Da=QuantumScale*(double) GetPixelAlpha(reconstruct_image,q);
1020 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1026 PixelChannel channel = GetPixelChannelChannel(image,i);
1027 PixelTrait traits = GetPixelChannelTraits(image,channel);
1028 PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
1030 if ((traits == UndefinedPixelTrait) ||
1031 (reconstruct_traits == UndefinedPixelTrait) ||
1032 ((reconstruct_traits & UpdatePixelTrait) == 0))
1034 if (channel == AlphaPixelChannel)
1036 alpha=QuantumScale*((double) p[i]-image_statistics[channel].mean);
1037 beta=QuantumScale*((double) GetPixelChannel(reconstruct_image,
1038 channel,q)-reconstruct_statistics[channel].mean);
1042 alpha=QuantumScale*(Sa*(double) p[i]-
1043 image_statistics[channel].mean);
1044 beta=QuantumScale*(Da*(double) GetPixelChannel(reconstruct_image,
1045 channel,q)-reconstruct_statistics[channel].mean);
1047 distortion[i]+=alpha*beta;
1048 alpha_variance[i]+=alpha*alpha;
1049 beta_variance[i]+=beta*beta;
1051 p+=(ptrdiff_t) GetPixelChannels(image);
1052 q+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
1054 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1059#if defined(MAGICKCORE_OPENMP_SUPPORT)
1063 proceed=SetImageProgress(image,SimilarityImageTag,progress,rows);
1064 if (proceed == MagickFalse)
1071 reconstruct_view=DestroyCacheView(reconstruct_view);
1072 image_view=DestroyCacheView(image_view);
1076 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1078 distortion[i]/=sqrt(alpha_variance[i]*beta_variance[i]);
1079 if (fabs(distortion[i]) > MagickEpsilon)
1080 distortion[CompositePixelChannel]+=distortion[i];
1082 distortion[CompositePixelChannel]=distortion[CompositePixelChannel]/
1083 GetImageChannels(image);
1088 reconstruct_statistics);
1094static MagickBooleanType GetPeakAbsoluteDistortion(
const Image *image,
1112 rows=MagickMax(image->rows,reconstruct_image->rows);
1113 columns=MagickMax(image->columns,reconstruct_image->columns);
1114 image_view=AcquireVirtualCacheView(image,exception);
1115 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1116#if defined(MAGICKCORE_OPENMP_SUPPORT)
1117 #pragma omp parallel for schedule(static) shared(status) \
1118 magick_number_threads(image,image,rows,1)
1120 for (y=0; y < (ssize_t) rows; y++)
1123 channel_distortion[MaxPixelChannels+1];
1133 if (status == MagickFalse)
1135 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1136 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1137 if ((p == (
const Quantum *) NULL) || (q == (
const Quantum *) NULL))
1142 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
1143 for (x=0; x < (ssize_t) columns; x++)
1152 if ((GetPixelReadMask(image,p) <= (QuantumRange/2)) ||
1153 (GetPixelReadMask(reconstruct_image,q) <= (QuantumRange/2)))
1155 p+=(ptrdiff_t) GetPixelChannels(image);
1156 q+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
1159 Sa=QuantumScale*(double) GetPixelAlpha(image,p);
1160 Da=QuantumScale*(double) GetPixelAlpha(reconstruct_image,q);
1161 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1166 PixelChannel channel = GetPixelChannelChannel(image,i);
1167 PixelTrait traits = GetPixelChannelTraits(image,channel);
1168 PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
1170 if ((traits == UndefinedPixelTrait) ||
1171 (reconstruct_traits == UndefinedPixelTrait) ||
1172 ((reconstruct_traits & UpdatePixelTrait) == 0))
1174 if (channel == AlphaPixelChannel)
1175 distance=QuantumScale*fabs((
double) p[i]-(
double)
1176 GetPixelChannel(reconstruct_image,channel,q));
1178 distance=QuantumScale*fabs(Sa*(
double) p[i]-Da*(
double)
1179 GetPixelChannel(reconstruct_image,channel,q));
1180 if (distance > channel_distortion[i])
1181 channel_distortion[i]=distance;
1182 if (distance > channel_distortion[CompositePixelChannel])
1183 channel_distortion[CompositePixelChannel]=distance;
1185 p+=(ptrdiff_t) GetPixelChannels(image);
1186 q+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
1188#if defined(MAGICKCORE_OPENMP_SUPPORT)
1189 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
1191 for (j=0; j <= MaxPixelChannels; j++)
1192 if (channel_distortion[j] > distortion[j])
1193 distortion[j]=channel_distortion[j];
1195 reconstruct_view=DestroyCacheView(reconstruct_view);
1196 image_view=DestroyCacheView(image_view);
1200static MagickBooleanType GetPeakSignalToNoiseRatio(
const Image *image,
1209 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
1210 for (i=0; i <= MaxPixelChannels; i++)
1211 if (fabs(distortion[i]) >= MagickEpsilon)
1212 distortion[i]=(-10.0*MagickLog10(distortion[i]));
1216static MagickBooleanType GetPerceptualHashDistortion(
const Image *image,
1232 channel_phash=GetImagePerceptualHash(image,exception);
1234 return(MagickFalse);
1235 reconstruct_phash=GetImagePerceptualHash(reconstruct_image,exception);
1240 return(MagickFalse);
1242#if defined(MAGICKCORE_OPENMP_SUPPORT)
1243 #pragma omp parallel for schedule(static)
1245 for (channel=0; channel < MaxPixelChannels; channel++)
1254 for (i=0; i < MaximumNumberOfImageMoments; i++)
1263 for (j=0; j < (ssize_t) channel_phash[0].number_colorspaces; j++)
1265 alpha=channel_phash[channel].phash[j][i];
1266 beta=reconstruct_phash[channel].phash[j][i];
1267 difference+=(beta-alpha)*(beta-alpha);
1270 distortion[channel]+=difference;
1271#if defined(MAGICKCORE_OPENMP_SUPPORT)
1272 #pragma omp critical (MagickCore_GetPerceptualHashDistortion)
1274 distortion[CompositePixelChannel]+=difference;
1276 artifact=GetImageArtifact(image,
"phash:normalize");
1277 if ((artifact != (
const char *) NULL) &&
1278 (IsStringTrue(artifact) != MagickFalse))
1283 for (j=0; j <= MaxPixelChannels; j++)
1284 distortion[j]=sqrt(distortion[j]/channel_phash[0].number_channels);
1295static MagickBooleanType GetRootMeanSquaredDistortion(
const Image *image,
1304 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
1305 for (i=0; i <= MaxPixelChannels; i++)
1306 distortion[i]=sqrt(distortion[i]);
1310static MagickBooleanType GetStructuralSimilarityDistortion(
const Image *image,
1313#define SSIMRadius 5.0
1314#define SSIMSigma 1.5
1315#define SSIMBlocksize 8
1325 geometry[MagickPathExtent];
1358 artifact=GetImageArtifact(image,
"compare:ssim-radius");
1359 if (artifact != (
const char *) NULL)
1360 radius=StringToDouble(artifact,(
char **) NULL);
1362 artifact=GetImageArtifact(image,
"compare:ssim-sigma");
1363 if (artifact != (
const char *) NULL)
1364 sigma=StringToDouble(artifact,(
char **) NULL);
1365 (void) FormatLocaleString(geometry,MagickPathExtent,
"gaussian:%.20gx%.20g",
1367 kernel_info=AcquireKernelInfo(geometry,exception);
1369 ThrowBinaryException(ResourceLimitError,
"MemoryAllocationFailed",
1371 c1=pow(SSIMK1*SSIML,2.0);
1372 artifact=GetImageArtifact(image,
"compare:ssim-k1");
1373 if (artifact != (
const char *) NULL)
1374 c1=pow(StringToDouble(artifact,(
char **) NULL)*SSIML,2.0);
1375 c2=pow(SSIMK2*SSIML,2.0);
1376 artifact=GetImageArtifact(image,
"compare:ssim-k2");
1377 if (artifact != (
const char *) NULL)
1378 c2=pow(StringToDouble(artifact,(
char **) NULL)*SSIML,2.0);
1381 rows=MagickMax(image->rows,reconstruct_image->rows);
1382 columns=MagickMax(image->columns,reconstruct_image->columns);
1383 image_view=AcquireVirtualCacheView(image,exception);
1384 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1385#if defined(MAGICKCORE_OPENMP_SUPPORT)
1386 #pragma omp parallel for schedule(static) shared(status) \
1387 magick_number_threads(image,reconstruct_image,rows,1)
1389 for (y=0; y < (ssize_t) rows; y++)
1392 channel_distortion[MaxPixelChannels+1];
1405 if (status == MagickFalse)
1407 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) kernel_info->width/2L),y-
1408 ((ssize_t) kernel_info->height/2L),columns+kernel_info->width,
1409 kernel_info->height,exception);
1410 q=GetCacheViewVirtualPixels(reconstruct_view,-((ssize_t) kernel_info->width/
1411 2L),y-((ssize_t) kernel_info->height/2L),columns+kernel_info->width,
1412 kernel_info->height,exception);
1413 if ((p == (
const Quantum *) NULL) || (q == (
const Quantum *) NULL))
1418 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
1419 for (x=0; x < (ssize_t) columns; x++)
1422 x_pixel_mu[MaxPixelChannels+1],
1423 x_pixel_sigma_squared[MaxPixelChannels+1],
1424 xy_sigma[MaxPixelChannels+1],
1425 y_pixel_mu[MaxPixelChannels+1],
1426 y_pixel_sigma_squared[MaxPixelChannels+1];
1429 *magick_restrict reference,
1430 *magick_restrict target;
1438 if ((GetPixelReadMask(image,p) <= (QuantumRange/2)) ||
1439 (GetPixelReadMask(reconstruct_image,q) <= (QuantumRange/2)))
1441 p+=(ptrdiff_t) GetPixelChannels(image);
1442 q+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
1445 (void) memset(x_pixel_mu,0,
sizeof(x_pixel_mu));
1446 (void) memset(x_pixel_sigma_squared,0,
sizeof(x_pixel_sigma_squared));
1447 (void) memset(xy_sigma,0,
sizeof(xy_sigma));
1448 (void) memset(x_pixel_sigma_squared,0,
sizeof(y_pixel_sigma_squared));
1449 (void) memset(y_pixel_mu,0,
sizeof(y_pixel_mu));
1450 (void) memset(y_pixel_sigma_squared,0,
sizeof(y_pixel_sigma_squared));
1451 k=kernel_info->values;
1454 for (v=0; v < (ssize_t) kernel_info->height; v++)
1459 for (u=0; u < (ssize_t) kernel_info->width; u++)
1461 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1467 PixelChannel channel = GetPixelChannelChannel(image,i);
1468 PixelTrait traits = GetPixelChannelTraits(image,channel);
1469 PixelTrait reconstruct_traits = GetPixelChannelTraits(
1470 reconstruct_image,channel);
1471 if ((traits == UndefinedPixelTrait) ||
1472 (reconstruct_traits == UndefinedPixelTrait) ||
1473 ((reconstruct_traits & UpdatePixelTrait) == 0))
1475 x_pixel=QuantumScale*(double) reference[i];
1476 x_pixel_mu[i]+=(*k)*x_pixel;
1477 x_pixel_sigma_squared[i]+=(*k)*x_pixel*x_pixel;
1478 y_pixel=QuantumScale*(double)
1479 GetPixelChannel(reconstruct_image,channel,target);
1480 y_pixel_mu[i]+=(*k)*y_pixel;
1481 y_pixel_sigma_squared[i]+=(*k)*y_pixel*y_pixel;
1482 xy_sigma[i]+=(*k)*x_pixel*y_pixel;
1485 reference+=(ptrdiff_t) GetPixelChannels(image);
1486 target+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
1488 reference+=(ptrdiff_t) GetPixelChannels(image)*columns;
1489 target+=(ptrdiff_t) GetPixelChannels(reconstruct_image)*columns;
1491 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1496 x_pixel_sigmas_squared,
1500 y_pixel_sigmas_squared;
1502 PixelChannel channel = GetPixelChannelChannel(image,i);
1503 PixelTrait traits = GetPixelChannelTraits(image,channel);
1504 PixelTrait reconstruct_traits = GetPixelChannelTraits(
1505 reconstruct_image,channel);
1506 if ((traits == UndefinedPixelTrait) ||
1507 (reconstruct_traits == UndefinedPixelTrait) ||
1508 ((reconstruct_traits & UpdatePixelTrait) == 0))
1510 x_pixel_mu_squared=x_pixel_mu[i]*x_pixel_mu[i];
1511 y_pixel_mu_squared=y_pixel_mu[i]*y_pixel_mu[i];
1512 xy_mu=x_pixel_mu[i]*y_pixel_mu[i];
1513 xy_sigmas=xy_sigma[i]-xy_mu;
1514 x_pixel_sigmas_squared=x_pixel_sigma_squared[i]-x_pixel_mu_squared;
1515 y_pixel_sigmas_squared=y_pixel_sigma_squared[i]-y_pixel_mu_squared;
1516 ssim=((2.0*xy_mu+c1)*(2.0*xy_sigmas+c2))/
1517 ((x_pixel_mu_squared+y_pixel_mu_squared+c1)*
1518 (x_pixel_sigmas_squared+y_pixel_sigmas_squared+c2));
1519 channel_distortion[i]+=ssim;
1520 channel_distortion[CompositePixelChannel]+=ssim;
1522 p+=(ptrdiff_t) GetPixelChannels(image);
1523 q+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
1526#if defined(MAGICKCORE_OPENMP_SUPPORT)
1527 #pragma omp critical (MagickCore_GetStructuralSimilarityDistortion)
1531 for (i=0; i <= MaxPixelChannels; i++)
1532 distortion[i]+=channel_distortion[i];
1535 image_view=DestroyCacheView(image_view);
1536 reconstruct_view=DestroyCacheView(reconstruct_view);
1537 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
1539 PixelChannel channel = GetPixelChannelChannel(image,j);
1540 PixelTrait traits = GetPixelChannelTraits(image,channel);
1541 if ((traits == UndefinedPixelTrait) || ((traits & UpdatePixelTrait) == 0))
1543 distortion[j]/=area;
1545 distortion[CompositePixelChannel]/=area;
1546 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
1547 kernel_info=DestroyKernelInfo(kernel_info);
1551static MagickBooleanType GetStructuralDisimilarityDistortion(
const Image *image,
1560 status=GetStructuralSimilarityDistortion(image,reconstruct_image,
1561 distortion,exception);
1562 for (i=0; i <= MaxPixelChannels; i++)
1563 distortion[i]=(1.0-(distortion[i]))/2.0;
1567MagickExport MagickBooleanType GetImageDistortion(
Image *image,
1568 const Image *reconstruct_image,
const MetricType metric,
double *distortion,
1572 *channel_distortion;
1580 assert(image != (
Image *) NULL);
1581 assert(image->signature == MagickCoreSignature);
1582 assert(reconstruct_image != (
const Image *) NULL);
1583 assert(reconstruct_image->signature == MagickCoreSignature);
1584 assert(distortion != (
double *) NULL);
1585 if (IsEventLogging() != MagickFalse)
1586 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
1591 length=MaxPixelChannels+1UL;
1592 channel_distortion=(
double *) AcquireQuantumMemory(length,
1593 sizeof(*channel_distortion));
1594 if (channel_distortion == (
double *) NULL)
1595 ThrowFatalException(ResourceLimitFatalError,
"MemoryAllocationFailed");
1596 (void) memset(channel_distortion,0,length*
1597 sizeof(*channel_distortion));
1600 case AbsoluteErrorMetric:
1602 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1606 case FuzzErrorMetric:
1608 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1612 case MeanAbsoluteErrorMetric:
1614 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1615 channel_distortion,exception);
1618 case MeanErrorPerPixelErrorMetric:
1620 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1624 case MeanSquaredErrorMetric:
1626 status=GetMeanSquaredDistortion(image,reconstruct_image,
1627 channel_distortion,exception);
1630 case NormalizedCrossCorrelationErrorMetric:
1633 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1634 channel_distortion,exception);
1637 case PeakAbsoluteErrorMetric:
1639 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1640 channel_distortion,exception);
1643 case PeakSignalToNoiseRatioErrorMetric:
1645 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1646 channel_distortion,exception);
1649 case PerceptualHashErrorMetric:
1651 status=GetPerceptualHashDistortion(image,reconstruct_image,
1652 channel_distortion,exception);
1655 case RootMeanSquaredErrorMetric:
1657 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1658 channel_distortion,exception);
1661 case StructuralSimilarityErrorMetric:
1663 status=GetStructuralSimilarityDistortion(image,reconstruct_image,
1664 channel_distortion,exception);
1667 case StructuralDissimilarityErrorMetric:
1669 status=GetStructuralDisimilarityDistortion(image,reconstruct_image,
1670 channel_distortion,exception);
1674 *distortion=channel_distortion[CompositePixelChannel];
1675 channel_distortion=(
double *) RelinquishMagickMemory(channel_distortion);
1676 (void) FormatImageProperty(image,
"distortion",
"%.*g",GetMagickPrecision(),
1713MagickExport
double *GetImageDistortions(
Image *image,
1714 const Image *reconstruct_image,
const MetricType metric,
1718 *channel_distortion;
1726 assert(image != (
Image *) NULL);
1727 assert(image->signature == MagickCoreSignature);
1728 assert(reconstruct_image != (
const Image *) NULL);
1729 assert(reconstruct_image->signature == MagickCoreSignature);
1730 if (IsEventLogging() != MagickFalse)
1731 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
1735 length=MaxPixelChannels+1UL;
1736 channel_distortion=(
double *) AcquireQuantumMemory(length,
1737 sizeof(*channel_distortion));
1738 if (channel_distortion == (
double *) NULL)
1739 ThrowFatalException(ResourceLimitFatalError,
"MemoryAllocationFailed");
1740 (void) memset(channel_distortion,0,length*
1741 sizeof(*channel_distortion));
1745 case AbsoluteErrorMetric:
1747 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1751 case FuzzErrorMetric:
1753 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1757 case MeanAbsoluteErrorMetric:
1759 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1760 channel_distortion,exception);
1763 case MeanErrorPerPixelErrorMetric:
1765 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1769 case MeanSquaredErrorMetric:
1771 status=GetMeanSquaredDistortion(image,reconstruct_image,
1772 channel_distortion,exception);
1775 case NormalizedCrossCorrelationErrorMetric:
1778 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1779 channel_distortion,exception);
1782 case PeakAbsoluteErrorMetric:
1784 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1785 channel_distortion,exception);
1788 case PeakSignalToNoiseRatioErrorMetric:
1790 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1791 channel_distortion,exception);
1794 case PerceptualHashErrorMetric:
1796 status=GetPerceptualHashDistortion(image,reconstruct_image,
1797 channel_distortion,exception);
1800 case RootMeanSquaredErrorMetric:
1802 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1803 channel_distortion,exception);
1806 case StructuralSimilarityErrorMetric:
1808 status=GetStructuralSimilarityDistortion(image,reconstruct_image,
1809 channel_distortion,exception);
1812 case StructuralDissimilarityErrorMetric:
1814 status=GetStructuralDisimilarityDistortion(image,reconstruct_image,
1815 channel_distortion,exception);
1819 if (status == MagickFalse)
1821 channel_distortion=(
double *) RelinquishMagickMemory(channel_distortion);
1822 return((
double *) NULL);
1824 return(channel_distortion);
1855MagickExport MagickBooleanType IsImagesEqual(
const Image *image,
1869 assert(image != (
Image *) NULL);
1870 assert(image->signature == MagickCoreSignature);
1871 assert(reconstruct_image != (
const Image *) NULL);
1872 assert(reconstruct_image->signature == MagickCoreSignature);
1873 rows=MagickMax(image->rows,reconstruct_image->rows);
1874 columns=MagickMax(image->columns,reconstruct_image->columns);
1875 image_view=AcquireVirtualCacheView(image,exception);
1876 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1877 for (y=0; y < (ssize_t) rows; y++)
1886 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1887 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1888 if ((p == (
const Quantum *) NULL) || (q == (
const Quantum *) NULL))
1890 for (x=0; x < (ssize_t) columns; x++)
1895 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1900 PixelChannel channel = GetPixelChannelChannel(image,i);
1901 PixelTrait traits = GetPixelChannelTraits(image,channel);
1902 PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
1904 if ((traits == UndefinedPixelTrait) ||
1905 (reconstruct_traits == UndefinedPixelTrait) ||
1906 ((reconstruct_traits & UpdatePixelTrait) == 0))
1908 distance=fabs((
double) p[i]-(
double) GetPixelChannel(reconstruct_image,
1910 if (distance >= MagickEpsilon)
1913 if (i < (ssize_t) GetPixelChannels(image))
1915 p+=(ptrdiff_t) GetPixelChannels(image);
1916 q+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
1918 if (x < (ssize_t) columns)
1921 reconstruct_view=DestroyCacheView(reconstruct_view);
1922 image_view=DestroyCacheView(image_view);
1923 return(y < (ssize_t) rows ? MagickFalse : MagickTrue);
1975MagickExport MagickBooleanType SetImageColorMetric(
Image *image,
1986 mean_error_per_pixel;
1998 assert(image != (
Image *) NULL);
1999 assert(image->signature == MagickCoreSignature);
2000 assert(reconstruct_image != (
const Image *) NULL);
2001 assert(reconstruct_image->signature == MagickCoreSignature);
2004 mean_error_per_pixel=0.0;
2006 rows=MagickMax(image->rows,reconstruct_image->rows);
2007 columns=MagickMax(image->columns,reconstruct_image->columns);
2008 image_view=AcquireVirtualCacheView(image,exception);
2009 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
2010 for (y=0; y < (ssize_t) rows; y++)
2019 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
2020 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
2021 if ((p == (
const Quantum *) NULL) || (q == (Quantum *) NULL))
2023 for (x=0; x < (ssize_t) columns; x++)
2028 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2033 PixelChannel channel = GetPixelChannelChannel(image,i);
2034 PixelTrait traits = GetPixelChannelTraits(image,channel);
2035 PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
2037 if ((traits == UndefinedPixelTrait) ||
2038 (reconstruct_traits == UndefinedPixelTrait) ||
2039 ((reconstruct_traits & UpdatePixelTrait) == 0))
2041 distance=fabs((
double) p[i]-(
double) GetPixelChannel(reconstruct_image,
2043 if (distance >= MagickEpsilon)
2045 mean_error_per_pixel+=distance;
2046 mean_error+=distance*distance;
2047 if (distance > maximum_error)
2048 maximum_error=distance;
2052 p+=(ptrdiff_t) GetPixelChannels(image);
2053 q+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
2056 reconstruct_view=DestroyCacheView(reconstruct_view);
2057 image_view=DestroyCacheView(image_view);
2058 image->error.mean_error_per_pixel=(double) (mean_error_per_pixel/area);
2059 image->error.normalized_mean_error=(double) (QuantumScale*QuantumScale*
2061 image->error.normalized_maximum_error=(double) (QuantumScale*maximum_error);
2062 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
2106#if defined(MAGICKCORE_HDRI_SUPPORT) && defined(MAGICKCORE_FFTW_DELEGATE)
2107static Image *CrossCorrelationImage(
const Image *alpha_image,
2113 *complex_multiplication,
2120 clone_image=CloneImage(beta_image,0,0,MagickTrue,exception);
2121 if (clone_image == (
Image *) NULL)
2122 return(clone_image);
2123 (void) SetImageArtifact(clone_image,
"fourier:normalize",
"inverse");
2124 fft_images=ForwardFourierTransformImage(clone_image,MagickFalse,
2126 clone_image=DestroyImageList(clone_image);
2127 if (fft_images == (
Image *) NULL)
2132 complex_conjugate=ComplexImages(fft_images,ConjugateComplexOperator,
2134 fft_images=DestroyImageList(fft_images);
2135 if (complex_conjugate == (
Image *) NULL)
2136 return(complex_conjugate);
2140 clone_image=CloneImage(alpha_image,0,0,MagickTrue,exception);
2141 if (clone_image == (
Image *) NULL)
2143 complex_conjugate=DestroyImageList(complex_conjugate);
2144 return(clone_image);
2146 (void) SetImageArtifact(clone_image,
"fourier:normalize",
"inverse");
2147 fft_images=ForwardFourierTransformImage(clone_image,MagickFalse,exception);
2148 clone_image=DestroyImageList(clone_image);
2149 if (fft_images == (
Image *) NULL)
2151 complex_conjugate=DestroyImageList(complex_conjugate);
2154 complex_conjugate->next->next=fft_images;
2158 DisableCompositeClampUnlessSpecified(complex_conjugate);
2159 complex_multiplication=ComplexImages(complex_conjugate,
2160 MultiplyComplexOperator,exception);
2161 complex_conjugate=DestroyImageList(complex_conjugate);
2162 if (fft_images == (
Image *) NULL)
2167 cross_correlation=InverseFourierTransformImage(complex_multiplication,
2168 complex_multiplication->next,MagickFalse,exception);
2169 complex_multiplication=DestroyImageList(complex_multiplication);
2170 return(cross_correlation);
2173static Image *NCCDivideImage(
const Image *alpha_image,
const Image *beta_image,
2192 divide_image=CloneImage(alpha_image,0,0,MagickTrue,exception);
2193 if (divide_image == (
Image *) NULL)
2194 return(divide_image);
2196 alpha_view=AcquireAuthenticCacheView(divide_image,exception);
2197 beta_view=AcquireVirtualCacheView(beta_image,exception);
2198#if defined(MAGICKCORE_OPENMP_SUPPORT)
2199 #pragma omp parallel for schedule(static) shared(status) \
2200 magick_number_threads(beta_image,divide_image,divide_image->rows,1)
2202 for (y=0; y < (ssize_t) divide_image->rows; y++)
2213 if (status == MagickFalse)
2215 p=GetCacheViewVirtualPixels(beta_view,0,y,beta_image->columns,1,
2217 q=GetCacheViewAuthenticPixels(alpha_view,0,y,divide_image->columns,1,
2219 if ((p == (
const Quantum *) NULL) || (q == (Quantum *) NULL))
2224 for (x=0; x < (ssize_t) divide_image->columns; x++)
2229 for (i=0; i < (ssize_t) GetPixelChannels(divide_image); i++)
2231 PixelChannel channel = GetPixelChannelChannel(divide_image,i);
2232 PixelTrait traits = GetPixelChannelTraits(divide_image,channel);
2233 if ((traits & UpdatePixelTrait) == 0)
2235 if (fabs(p[i]) >= MagickEpsilon)
2236 q[i]=(Quantum) ((
double) q[i]*PerceptibleReciprocal(QuantumScale*
2239 p+=(ptrdiff_t) GetPixelChannels(beta_image);
2240 q+=(ptrdiff_t) GetPixelChannels(divide_image);
2242 if (SyncCacheViewAuthenticPixels(alpha_view,exception) == MagickFalse)
2245 beta_view=DestroyCacheView(beta_view);
2246 alpha_view=DestroyCacheView(alpha_view);
2247 if (status == MagickFalse)
2248 divide_image=DestroyImage(divide_image);
2249 return(divide_image);
2252static MagickBooleanType NCCMaximaImage(
const Image *image,
double *maxima,
2271 image_view=AcquireVirtualCacheView(image,exception);
2272 for (y=0; y < (ssize_t) image->rows; y++)
2280 if (status == MagickFalse)
2282 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
2283 if (p == (
const Quantum *) NULL)
2288 for (x=0; x < (ssize_t) image->columns; x++)
2297 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2299 PixelChannel channel = GetPixelChannelChannel(image,i);
2300 PixelTrait traits = GetPixelChannelTraits(image,channel);
2301 if ((traits & UpdatePixelTrait) == 0)
2306 if ((channels != 0) && ((sum/channels) > *maxima))
2308 *maxima=sum/channels;
2312 p+=(ptrdiff_t) GetPixelChannels(image);
2314 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2317 image_view=DestroyCacheView(image_view);
2321static MagickBooleanType NCCMultiplyImage(
Image *image,
const double factor,
2337 image_view=AcquireAuthenticCacheView(image,exception);
2338#if defined(MAGICKCORE_OPENMP_SUPPORT)
2339 #pragma omp parallel for schedule(static) shared(status) \
2340 magick_number_threads(image,image,image->rows,1)
2342 for (y=0; y < (ssize_t) image->rows; y++)
2350 if (status == MagickFalse)
2352 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2353 if (q == (Quantum *) NULL)
2358 for (x=0; x < (ssize_t) image->columns; x++)
2363 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2365 PixelChannel channel = GetPixelChannelChannel(image,i);
2366 PixelTrait traits = GetPixelChannelTraits(image,channel);
2367 if ((traits & UpdatePixelTrait) == 0)
2370 q[i]=(Quantum) ((
double) q[i]*QuantumScale*
2371 channel_statistics[channel].standard_deviation);
2372 q[i]=(Quantum) ((
double) q[i]*factor);
2374 q+=(ptrdiff_t) GetPixelChannels(image);
2376 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2379 image_view=DestroyCacheView(image_view);
2400 square_image=CloneImage(image,0,0,MagickTrue,exception);
2401 if (square_image == (
Image *) NULL)
2402 return(square_image);
2404 image_view=AcquireAuthenticCacheView(square_image,exception);
2405#if defined(MAGICKCORE_OPENMP_SUPPORT)
2406 #pragma omp parallel for schedule(static) shared(status) \
2407 magick_number_threads(square_image,square_image,square_image->rows,1)
2409 for (y=0; y < (ssize_t) square_image->rows; y++)
2417 if (status == MagickFalse)
2419 q=GetCacheViewAuthenticPixels(image_view,0,y,square_image->columns,1,
2421 if (q == (Quantum *) NULL)
2426 for (x=0; x < (ssize_t) square_image->columns; x++)
2431 for (i=0; i < (ssize_t) GetPixelChannels(square_image); i++)
2433 PixelChannel channel = GetPixelChannelChannel(square_image,i);
2434 PixelTrait traits = GetPixelChannelTraits(square_image,channel);
2435 if ((traits & UpdatePixelTrait) == 0)
2437 q[i]=(Quantum) ((
double) q[i]*QuantumScale*(
double) q[i]);
2439 q+=(ptrdiff_t) GetPixelChannels(square_image);
2441 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2444 image_view=DestroyCacheView(image_view);
2445 if (status == MagickFalse)
2446 square_image=DestroyImage(square_image);
2447 return(square_image);
2450static Image *NCCSubtractImageMean(
const Image *alpha_image,
2470 gamma_image=CloneImage(beta_image,alpha_image->columns,alpha_image->rows,
2471 MagickTrue,exception);
2472 if (gamma_image == (
Image *) NULL)
2473 return(gamma_image);
2475 image_view=AcquireAuthenticCacheView(gamma_image,exception);
2476 beta_view=AcquireVirtualCacheView(beta_image,exception);
2477#if defined(MAGICKCORE_OPENMP_SUPPORT)
2478 #pragma omp parallel for schedule(static) shared(status) \
2479 magick_number_threads(beta_image,gamma_image,gamma_image->rows,1)
2481 for (y=0; y < (ssize_t) gamma_image->rows; y++)
2492 if (status == MagickFalse)
2494 p=GetCacheViewVirtualPixels(beta_view,0,y,beta_image->columns,1,
2496 q=GetCacheViewAuthenticPixels(image_view,0,y,gamma_image->columns,1,
2498 if ((p == (
const Quantum *) NULL) || (q == (Quantum *) NULL))
2503 for (x=0; x < (ssize_t) gamma_image->columns; x++)
2508 for (i=0; i < (ssize_t) GetPixelChannels(gamma_image); i++)
2510 PixelChannel channel = GetPixelChannelChannel(gamma_image,i);
2511 PixelTrait traits = GetPixelChannelTraits(gamma_image,channel);
2512 if ((traits & UpdatePixelTrait) == 0)
2514 if ((x >= (ssize_t) beta_image->columns) ||
2515 (y >= (ssize_t) beta_image->rows))
2518 q[i]=(Quantum) ((
double) p[i]-channel_statistics[channel].mean);
2520 p+=(ptrdiff_t) GetPixelChannels(beta_image);
2521 q+=(ptrdiff_t) GetPixelChannels(gamma_image);
2523 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2526 beta_view=DestroyCacheView(beta_view);
2527 image_view=DestroyCacheView(image_view);
2528 if (status == MagickFalse)
2529 gamma_image=DestroyImage(gamma_image);
2530 return(gamma_image);
2533static Image *NCCUnityImage(
const Image *alpha_image,
const Image *beta_image,
2551 unity_image=CloneImage(alpha_image,alpha_image->columns,alpha_image->rows,
2552 MagickTrue,exception);
2553 if (unity_image == (
Image *) NULL)
2554 return(unity_image);
2555 if (SetImageStorageClass(unity_image,DirectClass,exception) == MagickFalse)
2556 return(DestroyImage(unity_image));
2558 image_view=AcquireAuthenticCacheView(unity_image,exception);
2559#if defined(MAGICKCORE_OPENMP_SUPPORT)
2560 #pragma omp parallel for schedule(static) shared(status) \
2561 magick_number_threads(unity_image,unity_image,unity_image->rows,1)
2563 for (y=0; y < (ssize_t) unity_image->rows; y++)
2571 if (status == MagickFalse)
2573 q=GetCacheViewAuthenticPixels(image_view,0,y,unity_image->columns,1,
2575 if (q == (Quantum *) NULL)
2580 for (x=0; x < (ssize_t) unity_image->columns; x++)
2585 for (i=0; i < (ssize_t) GetPixelChannels(unity_image); i++)
2587 PixelChannel channel = GetPixelChannelChannel(unity_image,i);
2588 PixelTrait traits = GetPixelChannelTraits(unity_image,channel);
2589 if ((traits & UpdatePixelTrait) == 0)
2592 if ((x >= (ssize_t) beta_image->columns) ||
2593 (y >= (ssize_t) beta_image->rows))
2596 q+=(ptrdiff_t) GetPixelChannels(unity_image);
2598 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2601 image_view=DestroyCacheView(image_view);
2602 if (status == MagickFalse)
2603 unity_image=DestroyImage(unity_image);
2604 return(unity_image);
2607static Image *NCCVarianceImage(
Image *alpha_image,
const Image *beta_image,
2626 variance_image=CloneImage(alpha_image,0,0,MagickTrue,exception);
2627 if (variance_image == (
Image *) NULL)
2628 return(variance_image);
2630 image_view=AcquireAuthenticCacheView(variance_image,exception);
2631 beta_view=AcquireVirtualCacheView(beta_image,exception);
2632#if defined(MAGICKCORE_OPENMP_SUPPORT)
2633 #pragma omp parallel for schedule(static) shared(status) \
2634 magick_number_threads(beta_image,variance_image,variance_image->rows,1)
2636 for (y=0; y < (ssize_t) variance_image->rows; y++)
2647 if (status == MagickFalse)
2649 p=GetCacheViewVirtualPixels(beta_view,0,y,beta_image->columns,1,
2651 q=GetCacheViewAuthenticPixels(image_view,0,y,variance_image->columns,1,
2653 if ((p == (
const Quantum *) NULL) || (q == (Quantum *) NULL))
2658 for (x=0; x < (ssize_t) variance_image->columns; x++)
2663 for (i=0; i < (ssize_t) GetPixelChannels(variance_image); i++)
2665 PixelChannel channel = GetPixelChannelChannel(variance_image,i);
2666 PixelTrait traits = GetPixelChannelTraits(variance_image,channel);
2667 if ((traits & UpdatePixelTrait) == 0)
2669 q[i]=(Quantum) ((
double) ClampToQuantum((
double) QuantumRange*sqrt(fabs(
2670 QuantumScale*((
double) q[i]-(
double) p[i]))))/
2671 sqrt((
double) QuantumRange));
2673 p+=(ptrdiff_t) GetPixelChannels(beta_image);
2674 q+=(ptrdiff_t) GetPixelChannels(variance_image);
2676 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2679 beta_view=DestroyCacheView(beta_view);
2680 image_view=DestroyCacheView(image_view);
2681 if (status == MagickFalse)
2682 variance_image=DestroyImage(variance_image);
2683 return(variance_image);
2686static Image *NCCSimilarityImage(
const Image *image,
const Image *reference,
2689#define DestroySimilarityResources() \
2691 if (channel_statistics != (ChannelStatistics *) NULL) \
2692 channel_statistics=(ChannelStatistics *) \
2693 RelinquishMagickMemory(channel_statistics); \
2694 if (beta_image != (Image *) NULL) \
2695 beta_image=DestroyImage(beta_image); \
2696 if (gamma_image != (Image *) NULL) \
2697 gamma_image=DestroyImage(gamma_image); \
2698 if (ncc_image != (Image *) NULL) \
2699 ncc_image=DestroyImage(ncc_image); \
2700 if (normalize_image != (Image *) NULL) \
2701 normalize_image=DestroyImage(normalize_image); \
2702 if (square_image != (Image *) NULL) \
2703 square_image=DestroyImage(square_image); \
2704 if (unity_image != (Image *) NULL) \
2705 unity_image=DestroyImage(unity_image); \
2707#define ThrowSimilarityException() \
2709 DestroySimilarityResources() \
2710 return((Image *) NULL); \
2720 *beta_image = (
Image *) NULL,
2721 *correlation_image = (
Image *) NULL,
2722 *gamma_image = (
Image *) NULL,
2723 *ncc_image = (
Image *) NULL,
2724 *normalize_image = (
Image *) NULL,
2725 *square_image = (
Image *) NULL,
2726 *unity_image = (
Image *) NULL;
2738 square_image=NCCSquareImage(image,exception);
2739 if (square_image == (
Image *) NULL)
2740 ThrowSimilarityException();
2741 unity_image=NCCUnityImage(image,reference,exception);
2742 if (unity_image == (
Image *) NULL)
2743 ThrowSimilarityException();
2747 ncc_image=CrossCorrelationImage(square_image,unity_image,exception);
2748 square_image=DestroyImage(square_image);
2749 if (ncc_image == (
Image *) NULL)
2750 ThrowSimilarityException();
2751 status=NCCMultiplyImage(ncc_image,(
double) QuantumRange*reference->columns*
2753 if (status == MagickFalse)
2754 ThrowSimilarityException();
2758 gamma_image=CrossCorrelationImage(image,unity_image,exception);
2759 unity_image=DestroyImage(unity_image);
2760 if (gamma_image == (
Image *) NULL)
2761 ThrowSimilarityException();
2762 square_image=NCCSquareImage(gamma_image,exception);
2763 gamma_image=DestroyImage(gamma_image);
2764 status=NCCMultiplyImage(square_image,(
double) QuantumRange,
2766 if (status == MagickFalse)
2767 ThrowSimilarityException();
2771 gamma_image=NCCVarianceImage(ncc_image,square_image,exception);
2772 square_image=DestroyImage(square_image);
2773 ncc_image=DestroyImage(ncc_image);
2774 if (gamma_image == (
Image *) NULL)
2775 ThrowSimilarityException();
2776 channel_statistics=GetImageStatistics(reference,exception);
2778 ThrowSimilarityException();
2782 status=NCCMultiplyImage(gamma_image,1.0,channel_statistics,exception);
2783 if (status == MagickFalse)
2784 ThrowSimilarityException();
2785 normalize_image=NCCSubtractImageMean(image,reference,channel_statistics,
2787 if (normalize_image == (
Image *) NULL)
2788 ThrowSimilarityException();
2789 ncc_image=CrossCorrelationImage(image,normalize_image,exception);
2790 normalize_image=DestroyImage(normalize_image);
2791 if (ncc_image == (
Image *) NULL)
2792 ThrowSimilarityException();
2796 beta_image=NCCDivideImage(ncc_image,gamma_image,exception);
2797 ncc_image=DestroyImage(ncc_image);
2798 gamma_image=DestroyImage(gamma_image);
2799 if (beta_image == (
Image *) NULL)
2800 ThrowSimilarityException();
2801 (void) ResetImagePage(beta_image,
"0x0+0+0");
2802 SetGeometry(image,&geometry);
2803 geometry.width=image->columns-reference->columns;
2804 geometry.height=image->rows-reference->rows;
2808 correlation_image=CropImage(beta_image,&geometry,exception);
2809 beta_image=DestroyImage(beta_image);
2810 if (correlation_image == (
Image *) NULL)
2811 ThrowSimilarityException();
2812 (void) ResetImagePage(correlation_image,
"0x0+0+0");
2816 status=GrayscaleImage(correlation_image,AveragePixelIntensityMethod,
2818 if (status == MagickFalse)
2819 ThrowSimilarityException();
2820 status=NCCMaximaImage(correlation_image,&maxima,offset,exception);
2821 if (status == MagickFalse)
2823 correlation_image=DestroyImage(correlation_image);
2824 ThrowSimilarityException();
2826 *similarity_metric=1.0-QuantumScale*maxima;
2827 DestroySimilarityResources();
2828 return(correlation_image);
2832static double GetSimilarityMetric(
const Image *image,
const Image *reference,
2833 const MetricType metric,
const ssize_t x_offset,
const ssize_t y_offset,
2848 SetGeometry(reference,&geometry);
2849 geometry.x=x_offset;
2850 geometry.y=y_offset;
2851 similarity_image=CropImage(image,&geometry,exception);
2852 if (similarity_image == (
Image *) NULL)
2855 status=GetImageDistortion(similarity_image,reference,metric,&distortion,
2857 similarity_image=DestroyImage(similarity_image);
2858 if (status == MagickFalse)
2863MagickExport
Image *SimilarityImage(
const Image *image,
const Image *reference,
2864 const MetricType metric,
const double similarity_threshold,
2867#define SimilarityImageTag "Similarity/Image"
2873 *similarity_image = (
Image *) NULL;
2884 assert(image != (
const Image *) NULL);
2885 assert(image->signature == MagickCoreSignature);
2887 assert(exception->signature == MagickCoreSignature);
2889 if (IsEventLogging() != MagickFalse)
2890 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
2891 SetGeometry(reference,offset);
2892 *similarity_metric=MagickMaximumValue;
2893#if defined(MAGICKCORE_HDRI_SUPPORT) && defined(MAGICKCORE_FFTW_DELEGATE)
2894 if ((image->channels & ReadMaskChannel) == 0)
2896 const char *artifact = GetImageArtifact(image,
"compare:accelerate-ncc");
2897 MagickBooleanType accelerate = (artifact != (
const char *) NULL) &&
2898 (IsStringTrue(artifact) == MagickFalse) ? MagickFalse : MagickTrue;
2899 if ((accelerate != MagickFalse) &&
2900 (metric == NormalizedCrossCorrelationErrorMetric))
2902 similarity_image=NCCSimilarityImage(image,reference,offset,
2903 similarity_metric,exception);
2904 return(similarity_image);
2908 if ((image->columns >= reference->columns) &&
2909 (image->rows >= reference->rows))
2910 similarity_image=CloneImage(image,image->columns-reference->columns+1,
2911 image->rows-reference->rows+1,MagickTrue,exception);
2912 if (similarity_image == (
Image *) NULL)
2913 return((
Image *) NULL);
2914 status=SetImageStorageClass(similarity_image,DirectClass,exception);
2915 if (status == MagickFalse)
2917 similarity_image=DestroyImage(similarity_image);
2918 return((
Image *) NULL);
2920 (void) SetImageAlphaChannel(similarity_image,DeactivateAlphaChannel,
2927 similarity_view=AcquireAuthenticCacheView(similarity_image,exception);
2928#if defined(MAGICKCORE_OPENMP_SUPPORT)
2929 #pragma omp parallel for schedule(static) \
2930 shared(progress,status,similarity_metric) \
2931 magick_number_threads(image,image,image->rows-reference->rows+1,1)
2933 for (y=0; y < (ssize_t) (image->rows-reference->rows+1); y++)
2944 if (status == MagickFalse)
2946#if defined(MAGICKCORE_OPENMP_SUPPORT)
2947 #pragma omp flush(similarity_metric)
2949 if (*similarity_metric <= similarity_threshold)
2951 q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
2953 if (q == (Quantum *) NULL)
2958 for (x=0; x < (ssize_t) (image->columns-reference->columns+1); x++)
2963#if defined(MAGICKCORE_OPENMP_SUPPORT)
2964 #pragma omp flush(similarity_metric)
2966 if (*similarity_metric <= similarity_threshold)
2968 similarity=GetSimilarityMetric(image,reference,metric,x,y,exception);
2969 if (metric == PeakSignalToNoiseRatioErrorMetric)
2971 if ((metric == NormalizedCrossCorrelationErrorMetric) ||
2972 (metric == UndefinedErrorMetric))
2973 similarity=1.0-similarity;
2974#if defined(MAGICKCORE_OPENMP_SUPPORT)
2975 #pragma omp critical (MagickCore_SimilarityImage)
2977 if (similarity < *similarity_metric)
2981 *similarity_metric=similarity;
2983 if (metric == PerceptualHashErrorMetric)
2984 similarity=MagickMin(0.01*similarity,1.0);
2985 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2987 PixelChannel channel = GetPixelChannelChannel(image,i);
2988 PixelTrait traits = GetPixelChannelTraits(image,channel);
2989 PixelTrait similarity_traits=GetPixelChannelTraits(similarity_image,
2991 if ((traits == UndefinedPixelTrait) ||
2992 (similarity_traits == UndefinedPixelTrait) ||
2993 ((similarity_traits & UpdatePixelTrait) == 0))
2995 SetPixelChannel(similarity_image,channel,ClampToQuantum((
double)
2996 QuantumRange-(
double) QuantumRange*similarity),q);
2998 q+=(ptrdiff_t) GetPixelChannels(similarity_image);
3000 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
3002 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3007#if defined(MAGICKCORE_OPENMP_SUPPORT)
3011 proceed=SetImageProgress(image,SimilarityImageTag,progress,image->rows);
3012 if (proceed == MagickFalse)
3016 similarity_view=DestroyCacheView(similarity_view);
3017 if (status == MagickFalse)
3018 similarity_image=DestroyImage(similarity_image);
3019 return(similarity_image);