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 magick_threads(image,highlight_image,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++)
234 PixelChannel channel=GetPixelChannelChannel(image,i);
235 PixelTrait traits=GetPixelChannelTraits(image,channel);
236 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
238 if ((traits == UndefinedPixelTrait) ||
239 (reconstruct_traits == UndefinedPixelTrait) ||
240 ((reconstruct_traits & UpdatePixelTrait) == 0))
242 distance=p[i]-(double) GetPixelChannel(reconstruct_image,channel,q);
243 if (fabs((double) distance) >= MagickEpsilon)
244 difference=MagickTrue;
246 if (difference == MagickFalse)
247 SetPixelInfoPixel(highlight_image,&lowlight,r);
249 SetPixelInfoPixel(highlight_image,&highlight,r);
250 p+=GetPixelChannels(image);
251 q+=GetPixelChannels(reconstruct_image);
252 r+=GetPixelChannels(highlight_image);
254 sync=SyncCacheViewAuthenticPixels(highlight_view,exception);
255 if (sync == MagickFalse)
258 highlight_view=DestroyCacheView(highlight_view);
259 reconstruct_view=DestroyCacheView(reconstruct_view);
260 image_view=DestroyCacheView(image_view);
261 (void) CompositeImage(difference_image,highlight_image,image->compose,
262 MagickTrue,0,0,exception);
263 highlight_image=DestroyImage(highlight_image);
264 if (status == MagickFalse)
265 difference_image=DestroyImage(difference_image);
266 return(difference_image);
270 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
274 % G e t I m a g e D i s t o r t i o n %
278 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
280 % GetImageDistortion() compares one or more pixel channels of an image to a
281 % reconstructed image and returns the specified distortion metric.
283 % The format of the GetImageDistortion method is:
285 % MagickBooleanType GetImageDistortion(const Image *image,
286 % const Image *reconstruct_image,const MetricType metric,
287 % double *distortion,ExceptionInfo *exception)
289 % A description of each parameter follows:
291 % o image: the image.
293 % o reconstruct_image: the reconstruct image.
295 % o metric: the metric.
297 % o distortion: the computed distortion between the images.
299 % o exception: return any errors or warnings in this structure.
303 static MagickBooleanType GetAbsoluteDistortion(const Image *image,
304 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
317 Compute the absolute difference in pixels between two images.
320 image_view=AcquireVirtualCacheView(image,exception);
321 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
322 #if defined(MAGICKCORE_OPENMP_SUPPORT)
323 #pragma omp parallel for schedule(static,4) shared(status) \
324 magick_threads(image,image,image->rows,1)
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 if (GetPixelMask(image,p) != 0)
360 p+=GetPixelChannels(image);
361 q+=GetPixelChannels(reconstruct_image);
364 difference=MagickFalse;
365 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
367 PixelChannel channel=GetPixelChannelChannel(image,i);
368 PixelTrait traits=GetPixelChannelTraits(image,channel);
369 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
371 if ((traits == UndefinedPixelTrait) ||
372 (reconstruct_traits == UndefinedPixelTrait) ||
373 ((reconstruct_traits & UpdatePixelTrait) == 0))
375 if (p[i] != GetPixelChannel(reconstruct_image,channel,q))
376 difference=MagickTrue;
378 if (difference != MagickFalse)
380 channel_distortion[i]++;
381 channel_distortion[CompositePixelChannel]++;
383 p+=GetPixelChannels(image);
384 q+=GetPixelChannels(reconstruct_image);
386 #if defined(MAGICKCORE_OPENMP_SUPPORT)
387 #pragma omp critical (MagickCore_GetAbsoluteError)
389 for (i=0; i <= MaxPixelChannels; i++)
390 distortion[i]+=channel_distortion[i];
392 reconstruct_view=DestroyCacheView(reconstruct_view);
393 image_view=DestroyCacheView(image_view);
397 static size_t GetImageChannels(const Image *image)
406 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
408 PixelChannel channel=GetPixelChannelChannel(image,i);
409 PixelTrait traits=GetPixelChannelTraits(image,channel);
410 if ((traits & UpdatePixelTrait) != 0)
416 static MagickBooleanType GetFuzzDistortion(const Image *image,
417 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
433 image_view=AcquireVirtualCacheView(image,exception);
434 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
435 #if defined(MAGICKCORE_OPENMP_SUPPORT)
436 #pragma omp parallel for schedule(static,4) shared(status) \
437 magick_threads(image,image,image->rows,1)
439 for (y=0; y < (ssize_t) image->rows; y++)
442 channel_distortion[MaxPixelChannels+1];
444 register const Quantum
452 if (status == MagickFalse)
454 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
455 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
457 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
462 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
463 for (x=0; x < (ssize_t) image->columns; x++)
468 if (GetPixelMask(image,p) != 0)
470 p+=GetPixelChannels(image);
471 q+=GetPixelChannels(reconstruct_image);
474 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
479 PixelChannel channel=GetPixelChannelChannel(image,i);
480 PixelTrait traits=GetPixelChannelTraits(image,channel);
481 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
483 if ((traits == UndefinedPixelTrait) ||
484 (reconstruct_traits == UndefinedPixelTrait) ||
485 ((reconstruct_traits & UpdatePixelTrait) == 0))
487 distance=QuantumScale*(p[i]-(double) GetPixelChannel(reconstruct_image,
490 channel_distortion[i]+=distance;
491 channel_distortion[CompositePixelChannel]+=distance;
493 p+=GetPixelChannels(image);
494 q+=GetPixelChannels(reconstruct_image);
496 #if defined(MAGICKCORE_OPENMP_SUPPORT)
497 #pragma omp critical (MagickCore_GetFuzzDistortion)
499 for (i=0; i <= MaxPixelChannels; i++)
500 distortion[i]+=channel_distortion[i];
502 reconstruct_view=DestroyCacheView(reconstruct_view);
503 image_view=DestroyCacheView(image_view);
504 for (i=0; i <= MaxPixelChannels; i++)
505 distortion[i]/=((double) image->columns*image->rows);
506 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
507 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]);
511 static MagickBooleanType GetMeanAbsoluteDistortion(const Image *image,
512 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
528 image_view=AcquireVirtualCacheView(image,exception);
529 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
530 #if defined(MAGICKCORE_OPENMP_SUPPORT)
531 #pragma omp parallel for schedule(static,4) shared(status) \
532 magick_threads(image,image,image->rows,1)
534 for (y=0; y < (ssize_t) image->rows; y++)
537 channel_distortion[MaxPixelChannels+1];
539 register const Quantum
547 if (status == MagickFalse)
549 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
550 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
552 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
557 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
558 for (x=0; x < (ssize_t) image->columns; x++)
563 if (GetPixelMask(image,p) != 0)
565 p+=GetPixelChannels(image);
566 q+=GetPixelChannels(reconstruct_image);
569 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
574 PixelChannel channel=GetPixelChannelChannel(image,i);
575 PixelTrait traits=GetPixelChannelTraits(image,channel);
576 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
578 if ((traits == UndefinedPixelTrait) ||
579 (reconstruct_traits == UndefinedPixelTrait) ||
580 ((reconstruct_traits & UpdatePixelTrait) == 0))
582 distance=QuantumScale*fabs(p[i]-(double) GetPixelChannel(
583 reconstruct_image,channel,q));
584 channel_distortion[i]+=distance;
585 channel_distortion[CompositePixelChannel]+=distance;
587 p+=GetPixelChannels(image);
588 q+=GetPixelChannels(reconstruct_image);
590 #if defined(MAGICKCORE_OPENMP_SUPPORT)
591 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
593 for (i=0; i <= MaxPixelChannels; i++)
594 distortion[i]+=channel_distortion[i];
596 reconstruct_view=DestroyCacheView(reconstruct_view);
597 image_view=DestroyCacheView(image_view);
598 for (i=0; i <= MaxPixelChannels; i++)
599 distortion[i]/=((double) image->columns*image->rows);
600 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
604 static MagickBooleanType GetMeanErrorPerPixel(Image *image,
605 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
630 image_view=AcquireVirtualCacheView(image,exception);
631 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
632 for (y=0; y < (ssize_t) image->rows; y++)
634 register const Quantum
641 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
642 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
644 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
649 for (x=0; x < (ssize_t) image->columns; x++)
654 if (GetPixelMask(image,p) != 0)
656 p+=GetPixelChannels(image);
657 q+=GetPixelChannels(reconstruct_image);
660 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
665 PixelChannel channel=GetPixelChannelChannel(image,i);
666 PixelTrait traits=GetPixelChannelTraits(image,channel);
667 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
669 if ((traits == UndefinedPixelTrait) ||
670 (reconstruct_traits == UndefinedPixelTrait) ||
671 ((reconstruct_traits & UpdatePixelTrait) == 0))
673 distance=fabs((double) (alpha*p[i]-beta*GetPixelChannel(
674 reconstruct_image,channel,q)));
675 distortion[i]+=distance;
676 distortion[CompositePixelChannel]+=distance;
677 mean_error+=distance*distance;
678 if (distance > maximum_error)
679 maximum_error=distance;
682 p+=GetPixelChannels(image);
683 q+=GetPixelChannels(reconstruct_image);
686 reconstruct_view=DestroyCacheView(reconstruct_view);
687 image_view=DestroyCacheView(image_view);
688 image->error.mean_error_per_pixel=distortion[CompositePixelChannel]/area;
689 image->error.normalized_mean_error=QuantumScale*QuantumScale*mean_error/area;
690 image->error.normalized_maximum_error=QuantumScale*maximum_error;
694 static MagickBooleanType GetMeanSquaredDistortion(const Image *image,
695 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
711 image_view=AcquireVirtualCacheView(image,exception);
712 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
713 #if defined(MAGICKCORE_OPENMP_SUPPORT)
714 #pragma omp parallel for schedule(static,4) shared(status) \
715 magick_threads(image,image,image->rows,1)
717 for (y=0; y < (ssize_t) image->rows; y++)
720 channel_distortion[MaxPixelChannels+1];
722 register const Quantum
730 if (status == MagickFalse)
732 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
733 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
735 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
740 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
741 for (x=0; x < (ssize_t) image->columns; x++)
746 if (GetPixelMask(image,p) != 0)
748 p+=GetPixelChannels(image);
749 q+=GetPixelChannels(reconstruct_image);
752 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
757 PixelChannel channel=GetPixelChannelChannel(image,i);
758 PixelTrait traits=GetPixelChannelTraits(image,channel);
759 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
761 if ((traits == UndefinedPixelTrait) ||
762 (reconstruct_traits == UndefinedPixelTrait) ||
763 ((reconstruct_traits & UpdatePixelTrait) == 0))
765 distance=QuantumScale*(p[i]-(double) GetPixelChannel(
766 reconstruct_image,channel,q));
768 channel_distortion[i]+=distance;
769 channel_distortion[CompositePixelChannel]+=distance;
771 p+=GetPixelChannels(image);
772 q+=GetPixelChannels(reconstruct_image);
774 #if defined(MAGICKCORE_OPENMP_SUPPORT)
775 #pragma omp critical (MagickCore_GetMeanSquaredError)
777 for (i=0; i <= MaxPixelChannels; i++)
778 distortion[i]+=channel_distortion[i];
780 reconstruct_view=DestroyCacheView(reconstruct_view);
781 image_view=DestroyCacheView(image_view);
782 for (i=0; i <= MaxPixelChannels; i++)
783 distortion[i]/=((double) image->columns*image->rows);
784 distortion[CompositePixelChannel]/=GetImageChannels(image);
788 static MagickBooleanType GetNormalizedCrossCorrelationDistortion(
789 const Image *image,const Image *reconstruct_image,double *distortion,
790 ExceptionInfo *exception)
792 #define SimilarityImageTag "Similarity/Image"
800 *reconstruct_statistics;
818 Normalize to account for variation due to lighting and exposure condition.
820 image_statistics=GetImageStatistics(image,exception);
821 reconstruct_statistics=GetImageStatistics(reconstruct_image,exception);
824 for (i=0; i <= MaxPixelChannels; i++)
826 area=1.0/((double) image->columns*image->rows-1);
827 image_view=AcquireVirtualCacheView(image,exception);
828 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
829 for (y=0; y < (ssize_t) image->rows; y++)
831 register const Quantum
838 if (status == MagickFalse)
840 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
841 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
843 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
848 for (x=0; x < (ssize_t) image->columns; x++)
853 if (GetPixelMask(image,p) != 0)
855 p+=GetPixelChannels(image);
856 q+=GetPixelChannels(reconstruct_image);
859 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
861 PixelChannel channel=GetPixelChannelChannel(image,i);
862 PixelTrait traits=GetPixelChannelTraits(image,channel);
863 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
865 if ((traits == UndefinedPixelTrait) ||
866 (reconstruct_traits == UndefinedPixelTrait) ||
867 ((reconstruct_traits & UpdatePixelTrait) == 0))
869 distortion[i]+=area*QuantumScale*(p[i]-image_statistics[i].mean)*
870 (GetPixelChannel(reconstruct_image,channel,q)-
871 reconstruct_statistics[channel].mean);
873 p+=GetPixelChannels(image);
874 q+=GetPixelChannels(reconstruct_image);
876 if (image->progress_monitor != (MagickProgressMonitor) NULL)
881 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
883 if (proceed == MagickFalse)
887 reconstruct_view=DestroyCacheView(reconstruct_view);
888 image_view=DestroyCacheView(image_view);
890 Divide by the standard deviation.
892 distortion[CompositePixelChannel]=0.0;
893 for (i=0; i < MaxPixelChannels; i++)
898 PixelChannel channel=GetPixelChannelChannel(image,i);
899 gamma=image_statistics[i].standard_deviation*
900 reconstruct_statistics[channel].standard_deviation;
901 gamma=PerceptibleReciprocal(gamma);
902 distortion[i]=QuantumRange*gamma*distortion[i];
903 distortion[CompositePixelChannel]+=distortion[i]*distortion[i];
905 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]/
906 GetImageChannels(image));
910 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
911 reconstruct_statistics);
912 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
917 static MagickBooleanType GetPeakAbsoluteDistortion(const Image *image,
918 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
931 image_view=AcquireVirtualCacheView(image,exception);
932 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
933 #if defined(MAGICKCORE_OPENMP_SUPPORT)
934 #pragma omp parallel for schedule(static,4) shared(status) \
935 magick_threads(image,image,image->rows,1)
937 for (y=0; y < (ssize_t) image->rows; y++)
940 channel_distortion[MaxPixelChannels+1];
942 register const Quantum
950 if (status == MagickFalse)
952 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
953 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
955 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
960 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
961 for (x=0; x < (ssize_t) image->columns; x++)
966 if (GetPixelMask(image,p) != 0)
968 p+=GetPixelChannels(image);
969 q+=GetPixelChannels(reconstruct_image);
972 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
977 PixelChannel channel=GetPixelChannelChannel(image,i);
978 PixelTrait traits=GetPixelChannelTraits(image,channel);
979 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
981 if ((traits == UndefinedPixelTrait) ||
982 (reconstruct_traits == UndefinedPixelTrait) ||
983 ((reconstruct_traits & UpdatePixelTrait) == 0))
985 distance=QuantumScale*fabs(p[i]-(double) GetPixelChannel(
986 reconstruct_image,channel,q));
987 if (distance > channel_distortion[i])
988 channel_distortion[i]=distance;
989 if (distance > channel_distortion[CompositePixelChannel])
990 channel_distortion[CompositePixelChannel]=distance;
992 p+=GetPixelChannels(image);
993 q+=GetPixelChannels(reconstruct_image);
995 #if defined(MAGICKCORE_OPENMP_SUPPORT)
996 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
998 for (i=0; i <= MaxPixelChannels; i++)
999 if (channel_distortion[i] > distortion[i])
1000 distortion[i]=channel_distortion[i];
1002 reconstruct_view=DestroyCacheView(reconstruct_view);
1003 image_view=DestroyCacheView(image_view);
1007 static MagickBooleanType GetPeakSignalToNoiseRatio(const Image *image,
1008 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1016 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
1017 for (i=0; i <= MaxPixelChannels; i++)
1018 distortion[i]=20.0*log10((double) 1.0/sqrt(distortion[i]));
1022 static MagickBooleanType GetRootMeanSquaredDistortion(const Image *image,
1023 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1031 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
1032 for (i=0; i <= MaxPixelChannels; i++)
1033 distortion[i]=sqrt(distortion[i]);
1037 MagickExport MagickBooleanType GetImageDistortion(Image *image,
1038 const Image *reconstruct_image,const MetricType metric,double *distortion,
1039 ExceptionInfo *exception)
1042 *channel_distortion;
1050 assert(image != (Image *) NULL);
1051 assert(image->signature == MagickSignature);
1052 if (image->debug != MagickFalse)
1053 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1054 assert(reconstruct_image != (const Image *) NULL);
1055 assert(reconstruct_image->signature == MagickSignature);
1056 assert(distortion != (double *) NULL);
1058 if (image->debug != MagickFalse)
1059 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1060 if ((reconstruct_image->columns != image->columns) ||
1061 (reconstruct_image->rows != image->rows))
1062 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1064 Get image distortion.
1066 length=MaxPixelChannels+1;
1067 channel_distortion=(double *) AcquireQuantumMemory(length,
1068 sizeof(*channel_distortion));
1069 if (channel_distortion == (double *) NULL)
1070 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1071 (void) ResetMagickMemory(channel_distortion,0,length*
1072 sizeof(*channel_distortion));
1075 case AbsoluteErrorMetric:
1077 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1081 case FuzzErrorMetric:
1083 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1087 case MeanAbsoluteErrorMetric:
1089 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1090 channel_distortion,exception);
1093 case MeanErrorPerPixelMetric:
1095 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1099 case MeanSquaredErrorMetric:
1101 status=GetMeanSquaredDistortion(image,reconstruct_image,
1102 channel_distortion,exception);
1105 case NormalizedCrossCorrelationErrorMetric:
1108 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1109 channel_distortion,exception);
1112 case PeakAbsoluteErrorMetric:
1114 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1115 channel_distortion,exception);
1118 case PeakSignalToNoiseRatioMetric:
1120 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1121 channel_distortion,exception);
1124 case RootMeanSquaredErrorMetric:
1126 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1127 channel_distortion,exception);
1131 *distortion=channel_distortion[CompositePixelChannel];
1132 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1137 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1141 % G e t I m a g e D i s t o r t i o n s %
1145 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1147 % GetImageDistortions() compares the pixel channels of an image to a
1148 % reconstructed image and returns the specified distortion metric for each
1151 % The format of the GetImageDistortions method is:
1153 % double *GetImageDistortions(const Image *image,
1154 % const Image *reconstruct_image,const MetricType metric,
1155 % ExceptionInfo *exception)
1157 % A description of each parameter follows:
1159 % o image: the image.
1161 % o reconstruct_image: the reconstruct image.
1163 % o metric: the metric.
1165 % o exception: return any errors or warnings in this structure.
1168 MagickExport double *GetImageDistortions(Image *image,
1169 const Image *reconstruct_image,const MetricType metric,
1170 ExceptionInfo *exception)
1173 *channel_distortion;
1181 assert(image != (Image *) NULL);
1182 assert(image->signature == MagickSignature);
1183 if (image->debug != MagickFalse)
1184 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1185 assert(reconstruct_image != (const Image *) NULL);
1186 assert(reconstruct_image->signature == MagickSignature);
1187 if (image->debug != MagickFalse)
1188 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1189 if ((reconstruct_image->columns != image->columns) ||
1190 (reconstruct_image->rows != image->rows))
1192 (void) ThrowMagickException(exception,GetMagickModule(),ImageError,
1193 "ImageSizeDiffers","`%s'",image->filename);
1194 return((double *) NULL);
1197 Get image distortion.
1199 length=MaxPixelChannels+1UL;
1200 channel_distortion=(double *) AcquireQuantumMemory(length,
1201 sizeof(*channel_distortion));
1202 if (channel_distortion == (double *) NULL)
1203 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1204 (void) ResetMagickMemory(channel_distortion,0,length*
1205 sizeof(*channel_distortion));
1209 case AbsoluteErrorMetric:
1211 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1215 case FuzzErrorMetric:
1217 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1221 case MeanAbsoluteErrorMetric:
1223 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1224 channel_distortion,exception);
1227 case MeanErrorPerPixelMetric:
1229 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1233 case MeanSquaredErrorMetric:
1235 status=GetMeanSquaredDistortion(image,reconstruct_image,
1236 channel_distortion,exception);
1239 case NormalizedCrossCorrelationErrorMetric:
1242 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1243 channel_distortion,exception);
1246 case PeakAbsoluteErrorMetric:
1248 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1249 channel_distortion,exception);
1252 case PeakSignalToNoiseRatioMetric:
1254 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1255 channel_distortion,exception);
1258 case RootMeanSquaredErrorMetric:
1260 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1261 channel_distortion,exception);
1265 if (status == MagickFalse)
1267 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1268 return((double *) NULL);
1270 return(channel_distortion);
1274 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1278 % I s I m a g e s E q u a l %
1282 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1284 % IsImagesEqual() measures the difference between colors at each pixel
1285 % location of two images. A value other than 0 means the colors match
1286 % exactly. Otherwise an error measure is computed by summing over all
1287 % pixels in an image the distance squared in RGB space between each image
1288 % pixel and its corresponding pixel in the reconstruct image. The error
1289 % measure is assigned to these image members:
1291 % o mean_error_per_pixel: The mean error for any single pixel in
1294 % o normalized_mean_error: The normalized mean quantization error for
1295 % any single pixel in the image. This distance measure is normalized to
1296 % a range between 0 and 1. It is independent of the range of red, green,
1297 % and blue values in the image.
1299 % o normalized_maximum_error: The normalized maximum quantization
1300 % error for any single pixel in the image. This distance measure is
1301 % normalized to a range between 0 and 1. It is independent of the range
1302 % of red, green, and blue values in your image.
1304 % A small normalized mean square error, accessed as
1305 % image->normalized_mean_error, suggests the images are very similar in
1306 % spatial layout and color.
1308 % The format of the IsImagesEqual method is:
1310 % MagickBooleanType IsImagesEqual(Image *image,
1311 % const Image *reconstruct_image,ExceptionInfo *exception)
1313 % A description of each parameter follows.
1315 % o image: the image.
1317 % o reconstruct_image: the reconstruct image.
1319 % o exception: return any errors or warnings in this structure.
1322 MagickExport MagickBooleanType IsImagesEqual(Image *image,
1323 const Image *reconstruct_image,ExceptionInfo *exception)
1336 mean_error_per_pixel;
1341 assert(image != (Image *) NULL);
1342 assert(image->signature == MagickSignature);
1343 assert(reconstruct_image != (const Image *) NULL);
1344 assert(reconstruct_image->signature == MagickSignature);
1345 if ((reconstruct_image->columns != image->columns) ||
1346 (reconstruct_image->rows != image->rows))
1347 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1350 mean_error_per_pixel=0.0;
1352 image_view=AcquireVirtualCacheView(image,exception);
1353 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1354 for (y=0; y < (ssize_t) image->rows; y++)
1356 register const Quantum
1363 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1364 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1366 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1368 for (x=0; x < (ssize_t) image->columns; x++)
1373 if (GetPixelMask(image,p) != 0)
1375 p+=GetPixelChannels(image);
1376 q+=GetPixelChannels(reconstruct_image);
1379 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1384 PixelChannel channel=GetPixelChannelChannel(image,i);
1385 PixelTrait traits=GetPixelChannelTraits(image,channel);
1386 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
1388 if ((traits == UndefinedPixelTrait) ||
1389 (reconstruct_traits == UndefinedPixelTrait) ||
1390 ((reconstruct_traits & UpdatePixelTrait) == 0))
1392 distance=fabs(p[i]-(double) GetPixelChannel(reconstruct_image,
1394 mean_error_per_pixel+=distance;
1395 mean_error+=distance*distance;
1396 if (distance > maximum_error)
1397 maximum_error=distance;
1400 p+=GetPixelChannels(image);
1401 q+=GetPixelChannels(reconstruct_image);
1404 reconstruct_view=DestroyCacheView(reconstruct_view);
1405 image_view=DestroyCacheView(image_view);
1406 image->error.mean_error_per_pixel=(double) (mean_error_per_pixel/area);
1407 image->error.normalized_mean_error=(double) (QuantumScale*QuantumScale*
1409 image->error.normalized_maximum_error=(double) (QuantumScale*maximum_error);
1410 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
1415 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1419 % S i m i l a r i t y I m a g e %
1423 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1425 % SimilarityImage() compares the reference image of the image and returns the
1426 % best match offset. In addition, it returns a similarity image such that an
1427 % exact match location is completely white and if none of the pixels match,
1428 % black, otherwise some gray level in-between.
1430 % The format of the SimilarityImageImage method is:
1432 % Image *SimilarityImage(const Image *image,const Image *reference,
1433 % const MetricType metric,RectangleInfo *offset,double *similarity,
1434 % ExceptionInfo *exception)
1436 % A description of each parameter follows:
1438 % o image: the image.
1440 % o reference: find an area of the image that closely resembles this image.
1442 % o metric: the metric.
1444 % o the best match offset of the reference image within the image.
1446 % o similarity: the computed similarity between the images.
1448 % o exception: return any errors or warnings in this structure.
1452 static double GetSimilarityMetric(const Image *image,const Image *reference,
1453 const MetricType metric,const ssize_t x_offset,const ssize_t y_offset,
1454 ExceptionInfo *exception)
1468 SetGeometry(reference,&geometry);
1469 geometry.x=x_offset;
1470 geometry.y=y_offset;
1471 similarity_image=CropImage(image,&geometry,exception);
1472 if (similarity_image == (Image *) NULL)
1475 status=GetImageDistortion(similarity_image,reference,metric,&distortion,
1477 similarity_image=DestroyImage(similarity_image);
1478 if (status == MagickFalse)
1483 MagickExport Image *SimilarityImage(Image *image,const Image *reference,
1484 const MetricType metric,RectangleInfo *offset,double *similarity_metric,
1485 ExceptionInfo *exception)
1487 #define SimilarityImageTag "Similarity/Image"
1504 assert(image != (const Image *) NULL);
1505 assert(image->signature == MagickSignature);
1506 if (image->debug != MagickFalse)
1507 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1508 assert(exception != (ExceptionInfo *) NULL);
1509 assert(exception->signature == MagickSignature);
1510 assert(offset != (RectangleInfo *) NULL);
1511 SetGeometry(reference,offset);
1512 *similarity_metric=1.0;
1513 if ((reference->columns > image->columns) || (reference->rows > image->rows))
1514 ThrowImageException(ImageError,"ImageSizeDiffers");
1515 similarity_image=CloneImage(image,image->columns-reference->columns+1,
1516 image->rows-reference->rows+1,MagickTrue,exception);
1517 if (similarity_image == (Image *) NULL)
1518 return((Image *) NULL);
1519 status=SetImageStorageClass(similarity_image,DirectClass,exception);
1520 if (status == MagickFalse)
1522 similarity_image=DestroyImage(similarity_image);
1523 return((Image *) NULL);
1526 Measure similarity of reference image against image.
1530 similarity_view=AcquireAuthenticCacheView(similarity_image,exception);
1531 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1532 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1533 magick_threads(image,image,image->rows,1)
1535 for (y=0; y < (ssize_t) (image->rows-reference->rows+1); y++)
1546 if (status == MagickFalse)
1548 q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
1550 if (q == (Quantum *) NULL)
1555 for (x=0; x < (ssize_t) (image->columns-reference->columns+1); x++)
1560 similarity=GetSimilarityMetric(image,reference,metric,x,y,exception);
1561 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1562 #pragma omp critical (MagickCore_SimilarityImage)
1564 if (similarity < *similarity_metric)
1566 *similarity_metric=similarity;
1570 if (GetPixelMask(similarity_image,q) != 0)
1572 q+=GetPixelChannels(similarity_image);
1575 for (i=0; i < (ssize_t) GetPixelChannels(similarity_image); i++)
1577 PixelChannel channel=GetPixelChannelChannel(image,i);
1578 PixelTrait traits=GetPixelChannelTraits(image,channel);
1579 PixelTrait similarity_traits=GetPixelChannelTraits(similarity_image,
1581 if ((traits == UndefinedPixelTrait) ||
1582 (similarity_traits == UndefinedPixelTrait) ||
1583 ((similarity_traits & UpdatePixelTrait) == 0))
1585 SetPixelChannel(similarity_image,channel,ClampToQuantum(QuantumRange-
1586 QuantumRange*similarity),q);
1588 q+=GetPixelChannels(similarity_image);
1590 if (IfMagickFalse( SyncCacheViewAuthenticPixels(similarity_view,exception) ))
1592 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1594 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1595 #pragma omp critical (MagickCore_SimilarityImage)
1597 if (IfMagickFalse(SetImageProgress(image,SimilarityImageTag,
1598 progress++,image->rows) ))
1602 similarity_view=DestroyCacheView(similarity_view);
1603 if (status == MagickFalse)
1604 similarity_image=DestroyImage(similarity_image);
1605 return(similarity_image);