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) \
181 if ((image->rows*image->columns) > 8192) \
182 num_threads(GetMagickResourceLimit(ThreadResource))
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=GetPixelChannelMapChannel(image,i);
242 traits=GetPixelChannelMapTraits(image,channel);
243 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
244 if ((traits == UndefinedPixelTrait) ||
245 (reconstruct_traits == UndefinedPixelTrait) ||
246 ((reconstruct_traits & UpdatePixelTrait) == 0))
248 distance=p[i]-(MagickRealType) GetPixelChannel(reconstruct_image,
250 if (fabs((double) distance) >= MagickEpsilon)
251 difference=MagickTrue;
253 if (difference == MagickFalse)
254 SetPixelInfoPixel(highlight_image,&lowlight,r);
256 SetPixelInfoPixel(highlight_image,&highlight,r);
257 p+=GetPixelChannels(image);
258 q+=GetPixelChannels(reconstruct_image);
259 r+=GetPixelChannels(highlight_image);
261 sync=SyncCacheViewAuthenticPixels(highlight_view,exception);
262 if (sync == MagickFalse)
265 highlight_view=DestroyCacheView(highlight_view);
266 reconstruct_view=DestroyCacheView(reconstruct_view);
267 image_view=DestroyCacheView(image_view);
268 (void) CompositeImage(difference_image,highlight_image,image->compose,
269 MagickTrue,0,0,exception);
270 highlight_image=DestroyImage(highlight_image);
271 if (status == MagickFalse)
272 difference_image=DestroyImage(difference_image);
273 return(difference_image);
277 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
281 % G e t I m a g e D i s t o r t i o n %
285 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
287 % GetImageDistortion() compares one or more pixel channels of an image to a
288 % reconstructed image and returns the specified distortion metric.
290 % The format of the CompareImages method is:
292 % MagickBooleanType GetImageDistortion(const Image *image,
293 % const Image *reconstruct_image,const MetricType metric,
294 % double *distortion,ExceptionInfo *exception)
296 % A description of each parameter follows:
298 % o image: the image.
300 % o reconstruct_image: the reconstruct image.
302 % o metric: the metric.
304 % o distortion: the computed distortion between the images.
306 % o exception: return any errors or warnings in this structure.
310 static MagickBooleanType GetAbsoluteDistortion(const Image *image,
311 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
324 Compute the absolute difference in pixels between two images.
327 image_view=AcquireVirtualCacheView(image,exception);
328 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
329 #if defined(MAGICKCORE_OPENMP_SUPPORT)
330 #pragma omp parallel for schedule(static,4) shared(status) \
331 if ((image->rows*image->columns) > 8192) \
332 num_threads(GetMagickResourceLimit(ThreadResource))
334 for (y=0; y < (ssize_t) image->rows; y++)
337 channel_distortion[MaxPixelChannels+1];
339 register const Quantum
347 if (status == MagickFalse)
349 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
350 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
352 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
357 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
358 for (x=0; x < (ssize_t) image->columns; x++)
366 if (GetPixelMask(image,p) != 0)
368 p+=GetPixelChannels(image);
369 q+=GetPixelChannels(reconstruct_image);
372 difference=MagickFalse;
373 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
382 channel=GetPixelChannelMapChannel(image,i);
383 traits=GetPixelChannelMapTraits(image,channel);
384 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
385 if ((traits == UndefinedPixelTrait) ||
386 (reconstruct_traits == UndefinedPixelTrait) ||
387 ((reconstruct_traits & UpdatePixelTrait) == 0))
389 if (p[i] != GetPixelChannel(reconstruct_image,channel,q))
390 difference=MagickTrue;
392 if (difference != MagickFalse)
394 channel_distortion[i]++;
395 channel_distortion[CompositePixelChannel]++;
397 p+=GetPixelChannels(image);
398 q+=GetPixelChannels(reconstruct_image);
400 #if defined(MAGICKCORE_OPENMP_SUPPORT)
401 #pragma omp critical (MagickCore_GetAbsoluteError)
403 for (i=0; i <= MaxPixelChannels; i++)
404 distortion[i]+=channel_distortion[i];
406 reconstruct_view=DestroyCacheView(reconstruct_view);
407 image_view=DestroyCacheView(image_view);
411 static size_t GetImageChannels(const Image *image)
420 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
428 channel=GetPixelChannelMapChannel(image,i);
429 traits=GetPixelChannelMapTraits(image,channel);
430 if ((traits & UpdatePixelTrait) != 0)
436 static MagickBooleanType GetFuzzDistortion(const Image *image,
437 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
453 image_view=AcquireVirtualCacheView(image,exception);
454 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
455 #if defined(MAGICKCORE_OPENMP_SUPPORT)
456 #pragma omp parallel for schedule(static,4) shared(status) \
457 if ((image->rows*image->columns) > 8192) \
458 num_threads(GetMagickResourceLimit(ThreadResource))
460 for (y=0; y < (ssize_t) image->rows; y++)
463 channel_distortion[MaxPixelChannels+1];
465 register const Quantum
473 if (status == MagickFalse)
475 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
476 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
478 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
483 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
484 for (x=0; x < (ssize_t) image->columns; x++)
489 if (GetPixelMask(image,p) != 0)
491 p+=GetPixelChannels(image);
492 q+=GetPixelChannels(reconstruct_image);
495 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
507 channel=GetPixelChannelMapChannel(image,i);
508 traits=GetPixelChannelMapTraits(image,channel);
509 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
510 if ((traits == UndefinedPixelTrait) ||
511 (reconstruct_traits == UndefinedPixelTrait) ||
512 ((reconstruct_traits & UpdatePixelTrait) == 0))
514 distance=QuantumScale*(p[i]-(MagickRealType) GetPixelChannel(
515 reconstruct_image,channel,q));
517 channel_distortion[i]+=distance;
518 channel_distortion[CompositePixelChannel]+=distance;
520 p+=GetPixelChannels(image);
521 q+=GetPixelChannels(reconstruct_image);
523 #if defined(MAGICKCORE_OPENMP_SUPPORT)
524 #pragma omp critical (MagickCore_GetFuzzDistortion)
526 for (i=0; i <= MaxPixelChannels; i++)
527 distortion[i]+=channel_distortion[i];
529 reconstruct_view=DestroyCacheView(reconstruct_view);
530 image_view=DestroyCacheView(image_view);
531 for (i=0; i <= MaxPixelChannels; i++)
532 distortion[i]/=((double) image->columns*image->rows);
533 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
534 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]);
538 static MagickBooleanType GetMeanAbsoluteDistortion(const Image *image,
539 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
555 image_view=AcquireVirtualCacheView(image,exception);
556 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
557 #if defined(MAGICKCORE_OPENMP_SUPPORT)
558 #pragma omp parallel for schedule(static,4) shared(status) \
559 if ((image->rows*image->columns) > 8192) \
560 num_threads(GetMagickResourceLimit(ThreadResource))
562 for (y=0; y < (ssize_t) image->rows; y++)
565 channel_distortion[MaxPixelChannels+1];
567 register const Quantum
575 if (status == MagickFalse)
577 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
578 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
580 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
585 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
586 for (x=0; x < (ssize_t) image->columns; x++)
591 if (GetPixelMask(image,p) != 0)
593 p+=GetPixelChannels(image);
594 q+=GetPixelChannels(reconstruct_image);
597 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
609 channel=GetPixelChannelMapChannel(image,i);
610 traits=GetPixelChannelMapTraits(image,channel);
611 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
612 if ((traits == UndefinedPixelTrait) ||
613 (reconstruct_traits == UndefinedPixelTrait) ||
614 ((reconstruct_traits & UpdatePixelTrait) == 0))
616 distance=QuantumScale*fabs(p[i]-(MagickRealType) GetPixelChannel(
617 reconstruct_image,channel,q));
618 channel_distortion[i]+=distance;
619 channel_distortion[CompositePixelChannel]+=distance;
621 p+=GetPixelChannels(image);
622 q+=GetPixelChannels(reconstruct_image);
624 #if defined(MAGICKCORE_OPENMP_SUPPORT)
625 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
627 for (i=0; i <= MaxPixelChannels; i++)
628 distortion[i]+=channel_distortion[i];
630 reconstruct_view=DestroyCacheView(reconstruct_view);
631 image_view=DestroyCacheView(image_view);
632 for (i=0; i <= MaxPixelChannels; i++)
633 distortion[i]/=((double) image->columns*image->rows);
634 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
638 static MagickBooleanType GetMeanErrorPerPixel(Image *image,
639 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
664 image_view=AcquireVirtualCacheView(image,exception);
665 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
666 for (y=0; y < (ssize_t) image->rows; y++)
668 register const Quantum
675 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
676 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
678 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
683 for (x=0; x < (ssize_t) image->columns; x++)
688 if (GetPixelMask(image,p) != 0)
690 p+=GetPixelChannels(image);
691 q+=GetPixelChannels(reconstruct_image);
694 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
706 channel=GetPixelChannelMapChannel(image,i);
707 traits=GetPixelChannelMapTraits(image,channel);
708 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
709 if ((traits == UndefinedPixelTrait) ||
710 (reconstruct_traits == UndefinedPixelTrait) ||
711 ((reconstruct_traits & UpdatePixelTrait) == 0))
713 distance=fabs((double) (alpha*p[i]-beta*GetPixelChannel(
714 reconstruct_image,channel,q)));
715 distortion[i]+=distance;
716 distortion[CompositePixelChannel]+=distance;
717 mean_error+=distance*distance;
718 if (distance > maximum_error)
719 maximum_error=distance;
722 p+=GetPixelChannels(image);
723 q+=GetPixelChannels(reconstruct_image);
726 reconstruct_view=DestroyCacheView(reconstruct_view);
727 image_view=DestroyCacheView(image_view);
728 image->error.mean_error_per_pixel=distortion[CompositePixelChannel]/area;
729 image->error.normalized_mean_error=QuantumScale*QuantumScale*mean_error/area;
730 image->error.normalized_maximum_error=QuantumScale*maximum_error;
734 static MagickBooleanType GetMeanSquaredDistortion(const Image *image,
735 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
751 image_view=AcquireVirtualCacheView(image,exception);
752 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
753 #if defined(MAGICKCORE_OPENMP_SUPPORT)
754 #pragma omp parallel for schedule(static,4) shared(status) \
755 if ((image->rows*image->columns) > 8192) \
756 num_threads(GetMagickResourceLimit(ThreadResource))
758 for (y=0; y < (ssize_t) image->rows; y++)
761 channel_distortion[MaxPixelChannels+1];
763 register const Quantum
771 if (status == MagickFalse)
773 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
774 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
776 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
781 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
782 for (x=0; x < (ssize_t) image->columns; x++)
787 if (GetPixelMask(image,p) != 0)
789 p+=GetPixelChannels(image);
790 q+=GetPixelChannels(reconstruct_image);
793 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
805 channel=GetPixelChannelMapChannel(image,i);
806 traits=GetPixelChannelMapTraits(image,channel);
807 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
808 if ((traits == UndefinedPixelTrait) ||
809 (reconstruct_traits == UndefinedPixelTrait) ||
810 ((reconstruct_traits & UpdatePixelTrait) == 0))
812 distance=QuantumScale*(p[i]-(MagickRealType) GetPixelChannel(
813 reconstruct_image,channel,q));
815 channel_distortion[i]+=distance;
816 channel_distortion[CompositePixelChannel]+=distance;
818 p+=GetPixelChannels(image);
819 q+=GetPixelChannels(reconstruct_image);
821 #if defined(MAGICKCORE_OPENMP_SUPPORT)
822 #pragma omp critical (MagickCore_GetMeanSquaredError)
824 for (i=0; i <= MaxPixelChannels; i++)
825 distortion[i]+=channel_distortion[i];
827 reconstruct_view=DestroyCacheView(reconstruct_view);
828 image_view=DestroyCacheView(image_view);
829 for (i=0; i <= MaxPixelChannels; i++)
830 distortion[i]/=((double) image->columns*image->rows);
831 distortion[CompositePixelChannel]/=GetImageChannels(image);
835 static MagickBooleanType GetNormalizedCrossCorrelationDistortion(
836 const Image *image,const Image *reconstruct_image,double *distortion,
837 ExceptionInfo *exception)
839 #define SimilarityImageTag "Similarity/Image"
847 *reconstruct_statistics;
865 Normalize to account for variation due to lighting and exposure condition.
867 image_statistics=GetImageStatistics(image,exception);
868 reconstruct_statistics=GetImageStatistics(reconstruct_image,exception);
871 for (i=0; i <= MaxPixelChannels; i++)
873 area=1.0/((MagickRealType) image->columns*image->rows-1);
874 image_view=AcquireVirtualCacheView(image,exception);
875 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
876 for (y=0; y < (ssize_t) image->rows; y++)
878 register const Quantum
885 if (status == MagickFalse)
887 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
888 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
890 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
895 for (x=0; x < (ssize_t) image->columns; x++)
900 if (GetPixelMask(image,p) != 0)
902 p+=GetPixelChannels(image);
903 q+=GetPixelChannels(reconstruct_image);
906 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
915 channel=GetPixelChannelMapChannel(image,i);
916 traits=GetPixelChannelMapTraits(image,channel);
917 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
918 if ((traits == UndefinedPixelTrait) ||
919 (reconstruct_traits == UndefinedPixelTrait) ||
920 ((reconstruct_traits & UpdatePixelTrait) == 0))
922 distortion[i]+=area*QuantumScale*(p[i]-image_statistics[i].mean)*
923 (GetPixelChannel(reconstruct_image,channel,q)-
924 reconstruct_statistics[channel].mean);
926 p+=GetPixelChannels(image);
927 q+=GetPixelChannels(reconstruct_image);
929 if (image->progress_monitor != (MagickProgressMonitor) NULL)
934 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
936 if (proceed == MagickFalse)
940 reconstruct_view=DestroyCacheView(reconstruct_view);
941 image_view=DestroyCacheView(image_view);
943 Divide by the standard deviation.
945 distortion[CompositePixelChannel]=0.0;
946 for (i=0; i < MaxPixelChannels; i++)
954 channel=GetPixelChannelMapChannel(image,i);
955 gamma=image_statistics[i].standard_deviation*
956 reconstruct_statistics[channel].standard_deviation;
957 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
958 distortion[i]=QuantumRange*gamma*distortion[i];
959 distortion[CompositePixelChannel]+=distortion[i]*distortion[i];
961 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]/
962 GetImageChannels(image));
966 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
967 reconstruct_statistics);
968 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
973 static MagickBooleanType GetPeakAbsoluteDistortion(const Image *image,
974 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
987 image_view=AcquireVirtualCacheView(image,exception);
988 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
989 #if defined(MAGICKCORE_OPENMP_SUPPORT)
990 #pragma omp parallel for schedule(static,4) shared(status) \
991 if ((image->rows*image->columns) > 8192) \
992 num_threads(GetMagickResourceLimit(ThreadResource))
994 for (y=0; y < (ssize_t) image->rows; y++)
997 channel_distortion[MaxPixelChannels+1];
999 register const Quantum
1007 if (status == MagickFalse)
1009 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1010 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1012 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
1017 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
1018 for (x=0; x < (ssize_t) image->columns; x++)
1023 if (GetPixelMask(image,p) != 0)
1025 p+=GetPixelChannels(image);
1026 q+=GetPixelChannels(reconstruct_image);
1029 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1041 channel=GetPixelChannelMapChannel(image,i);
1042 traits=GetPixelChannelMapTraits(image,channel);
1043 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
1044 if ((traits == UndefinedPixelTrait) ||
1045 (reconstruct_traits == UndefinedPixelTrait) ||
1046 ((reconstruct_traits & UpdatePixelTrait) == 0))
1048 distance=QuantumScale*fabs(p[i]-(MagickRealType) GetPixelChannel(
1049 reconstruct_image,channel,q));
1050 if (distance > channel_distortion[i])
1051 channel_distortion[i]=distance;
1052 if (distance > channel_distortion[CompositePixelChannel])
1053 channel_distortion[CompositePixelChannel]=distance;
1055 p+=GetPixelChannels(image);
1056 q+=GetPixelChannels(reconstruct_image);
1058 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1059 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
1061 for (i=0; i <= MaxPixelChannels; i++)
1062 if (channel_distortion[i] > distortion[i])
1063 distortion[i]=channel_distortion[i];
1065 reconstruct_view=DestroyCacheView(reconstruct_view);
1066 image_view=DestroyCacheView(image_view);
1070 static MagickBooleanType GetPeakSignalToNoiseRatio(const Image *image,
1071 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1079 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
1080 for (i=0; i <= MaxPixelChannels; i++)
1081 distortion[i]=20.0*log10((double) 1.0/sqrt(distortion[i]));
1085 static MagickBooleanType GetRootMeanSquaredDistortion(const Image *image,
1086 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1094 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
1095 for (i=0; i <= MaxPixelChannels; i++)
1096 distortion[i]=sqrt(distortion[i]);
1100 MagickExport MagickBooleanType GetImageDistortion(Image *image,
1101 const Image *reconstruct_image,const MetricType metric,double *distortion,
1102 ExceptionInfo *exception)
1105 *channel_distortion;
1113 assert(image != (Image *) NULL);
1114 assert(image->signature == MagickSignature);
1115 if (image->debug != MagickFalse)
1116 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1117 assert(reconstruct_image != (const Image *) NULL);
1118 assert(reconstruct_image->signature == MagickSignature);
1119 assert(distortion != (double *) NULL);
1121 if (image->debug != MagickFalse)
1122 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1123 if ((reconstruct_image->columns != image->columns) ||
1124 (reconstruct_image->rows != image->rows))
1125 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1127 Get image distortion.
1129 length=MaxPixelChannels+1;
1130 channel_distortion=(double *) AcquireQuantumMemory(length,
1131 sizeof(*channel_distortion));
1132 if (channel_distortion == (double *) NULL)
1133 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1134 (void) ResetMagickMemory(channel_distortion,0,length*
1135 sizeof(*channel_distortion));
1138 case AbsoluteErrorMetric:
1140 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1144 case FuzzErrorMetric:
1146 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1150 case MeanAbsoluteErrorMetric:
1152 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1153 channel_distortion,exception);
1156 case MeanErrorPerPixelMetric:
1158 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1162 case MeanSquaredErrorMetric:
1164 status=GetMeanSquaredDistortion(image,reconstruct_image,
1165 channel_distortion,exception);
1168 case NormalizedCrossCorrelationErrorMetric:
1171 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1172 channel_distortion,exception);
1175 case PeakAbsoluteErrorMetric:
1177 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1178 channel_distortion,exception);
1181 case PeakSignalToNoiseRatioMetric:
1183 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1184 channel_distortion,exception);
1187 case RootMeanSquaredErrorMetric:
1189 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1190 channel_distortion,exception);
1194 *distortion=channel_distortion[CompositePixelChannel];
1195 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1200 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1204 % G e t I m a g e D i s t o r t i o n s %
1208 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1210 % GetImageDistrortion() compares the pixel channels of an image to a
1211 % reconstructed image and returns the specified distortion metric for each
1214 % The format of the CompareImages method is:
1216 % double *GetImageDistortions(const Image *image,
1217 % const Image *reconstruct_image,const MetricType metric,
1218 % ExceptionInfo *exception)
1220 % A description of each parameter follows:
1222 % o image: the image.
1224 % o reconstruct_image: the reconstruct image.
1226 % o metric: the metric.
1228 % o exception: return any errors or warnings in this structure.
1231 MagickExport double *GetImageDistortions(Image *image,
1232 const Image *reconstruct_image,const MetricType metric,
1233 ExceptionInfo *exception)
1236 *channel_distortion;
1244 assert(image != (Image *) NULL);
1245 assert(image->signature == MagickSignature);
1246 if (image->debug != MagickFalse)
1247 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1248 assert(reconstruct_image != (const Image *) NULL);
1249 assert(reconstruct_image->signature == MagickSignature);
1250 if (image->debug != MagickFalse)
1251 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1252 if ((reconstruct_image->columns != image->columns) ||
1253 (reconstruct_image->rows != image->rows))
1255 (void) ThrowMagickException(exception,GetMagickModule(),ImageError,
1256 "ImageSizeDiffers","'%s'",image->filename);
1257 return((double *) NULL);
1260 Get image distortion.
1262 length=MaxPixelChannels+1UL;
1263 channel_distortion=(double *) AcquireQuantumMemory(length,
1264 sizeof(*channel_distortion));
1265 if (channel_distortion == (double *) NULL)
1266 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1267 (void) ResetMagickMemory(channel_distortion,0,length*
1268 sizeof(*channel_distortion));
1272 case AbsoluteErrorMetric:
1274 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1278 case FuzzErrorMetric:
1280 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1284 case MeanAbsoluteErrorMetric:
1286 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1287 channel_distortion,exception);
1290 case MeanErrorPerPixelMetric:
1292 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1296 case MeanSquaredErrorMetric:
1298 status=GetMeanSquaredDistortion(image,reconstruct_image,
1299 channel_distortion,exception);
1302 case NormalizedCrossCorrelationErrorMetric:
1305 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1306 channel_distortion,exception);
1309 case PeakAbsoluteErrorMetric:
1311 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1312 channel_distortion,exception);
1315 case PeakSignalToNoiseRatioMetric:
1317 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1318 channel_distortion,exception);
1321 case RootMeanSquaredErrorMetric:
1323 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1324 channel_distortion,exception);
1328 if (status == MagickFalse)
1330 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1331 return((double *) NULL);
1333 return(channel_distortion);
1337 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1341 % I s I m a g e s E q u a l %
1345 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1347 % IsImagesEqual() measures the difference between colors at each pixel
1348 % location of two images. A value other than 0 means the colors match
1349 % exactly. Otherwise an error measure is computed by summing over all
1350 % pixels in an image the distance squared in RGB space between each image
1351 % pixel and its corresponding pixel in the reconstruct image. The error
1352 % measure is assigned to these image members:
1354 % o mean_error_per_pixel: The mean error for any single pixel in
1357 % o normalized_mean_error: The normalized mean quantization error for
1358 % any single pixel in the image. This distance measure is normalized to
1359 % a range between 0 and 1. It is independent of the range of red, green,
1360 % and blue values in the image.
1362 % o normalized_maximum_error: The normalized maximum quantization
1363 % error for any single pixel in the image. This distance measure is
1364 % normalized to a range between 0 and 1. It is independent of the range
1365 % of red, green, and blue values in your image.
1367 % A small normalized mean square error, accessed as
1368 % image->normalized_mean_error, suggests the images are very similar in
1369 % spatial layout and color.
1371 % The format of the IsImagesEqual method is:
1373 % MagickBooleanType IsImagesEqual(Image *image,
1374 % const Image *reconstruct_image,ExceptionInfo *exception)
1376 % A description of each parameter follows.
1378 % o image: the image.
1380 % o reconstruct_image: the reconstruct image.
1382 % o exception: return any errors or warnings in this structure.
1385 MagickExport MagickBooleanType IsImagesEqual(Image *image,
1386 const Image *reconstruct_image,ExceptionInfo *exception)
1399 mean_error_per_pixel;
1404 assert(image != (Image *) NULL);
1405 assert(image->signature == MagickSignature);
1406 assert(reconstruct_image != (const Image *) NULL);
1407 assert(reconstruct_image->signature == MagickSignature);
1408 if ((reconstruct_image->columns != image->columns) ||
1409 (reconstruct_image->rows != image->rows))
1410 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1413 mean_error_per_pixel=0.0;
1415 image_view=AcquireVirtualCacheView(image,exception);
1416 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1417 for (y=0; y < (ssize_t) image->rows; y++)
1419 register const Quantum
1426 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1427 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1429 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1431 for (x=0; x < (ssize_t) image->columns; x++)
1436 if (GetPixelMask(image,p) != 0)
1438 p+=GetPixelChannels(image);
1439 q+=GetPixelChannels(reconstruct_image);
1442 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1454 channel=GetPixelChannelMapChannel(image,i);
1455 traits=GetPixelChannelMapTraits(image,channel);
1456 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
1457 if ((traits == UndefinedPixelTrait) ||
1458 (reconstruct_traits == UndefinedPixelTrait) ||
1459 ((reconstruct_traits & UpdatePixelTrait) == 0))
1461 distance=fabs(p[i]-(MagickRealType) GetPixelChannel(reconstruct_image,
1463 mean_error_per_pixel+=distance;
1464 mean_error+=distance*distance;
1465 if (distance > maximum_error)
1466 maximum_error=distance;
1469 p+=GetPixelChannels(image);
1470 q+=GetPixelChannels(reconstruct_image);
1473 reconstruct_view=DestroyCacheView(reconstruct_view);
1474 image_view=DestroyCacheView(image_view);
1475 image->error.mean_error_per_pixel=(double) (mean_error_per_pixel/area);
1476 image->error.normalized_mean_error=(double) (QuantumScale*QuantumScale*
1478 image->error.normalized_maximum_error=(double) (QuantumScale*maximum_error);
1479 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
1484 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1488 % S i m i l a r i t y I m a g e %
1492 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1494 % SimilarityImage() compares the reference image of the image and returns the
1495 % best match offset. In addition, it returns a similarity image such that an
1496 % exact match location is completely white and if none of the pixels match,
1497 % black, otherwise some gray level in-between.
1499 % The format of the SimilarityImageImage method is:
1501 % Image *SimilarityImage(const Image *image,const Image *reference,
1502 % const MetricType metric,RectangleInfo *offset,double *similarity,
1503 % ExceptionInfo *exception)
1505 % A description of each parameter follows:
1507 % o image: the image.
1509 % o reference: find an area of the image that closely resembles this image.
1511 % o metric: the metric.
1513 % o the best match offset of the reference image within the image.
1515 % o similarity: the computed similarity between the images.
1517 % o exception: return any errors or warnings in this structure.
1521 static double GetSimilarityMetric(const Image *image,const Image *reference,
1522 const MetricType metric,const ssize_t x_offset,const ssize_t y_offset,
1523 ExceptionInfo *exception)
1537 SetGeometry(reference,&geometry);
1538 geometry.x=x_offset;
1539 geometry.y=y_offset;
1540 similarity_image=CropImage(image,&geometry,exception);
1541 if (similarity_image == (Image *) NULL)
1544 status=GetImageDistortion(similarity_image,reference,metric,&distortion,
1546 similarity_image=DestroyImage(similarity_image);
1547 if (status == MagickFalse)
1552 MagickExport Image *SimilarityImage(Image *image,const Image *reference,
1553 const MetricType metric,RectangleInfo *offset,double *similarity_metric,
1554 ExceptionInfo *exception)
1556 #define SimilarityImageTag "Similarity/Image"
1573 assert(image != (const Image *) NULL);
1574 assert(image->signature == MagickSignature);
1575 if (image->debug != MagickFalse)
1576 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1577 assert(exception != (ExceptionInfo *) NULL);
1578 assert(exception->signature == MagickSignature);
1579 assert(offset != (RectangleInfo *) NULL);
1580 SetGeometry(reference,offset);
1581 *similarity_metric=1.0;
1582 if ((reference->columns > image->columns) || (reference->rows > image->rows))
1583 ThrowImageException(ImageError,"ImageSizeDiffers");
1584 similarity_image=CloneImage(image,image->columns-reference->columns+1,
1585 image->rows-reference->rows+1,MagickTrue,exception);
1586 if (similarity_image == (Image *) NULL)
1587 return((Image *) NULL);
1588 status=SetImageStorageClass(similarity_image,DirectClass,exception);
1589 if (status == MagickFalse)
1591 similarity_image=DestroyImage(similarity_image);
1592 return((Image *) NULL);
1595 Measure similarity of reference image against image.
1599 similarity_view=AcquireAuthenticCacheView(similarity_image,exception);
1600 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1601 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1602 if ((image->rows*image->columns) > 8192) \
1603 num_threads(GetMagickResourceLimit(ThreadResource))
1605 for (y=0; y < (ssize_t) (image->rows-reference->rows+1); y++)
1616 if (status == MagickFalse)
1618 q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
1620 if (q == (Quantum *) NULL)
1625 for (x=0; x < (ssize_t) (image->columns-reference->columns+1); x++)
1630 similarity=GetSimilarityMetric(image,reference,metric,x,y,exception);
1631 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1632 #pragma omp critical (MagickCore_SimilarityImage)
1634 if (similarity < *similarity_metric)
1636 *similarity_metric=similarity;
1640 if (GetPixelMask(similarity_image,q) != 0)
1642 q+=GetPixelChannels(similarity_image);
1645 for (i=0; i < (ssize_t) GetPixelChannels(similarity_image); i++)
1654 channel=GetPixelChannelMapChannel(image,i);
1655 traits=GetPixelChannelMapTraits(image,channel);
1656 similarity_traits=GetPixelChannelMapTraits(similarity_image,channel);
1657 if ((traits == UndefinedPixelTrait) ||
1658 (similarity_traits == UndefinedPixelTrait) ||
1659 ((similarity_traits & UpdatePixelTrait) == 0))
1661 SetPixelChannel(similarity_image,channel,ClampToQuantum(QuantumRange-
1662 QuantumRange*similarity),q);
1664 q+=GetPixelChannels(similarity_image);
1666 if (IfMagickFalse( SyncCacheViewAuthenticPixels(similarity_view,exception) ))
1668 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1670 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1671 #pragma omp critical (MagickCore_SimilarityImage)
1673 if (IfMagickFalse(SetImageProgress(image,SimilarityImageTag,
1674 progress++,image->rows) ))
1678 similarity_view=DestroyCacheView(similarity_view);
1679 return(similarity_image);