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 == MagickCoreSignature);
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 == MagickCoreSignature);
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 (j=0; j <= MaxPixelChannels; j++)
463 distortion[j]+=channel_distortion[j];
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
511 if (status == MagickFalse)
513 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
514 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
515 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
520 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
521 for (x=0; x < (ssize_t) columns; x++)
530 if (GetPixelReadMask(image,p) == 0)
532 p+=GetPixelChannels(image);
533 q+=GetPixelChannels(reconstruct_image);
536 Sa=QuantumScale*GetPixelAlpha(image,p);
537 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
538 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
543 PixelChannel channel=GetPixelChannelChannel(image,i);
544 PixelTrait traits=GetPixelChannelTraits(image,channel);
545 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
547 if ((traits == UndefinedPixelTrait) ||
548 (reconstruct_traits == UndefinedPixelTrait) ||
549 ((reconstruct_traits & UpdatePixelTrait) == 0))
551 distance=QuantumScale*(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
553 channel_distortion[i]+=distance*distance;
554 channel_distortion[CompositePixelChannel]+=distance*distance;
556 p+=GetPixelChannels(image);
557 q+=GetPixelChannels(reconstruct_image);
559 #if defined(MAGICKCORE_OPENMP_SUPPORT)
560 #pragma omp critical (MagickCore_GetFuzzDistortion)
562 for (j=0; j <= MaxPixelChannels; j++)
563 distortion[j]+=channel_distortion[j];
565 reconstruct_view=DestroyCacheView(reconstruct_view);
566 image_view=DestroyCacheView(image_view);
567 for (j=0; j <= MaxPixelChannels; j++)
568 distortion[j]/=((double) columns*rows);
569 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
570 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]);
574 static MagickBooleanType GetMeanAbsoluteDistortion(const Image *image,
575 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
595 rows=MagickMax(image->rows,reconstruct_image->rows);
596 columns=MagickMax(image->columns,reconstruct_image->columns);
597 image_view=AcquireVirtualCacheView(image,exception);
598 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
599 #if defined(MAGICKCORE_OPENMP_SUPPORT)
600 #pragma omp parallel for schedule(static,4) shared(status) \
601 magick_threads(image,image,rows,1)
603 for (y=0; y < (ssize_t) rows; y++)
606 channel_distortion[MaxPixelChannels+1];
608 register const Quantum
615 if (status == MagickFalse)
617 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
618 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
619 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
624 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
625 for (x=0; x < (ssize_t) columns; x++)
634 if (GetPixelReadMask(image,p) == 0)
636 p+=GetPixelChannels(image);
637 q+=GetPixelChannels(reconstruct_image);
640 Sa=QuantumScale*GetPixelAlpha(image,p);
641 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
642 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
647 PixelChannel channel=GetPixelChannelChannel(image,i);
648 PixelTrait traits=GetPixelChannelTraits(image,channel);
649 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
651 if ((traits == UndefinedPixelTrait) ||
652 (reconstruct_traits == UndefinedPixelTrait) ||
653 ((reconstruct_traits & UpdatePixelTrait) == 0))
655 distance=QuantumScale*fabs(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
657 channel_distortion[i]+=distance;
658 channel_distortion[CompositePixelChannel]+=distance;
660 p+=GetPixelChannels(image);
661 q+=GetPixelChannels(reconstruct_image);
663 #if defined(MAGICKCORE_OPENMP_SUPPORT)
664 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
666 for (j=0; j <= MaxPixelChannels; j++)
667 distortion[j]+=channel_distortion[j];
669 reconstruct_view=DestroyCacheView(reconstruct_view);
670 image_view=DestroyCacheView(image_view);
671 for (j=0; j <= MaxPixelChannels; j++)
672 distortion[j]/=((double) columns*rows);
673 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
677 static MagickBooleanType GetMeanErrorPerPixel(Image *image,
678 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
703 rows=MagickMax(image->rows,reconstruct_image->rows);
704 columns=MagickMax(image->columns,reconstruct_image->columns);
705 image_view=AcquireVirtualCacheView(image,exception);
706 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
707 for (y=0; y < (ssize_t) rows; y++)
709 register const Quantum
716 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
717 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
718 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
723 for (x=0; x < (ssize_t) columns; x++)
732 if (GetPixelReadMask(image,p) == 0)
734 p+=GetPixelChannels(image);
735 q+=GetPixelChannels(reconstruct_image);
738 Sa=QuantumScale*GetPixelAlpha(image,p);
739 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
740 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
745 PixelChannel channel=GetPixelChannelChannel(image,i);
746 PixelTrait traits=GetPixelChannelTraits(image,channel);
747 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
749 if ((traits == UndefinedPixelTrait) ||
750 (reconstruct_traits == UndefinedPixelTrait) ||
751 ((reconstruct_traits & UpdatePixelTrait) == 0))
753 distance=fabs(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,channel,q));
754 distortion[i]+=distance;
755 distortion[CompositePixelChannel]+=distance;
756 mean_error+=distance*distance;
757 if (distance > maximum_error)
758 maximum_error=distance;
761 p+=GetPixelChannels(image);
762 q+=GetPixelChannels(reconstruct_image);
765 reconstruct_view=DestroyCacheView(reconstruct_view);
766 image_view=DestroyCacheView(image_view);
767 image->error.mean_error_per_pixel=distortion[CompositePixelChannel]/area;
768 image->error.normalized_mean_error=QuantumScale*QuantumScale*mean_error/area;
769 image->error.normalized_maximum_error=QuantumScale*maximum_error;
773 static MagickBooleanType GetMeanSquaredDistortion(const Image *image,
774 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
794 rows=MagickMax(image->rows,reconstruct_image->rows);
795 columns=MagickMax(image->columns,reconstruct_image->columns);
796 image_view=AcquireVirtualCacheView(image,exception);
797 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
798 #if defined(MAGICKCORE_OPENMP_SUPPORT)
799 #pragma omp parallel for schedule(static,4) shared(status) \
800 magick_threads(image,image,rows,1)
802 for (y=0; y < (ssize_t) rows; y++)
805 channel_distortion[MaxPixelChannels+1];
807 register const Quantum
814 if (status == MagickFalse)
816 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
817 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
818 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
823 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
824 for (x=0; x < (ssize_t) columns; x++)
833 if (GetPixelReadMask(image,p) == 0)
835 p+=GetPixelChannels(image);
836 q+=GetPixelChannels(reconstruct_image);
839 Sa=QuantumScale*GetPixelAlpha(image,p);
840 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
841 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
846 PixelChannel channel=GetPixelChannelChannel(image,i);
847 PixelTrait traits=GetPixelChannelTraits(image,channel);
848 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
850 if ((traits == UndefinedPixelTrait) ||
851 (reconstruct_traits == UndefinedPixelTrait) ||
852 ((reconstruct_traits & UpdatePixelTrait) == 0))
854 distance=QuantumScale*(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
856 channel_distortion[i]+=distance*distance;
857 channel_distortion[CompositePixelChannel]+=distance*distance;
859 p+=GetPixelChannels(image);
860 q+=GetPixelChannels(reconstruct_image);
862 #if defined(MAGICKCORE_OPENMP_SUPPORT)
863 #pragma omp critical (MagickCore_GetMeanSquaredError)
865 for (j=0; j <= MaxPixelChannels; j++)
866 distortion[j]+=channel_distortion[j];
868 reconstruct_view=DestroyCacheView(reconstruct_view);
869 image_view=DestroyCacheView(image_view);
870 for (j=0; j <= MaxPixelChannels; j++)
871 distortion[j]/=((double) columns*rows);
872 distortion[CompositePixelChannel]/=GetImageChannels(image);
876 static MagickBooleanType GetNormalizedCrossCorrelationDistortion(
877 const Image *image,const Image *reconstruct_image,double *distortion,
878 ExceptionInfo *exception)
880 #define SimilarityImageTag "Similarity/Image"
888 *reconstruct_statistics;
910 Normalize to account for variation due to lighting and exposure condition.
912 image_statistics=GetImageStatistics(image,exception);
913 reconstruct_statistics=GetImageStatistics(reconstruct_image,exception);
914 if ((image_statistics == (ChannelStatistics *) NULL) ||
915 (reconstruct_statistics == (ChannelStatistics *) NULL))
917 if (image_statistics != (ChannelStatistics *) NULL)
918 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
920 if (reconstruct_statistics != (ChannelStatistics *) NULL)
921 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
922 reconstruct_statistics);
927 for (i=0; i <= MaxPixelChannels; i++)
929 rows=MagickMax(image->rows,reconstruct_image->rows);
930 columns=MagickMax(image->columns,reconstruct_image->columns);
931 area=1.0/((double) columns*rows-1);
932 image_view=AcquireVirtualCacheView(image,exception);
933 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
934 for (y=0; y < (ssize_t) rows; y++)
936 register const Quantum
943 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
944 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
945 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
950 for (x=0; x < (ssize_t) columns; x++)
956 if (GetPixelReadMask(image,p) == 0)
958 p+=GetPixelChannels(image);
959 q+=GetPixelChannels(reconstruct_image);
962 Sa=QuantumScale*(image->alpha_trait != UndefinedPixelTrait ?
963 GetPixelAlpha(image,p) : OpaqueAlpha);
964 Da=QuantumScale*(reconstruct_image->alpha_trait != UndefinedPixelTrait ?
965 GetPixelAlpha(reconstruct_image,q) : OpaqueAlpha);
966 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
968 PixelChannel channel=GetPixelChannelChannel(image,i);
969 PixelTrait traits=GetPixelChannelTraits(image,channel);
970 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
972 if ((traits == UndefinedPixelTrait) ||
973 (reconstruct_traits == UndefinedPixelTrait) ||
974 ((reconstruct_traits & UpdatePixelTrait) == 0))
976 if (channel == AlphaPixelChannel)
978 distortion[i]+=area*QuantumScale*(p[i]-
979 image_statistics[channel].mean)*(GetPixelChannel(
980 reconstruct_image,channel,q)-
981 reconstruct_statistics[channel].mean);
985 distortion[i]+=area*QuantumScale*(Sa*p[i]-
986 image_statistics[channel].mean)*(Da*GetPixelChannel(
987 reconstruct_image,channel,q)-
988 reconstruct_statistics[channel].mean);
991 p+=GetPixelChannels(image);
992 q+=GetPixelChannels(reconstruct_image);
994 if (image->progress_monitor != (MagickProgressMonitor) NULL)
999 proceed=SetImageProgress(image,SimilarityImageTag,progress++,rows);
1000 if (proceed == MagickFalse)
1007 reconstruct_view=DestroyCacheView(reconstruct_view);
1008 image_view=DestroyCacheView(image_view);
1010 Divide by the standard deviation.
1012 distortion[CompositePixelChannel]=0.0;
1013 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1018 PixelChannel channel=GetPixelChannelChannel(image,i);
1019 gamma=image_statistics[channel].standard_deviation*
1020 reconstruct_statistics[channel].standard_deviation;
1021 gamma=PerceptibleReciprocal(gamma);
1022 distortion[i]=QuantumRange*gamma*distortion[i];
1023 distortion[CompositePixelChannel]+=distortion[i]*distortion[i];
1025 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]/
1026 GetImageChannels(image));
1030 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1031 reconstruct_statistics);
1032 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1037 static MagickBooleanType GetPeakAbsoluteDistortion(const Image *image,
1038 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1055 rows=MagickMax(image->rows,reconstruct_image->rows);
1056 columns=MagickMax(image->columns,reconstruct_image->columns);
1057 image_view=AcquireVirtualCacheView(image,exception);
1058 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1059 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1060 #pragma omp parallel for schedule(static,4) shared(status) \
1061 magick_threads(image,image,rows,1)
1063 for (y=0; y < (ssize_t) rows; y++)
1066 channel_distortion[MaxPixelChannels+1];
1068 register const Quantum
1076 if (status == MagickFalse)
1078 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1079 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1080 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
1085 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
1086 for (x=0; x < (ssize_t) columns; x++)
1095 if (GetPixelReadMask(image,p) == 0)
1097 p+=GetPixelChannels(image);
1098 q+=GetPixelChannels(reconstruct_image);
1101 Sa=QuantumScale*GetPixelAlpha(image,p);
1102 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
1103 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1108 PixelChannel channel=GetPixelChannelChannel(image,i);
1109 PixelTrait traits=GetPixelChannelTraits(image,channel);
1110 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
1112 if ((traits == UndefinedPixelTrait) ||
1113 (reconstruct_traits == UndefinedPixelTrait) ||
1114 ((reconstruct_traits & UpdatePixelTrait) == 0))
1116 distance=QuantumScale*fabs(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
1118 if (distance > channel_distortion[i])
1119 channel_distortion[i]=distance;
1120 if (distance > channel_distortion[CompositePixelChannel])
1121 channel_distortion[CompositePixelChannel]=distance;
1123 p+=GetPixelChannels(image);
1124 q+=GetPixelChannels(reconstruct_image);
1126 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1127 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
1129 for (j=0; j <= MaxPixelChannels; j++)
1130 if (channel_distortion[j] > distortion[j])
1131 distortion[j]=channel_distortion[j];
1133 reconstruct_view=DestroyCacheView(reconstruct_view);
1134 image_view=DestroyCacheView(image_view);
1138 static inline double MagickLog10(const double x)
1140 #define Log10Epsilon (1.0e-11)
1142 if (fabs(x) < Log10Epsilon)
1143 return(log10(Log10Epsilon));
1144 return(log10(fabs(x)));
1147 static MagickBooleanType GetPeakSignalToNoiseRatio(const Image *image,
1148 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1156 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
1157 for (i=0; i <= MaxPixelChannels; i++)
1158 distortion[i]=20.0*MagickLog10((double) 1.0/sqrt(distortion[i]));
1162 static MagickBooleanType GetPerceptualHashDistortion(const Image *image,
1163 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1165 ChannelPerceptualHash
1173 Compute perceptual hash in the sRGB colorspace.
1175 image_phash=GetImagePerceptualHash(image,exception);
1176 if (image_phash == (ChannelPerceptualHash *) NULL)
1177 return(MagickFalse);
1178 reconstruct_phash=GetImagePerceptualHash(reconstruct_image,exception);
1179 if (reconstruct_phash == (ChannelPerceptualHash *) NULL)
1181 image_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(image_phash);
1182 return(MagickFalse);
1184 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1185 #pragma omp parallel for schedule(static,4)
1187 for (channel=0; channel < MaxPixelChannels; channel++)
1196 for (i=0; i < MaximumNumberOfImageMoments; i++)
1202 alpha=image_phash[channel].srgb_hu_phash[i];
1203 beta=reconstruct_phash[channel].srgb_hu_phash[i];
1204 difference+=(beta-alpha)*(beta-alpha);
1206 distortion[channel]+=difference;
1207 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1208 #pragma omp critical (MagickCore_GetPerceptualHashDistortion)
1210 distortion[CompositePixelChannel]+=difference;
1213 Compute perceptual hash in the HCLP colorspace.
1215 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1216 #pragma omp parallel for schedule(static,4)
1218 for (channel=0; channel < MaxPixelChannels; channel++)
1227 for (i=0; i < MaximumNumberOfImageMoments; i++)
1233 alpha=image_phash[channel].hclp_hu_phash[i];
1234 beta=reconstruct_phash[channel].hclp_hu_phash[i];
1235 difference+=(beta-alpha)*(beta-alpha);
1237 distortion[channel]+=difference;
1238 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1239 #pragma omp critical (MagickCore_GetPerceptualHashDistortion)
1241 distortion[CompositePixelChannel]+=difference;
1246 reconstruct_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(
1248 image_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(image_phash);
1252 static MagickBooleanType GetRootMeanSquaredDistortion(const Image *image,
1253 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1261 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
1262 for (i=0; i <= MaxPixelChannels; i++)
1263 distortion[i]=sqrt(distortion[i]);
1267 MagickExport MagickBooleanType GetImageDistortion(Image *image,
1268 const Image *reconstruct_image,const MetricType metric,double *distortion,
1269 ExceptionInfo *exception)
1272 *channel_distortion;
1280 assert(image != (Image *) NULL);
1281 assert(image->signature == MagickCoreSignature);
1282 if (image->debug != MagickFalse)
1283 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1284 assert(reconstruct_image != (const Image *) NULL);
1285 assert(reconstruct_image->signature == MagickCoreSignature);
1286 assert(distortion != (double *) NULL);
1288 if (image->debug != MagickFalse)
1289 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1290 if (metric != PerceptualHashErrorMetric)
1291 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1292 ThrowBinaryException(ImageError,"ImageMorphologyDiffers",image->filename);
1294 Get image distortion.
1296 length=MaxPixelChannels+1;
1297 channel_distortion=(double *) AcquireQuantumMemory(length,
1298 sizeof(*channel_distortion));
1299 if (channel_distortion == (double *) NULL)
1300 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1301 (void) ResetMagickMemory(channel_distortion,0,length*
1302 sizeof(*channel_distortion));
1305 case AbsoluteErrorMetric:
1307 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1311 case FuzzErrorMetric:
1313 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1317 case MeanAbsoluteErrorMetric:
1319 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1320 channel_distortion,exception);
1323 case MeanErrorPerPixelErrorMetric:
1325 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1329 case MeanSquaredErrorMetric:
1331 status=GetMeanSquaredDistortion(image,reconstruct_image,
1332 channel_distortion,exception);
1335 case NormalizedCrossCorrelationErrorMetric:
1338 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1339 channel_distortion,exception);
1342 case PeakAbsoluteErrorMetric:
1344 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1345 channel_distortion,exception);
1348 case PeakSignalToNoiseRatioErrorMetric:
1350 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1351 channel_distortion,exception);
1354 case PerceptualHashErrorMetric:
1356 status=GetPerceptualHashDistortion(image,reconstruct_image,
1357 channel_distortion,exception);
1360 case RootMeanSquaredErrorMetric:
1362 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1363 channel_distortion,exception);
1367 *distortion=channel_distortion[CompositePixelChannel];
1368 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1369 (void) FormatImageProperty(image,"distortion","%.*g",GetMagickPrecision(),
1375 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1379 % G e t I m a g e D i s t o r t i o n s %
1383 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1385 % GetImageDistortions() compares the pixel channels of an image to a
1386 % reconstructed image and returns the specified distortion metric for each
1389 % The format of the GetImageDistortions method is:
1391 % double *GetImageDistortions(const Image *image,
1392 % const Image *reconstruct_image,const MetricType metric,
1393 % ExceptionInfo *exception)
1395 % A description of each parameter follows:
1397 % o image: the image.
1399 % o reconstruct_image: the reconstruct image.
1401 % o metric: the metric.
1403 % o exception: return any errors or warnings in this structure.
1406 MagickExport double *GetImageDistortions(Image *image,
1407 const Image *reconstruct_image,const MetricType metric,
1408 ExceptionInfo *exception)
1411 *channel_distortion;
1419 assert(image != (Image *) NULL);
1420 assert(image->signature == MagickCoreSignature);
1421 if (image->debug != MagickFalse)
1422 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1423 assert(reconstruct_image != (const Image *) NULL);
1424 assert(reconstruct_image->signature == MagickCoreSignature);
1425 if (image->debug != MagickFalse)
1426 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1427 if (metric != PerceptualHashErrorMetric)
1428 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1430 (void) ThrowMagickException(exception,GetMagickModule(),ImageError,
1431 "ImageMorphologyDiffers","`%s'",image->filename);
1432 return((double *) NULL);
1435 Get image distortion.
1437 length=MaxPixelChannels+1UL;
1438 channel_distortion=(double *) AcquireQuantumMemory(length,
1439 sizeof(*channel_distortion));
1440 if (channel_distortion == (double *) NULL)
1441 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1442 (void) ResetMagickMemory(channel_distortion,0,length*
1443 sizeof(*channel_distortion));
1447 case AbsoluteErrorMetric:
1449 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1453 case FuzzErrorMetric:
1455 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1459 case MeanAbsoluteErrorMetric:
1461 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1462 channel_distortion,exception);
1465 case MeanErrorPerPixelErrorMetric:
1467 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1471 case MeanSquaredErrorMetric:
1473 status=GetMeanSquaredDistortion(image,reconstruct_image,
1474 channel_distortion,exception);
1477 case NormalizedCrossCorrelationErrorMetric:
1480 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1481 channel_distortion,exception);
1484 case PeakAbsoluteErrorMetric:
1486 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1487 channel_distortion,exception);
1490 case PeakSignalToNoiseRatioErrorMetric:
1492 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1493 channel_distortion,exception);
1496 case PerceptualHashErrorMetric:
1498 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1499 channel_distortion,exception);
1502 case RootMeanSquaredErrorMetric:
1504 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1505 channel_distortion,exception);
1509 if (status == MagickFalse)
1511 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1512 return((double *) NULL);
1514 return(channel_distortion);
1518 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1522 % I s I m a g e s E q u a l %
1526 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1528 % IsImagesEqual() measures the difference between colors at each pixel
1529 % location of two images. A value other than 0 means the colors match
1530 % exactly. Otherwise an error measure is computed by summing over all
1531 % pixels in an image the distance squared in RGB space between each image
1532 % pixel and its corresponding pixel in the reconstruct image. The error
1533 % measure is assigned to these image members:
1535 % o mean_error_per_pixel: The mean error for any single pixel in
1538 % o normalized_mean_error: The normalized mean quantization error for
1539 % any single pixel in the image. This distance measure is normalized to
1540 % a range between 0 and 1. It is independent of the range of red, green,
1541 % and blue values in the image.
1543 % o normalized_maximum_error: The normalized maximum quantization
1544 % error for any single pixel in the image. This distance measure is
1545 % normalized to a range between 0 and 1. It is independent of the range
1546 % of red, green, and blue values in your image.
1548 % A small normalized mean square error, accessed as
1549 % image->normalized_mean_error, suggests the images are very similar in
1550 % spatial layout and color.
1552 % The format of the IsImagesEqual method is:
1554 % MagickBooleanType IsImagesEqual(Image *image,
1555 % const Image *reconstruct_image,ExceptionInfo *exception)
1557 % A description of each parameter follows.
1559 % o image: the image.
1561 % o reconstruct_image: the reconstruct image.
1563 % o exception: return any errors or warnings in this structure.
1566 MagickExport MagickBooleanType IsImagesEqual(Image *image,
1567 const Image *reconstruct_image,ExceptionInfo *exception)
1580 mean_error_per_pixel;
1589 assert(image != (Image *) NULL);
1590 assert(image->signature == MagickCoreSignature);
1591 assert(reconstruct_image != (const Image *) NULL);
1592 assert(reconstruct_image->signature == MagickCoreSignature);
1593 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1594 ThrowBinaryException(ImageError,"ImageMorphologyDiffers",image->filename);
1597 mean_error_per_pixel=0.0;
1599 rows=MagickMax(image->rows,reconstruct_image->rows);
1600 columns=MagickMax(image->columns,reconstruct_image->columns);
1601 image_view=AcquireVirtualCacheView(image,exception);
1602 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1603 for (y=0; y < (ssize_t) rows; y++)
1605 register const Quantum
1612 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1613 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1614 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1616 for (x=0; x < (ssize_t) columns; x++)
1621 if (GetPixelReadMask(image,p) == 0)
1623 p+=GetPixelChannels(image);
1624 q+=GetPixelChannels(reconstruct_image);
1627 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1632 PixelChannel channel=GetPixelChannelChannel(image,i);
1633 PixelTrait traits=GetPixelChannelTraits(image,channel);
1634 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
1636 if ((traits == UndefinedPixelTrait) ||
1637 (reconstruct_traits == UndefinedPixelTrait) ||
1638 ((reconstruct_traits & UpdatePixelTrait) == 0))
1640 distance=fabs(p[i]-(double) GetPixelChannel(reconstruct_image,
1642 if (distance >= MagickEpsilon)
1644 mean_error_per_pixel+=distance;
1645 mean_error+=distance*distance;
1646 if (distance > maximum_error)
1647 maximum_error=distance;
1651 p+=GetPixelChannels(image);
1652 q+=GetPixelChannels(reconstruct_image);
1655 reconstruct_view=DestroyCacheView(reconstruct_view);
1656 image_view=DestroyCacheView(image_view);
1657 image->error.mean_error_per_pixel=(double) (mean_error_per_pixel/area);
1658 image->error.normalized_mean_error=(double) (QuantumScale*QuantumScale*
1660 image->error.normalized_maximum_error=(double) (QuantumScale*maximum_error);
1661 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
1666 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1670 % S i m i l a r i t y I m a g e %
1674 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1676 % SimilarityImage() compares the reference image of the image and returns the
1677 % best match offset. In addition, it returns a similarity image such that an
1678 % exact match location is completely white and if none of the pixels match,
1679 % black, otherwise some gray level in-between.
1681 % The format of the SimilarityImageImage method is:
1683 % Image *SimilarityImage(const Image *image,const Image *reference,
1684 % const MetricType metric,const double similarity_threshold,
1685 % RectangleInfo *offset,double *similarity,ExceptionInfo *exception)
1687 % A description of each parameter follows:
1689 % o image: the image.
1691 % o reference: find an area of the image that closely resembles this image.
1693 % o metric: the metric.
1695 % o similarity_threshold: minimum distortion for (sub)image match.
1697 % o offset: the best match offset of the reference image within the image.
1699 % o similarity: the computed similarity between the images.
1701 % o exception: return any errors or warnings in this structure.
1705 static double GetSimilarityMetric(const Image *image,const Image *reference,
1706 const MetricType metric,const ssize_t x_offset,const ssize_t y_offset,
1707 ExceptionInfo *exception)
1721 SetGeometry(reference,&geometry);
1722 geometry.x=x_offset;
1723 geometry.y=y_offset;
1724 similarity_image=CropImage(image,&geometry,exception);
1725 if (similarity_image == (Image *) NULL)
1728 status=GetImageDistortion(similarity_image,reference,metric,&distortion,
1730 similarity_image=DestroyImage(similarity_image);
1731 if (status == MagickFalse)
1736 MagickExport Image *SimilarityImage(Image *image,const Image *reference,
1737 const MetricType metric,const double similarity_threshold,
1738 RectangleInfo *offset,double *similarity_metric,ExceptionInfo *exception)
1740 #define SimilarityImageTag "Similarity/Image"
1757 assert(image != (const Image *) NULL);
1758 assert(image->signature == MagickCoreSignature);
1759 if (image->debug != MagickFalse)
1760 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1761 assert(exception != (ExceptionInfo *) NULL);
1762 assert(exception->signature == MagickCoreSignature);
1763 assert(offset != (RectangleInfo *) NULL);
1764 SetGeometry(reference,offset);
1765 *similarity_metric=MagickMaximumValue;
1766 if (ValidateImageMorphology(image,reference) == MagickFalse)
1767 ThrowImageException(ImageError,"ImageMorphologyDiffers");
1768 similarity_image=CloneImage(image,image->columns-reference->columns+1,
1769 image->rows-reference->rows+1,MagickTrue,exception);
1770 if (similarity_image == (Image *) NULL)
1771 return((Image *) NULL);
1772 status=SetImageStorageClass(similarity_image,DirectClass,exception);
1773 if (status == MagickFalse)
1775 similarity_image=DestroyImage(similarity_image);
1776 return((Image *) NULL);
1778 (void) SetImageAlphaChannel(similarity_image,DeactivateAlphaChannel,
1781 Measure similarity of reference image against image.
1785 similarity_view=AcquireAuthenticCacheView(similarity_image,exception);
1786 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1787 #pragma omp parallel for schedule(static,4) \
1788 shared(progress,status,similarity_metric) \
1789 magick_threads(image,image,image->rows,1)
1791 for (y=0; y < (ssize_t) (image->rows-reference->rows+1); y++)
1802 if (status == MagickFalse)
1804 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1805 #pragma omp flush(similarity_metric)
1807 if (*similarity_metric <= similarity_threshold)
1809 q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
1811 if (q == (Quantum *) NULL)
1816 for (x=0; x < (ssize_t) (image->columns-reference->columns+1); x++)
1821 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1822 #pragma omp flush(similarity_metric)
1824 if (*similarity_metric <= similarity_threshold)
1826 similarity=GetSimilarityMetric(image,reference,metric,x,y,exception);
1827 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1828 #pragma omp critical (MagickCore_SimilarityImage)
1830 if ((metric == NormalizedCrossCorrelationErrorMetric) ||
1831 (metric == UndefinedErrorMetric))
1832 similarity=1.0-similarity;
1833 if (similarity < *similarity_metric)
1837 *similarity_metric=similarity;
1839 if (metric == PerceptualHashErrorMetric)
1840 similarity=MagickMin(0.01*similarity,1.0);
1841 if (GetPixelReadMask(similarity_image,q) == 0)
1843 SetPixelBackgoundColor(similarity_image,q);
1844 q+=GetPixelChannels(similarity_image);
1847 for (i=0; i < (ssize_t) GetPixelChannels(similarity_image); i++)
1849 PixelChannel channel=GetPixelChannelChannel(image,i);
1850 PixelTrait traits=GetPixelChannelTraits(image,channel);
1851 PixelTrait similarity_traits=GetPixelChannelTraits(similarity_image,
1853 if ((traits == UndefinedPixelTrait) ||
1854 (similarity_traits == UndefinedPixelTrait) ||
1855 ((similarity_traits & UpdatePixelTrait) == 0))
1857 SetPixelChannel(similarity_image,channel,ClampToQuantum(QuantumRange-
1858 QuantumRange*similarity),q);
1860 q+=GetPixelChannels(similarity_image);
1862 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
1864 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1869 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1870 #pragma omp critical (MagickCore_SimilarityImage)
1872 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
1874 if (proceed == MagickFalse)
1878 similarity_view=DestroyCacheView(similarity_view);
1879 if (status == MagickFalse)
1880 similarity_image=DestroyImage(similarity_image);
1881 return(similarity_image);