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 % https://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 status=SetImageStorageClass(highlight_image,DirectClass,exception);
199 if (status == MagickFalse)
201 difference_image=DestroyImage(difference_image);
202 highlight_image=DestroyImage(highlight_image);
203 return((Image *) NULL);
205 (void) SetImageMask(highlight_image,ReadPixelMask,(Image *) NULL,exception);
206 (void) SetImageAlphaChannel(highlight_image,OpaqueAlphaChannel,exception);
207 (void) QueryColorCompliance("#f1001ecc",AllCompliance,&highlight,exception);
208 artifact=GetImageArtifact(image,"compare: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,"compare: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,"compare: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) reduction(+:area)
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) reduction(+:area)
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) reduction(+:area)
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]=INFINITY;
1218 distortion[i]=20.0*MagickLog10(1.0/sqrt(distortion[i]));
1222 static MagickBooleanType GetPerceptualHashDistortion(const Image *image,
1223 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1225 ChannelPerceptualHash
1239 Compute perceptual hash in the sRGB colorspace.
1241 channel_phash=GetImagePerceptualHash(image,exception);
1242 if (channel_phash == (ChannelPerceptualHash *) NULL)
1243 return(MagickFalse);
1244 reconstruct_phash=GetImagePerceptualHash(reconstruct_image,exception);
1245 if (reconstruct_phash == (ChannelPerceptualHash *) NULL)
1247 channel_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(
1249 return(MagickFalse);
1251 artifact=GetImageArtifact(image,"phash:normalize");
1252 normalize=(artifact == (const char *) NULL) ||
1253 (IsStringTrue(artifact) == MagickFalse) ? MagickFalse : MagickTrue;
1254 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1255 #pragma omp parallel for schedule(static,4)
1257 for (channel=0; channel < MaxPixelChannels; channel++)
1266 for (i=0; i < MaximumNumberOfImageMoments; i++)
1275 for (j=0; j < (ssize_t) channel_phash[0].number_colorspaces; j++)
1277 alpha=channel_phash[channel].phash[j][i];
1278 beta=reconstruct_phash[channel].phash[j][i];
1279 if (normalize == MagickFalse)
1280 difference+=(beta-alpha)*(beta-alpha);
1282 difference=sqrt((beta-alpha)*(beta-alpha)/
1283 channel_phash[0].number_channels);
1286 distortion[channel]+=difference;
1287 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1288 #pragma omp critical (MagickCore_GetPerceptualHashDistortion)
1290 distortion[CompositePixelChannel]+=difference;
1295 reconstruct_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(
1297 channel_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(channel_phash);
1301 static MagickBooleanType GetRootMeanSquaredDistortion(const Image *image,
1302 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1310 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
1311 for (i=0; i <= MaxPixelChannels; i++)
1312 distortion[i]=sqrt(distortion[i]);
1316 MagickExport MagickBooleanType GetImageDistortion(Image *image,
1317 const Image *reconstruct_image,const MetricType metric,double *distortion,
1318 ExceptionInfo *exception)
1321 *channel_distortion;
1329 assert(image != (Image *) NULL);
1330 assert(image->signature == MagickCoreSignature);
1331 if (image->debug != MagickFalse)
1332 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1333 assert(reconstruct_image != (const Image *) NULL);
1334 assert(reconstruct_image->signature == MagickCoreSignature);
1335 assert(distortion != (double *) NULL);
1337 if (image->debug != MagickFalse)
1338 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1340 Get image distortion.
1342 length=MaxPixelChannels+1;
1343 channel_distortion=(double *) AcquireQuantumMemory(length,
1344 sizeof(*channel_distortion));
1345 if (channel_distortion == (double *) NULL)
1346 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1347 (void) ResetMagickMemory(channel_distortion,0,length*
1348 sizeof(*channel_distortion));
1351 case AbsoluteErrorMetric:
1353 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1357 case FuzzErrorMetric:
1359 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1363 case MeanAbsoluteErrorMetric:
1365 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1366 channel_distortion,exception);
1369 case MeanErrorPerPixelErrorMetric:
1371 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1375 case MeanSquaredErrorMetric:
1377 status=GetMeanSquaredDistortion(image,reconstruct_image,
1378 channel_distortion,exception);
1381 case NormalizedCrossCorrelationErrorMetric:
1384 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1385 channel_distortion,exception);
1388 case PeakAbsoluteErrorMetric:
1390 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1391 channel_distortion,exception);
1394 case PeakSignalToNoiseRatioErrorMetric:
1396 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1397 channel_distortion,exception);
1400 case PerceptualHashErrorMetric:
1402 status=GetPerceptualHashDistortion(image,reconstruct_image,
1403 channel_distortion,exception);
1406 case RootMeanSquaredErrorMetric:
1408 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1409 channel_distortion,exception);
1413 *distortion=channel_distortion[CompositePixelChannel];
1414 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1415 (void) FormatImageProperty(image,"distortion","%.*g",GetMagickPrecision(),
1421 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1425 % G e t I m a g e D i s t o r t i o n s %
1429 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1431 % GetImageDistortions() compares the pixel channels of an image to a
1432 % reconstructed image and returns the specified distortion metric for each
1435 % The format of the GetImageDistortions method is:
1437 % double *GetImageDistortions(const Image *image,
1438 % const Image *reconstruct_image,const MetricType metric,
1439 % ExceptionInfo *exception)
1441 % A description of each parameter follows:
1443 % o image: the image.
1445 % o reconstruct_image: the reconstruct image.
1447 % o metric: the metric.
1449 % o exception: return any errors or warnings in this structure.
1452 MagickExport double *GetImageDistortions(Image *image,
1453 const Image *reconstruct_image,const MetricType metric,
1454 ExceptionInfo *exception)
1457 *channel_distortion;
1465 assert(image != (Image *) NULL);
1466 assert(image->signature == MagickCoreSignature);
1467 if (image->debug != MagickFalse)
1468 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1469 assert(reconstruct_image != (const Image *) NULL);
1470 assert(reconstruct_image->signature == MagickCoreSignature);
1471 if (image->debug != MagickFalse)
1472 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1474 Get image distortion.
1476 length=MaxPixelChannels+1UL;
1477 channel_distortion=(double *) AcquireQuantumMemory(length,
1478 sizeof(*channel_distortion));
1479 if (channel_distortion == (double *) NULL)
1480 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1481 (void) ResetMagickMemory(channel_distortion,0,length*
1482 sizeof(*channel_distortion));
1486 case AbsoluteErrorMetric:
1488 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1492 case FuzzErrorMetric:
1494 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1498 case MeanAbsoluteErrorMetric:
1500 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1501 channel_distortion,exception);
1504 case MeanErrorPerPixelErrorMetric:
1506 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1510 case MeanSquaredErrorMetric:
1512 status=GetMeanSquaredDistortion(image,reconstruct_image,
1513 channel_distortion,exception);
1516 case NormalizedCrossCorrelationErrorMetric:
1519 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1520 channel_distortion,exception);
1523 case PeakAbsoluteErrorMetric:
1525 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1526 channel_distortion,exception);
1529 case PeakSignalToNoiseRatioErrorMetric:
1531 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1532 channel_distortion,exception);
1535 case PerceptualHashErrorMetric:
1537 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1538 channel_distortion,exception);
1541 case RootMeanSquaredErrorMetric:
1543 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1544 channel_distortion,exception);
1548 if (status == MagickFalse)
1550 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1551 return((double *) NULL);
1553 return(channel_distortion);
1557 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1561 % I s I m a g e s E q u a l %
1565 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1567 % IsImagesEqual() compare the pixels of two images and returns immediately
1568 % if any pixel is not identical.
1570 % The format of the IsImagesEqual method is:
1572 % MagickBooleanType IsImagesEqual(const Image *image,
1573 % const Image *reconstruct_image,ExceptionInfo *exception)
1575 % A description of each parameter follows.
1577 % o image: the image.
1579 % o reconstruct_image: the reconstruct image.
1581 % o exception: return any errors or warnings in this structure.
1584 MagickExport MagickBooleanType IsImagesEqual(const Image *image,
1585 const Image *reconstruct_image,ExceptionInfo *exception)
1598 assert(image != (Image *) NULL);
1599 assert(image->signature == MagickCoreSignature);
1600 assert(reconstruct_image != (const Image *) NULL);
1601 assert(reconstruct_image->signature == MagickCoreSignature);
1602 rows=MagickMax(image->rows,reconstruct_image->rows);
1603 columns=MagickMax(image->columns,reconstruct_image->columns);
1604 image_view=AcquireVirtualCacheView(image,exception);
1605 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1606 for (y=0; y < (ssize_t) rows; y++)
1608 register const Quantum
1615 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1616 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1617 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1619 for (x=0; x < (ssize_t) columns; x++)
1624 if (GetPixelWriteMask(image,p) == 0)
1626 p+=GetPixelChannels(image);
1627 q+=GetPixelChannels(reconstruct_image);
1630 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1635 PixelChannel channel=GetPixelChannelChannel(image,i);
1636 PixelTrait traits=GetPixelChannelTraits(image,channel);
1637 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
1639 if ((traits == UndefinedPixelTrait) ||
1640 (reconstruct_traits == UndefinedPixelTrait) ||
1641 ((reconstruct_traits & UpdatePixelTrait) == 0))
1643 distance=fabs(p[i]-(double) GetPixelChannel(reconstruct_image,
1645 if (distance >= MagickEpsilon)
1648 if (i < (ssize_t) GetPixelChannels(image))
1650 p+=GetPixelChannels(image);
1651 q+=GetPixelChannels(reconstruct_image);
1653 if (x < (ssize_t) columns)
1656 reconstruct_view=DestroyCacheView(reconstruct_view);
1657 image_view=DestroyCacheView(image_view);
1658 return(y < (ssize_t) rows ? MagickFalse : MagickTrue);
1662 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1666 % S e t I m a g e C o l o r M e t r i c %
1670 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1672 % SetImageColorMetric() measures the difference between colors at each pixel
1673 % location of two images. A value other than 0 means the colors match
1674 % exactly. Otherwise an error measure is computed by summing over all
1675 % pixels in an image the distance squared in RGB space between each image
1676 % pixel and its corresponding pixel in the reconstruct image. The error
1677 % measure is assigned to these image members:
1679 % o mean_error_per_pixel: The mean error for any single pixel in
1682 % o normalized_mean_error: The normalized mean quantization error for
1683 % any single pixel in the image. This distance measure is normalized to
1684 % a range between 0 and 1. It is independent of the range of red, green,
1685 % and blue values in the image.
1687 % o normalized_maximum_error: The normalized maximum quantization
1688 % error for any single pixel in the image. This distance measure is
1689 % normalized to a range between 0 and 1. It is independent of the range
1690 % of red, green, and blue values in your image.
1692 % A small normalized mean square error, accessed as
1693 % image->normalized_mean_error, suggests the images are very similar in
1694 % spatial layout and color.
1696 % The format of the SetImageColorMetric method is:
1698 % MagickBooleanType SetImageColorMetric(Image *image,
1699 % const Image *reconstruct_image,ExceptionInfo *exception)
1701 % A description of each parameter follows.
1703 % o image: the image.
1705 % o reconstruct_image: the reconstruct image.
1707 % o exception: return any errors or warnings in this structure.
1710 MagickExport MagickBooleanType SetImageColorMetric(Image *image,
1711 const Image *reconstruct_image,ExceptionInfo *exception)
1721 mean_error_per_pixel;
1733 assert(image != (Image *) NULL);
1734 assert(image->signature == MagickCoreSignature);
1735 assert(reconstruct_image != (const Image *) NULL);
1736 assert(reconstruct_image->signature == MagickCoreSignature);
1739 mean_error_per_pixel=0.0;
1741 rows=MagickMax(image->rows,reconstruct_image->rows);
1742 columns=MagickMax(image->columns,reconstruct_image->columns);
1743 image_view=AcquireVirtualCacheView(image,exception);
1744 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1745 for (y=0; y < (ssize_t) rows; y++)
1747 register const Quantum
1754 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1755 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1756 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1758 for (x=0; x < (ssize_t) columns; x++)
1763 if (GetPixelWriteMask(image,p) == 0)
1765 p+=GetPixelChannels(image);
1766 q+=GetPixelChannels(reconstruct_image);
1769 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1774 PixelChannel channel=GetPixelChannelChannel(image,i);
1775 PixelTrait traits=GetPixelChannelTraits(image,channel);
1776 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
1778 if ((traits == UndefinedPixelTrait) ||
1779 (reconstruct_traits == UndefinedPixelTrait) ||
1780 ((reconstruct_traits & UpdatePixelTrait) == 0))
1782 distance=fabs(p[i]-(double) GetPixelChannel(reconstruct_image,
1784 if (distance >= MagickEpsilon)
1786 mean_error_per_pixel+=distance;
1787 mean_error+=distance*distance;
1788 if (distance > maximum_error)
1789 maximum_error=distance;
1793 p+=GetPixelChannels(image);
1794 q+=GetPixelChannels(reconstruct_image);
1797 reconstruct_view=DestroyCacheView(reconstruct_view);
1798 image_view=DestroyCacheView(image_view);
1799 image->error.mean_error_per_pixel=(double) (mean_error_per_pixel/area);
1800 image->error.normalized_mean_error=(double) (QuantumScale*QuantumScale*
1802 image->error.normalized_maximum_error=(double) (QuantumScale*maximum_error);
1803 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
1808 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1812 % S i m i l a r i t y I m a g e %
1816 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1818 % SimilarityImage() compares the reference image of the image and returns the
1819 % best match offset. In addition, it returns a similarity image such that an
1820 % exact match location is completely white and if none of the pixels match,
1821 % black, otherwise some gray level in-between.
1823 % The format of the SimilarityImageImage method is:
1825 % Image *SimilarityImage(const Image *image,const Image *reference,
1826 % const MetricType metric,const double similarity_threshold,
1827 % RectangleInfo *offset,double *similarity,ExceptionInfo *exception)
1829 % A description of each parameter follows:
1831 % o image: the image.
1833 % o reference: find an area of the image that closely resembles this image.
1835 % o metric: the metric.
1837 % o similarity_threshold: minimum distortion for (sub)image match.
1839 % o offset: the best match offset of the reference image within the image.
1841 % o similarity: the computed similarity between the images.
1843 % o exception: return any errors or warnings in this structure.
1847 static double GetSimilarityMetric(const Image *image,const Image *reference,
1848 const MetricType metric,const ssize_t x_offset,const ssize_t y_offset,
1849 ExceptionInfo *exception)
1863 SetGeometry(reference,&geometry);
1864 geometry.x=x_offset;
1865 geometry.y=y_offset;
1866 similarity_image=CropImage(image,&geometry,exception);
1867 if (similarity_image == (Image *) NULL)
1870 status=GetImageDistortion(similarity_image,reference,metric,&distortion,
1872 similarity_image=DestroyImage(similarity_image);
1873 if (status == MagickFalse)
1878 MagickExport Image *SimilarityImage(const Image *image,const Image *reference,
1879 const MetricType metric,const double similarity_threshold,
1880 RectangleInfo *offset,double *similarity_metric,ExceptionInfo *exception)
1882 #define SimilarityImageTag "Similarity/Image"
1899 assert(image != (const Image *) NULL);
1900 assert(image->signature == MagickCoreSignature);
1901 if (image->debug != MagickFalse)
1902 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1903 assert(exception != (ExceptionInfo *) NULL);
1904 assert(exception->signature == MagickCoreSignature);
1905 assert(offset != (RectangleInfo *) NULL);
1906 SetGeometry(reference,offset);
1907 *similarity_metric=MagickMaximumValue;
1908 similarity_image=CloneImage(image,image->columns-reference->columns+1,
1909 image->rows-reference->rows+1,MagickTrue,exception);
1910 if (similarity_image == (Image *) NULL)
1911 return((Image *) NULL);
1912 status=SetImageStorageClass(similarity_image,DirectClass,exception);
1913 if (status == MagickFalse)
1915 similarity_image=DestroyImage(similarity_image);
1916 return((Image *) NULL);
1918 (void) SetImageAlphaChannel(similarity_image,DeactivateAlphaChannel,
1921 Measure similarity of reference image against image.
1925 similarity_view=AcquireAuthenticCacheView(similarity_image,exception);
1926 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1927 #pragma omp parallel for schedule(static,4) \
1928 shared(progress,status,similarity_metric) \
1929 magick_threads(image,image,image->rows,1)
1931 for (y=0; y < (ssize_t) (image->rows-reference->rows+1); y++)
1942 if (status == MagickFalse)
1944 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1945 #pragma omp flush(similarity_metric)
1947 if (*similarity_metric <= similarity_threshold)
1949 q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
1951 if (q == (Quantum *) NULL)
1956 for (x=0; x < (ssize_t) (image->columns-reference->columns+1); x++)
1961 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1962 #pragma omp flush(similarity_metric)
1964 if (*similarity_metric <= similarity_threshold)
1966 similarity=GetSimilarityMetric(image,reference,metric,x,y,exception);
1967 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1968 #pragma omp critical (MagickCore_SimilarityImage)
1970 if ((metric == NormalizedCrossCorrelationErrorMetric) ||
1971 (metric == UndefinedErrorMetric))
1972 similarity=1.0-similarity;
1973 if (similarity < *similarity_metric)
1977 *similarity_metric=similarity;
1979 if (metric == PerceptualHashErrorMetric)
1980 similarity=MagickMin(0.01*similarity,1.0);
1981 if (GetPixelWriteMask(similarity_image,q) == 0)
1983 SetPixelBackgoundColor(similarity_image,q);
1984 q+=GetPixelChannels(similarity_image);
1987 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1989 PixelChannel channel=GetPixelChannelChannel(image,i);
1990 PixelTrait traits=GetPixelChannelTraits(image,channel);
1991 PixelTrait similarity_traits=GetPixelChannelTraits(similarity_image,
1993 if ((traits == UndefinedPixelTrait) ||
1994 (similarity_traits == UndefinedPixelTrait) ||
1995 ((similarity_traits & UpdatePixelTrait) == 0))
1997 SetPixelChannel(similarity_image,channel,ClampToQuantum(QuantumRange-
1998 QuantumRange*similarity),q);
2000 q+=GetPixelChannels(similarity_image);
2002 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
2004 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2009 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2010 #pragma omp critical (MagickCore_SimilarityImage)
2012 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
2014 if (proceed == MagickFalse)
2018 similarity_view=DestroyCacheView(similarity_view);
2019 (void) SetImageAlphaChannel(similarity_image,OffAlphaChannel,exception);
2020 if (status == MagickFalse)
2021 similarity_image=DestroyImage(similarity_image);
2022 return(similarity_image);