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-2011 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("#f1001ecc",AllCompliance,&highlight,
166 artifact=GetImageArtifact(image,"highlight-color");
167 if (artifact != (const char *) NULL)
168 (void) QueryColorCompliance(artifact,AllCompliance,&highlight,
170 (void) QueryColorCompliance("#ffffffcc",AllCompliance,&lowlight,
172 artifact=GetImageArtifact(image,"lowlight-color");
173 if (artifact != (const char *) NULL)
174 (void) QueryColorCompliance(artifact,AllCompliance,&lowlight,exception);
176 Generate difference image.
179 image_view=AcquireCacheView(image);
180 reconstruct_view=AcquireCacheView(reconstruct_image);
181 highlight_view=AcquireCacheView(highlight_image);
182 #if defined(MAGICKCORE_OPENMP_SUPPORT)
183 #pragma omp parallel for schedule(dynamic,4) shared(status)
185 for (y=0; y < (ssize_t) image->rows; y++)
190 register const Quantum
200 if (status == MagickFalse)
202 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
203 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
205 r=QueueCacheViewAuthenticPixels(highlight_view,0,y,highlight_image->columns,
207 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL) ||
208 (r == (Quantum *) NULL))
213 for (x=0; x < (ssize_t) image->columns; x++)
221 difference=MagickFalse;
222 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
234 channel=GetPixelChannelMapChannel(image,i);
235 traits=GetPixelChannelMapTraits(image,channel);
236 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
237 if ((traits == UndefinedPixelTrait) ||
238 (reconstruct_traits == UndefinedPixelTrait))
240 if ((reconstruct_traits & UpdatePixelTrait) == 0)
242 distance=p[i]-(MagickRealType)
243 GetPixelChannel(reconstruct_image,channel,q);
244 if (fabs((double) distance) >= MagickEpsilon)
245 difference=MagickTrue;
247 if (difference == MagickFalse)
248 SetPixelInfoPixel(highlight_image,&lowlight,r);
250 SetPixelInfoPixel(highlight_image,&highlight,r);
251 p+=GetPixelChannels(image);
252 q+=GetPixelChannels(reconstruct_image);
253 r+=GetPixelChannels(highlight_image);
255 sync=SyncCacheViewAuthenticPixels(highlight_view,exception);
256 if (sync == MagickFalse)
259 highlight_view=DestroyCacheView(highlight_view);
260 reconstruct_view=DestroyCacheView(reconstruct_view);
261 image_view=DestroyCacheView(image_view);
262 (void) CompositeImage(difference_image,image->compose,highlight_image,0,0,
264 highlight_image=DestroyImage(highlight_image);
265 if (status == MagickFalse)
266 difference_image=DestroyImage(difference_image);
267 return(difference_image);
271 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
275 % G e t I m a g e D i s t o r t i o n %
279 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
281 % GetImageDistortion() compares one or more pixel channels of an image to a
282 % reconstructed image and returns the specified distortion metric.
284 % The format of the CompareImages method is:
286 % MagickBooleanType GetImageDistortion(const Image *image,
287 % const Image *reconstruct_image,const MetricType metric,
288 % double *distortion,ExceptionInfo *exception)
290 % A description of each parameter follows:
292 % o image: the image.
294 % o reconstruct_image: the reconstruct image.
296 % o metric: the metric.
298 % o distortion: the computed distortion between the images.
300 % o exception: return any errors or warnings in this structure.
304 static MagickBooleanType GetAbsoluteDistortion(const Image *image,
305 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
318 Compute the absolute difference in pixels between two images.
321 image_view=AcquireCacheView(image);
322 reconstruct_view=AcquireCacheView(reconstruct_image);
323 #if defined(MAGICKCORE_OPENMP_SUPPORT)
324 #pragma omp parallel for schedule(dynamic,4) shared(status)
326 for (y=0; y < (ssize_t) image->rows; y++)
329 channel_distortion[MaxPixelChannels+1];
331 register const Quantum
339 if (status == MagickFalse)
341 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
342 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
344 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
349 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
350 for (x=0; x < (ssize_t) image->columns; x++)
358 difference=MagickFalse;
359 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
368 channel=GetPixelChannelMapChannel(image,i);
369 traits=GetPixelChannelMapTraits(image,channel);
370 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
371 if ((traits == UndefinedPixelTrait) ||
372 (reconstruct_traits == UndefinedPixelTrait))
374 if ((reconstruct_traits & UpdatePixelTrait) == 0)
376 if (p[i] != GetPixelChannel(reconstruct_image,channel,q))
377 difference=MagickTrue;
379 if (difference != MagickFalse)
381 channel_distortion[i]++;
382 channel_distortion[CompositePixelChannel]++;
384 p+=GetPixelChannels(image);
385 q+=GetPixelChannels(reconstruct_image);
387 #if defined(MAGICKCORE_OPENMP_SUPPORT)
388 #pragma omp critical (MagickCore_GetAbsoluteError)
390 for (i=0; i <= MaxPixelChannels; i++)
391 distortion[i]+=channel_distortion[i];
393 reconstruct_view=DestroyCacheView(reconstruct_view);
394 image_view=DestroyCacheView(image_view);
398 static size_t GetImageChannels(const Image *image)
407 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
415 channel=GetPixelChannelMapChannel(image,i);
416 traits=GetPixelChannelMapTraits(image,channel);
417 if ((traits & UpdatePixelTrait) != 0)
423 static MagickBooleanType GetFuzzDistortion(const Image *image,
424 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
440 image_view=AcquireCacheView(image);
441 reconstruct_view=AcquireCacheView(reconstruct_image);
442 #if defined(MAGICKCORE_OPENMP_SUPPORT)
443 #pragma omp parallel for schedule(dynamic,4) shared(status)
445 for (y=0; y < (ssize_t) image->rows; y++)
448 channel_distortion[MaxPixelChannels+1];
450 register const Quantum
458 if (status == MagickFalse)
460 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
461 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
463 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
468 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
469 for (x=0; x < (ssize_t) image->columns; x++)
474 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
486 channel=GetPixelChannelMapChannel(image,i);
487 traits=GetPixelChannelMapTraits(image,channel);
488 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
489 if ((traits == UndefinedPixelTrait) ||
490 (reconstruct_traits == UndefinedPixelTrait))
492 if ((reconstruct_traits & UpdatePixelTrait) == 0)
494 distance=QuantumScale*(p[i]-(MagickRealType) GetPixelChannel(
495 reconstruct_image,channel,q));
497 channel_distortion[i]+=distance;
498 channel_distortion[CompositePixelChannel]+=distance;
500 p+=GetPixelChannels(image);
501 q+=GetPixelChannels(reconstruct_image);
503 #if defined(MAGICKCORE_OPENMP_SUPPORT)
504 #pragma omp critical (MagickCore_GetMeanSquaredError)
506 for (i=0; i <= MaxPixelChannels; i++)
507 distortion[i]+=channel_distortion[i];
509 reconstruct_view=DestroyCacheView(reconstruct_view);
510 image_view=DestroyCacheView(image_view);
511 for (i=0; i <= MaxPixelChannels; i++)
512 distortion[i]/=((double) image->columns*image->rows);
513 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
514 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]);
518 static MagickBooleanType GetMeanAbsoluteDistortion(const Image *image,
519 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
535 image_view=AcquireCacheView(image);
536 reconstruct_view=AcquireCacheView(reconstruct_image);
537 #if defined(MAGICKCORE_OPENMP_SUPPORT)
538 #pragma omp parallel for schedule(dynamic,4) shared(status)
540 for (y=0; y < (ssize_t) image->rows; y++)
543 channel_distortion[MaxPixelChannels+1];
545 register const Quantum
553 if (status == MagickFalse)
555 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
556 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
558 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
563 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
564 for (x=0; x < (ssize_t) image->columns; x++)
569 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
581 channel=GetPixelChannelMapChannel(image,i);
582 traits=GetPixelChannelMapTraits(image,channel);
583 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
584 if ((traits == UndefinedPixelTrait) ||
585 (reconstruct_traits == UndefinedPixelTrait))
587 if ((reconstruct_traits & UpdatePixelTrait) == 0)
589 distance=QuantumScale*fabs(p[i]-(MagickRealType) GetPixelChannel(
590 reconstruct_image,channel,q));
591 channel_distortion[i]+=distance;
592 channel_distortion[CompositePixelChannel]+=distance;
594 p+=GetPixelChannels(image);
595 q+=GetPixelChannels(reconstruct_image);
597 #if defined(MAGICKCORE_OPENMP_SUPPORT)
598 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
600 for (i=0; i <= MaxPixelChannels; i++)
601 distortion[i]+=channel_distortion[i];
603 reconstruct_view=DestroyCacheView(reconstruct_view);
604 image_view=DestroyCacheView(image_view);
605 for (i=0; i <= MaxPixelChannels; i++)
606 distortion[i]/=((double) image->columns*image->rows);
607 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
611 static MagickBooleanType GetMeanErrorPerPixel(Image *image,
612 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
637 image_view=AcquireCacheView(image);
638 reconstruct_view=AcquireCacheView(reconstruct_image);
639 for (y=0; y < (ssize_t) image->rows; y++)
641 register const Quantum
648 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
649 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
651 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
656 for (x=0; x < (ssize_t) image->columns; x++)
661 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
673 channel=GetPixelChannelMapChannel(image,i);
674 traits=GetPixelChannelMapTraits(image,channel);
675 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
676 if ((traits == UndefinedPixelTrait) ||
677 (reconstruct_traits == UndefinedPixelTrait))
679 if ((reconstruct_traits & UpdatePixelTrait) == 0)
681 distance=fabs((double) (alpha*p[i]-beta*GetPixelChannel(
682 reconstruct_image,channel,q)));
683 distortion[i]+=distance;
684 distortion[CompositePixelChannel]+=distance;
685 mean_error+=distance*distance;
686 if (distance > maximum_error)
687 maximum_error=distance;
690 p+=GetPixelChannels(image);
691 q+=GetPixelChannels(reconstruct_image);
694 reconstruct_view=DestroyCacheView(reconstruct_view);
695 image_view=DestroyCacheView(image_view);
696 image->error.mean_error_per_pixel=distortion[CompositePixelChannel]/area;
697 image->error.normalized_mean_error=QuantumScale*QuantumScale*mean_error/area;
698 image->error.normalized_maximum_error=QuantumScale*maximum_error;
702 static MagickBooleanType GetMeanSquaredDistortion(const Image *image,
703 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
719 image_view=AcquireCacheView(image);
720 reconstruct_view=AcquireCacheView(reconstruct_image);
721 #if defined(MAGICKCORE_OPENMP_SUPPORT)
722 #pragma omp parallel for schedule(dynamic,4) shared(status)
724 for (y=0; y < (ssize_t) image->rows; y++)
727 channel_distortion[MaxPixelChannels+1];
729 register const Quantum
737 if (status == MagickFalse)
739 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
740 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
742 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
747 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
748 for (x=0; x < (ssize_t) image->columns; x++)
753 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
765 channel=GetPixelChannelMapChannel(image,i);
766 traits=GetPixelChannelMapTraits(image,channel);
767 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
768 if ((traits == UndefinedPixelTrait) ||
769 (reconstruct_traits == UndefinedPixelTrait))
771 if ((reconstruct_traits & UpdatePixelTrait) == 0)
773 distance=QuantumScale*(p[i]-(MagickRealType) GetPixelChannel(
774 reconstruct_image,channel,q));
776 channel_distortion[i]+=distance;
777 channel_distortion[CompositePixelChannel]+=distance;
779 p+=GetPixelChannels(image);
780 q+=GetPixelChannels(reconstruct_image);
782 #if defined(MAGICKCORE_OPENMP_SUPPORT)
783 #pragma omp critical (MagickCore_GetMeanSquaredError)
785 for (i=0; i <= MaxPixelChannels; i++)
786 distortion[i]+=channel_distortion[i];
788 reconstruct_view=DestroyCacheView(reconstruct_view);
789 image_view=DestroyCacheView(image_view);
790 for (i=0; i <= MaxPixelChannels; i++)
791 distortion[i]/=((double) image->columns*image->rows);
792 distortion[CompositePixelChannel]/=GetImageChannels(image);
796 static MagickBooleanType GetNormalizedCrossCorrelationDistortion(
797 const Image *image,const Image *reconstruct_image,double *distortion,
798 ExceptionInfo *exception)
800 #define SimilarityImageTag "Similarity/Image"
808 *reconstruct_statistics;
826 Normalize to account for variation due to lighting and exposure condition.
828 image_statistics=GetImageStatistics(image,exception);
829 reconstruct_statistics=GetImageStatistics(reconstruct_image,exception);
832 for (i=0; i <= MaxPixelChannels; i++)
834 area=1.0/((MagickRealType) image->columns*image->rows-1);
835 image_view=AcquireCacheView(image);
836 reconstruct_view=AcquireCacheView(reconstruct_image);
837 for (y=0; y < (ssize_t) image->rows; y++)
839 register const Quantum
846 if (status == MagickFalse)
848 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
849 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
851 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
856 for (x=0; x < (ssize_t) image->columns; x++)
861 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
870 channel=GetPixelChannelMapChannel(image,i);
871 traits=GetPixelChannelMapTraits(image,channel);
872 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
873 if ((traits == UndefinedPixelTrait) ||
874 (reconstruct_traits == UndefinedPixelTrait))
876 if ((reconstruct_traits & UpdatePixelTrait) == 0)
878 distortion[i]+=area*QuantumScale*(p[i]-image_statistics[i].mean)*
879 (GetPixelChannel(reconstruct_image,channel,q)-
880 reconstruct_statistics[channel].mean);
882 p+=GetPixelChannels(image);
883 q+=GetPixelChannels(image);
885 if (image->progress_monitor != (MagickProgressMonitor) NULL)
890 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
892 if (proceed == MagickFalse)
896 reconstruct_view=DestroyCacheView(reconstruct_view);
897 image_view=DestroyCacheView(image_view);
899 Divide by the standard deviation.
901 distortion[CompositePixelChannel]=0.0;
902 for (i=0; i < MaxPixelChannels; i++)
910 channel=GetPixelChannelMapChannel(image,i);
911 gamma=image_statistics[i].standard_deviation*
912 reconstruct_statistics[channel].standard_deviation;
913 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
914 distortion[i]=QuantumRange*gamma*distortion[i];
915 distortion[CompositePixelChannel]+=distortion[i]*distortion[i];
917 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]/
918 GetImageChannels(image));
922 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
923 reconstruct_statistics);
924 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
929 static MagickBooleanType GetPeakAbsoluteDistortion(const Image *image,
930 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
943 image_view=AcquireCacheView(image);
944 reconstruct_view=AcquireCacheView(reconstruct_image);
945 #if defined(MAGICKCORE_OPENMP_SUPPORT)
946 #pragma omp parallel for schedule(dynamic,4) shared(status)
948 for (y=0; y < (ssize_t) image->rows; y++)
951 channel_distortion[MaxPixelChannels+1];
953 register const Quantum
961 if (status == MagickFalse)
963 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
964 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,
965 reconstruct_image->columns,1,exception);
966 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
971 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
972 for (x=0; x < (ssize_t) image->columns; x++)
977 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
989 channel=GetPixelChannelMapChannel(image,i);
990 traits=GetPixelChannelMapTraits(image,channel);
991 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
992 if ((traits == UndefinedPixelTrait) ||
993 (reconstruct_traits == UndefinedPixelTrait))
995 if ((reconstruct_traits & UpdatePixelTrait) == 0)
997 distance=QuantumScale*fabs(p[i]-(MagickRealType) GetPixelChannel(
998 reconstruct_image,channel,q));
999 if (distance > channel_distortion[i])
1000 channel_distortion[i]=distance;
1001 if (distance > channel_distortion[CompositePixelChannel])
1002 channel_distortion[CompositePixelChannel]=distance;
1004 p+=GetPixelChannels(image);
1005 q+=GetPixelChannels(image);
1007 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1008 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
1010 for (i=0; i <= MaxPixelChannels; i++)
1011 if (channel_distortion[i] > distortion[i])
1012 distortion[i]=channel_distortion[i];
1014 reconstruct_view=DestroyCacheView(reconstruct_view);
1015 image_view=DestroyCacheView(image_view);
1019 static MagickBooleanType GetPeakSignalToNoiseRatio(const Image *image,
1020 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1028 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
1029 for (i=0; i <= MaxPixelChannels; i++)
1030 distortion[i]=20.0*log10((double) 1.0/sqrt(distortion[i]));
1034 static MagickBooleanType GetRootMeanSquaredDistortion(const Image *image,
1035 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1043 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
1044 for (i=0; i <= MaxPixelChannels; i++)
1045 distortion[i]=sqrt(distortion[i]);
1049 MagickExport MagickBooleanType GetImageDistortion(Image *image,
1050 const Image *reconstruct_image,const MetricType metric,double *distortion,
1051 ExceptionInfo *exception)
1054 *channel_distortion;
1062 assert(image != (Image *) NULL);
1063 assert(image->signature == MagickSignature);
1064 if (image->debug != MagickFalse)
1065 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1066 assert(reconstruct_image != (const Image *) NULL);
1067 assert(reconstruct_image->signature == MagickSignature);
1068 assert(distortion != (double *) NULL);
1070 if (image->debug != MagickFalse)
1071 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1072 if ((reconstruct_image->columns != image->columns) ||
1073 (reconstruct_image->rows != image->rows))
1074 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1076 Get image distortion.
1078 length=MaxPixelChannels+1;
1079 channel_distortion=(double *) AcquireQuantumMemory(length,
1080 sizeof(*channel_distortion));
1081 if (channel_distortion == (double *) NULL)
1082 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1083 (void) ResetMagickMemory(channel_distortion,0,length*
1084 sizeof(*channel_distortion));
1087 case AbsoluteErrorMetric:
1089 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1093 case FuzzErrorMetric:
1095 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1099 case MeanAbsoluteErrorMetric:
1101 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1102 channel_distortion,exception);
1105 case MeanErrorPerPixelMetric:
1107 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1111 case MeanSquaredErrorMetric:
1113 status=GetMeanSquaredDistortion(image,reconstruct_image,
1114 channel_distortion,exception);
1117 case NormalizedCrossCorrelationErrorMetric:
1120 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1121 channel_distortion,exception);
1124 case PeakAbsoluteErrorMetric:
1126 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1127 channel_distortion,exception);
1130 case PeakSignalToNoiseRatioMetric:
1132 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1133 channel_distortion,exception);
1136 case RootMeanSquaredErrorMetric:
1138 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1139 channel_distortion,exception);
1143 *distortion=channel_distortion[CompositePixelChannel];
1144 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1149 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1153 % G e t I m a g e D i s t o r t i o n s %
1157 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1159 % GetImageDistrortion() compares the pixel channels of an image to a
1160 % reconstructed image and returns the specified distortion metric for each
1163 % The format of the CompareImages method is:
1165 % double *GetImageDistortions(const Image *image,
1166 % const Image *reconstruct_image,const MetricType metric,
1167 % ExceptionInfo *exception)
1169 % A description of each parameter follows:
1171 % o image: the image.
1173 % o reconstruct_image: the reconstruct image.
1175 % o metric: the metric.
1177 % o exception: return any errors or warnings in this structure.
1180 MagickExport double *GetImageDistortions(Image *image,
1181 const Image *reconstruct_image,const MetricType metric,
1182 ExceptionInfo *exception)
1185 *channel_distortion;
1193 assert(image != (Image *) NULL);
1194 assert(image->signature == MagickSignature);
1195 if (image->debug != MagickFalse)
1196 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1197 assert(reconstruct_image != (const Image *) NULL);
1198 assert(reconstruct_image->signature == MagickSignature);
1199 if (image->debug != MagickFalse)
1200 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1201 if ((reconstruct_image->columns != image->columns) ||
1202 (reconstruct_image->rows != image->rows))
1204 (void) ThrowMagickException(exception,GetMagickModule(),ImageError,
1205 "ImageSizeDiffers","`%s'",image->filename);
1206 return((double *) NULL);
1209 Get image distortion.
1211 length=MaxPixelChannels+1UL;
1212 channel_distortion=(double *) AcquireQuantumMemory(length,
1213 sizeof(*channel_distortion));
1214 if (channel_distortion == (double *) NULL)
1215 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1216 (void) ResetMagickMemory(channel_distortion,0,length*
1217 sizeof(*channel_distortion));
1221 case AbsoluteErrorMetric:
1223 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1227 case FuzzErrorMetric:
1229 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1233 case MeanAbsoluteErrorMetric:
1235 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1236 channel_distortion,exception);
1239 case MeanErrorPerPixelMetric:
1241 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1245 case MeanSquaredErrorMetric:
1247 status=GetMeanSquaredDistortion(image,reconstruct_image,
1248 channel_distortion,exception);
1251 case NormalizedCrossCorrelationErrorMetric:
1254 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1255 channel_distortion,exception);
1258 case PeakAbsoluteErrorMetric:
1260 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1261 channel_distortion,exception);
1264 case PeakSignalToNoiseRatioMetric:
1266 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1267 channel_distortion,exception);
1270 case RootMeanSquaredErrorMetric:
1272 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1273 channel_distortion,exception);
1277 if (status == MagickFalse)
1279 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1280 return((double *) NULL);
1282 return(channel_distortion);
1286 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1290 % I s I m a g e s E q u a l %
1294 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1296 % IsImagesEqual() measures the difference between colors at each pixel
1297 % location of two images. A value other than 0 means the colors match
1298 % exactly. Otherwise an error measure is computed by summing over all
1299 % pixels in an image the distance squared in RGB space between each image
1300 % pixel and its corresponding pixel in the reconstruct image. The error
1301 % measure is assigned to these image members:
1303 % o mean_error_per_pixel: The mean error for any single pixel in
1306 % o normalized_mean_error: The normalized mean quantization error for
1307 % any single pixel in the image. This distance measure is normalized to
1308 % a range between 0 and 1. It is independent of the range of red, green,
1309 % and blue values in the image.
1311 % o normalized_maximum_error: The normalized maximum quantization
1312 % error for any single pixel in the image. This distance measure is
1313 % normalized to a range between 0 and 1. It is independent of the range
1314 % of red, green, and blue values in your image.
1316 % A small normalized mean square error, accessed as
1317 % image->normalized_mean_error, suggests the images are very similar in
1318 % spatial layout and color.
1320 % The format of the IsImagesEqual method is:
1322 % MagickBooleanType IsImagesEqual(Image *image,
1323 % const Image *reconstruct_image,ExceptionInfo *exception)
1325 % A description of each parameter follows.
1327 % o image: the image.
1329 % o reconstruct_image: the reconstruct image.
1331 % o exception: return any errors or warnings in this structure.
1334 MagickExport MagickBooleanType IsImagesEqual(Image *image,
1335 const Image *reconstruct_image,ExceptionInfo *exception)
1348 mean_error_per_pixel;
1353 assert(image != (Image *) NULL);
1354 assert(image->signature == MagickSignature);
1355 assert(reconstruct_image != (const Image *) NULL);
1356 assert(reconstruct_image->signature == MagickSignature);
1357 if ((reconstruct_image->columns != image->columns) ||
1358 (reconstruct_image->rows != image->rows))
1359 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1362 mean_error_per_pixel=0.0;
1364 image_view=AcquireCacheView(image);
1365 reconstruct_view=AcquireCacheView(reconstruct_image);
1366 for (y=0; y < (ssize_t) image->rows; y++)
1368 register const Quantum
1375 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1376 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1378 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1380 for (x=0; x < (ssize_t) image->columns; x++)
1385 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1397 channel=GetPixelChannelMapChannel(image,i);
1398 traits=GetPixelChannelMapTraits(image,channel);
1399 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
1400 if ((traits == UndefinedPixelTrait) ||
1401 (reconstruct_traits == UndefinedPixelTrait))
1403 if ((reconstruct_traits & UpdatePixelTrait) == 0)
1405 distance=fabs(p[i]-(MagickRealType) GetPixelChannel(reconstruct_image,
1407 mean_error_per_pixel+=distance;
1408 mean_error+=distance*distance;
1409 if (distance > maximum_error)
1410 maximum_error=distance;
1413 p+=GetPixelChannels(image);
1414 q+=GetPixelChannels(reconstruct_image);
1417 reconstruct_view=DestroyCacheView(reconstruct_view);
1418 image_view=DestroyCacheView(image_view);
1419 image->error.mean_error_per_pixel=(double) (mean_error_per_pixel/area);
1420 image->error.normalized_mean_error=(double) (QuantumScale*QuantumScale*
1422 image->error.normalized_maximum_error=(double) (QuantumScale*maximum_error);
1423 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
1428 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1432 % S i m i l a r i t y I m a g e %
1436 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1438 % SimilarityImage() compares the reference image of the image and returns the
1439 % best match offset. In addition, it returns a similarity image such that an
1440 % exact match location is completely white and if none of the pixels match,
1441 % black, otherwise some gray level in-between.
1443 % The format of the SimilarityImageImage method is:
1445 % Image *SimilarityImage(const Image *image,const Image *reference,
1446 % const MetricType metric,RectangleInfo *offset,double *similarity,
1447 % ExceptionInfo *exception)
1449 % A description of each parameter follows:
1451 % o image: the image.
1453 % o reference: find an area of the image that closely resembles this image.
1455 % o metric: the metric.
1457 % o the best match offset of the reference image within the image.
1459 % o similarity: the computed similarity between the images.
1461 % o exception: return any errors or warnings in this structure.
1465 static double GetSimilarityMetric(const Image *image,const Image *reference,
1466 const MetricType metric,const ssize_t x_offset,const ssize_t y_offset,
1467 ExceptionInfo *exception)
1481 SetGeometry(reference,&geometry);
1482 geometry.x=x_offset;
1483 geometry.y=y_offset;
1484 similarity_image=CropImage(image,&geometry,exception);
1485 if (similarity_image == (Image *) NULL)
1488 status=GetImageDistortion(similarity_image,reference,metric,&distortion,
1490 similarity_image=DestroyImage(similarity_image);
1491 if (status == MagickFalse)
1496 MagickExport Image *SimilarityImage(Image *image,const Image *reference,
1497 const MetricType metric,RectangleInfo *offset,double *similarity_metric,
1498 ExceptionInfo *exception)
1500 #define SimilarityImageTag "Similarity/Image"
1517 assert(image != (const Image *) NULL);
1518 assert(image->signature == MagickSignature);
1519 if (image->debug != MagickFalse)
1520 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1521 assert(exception != (ExceptionInfo *) NULL);
1522 assert(exception->signature == MagickSignature);
1523 assert(offset != (RectangleInfo *) NULL);
1524 SetGeometry(reference,offset);
1525 *similarity_metric=1.0;
1526 if ((reference->columns > image->columns) || (reference->rows > image->rows))
1527 ThrowImageException(ImageError,"ImageSizeDiffers");
1528 similarity_image=CloneImage(image,image->columns-reference->columns+1,
1529 image->rows-reference->rows+1,MagickTrue,exception);
1530 if (similarity_image == (Image *) NULL)
1531 return((Image *) NULL);
1532 status=SetImageStorageClass(similarity_image,DirectClass,exception);
1533 if (status == MagickFalse)
1535 similarity_image=DestroyImage(similarity_image);
1536 return((Image *) NULL);
1539 Measure similarity of reference image against image.
1543 similarity_view=AcquireCacheView(similarity_image);
1544 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1545 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1547 for (y=0; y < (ssize_t) (image->rows-reference->rows+1); y++)
1558 if (status == MagickFalse)
1560 q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
1562 if (q == (Quantum *) NULL)
1567 for (x=0; x < (ssize_t) (image->columns-reference->columns+1); x++)
1572 similarity=GetSimilarityMetric(image,reference,metric,x,y,exception);
1573 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1574 #pragma omp critical (MagickCore_SimilarityImage)
1576 if (similarity < *similarity_metric)
1578 *similarity_metric=similarity;
1582 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1591 channel=GetPixelChannelMapChannel(image,i);
1592 traits=GetPixelChannelMapTraits(image,channel);
1593 similarity_traits=GetPixelChannelMapTraits(similarity_image,channel);
1594 if ((traits == UndefinedPixelTrait) ||
1595 (similarity_traits == UndefinedPixelTrait))
1597 if ((similarity_traits & UpdatePixelTrait) == 0)
1599 SetPixelChannel(similarity_image,channel,ClampToQuantum(QuantumRange-
1600 QuantumRange*similarity),q);
1602 q+=GetPixelChannels(similarity_image);
1604 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
1606 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1611 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1612 #pragma omp critical (MagickCore_SimilarityImage)
1614 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
1616 if (proceed == MagickFalse)
1620 similarity_view=DestroyCacheView(similarity_view);
1621 return(similarity_image);