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/string-private.h"
71 #include "MagickCore/thread-private.h"
72 #include "MagickCore/transform.h"
73 #include "MagickCore/utility.h"
74 #include "MagickCore/version.h"
77 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
81 % C o m p a r e I m a g e %
85 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
87 % CompareImages() compares one or more pixel channels of an image to a
88 % reconstructed image and returns the difference image.
90 % The format of the CompareImages method is:
92 % Image *CompareImages(const Image *image,const Image *reconstruct_image,
93 % const MetricType metric,double *distortion,ExceptionInfo *exception)
95 % A description of each parameter follows:
99 % o reconstruct_image: the reconstruct image.
101 % o metric: the metric.
103 % o distortion: the computed distortion between the images.
105 % o exception: return any errors or warnings in this structure.
109 static size_t GetImageChannels(const Image *image)
118 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
120 PixelChannel channel = GetPixelChannelChannel(image,i);
121 PixelTrait traits = GetPixelChannelTraits(image,channel);
122 if ((traits & UpdatePixelTrait) != 0)
125 return(channels == 0 ? (size_t) 1 : channels);
128 MagickExport Image *CompareImages(Image *image,const Image *reconstruct_image,
129 const MetricType metric,double *distortion,ExceptionInfo *exception)
165 assert(image != (Image *) NULL);
166 assert(image->signature == MagickCoreSignature);
167 if (image->debug != MagickFalse)
168 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
169 assert(reconstruct_image != (const Image *) NULL);
170 assert(reconstruct_image->signature == MagickCoreSignature);
171 assert(distortion != (double *) NULL);
173 if (image->debug != MagickFalse)
174 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
175 status=GetImageDistortion(image,reconstruct_image,metric,distortion,
177 if (status == MagickFalse)
178 return((Image *) NULL);
179 columns=MagickMax(image->columns,reconstruct_image->columns);
180 rows=MagickMax(image->rows,reconstruct_image->rows);
181 SetGeometry(image,&geometry);
182 geometry.width=columns;
183 geometry.height=rows;
184 clone_image=CloneImage(image,0,0,MagickTrue,exception);
185 if (clone_image == (Image *) NULL)
186 return((Image *) NULL);
187 (void) SetImageMask(clone_image,ReadPixelMask,(Image *) NULL,exception);
188 difference_image=ExtentImage(clone_image,&geometry,exception);
189 clone_image=DestroyImage(clone_image);
190 if (difference_image == (Image *) NULL)
191 return((Image *) NULL);
192 (void) SetImageAlphaChannel(difference_image,OpaqueAlphaChannel,exception);
193 highlight_image=CloneImage(image,columns,rows,MagickTrue,exception);
194 if (highlight_image == (Image *) NULL)
196 difference_image=DestroyImage(difference_image);
197 return((Image *) NULL);
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) SetImageMask(highlight_image,ReadPixelMask,(Image *) NULL,exception);
207 (void) SetImageAlphaChannel(highlight_image,OpaqueAlphaChannel,exception);
208 (void) QueryColorCompliance("#f1001ecc",AllCompliance,&highlight,exception);
209 artifact=GetImageArtifact(image,"compare:highlight-color");
210 if (artifact != (const char *) NULL)
211 (void) QueryColorCompliance(artifact,AllCompliance,&highlight,exception);
212 (void) QueryColorCompliance("#ffffffcc",AllCompliance,&lowlight,exception);
213 artifact=GetImageArtifact(image,"compare:lowlight-color");
214 if (artifact != (const char *) NULL)
215 (void) QueryColorCompliance(artifact,AllCompliance,&lowlight,exception);
216 (void) QueryColorCompliance("#888888cc",AllCompliance,&masklight,exception);
217 artifact=GetImageArtifact(image,"compare:masklight-color");
218 if (artifact != (const char *) NULL)
219 (void) QueryColorCompliance(artifact,AllCompliance,&masklight,exception);
221 Generate difference image.
224 fuzz=GetFuzzyColorDistance(image,reconstruct_image);
225 image_view=AcquireVirtualCacheView(image,exception);
226 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
227 highlight_view=AcquireAuthenticCacheView(highlight_image,exception);
228 #if defined(MAGICKCORE_OPENMP_SUPPORT)
229 #pragma omp parallel for schedule(static,4) shared(status) \
230 magick_threads(image,highlight_image,rows,1)
232 for (y=0; y < (ssize_t) rows; y++)
237 register const Quantum
247 if (status == MagickFalse)
249 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
250 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
251 r=QueueCacheViewAuthenticPixels(highlight_view,0,y,columns,1,exception);
252 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL) ||
253 (r == (Quantum *) NULL))
258 for (x=0; x < (ssize_t) columns; x++)
270 if ((GetPixelReadMask(image,p) <= (QuantumRange/2)) ||
271 (GetPixelReadMask(reconstruct_image,q) <= (QuantumRange/2)))
273 SetPixelViaPixelInfo(highlight_image,&masklight,r);
274 p+=GetPixelChannels(image);
275 q+=GetPixelChannels(reconstruct_image);
276 r+=GetPixelChannels(highlight_image);
279 difference=MagickFalse;
280 Sa=QuantumScale*GetPixelAlpha(image,p);
281 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
282 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
287 PixelChannel channel = GetPixelChannelChannel(image,i);
288 PixelTrait traits = GetPixelChannelTraits(image,channel);
289 PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
291 if ((traits == UndefinedPixelTrait) ||
292 (reconstruct_traits == UndefinedPixelTrait) ||
293 ((reconstruct_traits & UpdatePixelTrait) == 0))
295 distance=Sa*p[i]-Da*GetPixelChannel(reconstruct_image,channel,q);
296 if ((distance*distance) > fuzz)
298 difference=MagickTrue;
302 if (difference == MagickFalse)
303 SetPixelViaPixelInfo(highlight_image,&lowlight,r);
305 SetPixelViaPixelInfo(highlight_image,&highlight,r);
306 p+=GetPixelChannels(image);
307 q+=GetPixelChannels(reconstruct_image);
308 r+=GetPixelChannels(highlight_image);
310 sync=SyncCacheViewAuthenticPixels(highlight_view,exception);
311 if (sync == MagickFalse)
314 highlight_view=DestroyCacheView(highlight_view);
315 reconstruct_view=DestroyCacheView(reconstruct_view);
316 image_view=DestroyCacheView(image_view);
317 (void) CompositeImage(difference_image,highlight_image,image->compose,
318 MagickTrue,0,0,exception);
319 (void) SetImageAlphaChannel(difference_image,OffAlphaChannel,exception);
320 highlight_image=DestroyImage(highlight_image);
321 if (status == MagickFalse)
322 difference_image=DestroyImage(difference_image);
323 return(difference_image);
327 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
331 % G e t I m a g e D i s t o r t i o n %
335 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
337 % GetImageDistortion() compares one or more pixel channels of an image to a
338 % reconstructed image and returns the specified distortion metric.
340 % The format of the GetImageDistortion method is:
342 % MagickBooleanType GetImageDistortion(const Image *image,
343 % const Image *reconstruct_image,const MetricType metric,
344 % double *distortion,ExceptionInfo *exception)
346 % A description of each parameter follows:
348 % o image: the image.
350 % o reconstruct_image: the reconstruct image.
352 % o metric: the metric.
354 % o distortion: the computed distortion between the images.
356 % o exception: return any errors or warnings in this structure.
360 static MagickBooleanType GetAbsoluteDistortion(const Image *image,
361 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
381 Compute the absolute difference in pixels between two images.
384 fuzz=GetFuzzyColorDistance(image,reconstruct_image);
385 rows=MagickMax(image->rows,reconstruct_image->rows);
386 columns=MagickMax(image->columns,reconstruct_image->columns);
387 image_view=AcquireVirtualCacheView(image,exception);
388 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
389 #if defined(MAGICKCORE_OPENMP_SUPPORT)
390 #pragma omp parallel for schedule(static,4) shared(status) \
391 magick_threads(image,image,rows,1)
393 for (y=0; y < (ssize_t) rows; y++)
396 channel_distortion[MaxPixelChannels+1];
398 register const Quantum
406 if (status == MagickFalse)
408 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
409 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
410 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
415 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
416 for (x=0; x < (ssize_t) columns; x++)
428 if (GetPixelWriteMask(image,p) <= (QuantumRange/2))
430 p+=GetPixelChannels(image);
431 q+=GetPixelChannels(reconstruct_image);
434 difference=MagickFalse;
435 Sa=QuantumScale*GetPixelAlpha(image,p);
436 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
437 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
442 PixelChannel channel = GetPixelChannelChannel(image,i);
443 PixelTrait traits = GetPixelChannelTraits(image,channel);
444 PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
446 if ((traits == UndefinedPixelTrait) ||
447 (reconstruct_traits == UndefinedPixelTrait) ||
448 ((reconstruct_traits & UpdatePixelTrait) == 0))
450 distance=Sa*p[i]-Da*GetPixelChannel(reconstruct_image,channel,q);
451 if ((distance*distance) > fuzz)
453 channel_distortion[i]++;
454 difference=MagickTrue;
457 if (difference != MagickFalse)
458 channel_distortion[CompositePixelChannel]++;
459 p+=GetPixelChannels(image);
460 q+=GetPixelChannels(reconstruct_image);
462 #if defined(MAGICKCORE_OPENMP_SUPPORT)
463 #pragma omp critical (MagickCore_GetAbsoluteError)
465 for (j=0; j <= MaxPixelChannels; j++)
466 distortion[j]+=channel_distortion[j];
468 reconstruct_view=DestroyCacheView(reconstruct_view);
469 image_view=DestroyCacheView(image_view);
473 static MagickBooleanType GetFuzzDistortion(const Image *image,
474 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
497 rows=MagickMax(image->rows,reconstruct_image->rows);
498 columns=MagickMax(image->columns,reconstruct_image->columns);
500 image_view=AcquireVirtualCacheView(image,exception);
501 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
502 #if defined(MAGICKCORE_OPENMP_SUPPORT)
503 #pragma omp parallel for schedule(static,4) shared(status) \
504 magick_threads(image,image,rows,1) reduction(+:area)
506 for (y=0; y < (ssize_t) rows; y++)
509 channel_distortion[MaxPixelChannels+1];
511 register const Quantum
518 if (status == MagickFalse)
520 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
521 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
522 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
527 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
528 for (x=0; x < (ssize_t) columns; x++)
537 if ((GetPixelReadMask(image,p) <= (QuantumRange/2)) ||
538 (GetPixelReadMask(reconstruct_image,q) <= (QuantumRange/2)))
540 p+=GetPixelChannels(image);
541 q+=GetPixelChannels(reconstruct_image);
544 Sa=QuantumScale*GetPixelAlpha(image,p);
545 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
546 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
551 PixelChannel channel = GetPixelChannelChannel(image,i);
552 PixelTrait traits = GetPixelChannelTraits(image,channel);
553 PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
555 if ((traits == UndefinedPixelTrait) ||
556 (reconstruct_traits == UndefinedPixelTrait) ||
557 ((reconstruct_traits & UpdatePixelTrait) == 0))
559 distance=QuantumScale*(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
561 channel_distortion[i]+=distance*distance;
562 channel_distortion[CompositePixelChannel]+=distance*distance;
565 p+=GetPixelChannels(image);
566 q+=GetPixelChannels(reconstruct_image);
568 #if defined(MAGICKCORE_OPENMP_SUPPORT)
569 #pragma omp critical (MagickCore_GetFuzzDistortion)
571 for (j=0; j <= MaxPixelChannels; j++)
572 distortion[j]+=channel_distortion[j];
574 reconstruct_view=DestroyCacheView(reconstruct_view);
575 image_view=DestroyCacheView(image_view);
576 area=PerceptibleReciprocal(area);
577 for (j=0; j <= MaxPixelChannels; j++)
579 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
580 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]);
584 static MagickBooleanType GetMeanAbsoluteDistortion(const Image *image,
585 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
608 rows=MagickMax(image->rows,reconstruct_image->rows);
609 columns=MagickMax(image->columns,reconstruct_image->columns);
611 image_view=AcquireVirtualCacheView(image,exception);
612 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
613 #if defined(MAGICKCORE_OPENMP_SUPPORT)
614 #pragma omp parallel for schedule(static,4) shared(status) \
615 magick_threads(image,image,rows,1) reduction(+:area)
617 for (y=0; y < (ssize_t) rows; y++)
620 channel_distortion[MaxPixelChannels+1];
622 register const Quantum
629 if (status == MagickFalse)
631 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
632 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
633 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
638 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
639 for (x=0; x < (ssize_t) columns; x++)
648 if ((GetPixelReadMask(image,p) <= (QuantumRange/2)) ||
649 (GetPixelReadMask(reconstruct_image,q) <= (QuantumRange/2)))
651 p+=GetPixelChannels(image);
652 q+=GetPixelChannels(reconstruct_image);
655 Sa=QuantumScale*GetPixelAlpha(image,p);
656 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
657 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
662 PixelChannel channel = GetPixelChannelChannel(image,i);
663 PixelTrait traits = GetPixelChannelTraits(image,channel);
664 PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
666 if ((traits == UndefinedPixelTrait) ||
667 (reconstruct_traits == UndefinedPixelTrait) ||
668 ((reconstruct_traits & UpdatePixelTrait) == 0))
670 distance=QuantumScale*fabs(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
672 channel_distortion[i]+=distance;
673 channel_distortion[CompositePixelChannel]+=distance;
676 p+=GetPixelChannels(image);
677 q+=GetPixelChannels(reconstruct_image);
679 #if defined(MAGICKCORE_OPENMP_SUPPORT)
680 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
682 for (j=0; j <= MaxPixelChannels; j++)
683 distortion[j]+=channel_distortion[j];
685 reconstruct_view=DestroyCacheView(reconstruct_view);
686 image_view=DestroyCacheView(image_view);
687 area=PerceptibleReciprocal(area);
688 for (j=0; j <= MaxPixelChannels; j++)
690 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
694 static MagickBooleanType GetMeanErrorPerPixel(Image *image,
695 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
720 rows=MagickMax(image->rows,reconstruct_image->rows);
721 columns=MagickMax(image->columns,reconstruct_image->columns);
722 image_view=AcquireVirtualCacheView(image,exception);
723 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
724 for (y=0; y < (ssize_t) rows; y++)
726 register const Quantum
733 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
734 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
735 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
740 for (x=0; x < (ssize_t) columns; x++)
749 if ((GetPixelReadMask(image,p) <= (QuantumRange/2)) ||
750 (GetPixelReadMask(reconstruct_image,q) <= (QuantumRange/2)))
752 p+=GetPixelChannels(image);
753 q+=GetPixelChannels(reconstruct_image);
756 Sa=QuantumScale*GetPixelAlpha(image,p);
757 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
758 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
763 PixelChannel channel = GetPixelChannelChannel(image,i);
764 PixelTrait traits = GetPixelChannelTraits(image,channel);
765 PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
767 if ((traits == UndefinedPixelTrait) ||
768 (reconstruct_traits == UndefinedPixelTrait) ||
769 ((reconstruct_traits & UpdatePixelTrait) == 0))
771 distance=fabs(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,channel,q));
772 distortion[i]+=distance;
773 distortion[CompositePixelChannel]+=distance;
774 mean_error+=distance*distance;
775 if (distance > maximum_error)
776 maximum_error=distance;
779 p+=GetPixelChannels(image);
780 q+=GetPixelChannels(reconstruct_image);
783 reconstruct_view=DestroyCacheView(reconstruct_view);
784 image_view=DestroyCacheView(image_view);
785 image->error.mean_error_per_pixel=distortion[CompositePixelChannel]/area;
786 image->error.normalized_mean_error=QuantumScale*QuantumScale*mean_error/area;
787 image->error.normalized_maximum_error=QuantumScale*maximum_error;
791 static MagickBooleanType GetMeanSquaredDistortion(const Image *image,
792 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
815 rows=MagickMax(image->rows,reconstruct_image->rows);
816 columns=MagickMax(image->columns,reconstruct_image->columns);
818 image_view=AcquireVirtualCacheView(image,exception);
819 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
820 #if defined(MAGICKCORE_OPENMP_SUPPORT)
821 #pragma omp parallel for schedule(static,4) shared(status) \
822 magick_threads(image,image,rows,1) reduction(+:area)
824 for (y=0; y < (ssize_t) rows; y++)
827 channel_distortion[MaxPixelChannels+1];
829 register const Quantum
836 if (status == MagickFalse)
838 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
839 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
840 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
845 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
846 for (x=0; x < (ssize_t) columns; x++)
855 if ((GetPixelReadMask(image,p) <= (QuantumRange/2)) ||
856 (GetPixelReadMask(reconstruct_image,q) <= (QuantumRange/2)))
858 p+=GetPixelChannels(image);
859 q+=GetPixelChannels(reconstruct_image);
862 Sa=QuantumScale*GetPixelAlpha(image,p);
863 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
864 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
869 PixelChannel channel = GetPixelChannelChannel(image,i);
870 PixelTrait traits = GetPixelChannelTraits(image,channel);
871 PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
873 if ((traits == UndefinedPixelTrait) ||
874 (reconstruct_traits == UndefinedPixelTrait) ||
875 ((reconstruct_traits & UpdatePixelTrait) == 0))
877 distance=QuantumScale*(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
879 channel_distortion[i]+=distance*distance;
880 channel_distortion[CompositePixelChannel]+=distance*distance;
883 p+=GetPixelChannels(image);
884 q+=GetPixelChannels(reconstruct_image);
886 #if defined(MAGICKCORE_OPENMP_SUPPORT)
887 #pragma omp critical (MagickCore_GetMeanSquaredError)
889 for (j=0; j <= MaxPixelChannels; j++)
890 distortion[j]+=channel_distortion[j];
892 reconstruct_view=DestroyCacheView(reconstruct_view);
893 image_view=DestroyCacheView(image_view);
894 area=PerceptibleReciprocal(area);
895 for (j=0; j <= MaxPixelChannels; j++)
897 distortion[CompositePixelChannel]/=GetImageChannels(image);
901 static MagickBooleanType GetNormalizedCrossCorrelationDistortion(
902 const Image *image,const Image *reconstruct_image,double *distortion,
903 ExceptionInfo *exception)
905 #define SimilarityImageTag "Similarity/Image"
913 *reconstruct_statistics;
935 Normalize to account for variation due to lighting and exposure condition.
937 image_statistics=GetImageStatistics(image,exception);
938 reconstruct_statistics=GetImageStatistics(reconstruct_image,exception);
939 if ((image_statistics == (ChannelStatistics *) NULL) ||
940 (reconstruct_statistics == (ChannelStatistics *) NULL))
942 if (image_statistics != (ChannelStatistics *) NULL)
943 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
945 if (reconstruct_statistics != (ChannelStatistics *) NULL)
946 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
947 reconstruct_statistics);
952 for (i=0; i <= MaxPixelChannels; i++)
954 rows=MagickMax(image->rows,reconstruct_image->rows);
955 columns=MagickMax(image->columns,reconstruct_image->columns);
957 image_view=AcquireVirtualCacheView(image,exception);
958 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
959 for (y=0; y < (ssize_t) rows; y++)
961 register const Quantum
968 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
969 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
970 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
975 for (x=0; x < (ssize_t) columns; x++)
977 if ((GetPixelReadMask(image,p) <= (QuantumRange/2)) ||
978 (GetPixelReadMask(reconstruct_image,q) <= (QuantumRange/2)))
980 p+=GetPixelChannels(image);
981 q+=GetPixelChannels(reconstruct_image);
985 p+=GetPixelChannels(image);
986 q+=GetPixelChannels(reconstruct_image);
989 area=PerceptibleReciprocal(area);
990 for (y=0; y < (ssize_t) rows; y++)
992 register const Quantum
999 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1000 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1001 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
1006 for (x=0; x < (ssize_t) columns; x++)
1012 if ((GetPixelReadMask(image,p) <= (QuantumRange/2)) ||
1013 (GetPixelReadMask(reconstruct_image,q) <= (QuantumRange/2)))
1015 p+=GetPixelChannels(image);
1016 q+=GetPixelChannels(reconstruct_image);
1019 Sa=QuantumScale*(image->alpha_trait != UndefinedPixelTrait ?
1020 GetPixelAlpha(image,p) : OpaqueAlpha);
1021 Da=QuantumScale*(reconstruct_image->alpha_trait != UndefinedPixelTrait ?
1022 GetPixelAlpha(reconstruct_image,q) : OpaqueAlpha);
1023 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1025 PixelChannel channel = GetPixelChannelChannel(image,i);
1026 PixelTrait traits = GetPixelChannelTraits(image,channel);
1027 PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
1029 if ((traits == UndefinedPixelTrait) ||
1030 (reconstruct_traits == UndefinedPixelTrait) ||
1031 ((reconstruct_traits & UpdatePixelTrait) == 0))
1033 if (channel == AlphaPixelChannel)
1035 distortion[i]+=area*QuantumScale*(p[i]-
1036 image_statistics[channel].mean)*(GetPixelChannel(
1037 reconstruct_image,channel,q)-
1038 reconstruct_statistics[channel].mean);
1042 distortion[i]+=area*QuantumScale*(Sa*p[i]-
1043 image_statistics[channel].mean)*(Da*GetPixelChannel(
1044 reconstruct_image,channel,q)-
1045 reconstruct_statistics[channel].mean);
1048 p+=GetPixelChannels(image);
1049 q+=GetPixelChannels(reconstruct_image);
1051 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1056 proceed=SetImageProgress(image,SimilarityImageTag,progress++,rows);
1057 if (proceed == MagickFalse)
1064 reconstruct_view=DestroyCacheView(reconstruct_view);
1065 image_view=DestroyCacheView(image_view);
1067 Divide by the standard deviation.
1069 distortion[CompositePixelChannel]=0.0;
1070 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1075 PixelChannel channel = GetPixelChannelChannel(image,i);
1076 gamma=image_statistics[channel].standard_deviation*
1077 reconstruct_statistics[channel].standard_deviation;
1078 gamma=PerceptibleReciprocal(gamma);
1079 distortion[i]=QuantumRange*gamma*distortion[i];
1080 distortion[CompositePixelChannel]+=distortion[i]*distortion[i];
1082 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]/
1083 GetImageChannels(image));
1087 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1088 reconstruct_statistics);
1089 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1094 static MagickBooleanType GetPeakAbsoluteDistortion(const Image *image,
1095 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1112 rows=MagickMax(image->rows,reconstruct_image->rows);
1113 columns=MagickMax(image->columns,reconstruct_image->columns);
1114 image_view=AcquireVirtualCacheView(image,exception);
1115 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1116 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1117 #pragma omp parallel for schedule(static,4) shared(status) \
1118 magick_threads(image,image,rows,1)
1120 for (y=0; y < (ssize_t) rows; y++)
1123 channel_distortion[MaxPixelChannels+1];
1125 register const Quantum
1133 if (status == MagickFalse)
1135 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1136 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1137 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
1142 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
1143 for (x=0; x < (ssize_t) columns; x++)
1152 if ((GetPixelReadMask(image,p) <= (QuantumRange/2)) ||
1153 (GetPixelReadMask(reconstruct_image,q) <= (QuantumRange/2)))
1155 p+=GetPixelChannels(image);
1156 q+=GetPixelChannels(reconstruct_image);
1159 Sa=QuantumScale*GetPixelAlpha(image,p);
1160 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
1161 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1166 PixelChannel channel = GetPixelChannelChannel(image,i);
1167 PixelTrait traits = GetPixelChannelTraits(image,channel);
1168 PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
1170 if ((traits == UndefinedPixelTrait) ||
1171 (reconstruct_traits == UndefinedPixelTrait) ||
1172 ((reconstruct_traits & UpdatePixelTrait) == 0))
1174 distance=QuantumScale*fabs(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
1176 if (distance > channel_distortion[i])
1177 channel_distortion[i]=distance;
1178 if (distance > channel_distortion[CompositePixelChannel])
1179 channel_distortion[CompositePixelChannel]=distance;
1181 p+=GetPixelChannels(image);
1182 q+=GetPixelChannels(reconstruct_image);
1184 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1185 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
1187 for (j=0; j <= MaxPixelChannels; j++)
1188 if (channel_distortion[j] > distortion[j])
1189 distortion[j]=channel_distortion[j];
1191 reconstruct_view=DestroyCacheView(reconstruct_view);
1192 image_view=DestroyCacheView(image_view);
1196 static inline double MagickLog10(const double x)
1198 #define Log10Epsilon (1.0e-11)
1200 if (fabs(x) < Log10Epsilon)
1201 return(log10(Log10Epsilon));
1202 return(log10(fabs(x)));
1205 static MagickBooleanType GetPeakSignalToNoiseRatio(const Image *image,
1206 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1214 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
1215 for (i=0; i <= MaxPixelChannels; i++)
1216 if (fabs(distortion[i]) < MagickEpsilon)
1217 distortion[i]=INFINITY;
1219 distortion[i]=20.0*MagickLog10(1.0/sqrt(distortion[i]));
1223 static MagickBooleanType GetPerceptualHashDistortion(const Image *image,
1224 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1226 ChannelPerceptualHash
1240 Compute perceptual hash in the sRGB colorspace.
1242 channel_phash=GetImagePerceptualHash(image,exception);
1243 if (channel_phash == (ChannelPerceptualHash *) NULL)
1244 return(MagickFalse);
1245 reconstruct_phash=GetImagePerceptualHash(reconstruct_image,exception);
1246 if (reconstruct_phash == (ChannelPerceptualHash *) NULL)
1248 channel_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(
1250 return(MagickFalse);
1252 artifact=GetImageArtifact(image,"phash:normalize");
1253 normalize=(artifact == (const char *) NULL) ||
1254 (IsStringTrue(artifact) == MagickFalse) ? MagickFalse : MagickTrue;
1255 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1256 #pragma omp parallel for schedule(static,4)
1258 for (channel=0; channel < MaxPixelChannels; channel++)
1267 for (i=0; i < MaximumNumberOfImageMoments; i++)
1276 for (j=0; j < (ssize_t) channel_phash[0].number_colorspaces; j++)
1278 alpha=channel_phash[channel].phash[j][i];
1279 beta=reconstruct_phash[channel].phash[j][i];
1280 if (normalize == MagickFalse)
1281 difference+=(beta-alpha)*(beta-alpha);
1283 difference=sqrt((beta-alpha)*(beta-alpha)/
1284 channel_phash[0].number_channels);
1287 distortion[channel]+=difference;
1288 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1289 #pragma omp critical (MagickCore_GetPerceptualHashDistortion)
1291 distortion[CompositePixelChannel]+=difference;
1296 reconstruct_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(
1298 channel_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(channel_phash);
1302 static MagickBooleanType GetRootMeanSquaredDistortion(const Image *image,
1303 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1311 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
1312 for (i=0; i <= MaxPixelChannels; i++)
1313 distortion[i]=sqrt(distortion[i]);
1317 static MagickBooleanType GetStructuralSimilarityDistortion(const Image *image,
1318 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1320 #define SSIMRadius 5.0
1321 #define SSIMSigma 1.5
1322 #define SSIMBlocksize 8
1332 geometry[MagickPathExtent];
1359 Compute structural similarity index.
1362 artifact=GetImageArtifact(image,"compare:ssim-radius");
1363 if (artifact != (const char *) NULL)
1364 radius=StringToDouble(artifact,(char **) NULL);
1366 artifact=GetImageArtifact(image,"compare:ssim-sigma");
1367 if (artifact != (const char *) NULL)
1368 sigma=StringToDouble(artifact,(char **) NULL);
1369 (void) FormatLocaleString(geometry,MagickPathExtent,"gaussian:%.20gx%.20g",
1371 kernel_info=AcquireKernelInfo(geometry,exception);
1372 if (kernel_info == (KernelInfo *) NULL)
1373 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1375 c1=pow(SSIMK1*SSIML,2.0);
1376 artifact=GetImageArtifact(image,"compare:ssim-k1");
1377 if (artifact != (const char *) NULL)
1378 c1=pow(StringToDouble(artifact,(char **) NULL)*SSIML,2.0);
1379 c2=pow(SSIMK2*SSIML,2.0);
1380 artifact=GetImageArtifact(image,"compare:ssim-k2");
1381 if (artifact != (const char *) NULL)
1382 c2=pow(StringToDouble(artifact,(char **) NULL)*SSIML,2.0);
1384 image_view=AcquireVirtualCacheView(image,exception);
1385 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1386 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1387 #pragma omp parallel for schedule(static,4) shared(status) \
1388 magick_threads(image,image,1,1)
1390 for (y=0; y < (ssize_t) image->rows; y+=(ssize_t) kernel_info->height)
1393 channel_distortion[MaxPixelChannels+1];
1401 if (status == MagickFalse)
1403 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
1404 for (x=0; x < (ssize_t) image->columns; x+=(ssize_t) kernel_info->width)
1407 image_sum[MaxPixelChannels+1],
1408 image_sum_squared[MaxPixelChannels+1],
1409 reconstruct_sum[MaxPixelChannels+1],
1410 reconstruct_sum_squared[MaxPixelChannels+1],
1411 sum[MaxPixelChannels+1];
1413 register const Quantum
1423 p=GetCacheViewVirtualPixels(image_view,x,y,kernel_info->width,
1424 kernel_info->height,exception);
1425 q=GetCacheViewVirtualPixels(reconstruct_view,x,y,kernel_info->width,
1426 kernel_info->height,exception);
1427 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
1432 (void) ResetMagickMemory(image_sum,0,(MaxPixelChannels+1)*
1433 sizeof(*image_sum));
1434 (void) ResetMagickMemory(image_sum_squared,0,(MaxPixelChannels+1)*
1435 sizeof(*image_sum_squared));
1436 (void) ResetMagickMemory(reconstruct_sum,0,(MaxPixelChannels+1)*
1437 sizeof(*reconstruct_sum));
1438 (void) ResetMagickMemory(reconstruct_sum_squared,0,(MaxPixelChannels+1)*
1439 sizeof(*reconstruct_sum_squared));
1440 (void) ResetMagickMemory(sum,0,(MaxPixelChannels+1)*sizeof(*sum));
1441 k=kernel_info->values;
1442 for (v=0; v < (ssize_t) kernel_info->height; v++)
1447 for (u=0; u < (ssize_t) kernel_info->width; u++)
1452 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1454 PixelChannel channel = GetPixelChannelChannel(image,i);
1455 PixelTrait traits = GetPixelChannelTraits(image,channel);
1456 PixelTrait reconstruct_traits = GetPixelChannelTraits(
1457 reconstruct_image,channel);
1458 if ((traits == UndefinedPixelTrait) ||
1459 (reconstruct_traits == UndefinedPixelTrait) ||
1460 ((reconstruct_traits & UpdatePixelTrait) == 0))
1462 image_sum[i]+=(*k)*p[i];
1463 image_sum_squared[i]+=((*k)*p[i]*(*k)*p[i]);
1464 reconstruct_sum[i]+=(*k)*GetPixelChannel(reconstruct_image,channel,
1466 reconstruct_sum_squared[i]+=((*k)*GetPixelChannel(reconstruct_image,
1467 channel,q)*(*k)*GetPixelChannel(reconstruct_image,channel,q));
1468 sum[i]+=((*k)*p[i]*(*k)*GetPixelChannel(reconstruct_image,channel,
1471 p+=GetPixelChannels(image);
1472 q+=GetPixelChannels(reconstruct_image);
1475 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1482 reconstruct_variance;
1487 number_samples=kernel_info->width*kernel_info->height;
1488 image_mean=image_sum[i]/number_samples;
1489 image_variance=image_sum_squared[i]/number_samples-(image_mean*
1491 reconstruct_mean=reconstruct_sum[i]/number_samples;
1492 reconstruct_variance=reconstruct_sum_squared[i]/number_samples-
1493 (reconstruct_mean*reconstruct_mean);
1494 covarience=sum[i]/number_samples-(image_mean*reconstruct_mean);
1495 channel_distortion[i]+=((2.0*image_mean*reconstruct_mean+c1)*(2.0*
1496 covarience+c2))/((image_mean*image_mean+reconstruct_mean*
1497 reconstruct_mean+c1)*(image_variance+reconstruct_variance+c2));
1501 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1502 #pragma omp critical (MagickCore_GetStructuralSimilarityDistortion)
1504 for (j=0; j <= MaxPixelChannels; j++)
1505 distortion[j]+=channel_distortion[j];
1507 image_view=DestroyCacheView(image_view);
1508 reconstruct_view=DestroyCacheView(reconstruct_view);
1510 for (y=0; y < (ssize_t) image->rows; y+=(ssize_t) kernel_info->height)
1513 for (x=0; x < (ssize_t) image->columns; x+=(ssize_t) kernel_info->width)
1514 n+=kernel_info->width;
1516 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1518 PixelChannel channel = GetPixelChannelChannel(image,i);
1519 PixelTrait traits = GetPixelChannelTraits(image,channel);
1520 if ((traits == UndefinedPixelTrait) || ((traits & UpdatePixelTrait) == 0))
1522 distortion[i]/=(double) n;
1523 distortion[CompositePixelChannel]+=distortion[i];
1525 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
1526 kernel_info=DestroyKernelInfo(kernel_info);
1530 static MagickBooleanType GetStructuralDisimilarityDistortion(const Image *image,
1531 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1539 status=GetStructuralSimilarityDistortion(image,reconstruct_image,
1540 distortion,exception);
1541 for (i=0; i <= MaxPixelChannels; i++)
1542 distortion[i]=(1.0-(distortion[i]))/2.0;
1546 MagickExport MagickBooleanType GetImageDistortion(Image *image,
1547 const Image *reconstruct_image,const MetricType metric,double *distortion,
1548 ExceptionInfo *exception)
1551 *channel_distortion;
1559 assert(image != (Image *) NULL);
1560 assert(image->signature == MagickCoreSignature);
1561 if (image->debug != MagickFalse)
1562 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1563 assert(reconstruct_image != (const Image *) NULL);
1564 assert(reconstruct_image->signature == MagickCoreSignature);
1565 assert(distortion != (double *) NULL);
1567 if (image->debug != MagickFalse)
1568 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1570 Get image distortion.
1572 length=MaxPixelChannels+1;
1573 channel_distortion=(double *) AcquireQuantumMemory(length,
1574 sizeof(*channel_distortion));
1575 if (channel_distortion == (double *) NULL)
1576 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1577 (void) ResetMagickMemory(channel_distortion,0,length*
1578 sizeof(*channel_distortion));
1581 case AbsoluteErrorMetric:
1583 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1587 case FuzzErrorMetric:
1589 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1593 case MeanAbsoluteErrorMetric:
1595 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1596 channel_distortion,exception);
1599 case MeanErrorPerPixelErrorMetric:
1601 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1605 case MeanSquaredErrorMetric:
1607 status=GetMeanSquaredDistortion(image,reconstruct_image,
1608 channel_distortion,exception);
1611 case NormalizedCrossCorrelationErrorMetric:
1614 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1615 channel_distortion,exception);
1618 case PeakAbsoluteErrorMetric:
1620 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1621 channel_distortion,exception);
1624 case PeakSignalToNoiseRatioErrorMetric:
1626 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1627 channel_distortion,exception);
1630 case PerceptualHashErrorMetric:
1632 status=GetPerceptualHashDistortion(image,reconstruct_image,
1633 channel_distortion,exception);
1636 case RootMeanSquaredErrorMetric:
1638 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1639 channel_distortion,exception);
1642 case StructuralSimilarityErrorMetric:
1644 status=GetStructuralSimilarityDistortion(image,reconstruct_image,
1645 channel_distortion,exception);
1648 case StructuralDissimilarityErrorMetric:
1650 status=GetStructuralDisimilarityDistortion(image,reconstruct_image,
1651 channel_distortion,exception);
1655 *distortion=channel_distortion[CompositePixelChannel];
1656 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1657 (void) FormatImageProperty(image,"distortion","%.*g",GetMagickPrecision(),
1663 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1667 % G e t I m a g e D i s t o r t i o n s %
1671 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1673 % GetImageDistortions() compares the pixel channels of an image to a
1674 % reconstructed image and returns the specified distortion metric for each
1677 % The format of the GetImageDistortions method is:
1679 % double *GetImageDistortions(const Image *image,
1680 % const Image *reconstruct_image,const MetricType metric,
1681 % ExceptionInfo *exception)
1683 % A description of each parameter follows:
1685 % o image: the image.
1687 % o reconstruct_image: the reconstruct image.
1689 % o metric: the metric.
1691 % o exception: return any errors or warnings in this structure.
1694 MagickExport double *GetImageDistortions(Image *image,
1695 const Image *reconstruct_image,const MetricType metric,
1696 ExceptionInfo *exception)
1699 *channel_distortion;
1707 assert(image != (Image *) NULL);
1708 assert(image->signature == MagickCoreSignature);
1709 if (image->debug != MagickFalse)
1710 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1711 assert(reconstruct_image != (const Image *) NULL);
1712 assert(reconstruct_image->signature == MagickCoreSignature);
1713 if (image->debug != MagickFalse)
1714 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1716 Get image distortion.
1718 length=MaxPixelChannels+1UL;
1719 channel_distortion=(double *) AcquireQuantumMemory(length,
1720 sizeof(*channel_distortion));
1721 if (channel_distortion == (double *) NULL)
1722 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1723 (void) ResetMagickMemory(channel_distortion,0,length*
1724 sizeof(*channel_distortion));
1728 case AbsoluteErrorMetric:
1730 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1734 case FuzzErrorMetric:
1736 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1740 case MeanAbsoluteErrorMetric:
1742 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1743 channel_distortion,exception);
1746 case MeanErrorPerPixelErrorMetric:
1748 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1752 case MeanSquaredErrorMetric:
1754 status=GetMeanSquaredDistortion(image,reconstruct_image,
1755 channel_distortion,exception);
1758 case NormalizedCrossCorrelationErrorMetric:
1761 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1762 channel_distortion,exception);
1765 case PeakAbsoluteErrorMetric:
1767 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1768 channel_distortion,exception);
1771 case PeakSignalToNoiseRatioErrorMetric:
1773 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1774 channel_distortion,exception);
1777 case PerceptualHashErrorMetric:
1779 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1780 channel_distortion,exception);
1783 case RootMeanSquaredErrorMetric:
1785 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1786 channel_distortion,exception);
1789 case StructuralSimilarityErrorMetric:
1791 status=GetStructuralSimilarityDistortion(image,reconstruct_image,
1792 channel_distortion,exception);
1795 case StructuralDissimilarityErrorMetric:
1797 status=GetStructuralDisimilarityDistortion(image,reconstruct_image,
1798 channel_distortion,exception);
1802 if (status == MagickFalse)
1804 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1805 return((double *) NULL);
1807 return(channel_distortion);
1811 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1815 % I s I m a g e s E q u a l %
1819 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1821 % IsImagesEqual() compare the pixels of two images and returns immediately
1822 % if any pixel is not identical.
1824 % The format of the IsImagesEqual method is:
1826 % MagickBooleanType IsImagesEqual(const Image *image,
1827 % const Image *reconstruct_image,ExceptionInfo *exception)
1829 % A description of each parameter follows.
1831 % o image: the image.
1833 % o reconstruct_image: the reconstruct image.
1835 % o exception: return any errors or warnings in this structure.
1838 MagickExport MagickBooleanType IsImagesEqual(const Image *image,
1839 const Image *reconstruct_image,ExceptionInfo *exception)
1852 assert(image != (Image *) NULL);
1853 assert(image->signature == MagickCoreSignature);
1854 assert(reconstruct_image != (const Image *) NULL);
1855 assert(reconstruct_image->signature == MagickCoreSignature);
1856 rows=MagickMax(image->rows,reconstruct_image->rows);
1857 columns=MagickMax(image->columns,reconstruct_image->columns);
1858 image_view=AcquireVirtualCacheView(image,exception);
1859 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1860 for (y=0; y < (ssize_t) rows; y++)
1862 register const Quantum
1869 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1870 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1871 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1873 for (x=0; x < (ssize_t) columns; x++)
1878 if (GetPixelWriteMask(image,p) <= (QuantumRange/2))
1880 p+=GetPixelChannels(image);
1881 q+=GetPixelChannels(reconstruct_image);
1884 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1889 PixelChannel channel = GetPixelChannelChannel(image,i);
1890 PixelTrait traits = GetPixelChannelTraits(image,channel);
1891 PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
1893 if ((traits == UndefinedPixelTrait) ||
1894 (reconstruct_traits == UndefinedPixelTrait) ||
1895 ((reconstruct_traits & UpdatePixelTrait) == 0))
1897 distance=fabs(p[i]-(double) GetPixelChannel(reconstruct_image,
1899 if (distance >= MagickEpsilon)
1902 if (i < (ssize_t) GetPixelChannels(image))
1904 p+=GetPixelChannels(image);
1905 q+=GetPixelChannels(reconstruct_image);
1907 if (x < (ssize_t) columns)
1910 reconstruct_view=DestroyCacheView(reconstruct_view);
1911 image_view=DestroyCacheView(image_view);
1912 return(y < (ssize_t) rows ? MagickFalse : MagickTrue);
1916 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1920 % S e t I m a g e C o l o r M e t r i c %
1924 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1926 % SetImageColorMetric() measures the difference between colors at each pixel
1927 % location of two images. A value other than 0 means the colors match
1928 % exactly. Otherwise an error measure is computed by summing over all
1929 % pixels in an image the distance squared in RGB space between each image
1930 % pixel and its corresponding pixel in the reconstruct image. The error
1931 % measure is assigned to these image members:
1933 % o mean_error_per_pixel: The mean error for any single pixel in
1936 % o normalized_mean_error: The normalized mean quantization error for
1937 % any single pixel in the image. This distance measure is normalized to
1938 % a range between 0 and 1. It is independent of the range of red, green,
1939 % and blue values in the image.
1941 % o normalized_maximum_error: The normalized maximum quantization
1942 % error for any single pixel in the image. This distance measure is
1943 % normalized to a range between 0 and 1. It is independent of the range
1944 % of red, green, and blue values in your image.
1946 % A small normalized mean square error, accessed as
1947 % image->normalized_mean_error, suggests the images are very similar in
1948 % spatial layout and color.
1950 % The format of the SetImageColorMetric method is:
1952 % MagickBooleanType SetImageColorMetric(Image *image,
1953 % const Image *reconstruct_image,ExceptionInfo *exception)
1955 % A description of each parameter follows.
1957 % o image: the image.
1959 % o reconstruct_image: the reconstruct image.
1961 % o exception: return any errors or warnings in this structure.
1964 MagickExport MagickBooleanType SetImageColorMetric(Image *image,
1965 const Image *reconstruct_image,ExceptionInfo *exception)
1975 mean_error_per_pixel;
1987 assert(image != (Image *) NULL);
1988 assert(image->signature == MagickCoreSignature);
1989 assert(reconstruct_image != (const Image *) NULL);
1990 assert(reconstruct_image->signature == MagickCoreSignature);
1993 mean_error_per_pixel=0.0;
1995 rows=MagickMax(image->rows,reconstruct_image->rows);
1996 columns=MagickMax(image->columns,reconstruct_image->columns);
1997 image_view=AcquireVirtualCacheView(image,exception);
1998 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1999 for (y=0; y < (ssize_t) rows; y++)
2001 register const Quantum
2008 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
2009 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
2010 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
2012 for (x=0; x < (ssize_t) columns; x++)
2017 if (GetPixelWriteMask(image,p) <= (QuantumRange/2))
2019 p+=GetPixelChannels(image);
2020 q+=GetPixelChannels(reconstruct_image);
2023 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2028 PixelChannel channel = GetPixelChannelChannel(image,i);
2029 PixelTrait traits = GetPixelChannelTraits(image,channel);
2030 PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
2032 if ((traits == UndefinedPixelTrait) ||
2033 (reconstruct_traits == UndefinedPixelTrait) ||
2034 ((reconstruct_traits & UpdatePixelTrait) == 0))
2036 distance=fabs(p[i]-(double) GetPixelChannel(reconstruct_image,
2038 if (distance >= MagickEpsilon)
2040 mean_error_per_pixel+=distance;
2041 mean_error+=distance*distance;
2042 if (distance > maximum_error)
2043 maximum_error=distance;
2047 p+=GetPixelChannels(image);
2048 q+=GetPixelChannels(reconstruct_image);
2051 reconstruct_view=DestroyCacheView(reconstruct_view);
2052 image_view=DestroyCacheView(image_view);
2053 image->error.mean_error_per_pixel=(double) (mean_error_per_pixel/area);
2054 image->error.normalized_mean_error=(double) (QuantumScale*QuantumScale*
2056 image->error.normalized_maximum_error=(double) (QuantumScale*maximum_error);
2057 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
2062 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2066 % S i m i l a r i t y I m a g e %
2070 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2072 % SimilarityImage() compares the reference image of the image and returns the
2073 % best match offset. In addition, it returns a similarity image such that an
2074 % exact match location is completely white and if none of the pixels match,
2075 % black, otherwise some gray level in-between.
2077 % The format of the SimilarityImageImage method is:
2079 % Image *SimilarityImage(const Image *image,const Image *reference,
2080 % const MetricType metric,const double similarity_threshold,
2081 % RectangleInfo *offset,double *similarity,ExceptionInfo *exception)
2083 % A description of each parameter follows:
2085 % o image: the image.
2087 % o reference: find an area of the image that closely resembles this image.
2089 % o metric: the metric.
2091 % o similarity_threshold: minimum distortion for (sub)image match.
2093 % o offset: the best match offset of the reference image within the image.
2095 % o similarity: the computed similarity between the images.
2097 % o exception: return any errors or warnings in this structure.
2101 static double GetSimilarityMetric(const Image *image,const Image *reference,
2102 const MetricType metric,const ssize_t x_offset,const ssize_t y_offset,
2103 ExceptionInfo *exception)
2117 SetGeometry(reference,&geometry);
2118 geometry.x=x_offset;
2119 geometry.y=y_offset;
2120 similarity_image=CropImage(image,&geometry,exception);
2121 if (similarity_image == (Image *) NULL)
2124 status=GetImageDistortion(similarity_image,reference,metric,&distortion,
2126 similarity_image=DestroyImage(similarity_image);
2127 if (status == MagickFalse)
2132 MagickExport Image *SimilarityImage(const Image *image,const Image *reference,
2133 const MetricType metric,const double similarity_threshold,
2134 RectangleInfo *offset,double *similarity_metric,ExceptionInfo *exception)
2136 #define SimilarityImageTag "Similarity/Image"
2153 assert(image != (const Image *) NULL);
2154 assert(image->signature == MagickCoreSignature);
2155 if (image->debug != MagickFalse)
2156 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2157 assert(exception != (ExceptionInfo *) NULL);
2158 assert(exception->signature == MagickCoreSignature);
2159 assert(offset != (RectangleInfo *) NULL);
2160 SetGeometry(reference,offset);
2161 *similarity_metric=MagickMaximumValue;
2162 similarity_image=CloneImage(image,image->columns-reference->columns+1,
2163 image->rows-reference->rows+1,MagickTrue,exception);
2164 if (similarity_image == (Image *) NULL)
2165 return((Image *) NULL);
2166 status=SetImageStorageClass(similarity_image,DirectClass,exception);
2167 if (status == MagickFalse)
2169 similarity_image=DestroyImage(similarity_image);
2170 return((Image *) NULL);
2172 (void) SetImageAlphaChannel(similarity_image,DeactivateAlphaChannel,
2175 Measure similarity of reference image against image.
2179 similarity_view=AcquireAuthenticCacheView(similarity_image,exception);
2180 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2181 #pragma omp parallel for schedule(static,4) \
2182 shared(progress,status,similarity_metric) \
2183 magick_threads(image,image,image->rows,1)
2185 for (y=0; y < (ssize_t) (image->rows-reference->rows+1); y++)
2196 if (status == MagickFalse)
2198 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2199 #pragma omp flush(similarity_metric)
2201 if (*similarity_metric <= similarity_threshold)
2203 q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
2205 if (q == (Quantum *) NULL)
2210 for (x=0; x < (ssize_t) (image->columns-reference->columns+1); x++)
2215 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2216 #pragma omp flush(similarity_metric)
2218 if (*similarity_metric <= similarity_threshold)
2220 similarity=GetSimilarityMetric(image,reference,metric,x,y,exception);
2221 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2222 #pragma omp critical (MagickCore_SimilarityImage)
2224 if ((metric == NormalizedCrossCorrelationErrorMetric) ||
2225 (metric == UndefinedErrorMetric))
2226 similarity=1.0-similarity;
2227 if (similarity < *similarity_metric)
2231 *similarity_metric=similarity;
2233 if (metric == PerceptualHashErrorMetric)
2234 similarity=MagickMin(0.01*similarity,1.0);
2235 if (GetPixelWriteMask(similarity_image,q) <= (QuantumRange/2))
2237 SetPixelBackgoundColor(similarity_image,q);
2238 q+=GetPixelChannels(similarity_image);
2241 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2243 PixelChannel channel = GetPixelChannelChannel(image,i);
2244 PixelTrait traits = GetPixelChannelTraits(image,channel);
2245 PixelTrait similarity_traits=GetPixelChannelTraits(similarity_image,
2247 if ((traits == UndefinedPixelTrait) ||
2248 (similarity_traits == UndefinedPixelTrait) ||
2249 ((similarity_traits & UpdatePixelTrait) == 0))
2251 SetPixelChannel(similarity_image,channel,ClampToQuantum(QuantumRange-
2252 QuantumRange*similarity),q);
2254 q+=GetPixelChannels(similarity_image);
2256 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
2258 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2263 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2264 #pragma omp critical (MagickCore_SimilarityImage)
2266 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
2268 if (proceed == MagickFalse)
2272 similarity_view=DestroyCacheView(similarity_view);
2273 (void) SetImageAlphaChannel(similarity_image,OffAlphaChannel,exception);
2274 if (status == MagickFalse)
2275 similarity_image=DestroyImage(similarity_image);
2276 return(similarity_image);