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 ((reconstruct_image->columns != image->columns) ||
143 (reconstruct_image->rows != image->rows))
144 ThrowImageException(ImageError,"ImageSizeDiffers");
145 status=GetImageDistortion(image,reconstruct_image,metric,distortion,
147 if (status == MagickFalse)
148 return((Image *) NULL);
149 difference_image=CloneImage(image,0,0,MagickTrue,exception);
150 if (difference_image == (Image *) NULL)
151 return((Image *) NULL);
152 (void) SetImageAlphaChannel(difference_image,OpaqueAlphaChannel,exception);
153 highlight_image=CloneImage(image,image->columns,image->rows,MagickTrue,
155 if (highlight_image == (Image *) NULL)
157 difference_image=DestroyImage(difference_image);
158 return((Image *) NULL);
160 status=SetImageStorageClass(highlight_image,DirectClass,exception);
161 if (status == MagickFalse)
163 difference_image=DestroyImage(difference_image);
164 highlight_image=DestroyImage(highlight_image);
165 return((Image *) NULL);
167 (void) SetImageAlphaChannel(highlight_image,OpaqueAlphaChannel,exception);
168 (void) QueryColorCompliance("#f1001ecc",AllCompliance,&highlight,exception);
169 artifact=GetImageArtifact(image,"highlight-color");
170 if (artifact != (const char *) NULL)
171 (void) QueryColorCompliance(artifact,AllCompliance,&highlight,exception);
172 (void) QueryColorCompliance("#ffffffcc",AllCompliance,&lowlight,exception);
173 artifact=GetImageArtifact(image,"lowlight-color");
174 if (artifact != (const char *) NULL)
175 (void) QueryColorCompliance(artifact,AllCompliance,&lowlight,exception);
177 Generate difference image.
180 image_view=AcquireVirtualCacheView(image,exception);
181 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
182 highlight_view=AcquireAuthenticCacheView(highlight_image,exception);
183 #if defined(MAGICKCORE_OPENMP_SUPPORT)
184 #pragma omp parallel for schedule(static,4) shared(status) \
185 magick_threads(image,highlight_image,image->rows,1)
187 for (y=0; y < (ssize_t) image->rows; y++)
192 register const Quantum
202 if (status == MagickFalse)
204 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
205 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
207 r=QueueCacheViewAuthenticPixels(highlight_view,0,y,highlight_image->columns,
209 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL) ||
210 (r == (Quantum *) NULL))
215 for (x=0; x < (ssize_t) image->columns; x++)
227 if (GetPixelReadMask(image,p) == 0)
229 SetPixelInfoPixel(highlight_image,&lowlight,r);
230 p+=GetPixelChannels(image);
231 q+=GetPixelChannels(reconstruct_image);
232 r+=GetPixelChannels(highlight_image);
235 difference=MagickFalse;
236 Sa=QuantumScale*GetPixelAlpha(image,p);
237 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
238 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
243 PixelChannel channel=GetPixelChannelChannel(image,i);
244 PixelTrait traits=GetPixelChannelTraits(image,channel);
245 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
247 if ((traits == UndefinedPixelTrait) ||
248 (reconstruct_traits == UndefinedPixelTrait) ||
249 ((reconstruct_traits & UpdatePixelTrait) == 0))
251 distance=Sa*p[i]-Da*GetPixelChannel(reconstruct_image,channel,q);
252 if (fabs((double) distance) >= MagickEpsilon)
253 difference=MagickTrue;
255 if (difference == MagickFalse)
256 SetPixelInfoPixel(highlight_image,&lowlight,r);
258 SetPixelInfoPixel(highlight_image,&highlight,r);
259 p+=GetPixelChannels(image);
260 q+=GetPixelChannels(reconstruct_image);
261 r+=GetPixelChannels(highlight_image);
263 sync=SyncCacheViewAuthenticPixels(highlight_view,exception);
264 if (sync == MagickFalse)
267 highlight_view=DestroyCacheView(highlight_view);
268 reconstruct_view=DestroyCacheView(reconstruct_view);
269 image_view=DestroyCacheView(image_view);
270 (void) CompositeImage(difference_image,highlight_image,image->compose,
271 MagickTrue,0,0,exception);
272 highlight_image=DestroyImage(highlight_image);
273 if (status == MagickFalse)
274 difference_image=DestroyImage(difference_image);
275 return(difference_image);
279 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
283 % G e t I m a g e D i s t o r t i o n %
287 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
289 % GetImageDistortion() compares one or more pixel channels of an image to a
290 % reconstructed image and returns the specified distortion metric.
292 % The format of the GetImageDistortion method is:
294 % MagickBooleanType GetImageDistortion(const Image *image,
295 % const Image *reconstruct_image,const MetricType metric,
296 % double *distortion,ExceptionInfo *exception)
298 % A description of each parameter follows:
300 % o image: the image.
302 % o reconstruct_image: the reconstruct image.
304 % o metric: the metric.
306 % o distortion: the computed distortion between the images.
308 % o exception: return any errors or warnings in this structure.
312 static inline double MagickMax(const double x,const double y)
319 static MagickBooleanType GetAbsoluteDistortion(const Image *image,
320 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
336 Compute the absolute difference in pixels between two images.
339 if (image->fuzz == 0.0)
340 fuzz=MagickMax(reconstruct_image->fuzz,MagickSQ1_2)*
341 MagickMax(reconstruct_image->fuzz,MagickSQ1_2);
343 if (reconstruct_image->fuzz == 0.0)
344 fuzz=MagickMax(image->fuzz,MagickSQ1_2)*
345 MagickMax(image->fuzz,MagickSQ1_2);
347 fuzz=MagickMax(image->fuzz,MagickSQ1_2)*
348 MagickMax(reconstruct_image->fuzz,MagickSQ1_2);
349 image_view=AcquireVirtualCacheView(image,exception);
350 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
351 #if defined(MAGICKCORE_OPENMP_SUPPORT)
352 #pragma omp parallel for schedule(static,4) shared(status) \
353 magick_threads(image,image,image->rows,1)
355 for (y=0; y < (ssize_t) image->rows; y++)
358 channel_distortion[MaxPixelChannels+1];
360 register const Quantum
368 if (status == MagickFalse)
370 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
371 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
373 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
378 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
379 for (x=0; x < (ssize_t) image->columns; x++)
391 if (GetPixelReadMask(image,p) == 0)
393 p+=GetPixelChannels(image);
394 q+=GetPixelChannels(reconstruct_image);
397 difference=MagickFalse;
398 Sa=QuantumScale*GetPixelAlpha(image,p);
399 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
400 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
405 PixelChannel channel=GetPixelChannelChannel(image,i);
406 PixelTrait traits=GetPixelChannelTraits(image,channel);
407 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
409 if ((traits == UndefinedPixelTrait) ||
410 (reconstruct_traits == UndefinedPixelTrait) ||
411 ((reconstruct_traits & UpdatePixelTrait) == 0))
413 distance=Sa*p[i]-Da*GetPixelChannel(reconstruct_image,channel,q);
414 if ((distance*distance) > fuzz)
416 difference=MagickTrue;
420 if (difference != MagickFalse)
422 channel_distortion[i]++;
423 channel_distortion[CompositePixelChannel]++;
425 p+=GetPixelChannels(image);
426 q+=GetPixelChannels(reconstruct_image);
428 #if defined(MAGICKCORE_OPENMP_SUPPORT)
429 #pragma omp critical (MagickCore_GetAbsoluteError)
431 for (i=0; i <= MaxPixelChannels; i++)
432 distortion[i]+=channel_distortion[i];
434 reconstruct_view=DestroyCacheView(reconstruct_view);
435 image_view=DestroyCacheView(image_view);
439 static size_t GetImageChannels(const Image *image)
448 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
450 PixelChannel channel=GetPixelChannelChannel(image,i);
451 PixelTrait traits=GetPixelChannelTraits(image,channel);
452 if ((traits & UpdatePixelTrait) != 0)
458 static MagickBooleanType GetFuzzDistortion(const Image *image,
459 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
475 image_view=AcquireVirtualCacheView(image,exception);
476 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
477 #if defined(MAGICKCORE_OPENMP_SUPPORT)
478 #pragma omp parallel for schedule(static,4) shared(status) \
479 magick_threads(image,image,image->rows,1)
481 for (y=0; y < (ssize_t) image->rows; y++)
484 channel_distortion[MaxPixelChannels+1];
486 register const Quantum
494 if (status == MagickFalse)
496 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
497 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
499 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
504 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
505 for (x=0; x < (ssize_t) image->columns; x++)
514 if (GetPixelReadMask(image,p) == 0)
516 p+=GetPixelChannels(image);
517 q+=GetPixelChannels(reconstruct_image);
520 Sa=QuantumScale*GetPixelAlpha(image,p);
521 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
522 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
527 PixelChannel channel=GetPixelChannelChannel(image,i);
528 PixelTrait traits=GetPixelChannelTraits(image,channel);
529 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
531 if ((traits == UndefinedPixelTrait) ||
532 (reconstruct_traits == UndefinedPixelTrait) ||
533 ((reconstruct_traits & UpdatePixelTrait) == 0))
535 distance=QuantumScale*(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
537 channel_distortion[i]+=distance*distance;
538 channel_distortion[CompositePixelChannel]+=distance*distance;
540 p+=GetPixelChannels(image);
541 q+=GetPixelChannels(reconstruct_image);
543 #if defined(MAGICKCORE_OPENMP_SUPPORT)
544 #pragma omp critical (MagickCore_GetFuzzDistortion)
546 for (i=0; i <= MaxPixelChannels; i++)
547 distortion[i]+=channel_distortion[i];
549 reconstruct_view=DestroyCacheView(reconstruct_view);
550 image_view=DestroyCacheView(image_view);
551 for (i=0; i <= MaxPixelChannels; i++)
552 distortion[i]/=((double) image->columns*image->rows);
553 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
554 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]);
558 static MagickBooleanType GetMeanAbsoluteDistortion(const Image *image,
559 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
575 image_view=AcquireVirtualCacheView(image,exception);
576 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
577 #if defined(MAGICKCORE_OPENMP_SUPPORT)
578 #pragma omp parallel for schedule(static,4) shared(status) \
579 magick_threads(image,image,image->rows,1)
581 for (y=0; y < (ssize_t) image->rows; y++)
584 channel_distortion[MaxPixelChannels+1];
586 register const Quantum
594 if (status == MagickFalse)
596 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
597 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
599 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
604 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
605 for (x=0; x < (ssize_t) image->columns; x++)
614 if (GetPixelReadMask(image,p) == 0)
616 p+=GetPixelChannels(image);
617 q+=GetPixelChannels(reconstruct_image);
620 Sa=QuantumScale*GetPixelAlpha(image,p);
621 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
622 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
627 PixelChannel channel=GetPixelChannelChannel(image,i);
628 PixelTrait traits=GetPixelChannelTraits(image,channel);
629 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
631 if ((traits == UndefinedPixelTrait) ||
632 (reconstruct_traits == UndefinedPixelTrait) ||
633 ((reconstruct_traits & UpdatePixelTrait) == 0))
635 distance=QuantumScale*fabs(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
637 channel_distortion[i]+=distance;
638 channel_distortion[CompositePixelChannel]+=distance;
640 p+=GetPixelChannels(image);
641 q+=GetPixelChannels(reconstruct_image);
643 #if defined(MAGICKCORE_OPENMP_SUPPORT)
644 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
646 for (i=0; i <= MaxPixelChannels; i++)
647 distortion[i]+=channel_distortion[i];
649 reconstruct_view=DestroyCacheView(reconstruct_view);
650 image_view=DestroyCacheView(image_view);
651 for (i=0; i <= MaxPixelChannels; i++)
652 distortion[i]/=((double) image->columns*image->rows);
653 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
657 static MagickBooleanType GetMeanErrorPerPixel(Image *image,
658 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
679 image_view=AcquireVirtualCacheView(image,exception);
680 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
681 for (y=0; y < (ssize_t) image->rows; y++)
683 register const Quantum
690 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
691 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
693 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
698 for (x=0; x < (ssize_t) image->columns; x++)
707 if (GetPixelReadMask(image,p) == 0)
709 p+=GetPixelChannels(image);
710 q+=GetPixelChannels(reconstruct_image);
713 Sa=QuantumScale*GetPixelAlpha(image,p);
714 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
715 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
720 PixelChannel channel=GetPixelChannelChannel(image,i);
721 PixelTrait traits=GetPixelChannelTraits(image,channel);
722 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
724 if ((traits == UndefinedPixelTrait) ||
725 (reconstruct_traits == UndefinedPixelTrait) ||
726 ((reconstruct_traits & UpdatePixelTrait) == 0))
728 distance=fabs((double) (Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
730 distortion[i]+=distance;
731 distortion[CompositePixelChannel]+=distance;
732 mean_error+=distance*distance;
733 if (distance > maximum_error)
734 maximum_error=distance;
737 p+=GetPixelChannels(image);
738 q+=GetPixelChannels(reconstruct_image);
741 reconstruct_view=DestroyCacheView(reconstruct_view);
742 image_view=DestroyCacheView(image_view);
743 image->error.mean_error_per_pixel=distortion[CompositePixelChannel]/area;
744 image->error.normalized_mean_error=QuantumScale*QuantumScale*mean_error/area;
745 image->error.normalized_maximum_error=QuantumScale*maximum_error;
749 static MagickBooleanType GetMeanSquaredDistortion(const Image *image,
750 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
766 image_view=AcquireVirtualCacheView(image,exception);
767 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
768 #if defined(MAGICKCORE_OPENMP_SUPPORT)
769 #pragma omp parallel for schedule(static,4) shared(status) \
770 magick_threads(image,image,image->rows,1)
772 for (y=0; y < (ssize_t) image->rows; y++)
775 channel_distortion[MaxPixelChannels+1];
777 register const Quantum
785 if (status == MagickFalse)
787 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
788 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
790 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
795 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
796 for (x=0; x < (ssize_t) image->columns; x++)
805 if (GetPixelReadMask(image,p) == 0)
807 p+=GetPixelChannels(image);
808 q+=GetPixelChannels(reconstruct_image);
811 Sa=QuantumScale*GetPixelAlpha(image,p);
812 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
813 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
818 PixelChannel channel=GetPixelChannelChannel(image,i);
819 PixelTrait traits=GetPixelChannelTraits(image,channel);
820 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
822 if ((traits == UndefinedPixelTrait) ||
823 (reconstruct_traits == UndefinedPixelTrait) ||
824 ((reconstruct_traits & UpdatePixelTrait) == 0))
826 distance=QuantumScale*(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
828 channel_distortion[i]+=distance*distance;
829 channel_distortion[CompositePixelChannel]+=distance*distance;
831 p+=GetPixelChannels(image);
832 q+=GetPixelChannels(reconstruct_image);
834 #if defined(MAGICKCORE_OPENMP_SUPPORT)
835 #pragma omp critical (MagickCore_GetMeanSquaredError)
837 for (i=0; i <= MaxPixelChannels; i++)
838 distortion[i]+=channel_distortion[i];
840 reconstruct_view=DestroyCacheView(reconstruct_view);
841 image_view=DestroyCacheView(image_view);
842 for (i=0; i <= MaxPixelChannels; i++)
843 distortion[i]/=((double) image->columns*image->rows);
844 distortion[CompositePixelChannel]/=GetImageChannels(image);
848 static MagickBooleanType GetNormalizedCrossCorrelationDistortion(
849 const Image *image,const Image *reconstruct_image,double *distortion,
850 ExceptionInfo *exception)
852 #define SimilarityImageTag "Similarity/Image"
860 *reconstruct_statistics;
878 Normalize to account for variation due to lighting and exposure condition.
880 image_statistics=GetImageStatistics(image,exception);
881 reconstruct_statistics=GetImageStatistics(reconstruct_image,exception);
882 if ((image_statistics == (ChannelStatistics *) NULL) ||
883 (reconstruct_statistics == (ChannelStatistics *) NULL))
885 if (image_statistics != (ChannelStatistics *) NULL)
886 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
888 if (reconstruct_statistics != (ChannelStatistics *) NULL)
889 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
890 reconstruct_statistics);
895 for (i=0; i <= MaxPixelChannels; i++)
897 area=1.0/((double) image->columns*image->rows);
898 image_view=AcquireVirtualCacheView(image,exception);
899 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
900 for (y=0; y < (ssize_t) image->rows; y++)
902 register const Quantum
909 if (status == MagickFalse)
911 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
912 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
914 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
919 for (x=0; x < (ssize_t) image->columns; x++)
928 if (GetPixelReadMask(image,p) == 0)
930 p+=GetPixelChannels(image);
931 q+=GetPixelChannels(reconstruct_image);
934 Sa=QuantumScale*GetPixelAlpha(image,p);
935 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
936 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
938 PixelChannel channel=GetPixelChannelChannel(image,i);
939 PixelTrait traits=GetPixelChannelTraits(image,channel);
940 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
942 if ((traits == UndefinedPixelTrait) ||
943 (reconstruct_traits == UndefinedPixelTrait) ||
944 ((reconstruct_traits & UpdatePixelTrait) == 0))
946 distortion[i]+=area*QuantumScale*(Sa*p[i]-image_statistics[i].mean)*
947 (Da*GetPixelChannel(reconstruct_image,channel,q)-
948 reconstruct_statistics[channel].mean);
950 p+=GetPixelChannels(image);
951 q+=GetPixelChannels(reconstruct_image);
953 if (image->progress_monitor != (MagickProgressMonitor) NULL)
958 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
960 if (proceed == MagickFalse)
964 reconstruct_view=DestroyCacheView(reconstruct_view);
965 image_view=DestroyCacheView(image_view);
967 Divide by the standard deviation.
969 distortion[CompositePixelChannel]=0.0;
970 for (i=0; i < MaxPixelChannels; i++)
975 PixelChannel channel=GetPixelChannelChannel(image,i);
976 gamma=image_statistics[i].standard_deviation*
977 reconstruct_statistics[channel].standard_deviation;
978 gamma=PerceptibleReciprocal(gamma);
979 distortion[i]=QuantumRange*gamma*distortion[i];
980 distortion[CompositePixelChannel]+=distortion[i]*distortion[i];
982 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]/
983 GetImageChannels(image));
987 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
988 reconstruct_statistics);
989 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
994 static MagickBooleanType GetPeakAbsoluteDistortion(const Image *image,
995 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1008 image_view=AcquireVirtualCacheView(image,exception);
1009 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1010 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1011 #pragma omp parallel for schedule(static,4) shared(status) \
1012 magick_threads(image,image,image->rows,1)
1014 for (y=0; y < (ssize_t) image->rows; y++)
1017 channel_distortion[MaxPixelChannels+1];
1019 register const Quantum
1027 if (status == MagickFalse)
1029 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1030 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1032 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
1037 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
1038 for (x=0; x < (ssize_t) image->columns; x++)
1047 if (GetPixelReadMask(image,p) == 0)
1049 p+=GetPixelChannels(image);
1050 q+=GetPixelChannels(reconstruct_image);
1053 Sa=QuantumScale*GetPixelAlpha(image,p);
1054 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
1055 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1060 PixelChannel channel=GetPixelChannelChannel(image,i);
1061 PixelTrait traits=GetPixelChannelTraits(image,channel);
1062 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
1064 if ((traits == UndefinedPixelTrait) ||
1065 (reconstruct_traits == UndefinedPixelTrait) ||
1066 ((reconstruct_traits & UpdatePixelTrait) == 0))
1068 distance=QuantumScale*fabs(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
1070 if (distance > channel_distortion[i])
1071 channel_distortion[i]=distance;
1072 if (distance > channel_distortion[CompositePixelChannel])
1073 channel_distortion[CompositePixelChannel]=distance;
1075 p+=GetPixelChannels(image);
1076 q+=GetPixelChannels(reconstruct_image);
1078 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1079 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
1081 for (i=0; i <= MaxPixelChannels; i++)
1082 if (channel_distortion[i] > distortion[i])
1083 distortion[i]=channel_distortion[i];
1085 reconstruct_view=DestroyCacheView(reconstruct_view);
1086 image_view=DestroyCacheView(image_view);
1090 static MagickBooleanType GetPeakSignalToNoiseRatio(const Image *image,
1091 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1099 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
1100 for (i=0; i <= MaxPixelChannels; i++)
1101 distortion[i]=20.0*log10((double) 1.0/sqrt(distortion[i]));
1105 static MagickBooleanType GetPerceptualHashDistortion(const Image *image,
1106 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1110 *reconstruct_moments;
1120 Compute perceptual hash in the native image colorspace.
1122 blur_image=BlurImage(image,0.0,1.0,exception);
1123 if (blur_image == (Image *) NULL)
1124 return(MagickFalse);
1125 image_moments=GetImageMoments(blur_image,exception);
1126 if (image_moments == (ChannelMoments *) NULL)
1128 blur_image=DestroyImage(blur_image);
1129 return(MagickFalse);
1131 blur_reconstruct=BlurImage(reconstruct_image,0.0,1.0,exception);
1132 if (blur_reconstruct == (Image *) NULL)
1134 image_moments=(ChannelMoments *) RelinquishMagickMemory(image_moments);
1135 blur_image=DestroyImage(blur_image);
1136 return(MagickFalse);
1138 reconstruct_moments=GetImageMoments(blur_reconstruct,exception);
1139 if (reconstruct_moments == (ChannelMoments *) NULL)
1141 image_moments=(ChannelMoments *) RelinquishMagickMemory(image_moments);
1142 blur_image=DestroyImage(blur_image);
1143 blur_reconstruct=DestroyImage(blur_reconstruct);
1144 return(MagickFalse);
1146 for (i=0; i < 7; i++)
1152 Compute sum of moment differences squared.
1154 for (channel=0; channel < MaxPixelChannels; channel++)
1159 if ((fabs(image_moments[channel].ellipse_intensity) < MagickEpsilon) ||
1160 (fabs(reconstruct_moments[channel].ellipse_intensity) < MagickEpsilon))
1162 difference=log10(fabs(reconstruct_moments[channel].I[i]))-
1163 log10(fabs(image_moments[channel].I[i]));
1164 distortion[channel]+=difference*difference;
1165 distortion[CompositePixelChannel]+=difference*difference;
1168 image_moments=(ChannelMoments *) RelinquishMagickMemory(image_moments);
1169 reconstruct_moments=(ChannelMoments *) RelinquishMagickMemory(
1170 reconstruct_moments);
1171 if ((IsImageGray(blur_image,exception) != MagickFalse) ||
1172 (IsImageGray(blur_reconstruct,exception) != MagickFalse))
1174 blur_reconstruct=DestroyImage(blur_reconstruct);
1175 blur_image=DestroyImage(blur_image);
1179 Compute perceptual hash in the HCLP colorspace.
1181 if ((TransformImageColorspace(blur_image,HCLpColorspace,exception) == MagickFalse) ||
1182 (TransformImageColorspace(blur_reconstruct,HCLpColorspace,exception) == MagickFalse))
1184 blur_reconstruct=DestroyImage(blur_reconstruct);
1185 blur_image=DestroyImage(blur_image);
1186 return(MagickFalse);
1188 image_moments=GetImageMoments(blur_image,exception);
1189 blur_image=DestroyImage(blur_image);
1190 if (image_moments == (ChannelMoments *) NULL)
1192 blur_reconstruct=DestroyImage(blur_reconstruct);
1193 return(MagickFalse);
1195 reconstruct_moments=GetImageMoments(blur_reconstruct,exception);
1196 blur_reconstruct=DestroyImage(blur_reconstruct);
1197 if (reconstruct_moments == (ChannelMoments *) NULL)
1199 image_moments=(ChannelMoments *) RelinquishMagickMemory(image_moments);
1200 return(MagickFalse);
1202 for (i=0; i < 7; i++)
1208 Compute sum of moment differences squared.
1210 for (channel=0; channel < MaxPixelChannels; channel++)
1215 if ((fabs(image_moments[channel].ellipse_intensity) < MagickEpsilon) ||
1216 (fabs(reconstruct_moments[channel].ellipse_intensity) < MagickEpsilon))
1218 difference=log10(fabs(reconstruct_moments[channel].I[i]))-
1219 log10(fabs(image_moments[channel].I[i]));
1220 distortion[channel]+=difference*difference;
1221 distortion[CompositePixelChannel]+=difference*difference;
1224 image_moments=(ChannelMoments *) RelinquishMagickMemory(image_moments);
1225 reconstruct_moments=(ChannelMoments *) RelinquishMagickMemory(
1226 reconstruct_moments);
1230 static MagickBooleanType GetRootMeanSquaredDistortion(const Image *image,
1231 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1239 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
1240 for (i=0; i <= MaxPixelChannels; i++)
1241 distortion[i]=sqrt(distortion[i]);
1245 MagickExport MagickBooleanType GetImageDistortion(Image *image,
1246 const Image *reconstruct_image,const MetricType metric,double *distortion,
1247 ExceptionInfo *exception)
1250 *channel_distortion;
1258 assert(image != (Image *) NULL);
1259 assert(image->signature == MagickSignature);
1260 if (image->debug != MagickFalse)
1261 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1262 assert(reconstruct_image != (const Image *) NULL);
1263 assert(reconstruct_image->signature == MagickSignature);
1264 assert(distortion != (double *) NULL);
1266 if (image->debug != MagickFalse)
1267 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1268 if ((reconstruct_image->columns != image->columns) ||
1269 (reconstruct_image->rows != image->rows))
1270 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1272 Get image distortion.
1274 length=MaxPixelChannels+1;
1275 channel_distortion=(double *) AcquireQuantumMemory(length,
1276 sizeof(*channel_distortion));
1277 if (channel_distortion == (double *) NULL)
1278 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1279 (void) ResetMagickMemory(channel_distortion,0,length*
1280 sizeof(*channel_distortion));
1283 case AbsoluteErrorMetric:
1285 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1289 case FuzzErrorMetric:
1291 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1295 case MeanAbsoluteErrorMetric:
1297 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1298 channel_distortion,exception);
1301 case MeanErrorPerPixelErrorMetric:
1303 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1307 case MeanSquaredErrorMetric:
1309 status=GetMeanSquaredDistortion(image,reconstruct_image,
1310 channel_distortion,exception);
1313 case NormalizedCrossCorrelationErrorMetric:
1316 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1317 channel_distortion,exception);
1320 case PeakAbsoluteErrorMetric:
1322 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1323 channel_distortion,exception);
1326 case PeakSignalToNoiseRatioErrorMetric:
1328 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1329 channel_distortion,exception);
1332 case PerceptualHashErrorMetric:
1334 status=GetPerceptualHashDistortion(image,reconstruct_image,
1335 channel_distortion,exception);
1338 case RootMeanSquaredErrorMetric:
1340 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1341 channel_distortion,exception);
1345 *distortion=channel_distortion[CompositePixelChannel];
1346 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1347 (void) FormatImageProperty(image,"distortion","%.*g",GetMagickPrecision(),
1353 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1357 % G e t I m a g e D i s t o r t i o n s %
1361 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1363 % GetImageDistortions() compares the pixel channels of an image to a
1364 % reconstructed image and returns the specified distortion metric for each
1367 % The format of the GetImageDistortions method is:
1369 % double *GetImageDistortions(const Image *image,
1370 % const Image *reconstruct_image,const MetricType metric,
1371 % ExceptionInfo *exception)
1373 % A description of each parameter follows:
1375 % o image: the image.
1377 % o reconstruct_image: the reconstruct image.
1379 % o metric: the metric.
1381 % o exception: return any errors or warnings in this structure.
1384 MagickExport double *GetImageDistortions(Image *image,
1385 const Image *reconstruct_image,const MetricType metric,
1386 ExceptionInfo *exception)
1389 *channel_distortion;
1397 assert(image != (Image *) NULL);
1398 assert(image->signature == MagickSignature);
1399 if (image->debug != MagickFalse)
1400 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1401 assert(reconstruct_image != (const Image *) NULL);
1402 assert(reconstruct_image->signature == MagickSignature);
1403 if (image->debug != MagickFalse)
1404 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1405 if ((reconstruct_image->columns != image->columns) ||
1406 (reconstruct_image->rows != image->rows))
1408 (void) ThrowMagickException(exception,GetMagickModule(),ImageError,
1409 "ImageSizeDiffers","`%s'",image->filename);
1410 return((double *) NULL);
1413 Get image distortion.
1415 length=MaxPixelChannels+1UL;
1416 channel_distortion=(double *) AcquireQuantumMemory(length,
1417 sizeof(*channel_distortion));
1418 if (channel_distortion == (double *) NULL)
1419 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1420 (void) ResetMagickMemory(channel_distortion,0,length*
1421 sizeof(*channel_distortion));
1425 case AbsoluteErrorMetric:
1427 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1431 case FuzzErrorMetric:
1433 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1437 case MeanAbsoluteErrorMetric:
1439 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1440 channel_distortion,exception);
1443 case MeanErrorPerPixelErrorMetric:
1445 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1449 case MeanSquaredErrorMetric:
1451 status=GetMeanSquaredDistortion(image,reconstruct_image,
1452 channel_distortion,exception);
1455 case NormalizedCrossCorrelationErrorMetric:
1458 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1459 channel_distortion,exception);
1462 case PeakAbsoluteErrorMetric:
1464 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1465 channel_distortion,exception);
1468 case PeakSignalToNoiseRatioErrorMetric:
1470 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1471 channel_distortion,exception);
1474 case PerceptualHashErrorMetric:
1476 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1477 channel_distortion,exception);
1480 case RootMeanSquaredErrorMetric:
1482 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1483 channel_distortion,exception);
1487 if (status == MagickFalse)
1489 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1490 return((double *) NULL);
1492 return(channel_distortion);
1496 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1500 % I s I m a g e s E q u a l %
1504 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1506 % IsImagesEqual() measures the difference between colors at each pixel
1507 % location of two images. A value other than 0 means the colors match
1508 % exactly. Otherwise an error measure is computed by summing over all
1509 % pixels in an image the distance squared in RGB space between each image
1510 % pixel and its corresponding pixel in the reconstruct image. The error
1511 % measure is assigned to these image members:
1513 % o mean_error_per_pixel: The mean error for any single pixel in
1516 % o normalized_mean_error: The normalized mean quantization error for
1517 % any single pixel in the image. This distance measure is normalized to
1518 % a range between 0 and 1. It is independent of the range of red, green,
1519 % and blue values in the image.
1521 % o normalized_maximum_error: The normalized maximum quantization
1522 % error for any single pixel in the image. This distance measure is
1523 % normalized to a range between 0 and 1. It is independent of the range
1524 % of red, green, and blue values in your image.
1526 % A small normalized mean square error, accessed as
1527 % image->normalized_mean_error, suggests the images are very similar in
1528 % spatial layout and color.
1530 % The format of the IsImagesEqual method is:
1532 % MagickBooleanType IsImagesEqual(Image *image,
1533 % const Image *reconstruct_image,ExceptionInfo *exception)
1535 % A description of each parameter follows.
1537 % o image: the image.
1539 % o reconstruct_image: the reconstruct image.
1541 % o exception: return any errors or warnings in this structure.
1544 MagickExport MagickBooleanType IsImagesEqual(Image *image,
1545 const Image *reconstruct_image,ExceptionInfo *exception)
1558 mean_error_per_pixel;
1563 assert(image != (Image *) NULL);
1564 assert(image->signature == MagickSignature);
1565 assert(reconstruct_image != (const Image *) NULL);
1566 assert(reconstruct_image->signature == MagickSignature);
1567 if ((reconstruct_image->columns != image->columns) ||
1568 (reconstruct_image->rows != image->rows))
1569 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1572 mean_error_per_pixel=0.0;
1574 image_view=AcquireVirtualCacheView(image,exception);
1575 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1576 for (y=0; y < (ssize_t) image->rows; y++)
1578 register const Quantum
1585 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1586 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1588 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1590 for (x=0; x < (ssize_t) image->columns; x++)
1595 if (GetPixelReadMask(image,p) == 0)
1597 p+=GetPixelChannels(image);
1598 q+=GetPixelChannels(reconstruct_image);
1601 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1606 PixelChannel channel=GetPixelChannelChannel(image,i);
1607 PixelTrait traits=GetPixelChannelTraits(image,channel);
1608 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
1610 if ((traits == UndefinedPixelTrait) ||
1611 (reconstruct_traits == UndefinedPixelTrait) ||
1612 ((reconstruct_traits & UpdatePixelTrait) == 0))
1614 distance=fabs(p[i]-(double) GetPixelChannel(reconstruct_image,
1616 mean_error_per_pixel+=distance;
1617 mean_error+=distance*distance;
1618 if (distance > maximum_error)
1619 maximum_error=distance;
1622 p+=GetPixelChannels(image);
1623 q+=GetPixelChannels(reconstruct_image);
1626 reconstruct_view=DestroyCacheView(reconstruct_view);
1627 image_view=DestroyCacheView(image_view);
1628 image->error.mean_error_per_pixel=(double) (mean_error_per_pixel/area);
1629 image->error.normalized_mean_error=(double) (QuantumScale*QuantumScale*
1631 image->error.normalized_maximum_error=(double) (QuantumScale*maximum_error);
1632 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
1637 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1641 % S i m i l a r i t y I m a g e %
1645 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1647 % SimilarityImage() compares the reference image of the image and returns the
1648 % best match offset. In addition, it returns a similarity image such that an
1649 % exact match location is completely white and if none of the pixels match,
1650 % black, otherwise some gray level in-between.
1652 % The format of the SimilarityImageImage method is:
1654 % Image *SimilarityImage(const Image *image,const Image *reference,
1655 % const MetricType metric,const double similarity_threshold,
1656 % RectangleInfo *offset,double *similarity,ExceptionInfo *exception)
1658 % A description of each parameter follows:
1660 % o image: the image.
1662 % o reference: find an area of the image that closely resembles this image.
1664 % o metric: the metric.
1666 % o similarity_threshold: minimum distortion for (sub)image match.
1668 % o offset: the best match offset of the reference image within the image.
1670 % o similarity: the computed similarity between the images.
1672 % o exception: return any errors or warnings in this structure.
1676 static double GetSimilarityMetric(const Image *image,const Image *reference,
1677 const MetricType metric,const ssize_t x_offset,const ssize_t y_offset,
1678 ExceptionInfo *exception)
1692 SetGeometry(reference,&geometry);
1693 geometry.x=x_offset;
1694 geometry.y=y_offset;
1695 similarity_image=CropImage(image,&geometry,exception);
1696 if (similarity_image == (Image *) NULL)
1699 status=GetImageDistortion(similarity_image,reference,metric,&distortion,
1701 similarity_image=DestroyImage(similarity_image);
1702 if (status == MagickFalse)
1707 MagickExport Image *SimilarityImage(Image *image,const Image *reference,
1708 const MetricType metric,const double similarity_threshold,
1709 RectangleInfo *offset,double *similarity_metric,ExceptionInfo *exception)
1711 #define SimilarityImageTag "Similarity/Image"
1728 assert(image != (const Image *) NULL);
1729 assert(image->signature == MagickSignature);
1730 if (image->debug != MagickFalse)
1731 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1732 assert(exception != (ExceptionInfo *) NULL);
1733 assert(exception->signature == MagickSignature);
1734 assert(offset != (RectangleInfo *) NULL);
1735 SetGeometry(reference,offset);
1736 *similarity_metric=1.0;
1737 if ((reference->columns > image->columns) || (reference->rows > image->rows))
1738 ThrowImageException(ImageError,"ImageSizeDiffers");
1739 similarity_image=CloneImage(image,image->columns-reference->columns+1,
1740 image->rows-reference->rows+1,MagickTrue,exception);
1741 if (similarity_image == (Image *) NULL)
1742 return((Image *) NULL);
1743 status=SetImageStorageClass(similarity_image,DirectClass,exception);
1744 if (status == MagickFalse)
1746 similarity_image=DestroyImage(similarity_image);
1747 return((Image *) NULL);
1749 (void) SetImageAlphaChannel(similarity_image,DeactivateAlphaChannel,
1752 Measure similarity of reference image against image.
1756 similarity_view=AcquireAuthenticCacheView(similarity_image,exception);
1757 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1758 #pragma omp parallel for schedule(static,4) \
1759 shared(progress,status,similarity_metric) \
1760 magick_threads(image,image,image->rows,1)
1762 for (y=0; y < (ssize_t) (image->rows-reference->rows+1); y++)
1773 if (status == MagickFalse)
1775 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1776 #pragma omp flush(similarity_metric)
1778 if (*similarity_metric <= similarity_threshold)
1780 q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
1782 if (q == (Quantum *) NULL)
1787 for (x=0; x < (ssize_t) (image->columns-reference->columns+1); x++)
1792 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1793 #pragma omp flush(similarity_metric)
1795 if (*similarity_metric <= similarity_threshold)
1797 similarity=GetSimilarityMetric(image,reference,metric,x,y,exception);
1798 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1799 #pragma omp critical (MagickCore_SimilarityImage)
1801 if (similarity < *similarity_metric)
1805 *similarity_metric=similarity;
1807 if (GetPixelReadMask(similarity_image,q) == 0)
1809 SetPixelBackgoundColor(similarity_image,q);
1810 q+=GetPixelChannels(similarity_image);
1813 for (i=0; i < (ssize_t) GetPixelChannels(similarity_image); i++)
1815 PixelChannel channel=GetPixelChannelChannel(image,i);
1816 PixelTrait traits=GetPixelChannelTraits(image,channel);
1817 PixelTrait similarity_traits=GetPixelChannelTraits(similarity_image,
1819 if ((traits == UndefinedPixelTrait) ||
1820 (similarity_traits == UndefinedPixelTrait) ||
1821 ((similarity_traits & UpdatePixelTrait) == 0))
1823 SetPixelChannel(similarity_image,channel,ClampToQuantum(QuantumRange-
1824 QuantumRange*similarity),q);
1826 q+=GetPixelChannels(similarity_image);
1828 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
1830 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1835 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1836 #pragma omp critical (MagickCore_SimilarityImage)
1838 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
1840 if (proceed == MagickFalse)
1844 similarity_view=DestroyCacheView(similarity_view);
1845 if (status == MagickFalse)
1846 similarity_image=DestroyImage(similarity_image);
1847 return(similarity_image);