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-2017 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/attribute.h"
46 #include "MagickCore/cache-view.h"
47 #include "MagickCore/channel.h"
48 #include "MagickCore/client.h"
49 #include "MagickCore/color.h"
50 #include "MagickCore/color-private.h"
51 #include "MagickCore/colorspace.h"
52 #include "MagickCore/colorspace-private.h"
53 #include "MagickCore/compare.h"
54 #include "MagickCore/composite-private.h"
55 #include "MagickCore/constitute.h"
56 #include "MagickCore/exception-private.h"
57 #include "MagickCore/geometry.h"
58 #include "MagickCore/image-private.h"
59 #include "MagickCore/list.h"
60 #include "MagickCore/log.h"
61 #include "MagickCore/memory_.h"
62 #include "MagickCore/monitor.h"
63 #include "MagickCore/monitor-private.h"
64 #include "MagickCore/option.h"
65 #include "MagickCore/pixel-accessor.h"
66 #include "MagickCore/property.h"
67 #include "MagickCore/resource_.h"
68 #include "MagickCore/string_.h"
69 #include "MagickCore/statistic.h"
70 #include "MagickCore/thread-private.h"
71 #include "MagickCore/transform.h"
72 #include "MagickCore/utility.h"
73 #include "MagickCore/version.h"
76 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
80 % C o m p a r e I m a g e %
84 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
86 % CompareImages() compares one or more pixel channels of an image to a
87 % reconstructed image and returns the difference image.
89 % The format of the CompareImages method is:
91 % Image *CompareImages(const Image *image,const Image *reconstruct_image,
92 % const MetricType metric,double *distortion,ExceptionInfo *exception)
94 % A description of each parameter follows:
98 % o reconstruct_image: the reconstruct image.
100 % o metric: the metric.
102 % o distortion: the computed distortion between the images.
104 % o exception: return any errors or warnings in this structure.
108 static size_t GetImageChannels(const Image *image)
117 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
119 PixelChannel channel=GetPixelChannelChannel(image,i);
120 PixelTrait traits=GetPixelChannelTraits(image,channel);
121 if ((traits & UpdatePixelTrait) != 0)
124 return(channels == 0 ? (size_t) 1 : channels);
127 MagickExport Image *CompareImages(Image *image,const Image *reconstruct_image,
128 const MetricType metric,double *distortion,ExceptionInfo *exception)
164 assert(image != (Image *) NULL);
165 assert(image->signature == MagickCoreSignature);
166 if (image->debug != MagickFalse)
167 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
168 assert(reconstruct_image != (const Image *) NULL);
169 assert(reconstruct_image->signature == MagickCoreSignature);
170 assert(distortion != (double *) NULL);
172 if (image->debug != MagickFalse)
173 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
174 status=GetImageDistortion(image,reconstruct_image,metric,distortion,
176 if (status == MagickFalse)
177 return((Image *) NULL);
178 columns=MagickMax(image->columns,reconstruct_image->columns);
179 rows=MagickMax(image->rows,reconstruct_image->rows);
180 SetGeometry(image,&geometry);
181 geometry.width=columns;
182 geometry.height=rows;
183 clone_image=CloneImage(image,0,0,MagickTrue,exception);
184 if (clone_image == (Image *) NULL)
185 return((Image *) NULL);
186 (void) SetImageMask(clone_image,ReadPixelMask,(Image *) NULL,exception);
187 difference_image=ExtentImage(clone_image,&geometry,exception);
188 clone_image=DestroyImage(clone_image);
189 if (difference_image == (Image *) NULL)
190 return((Image *) NULL);
191 (void) SetImageAlphaChannel(difference_image,OpaqueAlphaChannel,exception);
192 highlight_image=CloneImage(image,columns,rows,MagickTrue,exception);
193 if (highlight_image == (Image *) NULL)
195 difference_image=DestroyImage(difference_image);
196 return((Image *) NULL);
198 (void) SetImageMask(highlight_image,ReadPixelMask,(Image *) NULL,exception);
199 status=SetImageStorageClass(highlight_image,DirectClass,exception);
200 if (status == MagickFalse)
202 difference_image=DestroyImage(difference_image);
203 highlight_image=DestroyImage(highlight_image);
204 return((Image *) NULL);
206 (void) SetImageAlphaChannel(highlight_image,OpaqueAlphaChannel,exception);
207 (void) QueryColorCompliance("#f1001ecc",AllCompliance,&highlight,exception);
208 artifact=GetImageArtifact(image,"highlight-color");
209 if (artifact != (const char *) NULL)
210 (void) QueryColorCompliance(artifact,AllCompliance,&highlight,exception);
211 (void) QueryColorCompliance("#ffffffcc",AllCompliance,&lowlight,exception);
212 artifact=GetImageArtifact(image,"lowlight-color");
213 if (artifact != (const char *) NULL)
214 (void) QueryColorCompliance(artifact,AllCompliance,&lowlight,exception);
215 (void) QueryColorCompliance("#888888cc",AllCompliance,&masklight,exception);
216 artifact=GetImageArtifact(image,"masklight-color");
217 if (artifact != (const char *) NULL)
218 (void) QueryColorCompliance(artifact,AllCompliance,&masklight,exception);
220 Generate difference image.
223 fuzz=GetFuzzyColorDistance(image,reconstruct_image);
224 image_view=AcquireVirtualCacheView(image,exception);
225 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
226 highlight_view=AcquireAuthenticCacheView(highlight_image,exception);
227 #if defined(MAGICKCORE_OPENMP_SUPPORT)
228 #pragma omp parallel for schedule(static,4) shared(status) \
229 magick_threads(image,highlight_image,rows,1)
231 for (y=0; y < (ssize_t) rows; y++)
236 register const Quantum
246 if (status == MagickFalse)
248 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
249 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
250 r=QueueCacheViewAuthenticPixels(highlight_view,0,y,columns,1,exception);
251 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL) ||
252 (r == (Quantum *) NULL))
257 for (x=0; x < (ssize_t) columns; x++)
269 if ((GetPixelReadMask(image,p) == 0) ||
270 (GetPixelReadMask(reconstruct_image,q) == 0))
272 SetPixelViaPixelInfo(highlight_image,&masklight,r);
273 p+=GetPixelChannels(image);
274 q+=GetPixelChannels(reconstruct_image);
275 r+=GetPixelChannels(highlight_image);
278 difference=MagickFalse;
279 Sa=QuantumScale*GetPixelAlpha(image,p);
280 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
281 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
286 PixelChannel channel=GetPixelChannelChannel(image,i);
287 PixelTrait traits=GetPixelChannelTraits(image,channel);
288 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
290 if ((traits == UndefinedPixelTrait) ||
291 (reconstruct_traits == UndefinedPixelTrait) ||
292 ((reconstruct_traits & UpdatePixelTrait) == 0))
294 distance=Sa*p[i]-Da*GetPixelChannel(reconstruct_image,channel,q);
295 if ((distance*distance) > fuzz)
297 difference=MagickTrue;
301 if (difference == MagickFalse)
302 SetPixelViaPixelInfo(highlight_image,&lowlight,r);
304 SetPixelViaPixelInfo(highlight_image,&highlight,r);
305 p+=GetPixelChannels(image);
306 q+=GetPixelChannels(reconstruct_image);
307 r+=GetPixelChannels(highlight_image);
309 sync=SyncCacheViewAuthenticPixels(highlight_view,exception);
310 if (sync == MagickFalse)
313 highlight_view=DestroyCacheView(highlight_view);
314 reconstruct_view=DestroyCacheView(reconstruct_view);
315 image_view=DestroyCacheView(image_view);
316 (void) CompositeImage(difference_image,highlight_image,image->compose,
317 MagickTrue,0,0,exception);
318 (void) SetImageAlphaChannel(difference_image,OffAlphaChannel,exception);
319 highlight_image=DestroyImage(highlight_image);
320 if (status == MagickFalse)
321 difference_image=DestroyImage(difference_image);
322 return(difference_image);
326 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
330 % G e t I m a g e D i s t o r t i o n %
334 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
336 % GetImageDistortion() compares one or more pixel channels of an image to a
337 % reconstructed image and returns the specified distortion metric.
339 % The format of the GetImageDistortion method is:
341 % MagickBooleanType GetImageDistortion(const Image *image,
342 % const Image *reconstruct_image,const MetricType metric,
343 % double *distortion,ExceptionInfo *exception)
345 % A description of each parameter follows:
347 % o image: the image.
349 % o reconstruct_image: the reconstruct image.
351 % o metric: the metric.
353 % o distortion: the computed distortion between the images.
355 % o exception: return any errors or warnings in this structure.
359 static MagickBooleanType GetAbsoluteDistortion(const Image *image,
360 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
380 Compute the absolute difference in pixels between two images.
383 fuzz=GetFuzzyColorDistance(image,reconstruct_image);
384 rows=MagickMax(image->rows,reconstruct_image->rows);
385 columns=MagickMax(image->columns,reconstruct_image->columns);
386 image_view=AcquireVirtualCacheView(image,exception);
387 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
388 #if defined(MAGICKCORE_OPENMP_SUPPORT)
389 #pragma omp parallel for schedule(static,4) shared(status) \
390 magick_threads(image,image,rows,1)
392 for (y=0; y < (ssize_t) rows; y++)
395 channel_distortion[MaxPixelChannels+1];
397 register const Quantum
405 if (status == MagickFalse)
407 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
408 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
409 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
414 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
415 for (x=0; x < (ssize_t) columns; x++)
427 if (GetPixelWriteMask(image,p) == 0)
429 p+=GetPixelChannels(image);
430 q+=GetPixelChannels(reconstruct_image);
433 difference=MagickFalse;
434 Sa=QuantumScale*GetPixelAlpha(image,p);
435 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
436 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
441 PixelChannel channel=GetPixelChannelChannel(image,i);
442 PixelTrait traits=GetPixelChannelTraits(image,channel);
443 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
445 if ((traits == UndefinedPixelTrait) ||
446 (reconstruct_traits == UndefinedPixelTrait) ||
447 ((reconstruct_traits & UpdatePixelTrait) == 0))
449 distance=Sa*p[i]-Da*GetPixelChannel(reconstruct_image,channel,q);
450 if ((distance*distance) > fuzz)
452 channel_distortion[i]++;
453 difference=MagickTrue;
456 if (difference != MagickFalse)
457 channel_distortion[CompositePixelChannel]++;
458 p+=GetPixelChannels(image);
459 q+=GetPixelChannels(reconstruct_image);
461 #if defined(MAGICKCORE_OPENMP_SUPPORT)
462 #pragma omp critical (MagickCore_GetAbsoluteError)
464 for (j=0; j <= MaxPixelChannels; j++)
465 distortion[j]+=channel_distortion[j];
467 reconstruct_view=DestroyCacheView(reconstruct_view);
468 image_view=DestroyCacheView(image_view);
472 static MagickBooleanType GetFuzzDistortion(const Image *image,
473 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
496 rows=MagickMax(image->rows,reconstruct_image->rows);
497 columns=MagickMax(image->columns,reconstruct_image->columns);
499 image_view=AcquireVirtualCacheView(image,exception);
500 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
501 #if defined(MAGICKCORE_OPENMP_SUPPORT)
502 #pragma omp parallel for schedule(static,4) shared(status) \
503 magick_threads(image,image,rows,1)
505 for (y=0; y < (ssize_t) rows; y++)
508 channel_distortion[MaxPixelChannels+1];
510 register const Quantum
517 if (status == MagickFalse)
519 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
520 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
521 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
526 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
527 for (x=0; x < (ssize_t) columns; x++)
536 if ((GetPixelReadMask(image,p) == 0) ||
537 (GetPixelReadMask(reconstruct_image,q) == 0))
539 p+=GetPixelChannels(image);
540 q+=GetPixelChannels(reconstruct_image);
543 Sa=QuantumScale*GetPixelAlpha(image,p);
544 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
545 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
550 PixelChannel channel=GetPixelChannelChannel(image,i);
551 PixelTrait traits=GetPixelChannelTraits(image,channel);
552 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
554 if ((traits == UndefinedPixelTrait) ||
555 (reconstruct_traits == UndefinedPixelTrait) ||
556 ((reconstruct_traits & UpdatePixelTrait) == 0))
558 distance=QuantumScale*(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
560 channel_distortion[i]+=distance*distance;
561 channel_distortion[CompositePixelChannel]+=distance*distance;
564 p+=GetPixelChannels(image);
565 q+=GetPixelChannels(reconstruct_image);
567 #if defined(MAGICKCORE_OPENMP_SUPPORT)
568 #pragma omp critical (MagickCore_GetFuzzDistortion)
570 for (j=0; j <= MaxPixelChannels; j++)
571 distortion[j]+=channel_distortion[j];
573 reconstruct_view=DestroyCacheView(reconstruct_view);
574 image_view=DestroyCacheView(image_view);
575 area=PerceptibleReciprocal(area);
576 for (j=0; j <= MaxPixelChannels; j++)
578 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
579 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]);
583 static MagickBooleanType GetMeanAbsoluteDistortion(const Image *image,
584 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
607 rows=MagickMax(image->rows,reconstruct_image->rows);
608 columns=MagickMax(image->columns,reconstruct_image->columns);
610 image_view=AcquireVirtualCacheView(image,exception);
611 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
612 #if defined(MAGICKCORE_OPENMP_SUPPORT)
613 #pragma omp parallel for schedule(static,4) shared(status) \
614 magick_threads(image,image,rows,1)
616 for (y=0; y < (ssize_t) rows; y++)
619 channel_distortion[MaxPixelChannels+1];
621 register const Quantum
628 if (status == MagickFalse)
630 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
631 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
632 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
637 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
638 for (x=0; x < (ssize_t) columns; x++)
647 if ((GetPixelReadMask(image,p) == 0) ||
648 (GetPixelReadMask(reconstruct_image,q) == 0))
650 p+=GetPixelChannels(image);
651 q+=GetPixelChannels(reconstruct_image);
654 Sa=QuantumScale*GetPixelAlpha(image,p);
655 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
656 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
661 PixelChannel channel=GetPixelChannelChannel(image,i);
662 PixelTrait traits=GetPixelChannelTraits(image,channel);
663 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
665 if ((traits == UndefinedPixelTrait) ||
666 (reconstruct_traits == UndefinedPixelTrait) ||
667 ((reconstruct_traits & UpdatePixelTrait) == 0))
669 distance=QuantumScale*fabs(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
671 channel_distortion[i]+=distance;
672 channel_distortion[CompositePixelChannel]+=distance;
675 p+=GetPixelChannels(image);
676 q+=GetPixelChannels(reconstruct_image);
678 #if defined(MAGICKCORE_OPENMP_SUPPORT)
679 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
681 for (j=0; j <= MaxPixelChannels; j++)
682 distortion[j]+=channel_distortion[j];
684 reconstruct_view=DestroyCacheView(reconstruct_view);
685 image_view=DestroyCacheView(image_view);
686 area=PerceptibleReciprocal(area);
687 for (j=0; j <= MaxPixelChannels; j++)
689 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
693 static MagickBooleanType GetMeanErrorPerPixel(Image *image,
694 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
719 rows=MagickMax(image->rows,reconstruct_image->rows);
720 columns=MagickMax(image->columns,reconstruct_image->columns);
721 image_view=AcquireVirtualCacheView(image,exception);
722 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
723 for (y=0; y < (ssize_t) rows; y++)
725 register const Quantum
732 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
733 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
734 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
739 for (x=0; x < (ssize_t) columns; x++)
748 if ((GetPixelReadMask(image,p) == 0) ||
749 (GetPixelReadMask(reconstruct_image,q) == 0))
751 p+=GetPixelChannels(image);
752 q+=GetPixelChannels(reconstruct_image);
755 Sa=QuantumScale*GetPixelAlpha(image,p);
756 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
757 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
762 PixelChannel channel=GetPixelChannelChannel(image,i);
763 PixelTrait traits=GetPixelChannelTraits(image,channel);
764 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
766 if ((traits == UndefinedPixelTrait) ||
767 (reconstruct_traits == UndefinedPixelTrait) ||
768 ((reconstruct_traits & UpdatePixelTrait) == 0))
770 distance=fabs(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,channel,q));
771 distortion[i]+=distance;
772 distortion[CompositePixelChannel]+=distance;
773 mean_error+=distance*distance;
774 if (distance > maximum_error)
775 maximum_error=distance;
778 p+=GetPixelChannels(image);
779 q+=GetPixelChannels(reconstruct_image);
782 reconstruct_view=DestroyCacheView(reconstruct_view);
783 image_view=DestroyCacheView(image_view);
784 image->error.mean_error_per_pixel=distortion[CompositePixelChannel]/area;
785 image->error.normalized_mean_error=QuantumScale*QuantumScale*mean_error/area;
786 image->error.normalized_maximum_error=QuantumScale*maximum_error;
790 static MagickBooleanType GetMeanSquaredDistortion(const Image *image,
791 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
814 rows=MagickMax(image->rows,reconstruct_image->rows);
815 columns=MagickMax(image->columns,reconstruct_image->columns);
817 image_view=AcquireVirtualCacheView(image,exception);
818 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
819 #if defined(MAGICKCORE_OPENMP_SUPPORT)
820 #pragma omp parallel for schedule(static,4) shared(status) \
821 magick_threads(image,image,rows,1)
823 for (y=0; y < (ssize_t) rows; y++)
826 channel_distortion[MaxPixelChannels+1];
828 register const Quantum
835 if (status == MagickFalse)
837 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
838 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
839 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
844 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
845 for (x=0; x < (ssize_t) columns; x++)
854 if ((GetPixelReadMask(image,p) == 0) ||
855 (GetPixelReadMask(reconstruct_image,q) == 0))
857 p+=GetPixelChannels(image);
858 q+=GetPixelChannels(reconstruct_image);
861 Sa=QuantumScale*GetPixelAlpha(image,p);
862 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
863 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
868 PixelChannel channel=GetPixelChannelChannel(image,i);
869 PixelTrait traits=GetPixelChannelTraits(image,channel);
870 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
872 if ((traits == UndefinedPixelTrait) ||
873 (reconstruct_traits == UndefinedPixelTrait) ||
874 ((reconstruct_traits & UpdatePixelTrait) == 0))
876 distance=QuantumScale*(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
878 channel_distortion[i]+=distance*distance;
879 channel_distortion[CompositePixelChannel]+=distance*distance;
882 p+=GetPixelChannels(image);
883 q+=GetPixelChannels(reconstruct_image);
885 #if defined(MAGICKCORE_OPENMP_SUPPORT)
886 #pragma omp critical (MagickCore_GetMeanSquaredError)
888 for (j=0; j <= MaxPixelChannels; j++)
889 distortion[j]+=channel_distortion[j];
891 reconstruct_view=DestroyCacheView(reconstruct_view);
892 image_view=DestroyCacheView(image_view);
893 area=PerceptibleReciprocal(area);
894 for (j=0; j <= MaxPixelChannels; j++)
896 distortion[CompositePixelChannel]/=GetImageChannels(image);
900 static MagickBooleanType GetNormalizedCrossCorrelationDistortion(
901 const Image *image,const Image *reconstruct_image,double *distortion,
902 ExceptionInfo *exception)
904 #define SimilarityImageTag "Similarity/Image"
912 *reconstruct_statistics;
934 Normalize to account for variation due to lighting and exposure condition.
936 image_statistics=GetImageStatistics(image,exception);
937 reconstruct_statistics=GetImageStatistics(reconstruct_image,exception);
938 if ((image_statistics == (ChannelStatistics *) NULL) ||
939 (reconstruct_statistics == (ChannelStatistics *) NULL))
941 if (image_statistics != (ChannelStatistics *) NULL)
942 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
944 if (reconstruct_statistics != (ChannelStatistics *) NULL)
945 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
946 reconstruct_statistics);
951 for (i=0; i <= MaxPixelChannels; i++)
953 rows=MagickMax(image->rows,reconstruct_image->rows);
954 columns=MagickMax(image->columns,reconstruct_image->columns);
956 image_view=AcquireVirtualCacheView(image,exception);
957 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
958 for (y=0; y < (ssize_t) rows; y++)
960 register const Quantum
967 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
968 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
969 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
974 for (x=0; x < (ssize_t) columns; x++)
976 if ((GetPixelReadMask(image,p) == 0) ||
977 (GetPixelReadMask(reconstruct_image,q) == 0))
979 p+=GetPixelChannels(image);
980 q+=GetPixelChannels(reconstruct_image);
984 p+=GetPixelChannels(image);
985 q+=GetPixelChannels(reconstruct_image);
988 area=PerceptibleReciprocal(area);
989 for (y=0; y < (ssize_t) rows; y++)
991 register const Quantum
998 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
999 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1000 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
1005 for (x=0; x < (ssize_t) columns; x++)
1011 if ((GetPixelReadMask(image,p) == 0) ||
1012 (GetPixelReadMask(reconstruct_image,q) == 0))
1014 p+=GetPixelChannels(image);
1015 q+=GetPixelChannels(reconstruct_image);
1018 Sa=QuantumScale*(image->alpha_trait != UndefinedPixelTrait ?
1019 GetPixelAlpha(image,p) : OpaqueAlpha);
1020 Da=QuantumScale*(reconstruct_image->alpha_trait != UndefinedPixelTrait ?
1021 GetPixelAlpha(reconstruct_image,q) : OpaqueAlpha);
1022 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1024 PixelChannel channel=GetPixelChannelChannel(image,i);
1025 PixelTrait traits=GetPixelChannelTraits(image,channel);
1026 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
1028 if ((traits == UndefinedPixelTrait) ||
1029 (reconstruct_traits == UndefinedPixelTrait) ||
1030 ((reconstruct_traits & UpdatePixelTrait) == 0))
1032 if (channel == AlphaPixelChannel)
1034 distortion[i]+=area*QuantumScale*(p[i]-
1035 image_statistics[channel].mean)*(GetPixelChannel(
1036 reconstruct_image,channel,q)-
1037 reconstruct_statistics[channel].mean);
1041 distortion[i]+=area*QuantumScale*(Sa*p[i]-
1042 image_statistics[channel].mean)*(Da*GetPixelChannel(
1043 reconstruct_image,channel,q)-
1044 reconstruct_statistics[channel].mean);
1047 p+=GetPixelChannels(image);
1048 q+=GetPixelChannels(reconstruct_image);
1050 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1055 proceed=SetImageProgress(image,SimilarityImageTag,progress++,rows);
1056 if (proceed == MagickFalse)
1063 reconstruct_view=DestroyCacheView(reconstruct_view);
1064 image_view=DestroyCacheView(image_view);
1066 Divide by the standard deviation.
1068 distortion[CompositePixelChannel]=0.0;
1069 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1074 PixelChannel channel=GetPixelChannelChannel(image,i);
1075 gamma=image_statistics[channel].standard_deviation*
1076 reconstruct_statistics[channel].standard_deviation;
1077 gamma=PerceptibleReciprocal(gamma);
1078 distortion[i]=QuantumRange*gamma*distortion[i];
1079 distortion[CompositePixelChannel]+=distortion[i]*distortion[i];
1081 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]/
1082 GetImageChannels(image));
1086 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1087 reconstruct_statistics);
1088 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1093 static MagickBooleanType GetPeakAbsoluteDistortion(const Image *image,
1094 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1111 rows=MagickMax(image->rows,reconstruct_image->rows);
1112 columns=MagickMax(image->columns,reconstruct_image->columns);
1113 image_view=AcquireVirtualCacheView(image,exception);
1114 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1115 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1116 #pragma omp parallel for schedule(static,4) shared(status) \
1117 magick_threads(image,image,rows,1)
1119 for (y=0; y < (ssize_t) rows; y++)
1122 channel_distortion[MaxPixelChannels+1];
1124 register const Quantum
1132 if (status == MagickFalse)
1134 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1135 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1136 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
1141 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
1142 for (x=0; x < (ssize_t) columns; x++)
1151 if ((GetPixelReadMask(image,p) == 0) ||
1152 (GetPixelReadMask(reconstruct_image,q) == 0))
1154 p+=GetPixelChannels(image);
1155 q+=GetPixelChannels(reconstruct_image);
1158 Sa=QuantumScale*GetPixelAlpha(image,p);
1159 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
1160 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1165 PixelChannel channel=GetPixelChannelChannel(image,i);
1166 PixelTrait traits=GetPixelChannelTraits(image,channel);
1167 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
1169 if ((traits == UndefinedPixelTrait) ||
1170 (reconstruct_traits == UndefinedPixelTrait) ||
1171 ((reconstruct_traits & UpdatePixelTrait) == 0))
1173 distance=QuantumScale*fabs(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
1175 if (distance > channel_distortion[i])
1176 channel_distortion[i]=distance;
1177 if (distance > channel_distortion[CompositePixelChannel])
1178 channel_distortion[CompositePixelChannel]=distance;
1180 p+=GetPixelChannels(image);
1181 q+=GetPixelChannels(reconstruct_image);
1183 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1184 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
1186 for (j=0; j <= MaxPixelChannels; j++)
1187 if (channel_distortion[j] > distortion[j])
1188 distortion[j]=channel_distortion[j];
1190 reconstruct_view=DestroyCacheView(reconstruct_view);
1191 image_view=DestroyCacheView(image_view);
1195 static inline double MagickLog10(const double x)
1197 #define Log10Epsilon (1.0e-11)
1199 if (fabs(x) < Log10Epsilon)
1200 return(log10(Log10Epsilon));
1201 return(log10(fabs(x)));
1204 static MagickBooleanType GetPeakSignalToNoiseRatio(const Image *image,
1205 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1213 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
1214 for (i=0; i <= MaxPixelChannels; i++)
1215 if (fabs(distortion[i]) >= MagickEpsilon)
1216 distortion[i]=20.0*MagickLog10((double) 1.0/sqrt(distortion[i]));
1220 static MagickBooleanType GetPerceptualHashDistortion(const Image *image,
1221 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1223 ChannelPerceptualHash
1237 Compute perceptual hash in the sRGB colorspace.
1239 channel_phash=GetImagePerceptualHash(image,exception);
1240 if (channel_phash == (ChannelPerceptualHash *) NULL)
1241 return(MagickFalse);
1242 reconstruct_phash=GetImagePerceptualHash(reconstruct_image,exception);
1243 if (reconstruct_phash == (ChannelPerceptualHash *) NULL)
1245 channel_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(
1247 return(MagickFalse);
1249 artifact=GetImageArtifact(image,"phash:normalize");
1250 normalize=(artifact == (const char *) NULL) ||
1251 (IsStringTrue(artifact) == MagickFalse) ? MagickFalse : MagickTrue;
1252 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1253 #pragma omp parallel for schedule(static,4)
1255 for (channel=0; channel < MaxPixelChannels; channel++)
1264 for (i=0; i < MaximumNumberOfImageMoments; i++)
1273 for (j=0; j < (ssize_t) channel_phash[0].number_colorspaces; j++)
1275 alpha=channel_phash[channel].phash[j][i];
1276 beta=reconstruct_phash[channel].phash[j][i];
1277 if (normalize == MagickFalse)
1278 difference+=(beta-alpha)*(beta-alpha);
1280 difference=sqrt((beta-alpha)*(beta-alpha)/
1281 channel_phash[0].number_channels);
1284 distortion[channel]+=difference;
1285 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1286 #pragma omp critical (MagickCore_GetPerceptualHashDistortion)
1288 distortion[CompositePixelChannel]+=difference;
1293 reconstruct_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(
1295 channel_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(channel_phash);
1299 static MagickBooleanType GetRootMeanSquaredDistortion(const Image *image,
1300 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1308 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
1309 for (i=0; i <= MaxPixelChannels; i++)
1310 distortion[i]=sqrt(distortion[i]);
1314 MagickExport MagickBooleanType GetImageDistortion(Image *image,
1315 const Image *reconstruct_image,const MetricType metric,double *distortion,
1316 ExceptionInfo *exception)
1319 *channel_distortion;
1327 assert(image != (Image *) NULL);
1328 assert(image->signature == MagickCoreSignature);
1329 if (image->debug != MagickFalse)
1330 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1331 assert(reconstruct_image != (const Image *) NULL);
1332 assert(reconstruct_image->signature == MagickCoreSignature);
1333 assert(distortion != (double *) NULL);
1335 if (image->debug != MagickFalse)
1336 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1338 Get image distortion.
1340 length=MaxPixelChannels+1;
1341 channel_distortion=(double *) AcquireQuantumMemory(length,
1342 sizeof(*channel_distortion));
1343 if (channel_distortion == (double *) NULL)
1344 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1345 (void) ResetMagickMemory(channel_distortion,0,length*
1346 sizeof(*channel_distortion));
1349 case AbsoluteErrorMetric:
1351 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1355 case FuzzErrorMetric:
1357 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1361 case MeanAbsoluteErrorMetric:
1363 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1364 channel_distortion,exception);
1367 case MeanErrorPerPixelErrorMetric:
1369 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1373 case MeanSquaredErrorMetric:
1375 status=GetMeanSquaredDistortion(image,reconstruct_image,
1376 channel_distortion,exception);
1379 case NormalizedCrossCorrelationErrorMetric:
1382 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1383 channel_distortion,exception);
1386 case PeakAbsoluteErrorMetric:
1388 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1389 channel_distortion,exception);
1392 case PeakSignalToNoiseRatioErrorMetric:
1394 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1395 channel_distortion,exception);
1398 case PerceptualHashErrorMetric:
1400 status=GetPerceptualHashDistortion(image,reconstruct_image,
1401 channel_distortion,exception);
1404 case RootMeanSquaredErrorMetric:
1406 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1407 channel_distortion,exception);
1411 *distortion=channel_distortion[CompositePixelChannel];
1412 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1413 (void) FormatImageProperty(image,"distortion","%.*g",GetMagickPrecision(),
1419 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1423 % G e t I m a g e D i s t o r t i o n s %
1427 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1429 % GetImageDistortions() compares the pixel channels of an image to a
1430 % reconstructed image and returns the specified distortion metric for each
1433 % The format of the GetImageDistortions method is:
1435 % double *GetImageDistortions(const Image *image,
1436 % const Image *reconstruct_image,const MetricType metric,
1437 % ExceptionInfo *exception)
1439 % A description of each parameter follows:
1441 % o image: the image.
1443 % o reconstruct_image: the reconstruct image.
1445 % o metric: the metric.
1447 % o exception: return any errors or warnings in this structure.
1450 MagickExport double *GetImageDistortions(Image *image,
1451 const Image *reconstruct_image,const MetricType metric,
1452 ExceptionInfo *exception)
1455 *channel_distortion;
1463 assert(image != (Image *) NULL);
1464 assert(image->signature == MagickCoreSignature);
1465 if (image->debug != MagickFalse)
1466 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1467 assert(reconstruct_image != (const Image *) NULL);
1468 assert(reconstruct_image->signature == MagickCoreSignature);
1469 if (image->debug != MagickFalse)
1470 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1472 Get image distortion.
1474 length=MaxPixelChannels+1UL;
1475 channel_distortion=(double *) AcquireQuantumMemory(length,
1476 sizeof(*channel_distortion));
1477 if (channel_distortion == (double *) NULL)
1478 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1479 (void) ResetMagickMemory(channel_distortion,0,length*
1480 sizeof(*channel_distortion));
1484 case AbsoluteErrorMetric:
1486 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1490 case FuzzErrorMetric:
1492 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1496 case MeanAbsoluteErrorMetric:
1498 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1499 channel_distortion,exception);
1502 case MeanErrorPerPixelErrorMetric:
1504 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1508 case MeanSquaredErrorMetric:
1510 status=GetMeanSquaredDistortion(image,reconstruct_image,
1511 channel_distortion,exception);
1514 case NormalizedCrossCorrelationErrorMetric:
1517 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1518 channel_distortion,exception);
1521 case PeakAbsoluteErrorMetric:
1523 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1524 channel_distortion,exception);
1527 case PeakSignalToNoiseRatioErrorMetric:
1529 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1530 channel_distortion,exception);
1533 case PerceptualHashErrorMetric:
1535 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1536 channel_distortion,exception);
1539 case RootMeanSquaredErrorMetric:
1541 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1542 channel_distortion,exception);
1546 if (status == MagickFalse)
1548 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1549 return((double *) NULL);
1551 return(channel_distortion);
1555 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1559 % I s I m a g e s E q u a l %
1563 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1565 % IsImagesEqual() compare the pixels of two images and returns immediately
1566 % if any pixel is not identical.
1568 % The format of the IsImagesEqual method is:
1570 % MagickBooleanType IsImagesEqual(const Image *image,
1571 % const Image *reconstruct_image,ExceptionInfo *exception)
1573 % A description of each parameter follows.
1575 % o image: the image.
1577 % o reconstruct_image: the reconstruct image.
1579 % o exception: return any errors or warnings in this structure.
1582 MagickExport MagickBooleanType IsImagesEqual(const Image *image,
1583 const Image *reconstruct_image,ExceptionInfo *exception)
1596 assert(image != (Image *) NULL);
1597 assert(image->signature == MagickCoreSignature);
1598 assert(reconstruct_image != (const Image *) NULL);
1599 assert(reconstruct_image->signature == MagickCoreSignature);
1600 rows=MagickMax(image->rows,reconstruct_image->rows);
1601 columns=MagickMax(image->columns,reconstruct_image->columns);
1602 image_view=AcquireVirtualCacheView(image,exception);
1603 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1604 for (y=0; y < (ssize_t) rows; y++)
1606 register const Quantum
1613 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1614 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1615 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1617 for (x=0; x < (ssize_t) columns; x++)
1622 if (GetPixelWriteMask(image,p) == 0)
1624 p+=GetPixelChannels(image);
1625 q+=GetPixelChannels(reconstruct_image);
1628 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1633 PixelChannel channel=GetPixelChannelChannel(image,i);
1634 PixelTrait traits=GetPixelChannelTraits(image,channel);
1635 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
1637 if ((traits == UndefinedPixelTrait) ||
1638 (reconstruct_traits == UndefinedPixelTrait) ||
1639 ((reconstruct_traits & UpdatePixelTrait) == 0))
1641 distance=fabs(p[i]-(double) GetPixelChannel(reconstruct_image,
1643 if (distance >= MagickEpsilon)
1646 if (i < (ssize_t) GetPixelChannels(image))
1648 p+=GetPixelChannels(image);
1649 q+=GetPixelChannels(reconstruct_image);
1651 if (x < (ssize_t) columns)
1654 reconstruct_view=DestroyCacheView(reconstruct_view);
1655 image_view=DestroyCacheView(image_view);
1656 return(y < (ssize_t) rows ? MagickFalse : MagickTrue);
1660 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1664 % S e t I m a g e C o l o r M e t r i c %
1668 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1670 % SetImageColorMetric() measures the difference between colors at each pixel
1671 % location of two images. A value other than 0 means the colors match
1672 % exactly. Otherwise an error measure is computed by summing over all
1673 % pixels in an image the distance squared in RGB space between each image
1674 % pixel and its corresponding pixel in the reconstruct image. The error
1675 % measure is assigned to these image members:
1677 % o mean_error_per_pixel: The mean error for any single pixel in
1680 % o normalized_mean_error: The normalized mean quantization error for
1681 % any single pixel in the image. This distance measure is normalized to
1682 % a range between 0 and 1. It is independent of the range of red, green,
1683 % and blue values in the image.
1685 % o normalized_maximum_error: The normalized maximum quantization
1686 % error for any single pixel in the image. This distance measure is
1687 % normalized to a range between 0 and 1. It is independent of the range
1688 % of red, green, and blue values in your image.
1690 % A small normalized mean square error, accessed as
1691 % image->normalized_mean_error, suggests the images are very similar in
1692 % spatial layout and color.
1694 % The format of the SetImageColorMetric method is:
1696 % MagickBooleanType SetImageColorMetric(Image *image,
1697 % const Image *reconstruct_image,ExceptionInfo *exception)
1699 % A description of each parameter follows.
1701 % o image: the image.
1703 % o reconstruct_image: the reconstruct image.
1705 % o exception: return any errors or warnings in this structure.
1708 MagickExport MagickBooleanType SetImageColorMetric(Image *image,
1709 const Image *reconstruct_image,ExceptionInfo *exception)
1719 mean_error_per_pixel;
1731 assert(image != (Image *) NULL);
1732 assert(image->signature == MagickCoreSignature);
1733 assert(reconstruct_image != (const Image *) NULL);
1734 assert(reconstruct_image->signature == MagickCoreSignature);
1737 mean_error_per_pixel=0.0;
1739 rows=MagickMax(image->rows,reconstruct_image->rows);
1740 columns=MagickMax(image->columns,reconstruct_image->columns);
1741 image_view=AcquireVirtualCacheView(image,exception);
1742 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1743 for (y=0; y < (ssize_t) rows; y++)
1745 register const Quantum
1752 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1753 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1754 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1756 for (x=0; x < (ssize_t) columns; x++)
1761 if (GetPixelWriteMask(image,p) == 0)
1763 p+=GetPixelChannels(image);
1764 q+=GetPixelChannels(reconstruct_image);
1767 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1772 PixelChannel channel=GetPixelChannelChannel(image,i);
1773 PixelTrait traits=GetPixelChannelTraits(image,channel);
1774 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
1776 if ((traits == UndefinedPixelTrait) ||
1777 (reconstruct_traits == UndefinedPixelTrait) ||
1778 ((reconstruct_traits & UpdatePixelTrait) == 0))
1780 distance=fabs(p[i]-(double) GetPixelChannel(reconstruct_image,
1782 if (distance >= MagickEpsilon)
1784 mean_error_per_pixel+=distance;
1785 mean_error+=distance*distance;
1786 if (distance > maximum_error)
1787 maximum_error=distance;
1791 p+=GetPixelChannels(image);
1792 q+=GetPixelChannels(reconstruct_image);
1795 reconstruct_view=DestroyCacheView(reconstruct_view);
1796 image_view=DestroyCacheView(image_view);
1797 image->error.mean_error_per_pixel=(double) (mean_error_per_pixel/area);
1798 image->error.normalized_mean_error=(double) (QuantumScale*QuantumScale*
1800 image->error.normalized_maximum_error=(double) (QuantumScale*maximum_error);
1801 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
1806 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1810 % S i m i l a r i t y I m a g e %
1814 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1816 % SimilarityImage() compares the reference image of the image and returns the
1817 % best match offset. In addition, it returns a similarity image such that an
1818 % exact match location is completely white and if none of the pixels match,
1819 % black, otherwise some gray level in-between.
1821 % The format of the SimilarityImageImage method is:
1823 % Image *SimilarityImage(const Image *image,const Image *reference,
1824 % const MetricType metric,const double similarity_threshold,
1825 % RectangleInfo *offset,double *similarity,ExceptionInfo *exception)
1827 % A description of each parameter follows:
1829 % o image: the image.
1831 % o reference: find an area of the image that closely resembles this image.
1833 % o metric: the metric.
1835 % o similarity_threshold: minimum distortion for (sub)image match.
1837 % o offset: the best match offset of the reference image within the image.
1839 % o similarity: the computed similarity between the images.
1841 % o exception: return any errors or warnings in this structure.
1845 static double GetSimilarityMetric(const Image *image,const Image *reference,
1846 const MetricType metric,const ssize_t x_offset,const ssize_t y_offset,
1847 ExceptionInfo *exception)
1861 SetGeometry(reference,&geometry);
1862 geometry.x=x_offset;
1863 geometry.y=y_offset;
1864 similarity_image=CropImage(image,&geometry,exception);
1865 if (similarity_image == (Image *) NULL)
1868 status=GetImageDistortion(similarity_image,reference,metric,&distortion,
1870 similarity_image=DestroyImage(similarity_image);
1871 if (status == MagickFalse)
1876 MagickExport Image *SimilarityImage(const Image *image,const Image *reference,
1877 const MetricType metric,const double similarity_threshold,
1878 RectangleInfo *offset,double *similarity_metric,ExceptionInfo *exception)
1880 #define SimilarityImageTag "Similarity/Image"
1897 assert(image != (const Image *) NULL);
1898 assert(image->signature == MagickCoreSignature);
1899 if (image->debug != MagickFalse)
1900 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1901 assert(exception != (ExceptionInfo *) NULL);
1902 assert(exception->signature == MagickCoreSignature);
1903 assert(offset != (RectangleInfo *) NULL);
1904 SetGeometry(reference,offset);
1905 *similarity_metric=MagickMaximumValue;
1906 similarity_image=CloneImage(image,image->columns-reference->columns+1,
1907 image->rows-reference->rows+1,MagickTrue,exception);
1908 if (similarity_image == (Image *) NULL)
1909 return((Image *) NULL);
1910 status=SetImageStorageClass(similarity_image,DirectClass,exception);
1911 if (status == MagickFalse)
1913 similarity_image=DestroyImage(similarity_image);
1914 return((Image *) NULL);
1916 (void) SetImageAlphaChannel(similarity_image,DeactivateAlphaChannel,
1919 Measure similarity of reference image against image.
1923 similarity_view=AcquireAuthenticCacheView(similarity_image,exception);
1924 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1925 #pragma omp parallel for schedule(static,4) \
1926 shared(progress,status,similarity_metric) \
1927 magick_threads(image,image,image->rows,1)
1929 for (y=0; y < (ssize_t) (image->rows-reference->rows+1); y++)
1940 if (status == MagickFalse)
1942 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1943 #pragma omp flush(similarity_metric)
1945 if (*similarity_metric <= similarity_threshold)
1947 q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
1949 if (q == (Quantum *) NULL)
1954 for (x=0; x < (ssize_t) (image->columns-reference->columns+1); x++)
1959 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1960 #pragma omp flush(similarity_metric)
1962 if (*similarity_metric <= similarity_threshold)
1964 similarity=GetSimilarityMetric(image,reference,metric,x,y,exception);
1965 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1966 #pragma omp critical (MagickCore_SimilarityImage)
1968 if ((metric == NormalizedCrossCorrelationErrorMetric) ||
1969 (metric == UndefinedErrorMetric))
1970 similarity=1.0-similarity;
1971 if (similarity < *similarity_metric)
1975 *similarity_metric=similarity;
1977 if (metric == PerceptualHashErrorMetric)
1978 similarity=MagickMin(0.01*similarity,1.0);
1979 if (GetPixelWriteMask(similarity_image,q) == 0)
1981 SetPixelBackgoundColor(similarity_image,q);
1982 q+=GetPixelChannels(similarity_image);
1985 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1987 PixelChannel channel=GetPixelChannelChannel(image,i);
1988 PixelTrait traits=GetPixelChannelTraits(image,channel);
1989 PixelTrait similarity_traits=GetPixelChannelTraits(similarity_image,
1991 if ((traits == UndefinedPixelTrait) ||
1992 (similarity_traits == UndefinedPixelTrait) ||
1993 ((similarity_traits & UpdatePixelTrait) == 0))
1995 SetPixelChannel(similarity_image,channel,ClampToQuantum(QuantumRange-
1996 QuantumRange*similarity),q);
1998 q+=GetPixelChannels(similarity_image);
2000 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
2002 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2007 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2008 #pragma omp critical (MagickCore_SimilarityImage)
2010 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
2012 if (proceed == MagickFalse)
2016 similarity_view=DestroyCacheView(similarity_view);
2017 if (status == MagickFalse)
2018 similarity_image=DestroyImage(similarity_image);
2019 return(similarity_image);