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/string-private.h"
74 #include "MagickCore/thread-private.h"
75 #include "MagickCore/transform.h"
76 #include "MagickCore/utility.h"
77 #include "MagickCore/version.h"
112 MagickExport
Image *CompareImages(
Image *image,
const Image *reconstruct_image,
113 const MetricType metric,
double *distortion,
ExceptionInfo *exception)
149 assert(image != (
Image *) NULL);
150 assert(image->signature == MagickCoreSignature);
151 assert(reconstruct_image != (
const Image *) NULL);
152 assert(reconstruct_image->signature == MagickCoreSignature);
153 assert(distortion != (
double *) NULL);
154 if (IsEventLogging() != MagickFalse)
155 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
157 status=GetImageDistortion(image,reconstruct_image,metric,distortion,
159 if (status == MagickFalse)
160 return((
Image *) NULL);
161 columns=MagickMax(image->columns,reconstruct_image->columns);
162 rows=MagickMax(image->rows,reconstruct_image->rows);
163 SetGeometry(image,&geometry);
164 geometry.width=columns;
165 geometry.height=rows;
166 clone_image=CloneImage(image,0,0,MagickTrue,exception);
167 if (clone_image == (
Image *) NULL)
168 return((
Image *) NULL);
169 (void) SetImageMask(clone_image,ReadPixelMask,(
Image *) NULL,exception);
170 difference_image=ExtentImage(clone_image,&geometry,exception);
171 clone_image=DestroyImage(clone_image);
172 if (difference_image == (
Image *) NULL)
173 return((
Image *) NULL);
174 (void) SetImageAlphaChannel(difference_image,OpaqueAlphaChannel,exception);
175 highlight_image=CloneImage(image,columns,rows,MagickTrue,exception);
176 if (highlight_image == (
Image *) NULL)
178 difference_image=DestroyImage(difference_image);
179 return((
Image *) NULL);
181 status=SetImageStorageClass(highlight_image,DirectClass,exception);
182 if (status == MagickFalse)
184 difference_image=DestroyImage(difference_image);
185 highlight_image=DestroyImage(highlight_image);
186 return((
Image *) NULL);
188 (void) SetImageMask(highlight_image,ReadPixelMask,(
Image *) NULL,exception);
189 (void) SetImageAlphaChannel(highlight_image,OpaqueAlphaChannel,exception);
190 (void) QueryColorCompliance(
"#f1001ecc",AllCompliance,&highlight,exception);
191 artifact=GetImageArtifact(image,
"compare:highlight-color");
192 if (artifact != (
const char *) NULL)
193 (void) QueryColorCompliance(artifact,AllCompliance,&highlight,exception);
194 (void) QueryColorCompliance(
"#ffffffcc",AllCompliance,&lowlight,exception);
195 artifact=GetImageArtifact(image,
"compare:lowlight-color");
196 if (artifact != (
const char *) NULL)
197 (void) QueryColorCompliance(artifact,AllCompliance,&lowlight,exception);
198 (void) QueryColorCompliance(
"#888888cc",AllCompliance,&masklight,exception);
199 artifact=GetImageArtifact(image,
"compare:masklight-color");
200 if (artifact != (
const char *) NULL)
201 (void) QueryColorCompliance(artifact,AllCompliance,&masklight,exception);
206 fuzz=GetFuzzyColorDistance(image,reconstruct_image);
207 image_view=AcquireVirtualCacheView(image,exception);
208 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
209 highlight_view=AcquireAuthenticCacheView(highlight_image,exception);
210 #if defined(MAGICKCORE_OPENMP_SUPPORT)
211 #pragma omp parallel for schedule(static) shared(status) \
212 magick_number_threads(image,highlight_image,rows,1)
214 for (y=0; y < (ssize_t) rows; y++)
229 if (status == MagickFalse)
231 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
232 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
233 r=QueueCacheViewAuthenticPixels(highlight_view,0,y,columns,1,exception);
234 if ((p == (
const Quantum *) NULL) || (q == (
const Quantum *) NULL) ||
235 (r == (Quantum *) NULL))
240 for (x=0; x < (ssize_t) columns; x++)
252 if ((GetPixelReadMask(image,p) <= (QuantumRange/2)) ||
253 (GetPixelReadMask(reconstruct_image,q) <= (QuantumRange/2)))
255 SetPixelViaPixelInfo(highlight_image,&masklight,r);
256 p+=GetPixelChannels(image);
257 q+=GetPixelChannels(reconstruct_image);
258 r+=GetPixelChannels(highlight_image);
261 difference=MagickFalse;
262 Sa=QuantumScale*GetPixelAlpha(image,p);
263 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
264 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
270 PixelChannel channel = GetPixelChannelChannel(image,i);
271 PixelTrait traits = GetPixelChannelTraits(image,channel);
272 PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
274 if ((traits == UndefinedPixelTrait) ||
275 (reconstruct_traits == UndefinedPixelTrait) ||
276 ((reconstruct_traits & UpdatePixelTrait) == 0))
278 if (channel == AlphaPixelChannel)
279 pixel=(double) p[i]-GetPixelChannel(reconstruct_image,channel,q);
281 pixel=Sa*p[i]-Da*GetPixelChannel(reconstruct_image,channel,q);
282 distance=pixel*pixel;
283 if (distance >= fuzz)
285 difference=MagickTrue;
289 if (difference == MagickFalse)
290 SetPixelViaPixelInfo(highlight_image,&lowlight,r);
292 SetPixelViaPixelInfo(highlight_image,&highlight,r);
293 p+=GetPixelChannels(image);
294 q+=GetPixelChannels(reconstruct_image);
295 r+=GetPixelChannels(highlight_image);
297 sync=SyncCacheViewAuthenticPixels(highlight_view,exception);
298 if (sync == MagickFalse)
301 highlight_view=DestroyCacheView(highlight_view);
302 reconstruct_view=DestroyCacheView(reconstruct_view);
303 image_view=DestroyCacheView(image_view);
304 (void) CompositeImage(difference_image,highlight_image,image->compose,
305 MagickTrue,0,0,exception);
306 highlight_image=DestroyImage(highlight_image);
307 if (status == MagickFalse)
308 difference_image=DestroyImage(difference_image);
309 return(difference_image);
346 static MagickBooleanType GetAbsoluteDistortion(
const Image *image,
370 fuzz=GetFuzzyColorDistance(image,reconstruct_image);
371 rows=MagickMax(image->rows,reconstruct_image->rows);
372 columns=MagickMax(image->columns,reconstruct_image->columns);
373 image_view=AcquireVirtualCacheView(image,exception);
374 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
375 #if defined(MAGICKCORE_OPENMP_SUPPORT)
376 #pragma omp parallel for schedule(static) shared(status) \
377 magick_number_threads(image,image,rows,1)
379 for (y=0; y < (ssize_t) rows; y++)
382 channel_distortion[MaxPixelChannels+1];
392 if (status == MagickFalse)
394 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
395 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
396 if ((p == (
const Quantum *) NULL) || (q == (
const Quantum *) NULL))
401 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
402 for (x=0; x < (ssize_t) columns; x++)
414 if ((GetPixelReadMask(image,p) <= (QuantumRange/2)) ||
415 (GetPixelReadMask(reconstruct_image,q) <= (QuantumRange/2)))
417 p+=GetPixelChannels(image);
418 q+=GetPixelChannels(reconstruct_image);
421 difference=MagickFalse;
422 Sa=QuantumScale*GetPixelAlpha(image,p);
423 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
424 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
430 PixelChannel channel = GetPixelChannelChannel(image,i);
431 PixelTrait traits = GetPixelChannelTraits(image,channel);
432 PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
434 if ((traits == UndefinedPixelTrait) ||
435 (reconstruct_traits == UndefinedPixelTrait) ||
436 ((reconstruct_traits & UpdatePixelTrait) == 0))
438 if (channel == AlphaPixelChannel)
439 pixel=(double) p[i]-GetPixelChannel(reconstruct_image,channel,q);
441 pixel=Sa*p[i]-Da*GetPixelChannel(reconstruct_image,channel,q);
442 distance=pixel*pixel;
443 if (distance >= fuzz)
445 channel_distortion[i]++;
446 difference=MagickTrue;
449 if (difference != MagickFalse)
450 channel_distortion[CompositePixelChannel]++;
451 p+=GetPixelChannels(image);
452 q+=GetPixelChannels(reconstruct_image);
454 #if defined(MAGICKCORE_OPENMP_SUPPORT)
455 #pragma omp critical (MagickCore_GetAbsoluteDistortion)
457 for (j=0; j <= MaxPixelChannels; j++)
458 distortion[j]+=channel_distortion[j];
460 reconstruct_view=DestroyCacheView(reconstruct_view);
461 image_view=DestroyCacheView(image_view);
465 static MagickBooleanType GetFuzzDistortion(
const Image *image,
489 rows=MagickMax(image->rows,reconstruct_image->rows);
490 columns=MagickMax(image->columns,reconstruct_image->columns);
492 image_view=AcquireVirtualCacheView(image,exception);
493 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
494 #if defined(MAGICKCORE_OPENMP_SUPPORT)
495 #pragma omp parallel for schedule(static) shared(status) \
496 magick_number_threads(image,image,rows,1)
498 for (y=0; y < (ssize_t) rows; y++)
501 channel_distortion[MaxPixelChannels+1];
513 if (status == MagickFalse)
515 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
516 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
517 if ((p == (
const Quantum *) NULL) || (q == (Quantum *) NULL))
522 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
523 for (x=0; x < (ssize_t) columns; x++)
532 if ((GetPixelReadMask(image,p) <= (QuantumRange/2)) ||
533 (GetPixelReadMask(reconstruct_image,q) <= (QuantumRange/2)))
535 p+=GetPixelChannels(image);
536 q+=GetPixelChannels(reconstruct_image);
539 Sa=QuantumScale*GetPixelAlpha(image,p);
540 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
541 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
546 PixelChannel channel = GetPixelChannelChannel(image,i);
547 PixelTrait traits = GetPixelChannelTraits(image,channel);
548 PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
550 if ((traits == UndefinedPixelTrait) ||
551 (reconstruct_traits == UndefinedPixelTrait) ||
552 ((reconstruct_traits & UpdatePixelTrait) == 0))
554 if (channel == AlphaPixelChannel)
555 distance=QuantumScale*(p[i]-GetPixelChannel(reconstruct_image,
558 distance=QuantumScale*(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
560 channel_distortion[i]+=distance*distance;
561 channel_distortion[CompositePixelChannel]+=distance*distance;
564 p+=GetPixelChannels(image);
565 q+=GetPixelChannels(reconstruct_image);
567 #if defined(MAGICKCORE_OPENMP_SUPPORT)
568 #pragma omp critical (MagickCore_GetFuzzDistortion)
572 for (j=0; j <= MaxPixelChannels; j++)
573 distortion[j]+=channel_distortion[j];
576 reconstruct_view=DestroyCacheView(reconstruct_view);
577 image_view=DestroyCacheView(image_view);
578 area=PerceptibleReciprocal(area);
579 for (j=0; j <= MaxPixelChannels; j++)
581 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
582 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]);
586 static MagickBooleanType GetMeanAbsoluteDistortion(
const Image *image,
610 rows=MagickMax(image->rows,reconstruct_image->rows);
611 columns=MagickMax(image->columns,reconstruct_image->columns);
613 image_view=AcquireVirtualCacheView(image,exception);
614 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
615 #if defined(MAGICKCORE_OPENMP_SUPPORT)
616 #pragma omp parallel for schedule(static) shared(status) \
617 magick_number_threads(image,image,rows,1)
619 for (y=0; y < (ssize_t) rows; y++)
622 channel_distortion[MaxPixelChannels+1];
634 if (status == MagickFalse)
636 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
637 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
638 if ((p == (
const Quantum *) NULL) || (q == (
const Quantum *) NULL))
643 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
644 for (x=0; x < (ssize_t) columns; x++)
653 if ((GetPixelReadMask(image,p) <= (QuantumRange/2)) ||
654 (GetPixelReadMask(reconstruct_image,q) <= (QuantumRange/2)))
656 p+=GetPixelChannels(image);
657 q+=GetPixelChannels(reconstruct_image);
660 Sa=QuantumScale*GetPixelAlpha(image,p);
661 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
662 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
667 PixelChannel channel = GetPixelChannelChannel(image,i);
668 PixelTrait traits = GetPixelChannelTraits(image,channel);
669 PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
671 if ((traits == UndefinedPixelTrait) ||
672 (reconstruct_traits == UndefinedPixelTrait) ||
673 ((reconstruct_traits & UpdatePixelTrait) == 0))
675 if (channel == AlphaPixelChannel)
676 distance=QuantumScale*fabs((
double) (p[i]-(
double)
677 GetPixelChannel(reconstruct_image,channel,q)));
679 distance=QuantumScale*fabs((
double) (Sa*p[i]-Da*
680 GetPixelChannel(reconstruct_image,channel,q)));
681 channel_distortion[i]+=distance;
682 channel_distortion[CompositePixelChannel]+=distance;
685 p+=GetPixelChannels(image);
686 q+=GetPixelChannels(reconstruct_image);
688 #if defined(MAGICKCORE_OPENMP_SUPPORT)
689 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
693 for (j=0; j <= MaxPixelChannels; j++)
694 distortion[j]+=channel_distortion[j];
697 reconstruct_view=DestroyCacheView(reconstruct_view);
698 image_view=DestroyCacheView(image_view);
699 area=PerceptibleReciprocal(area);
700 for (j=0; j <= MaxPixelChannels; j++)
702 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
706 static MagickBooleanType GetMeanErrorPerPixel(
Image *image,
732 rows=MagickMax(image->rows,reconstruct_image->rows);
733 columns=MagickMax(image->columns,reconstruct_image->columns);
734 image_view=AcquireVirtualCacheView(image,exception);
735 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
736 for (y=0; y < (ssize_t) rows; y++)
745 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
746 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
747 if ((p == (
const Quantum *) NULL) || (q == (
const Quantum *) NULL))
752 for (x=0; x < (ssize_t) columns; x++)
761 if ((GetPixelReadMask(image,p) <= (QuantumRange/2)) ||
762 (GetPixelReadMask(reconstruct_image,q) <= (QuantumRange/2)))
764 p+=GetPixelChannels(image);
765 q+=GetPixelChannels(reconstruct_image);
768 Sa=QuantumScale*GetPixelAlpha(image,p);
769 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
770 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
775 PixelChannel channel = GetPixelChannelChannel(image,i);
776 PixelTrait traits = GetPixelChannelTraits(image,channel);
777 PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
779 if ((traits == UndefinedPixelTrait) ||
780 (reconstruct_traits == UndefinedPixelTrait) ||
781 ((reconstruct_traits & UpdatePixelTrait) == 0))
783 if (channel == AlphaPixelChannel)
784 distance=fabs((
double) (p[i]-(
double)
785 GetPixelChannel(reconstruct_image,channel,q)));
787 distance=fabs((
double) (Sa*p[i]-Da*
788 GetPixelChannel(reconstruct_image,channel,q)));
789 distortion[i]+=distance;
790 distortion[CompositePixelChannel]+=distance;
791 mean_error+=distance*distance;
792 if (distance > maximum_error)
793 maximum_error=distance;
796 p+=GetPixelChannels(image);
797 q+=GetPixelChannels(reconstruct_image);
800 reconstruct_view=DestroyCacheView(reconstruct_view);
801 image_view=DestroyCacheView(image_view);
802 area=PerceptibleReciprocal(area);
803 image->error.mean_error_per_pixel=area*distortion[CompositePixelChannel];
804 image->error.normalized_mean_error=area*QuantumScale*QuantumScale*mean_error;
805 image->error.normalized_maximum_error=QuantumScale*maximum_error;
809 static MagickBooleanType GetMeanSquaredDistortion(
const Image *image,
831 rows=MagickMax(image->rows,reconstruct_image->rows);
832 columns=MagickMax(image->columns,reconstruct_image->columns);
834 image_view=AcquireVirtualCacheView(image,exception);
835 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
836 #if defined(MAGICKCORE_OPENMP_SUPPORT)
837 #pragma omp parallel for schedule(static) shared(status) \
838 magick_number_threads(image,image,rows,1)
840 for (y=0; y < (ssize_t) rows; y++)
847 channel_distortion[MaxPixelChannels+1];
855 if (status == MagickFalse)
857 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
858 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
859 if ((p == (
const Quantum *) NULL) || (q == (
const Quantum *) NULL))
864 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
865 for (x=0; x < (ssize_t) columns; x++)
874 if ((GetPixelReadMask(image,p) <= (QuantumRange/2)) ||
875 (GetPixelReadMask(reconstruct_image,q) <= (QuantumRange/2)))
877 p+=GetPixelChannels(image);
878 q+=GetPixelChannels(reconstruct_image);
881 Sa=QuantumScale*GetPixelAlpha(image,p);
882 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
883 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
888 PixelChannel channel = GetPixelChannelChannel(image,i);
889 PixelTrait traits = GetPixelChannelTraits(image,channel);
890 PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
892 if ((traits == UndefinedPixelTrait) ||
893 (reconstruct_traits == UndefinedPixelTrait) ||
894 ((reconstruct_traits & UpdatePixelTrait) == 0))
896 if (channel == AlphaPixelChannel)
897 distance=QuantumScale*(p[i]-GetPixelChannel(reconstruct_image,
900 distance=QuantumScale*(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
902 channel_distortion[i]+=distance*distance;
903 channel_distortion[CompositePixelChannel]+=distance*distance;
906 p+=GetPixelChannels(image);
907 q+=GetPixelChannels(reconstruct_image);
909 #if defined(MAGICKCORE_OPENMP_SUPPORT)
910 #pragma omp critical (MagickCore_GetMeanSquaredError)
914 for (j=0; j <= MaxPixelChannels; j++)
915 distortion[j]+=channel_distortion[j];
918 reconstruct_view=DestroyCacheView(reconstruct_view);
919 image_view=DestroyCacheView(image_view);
920 area=PerceptibleReciprocal(area);
921 for (j=0; j <= MaxPixelChannels; j++)
923 distortion[CompositePixelChannel]/=GetImageChannels(image);
927 static MagickBooleanType GetNormalizedCrossCorrelationDistortion(
928 const Image *image,
const Image *reconstruct_image,
double *distortion,
931 #define SimilarityImageTag "Similarity/Image"
939 *reconstruct_statistics;
964 image_statistics=GetImageStatistics(image,exception);
965 reconstruct_statistics=GetImageStatistics(reconstruct_image,exception);
974 reconstruct_statistics);
979 for (i=0; i <= MaxPixelChannels; i++)
981 rows=MagickMax(image->rows,reconstruct_image->rows);
982 columns=MagickMax(image->columns,reconstruct_image->columns);
984 image_view=AcquireVirtualCacheView(image,exception);
985 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
986 for (y=0; y < (ssize_t) rows; y++)
995 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
996 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
997 if ((p == (
const Quantum *) NULL) || (q == (
const Quantum *) NULL))
1002 for (x=0; x < (ssize_t) columns; x++)
1004 if ((GetPixelReadMask(image,p) <= (QuantumRange/2)) ||
1005 (GetPixelReadMask(reconstruct_image,q) <= (QuantumRange/2)))
1007 p+=GetPixelChannels(image);
1008 q+=GetPixelChannels(reconstruct_image);
1012 p+=GetPixelChannels(image);
1013 q+=GetPixelChannels(reconstruct_image);
1016 area=PerceptibleReciprocal(area);
1017 for (y=0; y < (ssize_t) rows; y++)
1026 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1027 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1028 if ((p == (
const Quantum *) NULL) || (q == (
const Quantum *) NULL))
1033 for (x=0; x < (ssize_t) columns; x++)
1039 if ((GetPixelReadMask(image,p) <= (QuantumRange/2)) ||
1040 (GetPixelReadMask(reconstruct_image,q) <= (QuantumRange/2)))
1042 p+=GetPixelChannels(image);
1043 q+=GetPixelChannels(reconstruct_image);
1046 Sa=QuantumScale*GetPixelAlpha(image,p);
1047 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
1048 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1050 PixelChannel channel = GetPixelChannelChannel(image,i);
1051 PixelTrait traits = GetPixelChannelTraits(image,channel);
1052 PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
1054 if ((traits == UndefinedPixelTrait) ||
1055 (reconstruct_traits == UndefinedPixelTrait) ||
1056 ((reconstruct_traits & UpdatePixelTrait) == 0))
1058 if (channel == AlphaPixelChannel)
1059 distortion[i]+=area*QuantumScale*((double) p[i]-
1060 image_statistics[channel].mean)*(GetPixelChannel(reconstruct_image,
1061 channel,q)-reconstruct_statistics[channel].mean);
1063 distortion[i]+=area*QuantumScale*(Sa*p[i]-
1064 image_statistics[channel].mean)*(Da*GetPixelChannel(
1065 reconstruct_image,channel,q)-reconstruct_statistics[channel].mean);
1067 p+=GetPixelChannels(image);
1068 q+=GetPixelChannels(reconstruct_image);
1070 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1075 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1079 proceed=SetImageProgress(image,SimilarityImageTag,progress,rows);
1080 if (proceed == MagickFalse)
1087 reconstruct_view=DestroyCacheView(reconstruct_view);
1088 image_view=DestroyCacheView(image_view);
1093 distortion[CompositePixelChannel]=0.0;
1094 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1099 PixelChannel channel = GetPixelChannelChannel(image,i);
1100 gamma=image_statistics[channel].standard_deviation*
1101 reconstruct_statistics[channel].standard_deviation;
1102 if (fabs(gamma) >= MagickEpsilon)
1104 gamma=PerceptibleReciprocal(gamma);
1105 distortion[i]=QuantumRange*gamma*distortion[i];
1106 distortion[CompositePixelChannel]+=distortion[i]*distortion[i];
1111 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]/
1117 reconstruct_statistics);
1123 static MagickBooleanType GetPeakAbsoluteDistortion(
const Image *image,
1141 rows=MagickMax(image->rows,reconstruct_image->rows);
1142 columns=MagickMax(image->columns,reconstruct_image->columns);
1143 image_view=AcquireVirtualCacheView(image,exception);
1144 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1145 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1146 #pragma omp parallel for schedule(static) shared(status) \
1147 magick_number_threads(image,image,rows,1)
1149 for (y=0; y < (ssize_t) rows; y++)
1152 channel_distortion[MaxPixelChannels+1];
1162 if (status == MagickFalse)
1164 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1165 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1166 if ((p == (
const Quantum *) NULL) || (q == (
const Quantum *) NULL))
1171 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
1172 for (x=0; x < (ssize_t) columns; x++)
1181 if ((GetPixelReadMask(image,p) <= (QuantumRange/2)) ||
1182 (GetPixelReadMask(reconstruct_image,q) <= (QuantumRange/2)))
1184 p+=GetPixelChannels(image);
1185 q+=GetPixelChannels(reconstruct_image);
1188 Sa=QuantumScale*GetPixelAlpha(image,p);
1189 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
1190 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1195 PixelChannel channel = GetPixelChannelChannel(image,i);
1196 PixelTrait traits = GetPixelChannelTraits(image,channel);
1197 PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
1199 if ((traits == UndefinedPixelTrait) ||
1200 (reconstruct_traits == UndefinedPixelTrait) ||
1201 ((reconstruct_traits & UpdatePixelTrait) == 0))
1203 if (channel == AlphaPixelChannel)
1204 distance=QuantumScale*fabs((
double) (p[i]-(
double)
1205 GetPixelChannel(reconstruct_image,channel,q)));
1207 distance=QuantumScale*fabs((
double) (Sa*p[i]-Da*
1208 GetPixelChannel(reconstruct_image,channel,q)));
1209 if (distance > channel_distortion[i])
1210 channel_distortion[i]=distance;
1211 if (distance > channel_distortion[CompositePixelChannel])
1212 channel_distortion[CompositePixelChannel]=distance;
1214 p+=GetPixelChannels(image);
1215 q+=GetPixelChannels(reconstruct_image);
1217 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1218 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
1220 for (j=0; j <= MaxPixelChannels; j++)
1221 if (channel_distortion[j] > distortion[j])
1222 distortion[j]=channel_distortion[j];
1224 reconstruct_view=DestroyCacheView(reconstruct_view);
1225 image_view=DestroyCacheView(image_view);
1229 static inline double MagickLog10(
const double x)
1231 #define Log10Epsilon (1.0e-11)
1233 if (fabs(x) < Log10Epsilon)
1234 return(log10(Log10Epsilon));
1235 return(log10(fabs(x)));
1238 static MagickBooleanType GetPeakSignalToNoiseRatio(
const Image *image,
1247 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
1248 for (i=0; i <= MaxPixelChannels; i++)
1249 if (fabs(distortion[i]) < MagickEpsilon)
1250 distortion[i]=INFINITY;
1252 distortion[i]=10.0*MagickLog10(1.0)-10.0*MagickLog10(distortion[i]);
1256 static MagickBooleanType GetPerceptualHashDistortion(
const Image *image,
1272 channel_phash=GetImagePerceptualHash(image,exception);
1274 return(MagickFalse);
1275 reconstruct_phash=GetImagePerceptualHash(reconstruct_image,exception);
1280 return(MagickFalse);
1282 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1283 #pragma omp parallel for schedule(static)
1285 for (channel=0; channel < MaxPixelChannels; channel++)
1294 for (i=0; i < MaximumNumberOfImageMoments; i++)
1303 for (j=0; j < (ssize_t) channel_phash[0].number_colorspaces; j++)
1305 alpha=channel_phash[channel].phash[j][i];
1306 beta=reconstruct_phash[channel].phash[j][i];
1307 difference+=(beta-alpha)*(beta-alpha);
1310 distortion[channel]+=difference;
1311 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1312 #pragma omp critical (MagickCore_GetPerceptualHashDistortion)
1314 distortion[CompositePixelChannel]+=difference;
1316 artifact=GetImageArtifact(image,
"phash:normalize");
1317 if ((artifact != (
const char *) NULL) &&
1318 (IsStringTrue(artifact) != MagickFalse))
1323 for (j=0; j <= MaxPixelChannels; j++)
1324 distortion[j]=sqrt(distortion[j]/channel_phash[0].number_channels);
1335 static MagickBooleanType GetRootMeanSquaredDistortion(
const Image *image,
1344 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
1345 for (i=0; i <= MaxPixelChannels; i++)
1346 distortion[i]=sqrt(distortion[i]);
1350 static MagickBooleanType GetStructuralSimilarityDistortion(
const Image *image,
1353 #define SSIMRadius 5.0
1354 #define SSIMSigma 1.5
1355 #define SSIMBlocksize 8
1365 geometry[MagickPathExtent];
1398 artifact=GetImageArtifact(image,
"compare:ssim-radius");
1399 if (artifact != (
const char *) NULL)
1400 radius=StringToDouble(artifact,(
char **) NULL);
1402 artifact=GetImageArtifact(image,
"compare:ssim-sigma");
1403 if (artifact != (
const char *) NULL)
1404 sigma=StringToDouble(artifact,(
char **) NULL);
1405 (void) FormatLocaleString(geometry,MagickPathExtent,
"gaussian:%.20gx%.20g",
1407 kernel_info=AcquireKernelInfo(geometry,exception);
1409 ThrowBinaryException(ResourceLimitError,
"MemoryAllocationFailed",
1411 c1=pow(SSIMK1*SSIML,2.0);
1412 artifact=GetImageArtifact(image,
"compare:ssim-k1");
1413 if (artifact != (
const char *) NULL)
1414 c1=pow(StringToDouble(artifact,(
char **) NULL)*SSIML,2.0);
1415 c2=pow(SSIMK2*SSIML,2.0);
1416 artifact=GetImageArtifact(image,
"compare:ssim-k2");
1417 if (artifact != (
const char *) NULL)
1418 c2=pow(StringToDouble(artifact,(
char **) NULL)*SSIML,2.0);
1421 rows=MagickMax(image->rows,reconstruct_image->rows);
1422 columns=MagickMax(image->columns,reconstruct_image->columns);
1423 image_view=AcquireVirtualCacheView(image,exception);
1424 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1425 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1426 #pragma omp parallel for schedule(static) shared(status) \
1427 magick_number_threads(image,reconstruct_image,rows,1)
1429 for (y=0; y < (ssize_t) rows; y++)
1432 channel_distortion[MaxPixelChannels+1];
1445 if (status == MagickFalse)
1447 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) kernel_info->width/2L),y-
1448 ((ssize_t) kernel_info->height/2L),columns+kernel_info->width,
1449 kernel_info->height,exception);
1450 q=GetCacheViewVirtualPixels(reconstruct_view,-((ssize_t) kernel_info->width/
1451 2L),y-((ssize_t) kernel_info->height/2L),columns+kernel_info->width,
1452 kernel_info->height,exception);
1453 if ((p == (
const Quantum *) NULL) || (q == (
const Quantum *) NULL))
1458 (void) memset(channel_distortion,0,
sizeof(channel_distortion));
1459 for (x=0; x < (ssize_t) columns; x++)
1462 x_pixel_mu[MaxPixelChannels+1],
1463 x_pixel_sigma_squared[MaxPixelChannels+1],
1464 xy_sigma[MaxPixelChannels+1],
1465 y_pixel_mu[MaxPixelChannels+1],
1466 y_pixel_sigma_squared[MaxPixelChannels+1];
1469 *magick_restrict reference,
1470 *magick_restrict target;
1478 if ((GetPixelReadMask(image,p) <= (QuantumRange/2)) ||
1479 (GetPixelReadMask(reconstruct_image,q) <= (QuantumRange/2)))
1481 p+=GetPixelChannels(image);
1482 q+=GetPixelChannels(reconstruct_image);
1485 (void) memset(x_pixel_mu,0,
sizeof(x_pixel_mu));
1486 (void) memset(x_pixel_sigma_squared,0,
sizeof(x_pixel_sigma_squared));
1487 (void) memset(xy_sigma,0,
sizeof(xy_sigma));
1488 (void) memset(x_pixel_sigma_squared,0,
sizeof(y_pixel_sigma_squared));
1489 (void) memset(y_pixel_mu,0,
sizeof(y_pixel_mu));
1490 (void) memset(y_pixel_sigma_squared,0,
sizeof(y_pixel_sigma_squared));
1491 k=kernel_info->values;
1494 for (v=0; v < (ssize_t) kernel_info->height; v++)
1499 for (u=0; u < (ssize_t) kernel_info->width; u++)
1501 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1507 PixelChannel channel = GetPixelChannelChannel(image,i);
1508 PixelTrait traits = GetPixelChannelTraits(image,channel);
1509 PixelTrait reconstruct_traits = GetPixelChannelTraits(
1510 reconstruct_image,channel);
1511 if ((traits == UndefinedPixelTrait) ||
1512 (reconstruct_traits == UndefinedPixelTrait) ||
1513 ((reconstruct_traits & UpdatePixelTrait) == 0))
1515 x_pixel=QuantumScale*reference[i];
1516 x_pixel_mu[i]+=(*k)*x_pixel;
1517 x_pixel_sigma_squared[i]+=(*k)*x_pixel*x_pixel;
1518 y_pixel=QuantumScale*
1519 GetPixelChannel(reconstruct_image,channel,target);
1520 y_pixel_mu[i]+=(*k)*y_pixel;
1521 y_pixel_sigma_squared[i]+=(*k)*y_pixel*y_pixel;
1522 xy_sigma[i]+=(*k)*x_pixel*y_pixel;
1525 reference+=GetPixelChannels(image);
1526 target+=GetPixelChannels(reconstruct_image);
1528 reference+=GetPixelChannels(image)*columns;
1529 target+=GetPixelChannels(reconstruct_image)*columns;
1531 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1536 x_pixel_sigmas_squared,
1540 y_pixel_sigmas_squared;
1542 PixelChannel channel = GetPixelChannelChannel(image,i);
1543 PixelTrait traits = GetPixelChannelTraits(image,channel);
1544 PixelTrait reconstruct_traits = GetPixelChannelTraits(
1545 reconstruct_image,channel);
1546 if ((traits == UndefinedPixelTrait) ||
1547 (reconstruct_traits == UndefinedPixelTrait) ||
1548 ((reconstruct_traits & UpdatePixelTrait) == 0))
1550 x_pixel_mu_squared=x_pixel_mu[i]*x_pixel_mu[i];
1551 y_pixel_mu_squared=y_pixel_mu[i]*y_pixel_mu[i];
1552 xy_mu=x_pixel_mu[i]*y_pixel_mu[i];
1553 xy_sigmas=xy_sigma[i]-xy_mu;
1554 x_pixel_sigmas_squared=x_pixel_sigma_squared[i]-x_pixel_mu_squared;
1555 y_pixel_sigmas_squared=y_pixel_sigma_squared[i]-y_pixel_mu_squared;
1556 ssim=((2.0*xy_mu+c1)*(2.0*xy_sigmas+c2))/
1557 ((x_pixel_mu_squared+y_pixel_mu_squared+c1)*
1558 (x_pixel_sigmas_squared+y_pixel_sigmas_squared+c2));
1559 channel_distortion[i]+=ssim;
1560 channel_distortion[CompositePixelChannel]+=ssim;
1562 p+=GetPixelChannels(image);
1563 q+=GetPixelChannels(reconstruct_image);
1566 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1567 #pragma omp critical (MagickCore_GetStructuralSimilarityDistortion)
1571 for (i=0; i <= MaxPixelChannels; i++)
1572 distortion[i]+=channel_distortion[i];
1575 image_view=DestroyCacheView(image_view);
1576 reconstruct_view=DestroyCacheView(reconstruct_view);
1577 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
1579 PixelChannel channel = GetPixelChannelChannel(image,j);
1580 PixelTrait traits = GetPixelChannelTraits(image,channel);
1581 if ((traits == UndefinedPixelTrait) || ((traits & UpdatePixelTrait) == 0))
1583 distortion[j]/=area;
1585 distortion[CompositePixelChannel]/=area;
1586 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
1587 kernel_info=DestroyKernelInfo(kernel_info);
1591 static MagickBooleanType GetStructuralDisimilarityDistortion(
const Image *image,
1600 status=GetStructuralSimilarityDistortion(image,reconstruct_image,
1601 distortion,exception);
1602 for (i=0; i <= MaxPixelChannels; i++)
1603 distortion[i]=(1.0-(distortion[i]))/2.0;
1607 MagickExport MagickBooleanType GetImageDistortion(
Image *image,
1608 const Image *reconstruct_image,
const MetricType metric,
double *distortion,
1612 *channel_distortion;
1620 assert(image != (
Image *) NULL);
1621 assert(image->signature == MagickCoreSignature);
1622 assert(reconstruct_image != (
const Image *) NULL);
1623 assert(reconstruct_image->signature == MagickCoreSignature);
1624 assert(distortion != (
double *) NULL);
1625 if (IsEventLogging() != MagickFalse)
1626 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
1631 length=MaxPixelChannels+1UL;
1632 channel_distortion=(
double *) AcquireQuantumMemory(length,
1633 sizeof(*channel_distortion));
1634 if (channel_distortion == (
double *) NULL)
1635 ThrowFatalException(ResourceLimitFatalError,
"MemoryAllocationFailed");
1636 (void) memset(channel_distortion,0,length*
1637 sizeof(*channel_distortion));
1640 case AbsoluteErrorMetric:
1642 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1646 case FuzzErrorMetric:
1648 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1652 case MeanAbsoluteErrorMetric:
1654 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1655 channel_distortion,exception);
1658 case MeanErrorPerPixelErrorMetric:
1660 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1664 case MeanSquaredErrorMetric:
1666 status=GetMeanSquaredDistortion(image,reconstruct_image,
1667 channel_distortion,exception);
1670 case NormalizedCrossCorrelationErrorMetric:
1673 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1674 channel_distortion,exception);
1677 case PeakAbsoluteErrorMetric:
1679 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1680 channel_distortion,exception);
1683 case PeakSignalToNoiseRatioErrorMetric:
1685 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1686 channel_distortion,exception);
1689 case PerceptualHashErrorMetric:
1691 status=GetPerceptualHashDistortion(image,reconstruct_image,
1692 channel_distortion,exception);
1695 case RootMeanSquaredErrorMetric:
1697 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1698 channel_distortion,exception);
1701 case StructuralSimilarityErrorMetric:
1703 status=GetStructuralSimilarityDistortion(image,reconstruct_image,
1704 channel_distortion,exception);
1707 case StructuralDissimilarityErrorMetric:
1709 status=GetStructuralDisimilarityDistortion(image,reconstruct_image,
1710 channel_distortion,exception);
1714 *distortion=channel_distortion[CompositePixelChannel];
1715 channel_distortion=(
double *) RelinquishMagickMemory(channel_distortion);
1716 (void) FormatImageProperty(image,
"distortion",
"%.*g",GetMagickPrecision(),
1753 MagickExport
double *GetImageDistortions(
Image *image,
1754 const Image *reconstruct_image,
const MetricType metric,
1758 *channel_distortion;
1766 assert(image != (
Image *) NULL);
1767 assert(image->signature == MagickCoreSignature);
1768 assert(reconstruct_image != (
const Image *) NULL);
1769 assert(reconstruct_image->signature == MagickCoreSignature);
1770 if (IsEventLogging() != MagickFalse)
1771 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
1775 length=MaxPixelChannels+1UL;
1776 channel_distortion=(
double *) AcquireQuantumMemory(length,
1777 sizeof(*channel_distortion));
1778 if (channel_distortion == (
double *) NULL)
1779 ThrowFatalException(ResourceLimitFatalError,
"MemoryAllocationFailed");
1780 (void) memset(channel_distortion,0,length*
1781 sizeof(*channel_distortion));
1785 case AbsoluteErrorMetric:
1787 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1791 case FuzzErrorMetric:
1793 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1797 case MeanAbsoluteErrorMetric:
1799 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1800 channel_distortion,exception);
1803 case MeanErrorPerPixelErrorMetric:
1805 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1809 case MeanSquaredErrorMetric:
1811 status=GetMeanSquaredDistortion(image,reconstruct_image,
1812 channel_distortion,exception);
1815 case NormalizedCrossCorrelationErrorMetric:
1818 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1819 channel_distortion,exception);
1822 case PeakAbsoluteErrorMetric:
1824 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1825 channel_distortion,exception);
1828 case PeakSignalToNoiseRatioErrorMetric:
1830 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1831 channel_distortion,exception);
1834 case PerceptualHashErrorMetric:
1836 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1837 channel_distortion,exception);
1840 case RootMeanSquaredErrorMetric:
1842 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1843 channel_distortion,exception);
1846 case StructuralSimilarityErrorMetric:
1848 status=GetStructuralSimilarityDistortion(image,reconstruct_image,
1849 channel_distortion,exception);
1852 case StructuralDissimilarityErrorMetric:
1854 status=GetStructuralDisimilarityDistortion(image,reconstruct_image,
1855 channel_distortion,exception);
1859 if (status == MagickFalse)
1861 channel_distortion=(
double *) RelinquishMagickMemory(channel_distortion);
1862 return((
double *) NULL);
1864 return(channel_distortion);
1895 MagickExport MagickBooleanType IsImagesEqual(
const Image *image,
1909 assert(image != (
Image *) NULL);
1910 assert(image->signature == MagickCoreSignature);
1911 assert(reconstruct_image != (
const Image *) NULL);
1912 assert(reconstruct_image->signature == MagickCoreSignature);
1913 rows=MagickMax(image->rows,reconstruct_image->rows);
1914 columns=MagickMax(image->columns,reconstruct_image->columns);
1915 image_view=AcquireVirtualCacheView(image,exception);
1916 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1917 for (y=0; y < (ssize_t) rows; y++)
1926 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1927 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1928 if ((p == (
const Quantum *) NULL) || (q == (
const Quantum *) NULL))
1930 for (x=0; x < (ssize_t) columns; x++)
1935 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1940 PixelChannel channel = GetPixelChannelChannel(image,i);
1941 PixelTrait traits = GetPixelChannelTraits(image,channel);
1942 PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
1944 if ((traits == UndefinedPixelTrait) ||
1945 (reconstruct_traits == UndefinedPixelTrait) ||
1946 ((reconstruct_traits & UpdatePixelTrait) == 0))
1948 distance=fabs((
double) (p[i]-(
double) GetPixelChannel(reconstruct_image,
1950 if (distance >= MagickEpsilon)
1953 if (i < (ssize_t) GetPixelChannels(image))
1955 p+=GetPixelChannels(image);
1956 q+=GetPixelChannels(reconstruct_image);
1958 if (x < (ssize_t) columns)
1961 reconstruct_view=DestroyCacheView(reconstruct_view);
1962 image_view=DestroyCacheView(image_view);
1963 return(y < (ssize_t) rows ? MagickFalse : MagickTrue);
2015 MagickExport MagickBooleanType SetImageColorMetric(
Image *image,
2026 mean_error_per_pixel;
2038 assert(image != (
Image *) NULL);
2039 assert(image->signature == MagickCoreSignature);
2040 assert(reconstruct_image != (
const Image *) NULL);
2041 assert(reconstruct_image->signature == MagickCoreSignature);
2044 mean_error_per_pixel=0.0;
2046 rows=MagickMax(image->rows,reconstruct_image->rows);
2047 columns=MagickMax(image->columns,reconstruct_image->columns);
2048 image_view=AcquireVirtualCacheView(image,exception);
2049 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
2050 for (y=0; y < (ssize_t) rows; y++)
2059 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
2060 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
2061 if ((p == (
const Quantum *) NULL) || (q == (Quantum *) NULL))
2063 for (x=0; x < (ssize_t) columns; x++)
2068 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2073 PixelChannel channel = GetPixelChannelChannel(image,i);
2074 PixelTrait traits = GetPixelChannelTraits(image,channel);
2075 PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
2077 if ((traits == UndefinedPixelTrait) ||
2078 (reconstruct_traits == UndefinedPixelTrait) ||
2079 ((reconstruct_traits & UpdatePixelTrait) == 0))
2081 distance=fabs((
double) (p[i]-(
double) GetPixelChannel(reconstruct_image,
2083 if (distance >= MagickEpsilon)
2085 mean_error_per_pixel+=distance;
2086 mean_error+=distance*distance;
2087 if (distance > maximum_error)
2088 maximum_error=distance;
2092 p+=GetPixelChannels(image);
2093 q+=GetPixelChannels(reconstruct_image);
2096 reconstruct_view=DestroyCacheView(reconstruct_view);
2097 image_view=DestroyCacheView(image_view);
2098 image->error.mean_error_per_pixel=(double) (mean_error_per_pixel/area);
2099 image->error.normalized_mean_error=(double) (QuantumScale*QuantumScale*
2101 image->error.normalized_maximum_error=(double) (QuantumScale*maximum_error);
2102 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
2146 #if defined(MAGICKCORE_HDRI_SUPPORT) && defined(MAGICKCORE_FFTW_DELEGATE)
2147 static Image *CrossCorrelationImage(
const Image *alpha_image,
2153 *complex_multiplication,
2160 clone_image=CloneImage(beta_image,0,0,MagickTrue,exception);
2161 if (clone_image == (
Image *) NULL)
2162 return(clone_image);
2163 (void) SetImageArtifact(clone_image,
"fourier:normalize",
"inverse");
2164 fft_images=ForwardFourierTransformImage(clone_image,MagickFalse,
2166 clone_image=DestroyImageList(clone_image);
2167 if (fft_images == (
Image *) NULL)
2172 complex_conjugate=ComplexImages(fft_images,ConjugateComplexOperator,
2174 fft_images=DestroyImageList(fft_images);
2175 if (complex_conjugate == (
Image *) NULL)
2176 return(complex_conjugate);
2180 clone_image=CloneImage(alpha_image,0,0,MagickTrue,exception);
2181 if (clone_image == (
Image *) NULL)
2183 complex_conjugate=DestroyImageList(complex_conjugate);
2184 return(clone_image);
2186 (void) SetImageArtifact(clone_image,
"fourier:normalize",
"inverse");
2187 fft_images=ForwardFourierTransformImage(clone_image,MagickFalse,exception);
2188 clone_image=DestroyImageList(clone_image);
2189 if (fft_images == (
Image *) NULL)
2191 complex_conjugate=DestroyImageList(complex_conjugate);
2194 complex_conjugate->next->next=fft_images;
2198 (void) SetImageArtifact(complex_conjugate,
"compose:clamp",
"false");
2199 complex_multiplication=ComplexImages(complex_conjugate,
2200 MultiplyComplexOperator,exception);
2201 complex_conjugate=DestroyImageList(complex_conjugate);
2202 if (fft_images == (
Image *) NULL)
2207 cross_correlation=InverseFourierTransformImage(complex_multiplication,
2208 complex_multiplication->next,MagickFalse,exception);
2209 complex_multiplication=DestroyImageList(complex_multiplication);
2210 return(cross_correlation);
2213 static Image *NCCDivideImage(
const Image *alpha_image,
const Image *beta_image,
2232 divide_image=CloneImage(alpha_image,0,0,MagickTrue,exception);
2233 if (divide_image == (
Image *) NULL)
2234 return(divide_image);
2236 alpha_view=AcquireAuthenticCacheView(divide_image,exception);
2237 beta_view=AcquireVirtualCacheView(beta_image,exception);
2238 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2239 #pragma omp parallel for schedule(static) shared(status) \
2240 magick_number_threads(beta_image,divide_image,divide_image->rows,1)
2242 for (y=0; y < (ssize_t) divide_image->rows; y++)
2253 if (status == MagickFalse)
2255 p=GetCacheViewVirtualPixels(beta_view,0,y,beta_image->columns,1,
2257 q=GetCacheViewAuthenticPixels(alpha_view,0,y,divide_image->columns,1,
2259 if ((p == (
const Quantum *) NULL) || (q == (Quantum *) NULL))
2264 for (x=0; x < (ssize_t) divide_image->columns; x++)
2269 for (i=0; i < (ssize_t) GetPixelChannels(divide_image); i++)
2271 PixelChannel channel = GetPixelChannelChannel(divide_image,i);
2272 PixelTrait traits = GetPixelChannelTraits(divide_image,channel);
2273 if ((traits & UpdatePixelTrait) == 0)
2275 if (fabs(p[i]) >= MagickEpsilon)
2276 q[i]*=PerceptibleReciprocal(QuantumScale*p[i]);
2278 p+=GetPixelChannels(beta_image);
2279 q+=GetPixelChannels(divide_image);
2281 if (SyncCacheViewAuthenticPixels(alpha_view,exception) == MagickFalse)
2284 beta_view=DestroyCacheView(beta_view);
2285 alpha_view=DestroyCacheView(alpha_view);
2286 if (status == MagickFalse)
2287 divide_image=DestroyImage(divide_image);
2288 return(divide_image);
2291 static MagickBooleanType NCCMaximaImage(
const Image *image,
double *maxima,
2310 image_view=AcquireVirtualCacheView(image,exception);
2311 for (y=0; y < (ssize_t) image->rows; y++)
2319 if (status == MagickFalse)
2321 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
2322 if (p == (
const Quantum *) NULL)
2327 for (x=0; x < (ssize_t) image->columns; x++)
2336 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2338 PixelChannel channel = GetPixelChannelChannel(image,i);
2339 PixelTrait traits = GetPixelChannelTraits(image,channel);
2340 if ((traits & UpdatePixelTrait) == 0)
2345 if ((channels != 0) && ((sum/channels) > *maxima))
2347 *maxima=sum/channels;
2351 p+=GetPixelChannels(image);
2353 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2356 image_view=DestroyCacheView(image_view);
2360 static MagickBooleanType NCCMultiplyImage(
Image *image,
const double factor,
2376 image_view=AcquireAuthenticCacheView(image,exception);
2377 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2378 #pragma omp parallel for schedule(static) shared(status) \
2379 magick_number_threads(image,image,image->rows,1)
2381 for (y=0; y < (ssize_t) image->rows; y++)
2389 if (status == MagickFalse)
2391 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2392 if (q == (Quantum *) NULL)
2397 for (x=0; x < (ssize_t) image->columns; x++)
2402 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2404 PixelChannel channel = GetPixelChannelChannel(image,i);
2405 PixelTrait traits = GetPixelChannelTraits(image,channel);
2406 if ((traits & UpdatePixelTrait) == 0)
2409 q[i]*=QuantumScale*channel_statistics[channel].standard_deviation;
2412 q+=GetPixelChannels(image);
2414 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2417 image_view=DestroyCacheView(image_view);
2438 square_image=CloneImage(image,0,0,MagickTrue,exception);
2439 if (square_image == (
Image *) NULL)
2440 return(square_image);
2442 image_view=AcquireAuthenticCacheView(square_image,exception);
2443 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2444 #pragma omp parallel for schedule(static) shared(status) \
2445 magick_number_threads(square_image,square_image,square_image->rows,1)
2447 for (y=0; y < (ssize_t) square_image->rows; y++)
2455 if (status == MagickFalse)
2457 q=GetCacheViewAuthenticPixels(image_view,0,y,square_image->columns,1,
2459 if (q == (Quantum *) NULL)
2464 for (x=0; x < (ssize_t) square_image->columns; x++)
2469 for (i=0; i < (ssize_t) GetPixelChannels(square_image); i++)
2471 PixelChannel channel = GetPixelChannelChannel(square_image,i);
2472 PixelTrait traits = GetPixelChannelTraits(square_image,channel);
2473 if ((traits & UpdatePixelTrait) == 0)
2475 q[i]*=QuantumScale*q[i];
2477 q+=GetPixelChannels(square_image);
2479 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2482 image_view=DestroyCacheView(image_view);
2483 if (status == MagickFalse)
2484 square_image=DestroyImage(square_image);
2485 return(square_image);
2488 static Image *NCCSubtractImageMean(
const Image *alpha_image,
2508 gamma_image=CloneImage(beta_image,alpha_image->columns,alpha_image->rows,
2509 MagickTrue,exception);
2510 if (gamma_image == (
Image *) NULL)
2511 return(gamma_image);
2513 image_view=AcquireAuthenticCacheView(gamma_image,exception);
2514 beta_view=AcquireVirtualCacheView(beta_image,exception);
2515 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2516 #pragma omp parallel for schedule(static) shared(status) \
2517 magick_number_threads(beta_image,gamma_image,gamma_image->rows,1)
2519 for (y=0; y < (ssize_t) gamma_image->rows; y++)
2530 if (status == MagickFalse)
2532 p=GetCacheViewVirtualPixels(beta_view,0,y,beta_image->columns,1,
2534 q=GetCacheViewAuthenticPixels(image_view,0,y,gamma_image->columns,1,
2536 if ((p == (
const Quantum *) NULL) || (q == (Quantum *) NULL))
2541 for (x=0; x < (ssize_t) gamma_image->columns; x++)
2546 for (i=0; i < (ssize_t) GetPixelChannels(gamma_image); i++)
2548 PixelChannel channel = GetPixelChannelChannel(gamma_image,i);
2549 PixelTrait traits = GetPixelChannelTraits(gamma_image,channel);
2550 if ((traits & UpdatePixelTrait) == 0)
2552 if ((x >= (ssize_t) beta_image->columns) ||
2553 (y >= (ssize_t) beta_image->rows))
2556 q[i]=p[i]-channel_statistics[channel].mean;
2558 p+=GetPixelChannels(beta_image);
2559 q+=GetPixelChannels(gamma_image);
2561 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2564 beta_view=DestroyCacheView(beta_view);
2565 image_view=DestroyCacheView(image_view);
2566 if (status == MagickFalse)
2567 gamma_image=DestroyImage(gamma_image);
2568 return(gamma_image);
2571 static Image *NCCUnityImage(
const Image *alpha_image,
const Image *beta_image,
2589 unity_image=CloneImage(alpha_image,alpha_image->columns,alpha_image->rows,
2590 MagickTrue,exception);
2591 if (unity_image == (
Image *) NULL)
2592 return(unity_image);
2594 image_view=AcquireAuthenticCacheView(unity_image,exception);
2595 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2596 #pragma omp parallel for schedule(static) shared(status) \
2597 magick_number_threads(unity_image,unity_image,unity_image->rows,1)
2599 for (y=0; y < (ssize_t) unity_image->rows; y++)
2607 if (status == MagickFalse)
2609 q=GetCacheViewAuthenticPixels(image_view,0,y,unity_image->columns,1,
2611 if (q == (Quantum *) NULL)
2616 for (x=0; x < (ssize_t) unity_image->columns; x++)
2621 for (i=0; i < (ssize_t) GetPixelChannels(unity_image); i++)
2623 PixelChannel channel = GetPixelChannelChannel(unity_image,i);
2624 PixelTrait traits = GetPixelChannelTraits(unity_image,channel);
2625 if ((traits & UpdatePixelTrait) == 0)
2628 if ((x >= (ssize_t) beta_image->columns) ||
2629 (y >= (ssize_t) beta_image->rows))
2632 q+=GetPixelChannels(unity_image);
2634 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2637 image_view=DestroyCacheView(image_view);
2638 if (status == MagickFalse)
2639 unity_image=DestroyImage(unity_image);
2640 return(unity_image);
2643 static Image *NCCVarianceImage(
Image *alpha_image,
const Image *beta_image,
2662 variance_image=CloneImage(alpha_image,0,0,MagickTrue,exception);
2663 if (variance_image == (
Image *) NULL)
2664 return(variance_image);
2666 image_view=AcquireAuthenticCacheView(variance_image,exception);
2667 beta_view=AcquireVirtualCacheView(beta_image,exception);
2668 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2669 #pragma omp parallel for schedule(static) shared(status) \
2670 magick_number_threads(beta_image,variance_image,variance_image->rows,1)
2672 for (y=0; y < (ssize_t) variance_image->rows; y++)
2683 if (status == MagickFalse)
2685 p=GetCacheViewVirtualPixels(beta_view,0,y,beta_image->columns,1,
2687 q=GetCacheViewAuthenticPixels(image_view,0,y,variance_image->columns,1,
2689 if ((p == (
const Quantum *) NULL) || (q == (Quantum *) NULL))
2694 for (x=0; x < (ssize_t) variance_image->columns; x++)
2699 for (i=0; i < (ssize_t) GetPixelChannels(variance_image); i++)
2701 PixelChannel channel = GetPixelChannelChannel(variance_image,i);
2702 PixelTrait traits = GetPixelChannelTraits(variance_image,channel);
2703 if ((traits & UpdatePixelTrait) == 0)
2705 q[i]=ClampToQuantum((QuantumRange*sqrt(fabs((
double) QuantumScale*
2706 (q[i]-p[i])))))/sqrt((
double) QuantumRange);
2708 p+=GetPixelChannels(beta_image);
2709 q+=GetPixelChannels(variance_image);
2711 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2714 beta_view=DestroyCacheView(beta_view);
2715 image_view=DestroyCacheView(image_view);
2716 if (status == MagickFalse)
2717 variance_image=DestroyImage(variance_image);
2718 return(variance_image);
2721 static Image *NCCSimilarityImage(
const Image *image,
const Image *reference,
2724 #define DestroySimilarityResources() \
2726 if (channel_statistics != (ChannelStatistics *) NULL) \
2727 channel_statistics=(ChannelStatistics *) \
2728 RelinquishMagickMemory(channel_statistics); \
2729 if (beta_image != (Image *) NULL) \
2730 beta_image=DestroyImage(beta_image); \
2731 if (gamma_image != (Image *) NULL) \
2732 gamma_image=DestroyImage(gamma_image); \
2733 if (ncc_image != (Image *) NULL) \
2734 ncc_image=DestroyImage(ncc_image); \
2735 if (normalize_image != (Image *) NULL) \
2736 normalize_image=DestroyImage(normalize_image); \
2737 if (square_image != (Image *) NULL) \
2738 square_image=DestroyImage(square_image); \
2739 if (unity_image != (Image *) NULL) \
2740 unity_image=DestroyImage(unity_image); \
2742 #define ThrowSimilarityException() \
2744 DestroySimilarityResources() \
2745 return((Image *) NULL); \
2755 *beta_image = (
Image *) NULL,
2756 *correlation_image = (
Image *) NULL,
2757 *gamma_image = (
Image *) NULL,
2758 *ncc_image = (
Image *) NULL,
2759 *normalize_image = (
Image *) NULL,
2760 *square_image = (
Image *) NULL,
2761 *unity_image = (
Image *) NULL;
2773 square_image=NCCSquareImage(image,exception);
2774 if (square_image == (
Image *) NULL)
2775 ThrowSimilarityException();
2776 unity_image=NCCUnityImage(image,reference,exception);
2777 if (unity_image == (
Image *) NULL)
2778 ThrowSimilarityException();
2782 ncc_image=CrossCorrelationImage(square_image,unity_image,exception);
2783 square_image=DestroyImage(square_image);
2784 if (ncc_image == (
Image *) NULL)
2785 ThrowSimilarityException();
2786 status=NCCMultiplyImage(ncc_image,(
double) QuantumRange*reference->columns*
2788 if (status == MagickFalse)
2789 ThrowSimilarityException();
2793 gamma_image=CrossCorrelationImage(image,unity_image,exception);
2794 unity_image=DestroyImage(unity_image);
2795 if (gamma_image == (
Image *) NULL)
2796 ThrowSimilarityException();
2797 square_image=NCCSquareImage(gamma_image,exception);
2798 gamma_image=DestroyImage(gamma_image);
2799 status=NCCMultiplyImage(square_image,(
double) QuantumRange,
2801 if (status == MagickFalse)
2802 ThrowSimilarityException();
2806 gamma_image=NCCVarianceImage(ncc_image,square_image,exception);
2807 square_image=DestroyImage(square_image);
2808 ncc_image=DestroyImage(ncc_image);
2809 if (gamma_image == (
Image *) NULL)
2810 ThrowSimilarityException();
2811 channel_statistics=GetImageStatistics(reference,exception);
2813 ThrowSimilarityException();
2817 status=NCCMultiplyImage(gamma_image,1.0,channel_statistics,exception);
2818 if (status == MagickFalse)
2819 ThrowSimilarityException();
2820 normalize_image=NCCSubtractImageMean(image,reference,channel_statistics,
2822 if (normalize_image == (
Image *) NULL)
2823 ThrowSimilarityException();
2824 ncc_image=CrossCorrelationImage(image,normalize_image,exception);
2825 normalize_image=DestroyImage(normalize_image);
2826 if (ncc_image == (
Image *) NULL)
2827 ThrowSimilarityException();
2831 beta_image=NCCDivideImage(ncc_image,gamma_image,exception);
2832 ncc_image=DestroyImage(ncc_image);
2833 gamma_image=DestroyImage(gamma_image);
2834 if (beta_image == (
Image *) NULL)
2835 ThrowSimilarityException();
2836 (void) ResetImagePage(beta_image,
"0x0+0+0");
2837 SetGeometry(image,&geometry);
2838 geometry.width=image->columns-reference->columns;
2839 geometry.height=image->rows-reference->rows;
2843 correlation_image=CropImage(beta_image,&geometry,exception);
2844 beta_image=DestroyImage(beta_image);
2845 if (correlation_image == (
Image *) NULL)
2846 ThrowSimilarityException();
2847 (void) ResetImagePage(correlation_image,
"0x0+0+0");
2851 status=GrayscaleImage(correlation_image,AveragePixelIntensityMethod,
2853 if (status == MagickFalse)
2854 ThrowSimilarityException();
2855 status=NCCMaximaImage(correlation_image,&maxima,offset,exception);
2856 if (status == MagickFalse)
2858 correlation_image=DestroyImage(correlation_image);
2859 ThrowSimilarityException();
2861 *similarity_metric=1.0-QuantumScale*maxima;
2862 DestroySimilarityResources();
2863 return(correlation_image);
2867 static double GetSimilarityMetric(
const Image *image,
const Image *reference,
2868 const MetricType metric,
const ssize_t x_offset,
const ssize_t y_offset,
2883 SetGeometry(reference,&geometry);
2884 geometry.x=x_offset;
2885 geometry.y=y_offset;
2886 similarity_image=CropImage(image,&geometry,exception);
2887 if (similarity_image == (
Image *) NULL)
2890 status=GetImageDistortion(similarity_image,reference,metric,&distortion,
2892 similarity_image=DestroyImage(similarity_image);
2893 if (status == MagickFalse)
2898 MagickExport
Image *SimilarityImage(
const Image *image,
const Image *reference,
2899 const MetricType metric,
const double similarity_threshold,
2902 #define SimilarityImageTag "Similarity/Image"
2919 assert(image != (
const Image *) NULL);
2920 assert(image->signature == MagickCoreSignature);
2922 assert(exception->signature == MagickCoreSignature);
2924 if (IsEventLogging() != MagickFalse)
2925 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
2926 SetGeometry(reference,offset);
2927 *similarity_metric=MagickMaximumValue;
2928 #if defined(MAGICKCORE_HDRI_SUPPORT) && defined(MAGICKCORE_FFTW_DELEGATE)
2929 if ((image->channels & ReadMaskChannel) != 0)
2931 const char *artifact = GetImageArtifact(image,
"compare:accelerate-ncc");
2932 MagickBooleanType accelerate = (artifact != (
const char *) NULL) &&
2933 (IsStringTrue(artifact) == MagickFalse) ? MagickFalse : MagickTrue;
2934 if ((accelerate != MagickFalse) &&
2935 (metric == NormalizedCrossCorrelationErrorMetric))
2937 similarity_image=NCCSimilarityImage(image,reference,offset,
2938 similarity_metric,exception);
2939 return(similarity_image);
2943 similarity_image=CloneImage(image,image->columns-reference->columns+1,
2944 image->rows-reference->rows+1,MagickTrue,exception);
2945 if (similarity_image == (
Image *) NULL)
2946 return((
Image *) NULL);
2947 status=SetImageStorageClass(similarity_image,DirectClass,exception);
2948 if (status == MagickFalse)
2950 similarity_image=DestroyImage(similarity_image);
2951 return((
Image *) NULL);
2953 (void) SetImageAlphaChannel(similarity_image,DeactivateAlphaChannel,
2960 similarity_view=AcquireAuthenticCacheView(similarity_image,exception);
2961 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2962 #pragma omp parallel for schedule(static) \
2963 shared(progress,status,similarity_metric) \
2964 magick_number_threads(image,image,image->rows-reference->rows+1,1)
2966 for (y=0; y < (ssize_t) (image->rows-reference->rows+1); y++)
2977 if (status == MagickFalse)
2979 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2980 #pragma omp flush(similarity_metric)
2982 if (*similarity_metric <= similarity_threshold)
2984 q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
2986 if (q == (Quantum *) NULL)
2991 for (x=0; x < (ssize_t) (image->columns-reference->columns+1); x++)
2996 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2997 #pragma omp flush(similarity_metric)
2999 if (*similarity_metric <= similarity_threshold)
3001 similarity=GetSimilarityMetric(image,reference,metric,x,y,exception);
3002 if ((metric == NormalizedCrossCorrelationErrorMetric) ||
3003 (metric == UndefinedErrorMetric))
3004 similarity=1.0-similarity;
3005 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3006 #pragma omp critical (MagickCore_SimilarityImage)
3008 if (similarity < *similarity_metric)
3012 *similarity_metric=similarity;
3014 if (metric == PerceptualHashErrorMetric)
3015 similarity=MagickMin(0.01*similarity,1.0);
3016 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3018 PixelChannel channel = GetPixelChannelChannel(image,i);
3019 PixelTrait traits = GetPixelChannelTraits(image,channel);
3020 PixelTrait similarity_traits=GetPixelChannelTraits(similarity_image,
3022 if ((traits == UndefinedPixelTrait) ||
3023 (similarity_traits == UndefinedPixelTrait) ||
3024 ((similarity_traits & UpdatePixelTrait) == 0))
3026 SetPixelChannel(similarity_image,channel,ClampToQuantum(QuantumRange-
3027 QuantumRange*similarity),q);
3029 q+=GetPixelChannels(similarity_image);
3031 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
3033 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3038 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3042 proceed=SetImageProgress(image,SimilarityImageTag,progress,image->rows);
3043 if (proceed == MagickFalse)
3047 similarity_view=DestroyCacheView(similarity_view);
3048 if (status == MagickFalse)
3049 similarity_image=DestroyImage(similarity_image);
3050 return(similarity_image);