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 traits=GetPixelChannelMapTraits(image,i);
235 channel=GetPixelChannelMapChannel(image,i);
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 traits=GetPixelChannelMapTraits(image,i);
369 channel=GetPixelChannelMapChannel(image,i);
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++)
412 traits=GetPixelChannelMapTraits(image,i);
413 if ((traits & UpdatePixelTrait) != 0)
419 static MagickBooleanType GetFuzzDistortion(const Image *image,
420 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
436 image_view=AcquireCacheView(image);
437 reconstruct_view=AcquireCacheView(reconstruct_image);
438 #if defined(MAGICKCORE_OPENMP_SUPPORT)
439 #pragma omp parallel for schedule(dynamic,4) shared(status)
441 for (y=0; y < (ssize_t) image->rows; y++)
444 channel_distortion[MaxPixelChannels+1];
446 register const Quantum
454 if (status == MagickFalse)
456 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
457 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
459 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
464 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
465 for (x=0; x < (ssize_t) image->columns; x++)
470 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
482 traits=GetPixelChannelMapTraits(image,i);
483 channel=GetPixelChannelMapChannel(image,i);
484 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
485 if ((traits == UndefinedPixelTrait) ||
486 (reconstruct_traits == UndefinedPixelTrait))
488 if ((reconstruct_traits & UpdatePixelTrait) == 0)
490 distance=QuantumScale*(p[i]-(MagickRealType) GetPixelChannel(
491 reconstruct_image,channel,q));
493 channel_distortion[i]+=distance;
494 channel_distortion[CompositePixelChannel]+=distance;
496 p+=GetPixelChannels(image);
497 q+=GetPixelChannels(reconstruct_image);
499 #if defined(MAGICKCORE_OPENMP_SUPPORT)
500 #pragma omp critical (MagickCore_GetMeanSquaredError)
502 for (i=0; i <= MaxPixelChannels; i++)
503 distortion[i]+=channel_distortion[i];
505 reconstruct_view=DestroyCacheView(reconstruct_view);
506 image_view=DestroyCacheView(image_view);
507 for (i=0; i <= MaxPixelChannels; i++)
508 distortion[i]/=((double) image->columns*image->rows);
509 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
510 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]);
514 static MagickBooleanType GetMeanAbsoluteDistortion(const Image *image,
515 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
531 image_view=AcquireCacheView(image);
532 reconstruct_view=AcquireCacheView(reconstruct_image);
533 #if defined(MAGICKCORE_OPENMP_SUPPORT)
534 #pragma omp parallel for schedule(dynamic,4) shared(status)
536 for (y=0; y < (ssize_t) image->rows; y++)
539 channel_distortion[MaxPixelChannels+1];
541 register const Quantum
549 if (status == MagickFalse)
551 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
552 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
554 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
559 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
560 for (x=0; x < (ssize_t) image->columns; x++)
565 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
577 traits=GetPixelChannelMapTraits(image,i);
578 channel=GetPixelChannelMapChannel(image,i);
579 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
580 if ((traits == UndefinedPixelTrait) ||
581 (reconstruct_traits == UndefinedPixelTrait))
583 if ((reconstruct_traits & UpdatePixelTrait) == 0)
585 distance=QuantumScale*fabs(p[i]-(MagickRealType) GetPixelChannel(
586 reconstruct_image,channel,q));
587 channel_distortion[i]+=distance;
588 channel_distortion[CompositePixelChannel]+=distance;
590 p+=GetPixelChannels(image);
591 q+=GetPixelChannels(reconstruct_image);
593 #if defined(MAGICKCORE_OPENMP_SUPPORT)
594 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
596 for (i=0; i <= MaxPixelChannels; i++)
597 distortion[i]+=channel_distortion[i];
599 reconstruct_view=DestroyCacheView(reconstruct_view);
600 image_view=DestroyCacheView(image_view);
601 for (i=0; i <= MaxPixelChannels; i++)
602 distortion[i]/=((double) image->columns*image->rows);
603 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
607 static MagickBooleanType GetMeanErrorPerPixel(Image *image,
608 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
633 image_view=AcquireCacheView(image);
634 reconstruct_view=AcquireCacheView(reconstruct_image);
635 for (y=0; y < (ssize_t) image->rows; y++)
637 register const Quantum
644 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
645 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
647 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
652 for (x=0; x < (ssize_t) image->columns; x++)
657 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
669 traits=GetPixelChannelMapTraits(image,i);
670 channel=GetPixelChannelMapChannel(image,i);
671 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
672 if ((traits == UndefinedPixelTrait) ||
673 (reconstruct_traits == UndefinedPixelTrait))
675 if ((reconstruct_traits & UpdatePixelTrait) == 0)
677 distance=fabs((double) (alpha*p[i]-beta*GetPixelChannel(
678 reconstruct_image,channel,q)));
679 distortion[i]+=distance;
680 distortion[CompositePixelChannel]+=distance;
681 mean_error+=distance*distance;
682 if (distance > maximum_error)
683 maximum_error=distance;
686 p+=GetPixelChannels(image);
687 q+=GetPixelChannels(reconstruct_image);
690 reconstruct_view=DestroyCacheView(reconstruct_view);
691 image_view=DestroyCacheView(image_view);
692 image->error.mean_error_per_pixel=distortion[CompositePixelChannel]/area;
693 image->error.normalized_mean_error=QuantumScale*QuantumScale*mean_error/area;
694 image->error.normalized_maximum_error=QuantumScale*maximum_error;
698 static MagickBooleanType GetMeanSquaredDistortion(const Image *image,
699 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
715 image_view=AcquireCacheView(image);
716 reconstruct_view=AcquireCacheView(reconstruct_image);
717 #if defined(MAGICKCORE_OPENMP_SUPPORT)
718 #pragma omp parallel for schedule(dynamic,4) shared(status)
720 for (y=0; y < (ssize_t) image->rows; y++)
723 channel_distortion[MaxPixelChannels+1];
725 register const Quantum
733 if (status == MagickFalse)
735 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
736 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
738 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
743 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
744 for (x=0; x < (ssize_t) image->columns; x++)
749 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
761 traits=GetPixelChannelMapTraits(image,i);
762 channel=GetPixelChannelMapChannel(image,i);
763 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
764 if ((traits == UndefinedPixelTrait) ||
765 (reconstruct_traits == UndefinedPixelTrait))
767 if ((reconstruct_traits & UpdatePixelTrait) == 0)
769 distance=QuantumScale*(p[i]-(MagickRealType) GetPixelChannel(
770 reconstruct_image,channel,q));
772 channel_distortion[i]+=distance;
773 channel_distortion[CompositePixelChannel]+=distance;
775 p+=GetPixelChannels(image);
776 q+=GetPixelChannels(reconstruct_image);
778 #if defined(MAGICKCORE_OPENMP_SUPPORT)
779 #pragma omp critical (MagickCore_GetMeanSquaredError)
781 for (i=0; i <= MaxPixelChannels; i++)
782 distortion[i]+=channel_distortion[i];
784 reconstruct_view=DestroyCacheView(reconstruct_view);
785 image_view=DestroyCacheView(image_view);
786 for (i=0; i <= MaxPixelChannels; i++)
787 distortion[i]/=((double) image->columns*image->rows);
788 distortion[CompositePixelChannel]/=GetImageChannels(image);
792 static MagickBooleanType GetNormalizedCrossCorrelationDistortion(
793 const Image *image,const Image *reconstruct_image,double *distortion,
794 ExceptionInfo *exception)
796 #define SimilarityImageTag "Similarity/Image"
804 *reconstruct_statistics;
822 Normalize to account for variation due to lighting and exposure condition.
824 image_statistics=GetImageStatistics(image,exception);
825 reconstruct_statistics=GetImageStatistics(reconstruct_image,exception);
828 for (i=0; i <= MaxPixelChannels; i++)
830 area=1.0/((MagickRealType) image->columns*image->rows-1);
831 image_view=AcquireCacheView(image);
832 reconstruct_view=AcquireCacheView(reconstruct_image);
833 for (y=0; y < (ssize_t) image->rows; y++)
835 register const Quantum
842 if (status == MagickFalse)
844 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
845 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
847 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
852 for (x=0; x < (ssize_t) image->columns; x++)
857 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
866 traits=GetPixelChannelMapTraits(image,i);
867 channel=GetPixelChannelMapChannel(image,i);
868 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
869 if ((traits == UndefinedPixelTrait) ||
870 (reconstruct_traits == UndefinedPixelTrait))
872 if ((reconstruct_traits & UpdatePixelTrait) == 0)
874 distortion[i]+=area*QuantumScale*(p[i]-image_statistics[i].mean)*
875 (GetPixelChannel(reconstruct_image,channel,q)-
876 reconstruct_statistics[channel].mean);
878 p+=GetPixelChannels(image);
879 q+=GetPixelChannels(image);
881 if (image->progress_monitor != (MagickProgressMonitor) NULL)
886 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
888 if (proceed == MagickFalse)
892 reconstruct_view=DestroyCacheView(reconstruct_view);
893 image_view=DestroyCacheView(image_view);
895 Divide by the standard deviation.
897 distortion[CompositePixelChannel]=0.0;
898 for (i=0; i < MaxPixelChannels; i++)
906 channel=GetPixelChannelMapChannel(image,i);
907 gamma=image_statistics[i].standard_deviation*
908 reconstruct_statistics[channel].standard_deviation;
909 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
910 distortion[i]=QuantumRange*gamma*distortion[i];
911 distortion[CompositePixelChannel]+=distortion[i]*distortion[i];
913 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]/
914 GetImageChannels(image));
918 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
919 reconstruct_statistics);
920 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
925 static MagickBooleanType GetPeakAbsoluteDistortion(const Image *image,
926 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
939 image_view=AcquireCacheView(image);
940 reconstruct_view=AcquireCacheView(reconstruct_image);
941 #if defined(MAGICKCORE_OPENMP_SUPPORT)
942 #pragma omp parallel for schedule(dynamic,4) shared(status)
944 for (y=0; y < (ssize_t) image->rows; y++)
947 channel_distortion[MaxPixelChannels+1];
949 register const Quantum
957 if (status == MagickFalse)
959 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
960 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,
961 reconstruct_image->columns,1,exception);
962 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
967 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
968 for (x=0; x < (ssize_t) image->columns; x++)
973 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
985 traits=GetPixelChannelMapTraits(image,i);
986 channel=GetPixelChannelMapChannel(image,i);
987 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
988 if ((traits == UndefinedPixelTrait) ||
989 (reconstruct_traits == UndefinedPixelTrait))
991 if ((reconstruct_traits & UpdatePixelTrait) == 0)
993 distance=QuantumScale*fabs(p[i]-(MagickRealType) GetPixelChannel(
994 reconstruct_image,channel,q));
995 if (distance > channel_distortion[i])
996 channel_distortion[i]=distance;
997 if (distance > channel_distortion[CompositePixelChannel])
998 channel_distortion[CompositePixelChannel]=distance;
1000 p+=GetPixelChannels(image);
1001 q+=GetPixelChannels(image);
1003 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1004 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
1006 for (i=0; i <= MaxPixelChannels; i++)
1007 if (channel_distortion[i] > distortion[i])
1008 distortion[i]=channel_distortion[i];
1010 reconstruct_view=DestroyCacheView(reconstruct_view);
1011 image_view=DestroyCacheView(image_view);
1015 static MagickBooleanType GetPeakSignalToNoiseRatio(const Image *image,
1016 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1024 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
1025 for (i=0; i <= MaxPixelChannels; i++)
1026 distortion[i]=20.0*log10((double) 1.0/sqrt(distortion[i]));
1030 static MagickBooleanType GetRootMeanSquaredDistortion(const Image *image,
1031 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1039 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
1040 for (i=0; i <= MaxPixelChannels; i++)
1041 distortion[i]=sqrt(distortion[i]);
1045 MagickExport MagickBooleanType GetImageDistortion(Image *image,
1046 const Image *reconstruct_image,const MetricType metric,double *distortion,
1047 ExceptionInfo *exception)
1050 *channel_distortion;
1058 assert(image != (Image *) NULL);
1059 assert(image->signature == MagickSignature);
1060 if (image->debug != MagickFalse)
1061 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1062 assert(reconstruct_image != (const Image *) NULL);
1063 assert(reconstruct_image->signature == MagickSignature);
1064 assert(distortion != (double *) NULL);
1066 if (image->debug != MagickFalse)
1067 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1068 if ((reconstruct_image->columns != image->columns) ||
1069 (reconstruct_image->rows != image->rows))
1070 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1072 Get image distortion.
1074 length=MaxPixelChannels+1;
1075 channel_distortion=(double *) AcquireQuantumMemory(length,
1076 sizeof(*channel_distortion));
1077 if (channel_distortion == (double *) NULL)
1078 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1079 (void) ResetMagickMemory(channel_distortion,0,length*
1080 sizeof(*channel_distortion));
1083 case AbsoluteErrorMetric:
1085 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1089 case FuzzErrorMetric:
1091 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1095 case MeanAbsoluteErrorMetric:
1097 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1098 channel_distortion,exception);
1101 case MeanErrorPerPixelMetric:
1103 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1107 case MeanSquaredErrorMetric:
1109 status=GetMeanSquaredDistortion(image,reconstruct_image,
1110 channel_distortion,exception);
1113 case NormalizedCrossCorrelationErrorMetric:
1116 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1117 channel_distortion,exception);
1120 case PeakAbsoluteErrorMetric:
1122 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1123 channel_distortion,exception);
1126 case PeakSignalToNoiseRatioMetric:
1128 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1129 channel_distortion,exception);
1132 case RootMeanSquaredErrorMetric:
1134 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1135 channel_distortion,exception);
1139 *distortion=channel_distortion[CompositePixelChannel];
1140 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1145 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1149 % G e t I m a g e D i s t o r t i o n s %
1153 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1155 % GetImageDistrortion() compares the pixel channels of an image to a
1156 % reconstructed image and returns the specified distortion metric for each
1159 % The format of the CompareImages method is:
1161 % double *GetImageDistortions(const Image *image,
1162 % const Image *reconstruct_image,const MetricType metric,
1163 % ExceptionInfo *exception)
1165 % A description of each parameter follows:
1167 % o image: the image.
1169 % o reconstruct_image: the reconstruct image.
1171 % o metric: the metric.
1173 % o exception: return any errors or warnings in this structure.
1176 MagickExport double *GetImageDistortions(Image *image,
1177 const Image *reconstruct_image,const MetricType metric,
1178 ExceptionInfo *exception)
1181 *channel_distortion;
1189 assert(image != (Image *) NULL);
1190 assert(image->signature == MagickSignature);
1191 if (image->debug != MagickFalse)
1192 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1193 assert(reconstruct_image != (const Image *) NULL);
1194 assert(reconstruct_image->signature == MagickSignature);
1195 if (image->debug != MagickFalse)
1196 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1197 if ((reconstruct_image->columns != image->columns) ||
1198 (reconstruct_image->rows != image->rows))
1200 (void) ThrowMagickException(exception,GetMagickModule(),ImageError,
1201 "ImageSizeDiffers","`%s'",image->filename);
1202 return((double *) NULL);
1205 Get image distortion.
1207 length=MaxPixelChannels+1UL;
1208 channel_distortion=(double *) AcquireQuantumMemory(length,
1209 sizeof(*channel_distortion));
1210 if (channel_distortion == (double *) NULL)
1211 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1212 (void) ResetMagickMemory(channel_distortion,0,length*
1213 sizeof(*channel_distortion));
1217 case AbsoluteErrorMetric:
1219 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1223 case FuzzErrorMetric:
1225 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1229 case MeanAbsoluteErrorMetric:
1231 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1232 channel_distortion,exception);
1235 case MeanErrorPerPixelMetric:
1237 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1241 case MeanSquaredErrorMetric:
1243 status=GetMeanSquaredDistortion(image,reconstruct_image,
1244 channel_distortion,exception);
1247 case NormalizedCrossCorrelationErrorMetric:
1250 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1251 channel_distortion,exception);
1254 case PeakAbsoluteErrorMetric:
1256 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1257 channel_distortion,exception);
1260 case PeakSignalToNoiseRatioMetric:
1262 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1263 channel_distortion,exception);
1266 case RootMeanSquaredErrorMetric:
1268 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1269 channel_distortion,exception);
1273 if (status == MagickFalse)
1275 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1276 return((double *) NULL);
1278 return(channel_distortion);
1282 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1286 % I s I m a g e s E q u a l %
1290 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1292 % IsImagesEqual() measures the difference between colors at each pixel
1293 % location of two images. A value other than 0 means the colors match
1294 % exactly. Otherwise an error measure is computed by summing over all
1295 % pixels in an image the distance squared in RGB space between each image
1296 % pixel and its corresponding pixel in the reconstruct image. The error
1297 % measure is assigned to these image members:
1299 % o mean_error_per_pixel: The mean error for any single pixel in
1302 % o normalized_mean_error: The normalized mean quantization error for
1303 % any single pixel in the image. This distance measure is normalized to
1304 % a range between 0 and 1. It is independent of the range of red, green,
1305 % and blue values in the image.
1307 % o normalized_maximum_error: The normalized maximum quantization
1308 % error for any single pixel in the image. This distance measure is
1309 % normalized to a range between 0 and 1. It is independent of the range
1310 % of red, green, and blue values in your image.
1312 % A small normalized mean square error, accessed as
1313 % image->normalized_mean_error, suggests the images are very similar in
1314 % spatial layout and color.
1316 % The format of the IsImagesEqual method is:
1318 % MagickBooleanType IsImagesEqual(Image *image,
1319 % const Image *reconstruct_image,ExceptionInfo *exception)
1321 % A description of each parameter follows.
1323 % o image: the image.
1325 % o reconstruct_image: the reconstruct image.
1327 % o exception: return any errors or warnings in this structure.
1330 MagickExport MagickBooleanType IsImagesEqual(Image *image,
1331 const Image *reconstruct_image,ExceptionInfo *exception)
1344 mean_error_per_pixel;
1349 assert(image != (Image *) NULL);
1350 assert(image->signature == MagickSignature);
1351 assert(reconstruct_image != (const Image *) NULL);
1352 assert(reconstruct_image->signature == MagickSignature);
1353 if ((reconstruct_image->columns != image->columns) ||
1354 (reconstruct_image->rows != image->rows))
1355 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1358 mean_error_per_pixel=0.0;
1360 image_view=AcquireCacheView(image);
1361 reconstruct_view=AcquireCacheView(reconstruct_image);
1362 for (y=0; y < (ssize_t) image->rows; y++)
1364 register const Quantum
1371 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1372 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1374 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1376 for (x=0; x < (ssize_t) image->columns; x++)
1381 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1393 traits=GetPixelChannelMapTraits(image,i);
1394 channel=GetPixelChannelMapChannel(image,i);
1395 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
1396 if ((traits == UndefinedPixelTrait) ||
1397 (reconstruct_traits == UndefinedPixelTrait))
1399 if ((reconstruct_traits & UpdatePixelTrait) == 0)
1401 distance=fabs(p[i]-(MagickRealType) GetPixelChannel(reconstruct_image,
1403 mean_error_per_pixel+=distance;
1404 mean_error+=distance*distance;
1405 if (distance > maximum_error)
1406 maximum_error=distance;
1409 p+=GetPixelChannels(image);
1410 q+=GetPixelChannels(reconstruct_image);
1413 reconstruct_view=DestroyCacheView(reconstruct_view);
1414 image_view=DestroyCacheView(image_view);
1415 image->error.mean_error_per_pixel=(double) (mean_error_per_pixel/area);
1416 image->error.normalized_mean_error=(double) (QuantumScale*QuantumScale*
1418 image->error.normalized_maximum_error=(double) (QuantumScale*maximum_error);
1419 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
1424 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1428 % S i m i l a r i t y I m a g e %
1432 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1434 % SimilarityImage() compares the reference image of the image and returns the
1435 % best match offset. In addition, it returns a similarity image such that an
1436 % exact match location is completely white and if none of the pixels match,
1437 % black, otherwise some gray level in-between.
1439 % The format of the SimilarityImageImage method is:
1441 % Image *SimilarityImage(const Image *image,const Image *reference,
1442 % const MetricType metric,RectangleInfo *offset,double *similarity,
1443 % ExceptionInfo *exception)
1445 % A description of each parameter follows:
1447 % o image: the image.
1449 % o reference: find an area of the image that closely resembles this image.
1451 % o metric: the metric.
1453 % o the best match offset of the reference image within the image.
1455 % o similarity: the computed similarity between the images.
1457 % o exception: return any errors or warnings in this structure.
1461 static double GetSimilarityMetric(const Image *image,const Image *reference,
1462 const MetricType metric,const ssize_t x_offset,const ssize_t y_offset,
1463 ExceptionInfo *exception)
1477 SetGeometry(reference,&geometry);
1478 geometry.x=x_offset;
1479 geometry.y=y_offset;
1480 similarity_image=CropImage(image,&geometry,exception);
1481 if (similarity_image == (Image *) NULL)
1484 status=GetImageDistortion(similarity_image,reference,metric,&distortion,
1486 similarity_image=DestroyImage(similarity_image);
1487 if (status == MagickFalse)
1492 MagickExport Image *SimilarityImage(Image *image,const Image *reference,
1493 const MetricType metric,RectangleInfo *offset,double *similarity_metric,
1494 ExceptionInfo *exception)
1496 #define SimilarityImageTag "Similarity/Image"
1513 assert(image != (const Image *) NULL);
1514 assert(image->signature == MagickSignature);
1515 if (image->debug != MagickFalse)
1516 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1517 assert(exception != (ExceptionInfo *) NULL);
1518 assert(exception->signature == MagickSignature);
1519 assert(offset != (RectangleInfo *) NULL);
1520 SetGeometry(reference,offset);
1521 *similarity_metric=1.0;
1522 if ((reference->columns > image->columns) || (reference->rows > image->rows))
1523 ThrowImageException(ImageError,"ImageSizeDiffers");
1524 similarity_image=CloneImage(image,image->columns-reference->columns+1,
1525 image->rows-reference->rows+1,MagickTrue,exception);
1526 if (similarity_image == (Image *) NULL)
1527 return((Image *) NULL);
1528 status=SetImageStorageClass(similarity_image,DirectClass,exception);
1529 if (status == MagickFalse)
1531 similarity_image=DestroyImage(similarity_image);
1532 return((Image *) NULL);
1535 Measure similarity of reference image against image.
1539 similarity_view=AcquireCacheView(similarity_image);
1540 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1541 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1543 for (y=0; y < (ssize_t) (image->rows-reference->rows+1); y++)
1554 if (status == MagickFalse)
1556 q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
1558 if (q == (Quantum *) NULL)
1563 for (x=0; x < (ssize_t) (image->columns-reference->columns+1); x++)
1568 similarity=GetSimilarityMetric(image,reference,metric,x,y,exception);
1569 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1570 #pragma omp critical (MagickCore_SimilarityImage)
1572 if (similarity < *similarity_metric)
1574 *similarity_metric=similarity;
1578 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1587 traits=GetPixelChannelMapTraits(image,i);
1588 channel=GetPixelChannelMapChannel(image,i);
1589 similarity_traits=GetPixelChannelMapTraits(similarity_image,channel);
1590 if ((traits == UndefinedPixelTrait) ||
1591 (similarity_traits == UndefinedPixelTrait))
1593 if ((similarity_traits & UpdatePixelTrait) == 0)
1595 SetPixelChannel(similarity_image,channel,ClampToQuantum(QuantumRange-
1596 QuantumRange*similarity),q);
1598 q+=GetPixelChannels(similarity_image);
1600 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
1602 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1607 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1608 #pragma omp critical (MagickCore_SimilarityImage)
1610 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
1612 if (proceed == MagickFalse)
1616 similarity_view=DestroyCacheView(similarity_view);
1617 return(similarity_image);