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-2013 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/thread-private.h"
68 #include "MagickCore/transform.h"
69 #include "MagickCore/utility.h"
70 #include "MagickCore/version.h"
73 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
77 % C o m p a r e I m a g e %
81 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
83 % CompareImages() compares one or more pixel channels of an image to a
84 % reconstructed image and returns the difference image.
86 % The format of the CompareImages method is:
88 % Image *CompareImages(const Image *image,const Image *reconstruct_image,
89 % const MetricType metric,double *distortion,ExceptionInfo *exception)
91 % A description of each parameter follows:
95 % o reconstruct_image: the reconstruct image.
97 % o metric: the metric.
99 % o distortion: the computed distortion between the images.
101 % o exception: return any errors or warnings in this structure.
104 MagickExport Image *CompareImages(Image *image,const Image *reconstruct_image,
105 const MetricType metric,double *distortion,ExceptionInfo *exception)
129 assert(image != (Image *) NULL);
130 assert(image->signature == MagickSignature);
131 if (image->debug != MagickFalse)
132 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
133 assert(reconstruct_image != (const Image *) NULL);
134 assert(reconstruct_image->signature == MagickSignature);
135 assert(distortion != (double *) NULL);
137 if (image->debug != MagickFalse)
138 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
139 if ((reconstruct_image->columns != image->columns) ||
140 (reconstruct_image->rows != image->rows))
141 ThrowImageException(ImageError,"ImageSizeDiffers");
142 status=GetImageDistortion(image,reconstruct_image,metric,distortion,
144 if (status == MagickFalse)
145 return((Image *) NULL);
146 difference_image=CloneImage(image,0,0,MagickTrue,exception);
147 if (difference_image == (Image *) NULL)
148 return((Image *) NULL);
149 (void) SetImageAlphaChannel(difference_image,OpaqueAlphaChannel,exception);
150 highlight_image=CloneImage(image,image->columns,image->rows,MagickTrue,
152 if (highlight_image == (Image *) NULL)
154 difference_image=DestroyImage(difference_image);
155 return((Image *) NULL);
157 status=SetImageStorageClass(highlight_image,DirectClass,exception);
158 if (status == MagickFalse)
160 difference_image=DestroyImage(difference_image);
161 highlight_image=DestroyImage(highlight_image);
162 return((Image *) NULL);
164 (void) SetImageAlphaChannel(highlight_image,OpaqueAlphaChannel,exception);
165 (void) QueryColorCompliance("#f1001e33",AllCompliance,&highlight,exception);
166 artifact=GetImageArtifact(image,"highlight-color");
167 if (artifact != (const char *) NULL)
168 (void) QueryColorCompliance(artifact,AllCompliance,&highlight,exception);
169 (void) QueryColorCompliance("#ffffff33",AllCompliance,&lowlight,exception);
170 artifact=GetImageArtifact(image,"lowlight-color");
171 if (artifact != (const char *) NULL)
172 (void) QueryColorCompliance(artifact,AllCompliance,&lowlight,exception);
174 Generate difference image.
177 image_view=AcquireVirtualCacheView(image,exception);
178 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
179 highlight_view=AcquireAuthenticCacheView(highlight_image,exception);
180 #if defined(MAGICKCORE_OPENMP_SUPPORT)
181 #pragma omp parallel for schedule(static,4) shared(status) \
182 dynamic_number_threads(image,image->columns,image->rows,1)
184 for (y=0; y < (ssize_t) image->rows; y++)
189 register const Quantum
199 if (status == MagickFalse)
201 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
202 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
204 r=QueueCacheViewAuthenticPixels(highlight_view,0,y,highlight_image->columns,
206 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL) ||
207 (r == (Quantum *) NULL))
212 for (x=0; x < (ssize_t) image->columns; x++)
220 if (GetPixelMask(image,p) != 0)
222 SetPixelInfoPixel(highlight_image,&lowlight,r);
223 p+=GetPixelChannels(image);
224 q+=GetPixelChannels(reconstruct_image);
225 r+=GetPixelChannels(highlight_image);
228 difference=MagickFalse;
229 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
241 channel=GetPixelChannelChannel(image,i);
242 traits=GetPixelChannelTraits(image,channel);
243 reconstruct_traits=GetPixelChannelTraits(reconstruct_image,channel);
244 if ((traits == UndefinedPixelTrait) ||
245 (reconstruct_traits == UndefinedPixelTrait) ||
246 ((reconstruct_traits & UpdatePixelTrait) == 0))
248 distance=p[i]-(double) GetPixelChannel(reconstruct_image,channel,q);
249 if (fabs((double) distance) >= MagickEpsilon)
250 difference=MagickTrue;
252 if (difference == MagickFalse)
253 SetPixelInfoPixel(highlight_image,&lowlight,r);
255 SetPixelInfoPixel(highlight_image,&highlight,r);
256 p+=GetPixelChannels(image);
257 q+=GetPixelChannels(reconstruct_image);
258 r+=GetPixelChannels(highlight_image);
260 sync=SyncCacheViewAuthenticPixels(highlight_view,exception);
261 if (sync == MagickFalse)
264 highlight_view=DestroyCacheView(highlight_view);
265 reconstruct_view=DestroyCacheView(reconstruct_view);
266 image_view=DestroyCacheView(image_view);
267 (void) CompositeImage(difference_image,highlight_image,image->compose,
268 MagickTrue,0,0,exception);
269 highlight_image=DestroyImage(highlight_image);
270 if (status == MagickFalse)
271 difference_image=DestroyImage(difference_image);
272 return(difference_image);
276 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
280 % G e t I m a g e D i s t o r t i o n %
284 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
286 % GetImageDistortion() compares one or more pixel channels of an image to a
287 % reconstructed image and returns the specified distortion metric.
289 % The format of the CompareImages method is:
291 % MagickBooleanType GetImageDistortion(const Image *image,
292 % const Image *reconstruct_image,const MetricType metric,
293 % double *distortion,ExceptionInfo *exception)
295 % A description of each parameter follows:
297 % o image: the image.
299 % o reconstruct_image: the reconstruct image.
301 % o metric: the metric.
303 % o distortion: the computed distortion between the images.
305 % o exception: return any errors or warnings in this structure.
309 static MagickBooleanType GetAbsoluteDistortion(const Image *image,
310 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
323 Compute the absolute difference in pixels between two images.
326 image_view=AcquireVirtualCacheView(image,exception);
327 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
328 #if defined(MAGICKCORE_OPENMP_SUPPORT)
329 #pragma omp parallel for schedule(static,4) shared(status) \
330 dynamic_number_threads(image,image->columns,image->rows,1)
332 for (y=0; y < (ssize_t) image->rows; y++)
335 channel_distortion[MaxPixelChannels+1];
337 register const Quantum
345 if (status == MagickFalse)
347 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
348 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
350 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
355 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
356 for (x=0; x < (ssize_t) image->columns; x++)
364 if (GetPixelMask(image,p) != 0)
366 p+=GetPixelChannels(image);
367 q+=GetPixelChannels(reconstruct_image);
370 difference=MagickFalse;
371 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
380 channel=GetPixelChannelChannel(image,i);
381 traits=GetPixelChannelTraits(image,channel);
382 reconstruct_traits=GetPixelChannelTraits(reconstruct_image,channel);
383 if ((traits == UndefinedPixelTrait) ||
384 (reconstruct_traits == UndefinedPixelTrait) ||
385 ((reconstruct_traits & UpdatePixelTrait) == 0))
387 if (p[i] != GetPixelChannel(reconstruct_image,channel,q))
388 difference=MagickTrue;
390 if (difference != MagickFalse)
392 channel_distortion[i]++;
393 channel_distortion[CompositePixelChannel]++;
395 p+=GetPixelChannels(image);
396 q+=GetPixelChannels(reconstruct_image);
398 #if defined(MAGICKCORE_OPENMP_SUPPORT)
399 #pragma omp critical (MagickCore_GetAbsoluteError)
401 for (i=0; i <= MaxPixelChannels; i++)
402 distortion[i]+=channel_distortion[i];
404 reconstruct_view=DestroyCacheView(reconstruct_view);
405 image_view=DestroyCacheView(image_view);
409 static size_t GetImageChannels(const Image *image)
418 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
426 channel=GetPixelChannelChannel(image,i);
427 traits=GetPixelChannelTraits(image,channel);
428 if ((traits & UpdatePixelTrait) != 0)
434 static MagickBooleanType GetFuzzDistortion(const Image *image,
435 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
451 image_view=AcquireVirtualCacheView(image,exception);
452 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
453 #if defined(MAGICKCORE_OPENMP_SUPPORT)
454 #pragma omp parallel for schedule(static,4) shared(status) \
455 dynamic_number_threads(image,image->columns,image->rows,1)
457 for (y=0; y < (ssize_t) image->rows; y++)
460 channel_distortion[MaxPixelChannels+1];
462 register const Quantum
470 if (status == MagickFalse)
472 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
473 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
475 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
480 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
481 for (x=0; x < (ssize_t) image->columns; x++)
486 if (GetPixelMask(image,p) != 0)
488 p+=GetPixelChannels(image);
489 q+=GetPixelChannels(reconstruct_image);
492 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
504 channel=GetPixelChannelChannel(image,i);
505 traits=GetPixelChannelTraits(image,channel);
506 reconstruct_traits=GetPixelChannelTraits(reconstruct_image,channel);
507 if ((traits == UndefinedPixelTrait) ||
508 (reconstruct_traits == UndefinedPixelTrait) ||
509 ((reconstruct_traits & UpdatePixelTrait) == 0))
511 distance=QuantumScale*(p[i]-(double) GetPixelChannel(reconstruct_image,
514 channel_distortion[i]+=distance;
515 channel_distortion[CompositePixelChannel]+=distance;
517 p+=GetPixelChannels(image);
518 q+=GetPixelChannels(reconstruct_image);
520 #if defined(MAGICKCORE_OPENMP_SUPPORT)
521 #pragma omp critical (MagickCore_GetFuzzDistortion)
523 for (i=0; i <= MaxPixelChannels; i++)
524 distortion[i]+=channel_distortion[i];
526 reconstruct_view=DestroyCacheView(reconstruct_view);
527 image_view=DestroyCacheView(image_view);
528 for (i=0; i <= MaxPixelChannels; i++)
529 distortion[i]/=((double) image->columns*image->rows);
530 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
531 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]);
535 static MagickBooleanType GetMeanAbsoluteDistortion(const Image *image,
536 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
552 image_view=AcquireVirtualCacheView(image,exception);
553 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
554 #if defined(MAGICKCORE_OPENMP_SUPPORT)
555 #pragma omp parallel for schedule(static,4) shared(status) \
556 dynamic_number_threads(image,image->columns,image->rows,1)
558 for (y=0; y < (ssize_t) image->rows; y++)
561 channel_distortion[MaxPixelChannels+1];
563 register const Quantum
571 if (status == MagickFalse)
573 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
574 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
576 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
581 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
582 for (x=0; x < (ssize_t) image->columns; x++)
587 if (GetPixelMask(image,p) != 0)
589 p+=GetPixelChannels(image);
590 q+=GetPixelChannels(reconstruct_image);
593 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
605 channel=GetPixelChannelChannel(image,i);
606 traits=GetPixelChannelTraits(image,channel);
607 reconstruct_traits=GetPixelChannelTraits(reconstruct_image,channel);
608 if ((traits == UndefinedPixelTrait) ||
609 (reconstruct_traits == UndefinedPixelTrait) ||
610 ((reconstruct_traits & UpdatePixelTrait) == 0))
612 distance=QuantumScale*fabs(p[i]-(double) GetPixelChannel(
613 reconstruct_image,channel,q));
614 channel_distortion[i]+=distance;
615 channel_distortion[CompositePixelChannel]+=distance;
617 p+=GetPixelChannels(image);
618 q+=GetPixelChannels(reconstruct_image);
620 #if defined(MAGICKCORE_OPENMP_SUPPORT)
621 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
623 for (i=0; i <= MaxPixelChannels; i++)
624 distortion[i]+=channel_distortion[i];
626 reconstruct_view=DestroyCacheView(reconstruct_view);
627 image_view=DestroyCacheView(image_view);
628 for (i=0; i <= MaxPixelChannels; i++)
629 distortion[i]/=((double) image->columns*image->rows);
630 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
634 static MagickBooleanType GetMeanErrorPerPixel(Image *image,
635 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
660 image_view=AcquireVirtualCacheView(image,exception);
661 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
662 for (y=0; y < (ssize_t) image->rows; y++)
664 register const Quantum
671 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
672 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
674 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
679 for (x=0; x < (ssize_t) image->columns; x++)
684 if (GetPixelMask(image,p) != 0)
686 p+=GetPixelChannels(image);
687 q+=GetPixelChannels(reconstruct_image);
690 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
702 channel=GetPixelChannelChannel(image,i);
703 traits=GetPixelChannelTraits(image,channel);
704 reconstruct_traits=GetPixelChannelTraits(reconstruct_image,channel);
705 if ((traits == UndefinedPixelTrait) ||
706 (reconstruct_traits == UndefinedPixelTrait) ||
707 ((reconstruct_traits & UpdatePixelTrait) == 0))
709 distance=fabs((double) (alpha*p[i]-beta*GetPixelChannel(
710 reconstruct_image,channel,q)));
711 distortion[i]+=distance;
712 distortion[CompositePixelChannel]+=distance;
713 mean_error+=distance*distance;
714 if (distance > maximum_error)
715 maximum_error=distance;
718 p+=GetPixelChannels(image);
719 q+=GetPixelChannels(reconstruct_image);
722 reconstruct_view=DestroyCacheView(reconstruct_view);
723 image_view=DestroyCacheView(image_view);
724 image->error.mean_error_per_pixel=distortion[CompositePixelChannel]/area;
725 image->error.normalized_mean_error=QuantumScale*QuantumScale*mean_error/area;
726 image->error.normalized_maximum_error=QuantumScale*maximum_error;
730 static MagickBooleanType GetMeanSquaredDistortion(const Image *image,
731 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
747 image_view=AcquireVirtualCacheView(image,exception);
748 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
749 #if defined(MAGICKCORE_OPENMP_SUPPORT)
750 #pragma omp parallel for schedule(static,4) shared(status) \
751 dynamic_number_threads(image,image->columns,image->rows,1)
753 for (y=0; y < (ssize_t) image->rows; y++)
756 channel_distortion[MaxPixelChannels+1];
758 register const Quantum
766 if (status == MagickFalse)
768 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
769 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
771 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
776 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
777 for (x=0; x < (ssize_t) image->columns; x++)
782 if (GetPixelMask(image,p) != 0)
784 p+=GetPixelChannels(image);
785 q+=GetPixelChannels(reconstruct_image);
788 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
800 channel=GetPixelChannelChannel(image,i);
801 traits=GetPixelChannelTraits(image,channel);
802 reconstruct_traits=GetPixelChannelTraits(reconstruct_image,channel);
803 if ((traits == UndefinedPixelTrait) ||
804 (reconstruct_traits == UndefinedPixelTrait) ||
805 ((reconstruct_traits & UpdatePixelTrait) == 0))
807 distance=QuantumScale*(p[i]-(double) GetPixelChannel(
808 reconstruct_image,channel,q));
810 channel_distortion[i]+=distance;
811 channel_distortion[CompositePixelChannel]+=distance;
813 p+=GetPixelChannels(image);
814 q+=GetPixelChannels(reconstruct_image);
816 #if defined(MAGICKCORE_OPENMP_SUPPORT)
817 #pragma omp critical (MagickCore_GetMeanSquaredError)
819 for (i=0; i <= MaxPixelChannels; i++)
820 distortion[i]+=channel_distortion[i];
822 reconstruct_view=DestroyCacheView(reconstruct_view);
823 image_view=DestroyCacheView(image_view);
824 for (i=0; i <= MaxPixelChannels; i++)
825 distortion[i]/=((double) image->columns*image->rows);
826 distortion[CompositePixelChannel]/=GetImageChannels(image);
830 static MagickBooleanType GetNormalizedCrossCorrelationDistortion(
831 const Image *image,const Image *reconstruct_image,double *distortion,
832 ExceptionInfo *exception)
834 #define SimilarityImageTag "Similarity/Image"
842 *reconstruct_statistics;
860 Normalize to account for variation due to lighting and exposure condition.
862 image_statistics=GetImageStatistics(image,exception);
863 reconstruct_statistics=GetImageStatistics(reconstruct_image,exception);
866 for (i=0; i <= MaxPixelChannels; i++)
868 area=1.0/((double) image->columns*image->rows-1);
869 image_view=AcquireVirtualCacheView(image,exception);
870 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
871 for (y=0; y < (ssize_t) image->rows; y++)
873 register const Quantum
880 if (status == MagickFalse)
882 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
883 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
885 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
890 for (x=0; x < (ssize_t) image->columns; x++)
895 if (GetPixelMask(image,p) != 0)
897 p+=GetPixelChannels(image);
898 q+=GetPixelChannels(reconstruct_image);
901 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
910 channel=GetPixelChannelChannel(image,i);
911 traits=GetPixelChannelTraits(image,channel);
912 reconstruct_traits=GetPixelChannelTraits(reconstruct_image,channel);
913 if ((traits == UndefinedPixelTrait) ||
914 (reconstruct_traits == UndefinedPixelTrait) ||
915 ((reconstruct_traits & UpdatePixelTrait) == 0))
917 distortion[i]+=area*QuantumScale*(p[i]-image_statistics[i].mean)*
918 (GetPixelChannel(reconstruct_image,channel,q)-
919 reconstruct_statistics[channel].mean);
921 p+=GetPixelChannels(image);
922 q+=GetPixelChannels(reconstruct_image);
924 if (image->progress_monitor != (MagickProgressMonitor) NULL)
929 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
931 if (proceed == MagickFalse)
935 reconstruct_view=DestroyCacheView(reconstruct_view);
936 image_view=DestroyCacheView(image_view);
938 Divide by the standard deviation.
940 distortion[CompositePixelChannel]=0.0;
941 for (i=0; i < MaxPixelChannels; i++)
949 channel=GetPixelChannelChannel(image,i);
950 gamma=image_statistics[i].standard_deviation*
951 reconstruct_statistics[channel].standard_deviation;
952 gamma=PerceptibleReciprocal(gamma);
953 distortion[i]=QuantumRange*gamma*distortion[i];
954 distortion[CompositePixelChannel]+=distortion[i]*distortion[i];
956 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]/
957 GetImageChannels(image));
961 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
962 reconstruct_statistics);
963 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
968 static MagickBooleanType GetPeakAbsoluteDistortion(const Image *image,
969 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
982 image_view=AcquireVirtualCacheView(image,exception);
983 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
984 #if defined(MAGICKCORE_OPENMP_SUPPORT)
985 #pragma omp parallel for schedule(static,4) shared(status) \
986 dynamic_number_threads(image,image->columns,image->rows,1)
988 for (y=0; y < (ssize_t) image->rows; y++)
991 channel_distortion[MaxPixelChannels+1];
993 register const Quantum
1001 if (status == MagickFalse)
1003 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1004 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1006 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
1011 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
1012 for (x=0; x < (ssize_t) image->columns; x++)
1017 if (GetPixelMask(image,p) != 0)
1019 p+=GetPixelChannels(image);
1020 q+=GetPixelChannels(reconstruct_image);
1023 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1035 channel=GetPixelChannelChannel(image,i);
1036 traits=GetPixelChannelTraits(image,channel);
1037 reconstruct_traits=GetPixelChannelTraits(reconstruct_image,channel);
1038 if ((traits == UndefinedPixelTrait) ||
1039 (reconstruct_traits == UndefinedPixelTrait) ||
1040 ((reconstruct_traits & UpdatePixelTrait) == 0))
1042 distance=QuantumScale*fabs(p[i]-(double) GetPixelChannel(
1043 reconstruct_image,channel,q));
1044 if (distance > channel_distortion[i])
1045 channel_distortion[i]=distance;
1046 if (distance > channel_distortion[CompositePixelChannel])
1047 channel_distortion[CompositePixelChannel]=distance;
1049 p+=GetPixelChannels(image);
1050 q+=GetPixelChannels(reconstruct_image);
1052 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1053 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
1055 for (i=0; i <= MaxPixelChannels; i++)
1056 if (channel_distortion[i] > distortion[i])
1057 distortion[i]=channel_distortion[i];
1059 reconstruct_view=DestroyCacheView(reconstruct_view);
1060 image_view=DestroyCacheView(image_view);
1064 static MagickBooleanType GetPeakSignalToNoiseRatio(const Image *image,
1065 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1073 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
1074 for (i=0; i <= MaxPixelChannels; i++)
1075 distortion[i]=20.0*log10((double) 1.0/sqrt(distortion[i]));
1079 static MagickBooleanType GetRootMeanSquaredDistortion(const Image *image,
1080 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1088 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
1089 for (i=0; i <= MaxPixelChannels; i++)
1090 distortion[i]=sqrt(distortion[i]);
1094 MagickExport MagickBooleanType GetImageDistortion(Image *image,
1095 const Image *reconstruct_image,const MetricType metric,double *distortion,
1096 ExceptionInfo *exception)
1099 *channel_distortion;
1107 assert(image != (Image *) NULL);
1108 assert(image->signature == MagickSignature);
1109 if (image->debug != MagickFalse)
1110 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1111 assert(reconstruct_image != (const Image *) NULL);
1112 assert(reconstruct_image->signature == MagickSignature);
1113 assert(distortion != (double *) NULL);
1115 if (image->debug != MagickFalse)
1116 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1117 if ((reconstruct_image->columns != image->columns) ||
1118 (reconstruct_image->rows != image->rows))
1119 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1121 Get image distortion.
1123 length=MaxPixelChannels+1;
1124 channel_distortion=(double *) AcquireQuantumMemory(length,
1125 sizeof(*channel_distortion));
1126 if (channel_distortion == (double *) NULL)
1127 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1128 (void) ResetMagickMemory(channel_distortion,0,length*
1129 sizeof(*channel_distortion));
1132 case AbsoluteErrorMetric:
1134 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1138 case FuzzErrorMetric:
1140 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1144 case MeanAbsoluteErrorMetric:
1146 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1147 channel_distortion,exception);
1150 case MeanErrorPerPixelMetric:
1152 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1156 case MeanSquaredErrorMetric:
1158 status=GetMeanSquaredDistortion(image,reconstruct_image,
1159 channel_distortion,exception);
1162 case NormalizedCrossCorrelationErrorMetric:
1165 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1166 channel_distortion,exception);
1169 case PeakAbsoluteErrorMetric:
1171 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1172 channel_distortion,exception);
1175 case PeakSignalToNoiseRatioMetric:
1177 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1178 channel_distortion,exception);
1181 case RootMeanSquaredErrorMetric:
1183 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1184 channel_distortion,exception);
1188 *distortion=channel_distortion[CompositePixelChannel];
1189 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1194 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1198 % G e t I m a g e D i s t o r t i o n s %
1202 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1204 % GetImageDistrortion() compares the pixel channels of an image to a
1205 % reconstructed image and returns the specified distortion metric for each
1208 % The format of the CompareImages method is:
1210 % double *GetImageDistortions(const Image *image,
1211 % const Image *reconstruct_image,const MetricType metric,
1212 % ExceptionInfo *exception)
1214 % A description of each parameter follows:
1216 % o image: the image.
1218 % o reconstruct_image: the reconstruct image.
1220 % o metric: the metric.
1222 % o exception: return any errors or warnings in this structure.
1225 MagickExport double *GetImageDistortions(Image *image,
1226 const Image *reconstruct_image,const MetricType metric,
1227 ExceptionInfo *exception)
1230 *channel_distortion;
1238 assert(image != (Image *) NULL);
1239 assert(image->signature == MagickSignature);
1240 if (image->debug != MagickFalse)
1241 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1242 assert(reconstruct_image != (const Image *) NULL);
1243 assert(reconstruct_image->signature == MagickSignature);
1244 if (image->debug != MagickFalse)
1245 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1246 if ((reconstruct_image->columns != image->columns) ||
1247 (reconstruct_image->rows != image->rows))
1249 (void) ThrowMagickException(exception,GetMagickModule(),ImageError,
1250 "ImageSizeDiffers","'%s'",image->filename);
1251 return((double *) NULL);
1254 Get image distortion.
1256 length=MaxPixelChannels+1UL;
1257 channel_distortion=(double *) AcquireQuantumMemory(length,
1258 sizeof(*channel_distortion));
1259 if (channel_distortion == (double *) NULL)
1260 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1261 (void) ResetMagickMemory(channel_distortion,0,length*
1262 sizeof(*channel_distortion));
1266 case AbsoluteErrorMetric:
1268 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1272 case FuzzErrorMetric:
1274 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1278 case MeanAbsoluteErrorMetric:
1280 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1281 channel_distortion,exception);
1284 case MeanErrorPerPixelMetric:
1286 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1290 case MeanSquaredErrorMetric:
1292 status=GetMeanSquaredDistortion(image,reconstruct_image,
1293 channel_distortion,exception);
1296 case NormalizedCrossCorrelationErrorMetric:
1299 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1300 channel_distortion,exception);
1303 case PeakAbsoluteErrorMetric:
1305 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1306 channel_distortion,exception);
1309 case PeakSignalToNoiseRatioMetric:
1311 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1312 channel_distortion,exception);
1315 case RootMeanSquaredErrorMetric:
1317 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1318 channel_distortion,exception);
1322 if (status == MagickFalse)
1324 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1325 return((double *) NULL);
1327 return(channel_distortion);
1331 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1335 % I s I m a g e s E q u a l %
1339 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1341 % IsImagesEqual() measures the difference between colors at each pixel
1342 % location of two images. A value other than 0 means the colors match
1343 % exactly. Otherwise an error measure is computed by summing over all
1344 % pixels in an image the distance squared in RGB space between each image
1345 % pixel and its corresponding pixel in the reconstruct image. The error
1346 % measure is assigned to these image members:
1348 % o mean_error_per_pixel: The mean error for any single pixel in
1351 % o normalized_mean_error: The normalized mean quantization error for
1352 % any single pixel in the image. This distance measure is normalized to
1353 % a range between 0 and 1. It is independent of the range of red, green,
1354 % and blue values in the image.
1356 % o normalized_maximum_error: The normalized maximum quantization
1357 % error for any single pixel in the image. This distance measure is
1358 % normalized to a range between 0 and 1. It is independent of the range
1359 % of red, green, and blue values in your image.
1361 % A small normalized mean square error, accessed as
1362 % image->normalized_mean_error, suggests the images are very similar in
1363 % spatial layout and color.
1365 % The format of the IsImagesEqual method is:
1367 % MagickBooleanType IsImagesEqual(Image *image,
1368 % const Image *reconstruct_image,ExceptionInfo *exception)
1370 % A description of each parameter follows.
1372 % o image: the image.
1374 % o reconstruct_image: the reconstruct image.
1376 % o exception: return any errors or warnings in this structure.
1379 MagickExport MagickBooleanType IsImagesEqual(Image *image,
1380 const Image *reconstruct_image,ExceptionInfo *exception)
1393 mean_error_per_pixel;
1398 assert(image != (Image *) NULL);
1399 assert(image->signature == MagickSignature);
1400 assert(reconstruct_image != (const Image *) NULL);
1401 assert(reconstruct_image->signature == MagickSignature);
1402 if ((reconstruct_image->columns != image->columns) ||
1403 (reconstruct_image->rows != image->rows))
1404 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1407 mean_error_per_pixel=0.0;
1409 image_view=AcquireVirtualCacheView(image,exception);
1410 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1411 for (y=0; y < (ssize_t) image->rows; y++)
1413 register const Quantum
1420 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1421 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1423 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1425 for (x=0; x < (ssize_t) image->columns; x++)
1430 if (GetPixelMask(image,p) != 0)
1432 p+=GetPixelChannels(image);
1433 q+=GetPixelChannels(reconstruct_image);
1436 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1448 channel=GetPixelChannelChannel(image,i);
1449 traits=GetPixelChannelTraits(image,channel);
1450 reconstruct_traits=GetPixelChannelTraits(reconstruct_image,channel);
1451 if ((traits == UndefinedPixelTrait) ||
1452 (reconstruct_traits == UndefinedPixelTrait) ||
1453 ((reconstruct_traits & UpdatePixelTrait) == 0))
1455 distance=fabs(p[i]-(double) GetPixelChannel(reconstruct_image,
1457 mean_error_per_pixel+=distance;
1458 mean_error+=distance*distance;
1459 if (distance > maximum_error)
1460 maximum_error=distance;
1463 p+=GetPixelChannels(image);
1464 q+=GetPixelChannels(reconstruct_image);
1467 reconstruct_view=DestroyCacheView(reconstruct_view);
1468 image_view=DestroyCacheView(image_view);
1469 image->error.mean_error_per_pixel=(double) (mean_error_per_pixel/area);
1470 image->error.normalized_mean_error=(double) (QuantumScale*QuantumScale*
1472 image->error.normalized_maximum_error=(double) (QuantumScale*maximum_error);
1473 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
1478 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1482 % S i m i l a r i t y I m a g e %
1486 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1488 % SimilarityImage() compares the reference image of the image and returns the
1489 % best match offset. In addition, it returns a similarity image such that an
1490 % exact match location is completely white and if none of the pixels match,
1491 % black, otherwise some gray level in-between.
1493 % The format of the SimilarityImageImage method is:
1495 % Image *SimilarityImage(const Image *image,const Image *reference,
1496 % const MetricType metric,RectangleInfo *offset,double *similarity,
1497 % ExceptionInfo *exception)
1499 % A description of each parameter follows:
1501 % o image: the image.
1503 % o reference: find an area of the image that closely resembles this image.
1505 % o metric: the metric.
1507 % o the best match offset of the reference image within the image.
1509 % o similarity: the computed similarity between the images.
1511 % o exception: return any errors or warnings in this structure.
1515 static double GetSimilarityMetric(const Image *image,const Image *reference,
1516 const MetricType metric,const ssize_t x_offset,const ssize_t y_offset,
1517 ExceptionInfo *exception)
1531 SetGeometry(reference,&geometry);
1532 geometry.x=x_offset;
1533 geometry.y=y_offset;
1534 similarity_image=CropImage(image,&geometry,exception);
1535 if (similarity_image == (Image *) NULL)
1538 status=GetImageDistortion(similarity_image,reference,metric,&distortion,
1540 similarity_image=DestroyImage(similarity_image);
1541 if (status == MagickFalse)
1546 MagickExport Image *SimilarityImage(Image *image,const Image *reference,
1547 const MetricType metric,RectangleInfo *offset,double *similarity_metric,
1548 ExceptionInfo *exception)
1550 #define SimilarityImageTag "Similarity/Image"
1567 assert(image != (const Image *) NULL);
1568 assert(image->signature == MagickSignature);
1569 if (image->debug != MagickFalse)
1570 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1571 assert(exception != (ExceptionInfo *) NULL);
1572 assert(exception->signature == MagickSignature);
1573 assert(offset != (RectangleInfo *) NULL);
1574 SetGeometry(reference,offset);
1575 *similarity_metric=1.0;
1576 if ((reference->columns > image->columns) || (reference->rows > image->rows))
1577 ThrowImageException(ImageError,"ImageSizeDiffers");
1578 similarity_image=CloneImage(image,image->columns-reference->columns+1,
1579 image->rows-reference->rows+1,MagickTrue,exception);
1580 if (similarity_image == (Image *) NULL)
1581 return((Image *) NULL);
1582 status=SetImageStorageClass(similarity_image,DirectClass,exception);
1583 if (status == MagickFalse)
1585 similarity_image=DestroyImage(similarity_image);
1586 return((Image *) NULL);
1589 Measure similarity of reference image against image.
1593 similarity_view=AcquireAuthenticCacheView(similarity_image,exception);
1594 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1595 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1596 dynamic_number_threads(image,image->columns,image->rows,1)
1598 for (y=0; y < (ssize_t) (image->rows-reference->rows+1); y++)
1609 if (status == MagickFalse)
1611 q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
1613 if (q == (Quantum *) NULL)
1618 for (x=0; x < (ssize_t) (image->columns-reference->columns+1); x++)
1623 similarity=GetSimilarityMetric(image,reference,metric,x,y,exception);
1624 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1625 #pragma omp critical (MagickCore_SimilarityImage)
1627 if (similarity < *similarity_metric)
1629 *similarity_metric=similarity;
1633 if (GetPixelMask(similarity_image,q) != 0)
1635 q+=GetPixelChannels(similarity_image);
1638 for (i=0; i < (ssize_t) GetPixelChannels(similarity_image); i++)
1647 channel=GetPixelChannelChannel(image,i);
1648 traits=GetPixelChannelTraits(image,channel);
1649 similarity_traits=GetPixelChannelTraits(similarity_image,channel);
1650 if ((traits == UndefinedPixelTrait) ||
1651 (similarity_traits == UndefinedPixelTrait) ||
1652 ((similarity_traits & UpdatePixelTrait) == 0))
1654 SetPixelChannel(similarity_image,channel,ClampToQuantum(QuantumRange-
1655 QuantumRange*similarity),q);
1657 q+=GetPixelChannels(similarity_image);
1659 if (IfMagickFalse( SyncCacheViewAuthenticPixels(similarity_view,exception) ))
1661 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1663 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1664 #pragma omp critical (MagickCore_SimilarityImage)
1666 if (IfMagickFalse(SetImageProgress(image,SimilarityImageTag,
1667 progress++,image->rows) ))
1671 similarity_view=DestroyCacheView(similarity_view);
1672 return(similarity_image);