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/channel.h"
47 #include "MagickCore/client.h"
48 #include "MagickCore/color.h"
49 #include "MagickCore/color-private.h"
50 #include "MagickCore/colorspace.h"
51 #include "MagickCore/colorspace-private.h"
52 #include "MagickCore/compare.h"
53 #include "MagickCore/composite-private.h"
54 #include "MagickCore/constitute.h"
55 #include "MagickCore/exception-private.h"
56 #include "MagickCore/geometry.h"
57 #include "MagickCore/image-private.h"
58 #include "MagickCore/list.h"
59 #include "MagickCore/log.h"
60 #include "MagickCore/memory_.h"
61 #include "MagickCore/monitor.h"
62 #include "MagickCore/monitor-private.h"
63 #include "MagickCore/option.h"
64 #include "MagickCore/pixel-accessor.h"
65 #include "MagickCore/property.h"
66 #include "MagickCore/resource_.h"
67 #include "MagickCore/string_.h"
68 #include "MagickCore/statistic.h"
69 #include "MagickCore/thread-private.h"
70 #include "MagickCore/transform.h"
71 #include "MagickCore/utility.h"
72 #include "MagickCore/version.h"
75 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
79 % C o m p a r e I m a g e %
83 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
85 % CompareImages() compares one or more pixel channels of an image to a
86 % reconstructed image and returns the difference image.
88 % The format of the CompareImages method is:
90 % Image *CompareImages(const Image *image,const Image *reconstruct_image,
91 % const MetricType metric,double *distortion,ExceptionInfo *exception)
93 % A description of each parameter follows:
97 % o reconstruct_image: the reconstruct image.
99 % o metric: the metric.
101 % o distortion: the computed distortion between the images.
103 % o exception: return any errors or warnings in this structure.
106 MagickExport Image *CompareImages(Image *image,const Image *reconstruct_image,
107 const MetricType metric,double *distortion,ExceptionInfo *exception)
131 assert(image != (Image *) NULL);
132 assert(image->signature == MagickSignature);
133 if (image->debug != MagickFalse)
134 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
135 assert(reconstruct_image != (const Image *) NULL);
136 assert(reconstruct_image->signature == MagickSignature);
137 assert(distortion != (double *) NULL);
139 if (image->debug != MagickFalse)
140 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
141 if ((reconstruct_image->columns != image->columns) ||
142 (reconstruct_image->rows != image->rows))
143 ThrowImageException(ImageError,"ImageSizeDiffers");
144 status=GetImageDistortion(image,reconstruct_image,metric,distortion,
146 if (status == MagickFalse)
147 return((Image *) NULL);
148 difference_image=CloneImage(image,0,0,MagickTrue,exception);
149 if (difference_image == (Image *) NULL)
150 return((Image *) NULL);
151 (void) SetImageAlphaChannel(difference_image,OpaqueAlphaChannel,exception);
152 highlight_image=CloneImage(image,image->columns,image->rows,MagickTrue,
154 if (highlight_image == (Image *) NULL)
156 difference_image=DestroyImage(difference_image);
157 return((Image *) NULL);
159 status=SetImageStorageClass(highlight_image,DirectClass,exception);
160 if (status == MagickFalse)
162 difference_image=DestroyImage(difference_image);
163 highlight_image=DestroyImage(highlight_image);
164 return((Image *) NULL);
166 (void) SetImageAlphaChannel(highlight_image,OpaqueAlphaChannel,exception);
167 (void) QueryColorCompliance("#f1001ecc",AllCompliance,&highlight,exception);
168 artifact=GetImageArtifact(image,"highlight-color");
169 if (artifact != (const char *) NULL)
170 (void) QueryColorCompliance(artifact,AllCompliance,&highlight,exception);
171 (void) QueryColorCompliance("#ffffffcc",AllCompliance,&lowlight,exception);
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=AcquireVirtualCacheView(image,exception);
180 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
181 highlight_view=AcquireAuthenticCacheView(highlight_image,exception);
182 #if defined(MAGICKCORE_OPENMP_SUPPORT)
183 #pragma omp parallel for schedule(static,4) shared(status) \
184 magick_threads(image,highlight_image,image->rows,1)
186 for (y=0; y < (ssize_t) image->rows; y++)
191 register const Quantum
201 if (status == MagickFalse)
203 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
204 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
206 r=QueueCacheViewAuthenticPixels(highlight_view,0,y,highlight_image->columns,
208 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL) ||
209 (r == (Quantum *) NULL))
214 for (x=0; x < (ssize_t) image->columns; x++)
226 if (GetPixelReadMask(image,p) == 0)
228 SetPixelInfoPixel(highlight_image,&lowlight,r);
229 p+=GetPixelChannels(image);
230 q+=GetPixelChannels(reconstruct_image);
231 r+=GetPixelChannels(highlight_image);
234 difference=MagickFalse;
235 Sa=QuantumScale*GetPixelAlpha(image,p);
236 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
237 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
242 PixelChannel channel=GetPixelChannelChannel(image,i);
243 PixelTrait traits=GetPixelChannelTraits(image,channel);
244 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
246 if ((traits == UndefinedPixelTrait) ||
247 (reconstruct_traits == UndefinedPixelTrait) ||
248 ((reconstruct_traits & UpdatePixelTrait) == 0))
250 distance=Sa*p[i]-Da*GetPixelChannel(reconstruct_image,channel,q);
251 if (fabs((double) distance) >= MagickEpsilon)
252 difference=MagickTrue;
254 if (difference == MagickFalse)
255 SetPixelInfoPixel(highlight_image,&lowlight,r);
257 SetPixelInfoPixel(highlight_image,&highlight,r);
258 p+=GetPixelChannels(image);
259 q+=GetPixelChannels(reconstruct_image);
260 r+=GetPixelChannels(highlight_image);
262 sync=SyncCacheViewAuthenticPixels(highlight_view,exception);
263 if (sync == MagickFalse)
266 highlight_view=DestroyCacheView(highlight_view);
267 reconstruct_view=DestroyCacheView(reconstruct_view);
268 image_view=DestroyCacheView(image_view);
269 (void) CompositeImage(difference_image,highlight_image,image->compose,
270 MagickTrue,0,0,exception);
271 highlight_image=DestroyImage(highlight_image);
272 if (status == MagickFalse)
273 difference_image=DestroyImage(difference_image);
274 return(difference_image);
278 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
282 % G e t I m a g e D i s t o r t i o n %
286 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
288 % GetImageDistortion() compares one or more pixel channels of an image to a
289 % reconstructed image and returns the specified distortion metric.
291 % The format of the GetImageDistortion method is:
293 % MagickBooleanType GetImageDistortion(const Image *image,
294 % const Image *reconstruct_image,const MetricType metric,
295 % double *distortion,ExceptionInfo *exception)
297 % A description of each parameter follows:
299 % o image: the image.
301 % o reconstruct_image: the reconstruct image.
303 % o metric: the metric.
305 % o distortion: the computed distortion between the images.
307 % o exception: return any errors or warnings in this structure.
311 static inline double MagickMax(const double x,const double y)
318 static MagickBooleanType GetAbsoluteDistortion(const Image *image,
319 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
335 Compute the absolute difference in pixels between two images.
338 if (image->fuzz == 0.0)
339 fuzz=MagickMax(reconstruct_image->fuzz,MagickSQ1_2)*
340 MagickMax(reconstruct_image->fuzz,MagickSQ1_2);
342 if (reconstruct_image->fuzz == 0.0)
343 fuzz=MagickMax(image->fuzz,MagickSQ1_2)*
344 MagickMax(image->fuzz,MagickSQ1_2);
346 fuzz=MagickMax(image->fuzz,MagickSQ1_2)*
347 MagickMax(reconstruct_image->fuzz,MagickSQ1_2);
348 image_view=AcquireVirtualCacheView(image,exception);
349 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
350 #if defined(MAGICKCORE_OPENMP_SUPPORT)
351 #pragma omp parallel for schedule(static,4) shared(status) \
352 magick_threads(image,image,image->rows,1)
354 for (y=0; y < (ssize_t) image->rows; y++)
357 channel_distortion[MaxPixelChannels+1];
359 register const Quantum
367 if (status == MagickFalse)
369 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
370 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
372 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
377 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
378 for (x=0; x < (ssize_t) image->columns; x++)
390 if (GetPixelReadMask(image,p) == 0)
392 p+=GetPixelChannels(image);
393 q+=GetPixelChannels(reconstruct_image);
396 difference=MagickFalse;
397 Sa=QuantumScale*GetPixelAlpha(image,p);
398 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
399 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
404 PixelChannel channel=GetPixelChannelChannel(image,i);
405 PixelTrait traits=GetPixelChannelTraits(image,channel);
406 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
408 if ((traits == UndefinedPixelTrait) ||
409 (reconstruct_traits == UndefinedPixelTrait) ||
410 ((reconstruct_traits & UpdatePixelTrait) == 0))
412 distance=Sa*p[i]-Da*GetPixelChannel(reconstruct_image,channel,q);
413 if ((distance*distance) > fuzz)
415 difference=MagickTrue;
419 if (difference != MagickFalse)
421 channel_distortion[i]++;
422 channel_distortion[CompositePixelChannel]++;
424 p+=GetPixelChannels(image);
425 q+=GetPixelChannels(reconstruct_image);
427 #if defined(MAGICKCORE_OPENMP_SUPPORT)
428 #pragma omp critical (MagickCore_GetAbsoluteError)
430 for (i=0; i <= MaxPixelChannels; i++)
431 distortion[i]+=channel_distortion[i];
433 reconstruct_view=DestroyCacheView(reconstruct_view);
434 image_view=DestroyCacheView(image_view);
438 static size_t GetImageChannels(const Image *image)
447 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
449 PixelChannel channel=GetPixelChannelChannel(image,i);
450 PixelTrait traits=GetPixelChannelTraits(image,channel);
451 if ((traits & UpdatePixelTrait) != 0)
457 static MagickBooleanType GetFuzzDistortion(const Image *image,
458 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
474 image_view=AcquireVirtualCacheView(image,exception);
475 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
476 #if defined(MAGICKCORE_OPENMP_SUPPORT)
477 #pragma omp parallel for schedule(static,4) shared(status) \
478 magick_threads(image,image,image->rows,1)
480 for (y=0; y < (ssize_t) image->rows; y++)
483 channel_distortion[MaxPixelChannels+1];
485 register const Quantum
493 if (status == MagickFalse)
495 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
496 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
498 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
503 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
504 for (x=0; x < (ssize_t) image->columns; x++)
513 if (GetPixelReadMask(image,p) == 0)
515 p+=GetPixelChannels(image);
516 q+=GetPixelChannels(reconstruct_image);
519 Sa=QuantumScale*GetPixelAlpha(image,p);
520 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
521 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
526 PixelChannel channel=GetPixelChannelChannel(image,i);
527 PixelTrait traits=GetPixelChannelTraits(image,channel);
528 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
530 if ((traits == UndefinedPixelTrait) ||
531 (reconstruct_traits == UndefinedPixelTrait) ||
532 ((reconstruct_traits & UpdatePixelTrait) == 0))
534 distance=QuantumScale*(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
536 channel_distortion[i]+=distance*distance;
537 channel_distortion[CompositePixelChannel]+=distance*distance;
539 p+=GetPixelChannels(image);
540 q+=GetPixelChannels(reconstruct_image);
542 #if defined(MAGICKCORE_OPENMP_SUPPORT)
543 #pragma omp critical (MagickCore_GetFuzzDistortion)
545 for (i=0; i <= MaxPixelChannels; i++)
546 distortion[i]+=channel_distortion[i];
548 reconstruct_view=DestroyCacheView(reconstruct_view);
549 image_view=DestroyCacheView(image_view);
550 for (i=0; i <= MaxPixelChannels; i++)
551 distortion[i]/=((double) image->columns*image->rows);
552 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
553 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]);
557 static MagickBooleanType GetMeanAbsoluteDistortion(const Image *image,
558 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
574 image_view=AcquireVirtualCacheView(image,exception);
575 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
576 #if defined(MAGICKCORE_OPENMP_SUPPORT)
577 #pragma omp parallel for schedule(static,4) shared(status) \
578 magick_threads(image,image,image->rows,1)
580 for (y=0; y < (ssize_t) image->rows; y++)
583 channel_distortion[MaxPixelChannels+1];
585 register const Quantum
593 if (status == MagickFalse)
595 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
596 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
598 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
603 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
604 for (x=0; x < (ssize_t) image->columns; x++)
613 if (GetPixelReadMask(image,p) == 0)
615 p+=GetPixelChannels(image);
616 q+=GetPixelChannels(reconstruct_image);
619 Sa=QuantumScale*GetPixelAlpha(image,p);
620 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
621 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
626 PixelChannel channel=GetPixelChannelChannel(image,i);
627 PixelTrait traits=GetPixelChannelTraits(image,channel);
628 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
630 if ((traits == UndefinedPixelTrait) ||
631 (reconstruct_traits == UndefinedPixelTrait) ||
632 ((reconstruct_traits & UpdatePixelTrait) == 0))
634 distance=QuantumScale*fabs(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
636 channel_distortion[i]+=distance;
637 channel_distortion[CompositePixelChannel]+=distance;
639 p+=GetPixelChannels(image);
640 q+=GetPixelChannels(reconstruct_image);
642 #if defined(MAGICKCORE_OPENMP_SUPPORT)
643 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
645 for (i=0; i <= MaxPixelChannels; i++)
646 distortion[i]+=channel_distortion[i];
648 reconstruct_view=DestroyCacheView(reconstruct_view);
649 image_view=DestroyCacheView(image_view);
650 for (i=0; i <= MaxPixelChannels; i++)
651 distortion[i]/=((double) image->columns*image->rows);
652 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
656 static MagickBooleanType GetMeanErrorPerPixel(Image *image,
657 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
678 image_view=AcquireVirtualCacheView(image,exception);
679 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
680 for (y=0; y < (ssize_t) image->rows; y++)
682 register const Quantum
689 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
690 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
692 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
697 for (x=0; x < (ssize_t) image->columns; x++)
706 if (GetPixelReadMask(image,p) == 0)
708 p+=GetPixelChannels(image);
709 q+=GetPixelChannels(reconstruct_image);
712 Sa=QuantumScale*GetPixelAlpha(image,p);
713 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
714 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
719 PixelChannel channel=GetPixelChannelChannel(image,i);
720 PixelTrait traits=GetPixelChannelTraits(image,channel);
721 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
723 if ((traits == UndefinedPixelTrait) ||
724 (reconstruct_traits == UndefinedPixelTrait) ||
725 ((reconstruct_traits & UpdatePixelTrait) == 0))
727 distance=fabs((double) (Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
729 distortion[i]+=distance;
730 distortion[CompositePixelChannel]+=distance;
731 mean_error+=distance*distance;
732 if (distance > maximum_error)
733 maximum_error=distance;
736 p+=GetPixelChannels(image);
737 q+=GetPixelChannels(reconstruct_image);
740 reconstruct_view=DestroyCacheView(reconstruct_view);
741 image_view=DestroyCacheView(image_view);
742 image->error.mean_error_per_pixel=distortion[CompositePixelChannel]/area;
743 image->error.normalized_mean_error=QuantumScale*QuantumScale*mean_error/area;
744 image->error.normalized_maximum_error=QuantumScale*maximum_error;
748 static MagickBooleanType GetMeanSquaredDistortion(const Image *image,
749 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
765 image_view=AcquireVirtualCacheView(image,exception);
766 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
767 #if defined(MAGICKCORE_OPENMP_SUPPORT)
768 #pragma omp parallel for schedule(static,4) shared(status) \
769 magick_threads(image,image,image->rows,1)
771 for (y=0; y < (ssize_t) image->rows; y++)
774 channel_distortion[MaxPixelChannels+1];
776 register const Quantum
784 if (status == MagickFalse)
786 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
787 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
789 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
794 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
795 for (x=0; x < (ssize_t) image->columns; x++)
804 if (GetPixelReadMask(image,p) == 0)
806 p+=GetPixelChannels(image);
807 q+=GetPixelChannels(reconstruct_image);
810 Sa=QuantumScale*GetPixelAlpha(image,p);
811 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
812 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
817 PixelChannel channel=GetPixelChannelChannel(image,i);
818 PixelTrait traits=GetPixelChannelTraits(image,channel);
819 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
821 if ((traits == UndefinedPixelTrait) ||
822 (reconstruct_traits == UndefinedPixelTrait) ||
823 ((reconstruct_traits & UpdatePixelTrait) == 0))
825 distance=QuantumScale*(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
827 channel_distortion[i]+=distance*distance;
828 channel_distortion[CompositePixelChannel]+=distance*distance;
830 p+=GetPixelChannels(image);
831 q+=GetPixelChannels(reconstruct_image);
833 #if defined(MAGICKCORE_OPENMP_SUPPORT)
834 #pragma omp critical (MagickCore_GetMeanSquaredError)
836 for (i=0; i <= MaxPixelChannels; i++)
837 distortion[i]+=channel_distortion[i];
839 reconstruct_view=DestroyCacheView(reconstruct_view);
840 image_view=DestroyCacheView(image_view);
841 for (i=0; i <= MaxPixelChannels; i++)
842 distortion[i]/=((double) image->columns*image->rows);
843 distortion[CompositePixelChannel]/=GetImageChannels(image);
847 static MagickBooleanType GetNormalizedCrossCorrelationDistortion(
848 const Image *image,const Image *reconstruct_image,double *distortion,
849 ExceptionInfo *exception)
851 #define SimilarityImageTag "Similarity/Image"
859 *reconstruct_statistics;
877 Normalize to account for variation due to lighting and exposure condition.
879 image_statistics=GetImageStatistics(image,exception);
880 reconstruct_statistics=GetImageStatistics(reconstruct_image,exception);
881 if ((image_statistics == (ChannelStatistics *) NULL) ||
882 (reconstruct_statistics == (ChannelStatistics *) NULL))
884 if (image_statistics != (ChannelStatistics *) NULL)
885 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
887 if (reconstruct_statistics != (ChannelStatistics *) NULL)
888 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
889 reconstruct_statistics);
894 for (i=0; i <= MaxPixelChannels; i++)
896 area=1.0/((double) image->columns*image->rows);
897 image_view=AcquireVirtualCacheView(image,exception);
898 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
899 for (y=0; y < (ssize_t) image->rows; y++)
901 register const Quantum
908 if (status == MagickFalse)
910 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
911 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
913 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
918 for (x=0; x < (ssize_t) image->columns; x++)
927 if (GetPixelReadMask(image,p) == 0)
929 p+=GetPixelChannels(image);
930 q+=GetPixelChannels(reconstruct_image);
933 Sa=QuantumScale*GetPixelAlpha(image,p);
934 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
935 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
937 PixelChannel channel=GetPixelChannelChannel(image,i);
938 PixelTrait traits=GetPixelChannelTraits(image,channel);
939 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
941 if ((traits == UndefinedPixelTrait) ||
942 (reconstruct_traits == UndefinedPixelTrait) ||
943 ((reconstruct_traits & UpdatePixelTrait) == 0))
945 distortion[i]+=area*QuantumScale*(Sa*p[i]-image_statistics[i].mean)*
946 (Da*GetPixelChannel(reconstruct_image,channel,q)-
947 reconstruct_statistics[channel].mean);
949 p+=GetPixelChannels(image);
950 q+=GetPixelChannels(reconstruct_image);
952 if (image->progress_monitor != (MagickProgressMonitor) NULL)
957 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
959 if (proceed == MagickFalse)
963 reconstruct_view=DestroyCacheView(reconstruct_view);
964 image_view=DestroyCacheView(image_view);
966 Divide by the standard deviation.
968 distortion[CompositePixelChannel]=0.0;
969 for (i=0; i < MaxPixelChannels; i++)
974 PixelChannel channel=GetPixelChannelChannel(image,i);
975 gamma=image_statistics[i].standard_deviation*
976 reconstruct_statistics[channel].standard_deviation;
977 gamma=PerceptibleReciprocal(gamma);
978 distortion[i]=QuantumRange*gamma*distortion[i];
979 distortion[CompositePixelChannel]+=distortion[i]*distortion[i];
981 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]/
982 GetImageChannels(image));
986 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
987 reconstruct_statistics);
988 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
993 static MagickBooleanType GetPeakAbsoluteDistortion(const Image *image,
994 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1007 image_view=AcquireVirtualCacheView(image,exception);
1008 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1009 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1010 #pragma omp parallel for schedule(static,4) shared(status) \
1011 magick_threads(image,image,image->rows,1)
1013 for (y=0; y < (ssize_t) image->rows; y++)
1016 channel_distortion[MaxPixelChannels+1];
1018 register const Quantum
1026 if (status == MagickFalse)
1028 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1029 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1031 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
1036 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
1037 for (x=0; x < (ssize_t) image->columns; x++)
1046 if (GetPixelReadMask(image,p) == 0)
1048 p+=GetPixelChannels(image);
1049 q+=GetPixelChannels(reconstruct_image);
1052 Sa=QuantumScale*GetPixelAlpha(image,p);
1053 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
1054 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1059 PixelChannel channel=GetPixelChannelChannel(image,i);
1060 PixelTrait traits=GetPixelChannelTraits(image,channel);
1061 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
1063 if ((traits == UndefinedPixelTrait) ||
1064 (reconstruct_traits == UndefinedPixelTrait) ||
1065 ((reconstruct_traits & UpdatePixelTrait) == 0))
1067 distance=QuantumScale*fabs(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
1069 if (distance > channel_distortion[i])
1070 channel_distortion[i]=distance;
1071 if (distance > channel_distortion[CompositePixelChannel])
1072 channel_distortion[CompositePixelChannel]=distance;
1074 p+=GetPixelChannels(image);
1075 q+=GetPixelChannels(reconstruct_image);
1077 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1078 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
1080 for (i=0; i <= MaxPixelChannels; i++)
1081 if (channel_distortion[i] > distortion[i])
1082 distortion[i]=channel_distortion[i];
1084 reconstruct_view=DestroyCacheView(reconstruct_view);
1085 image_view=DestroyCacheView(image_view);
1089 static MagickBooleanType GetPeakSignalToNoiseRatio(const Image *image,
1090 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1098 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
1099 for (i=0; i <= MaxPixelChannels; i++)
1100 distortion[i]=20.0*log10((double) 1.0/sqrt(distortion[i]));
1104 static MagickBooleanType GetRootMeanSquaredDistortion(const Image *image,
1105 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1113 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
1114 for (i=0; i <= MaxPixelChannels; i++)
1115 distortion[i]=sqrt(distortion[i]);
1119 MagickExport MagickBooleanType GetImageDistortion(Image *image,
1120 const Image *reconstruct_image,const MetricType metric,double *distortion,
1121 ExceptionInfo *exception)
1124 *channel_distortion;
1132 assert(image != (Image *) NULL);
1133 assert(image->signature == MagickSignature);
1134 if (image->debug != MagickFalse)
1135 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1136 assert(reconstruct_image != (const Image *) NULL);
1137 assert(reconstruct_image->signature == MagickSignature);
1138 assert(distortion != (double *) NULL);
1140 if (image->debug != MagickFalse)
1141 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1142 if ((reconstruct_image->columns != image->columns) ||
1143 (reconstruct_image->rows != image->rows))
1144 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1146 Get image distortion.
1148 length=MaxPixelChannels+1;
1149 channel_distortion=(double *) AcquireQuantumMemory(length,
1150 sizeof(*channel_distortion));
1151 if (channel_distortion == (double *) NULL)
1152 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1153 (void) ResetMagickMemory(channel_distortion,0,length*
1154 sizeof(*channel_distortion));
1157 case AbsoluteErrorMetric:
1159 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1163 case FuzzErrorMetric:
1165 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1169 case MeanAbsoluteErrorMetric:
1171 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1172 channel_distortion,exception);
1175 case MeanErrorPerPixelMetric:
1177 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1181 case MeanSquaredErrorMetric:
1183 status=GetMeanSquaredDistortion(image,reconstruct_image,
1184 channel_distortion,exception);
1187 case NormalizedCrossCorrelationErrorMetric:
1190 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1191 channel_distortion,exception);
1194 case PeakAbsoluteErrorMetric:
1196 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1197 channel_distortion,exception);
1200 case PeakSignalToNoiseRatioMetric:
1202 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1203 channel_distortion,exception);
1206 case RootMeanSquaredErrorMetric:
1208 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1209 channel_distortion,exception);
1213 *distortion=channel_distortion[CompositePixelChannel];
1214 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1215 (void) FormatImageProperty(image,"distortion","%.20g",*distortion);
1220 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1224 % G e t I m a g e D i s t o r t i o n s %
1228 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1230 % GetImageDistortions() compares the pixel channels of an image to a
1231 % reconstructed image and returns the specified distortion metric for each
1234 % The format of the GetImageDistortions method is:
1236 % double *GetImageDistortions(const Image *image,
1237 % const Image *reconstruct_image,const MetricType metric,
1238 % ExceptionInfo *exception)
1240 % A description of each parameter follows:
1242 % o image: the image.
1244 % o reconstruct_image: the reconstruct image.
1246 % o metric: the metric.
1248 % o exception: return any errors or warnings in this structure.
1251 MagickExport double *GetImageDistortions(Image *image,
1252 const Image *reconstruct_image,const MetricType metric,
1253 ExceptionInfo *exception)
1256 *channel_distortion;
1264 assert(image != (Image *) NULL);
1265 assert(image->signature == MagickSignature);
1266 if (image->debug != MagickFalse)
1267 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1268 assert(reconstruct_image != (const Image *) NULL);
1269 assert(reconstruct_image->signature == MagickSignature);
1270 if (image->debug != MagickFalse)
1271 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1272 if ((reconstruct_image->columns != image->columns) ||
1273 (reconstruct_image->rows != image->rows))
1275 (void) ThrowMagickException(exception,GetMagickModule(),ImageError,
1276 "ImageSizeDiffers","`%s'",image->filename);
1277 return((double *) NULL);
1280 Get image distortion.
1282 length=MaxPixelChannels+1UL;
1283 channel_distortion=(double *) AcquireQuantumMemory(length,
1284 sizeof(*channel_distortion));
1285 if (channel_distortion == (double *) NULL)
1286 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1287 (void) ResetMagickMemory(channel_distortion,0,length*
1288 sizeof(*channel_distortion));
1292 case AbsoluteErrorMetric:
1294 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1298 case FuzzErrorMetric:
1300 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1304 case MeanAbsoluteErrorMetric:
1306 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1307 channel_distortion,exception);
1310 case MeanErrorPerPixelMetric:
1312 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1316 case MeanSquaredErrorMetric:
1318 status=GetMeanSquaredDistortion(image,reconstruct_image,
1319 channel_distortion,exception);
1322 case NormalizedCrossCorrelationErrorMetric:
1325 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1326 channel_distortion,exception);
1329 case PeakAbsoluteErrorMetric:
1331 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1332 channel_distortion,exception);
1335 case PeakSignalToNoiseRatioMetric:
1337 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1338 channel_distortion,exception);
1341 case RootMeanSquaredErrorMetric:
1343 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1344 channel_distortion,exception);
1348 if (status == MagickFalse)
1350 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1351 return((double *) NULL);
1353 return(channel_distortion);
1357 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1361 % I s I m a g e s E q u a l %
1365 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1367 % IsImagesEqual() measures the difference between colors at each pixel
1368 % location of two images. A value other than 0 means the colors match
1369 % exactly. Otherwise an error measure is computed by summing over all
1370 % pixels in an image the distance squared in RGB space between each image
1371 % pixel and its corresponding pixel in the reconstruct image. The error
1372 % measure is assigned to these image members:
1374 % o mean_error_per_pixel: The mean error for any single pixel in
1377 % o normalized_mean_error: The normalized mean quantization error for
1378 % any single pixel in the image. This distance measure is normalized to
1379 % a range between 0 and 1. It is independent of the range of red, green,
1380 % and blue values in the image.
1382 % o normalized_maximum_error: The normalized maximum quantization
1383 % error for any single pixel in the image. This distance measure is
1384 % normalized to a range between 0 and 1. It is independent of the range
1385 % of red, green, and blue values in your image.
1387 % A small normalized mean square error, accessed as
1388 % image->normalized_mean_error, suggests the images are very similar in
1389 % spatial layout and color.
1391 % The format of the IsImagesEqual method is:
1393 % MagickBooleanType IsImagesEqual(Image *image,
1394 % const Image *reconstruct_image,ExceptionInfo *exception)
1396 % A description of each parameter follows.
1398 % o image: the image.
1400 % o reconstruct_image: the reconstruct image.
1402 % o exception: return any errors or warnings in this structure.
1405 MagickExport MagickBooleanType IsImagesEqual(Image *image,
1406 const Image *reconstruct_image,ExceptionInfo *exception)
1419 mean_error_per_pixel;
1424 assert(image != (Image *) NULL);
1425 assert(image->signature == MagickSignature);
1426 assert(reconstruct_image != (const Image *) NULL);
1427 assert(reconstruct_image->signature == MagickSignature);
1428 if ((reconstruct_image->columns != image->columns) ||
1429 (reconstruct_image->rows != image->rows))
1430 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1433 mean_error_per_pixel=0.0;
1435 image_view=AcquireVirtualCacheView(image,exception);
1436 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1437 for (y=0; y < (ssize_t) image->rows; y++)
1439 register const Quantum
1446 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1447 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1449 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1451 for (x=0; x < (ssize_t) image->columns; x++)
1456 if (GetPixelReadMask(image,p) == 0)
1458 p+=GetPixelChannels(image);
1459 q+=GetPixelChannels(reconstruct_image);
1462 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1467 PixelChannel channel=GetPixelChannelChannel(image,i);
1468 PixelTrait traits=GetPixelChannelTraits(image,channel);
1469 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
1471 if ((traits == UndefinedPixelTrait) ||
1472 (reconstruct_traits == UndefinedPixelTrait) ||
1473 ((reconstruct_traits & UpdatePixelTrait) == 0))
1475 distance=fabs(p[i]-(double) GetPixelChannel(reconstruct_image,
1477 mean_error_per_pixel+=distance;
1478 mean_error+=distance*distance;
1479 if (distance > maximum_error)
1480 maximum_error=distance;
1483 p+=GetPixelChannels(image);
1484 q+=GetPixelChannels(reconstruct_image);
1487 reconstruct_view=DestroyCacheView(reconstruct_view);
1488 image_view=DestroyCacheView(image_view);
1489 image->error.mean_error_per_pixel=(double) (mean_error_per_pixel/area);
1490 image->error.normalized_mean_error=(double) (QuantumScale*QuantumScale*
1492 image->error.normalized_maximum_error=(double) (QuantumScale*maximum_error);
1493 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
1498 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1502 % S i m i l a r i t y I m a g e %
1506 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1508 % SimilarityImage() compares the reference image of the image and returns the
1509 % best match offset. In addition, it returns a similarity image such that an
1510 % exact match location is completely white and if none of the pixels match,
1511 % black, otherwise some gray level in-between.
1513 % The format of the SimilarityImageImage method is:
1515 % Image *SimilarityImage(const Image *image,const Image *reference,
1516 % const MetricType metric,const double similarity_threshold,
1517 % RectangleInfo *offset,double *similarity,ExceptionInfo *exception)
1519 % A description of each parameter follows:
1521 % o image: the image.
1523 % o reference: find an area of the image that closely resembles this image.
1525 % o metric: the metric.
1527 % o similarity_threshold: minimum distortion for (sub)image match.
1529 % o offset: the best match offset of the reference image within the image.
1531 % o similarity: the computed similarity between the images.
1533 % o exception: return any errors or warnings in this structure.
1537 static double GetSimilarityMetric(const Image *image,const Image *reference,
1538 const MetricType metric,const ssize_t x_offset,const ssize_t y_offset,
1539 ExceptionInfo *exception)
1553 SetGeometry(reference,&geometry);
1554 geometry.x=x_offset;
1555 geometry.y=y_offset;
1556 similarity_image=CropImage(image,&geometry,exception);
1557 if (similarity_image == (Image *) NULL)
1560 status=GetImageDistortion(similarity_image,reference,metric,&distortion,
1562 similarity_image=DestroyImage(similarity_image);
1563 if (status == MagickFalse)
1568 MagickExport Image *SimilarityImage(Image *image,const Image *reference,
1569 const MetricType metric,const double similarity_threshold,
1570 RectangleInfo *offset,double *similarity_metric,ExceptionInfo *exception)
1572 #define SimilarityImageTag "Similarity/Image"
1589 assert(image != (const Image *) NULL);
1590 assert(image->signature == MagickSignature);
1591 if (image->debug != MagickFalse)
1592 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1593 assert(exception != (ExceptionInfo *) NULL);
1594 assert(exception->signature == MagickSignature);
1595 assert(offset != (RectangleInfo *) NULL);
1596 SetGeometry(reference,offset);
1597 *similarity_metric=1.0;
1598 if ((reference->columns > image->columns) || (reference->rows > image->rows))
1599 ThrowImageException(ImageError,"ImageSizeDiffers");
1600 similarity_image=CloneImage(image,image->columns-reference->columns+1,
1601 image->rows-reference->rows+1,MagickTrue,exception);
1602 if (similarity_image == (Image *) NULL)
1603 return((Image *) NULL);
1604 status=SetImageStorageClass(similarity_image,DirectClass,exception);
1605 if (status == MagickFalse)
1607 similarity_image=DestroyImage(similarity_image);
1608 return((Image *) NULL);
1610 (void) SetImageAlphaChannel(similarity_image,DeactivateAlphaChannel,
1613 Measure similarity of reference image against image.
1617 similarity_view=AcquireAuthenticCacheView(similarity_image,exception);
1618 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1619 #pragma omp parallel for schedule(static,4) \
1620 shared(progress,status,similarity_metric) \
1621 magick_threads(image,image,image->rows,1)
1623 for (y=0; y < (ssize_t) (image->rows-reference->rows+1); y++)
1634 if (status == MagickFalse)
1636 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1637 #pragma omp flush(similarity_metric)
1639 if (*similarity_metric <= similarity_threshold)
1641 q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
1643 if (q == (Quantum *) NULL)
1648 for (x=0; x < (ssize_t) (image->columns-reference->columns+1); x++)
1653 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1654 #pragma omp flush(similarity_metric)
1656 if (*similarity_metric <= similarity_threshold)
1658 similarity=GetSimilarityMetric(image,reference,metric,x,y,exception);
1659 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1660 #pragma omp critical (MagickCore_SimilarityImage)
1662 if (similarity < *similarity_metric)
1666 *similarity_metric=similarity;
1668 if (GetPixelReadMask(similarity_image,q) == 0)
1670 q+=GetPixelChannels(similarity_image);
1673 for (i=0; i < (ssize_t) GetPixelChannels(similarity_image); i++)
1675 PixelChannel channel=GetPixelChannelChannel(image,i);
1676 PixelTrait traits=GetPixelChannelTraits(image,channel);
1677 PixelTrait similarity_traits=GetPixelChannelTraits(similarity_image,
1679 if ((traits == UndefinedPixelTrait) ||
1680 (similarity_traits == UndefinedPixelTrait) ||
1681 ((similarity_traits & UpdatePixelTrait) == 0))
1683 SetPixelChannel(similarity_image,channel,ClampToQuantum(QuantumRange-
1684 QuantumRange*similarity),q);
1686 q+=GetPixelChannels(similarity_image);
1688 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
1690 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1695 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1696 #pragma omp critical (MagickCore_SimilarityImage)
1698 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
1700 if (proceed == MagickFalse)
1704 similarity_view=DestroyCacheView(similarity_view);
1705 if (status == MagickFalse)
1706 similarity_image=DestroyImage(similarity_image);
1707 return(similarity_image);