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-2012 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/cache-view.h"
46 #include "MagickCore/client.h"
47 #include "MagickCore/color.h"
48 #include "MagickCore/color-private.h"
49 #include "MagickCore/colorspace.h"
50 #include "MagickCore/colorspace-private.h"
51 #include "MagickCore/compare.h"
52 #include "MagickCore/composite-private.h"
53 #include "MagickCore/constitute.h"
54 #include "MagickCore/exception-private.h"
55 #include "MagickCore/geometry.h"
56 #include "MagickCore/image-private.h"
57 #include "MagickCore/list.h"
58 #include "MagickCore/log.h"
59 #include "MagickCore/memory_.h"
60 #include "MagickCore/monitor.h"
61 #include "MagickCore/monitor-private.h"
62 #include "MagickCore/option.h"
63 #include "MagickCore/pixel-accessor.h"
64 #include "MagickCore/resource_.h"
65 #include "MagickCore/string_.h"
66 #include "MagickCore/statistic.h"
67 #include "MagickCore/transform.h"
68 #include "MagickCore/utility.h"
69 #include "MagickCore/version.h"
72 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
76 % C o m p a r e I m a g e %
80 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
82 % CompareImages() compares one or more pixel channels of an image to a
83 % reconstructed image and returns the difference image.
85 % The format of the CompareImages method is:
87 % Image *CompareImages(const Image *image,const Image *reconstruct_image,
88 % const MetricType metric,double *distortion,ExceptionInfo *exception)
90 % A description of each parameter follows:
94 % o reconstruct_image: the reconstruct image.
96 % o metric: the metric.
98 % o distortion: the computed distortion between the images.
100 % o exception: return any errors or warnings in this structure.
103 MagickExport Image *CompareImages(Image *image,const Image *reconstruct_image,
104 const MetricType metric,double *distortion,ExceptionInfo *exception)
128 assert(image != (Image *) NULL);
129 assert(image->signature == MagickSignature);
130 if (image->debug != MagickFalse)
131 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
132 assert(reconstruct_image != (const Image *) NULL);
133 assert(reconstruct_image->signature == MagickSignature);
134 assert(distortion != (double *) NULL);
136 if (image->debug != MagickFalse)
137 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
138 if ((reconstruct_image->columns != image->columns) ||
139 (reconstruct_image->rows != image->rows))
140 ThrowImageException(ImageError,"ImageSizeDiffers");
141 status=GetImageDistortion(image,reconstruct_image,metric,distortion,
143 if (status == MagickFalse)
144 return((Image *) NULL);
145 difference_image=CloneImage(image,0,0,MagickTrue,exception);
146 if (difference_image == (Image *) NULL)
147 return((Image *) NULL);
148 (void) SetImageAlphaChannel(difference_image,OpaqueAlphaChannel,exception);
149 highlight_image=CloneImage(image,image->columns,image->rows,MagickTrue,
151 if (highlight_image == (Image *) NULL)
153 difference_image=DestroyImage(difference_image);
154 return((Image *) NULL);
156 status=SetImageStorageClass(highlight_image,DirectClass,exception);
157 if (status == MagickFalse)
159 difference_image=DestroyImage(difference_image);
160 highlight_image=DestroyImage(highlight_image);
161 return((Image *) NULL);
163 (void) SetImageAlphaChannel(highlight_image,OpaqueAlphaChannel,exception);
164 (void) QueryColorCompliance("#f1001e33",AllCompliance,&highlight,exception);
165 artifact=GetImageArtifact(image,"highlight-color");
166 if (artifact != (const char *) NULL)
167 (void) QueryColorCompliance(artifact,AllCompliance,&highlight,exception);
168 (void) QueryColorCompliance("#ffffff33",AllCompliance,&lowlight,exception);
169 artifact=GetImageArtifact(image,"lowlight-color");
170 if (artifact != (const char *) NULL)
171 (void) QueryColorCompliance(artifact,AllCompliance,&lowlight,exception);
173 Generate difference image.
176 image_view=AcquireVirtualCacheView(image,exception);
177 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
178 highlight_view=AcquireAuthenticCacheView(highlight_image,exception);
179 #if defined(MAGICKCORE_OPENMP_SUPPORT)
180 #pragma omp parallel for schedule(static,4) shared(status)
182 for (y=0; y < (ssize_t) image->rows; y++)
187 register const Quantum
197 if (status == MagickFalse)
199 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
200 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
202 r=QueueCacheViewAuthenticPixels(highlight_view,0,y,highlight_image->columns,
204 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL) ||
205 (r == (Quantum *) NULL))
210 for (x=0; x < (ssize_t) image->columns; x++)
218 if (GetPixelMask(image,p) != 0)
220 SetPixelInfoPixel(highlight_image,&lowlight,r);
221 p+=GetPixelChannels(image);
222 q+=GetPixelChannels(reconstruct_image);
223 r+=GetPixelChannels(highlight_image);
226 difference=MagickFalse;
227 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
239 channel=GetPixelChannelMapChannel(image,i);
240 traits=GetPixelChannelMapTraits(image,channel);
241 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
242 if ((traits == UndefinedPixelTrait) ||
243 (reconstruct_traits == UndefinedPixelTrait) ||
244 ((reconstruct_traits & UpdatePixelTrait) == 0))
246 distance=p[i]-(MagickRealType) GetPixelChannel(reconstruct_image,
248 if (fabs((double) distance) >= MagickEpsilon)
249 difference=MagickTrue;
251 if (difference == MagickFalse)
252 SetPixelInfoPixel(highlight_image,&lowlight,r);
254 SetPixelInfoPixel(highlight_image,&highlight,r);
255 p+=GetPixelChannels(image);
256 q+=GetPixelChannels(reconstruct_image);
257 r+=GetPixelChannels(highlight_image);
259 sync=SyncCacheViewAuthenticPixels(highlight_view,exception);
260 if (sync == MagickFalse)
263 highlight_view=DestroyCacheView(highlight_view);
264 reconstruct_view=DestroyCacheView(reconstruct_view);
265 image_view=DestroyCacheView(image_view);
266 (void) CompositeImage(difference_image,highlight_image,image->compose,
267 MagickTrue,0,0,exception);
268 highlight_image=DestroyImage(highlight_image);
269 if (status == MagickFalse)
270 difference_image=DestroyImage(difference_image);
271 return(difference_image);
275 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
279 % G e t I m a g e D i s t o r t i o n %
283 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
285 % GetImageDistortion() compares one or more pixel channels of an image to a
286 % reconstructed image and returns the specified distortion metric.
288 % The format of the CompareImages method is:
290 % MagickBooleanType GetImageDistortion(const Image *image,
291 % const Image *reconstruct_image,const MetricType metric,
292 % double *distortion,ExceptionInfo *exception)
294 % A description of each parameter follows:
296 % o image: the image.
298 % o reconstruct_image: the reconstruct image.
300 % o metric: the metric.
302 % o distortion: the computed distortion between the images.
304 % o exception: return any errors or warnings in this structure.
308 static MagickBooleanType GetAbsoluteDistortion(const Image *image,
309 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
322 Compute the absolute difference in pixels between two images.
325 image_view=AcquireVirtualCacheView(image,exception);
326 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
327 #if defined(MAGICKCORE_OPENMP_SUPPORT)
328 #pragma omp parallel for schedule(static,4) shared(status)
330 for (y=0; y < (ssize_t) image->rows; y++)
333 channel_distortion[MaxPixelChannels+1];
335 register const Quantum
343 if (status == MagickFalse)
345 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
346 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
348 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
353 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
354 for (x=0; x < (ssize_t) image->columns; x++)
362 if (GetPixelMask(image,p) != 0)
364 p+=GetPixelChannels(image);
365 q+=GetPixelChannels(reconstruct_image);
368 difference=MagickFalse;
369 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
378 channel=GetPixelChannelMapChannel(image,i);
379 traits=GetPixelChannelMapTraits(image,channel);
380 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
381 if ((traits == UndefinedPixelTrait) ||
382 (reconstruct_traits == UndefinedPixelTrait) ||
383 ((reconstruct_traits & UpdatePixelTrait) == 0))
385 if (p[i] != GetPixelChannel(reconstruct_image,channel,q))
386 difference=MagickTrue;
388 if (difference != MagickFalse)
390 channel_distortion[i]++;
391 channel_distortion[CompositePixelChannel]++;
393 p+=GetPixelChannels(image);
394 q+=GetPixelChannels(reconstruct_image);
396 #if defined(MAGICKCORE_OPENMP_SUPPORT)
397 #pragma omp critical (MagickCore_GetAbsoluteError)
399 for (i=0; i <= MaxPixelChannels; i++)
400 distortion[i]+=channel_distortion[i];
402 reconstruct_view=DestroyCacheView(reconstruct_view);
403 image_view=DestroyCacheView(image_view);
407 static size_t GetImageChannels(const Image *image)
416 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
424 channel=GetPixelChannelMapChannel(image,i);
425 traits=GetPixelChannelMapTraits(image,channel);
426 if ((traits & UpdatePixelTrait) != 0)
432 static MagickBooleanType GetFuzzDistortion(const Image *image,
433 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
449 image_view=AcquireVirtualCacheView(image,exception);
450 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
451 #if defined(MAGICKCORE_OPENMP_SUPPORT)
452 #pragma omp parallel for schedule(static,4) shared(status)
454 for (y=0; y < (ssize_t) image->rows; y++)
457 channel_distortion[MaxPixelChannels+1];
459 register const Quantum
467 if (status == MagickFalse)
469 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
470 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
472 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
477 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
478 for (x=0; x < (ssize_t) image->columns; x++)
483 if (GetPixelMask(image,p) != 0)
485 p+=GetPixelChannels(image);
486 q+=GetPixelChannels(reconstruct_image);
489 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
501 channel=GetPixelChannelMapChannel(image,i);
502 traits=GetPixelChannelMapTraits(image,channel);
503 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
504 if ((traits == UndefinedPixelTrait) ||
505 (reconstruct_traits == UndefinedPixelTrait) ||
506 ((reconstruct_traits & UpdatePixelTrait) == 0))
508 distance=QuantumScale*(p[i]-(MagickRealType) GetPixelChannel(
509 reconstruct_image,channel,q));
511 channel_distortion[i]+=distance;
512 channel_distortion[CompositePixelChannel]+=distance;
514 p+=GetPixelChannels(image);
515 q+=GetPixelChannels(reconstruct_image);
517 #if defined(MAGICKCORE_OPENMP_SUPPORT)
518 #pragma omp critical (MagickCore_GetMeanSquaredError)
520 for (i=0; i <= MaxPixelChannels; i++)
521 distortion[i]+=channel_distortion[i];
523 reconstruct_view=DestroyCacheView(reconstruct_view);
524 image_view=DestroyCacheView(image_view);
525 for (i=0; i <= MaxPixelChannels; i++)
526 distortion[i]/=((double) image->columns*image->rows);
527 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
528 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]);
532 static MagickBooleanType GetMeanAbsoluteDistortion(const Image *image,
533 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
549 image_view=AcquireVirtualCacheView(image,exception);
550 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
551 #if defined(MAGICKCORE_OPENMP_SUPPORT)
552 #pragma omp parallel for schedule(static,4) shared(status)
554 for (y=0; y < (ssize_t) image->rows; y++)
557 channel_distortion[MaxPixelChannels+1];
559 register const Quantum
567 if (status == MagickFalse)
569 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
570 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
572 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
577 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
578 for (x=0; x < (ssize_t) image->columns; x++)
583 if (GetPixelMask(image,p) != 0)
585 p+=GetPixelChannels(image);
586 q+=GetPixelChannels(reconstruct_image);
589 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
601 channel=GetPixelChannelMapChannel(image,i);
602 traits=GetPixelChannelMapTraits(image,channel);
603 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
604 if ((traits == UndefinedPixelTrait) ||
605 (reconstruct_traits == UndefinedPixelTrait) ||
606 ((reconstruct_traits & UpdatePixelTrait) == 0))
608 distance=QuantumScale*fabs(p[i]-(MagickRealType) GetPixelChannel(
609 reconstruct_image,channel,q));
610 channel_distortion[i]+=distance;
611 channel_distortion[CompositePixelChannel]+=distance;
613 p+=GetPixelChannels(image);
614 q+=GetPixelChannels(reconstruct_image);
616 #if defined(MAGICKCORE_OPENMP_SUPPORT)
617 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
619 for (i=0; i <= MaxPixelChannels; i++)
620 distortion[i]+=channel_distortion[i];
622 reconstruct_view=DestroyCacheView(reconstruct_view);
623 image_view=DestroyCacheView(image_view);
624 for (i=0; i <= MaxPixelChannels; i++)
625 distortion[i]/=((double) image->columns*image->rows);
626 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
630 static MagickBooleanType GetMeanErrorPerPixel(Image *image,
631 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
656 image_view=AcquireVirtualCacheView(image,exception);
657 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
658 for (y=0; y < (ssize_t) image->rows; y++)
660 register const Quantum
667 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
668 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
670 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
675 for (x=0; x < (ssize_t) image->columns; x++)
680 if (GetPixelMask(image,p) != 0)
682 p+=GetPixelChannels(image);
683 q+=GetPixelChannels(reconstruct_image);
686 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
698 channel=GetPixelChannelMapChannel(image,i);
699 traits=GetPixelChannelMapTraits(image,channel);
700 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
701 if ((traits == UndefinedPixelTrait) ||
702 (reconstruct_traits == UndefinedPixelTrait) ||
703 ((reconstruct_traits & UpdatePixelTrait) == 0))
705 distance=fabs((double) (alpha*p[i]-beta*GetPixelChannel(
706 reconstruct_image,channel,q)));
707 distortion[i]+=distance;
708 distortion[CompositePixelChannel]+=distance;
709 mean_error+=distance*distance;
710 if (distance > maximum_error)
711 maximum_error=distance;
714 p+=GetPixelChannels(image);
715 q+=GetPixelChannels(reconstruct_image);
718 reconstruct_view=DestroyCacheView(reconstruct_view);
719 image_view=DestroyCacheView(image_view);
720 image->error.mean_error_per_pixel=distortion[CompositePixelChannel]/area;
721 image->error.normalized_mean_error=QuantumScale*QuantumScale*mean_error/area;
722 image->error.normalized_maximum_error=QuantumScale*maximum_error;
726 static MagickBooleanType GetMeanSquaredDistortion(const Image *image,
727 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
743 image_view=AcquireVirtualCacheView(image,exception);
744 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
745 #if defined(MAGICKCORE_OPENMP_SUPPORT)
746 #pragma omp parallel for schedule(static,4) shared(status)
748 for (y=0; y < (ssize_t) image->rows; y++)
751 channel_distortion[MaxPixelChannels+1];
753 register const Quantum
761 if (status == MagickFalse)
763 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
764 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
766 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
771 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
772 for (x=0; x < (ssize_t) image->columns; x++)
777 if (GetPixelMask(image,p) != 0)
779 p+=GetPixelChannels(image);
780 q+=GetPixelChannels(reconstruct_image);
783 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
795 channel=GetPixelChannelMapChannel(image,i);
796 traits=GetPixelChannelMapTraits(image,channel);
797 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
798 if ((traits == UndefinedPixelTrait) ||
799 (reconstruct_traits == UndefinedPixelTrait) ||
800 ((reconstruct_traits & UpdatePixelTrait) == 0))
802 distance=QuantumScale*(p[i]-(MagickRealType) GetPixelChannel(
803 reconstruct_image,channel,q));
805 channel_distortion[i]+=distance;
806 channel_distortion[CompositePixelChannel]+=distance;
808 p+=GetPixelChannels(image);
809 q+=GetPixelChannels(reconstruct_image);
811 #if defined(MAGICKCORE_OPENMP_SUPPORT)
812 #pragma omp critical (MagickCore_GetMeanSquaredError)
814 for (i=0; i <= MaxPixelChannels; i++)
815 distortion[i]+=channel_distortion[i];
817 reconstruct_view=DestroyCacheView(reconstruct_view);
818 image_view=DestroyCacheView(image_view);
819 for (i=0; i <= MaxPixelChannels; i++)
820 distortion[i]/=((double) image->columns*image->rows);
821 distortion[CompositePixelChannel]/=GetImageChannels(image);
825 static MagickBooleanType GetNormalizedCrossCorrelationDistortion(
826 const Image *image,const Image *reconstruct_image,double *distortion,
827 ExceptionInfo *exception)
829 #define SimilarityImageTag "Similarity/Image"
837 *reconstruct_statistics;
855 Normalize to account for variation due to lighting and exposure condition.
857 image_statistics=GetImageStatistics(image,exception);
858 reconstruct_statistics=GetImageStatistics(reconstruct_image,exception);
861 for (i=0; i <= MaxPixelChannels; i++)
863 area=1.0/((MagickRealType) image->columns*image->rows-1);
864 image_view=AcquireVirtualCacheView(image,exception);
865 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
866 for (y=0; y < (ssize_t) image->rows; y++)
868 register const Quantum
875 if (status == MagickFalse)
877 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
878 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
880 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
885 for (x=0; x < (ssize_t) image->columns; x++)
890 if (GetPixelMask(image,p) != 0)
892 p+=GetPixelChannels(image);
893 q+=GetPixelChannels(reconstruct_image);
896 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
905 channel=GetPixelChannelMapChannel(image,i);
906 traits=GetPixelChannelMapTraits(image,channel);
907 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
908 if ((traits == UndefinedPixelTrait) ||
909 (reconstruct_traits == UndefinedPixelTrait) ||
910 ((reconstruct_traits & UpdatePixelTrait) == 0))
912 distortion[i]+=area*QuantumScale*(p[i]-image_statistics[i].mean)*
913 (GetPixelChannel(reconstruct_image,channel,q)-
914 reconstruct_statistics[channel].mean);
916 p+=GetPixelChannels(image);
917 q+=GetPixelChannels(reconstruct_image);
919 if (image->progress_monitor != (MagickProgressMonitor) NULL)
924 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
926 if (proceed == MagickFalse)
930 reconstruct_view=DestroyCacheView(reconstruct_view);
931 image_view=DestroyCacheView(image_view);
933 Divide by the standard deviation.
935 distortion[CompositePixelChannel]=0.0;
936 for (i=0; i < MaxPixelChannels; i++)
944 channel=GetPixelChannelMapChannel(image,i);
945 gamma=image_statistics[i].standard_deviation*
946 reconstruct_statistics[channel].standard_deviation;
947 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
948 distortion[i]=QuantumRange*gamma*distortion[i];
949 distortion[CompositePixelChannel]+=distortion[i]*distortion[i];
951 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]/
952 GetImageChannels(image));
956 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
957 reconstruct_statistics);
958 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
963 static MagickBooleanType GetPeakAbsoluteDistortion(const Image *image,
964 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
977 image_view=AcquireVirtualCacheView(image,exception);
978 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
979 #if defined(MAGICKCORE_OPENMP_SUPPORT)
980 #pragma omp parallel for schedule(static,4) shared(status)
982 for (y=0; y < (ssize_t) image->rows; y++)
985 channel_distortion[MaxPixelChannels+1];
987 register const Quantum
995 if (status == MagickFalse)
997 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
998 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1000 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
1005 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
1006 for (x=0; x < (ssize_t) image->columns; x++)
1011 if (GetPixelMask(image,p) != 0)
1013 p+=GetPixelChannels(image);
1014 q+=GetPixelChannels(reconstruct_image);
1017 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1029 channel=GetPixelChannelMapChannel(image,i);
1030 traits=GetPixelChannelMapTraits(image,channel);
1031 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
1032 if ((traits == UndefinedPixelTrait) ||
1033 (reconstruct_traits == UndefinedPixelTrait) ||
1034 ((reconstruct_traits & UpdatePixelTrait) == 0))
1036 distance=QuantumScale*fabs(p[i]-(MagickRealType) GetPixelChannel(
1037 reconstruct_image,channel,q));
1038 if (distance > channel_distortion[i])
1039 channel_distortion[i]=distance;
1040 if (distance > channel_distortion[CompositePixelChannel])
1041 channel_distortion[CompositePixelChannel]=distance;
1043 p+=GetPixelChannels(image);
1044 q+=GetPixelChannels(reconstruct_image);
1046 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1047 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
1049 for (i=0; i <= MaxPixelChannels; i++)
1050 if (channel_distortion[i] > distortion[i])
1051 distortion[i]=channel_distortion[i];
1053 reconstruct_view=DestroyCacheView(reconstruct_view);
1054 image_view=DestroyCacheView(image_view);
1058 static MagickBooleanType GetPeakSignalToNoiseRatio(const Image *image,
1059 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1067 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
1068 for (i=0; i <= MaxPixelChannels; i++)
1069 distortion[i]=20.0*log10((double) 1.0/sqrt(distortion[i]));
1073 static MagickBooleanType GetRootMeanSquaredDistortion(const Image *image,
1074 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1082 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
1083 for (i=0; i <= MaxPixelChannels; i++)
1084 distortion[i]=sqrt(distortion[i]);
1088 MagickExport MagickBooleanType GetImageDistortion(Image *image,
1089 const Image *reconstruct_image,const MetricType metric,double *distortion,
1090 ExceptionInfo *exception)
1093 *channel_distortion;
1101 assert(image != (Image *) NULL);
1102 assert(image->signature == MagickSignature);
1103 if (image->debug != MagickFalse)
1104 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1105 assert(reconstruct_image != (const Image *) NULL);
1106 assert(reconstruct_image->signature == MagickSignature);
1107 assert(distortion != (double *) NULL);
1109 if (image->debug != MagickFalse)
1110 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1111 if ((reconstruct_image->columns != image->columns) ||
1112 (reconstruct_image->rows != image->rows))
1113 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1115 Get image distortion.
1117 length=MaxPixelChannels+1;
1118 channel_distortion=(double *) AcquireQuantumMemory(length,
1119 sizeof(*channel_distortion));
1120 if (channel_distortion == (double *) NULL)
1121 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1122 (void) ResetMagickMemory(channel_distortion,0,length*
1123 sizeof(*channel_distortion));
1126 case AbsoluteErrorMetric:
1128 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1132 case FuzzErrorMetric:
1134 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1138 case MeanAbsoluteErrorMetric:
1140 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1141 channel_distortion,exception);
1144 case MeanErrorPerPixelMetric:
1146 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1150 case MeanSquaredErrorMetric:
1152 status=GetMeanSquaredDistortion(image,reconstruct_image,
1153 channel_distortion,exception);
1156 case NormalizedCrossCorrelationErrorMetric:
1159 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1160 channel_distortion,exception);
1163 case PeakAbsoluteErrorMetric:
1165 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1166 channel_distortion,exception);
1169 case PeakSignalToNoiseRatioMetric:
1171 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1172 channel_distortion,exception);
1175 case RootMeanSquaredErrorMetric:
1177 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1178 channel_distortion,exception);
1182 *distortion=channel_distortion[CompositePixelChannel];
1183 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1188 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1192 % G e t I m a g e D i s t o r t i o n s %
1196 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1198 % GetImageDistrortion() compares the pixel channels of an image to a
1199 % reconstructed image and returns the specified distortion metric for each
1202 % The format of the CompareImages method is:
1204 % double *GetImageDistortions(const Image *image,
1205 % const Image *reconstruct_image,const MetricType metric,
1206 % ExceptionInfo *exception)
1208 % A description of each parameter follows:
1210 % o image: the image.
1212 % o reconstruct_image: the reconstruct image.
1214 % o metric: the metric.
1216 % o exception: return any errors or warnings in this structure.
1219 MagickExport double *GetImageDistortions(Image *image,
1220 const Image *reconstruct_image,const MetricType metric,
1221 ExceptionInfo *exception)
1224 *channel_distortion;
1232 assert(image != (Image *) NULL);
1233 assert(image->signature == MagickSignature);
1234 if (image->debug != MagickFalse)
1235 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1236 assert(reconstruct_image != (const Image *) NULL);
1237 assert(reconstruct_image->signature == MagickSignature);
1238 if (image->debug != MagickFalse)
1239 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1240 if ((reconstruct_image->columns != image->columns) ||
1241 (reconstruct_image->rows != image->rows))
1243 (void) ThrowMagickException(exception,GetMagickModule(),ImageError,
1244 "ImageSizeDiffers","'%s'",image->filename);
1245 return((double *) NULL);
1248 Get image distortion.
1250 length=MaxPixelChannels+1UL;
1251 channel_distortion=(double *) AcquireQuantumMemory(length,
1252 sizeof(*channel_distortion));
1253 if (channel_distortion == (double *) NULL)
1254 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1255 (void) ResetMagickMemory(channel_distortion,0,length*
1256 sizeof(*channel_distortion));
1260 case AbsoluteErrorMetric:
1262 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1266 case FuzzErrorMetric:
1268 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1272 case MeanAbsoluteErrorMetric:
1274 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1275 channel_distortion,exception);
1278 case MeanErrorPerPixelMetric:
1280 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1284 case MeanSquaredErrorMetric:
1286 status=GetMeanSquaredDistortion(image,reconstruct_image,
1287 channel_distortion,exception);
1290 case NormalizedCrossCorrelationErrorMetric:
1293 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1294 channel_distortion,exception);
1297 case PeakAbsoluteErrorMetric:
1299 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1300 channel_distortion,exception);
1303 case PeakSignalToNoiseRatioMetric:
1305 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1306 channel_distortion,exception);
1309 case RootMeanSquaredErrorMetric:
1311 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1312 channel_distortion,exception);
1316 if (status == MagickFalse)
1318 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1319 return((double *) NULL);
1321 return(channel_distortion);
1325 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1329 % I s I m a g e s E q u a l %
1333 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1335 % IsImagesEqual() measures the difference between colors at each pixel
1336 % location of two images. A value other than 0 means the colors match
1337 % exactly. Otherwise an error measure is computed by summing over all
1338 % pixels in an image the distance squared in RGB space between each image
1339 % pixel and its corresponding pixel in the reconstruct image. The error
1340 % measure is assigned to these image members:
1342 % o mean_error_per_pixel: The mean error for any single pixel in
1345 % o normalized_mean_error: The normalized mean quantization error for
1346 % any single pixel in the image. This distance measure is normalized to
1347 % a range between 0 and 1. It is independent of the range of red, green,
1348 % and blue values in the image.
1350 % o normalized_maximum_error: The normalized maximum quantization
1351 % error for any single pixel in the image. This distance measure is
1352 % normalized to a range between 0 and 1. It is independent of the range
1353 % of red, green, and blue values in your image.
1355 % A small normalized mean square error, accessed as
1356 % image->normalized_mean_error, suggests the images are very similar in
1357 % spatial layout and color.
1359 % The format of the IsImagesEqual method is:
1361 % MagickBooleanType IsImagesEqual(Image *image,
1362 % const Image *reconstruct_image,ExceptionInfo *exception)
1364 % A description of each parameter follows.
1366 % o image: the image.
1368 % o reconstruct_image: the reconstruct image.
1370 % o exception: return any errors or warnings in this structure.
1373 MagickExport MagickBooleanType IsImagesEqual(Image *image,
1374 const Image *reconstruct_image,ExceptionInfo *exception)
1387 mean_error_per_pixel;
1392 assert(image != (Image *) NULL);
1393 assert(image->signature == MagickSignature);
1394 assert(reconstruct_image != (const Image *) NULL);
1395 assert(reconstruct_image->signature == MagickSignature);
1396 if ((reconstruct_image->columns != image->columns) ||
1397 (reconstruct_image->rows != image->rows))
1398 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1401 mean_error_per_pixel=0.0;
1403 image_view=AcquireVirtualCacheView(image,exception);
1404 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1405 for (y=0; y < (ssize_t) image->rows; y++)
1407 register const Quantum
1414 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1415 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1417 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1419 for (x=0; x < (ssize_t) image->columns; x++)
1424 if (GetPixelMask(image,p) != 0)
1426 p+=GetPixelChannels(image);
1427 q+=GetPixelChannels(reconstruct_image);
1430 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1442 channel=GetPixelChannelMapChannel(image,i);
1443 traits=GetPixelChannelMapTraits(image,channel);
1444 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
1445 if ((traits == UndefinedPixelTrait) ||
1446 (reconstruct_traits == UndefinedPixelTrait) ||
1447 ((reconstruct_traits & UpdatePixelTrait) == 0))
1449 distance=fabs(p[i]-(MagickRealType) GetPixelChannel(reconstruct_image,
1451 mean_error_per_pixel+=distance;
1452 mean_error+=distance*distance;
1453 if (distance > maximum_error)
1454 maximum_error=distance;
1457 p+=GetPixelChannels(image);
1458 q+=GetPixelChannels(reconstruct_image);
1461 reconstruct_view=DestroyCacheView(reconstruct_view);
1462 image_view=DestroyCacheView(image_view);
1463 image->error.mean_error_per_pixel=(double) (mean_error_per_pixel/area);
1464 image->error.normalized_mean_error=(double) (QuantumScale*QuantumScale*
1466 image->error.normalized_maximum_error=(double) (QuantumScale*maximum_error);
1467 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
1472 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1476 % S i m i l a r i t y I m a g e %
1480 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1482 % SimilarityImage() compares the reference image of the image and returns the
1483 % best match offset. In addition, it returns a similarity image such that an
1484 % exact match location is completely white and if none of the pixels match,
1485 % black, otherwise some gray level in-between.
1487 % The format of the SimilarityImageImage method is:
1489 % Image *SimilarityImage(const Image *image,const Image *reference,
1490 % const MetricType metric,RectangleInfo *offset,double *similarity,
1491 % ExceptionInfo *exception)
1493 % A description of each parameter follows:
1495 % o image: the image.
1497 % o reference: find an area of the image that closely resembles this image.
1499 % o metric: the metric.
1501 % o the best match offset of the reference image within the image.
1503 % o similarity: the computed similarity between the images.
1505 % o exception: return any errors or warnings in this structure.
1509 static double GetSimilarityMetric(const Image *image,const Image *reference,
1510 const MetricType metric,const ssize_t x_offset,const ssize_t y_offset,
1511 ExceptionInfo *exception)
1525 SetGeometry(reference,&geometry);
1526 geometry.x=x_offset;
1527 geometry.y=y_offset;
1528 similarity_image=CropImage(image,&geometry,exception);
1529 if (similarity_image == (Image *) NULL)
1532 status=GetImageDistortion(similarity_image,reference,metric,&distortion,
1534 similarity_image=DestroyImage(similarity_image);
1535 if (status == MagickFalse)
1540 MagickExport Image *SimilarityImage(Image *image,const Image *reference,
1541 const MetricType metric,RectangleInfo *offset,double *similarity_metric,
1542 ExceptionInfo *exception)
1544 #define SimilarityImageTag "Similarity/Image"
1561 assert(image != (const Image *) NULL);
1562 assert(image->signature == MagickSignature);
1563 if (image->debug != MagickFalse)
1564 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1565 assert(exception != (ExceptionInfo *) NULL);
1566 assert(exception->signature == MagickSignature);
1567 assert(offset != (RectangleInfo *) NULL);
1568 SetGeometry(reference,offset);
1569 *similarity_metric=1.0;
1570 if ((reference->columns > image->columns) || (reference->rows > image->rows))
1571 ThrowImageException(ImageError,"ImageSizeDiffers");
1572 similarity_image=CloneImage(image,image->columns-reference->columns+1,
1573 image->rows-reference->rows+1,MagickTrue,exception);
1574 if (similarity_image == (Image *) NULL)
1575 return((Image *) NULL);
1576 status=SetImageStorageClass(similarity_image,DirectClass,exception);
1577 if (status == MagickFalse)
1579 similarity_image=DestroyImage(similarity_image);
1580 return((Image *) NULL);
1583 Measure similarity of reference image against image.
1587 similarity_view=AcquireAuthenticCacheView(similarity_image,exception);
1588 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1589 #pragma omp parallel for schedule(static,4) shared(progress,status)
1591 for (y=0; y < (ssize_t) (image->rows-reference->rows+1); y++)
1602 if (status == MagickFalse)
1604 q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
1606 if (q == (Quantum *) NULL)
1611 for (x=0; x < (ssize_t) (image->columns-reference->columns+1); x++)
1616 similarity=GetSimilarityMetric(image,reference,metric,x,y,exception);
1617 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1618 #pragma omp critical (MagickCore_SimilarityImage)
1620 if (similarity < *similarity_metric)
1622 *similarity_metric=similarity;
1626 if (GetPixelMask(similarity_image,q) != 0)
1628 q+=GetPixelChannels(similarity_image);
1631 for (i=0; i < (ssize_t) GetPixelChannels(similarity_image); i++)
1640 channel=GetPixelChannelMapChannel(image,i);
1641 traits=GetPixelChannelMapTraits(image,channel);
1642 similarity_traits=GetPixelChannelMapTraits(similarity_image,channel);
1643 if ((traits == UndefinedPixelTrait) ||
1644 (similarity_traits == UndefinedPixelTrait) ||
1645 ((similarity_traits & UpdatePixelTrait) == 0))
1647 SetPixelChannel(similarity_image,channel,ClampToQuantum(QuantumRange-
1648 QuantumRange*similarity),q);
1650 q+=GetPixelChannels(similarity_image);
1652 if (IfMagickFalse( SyncCacheViewAuthenticPixels(similarity_view,exception) ))
1654 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1656 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1657 #pragma omp critical (MagickCore_SimilarityImage)
1659 if (IfMagickFalse( SetImageProgress(image,SimilarityImageTag,
1660 progress++,image->rows) ))
1664 similarity_view=DestroyCacheView(similarity_view);
1665 return(similarity_image);