2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
6 % CCCC OOO M M PPPP AAA RRRR EEEEE %
7 % C O O MM MM P P A A R R E %
8 % C O O M M M PPPP AAAAA RRRR EEE %
9 % C O O M M P A A R R E %
10 % CCCC OOO M M P A A R R EEEEE %
13 % MagickCore Image Comparison Methods %
20 % Copyright 1999-2015 ImageMagick Studio LLC, a non-profit organization %
21 % dedicated to making software imaging solutions freely available. %
23 % You may not use this file except in compliance with the License. You may %
24 % obtain a copy of the License at %
26 % http://www.imagemagick.org/script/license.php %
28 % Unless required by applicable law or agreed to in writing, software %
29 % distributed under the License is distributed on an "AS IS" BASIS, %
30 % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31 % See the License for the specific language governing permissions and %
32 % limitations under the License. %
34 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
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/geometry.h"
58 #include "MagickCore/image-private.h"
59 #include "MagickCore/list.h"
60 #include "MagickCore/log.h"
61 #include "MagickCore/memory_.h"
62 #include "MagickCore/monitor.h"
63 #include "MagickCore/monitor-private.h"
64 #include "MagickCore/option.h"
65 #include "MagickCore/pixel-accessor.h"
66 #include "MagickCore/property.h"
67 #include "MagickCore/resource_.h"
68 #include "MagickCore/string_.h"
69 #include "MagickCore/statistic.h"
70 #include "MagickCore/thread-private.h"
71 #include "MagickCore/transform.h"
72 #include "MagickCore/utility.h"
73 #include "MagickCore/version.h"
76 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
80 % C o m p a r e I m a g e %
84 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
86 % CompareImages() compares one or more pixel channels of an image to a
87 % reconstructed image and returns the difference image.
89 % The format of the CompareImages method is:
91 % Image *CompareImages(const Image *image,const Image *reconstruct_image,
92 % const MetricType metric,double *distortion,ExceptionInfo *exception)
94 % A description of each parameter follows:
98 % o reconstruct_image: the reconstruct image.
100 % o metric: the metric.
102 % o distortion: the computed distortion between the images.
104 % o exception: return any errors or warnings in this structure.
108 static size_t GetImageChannels(const Image *image)
117 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
119 PixelChannel channel=GetPixelChannelChannel(image,i);
120 PixelTrait traits=GetPixelChannelTraits(image,channel);
121 if ((traits & UpdatePixelTrait) != 0)
124 return(channels == 0 ? 1 : channels);
127 static inline MagickBooleanType ValidateImageMorphology(
128 const Image *restrict image,const Image *restrict reconstruct_image)
131 Does the image match the reconstructed image morphology?
136 MagickExport Image *CompareImages(Image *image,const Image *reconstruct_image,
137 const MetricType metric,double *distortion,ExceptionInfo *exception)
171 assert(image != (Image *) NULL);
172 assert(image->signature == MagickSignature);
173 if (image->debug != MagickFalse)
174 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
175 assert(reconstruct_image != (const Image *) NULL);
176 assert(reconstruct_image->signature == MagickSignature);
177 assert(distortion != (double *) NULL);
179 if (image->debug != MagickFalse)
180 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
181 if (metric != PerceptualHashErrorMetric)
182 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
183 ThrowImageException(ImageError,"ImageMorphologyDiffers");
184 status=GetImageDistortion(image,reconstruct_image,metric,distortion,
186 if (status == MagickFalse)
187 return((Image *) NULL);
188 columns=MagickMax(image->columns,reconstruct_image->columns);
189 rows=MagickMax(image->rows,reconstruct_image->rows);
190 SetGeometry(image,&geometry);
191 geometry.width=columns;
192 geometry.height=rows;
193 difference_image=ExtentImage(image,&geometry,exception);
194 if (difference_image == (Image *) NULL)
195 return((Image *) NULL);
196 (void) SetImageAlphaChannel(difference_image,OpaqueAlphaChannel,exception);
197 highlight_image=CloneImage(image,columns,rows,MagickTrue,exception);
198 if (highlight_image == (Image *) NULL)
200 difference_image=DestroyImage(difference_image);
201 return((Image *) NULL);
203 status=SetImageStorageClass(highlight_image,DirectClass,exception);
204 if (status == MagickFalse)
206 difference_image=DestroyImage(difference_image);
207 highlight_image=DestroyImage(highlight_image);
208 return((Image *) NULL);
210 (void) SetImageAlphaChannel(highlight_image,OpaqueAlphaChannel,exception);
211 (void) QueryColorCompliance("#f1001ecc",AllCompliance,&highlight,exception);
212 artifact=GetImageArtifact(image,"highlight-color");
213 if (artifact != (const char *) NULL)
214 (void) QueryColorCompliance(artifact,AllCompliance,&highlight,exception);
215 (void) QueryColorCompliance("#ffffffcc",AllCompliance,&lowlight,exception);
216 artifact=GetImageArtifact(image,"lowlight-color");
217 if (artifact != (const char *) NULL)
218 (void) QueryColorCompliance(artifact,AllCompliance,&lowlight,exception);
220 Generate difference image.
223 fuzz=GetFuzzyColorDistance(image,reconstruct_image);
224 image_view=AcquireVirtualCacheView(image,exception);
225 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
226 highlight_view=AcquireAuthenticCacheView(highlight_image,exception);
227 #if defined(MAGICKCORE_OPENMP_SUPPORT)
228 #pragma omp parallel for schedule(static,4) shared(status) \
229 magick_threads(image,highlight_image,rows,1)
231 for (y=0; y < (ssize_t) rows; y++)
236 register const Quantum
246 if (status == MagickFalse)
248 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
249 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
250 r=QueueCacheViewAuthenticPixels(highlight_view,0,y,columns,1,exception);
251 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL) ||
252 (r == (Quantum *) NULL))
257 for (x=0; x < (ssize_t) columns; x++)
269 if (GetPixelReadMask(image,p) == 0)
271 SetPixelViaPixelInfo(highlight_image,&lowlight,r);
272 p+=GetPixelChannels(image);
273 q+=GetPixelChannels(reconstruct_image);
274 r+=GetPixelChannels(highlight_image);
277 difference=MagickFalse;
278 Sa=QuantumScale*GetPixelAlpha(image,p);
279 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
280 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
285 PixelChannel channel=GetPixelChannelChannel(image,i);
286 PixelTrait traits=GetPixelChannelTraits(image,channel);
287 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
289 if ((traits == UndefinedPixelTrait) ||
290 (reconstruct_traits == UndefinedPixelTrait) ||
291 ((reconstruct_traits & UpdatePixelTrait) == 0))
293 distance=Sa*p[i]-Da*GetPixelChannel(reconstruct_image,channel,q);
294 if ((distance*distance) > fuzz)
296 difference=MagickTrue;
300 if (difference == MagickFalse)
301 SetPixelViaPixelInfo(highlight_image,&lowlight,r);
303 SetPixelViaPixelInfo(highlight_image,&highlight,r);
304 p+=GetPixelChannels(image);
305 q+=GetPixelChannels(reconstruct_image);
306 r+=GetPixelChannels(highlight_image);
308 sync=SyncCacheViewAuthenticPixels(highlight_view,exception);
309 if (sync == MagickFalse)
312 highlight_view=DestroyCacheView(highlight_view);
313 reconstruct_view=DestroyCacheView(reconstruct_view);
314 image_view=DestroyCacheView(image_view);
315 (void) CompositeImage(difference_image,highlight_image,image->compose,
316 MagickTrue,0,0,exception);
317 highlight_image=DestroyImage(highlight_image);
318 if (status == MagickFalse)
319 difference_image=DestroyImage(difference_image);
320 return(difference_image);
324 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
328 % G e t I m a g e D i s t o r t i o n %
332 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
334 % GetImageDistortion() compares one or more pixel channels of an image to a
335 % reconstructed image and returns the specified distortion metric.
337 % The format of the GetImageDistortion method is:
339 % MagickBooleanType GetImageDistortion(const Image *image,
340 % const Image *reconstruct_image,const MetricType metric,
341 % double *distortion,ExceptionInfo *exception)
343 % A description of each parameter follows:
345 % o image: the image.
347 % o reconstruct_image: the reconstruct image.
349 % o metric: the metric.
351 % o distortion: the computed distortion between the images.
353 % o exception: return any errors or warnings in this structure.
357 static MagickBooleanType GetAbsoluteDistortion(const Image *image,
358 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
378 Compute the absolute difference in pixels between two images.
381 fuzz=GetFuzzyColorDistance(image,reconstruct_image);
382 rows=MagickMax(image->rows,reconstruct_image->rows);
383 columns=MagickMax(image->columns,reconstruct_image->columns);
384 image_view=AcquireVirtualCacheView(image,exception);
385 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
386 #if defined(MAGICKCORE_OPENMP_SUPPORT)
387 #pragma omp parallel for schedule(static,4) shared(status) \
388 magick_threads(image,image,rows,1)
390 for (y=0; y < (ssize_t) rows; y++)
393 channel_distortion[MaxPixelChannels+1];
395 register const Quantum
403 if (status == MagickFalse)
405 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
406 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
407 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
412 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
413 for (x=0; x < (ssize_t) columns; x++)
425 if (GetPixelReadMask(image,p) == 0)
427 p+=GetPixelChannels(image);
428 q+=GetPixelChannels(reconstruct_image);
431 difference=MagickFalse;
432 Sa=QuantumScale*GetPixelAlpha(image,p);
433 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
434 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
439 PixelChannel channel=GetPixelChannelChannel(image,i);
440 PixelTrait traits=GetPixelChannelTraits(image,channel);
441 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
443 if ((traits == UndefinedPixelTrait) ||
444 (reconstruct_traits == UndefinedPixelTrait) ||
445 ((reconstruct_traits & UpdatePixelTrait) == 0))
447 distance=Sa*p[i]-Da*GetPixelChannel(reconstruct_image,channel,q);
448 if ((distance*distance) > fuzz)
450 channel_distortion[i]++;
451 difference=MagickTrue;
454 if (difference != MagickFalse)
455 channel_distortion[CompositePixelChannel]++;
456 p+=GetPixelChannels(image);
457 q+=GetPixelChannels(reconstruct_image);
459 #if defined(MAGICKCORE_OPENMP_SUPPORT)
460 #pragma omp critical (MagickCore_GetAbsoluteError)
462 for (i=0; i <= MaxPixelChannels; i++)
463 distortion[i]+=channel_distortion[i];
465 reconstruct_view=DestroyCacheView(reconstruct_view);
466 image_view=DestroyCacheView(image_view);
470 static MagickBooleanType GetFuzzDistortion(const Image *image,
471 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
491 rows=MagickMax(image->rows,reconstruct_image->rows);
492 columns=MagickMax(image->columns,reconstruct_image->columns);
493 image_view=AcquireVirtualCacheView(image,exception);
494 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
495 #if defined(MAGICKCORE_OPENMP_SUPPORT)
496 #pragma omp parallel for schedule(static,4) shared(status) \
497 magick_threads(image,image,rows,1)
499 for (y=0; y < (ssize_t) rows; y++)
502 channel_distortion[MaxPixelChannels+1];
504 register const Quantum
512 if (status == MagickFalse)
514 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
515 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
516 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
521 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
522 for (x=0; x < (ssize_t) columns; x++)
531 if (GetPixelReadMask(image,p) == 0)
533 p+=GetPixelChannels(image);
534 q+=GetPixelChannels(reconstruct_image);
537 Sa=QuantumScale*GetPixelAlpha(image,p);
538 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
539 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
544 PixelChannel channel=GetPixelChannelChannel(image,i);
545 PixelTrait traits=GetPixelChannelTraits(image,channel);
546 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
548 if ((traits == UndefinedPixelTrait) ||
549 (reconstruct_traits == UndefinedPixelTrait) ||
550 ((reconstruct_traits & UpdatePixelTrait) == 0))
552 distance=QuantumScale*(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
554 channel_distortion[i]+=distance*distance;
555 channel_distortion[CompositePixelChannel]+=distance*distance;
557 p+=GetPixelChannels(image);
558 q+=GetPixelChannels(reconstruct_image);
560 #if defined(MAGICKCORE_OPENMP_SUPPORT)
561 #pragma omp critical (MagickCore_GetFuzzDistortion)
563 for (i=0; i <= MaxPixelChannels; i++)
564 distortion[i]+=channel_distortion[i];
566 reconstruct_view=DestroyCacheView(reconstruct_view);
567 image_view=DestroyCacheView(image_view);
568 for (i=0; i <= MaxPixelChannels; i++)
569 distortion[i]/=((double) columns*rows);
570 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
571 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]);
575 static MagickBooleanType GetMeanAbsoluteDistortion(const Image *image,
576 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
596 rows=MagickMax(image->rows,reconstruct_image->rows);
597 columns=MagickMax(image->columns,reconstruct_image->columns);
598 image_view=AcquireVirtualCacheView(image,exception);
599 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
600 #if defined(MAGICKCORE_OPENMP_SUPPORT)
601 #pragma omp parallel for schedule(static,4) shared(status) \
602 magick_threads(image,image,rows,1)
604 for (y=0; y < (ssize_t) rows; y++)
607 channel_distortion[MaxPixelChannels+1];
609 register const Quantum
617 if (status == MagickFalse)
619 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
620 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
621 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
626 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
627 for (x=0; x < (ssize_t) columns; x++)
636 if (GetPixelReadMask(image,p) == 0)
638 p+=GetPixelChannels(image);
639 q+=GetPixelChannels(reconstruct_image);
642 Sa=QuantumScale*GetPixelAlpha(image,p);
643 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
644 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
649 PixelChannel channel=GetPixelChannelChannel(image,i);
650 PixelTrait traits=GetPixelChannelTraits(image,channel);
651 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
653 if ((traits == UndefinedPixelTrait) ||
654 (reconstruct_traits == UndefinedPixelTrait) ||
655 ((reconstruct_traits & UpdatePixelTrait) == 0))
657 distance=QuantumScale*fabs(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
659 channel_distortion[i]+=distance;
660 channel_distortion[CompositePixelChannel]+=distance;
662 p+=GetPixelChannels(image);
663 q+=GetPixelChannels(reconstruct_image);
665 #if defined(MAGICKCORE_OPENMP_SUPPORT)
666 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
668 for (i=0; i <= MaxPixelChannels; i++)
669 distortion[i]+=channel_distortion[i];
671 reconstruct_view=DestroyCacheView(reconstruct_view);
672 image_view=DestroyCacheView(image_view);
673 for (i=0; i <= MaxPixelChannels; i++)
674 distortion[i]/=((double) columns*rows);
675 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
679 static MagickBooleanType GetMeanErrorPerPixel(Image *image,
680 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
705 rows=MagickMax(image->rows,reconstruct_image->rows);
706 columns=MagickMax(image->columns,reconstruct_image->columns);
707 image_view=AcquireVirtualCacheView(image,exception);
708 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
709 for (y=0; y < (ssize_t) rows; y++)
711 register const Quantum
718 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
719 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
720 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
725 for (x=0; x < (ssize_t) columns; x++)
734 if (GetPixelReadMask(image,p) == 0)
736 p+=GetPixelChannels(image);
737 q+=GetPixelChannels(reconstruct_image);
740 Sa=QuantumScale*GetPixelAlpha(image,p);
741 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
742 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
747 PixelChannel channel=GetPixelChannelChannel(image,i);
748 PixelTrait traits=GetPixelChannelTraits(image,channel);
749 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
751 if ((traits == UndefinedPixelTrait) ||
752 (reconstruct_traits == UndefinedPixelTrait) ||
753 ((reconstruct_traits & UpdatePixelTrait) == 0))
755 distance=fabs(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,channel,q));
756 distortion[i]+=distance;
757 distortion[CompositePixelChannel]+=distance;
758 mean_error+=distance*distance;
759 if (distance > maximum_error)
760 maximum_error=distance;
763 p+=GetPixelChannels(image);
764 q+=GetPixelChannels(reconstruct_image);
767 reconstruct_view=DestroyCacheView(reconstruct_view);
768 image_view=DestroyCacheView(image_view);
769 image->error.mean_error_per_pixel=distortion[CompositePixelChannel]/area;
770 image->error.normalized_mean_error=QuantumScale*QuantumScale*mean_error/area;
771 image->error.normalized_maximum_error=QuantumScale*maximum_error;
775 static MagickBooleanType GetMeanSquaredDistortion(const Image *image,
776 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
796 rows=MagickMax(image->rows,reconstruct_image->rows);
797 columns=MagickMax(image->columns,reconstruct_image->columns);
798 image_view=AcquireVirtualCacheView(image,exception);
799 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
800 #if defined(MAGICKCORE_OPENMP_SUPPORT)
801 #pragma omp parallel for schedule(static,4) shared(status) \
802 magick_threads(image,image,rows,1)
804 for (y=0; y < (ssize_t) rows; y++)
807 channel_distortion[MaxPixelChannels+1];
809 register const Quantum
817 if (status == MagickFalse)
819 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
820 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
821 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
826 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
827 for (x=0; x < (ssize_t) columns; x++)
836 if (GetPixelReadMask(image,p) == 0)
838 p+=GetPixelChannels(image);
839 q+=GetPixelChannels(reconstruct_image);
842 Sa=QuantumScale*GetPixelAlpha(image,p);
843 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
844 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
849 PixelChannel channel=GetPixelChannelChannel(image,i);
850 PixelTrait traits=GetPixelChannelTraits(image,channel);
851 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
853 if ((traits == UndefinedPixelTrait) ||
854 (reconstruct_traits == UndefinedPixelTrait) ||
855 ((reconstruct_traits & UpdatePixelTrait) == 0))
857 distance=QuantumScale*(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
859 channel_distortion[i]+=distance*distance;
860 channel_distortion[CompositePixelChannel]+=distance*distance;
862 p+=GetPixelChannels(image);
863 q+=GetPixelChannels(reconstruct_image);
865 #if defined(MAGICKCORE_OPENMP_SUPPORT)
866 #pragma omp critical (MagickCore_GetMeanSquaredError)
868 for (i=0; i <= MaxPixelChannels; i++)
869 distortion[i]+=channel_distortion[i];
871 reconstruct_view=DestroyCacheView(reconstruct_view);
872 image_view=DestroyCacheView(image_view);
873 for (i=0; i <= MaxPixelChannels; i++)
874 distortion[i]/=((double) columns*rows);
875 distortion[CompositePixelChannel]/=GetImageChannels(image);
879 static MagickBooleanType GetNormalizedCrossCorrelationDistortion(
880 const Image *image,const Image *reconstruct_image,double *distortion,
881 ExceptionInfo *exception)
883 #define SimilarityImageTag "Similarity/Image"
891 *reconstruct_statistics;
913 Normalize to account for variation due to lighting and exposure condition.
915 image_statistics=GetImageStatistics(image,exception);
916 reconstruct_statistics=GetImageStatistics(reconstruct_image,exception);
917 if ((image_statistics == (ChannelStatistics *) NULL) ||
918 (reconstruct_statistics == (ChannelStatistics *) NULL))
920 if (image_statistics != (ChannelStatistics *) NULL)
921 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
923 if (reconstruct_statistics != (ChannelStatistics *) NULL)
924 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
925 reconstruct_statistics);
930 for (i=0; i <= MaxPixelChannels; i++)
932 rows=MagickMax(image->rows,reconstruct_image->rows);
933 columns=MagickMax(image->columns,reconstruct_image->columns);
934 area=1.0/((double) columns*rows);
935 image_view=AcquireVirtualCacheView(image,exception);
936 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
937 for (y=0; y < (ssize_t) rows; y++)
939 register const Quantum
946 if (status == MagickFalse)
948 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
949 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
950 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
955 for (x=0; x < (ssize_t) columns; x++)
964 if (GetPixelReadMask(image,p) == 0)
966 p+=GetPixelChannels(image);
967 q+=GetPixelChannels(reconstruct_image);
970 Sa=QuantumScale*GetPixelAlpha(image,p);
971 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
972 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
974 PixelChannel channel=GetPixelChannelChannel(image,i);
975 PixelTrait traits=GetPixelChannelTraits(image,channel);
976 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
978 if ((traits == UndefinedPixelTrait) ||
979 (reconstruct_traits == UndefinedPixelTrait) ||
980 ((reconstruct_traits & UpdatePixelTrait) == 0))
982 distortion[i]+=area*QuantumScale*(Sa*p[i]-image_statistics[i].mean)*
983 (Da*GetPixelChannel(reconstruct_image,channel,q)-
984 reconstruct_statistics[channel].mean);
986 p+=GetPixelChannels(image);
987 q+=GetPixelChannels(reconstruct_image);
989 if (image->progress_monitor != (MagickProgressMonitor) NULL)
994 proceed=SetImageProgress(image,SimilarityImageTag,progress++,rows);
995 if (proceed == MagickFalse)
999 reconstruct_view=DestroyCacheView(reconstruct_view);
1000 image_view=DestroyCacheView(image_view);
1002 Divide by the standard deviation.
1004 distortion[CompositePixelChannel]=0.0;
1005 for (i=0; i < MaxPixelChannels; i++)
1010 PixelChannel channel=GetPixelChannelChannel(image,i);
1011 gamma=image_statistics[i].standard_deviation*
1012 reconstruct_statistics[channel].standard_deviation;
1013 gamma=PerceptibleReciprocal(gamma);
1014 distortion[i]=QuantumRange*gamma*distortion[i];
1015 distortion[CompositePixelChannel]+=distortion[i]*distortion[i];
1017 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]/
1018 GetImageChannels(image));
1022 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1023 reconstruct_statistics);
1024 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1029 static MagickBooleanType GetPeakAbsoluteDistortion(const Image *image,
1030 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1047 rows=MagickMax(image->rows,reconstruct_image->rows);
1048 columns=MagickMax(image->columns,reconstruct_image->columns);
1049 image_view=AcquireVirtualCacheView(image,exception);
1050 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1051 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1052 #pragma omp parallel for schedule(static,4) shared(status) \
1053 magick_threads(image,image,rows,1)
1055 for (y=0; y < (ssize_t) rows; y++)
1058 channel_distortion[MaxPixelChannels+1];
1060 register const Quantum
1068 if (status == MagickFalse)
1070 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1071 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1072 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
1077 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
1078 for (x=0; x < (ssize_t) columns; x++)
1087 if (GetPixelReadMask(image,p) == 0)
1089 p+=GetPixelChannels(image);
1090 q+=GetPixelChannels(reconstruct_image);
1093 Sa=QuantumScale*GetPixelAlpha(image,p);
1094 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
1095 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1100 PixelChannel channel=GetPixelChannelChannel(image,i);
1101 PixelTrait traits=GetPixelChannelTraits(image,channel);
1102 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
1104 if ((traits == UndefinedPixelTrait) ||
1105 (reconstruct_traits == UndefinedPixelTrait) ||
1106 ((reconstruct_traits & UpdatePixelTrait) == 0))
1108 distance=QuantumScale*fabs(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
1110 if (distance > channel_distortion[i])
1111 channel_distortion[i]=distance;
1112 if (distance > channel_distortion[CompositePixelChannel])
1113 channel_distortion[CompositePixelChannel]=distance;
1115 p+=GetPixelChannels(image);
1116 q+=GetPixelChannels(reconstruct_image);
1118 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1119 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
1121 for (i=0; i <= MaxPixelChannels; i++)
1122 if (channel_distortion[i] > distortion[i])
1123 distortion[i]=channel_distortion[i];
1125 reconstruct_view=DestroyCacheView(reconstruct_view);
1126 image_view=DestroyCacheView(image_view);
1130 static inline double MagickLog10(const double x)
1132 #define Log10Epsilon (1.0e-11)
1134 if (fabs(x) < Log10Epsilon)
1135 return(log10(Log10Epsilon));
1136 return(log10(fabs(x)));
1139 static MagickBooleanType GetPeakSignalToNoiseRatio(const Image *image,
1140 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1148 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
1149 for (i=0; i <= MaxPixelChannels; i++)
1150 distortion[i]=20.0*MagickLog10((double) 1.0/sqrt(distortion[i]));
1154 static MagickBooleanType GetPerceptualHashDistortion(const Image *image,
1155 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1157 ChannelPerceptualHash
1165 Compute perceptual hash in the sRGB colorspace.
1167 image_phash=GetImagePerceptualHash(image,exception);
1168 if (image_phash == (ChannelPerceptualHash *) NULL)
1169 return(MagickFalse);
1170 reconstruct_phash=GetImagePerceptualHash(reconstruct_image,exception);
1171 if (reconstruct_phash == (ChannelPerceptualHash *) NULL)
1173 image_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(image_phash);
1174 return(MagickFalse);
1176 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1177 #pragma omp parallel for schedule(static,4)
1179 for (channel=0; channel < MaxPixelChannels; channel++)
1188 for (i=0; i < MaximumNumberOfImageMoments; i++)
1194 alpha=image_phash[channel].srgb_hu_phash[i];
1195 beta=reconstruct_phash[channel].srgb_hu_phash[i];
1196 difference+=(beta-alpha)*(beta-alpha);
1198 distortion[channel]+=difference;
1199 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1200 #pragma omp critical (MagickCore_GetPerceptualHashDistortion)
1202 distortion[CompositePixelChannel]+=difference;
1205 Compute perceptual hash in the HCLP colorspace.
1207 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1208 #pragma omp parallel for schedule(static,4)
1210 for (channel=0; channel < MaxPixelChannels; channel++)
1219 for (i=0; i < MaximumNumberOfImageMoments; i++)
1225 alpha=image_phash[channel].hclp_hu_phash[i];
1226 beta=reconstruct_phash[channel].hclp_hu_phash[i];
1227 difference+=(beta-alpha)*(beta-alpha);
1229 distortion[channel]+=difference;
1230 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1231 #pragma omp critical (MagickCore_GetPerceptualHashDistortion)
1233 distortion[CompositePixelChannel]+=difference;
1238 reconstruct_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(
1240 image_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(image_phash);
1244 static MagickBooleanType GetRootMeanSquaredDistortion(const Image *image,
1245 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1253 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
1254 for (i=0; i <= MaxPixelChannels; i++)
1255 distortion[i]=sqrt(distortion[i]);
1259 MagickExport MagickBooleanType GetImageDistortion(Image *image,
1260 const Image *reconstruct_image,const MetricType metric,double *distortion,
1261 ExceptionInfo *exception)
1264 *channel_distortion;
1272 assert(image != (Image *) NULL);
1273 assert(image->signature == MagickSignature);
1274 if (image->debug != MagickFalse)
1275 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1276 assert(reconstruct_image != (const Image *) NULL);
1277 assert(reconstruct_image->signature == MagickSignature);
1278 assert(distortion != (double *) NULL);
1280 if (image->debug != MagickFalse)
1281 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1282 if (metric != PerceptualHashErrorMetric)
1283 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1284 ThrowBinaryException(ImageError,"ImageMorphologyDiffers",image->filename);
1286 Get image distortion.
1288 length=MaxPixelChannels+1;
1289 channel_distortion=(double *) AcquireQuantumMemory(length,
1290 sizeof(*channel_distortion));
1291 if (channel_distortion == (double *) NULL)
1292 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1293 (void) ResetMagickMemory(channel_distortion,0,length*
1294 sizeof(*channel_distortion));
1297 case AbsoluteErrorMetric:
1299 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1303 case FuzzErrorMetric:
1305 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1309 case MeanAbsoluteErrorMetric:
1311 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1312 channel_distortion,exception);
1315 case MeanErrorPerPixelErrorMetric:
1317 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1321 case MeanSquaredErrorMetric:
1323 status=GetMeanSquaredDistortion(image,reconstruct_image,
1324 channel_distortion,exception);
1327 case NormalizedCrossCorrelationErrorMetric:
1330 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1331 channel_distortion,exception);
1334 case PeakAbsoluteErrorMetric:
1336 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1337 channel_distortion,exception);
1340 case PeakSignalToNoiseRatioErrorMetric:
1342 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1343 channel_distortion,exception);
1346 case PerceptualHashErrorMetric:
1348 status=GetPerceptualHashDistortion(image,reconstruct_image,
1349 channel_distortion,exception);
1352 case RootMeanSquaredErrorMetric:
1354 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1355 channel_distortion,exception);
1359 *distortion=channel_distortion[CompositePixelChannel];
1360 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1361 (void) FormatImageProperty(image,"distortion","%.*g",GetMagickPrecision(),
1367 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1371 % G e t I m a g e D i s t o r t i o n s %
1375 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1377 % GetImageDistortions() compares the pixel channels of an image to a
1378 % reconstructed image and returns the specified distortion metric for each
1381 % The format of the GetImageDistortions method is:
1383 % double *GetImageDistortions(const Image *image,
1384 % const Image *reconstruct_image,const MetricType metric,
1385 % ExceptionInfo *exception)
1387 % A description of each parameter follows:
1389 % o image: the image.
1391 % o reconstruct_image: the reconstruct image.
1393 % o metric: the metric.
1395 % o exception: return any errors or warnings in this structure.
1398 MagickExport double *GetImageDistortions(Image *image,
1399 const Image *reconstruct_image,const MetricType metric,
1400 ExceptionInfo *exception)
1403 *channel_distortion;
1411 assert(image != (Image *) NULL);
1412 assert(image->signature == MagickSignature);
1413 if (image->debug != MagickFalse)
1414 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1415 assert(reconstruct_image != (const Image *) NULL);
1416 assert(reconstruct_image->signature == MagickSignature);
1417 if (image->debug != MagickFalse)
1418 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1419 if (metric != PerceptualHashErrorMetric)
1420 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1422 (void) ThrowMagickException(exception,GetMagickModule(),ImageError,
1423 "ImageMorphologyDiffers","`%s'",image->filename);
1424 return((double *) NULL);
1427 Get image distortion.
1429 length=MaxPixelChannels+1UL;
1430 channel_distortion=(double *) AcquireQuantumMemory(length,
1431 sizeof(*channel_distortion));
1432 if (channel_distortion == (double *) NULL)
1433 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1434 (void) ResetMagickMemory(channel_distortion,0,length*
1435 sizeof(*channel_distortion));
1439 case AbsoluteErrorMetric:
1441 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1445 case FuzzErrorMetric:
1447 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1451 case MeanAbsoluteErrorMetric:
1453 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1454 channel_distortion,exception);
1457 case MeanErrorPerPixelErrorMetric:
1459 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1463 case MeanSquaredErrorMetric:
1465 status=GetMeanSquaredDistortion(image,reconstruct_image,
1466 channel_distortion,exception);
1469 case NormalizedCrossCorrelationErrorMetric:
1472 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1473 channel_distortion,exception);
1476 case PeakAbsoluteErrorMetric:
1478 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1479 channel_distortion,exception);
1482 case PeakSignalToNoiseRatioErrorMetric:
1484 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1485 channel_distortion,exception);
1488 case PerceptualHashErrorMetric:
1490 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1491 channel_distortion,exception);
1494 case RootMeanSquaredErrorMetric:
1496 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1497 channel_distortion,exception);
1501 if (status == MagickFalse)
1503 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1504 return((double *) NULL);
1506 return(channel_distortion);
1510 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1514 % I s I m a g e s E q u a l %
1518 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1520 % IsImagesEqual() measures the difference between colors at each pixel
1521 % location of two images. A value other than 0 means the colors match
1522 % exactly. Otherwise an error measure is computed by summing over all
1523 % pixels in an image the distance squared in RGB space between each image
1524 % pixel and its corresponding pixel in the reconstruct image. The error
1525 % measure is assigned to these image members:
1527 % o mean_error_per_pixel: The mean error for any single pixel in
1530 % o normalized_mean_error: The normalized mean quantization error for
1531 % any single pixel in the image. This distance measure is normalized to
1532 % a range between 0 and 1. It is independent of the range of red, green,
1533 % and blue values in the image.
1535 % o normalized_maximum_error: The normalized maximum quantization
1536 % error for any single pixel in the image. This distance measure is
1537 % normalized to a range between 0 and 1. It is independent of the range
1538 % of red, green, and blue values in your image.
1540 % A small normalized mean square error, accessed as
1541 % image->normalized_mean_error, suggests the images are very similar in
1542 % spatial layout and color.
1544 % The format of the IsImagesEqual method is:
1546 % MagickBooleanType IsImagesEqual(Image *image,
1547 % const Image *reconstruct_image,ExceptionInfo *exception)
1549 % A description of each parameter follows.
1551 % o image: the image.
1553 % o reconstruct_image: the reconstruct image.
1555 % o exception: return any errors or warnings in this structure.
1558 MagickExport MagickBooleanType IsImagesEqual(Image *image,
1559 const Image *reconstruct_image,ExceptionInfo *exception)
1572 mean_error_per_pixel;
1581 assert(image != (Image *) NULL);
1582 assert(image->signature == MagickSignature);
1583 assert(reconstruct_image != (const Image *) NULL);
1584 assert(reconstruct_image->signature == MagickSignature);
1585 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1586 ThrowBinaryException(ImageError,"ImageMorphologyDiffers",image->filename);
1589 mean_error_per_pixel=0.0;
1591 rows=MagickMax(image->rows,reconstruct_image->rows);
1592 columns=MagickMax(image->columns,reconstruct_image->columns);
1593 image_view=AcquireVirtualCacheView(image,exception);
1594 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1595 for (y=0; y < (ssize_t) rows; y++)
1597 register const Quantum
1604 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1605 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1606 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1608 for (x=0; x < (ssize_t) columns; x++)
1613 if (GetPixelReadMask(image,p) == 0)
1615 p+=GetPixelChannels(image);
1616 q+=GetPixelChannels(reconstruct_image);
1619 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1624 PixelChannel channel=GetPixelChannelChannel(image,i);
1625 PixelTrait traits=GetPixelChannelTraits(image,channel);
1626 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
1628 if ((traits == UndefinedPixelTrait) ||
1629 (reconstruct_traits == UndefinedPixelTrait) ||
1630 ((reconstruct_traits & UpdatePixelTrait) == 0))
1632 distance=fabs(p[i]-(double) GetPixelChannel(reconstruct_image,
1634 if (distance >= MagickEpsilon)
1636 mean_error_per_pixel+=distance;
1637 mean_error+=distance*distance;
1638 if (distance > maximum_error)
1639 maximum_error=distance;
1643 p+=GetPixelChannels(image);
1644 q+=GetPixelChannels(reconstruct_image);
1647 reconstruct_view=DestroyCacheView(reconstruct_view);
1648 image_view=DestroyCacheView(image_view);
1649 image->error.mean_error_per_pixel=(double) (mean_error_per_pixel/area);
1650 image->error.normalized_mean_error=(double) (QuantumScale*QuantumScale*
1652 image->error.normalized_maximum_error=(double) (QuantumScale*maximum_error);
1653 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
1658 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1662 % S i m i l a r i t y I m a g e %
1666 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1668 % SimilarityImage() compares the reference image of the image and returns the
1669 % best match offset. In addition, it returns a similarity image such that an
1670 % exact match location is completely white and if none of the pixels match,
1671 % black, otherwise some gray level in-between.
1673 % The format of the SimilarityImageImage method is:
1675 % Image *SimilarityImage(const Image *image,const Image *reference,
1676 % const MetricType metric,const double similarity_threshold,
1677 % RectangleInfo *offset,double *similarity,ExceptionInfo *exception)
1679 % A description of each parameter follows:
1681 % o image: the image.
1683 % o reference: find an area of the image that closely resembles this image.
1685 % o metric: the metric.
1687 % o similarity_threshold: minimum distortion for (sub)image match.
1689 % o offset: the best match offset of the reference image within the image.
1691 % o similarity: the computed similarity between the images.
1693 % o exception: return any errors or warnings in this structure.
1697 static double GetSimilarityMetric(const Image *image,const Image *reference,
1698 const MetricType metric,const ssize_t x_offset,const ssize_t y_offset,
1699 ExceptionInfo *exception)
1713 SetGeometry(reference,&geometry);
1714 geometry.x=x_offset;
1715 geometry.y=y_offset;
1716 similarity_image=CropImage(image,&geometry,exception);
1717 if (similarity_image == (Image *) NULL)
1720 status=GetImageDistortion(similarity_image,reference,metric,&distortion,
1722 similarity_image=DestroyImage(similarity_image);
1723 if (status == MagickFalse)
1728 MagickExport Image *SimilarityImage(Image *image,const Image *reference,
1729 const MetricType metric,const double similarity_threshold,
1730 RectangleInfo *offset,double *similarity_metric,ExceptionInfo *exception)
1732 #define SimilarityImageTag "Similarity/Image"
1749 assert(image != (const Image *) NULL);
1750 assert(image->signature == MagickSignature);
1751 if (image->debug != MagickFalse)
1752 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1753 assert(exception != (ExceptionInfo *) NULL);
1754 assert(exception->signature == MagickSignature);
1755 assert(offset != (RectangleInfo *) NULL);
1756 SetGeometry(reference,offset);
1757 *similarity_metric=MagickMaximumValue;
1758 if (ValidateImageMorphology(image,reference) == MagickFalse)
1759 ThrowImageException(ImageError,"ImageMorphologyDiffers");
1760 similarity_image=CloneImage(image,image->columns-reference->columns+1,
1761 image->rows-reference->rows+1,MagickTrue,exception);
1762 if (similarity_image == (Image *) NULL)
1763 return((Image *) NULL);
1764 status=SetImageStorageClass(similarity_image,DirectClass,exception);
1765 if (status == MagickFalse)
1767 similarity_image=DestroyImage(similarity_image);
1768 return((Image *) NULL);
1770 (void) SetImageAlphaChannel(similarity_image,DeactivateAlphaChannel,
1773 Measure similarity of reference image against image.
1777 similarity_view=AcquireAuthenticCacheView(similarity_image,exception);
1778 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1779 #pragma omp parallel for schedule(static,4) \
1780 shared(progress,status,similarity_metric) \
1781 magick_threads(image,image,image->rows,1)
1783 for (y=0; y < (ssize_t) (image->rows-reference->rows+1); y++)
1794 if (status == MagickFalse)
1796 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1797 #pragma omp flush(similarity_metric)
1799 if (*similarity_metric <= similarity_threshold)
1801 q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
1803 if (q == (Quantum *) NULL)
1808 for (x=0; x < (ssize_t) (image->columns-reference->columns+1); x++)
1813 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1814 #pragma omp flush(similarity_metric)
1816 if (*similarity_metric <= similarity_threshold)
1818 similarity=GetSimilarityMetric(image,reference,metric,x,y,exception);
1819 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1820 #pragma omp critical (MagickCore_SimilarityImage)
1822 if ((metric == NormalizedCrossCorrelationErrorMetric) ||
1823 (metric == UndefinedErrorMetric))
1824 similarity=1.0-similarity;
1825 if (similarity < *similarity_metric)
1829 *similarity_metric=similarity;
1831 if (metric == PerceptualHashErrorMetric)
1832 similarity=MagickMin(0.01*similarity,1.0);
1833 if (GetPixelReadMask(similarity_image,q) == 0)
1835 SetPixelBackgoundColor(similarity_image,q);
1836 q+=GetPixelChannels(similarity_image);
1839 for (i=0; i < (ssize_t) GetPixelChannels(similarity_image); i++)
1841 PixelChannel channel=GetPixelChannelChannel(image,i);
1842 PixelTrait traits=GetPixelChannelTraits(image,channel);
1843 PixelTrait similarity_traits=GetPixelChannelTraits(similarity_image,
1845 if ((traits == UndefinedPixelTrait) ||
1846 (similarity_traits == UndefinedPixelTrait) ||
1847 ((similarity_traits & UpdatePixelTrait) == 0))
1849 SetPixelChannel(similarity_image,channel,ClampToQuantum(QuantumRange-
1850 QuantumRange*similarity),q);
1852 q+=GetPixelChannels(similarity_image);
1854 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
1856 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1861 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1862 #pragma omp critical (MagickCore_SimilarityImage)
1864 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
1866 if (proceed == MagickFalse)
1870 similarity_view=DestroyCacheView(similarity_view);
1871 if (status == MagickFalse)
1872 similarity_image=DestroyImage(similarity_image);
1873 return(similarity_image);