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 #define Log10Epsilon (1.0e-11)
1095 if (fabs(x) < Log10Epsilon)
1096 return(log10(Log10Epsilon));
1097 return(log10(fabs(x)));
1100 static MagickBooleanType GetPeakSignalToNoiseRatio(const Image *image,
1101 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1109 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
1110 for (i=0; i <= MaxPixelChannels; i++)
1111 distortion[i]=20.0*MagickLog10((double) 1.0/sqrt(distortion[i]));
1115 static MagickBooleanType GetPerceptualHashDistortion(const Image *image,
1116 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1118 ChannelPerceptualHash
1126 Compute perceptual hash in the sRGB colorspace.
1128 image_phash=GetImagePerceptualHash(image,exception);
1129 if (image_phash == (ChannelPerceptualHash *) NULL)
1130 return(MagickFalse);
1131 reconstruct_phash=GetImagePerceptualHash(reconstruct_image,exception);
1132 if (image_phash == (ChannelPerceptualHash *) NULL)
1134 image_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(image_phash);
1135 return(MagickFalse);
1137 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1138 #pragma omp parallel for schedule(static,4)
1140 for (channel=0; channel < MaxPixelChannels; channel++)
1149 for (i=0; i < 7; i++)
1155 alpha=image_phash[channel].P[i];
1156 beta=reconstruct_phash[channel].P[i];
1157 difference+=(beta-alpha)*(beta-alpha);
1159 distortion[channel]+=difference;
1160 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1161 #pragma omp critical (MagickCore_GetPerceptualHashDistortion)
1163 distortion[CompositePixelChannel]+=difference;
1166 Compute perceptual hash in the HCLP colorspace.
1168 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1169 #pragma omp parallel for schedule(static,4)
1171 for (channel=0; channel < MaxPixelChannels; channel++)
1180 for (i=0; i < 7; i++)
1186 alpha=image_phash[channel].Q[i];
1187 beta=reconstruct_phash[channel].Q[i];
1188 difference+=(beta-alpha)*(beta-alpha);
1190 distortion[channel]+=difference;
1191 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1192 #pragma omp critical (MagickCore_GetPerceptualHashDistortion)
1194 distortion[CompositePixelChannel]+=difference;
1199 reconstruct_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(
1201 image_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(image_phash);
1207 static MagickBooleanType GetRootMeanSquaredDistortion(const Image *image,
1208 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1216 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
1217 for (i=0; i <= MaxPixelChannels; i++)
1218 distortion[i]=sqrt(distortion[i]);
1222 MagickExport MagickBooleanType GetImageDistortion(Image *image,
1223 const Image *reconstruct_image,const MetricType metric,double *distortion,
1224 ExceptionInfo *exception)
1227 *channel_distortion;
1235 assert(image != (Image *) NULL);
1236 assert(image->signature == MagickSignature);
1237 if (image->debug != MagickFalse)
1238 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1239 assert(reconstruct_image != (const Image *) NULL);
1240 assert(reconstruct_image->signature == MagickSignature);
1241 assert(distortion != (double *) NULL);
1243 if (image->debug != MagickFalse)
1244 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1245 if (metric != PerceptualHashErrorMetric)
1246 if ((reconstruct_image->columns != image->columns) ||
1247 (reconstruct_image->rows != image->rows))
1248 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1250 Get image distortion.
1252 length=MaxPixelChannels+1;
1253 channel_distortion=(double *) AcquireQuantumMemory(length,
1254 sizeof(*channel_distortion));
1255 if (channel_distortion == (double *) NULL)
1256 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1257 (void) ResetMagickMemory(channel_distortion,0,length*
1258 sizeof(*channel_distortion));
1261 case AbsoluteErrorMetric:
1263 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1267 case FuzzErrorMetric:
1269 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1273 case MeanAbsoluteErrorMetric:
1275 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1276 channel_distortion,exception);
1279 case MeanErrorPerPixelErrorMetric:
1281 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1285 case MeanSquaredErrorMetric:
1287 status=GetMeanSquaredDistortion(image,reconstruct_image,
1288 channel_distortion,exception);
1291 case NormalizedCrossCorrelationErrorMetric:
1294 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1295 channel_distortion,exception);
1298 case PeakAbsoluteErrorMetric:
1300 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1301 channel_distortion,exception);
1304 case PeakSignalToNoiseRatioErrorMetric:
1306 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1307 channel_distortion,exception);
1310 case PerceptualHashErrorMetric:
1312 status=GetPerceptualHashDistortion(image,reconstruct_image,
1313 channel_distortion,exception);
1316 case RootMeanSquaredErrorMetric:
1318 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1319 channel_distortion,exception);
1323 *distortion=channel_distortion[CompositePixelChannel];
1324 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1325 (void) FormatImageProperty(image,"distortion","%.*g",GetMagickPrecision(),
1331 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1335 % G e t I m a g e D i s t o r t i o n s %
1339 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1341 % GetImageDistortions() compares the pixel channels of an image to a
1342 % reconstructed image and returns the specified distortion metric for each
1345 % The format of the GetImageDistortions method is:
1347 % double *GetImageDistortions(const Image *image,
1348 % const Image *reconstruct_image,const MetricType metric,
1349 % ExceptionInfo *exception)
1351 % A description of each parameter follows:
1353 % o image: the image.
1355 % o reconstruct_image: the reconstruct image.
1357 % o metric: the metric.
1359 % o exception: return any errors or warnings in this structure.
1362 MagickExport double *GetImageDistortions(Image *image,
1363 const Image *reconstruct_image,const MetricType metric,
1364 ExceptionInfo *exception)
1367 *channel_distortion;
1375 assert(image != (Image *) NULL);
1376 assert(image->signature == MagickSignature);
1377 if (image->debug != MagickFalse)
1378 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1379 assert(reconstruct_image != (const Image *) NULL);
1380 assert(reconstruct_image->signature == MagickSignature);
1381 if (image->debug != MagickFalse)
1382 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1383 if (metric != PerceptualHashErrorMetric)
1384 if ((reconstruct_image->columns != image->columns) ||
1385 (reconstruct_image->rows != image->rows))
1387 (void) ThrowMagickException(exception,GetMagickModule(),ImageError,
1388 "ImageSizeDiffers","`%s'",image->filename);
1389 return((double *) NULL);
1392 Get image distortion.
1394 length=MaxPixelChannels+1UL;
1395 channel_distortion=(double *) AcquireQuantumMemory(length,
1396 sizeof(*channel_distortion));
1397 if (channel_distortion == (double *) NULL)
1398 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1399 (void) ResetMagickMemory(channel_distortion,0,length*
1400 sizeof(*channel_distortion));
1404 case AbsoluteErrorMetric:
1406 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1410 case FuzzErrorMetric:
1412 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1416 case MeanAbsoluteErrorMetric:
1418 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1419 channel_distortion,exception);
1422 case MeanErrorPerPixelErrorMetric:
1424 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1428 case MeanSquaredErrorMetric:
1430 status=GetMeanSquaredDistortion(image,reconstruct_image,
1431 channel_distortion,exception);
1434 case NormalizedCrossCorrelationErrorMetric:
1437 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1438 channel_distortion,exception);
1441 case PeakAbsoluteErrorMetric:
1443 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1444 channel_distortion,exception);
1447 case PeakSignalToNoiseRatioErrorMetric:
1449 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1450 channel_distortion,exception);
1453 case PerceptualHashErrorMetric:
1455 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1456 channel_distortion,exception);
1459 case RootMeanSquaredErrorMetric:
1461 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1462 channel_distortion,exception);
1466 if (status == MagickFalse)
1468 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1469 return((double *) NULL);
1471 return(channel_distortion);
1475 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1479 % I s I m a g e s E q u a l %
1483 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1485 % IsImagesEqual() measures the difference between colors at each pixel
1486 % location of two images. A value other than 0 means the colors match
1487 % exactly. Otherwise an error measure is computed by summing over all
1488 % pixels in an image the distance squared in RGB space between each image
1489 % pixel and its corresponding pixel in the reconstruct image. The error
1490 % measure is assigned to these image members:
1492 % o mean_error_per_pixel: The mean error for any single pixel in
1495 % o normalized_mean_error: The normalized mean quantization error for
1496 % any single pixel in the image. This distance measure is normalized to
1497 % a range between 0 and 1. It is independent of the range of red, green,
1498 % and blue values in the image.
1500 % o normalized_maximum_error: The normalized maximum quantization
1501 % error for any single pixel in the image. This distance measure is
1502 % normalized to a range between 0 and 1. It is independent of the range
1503 % of red, green, and blue values in your image.
1505 % A small normalized mean square error, accessed as
1506 % image->normalized_mean_error, suggests the images are very similar in
1507 % spatial layout and color.
1509 % The format of the IsImagesEqual method is:
1511 % MagickBooleanType IsImagesEqual(Image *image,
1512 % const Image *reconstruct_image,ExceptionInfo *exception)
1514 % A description of each parameter follows.
1516 % o image: the image.
1518 % o reconstruct_image: the reconstruct image.
1520 % o exception: return any errors or warnings in this structure.
1523 MagickExport MagickBooleanType IsImagesEqual(Image *image,
1524 const Image *reconstruct_image,ExceptionInfo *exception)
1537 mean_error_per_pixel;
1542 assert(image != (Image *) NULL);
1543 assert(image->signature == MagickSignature);
1544 assert(reconstruct_image != (const Image *) NULL);
1545 assert(reconstruct_image->signature == MagickSignature);
1546 if ((reconstruct_image->columns != image->columns) ||
1547 (reconstruct_image->rows != image->rows))
1548 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1551 mean_error_per_pixel=0.0;
1553 image_view=AcquireVirtualCacheView(image,exception);
1554 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1555 for (y=0; y < (ssize_t) image->rows; y++)
1557 register const Quantum
1564 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1565 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1567 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1569 for (x=0; x < (ssize_t) image->columns; x++)
1574 if (GetPixelReadMask(image,p) == 0)
1576 p+=GetPixelChannels(image);
1577 q+=GetPixelChannels(reconstruct_image);
1580 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1585 PixelChannel channel=GetPixelChannelChannel(image,i);
1586 PixelTrait traits=GetPixelChannelTraits(image,channel);
1587 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
1589 if ((traits == UndefinedPixelTrait) ||
1590 (reconstruct_traits == UndefinedPixelTrait) ||
1591 ((reconstruct_traits & UpdatePixelTrait) == 0))
1593 distance=fabs(p[i]-(double) GetPixelChannel(reconstruct_image,
1595 mean_error_per_pixel+=distance;
1596 mean_error+=distance*distance;
1597 if (distance > maximum_error)
1598 maximum_error=distance;
1601 p+=GetPixelChannels(image);
1602 q+=GetPixelChannels(reconstruct_image);
1605 reconstruct_view=DestroyCacheView(reconstruct_view);
1606 image_view=DestroyCacheView(image_view);
1607 image->error.mean_error_per_pixel=(double) (mean_error_per_pixel/area);
1608 image->error.normalized_mean_error=(double) (QuantumScale*QuantumScale*
1610 image->error.normalized_maximum_error=(double) (QuantumScale*maximum_error);
1611 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
1616 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1620 % S i m i l a r i t y I m a g e %
1624 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1626 % SimilarityImage() compares the reference image of the image and returns the
1627 % best match offset. In addition, it returns a similarity image such that an
1628 % exact match location is completely white and if none of the pixels match,
1629 % black, otherwise some gray level in-between.
1631 % The format of the SimilarityImageImage method is:
1633 % Image *SimilarityImage(const Image *image,const Image *reference,
1634 % const MetricType metric,const double similarity_threshold,
1635 % RectangleInfo *offset,double *similarity,ExceptionInfo *exception)
1637 % A description of each parameter follows:
1639 % o image: the image.
1641 % o reference: find an area of the image that closely resembles this image.
1643 % o metric: the metric.
1645 % o similarity_threshold: minimum distortion for (sub)image match.
1647 % o offset: the best match offset of the reference image within the image.
1649 % o similarity: the computed similarity between the images.
1651 % o exception: return any errors or warnings in this structure.
1655 static double GetSimilarityMetric(const Image *image,const Image *reference,
1656 const MetricType metric,const ssize_t x_offset,const ssize_t y_offset,
1657 ExceptionInfo *exception)
1671 SetGeometry(reference,&geometry);
1672 geometry.x=x_offset;
1673 geometry.y=y_offset;
1674 similarity_image=CropImage(image,&geometry,exception);
1675 if (similarity_image == (Image *) NULL)
1678 status=GetImageDistortion(similarity_image,reference,metric,&distortion,
1680 similarity_image=DestroyImage(similarity_image);
1681 if (status == MagickFalse)
1686 static inline double MagickMin(const double x,const double y)
1693 MagickExport Image *SimilarityImage(Image *image,const Image *reference,
1694 const MetricType metric,const double similarity_threshold,
1695 RectangleInfo *offset,double *similarity_metric,ExceptionInfo *exception)
1697 #define SimilarityImageTag "Similarity/Image"
1714 assert(image != (const Image *) NULL);
1715 assert(image->signature == MagickSignature);
1716 if (image->debug != MagickFalse)
1717 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1718 assert(exception != (ExceptionInfo *) NULL);
1719 assert(exception->signature == MagickSignature);
1720 assert(offset != (RectangleInfo *) NULL);
1721 SetGeometry(reference,offset);
1722 *similarity_metric=MagickMaximumValue;
1723 if ((reference->columns > image->columns) || (reference->rows > image->rows))
1724 ThrowImageException(ImageError,"ImageSizeDiffers");
1725 similarity_image=CloneImage(image,image->columns-reference->columns+1,
1726 image->rows-reference->rows+1,MagickTrue,exception);
1727 if (similarity_image == (Image *) NULL)
1728 return((Image *) NULL);
1729 status=SetImageStorageClass(similarity_image,DirectClass,exception);
1730 if (status == MagickFalse)
1732 similarity_image=DestroyImage(similarity_image);
1733 return((Image *) NULL);
1735 (void) SetImageAlphaChannel(similarity_image,DeactivateAlphaChannel,
1738 Measure similarity of reference image against image.
1742 similarity_view=AcquireAuthenticCacheView(similarity_image,exception);
1743 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1744 #pragma omp parallel for schedule(static,4) \
1745 shared(progress,status,similarity_metric) \
1746 magick_threads(image,image,image->rows,1)
1748 for (y=0; y < (ssize_t) (image->rows-reference->rows+1); y++)
1759 if (status == MagickFalse)
1761 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1762 #pragma omp flush(similarity_metric)
1764 if (*similarity_metric <= similarity_threshold)
1766 q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
1768 if (q == (Quantum *) NULL)
1773 for (x=0; x < (ssize_t) (image->columns-reference->columns+1); x++)
1778 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1779 #pragma omp flush(similarity_metric)
1781 if (*similarity_metric <= similarity_threshold)
1783 similarity=GetSimilarityMetric(image,reference,metric,x,y,exception);
1784 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1785 #pragma omp critical (MagickCore_SimilarityImage)
1787 if (similarity < *similarity_metric)
1791 *similarity_metric=similarity;
1793 if (metric == PerceptualHashErrorMetric)
1794 similarity=MagickMin(0.01*similarity,1.0);
1795 if (GetPixelReadMask(similarity_image,q) == 0)
1797 SetPixelBackgoundColor(similarity_image,q);
1798 q+=GetPixelChannels(similarity_image);
1801 for (i=0; i < (ssize_t) GetPixelChannels(similarity_image); i++)
1803 PixelChannel channel=GetPixelChannelChannel(image,i);
1804 PixelTrait traits=GetPixelChannelTraits(image,channel);
1805 PixelTrait similarity_traits=GetPixelChannelTraits(similarity_image,
1807 if ((traits == UndefinedPixelTrait) ||
1808 (similarity_traits == UndefinedPixelTrait) ||
1809 ((similarity_traits & UpdatePixelTrait) == 0))
1811 SetPixelChannel(similarity_image,channel,ClampToQuantum(QuantumRange-
1812 QuantumRange*similarity),q);
1814 q+=GetPixelChannels(similarity_image);
1816 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
1818 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1823 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1824 #pragma omp critical (MagickCore_SimilarityImage)
1826 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
1828 if (proceed == MagickFalse)
1832 similarity_view=DestroyCacheView(similarity_view);
1833 if (status == MagickFalse)
1834 similarity_image=DestroyImage(similarity_image);
1835 return(similarity_image);