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-2014 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.
107 MagickExport Image *CompareImages(Image *image,const Image *reconstruct_image,
108 const MetricType metric,double *distortion,ExceptionInfo *exception)
132 assert(image != (Image *) NULL);
133 assert(image->signature == MagickSignature);
134 if (image->debug != MagickFalse)
135 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
136 assert(reconstruct_image != (const Image *) NULL);
137 assert(reconstruct_image->signature == MagickSignature);
138 assert(distortion != (double *) NULL);
140 if (image->debug != MagickFalse)
141 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
142 if (metric != PerceptualHashErrorMetric)
143 if ((reconstruct_image->columns != image->columns) ||
144 (reconstruct_image->rows != image->rows))
145 ThrowImageException(ImageError,"ImageSizeDiffers");
146 status=GetImageDistortion(image,reconstruct_image,metric,distortion,
148 if (status == MagickFalse)
149 return((Image *) NULL);
150 difference_image=CloneImage(image,0,0,MagickTrue,exception);
151 if (difference_image == (Image *) NULL)
152 return((Image *) NULL);
153 (void) SetImageAlphaChannel(difference_image,OpaqueAlphaChannel,exception);
154 highlight_image=CloneImage(image,image->columns,image->rows,MagickTrue,
156 if (highlight_image == (Image *) NULL)
158 difference_image=DestroyImage(difference_image);
159 return((Image *) NULL);
161 status=SetImageStorageClass(highlight_image,DirectClass,exception);
162 if (status == MagickFalse)
164 difference_image=DestroyImage(difference_image);
165 highlight_image=DestroyImage(highlight_image);
166 return((Image *) NULL);
168 (void) SetImageAlphaChannel(highlight_image,OpaqueAlphaChannel,exception);
169 (void) QueryColorCompliance("#f1001ecc",AllCompliance,&highlight,exception);
170 artifact=GetImageArtifact(image,"highlight-color");
171 if (artifact != (const char *) NULL)
172 (void) QueryColorCompliance(artifact,AllCompliance,&highlight,exception);
173 (void) QueryColorCompliance("#ffffffcc",AllCompliance,&lowlight,exception);
174 artifact=GetImageArtifact(image,"lowlight-color");
175 if (artifact != (const char *) NULL)
176 (void) QueryColorCompliance(artifact,AllCompliance,&lowlight,exception);
178 Generate difference image.
181 image_view=AcquireVirtualCacheView(image,exception);
182 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
183 highlight_view=AcquireAuthenticCacheView(highlight_image,exception);
184 #if defined(MAGICKCORE_OPENMP_SUPPORT)
185 #pragma omp parallel for schedule(static,4) shared(status) \
186 magick_threads(image,highlight_image,image->rows,1)
188 for (y=0; y < (ssize_t) image->rows; y++)
193 register const Quantum
203 if (status == MagickFalse)
205 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
206 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,image->columns,1,
208 r=QueueCacheViewAuthenticPixels(highlight_view,0,y,highlight_image->columns,
210 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL) ||
211 (r == (Quantum *) NULL))
216 for (x=0; x < (ssize_t) image->columns; x++)
228 if (GetPixelReadMask(image,p) == 0)
230 SetPixelInfoPixel(highlight_image,&lowlight,r);
231 p+=GetPixelChannels(image);
232 q+=GetPixelChannels(reconstruct_image);
233 r+=GetPixelChannels(highlight_image);
236 difference=MagickFalse;
237 Sa=QuantumScale*GetPixelAlpha(image,p);
238 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
239 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
244 PixelChannel channel=GetPixelChannelChannel(image,i);
245 PixelTrait traits=GetPixelChannelTraits(image,channel);
246 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
248 if ((traits == UndefinedPixelTrait) ||
249 (reconstruct_traits == UndefinedPixelTrait) ||
250 ((reconstruct_traits & UpdatePixelTrait) == 0))
252 distance=Sa*p[i]-Da*GetPixelChannel(reconstruct_image,channel,q);
253 if (fabs((double) distance) >= MagickEpsilon)
254 difference=MagickTrue;
256 if (difference == MagickFalse)
257 SetPixelInfoPixel(highlight_image,&lowlight,r);
259 SetPixelInfoPixel(highlight_image,&highlight,r);
260 p+=GetPixelChannels(image);
261 q+=GetPixelChannels(reconstruct_image);
262 r+=GetPixelChannels(highlight_image);
264 sync=SyncCacheViewAuthenticPixels(highlight_view,exception);
265 if (sync == MagickFalse)
268 highlight_view=DestroyCacheView(highlight_view);
269 reconstruct_view=DestroyCacheView(reconstruct_view);
270 image_view=DestroyCacheView(image_view);
271 (void) CompositeImage(difference_image,highlight_image,image->compose,
272 MagickTrue,0,0,exception);
273 highlight_image=DestroyImage(highlight_image);
274 if (status == MagickFalse)
275 difference_image=DestroyImage(difference_image);
276 return(difference_image);
280 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
284 % G e t I m a g e D i s t o r t i o n %
288 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
290 % GetImageDistortion() compares one or more pixel channels of an image to a
291 % reconstructed image and returns the specified distortion metric.
293 % The format of the GetImageDistortion method is:
295 % MagickBooleanType GetImageDistortion(const Image *image,
296 % const Image *reconstruct_image,const MetricType metric,
297 % double *distortion,ExceptionInfo *exception)
299 % A description of each parameter follows:
301 % o image: the image.
303 % o reconstruct_image: the reconstruct image.
305 % o metric: the metric.
307 % o distortion: the computed distortion between the images.
309 % o exception: return any errors or warnings in this structure.
313 static inline double MagickMax(const double x,const double y)
320 static MagickBooleanType GetAbsoluteDistortion(const Image *image,
321 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
337 Compute the absolute difference in pixels between two images.
340 if (image->fuzz == 0.0)
341 fuzz=MagickMax(reconstruct_image->fuzz,MagickSQ1_2)*
342 MagickMax(reconstruct_image->fuzz,MagickSQ1_2);
344 if (reconstruct_image->fuzz == 0.0)
345 fuzz=MagickMax(image->fuzz,MagickSQ1_2)*
346 MagickMax(image->fuzz,MagickSQ1_2);
348 fuzz=MagickMax(image->fuzz,MagickSQ1_2)*
349 MagickMax(reconstruct_image->fuzz,MagickSQ1_2);
350 image_view=AcquireVirtualCacheView(image,exception);
351 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
352 #if defined(MAGICKCORE_OPENMP_SUPPORT)
353 #pragma omp parallel for schedule(static,4) shared(status) \
354 magick_threads(image,image,image->rows,1)
356 for (y=0; y < (ssize_t) image->rows; y++)
359 channel_distortion[MaxPixelChannels+1];
361 register const Quantum
369 if (status == MagickFalse)
371 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
372 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
374 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
379 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
380 for (x=0; x < (ssize_t) image->columns; x++)
392 if (GetPixelReadMask(image,p) == 0)
394 p+=GetPixelChannels(image);
395 q+=GetPixelChannels(reconstruct_image);
398 difference=MagickFalse;
399 Sa=QuantumScale*GetPixelAlpha(image,p);
400 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
401 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
406 PixelChannel channel=GetPixelChannelChannel(image,i);
407 PixelTrait traits=GetPixelChannelTraits(image,channel);
408 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
410 if ((traits == UndefinedPixelTrait) ||
411 (reconstruct_traits == UndefinedPixelTrait) ||
412 ((reconstruct_traits & UpdatePixelTrait) == 0))
414 distance=Sa*p[i]-Da*GetPixelChannel(reconstruct_image,channel,q);
415 if ((distance*distance) > fuzz)
417 difference=MagickTrue;
421 if (difference != MagickFalse)
423 channel_distortion[i]++;
424 channel_distortion[CompositePixelChannel]++;
426 p+=GetPixelChannels(image);
427 q+=GetPixelChannels(reconstruct_image);
429 #if defined(MAGICKCORE_OPENMP_SUPPORT)
430 #pragma omp critical (MagickCore_GetAbsoluteError)
432 for (i=0; i <= MaxPixelChannels; i++)
433 distortion[i]+=channel_distortion[i];
435 reconstruct_view=DestroyCacheView(reconstruct_view);
436 image_view=DestroyCacheView(image_view);
440 static size_t GetImageChannels(const Image *image)
449 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
451 PixelChannel channel=GetPixelChannelChannel(image,i);
452 PixelTrait traits=GetPixelChannelTraits(image,channel);
453 if ((traits & UpdatePixelTrait) != 0)
459 static MagickBooleanType GetFuzzDistortion(const Image *image,
460 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
476 image_view=AcquireVirtualCacheView(image,exception);
477 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
478 #if defined(MAGICKCORE_OPENMP_SUPPORT)
479 #pragma omp parallel for schedule(static,4) shared(status) \
480 magick_threads(image,image,image->rows,1)
482 for (y=0; y < (ssize_t) image->rows; y++)
485 channel_distortion[MaxPixelChannels+1];
487 register const Quantum
495 if (status == MagickFalse)
497 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
498 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
500 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
505 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
506 for (x=0; x < (ssize_t) image->columns; x++)
515 if (GetPixelReadMask(image,p) == 0)
517 p+=GetPixelChannels(image);
518 q+=GetPixelChannels(reconstruct_image);
521 Sa=QuantumScale*GetPixelAlpha(image,p);
522 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
523 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
528 PixelChannel channel=GetPixelChannelChannel(image,i);
529 PixelTrait traits=GetPixelChannelTraits(image,channel);
530 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
532 if ((traits == UndefinedPixelTrait) ||
533 (reconstruct_traits == UndefinedPixelTrait) ||
534 ((reconstruct_traits & UpdatePixelTrait) == 0))
536 distance=QuantumScale*(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
538 channel_distortion[i]+=distance*distance;
539 channel_distortion[CompositePixelChannel]+=distance*distance;
541 p+=GetPixelChannels(image);
542 q+=GetPixelChannels(reconstruct_image);
544 #if defined(MAGICKCORE_OPENMP_SUPPORT)
545 #pragma omp critical (MagickCore_GetFuzzDistortion)
547 for (i=0; i <= MaxPixelChannels; i++)
548 distortion[i]+=channel_distortion[i];
550 reconstruct_view=DestroyCacheView(reconstruct_view);
551 image_view=DestroyCacheView(image_view);
552 for (i=0; i <= MaxPixelChannels; i++)
553 distortion[i]/=((double) image->columns*image->rows);
554 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
555 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]);
559 static MagickBooleanType GetMeanAbsoluteDistortion(const Image *image,
560 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
576 image_view=AcquireVirtualCacheView(image,exception);
577 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
578 #if defined(MAGICKCORE_OPENMP_SUPPORT)
579 #pragma omp parallel for schedule(static,4) shared(status) \
580 magick_threads(image,image,image->rows,1)
582 for (y=0; y < (ssize_t) image->rows; y++)
585 channel_distortion[MaxPixelChannels+1];
587 register const Quantum
595 if (status == MagickFalse)
597 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
598 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
600 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
605 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
606 for (x=0; x < (ssize_t) image->columns; x++)
615 if (GetPixelReadMask(image,p) == 0)
617 p+=GetPixelChannels(image);
618 q+=GetPixelChannels(reconstruct_image);
621 Sa=QuantumScale*GetPixelAlpha(image,p);
622 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
623 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
628 PixelChannel channel=GetPixelChannelChannel(image,i);
629 PixelTrait traits=GetPixelChannelTraits(image,channel);
630 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
632 if ((traits == UndefinedPixelTrait) ||
633 (reconstruct_traits == UndefinedPixelTrait) ||
634 ((reconstruct_traits & UpdatePixelTrait) == 0))
636 distance=QuantumScale*fabs(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
638 channel_distortion[i]+=distance;
639 channel_distortion[CompositePixelChannel]+=distance;
641 p+=GetPixelChannels(image);
642 q+=GetPixelChannels(reconstruct_image);
644 #if defined(MAGICKCORE_OPENMP_SUPPORT)
645 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
647 for (i=0; i <= MaxPixelChannels; i++)
648 distortion[i]+=channel_distortion[i];
650 reconstruct_view=DestroyCacheView(reconstruct_view);
651 image_view=DestroyCacheView(image_view);
652 for (i=0; i <= MaxPixelChannels; i++)
653 distortion[i]/=((double) image->columns*image->rows);
654 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
658 static MagickBooleanType GetMeanErrorPerPixel(Image *image,
659 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
680 image_view=AcquireVirtualCacheView(image,exception);
681 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
682 for (y=0; y < (ssize_t) image->rows; y++)
684 register const Quantum
691 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
692 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
694 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
699 for (x=0; x < (ssize_t) image->columns; x++)
708 if (GetPixelReadMask(image,p) == 0)
710 p+=GetPixelChannels(image);
711 q+=GetPixelChannels(reconstruct_image);
714 Sa=QuantumScale*GetPixelAlpha(image,p);
715 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
716 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
721 PixelChannel channel=GetPixelChannelChannel(image,i);
722 PixelTrait traits=GetPixelChannelTraits(image,channel);
723 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
725 if ((traits == UndefinedPixelTrait) ||
726 (reconstruct_traits == UndefinedPixelTrait) ||
727 ((reconstruct_traits & UpdatePixelTrait) == 0))
729 distance=fabs((double) (Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
731 distortion[i]+=distance;
732 distortion[CompositePixelChannel]+=distance;
733 mean_error+=distance*distance;
734 if (distance > maximum_error)
735 maximum_error=distance;
738 p+=GetPixelChannels(image);
739 q+=GetPixelChannels(reconstruct_image);
742 reconstruct_view=DestroyCacheView(reconstruct_view);
743 image_view=DestroyCacheView(image_view);
744 image->error.mean_error_per_pixel=distortion[CompositePixelChannel]/area;
745 image->error.normalized_mean_error=QuantumScale*QuantumScale*mean_error/area;
746 image->error.normalized_maximum_error=QuantumScale*maximum_error;
750 static MagickBooleanType GetMeanSquaredDistortion(const Image *image,
751 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
767 image_view=AcquireVirtualCacheView(image,exception);
768 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
769 #if defined(MAGICKCORE_OPENMP_SUPPORT)
770 #pragma omp parallel for schedule(static,4) shared(status) \
771 magick_threads(image,image,image->rows,1)
773 for (y=0; y < (ssize_t) image->rows; y++)
776 channel_distortion[MaxPixelChannels+1];
778 register const Quantum
786 if (status == MagickFalse)
788 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
789 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
791 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
796 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
797 for (x=0; x < (ssize_t) image->columns; x++)
806 if (GetPixelReadMask(image,p) == 0)
808 p+=GetPixelChannels(image);
809 q+=GetPixelChannels(reconstruct_image);
812 Sa=QuantumScale*GetPixelAlpha(image,p);
813 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
814 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
819 PixelChannel channel=GetPixelChannelChannel(image,i);
820 PixelTrait traits=GetPixelChannelTraits(image,channel);
821 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
823 if ((traits == UndefinedPixelTrait) ||
824 (reconstruct_traits == UndefinedPixelTrait) ||
825 ((reconstruct_traits & UpdatePixelTrait) == 0))
827 distance=QuantumScale*(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
829 channel_distortion[i]+=distance*distance;
830 channel_distortion[CompositePixelChannel]+=distance*distance;
832 p+=GetPixelChannels(image);
833 q+=GetPixelChannels(reconstruct_image);
835 #if defined(MAGICKCORE_OPENMP_SUPPORT)
836 #pragma omp critical (MagickCore_GetMeanSquaredError)
838 for (i=0; i <= MaxPixelChannels; i++)
839 distortion[i]+=channel_distortion[i];
841 reconstruct_view=DestroyCacheView(reconstruct_view);
842 image_view=DestroyCacheView(image_view);
843 for (i=0; i <= MaxPixelChannels; i++)
844 distortion[i]/=((double) image->columns*image->rows);
845 distortion[CompositePixelChannel]/=GetImageChannels(image);
849 static MagickBooleanType GetNormalizedCrossCorrelationDistortion(
850 const Image *image,const Image *reconstruct_image,double *distortion,
851 ExceptionInfo *exception)
853 #define SimilarityImageTag "Similarity/Image"
861 *reconstruct_statistics;
879 Normalize to account for variation due to lighting and exposure condition.
881 image_statistics=GetImageStatistics(image,exception);
882 reconstruct_statistics=GetImageStatistics(reconstruct_image,exception);
883 if ((image_statistics == (ChannelStatistics *) NULL) ||
884 (reconstruct_statistics == (ChannelStatistics *) NULL))
886 if (image_statistics != (ChannelStatistics *) NULL)
887 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
889 if (reconstruct_statistics != (ChannelStatistics *) NULL)
890 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
891 reconstruct_statistics);
896 for (i=0; i <= MaxPixelChannels; i++)
898 area=1.0/((double) image->columns*image->rows);
899 image_view=AcquireVirtualCacheView(image,exception);
900 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
901 for (y=0; y < (ssize_t) image->rows; y++)
903 register const Quantum
910 if (status == MagickFalse)
912 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
913 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
915 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
920 for (x=0; x < (ssize_t) image->columns; x++)
929 if (GetPixelReadMask(image,p) == 0)
931 p+=GetPixelChannels(image);
932 q+=GetPixelChannels(reconstruct_image);
935 Sa=QuantumScale*GetPixelAlpha(image,p);
936 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
937 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
939 PixelChannel channel=GetPixelChannelChannel(image,i);
940 PixelTrait traits=GetPixelChannelTraits(image,channel);
941 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
943 if ((traits == UndefinedPixelTrait) ||
944 (reconstruct_traits == UndefinedPixelTrait) ||
945 ((reconstruct_traits & UpdatePixelTrait) == 0))
947 distortion[i]+=area*QuantumScale*(Sa*p[i]-image_statistics[i].mean)*
948 (Da*GetPixelChannel(reconstruct_image,channel,q)-
949 reconstruct_statistics[channel].mean);
951 p+=GetPixelChannels(image);
952 q+=GetPixelChannels(reconstruct_image);
954 if (image->progress_monitor != (MagickProgressMonitor) NULL)
959 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
961 if (proceed == MagickFalse)
965 reconstruct_view=DestroyCacheView(reconstruct_view);
966 image_view=DestroyCacheView(image_view);
968 Divide by the standard deviation.
970 distortion[CompositePixelChannel]=0.0;
971 for (i=0; i < MaxPixelChannels; i++)
976 PixelChannel channel=GetPixelChannelChannel(image,i);
977 gamma=image_statistics[i].standard_deviation*
978 reconstruct_statistics[channel].standard_deviation;
979 gamma=PerceptibleReciprocal(gamma);
980 distortion[i]=QuantumRange*gamma*distortion[i];
981 distortion[CompositePixelChannel]+=distortion[i]*distortion[i];
983 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]/
984 GetImageChannels(image));
988 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
989 reconstruct_statistics);
990 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
995 static MagickBooleanType GetPeakAbsoluteDistortion(const Image *image,
996 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1009 image_view=AcquireVirtualCacheView(image,exception);
1010 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1011 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1012 #pragma omp parallel for schedule(static,4) shared(status) \
1013 magick_threads(image,image,image->rows,1)
1015 for (y=0; y < (ssize_t) image->rows; y++)
1018 channel_distortion[MaxPixelChannels+1];
1020 register const Quantum
1028 if (status == MagickFalse)
1030 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1031 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1033 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
1038 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
1039 for (x=0; x < (ssize_t) image->columns; x++)
1048 if (GetPixelReadMask(image,p) == 0)
1050 p+=GetPixelChannels(image);
1051 q+=GetPixelChannels(reconstruct_image);
1054 Sa=QuantumScale*GetPixelAlpha(image,p);
1055 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
1056 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1061 PixelChannel channel=GetPixelChannelChannel(image,i);
1062 PixelTrait traits=GetPixelChannelTraits(image,channel);
1063 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
1065 if ((traits == UndefinedPixelTrait) ||
1066 (reconstruct_traits == UndefinedPixelTrait) ||
1067 ((reconstruct_traits & UpdatePixelTrait) == 0))
1069 distance=QuantumScale*fabs(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
1071 if (distance > channel_distortion[i])
1072 channel_distortion[i]=distance;
1073 if (distance > channel_distortion[CompositePixelChannel])
1074 channel_distortion[CompositePixelChannel]=distance;
1076 p+=GetPixelChannels(image);
1077 q+=GetPixelChannels(reconstruct_image);
1079 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1080 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
1082 for (i=0; i <= MaxPixelChannels; i++)
1083 if (channel_distortion[i] > distortion[i])
1084 distortion[i]=channel_distortion[i];
1086 reconstruct_view=DestroyCacheView(reconstruct_view);
1087 image_view=DestroyCacheView(image_view);
1091 static inline double MagickLog10(const double x)
1093 if (fabs(x) < MagickMinimumValue)
1094 return(log10(MagickMinimumValue));
1095 return(log10(fabs(x)));
1098 static MagickBooleanType GetPeakSignalToNoiseRatio(const Image *image,
1099 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1107 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
1108 for (i=0; i <= MaxPixelChannels; i++)
1109 distortion[i]=20.0*MagickLog10((double) 1.0/sqrt(distortion[i]));
1113 static Image *PerceptualImageHash(const Image *image,
1114 const ColorspaceType colorspace,ExceptionInfo *exception)
1123 Transform colorspace then blur perceptual hash image.
1125 phash_image=BlurImage(image,0.0,1.0,exception);
1126 if (phash_image == (Image *) NULL)
1127 return((Image *) NULL);
1128 phash_image->depth=8;
1129 status=TransformImageColorspace(phash_image,colorspace,exception);
1130 if (status == MagickFalse)
1131 phash_image=DestroyImage(phash_image);
1132 return(phash_image);
1135 static MagickBooleanType GetPerceptualHashDistortion(const Image *image,
1136 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1140 *reconstruct_moments;
1157 Compute perceptual hash in the native image colorspace.
1159 phash_image=PerceptualImageHash(image,image->colorspace,exception);
1160 if (phash_image == (Image *) NULL)
1161 return(MagickFalse);
1162 image_moments=GetImageMoments(phash_image,exception);
1163 phash_image=DestroyImage(phash_image);
1164 if (image_moments == (ChannelMoments *) NULL)
1165 return(MagickFalse);
1166 phash_image=PerceptualImageHash(reconstruct_image,
1167 reconstruct_image->colorspace,exception);
1168 if (phash_image == (Image *) NULL)
1170 image_moments=(ChannelMoments *) RelinquishMagickMemory(image_moments);
1171 return(MagickFalse);
1173 reconstruct_moments=GetImageMoments(phash_image,exception);
1174 phash_image=DestroyImage(phash_image);
1175 if (reconstruct_moments == (ChannelMoments *) NULL)
1177 image_moments=(ChannelMoments *) RelinquishMagickMemory(image_moments);
1178 return(MagickFalse);
1180 for (i=0; i < 7; i++)
1186 Compute sum of moment differences squared.
1188 for (channel=0; channel < MaxPixelChannels; channel++)
1190 alpha=MagickLog10(image_moments[channel].I[i]);
1191 beta=MagickLog10(reconstruct_moments[channel].I[i]);
1192 difference=beta-alpha;
1193 distortion[channel]+=difference*difference;
1194 distortion[CompositePixelChannel]+=difference*difference;
1197 image_moments=(ChannelMoments *) RelinquishMagickMemory(image_moments);
1198 reconstruct_moments=(ChannelMoments *) RelinquishMagickMemory(
1199 reconstruct_moments);
1200 grayscale=(IsImageGray(image,exception) != MagickFalse) &&
1201 (IsImageGray(reconstruct_image,exception) != MagickFalse) ? MagickTrue :
1203 if (grayscale != MagickFalse)
1206 Compute perceptual hash in the HCLP colorspace.
1208 phash_image=PerceptualImageHash(image,HCLpColorspace,exception);
1209 if (phash_image == (Image *) NULL)
1210 return(MagickFalse);
1211 image_moments=GetImageMoments(phash_image,exception);
1212 phash_image=DestroyImage(phash_image);
1213 if (image_moments == (ChannelMoments *) NULL)
1214 return(MagickFalse);
1215 phash_image=PerceptualImageHash(reconstruct_image,HCLpColorspace,exception);
1216 if (phash_image == (Image *) NULL)
1218 image_moments=(ChannelMoments *) RelinquishMagickMemory(image_moments);
1219 return(MagickFalse);
1221 reconstruct_moments=GetImageMoments(phash_image,exception);
1222 phash_image=DestroyImage(phash_image);
1223 if (reconstruct_moments == (ChannelMoments *) NULL)
1225 image_moments=(ChannelMoments *) RelinquishMagickMemory(image_moments);
1226 return(MagickFalse);
1228 for (i=0; i < 7; i++)
1234 Compute sum of moment differences squared.
1236 for (channel=0; channel < MaxPixelChannels; channel++)
1238 alpha=MagickLog10(image_moments[channel].I[i]);
1239 beta=MagickLog10(reconstruct_moments[channel].I[i]);
1240 difference=beta-alpha;
1241 distortion[channel]+=difference*difference;
1242 distortion[CompositePixelChannel]+=difference*difference;
1245 image_moments=(ChannelMoments *) RelinquishMagickMemory(image_moments);
1246 reconstruct_moments=(ChannelMoments *) RelinquishMagickMemory(
1247 reconstruct_moments);
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 == MagickSignature);
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 == MagickSignature);
1286 assert(distortion != (double *) NULL);
1288 if (image->debug != MagickFalse)
1289 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1290 if (metric != PerceptualHashErrorMetric)
1291 if ((reconstruct_image->columns != image->columns) ||
1292 (reconstruct_image->rows != image->rows))
1293 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1295 Get image distortion.
1297 length=MaxPixelChannels+1;
1298 channel_distortion=(double *) AcquireQuantumMemory(length,
1299 sizeof(*channel_distortion));
1300 if (channel_distortion == (double *) NULL)
1301 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1302 (void) ResetMagickMemory(channel_distortion,0,length*
1303 sizeof(*channel_distortion));
1306 case AbsoluteErrorMetric:
1308 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1312 case FuzzErrorMetric:
1314 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1318 case MeanAbsoluteErrorMetric:
1320 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1321 channel_distortion,exception);
1324 case MeanErrorPerPixelErrorMetric:
1326 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1330 case MeanSquaredErrorMetric:
1332 status=GetMeanSquaredDistortion(image,reconstruct_image,
1333 channel_distortion,exception);
1336 case NormalizedCrossCorrelationErrorMetric:
1339 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1340 channel_distortion,exception);
1343 case PeakAbsoluteErrorMetric:
1345 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1346 channel_distortion,exception);
1349 case PeakSignalToNoiseRatioErrorMetric:
1351 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1352 channel_distortion,exception);
1355 case PerceptualHashErrorMetric:
1357 status=GetPerceptualHashDistortion(image,reconstruct_image,
1358 channel_distortion,exception);
1361 case RootMeanSquaredErrorMetric:
1363 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1364 channel_distortion,exception);
1368 *distortion=channel_distortion[CompositePixelChannel];
1369 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1370 (void) FormatImageProperty(image,"distortion","%.*g",GetMagickPrecision(),
1376 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1380 % G e t I m a g e D i s t o r t i o n s %
1384 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1386 % GetImageDistortions() compares the pixel channels of an image to a
1387 % reconstructed image and returns the specified distortion metric for each
1390 % The format of the GetImageDistortions method is:
1392 % double *GetImageDistortions(const Image *image,
1393 % const Image *reconstruct_image,const MetricType metric,
1394 % ExceptionInfo *exception)
1396 % A description of each parameter follows:
1398 % o image: the image.
1400 % o reconstruct_image: the reconstruct image.
1402 % o metric: the metric.
1404 % o exception: return any errors or warnings in this structure.
1407 MagickExport double *GetImageDistortions(Image *image,
1408 const Image *reconstruct_image,const MetricType metric,
1409 ExceptionInfo *exception)
1412 *channel_distortion;
1420 assert(image != (Image *) NULL);
1421 assert(image->signature == MagickSignature);
1422 if (image->debug != MagickFalse)
1423 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1424 assert(reconstruct_image != (const Image *) NULL);
1425 assert(reconstruct_image->signature == MagickSignature);
1426 if (image->debug != MagickFalse)
1427 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1428 if (metric != PerceptualHashErrorMetric)
1429 if ((reconstruct_image->columns != image->columns) ||
1430 (reconstruct_image->rows != image->rows))
1432 (void) ThrowMagickException(exception,GetMagickModule(),ImageError,
1433 "ImageSizeDiffers","`%s'",image->filename);
1434 return((double *) NULL);
1437 Get image distortion.
1439 length=MaxPixelChannels+1UL;
1440 channel_distortion=(double *) AcquireQuantumMemory(length,
1441 sizeof(*channel_distortion));
1442 if (channel_distortion == (double *) NULL)
1443 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1444 (void) ResetMagickMemory(channel_distortion,0,length*
1445 sizeof(*channel_distortion));
1449 case AbsoluteErrorMetric:
1451 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1455 case FuzzErrorMetric:
1457 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1461 case MeanAbsoluteErrorMetric:
1463 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1464 channel_distortion,exception);
1467 case MeanErrorPerPixelErrorMetric:
1469 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1473 case MeanSquaredErrorMetric:
1475 status=GetMeanSquaredDistortion(image,reconstruct_image,
1476 channel_distortion,exception);
1479 case NormalizedCrossCorrelationErrorMetric:
1482 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1483 channel_distortion,exception);
1486 case PeakAbsoluteErrorMetric:
1488 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1489 channel_distortion,exception);
1492 case PeakSignalToNoiseRatioErrorMetric:
1494 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1495 channel_distortion,exception);
1498 case PerceptualHashErrorMetric:
1500 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1501 channel_distortion,exception);
1504 case RootMeanSquaredErrorMetric:
1506 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1507 channel_distortion,exception);
1511 if (status == MagickFalse)
1513 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1514 return((double *) NULL);
1516 return(channel_distortion);
1520 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1524 % I s I m a g e s E q u a l %
1528 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1530 % IsImagesEqual() measures the difference between colors at each pixel
1531 % location of two images. A value other than 0 means the colors match
1532 % exactly. Otherwise an error measure is computed by summing over all
1533 % pixels in an image the distance squared in RGB space between each image
1534 % pixel and its corresponding pixel in the reconstruct image. The error
1535 % measure is assigned to these image members:
1537 % o mean_error_per_pixel: The mean error for any single pixel in
1540 % o normalized_mean_error: The normalized mean quantization error for
1541 % any single pixel in the image. This distance measure is normalized to
1542 % a range between 0 and 1. It is independent of the range of red, green,
1543 % and blue values in the image.
1545 % o normalized_maximum_error: The normalized maximum quantization
1546 % error for any single pixel in the image. This distance measure is
1547 % normalized to a range between 0 and 1. It is independent of the range
1548 % of red, green, and blue values in your image.
1550 % A small normalized mean square error, accessed as
1551 % image->normalized_mean_error, suggests the images are very similar in
1552 % spatial layout and color.
1554 % The format of the IsImagesEqual method is:
1556 % MagickBooleanType IsImagesEqual(Image *image,
1557 % const Image *reconstruct_image,ExceptionInfo *exception)
1559 % A description of each parameter follows.
1561 % o image: the image.
1563 % o reconstruct_image: the reconstruct image.
1565 % o exception: return any errors or warnings in this structure.
1568 MagickExport MagickBooleanType IsImagesEqual(Image *image,
1569 const Image *reconstruct_image,ExceptionInfo *exception)
1582 mean_error_per_pixel;
1587 assert(image != (Image *) NULL);
1588 assert(image->signature == MagickSignature);
1589 assert(reconstruct_image != (const Image *) NULL);
1590 assert(reconstruct_image->signature == MagickSignature);
1591 if ((reconstruct_image->columns != image->columns) ||
1592 (reconstruct_image->rows != image->rows))
1593 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1596 mean_error_per_pixel=0.0;
1598 image_view=AcquireVirtualCacheView(image,exception);
1599 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1600 for (y=0; y < (ssize_t) image->rows; y++)
1602 register const Quantum
1609 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1610 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1612 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1614 for (x=0; x < (ssize_t) image->columns; x++)
1619 if (GetPixelReadMask(image,p) == 0)
1621 p+=GetPixelChannels(image);
1622 q+=GetPixelChannels(reconstruct_image);
1625 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1630 PixelChannel channel=GetPixelChannelChannel(image,i);
1631 PixelTrait traits=GetPixelChannelTraits(image,channel);
1632 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
1634 if ((traits == UndefinedPixelTrait) ||
1635 (reconstruct_traits == UndefinedPixelTrait) ||
1636 ((reconstruct_traits & UpdatePixelTrait) == 0))
1638 distance=fabs(p[i]-(double) GetPixelChannel(reconstruct_image,
1640 mean_error_per_pixel+=distance;
1641 mean_error+=distance*distance;
1642 if (distance > maximum_error)
1643 maximum_error=distance;
1646 p+=GetPixelChannels(image);
1647 q+=GetPixelChannels(reconstruct_image);
1650 reconstruct_view=DestroyCacheView(reconstruct_view);
1651 image_view=DestroyCacheView(image_view);
1652 image->error.mean_error_per_pixel=(double) (mean_error_per_pixel/area);
1653 image->error.normalized_mean_error=(double) (QuantumScale*QuantumScale*
1655 image->error.normalized_maximum_error=(double) (QuantumScale*maximum_error);
1656 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
1661 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1665 % S i m i l a r i t y I m a g e %
1669 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1671 % SimilarityImage() compares the reference image of the image and returns the
1672 % best match offset. In addition, it returns a similarity image such that an
1673 % exact match location is completely white and if none of the pixels match,
1674 % black, otherwise some gray level in-between.
1676 % The format of the SimilarityImageImage method is:
1678 % Image *SimilarityImage(const Image *image,const Image *reference,
1679 % const MetricType metric,const double similarity_threshold,
1680 % RectangleInfo *offset,double *similarity,ExceptionInfo *exception)
1682 % A description of each parameter follows:
1684 % o image: the image.
1686 % o reference: find an area of the image that closely resembles this image.
1688 % o metric: the metric.
1690 % o similarity_threshold: minimum distortion for (sub)image match.
1692 % o offset: the best match offset of the reference image within the image.
1694 % o similarity: the computed similarity between the images.
1696 % o exception: return any errors or warnings in this structure.
1700 static double GetSimilarityMetric(const Image *image,const Image *reference,
1701 const MetricType metric,const ssize_t x_offset,const ssize_t y_offset,
1702 ExceptionInfo *exception)
1716 SetGeometry(reference,&geometry);
1717 geometry.x=x_offset;
1718 geometry.y=y_offset;
1719 similarity_image=CropImage(image,&geometry,exception);
1720 if (similarity_image == (Image *) NULL)
1723 status=GetImageDistortion(similarity_image,reference,metric,&distortion,
1725 similarity_image=DestroyImage(similarity_image);
1726 if (status == MagickFalse)
1731 MagickExport Image *SimilarityImage(Image *image,const Image *reference,
1732 const MetricType metric,const double similarity_threshold,
1733 RectangleInfo *offset,double *similarity_metric,ExceptionInfo *exception)
1735 #define SimilarityImageTag "Similarity/Image"
1752 assert(image != (const Image *) NULL);
1753 assert(image->signature == MagickSignature);
1754 if (image->debug != MagickFalse)
1755 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1756 assert(exception != (ExceptionInfo *) NULL);
1757 assert(exception->signature == MagickSignature);
1758 assert(offset != (RectangleInfo *) NULL);
1759 SetGeometry(reference,offset);
1760 *similarity_metric=MagickMaximumValue;
1761 if ((reference->columns > image->columns) || (reference->rows > image->rows))
1762 ThrowImageException(ImageError,"ImageSizeDiffers");
1763 similarity_image=CloneImage(image,image->columns-reference->columns+1,
1764 image->rows-reference->rows+1,MagickTrue,exception);
1765 if (similarity_image == (Image *) NULL)
1766 return((Image *) NULL);
1767 status=SetImageStorageClass(similarity_image,DirectClass,exception);
1768 if (status == MagickFalse)
1770 similarity_image=DestroyImage(similarity_image);
1771 return((Image *) NULL);
1773 (void) SetImageAlphaChannel(similarity_image,DeactivateAlphaChannel,
1776 Measure similarity of reference image against image.
1780 similarity_view=AcquireAuthenticCacheView(similarity_image,exception);
1781 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1782 #pragma omp parallel for schedule(static,4) \
1783 shared(progress,status,similarity_metric) \
1784 magick_threads(image,image,image->rows,1)
1786 for (y=0; y < (ssize_t) (image->rows-reference->rows+1); y++)
1797 if (status == MagickFalse)
1799 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1800 #pragma omp flush(similarity_metric)
1802 if (*similarity_metric <= similarity_threshold)
1804 q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
1806 if (q == (Quantum *) NULL)
1811 for (x=0; x < (ssize_t) (image->columns-reference->columns+1); x++)
1816 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1817 #pragma omp flush(similarity_metric)
1819 if (*similarity_metric <= similarity_threshold)
1821 similarity=GetSimilarityMetric(image,reference,metric,x,y,exception);
1822 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1823 #pragma omp critical (MagickCore_SimilarityImage)
1825 if (similarity < *similarity_metric)
1829 *similarity_metric=similarity;
1831 if (GetPixelReadMask(similarity_image,q) == 0)
1833 SetPixelBackgoundColor(similarity_image,q);
1834 q+=GetPixelChannels(similarity_image);
1837 for (i=0; i < (ssize_t) GetPixelChannels(similarity_image); i++)
1839 PixelChannel channel=GetPixelChannelChannel(image,i);
1840 PixelTrait traits=GetPixelChannelTraits(image,channel);
1841 PixelTrait similarity_traits=GetPixelChannelTraits(similarity_image,
1843 if ((traits == UndefinedPixelTrait) ||
1844 (similarity_traits == UndefinedPixelTrait) ||
1845 ((similarity_traits & UpdatePixelTrait) == 0))
1847 SetPixelChannel(similarity_image,channel,ClampToQuantum(QuantumRange-
1848 QuantumRange*similarity),q);
1850 q+=GetPixelChannels(similarity_image);
1852 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
1854 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1859 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1860 #pragma omp critical (MagickCore_SimilarityImage)
1862 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
1864 if (proceed == MagickFalse)
1868 similarity_view=DestroyCacheView(similarity_view);
1869 if (status == MagickFalse)
1870 similarity_image=DestroyImage(similarity_image);
1871 return(similarity_image);